diff --git a/grunt.coffee b/grunt.coffee index 8b14515..e56c91c 100644 --- a/grunt.coffee +++ b/grunt.coffee @@ -91,12 +91,9 @@ module.exports = (grunt) -> install: src: [ - "src/index.html" - "src/priority.js" - "src/*_tmpl.js" - "src/style.css" "libs/jquery/jquery-1.7.2.js" "libs/require.js" + "libs/lawnchair/lawnchair.js" ] dest: "dist" @@ -106,7 +103,7 @@ module.exports = (grunt) -> uglify: {} - grunt.registerTask "default", "coffee:dev recess:dev haml:dev hamltojs:dev" + grunt.registerTask "default", "coffee:dev recess:dev haml:dev hamltojs:dev install" # _ _ _ __ __ _ _ _ _ _____ __ __ _ # | || | /_\ | \/ | | | |_ ___ | || |_ _| \/ | | @@ -143,7 +140,7 @@ module.exports = (grunt) -> grunt.registerHelper "templatize", (src, dest, done) -> file = grunt.file.read(src) out = path.basename(src, ".html") - grunt.file.write path.join(dest, out + ".js"), "define(" + _und.template(file).source + ");" + grunt.file.write path.join(dest, out + ".js"), "define(function() { return " + _und.template(result.stdout).source + "});" done() grunt.registerMultiTask "templatize", "Compile Underscored HTML to Javascript", -> @@ -169,7 +166,7 @@ module.exports = (grunt) -> grunt.utils.spawn args, (err, result) -> console.log err if err out = path.basename(src, ".haml") - grunt.file.write path.join(dest, out + ".js"), "define(" + _und.template(result.stdout).source + ");" + grunt.file.write path.join(dest, out + ".js"), "define(function() { return " + _und.template(result.stdout).source + "});" done() grunt.registerMultiTask "hamltojs", "Compile Underscored HAML to Javascript", -> diff --git a/grunt.js b/grunt.js index 55f2a88..b11bec7 100644 --- a/grunt.js +++ b/grunt.js @@ -111,7 +111,7 @@ module.exports = function(grunt) { findNestedDependencies: true }, install: { - src: ["src/index.html", "src/priority.js", "src/*_tmpl.js", "src/style.css", "libs/jquery/jquery-1.7.2.js", "libs/require.js"], + src: ["libs/jquery/jquery-1.7.2.js", "libs/require.js", "libs/lawnchair/lawnchair.js"], dest: "dist" }, mocha: { @@ -119,7 +119,7 @@ module.exports = function(grunt) { }, uglify: {} }); - grunt.registerTask("default", "coffee:dev recess:dev haml:dev hamltojs:dev"); + grunt.registerTask("default", "coffee:dev recess:dev haml:dev hamltojs:dev install"); grunt.registerHelper("haml", function(src, dest, done) { var args; args = { @@ -149,7 +149,7 @@ module.exports = function(grunt) { var file, out; file = grunt.file.read(src); out = path.basename(src, ".html"); - grunt.file.write(path.join(dest, out + ".js"), "define(" + _und.template(file).source + ");"); + grunt.file.write(path.join(dest, out + ".js"), "define(function() { return " + _und.template(result.stdout).source + "});"); return done(); }); grunt.registerMultiTask("templatize", "Compile Underscored HTML to Javascript", function() { @@ -176,7 +176,7 @@ module.exports = function(grunt) { console.log(err); } out = path.basename(src, ".haml"); - grunt.file.write(path.join(dest, out + ".js"), "define(" + _und.template(result.stdout).source + ");"); + grunt.file.write(path.join(dest, out + ".js"), "define(function() { return " + _und.template(result.stdout).source + "});"); return done(); }); }); diff --git a/libs/lawnchair/lawnchair.js b/libs/lawnchair/lawnchair.js new file mode 100644 index 0000000..260d051 --- /dev/null +++ b/libs/lawnchair/lawnchair.js @@ -0,0 +1,438 @@ +/** + * Lawnchair! + * --- + * clientside json store + * + */ +var Lawnchair = function (options, callback) { + // ensure Lawnchair was called as a constructor + if (!(this instanceof Lawnchair)) return new Lawnchair(options, callback); + + // lawnchair requires json + if (!JSON) throw 'JSON unavailable! Include http://www.json.org/json2.js to fix.' + // options are optional; callback is not + if (arguments.length <= 2 && arguments.length > 0) { + callback = (typeof arguments[0] === 'function') ? arguments[0] : arguments[1]; + options = (typeof arguments[0] === 'function') ? {} : arguments[0]; + } else { + throw 'Incorrect # of ctor args!' + } + // TODO perhaps allow for pub/sub instead? + if (typeof callback !== 'function') throw 'No callback was provided'; + + // default configuration + this.record = options.record || 'record' // default for records + this.name = options.name || 'records' // default name for underlying store + + // mixin first valid adapter + var adapter + // if the adapter is passed in we try to load that only + if (options.adapter) { + for (var i = 0, l = Lawnchair.adapters.length; i < l; i++) { + if (Lawnchair.adapters[i].adapter === options.adapter) { + adapter = Lawnchair.adapters[i].valid() ? Lawnchair.adapters[i] : undefined; + break; + } + } + // otherwise find the first valid adapter for this env + } + else { + for (var i = 0, l = Lawnchair.adapters.length; i < l; i++) { + adapter = Lawnchair.adapters[i].valid() ? Lawnchair.adapters[i] : undefined + if (adapter) break + } + } + + // we have failed + if (!adapter) throw 'No valid adapter.' + + // yay! mixin the adapter + for (var j in adapter) + this[j] = adapter[j] + + // call init for each mixed in plugin + for (var i = 0, l = Lawnchair.plugins.length; i < l; i++) + Lawnchair.plugins[i].call(this) + + // init the adapter + this.init(options, callback) +} + +Lawnchair.adapters = [] + +/** + * queues an adapter for mixin + * === + * - ensures an adapter conforms to a specific interface + * + */ +Lawnchair.adapter = function (id, obj) { + // add the adapter id to the adapter obj + // ugly here for a cleaner dsl for implementing adapters + obj['adapter'] = id + // methods required to implement a lawnchair adapter + var implementing = 'adapter valid init keys save batch get exists all remove nuke'.split(' ') + , indexOf = this.prototype.indexOf + // mix in the adapter + for (var i in obj) { + if (indexOf(implementing, i) === -1) throw 'Invalid adapter! Nonstandard method: ' + i + } + // if we made it this far the adapter interface is valid + // insert the new adapter as the preferred adapter + Lawnchair.adapters.splice(0,0,obj) +} + +Lawnchair.plugins = [] + +/** + * generic shallow extension for plugins + * === + * - if an init method is found it registers it to be called when the lawnchair is inited + * - yes we could use hasOwnProp but nobody here is an asshole + */ +Lawnchair.plugin = function (obj) { + for (var i in obj) + i === 'init' ? Lawnchair.plugins.push(obj[i]) : this.prototype[i] = obj[i] +} + +/** + * helpers + * + */ +Lawnchair.prototype = { + + isArray: Array.isArray || function(o) { return Object.prototype.toString.call(o) === '[object Array]' }, + + /** + * this code exists for ie8... for more background see: + * http://www.flickr.com/photos/westcoastlogic/5955365742/in/photostream + */ + indexOf: function(ary, item, i, l) { + if (ary.indexOf) return ary.indexOf(item) + for (i = 0, l = ary.length; i < l; i++) if (ary[i] === item) return i + return -1 + }, + + // awesome shorthand callbacks as strings. this is shameless theft from dojo. + lambda: function (callback) { + return this.fn(this.record, callback) + }, + + // first stab at named parameters for terse callbacks; dojo: first != best // ;D + fn: function (name, callback) { + return typeof callback == 'string' ? new Function(name, callback) : callback + }, + + // returns a unique identifier (by way of Backbone.localStorage.js) + // TODO investigate smaller UUIDs to cut on storage cost + uuid: function () { + var S4 = function () { + return (((1+Math.random())*0x10000)|0).toString(16).substring(1); + } + return (S4()+S4()+"-"+S4()+"-"+S4()+"-"+S4()+"-"+S4()+S4()+S4()); + }, + + // a classic iterator + each: function (callback) { + var cb = this.lambda(callback) + // iterate from chain + if (this.__results) { + for (var i = 0, l = this.__results.length; i < l; i++) cb.call(this, this.__results[i], i) + } + // otherwise iterate the entire collection + else { + this.all(function(r) { + for (var i = 0, l = r.length; i < l; i++) cb.call(this, r[i], i) + }) + } + return this + } +// -- +}; +// window.name code courtesy Remy Sharp: http://24ways.org/2009/breaking-out-the-edges-of-the-browser +Lawnchair.adapter('window-name', (function(index, store) { + if (typeof window==='undefined') { + window = { top: { } }; // node/optimizer compatibility + } + + var data = window.top.name ? JSON.parse(window.top.name) : {} + + return { + + valid: function () { + return typeof window.top.name != 'undefined' + }, + + init: function (options, callback) { + data[this.name] = data[this.name] || {index:[],store:{}} + index = data[this.name].index + store = data[this.name].store + this.fn(this.name, callback).call(this, this) + }, + + keys: function (callback) { + this.fn('keys', callback).call(this, index) + return this + }, + + save: function (obj, cb) { + // data[key] = value + ''; // force to string + // window.top.name = JSON.stringify(data); + var key = obj.key || this.uuid() + this.exists(key, function(exists) { + if (!exists) { + if (obj.key) delete obj.key + index.push(key) + } + store[key] = obj + window.top.name = JSON.stringify(data) // TODO wow, this is the only diff from the memory adapter + if (cb) { + obj.key = key + this.lambda(cb).call(this, obj) + } + }) + return this + }, + + batch: function (objs, cb) { + var r = [] + for (var i = 0, l = objs.length; i < l; i++) { + this.save(objs[i], function(record) { + r.push(record) + }) + } + if (cb) this.lambda(cb).call(this, r) + return this + }, + + get: function (keyOrArray, cb) { + var r; + if (this.isArray(keyOrArray)) { + r = [] + for (var i = 0, l = keyOrArray.length; i < l; i++) { + r.push(store[keyOrArray[i]]) + } + } else { + r = store[keyOrArray] + if (r) r.key = keyOrArray + } + if (cb) this.lambda(cb).call(this, r) + return this + }, + + exists: function (key, cb) { + this.lambda(cb).call(this, !!(store[key])) + return this + }, + + all: function (cb) { + var r = [] + for (var i = 0, l = index.length; i < l; i++) { + var obj = store[index[i]] + obj.key = index[i] + r.push(obj) + } + this.fn(this.name, cb).call(this, r) + return this + }, + + remove: function (keyOrArray, cb) { + var del = this.isArray(keyOrArray) ? keyOrArray : [keyOrArray] + for (var i = 0, l = del.length; i < l; i++) { + delete store[del[i]] + index.splice(this.indexOf(index, del[i]), 1) + } + window.top.name = JSON.stringify(data) + if (cb) this.lambda(cb).call(this) + return this + }, + + nuke: function (cb) { + store = {} + index = [] + window.top.name = JSON.stringify(data) + if (cb) this.lambda(cb).call(this) + return this + } + } +///// +})()) +/** + * dom storage adapter + * === + * - originally authored by Joseph Pecoraro + * + */ +// +// TODO does it make sense to be chainable all over the place? +// chainable: nuke, remove, all, get, save, all +// not chainable: valid, keys +// +Lawnchair.adapter('dom', (function() { + var storage = window.localStorage + // the indexer is an encapsulation of the helpers needed to keep an ordered index of the keys + var indexer = function(name) { + return { + // the key + key: name + '._index_', + // returns the index + all: function() { + var a = storage.getItem(this.key) + if (a) { + a = JSON.parse(a) + } + if (a === null) storage.setItem(this.key, JSON.stringify([])) // lazy init + return JSON.parse(storage.getItem(this.key)) + }, + // adds a key to the index + add: function (key) { + var a = this.all() + a.push(key) + storage.setItem(this.key, JSON.stringify(a)) + }, + // deletes a key from the index + del: function (key) { + var a = this.all(), r = [] + // FIXME this is crazy inefficient but I'm in a strata meeting and half concentrating + for (var i = 0, l = a.length; i < l; i++) { + if (a[i] != key) r.push(a[i]) + } + storage.setItem(this.key, JSON.stringify(r)) + }, + // returns index for a key + find: function (key) { + var a = this.all() + for (var i = 0, l = a.length; i < l; i++) { + if (key === a[i]) return i + } + return false + } + } + } + + // adapter api + return { + + // ensure we are in an env with localStorage + valid: function () { + return !!storage && function() { + // in mobile safari if safe browsing is enabled, window.storage + // is defined but setItem calls throw exceptions. + var success = true + var value = Math.random() + try { + storage.setItem(value, value) + } catch (e) { + success = false + } + storage.removeItem(value) + return success + }() + }, + + init: function (options, callback) { + this.indexer = indexer(this.name) + if (callback) this.fn(this.name, callback).call(this, this) + }, + + save: function (obj, callback) { + var key = obj.key ? this.name + '.' + obj.key : this.name + '.' + this.uuid() + // if the key is not in the index push it on + if (this.indexer.find(key) === false) this.indexer.add(key) + // now we kil the key and use it in the store colleciton + delete obj.key; + storage.setItem(key, JSON.stringify(obj)) + obj.key = key.slice(this.name.length + 1) + if (callback) { + this.lambda(callback).call(this, obj) + } + return this + }, + + batch: function (ary, callback) { + var saved = [] + // not particularily efficient but this is more for sqlite situations + for (var i = 0, l = ary.length; i < l; i++) { + this.save(ary[i], function(r){ + saved.push(r) + }) + } + if (callback) this.lambda(callback).call(this, saved) + return this + }, + + // accepts [options], callback + keys: function(callback) { + if (callback) { + var name = this.name + , keys = this.indexer.all().map(function(r){ return r.replace(name + '.', '') }) + this.fn('keys', callback).call(this, keys) + } + return this // TODO options for limit/offset, return promise + }, + + get: function (key, callback) { + if (this.isArray(key)) { + var r = [] + for (var i = 0, l = key.length; i < l; i++) { + var k = this.name + '.' + key[i] + var obj = storage.getItem(k) + if (obj) { + obj = JSON.parse(obj) + obj.key = key[i] + r.push(obj) + } + } + if (callback) this.lambda(callback).call(this, r) + } else { + var k = this.name + '.' + key + var obj = storage.getItem(k) + if (obj) { + obj = JSON.parse(obj) + obj.key = key + } + if (callback) this.lambda(callback).call(this, obj) + } + return this + }, + + exists: function (key, cb) { + var exists = this.indexer.find(this.name+'.'+key) === false ? false : true ; + this.lambda(cb).call(this, exists); + return this; + }, + // NOTE adapters cannot set this.__results but plugins do + // this probably should be reviewed + all: function (callback) { + var idx = this.indexer.all() + , r = [] + , o + , k + for (var i = 0, l = idx.length; i < l; i++) { + k = idx[i] //v + o = JSON.parse(storage.getItem(k)) + o.key = k.replace(this.name + '.', '') + r.push(o) + } + if (callback) this.fn(this.name, callback).call(this, r) + return this + }, + + remove: function (keyOrObj, callback) { + var key = this.name + '.' + ((keyOrObj.key) ? keyOrObj.key : keyOrObj) + this.indexer.del(key) + storage.removeItem(key) + if (callback) this.lambda(callback).call(this) + return this + }, + + nuke: function (callback) { + this.all(function(r) { + for (var i = 0, l = r.length; i < l; i++) { + this.remove(r[i]); + } + if (callback) this.lambda(callback).call(this) + }) + return this + } +}})()); diff --git a/src/edit_priority_template.haml b/src/edit_priority_template.haml new file mode 100644 index 0000000..5c42a3f --- /dev/null +++ b/src/edit_priority_template.haml @@ -0,0 +1,3 @@ +.edit-priority + .input.edit-priority-field(type="text" value="<%= p.name %>" data-for="<%= p.pos %>") + .button.delete-priority-field(data-for="<%= p.pos %>") diff --git a/src/index.haml b/src/index.haml index 3cdb1e6..2e8f5fb 100644 --- a/src/index.haml +++ b/src/index.haml @@ -12,13 +12,15 @@ %title Priority / Ignore %body - #content - #leftbrain.column - %h1.prioritize Today I Will Pay Attention To: - #priorities - #rightbrain.column - %h1.prioritize Today I Will Ignore: - #ignorities + #grid.container + .flow + .row-fluid + #leftbrain.span6 + %h1#prioritize(data-for="0") Today I Will Pay Attention To: + #priorities + #rightbrain.span6 + %h1#ignorize(data-for="0") Today I Will Ignore: + #ignorities #message(style="display:none") %h2 How to Use diff --git a/src/priority.coffee b/src/priority.coffee index b4ba3cb..d4763c0 100644 --- a/src/priority.coffee +++ b/src/priority.coffee @@ -2,47 +2,70 @@ require.config paths: 'jquery': 'jquery-1.7.2' -require ['jquery', 'priority_tmpl'], ($, priority_template) -> +require ['jquery', 'priority_tmpl', 'lawnchair'], ($, priority_template) -> class Prioritize - constructor(@repo) -> + constructor: (@repo) -> @repo.get 'priorities', (p) => @priorities = if p? and p.priorities? then p.priorities else [] @render() $('body').on 'click', @render - $('#thumbsup').on 'click', () => @newPriority('priority') - $('#thumbsdn').on 'click', () => @newPriority('ignore') + $('#prioritize').on 'click', (ev) => @editPriority(ev, 'priority') + $('#ignorize').on 'click', (ev) => @editPriority(ev, 'ignore') + + + editPriority: (ev, ty = 'parent') => + + + newPriority: (ty, ev) => + ev.stopPropagation() + return if $('.edit-priority').length > 0 + target = if ty == 'priority' then $('#priorities') else $('#ignorities') + target.append(edit_priorities_template({fur: ty})) + input = $('input.edit-priority-field', target) + + maybeNewPrioritySave = (ev) => + prioritySave = => + @priorities.push({cat: ty, name: input.val()}) + @save() + + code = if ev.keyCode then ev.keyCode else ev.which + return prioritySave() if code == 13 + return @cleanAndRender() if code == 27 + + input.on 'keyup', maybeNewPrioritySave + $('.delete-priority-field', target).on 'click', @render + input.focus() + + save: -> + @repo.save {key: 'priorities', 'priorities': @priorities}, () => + @render() + + clean: -> + @priorities = ({name: p.name, cat: p.cat} for p in @priorities when c.name.trim() != "") + + cleanAndRender: -> + @clean() + @render() save: -> @repo.save {key: 'priorities', 'priorities': @priorities}, => @render() render: => - priority_enumerate = (cat) -> - (c for c in @priorities if c.cat == cat) + priority_enumerate = (cat) => + r = [] + for i in [0...@priorities.length] + if @priorities[i].cat == cat + r.push({name: @priorities[i].name, cat: @priorities[i].cat, pos: i}) + r - priority_render = (cat) -> - $('#priority_list').html(priority_template({priorities: priority_enumerate('priority')})) - $('#ignore_list').html(priority_template({priorities: priority_enumerate('ignore')})) + $('#priorities').html(priority_template({priorities: priority_enumerate('priority')})) + $('#ignorities').html(priority_template({priorities: priority_enumerate('ignore')})) $ -> prioritize = new Lawnchair {name: 'Prioritize'}, -> - p = this - p.save - key: 'priorities', - priorities: [ - {cat: 'priority', name: 'Omaha, Stormy, Raeney'} - {cat: 'ignore', name: 'Politics'} - {cat: 'priority', name: 'Spiral Genetics'} - {cat: 'priority', name: 'Looking for Work'} - {cat: 'priority', name: 'Writing'} - {cat: 'priority', name: 'Productivity Core'} - {cat: 'priority', name: 'Story Core'} - {cat: 'ignore', name: 'Smut'} - {cat: 'ignore', name: 'Religious Arguments'} - {cat: 'ignore', name: 'PASWO'} - {cat: 'ignore', name: 'Twitter'} - ], -> - handler = new Prioritize(p) + handler = new Prioritize(this) + handler.render() diff --git a/src/style.less b/src/style.less index 6fe0f28..65048b4 100644 --- a/src/style.less +++ b/src/style.less @@ -55,41 +55,320 @@ input[type="search"]::-webkit-search-cancel-button { } -* { - border: 0; - margin: 0; - padding: 0; -} - - body { - font: 13pt/190% Calibri,Georgia,"Lucida Bright",Lucidabright,"Bitstream Vera Serif",serif; + font: 13pt/190% Montserrat,Calibri,Georgia,"Lucida Bright",Lucidabright,"Bitstream Vera Serif",serif; background-color: #fff; } -body { - margin: .25in .75in .25in 1.5in; -} -.clearoncolumn { - clear: none; +.clearfix { + *zoom: 1; + &:before, + &:after { + display: table; + content: ""; + } + &:after { + clear: both; + } } -@media only screen and (min-width: 480px) { - body { - margin: .25in .75in .25in 1.5in; - } - .clearoncolumn { - clear: none; - } +// Block level inputs +.input-block-level { + display: block; + width: 100%; + min-height: 28px; // Make inputs at least the height of their button counterpart + .box-sizing(border-box); // Makes inputs behave like true block-level elements } -@media only screen and (max-width: 479px) { - body { - margin: 2% 2% 0 6%; + +// GRID +// -------------------------------------------------- + +// Default 940px grid +// ------------------------- +@gridColumns: 12; +@gridColumnWidth: 60px; +@gridGutterWidth: 20px; +@gridRowWidth: (@gridColumns * @gridColumnWidth) + (@gridGutterWidth * (@gridColumns - 1)); + +// Fluid grid +// ------------------------- +@fluidGridColumnWidth: 6.382978723%; +@fluidGridGutterWidth: 2.127659574%; + +#grid { + + .core (@gridColumnWidth, @gridGutterWidth) { + + .spanX (@index) when (@index > 0) { + (~".span@{index}") { .span(@index); } + .spanX(@index - 1); } - .clearoncolumn { - clear: both; + .spanX (0) {} + + .offsetX (@index) when (@index > 0) { + (~".offset@{index}") { .offset(@index); } + .offsetX(@index - 1); } + .offsetX (0) {} + + .offset (@columns) { + margin-left: (@gridColumnWidth * @columns) + (@gridGutterWidth * (@columns + 1)); + } + + .span (@columns) { + width: (@gridColumnWidth * @columns) + (@gridGutterWidth * (@columns - 1)); + } + + .row { + margin-left: @gridGutterWidth * -1; + .clearfix(); + } + + [class*="span"] { + float: left; + margin-left: @gridGutterWidth; + } + + // Set the container width, and override it for fixed navbars in media queries + .container, + .navbar-fixed-top .container, + .navbar-fixed-bottom .container { .span(@gridColumns); } + + // generate .spanX and .offsetX + .spanX (@gridColumns); + .offsetX (@gridColumns); + + } + + .fluid (@fluidGridColumnWidth, @fluidGridGutterWidth) { + + .spanX (@index) when (@index > 0) { + (~".span@{index}") { .span(@index); } + .spanX(@index - 1); + } + .spanX (0) {} + + .span (@columns) { + width: (@fluidGridColumnWidth * @columns) + (@fluidGridGutterWidth * (@columns - 1)); + *width: (@fluidGridColumnWidth * @columns) + (@fluidGridGutterWidth * (@columns - 1)) - (.5 / @gridRowWidth * 100 * 1%); + } + + .row-fluid { + width: 100%; + .clearfix(); + [class*="span"] { + .input-block-level(); + float: left; + margin-left: @fluidGridGutterWidth; + *margin-left: @fluidGridGutterWidth - (.5 / @gridRowWidth * 100 * 1%); + } + [class*="span"]:first-child { + margin-left: 0; + } + + // generate .spanX + .spanX (@gridColumns); + } + + } + + .input(@gridColumnWidth, @gridGutterWidth) { + + .spanX (@index) when (@index > 0) { + (~"input.span@{index}, textarea.span@{index}, .uneditable-input.span@{index}") { .span(@index); } + .spanX(@index - 1); + } + .spanX (0) {} + + .span(@columns) { + width: ((@gridColumnWidth) * @columns) + (@gridGutterWidth * (@columns - 1)) - 10; + } + + input, + textarea, + .uneditable-input { + margin-left: 0; // override margin-left from core grid system + } + + // generate .spanX + .spanX (@gridColumns); + + } + +} + +@media (min-width: 768px) and (max-width: 979px) { + + // Fixed grid + #grid > .core(42px, 20px); + + // Fluid grid + #grid > .fluid(5.801104972%, 2.762430939%); + + // Input grid + #grid > .input(42px, 20px); + + // No need to reset .thumbnails here since it is the same @gridGutterWidth + +} + +@media (min-width: 1200px) { + + // Fixed grid + #grid > .core(70px, 30px); + + // Fluid grid + #grid > .fluid(5.982905983%, 2.564102564%); + + // Input grid + #grid > .input(70px, 30px); + + // Thumbnails + .thumbnails { + margin-left: -30px; + } + .thumbnails > li { + margin-left: 30px; + } + .row-fluid .thumbnails { + margin-left: 0; + } + +} + +@media (max-width: 480px) { + + // Smooth out the collapsing/expanding nav + .nav-collapse { + -webkit-transform: translate3d(0, 0, 0); // activate the GPU + } + + // Block level the page header small tag for readability + .page-header h1 small { + display: block; + line-height: @baseLineHeight; + } + + // Update checkboxes for iOS + input[type="checkbox"], + input[type="radio"] { + border: 1px solid #ccc; + } + + // Remove the horizontal form styles + .form-horizontal .control-group > label { + float: none; + width: auto; + padding-top: 0; + text-align: left; + } + + .modal-header .close { + padding: 10px; + margin: -10px; + } + + // Carousel + .carousel-caption { + position: static; + } + +} + + + +// LANDSCAPE PHONE TO SMALL DESKTOP & PORTRAIT TABLET +// -------------------------------------------------- + +@media (max-width: 767px) { + + // Padding to set content in a bit + body { + padding-left: 20px; + padding-right: 20px; + } + // Negative indent the now static "fixed" navbar + .navbar-fixed-top, + .navbar-fixed-bottom { + margin-left: -20px; + margin-right: -20px; + } + // Remove padding on container given explicit padding set on body + .container-fluid { + padding: 0; + } + + // TYPOGRAPHY + // ---------- + // Reset horizontal dl + .dl-horizontal { + dt { + float: none; + clear: none; + width: auto; + text-align: left; + } + dd { + margin-left: 0; + } + } + + // GRID & CONTAINERS + // ----------------- + // Remove width from containers + .container { + width: auto; + } + // Fluid rows + .row-fluid { + width: 100%; + } + // Undo negative margin on rows and thumbnails + .row, + .thumbnails { + margin-left: 0; + } + // Make all grid-sized elements block level again + [class*="span"], + .row-fluid [class*="span"] { + float: none; + display: block; + width: auto; + margin-left: 0; + } + + // FORM FIELDS + // ----------- + // Make span* classes full width + .input-large, + .input-xlarge, + .input-xxlarge, + input[class*="span"], + select[class*="span"], + textarea[class*="span"], + .uneditable-input { + .input-block-level(); + } + // But don't let it screw up prepend/append inputs + .input-prepend input, + .input-append input, + .input-prepend input[class*="span"], + .input-append input[class*="span"] { + display: inline-block; // redeclare so they don't wrap to new lines + width: auto; + } + +} + +@baseLineHeight: 18px; + +// Box sizing +.box-sizing(@boxmodel) { + -webkit-box-sizing: @boxmodel; + -moz-box-sizing: @boxmodel; + -ms-box-sizing: @boxmodel; + box-sizing: @boxmodel; } p,li,ul { @@ -110,92 +389,23 @@ ul + p { margin-top: 1em; } -#content { - max-width: 732px; -} - -#braindiv { - #msg { - font-size: 24px; - color: #999; +#prioritize { background: url(thumbsup.png) top left no-repeat; } +#ignorize { background: url(thumbsdown.png) top left no-repeat; } - p { - width: 7em; - font-size: 20px; - margin: -3px 0 -8px 0; - padding: 0 0 0 0; - .doing { - color: #c00; - } - } - } -} - -#braindiv { - float: left; - width: 45%; - padding-right: 5%; - - #img { - width: 164px; - height: 226px; - background: url(brain-in-a-jar.jpg) top right no-repeat; - padding: 0 0 1em 0; - } -} - -@media only screen and (min-width: 480px) { - #braindiv { - float: left; - width: 45%; - padding-right: 5%; - - #img { - width: 164px; - height: 226px; - background: url(brain-in-a-jar.jpg) top right no-repeat; - padding: 0 0 1em 0; - } - } -} - - -@media only screen and (max-width: 479px) { - #braindiv { - width: 100%; - - #img { - float: left; - width: 37.5%; - min-width: 122px; - height: 165px; - background: url(brain-in-a-jar-sm.jpg) top right no-repeat; - padding: 0 0 1em 0; - } - - #msg { - float: right; - width: 60.353125%; - } - padding-bottom: 2em; - } -} - - - -#rightnow { - float: left; - width: 49.9%; +#grid { + margin: 0 auto; h1 { display: block; - float: left; padding-bottom: 0; + min-height: 72px; border: none; color: #333; - font-size: 20px; + font-size: 24px; font-weight: normal; line-height: 1.2; + padding-left: 80px; + padding-top: 18px; strong { font-weight: bold; }