Browse Source

Implemented creation and update forms for most types

Markus Ochel 13 năm trước cách đây
mục cha
commit
3b01518454
47 tập tin đã thay đổi với 1190 bổ sung410 xóa
  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}}