Explorar o código

Implemented creation and update forms for most types

Markus Ochel %!s(int64=13) %!d(string=hai) anos
pai
achega
3b01518454
Modificáronse 47 ficheiros con 1190 adicións e 410 borrados
  1. 117 2
      admin/controllers/authors.coffee
  2. 112 2
      admin/controllers/blocks.coffee
  3. 1 1
      admin/controllers/collections.coffee
  4. 97 2
      admin/controllers/contacts.coffee
  5. 4 4
      admin/controllers/filter-box.coffee
  6. 2 1
      admin/controllers/sites.coffee
  7. 106 2
      admin/controllers/sponsors.coffee
  8. 4 0
      admin/kanso.json
  9. 2 0
      admin/models/author.coffee
  10. 3 1
      admin/models/block.coffee
  11. 3 1
      admin/models/site.coffee
  12. 5 2
      admin/server/rewrites.coffee
  13. 7 1
      admin/server/shows.coffee
  14. 142 0
      admin/static/css/common.styl
  15. 3 1
      admin/static/css/index.styl
  16. 45 0
      admin/static/css/mixin.styl
  17. 45 0
      admin/static/css/responsive.styl
  18. 20 147
      admin/static/css/theme.styl
  19. 46 0
      admin/templates/author-form.html
  20. 38 0
      admin/templates/block-form.html
  21. 5 5
      admin/templates/collection-form.html
  22. 1 1
      admin/templates/collections.html
  23. 19 0
      admin/templates/colors.html
  24. 27 0
      admin/templates/contact-form.html
  25. 1 1
      admin/templates/filter-box.html
  26. 21 9
      admin/templates/site-form.html
  27. 1 1
      admin/templates/sites.html
  28. 52 0
      admin/templates/sponsor-form.html
  29. 1 1
      site/kanso.json
  30. 6 1
      site/lib/app.coffee
  31. 34 20
      site/server/lists.coffee
  32. 3 0
      site/server/rewrites.coffee
  33. 2 1
      site/server/shows.coffee
  34. 4 1
      site/static/css/index.styl
  35. 0 94
      site/static/css/mixin.styl
  36. 6 0
      site/static/css/responsive.styl
  37. 73 0
      site/static/css/setup-theme.styl
  38. 77 104
      site/static/css/theme.styl
  39. 3 0
      site/static/css/themes/blue.styl
  40. 3 0
      site/static/css/themes/brown.styl
  41. 3 0
      site/static/css/themes/default.styl
  42. 3 0
      site/static/css/themes/green.styl
  43. 3 0
      site/static/css/themes/grey.styl
  44. 3 0
      site/static/css/themes/teal.styl
  45. 5 1
      site/templates/404.html
  46. 0 1
      site/templates/area.html
  47. 32 2
      site/templates/base.html

+ 117 - 2
admin/controllers/authors.coffee

@@ -3,10 +3,110 @@ Spine       = require('spine/core')
 templates   = require('duality/templates')
 
 Author      = require('models/author')
+Site        = require('models/site')
 
 
-class Authors extends Spine.Controller
-  className: 'authors panel'
+class AuthorForm extends Spine.Controller
+  className: 'author form panel'
+
+  elements:
+    '.item-title':             'itemTitle'
+    '.error-message':          'errorMessage'
+    'form':                    'form'
+    'select[name=site]':       'formSite'
+    'select[name=links]':      'formLinks'
+    '.save-button':            'saveButton'
+    '.cancel-button':          'cancelButton'
+
+  events:
+    'submit form':              'preventSubmit'
+    'click .save-button':       'save'
+    'click .cancel-button':     'cancel'
+    'click .delete-button':     'destroy'
+    'change select[name=site]': 'siteChange'
+
+  constructor: ->
+    super
+    @active @render
+
+  render: (params) ->
+    @editing = params.id?
+    if @editing
+      @copying = params.id.split('-')[0] is 'copy'
+      if @copying
+        @title = 'Copy Author'
+        @item = Author.find(params.id.split('-')[1]).dup()
+      else
+        @item = Author.find(params.id)
+        @title = @item.name
+    else
+      @title = 'New Author'
+      @item = {}
+    
+    @item.sites = Site.all().sort(Site.nameSort)
+    @html templates.render('author-form.html', {}, @item)
+
+    @itemTitle.html @title
+    
+    # Set few initial form values
+    if @editing
+      @formSite.val(@item.site)
+      @formLinks.val(JSON.stringify(@item.links))
+    else
+      @formSite.val(@stack.stack.filterBox.siteId)
+    @siteChange()
+
+  siteChange: ->
+    site = Site.find(@formSite.val())
+    @formSite.parents('.field').find('.site-selected').html "<div class=\"site-name theme-#{site.theme}\">#{site.name_html}</div>"
+
+  save: (e) ->
+    e.preventDefault()
+    if @editing
+      @item.fromForm(@form)
+    else
+      @item = new Author().fromForm(@form)
+
+    # Transform some fields before saving
+    @item.links = JSON.parse(@item.links)
+    
+    # Save the item and make sure it validates
+    if @item.save()
+      @back()
+    else
+      msg = @item.validate()
+      @showError msg
+
+  showError: (msg) ->
+    @errorMessage.html(msg).show()
+    @el.scrollTop(0, 0)
+  
+  destroy: ->
+    if @item and confirm "Are you sure you want to delete this #{@item.constructor.name}?"
+      @item.destroy()
+      @back()
+
+  cancel: (e) ->
+    e.preventDefault
+    if @dirtyForm
+      if confirm "You may have some unsaved changes.\nAre you sure you want to cancel?"
+        @back()
+    else
+      @back()
+
+  back: ->
+    @navigate('/authors/list')
+
+  preventSubmit: (e) ->
+    e.preventDefault
+    
+  deactivate: ->
+    super
+    @el.scrollTop(0, 0)
+
+
+class AuthorList extends Spine.Controller
+  className: 'author list panel'
 
   constructor: ->
     super
@@ -24,4 +124,19 @@ class Authors extends Spine.Controller
     @render()
 
 
+class Authors extends Spine.Stack
+  className: 'authors panel'
+
+  controllers:
+    list: AuthorList
+    form: AuthorForm
+
+  default: 'list'
+
+  routes:
+    '/authors/list': 'list'
+    '/author/new':   'form'
+    '/author/:id':   'form'
+
+
 module.exports = Authors

+ 112 - 2
admin/controllers/blocks.coffee

@@ -3,10 +3,105 @@ Spine       = require('spine/core')
 templates   = require('duality/templates')
 
 Block       = require('models/block')
