├── .gitignore ├── .meteor ├── .gitignore ├── release ├── platforms ├── cordova-plugins ├── .finished-upgraders ├── .id ├── packages └── versions ├── scss.json ├── client ├── initializers │ └── autoform.js ├── styles │ ├── utils.scss │ ├── lib │ │ ├── mixins.scss │ │ └── variables.scss │ ├── components │ │ ├── account.scss │ │ ├── main-slides.scss │ │ └── fixed-buttons.scss │ ├── modules │ │ ├── modal.scss │ │ ├── input-bottom.scss │ │ ├── header.scss │ │ ├── button.scss │ │ ├── ea-button.scss │ │ ├── img-preview.scss │ │ ├── guest-list.scss │ │ └── list-items.scss │ ├── layout.scss │ └── base.scss ├── views │ ├── events │ │ ├── _activity-edit.html │ │ ├── create-activity.js │ │ ├── show │ │ │ ├── rsvp.html │ │ │ ├── photos.html │ │ │ ├── wall.js │ │ │ ├── host.js │ │ │ ├── wall.html │ │ │ ├── guest-list.js │ │ │ ├── host.html │ │ │ ├── details.js │ │ │ ├── details.html │ │ │ └── guest-list.html │ │ ├── _list.js │ │ ├── account │ │ │ ├── account-input.html │ │ │ └── account-input.js │ │ ├── _edit.js │ │ ├── rsvp.html │ │ ├── _list.html │ │ ├── create-activity.html │ │ ├── account.html │ │ ├── index.html │ │ ├── _edit.html │ │ ├── account.js │ │ ├── index.js │ │ ├── show.js │ │ └── show.html │ ├── index.js │ ├── index.html │ └── layouts │ │ └── application.html ├── components │ ├── swiper-slides │ │ ├── swiper-slides.html │ │ └── swiper-slides.js │ └── modal │ │ └── modal.js ├── lib │ ├── helpers.js │ ├── formaldehyde.js │ └── ramjet.js └── swiper │ └── swiper.css ├── public └── images │ └── 57.png ├── methods └── users.js ├── controllers ├── lib │ └── application.js ├── index.js └── events │ ├── show.js │ └── index.js ├── routes.js ├── server ├── publications.js └── seeds.js ├── mobile-config.js ├── collections ├── events.js ├── lib │ └── schemas.js └── users.js └── index.scss /.gitignore: -------------------------------------------------------------------------------- 1 | settings.json 2 | -------------------------------------------------------------------------------- /.meteor/.gitignore: -------------------------------------------------------------------------------- 1 | local 2 | -------------------------------------------------------------------------------- /.meteor/release: -------------------------------------------------------------------------------- 1 | METEOR@1.1.0.2 2 | -------------------------------------------------------------------------------- /.meteor/platforms: -------------------------------------------------------------------------------- 1 | browser 2 | ios 3 | server 4 | -------------------------------------------------------------------------------- /scss.json: -------------------------------------------------------------------------------- 1 | { 2 | "useIndex": true 3 | } 4 | -------------------------------------------------------------------------------- /client/initializers/autoform.js: -------------------------------------------------------------------------------- 1 | AutoForm.setDefaultTemplate('materialize'); 2 | -------------------------------------------------------------------------------- /client/styles/utils.scss: -------------------------------------------------------------------------------- 1 | .horizontal-padded { 2 | @include horizontal-padded; 3 | } 4 | -------------------------------------------------------------------------------- /public/images/57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/poetic/meteor-events/master/public/images/57.png -------------------------------------------------------------------------------- /.meteor/cordova-plugins: -------------------------------------------------------------------------------- 1 | org.apache.cordova.inappbrowser@0.6.0 2 | org.apache.cordova.statusbar@0.1.10 3 | -------------------------------------------------------------------------------- /client/views/events/_activity-edit.html: -------------------------------------------------------------------------------- 1 | 5 | -------------------------------------------------------------------------------- /client/views/events/create-activity.js: -------------------------------------------------------------------------------- 1 | Template.CreateActivity.events({ 2 | 'click .back': Modal.closeModal 3 | }); 4 | -------------------------------------------------------------------------------- /client/views/events/show/rsvp.html: -------------------------------------------------------------------------------- 1 | 5 | -------------------------------------------------------------------------------- /client/views/events/show/photos.html: -------------------------------------------------------------------------------- 1 | 5 | -------------------------------------------------------------------------------- /client/views/index.js: -------------------------------------------------------------------------------- 1 | Template.Index.events({ 2 | 'click li': function(){ 3 | Router.go('events.index', { user_id: this._id }); 4 | } 5 | }); 6 | -------------------------------------------------------------------------------- /client/styles/lib/mixins.scss: -------------------------------------------------------------------------------- 1 | @mixin horizontal-padded { 2 | padding-left: 10px; 3 | padding-right: 10px; 4 | box-sizing: border-box; 5 | } 6 | 7 | -------------------------------------------------------------------------------- /methods/users.js: -------------------------------------------------------------------------------- 1 | Meteor.methods({ 2 | addComment: function(eventId, comment){ 3 | Events.update({ _id: eventId }, { $push: { comments: comment } }); 4 | } 5 | }); 6 | -------------------------------------------------------------------------------- /client/styles/lib/variables.scss: -------------------------------------------------------------------------------- 1 | $black: #000000; 2 | $lightgray: #D3D3D3; 3 | $gray: #808080; 4 | $white: #FFFFFF; 5 | $green: #008000; 6 | $red: #FF0000; 7 | $status-height: 23px; 8 | $header-height: 40px; 9 | -------------------------------------------------------------------------------- /controllers/lib/application.js: -------------------------------------------------------------------------------- 1 | ApplicationController = RouteController.extend({ 2 | layoutTemplate: 'ApplicationLayout', 3 | notFoundTemplate: 'notFoundLayout', 4 | loadingTemplate: 'loadingLayout' 5 | }); 6 | -------------------------------------------------------------------------------- /client/views/events/_list.js: -------------------------------------------------------------------------------- 1 | Template._eventsList.events({ 2 | 'click li': function(){ 3 | var userId = Router.current().params.user_id; 4 | 5 | Router.go('events.show', { user_id: userId, event_id: this._id }); 6 | } 7 | }); 8 | -------------------------------------------------------------------------------- /client/components/swiper-slides/swiper-slides.html: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /client/styles/components/account.scss: -------------------------------------------------------------------------------- 1 | #account { 2 | .title { 3 | font-weight: bold; 4 | border-bottom: 1px solid $gray; 5 | margin-bottom: 20px; 6 | margin-top: 32px; 7 | padding-bottom: 10px; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /routes.js: -------------------------------------------------------------------------------- 1 | Router.route('/', { 2 | name: 'index' 3 | }); 4 | 5 | Router.route('/users/:user_id/events', { 6 | name: 'events.index' 7 | }); 8 | 9 | Router.route('/users/:user_id/events/:event_id', { 10 | name: 'events.show' 11 | }); 12 | -------------------------------------------------------------------------------- /client/views/index.html: -------------------------------------------------------------------------------- 1 | 12 | -------------------------------------------------------------------------------- /controllers/index.js: -------------------------------------------------------------------------------- 1 | IndexController = ApplicationController.extend({ 2 | waitOn: function(){ 3 | Meteor.subscribe('users'); 4 | }, 5 | 6 | data: { 7 | users: function(){ 8 | return Meteor.users.find(); 9 | } 10 | } 11 | }); 12 | -------------------------------------------------------------------------------- /client/styles/modules/modal.scss: -------------------------------------------------------------------------------- 1 | .modal-full { 2 | display: none; 3 | top: 0; 4 | position: absolute; 5 | background-color: white; 6 | z-index: 100; 7 | width: 100%; 8 | height: 100%; 9 | } 10 | -------------------------------------------------------------------------------- /client/styles/components/main-slides.scss: -------------------------------------------------------------------------------- 1 | .swiper-slide-main { 2 | .swiper-slide { 3 | overflow-y: scroll; 4 | -webkit-overflow-scrolling: touch; 5 | 6 | .swiper-slide-scrollable { 7 | margin: 0; 8 | height: 100.2%; 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /server/publications.js: -------------------------------------------------------------------------------- 1 | Meteor.publish('users', function(){ 2 | return Meteor.users.find(); 3 | }); 4 | 5 | Meteor.publish('events', function(){ 6 | return Events.find(); 7 | }); 8 | 9 | Meteor.publish('event', function(eventId){ 10 | return Events.find({ _id: eventId }); 11 | }); 12 | -------------------------------------------------------------------------------- /.meteor/.finished-upgraders: -------------------------------------------------------------------------------- 1 | # This file contains information which helps Meteor properly upgrade your 2 | # app when you run 'meteor update'. You should check it into version control 3 | # with your project. 4 | 5 | notices-for-0.9.0 6 | notices-for-0.9.1 7 | 0.9.4-platform-file 8 | notices-for-facebook-graph-api-2 9 | -------------------------------------------------------------------------------- /client/styles/modules/input-bottom.scss: -------------------------------------------------------------------------------- 1 | .form-bottom { 2 | border-top: 1px solid lightgray; 3 | background-color: white; 4 | position: fixed; 5 | bottom: 0; 6 | width: 100%; 7 | 8 | .form-bottom-input { 9 | padding-left: 10px; 10 | margin-bottom: 0; 11 | height: 50px; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /client/views/events/show/wall.js: -------------------------------------------------------------------------------- 1 | Template.EventsShowWall.helpers({ 2 | timeAgoPosted: function(){ 3 | return moment(this.created).fromNow(); 4 | }, 5 | 6 | username: function(){ 7 | var user = Meteor.users.findOne({ '_id': this.user }) 8 | 9 | if (user) { return user.profile.fullName } 10 | } 11 | }); 12 | -------------------------------------------------------------------------------- /client/views/events/account/account-input.html: -------------------------------------------------------------------------------- 1 | 9 | -------------------------------------------------------------------------------- /.meteor/.id: -------------------------------------------------------------------------------- 1 | # This file contains a token that is unique to your project. 2 | # Check it into your repository along with the rest of this directory. 3 | # It can be used for purposes such as: 4 | # - ensuring you don't accidentally deploy one app on top of another 5 | # - providing package authors with aggregated statistics 6 | 7 | 1wv7fg61nhcof01rhqsd 8 | -------------------------------------------------------------------------------- /client/styles/components/fixed-buttons.scss: -------------------------------------------------------------------------------- 1 | #acct-btn-wrapper { 2 | position: fixed; 3 | bottom: 15px; 4 | left: 15px; 5 | margin-bottom: 0; 6 | 7 | i { 8 | font-size: 30px; 9 | } 10 | } 11 | 12 | #add-event-btn-wrapper { 13 | position: fixed; 14 | bottom: 15px; 15 | right: 15px; 16 | margin-bottom: 0; 17 | 18 | i { 19 | font-size: 30px; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /client/styles/modules/header.scss: -------------------------------------------------------------------------------- 1 | .header { 2 | text-align: center; 3 | border-bottom: 1px $gray solid; 4 | 5 | .header-left { 6 | display: inline-block; 7 | position: absolute; 8 | left: 10px; 9 | } 10 | .header-middle { 11 | font-weight: bold; 12 | } 13 | .header-right { 14 | display: inline-block; 15 | position: absolute; 16 | right: 10px; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /client/views/events/_edit.js: -------------------------------------------------------------------------------- 1 | Template._eventsEdit.events({ 2 | 'submit form': function(event) { 3 | stopEvent(event); 4 | 5 | // TODO: save event 6 | }, 7 | 8 | 'click #add-an-activity': Modal.openModal.bind(null, '#createActivity'), 9 | 10 | 'click .close': function(event){ 11 | stopEvent(event); 12 | 13 | ParamManager.setParam('edit-form', null); 14 | } 15 | }); 16 | 17 | -------------------------------------------------------------------------------- /client/views/events/account/account-input.js: -------------------------------------------------------------------------------- 1 | /** 2 | * params: 3 | * model: {Hash} 4 | * name: {String} 5 | */ 6 | 7 | Template.AccountInput.helpers({ 8 | _label: function() { 9 | var defalutLabel = s.underscored(this.name).replace(/_/g, ' ').toUpperCase(); 10 | return this.label || defalutLabel; 11 | }, 12 | _value: function() { 13 | return this.model[this.name]; 14 | }, 15 | }); 16 | -------------------------------------------------------------------------------- /client/styles/modules/button.scss: -------------------------------------------------------------------------------- 1 | .btn-bottom-wrapper { 2 | position: absolute; 3 | bottom: 0px; 4 | width: 100%; 5 | 6 | .btn-full { 7 | margin-bottom: 10px; 8 | } 9 | } 10 | 11 | .btn-full { 12 | width: 100%; 13 | 14 | // NOTE: the following should be the default 15 | background-color: $white; 16 | color: $black; 17 | border: solid 1px black; 18 | border-radius: 5px; 19 | } 20 | -------------------------------------------------------------------------------- /client/components/modal/modal.js: -------------------------------------------------------------------------------- 1 | Modal = { 2 | // TODO: add an option for animation 3 | openModal: function(selector) { 4 | $(selector).css('display', 'block'); 5 | $(selector).velocity({top: 0}, "swing"); 6 | return false; 7 | }, 8 | closeModal: function(event, template) { 9 | var modal = $(template.firstNode); 10 | modal.velocity({top: '100%'}, "swing", function() { 11 | modal.css('display', 'none'); 12 | }); 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /client/styles/modules/ea-button.scss: -------------------------------------------------------------------------------- 1 | .ea-btn { 2 | width: 100%; 3 | box-sizing: border-box; 4 | border: 1px solid $black; 5 | border-radius: 5px; 6 | font-weight: bold; 7 | font-size: 1.2rem; 8 | text-transform: uppercase; 9 | background-color: white; 10 | color: $black; 11 | box-shadow: none; 12 | 13 | &:hover { 14 | background-color: white; 15 | box-shadow: none; 16 | } 17 | 18 | i { 19 | font-size: 3rem; 20 | padding-left: 5px; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /client/views/events/rsvp.html: -------------------------------------------------------------------------------- 1 | 16 | -------------------------------------------------------------------------------- /client/views/events/show/host.js: -------------------------------------------------------------------------------- 1 | Template.EventsShowHost.events({ 2 | 'click .mdi-communication-email': function(event){ 3 | stopEvent(event); 4 | window.open('mailto:' + this.email, '_system'); 5 | }, 6 | 7 | 'click .mdi-communication-phone': function(event){ 8 | stopEvent(event); 9 | location.href = 'tel:' + this.phone; 10 | }, 11 | 12 | 'click .mdi-communication-message': function(event){ 13 | stopEvent(event); 14 | window.open('sms:' + this.phone, '_system'); 15 | }, 16 | }); 17 | -------------------------------------------------------------------------------- /mobile-config.js: -------------------------------------------------------------------------------- 1 | App.setPreference('Orientation', 'portrait'); 2 | App.setPreference('StatusBarBackgroundColor', '#000000'); 3 | App.setPreference('StatusBarOverlaysWebView', false); 4 | App.setPreference('StatusBarStyle', 'lightcontent'); 5 | 6 | App.accessRule('*://lorempixel.com/*'); 7 | App.accessRule('*.google.com/*'); 8 | App.accessRule('*.googleapis.com/*'); 9 | App.accessRule('*.gstatic.com/*'); 10 | App.accessRule('mailto:*', { launchExternal: true }); 11 | App.accessRule('sms:*', { launchExternal: true }); 12 | -------------------------------------------------------------------------------- /client/views/events/show/wall.html: -------------------------------------------------------------------------------- 1 | 16 | -------------------------------------------------------------------------------- /client/styles/layout.scss: -------------------------------------------------------------------------------- 1 | #content { 2 | position: relative; 3 | } 4 | 5 | .navbar { 6 | height: 55px; 7 | background-color: $black; 8 | color: $gray; 9 | border-bottom: 1px solid $gray; 10 | text-align: center; 11 | 12 | .swiper-slide { 13 | width: 40%; 14 | height: 55px; 15 | line-height: 55px; 16 | } 17 | 18 | .swiper-slide-active { 19 | color: $white; 20 | } 21 | } 22 | 23 | .slide-container { 24 | margin: 0; 25 | } 26 | 27 | .header { 28 | height: $header-height; 29 | line-height: $header-height; 30 | } 31 | -------------------------------------------------------------------------------- /client/styles/modules/img-preview.scss: -------------------------------------------------------------------------------- 1 | .img-preview { 2 | color: white; 3 | } 4 | 5 | .img-preview-inner { 6 | width: 100%; 7 | height: 100px; 8 | position: relative; 9 | 10 | .img-preview-top-center { 11 | padding: 0 10px 0 10px; 12 | position: absolute; 13 | left: 50%; 14 | margin: 0 0 0 -24px; 15 | } 16 | 17 | .img-preview-top-right { 18 | position: absolute; 19 | right: 0; 20 | top: 0; 21 | margin: 10px; 22 | } 23 | 24 | .img-preview-title { 25 | position: absolute; 26 | margin: 10px; 27 | bottom: 0; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /client/views/events/_list.html: -------------------------------------------------------------------------------- 1 | 17 | -------------------------------------------------------------------------------- /client/views/layouts/application.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | EventAssist 7 | 8 | 9 | 10 | 11 | 16 | 17 | 20 | 21 | 24 | -------------------------------------------------------------------------------- /.meteor/packages: -------------------------------------------------------------------------------- 1 | # Meteor packages used by this project, one per line. 2 | # Check this file (and the other files in this directory) into your repository. 3 | # 4 | # 'meteor add' and 'meteor remove' will edit this file for you, 5 | # but you can also edit it by hand. 6 | 7 | meteor-platform 8 | iron:router 9 | materialize:materialize 10 | fourseven:scss 11 | digilord:faker 12 | dburles:collection-helpers 13 | underscore 14 | aldeed:simple-schema 15 | aldeed:collection2 16 | msavin:mongol 17 | momentjs:moment 18 | velocityjs:velocityjs 19 | dburles:google-maps 20 | reactive-dict 21 | accounts-ui 22 | accounts-password 23 | underscorestring:underscore.string 24 | aldeed:autoform 25 | gildaspk:autoform-materialize 26 | fastclick 27 | -------------------------------------------------------------------------------- /client/views/events/show/guest-list.js: -------------------------------------------------------------------------------- 1 | Template.EventsShowGuestList.helpers({ 2 | yesGuests: function(){ 3 | var guests = this.currentEvent().guests; 4 | return guests.filter(function(guest){ return guest.attending }); 5 | }, 6 | 7 | noGuests: function(){ 8 | var guests = this.currentEvent().guests; 9 | return guests.filter(function(guest){ return !guest.attending }); 10 | }, 11 | 12 | noReplyGuests: function(){ 13 | var guests = this.currentEvent().guests; 14 | return guests.filter(function(guest){ return !guest.replied }); 15 | }, 16 | 17 | notYetInvitedGuests: function(){ 18 | var guests = this.currentEvent().guests; 19 | return guests.filter(function(guest){ return !guest.invited }); 20 | }, 21 | }); 22 | -------------------------------------------------------------------------------- /collections/events.js: -------------------------------------------------------------------------------- 1 | Events = new Mongo.Collection('events'); 2 | 3 | Events.attachSchema(new SimpleSchema({ 4 | user: { 5 | type: String, 6 | }, 7 | coverPhoto: { 8 | type: String, 9 | }, 10 | title: { 11 | type: String 12 | }, 13 | description: { 14 | type: String 15 | }, 16 | isPlusOne: { 17 | type: Boolean 18 | }, 19 | hosts: { 20 | type: [HostSchema], 21 | defaultValue: [], 22 | }, 23 | activities: { 24 | type: [ActivitySchema], 25 | defaultValue: [], 26 | }, 27 | comments: { 28 | type: [CommentSchema], 29 | defaultValue: [], 30 | }, 31 | guests: { 32 | type: [GuestSchema], 33 | defaultValue: [], 34 | }, 35 | })); 36 | 37 | Events.helpers({ 38 | eventStartTime: function(){ 39 | var eventStartTime = _.first(_.sortBy(this.activities, 'startTime')).startTime; 40 | 41 | return moment(eventStartTime).format('dddd, MMM D YYYY, h:mm a'); 42 | }, 43 | }); 44 | -------------------------------------------------------------------------------- /controllers/events/show.js: -------------------------------------------------------------------------------- 1 | EventsShowController = ApplicationController.extend({ 2 | waitOn: function(){ 3 | var eventId = Router.current().params.event_id; 4 | 5 | Meteor.subscribe('users'); 6 | Meteor.subscribe('event', eventId); 7 | }, 8 | 9 | data: function(){ 10 | var eventId = this.params.event_id; 11 | var routeParams = this.params.query; 12 | var initialSlide; 13 | 14 | if (_.has(routeParams, 'slides')) { 15 | initialSlide = routeParams.slides; 16 | } 17 | 18 | return { 19 | currentEvent: function(){ 20 | return Events.findOne(eventId); 21 | }, 22 | plannerNavOptions: { 23 | centeredSlides: true, 24 | slidesPerView: 'auto', 25 | slideToClickedSlide: true, 26 | initialSlide: initialSlide || 2, 27 | }, 28 | plannerSlideOptions: { 29 | initialSlide: initialSlide || 2, 30 | threshold: 10, 31 | }, 32 | }; 33 | }, 34 | }); 35 | -------------------------------------------------------------------------------- /client/views/events/create-activity.html: -------------------------------------------------------------------------------- 1 | 31 | -------------------------------------------------------------------------------- /client/views/events/show/host.html: -------------------------------------------------------------------------------- 1 | 28 | -------------------------------------------------------------------------------- /client/styles/base.scss: -------------------------------------------------------------------------------- 1 | html { 2 | font-size: 0.75rem; 3 | } 4 | 5 | html, body { 6 | margin: 0; 7 | padding: 0; 8 | } 9 | 10 | h1 { 11 | font-size: 2.25rem; 12 | line-height: 30px; 13 | } 14 | 15 | h2 { 16 | font-size: 1.6rem; 17 | line-height: 25px; 18 | } 19 | 20 | h3 { 21 | font-size: 1.5rem; 22 | line-height: 20px; 23 | } 24 | 25 | h5 { 26 | font-size: 0.9rem; 27 | line-height: 15px; 28 | } 29 | 30 | button { 31 | padding: 0 0 0 3px; 32 | 33 | i { 34 | font-size: 2.5rem; 35 | vertical-align: middle; 36 | } 37 | } 38 | 39 | i { 40 | font-size: 2rem; 41 | vertical-align: bottom; 42 | &:before { 43 | vertical-align: bottom; 44 | } 45 | } 46 | 47 | a { 48 | color: $black; 49 | } 50 | 51 | input[type=text], input[type=password], input[type=email], input[type=url], input[type=time], input[type=date], input[type=datetime-local], input[type=tel], input[type=number], input[type=search], textarea.materialize-textarea { 52 | @include horizontal-padded 53 | } 54 | -------------------------------------------------------------------------------- /client/styles/modules/guest-list.scss: -------------------------------------------------------------------------------- 1 | .guest-list-header { 2 | padding: 20px 10px 10px 10px; 3 | 4 | .guest-list-header-title { 5 | margin: 0; 6 | text-align: center; 7 | margin-bottom: 20px; 8 | } 9 | } 10 | 11 | .guest-list { 12 | margin: 0; 13 | 14 | .guest-list-header { 15 | position: relative; 16 | padding: 5px 0 5px 10px; 17 | background: $lightgray; 18 | color: $white; 19 | text-transform: uppercase; 20 | 21 | &.yes { 22 | border-bottom: 2px solid $green; 23 | } 24 | 25 | &.no { 26 | border-bottom: 2px solid $red; 27 | } 28 | } 29 | 30 | .guest-list-heading { 31 | margin: 0; 32 | font-weight: bold; 33 | } 34 | 35 | .guest-list-heading-right { 36 | position: absolute; 37 | right: 11px; 38 | } 39 | } 40 | 41 | .guest-list-preview-container { 42 | padding: 20px 10px 10px 10px; 43 | 44 | .guest-list-invite-link { 45 | margin: 0 0 20px 0; 46 | text-align: center; 47 | text-decoration: underline; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /client/views/events/account.html: -------------------------------------------------------------------------------- 1 | 33 | -------------------------------------------------------------------------------- /client/views/events/index.html: -------------------------------------------------------------------------------- 1 | 31 | -------------------------------------------------------------------------------- /index.scss: -------------------------------------------------------------------------------- 1 | // This file is auto generated by the scss package 2 | // New .scss and .sass files will be automatically '@import'ed at the bottom 3 | // Existing content in the file will not be touched 4 | // When deleting a .scss or .sass file you must manually delete it from here 5 | 6 | @import "client/styles/lib/variables.scss"; 7 | @import "client/styles/lib/mixins.scss"; 8 | 9 | @import "client/styles/components/fixed-buttons.scss"; 10 | @import "client/styles/components/main-slides.scss"; 11 | 12 | @import "client/styles/base.scss"; 13 | @import "client/styles/layout.scss"; 14 | @import "client/styles/modules/list-items.scss"; 15 | @import "client/styles/modules/img-preview.scss"; 16 | @import "client/styles/modules/guest-list.scss"; 17 | @import "client/styles/modules/ea-button.scss"; 18 | @import "client/styles/modules/input-bottom.scss"; 19 | 20 | @import "client/styles/utils.scss"; 21 | @import "client/styles/modules/button.scss"; 22 | 23 | @import "client/styles/components/fixed-buttons.scss"; 24 | @import "client/styles/components/account.scss"; 25 | 26 | @import "client/styles/modules/header.scss"; 27 | @import "client/styles/modules/modal.scss"; -------------------------------------------------------------------------------- /client/views/events/_edit.html: -------------------------------------------------------------------------------- 1 | 35 | -------------------------------------------------------------------------------- /client/views/events/account.js: -------------------------------------------------------------------------------- 1 | Template.Account.onRendered(function(){ 2 | if(Router.current().params['show-account']) { 3 | $(this.firstNode).css('display', 'block').css('top', 0); 4 | } 5 | }); 6 | 7 | Template.Account.helpers({ 8 | user: function() { 9 | return Meteor.user(); 10 | }, 11 | }); 12 | 13 | Template.Account.events({ 14 | 'submit form': function(event) { 15 | stopEvent(event); 16 | 17 | var accountBtn = $('#acct-btn'); 18 | var accountPage = $('#account'); 19 | 20 | ramjet.transform(accountPage[0], accountBtn[0], { 21 | //easing: ramjet.easeInOut, 22 | duration: 250, 23 | }); 24 | 25 | accountPage.css('display', 'none'); 26 | 27 | //Meteor.users.update(Meteor.userId(), { 28 | //$set: { 29 | //'profile.fullName': event.target.fullName.value, 30 | //'profile.email': event.target.email.value, 31 | //'profile.secondaryEmail': event.target.secondaryEmail.value, 32 | //'profile.phone': event.target.phone.value 33 | //} 34 | //}); 35 | 36 | //closeAccount(); 37 | 38 | //return false; 39 | }, 40 | 41 | 'click .close': function(event){ 42 | stopEvent(event); 43 | 44 | transformClose({ 45 | from: $('#account'), 46 | to: $('#acct-btn') 47 | }); 48 | } 49 | }); 50 | -------------------------------------------------------------------------------- /controllers/events/index.js: -------------------------------------------------------------------------------- 1 | EventsIndexController = ApplicationController.extend({ 2 | waitOn: function(){ 3 | Meteor.subscribe('events'); 4 | }, 5 | 6 | data: function(){ 7 | var userId = this.params.user_id; 8 | var routeParams = this.params.query; 9 | var initialSlide; 10 | 11 | if (_.has(routeParams, 'slides')) { 12 | initialSlide = routeParams.slides; 13 | } 14 | 15 | return { 16 | upcomingEvents: function(){ 17 | return Events.find( 18 | { $or: [{ guests: { $in: [userId] } }, { user: userId }] }, 19 | { sort: { 'activities.startTime': 1 } } 20 | ); 21 | }, 22 | pastEvents: function(){ 23 | return Events.find( 24 | { $or: [{ guests: { $in: [userId] } }, { user: userId }] }, 25 | { sort: { 'activities.startTime': 1 } } 26 | ); 27 | }, 28 | rsvps: function(){ 29 | return []; 30 | }, 31 | navOptions: { 32 | centeredSlides: true, 33 | slidesPerView: 'auto', 34 | slideToClickedSlide: true, 35 | touchRatio: 0.5, 36 | initialSlide: initialSlide || 1, 37 | runCallbacksOnInit: false, 38 | }, 39 | slideOptions: { 40 | initialSlide: initialSlide || 1, 41 | runCallbacksOnInit: false, 42 | }, 43 | }; 44 | } 45 | }); 46 | -------------------------------------------------------------------------------- /client/views/events/index.js: -------------------------------------------------------------------------------- 1 | Template.EventsIndex.created = function(){ 2 | this.subscribe('events'); 3 | }; 4 | 5 | Template.EventsIndex.rendered = function(){ 6 | Meteor.setTimeout(function(){ 7 | setSlideHeight.call(this); 8 | 9 | var swiper1 = this.$('.swiper-container')[0].swiper; 10 | var swiper2 = this.$('.swiper-container')[1].swiper; 11 | 12 | swiper1.params.control = swiper2; 13 | swiper2.params.control = swiper1; 14 | }.bind(this), 1000); 15 | 16 | registerEditFormParam(); 17 | }; 18 | 19 | Template.EventsIndex.destroyed = function(){ 20 | deregisterParam('edit-form'); 21 | }; 22 | 23 | Template.EventsIndex.events({ 24 | 'click #acct-btn-wrapper': function(event) { 25 | stopEvent(event); 26 | 27 | transformOpen({ 28 | from: $('#acct-btn'), 29 | to: $('#account') 30 | }); 31 | }, 32 | 33 | 'click #add-event-btn-wrapper': function(event){ 34 | stopEvent(event); 35 | 36 | ParamManager.setParam('edit-form', true); 37 | }, 38 | }); 39 | 40 | function setSlideHeight (){ 41 | var windowHeight = $(window).height(); 42 | var navHeight = $('.navbar').height(); 43 | 44 | // take off 1 extra pixel to prevent outer template from scrolling 45 | var availableHeight = windowHeight - (navHeight + 1); 46 | 47 | this.$('.swiper-slide-main').height(availableHeight); 48 | }; 49 | -------------------------------------------------------------------------------- /client/components/swiper-slides/swiper-slides.js: -------------------------------------------------------------------------------- 1 | Template.swiperSlides.rendered = function(){ 2 | setInitialParamValue.call(this); 3 | 4 | var options = this.data.options; 5 | var defaults = { onTransitionEnd: onTransitionEnd.bind(this) }; 6 | var swiperOptions = _.extend(options, defaults); 7 | 8 | this.$('.swiper-container').swiper(swiperOptions); 9 | }; 10 | 11 | Template.swiperSlides.destroyed = function(){ 12 | var param = this.data.param; 13 | 14 | if (ParamManager.isRegistered(param)) { 15 | ParamManager.DeRegisterParam(param); 16 | } 17 | }; 18 | 19 | function onTransitionEnd (event){ 20 | var param = this.data.param; 21 | var currentSlide = event.activeIndex.toString(); 22 | 23 | if (ParamManager.getParam(param) !== currentSlide) { 24 | ParamManager.setParam(param, currentSlide); 25 | } 26 | }; 27 | 28 | function setInitialParamValue (){ 29 | var param = this.data.param; 30 | var initialSlide = this.data.options.initialSlide; 31 | var routeParams = Router.current().params.query; 32 | 33 | if (!ParamManager.isRegistered(param)) { 34 | ParamManager.RegisterParam(param, onParamChange.bind(this)); 35 | } 36 | 37 | if (routeParams[param] !== initialSlide) { 38 | ParamManager.setParam(param, initialSlide, true); 39 | } 40 | }; 41 | 42 | function onParamChange (index){ 43 | var swiper = this.$('.swiper-container')[0].swiper; 44 | 45 | if (swiper) { swiper.slideTo(index) } 46 | }; 47 | 48 | -------------------------------------------------------------------------------- /client/views/events/show/details.js: -------------------------------------------------------------------------------- 1 | Template.EventsShowDetails.created = function(){ 2 | this.state = new ReactiveDict(); 3 | }; 4 | 5 | Template.EventsShowDetails.rendered = function(){ 6 | this.state.set('mapWidth', $('.li-lrg-banner').width()); 7 | }; 8 | 9 | Template.EventsShowDetails.events({ 10 | 'click .li-lrg-touch-footer': function(event){ 11 | stopEvent(event); 12 | 13 | var touchArea = event.target; 14 | var activityCard = $(touchArea).parent(); 15 | var arrow = activityCard.find('.arrow'); 16 | 17 | // remove 40px vertical padding from total height because it opens too far 18 | var totalCardHeight = activityCard[0].scrollHeight - 40; 19 | 20 | if (!arrow.hasClass('expanded')) { 21 | arrow.addClass('expanded'); 22 | 23 | $.Velocity(arrow, { rotateZ: '180deg' }, { duration: 200 }); 24 | $.Velocity(activityCard, 25 | { height: totalCardHeight }, { duration: 200 } 26 | ); 27 | } else { 28 | arrow.removeClass('expanded'); 29 | 30 | arrow.velocity('reverse'); 31 | activityCard.velocity('reverse'); 32 | } 33 | }, 34 | }); 35 | 36 | Template.EventsShowDetails.helpers({ 37 | formattedDate: function(){ 38 | return moment(this.startTime).format('dddd MMM D'); 39 | }, 40 | 41 | formattedStartTime: function(){ 42 | return moment(this.startTime).format('h:mm a'); 43 | }, 44 | 45 | formattedEndTime: function(){ 46 | return moment(this.endTime).format('h:mm a'); 47 | }, 48 | 49 | staticMapUrl: function(address){ 50 | var mapHeight = '100'; 51 | var mapWidth = Template.instance().state.get('mapWidth'); 52 | var encodedAddress = encodeFullAddress(this.address); 53 | 54 | if (mapWidth) { 55 | return buildMapUrl(encodedAddress, { 56 | width: mapWidth, height: mapHeight 57 | }); 58 | } 59 | } 60 | }); 61 | -------------------------------------------------------------------------------- /client/views/events/show/details.html: -------------------------------------------------------------------------------- 1 | 49 | -------------------------------------------------------------------------------- /client/lib/helpers.js: -------------------------------------------------------------------------------- 1 | ParamManager = Meteor.Poetic.ParamManager; 2 | Settings = Meteor.settings; 3 | 4 | stopEvent = function(event){ 5 | event.preventDefault(); 6 | event.stopPropagation(); 7 | }; 8 | 9 | encodeFullAddress = function(address){ 10 | var addressParts = [ 11 | address.address1, address.city, 12 | address.state, address.zipCode 13 | ]; 14 | 15 | if (address.address2) { 16 | addressParts.splice(1, 0, address.address2); 17 | } 18 | 19 | return encodeURIComponent(addressParts.join(' ')); 20 | }; 21 | 22 | buildMapUrl = function(encodedAddress, dimensions){ 23 | var url = Settings.public.googleApiUrl + 24 | '?markers=' + encodedAddress + 25 | '&size=' + dimensions.width + 'x' + dimensions.height + 26 | '&key=' + Settings.public.googleApiKey; 27 | 28 | return url; 29 | }; 30 | 31 | transformOpen = function(els){ 32 | els.to.css('display', 'initial'); 33 | 34 | ramjet.transform(els.from[0], els.to[0], { 35 | duration: 250, 36 | done: function(){ 37 | els.to.css('display', 'initial') 38 | }, 39 | }); 40 | 41 | els.to.css('display', 'none'); 42 | }; 43 | 44 | transformClose = function(els){ 45 | ramjet.transform(els.from[0], els.to[0], { 46 | duration: 250 47 | }); 48 | 49 | els.from.css('display', 'none'); 50 | }; 51 | 52 | deregisterParam = function(param){ 53 | if (ParamManager.isRegistered(param)) { 54 | ParamManager.DeRegisterParam(param); 55 | } 56 | }; 57 | 58 | registerEditFormParam = function(){ 59 | if (!ParamManager.isRegistered('edit-form')) { 60 | ParamManager.RegisterParam('edit-form', function(value){ 61 | 62 | if (value) { 63 | transformOpen({ 64 | from: $('#event-edit-btn'), to: $('#events-edit') 65 | }); 66 | } else { 67 | if ($('#events-edit').css('display') !== 'none') { 68 | transformClose({ 69 | from: $('#events-edit'), to: $('#event-edit-btn') 70 | }); 71 | } 72 | } 73 | }); 74 | } 75 | }; 76 | -------------------------------------------------------------------------------- /.meteor/versions: -------------------------------------------------------------------------------- 1 | accounts-base@1.2.0 2 | accounts-password@1.1.1 3 | accounts-ui@1.1.5 4 | accounts-ui-unstyled@1.1.7 5 | aldeed:autoform@5.1.2 6 | aldeed:collection2@2.3.3 7 | aldeed:simple-schema@1.3.2 8 | aldeed:template-extension@3.4.3 9 | autoupdate@1.2.1 10 | babrahams:editable-json@0.4.3 11 | base64@1.0.3 12 | binary-heap@1.0.3 13 | blaze@2.1.2 14 | blaze-tools@1.0.3 15 | boilerplate-generator@1.0.3 16 | callback-hook@1.0.3 17 | check@1.0.5 18 | coffeescript@1.0.6 19 | dburles:collection-helpers@1.0.3 20 | dburles:google-maps@1.0.8 21 | dburles:mongo-collection-instances@0.3.3 22 | ddp@1.1.0 23 | deps@1.0.7 24 | digilord:faker@1.0.6 25 | ejson@1.0.6 26 | email@1.0.6 27 | fastclick@1.0.3 28 | fourseven:scss@2.1.1 29 | geojson-utils@1.0.3 30 | gildaspk:autoform-materialize@0.0.18 31 | gwendall:session-json@0.1.7 32 | html-tools@1.0.4 33 | htmljs@1.0.4 34 | http@1.1.0 35 | id-map@1.0.3 36 | iron:controller@1.0.7 37 | iron:core@1.0.7 38 | iron:dynamic-template@1.0.7 39 | iron:layout@1.0.7 40 | iron:location@1.0.7 41 | iron:middleware-stack@1.0.7 42 | iron:router@1.0.7 43 | iron:url@1.0.7 44 | jquery@1.11.3_2 45 | json@1.0.3 46 | lai:collection-extensions@0.1.3 47 | launch-screen@1.0.2 48 | less@1.0.14 49 | livedata@1.0.13 50 | localstorage@1.0.3 51 | logging@1.0.7 52 | materialize:materialize@0.96.1 53 | meteor@1.1.6 54 | meteor-platform@1.2.2 55 | meteortoys:toykit@0.3.4 56 | minifiers@1.1.5 57 | minimongo@1.0.8 58 | mobile-status-bar@1.0.3 59 | momentjs:moment@2.10.3 60 | mongo@1.1.0 61 | msavin:mongol@1.0.22 62 | npm-bcrypt@0.7.8_2 63 | observe-sequence@1.0.6 64 | ordered-dict@1.0.3 65 | random@1.0.3 66 | reactive-dict@1.1.0 67 | reactive-var@1.0.5 68 | reload@1.1.3 69 | retry@1.0.3 70 | routepolicy@1.0.5 71 | service-configuration@1.0.4 72 | session@1.1.0 73 | sha@1.0.3 74 | spacebars@1.0.6 75 | spacebars-compiler@1.0.6 76 | srp@1.0.3 77 | templating@1.1.1 78 | tracker@1.0.7 79 | ui@1.0.6 80 | underscore@1.0.3 81 | underscorestring:underscore.string@3.0.3 82 | url@1.0.4 83 | velocityjs:velocityjs@1.2.1 84 | webapp@1.2.0 85 | webapp-hashing@1.0.3 86 | -------------------------------------------------------------------------------- /collections/lib/schemas.js: -------------------------------------------------------------------------------- 1 | Schemas = {}; 2 | 3 | HostSchema = new SimpleSchema({ 4 | name: { 5 | type: String 6 | }, 7 | role: { 8 | type: String 9 | }, 10 | email: { 11 | type: String 12 | }, 13 | phone: { 14 | type: String 15 | } 16 | }); 17 | 18 | AddressSchema = new SimpleSchema({ 19 | locationName: { 20 | type: String 21 | }, 22 | address1: { 23 | type: String 24 | }, 25 | address2: { 26 | type: String 27 | }, 28 | city: { 29 | type: String 30 | }, 31 | state: { 32 | type: String 33 | }, 34 | zipCode: { 35 | type: String 36 | } 37 | }); 38 | 39 | ActivitySchema = new SimpleSchema({ 40 | title: { 41 | type: String 42 | }, 43 | startTime: { 44 | type: Date 45 | }, 46 | endTime: { 47 | type: Date 48 | }, 49 | timeZone: { 50 | type: String 51 | }, 52 | address: { 53 | type: AddressSchema 54 | }, 55 | attire: { 56 | type: String 57 | }, 58 | transit: { 59 | type: String 60 | }, 61 | notes: { 62 | type: String 63 | }, 64 | //media: { 65 | //?? 66 | //} 67 | }); 68 | 69 | CommentSchema = new SimpleSchema({ 70 | user: { 71 | type: String, 72 | optional: true, 73 | }, 74 | body: { 75 | type: String 76 | }, 77 | created: { 78 | type: Date 79 | }, 80 | imageUrl: { 81 | type: String, 82 | optional: true 83 | }, 84 | }); 85 | 86 | GuestSchema = new SimpleSchema({ 87 | name: { 88 | type: String 89 | }, 90 | email: { 91 | type: String 92 | }, 93 | phone: { 94 | type: String 95 | }, 96 | invited: { 97 | type: Boolean 98 | }, 99 | attending: { 100 | type: Boolean 101 | }, 102 | plusOne: { 103 | type: Boolean 104 | }, 105 | replied: { 106 | type: Boolean 107 | }, 108 | mailStatus: { 109 | type: String 110 | }, 111 | // initially a guest may not have a user account because all we may have 112 | // is a name and phone number. when the guest becomes a user this field will 113 | // be populated and the event will be added to the user's events array. 114 | guest: { 115 | type: String 116 | }, 117 | }); 118 | -------------------------------------------------------------------------------- /collections/users.js: -------------------------------------------------------------------------------- 1 | Schemas.UserProfileSchema = new SimpleSchema({ 2 | fullName: { 3 | type: String 4 | }, 5 | //email: { 6 | //type: String, 7 | //regEx: SimpleSchema.RegEx.Email 8 | //}, 9 | 10 | secondaryEmail: { 11 | type: String, 12 | regEx: SimpleSchema.RegEx.Email, 13 | optional: true 14 | }, 15 | phone: { 16 | type: String, 17 | optional: true 18 | }, 19 | events: { 20 | type: [String], 21 | defaultValue: [], 22 | }, 23 | }); 24 | 25 | Meteor.users.attachSchema(new SimpleSchema({ 26 | username: { 27 | type: String, 28 | regEx: /^[a-z0-9A-Z_]{3,15}$/, 29 | optional: true 30 | }, 31 | emails: { 32 | type: [Object], 33 | // this must be optional if you also use other login services like facebook, 34 | // but if you use only accounts-password, then it can be required 35 | optional: true 36 | }, 37 | "emails.$.address": { 38 | type: String, 39 | regEx: SimpleSchema.RegEx.Email, 40 | optional: true 41 | }, 42 | "emails.$.verified": { 43 | type: Boolean, 44 | optional: true 45 | }, 46 | createdAt: { 47 | type: Date, 48 | optional: true 49 | }, 50 | profile: { 51 | type: Schemas.UserProfileSchema, 52 | optional: true 53 | }, 54 | services: { 55 | type: Object, 56 | optional: true, 57 | blackbox: true 58 | }, 59 | // // Add `roles` to your schema if you use the meteor-roles package. 60 | // // Option 1: Object type 61 | // // If you specify that type as Object, you must also specify the 62 | // // `Roles.GLOBAL_GROUP` group whenever you add a user to a role. 63 | // // Example: 64 | // // Roles.addUsersToRoles(userId, ["admin"], Roles.GLOBAL_GROUP); 65 | // // You can't mix and match adding with and without a group since 66 | // // you will fail validation in some cases. 67 | // roles: { 68 | // type: Object, 69 | // optional: true, 70 | // blackbox: true 71 | // }, 72 | // // Option 2: [String] type 73 | // // If you are sure you will never need to use role groups, then 74 | // // you can specify [String] as the type 75 | // roles: { 76 | // type: [String], 77 | // optional: true 78 | // } 79 | })); 80 | 81 | -------------------------------------------------------------------------------- /client/views/events/show.js: -------------------------------------------------------------------------------- 1 | Template.EventsShow.created = function(){ 2 | this.subscribe('event'); 3 | }; 4 | 5 | Template.EventsShow.rendered = function(){ 6 | Meteor.setTimeout(function(){ 7 | setEventsShowHeight.call(this); 8 | setCommentFieldPosition.call(this); 9 | 10 | var swiper1 = this.$('.swiper-container')[0].swiper; 11 | var swiper2 = this.$('.swiper-container')[1].swiper; 12 | 13 | swiper1.params.control = swiper2; 14 | swiper2.params.control = swiper1; 15 | }.bind(this), 1000); 16 | 17 | registerEditFormParam(); 18 | }; 19 | 20 | Template.EventsShow.destroyed = function(){ 21 | deregisterParam('edit-form'); 22 | }; 23 | 24 | Template.EventsShow.helpers({ 25 | isPlanner: function(){ 26 | if (this.currentEvent()) { 27 | return Router.current().params.user_id === this.currentEvent().user; 28 | } 29 | }, 30 | }); 31 | 32 | Template.EventsShow.events({ 33 | 'submit #new-post': function(event){ 34 | stopEvent(event); 35 | 36 | var routeParams = Router.current().params; 37 | var eventId = routeParams.event_id; 38 | 39 | var comment = { 40 | user: routeParams.user_id, 41 | body: event.target.comment.value, 42 | created: new Date(), 43 | }; 44 | 45 | Meteor.call('addComment', eventId, comment); 46 | scrollToBottom(); 47 | 48 | event.target.comment.value = ''; 49 | }, 50 | 51 | 'click #back-arrow': function(){ 52 | stopEvent(event); 53 | var userId = Router.current().params.user_id; 54 | 55 | Router.go('events.index', { user_id: userId }); 56 | }, 57 | 58 | 'click #edit-event-btn-wrapper': function(event){ 59 | stopEvent(event); 60 | 61 | ParamManager.setParam('edit-form', true); 62 | }, 63 | }); 64 | 65 | function setEventsShowHeight (){ 66 | var windowHeight = $(window).height(); 67 | var occupied = ( 68 | $('.img-preview-inner').height() + ($('.navbar').height() + 1) 69 | ); 70 | var availableHeight = windowHeight - occupied; 71 | 72 | this.$('.swiper-slide-main').height(availableHeight); 73 | }; 74 | 75 | function setCommentFieldPosition (){ 76 | var windowWidth = $(window).width(); 77 | var leftOffset = windowWidth * 2; 78 | 79 | this.$('.form-bottom').css({ left: leftOffset }); 80 | }; 81 | 82 | function scrollToBottom (){ 83 | var newPost = $('.wall-slide li').last(); 84 | 85 | newPost.velocity('scroll', { container: $('.wall-slide') }); 86 | }; 87 | 88 | 89 | -------------------------------------------------------------------------------- /client/views/events/show.html: -------------------------------------------------------------------------------- 1 | 78 | -------------------------------------------------------------------------------- /client/views/events/show/guest-list.html: -------------------------------------------------------------------------------- 1 | 96 | -------------------------------------------------------------------------------- /client/styles/modules/list-items.scss: -------------------------------------------------------------------------------- 1 | .li-xl { 2 | position: relative; 3 | width: 100%; 4 | height: 275px; 5 | 6 | .li-xl-footer { 7 | position: absolute; 8 | bottom: 10px; 9 | color: $white; 10 | padding: 0 10px 0 10px; 11 | } 12 | 13 | .li-xl-footer-title { 14 | margin: 0 0 10px 0; 15 | } 16 | 17 | .li-xl-footer-subtitle { 18 | margin: 0; 19 | } 20 | } 21 | 22 | .li-lrg { 23 | position: relative; 24 | padding: 20px 0 20px 0; 25 | border-bottom: 1px solid $lightgray; 26 | overflow-y: hidden; 27 | height: 124px; 28 | 29 | .li-lrg-preview { 30 | padding: 0 10px 20px 10px; 31 | } 32 | 33 | .li-lrg-preview-heading { 34 | font-weight: bold; 35 | margin: 0 0 5px 0; 36 | } 37 | 38 | .li-lrg-preview-subheading { 39 | margin: 0 0 20px 0; 40 | } 41 | 42 | .li-lrg-preview-offset { 43 | margin-left: 10px; 44 | color: $gray; 45 | } 46 | 47 | .li-lrg-preview-items { 48 | margin-right: 25px; 49 | 50 | .li-lrg-preview-item { 51 | margin: 0; 52 | } 53 | } 54 | 55 | .li-lrg-preview-bottom-right { 56 | position: absolute; 57 | right: 10px; 58 | top: 118px; 59 | } 60 | 61 | .li-lrg-touch-footer { 62 | position: absolute; 63 | top: 110px; 64 | height: 75px; 65 | width: 100%; 66 | z-index: 9; 67 | } 68 | 69 | .li-lrg-banner { 70 | border-top: 1px solid lightgray; 71 | border-bottom: 1px solid lightgray; 72 | height: 100px; 73 | width: 100%; 74 | } 75 | 76 | .li-lrg-more-details { 77 | padding: 20px 10px 0 10px; 78 | 79 | .detail { 80 | margin: 0 0 20px 0; 81 | 82 | &:last-child { 83 | margin-bottom: 0; 84 | } 85 | } 86 | 87 | .detail-heading { 88 | margin: 0 0 5px 0; 89 | font-weight: bold; 90 | color: $gray; 91 | text-transform: uppercase; 92 | } 93 | 94 | .detail-text { 95 | margin: 0; 96 | } 97 | } 98 | } 99 | 100 | .li-med { 101 | padding: 20px 10px 20px 10px; 102 | border-bottom: 1px solid lightgray; 103 | 104 | .li-med-heading { 105 | margin: 0; 106 | } 107 | 108 | .li-med-subheading { 109 | text-transform: capitalize; 110 | } 111 | 112 | .li-med-line-item { 113 | position: relative; 114 | 115 | .li-med-line-item-right { 116 | position: absolute; 117 | right: 0; 118 | top: -11px; 119 | } 120 | } 121 | 122 | .li-med-line-item-text { 123 | margin-right: 60px; 124 | 125 | &.no-bottom-margin { 126 | margin-bottom: 0; 127 | } 128 | } 129 | } 130 | 131 | .li-sml { 132 | padding: 10px 10px 0 10px; 133 | 134 | .li-sml-heading { 135 | font-weight: bold; 136 | text-transform: uppercase; 137 | margin-bottom: 5px; 138 | 139 | .li-sml-subheading { 140 | font-weight: normal; 141 | text-transform: initial; 142 | color: $gray; 143 | margin-left: 5px; 144 | } 145 | } 146 | 147 | .li-sml-text { 148 | margin: 0; 149 | } 150 | 151 | &:last-child { 152 | padding-bottom: 62px; 153 | } 154 | } 155 | 156 | .li-tiny { 157 | position: relative; 158 | padding-left: 10px; 159 | border-bottom: 1px solid $lightgray; 160 | 161 | .li-tiny-text { 162 | position: relative; 163 | margin: 10px 0 10px 0; 164 | } 165 | 166 | .li-tiny-right { 167 | position: absolute; 168 | color: $lightgray; 169 | } 170 | 171 | .right-text { 172 | right: 12px; 173 | text-transform: capitalize; 174 | } 175 | 176 | .right-icon { 177 | right: 5px; 178 | top: -9px; 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /client/lib/formaldehyde.js: -------------------------------------------------------------------------------- 1 | if(Meteor.isClient){ 2 | // Check if the Meteor.Poetic object is already populated... if we use this as a normal namespace 3 | // Some other module may have already declared it.. if not then it needs to be instantiated as an empty 4 | // object 5 | if (typeof Meteor.Poetic === "undefined") { 6 | Meteor.Poetic = {}; 7 | } 8 | else{ 9 | console.log("Meteor.Poetic already exhists"); 10 | } 11 | // Register the ParamManager under the Meteor.Poetic namespace 12 | Meteor.Poetic.ParamManager = (function(){ 13 | 14 | // Interface is the object that will be returned.. any function or variable added to this will be available 15 | // in the global scope Meteor.Poetic.ParamManager.[methodName] 16 | 17 | var Interface = {}; 18 | 19 | // this will be an array of all param objects. This is private and should remain so. 20 | // A param object will follow the structure such as 21 | // { 22 | // param: TheParameterName, 23 | // value: TheValueOfThisParam, 24 | // callback: TheFunctionToCallIfThisValueChanges 25 | // } 26 | 27 | var Params = []; 28 | 29 | // RegisterParamFunction is the method to register your parameter with a callback function. 30 | // based on the model above for params objects your call to this should look like 31 | // 32 | // Meteor.Poetic.ParamManager.RegisterParamFunction('user', function(value){console.log(value);}); 33 | // 34 | // this will successfully console.log the new value when the value of user changes. 35 | // your function will most likely impliment some logic based on the value passed back. 36 | 37 | Interface.RegisterParam = function(paramName, callback){ 38 | // TODO add a test to check if paramName or callback are null and throw and error 39 | Params[Params.length] = { 40 | param: paramName, // save the parameter name passed 41 | value: null, 42 | callback: function(){ 43 | callback(getParameterByName(paramName)); 44 | } // the user can maintain scope without using function.bind() 45 | } 46 | } 47 | 48 | // This allows you to deregister a param callback by paramname 49 | Interface.DeRegisterParam = function(paramName){ 50 | // iterate through the params object and find a match to the paramName passed then remove it 51 | for(var i = 0; i < Params.length; i++){ 52 | if(Params[i].param === paramName){ 53 | Params.splice(i, 1); 54 | i = Params.length; 55 | } 56 | } 57 | } 58 | 59 | Interface.isRegistered = function(paramName){ 60 | return Params.some(function(param){ return param.param === paramName }); 61 | } 62 | 63 | // build URL is the interface set to trigger any callbacks whose state may have changed from the current set call 64 | // This is orientated around a direct javascript call. A template (html) linking method should be added that calls based 65 | // on multiple param values being changed in one link. 66 | 67 | function buildUrl(param, value, replaceState){ 68 | var path = location.href.split('?')[0]; // get the URL currently in state 69 | var paramStrings = []; 70 | 71 | Params.forEach(function(urlParam){ 72 | if (param === urlParam.param) { 73 | if (value !== null) { 74 | paramStrings.push(urlParam.param + '=' + value); 75 | } 76 | } else { 77 | if (urlParam.value) { 78 | paramStrings.push(urlParam.param + '=' + urlParam.value); 79 | } 80 | } 81 | }); 82 | 83 | var queryString = '?' + paramStrings.join('&'); 84 | 85 | if (replaceState) { 86 | history.replaceState({}, "OptionalTitle", path + queryString); // store a new state in memory with the built url 87 | } else { 88 | history.pushState({}, "OptionalTitle", path + queryString); // store a new state in memory with the built url 89 | } 90 | } 91 | 92 | // this function needs to push the state of the window, build a new URL based on values passed and then trigger any 93 | // callback functions that are rigistered to the change in url replace is a third optional arguement to update params 94 | // but not push the state forward. 95 | Interface.setParam = function(param, value, replace){ 96 | buildUrl(param, value, replace); // pass the values to build a new url 97 | document.dispatchEvent(ChangedURL); // url has changed fire an event to trigger callbacks 98 | } 99 | 100 | // allow the user an easy interface to find or poll for the value of param. This shouldn't ever be needed if the que 101 | // Set and Link methods are used properly, but it does allow for restful principles and easier debugging. 102 | Interface.getParam = function(param){ 103 | return getParameterByName(param); 104 | } 105 | 106 | // ChangedURL is the event that will be listened to for url changes. Because of this you should always use 107 | // the paramManager to update the parameter url or ELSE your function will NOT be called. 108 | 109 | var ChangedURL = new Event('urlchange'); 110 | 111 | // executeCallbacks will iterate through every object in the params array and execute its callback. 112 | // It will check its current value and the value in the URL if these values are the same then its callback 113 | // won't be called. If the values are different then the url was Changed so then it will run its callback and then 114 | // update its internal value to the new value 115 | function executeCallbacks(){ 116 | Params.forEach(function(curParam, i){ 117 | var curVal = getParameterByName(curParam.param); 118 | if(curParam.value !== curVal){ 119 | curParam.callback(curVal); 120 | curParam.value = curVal; 121 | } 122 | }); 123 | } 124 | 125 | // when the document is ready register our callbacks function on the urlchange event. 126 | window.onload = function(){ 127 | document.addEventListener('urlchange', function(event){ 128 | executeCallbacks(); 129 | }, false); 130 | // register forward backwards and directly typed urls to fire this event. 131 | window.onpopstate = function(){ 132 | document.dispatchEvent(ChangedURL); 133 | } 134 | document.dispatchEvent(ChangedURL); 135 | }; 136 | 137 | // simple function that returns the value of a query param by name from the current url. 138 | function getParameterByName(name) { 139 | name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]"); 140 | var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"), 141 | results = regex.exec(location.search); 142 | return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " ")); 143 | } 144 | return Interface; 145 | })(); 146 | } 147 | 148 | -------------------------------------------------------------------------------- /server/seeds.js: -------------------------------------------------------------------------------- 1 | Meteor.startup(function(){ 2 | Meteor.users.remove({}) 3 | Events.remove({}) 4 | 5 | var user1, user2, user3, user4, user5, user6; 6 | var events = []; 7 | 8 | if (!Meteor.users.find().count()) { 9 | user1 = Accounts.createUser({ 10 | email: faker.internet.email(), 11 | password: faker.internet.password(), 12 | profile: { 13 | fullName: faker.name.findName(), 14 | secondaryEmail: faker.internet.email(), 15 | phone: faker.phone.phoneNumber(), 16 | }, 17 | }); 18 | 19 | user2 = Accounts.createUser({ 20 | email: faker.internet.email(), 21 | password: faker.internet.password(), 22 | profile: { 23 | fullName: faker.name.findName(), 24 | secondaryEmail: faker.internet.email(), 25 | phone: faker.phone.phoneNumber(), 26 | }, 27 | }); 28 | 29 | user3 = Accounts.createUser({ 30 | email: faker.internet.email(), 31 | password: faker.internet.password(), 32 | profile: { 33 | fullName: faker.name.findName(), 34 | secondaryEmail: faker.internet.email(), 35 | phone: faker.phone.phoneNumber(), 36 | }, 37 | }); 38 | 39 | user4 = Accounts.createUser({ 40 | email: faker.internet.email(), 41 | password: faker.internet.password(), 42 | profile: { 43 | fullName: faker.name.findName(), 44 | secondaryEmail: faker.internet.email(), 45 | phone: faker.phone.phoneNumber(), 46 | }, 47 | }); 48 | 49 | user5 = Accounts.createUser({ 50 | email: faker.internet.email(), 51 | password: faker.internet.password(), 52 | profile: { 53 | fullName: faker.name.findName(), 54 | secondaryEmail: faker.internet.email(), 55 | phone: faker.phone.phoneNumber(), 56 | }, 57 | }); 58 | 59 | user6 = Accounts.createUser({ 60 | email: faker.internet.email(), 61 | password: faker.internet.password(), 62 | profile: { 63 | fullName: faker.name.findName(), 64 | secondaryEmail: faker.internet.email(), 65 | phone: faker.phone.phoneNumber(), 66 | }, 67 | }); 68 | } 69 | 70 | if (!Events.find().count()) { 71 | for (var i = 0; i < 10; i++) { 72 | events[i] = Events.insert({ 73 | user: user1, 74 | coverPhoto: faker.image.image(), 75 | title: faker.name.findName(), 76 | description: faker.lorem.sentences(), 77 | isPlusOne: true, 78 | 79 | hosts: [{ 80 | name: faker.name.findName(), 81 | role: faker.hacker.adjective(), 82 | email: faker.internet.email(), 83 | phone: faker.phone.phoneNumber(), 84 | }, { 85 | name: faker.name.findName(), 86 | role: faker.hacker.adjective(), 87 | email: faker.internet.email(), 88 | phone: faker.phone.phoneNumber(), 89 | }], 90 | 91 | activities: [{ 92 | title: faker.name.findName(), 93 | startTime: faker.date.past(), 94 | endTime: faker.date.future(), 95 | timeZone: 'central', 96 | 97 | address: { 98 | locationName: faker.company.companyName(), 99 | address1: faker.address.streetAddress(), 100 | address2: faker.address.secondaryAddress(), 101 | city: faker.address.city(), 102 | state: faker.address.state(), 103 | zipCode: faker.address.zipCode(), 104 | }, 105 | 106 | attire: faker.lorem.sentence(), 107 | transit: faker.lorem.sentence(), 108 | notes: faker.lorem.sentence(), 109 | }, { 110 | title: faker.name.findName(), 111 | startTime: faker.date.past(), 112 | endTime: faker.date.future(), 113 | timeZone: 'central', 114 | 115 | address: { 116 | locationName: faker.company.companyName(), 117 | address1: faker.address.streetAddress(), 118 | address2: faker.address.secondaryAddress(), 119 | city: faker.address.city(), 120 | state: faker.address.state(), 121 | zipCode: faker.address.zipCode(), 122 | }, 123 | 124 | attire: faker.lorem.sentence(), 125 | transit: faker.lorem.sentence(), 126 | notes: faker.lorem.sentence(), 127 | }, { 128 | title: faker.name.findName(), 129 | startTime: faker.date.past(), 130 | endTime: faker.date.future(), 131 | timeZone: 'central', 132 | 133 | address: { 134 | locationName: faker.company.companyName(), 135 | address1: faker.address.streetAddress(), 136 | address2: faker.address.secondaryAddress(), 137 | city: faker.address.city(), 138 | state: faker.address.state(), 139 | zipCode: faker.address.zipCode(), 140 | }, 141 | 142 | attire: faker.lorem.sentence(), 143 | transit: faker.lorem.sentence(), 144 | notes: faker.lorem.sentence(), 145 | }], 146 | 147 | comments: [{ 148 | user: user1, 149 | body: faker.lorem.sentence(), 150 | imageUrl: faker.image.imageUrl(), 151 | created: new Date(), 152 | }, { 153 | user: user2, 154 | body: faker.lorem.sentence(), 155 | imageUrl: faker.image.imageUrl(), 156 | created: new Date(), 157 | }, { 158 | user: user1, 159 | body: faker.lorem.sentence(), 160 | imageUrl: faker.image.imageUrl(), 161 | created: new Date(), 162 | }, { 163 | user: user3, 164 | body: faker.lorem.sentence(), 165 | imageUrl: faker.image.imageUrl(), 166 | created: new Date(), 167 | }], 168 | }); 169 | }; 170 | } 171 | 172 | var auser2, auser3, auser4, auser5, auser6; 173 | 174 | if (Events.find().count()) { 175 | auser2 = Meteor.users.findOne({ _id: user2 }) 176 | auser3 = Meteor.users.findOne({ _id: user3 }) 177 | auser4 = Meteor.users.findOne({ _id: user4 }) 178 | auser5 = Meteor.users.findOne({ _id: user5 }) 179 | auser6 = Meteor.users.findOne({ _id: user6 }) 180 | } 181 | 182 | if (Events.find().count()) { 183 | Events.update({_id: events[0]}, {$push: {guests: {$each: [{ 184 | name: auser2.profile.fullName, 185 | email: auser2.emails[0].address, 186 | phone: auser2.profile.phone, 187 | invited: true, 188 | attending: true, 189 | plusOne: true, 190 | replied: true, 191 | mailStatus: 'opened', 192 | guest: user2, 193 | }, { 194 | name: auser3.profile.fullName, 195 | email: auser3.emails[0].address, 196 | phone: auser3.profile.phone, 197 | invited: false, 198 | attending: false, 199 | plusOne: false, 200 | replied: false, 201 | mailStatus: 'bounced', 202 | guest: user3, 203 | }, { 204 | name: auser5.profile.fullName, 205 | email: auser5.emails[0].address, 206 | phone: auser5.profile.phone, 207 | invited: true, 208 | attending: true, 209 | plusOne: true, 210 | replied: true, 211 | mailStatus: 'opened', 212 | guest: user5, 213 | }, { 214 | name: auser4.profile.fullName, 215 | email: auser4.emails[0].address, 216 | phone: auser4.profile.phone, 217 | invited: false, 218 | attending: false, 219 | plusOne: false, 220 | replied: false, 221 | mailStatus: 'bounced', 222 | guest: user4, 223 | }]}}}); 224 | 225 | Events.update({_id: events[1]}, {$push: {guests: {$each: [{ 226 | name: auser4.profile.fullName, 227 | email: auser4.emails[0].address, 228 | phone: auser4.profile.phone, 229 | invited: false, 230 | attending: false, 231 | plusOne: false, 232 | replied: false, 233 | mailStatus: 'bounced', 234 | guest: user4, 235 | }, { 236 | name: auser5.profile.fullName, 237 | email: auser5.emails[0].address, 238 | phone: auser5.profile.phone, 239 | invited: true, 240 | attending: true, 241 | plusOne: true, 242 | replied: true, 243 | mailStatus: 'opened', 244 | guest: user5, 245 | }]}}}) 246 | } 247 | 248 | if (Meteor.users.find().count() && Events.find().count()) { 249 | Meteor.users.update({ _id: user2 }, { $push: { 'profile.events': { $each: [events[0]] } } }) 250 | Meteor.users.update({ _id: user3 }, { $push: { 'profile.events': { $each: [events[0]] } } }) 251 | Meteor.users.update({ _id: user4 }, { $push: { 'profile.events': { $each: [events[0], events[1]] } } }) 252 | Meteor.users.update({ _id: user5 }, { $push: { 'profile.events': { $each: [events[0], events[1]] } } }) 253 | } 254 | }); 255 | -------------------------------------------------------------------------------- /client/swiper/swiper.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Swiper 3.0.6 3 | * Most modern mobile touch slider and framework with hardware accelerated transitions 4 | * 5 | * http://www.idangero.us/swiper/ 6 | * 7 | * Copyright 2015, Vladimir Kharlampidi 8 | * The iDangero.us 9 | * http://www.idangero.us/ 10 | * 11 | * Licensed under MIT 12 | * 13 | * Released on: March 27, 2015 14 | */ 15 | .swiper-container { 16 | margin: 0 auto; 17 | position: relative; 18 | overflow: hidden; 19 | /* Fix of Webkit flickering */ 20 | z-index: 1; 21 | } 22 | .swiper-container-no-flexbox .swiper-slide { 23 | float: left; 24 | } 25 | .swiper-container-vertical > .swiper-wrapper { 26 | -webkit-box-orient: vertical; 27 | -moz-box-orient: vertical; 28 | -ms-flex-direction: column; 29 | -webkit-flex-direction: column; 30 | flex-direction: column; 31 | } 32 | .swiper-wrapper { 33 | position: relative; 34 | width: 100%; 35 | height: 100%; 36 | z-index: 1; 37 | display: -webkit-box; 38 | display: -moz-box; 39 | display: -ms-flexbox; 40 | display: -webkit-flex; 41 | display: flex; 42 | -webkit-transform-style: preserve-3d; 43 | -moz-transform-style: preserve-3d; 44 | -ms-transform-style: preserve-3d; 45 | transform-style: preserve-3d; 46 | -webkit-transition-property: -webkit-transform; 47 | -moz-transition-property: -moz-transform; 48 | -o-transition-property: -o-transform; 49 | -ms-transition-property: -ms-transform; 50 | transition-property: transform; 51 | -webkit-box-sizing: content-box; 52 | -moz-box-sizing: content-box; 53 | box-sizing: content-box; 54 | } 55 | .swiper-container-android .swiper-slide, 56 | .swiper-wrapper { 57 | -webkit-transform: translate3d(0px, 0, 0); 58 | -moz-transform: translate3d(0px, 0, 0); 59 | -o-transform: translate(0px, 0px); 60 | -ms-transform: translate3d(0px, 0, 0); 61 | transform: translate3d(0px, 0, 0); 62 | } 63 | .swiper-container-multirow > .swiper-wrapper { 64 | -webkit-box-lines: multiple; 65 | -moz-box-lines: multiple; 66 | -ms-fles-wrap: wrap; 67 | -webkit-flex-wrap: wrap; 68 | flex-wrap: wrap; 69 | } 70 | .swiper-container-free-mode > .swiper-wrapper { 71 | -webkit-transition-timing-function: ease-out; 72 | -moz-transition-timing-function: ease-out; 73 | -ms-transition-timing-function: ease-out; 74 | -o-transition-timing-function: ease-out; 75 | transition-timing-function: ease-out; 76 | margin: 0 auto; 77 | } 78 | .swiper-slide { 79 | -webkit-transform-style: preserve-3d; 80 | -moz-transform-style: preserve-3d; 81 | -ms-transform-style: preserve-3d; 82 | transform-style: preserve-3d; 83 | -webkit-flex-shrink: 0; 84 | -ms-flex: 0 0 auto; 85 | flex-shrink: 0; 86 | width: 100%; 87 | height: 100%; 88 | position: relative; 89 | } 90 | /* a11y */ 91 | .swiper-container .swiper-notification { 92 | position: absolute; 93 | left: 0; 94 | top: 0; 95 | pointer-events: none; 96 | opacity: 0; 97 | z-index: -1000; 98 | } 99 | /* IE10 Windows Phone 8 Fixes */ 100 | .swiper-wp8-horizontal { 101 | -ms-touch-action: pan-y; 102 | touch-action: pan-y; 103 | } 104 | .swiper-wp8-vertical { 105 | -ms-touch-action: pan-x; 106 | touch-action: pan-x; 107 | } 108 | /* Arrows */ 109 | .swiper-button-prev, 110 | .swiper-button-next { 111 | position: absolute; 112 | top: 50%; 113 | width: 27px; 114 | height: 44px; 115 | margin-top: -22px; 116 | z-index: 10; 117 | cursor: pointer; 118 | -moz-background-size: 27px 44px; 119 | -webkit-background-size: 27px 44px; 120 | background-size: 27px 44px; 121 | background-position: center; 122 | background-repeat: no-repeat; 123 | } 124 | .swiper-button-prev.swiper-button-disabled, 125 | .swiper-button-next.swiper-button-disabled { 126 | opacity: 0.35; 127 | cursor: auto; 128 | pointer-events: none; 129 | } 130 | .swiper-button-prev, 131 | .swiper-container-rtl .swiper-button-next { 132 | background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20viewBox%3D'0%200%2027%2044'%3E%3Cpath%20d%3D'M0%2C22L22%2C0l2.1%2C2.1L4.2%2C22l19.9%2C19.9L22%2C44L0%2C22L0%2C22L0%2C22z'%20fill%3D'%23007aff'%2F%3E%3C%2Fsvg%3E"); 133 | left: 10px; 134 | right: auto; 135 | } 136 | .swiper-button-prev.swiper-button-black, 137 | .swiper-container-rtl .swiper-button-next.swiper-button-black { 138 | background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20viewBox%3D'0%200%2027%2044'%3E%3Cpath%20d%3D'M0%2C22L22%2C0l2.1%2C2.1L4.2%2C22l19.9%2C19.9L22%2C44L0%2C22L0%2C22L0%2C22z'%20fill%3D'%23000000'%2F%3E%3C%2Fsvg%3E"); 139 | } 140 | .swiper-button-prev.swiper-button-white, 141 | .swiper-container-rtl .swiper-button-next.swiper-button-white { 142 | background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20viewBox%3D'0%200%2027%2044'%3E%3Cpath%20d%3D'M0%2C22L22%2C0l2.1%2C2.1L4.2%2C22l19.9%2C19.9L22%2C44L0%2C22L0%2C22L0%2C22z'%20fill%3D'%23ffffff'%2F%3E%3C%2Fsvg%3E"); 143 | } 144 | .swiper-button-next, 145 | .swiper-container-rtl .swiper-button-prev { 146 | background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20viewBox%3D'0%200%2027%2044'%3E%3Cpath%20d%3D'M27%2C22L27%2C22L5%2C44l-2.1-2.1L22.8%2C22L2.9%2C2.1L5%2C0L27%2C22L27%2C22z'%20fill%3D'%23007aff'%2F%3E%3C%2Fsvg%3E"); 147 | right: 10px; 148 | left: auto; 149 | } 150 | .swiper-button-next.swiper-button-black, 151 | .swiper-container-rtl .swiper-button-prev.swiper-button-black { 152 | background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20viewBox%3D'0%200%2027%2044'%3E%3Cpath%20d%3D'M27%2C22L27%2C22L5%2C44l-2.1-2.1L22.8%2C22L2.9%2C2.1L5%2C0L27%2C22L27%2C22z'%20fill%3D'%23000000'%2F%3E%3C%2Fsvg%3E"); 153 | } 154 | .swiper-button-next.swiper-button-white, 155 | .swiper-container-rtl .swiper-button-prev.swiper-button-white { 156 | background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20viewBox%3D'0%200%2027%2044'%3E%3Cpath%20d%3D'M27%2C22L27%2C22L5%2C44l-2.1-2.1L22.8%2C22L2.9%2C2.1L5%2C0L27%2C22L27%2C22z'%20fill%3D'%23ffffff'%2F%3E%3C%2Fsvg%3E"); 157 | } 158 | /* Pagination Styles */ 159 | .swiper-pagination { 160 | position: absolute; 161 | text-align: center; 162 | -webkit-transition: 300ms; 163 | -moz-transition: 300ms; 164 | -o-transition: 300ms; 165 | transition: 300ms; 166 | -webkit-transform: translate3d(0, 0, 0); 167 | -ms-transform: translate3d(0, 0, 0); 168 | -o-transform: translate3d(0, 0, 0); 169 | transform: translate3d(0, 0, 0); 170 | z-index: 10; 171 | } 172 | .swiper-pagination.swiper-pagination-hidden { 173 | opacity: 0; 174 | } 175 | .swiper-pagination-bullet { 176 | width: 8px; 177 | height: 8px; 178 | display: inline-block; 179 | border-radius: 100%; 180 | background: #000; 181 | opacity: 0.2; 182 | } 183 | .swiper-pagination-clickable .swiper-pagination-bullet { 184 | cursor: pointer; 185 | } 186 | .swiper-pagination-white .swiper-pagination-bullet { 187 | background: #fff; 188 | } 189 | .swiper-pagination-bullet-active { 190 | opacity: 1; 191 | background: #007aff; 192 | } 193 | .swiper-pagination-white .swiper-pagination-bullet-active { 194 | background: #fff; 195 | } 196 | .swiper-pagination-black .swiper-pagination-bullet-active { 197 | background: #000; 198 | } 199 | .swiper-container-vertical > .swiper-pagination { 200 | right: 10px; 201 | top: 50%; 202 | -webkit-transform: translate3d(0px, -50%, 0); 203 | -moz-transform: translate3d(0px, -50%, 0); 204 | -o-transform: translate(0px, -50%); 205 | -ms-transform: translate3d(0px, -50%, 0); 206 | transform: translate3d(0px, -50%, 0); 207 | } 208 | .swiper-container-vertical > .swiper-pagination .swiper-pagination-bullet { 209 | margin: 5px 0; 210 | display: block; 211 | } 212 | .swiper-container-horizontal > .swiper-pagination { 213 | bottom: 10px; 214 | left: 0; 215 | width: 100%; 216 | } 217 | .swiper-container-horizontal > .swiper-pagination .swiper-pagination-bullet { 218 | margin: 0 5px; 219 | } 220 | /* 3D Container */ 221 | .swiper-container-3d { 222 | -webkit-perspective: 1200px; 223 | -moz-perspective: 1200px; 224 | -o-perspective: 1200px; 225 | perspective: 1200px; 226 | } 227 | .swiper-container-3d .swiper-wrapper, 228 | .swiper-container-3d .swiper-slide, 229 | .swiper-container-3d .swiper-slide-shadow-left, 230 | .swiper-container-3d .swiper-slide-shadow-right, 231 | .swiper-container-3d .swiper-slide-shadow-top, 232 | .swiper-container-3d .swiper-slide-shadow-bottom, 233 | .swiper-container-3d .swiper-cube-shadow { 234 | -webkit-transform-style: preserve-3d; 235 | -moz-transform-style: preserve-3d; 236 | -ms-transform-style: preserve-3d; 237 | transform-style: preserve-3d; 238 | } 239 | .swiper-container-3d .swiper-slide-shadow-left, 240 | .swiper-container-3d .swiper-slide-shadow-right, 241 | .swiper-container-3d .swiper-slide-shadow-top, 242 | .swiper-container-3d .swiper-slide-shadow-bottom { 243 | position: absolute; 244 | left: 0; 245 | top: 0; 246 | width: 100%; 247 | height: 100%; 248 | pointer-events: none; 249 | z-index: 10; 250 | } 251 | .swiper-container-3d .swiper-slide-shadow-left { 252 | background-image: -webkit-gradient(linear, left top, right top, from(rgba(0, 0, 0, 0.5)), to(rgba(0, 0, 0, 0))); 253 | /* Safari 4+, Chrome */ 254 | background-image: -webkit-linear-gradient(right, rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0)); 255 | /* Chrome 10+, Safari 5.1+, iOS 5+ */ 256 | background-image: -moz-linear-gradient(right, rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0)); 257 | /* Firefox 3.6-15 */ 258 | background-image: -o-linear-gradient(right, rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0)); 259 | /* Opera 11.10-12.00 */ 260 | background-image: linear-gradient(to left, rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0)); 261 | /* Firefox 16+, IE10, Opera 12.50+ */ 262 | } 263 | .swiper-container-3d .swiper-slide-shadow-right { 264 | background-image: -webkit-gradient(linear, right top, left top, from(rgba(0, 0, 0, 0.5)), to(rgba(0, 0, 0, 0))); 265 | /* Safari 4+, Chrome */ 266 | background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0)); 267 | /* Chrome 10+, Safari 5.1+, iOS 5+ */ 268 | background-image: -moz-linear-gradient(left, rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0)); 269 | /* Firefox 3.6-15 */ 270 | background-image: -o-linear-gradient(left, rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0)); 271 | /* Opera 11.10-12.00 */ 272 | background-image: linear-gradient(to right, rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0)); 273 | /* Firefox 16+, IE10, Opera 12.50+ */ 274 | } 275 | .swiper-container-3d .swiper-slide-shadow-top { 276 | background-image: -webkit-gradient(linear, left top, left bottom, from(rgba(0, 0, 0, 0.5)), to(rgba(0, 0, 0, 0))); 277 | /* Safari 4+, Chrome */ 278 | background-image: -webkit-linear-gradient(bottom, rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0)); 279 | /* Chrome 10+, Safari 5.1+, iOS 5+ */ 280 | background-image: -moz-linear-gradient(bottom, rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0)); 281 | /* Firefox 3.6-15 */ 282 | background-image: -o-linear-gradient(bottom, rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0)); 283 | /* Opera 11.10-12.00 */ 284 | background-image: linear-gradient(to top, rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0)); 285 | /* Firefox 16+, IE10, Opera 12.50+ */ 286 | } 287 | .swiper-container-3d .swiper-slide-shadow-bottom { 288 | background-image: -webkit-gradient(linear, left bottom, left top, from(rgba(0, 0, 0, 0.5)), to(rgba(0, 0, 0, 0))); 289 | /* Safari 4+, Chrome */ 290 | background-image: -webkit-linear-gradient(top, rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0)); 291 | /* Chrome 10+, Safari 5.1+, iOS 5+ */ 292 | background-image: -moz-linear-gradient(top, rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0)); 293 | /* Firefox 3.6-15 */ 294 | background-image: -o-linear-gradient(top, rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0)); 295 | /* Opera 11.10-12.00 */ 296 | background-image: linear-gradient(to bottom, rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0)); 297 | /* Firefox 16+, IE10, Opera 12.50+ */ 298 | } 299 | /* Coverflow */ 300 | .swiper-container-coverflow .swiper-wrapper { 301 | /* Windows 8 IE 10 fix */ 302 | -ms-perspective: 1200px; 303 | } 304 | /* Fade */ 305 | .swiper-container-fade.swiper-container-free-mode .swiper-slide { 306 | -webkit-transition-timing-function: ease-out; 307 | -moz-transition-timing-function: ease-out; 308 | -ms-transition-timing-function: ease-out; 309 | -o-transition-timing-function: ease-out; 310 | transition-timing-function: ease-out; 311 | } 312 | .swiper-container-fade .swiper-slide { 313 | pointer-events: none; 314 | } 315 | .swiper-container-fade .swiper-slide-active { 316 | pointer-events: auto; 317 | } 318 | /* Cube */ 319 | .swiper-container-cube { 320 | overflow: visible; 321 | } 322 | .swiper-container-cube .swiper-slide { 323 | pointer-events: none; 324 | visibility: hidden; 325 | -webkit-transform-origin: 0 0; 326 | -moz-transform-origin: 0 0; 327 | -ms-transform-origin: 0 0; 328 | transform-origin: 0 0; 329 | -webkit-backface-visibility: hidden; 330 | -moz-backface-visibility: hidden; 331 | -ms-backface-visibility: hidden; 332 | backface-visibility: hidden; 333 | width: 100%; 334 | height: 100%; 335 | } 336 | .swiper-container-cube.swiper-container-rtl .swiper-slide { 337 | -webkit-transform-origin: 100% 0; 338 | -moz-transform-origin: 100% 0; 339 | -ms-transform-origin: 100% 0; 340 | transform-origin: 100% 0; 341 | } 342 | .swiper-container-cube .swiper-slide-active, 343 | .swiper-container-cube .swiper-slide-next, 344 | .swiper-container-cube .swiper-slide-prev, 345 | .swiper-container-cube .swiper-slide-next + .swiper-slide { 346 | pointer-events: auto; 347 | visibility: visible; 348 | } 349 | .swiper-container-cube .swiper-cube-shadow { 350 | position: absolute; 351 | left: 0; 352 | bottom: 0px; 353 | width: 100%; 354 | height: 100%; 355 | background: #000; 356 | opacity: 0.6; 357 | -webkit-filter: blur(50px); 358 | filter: blur(50px); 359 | } 360 | .swiper-container-cube.swiper-container-vertical .swiper-cube-shadow { 361 | z-index: 0; 362 | } 363 | /* Scrollbar */ 364 | .swiper-scrollbar { 365 | border-radius: 10px; 366 | position: relative; 367 | -ms-touch-action: none; 368 | background: rgba(0, 0, 0, 0.1); 369 | } 370 | .swiper-container-horizontal > .swiper-scrollbar { 371 | position: absolute; 372 | left: 1%; 373 | bottom: 3px; 374 | z-index: 50; 375 | height: 5px; 376 | width: 98%; 377 | } 378 | .swiper-container-vertical > .swiper-scrollbar { 379 | position: absolute; 380 | right: 3px; 381 | top: 1%; 382 | z-index: 50; 383 | width: 5px; 384 | height: 98%; 385 | } 386 | .swiper-scrollbar-drag { 387 | height: 100%; 388 | width: 100%; 389 | position: relative; 390 | background: rgba(0, 0, 0, 0.5); 391 | border-radius: 10px; 392 | left: 0; 393 | top: 0; 394 | } 395 | .swiper-scrollbar-cursor-drag { 396 | cursor: move; 397 | } 398 | /* Preloader */ 399 | .swiper-lazy-preloader { 400 | width: 42px; 401 | height: 42px; 402 | position: absolute; 403 | left: 50%; 404 | top: 50%; 405 | margin-left: -21px; 406 | margin-top: -21px; 407 | z-index: 10; 408 | -webkit-transform-origin: 50%; 409 | -moz-transform-origin: 50%; 410 | transform-origin: 50%; 411 | -webkit-animation: swiper-preloader-spin 1s steps(12, end) infinite; 412 | -moz-animation: swiper-preloader-spin 1s steps(12, end) infinite; 413 | animation: swiper-preloader-spin 1s steps(12, end) infinite; 414 | } 415 | .swiper-lazy-preloader:after { 416 | display: block; 417 | content: ""; 418 | width: 100%; 419 | height: 100%; 420 | background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg%20viewBox%3D'0%200%20120%20120'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20xmlns%3Axlink%3D'http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink'%3E%3Cdefs%3E%3Cline%20id%3D'l'%20x1%3D'60'%20x2%3D'60'%20y1%3D'7'%20y2%3D'27'%20stroke%3D'%236c6c6c'%20stroke-width%3D'11'%20stroke-linecap%3D'round'%2F%3E%3C%2Fdefs%3E%3Cg%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.27'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.27'%20transform%3D'rotate(30%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.27'%20transform%3D'rotate(60%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.27'%20transform%3D'rotate(90%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.27'%20transform%3D'rotate(120%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.27'%20transform%3D'rotate(150%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.37'%20transform%3D'rotate(180%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.46'%20transform%3D'rotate(210%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.56'%20transform%3D'rotate(240%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.66'%20transform%3D'rotate(270%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.75'%20transform%3D'rotate(300%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.85'%20transform%3D'rotate(330%2060%2C60)'%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E"); 421 | background-position: 50%; 422 | -webkit-background-size: 100%; 423 | background-size: 100%; 424 | background-repeat: no-repeat; 425 | } 426 | .swiper-lazy-preloader-white:after { 427 | background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg%20viewBox%3D'0%200%20120%20120'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20xmlns%3Axlink%3D'http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink'%3E%3Cdefs%3E%3Cline%20id%3D'l'%20x1%3D'60'%20x2%3D'60'%20y1%3D'7'%20y2%3D'27'%20stroke%3D'%23fff'%20stroke-width%3D'11'%20stroke-linecap%3D'round'%2F%3E%3C%2Fdefs%3E%3Cg%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.27'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.27'%20transform%3D'rotate(30%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.27'%20transform%3D'rotate(60%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.27'%20transform%3D'rotate(90%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.27'%20transform%3D'rotate(120%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.27'%20transform%3D'rotate(150%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.37'%20transform%3D'rotate(180%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.46'%20transform%3D'rotate(210%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.56'%20transform%3D'rotate(240%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.66'%20transform%3D'rotate(270%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.75'%20transform%3D'rotate(300%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.85'%20transform%3D'rotate(330%2060%2C60)'%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E"); 428 | } 429 | @-webkit-keyframes swiper-preloader-spin { 430 | 100% { 431 | -webkit-transform: rotate(360deg); 432 | } 433 | } 434 | @keyframes swiper-preloader-spin { 435 | 100% { 436 | transform: rotate(360deg); 437 | } 438 | } 439 | -------------------------------------------------------------------------------- /client/lib/ramjet.js: -------------------------------------------------------------------------------- 1 | (function (global, factory) { 2 | typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : 3 | typeof define === 'function' && define.amd ? define(factory) : 4 | global.ramjet = factory() 5 | }(this, function () { 'use strict'; 6 | 7 | // for the sake of Safari, may it burn in hell 8 | var BLACKLIST = ['length', 'parentRule']; 9 | 10 | var styleKeys = undefined; 11 | 12 | if (typeof CSS2Properties !== 'undefined') { 13 | // why hello Firefox 14 | styleKeys = Object.keys(CSS2Properties.prototype); 15 | } else { 16 | styleKeys = Object.keys(document.createElement('div').style).filter(function (k) { 17 | return ! ~BLACKLIST.indexOf(k); 18 | }); 19 | } 20 | 21 | var utils_styleKeys = styleKeys; 22 | 23 | var svgns = 'http://www.w3.org/2000/svg'; 24 | var svg = document.createElementNS(svgns, 'svg'); 25 | 26 | svg.style.position = 'fixed'; 27 | svg.style.top = svg.style.left = '0'; 28 | svg.style.width = svg.style.height = '100%'; 29 | svg.style.overflow = 'visible'; 30 | svg.style.pointerEvents = 'none'; 31 | svg.setAttribute('class', 'mogrify-svg'); 32 | 33 | var appendedSvg = false; 34 | 35 | function appendSvg() { 36 | document.body.appendChild(svg); 37 | appendedSvg = true; 38 | } 39 | 40 | function cloneNode(node) { 41 | var clone = node.cloneNode(); 42 | 43 | var style = undefined; 44 | var len = undefined; 45 | var i = undefined; 46 | 47 | var attr = undefined; 48 | 49 | if (node.nodeType === 1) { 50 | style = window.getComputedStyle(node); 51 | 52 | utils_styleKeys.forEach(function (prop) { 53 | clone.style[prop] = style[prop]; 54 | }); 55 | 56 | len = node.childNodes.length; 57 | for (i = 0; i < len; i += 1) { 58 | clone.appendChild(cloneNode(node.childNodes[i])); 59 | } 60 | } 61 | 62 | return clone; 63 | } 64 | 65 | function wrapNode(node) { 66 | var isSvg = node.namespaceURI === svgns; 67 | 68 | var _node$getBoundingClientRect = node.getBoundingClientRect(); 69 | 70 | var left = _node$getBoundingClientRect.left; 71 | var right = _node$getBoundingClientRect.right; 72 | var top = _node$getBoundingClientRect.top; 73 | var bottom = _node$getBoundingClientRect.bottom; 74 | 75 | var style = window.getComputedStyle(node); 76 | 77 | var clone = cloneNode(node); 78 | 79 | var wrapper = { 80 | node: node, clone: clone, isSvg: isSvg, 81 | cx: (left + right) / 2, 82 | cy: (top + bottom) / 2, 83 | width: right - left, 84 | height: bottom - top, 85 | transform: null, 86 | borderRadius: null 87 | }; 88 | 89 | if (isSvg) { 90 | var ctm = node.getScreenCTM(); 91 | wrapper.transform = 'matrix(' + [ctm.a, ctm.b, ctm.c, ctm.d, ctm.e, ctm.f].join(',') + ')'; 92 | wrapper.borderRadius = [0, 0, 0, 0]; 93 | 94 | svg.appendChild(clone); 95 | } else { 96 | var offsetParent = node.offsetParent || document.body; 97 | var offsetParentStyle = window.getComputedStyle(offsetParent); 98 | var offsetParentBcr = offsetParent.getBoundingClientRect(); 99 | 100 | clone.style.position = 'absolute'; 101 | clone.style.top = top - parseInt(style.marginTop, 10) - (offsetParentBcr.top - parseInt(offsetParentStyle.marginTop, 10)) + 'px'; 102 | clone.style.left = left - parseInt(style.marginLeft, 10) - (offsetParentBcr.left - parseInt(offsetParentStyle.marginLeft, 10)) + 'px'; 103 | 104 | wrapper.transform = ''; // TODO...? 105 | wrapper.borderRadius = [parseFloat(style.borderTopLeftRadius), parseFloat(style.borderTopRightRadius), parseFloat(style.borderBottomRightRadius), parseFloat(style.borderBottomLeftRadius)]; 106 | 107 | node.parentNode.appendChild(clone); 108 | } 109 | 110 | return wrapper; 111 | } 112 | 113 | function hideNode(node) { 114 | node.__ramjetOriginalTransition__ = node.style.transition; 115 | node.style.transition = ''; 116 | 117 | node.style.opacity = 0; 118 | } 119 | 120 | function showNode(node) { 121 | node.style.transition = ''; 122 | node.style.opacity = 1; 123 | 124 | if (node.__ramjetOriginalTransition__) { 125 | setTimeout(function () { 126 | node.style.transition = node.__ramjetOriginalTransition__; 127 | }); 128 | } 129 | } 130 | 131 | var utils_getTransform = getTransform; 132 | 133 | function getTransform(isSvg, cx, cy, dx, dy, dsx, dsy, t) { 134 | var transform = isSvg ? "translate(" + cx + " " + cy + ") scale(" + (1 + t * dsx) + " " + (1 + t * dsy) + ") translate(" + -cx + " " + -cy + ") translate(" + t * dx + " " + t * dy + ")" : "translate(" + t * dx + "px," + t * dy + "px) scale(" + (1 + t * dsx) + "," + (1 + t * dsy) + ")"; 135 | 136 | return transform; 137 | } 138 | 139 | var utils_getBorderRadius = getBorderRadius; 140 | 141 | function getBorderRadius(a, b, dsx, dsy, t) { 142 | var sx = 1 + t * dsx; 143 | var sy = 1 + t * dsy; 144 | 145 | return a.map(function (from, i) { 146 | var to = b[i]; 147 | 148 | var rx = (from + t * (to - from)) / sx; 149 | var ry = (from + t * (to - from)) / sy; 150 | 151 | return "" + rx + "px " + ry + "px"; 152 | }); 153 | } 154 | 155 | function linear(pos) { 156 | return pos; 157 | } 158 | 159 | function easeIn(pos) { 160 | return Math.pow(pos, 3); 161 | } 162 | 163 | function easeOut(pos) { 164 | return Math.pow(pos - 1, 3) + 1; 165 | } 166 | 167 | function easeInOut(pos) { 168 | if ((pos /= 0.5) < 1) { 169 | return 0.5 * Math.pow(pos, 3); 170 | } 171 | 172 | return 0.5 * (Math.pow(pos - 2, 3) + 2); 173 | } 174 | 175 | var rAF = window.requestAnimationFrame || window.webkitRequestAnimationFrame || function (fn) { 176 | return setTimeout(fn, 16); 177 | }; 178 | 179 | var utils_rAF = rAF; 180 | 181 | function transformers_TimerTransformer___classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 182 | 183 | var transformers_TimerTransformer__TimerTransformer = function TimerTransformer(from, to, options) { 184 | transformers_TimerTransformer___classCallCheck(this, transformers_TimerTransformer__TimerTransformer); 185 | 186 | var dx = to.cx - from.cx; 187 | var dy = to.cy - from.cy; 188 | 189 | var dsxf = to.width / from.width - 1; 190 | var dsyf = to.height / from.height - 1; 191 | 192 | var dsxt = from.width / to.width - 1; 193 | var dsyt = from.height / to.height - 1; 194 | 195 | var startTime = Date.now(); 196 | var duration = options.duration || 400; 197 | var easing = options.easing || linear; 198 | 199 | function tick() { 200 | var timeNow = Date.now(); 201 | var elapsed = timeNow - startTime; 202 | 203 | if (elapsed > duration) { 204 | from.clone.parentNode.removeChild(from.clone); 205 | to.clone.parentNode.removeChild(to.clone); 206 | 207 | if (options.done) { 208 | options.done(); 209 | } 210 | 211 | return; 212 | } 213 | 214 | var t = easing(elapsed / duration); 215 | 216 | // opacity 217 | from.clone.style.opacity = 1 - t; 218 | to.clone.style.opacity = t; 219 | 220 | // border radius 221 | var borderRadius = utils_getBorderRadius(from.borderRadius, to.borderRadius, t); 222 | from.clone.style.borderTopLeftRadius = to.clone.style.borderTopLeftRadius = borderRadius[0]; 223 | from.clone.style.borderTopRightRadius = to.clone.style.borderTopRightRadius = borderRadius[1]; 224 | from.clone.style.borderBottomRightRadius = to.clone.style.borderBottomRightRadius = borderRadius[2]; 225 | from.clone.style.borderBottomLeftRadius = to.clone.style.borderBottomLeftRadius = borderRadius[3]; 226 | 227 | var cx = from.cx + dx * t; 228 | var cy = from.cy + dy * t; 229 | 230 | var fromTransform = utils_getTransform(from.isSvg, cx, cy, dx, dy, dsxf, dsyf, t) + ' ' + from.transform; 231 | var toTransform = utils_getTransform(to.isSvg, cx, cy, -dx, -dy, dsxt, dsyt, 1 - t) + ' ' + to.transform; 232 | 233 | if (from.isSvg) { 234 | from.clone.setAttribute('transform', fromTransform); 235 | } else { 236 | from.clone.style.transform = from.clone.style.webkitTransform = from.clone.style.msTransform = fromTransform; 237 | } 238 | 239 | if (to.isSvg) { 240 | to.clone.setAttribute('transform', toTransform); 241 | } else { 242 | to.clone.style.transform = to.clone.style.webkitTransform = to.clone.style.msTransform = toTransform; 243 | } 244 | 245 | utils_rAF(tick); 246 | } 247 | 248 | tick(); 249 | }; 250 | 251 | var transformers_TimerTransformer = transformers_TimerTransformer__TimerTransformer; 252 | 253 | var div = document.createElement('div'); 254 | 255 | var keyframesSupported = true; 256 | var TRANSFORM = undefined; 257 | var KEYFRAMES = undefined; 258 | var ANIMATION_DIRECTION = undefined; 259 | var ANIMATION_DURATION = undefined; 260 | var ANIMATION_ITERATION_COUNT = undefined; 261 | var ANIMATION_NAME = undefined; 262 | var ANIMATION_TIMING_FUNCTION = undefined; 263 | var ANIMATION_END = undefined; 264 | 265 | // We have to browser-sniff for IE11, because it was apparently written 266 | // by a barrel of stoned monkeys - http://jsfiddle.net/rich_harris/oquLu2qL/ 267 | 268 | // http://stackoverflow.com/questions/17907445/how-to-detect-ie11 269 | var isIe11 = !window.ActiveXObject && 'ActiveXObject' in window; 270 | 271 | if (!isIe11 && ('transform' in div.style || 'webkitTransform' in div.style) && ('animation' in div.style || 'webkitAnimation' in div.style)) { 272 | keyframesSupported = true; 273 | 274 | TRANSFORM = 'transform' in div.style ? 'transform' : '-webkit-transform'; 275 | 276 | if ('animation' in div.style) { 277 | KEYFRAMES = '@keyframes'; 278 | 279 | ANIMATION_DIRECTION = 'animationDirection'; 280 | ANIMATION_DURATION = 'animationDuration'; 281 | ANIMATION_ITERATION_COUNT = 'animationIterationCount'; 282 | ANIMATION_NAME = 'animationName'; 283 | ANIMATION_TIMING_FUNCTION = 'animationTimingFunction'; 284 | 285 | ANIMATION_END = 'animationend'; 286 | } else { 287 | KEYFRAMES = '@-webkit-keyframes'; 288 | 289 | ANIMATION_DIRECTION = 'webkitAnimationDirection'; 290 | ANIMATION_DURATION = 'webkitAnimationDuration'; 291 | ANIMATION_ITERATION_COUNT = 'webkitAnimationIterationCount'; 292 | ANIMATION_NAME = 'webkitAnimationName'; 293 | ANIMATION_TIMING_FUNCTION = 'webkitAnimationTimingFunction'; 294 | 295 | ANIMATION_END = 'webkitAnimationEnd'; 296 | } 297 | } else { 298 | keyframesSupported = false; 299 | } 300 | 301 | function transformers_KeyframeTransformer___classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 302 | 303 | var transformers_KeyframeTransformer__KeyframeTransformer = function KeyframeTransformer(from, to, options) { 304 | transformers_KeyframeTransformer___classCallCheck(this, transformers_KeyframeTransformer__KeyframeTransformer); 305 | 306 | var _getKeyframes = getKeyframes(from, to, options); 307 | 308 | var fromKeyframes = _getKeyframes.fromKeyframes; 309 | var toKeyframes = _getKeyframes.toKeyframes; 310 | 311 | var fromId = '_' + ~ ~(Math.random() * 1000000); 312 | var toId = '_' + ~ ~(Math.random() * 1000000); 313 | 314 | var css = '' + KEYFRAMES + ' ' + fromId + ' { ' + fromKeyframes + ' } ' + KEYFRAMES + ' ' + toId + ' { ' + toKeyframes + ' }'; 315 | var dispose = addCss(css); 316 | 317 | from.clone.style[ANIMATION_DIRECTION] = 'alternate'; 318 | from.clone.style[ANIMATION_DURATION] = '' + options.duration / 1000 + 's'; 319 | from.clone.style[ANIMATION_ITERATION_COUNT] = 1; 320 | from.clone.style[ANIMATION_NAME] = fromId; 321 | from.clone.style[ANIMATION_TIMING_FUNCTION] = 'linear'; 322 | 323 | to.clone.style[ANIMATION_DIRECTION] = 'alternate'; 324 | to.clone.style[ANIMATION_DURATION] = '' + options.duration / 1000 + 's'; 325 | to.clone.style[ANIMATION_ITERATION_COUNT] = 1; 326 | to.clone.style[ANIMATION_NAME] = toId; 327 | to.clone.style[ANIMATION_TIMING_FUNCTION] = 'linear'; 328 | 329 | var fromDone = undefined; 330 | var toDone = undefined; 331 | 332 | function done() { 333 | if (fromDone && toDone) { 334 | from.clone.parentNode.removeChild(from.clone); 335 | to.clone.parentNode.removeChild(to.clone); 336 | 337 | if (options.done) options.done(); 338 | 339 | dispose(); 340 | } 341 | } 342 | 343 | from.clone.addEventListener(ANIMATION_END, function () { 344 | fromDone = true; 345 | done(); 346 | }); 347 | 348 | to.clone.addEventListener(ANIMATION_END, function () { 349 | toDone = true; 350 | done(); 351 | }); 352 | }; 353 | 354 | var transformers_KeyframeTransformer = transformers_KeyframeTransformer__KeyframeTransformer; 355 | 356 | function addCss(css) { 357 | var styleElement = document.createElement('style'); 358 | styleElement.type = 'text/css'; 359 | 360 | var head = document.getElementsByTagName('head')[0]; 361 | 362 | // Internet Exploder won't let you use styleSheet.innerHTML - we have to 363 | // use styleSheet.cssText instead 364 | var styleSheet = styleElement.styleSheet; 365 | 366 | if (styleSheet) { 367 | styleSheet.cssText = css; 368 | } else { 369 | styleElement.innerHTML = css; 370 | } 371 | 372 | head.appendChild(styleElement); 373 | 374 | return function () { 375 | return head.removeChild(styleElement); 376 | }; 377 | } 378 | 379 | function getKeyframes(from, to, options) { 380 | var dx = to.cx - from.cx; 381 | var dy = to.cy - from.cy; 382 | 383 | var dsxf = to.width / from.width - 1; 384 | var dsyf = to.height / from.height - 1; 385 | 386 | var dsxt = from.width / to.width - 1; 387 | var dsyt = from.height / to.height - 1; 388 | 389 | var easing = options.easing || linear; 390 | 391 | var numFrames = options.duration / 50; // one keyframe per 50ms is probably enough... this may prove not to be the case though 392 | 393 | var fromKeyframes = []; 394 | var toKeyframes = []; 395 | var i; 396 | 397 | function addKeyframes(pc, t) { 398 | var cx = from.cx + dx * t; 399 | var cy = from.cy + dy * t; 400 | 401 | var fromBorderRadius = utils_getBorderRadius(from.borderRadius, to.borderRadius, dsxf, dsyf, t); 402 | var toBorderRadius = utils_getBorderRadius(to.borderRadius, from.borderRadius, dsxt, dsyt, 1 - t); 403 | 404 | var fromTransform = utils_getTransform(false, cx, cy, dx, dy, dsxf, dsyf, t) + ' ' + from.transform; 405 | var toTransform = utils_getTransform(false, cx, cy, -dx, -dy, dsxt, dsyt, 1 - t) + ' ' + to.transform; 406 | 407 | fromKeyframes.push('\n\t\t\t' + pc + '% {\n\t\t\t\topacity: ' + (1 - t) + ';\n\t\t\t\tborder-top-left-radius: ' + fromBorderRadius[0] + ';\n\t\t\t\tborder-top-right-radius: ' + fromBorderRadius[1] + ';\n\t\t\t\tborder-bottom-right-radius: ' + fromBorderRadius[2] + ';\n\t\t\t\tborder-bottom-left-radius: ' + fromBorderRadius[3] + ';\n\t\t\t\t' + TRANSFORM + ': ' + fromTransform + ';\n\t\t\t}'); 408 | 409 | toKeyframes.push('\n\t\t\t' + pc + '% {\n\t\t\t\topacity: ' + t + ';\n\t\t\t\tborder-top-left-radius: ' + toBorderRadius[0] + ';\n\t\t\t\tborder-top-right-radius: ' + toBorderRadius[1] + ';\n\t\t\t\tborder-bottom-right-radius: ' + toBorderRadius[2] + ';\n\t\t\t\tborder-bottom-left-radius: ' + toBorderRadius[3] + ';\n\t\t\t\t' + TRANSFORM + ': ' + toTransform + ';\n\t\t\t}'); 410 | } 411 | 412 | for (i = 0; i < numFrames; i += 1) { 413 | var pc = 100 * (i / numFrames); 414 | var t = easing(i / numFrames); 415 | 416 | addKeyframes(pc, t); 417 | } 418 | 419 | addKeyframes(100, 1); 420 | 421 | fromKeyframes = fromKeyframes.join('\n'); 422 | toKeyframes = toKeyframes.join('\n'); 423 | 424 | return { fromKeyframes: fromKeyframes, toKeyframes: toKeyframes }; 425 | } 426 | 427 | var ramjet = { 428 | transform: function (fromNode, toNode) { 429 | var options = arguments[2] === undefined ? {} : arguments[2]; 430 | 431 | if (typeof options === 'function') { 432 | options = { done: options }; 433 | } 434 | 435 | if (!('duration' in options)) { 436 | options.duration = 400; 437 | } 438 | 439 | var from = wrapNode(fromNode); 440 | var to = wrapNode(toNode); 441 | 442 | if (from.isSvg || to.isSvg && !appendedSvg) { 443 | appendSvg(); 444 | } 445 | 446 | if (!keyframesSupported || options.useTimer || from.isSvg || to.isSvg) { 447 | return new transformers_TimerTransformer(from, to, options); 448 | } else { 449 | return new transformers_KeyframeTransformer(from, to, options); 450 | } 451 | }, 452 | 453 | hide: function () { 454 | for (var _len = arguments.length, nodes = Array(_len), _key = 0; _key < _len; _key++) { 455 | nodes[_key] = arguments[_key]; 456 | } 457 | 458 | nodes.forEach(hideNode); 459 | }, 460 | 461 | show: function () { 462 | for (var _len2 = arguments.length, nodes = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { 463 | nodes[_key2] = arguments[_key2]; 464 | } 465 | 466 | nodes.forEach(showNode); 467 | }, 468 | 469 | // expose some basic easing functions 470 | linear: linear, easeIn: easeIn, easeOut: easeOut, easeInOut: easeInOut 471 | }; 472 | 473 | return ramjet; 474 | 475 | })); 476 | --------------------------------------------------------------------------------