8 |
<%= t[:artist] %>: <%= t[:name] %>
9 |
10 |
11 |
12 | <% end %>
--------------------------------------------------------------------------------
/config/compass.rb:
--------------------------------------------------------------------------------
1 | # Complile stylesheets for production using:
2 | # compass compile --force
3 |
4 | if defined?(Sinatra) # Running within sinatra
5 | project_path = Sinatra::Application.root
6 | environment = :development
7 | else # command line tool
8 | css_dir = File.join 'public', 'stylesheets'
9 | relative_assets = true
10 | environment = :production
11 | end
12 |
13 | # Common Config
14 | sass_dir = File.join 'views', 'stylesheets'
15 | images_dir = File.join 'public', 'images'
16 | http_path = "/"
17 | http_images_path = "/images"
18 | http_stylesheets_path = "/stylesheets"
19 | output_style = :compressed
--------------------------------------------------------------------------------
/views/stylesheets/partials/_bootstrap.scss:
--------------------------------------------------------------------------------
1 | /*!
2 | * Bootstrap v1.1.1
3 | *
4 | * Copyright 2011 Twitter, Inc
5 | * Licensed under the Apache License v2.0
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * Designed and built with all the love in the world @twitter by @mdo and @fat.
9 | *
10 | * Converted to Sass by @johnwlong.
11 | *
12 | * Date: @DATE
13 | */
14 |
15 | // CSS Reset
16 | @import "reset.scss";
17 |
18 | // Core
19 | @import "preboot.scss";
20 | @import "scaffolding.scss";
21 |
22 | // Styled patterns and elements
23 | @import "type.scss";
24 | @import "forms.scss";
25 | //@import "tables.scss";
26 | @import "patterns.scss";
27 | //@import "datepicker.scss";
28 |
--------------------------------------------------------------------------------
/views/stylesheets/partials/_main.scss:
--------------------------------------------------------------------------------
1 | li.spotify-track {
2 | margin: 0 0 10px;
3 | cursor: pointer;
4 | }
5 |
6 | #chat-container ul {
7 | width: 100%;
8 | height: 200px;
9 | border: 1px solid #eeeeee;
10 | background: #fbfbfb;
11 | overflow-y: scroll;
12 | font-size: 12px;
13 | list-style-position: inside;
14 | list-style: none;
15 | padding: 0;
16 | margin: 0px;
17 | margin-bottom: 15px;
18 | }
19 |
20 | #chat-container ul li { margin: 0 0 10px; }
21 |
22 | #message{ width: 90%; }
23 |
24 | .track-container{ display: block; }
25 |
26 | .track-image{ float: left; }
27 |
28 | .track-info{
29 | float: left;
30 | padding-top: 20px;
31 | padding-left: 20px;
32 | }
--------------------------------------------------------------------------------
/partials.rb:
--------------------------------------------------------------------------------
1 | # stolen from http://github.com/cschneid/irclogger/blob/master/lib/partials.rb
2 | # and made a lot more robust by me
3 | # this implementation uses erb by default. if you want to use any other template mechanism
4 | # then replace `erb` on line 13 and line 17 with `haml` or whatever
5 | module Sinatra::Partials
6 | def partial(template, *args)
7 | template_array = template.to_s.split('/')
8 | template = template_array[0..-2].join('/') + "/_#{template_array[-1]}"
9 | options = args.last.is_a?(Hash) ? args.pop : {}
10 | options.merge!(:layout => false)
11 | locals = options[:locals] || {}
12 | if collection = options.delete(:collection) then
13 | collection.inject([]) do |buffer, member|
14 | buffer << erb(:"#{template}", options.merge(:layout =>
15 | false, :locals => {template_array[-1].to_sym => member}.merge(locals)))
16 | end.join("\n")
17 | else
18 | erb(:"#{template}", options)
19 | end
20 | end
21 | end
--------------------------------------------------------------------------------
/node/faye_server.coffee:
--------------------------------------------------------------------------------
1 | http = require 'http'
2 | faye = require 'faye'
3 |
4 | # ********* Utility Stuff ***********
5 | process.on 'uncaughtException', (err) ->
6 | console.log "Error: #{err}"
7 | # Handle non-Bayeux requests
8 | server = http.createServer (request, response) ->
9 | response.writeHead 200, {'Content-Type': 'text/plain'}
10 | response.write 'Hello, non-Bayeux request'
11 | response.end
12 | # Server logging
13 | serverLog =
14 | incoming: (message, callback) ->
15 | if message.channel == '/meta/subscribe'
16 | logWithTimeStamp "CLIENT SUBSCRIBED Client ID: #{message.clientId}"
17 | if message.channel.match(/\/users\/*/)
18 | logWithTimeStamp "USER MESSAGE ON CHANNEL: #{message.channel}"
19 | callback(message)
20 |
21 | logWithTimeStamp = (logMessage) ->
22 | timestampedMessage = "#{Date()} | #{logMessage}"
23 | console.log timestampedMessage
24 | # *************************************
25 |
26 | bayeux = new faye.NodeAdapter( mount: '/faye', timeout: 45)
27 |
28 | bayeux.addExtension serverLog
29 | bayeux.attach server
30 | console.log "Starting Faye server on port 5222"
31 | server.listen 5222
--------------------------------------------------------------------------------
/views/layout.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
Spotify Water Cooler
6 |
7 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
22 |
23 |
24 |
25 |
26 |
27 | <%= yield %>
28 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/config.ru:
--------------------------------------------------------------------------------
1 | require "rubygems"
2 | require "bundler"
3 | Bundler.require
4 |
5 | require 'socket'
6 | require "./app.rb"
7 |
8 |
9 | def load_config(file)
10 | if File.exist?(file)
11 | yaml = YAML.load_file(file)
12 | ENV['HALLON_APPKEY'] = yaml['HALLON_APPKEY']
13 | ENV['HALLON_USERNAME'] = yaml['HALLON_USERNAME']
14 | ENV['HALLON_PASSWORD'] = yaml['HALLON_PASSWORD']
15 |
16 | ENV['TWILIO_ACCOUNT_SID'] = yaml['TWILIO_ACCOUNT_SID']
17 | ENV['TWILIO_AUTH_TOKEN'] = yaml['TWILIO_AUTH_TOKEN']
18 | ENV['TWILIO_NUMBER'] = yaml['TWILIO_NUMBER']
19 | ENV['TWILIO_APP_ID'] = yaml['TWILIO_APP_ID']
20 | else
21 | return "Please setup config.yml"
22 | end
23 | end
24 |
25 | configure do
26 | load_config "./config.yml"
27 |
28 | HALLON_SESSION = Hallon::Session.initialize IO.read(ENV['HALLON_APPKEY']) do
29 | on(:log_message) do |message|
30 | puts "[LOG] #{message}"
31 | end
32 | end
33 |
34 | HALLON_SESSION.login!(ENV['HALLON_USERNAME'], ENV['HALLON_PASSWORD'])
35 | puts "Successfully logged in!"
36 |
37 | set :scss, {:style => :compact, :debug_info => false}
38 | Compass.add_project_configuration(File.join(Sinatra::Application.root, 'config', 'compass.rb'))
39 | end
40 |
41 | use Faye::RackAdapter, :mount => '/faye', :timeout => 45
42 | run MainApp
--------------------------------------------------------------------------------
/node/faye_server.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | var bayeux, faye, http, logWithTimeStamp, server, serverLog;
3 | http = require('http');
4 | faye = require('faye');
5 | process.on('uncaughtException', function(err) {
6 | return console.log("Error: " + err);
7 | });
8 | server = http.createServer(function(request, response) {
9 | response.writeHead(200, {
10 | 'Content-Type': 'text/plain'
11 | });
12 | response.write('Hello, non-Bayeux request');
13 | return response.end;
14 | });
15 | serverLog = {
16 | incoming: function(message, callback) {
17 | if (message.channel === '/meta/subscribe') {
18 | logWithTimeStamp("CLIENT SUBSCRIBED Client ID: " + message.clientId);
19 | }
20 | if (message.channel.match(/\/users\/*/)) {
21 | logWithTimeStamp("USER MESSAGE ON CHANNEL: " + message.channel);
22 | }
23 | return callback(message);
24 | }
25 | };
26 | logWithTimeStamp = function(logMessage) {
27 | var timestampedMessage;
28 | timestampedMessage = "" + (Date()) + " | " + logMessage;
29 | return console.log(timestampedMessage);
30 | };
31 | bayeux = new faye.NodeAdapter({
32 | mount: '/faye',
33 | timeout: 45
34 | });
35 | bayeux.addExtension(serverLog);
36 | bayeux.attach(server);
37 | console.log("Starting Faye server on port 5222");
38 | server.listen(5222);
39 | }).call(this);
40 |
--------------------------------------------------------------------------------
/Gemfile.lock:
--------------------------------------------------------------------------------
1 | GEM
2 | remote: http://rubygems.org/
3 | specs:
4 | addressable (2.2.6)
5 | builder (3.0.0)
6 | chunky_png (1.2.5)
7 | compass (0.11.5)
8 | chunky_png (~> 1.2)
9 | fssm (>= 0.2.7)
10 | sass (~> 3.1)
11 | cookiejar (0.3.0)
12 | daemons (1.1.4)
13 | em-hiredis (0.1.0)
14 | hiredis (~> 0.3.0)
15 | em-http-request (0.3.0)
16 | addressable (>= 2.0.0)
17 | escape_utils
18 | eventmachine (>= 0.12.9)
19 | escape_utils (0.2.4)
20 | eventmachine (0.12.10)
21 | faye (0.7.1)
22 | cookiejar (>= 0.3.0)
23 | em-hiredis (>= 0.0.1)
24 | em-http-request (~> 0.3)
25 | eventmachine (~> 0.12.0)
26 | json (>= 1.0)
27 | rack (>= 1.0)
28 | thin (~> 1.2)
29 | ffi (1.0.11)
30 | fssm (0.2.7)
31 | hallon (0.12.0)
32 | ref (~> 1.0)
33 | spotify (~> 10.3.0)
34 | hiredis (0.3.2)
35 | json (1.6.5)
36 | jwt (0.1.4)
37 | json (>= 1.2.4)
38 | multi_json (1.0.4)
39 | rack (1.4.1)
40 | rack-protection (1.2.0)
41 | rack
42 | rack-test (0.6.1)
43 | rack (>= 1.0)
44 | racksh (0.9.10)
45 | rack (>= 1.0)
46 | rack-test (>= 0.5)
47 | ref (1.0.0)
48 | sass (3.1.13)
49 | sinatra (1.3.2)
50 | rack (>= 1.3.6, ~> 1.3)
51 | rack-protection (~> 1.2)
52 | tilt (>= 1.3.3, ~> 1.3)
53 | spotify (10.3.0)
54 | ffi (>= 1.0.11, ~> 1.0)
55 | thin (1.2.11)
56 | daemons (>= 1.0.9)
57 | eventmachine (>= 0.12.6)
58 | rack (>= 1.0.0)
59 | tilt (1.3.3)
60 | twilio-ruby (3.5.1)
61 | builder (>= 2.1.2)
62 | jwt (>= 0.1.2)
63 | multi_json (>= 1.0.3)
64 |
65 | PLATFORMS
66 | ruby
67 |
68 | DEPENDENCIES
69 | compass
70 | faye
71 | hallon
72 | racksh
73 | sinatra (= 1.3.2)
74 | thin
75 | twilio-ruby
76 |
--------------------------------------------------------------------------------
/app.rb:
--------------------------------------------------------------------------------
1 | # Set up initializers
2 | Dir["./initialize/**/*.rb"].each { |int| require int }
3 |
4 | # Include all of the models
5 | Dir["./models/**/*.rb"].each { |model| require model }
6 |
7 | # Routes
8 | require 'erb'
9 | require './partials'
10 |
11 | class MainApp < Sinatra::Base
12 | helpers Sinatra::Partials
13 |
14 | get '/stylesheets/:name.css' do
15 | content_type 'text/css', :charset => 'utf-8'
16 | scss(:"stylesheets/#{params[:name]}")
17 | end
18 |
19 | get "/" do
20 | @host_with_port = request.host_with_port
21 |
22 | if ENV['TWILIO_ACCOUNT_SID'] && ENV['TWILIO_AUTH_TOKEN'] && ENV['TWILIO_APP_ID']
23 | capability = Twilio::Util::Capability.new ENV['TWILIO_ACCOUNT_SID'], ENV['TWILIO_AUTH_TOKEN']
24 | capability.allow_client_outgoing ENV['TWILIO_APP_ID']
25 | @token = capability.generate
26 | else
27 | @token = nil
28 | end
29 | @all_playlists = HALLON_SESSION.container.contents
30 | @playlist_tracks = @all_playlists.first.tracks.map do |t|
31 | {
32 | :playlist_index => t.index ,
33 | :artist => t.artist.nil? ? nil : t.artist.name,
34 | :name => t.name,
35 | :image_url => t.album.nil? ? nil : t.album.cover(false).to_url
36 | }
37 | end.compact.sort_by{|h| h[:playlist_index]}
38 | erb :index
39 | end
40 |
41 | get "/add_track" do
42 | t = Hallon::Track.new( params[:spotify_url] )
43 | playlist = HALLON_SESSION.container.contents.find{|p| p.name == params[:playlist_name]}
44 | playlist.insert(0, t)
45 | state_changed = false
46 | playlist.on(:playlist_state_changed) { state_changed = true }
47 | HALLON_SESSION.wait_for { state_changed && ! playlist.pending? }
48 | playlist_tracks = playlist.tracks.map do |t|
49 | {
50 | :playlist_index => t.index ,
51 | :artist => t.artist.nil? ? nil : t.artist.name,
52 | :name => t.name,
53 | :image_url => t.album.nil? ? nil : t.album.cover(false).to_url
54 | }
55 | end.compact.sort_by{|h| h[:playlist_index]}
56 |
57 | partial(:playlist_tracks, :locals => {:tracks => playlist_tracks})
58 | end
59 |
60 | get "/change_playlist" do
61 | playlist = HALLON_SESSION.container.contents.find{|p| p.name == params[:playlist_name]}
62 | playlist_tracks = playlist.tracks.map do |t|
63 | {
64 | :playlist_index => t.index ,
65 | :artist => t.artist.nil? ? nil : t.artist.name,
66 | :name => t.name,
67 | :image_url => t.album.nil? ? nil : t.album.cover(false).to_url
68 | }
69 | end.compact.sort_by{|h| h[:playlist_index]}
70 |
71 | partial(:playlist_tracks, :locals => {:tracks => playlist_tracks})
72 | end
73 |
74 | post '/twilio' do
75 | '
76 |
77 | 1234
78 |
79 | '
80 | end
81 | end
82 |
--------------------------------------------------------------------------------
/views/index.erb:
--------------------------------------------------------------------------------
1 |
2 |
Sp
tify Water Cooler
3 |
Killing productivity...one beat at a time!
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
Search For Track
12 |
13 |
23 |
24 |
25 |
26 |
27 |
28 |
Current Playlist
29 |
30 |
43 |
44 |
45 |
46 | <%= partial(:playlist_tracks, :locals => {:tracks => @playlist_tracks}) %>
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
Water Cooler
55 |
56 |
Talk
57 |