+Site        = require('models/site')
 
 
-class Blocks extends Spine.Controller
-  className: 'blocks panel'
+class BlockForm extends Spine.Controller
+  className: 'block form panel'
+
+  elements:
+    '.item-title':             'itemTitle'
+    '.error-message':          'errorMessage'
+    'form':                    'form'
+    'select[name=site]':       'formSite'
+    '.save-button':            'saveButton'
+    '.cancel-button':          'cancelButton'
+
+  events:
+    'submit form':              'preventSubmit'
+    'click .save-button':       'save'
+    'click .cancel-button':     'cancel'
+    'click .delete-button':     'destroy'
+    'change select[name=site]': 'siteChange'
+
+  constructor: ->
+    super
+    @active @render
+
+  render: (params) ->
+    @editing = params.id?
+    if @editing
+      @copying = params.id.split('-')[0] is 'copy'
+      if @copying
+        @title = 'Copy Block'
+        @item = Block.find(params.id.split('-')[1]).dup()
+      else
+        @item = Block.find(params.id)
+        @title = @item.name
+    else
+      @title = 'New Block'
+      @item = {}
+    
+    @item.sites = Site.all().sort(Site.nameSort)
+    @html templates.render('block-form.html', {}, @item)
+
+    @itemTitle.html @title
+    
+    # Set few initial form values
+    if @editing
+      @formSite.val(@item.site)
+    else
+      @formSite.val(@stack.stack.filterBox.siteId)
+    @siteChange()
+
+  siteChange: ->
+    site = Site.find(@formSite.val())
+    @formSite.parents('.field').find('.site-selected').html "<div class=\"site-name theme-#{site.theme}\">#{site.name_html}</div>"
+
+  save: (e) ->
+    e.preventDefault()
+    if @editing
+      @item.fromForm(@form)
+    else
+      @item = new Block().fromForm(@form)
+    
+    # Save the item and make sure it validates
+    if @item.save()
+      @back()
+    else
+      msg = @item.validate()
+      @showError msg
+
+  showError: (msg) ->
+    @errorMessage.html(msg).show()
+    @el.scrollTop(0, 0)
+  
+  destroy: ->
+    if @item and confirm "Are you sure you want to delete this #{@item.constructor.name}?"
+      @item.destroy()
+      @back()
+
+  cancel: (e) ->
+    e.preventDefault
+    if @dirtyForm
+      if confirm "You may have some unsaved changes.\nAre you sure you want to cancel?"
+        @back()
+    else
+      @back()
+
+  back: ->
+    @navigate('/blocks/list')
+
+  preventSubmit: (e) ->
+    e.preventDefault
+    
+  deactivate: ->
+    super
+    @el.scrollTop(0, 0)
+
+
+class BlockList extends Spine.Controller
+  className: 'block list panel'
 
   constructor: ->
     super
@@ -24,4 +119,19 @@ class Blocks extends Spine.Controller
     @render()
 
 
+class Blocks extends Spine.Stack
+  className: 'blocks panel'
+
+  controllers:
+    list: BlockList
+    form: BlockForm
+
+  default: 'list'
+
+  routes:
+    '/blocks/list': 'list'
+    '/block/new':   'form'
+    '/block/:id':   'form'
+
+
 module.exports = Blocks

+ 1 - 1
admin/controllers/collections.coffee

@@ -60,7 +60,7 @@ class CollectionForm extends Spine.Controller
 
   siteChange: ->
     site = Site.find(@formSite.val())
-    @formSite.parents('.field').find('.site-selected').html "<div class=\"site-name\">#{site.name_html}</div>"
+    @formSite.parents('.field').find('.site-selected').html "<div class=\"site-name theme-#{site.theme}\">#{site.name_html}</div>"
 
   save: (e) ->
     e.preventDefault()

+ 97 - 2
admin/controllers/contacts.coffee

@@ -5,8 +5,88 @@ templates   = require('duality/templates')
 Contact     = require('models/contact')
 
 
-class Contacts extends Spine.Controller
-  className: 'contacts panel'
+class ContactForm extends Spine.Controller
+  className: 'contact form panel'
+
+  elements:
+    '.item-title':        'itemTitle'
+    '.error-message':     'errorMessage'
+    'form':               'form'
+    '.save-button':       'saveButton'
+    '.cancel-button':     'cancelButton'
+
+  events:
+    'submit form':          'preventSubmit'
+    'click .save-button':   'save'
+    'click .cancel-button': 'cancel'
+    'click .delete-button': 'destroy'
+
+  constructor: ->
+    super
+    @active @render
+
+  render: (params) ->
+    @editing = params.id?
+    if @editing
+      @copying = params.id.split('-')[0] is 'copy'
+      if @copying
+        @title = 'Copy Contact'
+        @item = Contact.find(params.id.split('-')[1]).dup()
+      else
+        @item = Contact.find(params.id)
+        @title = @item.name
+    else
+      @title = 'New Contact'
+      @item = {}
+    
+    @html templates.render('contact-form.html', {}, @item)
+
+    @itemTitle.html @title
+
+  save: (e) ->
+    e.preventDefault()
+    if @editing
+      @item.fromForm(@form)
+    else
+      @item = new Contact().fromForm(@form)
+    
+    # Save the item and make sure it validates
+    if @item.save()
+      @back()
+    else
+      msg = @item.validate()
+      @showError msg
+
+  showError: (msg) ->
+    @errorMessage.html(msg).show()
+    @el.scrollTop(0, 0)
+  
+  destroy: ->
+    if @item and confirm "Are you sure you want to delete this #{@item.constructor.name}?"
+      @item.destroy()
+      @back()
+  
+  cancel: (e) ->
+    e.preventDefault
+    if @dirtyForm
+      if confirm "You may have some unsaved changes.\nAre you sure you want to cancel?"
+        @back()
+    else
+      @back()
+
+  back: ->
+    @navigate('/contacts/list')
+
+  preventSubmit: (e) ->
+    e.preventDefault
+    
+  deactivate: ->
+    super
+    @el.scrollTop(0, 0)
+
+
+class ContactList extends Spine.Controller
+  className: 'contact list panel'
 
   constructor: ->
     super
@@ -24,4 +104,19 @@ class Contacts extends Spine.Controller
     @render()
 
 
+class Contacts extends Spine.Stack
+  className: 'contacts panel'
+
+  controllers:
+    list: ContactList
+    form: ContactForm
+
+  default: 'list'
+
+  routes:
+    '/contacts/list': 'list'
+    '/contact/new':   'form'
+    '/contact/:id':   'form'
+
+
 module.exports = Contacts

+ 4 - 4
admin/controllers/filter-box.coffee

@@ -1,5 +1,5 @@
 Spine       = require('spine/core')
-# $           = Spine.$
+$           = Spine.$
 templates   = require('duality/templates')
 
 Site  = require('models/site')
@@ -26,7 +26,7 @@ class FilterBox extends Spine.Controller
     @selectedSite = $(@el).find('.selected-site')
     @siteSelector = $(@el).find('ul.site-selector')
     
-    @filterInput.on 'keypress', (e) =>
+    @filterInput.on 'keyup', (e) =>
       @filter()
     
     @selectedSite.on 'click', (e) =>
@@ -34,7 +34,7 @@ class FilterBox extends Spine.Controller
     
     @siteSelector.find('li').on 'click', (e) =>
       $item = $(e.currentTarget)
-      @selectedSite.find('> span').html($item.html())
+      @selectedSite.find('> span').html($item.html()).attr('class', $item.attr('class'))
       @siteId = $item.attr('data-id')
       @siteSelector
         .hide()
@@ -45,7 +45,7 @@ class FilterBox extends Spine.Controller
       @filter()
 
   filter: =>
-    @query = @filterInput.val()
+    @query = $.trim(@filterInput.val())
     Spine.trigger 'filterbox:change',
       query: @query
       siteId: @siteId

+ 2 - 1
admin/controllers/sites.coffee

@@ -1,6 +1,7 @@
 Spine       = require('spine/core')
 # $           = Spine.$
 templates   = require('duality/templates')
+settings    = require('settings/root')
 
 Site        = require('models/site')
 
@@ -41,7 +42,7 @@ class SiteForm extends Spine.Controller
       @title = 'New Site'
       @item = {}
     
-    @item.themes = ['default','blue','green']
+    @item.themes = settings.app.themes
     @html templates.render('site-form.html', {}, @item)
 
     @itemTitle.html @title

+ 106 - 2
admin/controllers/sponsors.coffee

@@ -3,10 +3,99 @@ Spine       = require('spine/core')
 templates   = require('duality/templates')
 
 Sponsor     = require('models/sponsor')
