├── out ├── images │ ├── content │ │ └── .gitignore │ ├── header │ │ └── .gitignore │ ├── logo.png │ ├── favicon.ico │ ├── icon-57.png │ ├── icon-72.png │ ├── fb-thumb.jpg │ ├── icon-114.png │ ├── down-arrow.svg │ ├── facebook.svg │ ├── logo-mask.svg │ └── twitter.svg └── fonts │ └── c18aee57-7585-4a8b-9a1c-69d226301d73.woff ├── .gitignore ├── src ├── stylesheets │ ├── lib.styl │ ├── spinners.styl │ ├── social.styl │ ├── iscroll.styl │ ├── footer.styl │ ├── foreground.styl │ ├── index.styl │ ├── background.styl │ ├── header.styl │ └── mobile.styl ├── templates │ ├── content-layout.jade │ ├── footer.jade │ ├── mixpanel.html │ ├── scripts.jade │ ├── index.jade │ ├── code.txt │ ├── graph-svg.html │ └── content.jade └── scripts │ ├── vendor │ ├── morpheus-easings.js │ ├── zepto.touch.js │ └── zepto.js │ └── index.coffee ├── package.json ├── scripts ├── server.coffee └── to-s3.coffee ├── Makefile ├── LICENSE └── README.md /out/images/content/.gitignore: -------------------------------------------------------------------------------- 1 | * -------------------------------------------------------------------------------- /out/images/header/.gitignore: -------------------------------------------------------------------------------- 1 | * -------------------------------------------------------------------------------- /out/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artsy/artsy-2013/master/out/images/logo.png -------------------------------------------------------------------------------- /out/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artsy/artsy-2013/master/out/images/favicon.ico -------------------------------------------------------------------------------- /out/images/icon-57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artsy/artsy-2013/master/out/images/icon-57.png -------------------------------------------------------------------------------- /out/images/icon-72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artsy/artsy-2013/master/out/images/icon-72.png -------------------------------------------------------------------------------- /out/images/fb-thumb.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artsy/artsy-2013/master/out/images/fb-thumb.jpg -------------------------------------------------------------------------------- /out/images/icon-114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artsy/artsy-2013/master/out/images/icon-114.png -------------------------------------------------------------------------------- /out/fonts/c18aee57-7585-4a8b-9a1c-69d226301d73.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artsy/artsy-2013/master/out/fonts/c18aee57-7585-4a8b-9a1c-69d226301d73.woff -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | *.seed 3 | *.log 4 | *.csv 5 | *.dat 6 | *.out 7 | *.pid 8 | *.gz 9 | *.DS_Store 10 | pids 11 | logs 12 | results 13 | npm-debug.log 14 | node_modules 15 | out/index.html 16 | out/index.js 17 | out/index.css -------------------------------------------------------------------------------- /src/stylesheets/lib.styl: -------------------------------------------------------------------------------- 1 | foreground-width = calc(40% - 80px) 2 | background-width = 60% 3 | foreground-padding = 48px 4 | foreground-header-font-size = 52px 5 | default-font-size = 18px 6 | 7 | avant-garde() 8 | font-family 'AvantGardeGothicITCW01D 731075', 'AvantGardeGothicITCW01Dm', 'Helvetica', 'sans-serif' 9 | font-smoothing antialiased 10 | text-transform uppercase 11 | 12 | garamond() 13 | font-family 'adobe-garamond-pro', 'AGaramondPro-Regular', 'Times New Roman', 'Times', 'serif' 14 | font-smoothing antialiased -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "artsy-2014", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": {}, 6 | "devDependencies": { 7 | "jade": "*", 8 | "stylus": "*", 9 | "coffee-script": "*", 10 | "nib": "*", 11 | "knox": "*", 12 | "glob": "*", 13 | "express": "*", 14 | "browserify": "*", 15 | "browserify-dev-middleware": "*", 16 | "coffeeify": "*", 17 | "uglify-js": "*", 18 | "sqwish": "*", 19 | "underscore": "*", 20 | "iscroll": "*", 21 | "morpheus": "*" 22 | } 23 | } -------------------------------------------------------------------------------- /src/stylesheets/spinners.styl: -------------------------------------------------------------------------------- 1 | // 2 | // Styles for loading spinners of all varieties. 3 | // 4 | 5 | @keyframes spin 6 | 0% 7 | transform rotate(0deg) 8 | animation-timing-function linear 9 | 50% 10 | transform rotate(180deg) 11 | animation-timing-function linear 12 | 100% 13 | transform rotate(360deg) 14 | animation-timing-function linear 15 | 16 | spinner(width=25px, height=6px, color=#000) 17 | background color 18 | width width 19 | height height 20 | position absolute 21 | animation spin 1s infinite 22 | 23 | .loading-spinner 24 | spinner(30px, 6px) -------------------------------------------------------------------------------- /src/templates/content-layout.jade: -------------------------------------------------------------------------------- 1 | mixin fg-item(month, title) 2 | li 3 | header 4 | h1 5 | em #{month} — 6 | br 7 | != title 8 | .foreground-header-body 9 | block 10 | 11 | mixin bg-item 12 | li 13 | .phone-foreground-container 14 | block 15 | 16 | #foreground.viewport-height 17 | #foreground-gradient 18 | ul#foreground-content 19 | block foreground 20 | #foreground-social-buttons 21 | a.social-button-facebook 22 | a.social-button-twitter 23 | 24 | #background 25 | ul#background-content 26 | block background -------------------------------------------------------------------------------- /out/images/down-arrow.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/templates/footer.jade: -------------------------------------------------------------------------------- 1 | img#footer-bg-image( src='images/content/bottom-image-small.jpg' ) 2 | h3#footer-header Thank you for being a part of Artsy 3 | nav#footer-links 4 | a( href="https://artsy.net", target="_blank" ) Go to Artsy 5 | a( href="https://artsy.net/about/jobs", target="_blank" ) Join the Artsy Team 6 | a( href="https://artsy.net/about/partnering-with-artsy", target="_blank" ) Partner with Artsy 7 | #footer-social-buttons 8 | h4 Share This: 9 | .social-button-container 10 | small#social-button-facebook-count 0 11 | a.social-button-facebook 12 | .social-button-container 13 | small#social-button-twitter-count 0 14 | a.social-button-twitter.final-twitter-button -------------------------------------------------------------------------------- /scripts/server.coffee: -------------------------------------------------------------------------------- 1 | # 2 | # A simple static server to test things like HTML5 history 3 | # 4 | 5 | express = require 'express' 6 | { resolve } = require 'path' 7 | { exec } = require 'child_process' 8 | 9 | app = express() 10 | app.locals.pretty = true 11 | app.set 'views', resolve __dirname, '../src/templates' 12 | app.set 'view engine', 'jade' 13 | app.use require("stylus").middleware 14 | src: resolve(__dirname, '../src/stylesheets') 15 | dest: resolve(__dirname, "../out") 16 | app.use require('browserify-dev-middleware') 17 | src: resolve(__dirname, '../src/scripts') 18 | transforms: [require('coffeeify')] 19 | app.get '/', (req, res) -> res.render 'index' 20 | app.use express.static resolve __dirname, "../out" 21 | 22 | app.listen 3000, -> console.log 'Listenning on 3000' -------------------------------------------------------------------------------- /out/images/facebook.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 10 | 11 | -------------------------------------------------------------------------------- /scripts/to-s3.coffee: -------------------------------------------------------------------------------- 1 | # 2 | # Uploads the generated static assets to the production s3 bucket. 3 | # 4 | 5 | knox = require 'knox' 6 | glob = require 'glob' 7 | fs = require 'fs' 8 | { resolve } = require 'path' 9 | 10 | headers = 11 | 'x-amz-acl': 'public-read' 12 | 13 | client = knox.createClient 14 | key: process.env.S3_KEY 15 | secret: process.env.S3_SECRET 16 | bucket: if process.argv[2] is 'production' then '2013.artsy.net' else 'staging.2013.artsy.net' 17 | 18 | uploadFile = (file) -> 19 | console.log "Uploading #{file}...." 20 | client.putFile resolve(__dirname, '../', file), file.replace(/^out/, ''), headers, (err, res) -> 21 | return console.warn(err) if err 22 | console.log "Uploaded #{file}!" 23 | res.resume() 24 | 25 | for ext in ['html', 'css', 'js', 'jpg', 'png', 'svg'] 26 | for file in glob.sync('out/**/*.' + ext) 27 | uploadFile(file) unless file.match('_content') -------------------------------------------------------------------------------- /src/stylesheets/social.styl: -------------------------------------------------------------------------------- 1 | #foreground-social-buttons 2 | position absolute 3 | right foreground-padding 4 | bottom foreground-padding 5 | 6 | .social-button-facebook 7 | .social-button-twitter 8 | padding 25px 9 | background transparent 10 | background no-repeat center center 11 | background-size 40% 12 | font-size 0 13 | cursor pointer 14 | display inline-block 15 | position relative 16 | &::after 17 | content '.' 18 | color transparent 19 | border 2px solid white 20 | border-radius 50px 21 | width 100% 22 | height 100% 23 | position absolute 24 | top -1px 25 | left -1px 26 | transition all 0.15s ease-out 27 | &:hover, &:focus, &:active 28 | &::after 29 | transform scale(1.1) 30 | 31 | .social-button-facebook 32 | margin-right 20px 33 | background-image url(images/facebook.svg) 34 | 35 | .social-button-twitter 36 | background-image url(images/twitter.svg) -------------------------------------------------------------------------------- /out/images/logo-mask.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | BIN = node_modules/.bin 2 | 3 | s: 4 | $(BIN)/coffee scripts/server.coffee 5 | 6 | generate: 7 | $(BIN)/jade src/templates/index.jade -o out 8 | $(BIN)/stylus src/stylesheets/index.styl -o out/ --include out/ 9 | $(BIN)/sqwish out/index.css 10 | mv out/index.min.css out/index.css 11 | $(BIN)/browserify src/scripts/index.coffee -t coffeeify | $(BIN)/uglifyjs > out/index.js 12 | 13 | deploy: generate 14 | $(BIN)/coffee scripts/to-s3.coffee $(env) 15 | open http://2013.artsy.net/ 16 | 17 | # Use ImageMagick to copy images from out/images/_content to resized forms. 18 | images: 19 | $(foreach file, $(shell find out/images/_content/ -name '*.jpg' -exec basename {} \; | cut -d '.' -f 1), \ 20 | convert out/images/_content/$(file).jpg -resize 640x640 -quality 40 out/images/content/$(file)-small.jpg; \ 21 | convert out/images/_content/$(file).jpg -resize 1200x1200 -quality 80 out/images/content/$(file)-large.jpg; \ 22 | ) 23 | 24 | .PHONY: images deploy -------------------------------------------------------------------------------- /src/stylesheets/iscroll.styl: -------------------------------------------------------------------------------- 1 | body.iscroll { 2 | 3 | #wrapper { 4 | position: relative; 5 | width: 100%; 6 | overflow: hidden; 7 | 8 | /* Prevent native touch events on Windows */ 9 | -ms-touch-action: none; 10 | 11 | /* Prevent the callout on tap-hold and text selection */ 12 | -webkit-touch-callout: none; 13 | -webkit-user-select: none; 14 | -moz-user-select: none; 15 | -ms-user-select: none; 16 | user-select: none; 17 | 18 | /* Prevent text resize on orientation change, useful for web-apps */ 19 | -webkit-text-size-adjust: none; 20 | -moz-text-size-adjust: none; 21 | -ms-text-size-adjust: none; 22 | -o-text-size-adjust: none; 23 | text-size-adjust: none; 24 | } 25 | 26 | #scroller { 27 | position: absolute; 28 | width: 100% 29 | 30 | /* Prevent elements to be highlighted on tap */ 31 | -webkit-tap-highlight-color: rgba(0,0,0,0); 32 | 33 | /* Put the scroller into the HW Compositing layer right from the start */ 34 | transform translate3d(0,0,0) 35 | } 36 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Artsy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /src/templates/mixpanel.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/templates/scripts.jade: -------------------------------------------------------------------------------- 1 | //- This beacon for fonts.com determines how many page views to charge us for. Limit to production. 2 | script. 3 | var MTIProjectId='f7f47a40-b25b-44ee-9f9c-cfdfc8bb2741'; 4 | (function() { 5 | var mtiTracking = document.createElement('script'); 6 | mtiTracking.type='text/javascript'; 7 | mtiTracking.async='true'; 8 | mtiTracking.src=('https:'==document.location.protocol?'https:':'http:')+'//fast.fonts.net/t/trackingCode.js'; 9 | (document.getElementsByTagName('head')[0]||document.getElementsByTagName('body')[0]).appendChild( mtiTracking ); 10 | })(); 11 | 12 | //- Typekit for Garamond 13 | script. 14 | (function() { 15 | var config = { 16 | kitId: 'jyb7dwm', 17 | scriptTimeout: 3000 18 | }; 19 | var h=document.getElementsByTagName("html")[0];h.className+=" wf-loading";var t=setTimeout(function(){h.className=h.className.replace(/(\s|^)wf-loading(\s|$)/g," ");h.className+=" wf-inactive"},config.scriptTimeout);var tk=document.createElement("script"),d=false;tk.src='//use.typekit.net/'+config.kitId+'.js';tk.type="text/javascript";tk.async="true";tk.onload=tk.onreadystatechange=function(){var a=this.readyState;if(d||a&&a!="complete"&&a!="loaded")return;d=true;clearTimeout(t);try{Typekit.load(config)}catch(b){}};var s=document.getElementsByTagName("script")[0];s.parentNode.insertBefore(tk,s) 20 | })(); 21 | 22 | //- Mixpanel 23 | include ./mixpanel.html -------------------------------------------------------------------------------- /src/stylesheets/footer.styl: -------------------------------------------------------------------------------- 1 | #footer 2 | position relative 3 | background-size cover 4 | 5 | #footer-bg-image 6 | width 100% 7 | display block 8 | 9 | #footer-social-buttons 10 | position absolute 11 | bottom 50px 12 | text-align center 13 | width 100% 14 | z-index 2 15 | button 16 | display inline-block 17 | h4 18 | text-transform uppercase 19 | letter-spacing 1px 20 | margin-bottom 20px 21 | 22 | #footer-header 23 | text-align center 24 | position absolute 25 | bottom 400px 26 | margin-top -40px 27 | font-size foreground-header-font-size 28 | display block 29 | width 100% 30 | z-index 2 31 | 32 | #footer-links 33 | text-align center 34 | display block 35 | width 100% 36 | bottom 200px 37 | position absolute 38 | font-size 26px 39 | line-height 1.4em 40 | z-index 2 41 | a 42 | display block 43 | text-decoration none 44 | color white 45 | cursor pointer 46 | z-index 2 47 | &:hover, &:active, &:focus 48 | text-decoration underline 49 | 50 | .social-button-container 51 | position relative 52 | width 80px 53 | height @width 54 | vertical-align top 55 | display inline-block 56 | small 57 | display block 58 | position absolute 59 | bottom -32px 60 | left -4px 61 | display none 62 | text-align center 63 | width 100% 64 | a 65 | display block 66 | position absolute 67 | bottom 0 68 | right 0 69 | margin-right 20px -------------------------------------------------------------------------------- /src/stylesheets/foreground.styl: -------------------------------------------------------------------------------- 1 | @import 'lib' 2 | 3 | #foreground 4 | position absolute 5 | width 100% 6 | z-index 2 7 | transition opacity 0.3s ease-out 8 | transform translate3d(0,0,0) 9 | li 10 | padding foreground-padding 11 | height 100% 12 | width 100% 13 | position absolute 14 | opacity 0 15 | &:first-child 16 | opacity 1 17 | header 18 | width foreground-width 19 | 20 | #foreground, .phone-foreground-container 21 | small 22 | display block 23 | margin-top 20px 24 | font-size default-font-size 25 | p 26 | margin-bottom 1em 27 | blockquote 28 | font-style italic 29 | header 30 | padding-top 30px 31 | border-top 3px solid white 32 | position absolute 33 | position relative 34 | h1 35 | font-size foreground-header-font-size 36 | line-height 1em 37 | .foreground-header-body 38 | bottom foreground-padding 39 | font-size default-font-size 40 | line-height 23px 41 | margin-top 40px 42 | 43 | #foreground-gradient 44 | background linear-gradient(top, transparent, black) 45 | width 100% 46 | height 30% 47 | bottom -5px 48 | position absolute 49 | left 0 50 | 51 | #foreground-social-buttons 52 | z-index 2 53 | 54 | // IOS6 has a hard time dealing with the fluid widths of the foreground headers 55 | body.ios6 #foreground header 56 | width 290px !important 57 | @media only screen and (min-device-width : 768px) and (max-device-width : 1024px) and (orientation : portrait) 58 | body.ios6 #foreground header 59 | width 230px !important -------------------------------------------------------------------------------- /src/templates/index.jade: -------------------------------------------------------------------------------- 1 | doctype html 2 | html(lang="en") 3 | head 4 | title See Artsy's 2013 Year in Review by the Numbers 5 | meta( name="title", "See Artsy's 2013 Year in Review by the Numbers" ) 6 | meta( name="viewport", content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no" ) 7 | meta( property="og:image" content="http://2013.artsy.net/images/fb-thumb.jpg" ) 8 | link( type='text/css', rel='stylesheet', href=('index.css') ) 9 | link( rel='apple-touch-icon', href='images/icon-57.png' ) 10 | link( rel='apple-touch-icon', sizes='72x72', href='images/icon-72.png' ) 11 | link( rel='apple-touch-icon', sizes='114x114', href='images/icon-114.png' ) 12 | link( rel='icon', type='image/png', href='images/favicon.ico' ) 13 | body.body-loading.logo-loading 14 | #body-loading-spinner.loading-spinner 15 | #wrapper 16 | #scroller 17 | .non-logo 18 | #header-background 19 | #header-background-gradient 20 | ul 21 | for x, i in new Array(8) 22 | li( class=(i == 0 ? 'active' : null) ) 23 | header#main-header.viewport-height 24 | #main-logo 25 | img( src='images/logo.png' ) 26 | #main-header-down-arrow.non-logo 27 | .non-logo 28 | #intro-statement 29 | #intro-statement-inner 30 | hr 31 | p 32 | | Artsy was created to bring all the world’s art into one place — a tool for collectors, students, and art lovers. 33 | p 2013 was our first full year of making that vision a reality. 34 | .half-viewport-height 35 | main#content 36 | include content 37 | footer#footer 38 | include footer 39 | #scripts 40 | include scripts 41 | script( src='index.js' ) -------------------------------------------------------------------------------- /src/stylesheets/index.styl: -------------------------------------------------------------------------------- 1 | @import '../../node_modules/nib' 2 | @import 'foreground' 3 | @import 'background' 4 | @import 'header' 5 | @import 'iscroll' 6 | @import 'social' 7 | @import 'footer' 8 | @import 'mobile' 9 | @import 'spinners' 10 | 11 | global-reset() 12 | 13 | html 14 | background black 15 | 16 | body 17 | background black 18 | color white 19 | garamond() 20 | line-height 1.4em 21 | font-size default-font-size 22 | &.logo-loading 23 | #main-logo 24 | opacity 0 25 | #body-loading-spinner 26 | display block 27 | &.body-loading 28 | .non-logo 29 | opacity 0 30 | 31 | .non-logo 32 | transition opacity 1s cubic-bezier(0.550, 0.055, 0.675, 0.190) 33 | 34 | #body-loading-spinner 35 | display none 36 | top 50% 37 | left 50% 38 | margin-left -15px 39 | position fixed 40 | background white 41 | 42 | * 43 | box-sizing border-box 44 | 45 | em 46 | font-style italic 47 | 48 | button, a 49 | outline none 50 | color white 51 | 52 | #intro-statement, #content 53 | max-width 1600px 54 | margin-left auto 55 | margin-right auto 56 | #intro-statement .half-viewport-height 57 | margin-top -18% 58 | 59 | #content 60 | position relative 61 | 62 | .faux-underline 63 | & 64 | display: inline-block 65 | position: relative 66 | text-decoration: none 67 | &:before 68 | content: '' 69 | position: absolute 70 | left: 0 71 | display: inline-block 72 | height: 1em 73 | width: 100% 74 | margin-top: 0.15em 75 | border-bottom: 1px solid #777 76 | color: rgb(80, 80, 80) 77 | &:hover 78 | text-decoration none 79 | 80 | .iScrollIndicator 81 | border 1px solid #999 !important 82 | 83 | @font-face 84 | font-family 'AvantGardeGothicITCW01D 731075' 85 | font-weight normal 86 | font-style normal 87 | src url("fonts/c18aee57-7585-4a8b-9a1c-69d226301d73.woff") format("woff") -------------------------------------------------------------------------------- /out/images/twitter.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 10 | 11 | 12 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/stylesheets/background.styl: -------------------------------------------------------------------------------- 1 | @import '../../node_modules/nib' 2 | @import 'lib' 3 | 4 | img-margin = 40px 5 | caption-margin = 10px 6 | gray-color = #999 7 | 8 | #background 9 | padding 50px 10 | padding-bottom 0 11 | z-index -2 12 | clearfix() 13 | > ul 14 | float right 15 | width background-width 16 | img 17 | background black 18 | 19 | #background-content li 20 | img 21 | width 100% 22 | margin-bottom img-margin 23 | display block 24 | figure 25 | margin 0 0 img-margin 0 26 | img 27 | margin-bottom caption-margin 28 | figcaption 29 | font-style italic 30 | position relative 31 | line-height 20px 32 | margin-top 15px 33 | clearfix() 34 | .main 35 | width 65% 36 | float left 37 | .source 38 | width 35% 39 | color #666 40 | font-size 15px 41 | font-style italic 42 | float right 43 | text-align right 44 | a.source 45 | z-index 4 46 | text-decoration none 47 | position absolute 48 | right 0 49 | &:before 50 | opacity 0.5 51 | 52 | .two-column-images 53 | width 50% 54 | display inline-block 55 | vertical-align top 56 | &.two-column-left 57 | padding-right 10px 58 | float left 59 | &.two-column-right 60 | padding-left 10px 61 | li 62 | margin-bottom img-margin 63 | display inline-block 64 | img 65 | margin-bottom caption-margin !important 66 | p, em 67 | display block 68 | margin 0 69 | color gray-color 70 | line-height 1.2em 71 | font-size default-font-size - 2 72 | em 73 | font-style italic 74 | span 75 | font-style normal 76 | 77 | #background-code 78 | font-family "Courier New", Courier, monospace 79 | position relative 80 | overflow hidden 81 | line-height 1em 82 | font-size 13px 83 | pre 84 | padding-left 15% 85 | 86 | #background-code-mask 87 | position absolute 88 | top 0 89 | right 0 90 | width 100% 91 | border-bottom 5px solid black 92 | background transparent !important 93 | 94 | .artist-info 95 | color gray-color 96 | 97 | .phone-foreground-container 98 | display none 99 | 100 | #graph-wrapper 101 | position absolute 102 | top 35px 103 | h4 104 | font-size 28px 105 | margin-bottom 20px 106 | margin-top 10px 107 | #graph, #graph-wrapper 108 | width 100% 109 | 110 | #graph-container 111 | position relative 112 | 113 | #iphone-screens img 114 | margin 0 115 | display block 116 | 117 | .may-images 118 | li 119 | margin-bottom 20px 120 | display block 121 | img 122 | margin 0 !important 123 | display block -------------------------------------------------------------------------------- /src/stylesheets/header.styl: -------------------------------------------------------------------------------- 1 | @import 'lib' 2 | @import '../../node_modules/nib' 3 | 4 | #header-background 5 | position absolute 6 | width 100% 7 | #header-background-gradient 8 | content '.' 9 | color 'transparent' 10 | background linear-gradient(top, transparent, black) 11 | position absolute 12 | bottom 0 13 | width 100% 14 | height 40% 15 | opacity 0 16 | z-index 2 17 | li 18 | position absolute 19 | top 0 20 | left 0 21 | background center center no-repeat 22 | background-size cover 23 | z-index -1 24 | width 100% 25 | height 100% 26 | opacity 0 27 | transition opacity 1s ease-in-out 28 | transform translate3d(0,0,0) 29 | z-index 1 30 | &.active 31 | opacity 1 32 | 33 | #main-header 34 | width 100% 35 | height 100% 36 | position relative 37 | 38 | #main-logo 39 | width 100% 40 | top 50% 41 | padding 0 38% 42 | position absolute 43 | transition opacity 0.3s ease-in 44 | z-index 4 45 | text-align center 46 | img 47 | display inline-block 48 | max-width 100% 49 | 50 | @media (max-width: 1600px) 51 | #main-logo 52 | padding 0 30% 53 | margin-top -3% 54 | @media (max-width: 1440px) 55 | #main-logo 56 | padding 0 27% 57 | margin-top -6% 58 | @media (max-width: 1024px) 59 | #main-logo 60 | padding 0 20% 61 | margin-top -8% 62 | @media (max-width: 640px) 63 | #main-logo 64 | padding 0 20px 65 | margin-top -12% 66 | 67 | #main-header-down-arrow 68 | background center center no-repeat url('images/down-arrow.svg') 69 | background-size 40px 70 | width 100% 71 | height 80px 72 | margin auto 73 | position absolute 74 | bottom 0 75 | cursor pointer 76 | border 0 77 | z-index 2 78 | bottom -100px 79 | opacity 0 80 | transition-duration 0.3s !important 81 | 82 | #intro-statement 83 | font-size 42px 84 | line-height 1.2em 85 | padding 0 foreground-padding 86 | position relative 87 | margin-top 300px 88 | p 89 | margin-bottom 1em 90 | #intro-statement-inner 91 | padding-top 60px 92 | width 680px 93 | line-height 1.1em 94 | hr 95 | width foreground-width 96 | position absolute 97 | left foreground-padding 98 | top 0 99 | border 0 100 | border-top 3px solid white 101 | @media (max-width: 1024px) 102 | #intro-statement 103 | margin 200px 0 104 | #intro-statement-inner 105 | width 100% 106 | 107 | 108 | backgrounds(size) 109 | #header-background ul 110 | for i in (0..8) 111 | li:nth-child({i}) 112 | background-image url('images/header/Cover_0' + i + '_' + size + '.jpg') 113 | 114 | backgrounds(1920) 115 | @media (max-width: 1440px) 116 | backgrounds(1440) 117 | @media (max-width: 640px) 118 | backgrounds(640) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # artsy-2013 2 | 3 | The [2013.artsy.net](http://2013.artsy.net) static site using [Node.js](http://nodejs.org/) for some preprocessors. 4 | 5 | ## Getting Started 6 | 7 | 1. Install [Node.js](http://nodejs.org/) 8 | 2. Install node modules `npm install` 9 | 3. Edit in development mode `make s` and open [localhost:3000](http://localhost:3000) 10 | 4. Generate the static site under /out `make generate` 11 | 12 | Optionally... 13 | 14 | * Install [imagemagick](http://www.imagemagick.org/script/index.php) and generate responsive images `make images` 15 | * Deploy to production based on S3 credentials stored in your env variables `make deploy` 16 | 17 | ## Content Images 18 | 19 | Currently the image contents are not checked under git. If you are working at Artsy you can download the images from our S3 bucket. Otherwise you'll just have to download them off 2013.artsy.net yourself, or add your own place-holders with file names relative to what's pointed at under src/templates/content. 20 | 21 | Additionally there is a `make images` task to resize these images. To use this install imagemagick, place your images under the out/_content folder, and run the `make images` task. 22 | 23 | ## Overview 24 | 25 | Artsy 2013 is a static site that uses [stylus](http://learnboost.github.io/stylus/), [jade](http://jade-lang.com/), [coffeescript](http://coffeescript.org/), and [browserify](http://browserify.org/) to make development easier. All of the source files are found under /src and a Makefile has a couple tasks to output the site for production use. 26 | 27 | The majority of the code can be found under src/scripts/index.coffee. In here, scroll-driven animations are set up through a set of functions wrapped up in an `onScroll` function. Because supporting scroll events on iPad is crazy, we're using [iScroll](https://github.com/cubiq/iscroll) if we detect an iPad device, while using the more traditional `$(window).on('scroll')` for desktop browsers. This means we have to wrap a lot of code like `$(window).scrollTop()` in our own utility functions that calculate based on iscroll or `window` depending on the device. 28 | 29 | A combination of responsive and device detection is used to support the most common devices (desktop browser, iPhone, iPad). Responsive media queries pertaining to mobile devices can be found in src/stylesheets/mobile, as well as non-mobile specific media queries found among their respective component stylesheets. The src/scripts/index.coffee file has some device detection based on `window.navigator.userAgent`. On load we add classes to the body indicating things like `ios6`. Along-side the troublesome CSS selectors in our stylus files we will target the offenders via `body.ios6 #offending .class`. 30 | 31 | ## Contributing 32 | 33 | This is mostly just an example project for learning's sake, but if you would still like to contribute simply fork the project and submit a pull request. 34 | 35 | ## License 36 | 37 | MIT 38 | -------------------------------------------------------------------------------- /src/scripts/vendor/morpheus-easings.js: -------------------------------------------------------------------------------- 1 | /* The equations defined here are open source under BSD License. 2 | * http://www.robertpenner.com/easing_terms_of_use.html (c) 2003 Robert Penner 3 | * Adapted to single time-based by 4 | * Brian Crescimanno 5 | * Ken Snyder 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 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 2011 49 | 2012 50 | 2013 51 | Feb 52 | Mar 53 | Apr 54 | May 55 | Jun 56 | Jul 57 | Aug 58 | Sep 59 | Oct 60 | Nov 61 | Dec 62 | Jan 63 | Feb 64 | Mar 65 | Apr 66 | May 67 | Jun 68 | Jul 69 | Aug 70 | Sep 71 | Oct 72 | Nov 73 | Dec 74 | Jan 75 | Feb 76 | Mar 77 | Apr 78 | May 79 | Jun 80 | Jul 81 | Aug 82 | Sep 83 | Oct 84 | Nov 85 | Dec 86 | 87 | 91 | 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>") 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 = /)<[^<]*)*<\/script>/gi, 1146 | scriptTypeRE = /^(?:text|application)\/javascript/i, 1147 | xmlTypeRE = /^(?:text|application)\/xml/i, 1148 | jsonType = 'application/json', 1149 | htmlType = 'text/html', 1150 | blankRE = /^\s*$/ 1151 | 1152 | // trigger a custom event and return false if it was cancelled 1153 | function triggerAndReturn(context, eventName, data) { 1154 | var event = $.Event(eventName) 1155 | $(context).trigger(event, data) 1156 | return !event.isDefaultPrevented() 1157 | } 1158 | 1159 | // trigger an Ajax "global" event 1160 | function triggerGlobal(settings, context, eventName, data) { 1161 | if (settings.global) return triggerAndReturn(context || document, eventName, data) 1162 | } 1163 | 1164 | // Number of active Ajax requests 1165 | $.active = 0 1166 | 1167 | function ajaxStart(settings) { 1168 | if (settings.global && $.active++ === 0) triggerGlobal(settings, null, 'ajaxStart') 1169 | } 1170 | function ajaxStop(settings) { 1171 | if (settings.global && !(--$.active)) triggerGlobal(settings, null, 'ajaxStop') 1172 | } 1173 | 1174 | // triggers an extra global event "ajaxBeforeSend" that's like "ajaxSend" but cancelable 1175 | function ajaxBeforeSend(xhr, settings) { 1176 | var context = settings.context 1177 | if (settings.beforeSend.call(context, xhr, settings) === false || 1178 | triggerGlobal(settings, context, 'ajaxBeforeSend', [xhr, settings]) === false) 1179 | return false 1180 | 1181 | triggerGlobal(settings, context, 'ajaxSend', [xhr, settings]) 1182 | } 1183 | function ajaxSuccess(data, xhr, settings, deferred) { 1184 | var context = settings.context, status = 'success' 1185 | settings.success.call(context, data, status, xhr) 1186 | if (deferred) deferred.resolveWith(context, [data, status, xhr]) 1187 | triggerGlobal(settings, context, 'ajaxSuccess', [xhr, settings, data]) 1188 | ajaxComplete(status, xhr, settings) 1189 | } 1190 | // type: "timeout", "error", "abort", "parsererror" 1191 | function ajaxError(error, type, xhr, settings, deferred) { 1192 | var context = settings.context 1193 | settings.error.call(context, xhr, type, error) 1194 | if (deferred) deferred.rejectWith(context, [xhr, type, error]) 1195 | triggerGlobal(settings, context, 'ajaxError', [xhr, settings, error || type]) 1196 | ajaxComplete(type, xhr, settings) 1197 | } 1198 | // status: "success", "notmodified", "error", "timeout", "abort", "parsererror" 1199 | function ajaxComplete(status, xhr, settings) { 1200 | var context = settings.context 1201 | settings.complete.call(context, xhr, status) 1202 | triggerGlobal(settings, context, 'ajaxComplete', [xhr, settings]) 1203 | ajaxStop(settings) 1204 | } 1205 | 1206 | // Empty function, used as default callback 1207 | function empty() {} 1208 | 1209 | $.ajaxJSONP = function(options, deferred){ 1210 | if (!('type' in options)) return $.ajax(options) 1211 | 1212 | var _callbackName = options.jsonpCallback, 1213 | callbackName = ($.isFunction(_callbackName) ? 1214 | _callbackName() : _callbackName) || ('jsonp' + (++jsonpID)), 1215 | script = document.createElement('script'), 1216 | originalCallback = window[callbackName], 1217 | responseData, 1218 | abort = function(errorType) { 1219 | $(script).triggerHandler('error', errorType || 'abort') 1220 | }, 1221 | xhr = { abort: abort }, abortTimeout 1222 | 1223 | if (deferred) deferred.promise(xhr) 1224 | 1225 | $(script).on('load error', function(e, errorType){ 1226 | clearTimeout(abortTimeout) 1227 | $(script).off().remove() 1228 | 1229 | if (e.type == 'error' || !responseData) { 1230 | ajaxError(null, errorType || 'error', xhr, options, deferred) 1231 | } else { 1232 | ajaxSuccess(responseData[0], xhr, options, deferred) 1233 | } 1234 | 1235 | window[callbackName] = originalCallback 1236 | if (responseData && $.isFunction(originalCallback)) 1237 | originalCallback(responseData[0]) 1238 | 1239 | originalCallback = responseData = undefined 1240 | }) 1241 | 1242 | if (ajaxBeforeSend(xhr, options) === false) { 1243 | abort('abort') 1244 | return xhr 1245 | } 1246 | 1247 | window[callbackName] = function(){ 1248 | responseData = arguments 1249 | } 1250 | 1251 | script.src = options.url.replace(/=\?/, '=' + callbackName) 1252 | document.head.appendChild(script) 1253 | 1254 | if (options.timeout > 0) abortTimeout = setTimeout(function(){ 1255 | abort('timeout') 1256 | }, options.timeout) 1257 | 1258 | return xhr 1259 | } 1260 | 1261 | $.ajaxSettings = { 1262 | // Default type of request 1263 | type: 'GET', 1264 | // Callback that is executed before request 1265 | beforeSend: empty, 1266 | // Callback that is executed if the request succeeds 1267 | success: empty, 1268 | // Callback that is executed the the server drops error 1269 | error: empty, 1270 | // Callback that is executed on request complete (both: error and success) 1271 | complete: empty, 1272 | // The context for the callbacks 1273 | context: null, 1274 | // Whether to trigger "global" Ajax events 1275 | global: true, 1276 | // Transport 1277 | xhr: function () { 1278 | return new window.XMLHttpRequest() 1279 | }, 1280 | // MIME types mapping 1281 | // IIS returns Javascript as "application/x-javascript" 1282 | accepts: { 1283 | script: 'text/javascript, application/javascript, application/x-javascript', 1284 | json: jsonType, 1285 | xml: 'application/xml, text/xml', 1286 | html: htmlType, 1287 | text: 'text/plain' 1288 | }, 1289 | // Whether the request is to another domain 1290 | crossDomain: false, 1291 | // Default timeout 1292 | timeout: 0, 1293 | // Whether data should be serialized to string 1294 | processData: true, 1295 | // Whether the browser should be allowed to cache GET responses 1296 | cache: true 1297 | } 1298 | 1299 | function mimeToDataType(mime) { 1300 | if (mime) mime = mime.split(';', 2)[0] 1301 | return mime && ( mime == htmlType ? 'html' : 1302 | mime == jsonType ? 'json' : 1303 | scriptTypeRE.test(mime) ? 'script' : 1304 | xmlTypeRE.test(mime) && 'xml' ) || 'text' 1305 | } 1306 | 1307 | function appendQuery(url, query) { 1308 | if (query == '') return url 1309 | return (url + '&' + query).replace(/[&?]{1,2}/, '?') 1310 | } 1311 | 1312 | // serialize payload and append it to the URL for GET requests 1313 | function serializeData(options) { 1314 | if (options.processData && options.data && $.type(options.data) != "string") 1315 | options.data = $.param(options.data, options.traditional) 1316 | if (options.data && (!options.type || options.type.toUpperCase() == 'GET')) 1317 | options.url = appendQuery(options.url, options.data), options.data = undefined 1318 | } 1319 | 1320 | $.ajax = function(options){ 1321 | var settings = $.extend({}, options || {}), 1322 | deferred = $.Deferred && $.Deferred() 1323 | for (key in $.ajaxSettings) if (settings[key] === undefined) settings[key] = $.ajaxSettings[key] 1324 | 1325 | ajaxStart(settings) 1326 | 1327 | if (!settings.crossDomain) settings.crossDomain = /^([\w-]+:)?\/\/([^\/]+)/.test(settings.url) && 1328 | RegExp.$2 != window.location.host 1329 | 1330 | if (!settings.url) settings.url = window.location.toString() 1331 | serializeData(settings) 1332 | if (settings.cache === false) settings.url = appendQuery(settings.url, '_=' + Date.now()) 1333 | 1334 | var dataType = settings.dataType, hasPlaceholder = /=\?/.test(settings.url) 1335 | if (dataType == 'jsonp' || hasPlaceholder) { 1336 | if (!hasPlaceholder) 1337 | settings.url = appendQuery(settings.url, 1338 | settings.jsonp ? (settings.jsonp + '=?') : settings.jsonp === false ? '' : 'callback=?') 1339 | return $.ajaxJSONP(settings, deferred) 1340 | } 1341 | 1342 | var mime = settings.accepts[dataType], 1343 | headers = { }, 1344 | setHeader = function(name, value) { headers[name.toLowerCase()] = [name, value] }, 1345 | protocol = /^([\w-]+:)\/\//.test(settings.url) ? RegExp.$1 : window.location.protocol, 1346 | xhr = settings.xhr(), 1347 | nativeSetHeader = xhr.setRequestHeader, 1348 | abortTimeout 1349 | 1350 | if (deferred) deferred.promise(xhr) 1351 | 1352 | if (!settings.crossDomain) setHeader('X-Requested-With', 'XMLHttpRequest') 1353 | setHeader('Accept', mime || '*/*') 1354 | if (mime = settings.mimeType || mime) { 1355 | if (mime.indexOf(',') > -1) mime = mime.split(',', 2)[0] 1356 | xhr.overrideMimeType && xhr.overrideMimeType(mime) 1357 | } 1358 | if (settings.contentType || (settings.contentType !== false && settings.data && settings.type.toUpperCase() != 'GET')) 1359 | setHeader('Content-Type', settings.contentType || 'application/x-www-form-urlencoded') 1360 | 1361 | if (settings.headers) for (name in settings.headers) setHeader(name, settings.headers[name]) 1362 | xhr.setRequestHeader = setHeader 1363 | 1364 | xhr.onreadystatechange = function(){ 1365 | if (xhr.readyState == 4) { 1366 | xhr.onreadystatechange = empty 1367 | clearTimeout(abortTimeout) 1368 | var result, error = false 1369 | if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304 || (xhr.status == 0 && protocol == 'file:')) { 1370 | dataType = dataType || mimeToDataType(settings.mimeType || xhr.getResponseHeader('content-type')) 1371 | result = xhr.responseText 1372 | 1373 | try { 1374 | // http://perfectionkills.com/global-eval-what-are-the-options/ 1375 | if (dataType == 'script') (1,eval)(result) 1376 | else if (dataType == 'xml') result = xhr.responseXML 1377 | else if (dataType == 'json') result = blankRE.test(result) ? null : $.parseJSON(result) 1378 | } catch (e) { error = e } 1379 | 1380 | if (error) ajaxError(error, 'parsererror', xhr, settings, deferred) 1381 | else ajaxSuccess(result, xhr, settings, deferred) 1382 | } else { 1383 | ajaxError(xhr.statusText || null, xhr.status ? 'error' : 'abort', xhr, settings, deferred) 1384 | } 1385 | } 1386 | } 1387 | 1388 | if (ajaxBeforeSend(xhr, settings) === false) { 1389 | xhr.abort() 1390 | ajaxError(null, 'abort', xhr, settings, deferred) 1391 | return xhr 1392 | } 1393 | 1394 | if (settings.xhrFields) for (name in settings.xhrFields) xhr[name] = settings.xhrFields[name] 1395 | 1396 | var async = 'async' in settings ? settings.async : true 1397 | xhr.open(settings.type, settings.url, async, settings.username, settings.password) 1398 | 1399 | for (name in headers) nativeSetHeader.apply(xhr, headers[name]) 1400 | 1401 | if (settings.timeout > 0) abortTimeout = setTimeout(function(){ 1402 | xhr.onreadystatechange = empty 1403 | xhr.abort() 1404 | ajaxError(null, 'timeout', xhr, settings, deferred) 1405 | }, settings.timeout) 1406 | 1407 | // avoid sending empty string (#319) 1408 | xhr.send(settings.data ? settings.data : null) 1409 | return xhr 1410 | } 1411 | 1412 | // handle optional data/success arguments 1413 | function parseArguments(url, data, success, dataType) { 1414 | var hasData = !$.isFunction(data) 1415 | return { 1416 | url: url, 1417 | data: hasData ? data : undefined, 1418 | success: !hasData ? data : $.isFunction(success) ? success : undefined, 1419 | dataType: hasData ? dataType || success : success 1420 | } 1421 | } 1422 | 1423 | $.get = function(url, data, success, dataType){ 1424 | return $.ajax(parseArguments.apply(null, arguments)) 1425 | } 1426 | 1427 | $.post = function(url, data, success, dataType){ 1428 | var options = parseArguments.apply(null, arguments) 1429 | options.type = 'POST' 1430 | return $.ajax(options) 1431 | } 1432 | 1433 | $.getJSON = function(url, data, success){ 1434 | var options = parseArguments.apply(null, arguments) 1435 | options.dataType = 'json' 1436 | return $.ajax(options) 1437 | } 1438 | 1439 | $.fn.load = function(url, data, success){ 1440 | if (!this.length) return this 1441 | var self = this, parts = url.split(/\s/), selector, 1442 | options = parseArguments(url, data, success), 1443 | callback = options.success 1444 | if (parts.length > 1) options.url = parts[0], selector = parts[1] 1445 | options.success = function(response){ 1446 | self.html(selector ? 1447 | $('
    ').html(response.replace(rscript, "")).find(selector) 1448 | : response) 1449 | callback && callback.apply(self, arguments) 1450 | } 1451 | $.ajax(options) 1452 | return this 1453 | } 1454 | 1455 | var escape = encodeURIComponent 1456 | 1457 | function serialize(params, obj, traditional, scope){ 1458 | var type, array = $.isArray(obj), hash = $.isPlainObject(obj) 1459 | $.each(obj, function(key, value) { 1460 | type = $.type(value) 1461 | if (scope) key = traditional ? scope : 1462 | scope + '[' + (hash || type == 'object' || type == 'array' ? key : '') + ']' 1463 | // handle data in serializeArray() format 1464 | if (!scope && array) params.add(value.name, value.value) 1465 | // recurse into nested objects 1466 | else if (type == "array" || (!traditional && type == "object")) 1467 | serialize(params, value, traditional, key) 1468 | else params.add(key, value) 1469 | }) 1470 | } 1471 | 1472 | $.param = function(obj, traditional){ 1473 | var params = [] 1474 | params.add = function(k, v){ this.push(escape(k) + '=' + escape(v)) } 1475 | serialize(params, obj, traditional) 1476 | return params.join('&').replace(/%20/g, '+') 1477 | } 1478 | })(Zepto) 1479 | 1480 | ;(function($){ 1481 | $.fn.serializeArray = function() { 1482 | var result = [], el 1483 | $([].slice.call(this.get(0).elements)).each(function(){ 1484 | el = $(this) 1485 | var type = el.attr('type') 1486 | if (this.nodeName.toLowerCase() != 'fieldset' && 1487 | !this.disabled && type != 'submit' && type != 'reset' && type != 'button' && 1488 | ((type != 'radio' && type != 'checkbox') || this.checked)) 1489 | result.push({ 1490 | name: el.attr('name'), 1491 | value: el.val() 1492 | }) 1493 | }) 1494 | return result 1495 | } 1496 | 1497 | $.fn.serialize = function(){ 1498 | var result = [] 1499 | this.serializeArray().forEach(function(elm){ 1500 | result.push(encodeURIComponent(elm.name) + '=' + encodeURIComponent(elm.value)) 1501 | }) 1502 | return result.join('&') 1503 | } 1504 | 1505 | $.fn.submit = function(callback) { 1506 | if (callback) this.bind('submit', callback) 1507 | else if (this.length) { 1508 | var event = $.Event('submit') 1509 | this.eq(0).trigger(event) 1510 | if (!event.isDefaultPrevented()) this.get(0).submit() 1511 | } 1512 | return this 1513 | } 1514 | 1515 | })(Zepto) 1516 | 1517 | ;(function($){ 1518 | // __proto__ doesn't exist on IE<11, so redefine 1519 | // the Z function to use object extension instead 1520 | if (!('__proto__' in {})) { 1521 | $.extend($.zepto, { 1522 | Z: function(dom, selector){ 1523 | dom = dom || [] 1524 | $.extend(dom, $.fn) 1525 | dom.selector = selector || '' 1526 | dom.__Z = true 1527 | return dom 1528 | }, 1529 | // this is a kludge but works 1530 | isZ: function(object){ 1531 | return $.type(object) === 'array' && '__Z' in object 1532 | } 1533 | }) 1534 | } 1535 | 1536 | // getComputedStyle shouldn't freak out when called 1537 | // without a valid element as argument 1538 | try { 1539 | getComputedStyle(undefined) 1540 | } catch(e) { 1541 | var nativeGetComputedStyle = getComputedStyle; 1542 | window.getComputedStyle = function(element){ 1543 | try { 1544 | return nativeGetComputedStyle(element) 1545 | } catch(e) { 1546 | return null 1547 | } 1548 | } 1549 | } 1550 | })(Zepto) --------------------------------------------------------------------------------