58 |

59 |
60 |
Chat
61 |
73 |
74 |
84 |
85 |
86 |
--------------------------------------------------------------------------------
/public/js/jquery-spotifydata.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * jQuery Spotify Metadata API Plugin v0.0.1
3 | * http://rds.github.com/
4 | *
5 | * Copyright 2011 Richard Smith / May Contain Cocoa
6 | */
7 |
8 | (function($) {
9 | function grepWithLimit(elems, callback, limit, inv) {
10 | var ret = [], retVal;
11 | inv = !!inv;
12 |
13 | // Go through the array, only saving the items
14 | // that pass the validator function
15 | for ( var i = 0, length = elems.length; i < length; i++ ) {
16 | retVal = !!callback( elems[ i ], i );
17 | if ( inv !== retVal ) {
18 | ret.push( elems[ i ] );
19 | }
20 | if (ret.length >= limit) {
21 | break
22 | }
23 | }
24 |
25 | return ret;
26 | }
27 |
28 | $.spotifydata = function(method) {
29 | var methods = spotifydataMethods;
30 | if (methods[method]) {
31 | return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
32 | } else {
33 | $.error('jQuery.spotifydata#' + method + ' does not exist');
34 | }
35 | }
36 |
37 | var spotifydataMethods = {
38 | filter: function(tracks, twoLetterCountryCode, limit) {
39 | if (tracks.tracks) {
40 | tracks = tracks.tracks;
41 | }
42 | if (!tracks[0]) {
43 | return $.error('No tracks provided');
44 | } else if (!tracks[0]['album']['availability']) {
45 | return $.error('No tracks provided');
46 | }
47 | if (limit) {
48 | limit = 100;
49 | }
50 | if (twoLetterCountryCode == 'UK') {
51 | twoLetterCountryCode = 'GB';
52 | }
53 | var regex = new RegExp('('+twoLetterCountryCode+'|worldwide)', 'g')
54 | tracks = grepWithLimit(tracks, function(track, i) {
55 | return track.album.availability.territories.search(regex) >= 0;
56 | }, 1);
57 | return tracks;
58 | },
59 |
60 | lookup: function(uri, options, callback) {
61 | var data = { uri: uri };
62 | $.extend(options, {});
63 | if (options.extras) {
64 | data['extras'] = options.extras;
65 | }
66 | return $.get('http://ws.spotify.com/lookup/1/', data, callback, options.dataType || 'json');
67 | },
68 |
69 | search: function(query, options, callback) {
70 | if (typeof options === 'function') {
71 | callback = options;
72 | }
73 | if (typeof options !== 'objet') {
74 | options = {}
75 | }
76 | if ($.isArray(query)) {
77 | return $.each(query, function(i, query) {
78 | return spotifydataMethods.search(query, options, callback)
79 | })
80 | } else {
81 | var data = { q: query, page: options.page || 1 };
82 | return $.get('http://ws.spotify.com/search/1/' + (options.method || 'track'), data, callback, options.dataType || 'json')
83 | }
84 | },
85 |
86 | track: function(query, options, callback) {
87 | return spotifydataMethods.search(query, options, callback);
88 | },
89 |
90 | artist: function(query, options, callback) {
91 | return spotifydataMethods.search(query, $.extend(data, { method: 'artist' }), callback);
92 | },
93 |
94 | album: function(query, options, callback) {
95 | return spotifydataMethods.search(queryquery, $.extend(options, { method: 'album' }), callback);
96 | }
97 | }
98 | })(jQuery);
--------------------------------------------------------------------------------
/views/stylesheets/partials/_type.scss:
--------------------------------------------------------------------------------
1 | /* Typography.scss
2 | * Headings, body text, lists, code, and more for a versatile and durable typography system
3 | * ---------------------------------------------------------------------------------------- */
4 |
5 |
6 | // BODY TEXT
7 | // ---------
8 |
9 | p {
10 | @include shorthand-font(normal,$basefont,$baseline);
11 | margin-bottom: $baseline / 2;
12 | small {
13 | font-size: $basefont - 2;
14 | color: $grayLight;
15 | }
16 | }
17 |
18 |
19 | // HEADINGS
20 | // --------
21 |
22 | h1, h2, h3, h4, h5, h6 {
23 | font-weight: bold;
24 | color: $grayDark;
25 | small {
26 | color: $grayLight;
27 | }
28 | }
29 | h1 {
30 | margin-bottom: $baseline;
31 | font-size: 30px;
32 | line-height: $baseline * 2;
33 | small {
34 | font-size: 18px;
35 | }
36 | }
37 | h2 {
38 | font-size: 24px;
39 | line-height: $baseline * 2;
40 | small {
41 | font-size: 14px;
42 | }
43 | }
44 | h3, h4, h5, h6 {
45 | line-height: $baseline * 2;
46 | }
47 | h3 {
48 | font-size: 18px;
49 | small {
50 | font-size: 14px;
51 | }
52 | }
53 | h4 {
54 | font-size: 16px;
55 | small {
56 | font-size: 12px;
57 | }
58 | }
59 | h5 {
60 | font-size: 14px;
61 | }
62 | h6 {
63 | font-size: 13px;
64 | color: $grayLight;
65 | text-transform: uppercase;
66 | }
67 |
68 |
69 | // COLORS
70 | // ------
71 |
72 | // Unordered and Ordered lists
73 | ul, ol {
74 | margin: 0 0 $baseline 25px;
75 | }
76 | ul ul,
77 | ul ol,
78 | ol ol,
79 | ol ul {
80 | margin-bottom: 0;
81 | }
82 | ul {
83 | list-style: disc;
84 | }
85 | ol {
86 | list-style: decimal;
87 | }
88 | li {
89 | line-height: $baseline;
90 | color: $gray;
91 | }
92 | ul.unstyled {
93 | list-style: none;
94 | margin-left: 0;
95 | }
96 |
97 | // Description Lists
98 | dl {
99 | margin-bottom: $baseline;
100 | dt, dd {
101 | line-height: $baseline;
102 | }
103 | dt {
104 | font-weight: bold;
105 | }
106 | dd {
107 | margin-left: $baseline / 2;
108 | }
109 | }
110 |
111 | // MISC
112 | // ----
113 |
114 | // Horizontal rules
115 | hr {
116 | margin: 0 0 19px;
117 | border: 0;
118 | border-bottom: 1px solid #eee;
119 | }
120 |
121 | // Emphasis
122 | strong {
123 | font-style: inherit;
124 | font-weight: bold;
125 | line-height: inherit;
126 | }
127 | em {
128 | font-style: italic;
129 | font-weight: inherit;
130 | line-height: inherit;
131 | }
132 | .muted {
133 | color: $grayLight;
134 | }
135 |
136 | // Blockquotes
137 | blockquote {
138 | margin-bottom: $baseline;
139 | border-left: 5px solid #eee;
140 | padding-left: 15px;
141 | p {
142 | @include shorthand-font(300,14px,$baseline);
143 | margin-bottom: 0;
144 | }
145 | small {
146 | display: block;
147 | @include shorthand-font(300,12px,$baseline);
148 | color: $grayLight;
149 | &:before {
150 | content: '\2014 \00A0';
151 | }
152 | }
153 | }
154 |
155 | // Addresses
156 | address {
157 | display: block;
158 | line-height: $baseline;
159 | margin-bottom: $baseline;
160 | }
161 |
162 | // Inline and block code styles
163 | code, pre {
164 | padding: 0 3px 2px;
165 | font-family: Monaco, Andale Mono, Courier New, monospace;
166 | font-size: 12px;
167 | @include border-radius(3px);
168 | }
169 | code {
170 | background-color: lighten($orange, 40%);
171 | color: rgba(0,0,0,.75);
172 | padding: 1px 3px;
173 | }
174 | pre {
175 | background-color: #f5f5f5;
176 | display: block;
177 | padding: $baseline - 1;
178 | margin: 0 0 $baseline;
179 | line-height: $baseline;
180 | font-size: 12px;
181 | border: 1px solid #ccc;
182 | border: 1px solid rgba(0,0,0,.15);
183 | @include border-radius(3px);
184 | white-space: pre;
185 | white-space: pre-wrap;
186 | word-wrap: break-word;
187 |
188 | }
189 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Spotify Water Cooler - Killing Productivity...one beat at a time!
2 |
3 |
4 | ## Winner of API Hackday NYC 2012 ##
5 |
6 | 
7 |
8 | Do you love Spotify? Do you have Spotify running on a computer in your office or shared space that blasts awesome music all the time? Are you tired of having to physically or remotely go to the computer to manage the playlists on Spotify? Well fear not... Spotify Water Cooler is here!
9 |
10 | Search songs on Spotify and add them to your playlists being played! But this is not just to get your groove on, its a water cooler, chat with your friends, colleagues, frienemies, associates, & fellow sociopaths while you groove and choose to to groove to. Chat either with the built in chat or use voice chat in your browser powered by the awesome Twilio Voice Chat api all in your browser.
11 |
12 | Hmmm...joined the chat a bit late and wanna know what people were talking about? TOO BAD! Real life water coolers don't have chat logs and neither do we. Don't worry, no one is talking about you behind your back *wink wink*.
13 |
14 | ## Installation and usage ##
15 |
16 | These instructions have been written for OS X.
17 |
18 | ### Pre-requisites ###
19 | * [Spotify API Key](http://developer.spotify.com/en/libspotify/overview/) Download libspotify and your app key
20 | * [TWILIO Client API Key](http://www.twilio.com/api/client) Sign up, its SUPER easy!
21 | * [Ruby](http://www.ruby-lang.org/) 1.9. Use [RVM](http://rvm.beginrescueend.com/) to manage your Ruby installations. It's good.
22 | * [Rubygems](http://rubygems.org/)
23 | * [Git](http://git-scm.com/)
24 | * The [Bundler](http://rubygems.org/gems/bundler) gem. Install with 'gem install bundler'.
25 |
26 | ### Install dependencies ###
27 |
28 | Use Bundler to install project dependencies for you:
29 |
30 | $ bundle install
31 |
32 | This will install gems and various other dependencies if not already on your system. It will also create a Gemfile.lock file which will ensure that dependencies do not change unless you explicitly rerun `bundle install` again.
33 |
34 |
35 | ### Configure ###
36 |
37 | Copy the example config file:
38 |
39 | $ cp config.yml.example config.yml
40 |
41 | Enter your Spotify and Twilio credentials in config.yml. Make sure to place your Spotify key (e.g. spotify_appkey.key) in the root folder of the project.
42 |
43 | ### Run locally ###
44 |
45 | To run the application:
46 |
47 | $ thin start
48 |
49 | The app will be viewable at `http://localhost:3000`
50 |
51 | ## Screenshots ##
52 |
53 | 
54 |
55 | 
56 |
57 | ## Powered By ##
58 | 
59 |
60 | 
61 |
62 | ### License ###
63 |
64 | (The MIT License)
65 |
66 | Copyright (c) 2012 Haris Amin
67 |
68 | Permission is hereby granted, free of charge, to any person obtaining a copy of
69 | this software and associated documentation files (the 'Software'), to deal in
70 | the Software without restriction, including without limitation the rights to use,
71 | copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
72 | Software, and to permit persons to whom the Software is furnished to do so,
73 | subject to the following conditions:
74 |
75 | The above copyright notice and this permission notice shall be included in all
76 | copies or substantial portions of the Software.
77 |
78 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
79 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
80 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
81 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
82 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
83 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/public/js/application.js:
--------------------------------------------------------------------------------
1 | $(document).ready(function() {
2 |
3 |
4 | $('#search-form').bind('submit',function(event) {
5 | event.preventDefault();
6 |
7 | function trimCrap(line) {
8 | return (typeof line == 'string') ? $.trim(line).replace(/ \W /gi, ' ').replace(/\s+/gi, ' ') : line;
9 | };
10 |
11 | tracks = $.map($.unique($('#search').val().split('\n')), function(track) {
12 | return (/\w/).exec(track) ? trimCrap(track) : null;
13 | });
14 |
15 | var result, results = [], resultsCount = 0, foundResultsCount = 0, track, spotifyTracks;
16 |
17 | $.spotifydata('search', tracks, function lambda(data) {
18 | result = {};
19 | result['info'] = data.info;
20 | if (data.tracks.length > 0) {
21 | apiResults = $.spotifydata('filter', data.tracks, 'US', 10);
22 | result['track'] = apiResults[0];
23 | spotifyTracks = data.tracks.slice(0,9);
24 |
25 | if (!result.track) {
26 | result['unavailableTrack'] = data.tracks[0];
27 | }
28 | result['result'] = data.tracks.indexOf(result.track) + 1;
29 | } else {
30 | var altQuery = result.info.query.replace(/(\(|\[).+(\)|\])/,'');
31 |
32 | if (altQuery != result.info.query) {
33 | $.spotifydata('search', altQuery, lambda);
34 | }
35 | }
36 | results[resultsCount++] = result;
37 | result['found'] = !!result.track;
38 | if (result.track) {
39 | foundResultsCount++;
40 | };
41 | $("#search-results").html('');
42 |
43 | $.each(spotifyTracks, function(index, val) {
44 | html = "
" + val.artists[0].name + ": " + val.name + "";
45 | $("#search-results").append(html);
46 | }); // End of inserting tracks html
47 | }); // End of spotify search
48 | }); // End of search-form bind
49 |
50 | $("#search-results li.spotify-track").live("click", function(event) {
51 | playlistName = $("#current-playlist").val();
52 | $.get('/add_track', {spotify_url: $(this).attr('id'), playlist_name: playlistName}, function(data, textStatus, xhr) {
53 | //optional stuff to do after success
54 | $("#playlist-tracks").html(data);
55 | });
56 | });
57 |
58 |
59 | // Select Playlist
60 | $("#current-playlist").bind('change', function(event) {
61 | // Act on the event
62 | playlistName = $(this).val();
63 |
64 | $.get('/change_playlist', {playlist_name: playlistName}, function(data, textStatus, xhr) {
65 | //optional stuff to do after success
66 | $("#playlist-tracks").html(data);
67 | });
68 | });
69 |
70 | // Chat Stuff
71 | var conversation = $("#chat-container ul");
72 | var nickname = null;
73 | var subscription = client.subscribe('/chat', function(message) {
74 | conversation.append('
(' + message.cleanTime + ') ' + message.nick + ' — ' + message.msg + '');
75 | conversation.scrollTop(999999); // Hack: autoscroll down to last message
76 | });
77 |
78 | $('#login-form').bind('submit',function(event) {
79 | event.preventDefault();
80 | nickname = $('#login-nick').val();
81 | $('#chat-login').hide();
82 | $('#chat-container').show();
83 | // $('#what-to-do').removeClass('center');
84 | });
85 |
86 | $('#chat-form').bind('submit',function(event) {
87 | event.preventDefault();
88 | picture_id = $('.photo').attr('id');
89 | date = new Date();
90 | cleanDate = date.getHours() + ':' + date.getMinutes();
91 | msgDate = date.getHours() + ':' + date.getMinutes();
92 | message = { picture_id: picture_id, nick: nickname, cleanTime: cleanDate, time: date, msg: $('#message').val() };
93 | client.publish('/chat', message);
94 | $('#message').val('').focus();
95 | });
96 |
97 | // Twilio Stuff
98 | var connection;
99 | function call() {
100 | connection = Twilio.Device.connect();
101 | }
102 |
103 | function hangup() {
104 | connection.disconnect();
105 | }
106 |
107 | $('#hangup').hide();
108 |
109 | $('#call').click(function(e) {
110 | e.preventDefault();
111 | call();
112 | $(this).hide();
113 | $('#hangup').show();
114 | $('#chat-login grid_6.alpha.right').hide();
115 | });
116 |
117 | $('#hangup').click(function(e) {
118 | e.preventDefault();
119 | hangup();
120 | $(this).hide();
121 | $('#call').show();
122 | });
123 |
124 |
125 | });
--------------------------------------------------------------------------------
/views/stylesheets/partials/_reset.scss:
--------------------------------------------------------------------------------
1 | /* Reset.scss
2 | * Props to Eric Meyer (meyerweb.com) for his CSS reset file. We're using an adapted version here that cuts out some of the reset HTML elements we will never need here (i.e., dfn, samp, etc).
3 | * ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- */
4 |
5 |
6 | // ERIC MEYER RESET
7 | // --------------------------------------------------
8 |
9 | html, body { margin: 0; padding: 0; }
10 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, cite, code, del, dfn, em, img, q, s, samp, small, strike, strong, sub, sup, tt, var, dd, dl, dt, li, ol, ul, fieldset, form, label, legend, button, table, caption, tbody, tfoot, thead, tr, th, td { margin: 0; padding: 0; border: 0; font-weight: normal; font-style: normal; font-size: 100%; line-height: 1; font-family: inherit; }
11 | table { border-collapse: collapse; border-spacing: 0; }
12 | ol, ul { list-style: none; }
13 | q:before, q:after, blockquote:before, blockquote:after { content: ""; }
14 |
15 |
16 | // Normalize.css
17 | // Pulling in select resets form the normalize.css project
18 | // --------------------------------------------------
19 |
20 | // Display in IE6-9 and FF3
21 | // -------------------------
22 | // Source: http://github.com/necolas/normalize.css
23 | html {
24 | overflow-y: scroll;
25 | font-size: 100%;
26 | -webkit-text-size-adjust: 100%;
27 | -ms-text-size-adjust: 100%;
28 | }
29 | // Focus states
30 | a:focus {
31 | outline: thin dotted;
32 | }
33 |
34 | // Display in IE6-9 and FF3
35 | // -------------------------
36 | // Source: http://github.com/necolas/normalize.css
37 | article,
38 | aside,
39 | details,
40 | figcaption,
41 | figure,
42 | footer,
43 | header,
44 | hgroup,
45 | nav,
46 | section {
47 | display: block;
48 | }
49 |
50 | // Display block in IE6-9 and FF3
51 | // -------------------------
52 | // Source: http://github.com/necolas/normalize.css
53 | audio,
54 | canvas,
55 | video {
56 | display: inline-block;
57 | *display: inline;
58 | *zoom: 1;
59 | }
60 |
61 | // Prevents modern browsers from displaying 'audio' without controls
62 | // -------------------------
63 | // Source: http://github.com/necolas/normalize.css
64 | audio:not([controls]) {
65 | display: none;
66 | }
67 |
68 | // Prevents sub and sup affecting line-height in all browsers
69 | // -------------------------
70 | // Source: http://github.com/necolas/normalize.css
71 | sub,
72 | sup {
73 | font-size: 75%;
74 | line-height: 0;
75 | position: relative;
76 | vertical-align: baseline;
77 | }
78 | sup {
79 | top: -0.5em;
80 | }
81 | sub {
82 | bottom: -0.25em;
83 | }
84 |
85 | // Img border in a's and image quality
86 | // -------------------------
87 | // Source: http://github.com/necolas/normalize.css
88 | img {
89 | border: 0;
90 | -ms-interpolation-mode: bicubic;
91 | }
92 |
93 | // Forms
94 | // -------------------------
95 | // Source: http://github.com/necolas/normalize.css
96 |
97 | // Font size in all browsers, margin changes, misc consistency
98 | button,
99 | input,
100 | select,
101 | textarea {
102 | font-size: 100%;
103 | margin: 0;
104 | vertical-align: baseline;
105 | *vertical-align: middle;
106 | }
107 | button,
108 | input {
109 | line-height: normal; // FF3/4 have !important on line-height in UA stylesheet
110 | *overflow: visible; // Inner spacing ie IE6/7
111 | }
112 | button::-moz-focus-inner,
113 | input::-moz-focus-inner { // Inner padding and border oddities in FF3/4
114 | border: 0;
115 | padding: 0;
116 | }
117 | button,
118 | input[type="button"],
119 | input[type="reset"],
120 | input[type="submit"] {
121 | cursor: pointer; // Cursors on all buttons applied consistently
122 | -webkit-appearance: button; // Style clicable inputs in iOS
123 | }
124 | input[type="search"] { // Appearance in Safari/Chrome
125 | -webkit-appearance: textfield;
126 | -webkit-box-sizing: content-box;
127 | -moz-box-sizing: content-box;
128 | box-sizing: content-box;
129 | }
130 | input[type="search"]::-webkit-search-decoration {
131 | -webkit-appearance: none; // Inner-padding issues in Chrome OSX, Safari 5
132 | }
133 | textarea {
134 | overflow: auto; // Remove vertical scrollbar in IE6-9
135 | vertical-align: top; // Readability and alignment cross-browser
136 | }
137 |
138 | // Tables
139 | // -------------------------
140 | // Source: http://github.com/necolas/normalize.css
141 |
142 | // Remove spacing between table cells
143 | table {
144 | border-collapse: collapse;
145 | border-spacing: 0;
146 | }
--------------------------------------------------------------------------------
/views/stylesheets/partials/_scaffolding.scss:
--------------------------------------------------------------------------------
1 | /*
2 | * Scaffolding.scss
3 | * Basic and global styles for generating a grid system, structural layout, and page templates
4 | * ------------------------------------------------------------------------------------------- */
5 |
6 | // GRID SYSTEM
7 | // -----------
8 |
9 | .row {
10 | @include clearfix();
11 | margin-left: -20px;
12 |
13 | // Default columns
14 | .span1,
15 | .span2,
16 | .span3,
17 | .span4,
18 | .span5,
19 | .span6,
20 | .span7,
21 | .span8,
22 | .span9,
23 | .span10,
24 | .span11,
25 | .span12,
26 | .span13,
27 | .span14,
28 | .span15,
29 | .span16 {
30 | display: inline;
31 | float: left;
32 | margin-left: 20px;
33 | }
34 |
35 | // Default columns
36 | .span1 { @include columns(1); }
37 | .span2 { @include columns(2); }
38 | .span3 { @include columns(3); }
39 | .span4 { @include columns(4); }
40 | .span5 { @include columns(5); }
41 | .span6 { @include columns(6); }
42 | .span7 { @include columns(7); }
43 | .span8 { @include columns(8); }
44 | .span9 { @include columns(9); }
45 | .span10 { @include columns(10); }
46 | .span11 { @include columns(11); }
47 | .span12 { @include columns(12); }
48 | .span13 { @include columns(13); }
49 | .span14 { @include columns(14); }
50 | .span15 { @include columns(15); }
51 | .span16 { @include columns(16); }
52 |
53 | // Offset column options
54 | .offset1 { @include offset(1); }
55 | .offset2 { @include offset(2); }
56 | .offset3 { @include offset(3); }
57 | .offset4 { @include offset(4); }
58 | .offset5 { @include offset(5); }
59 | .offset6 { @include offset(6); }
60 | .offset7 { @include offset(7); }
61 | .offset8 { @include offset(8); }
62 | .offset9 { @include offset(9); }
63 | .offset10 { @include offset(10); }
64 | .offset11 { @include offset(11); }
65 | .offset12 { @include offset(12); }
66 | }
67 |
68 |
69 | // STRUCTURAL LAYOUT
70 | // -----------------
71 |
72 | html, body {
73 | background-color: #fff;
74 | }
75 | body {
76 | margin: 0;
77 | @include sans-serif-font(normal,$basefont,$baseline);
78 | color: $gray;
79 | text-rendering: optimizeLegibility;
80 | }
81 |
82 | // Container (centered, fixed-width layouts)
83 | .container {
84 | width: 940px;
85 | margin: 0 auto;
86 | }
87 |
88 | // Fluid layouts (left aligned, with sidebar, min- & max-width content)
89 | .container-fluid {
90 | padding: 0 20px;
91 | @include clearfix();
92 | .sidebar {
93 | float: left;
94 | width: 220px;
95 | }
96 | .content {
97 | min-width: 700px;
98 | max-width: 1180px;
99 | margin-left: 240px;
100 | }
101 | }
102 |
103 |
104 | // BASE STYLES
105 | // -----------
106 |
107 | // Links
108 | a {
109 | color: $linkColor;
110 | text-decoration: none;
111 | line-height: inherit;
112 | font-weight: inherit;
113 | &:hover {
114 | color: $linkColorHover;
115 | text-decoration: underline;
116 | }
117 | }
118 |
119 | // Buttons
120 | .btn {
121 | display: inline-block;
122 | @include vertical-three-colors-gradient(#fff, #fff, 0.25, darken(#fff, 10%));
123 | padding: 4px 14px;
124 | text-shadow: 0 1px 1px rgba(255,255,255,.75);
125 | color: #333;
126 | font-size: 13px;
127 | line-height: $baseline;
128 | border: 1px solid #ccc;
129 | border-bottom-color: #bbb;
130 | @include border-radius(4px);
131 | $shadow: inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05);
132 | @include box-shadow($shadow);
133 | &:hover {
134 | background-position: 0 -15px;
135 | color: #333;
136 | text-decoration: none;
137 | }
138 | }
139 | .primary {
140 | @include vertical-gradient(#049CDB, #0064CD);
141 | color: #fff;
142 | text-shadow: 0 -1px 0 rgba(0,0,0,.25);
143 | border: 1px solid darken(#0064CD, 10%);
144 | border-bottom-color: darken(#0064CD, 15%);
145 | &:hover {
146 | color: #fff;
147 | }
148 | }
149 |
150 | .btn {
151 | //.button(#1174C6);
152 | @include transition(.1s linear all);
153 | &.primary {
154 | //@include vertical-gradient($blue, $blueDark);
155 | color: #fff;
156 | text-shadow: 0 -1px 0 rgba(0,0,0,.25);
157 | border-color: $blueDark $blueDark darken($blueDark, 15%);
158 | border-color: rgba(0,0,0,.1) rgba(0,0,0,.1) fadein(rgba(0,0,0,.1), 15%);
159 | &:hover {
160 | color: #fff;
161 | }
162 | }
163 | &.large {
164 | font-size: 16px;
165 | line-height: 28px;
166 | @include border-radius(6px);
167 | }
168 | &.small {
169 | padding-right: 9px;
170 | padding-left: 9px;
171 | font-size: 11px;
172 | }
173 | &.disabled {
174 | background-image: none;
175 | @include opacity(65);
176 | cursor: default;
177 | @include box-shadow(none);
178 | }
179 |
180 | // this can't be included with the .disabled def because IE8 and below will drop it ;_;
181 | &:disabled {
182 | background-image: none;
183 | @include opacity(65);
184 | cursor: default;
185 | @include box-shadow(none);
186 | &.primary {
187 | color: #fff;
188 | }
189 | }
190 | &:active {
191 | $shadow: inset 0 3px 7px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.05);
192 | @include box-shadow($shadow);
193 | }
194 | }
195 |
196 | // Help Firefox not be a jerk about adding extra padding to buttons
197 | button.btn,
198 | input[type=submit].btn {
199 | &::-moz-focus-inner {
200 | padding: 0;
201 | border: 0;
202 | }
203 | }
204 |
--------------------------------------------------------------------------------
/views/stylesheets/partials/_forms.scss:
--------------------------------------------------------------------------------
1 | /* Forms.scss
2 | * Base styles for various input types, form layouts, and states
3 | * ------------------------------------------------------------- */
4 |
5 |
6 | // FORM STYLES
7 | // -----------
8 |
9 | form {
10 | margin-bottom: $baseline;
11 | }
12 |
13 | // Groups of fields with labels on top (legends)
14 | fieldset {
15 | margin-bottom: $baseline;
16 | padding-top: $baseline;
17 | legend {
18 | display: block;
19 | margin-left: 150px;
20 | font-size: 20px;
21 | line-height: 1;
22 | *margin: 0 0 5px 145px; /* IE6-7 */
23 | *line-height: 1.5; /* IE6-7 */
24 | color: $grayDark;
25 | }
26 | }
27 |
28 | // Parent element that clears floats and wraps labels and fields together
29 | .clearfix {
30 | margin-bottom: $baseline;
31 | }
32 |
33 | // Set font for forms
34 | label,
35 | input,
36 | select,
37 | textarea {
38 | @include sans-serif-font(normal,13px,normal);
39 | }
40 |
41 | // Float labels left
42 | label {
43 | padding-top: 6px;
44 | font-size: 13px;
45 | line-height: 18px;
46 | float: left;
47 | width: 130px;
48 | text-align: right;
49 | color: $grayDark;
50 | }
51 |
52 | // Shift over the inside div to align all label's relevant content
53 | div.input {
54 | margin-left: 150px;
55 | }
56 |
57 | // Checkboxs and radio buttons
58 | input[type=checkbox],
59 | input[type=radio] {
60 | cursor: pointer;
61 | }
62 |
63 | // Inputs, Textareas, Selects
64 | input[type=text],
65 | input[type=password],
66 | textarea,
67 | select,
68 | .uneditable-input {
69 | display: inline-block;
70 | width: 210px;
71 | padding: 4px;
72 | font-size: 13px;
73 | line-height: $baseline;
74 | height: $baseline;
75 | color: $gray;
76 | border: 1px solid #ccc;
77 | @include border-radius(3px);
78 | }
79 | select,
80 | input[type=file] {
81 | height: $baseline * 1.5;
82 | line-height: $baseline * 1.5;
83 | }
84 | textarea {
85 | height: auto;
86 | }
87 | .uneditable-input {
88 | background-color: #eee;
89 | display: block;
90 | border-color: #ccc;
91 | @include box-shadow(inset 0 1px 2px rgba(0,0,0,.075));
92 | }
93 |
94 | // Placeholder text gets special styles; can't be bundled together though for some reason
95 | :-moz-placeholder {
96 | color: $grayLight;
97 | }
98 | ::-webkit-input-placeholder {
99 | color: $grayLight;
100 | }
101 |
102 | // Focus states
103 | input[type=text],
104 | input[type=password],
105 | select, textarea {
106 | $transition: border linear .2s, box-shadow linear .2s;
107 | @include transition($transition);
108 | @include box-shadow(inset 0 1px 3px rgba(0,0,0,.1));
109 | }
110 | input[type=text]:focus,
111 | input[type=password]:focus,
112 | textarea:focus {
113 | outline: none;
114 | border-color: rgba(82,168,236,.8);
115 | $shadow: inset 0 1px 3px rgba(0,0,0,.1), 0 0 8px rgba(82,168,236,.6);
116 | @include box-shadow($shadow);
117 | }
118 |
119 | // Error styles
120 | form div.error {
121 | background: lighten($red, 57%);
122 | padding: 10px 0;
123 | margin: -10px 0 10px;
124 | @include border-radius(4px);
125 | $error-text: desaturate(lighten($red, 25%), 25%);
126 | > label,
127 | span.help-inline,
128 | span.help-block {
129 | color: $red;
130 | }
131 | input[type=text],
132 | input[type=password],
133 | textarea {
134 | border-color: $error-text;
135 | @include box-shadow(0 0 3px rgba(171,41,32,.25));
136 | &:focus {
137 | border-color: darken($error-text, 10%);
138 | @include box-shadow(0 0 6px rgba(171,41,32,.5));
139 | }
140 | }
141 | .input-prepend,
142 | .input-append {
143 | span.add-on {
144 | background: lighten($red, 50%);
145 | border-color: $error-text;
146 | color: darken($error-text, 10%);
147 | }
148 | }
149 | }
150 |
151 | // Form element sizes
152 | .input-mini, input.mini, textarea.mini, select.mini {
153 | width: 60px;
154 | }
155 | .input-small, input.small, textarea.small, select.small {
156 | width: 90px;
157 | }
158 | .input-medium, input.medium, textarea.medium, select.medium {
159 | width: 150px;
160 | }
161 | .input-large, input.large, textarea.large, select.large {
162 | width: 210px;
163 | }
164 | .input-xlarge, input.xlarge, textarea.xlarge, select.xlarge {
165 | width: 270px;
166 | }
167 | .input-xxlarge, input.xxlarge, textarea.xxlarge, select.xxlarge {
168 | width: 530px;
169 | }
170 | textarea.xxlarge {
171 | overflow-y: scroll;
172 | }
173 |
174 | // Turn off focus for disabled (read-only) form elements
175 | input[readonly]:focus,
176 | textarea[readonly]:focus,
177 | input.disabled {
178 | background: #f5f5f5;
179 | border-color: #ddd;
180 | @include box-shadow(none);
181 | }
182 |
183 | // Actions (the buttons)
184 | .actions {
185 | background: #f5f5f5;
186 | margin-top: $baseline;
187 | margin-bottom: $baseline;
188 | padding: ($baseline - 1) 20px $baseline 150px;
189 | border-top: 1px solid #ddd;
190 | @include border-radius(0 0 3px 3px);
191 | .secondary-action {
192 | float: right;
193 | a {
194 | line-height: 30px;
195 | &:hover {
196 | text-decoration: underline;
197 | }
198 | }
199 | }
200 | }
201 |
202 | // Help Text
203 | .help-inline,
204 | .help-block {
205 | font-size: 12px;
206 | line-height: $baseline;
207 | color: $grayLight;
208 | }
209 | .help-inline {
210 | padding-left: 5px;
211 | *position: relative; /* IE6-7 */
212 | *top: -5px; /* IE6-7 */
213 | }
214 |
215 | // Big blocks of help text
216 | .help-block {
217 | display: block;
218 | max-width: 600px;
219 | }
220 |
221 | // Inline Fields (input fields that appear as inline objects
222 | .inline-inputs {
223 | color: $gray;
224 | span, input[type=text] {
225 | display: inline-block;
226 | }
227 | input.mini {
228 | width: 60px;
229 | }
230 | input.small {
231 | width: 90px;
232 | }
233 | span {
234 | padding: 0 2px 0 1px;
235 | }
236 | }
237 |
238 | // Allow us to put symbols and text within the input field for a cleaner look
239 | .input-prepend,
240 | .input-append {
241 | input[type=text],
242 | input[type=password] {
243 | @include border-radius(0 3px 3px 0);
244 | }
245 | .add-on {
246 | background: #f5f5f5;
247 | float: left;
248 | display: block;
249 | width: auto;
250 | min-width: 16px;
251 | padding: 4px 4px 4px 5px;
252 | color: $grayLight;
253 | font-weight: normal;
254 | line-height: 18px;
255 | height: 18px;
256 | text-align: center;
257 | text-shadow: 0 1px 0 #fff;
258 | border: 1px solid #ccc;
259 | border-right-width: 0;
260 | @include border-radius(3px 0 0 3px);
261 | }
262 | .active {
263 | background: lighten($green, 30);
264 | border-color: $green;
265 | }
266 | }
267 | .input-prepend {
268 | .add-on {
269 | *margin-top: 1px; /* IE6-7 */
270 | }
271 | }
272 | .input-append {
273 | input[type=text],
274 | input[type=password] {
275 | float: left;
276 | @include border-radius(3px 0 0 3px);
277 | }
278 | .add-on {
279 | @include border-radius(0 3px 3px 0);
280 | border-right-width: 1px;
281 | border-left-width: 0;
282 | }
283 | }
284 |
285 | // Stacked options for forms (radio buttons or checkboxes)
286 | .inputs-list {
287 | margin: 0 0 5px;
288 | width: 100%;
289 | li {
290 | display: block;
291 | padding: 0;
292 | width: 100%;
293 | label {
294 | display: block;
295 | float: none;
296 | width: auto;
297 | padding: 0;
298 | line-height: $baseline;
299 | text-align: left;
300 | white-space: normal;
301 | strong {
302 | color: $gray;
303 | }
304 | small {
305 | font-size: 12px;
306 | font-weight: normal;
307 | }
308 | }
309 | ul.inputs-list {
310 | margin-left: 25px;
311 | margin-bottom: 10px;
312 | padding-top: 0;
313 | }
314 | &:first-child {
315 | padding-top: 5px;
316 | }
317 | }
318 | input[type=radio],
319 | input[type=checkbox] {
320 | margin-bottom: 0;
321 | }
322 | }
323 |
324 | // Stacked forms
325 | .form-stacked {
326 | padding-left: 20px;
327 | fieldset {
328 | padding-top: $baseline / 2;
329 | }
330 | legend {
331 | margin-left: 0;
332 | }
333 | label {
334 | display: block;
335 | float: none;
336 | width: auto;
337 | font-weight: bold;
338 | text-align: left;
339 | line-height: 20px;
340 | padding-top: 0;
341 | }
342 | .clearfix {
343 | margin-bottom: $baseline / 2;
344 | div.input {
345 | margin-left: 0;
346 | }
347 | }
348 | .inputs-list {
349 | margin-bottom: 0;
350 | li {
351 | padding-top: 0;
352 | label {
353 | font-weight: normal;
354 | padding-top: 0;
355 | }
356 | }
357 | }
358 | div.error {
359 | padding-top: 10px;
360 | padding-bottom: 10px;
361 | padding-left: 10px;
362 | margin-top: 0;
363 | margin-left: -10px;
364 | }
365 | .actions {
366 | margin-left: -20px;
367 | padding-left: 20px;
368 | }
369 | }
370 |
--------------------------------------------------------------------------------
/views/stylesheets/partials/_preboot.scss:
--------------------------------------------------------------------------------
1 | /* Preboot.scss
2 | * Variables and mixins to pre-ignite any new web development project
3 | * ------------------------------------------------------------------ */
4 |
5 |
6 | // VARIABLES
7 | // ---------
8 |
9 | // Links
10 | $linkColor: #0069d6;
11 | $linkColorHover: darken($linkColor, 10);
12 |
13 | // Grays
14 | $black: #000;
15 | $grayDark: lighten($black, 25%);
16 | $gray: lighten($black, 50%);
17 | $grayLight: lighten($black, 75%);
18 | $grayLighter: lighten($black, 90%);
19 | $white: #fff;
20 |
21 | // Accent Colors
22 | $blue: #049CDB;
23 | $blueDark: #0064CD;
24 | $green: #46a546;
25 | $red: #9d261d;
26 | $yellow: #ffc40d;
27 | $orange: #f89406;
28 | $pink: #c3325f;
29 | $purple: #7a43b6;
30 |
31 | // Baseline grid
32 | $basefont: 13px;
33 | $baseline: 18px;
34 |
35 | // Griditude
36 | $gridColumns: 16;
37 | $gridColumnWidth: 40px;
38 | $gridGutterWidth: 20px;
39 | $extraSpace: 40px;
40 | $siteWidth: ($gridColumns * $gridColumnWidth) + ($gridGutterWidth * ($gridColumns - 1));
41 |
42 | // Color Scheme
43 | $baseColor: $blue; // Set a base color
44 | $complement: complement($baseColor); // Determine a complementary color
45 | $split1: adjust-hue($baseColor, 158); // Split complements
46 | $split2: adjust-hue($baseColor, -158);
47 | $triad1: adjust-hue($baseColor, 135); // Triads colors
48 | $triad2: adjust-hue($baseColor, -135);
49 | $tetra1: adjust-hue($baseColor, 90); // Tetra colors
50 | $tetra2: adjust-hue($baseColor, -90);
51 | $analog1: adjust-hue($baseColor, 22); // Analogs colors
52 | $analog2: adjust-hue($baseColor, -22);
53 |
54 |
55 | // MIXINS
56 | // ------
57 |
58 | // Gradients
59 | @mixin horizontal-gradient ($startColor: #555, $endColor: #333) {
60 | background-color: $endColor;
61 | background-repeat: repeat-x;
62 | background-image: -khtml-gradient(linear, left top, right top, from($startColor), to($endColor)); // Konqueror
63 | background-image: -moz-linear-gradient(left, $startColor, $endColor); // FF 3.6+
64 | background-image: -ms-linear-gradient(left, $startColor, $endColor); // IE10
65 | background-image: -webkit-gradient(linear, left top, right top, color-stop(0%, $startColor), color-stop(100%, $endColor)); // Safari 4+, Chrome 2+
66 | background-image: -webkit-linear-gradient(left, $startColor, $endColor); // Safari 5.1+, Chrome 10+
67 | background-image: -o-linear-gradient(left, $startColor, $endColor); // Opera 11.10
68 | background-image: linear-gradient(left, $startColor, $endColor); // Le standard
69 | }
70 |
71 | @mixin vertical-gradient ($startColor: #555, $endColor: #333) {
72 | background-color: $endColor;
73 | background-repeat: repeat-x;
74 | background-image: -khtml-gradient(linear, left top, left bottom, from($startColor), to($endColor)); // Konqueror
75 | background-image: -moz-linear-gradient(top, $startColor, $endColor); // FF 3.6+
76 | background-image: -ms-linear-gradient(top, $startColor, $endColor); // IE10
77 | background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, $startColor), color-stop(100%, $endColor)); // Safari 4+, Chrome 2+
78 | background-image: -webkit-linear-gradient(top, $startColor, $endColor); // Safari 5.1+, Chrome 10+
79 | background-image: -o-linear-gradient(top, $startColor, $endColor); // Opera 11.10
80 | background-image: linear-gradient(top, $startColor, $endColor); // The standard
81 | }
82 |
83 | @mixin directional-gradient ($startColor: #555, $endColor: #333, $deg: 45deg) {
84 | background-color: $endColor;
85 | background-repeat: repeat-x;
86 | background-image: -moz-linear-gradient($deg, $startColor, $endColor); // FF 3.6+
87 | background-image: -ms-linear-gradient($deg, $startColor, $endColor); // IE10
88 | background-image: -webkit-linear-gradient($deg, $startColor, $endColor); // Safari 5.1+, Chrome 10+
89 | background-image: -o-linear-gradient($deg, $startColor, $endColor); // Opera 11.10
90 | background-image: linear-gradient($deg, $startColor, $endColor); // The standard
91 | }
92 |
93 | @mixin vertical-three-colors-gradient($startColor: #00b3ee, $midColor: #7a43b6, $colorStop: 50%, $endColor: #c3325f) {
94 | background-color: $endColor;
95 | background-repeat: no-repeat;
96 | background-image: -webkit-gradient(linear, 0 0, 0 100%, from($startColor), color-stop($colorStop, $midColor), to($endColor));
97 | background-image: -webkit-linear-gradient($startColor, $midColor $colorStop, $endColor);
98 | background-image: -moz-linear-gradient($startColor, $midColor $colorStop, $endColor);
99 | background-image: -ms-linear-gradient($startColor, $midColor $colorStop, $endColor);
100 | background-image: -o-linear-gradient($startColor, $midColor $colorStop, $endColor);
101 | background-image: linear-gradient($startColor, $midColor $colorStop, $endColor);
102 | }
103 |
104 | // Opacity
105 | @mixin opacity($opacity: 100) {
106 | filter: alpha(opacity=$opacity);
107 | -khtml-opacity: $opacity / 100;
108 | -moz-opacity: $opacity / 100;
109 | opacity: $opacity / 100;
110 | }
111 |
112 | // Gradient Bar Colors for buttons and allerts
113 | @mixin gradientBar($primaryColor, $secondaryColor) {
114 | @include vertical-gradient($primaryColor, $secondaryColor);
115 | text-shadow: 0 -1px 0 rgba(0,0,0,.25);
116 | border-color: $secondaryColor $secondaryColor darken($secondaryColor, 15%);
117 | border-color: rgba(0,0,0,.1) rgba(0,0,0,.1) fadein(rgba(0,0,0,.1), 15%);
118 | }
119 |
120 | // Clearfix for clearing floats like a boss h5bp.com/q
121 | @mixin clearfix {
122 | zoom: 1;
123 | &:before, &:after {
124 | display: table;
125 | content: "";
126 | }
127 | &:after {
128 | clear: both;
129 | }
130 | }
131 | .clearfix { @include clearfix; }
132 |
133 | // Center-align a block level element
134 | @mixin center-block {
135 | display: block;
136 | margin: 0 auto;
137 | }
138 |
139 | // Sizing shortcuts
140 | @mixin size($height: 5px, $width: 5px) {
141 | height: $height;
142 | width: $width;
143 | }
144 | @mixin square($size: 5px) {
145 | @include size($size, $size);
146 | }
147 |
148 | // Input placeholder text
149 | @mixin placeholder($color: $grayLight) {
150 | :-moz-placeholder {
151 | color: $color;
152 | }
153 | ::-webkit-input-placeholder {
154 | color: $color;
155 | }
156 | }
157 |
158 | // Font Stacks
159 | @mixin shorthand-font($weight: normal, $size: 14px, $lineHeight: 20px) {
160 | font-size: $size;
161 | font-weight: $weight;
162 | line-height: $lineHeight;
163 | }
164 | @mixin sans-serif-font($weight: normal, $size: 14px, $lineHeight: 20px) {
165 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
166 | font-size: $size;
167 | font-weight: $weight;
168 | line-height: $lineHeight;
169 | }
170 | @mixin serif-font($weight: normal, $size: 14px, $lineHeight: 20px) {
171 | font-family: "Georgia", Times New Roman, Times, serif;
172 | font-size: $size;
173 | font-weight: $weight;
174 | line-height: $lineHeight;
175 | }
176 | @mixin monospace-font($weight: normal, $size: 12px, $lineHeight: 20px) {
177 | font-family: "Monaco", Courier New, monospace;
178 | font-size: $size;
179 | font-weight: $weight;
180 | line-height: $lineHeight;
181 | }
182 |
183 | // Grid System
184 | @mixin container {
185 | width: $siteWidth;
186 | margin: 0 auto;
187 | @include clearfix();
188 | }
189 | .container { @include container; }
190 |
191 | @mixin columns($columnSpan: 1) {
192 | width: ($gridColumnWidth * $columnSpan) + ($gridGutterWidth * ($columnSpan - 1));
193 | }
194 |
195 | @mixin offset($columnOffset: 1) {
196 | margin-left: ($gridColumnWidth * $columnOffset) + ($gridGutterWidth * ($columnOffset - 1)) + $extraSpace;
197 | }
198 |
199 | // Border Radius
200 | @mixin border-radius($radius: 5px) {
201 | -webkit-border-radius: $radius;
202 | -moz-border-radius: $radius;
203 | border-radius: $radius;
204 | }
205 |
206 | // Drop shadows
207 | @mixin box-shadow($shadow: 0 1px 3px rgba(0,0,0,.25)) {
208 | -webkit-box-shadow: $shadow;
209 | -moz-box-shadow: $shadow;
210 | box-shadow: $shadow;
211 | }
212 |
213 | // Transitions
214 | @mixin transition($transition) {
215 | -webkit-transition: $transition;
216 | -moz-transition: $transition;
217 | transition: $transition;
218 | }
219 |
220 | // Background clipping
221 | @mixin background-clip($clip) {
222 | -webkit-background-clip: $clip;
223 | -moz-background-clip: $clip;
224 | background-clip: $clip;
225 | }
226 |
227 | // CSS3 Content Columns
228 | @mixin content-columns($columnCount, $columnGap: 20px) {
229 | -webkit-column-count: $columnCount;
230 | -moz-column-count: $columnCount;
231 | column-count: $columnCount;
232 | -webkit-column-gap: $columnGap;
233 | -moz-column-gap: $columnGap;
234 | column-gap: $columnGap;
235 | }
236 |
237 | // Add an alphatransparency value to any background or border color (via Elyse Holladay)
238 | @mixin translucent-background($color: $white, $alpha: 1) {
239 | background-color: hsla(hue($color), saturation($color), lightness($color), $alpha);
240 | }
241 | @mixin translucent-border($color: $white, $alpha: 1) {
242 | border-color: hsla(hue($color), saturation($color), lightness($color), $alpha);
243 | background-clip: padding-box;
244 | }
245 |
246 | // Shared colors for buttons and alerts
247 | .btn,
248 | .alert-message {
249 | // Set text color
250 | &.danger,
251 | &.danger:hover,
252 | &.error,
253 | &.error:hover,
254 | &.success,
255 | &.success:hover,
256 | &.info,
257 | &.info:hover {
258 | color: $white
259 | }
260 | // Danger and error appear as red
261 | &.danger,
262 | &.error {
263 | @include gradientBar(#ee5f5b, #c43c35);
264 | }
265 | // Success appears as green
266 | &.success {
267 | @include gradientBar(#62c462, #57a957);
268 | }
269 | // Info appears as a neutral blue
270 | &.info {
271 | @include gradientBar(#5bc0de, #339bb9);
272 | }
273 | }
--------------------------------------------------------------------------------
/views/stylesheets/partials/_patterns.scss:
--------------------------------------------------------------------------------
1 | /* Patterns.scss
2 | * Repeatable UI elements outside the base styles provided from the scaffolding
3 | * ---------------------------------------------------------------------------- */
4 |
5 |
6 | // TOPBAR
7 | // ------
8 |
9 | // Topbar for Branding and Nav
10 | .topbar {
11 | height: 40px;
12 | position: fixed;
13 | top: 0;
14 | left: 0;
15 | right: 0;
16 | z-index: 10000;
17 | overflow: visible;
18 |
19 | // gradient is applied to it's own element because overflow visible is not honored by ie when filter is present
20 | .fill {
21 | background:#222;
22 | @include vertical-gradient(#333, #222);
23 | $shadow: 0 1px 3px rgba(0,0,0,.25), inset 0 -1px 0 rgba(0,0,0,.1);
24 | @include box-shadow($shadow);
25 | }
26 |
27 | // Links get text shadow
28 | a {
29 | color: $grayLight;
30 | text-shadow: 0 -1px 0 rgba(0,0,0,.25);
31 | }
32 |
33 | // Hover and active states
34 | a:hover,
35 | ul li.active a {
36 | background-color: #333;
37 | background-color: rgba(255,255,255,.05);
38 | color: $white;
39 | text-decoration: none;
40 | }
41 |
42 | // Website name
43 | h3 {
44 | position:relative;
45 | a {
46 | float: left;
47 | display: block;
48 | padding: 8px 20px 12px;
49 | margin-left: -20px; // negative indent to left-align the text down the page
50 | color: $white;
51 | font-size: 20px;
52 | font-weight: 200;
53 | line-height: 1;
54 | }
55 | }
56 |
57 | // Search Form
58 | form {
59 | float: left;
60 | margin: 5px 0 0 0;
61 | position: relative;
62 | @include opacity(100);
63 | input {
64 | background-color: #444;
65 | background-color: rgba(255,255,255,.3);
66 | @include sans-serif-font(13px, normal, 1);
67 | width: 220px;
68 | padding: 4px 9px;
69 | color: #fff;
70 | color: rgba(255,255,255,.75);
71 | border: 1px solid #111;
72 | @include border-radius(4px);
73 | $shadow: inset 0 1px 2px rgba(0,0,0,.1), 0 1px 0px rgba(255,255,255,.25);
74 | @include box-shadow($shadow);
75 | @include transition(none);
76 |
77 | // Placeholder text gets special styles; can't be bundled together though for some reason
78 | &:-moz-placeholder {
79 | color: $grayLighter;
80 | }
81 | &::-webkit-input-placeholder {
82 | color: $grayLighter;
83 | }
84 | // Hover states
85 | &:hover {
86 | background-color: $grayLight;
87 | background-color: rgba(255,255,255,.5);
88 | color: #fff;
89 | }
90 | // Focus states (we use .focused since IE8 and down doesn't support :focus)
91 | &:focus,
92 | &.focused {
93 | outline: none;
94 | background-color: #fff;
95 | color: $grayDark;
96 | text-shadow: 0 1px 0 #fff;
97 | border: 0;
98 | padding: 5px 10px;
99 | @include box-shadow(0 0 3px rgba(0,0,0,.15));
100 | }
101 | }
102 | }
103 |
104 | // Navigation
105 | ul {
106 | display: block;
107 | float: left;
108 | margin: 0 10px 0 0;
109 | position: relative;
110 | &.secondary-nav {
111 | float: right;
112 | margin-left: 10px;
113 | margin-right: 0;
114 | }
115 | li {
116 | display: block;
117 | float: left;
118 | font-size: 13px;
119 | a {
120 | display: block;
121 | float: none;
122 | padding: 10px 10px 11px;
123 | line-height: 19px;
124 | text-decoration: none;
125 | &:hover {
126 | color: #fff;
127 | text-decoration: none;
128 | }
129 | }
130 | &.active a {
131 | background-color: #222;
132 | background-color: rgba(0,0,0,.5);
133 | }
134 | }
135 |
136 | // Dropdowns
137 | &.primary-nav li ul {
138 | left: 0;
139 | }
140 | &.secondary-nav li ul {
141 | right: 0;
142 | }
143 | li.menu {
144 | position: relative;
145 | a.menu {
146 | &:after {
147 | width: 0px;
148 | height: 0px;
149 | display: inline-block;
150 | content: "↓";
151 | text-indent: -99999px;
152 | vertical-align: top;
153 | margin-top: 8px;
154 | margin-left: 4px;
155 | border-left: 4px solid transparent;
156 | border-right: 4px solid transparent;
157 | border-top: 4px solid #fff;
158 | @include opacity(50);
159 | }
160 | }
161 | &.open {
162 | a.menu,
163 | a:hover {
164 | background-color: #444;
165 | background-color: rgba(255,255,255,.1);
166 | *background-color: #444; /* IE6-7 */
167 | color: #fff;
168 | }
169 | ul {
170 | display: block;
171 | li {
172 | a {
173 | background-color: transparent;
174 | font-weight: normal;
175 | &:hover {
176 | background-color: rgba(255,255,255,.1);
177 | *background-color: #444; /* IE6-7 */
178 | color: #fff;
179 | }
180 | }
181 | &.active a {
182 | background-color: rgba(255,255,255,.1);
183 | font-weight: bold;
184 | }
185 | }
186 | }
187 | }
188 | }
189 | li ul {
190 | background-color: #333;
191 | float: left;
192 | display: none;
193 | position: absolute;
194 | top: 40px;
195 | min-width: 160px;
196 | max-width: 220px;
197 | _width: 160px;
198 | margin-left: 0;
199 | margin-right: 0;
200 | padding: 0;
201 | text-align: left;
202 | border: 0;
203 | zoom: 1;
204 | @include border-radius(0 0 5px 5px);
205 | @include box-shadow(0 1px 2px rgba(0,0,0,0.6));
206 | li {
207 | float: none;
208 | clear: both;
209 | display: block;
210 | background: none;
211 | font-size: 12px;
212 | a {
213 | display: block;
214 | padding: 6px 15px;
215 | clear: both;
216 | font-weight: normal;
217 | line-height: 19px;
218 | color: #bbb;
219 | &:hover {
220 | background-color: #333;
221 | background-color: rgba(255,255,255,.25);
222 | color: #fff;
223 | }
224 | }
225 |
226 | // Dividers (basically an hr)
227 | &.divider {
228 | height: 1px;
229 | overflow: hidden;
230 | background: #222;
231 | background: rgba(0,0,0,.2);
232 | border-bottom: 1px solid rgba(255,255,255,.1);
233 | margin: 5px 0;
234 | }
235 |
236 | // Section separaters
237 | span {
238 | clear: both;
239 | display: block;
240 | background: rgba(0,0,0,.2);
241 | padding: 6px 15px;
242 | cursor: default;
243 | color: $gray;
244 | border-top: 1px solid rgba(0,0,0,.2);
245 | }
246 | }
247 | }
248 | }
249 | }
250 |
251 |
252 | // PAGE HEADERS
253 | // ------------
254 |
255 | .hero-unit {
256 | background-color: #f5f5f5;
257 | margin-top: 60px;
258 | margin-bottom: 30px;
259 | padding: 60px;
260 | @include border-radius(6px);
261 | h1 {
262 | margin-bottom: 0;
263 | font-size: 60px;
264 | line-height: 1;
265 | letter-spacing: -1px;
266 | }
267 | p {
268 | font-size: 18px;
269 | font-weight: 200;
270 | line-height: $baseline * 1.5;
271 | }
272 | }
273 | footer {
274 | margin-top: $baseline - 1;
275 | padding-top: $baseline - 1;
276 | border-top: 1px solid #eee;
277 | }
278 |
279 | // PAGE HEADERS
280 | // ------------
281 |
282 | .page-header {
283 | margin-bottom: $baseline - 1;
284 | border-bottom: 1px solid #ddd;
285 | @include box-shadow(0 1px 0 rgba(255,255,255,.5));
286 | h1 {
287 | margin-bottom: ($baseline / 2) - 1px;
288 | }
289 | }
290 |
291 | // BUTTON STYLES
292 | // -------------
293 |
294 | @mixin btnColor($primaryColor, $secondaryColor) {
295 | @include vertical-gradient($primaryColor, $secondaryColor);
296 | text-shadow: 0 -1px 0 rgba(0,0,0,.25);
297 | border-color: $secondaryColor $secondaryColor darken($secondaryColor, 15%);
298 | border-color: rgba(0,0,0,.1) rgba(0,0,0,.1) fade-in(rgba(0,0,0,.1), .15);
299 | }
300 |
301 |
302 | // Base .btn styles
303 | .btn {
304 | // Button Base
305 | cursor: pointer;
306 | display: inline-block;
307 | @include vertical-three-colors-gradient(#fff, #fff, 0.25, darken(#fff, 10%));
308 | padding: 5px 14px 6px;
309 | text-shadow: 0 1px 1px rgba(255,255,255,.75);
310 | color: #333;
311 | font-size: 13px;
312 | line-height: normal;
313 | border: 1px solid #ccc;
314 | border-bottom-color: #bbb;
315 | @include border-radius(4px);
316 | $shadow: inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05);
317 | @include box-shadow($shadow);
318 |
319 | &:hover {
320 | background-position: 0 -15px;
321 | color: #333;
322 | text-decoration: none;
323 | }
324 |
325 | // Primary Button Type
326 | &.primary {
327 | color:#fff;
328 | @include btnColor($blue, $blueDark);
329 | }
330 |
331 | // Transitions
332 | @include transition(.1s linear all);
333 |
334 | // Active and Disabled states
335 | &.disabled {
336 | cursor: default;
337 | background-image: none;
338 | @include opacity(65);
339 | }
340 |
341 | &:disabled {
342 | // disabled pseudo can't be included with .disabled
343 | // def because IE8 and below will drop it ;_;
344 | cursor: default;
345 | background-image: none;
346 | @include opacity(65);
347 | }
348 |
349 | &:active {
350 | $shadow: inset 0 3px 7px rgba(0,0,0,.1), 0 1px 2px rgba(0,0,0,.05);
351 | @include box-shadow($shadow);
352 | }
353 |
354 | // Button Sizes
355 | &.large {
356 | font-size: 16px;
357 | line-height: normal;
358 | padding: 9px 14px 9px;
359 | @include border-radius(6px);
360 | }
361 |
362 | &.small {
363 | padding: 7px 9px 7px;
364 | font-size: 11px;
365 | }
366 |
367 | }
368 |
369 | // Help Firefox not be a jerk about adding extra padding to buttons
370 | button.btn,
371 | input[type=submit].btn {
372 | &::-moz-focus-inner {
373 | padding: 0;
374 | border: 0;
375 | }
376 | }
377 |
378 |
379 |
380 | // ERROR STYLES
381 | // ------------
382 |
383 | // Base alert styles
384 | .alert-message {
385 | @include btnColor(#fceec1, #eedc94);
386 | margin-bottom: $baseline;
387 | padding: 7px 14px;
388 | color: $grayDark;
389 | text-shadow: 0 1px 0 rgba(255,255,255,.5);
390 | border-width: 1px;
391 | border-style: solid;
392 | @include border-radius(4px);
393 | @include box-shadow(inset 0 1px 0 rgba(255,255,255,.25));
394 |
395 | // Remove extra margin from content
396 | h5 {
397 | line-height: $baseline;
398 | }
399 | p {
400 | margin-bottom: 0;
401 | }
402 | div {
403 | margin-top: 5px;
404 | margin-bottom: 2px;
405 | line-height: 28px;
406 | }
407 | .btn {
408 | // Provide actions with buttons
409 | @include box-shadow(0 1px 0 rgba(255,255,255,.25));
410 | }
411 | .close {
412 | float: right;
413 | margin-top: -2px;
414 | color: $black;
415 | font-size: 20px;
416 | font-weight: bold;
417 | text-shadow: 0 1px 0 rgba(255,255,255,1);
418 | @include opacity(20);
419 | &:hover {
420 | color: $black;
421 | text-decoration: none;
422 | @include opacity(40);
423 | }
424 | }
425 |
426 | &.block-message {
427 | background-image: none;
428 | background-color: lighten(#fceec1, 5%);
429 | padding: 14px;
430 | border-color: #fceec1;
431 | @include box-shadow(none);
432 |
433 | p {
434 | margin-right: 30px;
435 | }
436 | .alert-actions {
437 | margin-top: 5px;
438 | }
439 | &.error,
440 | &.success,
441 | &.info {
442 | color: $grayDark;
443 | text-shadow: 0 1px 0 rgba(255,255,255,.5);
444 | }
445 | &.error {
446 | background-color: lighten(#f56a66, 25%);
447 | border-color: lighten(#f56a66, 20%);
448 | }
449 | &.success {
450 | background-color: lighten(#62c462, 30%);
451 | border-color: lighten(#62c462, 25%);
452 | }
453 | &.info {
454 | background-color: lighten(#6bd0ee, 25%);
455 | border-color: lighten(#6bd0ee, 20%);
456 | }
457 | }
458 | }
459 |
460 | // NAVIGATION
461 | // ----------
462 |
463 | // Common tab and pill styles
464 | .tabs,
465 | .pills {
466 | margin: 0 0 20px;
467 | padding: 0;
468 | @include clearfix();
469 | li {
470 | display: inline;
471 | a {
472 | float: left;
473 | width: auto;
474 | }
475 | }
476 | }
477 |
478 | // Basic Tabs
479 | .tabs {
480 | width: 100%;
481 | border-bottom: 1px solid $grayLight;
482 | li {
483 | a {
484 | margin-bottom: -1px;
485 | margin-right: 2px;
486 | padding: 0 15px;
487 | line-height: ($baseline * 2) - 1;
488 | @include border-radius(3px 3px 0 0);
489 | &:hover {
490 | background-color: $grayLighter;
491 | border-bottom: 1px solid $grayLight;
492 | }
493 | }
494 | &.active a {
495 | background-color: #fff;
496 | padding: 0 14px;
497 | border: 1px solid #ccc;
498 | border-bottom: 0;
499 | color: $gray;
500 | }
501 | }
502 | }
503 |
504 | // Basic pill nav
505 | .pills {
506 | li {
507 | a {
508 | margin: 5px 3px 5px 0;
509 | padding: 0 15px;
510 | text-shadow: 0 1px 1px #fff;
511 | line-height: 30px;
512 | @include border-radius(15px);
513 | &:hover {
514 | background: $linkColorHover;
515 | color: #fff;
516 | text-decoration: none;
517 | text-shadow: 0 1px 1px rgba(0,0,0,.25);
518 | }
519 | }
520 | &.active a {
521 | background: $linkColor;
522 | color: #fff;
523 | text-shadow: 0 1px 1px rgba(0,0,0,.25);
524 | }
525 | }
526 | }
527 |
528 |
529 | // PAGINATION
530 | // ----------
531 |
532 | .pagination {
533 | height: $baseline * 2;
534 | margin: $baseline 0;
535 | ul {
536 | float: left;
537 | margin: 0;
538 | border: 1px solid #ddd;
539 | border: 1px solid rgba(0,0,0,.15);
540 | @include border-radius(3px);
541 | @include box-shadow(0 1px 2px rgba(0,0,0,.05));
542 | li {
543 | display: inline;
544 | a {
545 | float: left;
546 | padding: 0 14px;
547 | line-height: ($baseline * 2) - 2;
548 | border-right: 1px solid;
549 | border-right-color: #ddd;
550 | border-right-color: rgba(0,0,0,.15);
551 | *border-right-color: #ddd; /* IE6-7 */
552 | text-decoration: none;
553 | }
554 | a:hover,
555 | &.active a {
556 | background-color: lighten($blue, 45%);
557 | }
558 | &.disabled a,
559 | &.disabled a:hover {
560 | background-color: transparent;
561 | color: $grayLight;
562 | }
563 | &.next a {
564 | border: 0;
565 | }
566 | }
567 | }
568 | }
569 |
570 |
571 | // WELLS
572 | // -----
573 |
574 | .well {
575 | background-color: #f5f5f5;
576 | margin-bottom: 20px;
577 | padding: 19px;
578 | min-height: 20px;
579 | border: 1px solid #eee;
580 | border: 1px solid rgba(0,0,0,.05);
581 | @include border-radius(4px);
582 | @include box-shadow(inset 0 1px 1px rgba(0,0,0,.05));
583 | }
584 |
585 |
586 | // MODALS
587 | // ------
588 |
589 | .modal-backdrop {
590 | background-color: rgba(0,0,0,.5);
591 | position: fixed;
592 | top: 0;
593 | left: 0;
594 | right: 0;
595 | bottom: 0;
596 | z-index: 1000;
597 | }
598 | .modal {
599 | position: fixed;
600 | top: 50%;
601 | left: 50%;
602 | z-index: 2000;
603 | width: 560px;
604 | margin: -280px 0 0 -250px;
605 | background-color: $white;
606 | border: 1px solid #999;
607 | border: 1px solid rgba(0,0,0,.3);
608 | *border: 1px solid #999; /* IE6-7 */
609 | @include border-radius(6px);
610 | @include box-shadow(0 3px 7px rgba(0,0,0,0.3));
611 | @include background-clip(padding-box);
612 | .modal-header {
613 | border-bottom: 1px solid #eee;
614 | padding: 5px 20px;
615 | .close {
616 | position: absolute;
617 | right: 10px;
618 | top: 10px;
619 | color: #999;
620 | line-height:10px;
621 | font-size: 18px;
622 | }
623 | }
624 | .modal-body {
625 | padding: 20px;
626 | }
627 | .modal-footer {
628 | background-color: #f5f5f5;
629 | padding: 14px 20px 15px;
630 | border-top: 1px solid #ddd;
631 | @include border-radius(0 0 6px 6px);
632 | @include box-shadow(inset 0 1px 0 #fff);
633 | @include clearfix();
634 | margin-bottom: 0;
635 | .btn {
636 | float: right;
637 | margin-left: 10px;
638 | }
639 | }
640 | }
641 |
642 |
643 | // POPOVER ARROWS
644 | // --------------
645 |
646 | @mixin popover-arrow-above($arrowWidth: 5px) {
647 | bottom: 0;
648 | left: 50%;
649 | margin-left: -$arrowWidth;
650 | border-left: $arrowWidth solid transparent;
651 | border-right: $arrowWidth solid transparent;
652 | border-top: $arrowWidth solid #000;
653 | }
654 |
655 | @mixin popover-arrow-left($arrowWidth: 5px) {
656 | top: 50%;
657 | right: 0;
658 | margin-top: -$arrowWidth;
659 | border-top: $arrowWidth solid transparent;
660 | border-bottom: $arrowWidth solid transparent;
661 | border-left: $arrowWidth solid #000;
662 | }
663 |
664 | @mixin popover-arrow-below($arrowWidth: 5px) {
665 | top: 0;
666 | left: 50%;
667 | margin-left: -$arrowWidth;
668 | border-left: $arrowWidth solid transparent;
669 | border-right: $arrowWidth solid transparent;
670 | border-bottom: $arrowWidth solid #000;
671 | }
672 |
673 | @mixin popover-arrow-right($arrowWidth: 5px) {
674 | top: 50%;
675 | left: 0;
676 | margin-top: -$arrowWidth;
677 | border-top: $arrowWidth solid transparent;
678 | border-bottom: $arrowWidth solid transparent;
679 | border-right: $arrowWidth solid #000;
680 | }
681 |
682 |
683 | // TWIPSY
684 | // ------
685 |
686 | .twipsy {
687 | display: block;
688 | position: absolute;
689 | visibility: visible;
690 | padding: 5px;
691 | font-size: 11px;
692 | z-index: 1000;
693 | @include opacity(80);
694 | &.above .twipsy-arrow { @include popover-arrow-above(); }
695 | &.left .twipsy-arrow { @include popover-arrow-left(); }
696 | &.below .twipsy-arrow { @include popover-arrow-below(); }
697 | &.right .twipsy-arrow { @include popover-arrow-right(); }
698 | .twipsy-inner {
699 | padding: 3px 8px;
700 | background-color: #000;
701 | color: white;
702 | text-align: center;
703 | max-width: 200px;
704 | text-decoration: none;
705 | @include border-radius(4px);
706 | }
707 | .twipsy-arrow {
708 | position: absolute;
709 | width: 0;
710 | height: 0;
711 | }
712 | }
713 |
714 |
715 | // Background clipping
716 | @mixin background-clip($clip) {
717 | -webkit-background-clip: $clip;
718 | -moz-background-clip: $clip;
719 | background-clip: $clip;
720 | }
721 |
722 |
723 | // POPOVERS
724 | // --------
725 |
726 | .popover {
727 | position: absolute;
728 | top: 0;
729 | left: 0;
730 | z-index: 1000;
731 | padding: 5px;
732 | display: none;
733 | &.above .arrow { @include popover-arrow-above(); }
734 | &.right .arrow { @include popover-arrow-right(); }
735 | &.below .arrow { @include popover-arrow-below(); }
736 | &.left .arrow { @include popover-arrow-left(); }
737 | .arrow {
738 | position: absolute;
739 | width: 0;
740 | height: 0;
741 | }
742 | .inner {
743 | background-color: #333;
744 | background-color: rgba(0,0,0,.8);
745 | *background-color: #333; /* IE 6-7 */
746 | padding: 3px;
747 | overflow: hidden;
748 | width: 280px;
749 | @include border-radius(6px);
750 | @include box-shadow(0 3px 7px rgba(0,0,0,0.3));
751 | }
752 | .title {
753 | background-color: #f5f5f5;
754 | padding: 9px 15px;
755 | line-height: 1;
756 | @include border-radius(3px 3px 0 0);
757 | border-bottom:1px solid #eee;
758 | }
759 | .content {
760 | background-color: $white;
761 | padding: 14px;
762 | @include border-radius(0 0 3px 3px);
763 | @include background-clip(padding-box);
764 | p, ul, ol {
765 | margin-bottom: 0;
766 | }
767 | }
768 | }
769 |
--------------------------------------------------------------------------------
/public/js/faye-browser-min.js:
--------------------------------------------------------------------------------
1 | if(!this.Faye)Faye={};Faye.extend=function(a,b,c){if(!b)return a;for(var d in b){if(!b.hasOwnProperty(d))continue;if(a.hasOwnProperty(d)&&c===false)continue;if(a[d]!==b[d])a[d]=b[d]}return a};Faye.extend(Faye,{VERSION:'0.6.4',BAYEUX_VERSION:'1.0',ID_LENGTH:128,JSONP_CALLBACK:'jsonpcallback',CONNECTION_TYPES:['long-polling','cross-origin-long-polling','callback-polling','websocket','in-process'],MANDATORY_CONNECTION_TYPES:['long-polling','callback-polling','in-process'],ENV:(function(){return this})(),random:function(a){a=a||this.ID_LENGTH;if(a>32){var b=Math.ceil(a/32),c='';while(b--)c+=this.random(32);return c}var d=Math.pow(2,a)-1,f=d.toString(36).length,c=Math.floor(Math.random()*d).toString(36);while(c.length
0)j();i=false};var m=function(){h+=1;k()};m()},toJSON:function(a){if(this.stringify)return this.stringify(a,function(key,value){return(this[key]instanceof Array)?this[key]:value});return JSON.stringify(a)},timestamp:function(){var b=new Date(),c=b.getFullYear(),d=b.getMonth()+1,f=b.getDate(),g=b.getHours(),h=b.getMinutes(),i=b.getSeconds();var j=function(a){return a<10?'0'+a:String(a)};return j(c)+'-'+j(d)+'-'+j(f)+' '+j(g)+':'+j(h)+':'+j(i)}});Faye.Class=function(a,b){if(typeof a!=='function'){b=a;a=Object}var c=function(){if(!this.initialize)return this;return this.initialize.apply(this,arguments)||this};var d=function(){};d.prototype=a.prototype;c.prototype=new d();Faye.extend(c.prototype,b);return c};Faye.Namespace=Faye.Class({initialize:function(){this._c={}},exists:function(a){return this._c.hasOwnProperty(a)},generate:function(){var a=Faye.random();while(this._c.hasOwnProperty(a))a=Faye.random();return this._c[a]=a},release:function(a){delete this._c[a]}});Faye.Error=Faye.Class({initialize:function(a,b,c){this.code=a;this.params=Array.prototype.slice.call(b);this.message=c},toString:function(){return this.code+':'+this.params.join(',')+':'+this.message}});Faye.Error.parse=function(a){a=a||'';if(!Faye.Grammar.ERROR.test(a))return new this(null,[],a);var b=a.split(':'),c=parseInt(b[0]),d=b[1].split(','),a=b[2];return new this(c,d,a)};Faye.Error.versionMismatch=function(){return new this(300,arguments,"Version mismatch").toString()};Faye.Error.conntypeMismatch=function(){return new this(301,arguments,"Connection types not supported").toString()};Faye.Error.extMismatch=function(){return new this(302,arguments,"Extension mismatch").toString()};Faye.Error.badRequest=function(){return new this(400,arguments,"Bad request").toString()};Faye.Error.clientUnknown=function(){return new this(401,arguments,"Unknown client").toString()};Faye.Error.parameterMissing=function(){return new this(402,arguments,"Missing required parameter").toString()};Faye.Error.channelForbidden=function(){return new this(403,arguments,"Forbidden channel").toString()};Faye.Error.channelUnknown=function(){return new this(404,arguments,"Unknown channel").toString()};Faye.Error.channelInvalid=function(){return new this(405,arguments,"Invalid channel").toString()};Faye.Error.extUnknown=function(){return new this(406,arguments,"Unknown extension").toString()};Faye.Error.publishFailed=function(){return new this(407,arguments,"Failed to publish").toString()};Faye.Error.serverError=function(){return new this(500,arguments,"Internal server error").toString()};Faye.Deferrable={callback:function(a,b){if(!a)return;if(this._t==='succeeded')return a.apply(b,this._j);this._k=this._k||[];this._k.push([a,b])},errback:function(a,b){if(!a)return;if(this._t==='failed')return a.apply(b,this._j);this._l=this._l||[];this._l.push([a,b])},setDeferredStatus:function(){var a=Array.prototype.slice.call(arguments),b=a.shift(),c;this._t=b;this._j=a;if(b==='succeeded')c=this._k;else if(b==='failed')c=this._l;if(!c)return;var d;while(d=c.shift())d[0].apply(d[1],this._j)}};Faye.Publisher={countSubscribers:function(a){if(!this._3||!this._3[a])return 0;return this._3[a].length},addSubscriber:function(a,b,c){this._3=this._3||{};var d=this._3[a]=this._3[a]||[];d.push([b,c])},removeSubscriber:function(a,b,c){if(!this._3||!this._3[a])return;if(!b){delete this._3[a];return}var d=this._3[a],f=d.length;while(f--){if(b!==d[f][0])continue;if(c&&d[f][1]!==c)continue;d.splice(f,1)}},removeSubscribers:function(){this._3={}},publishEvent:function(){var b=Array.prototype.slice.call(arguments),c=b.shift();if(!this._3||!this._3[c])return;Faye.each(this._3[c],function(a){a[0].apply(a[1],b)})}};Faye.Timeouts={addTimeout:function(a,b,c,d){this._4=this._4||{};if(this._4.hasOwnProperty(a))return;var f=this;this._4[a]=Faye.ENV.setTimeout(function(){delete f._4[a];c.call(d)},1000*b)},removeTimeout:function(a){this._4=this._4||{};var b=this._4[a];if(!b)return;clearTimeout(b);delete this._4[a]}};Faye.Logging={LOG_LEVELS:{error:3,warn:2,info:1,debug:0},logLevel:'error',log:function(a,b){if(!Faye.logger)return;var c=Faye.Logging.LOG_LEVELS;if(c[Faye.Logging.logLevel]>c[b])return;var a=Array.prototype.slice.apply(a),d=' ['+b.toUpperCase()+'] [Faye',f=this.className,g=a.shift().replace(/\?/g,function(){try{return Faye.toJSON(a.shift())}catch(e){return'[Object]'}});for(var h in Faye){if(f)continue;if(typeof Faye[h]!=='function')continue;if(this instanceof Faye[h])f=h}if(f)d+='.'+f;d+='] ';Faye.logger(Faye.timestamp()+d+g)}};Faye.each(Faye.Logging.LOG_LEVELS,function(a,b){Faye.Logging[a]=function(){this.log(arguments,a)}});Faye.Grammar={LOWALPHA:/^[a-z]$/,UPALPHA:/^[A-Z]$/,ALPHA:/^([a-z]|[A-Z])$/,DIGIT:/^[0-9]$/,ALPHANUM:/^(([a-z]|[A-Z])|[0-9])$/,MARK:/^(\-|\_|\!|\~|\(|\)|\$|\@)$/,STRING:/^(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)| |\/|\*|\.))*$/,TOKEN:/^(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)))+$/,INTEGER:/^([0-9])+$/,CHANNEL_SEGMENT:/^(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)))+$/,CHANNEL_SEGMENTS:/^(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)))+(\/(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)))+)*$/,CHANNEL_NAME:/^\/(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)))+(\/(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)))+)*$/,WILD_CARD:/^\*{1,2}$/,CHANNEL_PATTERN:/^(\/(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)))+)*\/\*{1,2}$/,VERSION_ELEMENT:/^(([a-z]|[A-Z])|[0-9])(((([a-z]|[A-Z])|[0-9])|\-|\_))*$/,VERSION:/^([0-9])+(\.(([a-z]|[A-Z])|[0-9])(((([a-z]|[A-Z])|[0-9])|\-|\_))*)*$/,CLIENT_ID:/^((([a-z]|[A-Z])|[0-9]))+$/,ID:/^((([a-z]|[A-Z])|[0-9]))+$/,ERROR_MESSAGE:/^(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)| |\/|\*|\.))*$/,ERROR_ARGS:/^(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)| |\/|\*|\.))*(,(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)| |\/|\*|\.))*)*$/,ERROR_CODE:/^[0-9][0-9][0-9]$/,ERROR:/^([0-9][0-9][0-9]:(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)| |\/|\*|\.))*(,(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)| |\/|\*|\.))*)*:(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)| |\/|\*|\.))*|[0-9][0-9][0-9]::(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)| |\/|\*|\.))*)$/};Faye.Extensible={addExtension:function(a){this._5=this._5||[];this._5.push(a);if(a.added)a.added()},removeExtension:function(a){if(!this._5)return;var b=this._5.length;while(b--){if(this._5[b]!==a)continue;this._5.splice(b,1);if(a.removed)a.removed()}},pipeThroughExtensions:function(c,d,f,g){this.debug('Passing through ? extensions: ?',c,d);if(!this._5)return f.call(g,d);var h=this._5.slice();var i=function(a){if(!a)return f.call(g,a);var b=h.shift();if(!b)return f.call(g,a);if(b[c])b[c](a,i);else i(a)};i(d)}};Faye.extend(Faye.Extensible,Faye.Logging);Faye.Channel=Faye.Class({initialize:function(a){this.id=this.name=a},push:function(a){this.publishEvent('message',a)},isUnused:function(){return this.countSubscribers('message')===0}});Faye.extend(Faye.Channel.prototype,Faye.Publisher);Faye.extend(Faye.Channel,{HANDSHAKE:'/meta/handshake',CONNECT:'/meta/connect',SUBSCRIBE:'/meta/subscribe',UNSUBSCRIBE:'/meta/unsubscribe',DISCONNECT:'/meta/disconnect',META:'meta',SERVICE:'service',expand:function(a){var b=this.parse(a),c=['/**',a];var d=b.slice();d[d.length-1]='*';c.push(this.unparse(d));for(var f=1,g=b.length;f=Math.pow(2,32))this._e=0;return this._e.toString(36)},_z:function(a){Faye.extend(this._6,a);if(this._6.reconnect===this.HANDSHAKE&&this._1!==this.DISCONNECTED){this._1=this.UNCONNECTED;this._0=null;this._w()}},_A:function(a){if(!a.channel||!a.data)return;this.info('Client ? calling listeners for ? with ?',this._0,a.channel,a.data);this._2.distributeMessage(a)},_C:function(){if(!this._p)return;this._p=null;this.info('Closed connection for ?',this._0)},_w:function(){this._C();var a=this;Faye.ENV.setTimeout(function(){a.connect()},this._6.interval)}});Faye.extend(Faye.Client.prototype,Faye.Deferrable);Faye.extend(Faye.Client.prototype,Faye.Logging);Faye.extend(Faye.Client.prototype,Faye.Extensible);Faye.Transport=Faye.extend(Faye.Class({MAX_DELAY:0.0,batching:true,initialize:function(a,b){this.debug('Created new ? transport for ?',this.connectionType,b);this._8=a;this._a=b;this._f=[]},send:function(a,b){this.debug('Client ? sending message to ?: ?',this._8._0,this._a,a);if(!this.batching)return this.request([a],b);this._f.push(a);this._7=b;if(a.channel===Faye.Channel.HANDSHAKE)return this.flush();if(a.channel===Faye.Channel.CONNECT)this._q=a;this.addTimeout('publish',this.MAX_DELAY,this.flush,this)},flush:function(){this.removeTimeout('publish');if(this._f.length>1&&this._q)this._q.advice={timeout:0};this.request(this._f,this._7);this._q=null;this._f=[]},receive:function(a){this.debug('Client ? received from ?: ?',this._8._0,this._a,a);Faye.each(a,this._8.receiveMessage,this._8)},retry:function(a,b){var c=this;return function(){Faye.ENV.setTimeout(function(){c.request(a,2*b)},1000*b)}}}),{get:function(g,h,i,j){var k=g.endpoint;if(h===undefined)h=this.supportedConnectionTypes();Faye.asyncEach(this._r,function(b,c){var d=b[0],f=b[1];if(Faye.indexOf(h,d)<0)return c();f.isUsable(k,function(a){if(a)i.call(j,new f(g,k));else c()})},function(){throw new Error('Could not find a usable connection type for '+k);})},register:function(a,b){this._r.push([a,b]);b.prototype.connectionType=a},_r:[],supportedConnectionTypes:function(){return Faye.map(this._r,function(a){return a[0]})}});Faye.extend(Faye.Transport.prototype,Faye.Logging);Faye.extend(Faye.Transport.prototype,Faye.Timeouts);Faye.Event={_g:[],on:function(a,b,c,d){var f=function(){c.call(d)};if(a.addEventListener)a.addEventListener(b,f,false);else a.attachEvent('on'+b,f);this._g.push({_h:a,_s:b,_m:c,_n:d,_x:f})},detach:function(a,b,c,d){var f=this._g.length,g;while(f--){g=this._g[f];if((a&&a!==g._h)||(b&&b!==g._s)||(c&&c!==g._m)||(d&&d!==g._n))continue;if(g._h.removeEventListener)g._h.removeEventListener(g._s,g._x,false);else g._h.detachEvent('on'+g._s,g._x);this._g.splice(f,1);g=null}}};Faye.Event.on(Faye.ENV,'unload',Faye.Event.detach,Faye.Event);Faye.URI=Faye.extend(Faye.Class({queryString:function(){var c=[],d;Faye.each(this.params,function(a,b){c.push(encodeURIComponent(a)+'='+encodeURIComponent(b))});return c.join('&')},isLocal:function(){var a=Faye.URI.parse(Faye.ENV.location.href);var b=(a.hostname!==this.hostname)||(a.port!==this.port)||(a.protocol!==this.protocol);return!b},toURL:function(){var a=this.queryString();return this.protocol+this.hostname+':'+this.port+this.pathname+(a?'?'+a:'')}}),{parse:function(d,f){if(typeof d!=='string')return d;var g=new this();var h=function(b,c){d=d.replace(c,function(a){if(a)g[b]=a;return''})};h('protocol',/^https?\:\/+/);h('hostname',/^[^\/\:]+/);h('port',/^:[0-9]+/);Faye.extend(g,{protocol:'http://',hostname:Faye.ENV.location.hostname,port:Faye.ENV.location.port},false);if(!g.port)g.port=(g.protocol==='https://')?'443':'80';g.port=g.port.replace(/\D/g,'');var i=d.split('?'),j=i.shift(),k=i.join('?'),m=k?k.split('&'):[],o=m.length,l={};while(o--){i=m[o].split('=');l[decodeURIComponent(i[0]||'')]=decodeURIComponent(i[1]||'')}if(typeof f==='object')Faye.extend(l,f);g.pathname=j;g.params=l;return g}});if(!this.JSON){JSON={}}(function(){function k(a){return a<10?'0'+a:a}if(typeof Date.prototype.toJSON!=='function'){Date.prototype.toJSON=function(a){return this.getUTCFullYear()+'-'+k(this.getUTCMonth()+1)+'-'+k(this.getUTCDate())+'T'+k(this.getUTCHours())+':'+k(this.getUTCMinutes())+':'+k(this.getUTCSeconds())+'Z'};String.prototype.toJSON=Number.prototype.toJSON=Boolean.prototype.toJSON=function(a){return this.valueOf()}}var m=/[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,o=/[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,l,p,s={'\b':'\\b','\t':'\\t','\n':'\\n','\f':'\\f','\r':'\\r','"':'\\"','\\':'\\\\'},n;function r(c){o.lastIndex=0;return o.test(c)?'"'+c.replace(o,function(a){var b=s[a];return typeof b==='string'?b:'\\u'+('0000'+a.charCodeAt(0).toString(16)).slice(-4)})+'"':'"'+c+'"'}function q(a,b){var c,d,f,g,h=l,i,j=b[a];if(j&&typeof j==='object'&&typeof j.toJSON==='function'){j=j.toJSON(a)}if(typeof n==='function'){j=n.call(b,a,j)}switch(typeof j){case'string':return r(j);case'number':return isFinite(j)?String(j):'null';case'boolean':case'null':return String(j);case'object':if(!j){return'null'}l+=p;i=[];if(Object.prototype.toString.apply(j)==='[object Array]'){g=j.length;for(c=0;c=200&&a<300)||a===304||a===1223)g.receive(JSON.parse(h.responseText));else d()}catch(e){d()}finally{Faye.Event.detach(Faye.ENV,'beforeunload',i);h.onreadystatechange=function(){};h=null}};var i=function(){h.abort()};Faye.Event.on(Faye.ENV,'beforeunload',i);h.send(Faye.toJSON(b))}}),{isUsable:function(a,b,c){b.call(c,Faye.URI.parse(a).isLocal())}});Faye.Transport.register('long-polling',Faye.Transport.XHR);Faye.Transport.CORS=Faye.extend(Faye.Class(Faye.Transport,{request:function(a,b){var c=Faye.ENV.XDomainRequest?XDomainRequest:XMLHttpRequest,d=new c(),f=this.retry(a,b),g=this;d.open('POST',this._a,true);d.onload=function(){try{g.receive(JSON.parse(d.responseText))}catch(e){f()}finally{d.onload=d.onerror=null;d=null}};d.onerror=f;d.onprogress=function(){};d.send('message='+encodeURIComponent(Faye.toJSON(a)))}}),{isUsable:function(a,b,c){if(Faye.URI.parse(a).isLocal())return b.call(c,false);if(Faye.ENV.XDomainRequest)return b.call(c,true);if(Faye.ENV.XMLHttpRequest){var d=new Faye.ENV.XMLHttpRequest();return b.call(c,d.withCredentials!==undefined)}return b.call(c,false)}});Faye.Transport.register('cross-origin-long-polling',Faye.Transport.CORS);Faye.Transport.JSONP=Faye.extend(Faye.Class(Faye.Transport,{request:function(b,c){var d={message:Faye.toJSON(b)},f=document.getElementsByTagName('head')[0],g=document.createElement('script'),h=Faye.Transport.JSONP.getCallbackName(),i=Faye.URI.parse(this._a,d),j=this;var k=function(){if(!g.parentNode)return false;g.parentNode.removeChild(g);return true};Faye.ENV[h]=function(a){Faye.ENV[h]=undefined;try{delete Faye.ENV[h]}catch(e){}if(!k())return;j.receive(a)};Faye.ENV.setTimeout(function(){if(!Faye.ENV[h])return;k();j.request(b,2*c)},1000*c);i.params.jsonp=h;g.type='text/javascript';g.src=i.toURL();f.appendChild(g)}}),{_y:0,getCallbackName:function(){this._y+=1;return'__jsonp'+this._y+'__'},isUsable:function(a,b,c){b.call(c,true)}});Faye.Transport.register('callback-polling',Faye.Transport.JSONP);
--------------------------------------------------------------------------------