+Contact     = require('models/contact')
 
 
-class Sponsors extends Spine.Controller
-  className: 'sponsors panel'
+class SponsorForm extends Spine.Controller
+  className: 'sponsor form panel'
+
+  elements:
+    '.item-title':             'itemTitle'
+    '.error-message':          'errorMessage'
+    'form':                    'form'
+    'select[name=contact_id]': 'formContactId'
+    'select[name=format]':     'formFormat'
+    '.save-button':            'saveButton'
+    '.cancel-button':          'cancelButton'
+
+  events:
+    'submit form':          'preventSubmit'
+    'click .save-button':   'save'
+    'click .cancel-button': 'cancel'
+    'click .delete-button': 'destroy'
+
+  constructor: ->
+    super
+    @active @render
+
+  render: (params) ->
+    @editing = params.id?
+    if @editing
+      @copying = params.id.split('-')[0] is 'copy'
+      if @copying
+        @title = 'Copy Sponsor'
+        @item = Sponsor.find(params.id.split('-')[1]).dup()
+      else
+        @item = Sponsor.find(params.id)
+        @title = @item.name
+    else
+      @title = 'New Sponsor'
+      @item = {}
+    
+    @item.contacts = Contact.all().sort(Contact.nameSort)
+    @html templates.render('sponsor-form.html', {}, @item)
+
+    @itemTitle.html @title
+    
+    # Set few initial form values
+    if @editing
+      @formContactId.val(@item.contact_id)
+      @formFormat.val(@item.format)
+
+  save: (e) ->
+    e.preventDefault()
+    if @editing
+      @item.fromForm(@form)
+    else
+      @item = new Sponsor().fromForm(@form)
+    
+    # Save the item and make sure it validates
+    if @item.save()
+      @back()
+    else
+      msg = @item.validate()
+      @showError msg
+
+  showError: (msg) ->
+    @errorMessage.html(msg).show()
+    @el.scrollTop(0, 0)
+  
+  destroy: ->
+    if @item and confirm "Are you sure you want to delete this #{@item.constructor.name}?"
+      @item.destroy()
+      @back()
+  
+  cancel: (e) ->
+    e.preventDefault
+    if @dirtyForm
+      if confirm "You may have some unsaved changes.\nAre you sure you want to cancel?"
+        @back()
+    else
+      @back()
+
+  back: ->
+    @navigate('/sponsors/list')
+
+  preventSubmit: (e) ->
+    e.preventDefault
+    
+  deactivate: ->
+    super
+    @el.scrollTop(0, 0)
+
+
+class SponsorList extends Spine.Controller
+  className: 'sponsor list panel'
 
   constructor: ->
     super
@@ -24,4 +113,19 @@ class Sponsors extends Spine.Controller
     @render()
 
 
+class Sponsors extends Spine.Stack
+  className: 'sponsors panel'
+
+  controllers:
+    list: SponsorList
+    form: SponsorForm
+
+  default: 'list'
+
+  routes:
+    '/sponsors/list': 'list'
+    '/sponsor/new':   'form'
+    '/sponsor/:id':   'form'
+
+
 module.exports = Sponsors

+ 4 - 0
admin/kanso.json

@@ -33,5 +33,9 @@
     "jquery": null,
     "spine": null,
     "underscore": null
+  },
+
+  "app": {
+    "themes": ["default","blue","green","brown","teal","grey"]
   }
 }

+ 2 - 0
admin/models/author.coffee

@@ -12,5 +12,7 @@ class Author extends BaseModel
     
   validate: ->
     return 'Name is required' unless @name
+    return 'Email is required' unless @email
+    return 'Bio is required' unless @bio
 
 module.exports = Author

+ 3 - 1
admin/models/block.coffee

@@ -11,7 +11,9 @@ class Block extends BaseModel
   @queryOn: ['name','code']
     
   validate: ->
-    return 'Name is required' unless @name
+    return 'Site is required' unless @site
     return 'Code is required' unless @code
+    return 'Name is required' unless @name
+    return 'Content is required' unless @content
 
 module.exports = Block

+ 3 - 1
admin/models/site.coffee

@@ -4,13 +4,15 @@ require('lib/spine-couch-ajax')
 BaseModel = require('models/base')
 
 class Site extends BaseModel
-  @configure "Site", "_id", "name", "name_html", "tagline", "footer_html", "link", "theme", "css", "google_analytics_code"
+  @configure "Site", "_id", "name", "name_html", "tagline", "menu_html", "footer_html", "link", "theme", "css", "seo_description", "seo_keywords", "google_analytics_code"
   
   @extend Spine.Model.CouchAjax
   
   @queryOn: ['name','tagline','_id']
     
   validate: ->
+    return 'Site ID is required' unless @_id
     return 'Name is required' unless @name
+    return 'Name HTML is required' unless @name_html
 
 module.exports = Site

+ 5 - 2
admin/server/rewrites.coffee

@@ -1,4 +1,7 @@
 module.exports = [
+  # show root
+  { from: "/", to: "_show/index" }
+
   # static
   { from: "/static/*", to: "static/*" }
 
@@ -43,8 +46,8 @@ module.exports = [
     }
   }
 
-  # show root
-  { from: "/", to: "_show/index" }
+  # show color page
+  { from: "/_colors", to: "_show/colors" }
 
   # catch all
   { from: "*", to: "_show/not_found" }

+ 7 - 1
admin/server/shows.coffee

@@ -6,4 +6,10 @@ exports.index = (doc, req) ->
 
 exports.not_found = (doc, req) ->
   title: "404 Not Found"
-  content: templates.render("404.html", req, {})
+  content: templates.render("404.html", req, {})
+
+exports.colors = (doc, req) ->
+  title: "Color Samples"
+  content: templates.render("colors.html", req, {
+    colors: ['#9EDFB5', '#DFA57C', '#E6BC7E', '#D5D298', '#98D5A7', '#98D5BA', '#61CDDB', '#8FB1DD', '#A8B5E7', '#AF9EDA', '#DA9EC7', '#DA9EAF', '#DA9EA1', '#DAA39E', '#DAAF9E', '#DABB9E', '#DAC79E', '#A5DA9E', '#9EDAB1', '#9EC4DA', '#B6B4EB', '#DD9DAC', '#BAA8E6', '#B5E6A8', '#E6D4A8', '#E6CBA8', '#E6B7A8']
+  })

+ 142 - 0
admin/static/css/common.styl

@@ -0,0 +1,142 @@
+/* Common CSS that is imported into Admin and Site */
+
+@import url('http://fonts.googleapis.com/css?family=Lato:300,400,700')
+
+// apply a natural box layout model to all elements
+*
+  border-box()
+
+body
+  margin: 0
+  padding: 0
+  background: #fff
+  color: $textColor
+  font-family: 'Lato', 'Helvetica Neue', Helvetica, Arial, sans-serif
+  font-weight: $normalFont
+  font-size: 16px
+  letter-spacing: 0em
+  word-spacing: 0.2em
+  line-height: 1.5em
+
+h1, h2, h3, h4, h5, h6
+  color: darken($textColor, 4%)
+  font-weight: $normalFont
+  line-height: 1.2em
+  margin: 0
+
+h1
+  font-size: 3.5em
+h2
+  font-size: 2.5em
+h3
+  font-size: 2em
+h4
+  font-size: 1.5em
+h5
+  font-size: 1.125em
+  font-weight: $boldFont
+h6
+  font-size: 0.875em
+  font-weight: $boldFont
+  line-height: 1.5em
+
+a
+  color: $linkColor
+  text-decoration: none
+
+  &:hover
+    text-decoration: none
+  
+  &.button
+    display: inline-block
+    padding: 10px 20px
+    background: $primaryColor
+    color: #fff
+    font-weight: $boldFont
+
+    &.large
+      font-size: 2em
+      line-height: 1.2em
+      padding: 14px 30px
+
+strong
+ font-weight: $boldFont
+
+b
+  font-weight: $heavyFont
+
+small
+  font-size: 0.875em
+  color: $lightGrey
+
+span.label
+  font-weight: $normalFont
+  color: $lightGrey
+
+blockquote
+  position: relative
+  border-right: 10px solid $primaryColor
+  padding: 0 1.5em 0 2.5em
+  margin: 1.5em 0
+  text-align: right
+  font-size: 1.125em
+  font-weight: $normalFont
+  &:before
+    content: '“'
+    position: absolute
+    top: 0.2em
+    left: 0
+    color: $primaryColor
+    font-size: 6em
+    font-weight: $heavyFont
+
+img
+  display: block
+  border: 0
+
+input, textarea, select
+  display: block
+  width: 100%
+  color: $textColor
+  font-family: 'Lato', 'Helvetica Neue', Helvetica, Arial, sans-serif
+  font-size: 1em
+  font-weight: $normalFont
+  word-spacing: 0.2em
+  padding: 5px
+  margin: 0
+  border: 1px solid #f3f3f3
+
+textarea
+  max-width: 100%
+  min-width: 50%
+  height: 120px
+  min-height: 60px
+
+button, .button
+  display: inline-block
+  padding: 10px 30px
+  font-family: 'Lato', 'Helvetica Neue', Helvetica, Arial, sans-serif
+  font-size: 1.1em
+  font-weight: $boldFont
+  color: #fff
+  background: $primaryColor
+  border: 0
+  border-radius(0)
+  cursor: pointer
+
+  &:hover
+    background: darken($primaryColor, 5%)
+    
+  &.plain
+    color: $primaryColor
+    background: #fff
+    border: 1px solid #f3f3f3
+
+    &:hover
+      background: #f3f3f3
+
+::-webkit-input-placeholder
+  color: #e3e3e3
+
+.clearfix
+  clearfix()

+ 3 - 1
admin/static/css/index.styl

@@ -1,3 +1,5 @@
 // @import './bootstrap.css'
 @import './mixin'
-@import './theme'
+@import './common'
+@import './theme'
+@import './responsive'

+ 45 - 0
admin/static/css/mixin.styl

@@ -1,3 +1,48 @@
+$defaultColor   = #BAD0EF
+$blueColor      = #BAD0EF
+$greenColor     = #81C977
+$brownColor     = #B4805B
+$tealColor      = #61E0DF // #97E6D3
+$greyColor      = #b4b4b4
+
+// New colors
+$aColor         = #9EDFB5
+$bColor         = #DFA57C // brown ?
+$cColor         = #E6BC7E
+$dColor         = #D5D298
+$eColor         = #98D5A7
+$fColor         = #98D5BA
+$gColor         = #61CDDB // teal ?
+$hColor         = #8FB1DD
+$iColor         = #A8B5E7
+$jColor         = #AF9EDA
+$kColor         = #DA9EC7
+$lColor         = #DA9EAF
+$mColor         = #DA9EA1 // red ?
+$nColor         = #DAA39E
+$oColor         = #DAAF9E
+$pColor         = #DABB9E
+$qColor         = #DAC79E
+$rColor         = #A5DA9E
+$sColor         = #9EDAB1
+$tColor         = #9EC4DA 
+$uColor         = #B6B4EB
+$vColor         = #DD9DAC
+$wColor         = #BAA8E6
+$xColor         = #B5E6A8
+$yColor         = #E6D4A8
+$zColor         = #E6CBA8
+$a1Color        = #E6B7A8
+
+$primaryColor   = $defaultColor
+$lightGrey      = #c4c4c4
+$linkColor      = darken($primaryColor, 20%)
+$textColor      = #848484
+
+$normalFont     = 300
+$boldFont       = 400
+$heavyFont      = 700
+
 border-radius()
   -moz-border-radius: arguments
   -webkit-border-radius: arguments

+ 45 - 0
admin/static/css/responsive.styl

@@ -0,0 +1,45 @@
+/* Responsive styles */
+
+// Portrait phones and down
+// @media (max-width: 320px)
+
+
+// Larger phones to smaller landscape phones, and down
+@media (max-width: 480px)
+
+  body
+    font-size: 14px
+
+  h1
+    font-size: 2.5em
+  h2
+    font-size: 1.8em
+  h3
+    font-size: 1.3em
+  h4
+    font-size: 1.1em
+
+// Odd one
+// @media (max-width: 600px)
+  
+
+// Landscape on larger phone to smaller tablet
+@media (min-width: 481px) and (max-width: 767px)
+  
+  h1
+    font-size: 3em
+  h2
+    font-size: 2em
+  h3
+    font-size: 1.5em
+  h4
+    font-size: 1.2em
+
+
+// Portrait tablet to landscape and desktop
+// @media (min-width: 768px) and (max-width: 979px)
+
+
+// Large desktop
+// @media (min-width: 1200px)
+

+ 20 - 147
admin/static/css/theme.styl

@@ -1,149 +1,11 @@
-@import url('http://fonts.googleapis.com/css?family=Lato:300,400,700')
+/* Admin theme.styl CSS */
 
-$tealColor  = #61E0DF // #97E6D3
-$greenColor = #81C977
-$brownColor = #B4805B
-$blueColor  = #BAD0EF
-$greyColor  = #b4b4b4
-
-$primaryColor = $blueColor
-$lightGrey    = #c4c4c4
-$linkColor    = darken($primaryColor, 20%)
-$textColor    = #848484
+$availableThemeColors = 'theme-default' $defaultColor, 'theme-blue' $blueColor, 'theme-green' $greenColor, 'theme-brown' $brownColor, 'theme-teal' $tealColor, 'theme-grey' $greyColor
 
 $navbarWidth  = 180px
 
-// apply a natural box layout model to all elements
-*
-  border-box()
-
 body
-  margin: 0
-  padding: 0
   background: #f3f3f3
-  color: $textColor
-  font-family: 'Lato', 'Helvetica Neue', Helvetica, Arial, sans-serif
-  font-weight: 300
-  font-size: 16px
-  letter-spacing: 0em
-  word-spacing: 0.2em
-  line-height: 1.5em
-
-h1, h2, h3, h4, h5, h6
-  color: darken($textColor, 4%)
-  font-weight: 300
-  line-height: 1.2em
-  margin: 0
-
-h1
-  font-size: 3.5em
-h2
-  font-size: 2.5em
-h3
-  font-size: 2em
-h4
-  font-size: 1.5em
-h5
-  font-size: 1.125em
-  font-weight: 400
-h6
-  font-size: 0.875em
-  font-weight: 400
-  line-height: 1.5em
-
-a
-  color: $linkColor
-  text-decoration: none
-
-  &:hover
-    text-decoration: none
-  
-  &.button
-    display: inline-block
-    padding: 10px 20px
-    background: $primaryColor
-    color: #fff
-    font-weight: 400
-
-    &.large
-      font-size: 2em
-      line-height: 1.2em
-      padding: 14px 30px
-
-strong
- font-weight: 400
-
-b
-  font-weight: 700
-
-small
-  font-size: 0.875em
-  color: $lightGrey
-
-blockquote
-  position: relative
-  border-right: 10px solid $primaryColor
-  padding: 0 1.5em 0 2.5em
-  margin: 1.5em 0
-  text-align: right
-  font-size: 1.125em
-  font-weight: 300
-  &:before
-    content: '“'
-    position: absolute
-    top: 0.2em
-    left: 0
-    color: $primaryColor
-    font-size: 6em
-    font-weight: 700
-
-img
-  display: block
-  border: 0
-
-input, textarea, select
-  display: block
-  width: 100%
-  color: $textColor
-  font-family: 'Lato', 'Helvetica Neue', Helvetica, Arial, sans-serif
-  font-size: 1em
-  font-weight: 300
-  word-spacing: 0.2em
-  padding: 5px
-  margin: 0
-  border: 1px solid #f3f3f3
-
-textarea
-  max-width: 100%
-  min-width: 50%
-  height: 120px
-  min-height: 60px
-
-button, .button
-  display: inline-block
-  padding: 10px 30px
-  font-family: 'Lato', 'Helvetica Neue', Helvetica, Arial, sans-serif
-  font-size: 1.1em
-  font-weight: 400
-  color: #fff
-  background: $primaryColor
-  border: 0
-  border-radius(0)
-  cursor: pointer
-
-  &:hover
-    background: darken($primaryColor, 5%)
-    
-  &.plain
-    color: $primaryColor
-    background: #fff
-    border: 1px solid #f3f3f3
-
-    &:hover
-      background: #f3f3f3
-
-::-webkit-input-placeholder
-  color: #f3f3f3
 
 #app
   position: absolute
@@ -155,18 +17,24 @@ button, .button
 .site-name
   font-size: 1.65em
   line-height: 1em
-  font-weight: 300
+  font-weight: $normalFont
   color: $lightGrey
   letter-spacing: -0.07em
   word-spacing: 0em
   white-space: nowrap
   a
     color: $lightGrey
-  b, strong
-    color: $primaryColor
+  // b, strong
+  //   color: $primaryColor
+
+  // loop through all available theme colors
+  for c in $availableThemeColors
+    &.{c[0]}
+      b, strong
+        color: c[1]
 
 span.label
-  font-weight: 300
+  font-weight: $normalFont
   color: $lightGrey
 
 .filter-box
@@ -182,7 +50,7 @@ span.label
     width: 100%
     color: $lightGrey
     font-size: 1.5em
-    font-weight: 300
+    font-weight: $normalFont
     padding: 5px 10px
     margin: 0
     border: 1px solid #f3f3f3
@@ -244,7 +112,7 @@ span.label
     margin-bottom: 20px
     font-size: 4.25em
     line-height: 1em
-    font-weight: 400
+    font-weight: $boldFont
     color: #fff
     letter-spacing: -0.07em
     word-spacing: 0em
@@ -264,7 +132,7 @@ span.label
       a
         display: block
         padding: 10px 20px
-        font-weight: 400
+        font-weight: $boldFont
         color: #fff
 
         &:hover
@@ -407,6 +275,11 @@ span.label
           float: left
           margin: 4px 10px 4px 0
 
+      .required label:after
+        content: ' *'
+        color: $textColor
+
+
       .error-message
         display: none
         color: #fff

+ 46 - 0
admin/templates/author-form.html

@@ -0,0 +1,46 @@
+<div class="buttons">
+  <button class="save-button">Save</button>
+  <button class="cancel-button plain">Cancel</button>
+</div>
+<h1>Author</h1>
+<h3 class="item-title">{{name}}</h3>
+
+<form class="author">
+  <div class="error-message"></div>
+
+  <div class="field">
+    <div class="field-left required">
+      <label>Site</label>
+      <select name="site">
+        {{#each sites}}
+        <option value="{{id}}">{{name}} &mdash; {{id}}</option>
+        {{/each}}
+      </select>
+    </div>
+    <div class="field-right site-selected"></div>
+  </div>
+  <div class="field required">
+    <label>Name</label>
+    <input type="text" name="name" value="{{name}}">
+  </div>
+  <div class="field required">
+    <label>Email</label>
+    <input type="text" name="email" value="{{email}}">
+  </div>
+  <div class="field required">
+    <label>Bio</label>
+    <textarea name="bio">{{bio}}</textarea>
+  </div>
+  <div class="field">
+    <label>Photo</label>
+    <input type="text" name="photo" value="{{photo}}" placeholder="URL to photo including http://">
+  </div>
+  <div class="field">
+    <label>Links JSON</label>
+    <textarea name="links" placeholder="[{ label: url }, ...]">{{links}}</textarea>
+  </div>
+</form>
+
+{{#if id}}
+<button class="delete-button plain">Delete {{type}}</button>
+{{/if}}

+ 38 - 0
admin/templates/block-form.html

@@ -0,0 +1,38 @@
+<div class="buttons">
+  <button class="save-button">Save</button>
+  <button class="cancel-button plain">Cancel</button>
+</div>
+<h1>Block</h1>
+<h3 class="item-title">{{name}}</h3>
+
+<form class="block">
+  <div class="error-message"></div>
+
+  <div class="field">
+    <div class="field-left required">
+      <label>Site</label>
+      <select name="site">
+        {{#each sites}}
+        <option value="{{id}}">{{name}} &mdash; {{id}}</option>
+        {{/each}}
+      </select>
+    </div>
+    <div class="field-right site-selected"></div>
+  </div>
+  <div class="field required">
+    <label>Code</label>
+    <input type="text" name="code" value="{{code}}" placeholder="like: site_intro or site_promo">
+  </div>
+  <div class="field required">
+    <label>Name</label>
+    <input type="text" name="name" value="{{name}}">
+  </div>
+  <div class="field required">
+    <label>Content HTML</label>
+    <textarea name="content" placeholder="HTML content of the block">{{content}}</textarea>
+  </div>
+</form>
+
+{{#if id}}
+<button class="delete-button plain">Delete {{type}}</button>
+{{/if}}

+ 5 - 5
admin/templates/collection-form.html

@@ -9,7 +9,7 @@
   <div class="error-message"></div>
 
   <div class="field">
-    <div class="field-left">
+    <div class="field-left required">
       <label>Site</label>
       <select name="site">
         {{#each sites}}
@@ -19,13 +19,13 @@
     </div>
     <div class="field-right site-selected"></div>
   </div>
-  <div class="field">
+  <div class="field required">
     <label>Name</label>
-    <input type="text" name="name" value="{{name}}" required>
+    <input type="text" name="name" value="{{name}}">
   </div>
-  <div class="field">
+  <div class="field required">
     <label>Slug</label>
-    <input type="text" name="slug" value="{{slug}}" required>
+    <input type="text" name="slug" value="{{slug}}">
   </div>
   <div class="field">
     <label>Intro</label>

+ 1 - 1
admin/templates/collections.html

@@ -8,7 +8,7 @@
   <li>
     <a href="#/collection/{{id}}">{{name}}</a>
     <div class="meta">
-      <div><a href="http://{{site}}/collection/{{slug}}" target="_blank">view on {{site}}</a></div>
+      <div><a href="http://{{site}}/collection/{{slug}}" target="_blank">http://{{site}}/collection/{{slug}}</a></div>
     </div>
   </li>
   {{/each}}

+ 19 - 0
admin/templates/colors.html

@@ -0,0 +1,19 @@
+<div style="background: #fff; padding: 20px;">
+  <h1>Color Samples</h1>
+
+  {{#each colors}}
+    <div style="background: {{.}}; width: 100px; height: 80px; float: left; margin: 20px; color: #fff; text-align: center;">{{.}}</div>
+  {{/each}}
+
+  <p style="clear: both;">&nbsp;</p>
+
+  {{#each colors}}
+  <div style="width: 960px;" class="clearfix">
+    <div style="background: {{.}}; width: 100px; height: 80px; float: right; color: #fff; text-align: center;">{{.}}</div>
+    <div class="site-name" style="font-size: 5.25em; float: left;">
+      Evolving <b style="color: {{.}};">Something</b>
+    </div>
+    <div>The color code is {{.}}</div>
+  </div>
+  {{/each}}
+</div>

+ 27 - 0
admin/templates/contact-form.html

@@ -0,0 +1,27 @@
+<div class="buttons">
+  <button class="save-button">Save</button>
+  <button class="cancel-button plain">Cancel</button>
+</div>
+<h1>Contact</h1>
+<h3 class="item-title">{{name}}</h3>
+
+<form class="contact">
+  <div class="error-message"></div>
+
+  <div class="field required">
+    <label>Name</label>
+    <input type="text" name="name" value="{{name}}">
+  </div>
+  <div class="field">
+    <label>Email</label>
+    <input type="text" name="email" value="{{email}}" placeholder="contact@example.com">
+  </div>
+  <div class="field">
+    <label>Note</label>
+    <textarea name="note" placeholder="">{{note}}</textarea>
+  </div>
+</form>
+
+{{#if id}}
+<button class="delete-button plain">Delete {{type}}</button>
+{{/if}}

+ 1 - 1
admin/templates/filter-box.html

@@ -6,6 +6,6 @@
 <ul class="site-selector">
   <li class="site-name" data-id="">All sites</li>
   {{#each sites}}
-  <li class="site-name" data-id="{{_id}}">{{{name_html}}}</li>
+  <li class="site-name theme-{{theme}}" data-id="{{_id}}">{{{name_html}}}</li>
   {{/each}}
 </ul>

+ 21 - 9
admin/templates/site-form.html

@@ -5,21 +5,21 @@
 <h1>Site</h1>
 <h3 class="item-title">{{name}}</h3>
 
-<form class="collection">
+<form class="site">
   <div class="error-message"></div>
 
-  <div class="field">
-    <label>ID</label>
-    <input type="text" name="_id" value="{{_id}}" required placeholder="www.example.com">
+  <div class="field required">
+    <label>Site ID</label>
+    <input type="text" name="_id" value="{{_id}}" placeholder="www.example.com">
   </div>
   <div class="field">
-    <div class="field-left">
+    <div class="field-left required">
       <label>Name</label>
-      <input type="text" name="name" value="{{name}}" required>
+      <input type="text" name="name" value="{{name}}">
     </div>
-    <div class="field-right">
+    <div class="field-right required">
       <label>Name HTML</label>
-      <input type="text" name="name_html" value="{{name_html}}" required>
+      <input type="text" name="name_html" value="{{name_html}}">
     </div>
   </div>
   <div class="field">
@@ -37,9 +37,13 @@
     </div>
     <div class="field-right">
       <label>Link</label>
-      <input type="text" name="link" value="{{link}}" required placeholder="http://www.example.com">
+      <input type="text" name="link" value="{{link}}" placeholder="http://www.example.com">
     </div>
   </div>
+  <div class="field">
+    <label>Menu HTML</label>
+    <textarea name="menu_html" placeholder="only LI items">{{menu_html}}</textarea>
+  </div>
   <div class="field">
     <label>Footer HTML</label>
     <textarea name="footer_html">{{footer_html}}</textarea>
@@ -48,6 +52,14 @@
     <label>CSS</label>
     <textarea name="css">{{css}}</textarea>
   </div>
+  <div class="field">
+    <label>SEO Description</label>
+    <input type="text" name="seo_description" value="{{seo_description}}">
+  </div>
+  <div class="field">
+    <label>SEO Keywords</label>
+    <input type="text" name="seo_keywords" value="{{seo_keywords}}">
+  </div>
   <div class="field">
     <label>Google Analytics Code</label>
     <input type="text" name="google_analytics_code" value="{{google_analytics_code}}">

+ 1 - 1
admin/templates/sites.html

@@ -9,7 +9,7 @@
     <div class="actions">
       <div><a href="{{link}}" target="_blank">{{id}}</span></div>
     </div>
-    <a class="site-name" href="#/site/{{id}}">{{{name_html}}}</a>
+    <a class="site-name theme-{{theme}}" href="#/site/{{id}}">{{{name_html}}}</a>
     <div class="meta">
       {{#if tagline}}<div>{{{tagline}}}</div>{{/if}}
     </div>

+ 52 - 0
admin/templates/sponsor-form.html

@@ -0,0 +1,52 @@
+<div class="buttons">
+  <button class="save-button">Save</button>
+  <button class="cancel-button plain">Cancel</button>
+</div>
+<h1>Sponsor</h1>
+<h3 class="item-title">{{name}}</h3>
+
+<form class="sponsor">
+  <div class="error-message"></div>
+
+  <div class="field required">
+    <label>Name</label>
+    <input type="text" name="name" value="{{name}}">
+  </div>
+  <div class="field">
+    <label>Link</label>
+    <input type="text" name="link" value="{{link}}">
+  </div>
+  <div class="field">
+    <label>Label</label>
+    <input type="text" name="label" value="{{label}}">
+  </div>
+  <div class="field">
+    <label>Format</label>
+    <select name="format">
+      <option value="text">Text</option>
+      <option value="image">Image</option>
+      <option value="video">Video</option>
+    </select>
+  </div>
+  <div class="field">
+    <label>Content</label>
+    <textarea name="content">{{content}}</textarea>
+  </div>
+  <div class="field">
+    <label>Note</label>
+    <textarea name="note">{{note}}</textarea>
+  </div>
+  <div class="field">
+    <label>Contact</label>
+    <select name="contact_id">
+      <option value=""></option>
+      {{#each contacts}}
+      <option value="{{id}}">{{name}}</option>
+      {{/each}}
+    </select>
+  </div>
+</form>
+
+{{#if id}}
+<button class="delete-button plain">Delete {{type}}</button>
+{{/if}}

+ 1 - 1
site/kanso.json

@@ -14,7 +14,7 @@
   },
   "stylus": {
       "compress": true,
-      "compile": "static/css/index.styl",
+      "compile": ["static/css/index.styl", "static/css/themes"],
       "remove_from_attachments": true
   },
   "dependencies": {

+ 6 - 1
site/lib/app.coffee

@@ -5,4 +5,9 @@ $ = require('jquery')
 exports.initialize = (config) ->
   touch = Modernizr.touch
 
-  # ...
+  $mainNav = $('.main-nav')
+  $mainNavIcon = $mainNav.find('> .icon')
+  $mainNavList = $mainNav.find('> ul')
+  
+  $mainNavIcon.on 'click', (e) ->
+    $mainNavList.toggle()

+ 34 - 20
site/server/lists.coffee

@@ -69,15 +69,22 @@ exports.collection = (head, req) ->
     sponsor.image_format = sponsor.format is 'image'
     sponsor.video_format = sponsor.format is 'video'
 
-  return {
-    site: site
-    title: collection.name
-    content: templates.render 'collection.html', req,
-      collection: collection
-      essays: essays
-      sponsor: sponsor
-      nav: 'collection'
-  }
+  if collection
+    return {
+      site: site
+      title: collection.name
+      content: templates.render 'collection.html', req,
+        collection: collection
+        essays: essays
+        sponsor: sponsor
+        nav: 'collection'
+    }
+  else
+    return {
+      code: 404
+      title: '404 Not Found'
+      content: templates.render '404.html', req, { host: req.headers.Host }
+    }
 
 
 exports.essays = (head, req) ->
@@ -152,7 +159,7 @@ exports.essay = (head, req) ->
     doc.updated_at_html = new Date(doc.updated_at).toDateString()
     return doc
 
-  essay = transformEssay(essay)
+  essay = transformEssay(essay) if essay
 
   collections = _.map collections, (doc) ->
     if doc.intro?
@@ -167,16 +174,23 @@ exports.essay = (head, req) ->
     sponsor.image_format = sponsor.format is 'image'
     sponsor.video_format = sponsor.format is 'video'
 
-  return {
-    site: site
-    title: essay.title
-    content: templates.render 'essay.html', req,
-      essay: essay
-      collections: collections
-      author: author
-      sponsor: sponsor
-      nav: 'essay'
-  }
+  if essay
+    return {
+      site: site
+      title: essay.title
+      content: templates.render 'essay.html', req,
+        essay: essay
+        collections: collections
+        author: author
+        sponsor: sponsor
+        nav: 'essay'
+    }
+  else
+    return {
+      code: 404
+      title: '404 Not Found'
+      content: templates.render '404.html', req, { host: req.headers.Host }
+    }
 
 
 ###

+ 3 - 0
site/server/rewrites.coffee

@@ -62,6 +62,9 @@ module.exports = [
   # Redirected old URLs
   # moved '/posts/some-old-path', '/some-new-path'
 
+  # 404 not found 
+  { from: '/not-found', to: '_show/not_found' }
+
   # Catch all route
   { from: '*', to: '_show/not_found' }
 ]

+ 2 - 1
site/server/shows.coffee

@@ -1,8 +1,9 @@
 templates = require("duality/templates") 
 
 exports.not_found = (doc, req) ->
+  code: 404
   title: "404 Not Found"
-  content: templates.render("404.html", req, {})
+  content: templates.render("404.html", req, { host: req.headers.Host })
 
 exports.moved = (doc, req) ->
   code: 301

+ 4 - 1
site/static/css/index.styl

@@ -1,3 +1,6 @@
-@import './mixin'
+// Following imports are part of the admin app
+@import '../../../admin/static/css/mixin'
+@import '../../../admin/static/css/common'
+
 @import './theme'
 @import './responsive'

+ 0 - 94
site/static/css/mixin.styl

@@ -1,94 +0,0 @@
-border-radius()
-  -moz-border-radius: arguments
-  -webkit-border-radius: arguments
-  border-radius: arguments
-
-/* Vertical Background Gradient */
-vbg-gradient(fc = #FFF, tc = #FFF)
-  background: fc
-  background: -webkit-gradient(linear, left top, left bottom, from(fc), to(tc))
-  background: -moz-linear-gradient(top, fc, tc)
-  background: linear-gradient(top, fc, tc)
-
-/* Horizontal Background Gradient */
-hbg-gradient(fc = #FFF, tc = #FFF) 
-  background: fc
-  background: -webkit-gradient(linear, left top, right top, from(fc), to(tc))
-  background: -moz-linear-gradient(left, fc, tc)
-  background: linear-gradient(left, fc, tc)
-
-box-shadow()
-  -moz-box-shadow: arguments
-  -webkit-box-shadow: arguments
-  box-shadow: arguments
-
-inset-box-shadow()
-  -moz-box-shadow: inset arguments
-  -webkit-box-shadow: inset arguments
-  box-shadow: inset arguments
-
-box-flex(s = 0)
-  -webkit-box-flex: s
-  -moz-box-flex: s
-  box-flex: s
-
-hbox()
-  display: -webkit-box
-  -webkit-box-orient: horizontal
-  -webkit-box-align: stretch
-  -webkit-box-pack: start
-  
-  display: -moz-box
-  -moz-box-orient: horizontal
-  -moz-box-align: stretch
-  -moz-box-pack: start
-
-vbox()
-  display: -webkit-box
-  -webkit-box-orient: vertical
-  -webkit-box-align: stretch
-
-  display: -moz-box
-  -moz-box-orient: vertical
-  -moz-box-align: stretch
-
-border-box()
-  -webkit-box-sizing: border-box
-  -moz-box-sizing: border-box
-  box-sizing: border-box
-
-content-box()
-  -webkit-box-sizing: content-box
-  -moz-box-sizing: content-box
-  box-sizing: content-box
-
-transition(s = 0.3s, o =  opacity, t = linear)
-  -webkit-transition: s o t
-  -moz-transition: s o t
-  transition: s o t
-
-ellipsis()
-  text-overflow: ellipsis
-  overflow: hidden
-  white-space:nowrap
-
-inset-line(opacity = 0.4, size = 1px)
-  inset-box-shadow(0, size, 0, rgba(255, 255, 255, opacity))
-
-outset-line(opacity = 0.4, size = 1px)
-  box-shadow(0, size, 0, rgba(255, 255, 255, opacity))
-
-box-pack(type = center)
-  -webkit-box-pack: type
-  -moz-box-pack: type
-  box-pack: type
-  
-transform(tr)
-  -webkit-transform: tr
-  -moz-transform: tr
-  -ms-transform: tr
-  -o-transform: tr
-  transform: tr
-  
-hacel()
-  transform(translate3d(0,0,0))

+ 6 - 0
site/static/css/responsive.styl

@@ -26,6 +26,9 @@
   .site-name
     font-size: 2.8em
 
+  .main-nav
+    margin: 0
+
 // Odd one
 @media (max-width: 600px)
   
@@ -55,6 +58,9 @@
   .site-name
     font-size: 3.5em
 
+  .main-nav
+    margin: 12px 12px 0 0
+
 
 // Portrait tablet to landscape and desktop
 // @media (min-width: 768px) and (max-width: 979px)

+ 73 - 0
site/static/css/setup-theme.styl

@@ -0,0 +1,73 @@
+// Following imports are part of the admin app
+@import '../../../admin/static/css/mixin'
+
+setupTheme($primaryColor = $blueColor, $secondaryColor = $lightGrey, $linkColor = null)
+  
+  if !$linkColor
+    $linkColor = darken($primaryColor, 20%)
+  
+  h1, h2, h3, h4, h5, h6
+    color: darken($textColor, 4%)
+
+  a
+    color: $linkColor
+    &.button
+      background: $primaryColor
+
+  blockquote
+    border-color: $primaryColor
+    &:before
+      color: $primaryColor
+
+  button, .button
+    background: $primaryColor
+    &:hover
+      background: darken($primaryColor, 5%)
+    &.plain
+      color: $primaryColor
+
+
+
+  .container
+    border-color: $primaryColor
+
+  .site-name
+    b, strong
+      color: $primaryColor
+
+  .site-promo
+    color: $primaryColor
+
+  .main-nav
+    .icon
+      background: $primaryColor
+      &:hover
+        background: darken($primaryColor, 10%)
+    > ul
+      background: $primaryColor
+      > li
+        a
+          &:hover
+            background: darken($primaryColor, 5%)
+          &.active
+            background: darken($primaryColor, 10%)
+
+  article
+    .sponsor
+      .label
+        background: $primaryColor
+
+  article.home
+    .collections      
+      > ul
+        > li
+          border-color: $primaryColor
+          .updated
+            background: $primaryColor
+
+  article.essay
+    .author
+      border-color: $primaryColor
+
+  footer
+    border-color: $primaryColor

+ 77 - 104
site/static/css/theme.styl

@@ -1,111 +1,17 @@
-@import url('http://fonts.googleapis.com/css?family=Lato:300,400,700')
-
-$tealColor  = #61E0DF // #97E6D3
-$greenColor = #81C977
-$brownColor = #B4805B
-$blueColor  = #BAD0EF
-$greyColor  = #b4b4b4
-
-$primaryColor = $blueColor
-$lightGrey    = #c4c4c4
-$linkColor    = darken($primaryColor, 20%)
-$textColor    = #848484
-
-// apply a natural box layout model to all elements
-*
-  border-box()
-
-body
-  margin: 0
-  padding: 0
-  background: #fff
-  color: $textColor
-  font-family: 'Lato', 'Helvetica Neue', Helvetica, Arial, sans-serif
-  font-weight: 300
-  font-size: 16px
-  letter-spacing: 0em
-  word-spacing: 0.2em
-  line-height: 1.5em
+/* Site theme.styl CSS */
 
 .container
+  position: relative
   max-width: 960px
   margin: 0
   padding: 40px
   border-left: 20px solid $primaryColor
 
-h1, h2, h3, h4, h5, h6
-  color: darken($textColor, 4%)
-  font-weight: 300
-  line-height: 1.2em
-
-h1
-  font-size: 3.5em
-h2
-  font-size: 2.5em
-h3
-  font-size: 2em
-h4
-  font-size: 1.5em
-h5
-  font-size: 1.125em
-  font-weight: 400
-h6
-  font-size: 0.875em
-  font-weight: 400
-  line-height: 1.5em
-
-a
-  color: $linkColor
-  text-decoration: none
-  
-  &.button
-    display: inline-block
-    padding: 10px 20px
-    background: $primaryColor
-    color: #fff
-    font-weight: 400
-
-    &.large
-      font-size: 2em
-      line-height: 1.2em
-      padding: 14px 30px
-
-b
-  font-weight: 700
-
-strong
- font-weight: 400 
-
-small
-  font-size: 0.875em
-  color: $lightGrey
-
-blockquote
-  position: relative
-  border-right: 10px solid $primaryColor
-  padding: 0 1.5em 0 2.5em
-  margin: 1.5em 0
-  text-align: right
-  font-size: 1.125em
-  font-weight: 300
-  &:before
-    content: '“'
-    position: absolute
-    top: 0.2em
-    left: 0
-    color: $primaryColor
-    font-size: 6em
-    font-weight: 700
-
-img
-  display: block
-  border: 0
-
 .site-name
   margin: 0 0 0.8em -0.05em
   font-size: 5.25em
   line-height: 0.8em
-  font-weight: 300
+  font-weight: $normalFont
   color: $lightGrey
   letter-spacing: -0.07em
   word-spacing: 0em
@@ -118,25 +24,92 @@ img
 .site-promo
   color: $primaryColor
   font-size: 1.25em
-  font-weight: 400
+  font-weight: $boldFont
+
+.main-nav
+  position: absolute
+  top: 0
+  right: 0
+  padding: 0
+  margin: 46px 40px 0 0
+  -webkit-user-select: none
+
+  $iconSize = 60px
+
+  .icon
+    position: absolute
+    top: 0
+    right: 0
+    width: $iconSize
+    height: $iconSize
+    padding: 10px
+    background: $primaryColor
+    cursor: pointer
+
+    &:hover
+      background: darken($primaryColor, 10%)
+
+    span
+      display: block
+      height: 7px
+      margin-bottom: 5px
+      background: #fff
+    i
+      margin: -7px 9px 0 9px
+      arrow('top', 12px, #fff, 12px, 7px)
+      top: 0
+
+  > ul
+    display: none
+    position: absolute
+    top: $iconSize
+    right: 0
+    min-width: 220px
+    list-style-type: none
+    padding: 0
+    margin: 0
+    background: $primaryColor
+
+    > li
+      padding: 0
+
+      a
+        display: block
+        padding: 7px 12px
+        font-size: 1.2em
+        font-weight: $normalFont
+        color: #fff
+
+        &:hover
+          background: darken($primaryColor, 5%)
+
+        &.active
+          background: darken($primaryColor, 10%)
+
+      &.search-box
+        padding: 10px
+        
+        input
+          background: #fff
+          border: 0
+          padding: 5px 10px
+          font-size: 1.2em
+          color: $textColor
 
-span.label
-  font-weight: 300
-  color: $lightGrey
 
 article
 
   > .intro
     margin-bottom: 2em
     font-size: 1.125em
-    font-weight: 400
+    font-weight: $boldFont
     line-height: 1.6em
     color: darken($textColor, 5%)
 
   .sponsor
     float: right
-    width: 300px
-    max-width: 300px
+    width: $normalFontpx
+    max-width: $normalFontpx
     margin: 0 0 1em 1em
     text-align: center
     background: #f9f9f9

+ 3 - 0
site/static/css/themes/blue.styl

@@ -0,0 +1,3 @@
+@import '../setup-theme'
+
+setupTheme($blueColor)

+ 3 - 0
site/static/css/themes/brown.styl

@@ -0,0 +1,3 @@
+@import '../setup-theme'
+
+setupTheme($brownColor)

+ 3 - 0
site/static/css/themes/default.styl

@@ -0,0 +1,3 @@
+@import '../setup-theme'
+
+setupTheme($blueColor)

+ 3 - 0
site/static/css/themes/green.styl

@@ -0,0 +1,3 @@
+@import '../setup-theme'
+
+setupTheme($greenColor)

+ 3 - 0
site/static/css/themes/grey.styl

@@ -0,0 +1,3 @@
+@import '../setup-theme'
+
+setupTheme($greyColor,  $lightGrey, $linkColor)

+ 3 - 0
site/static/css/themes/teal.styl

@@ -0,0 +1,3 @@
+@import '../setup-theme'
+
+setupTheme($tealColor)

+ 5 - 1
site/templates/404.html

@@ -1 +1,5 @@
-<h1>404 Not Found</h1>
+<div style="padding: 40px;">
+  <h1>404 <strong>Not Found</strong></h1>
+  <br>
+  <h3>Go directly to <strong><a href="http://{{host}}">{{host}}</a></strong></h3>
+</div>

+ 0 - 1
site/templates/area.html

@@ -1 +0,0 @@
-<h1>Site home for <u>{{site}}</u></h1>

+ 32 - 2
site/templates/base.html

@@ -1,3 +1,4 @@
+{{#if site}}
 <!DOCTYPE html>
 <html>
 <head>
@@ -14,7 +15,10 @@
   <!-- <link rel="shortcut icon" href="{{baseURL}}/static/img/favicon.ico">
   <link rel="apple-touch-icon-precomposed" href="{{baseURL}}/static/img/apple-touch-icon-precomposed.png"> -->
 
-  <link rel="stylesheet" href="{{baseURL}}/static/css/index.css" type="text/css" media="screen" charset="utf-8">
+  <link rel="stylesheet" href="{{baseURL}}/static/css/index.css" type="text/css" media="all" charset="utf-8">
+  {{#if site.theme}}
+  <link rel="stylesheet" href="{{baseURL}}/static/css/themes/{{site.theme}}.css" type="text/css" media="all" charset="utf-8">
+  {{/if}}
 
   <script src="{{baseURL}}/static/js/modernizr.custom.js"></script>
 
@@ -32,6 +36,16 @@
 
     {{{content}}}
 
+    <nav class="main-nav">
+      <div class="icon"><span></span><span></span><span></span><i></i></div>
+      <ul>
+        {{{site.menu_html}}}
+        <li class="search-box">
+          <input type="text" placeholder="Search">
+        </li>
+      </ul>
+    </nav>
+
     {{#if site.footer_html}}<footer>{{{site.footer_html}}}</footer>{{/if}}
   </div>
 
@@ -65,4 +79,20 @@
   {{/if}}
 
 </body>
-</html>
+</html>
+
+{{else}}
+
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>{{title}}</title>
+  <link rel="stylesheet" href="{{baseURL}}/static/css/index.css" type="text/css" media="all" charset="utf-8">
+</head>
+<body>
+  {{{content}}}
+</body>
+</html>
+
+{{/if}}