Переглянути джерело

Port sources to CoffeeScript

@soyjavi 13 роки тому
батько
коміт
f82473246a
33 змінених файлів з 2475 додано та 142 видалено
  1. 137 119
      example/components/lungo/lungo.css
  2. 2 2
      example/components/lungo/lungo.js
  3. 105 20
      example/components/lungo/theme.lungo.css
  4. 1 1
      example/index.html
  5. 12 0
      src/Lungo.coffee
  6. 40 0
      src/boot/Lungo.Boot.Data.coffee
  7. 23 0
      src/boot/Lungo.Boot.Device.coffee
  8. 87 0
      src/boot/Lungo.Boot.Events.coffee
  9. 52 0
      src/boot/Lungo.Boot.Layout.coffee
  10. 66 0
      src/data/Lungo.Data.Cache.coffee
  11. 168 0
      src/data/Lungo.Data.Sql.coffee
  12. 60 0
      src/data/Lungo.Data.Storage.coffee
  13. 16 0
      src/element/Lungo.Element.Cache.coffee
  14. 115 0
      src/element/Lungo.Element.Carousel.coffee
  15. 20 0
      src/element/Lungo.Element.Count.coffee
  16. 21 0
      src/element/Lungo.Element.Loading.coffee
  17. 18 0
      src/element/Lungo.Element.Progress.coffee
  18. 111 0
      src/element/Lungo.Element.Pull.coffee
  19. 57 0
      src/modules/Lungo.Attributes.coffee
  20. 76 0
      src/modules/Lungo.Constants.coffee
  21. 204 0
      src/modules/Lungo.Core.coffee
  22. 17 0
      src/modules/Lungo.Dom.coffee
  23. 20 0
      src/modules/Lungo.Events.coffee
  24. 17 0
      src/modules/Lungo.Fallback.coffee
  25. 14 0
      src/modules/Lungo.Init.coffee
  26. 141 0
      src/modules/Lungo.Notification.coffee
  27. 53 0
      src/modules/Lungo.Resource.coffee
  28. 337 0
      src/modules/Lungo.Scroll.coffee
  29. 118 0
      src/modules/Lungo.Service.coffee
  30. 47 0
      src/router/Lungo.Router.History.coffee
  31. 133 0
      src/router/Lungo.Router.coffee
  32. 45 0
      src/view/Lungo.View.Article.coffee
  33. 142 0
      src/view/Lungo.View.Aside.coffee

Різницю між файлами не показано, бо вона завелика
+ 137 - 119
example/components/lungo/lungo.css


Різницю між файлами не показано, бо вона завелика
+ 2 - 2
example/components/lungo/lungo.js


+ 105 - 20
example/components/lungo/theme.lungo.css

@@ -7,44 +7,39 @@
  * @author Javier Jimenez Villar <javi@tapquo.com> || @soyjavi
  */
 /* @import url("https://raw.github.com/soyjavi/CSSmethods/master/stylus/vendor.styl") */
-@import url("http://fonts.googleapis.com/css?family=Open+Sans:300,400,600,700");
 /* -------------------------- THEME -------------------------- */
+/* -------------------------- DEFAULTS -------------------------- */
 body {
-  background-color: #222;
+  background-color: #000;
+  color: #333;
   font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
   font-weight: 400;
   font-size: 13px;
   line-height: 1.3em;
   letter-spacing: -0.05em;
 }
-.theme,
-li.theme,
-a.theme {
+body .theme {
   background-color: THEME-light;
 }
-.theme:active,
-li.theme:active,
-a.theme:active {
+body .theme:active {
   background-color: #0093d5;
 }
-[data-control="pull"] {
-  color: #666;
-  -webkit-text-shadow: 0 1px 0 #fff;
-  -moz-text-shadow: 0 1px 0 #fff;
-  text-shadow: 0 1px 0 #fff;
-}
 /* -------------------------- LAYOUT COLORS -------------------------- */
 section {
   background-color: #222;
 }
 section > header {
   background-color: #0093d5;
-  -webkit-box-shadow: 0 1px 3px rgba(0,0,0,0.25), inset 0 1px 0 #03b1ff, inset 0 -1px 0 #007db5;
-  -moz-box-shadow: 0 1px 3px rgba(0,0,0,0.25), inset 0 1px 0 #03b1ff, inset 0 -1px 0 #007db5;
-  box-shadow: 0 1px 3px rgba(0,0,0,0.25), inset 0 1px 0 #03b1ff, inset 0 -1px 0 #007db5;
+  -webkit-box-shadow: 0 2px 0 rgba(0,0,0,0.15), inset 0 1px 0 #03b1ff, inset 0 -1px 0 #007db5;
+  -moz-box-shadow: 0 2px 0 rgba(0,0,0,0.15), inset 0 1px 0 #03b1ff, inset 0 -1px 0 #007db5;
+  -ms-box-shadow: 0 2px 0 rgba(0,0,0,0.15), inset 0 1px 0 #03b1ff, inset 0 -1px 0 #007db5;
+  -o-box-shadow: 0 2px 0 rgba(0,0,0,0.15), inset 0 1px 0 #03b1ff, inset 0 -1px 0 #007db5;
+  box-shadow: 0 2px 0 rgba(0,0,0,0.15), inset 0 1px 0 #03b1ff, inset 0 -1px 0 #007db5;
   color: #fff;
   -webkit-border-radius: 4px 4px 0 0;
   -moz-border-radius: 4px 4px 0 0;
+  -ms-border-radius: 4px 4px 0 0;
+  -o-border-radius: 4px 4px 0 0;
   border-radius: 4px 4px 0 0;
 }
 section > footer,
@@ -52,6 +47,8 @@ section nav.groupbar {
   background-color: #222;
   -webkit-box-shadow: inset 0 3px 0 #1d1d1d;
   -moz-box-shadow: inset 0 3px 0 #1d1d1d;
+  -ms-box-shadow: inset 0 3px 0 #1d1d1d;
+  -o-box-shadow: inset 0 3px 0 #1d1d1d;
   box-shadow: inset 0 3px 0 #1d1d1d;
 }
 section > article,
@@ -66,11 +63,15 @@ section > [data-control="pull"].splash {
 section.aside {
   -webkit-box-shadow: -1px 0 2px rgba(0,0,0,0.2);
   -moz-box-shadow: -1px 0 2px rgba(0,0,0,0.2);
+  -ms-box-shadow: -1px 0 2px rgba(0,0,0,0.2);
+  -o-box-shadow: -1px 0 2px rgba(0,0,0,0.2);
   box-shadow: -1px 0 2px rgba(0,0,0,0.2);
 }
 section.aside.right {
   -webkit-box-shadow: 1px 0 2px rgba(0,0,0,0.2);
   -moz-box-shadow: 1px 0 2px rgba(0,0,0,0.2);
+  -ms-box-shadow: 1px 0 2px rgba(0,0,0,0.2);
+  -o-box-shadow: 1px 0 2px rgba(0,0,0,0.2);
   box-shadow: 1px 0 2px rgba(0,0,0,0.2);
 }
 aside {
@@ -84,6 +85,8 @@ aside > footer {
 aside > header {
   -webkit-box-shadow: 0 1px 3px rgba(0,0,0,0.25), inset 0 1px 0 #222, inset 0 -1px 0 #141414;
   -moz-box-shadow: 0 1px 3px rgba(0,0,0,0.25), inset 0 1px 0 #222, inset 0 -1px 0 #141414;
+  -ms-box-shadow: 0 1px 3px rgba(0,0,0,0.25), inset 0 1px 0 #222, inset 0 -1px 0 #141414;
+  -o-box-shadow: 0 1px 3px rgba(0,0,0,0.25), inset 0 1px 0 #222, inset 0 -1px 0 #141414;
   box-shadow: 0 1px 3px rgba(0,0,0,0.25), inset 0 1px 0 #222, inset 0 -1px 0 #141414;
 }
 /* -------------------------- NAVIGATION -------------------------- */
@@ -91,6 +94,8 @@ section > header > nav a:not(.button) {
   color: #00608a;
   -webkit-text-shadow: 0 1px 0 #20baff;
   -moz-text-shadow: 0 1px 0 #20baff;
+  -ms-text-shadow: 0 1px 0 #20baff;
+  -o-text-shadow: 0 1px 0 #20baff;
   text-shadow: 0 1px 0 #20baff;
 }
 section > header > nav a:not(.button):active {
@@ -99,6 +104,8 @@ section > header > nav a:not(.button):active {
 section > nav.groupbar > a.active {
   -webkit-box-shadow: inset 0 -3px 0 #0093d5;
   -moz-box-shadow: inset 0 -3px 0 #0093d5;
+  -ms-box-shadow: inset 0 -3px 0 #0093d5;
+  -o-box-shadow: inset 0 -3px 0 #0093d5;
   box-shadow: inset 0 -3px 0 #0093d5;
 }
 section > footer > nav > a,
@@ -112,6 +119,8 @@ nav.groupbar > a.active {
 section > footer > nav > a {
   -webkit-box-shadow: 1px 0 0 #1d1d1d;
   -moz-box-shadow: 1px 0 0 #1d1d1d;
+  -ms-box-shadow: 1px 0 0 #1d1d1d;
+  -o-box-shadow: 1px 0 0 #1d1d1d;
   box-shadow: 1px 0 0 #1d1d1d;
 }
 section > footer > nav > a.active {
@@ -119,6 +128,8 @@ section > footer > nav > a.active {
   background-color: #1d1d1d;
   -webkit-box-shadow: inset 0 3px 0 #0093d5;
   -moz-box-shadow: inset 0 3px 0 #0093d5;
+  -ms-box-shadow: inset 0 3px 0 #0093d5;
+  -o-box-shadow: inset 0 3px 0 #0093d5;
   box-shadow: inset 0 3px 0 #0093d5;
 }
 aside nav a {
@@ -127,6 +138,32 @@ aside nav a {
 aside nav a:active {
   color: #919191;
 }
+nav[data-control=menu] {
+  background: #222;
+  -webkit-box-shadow: 0 2px 0 rgba(0,0,0,0.15);
+  -moz-box-shadow: 0 2px 0 rgba(0,0,0,0.15);
+  -ms-box-shadow: 0 2px 0 rgba(0,0,0,0.15);
+  -o-box-shadow: 0 2px 0 rgba(0,0,0,0.15);
+  box-shadow: 0 2px 0 rgba(0,0,0,0.15);
+}
+nav[data-control=menu] > a {
+  color: #fff;
+  -webkit-text-shadow: 0 1px 0 #111;
+  -moz-text-shadow: 0 1px 0 #111;
+  -ms-text-shadow: 0 1px 0 #111;
+  -o-text-shadow: 0 1px 0 #111;
+  text-shadow: 0 1px 0 #111;
+  font-weight: 700;
+  border-bottom: 1px solid #141414;
+  border-top: 1px solid #2d2d2d;
+}
+nav[data-control=menu] > a:active {
+  background: #111;
+  border-color: transparent;
+}
+nav[data-control=menu] > a > .icon {
+  color: #595959;
+}
 /* -------------------------- LISTS -------------------------- */
 section .list li {
   background: #fff;
@@ -137,21 +174,27 @@ section .list li:not(.anchor) {
 section .list li.secondary {
   -webkit-box-shadow: inset 4px 0px 0px #bfbfbf;
   -moz-box-shadow: inset 4px 0px 0px #bfbfbf;
+  -ms-box-shadow: inset 4px 0px 0px #bfbfbf;
+  -o-box-shadow: inset 4px 0px 0px #bfbfbf;
   box-shadow: inset 4px 0px 0px #bfbfbf;
 }
 section .list li.accept {
   -webkit-box-shadow: inset 4px 0px 0px #3fb58e;
   -moz-box-shadow: inset 4px 0px 0px #3fb58e;
+  -ms-box-shadow: inset 4px 0px 0px #3fb58e;
+  -o-box-shadow: inset 4px 0px 0px #3fb58e;
   box-shadow: inset 4px 0px 0px #3fb58e;
 }
 section .list li.cancel {
   -webkit-box-shadow: inset 4px 0px 0px #ee6557;
   -moz-box-shadow: inset 4px 0px 0px #ee6557;
+  -ms-box-shadow: inset 4px 0px 0px #ee6557;
+  -o-box-shadow: inset 4px 0px 0px #ee6557;
   box-shadow: inset 4px 0px 0px #ee6557;
 }
 section .list li,
 section .list li a {
-  color: #333;
+  color: LIST_color;
 }
 section .list li.anchor,
 section .list li a.anchor {
@@ -220,9 +263,14 @@ aside .list li:not(:first-child) {
 aside .list li:not(:last-child) {
   border-bottom: solid 1px #141414;
 }
-aside .list li.active,
 aside .list li:active {
+  background: #181818;
+}
+aside .list li.active {
   background: #0093d5;
+}
+aside .list li.active,
+aside .list li:active {
   border-color: transparent;
 }
 aside .list li.active strong,
@@ -245,6 +293,8 @@ aside .list li .icon {
   color: #fff;
   -webkit-border-radius: 2px;
   -moz-border-radius: 2px;
+  -ms-border-radius: 2px;
+  -o-border-radius: 2px;
   border-radius: 2px;
   font-weight: 700 !important;
 }
@@ -257,8 +307,17 @@ aside .list li .icon {
 footer .tag:not(.icon) {
   -webkit-box-shadow: inset 0 1px 1px rgba(255,255,255,0.3), 0 1px 2px rgba(0,0,0,0.5);
   -moz-box-shadow: inset 0 1px 1px rgba(255,255,255,0.3), 0 1px 2px rgba(0,0,0,0.5);
+  -ms-box-shadow: inset 0 1px 1px rgba(255,255,255,0.3), 0 1px 2px rgba(0,0,0,0.5);
+  -o-box-shadow: inset 0 1px 1px rgba(255,255,255,0.3), 0 1px 2px rgba(0,0,0,0.5);
   box-shadow: inset 0 1px 1px rgba(255,255,255,0.3), 0 1px 2px rgba(0,0,0,0.5);
 }
+[data-control="pull"] {
+  -webkit-text-shadow: 0 1px 0 #fff;
+  -moz-text-shadow: 0 1px 0 #fff;
+  -ms-text-shadow: 0 1px 0 #fff;
+  -o-text-shadow: 0 1px 0 #fff;
+  text-shadow: 0 1px 0 #fff;
+}
 /* -------------------------- NOTIFICATION -------------------------- */
 .notification {
   color: #fff;
@@ -271,6 +330,8 @@ footer .tag:not(.icon) {
   color: #222;
   -webkit-box-shadow: 0 0 8px #000;
   -moz-box-shadow: 0 0 8px #000;
+  -ms-box-shadow: 0 0 8px #000;
+  -o-box-shadow: 0 0 8px #000;
   box-shadow: 0 0 8px #000;
 }
 .notification .window:not(.growl) button,
@@ -278,10 +339,14 @@ footer .tag:not(.icon) {
   background: #fff !important;
   -webkit-box-shadow: none !important;
   -moz-box-shadow: none !important;
+  -ms-box-shadow: none !important;
+  -o-box-shadow: none !important;
   box-shadow: none !important;
   color: #007db5 !important;
   -webkit-border-radius: 0px !important;
   -moz-border-radius: 0px !important;
+  -ms-border-radius: 0px !important;
+  -o-border-radius: 0px !important;
   border-radius: 0px !important;
   border: none !important;
   margin-bottom: 1px;
@@ -300,9 +365,13 @@ header button {
   background-color: #007db5;
   -webkit-box-shadow: 0 1px 0 #03b1ff, inset 0 1px 0 #00608a;
   -moz-box-shadow: 0 1px 0 #03b1ff, inset 0 1px 0 #00608a;
+  -ms-box-shadow: 0 1px 0 #03b1ff, inset 0 1px 0 #00608a;
+  -o-box-shadow: 0 1px 0 #03b1ff, inset 0 1px 0 #00608a;
   box-shadow: 0 1px 0 #03b1ff, inset 0 1px 0 #00608a;
   -webkit-border-radius: 4px;
   -moz-border-radius: 4px;
+  -ms-border-radius: 4px;
+  -o-border-radius: 4px;
   border-radius: 4px;
 }
 header .button:active,
@@ -313,13 +382,19 @@ article .button,
 article button {
   -webkit-border-radius: FORM-border-radius;
   -moz-border-radius: FORM-border-radius;
+  -ms-border-radius: FORM-border-radius;
+  -o-border-radius: FORM-border-radius;
   border-radius: FORM-border-radius;
   color: #fff;
   -webkit-border-radius: 2px;
   -moz-border-radius: 2px;
+  -ms-border-radius: 2px;
+  -o-border-radius: 2px;
   border-radius: 2px;
   -webkit-box-shadow: inset 0 1px 0 rgba(255,255,255,0.2);
   -moz-box-shadow: inset 0 1px 0 rgba(255,255,255,0.2);
+  -ms-box-shadow: inset 0 1px 0 rgba(255,255,255,0.2);
+  -o-box-shadow: inset 0 1px 0 rgba(255,255,255,0.2);
   box-shadow: inset 0 1px 0 rgba(255,255,255,0.2);
   border: solid 1px rgba(0,0,0,0.1);
 }
@@ -327,6 +402,8 @@ article .button:active,
 article button:active {
   -webkit-box-shadow: inset 0 0 128px rgba(0,0,0,0.25);
   -moz-box-shadow: inset 0 0 128px rgba(0,0,0,0.25);
+  -ms-box-shadow: inset 0 0 128px rgba(0,0,0,0.25);
+  -o-box-shadow: inset 0 0 128px rgba(0,0,0,0.25);
   box-shadow: inset 0 0 128px rgba(0,0,0,0.25);
   border-color: none;
 }
@@ -358,7 +435,7 @@ article .button.cancel,
 article .tag:not(.icon).cancel {
   background-color: #ee6557;
 }
-/* -------------------------- BUTTONS -------------------------- */
+/* -------------------------- FORMS -------------------------- */
 form label,
 .form label {
   color: #aaa;
@@ -402,9 +479,13 @@ form textarea,
   border: 1px solid #ddd;
   -webkit-box-shadow: inset 0 1px 2px rgba(0,0,0,0.1);
   -moz-box-shadow: inset 0 1px 2px rgba(0,0,0,0.1);
+  -ms-box-shadow: inset 0 1px 2px rgba(0,0,0,0.1);
+  -o-box-shadow: inset 0 1px 2px rgba(0,0,0,0.1);
   box-shadow: inset 0 1px 2px rgba(0,0,0,0.1);
   -webkit-border-radius: 0px;
   -moz-border-radius: 0px;
+  -ms-border-radius: 0px;
+  -o-border-radius: 0px;
   border-radius: 0px;
 }
 form input[type="text"].error,
@@ -526,6 +607,8 @@ form input[type=range],
 .form input[type=range] {
   -webkit-box-shadow: 'inset 0 1px 2px rgba(0,0,0,0.1)';
   -moz-box-shadow: 'inset 0 1px 2px rgba(0,0,0,0.1)';
+  -ms-box-shadow: 'inset 0 1px 2px rgba(0,0,0,0.1)';
+  -o-box-shadow: 'inset 0 1px 2px rgba(0,0,0,0.1)';
   box-shadow: 'inset 0 1px 2px rgba(0,0,0,0.1)';
 }
 form input[type=range]:not(.checkbox),
@@ -550,6 +633,8 @@ form input[type=range]::-webkit-slider-thumb,
 .form input[type=range]::-webkit-slider-thumb {
   -webkit-border-radius: 2px;
   -moz-border-radius: 2px;
+  -ms-border-radius: 2px;
+  -o-border-radius: 2px;
   border-radius: 2px;
   background-color: #e7eae2;
   border: solid 1px #d0d4c6;

+ 1 - 1
example/index.html

@@ -57,7 +57,7 @@
 </head>
 
 <body class="app">
-    <section id="main" data-transition="">
+    <section id="main" data-transition="" data-aside="features">
         <header>
             <nav class="left">
                 <a href="#features" data-router="aside" data-icon="menu"></a>

+ 12 - 0
src/Lungo.coffee

@@ -0,0 +1,12 @@
+Lungo = Lungo or {}
+
+Lungo.VERSION = "2.2"
+
+Lungo.DEVICE = null
+Lungo.Element or (Lungo.Element = {})
+Lungo.Data or (Lungo.Data = {})
+Lungo.Sugar or (Lungo.Sugar = {})
+Lungo.View or (Lungo.View = {})
+Lungo.Boot or (Lungo.Boot = {})
+Lungo.Device or (Lungo.Device = {})
+Lungo.ready or (Lungo.ready = Quo().ready)

+ 40 - 0
src/boot/Lungo.Boot.Data.coffee

@@ -0,0 +1,40 @@
+###
+Make an analysis of Data attributes in HTML elements and creates a <markup>
+based on each data type.
+
+@namespace Lungo.Boot
+@class Data
+
+@author Javier Jimenez Villar <javi@tapquo.com>  || @soyjavi
+@author Guillermo Pascual <pasku@tapquo.com>     || @pasku1
+@author Ignacio Olalde <ina@tapquo.com>          || @piniphone
+###
+
+Lungo.Boot.Data = do(lng = Lungo) ->
+  BINDING = lng.Constants.BINDING
+
+  ###
+  Initialize the <markup> data-attributes analisys
+  @method init
+  ###
+  init = (selector) ->
+    el = lng.dom(selector or document.body)
+    _findDataAttributesIn el if el.length > 0
+
+  _findDataAttributesIn = (element) ->
+    for key of lng.Attributes
+      _findElements element, key  if lng.Core.isOwnProperty(lng.Attributes, key)
+
+  _findElements = (element, key) ->
+    attribute = lng.Attributes[key]
+    selector = attribute.selector + "[data-" + key + "]"
+    element.find(selector).each (index, children) ->
+      el = lng.dom(children)
+      _bindDataAttribute el, el.data(key), attribute.html
+
+
+  _bindDataAttribute = (element, value, html) ->
+    html_binded = html.replace(BINDING.START + BINDING.KEY + BINDING.END, value)
+    element.prepend html_binded
+
+  init: init

+ 23 - 0
src/boot/Lungo.Boot.Device.coffee

@@ -0,0 +1,23 @@
+###
+@todo
+
+@namespace Lungo.Boot
+@class Device
+
+@author Javier Jimenez Villar <javi@tapquo.com> || @soyjavi
+###
+
+Lungo.Boot.Device = do(lng = Lungo) ->
+  DEVICE = lng.Constants.DEVICE
+
+  ###
+  @todo
+  @method init
+  ###
+  init = ->
+    env = lng.Core.environment()
+    lng.DEVICE = (if env.screen.width < 768 then DEVICE.PHONE else DEVICE.TABLET)
+    lng.dom(document.body).data "data", lng.DEVICE
+    lng.View.Aside.draggable()  if lng.DEVICE is lng.Constants.DEVICE.PHONE
+
+  init: init

+ 87 - 0
src/boot/Lungo.Boot.Events.coffee

@@ -0,0 +1,87 @@
+###
+Initialize the automatic DOM UI events
+
+@namespace Lungo.Boot
+@class Events
+
+@author Javier Jimenez Villar <javi@tapquo.com> || @soyjavi
+@author Guillermo Pascual <pasku@tapquo.com> || @pasku1
+###
+
+Lungo.Boot.Events = do(lng = Lungo) ->
+  ATTRIBUTE = lng.Constants.ATTRIBUTE
+  CLASS = lng.Constants.CLASS
+  ELEMENT = lng.Constants.ELEMENT
+  QUERY = lng.Constants.QUERY
+  SELECTORS = INPUT_CHECKBOX: "input[type=range].checkbox"
+
+  ###
+  Initializes the automatic subscription events by markup of the project.
+
+  @method init
+  ###
+  init = ->
+    lng.dom(QUERY.HREF_ROUTER).tap _loadTarget
+    lng.dom(QUERY.MENU_HREF).tap _closeMenu
+    lng.dom(QUERY.INPUT_CHECKBOX).tap _changeCheckboxValue
+
+  _loadTarget = (event) ->
+    event.preventDefault()
+    link = lng.dom(this)
+    if link.data("async")
+      _loadAsyncTarget link
+    else
+      _selectTarget link
+
+  _changeCheckboxValue = (event) ->
+    event.preventDefault()
+    el = lng.dom(this)
+    current_value = (if el.val() > 0 then 0 else 1)
+    el.toggleClass("active").attr "value", current_value
+
+  _closeMenu = (event) ->
+    event.preventDefault()
+    el = lng.dom(this)
+    parent = el.parent("[data-control=menu]").removeClass(CLASS.SHOW)
+    lng.dom("[data-router=menu] > .icon").attr "class", "icon " + el.data("icon")
+
+  _loadAsyncTarget = (link) ->
+    lng.Notification.show()
+    lng.Resource.load link.data("async")
+    link[0].removeAttribute "data-async"
+    lng.Boot.Data.init link.attr(ATTRIBUTE.HREF)
+    setTimeout (->
+      _selectTarget link
+      lng.Notification.hide()
+    ), lng.Constants.TRANSITION.DURATION * 2
+
+  _selectTarget = (link) ->
+    lng.View.Aside.hide()  if link.closest(ELEMENT.ASIDE).length > 0
+    target_type = link.data(ATTRIBUTE.ROUTER)
+    target_id = link.attr(ATTRIBUTE.HREF)
+    switch target_type
+      when ELEMENT.SECTION
+        _goSection target_id
+      when ELEMENT.ARTICLE
+        _goArticle link
+      when ELEMENT.ASIDE
+        lng.View.Aside.toggle()
+      when ELEMENT.MENU
+        _goMenu target_id
+
+  _goSection = (id) ->
+    id = lng.Core.parseUrl(id)
+    if id is "#back"
+      lng.Router.back()
+    else
+      lng.Router.section id
+
+  _goArticle = (element) ->
+    section_id = lng.Router.History.current()
+    article_id = element.attr(ATTRIBUTE.HREF)
+    lng.Router.article section_id, article_id, element
+
+  _goMenu = (id) ->
+    lng.dom("[data-control=menu]" + id).toggleClass CLASS.SHOW
+
+  init: init

+ 52 - 0
src/boot/Lungo.Boot.Layout.coffee

@@ -0,0 +1,52 @@
+###
+Initialize the Layout of LungoJS (if it's a mobile environment)
+
+@namespace Lungo.Boot
+@class Layout
+
+@author Javier Jimenez Villar <javi@tapquo.com> || @soyjavi
+###
+
+Lungo.Boot.Layout = do(lng = Lungo) ->
+  ELEMENT = lng.Constants.ELEMENT
+  CLASS = lng.Constants.CLASS
+  ATTRIBUTE = lng.Constants.ATTRIBUTE
+  QUERY = lng.Constants.QUERY
+
+  ###
+  Initializes all <section> & <article> of the project
+  @method init
+  ###
+  init = ->
+    lng.Fallback.fixPositionInAndroid()
+    _initFirstSection()
+    _initElement QUERY.LIST_IN_ELEMENT, _createListElement
+    _initElement QUERY.ELEMENT_SCROLLABLE, _scrollFix
+
+  _initFirstSection = ->
+    section = lng.dom(ELEMENT.SECTION).first()
+    lng.Router.section section.attr("id")  if section
+
+  _initElement = (selector, callback) ->
+    found_elements = lng.dom(selector)
+    i = 0
+    len = found_elements.length
+
+    while i < len
+      element = lng.dom(found_elements[i])
+      lng.Core.execute callback, element
+      i++
+
+  _createListElement = (element) ->
+    if element.children().length is 0
+      element_id = element.attr(ATTRIBUTE.ID)
+      element.append ELEMENT.LIST
+
+  _scrollFix = (element) ->
+    element[0].addEventListener "touchstart", ((event) ->
+      scrollTop = @scrollTop
+      @scrollTop = 1  if scrollTop <= 1
+      @scrollTop = @scrollHeight - @offsetHeight - 1  if scrollTop + @offsetHeight >= @scrollHeight
+    ), false
+
+  init: init

+ 66 - 0
src/data/Lungo.Data.Cache.coffee

@@ -0,0 +1,66 @@
+###
+Temporary cache system
+
+@namespace Lungo.Data
+@class Cache
+
+@author Javier Jimenez Villar <javi@tapquo.com> || @soyjavi
+@author Guillermo Pascual <pasku@tapquo.com> || @pasku1
+###
+
+Lungo.Data.Cache = do(lng = Lungo) ->
+  _cache = {}
+
+  ###
+  Sets in the LungoJS cache system a new key/value
+  @method set
+  @param {string} Key for the new value
+  @param {object} Type of environment: DESKTOP_ENVIRONMENT or MOBILE_ENVIRONMENT
+  ###
+  set = (key, value) ->
+    if exists(key)
+      _cache[key] = lng.Core.mix(get(key), value)
+    else
+      _cache[key] = value
+
+
+  ###
+  Returns the value of a given key.
+  @method get
+  @param {string} Key in LungoJS Cache System
+  @param {string} [OPTIONAL] Subkey in LungoJS Cache System
+  @return {object} Value
+  ###
+  get = (key, value) ->
+    if arguments.length is 1
+      _cache[key]
+    else
+      (if (_cache[arguments[0]]) then _cache[arguments[0]][arguments[1]] else undefined)
+
+
+  ###
+  Removes the instance in LungoJs Cache System of a given key
+  @method remove
+  @param {string} Key in LungoJS Cache System
+  @param {string} [OPTIONAL] Subkey in LungoJS Cache System
+  ###
+  remove = (key, value) ->
+    if arguments.length is 1
+      delete _cache[key]
+    else
+      delete _cache[arguments[0]][arguments[1]]
+
+
+  ###
+  Returns the existence of a key in LungoJs Cache System
+  @method exists
+  @param {String} Key in LungoJS Cache System
+  @return {Boolean} true if exists, false if not
+  ###
+  exists = (key) ->
+    (if (_cache[key]) then true else false)
+
+  set: set
+  get: get
+  remove: remove
+  exists: exists

+ 168 - 0
src/data/Lungo.Data.Sql.coffee

@@ -0,0 +1,168 @@
+###
+Wrapper for using WebSql (HTML5 feature)
+
+@namespace Lungo.Data
+@class Sql
+
+@author Javier Jimenez Villar <javi@tapquo.com> || @soyjavi
+@author Guillermo Pascual <pasku@tapquo.com> || @pasku1
+###
+
+Lungo.Data.Sql = do(lng = Lungo) ->
+  ERROR = lng.Constants.ERROR
+  CONFIG =
+    name: "lungo_db"
+    version: "1.0"
+    size: 65536
+    schema: []
+
+  db = null
+
+  ###
+  Initialize the SQLite storage (HTML5 Feature)
+  @method init
+  @param {object} Configuration for the Database
+  ###
+  init = (db_config) ->
+    CONFIG = lng.Core.mix(CONFIG, db_config)
+    db = openDatabase(CONFIG.name, CONFIG.version, CONFIG.name, CONFIG.size)
+    if db
+      _createSchema()
+    else
+      throw new Error(ERROR.DATABASE)
+
+
+  ###
+  Select a data set of a given table and based on a selection object
+  @method select
+  @param {string} Name of the table in the database
+  @param {object} [OPTIONAL] Object selection condition
+  @param {Function} Callback when the process is complete
+  ###
+  select = (table, where_obj, callback) ->
+    where = (if (where_obj) then " WHERE " + _convertToSql(where_obj, "AND") else "")
+    execute "SELECT * FROM " + table + where, (rs) ->
+      result = []
+      i = 0
+      len = rs.rows.length
+
+      while i < len
+        result.push rs.rows.item(i)
+        i++
+      _callbackResponse callback, result
+
+
+  ###
+  Inserts a data set of a given table and based on a data object
+  @method insert
+  @param {string} Name of the table in the database
+  @param {object} Object (or Array of objects) to insert in table
+  ###
+  insert = (table, data, callback) ->
+    if lng.Core.toType(data) is "object"
+      _insertRow table, data
+    else
+      for row of data
+        _insertRow table, data[row]
+
+
+  ###
+  Updates a data set of a given table and based on a data object and
+  an optional selection object
+  @method update
+  @param {string} Name of the table in the database
+  @param {object} Data object to update in table
+  @param {object} [OPTIONAL] Object selection condition
+  ###
+  update = (table, data_obj, where_obj, callback) ->
+    sql = "UPDATE " + table + " SET " + _convertToSql(data_obj, ",")
+    sql += " WHERE " + _convertToSql(where_obj, "AND")  if where_obj
+    execute sql
+
+
+  ###
+  Delete a data set of a given table and based on a selection object
+  @method drop
+  @param {string} Name of the table in the database
+  @param {object} [OPTIONAL] Object selection condition
+  ###
+  drop = (table, where_obj, callback) ->
+    where = (if (where_obj) then " WHERE " + _convertToSql(where_obj, "AND") else "")
+    execute "DELETE FROM " + table + where + ";"
+
+
+  ###
+  Executes a SQL statement in the SQLite storage
+  @method execute
+  @param {string} SQL statement
+  @param {Function} Callback when the process is complete
+  ###
+  execute = (sql, callback) ->
+    lng.Core.log 1, "lng.Data.Sql >> " + sql
+    db.transaction (transaction) ->
+      transaction.executeSql sql, [], ((transaction, rs) ->
+        _callbackResponse callback, rs
+      ), (transaction, error) ->
+        transaction.executedQuery = sql
+        _throwError.apply null, arguments
+
+
+  _createSchema = ->
+    schema = CONFIG.schema
+    schema_len = schema.length
+    return  unless schema_len
+    i = 0
+
+    while i < schema_len
+      current = schema[i]
+      _regenerateTable current
+      _createTable current.name, current.fields
+      i++
+
+  _createTable = (table, fields) ->
+    sql_fields = ""
+    for field of fields
+      if lng.Core.isOwnProperty(fields, field)
+        sql_fields += ", "  if sql_fields
+        sql_fields += field + " " + fields[field]
+    execute "CREATE TABLE IF NOT EXISTS " + table + " (" + sql_fields + ");"
+
+  _regenerateTable = (table) ->
+    _dropTable table.name  if table.drop is true
+
+  _dropTable = (table) ->
+    execute "DROP TABLE IF EXISTS " + table
+
+  _convertToSql = (fields, separator) ->
+    sql = ""
+    for field of fields
+      if lng.Core.isOwnProperty(fields, field)
+        value = fields[field]
+        sql += " " + separator + " "  if sql
+        sql += field + "="
+        sql += (if (isNaN(value)) then "\"" + value + "\"" else value)
+    sql
+
+  _callbackResponse = (callback, response) ->
+    setTimeout callback, 100, response  if lng.Core.toType(callback) is "function"
+
+  _insertRow = (table, row) ->
+    fields = ""
+    values = ""
+    for field of row
+      if lng.Core.isOwnProperty(row, field)
+        value = row[field]
+        fields += (if (fields) then ", " + field else field)
+        values += ", "  if values
+        values += (if (isNaN(value) or value is "") then "\"" + value + "\"" else value)
+    execute "INSERT INTO " + table + " (" + fields + ") VALUES (" + values + ")"
+
+  _throwError = (transaction, error) ->
+    throw new Error(ERROR.DATABASE_TRANSACTION + error.code + ": " + error.message + " \n Executed query: " + transaction.executedQuery)
+
+  init: init
+  select: select
+  insert: insert
+  update: update
+  drop: drop
+  execute: execute

+ 60 - 0
src/data/Lungo.Data.Storage.coffee

@@ -0,0 +1,60 @@
+###
+Wrapper for using LocalStorage & SessionStorage (HTML5 Feature)
+
+@namespace Lungo.Data
+@class Storage
+
+@author Javier Jimenez Villar <javi@tapquo.com> || @soyjavi
+@author Guillermo Pascual <pasku@tapquo.com> || @pasku1
+###
+
+Lungo.Data.Storage = (lng = Lungo) ->
+  STORAGE =
+    PERSISTENT: "localStorage"
+    SESSION: "sessionStorage"
+
+
+  ###
+  Wrapper for SessionStorage
+  @method persistent
+  @param {string} Key
+  @param {object} Value
+  @return {string} If no value assigned returns the value of established key
+  ###
+  persistent = (key, value) ->
+    _handler STORAGE.PERSISTENT, key, value
+
+
+  ###
+  Wrapper for SessionStorage
+  @method session
+  @param {string} Key
+  @param {object} Value
+  @return {string} If no value assigned returns the value of established key
+  ###
+  session = (key, value) ->
+    _handler STORAGE.SESSION, key, value
+
+
+  _handler = (storage, key, value) ->
+    storage = window[storage]
+    if value
+      _saveKey storage, key, value
+    else if value is null
+      _removeKey storage, key
+    else
+      _getKey storage, key
+
+  _saveKey = (storage, key, value) ->
+    value = JSON.stringify(value)
+    storage.setItem key, value
+
+  _removeKey = (storage, key) ->
+    storage.removeItem key
+
+  _getKey = (storage, key) ->
+    value = storage.getItem(key)
+    JSON.parse value
+
+  session: session
+  persistent: persistent

+ 16 - 0
src/element/Lungo.Element.Cache.coffee

@@ -0,0 +1,16 @@
+###
+DOM Elements caching
+
+@namespace Lungo.Element
+@class Cache
+
+@author Javier Jimenez Villar <javi@tapquo.com> || @soyjavi
+###
+
+
+Lungo.Element.Cache =
+  # sections: null,
+  section: null
+  article: null
+  aside: null
+  navigation: null

+ 115 - 0
src/element/Lungo.Element.Carousel.coffee

@@ -0,0 +1,115 @@
+###
+Creates a instance of Carousel Element
+
+@namespace Lungo.Element
+@class Carousel
+@version 1.0
+
+@author Ignacio Olalde <ina@tapquo.com> || @piniphone
+@author Javier Jimenez Villar <javi@tapquo.com> || @soyjavi
+###
+
+
+Lungo.Element.Carousel = (element, callback) ->
+  _instance =
+    index: 0
+    speed: 300
+    callback: callback
+    container: element
+    element: element.children[0]
+    slide: undefined
+    slides: []
+    slides_length: 0
+    width: 0
+    start: {}
+    isScrolling: undefined
+    deltaX: 0
+
+  prev = (delay) ->
+    _slide _instance.index - 1, _instance.speed  if _instance.index
+
+  next = (delay) ->
+    if _instance.index < _instance.slides_length - 1
+      _slide _instance.index + 1, _instance.speed
+    else
+      _slide 0, _instance.speed
+
+  position = ->
+    _instance.index
+
+  refresh = ->
+    _setup()
+
+  _setup = ->
+    _instance.slides = _instance.element.children
+    _instance.slides_length = _instance.slides.length
+    return null  if _instance.slides_length < 2
+    _instance.width = (if ("getBoundingClientRect" of _instance.container) then _instance.container.getBoundingClientRect().width else _instance.container.offsetWidth)
+    return null  unless _instance.width
+    _instance.element.style.width = (_instance.slides.length * _instance.width) + "px"
+    index = _instance.slides.length
+    while index--
+      el = _instance.slides[index]
+      el.style.width = _instance.width + "px"
+      el.style.display = "table-cell"
+      el.style.verticalAlign = "top"
+    _slide _instance.index, 0
+    _instance.container.style.visibility = "visible"
+
+  _slide = (index, duration) ->
+    style = _instance.element.style
+    duration = _instance.speed  if duration is undefined
+    style.webkitTransitionDuration = style.MozTransitionDuration = style.msTransitionDuration = style.OTransitionDuration = style.transitionDuration = duration + "ms"
+    style.MozTransform = style.webkitTransform = "translate3d(" + -(index * _instance.width) + "px,0,0)"
+    style.msTransform = style.OTransform = "translateX(" + -(index * _instance.width) + "px)"
+    _instance.index = index
+
+  _handleGestures = ->
+    _instance.element.addEventListener "touchstart", _touchStart, false
+    _instance.element.addEventListener "touchmove", _touchMove, false
+    _instance.element.addEventListener "touchend", _touchEnd, false
+    _instance.element.addEventListener "webkitTransitionEnd", _transitionEnd, false
+    _instance.element.addEventListener "msTransitionEnd", _transitionEnd, false
+    _instance.element.addEventListener "oTransitionEnd", _transitionEnd, false
+    _instance.element.addEventListener "transitionend", _transitionEnd, false
+    window.addEventListener "resize", _setup, false
+
+  _touchStart = (event) ->
+    _instance.start =
+      pageX: event.touches[0].pageX
+      pageY: event.touches[0].pageY
+      time: Number(new Date())
+
+    _instance.isScrolling = undefined
+    _instance.deltaX = 0
+    _instance.element.style.MozTransitionDuration = _instance.element.style.webkitTransitionDuration = 0
+    event.stopPropagation()
+
+  _touchMove = (e) ->
+    return  if e.touches.length > 1 or e.scale and e.scale isnt 1
+    _instance.deltaX = e.touches[0].pageX - _instance.start.pageX
+    _instance.isScrolling = !!(_instance.isScrolling or Math.abs(_instance.deltaX) < Math.abs(e.touches[0].pageY - _instance.start.pageY))  if typeof _instance.isScrolling is "undefined"
+    unless _instance.isScrolling
+      e.preventDefault()
+      factor = ((if (not _instance.index and _instance.deltaX > 0 or _instance.index is _instance.slides_length - 1 and _instance.deltaX < 0) then (Math.abs(_instance.deltaX) / _instance.width + 1) else 1))
+      _instance.deltaX = _instance.deltaX / factor
+      pos = (_instance.deltaX - _instance.index * _instance.width)
+      _instance.element.style.MozTransform = _instance.element.style.webkitTransform = "translate3d(" + pos + "px,0,0)"
+      e.stopPropagation()
+
+  _touchEnd = (e) ->
+    isValidSlide = Number(new Date()) - _instance.start.time < 250 and Math.abs(_instance.deltaX) > 20 or Math.abs(_instance.deltaX) > _instance.width / 2
+    isPastBounds = not _instance.index and _instance.deltaX > 0 or _instance.index is _instance.slides_length - 1 and _instance.deltaX < 0
+    _slide _instance.index + ((if isValidSlide and not isPastBounds then ((if _instance.deltaX < 0 then 1 else -1)) else 0)), _instance.speed  unless _instance.isScrolling
+    e.stopPropagation()
+
+  _transitionEnd = (event) ->
+    _instance.callback.apply _instance.callback, [_instance.index, _instance.slides[_instance.index]]  if _instance.callback
+
+  _setup()
+  _handleGestures()
+
+  prev: prev
+  next: next
+  position: position
+  refresh: refresh

+ 20 - 0
src/element/Lungo.Element.Count.coffee

@@ -0,0 +1,20 @@
+###
+Set a counter to the element
+
+@namespace Lungo.Element
+@class count
+
+@param  {string} Element query selector
+@param  {number} Value for counter
+
+@author Javier Jimenez Villar <javi@tapquo.com> || @soyjavi
+###
+
+
+Lungo.Element.count = (selector, count) ->
+  element = Lungo.dom(selector)
+  element.children(".tag.count").remove()
+  if element and count
+    binding = Lungo.Constants.BINDING.SELECTOR
+    html = Lungo.Attributes.count.html.replace(binding, count)
+    element.append html

+ 21 - 0
src/element/Lungo.Element.Loading.coffee

@@ -0,0 +1,21 @@
+###
+Creates a loading element in any area of layout
+
+@namespace Lungo.Element
+@method loading
+
+@param  {string}  Element query selector
+@param  {number}  stylesheet (null for hide)
+
+@author Javier Jimenez Villar <javi@tapquo.com> || @soyjavi
+###
+
+
+Lungo.Element.loading = (selector, stylesheet) ->
+  element = Lungo.dom(selector)
+  if element
+    html = null
+    if stylesheet
+      binding = Lungo.Constants.BINDING.SELECTOR
+      html = Lungo.Attributes.loading.html.replace(binding, stylesheet)
+    element.html html

+ 18 - 0
src/element/Lungo.Element.Progress.coffee

@@ -0,0 +1,18 @@
+###
+Set a progress to the element
+
+@namespace Lungo.Element
+@method Progress
+
+@param  {string}  Element query selector
+@param  {number}  Percentage
+
+@author Javier Jimenez Villar <javi@tapquo.com> || @soyjavi
+###
+
+
+Lungo.Element.progress = (selector, percentage) ->
+  element = Lungo.dom(selector)
+  if element
+    percentage += Lungo.Constants.ATTRIBUTE.PERCENT
+    element.find(".value").style Lungo.Constants.ATTRIBUTE.WIDTH, percentage

+ 111 - 0
src/element/Lungo.Element.Pull.coffee

@@ -0,0 +1,111 @@
+###
+Creates a instance of Pull & Refresh Element
+
+@namespace Lungo.Element
+@class Pull
+@version 1.0
+
+@author Ignacio Olalde <ina@tapquo.com> || @piniphone
+@author Javier Jimenez Villar <javi@tapquo.com> || @soyjavi
+###
+
+
+Lungo.Element.Pull = (element_selector, config_data) ->
+  REFRESHING_HEIGHT = 60
+  MAX_HEIGHT = 80
+  ANIMATION_TIME = 300
+  CURRENT_DISTANCE = 0
+  REFRESHING = false
+  ELEMENT = $$(element_selector)
+  CONTAINER = ELEMENT.siblings("div[data-control=\"pull\"]")
+  CONFIG = undefined
+  CONFIG_BASE =
+    onPull: "Pull down to refresh"
+    onRelease: "Release to..."
+    onRefresh: "Loading..."
+    callback: undefined
+
+  CONFIG = Lungo.Core.mix(CONFIG_BASE, config_data)
+  hide = ->
+    _moveElementTo 0, true
+    setTimeout (->
+      REFRESHING = false
+      document.removeEventListener "touchmove", _blockGestures, false
+    ), ANIMATION_TIME
+    CURRENT_DISTANCE = 0
+
+  _moveElementTo = (posY, animate) ->
+    newPos = (if posY > MAX_HEIGHT then MAX_HEIGHT else posY)
+    ELEMENT.addClass "pull"  if animate
+    ELEMENT.style "-webkit-transform", "translate(0, " + (newPos) + "px)"
+    if animate
+      setTimeout (->
+        ELEMENT.removeClass "pull"
+      ), ANIMATION_TIME
+
+  _refreshStart = (event) ->
+    REFRESHING = true
+    document.addEventListener "touchmove", _blockGestures, false
+    _setContainerTitle CONFIG.onRefresh
+    _setContainerLoading true
+    _moveElementTo REFRESHING_HEIGHT, true
+    CONFIG.callback.apply this  if CONFIG.callback
+
+  _setContainerTitle = (title) ->
+    CONTAINER.find("strong").html title
+
+  _setContainerLoading = (op) ->
+    if op
+      CONTAINER.addClass "refresh"
+    else
+      CONTAINER.removeClass "refresh"
+
+  _setContainerOnPulling = (op) ->
+    if op
+      CONTAINER.addClass "rotate"
+    else
+      CONTAINER.removeClass "rotate"
+
+  _blockGestures = (touchEvent) ->
+    touchEvent.preventDefault()
+
+  _handlePulling = (event) ->
+    _moveElementTo CURRENT_DISTANCE, false
+    _setContainerLoading false
+    if CURRENT_DISTANCE > REFRESHING_HEIGHT
+      _setContainerTitle CONFIG.onRelease
+      _setContainerOnPulling true
+    else
+      _setContainerTitle CONFIG.onPull
+      _setContainerOnPulling false
+
+  _handlePullEnd = (event) ->
+    if CURRENT_DISTANCE > REFRESHING_HEIGHT
+      _refreshStart()
+    else
+      hide()
+
+  (->
+    STARTED = false
+    INI_Y = {}
+    ELEMENT.bind("touchstart", (event) ->
+      if ELEMENT[0].scrollTop <= 1
+        STARTED = true
+        INI_Y = (if $$.isMobile() then event.touches[0].pageY else event.pageY)
+    ).bind("touchmove", (event) ->
+      if not REFRESHING and STARTED
+        current_y = (if $$.isMobile() then event.touches[0].pageY else event.pageY)
+        CURRENT_DISTANCE = current_y - INI_Y
+        if CURRENT_DISTANCE >= 0
+          ELEMENT.style "overflow-y", "hidden"
+          _handlePulling()
+    ).bind "touchend", ->
+      if STARTED
+        ELEMENT.style "overflow-y", "scroll"
+        _handlePullEnd()
+      INI_TOUCH = {}
+      STARTED = false
+
+  )()
+
+  hide: hide

+ 57 - 0
src/modules/Lungo.Attributes.coffee

@@ -0,0 +1,57 @@
+###
+Object with data-attributes (HTML5) with a special <markup>
+
+@namespace Lungo
+@class Attributes
+
+@author Javier Jimenez Villar <javi@tapquo.com> || @soyjavi
+@author Guillermo Pascual <pasku@tapquo.com> || @pasku1
+###
+Lungo.Attributes =
+  count:
+    selector: "*"
+    html: "<span class=\"tag theme count\">{{value}}</span>"
+
+  pull:
+    selector: "section"
+    html: """
+      <div class=\"{{value}}\" data-control=\"pull\" data-icon=\"down\" data-loading=\"black\">
+        <strong>title</strong>
+      </div>"""
+
+  progress:
+    selector: "*"
+    html: """
+      <div class=\"progress\">
+        <span class=\"bar\"><span class=\"value\" style=\"width:{{value}};\"></span></span>
+      </div>"""
+
+  label:
+    selector: "*"
+    html: "<abbr>{{value}}</abbr>"
+
+  icon:
+    selector: "*"
+    html: "<span class=\"icon {{value}}\"></span>"
+
+  image:
+    selector: "*"
+    html: "<img src=\"{{value}}\" class=\"icon\" />"
+
+  title:
+    selector: "header"
+    html: "<span class=\"title centered\">{{value}}</span>"
+
+  loading:
+    selector: "*"
+    html: """
+      <div class=\"loading {{value}}\">
+        <span class=\"top\"></span>
+        <span class=\"right\"></span>
+        <span class=\"bottom\"></span>
+        <span class=\"left\"></span>
+      </div>"""
+
+  back:
+    selector: "header"
+    html: "<nav class=\"left\"><a href=\"#back\" data-router=\"section\" class=\"left\"><span class=\"icon {{value}}\"></span></a></nav>"

+ 76 - 0
src/modules/Lungo.Constants.coffee

@@ -0,0 +1,76 @@
+###
+Object with data-attributes (HTML5) with a special <markup>
+
+@namespace Lungo
+@class Constants
+
+@author Javier Jimenez Villar <javi@tapquo.com> || @soyjavi
+###
+Lungo.Constants =
+  ELEMENT:
+    SECTION: "section"
+    ARTICLE: "article"
+    ASIDE: "aside"
+    MENU: "menu"
+    BODY: "body"
+    DIV: "div"
+    LIST: "<ul></ul>"
+    LI: "li"
+
+  QUERY:
+    LIST_IN_ELEMENT: "article.list, aside.list"
+    ELEMENT_SCROLLABLE: "aside.scroll, article.scroll"
+    HREF_ASIDE: "section[data-aside]"
+    HREF_ROUTER: "a[href][data-router]"
+    MENU_HREF: "[data-control=menu] a[href]"
+    INPUT_CHECKBOX: "input[type=range].checkbox"
+
+  CLASS:
+    ACTIVE: "active"
+    ASIDE: "aside"
+    SHOW: "show"
+    HIDE: "hide"
+    HIDING: "hiding"
+    RIGHT: "right"
+    LEFT: "left"
+    HORIZONTAL: "horizontal"
+    SMALL: "small"
+
+  TRIGGER:
+    LOAD: "load"
+    UNLOAD: "unload"
+
+  TRANSITION:
+    DURATION: 350
+
+  ATTRIBUTE:
+    ID: "id"
+    HREF: "href"
+    TITLE: "title"
+    ARTICLE: "article"
+    CLASS: "class"
+    WIDTH: "width"
+    HEIGHT: "height"
+    PIXEL: "px"
+    PERCENT: "%"
+    ROUTER: "router"
+    FIRST: "first"
+    LAST: "last"
+    EMPTY: ""
+
+  BINDING:
+    START: "{{"
+    END: "}}"
+    KEY: "value"
+    SELECTOR: "{{value}}"
+
+  DEVICE:
+    PHONE: "phone"
+    TABLET: "tablet"
+    TV: "tv"
+
+  ERROR:
+    DATABASE: "ERROR: Connecting to Data.Sql."
+    DATABASE_TRANSACTION: "ERROR: Data.Sql >> "
+    ROUTER: "ERROR: The target does not exists >>"
+    LOADING_RESOURCE: "ERROR: Loading resource: "

+ 204 - 0
src/modules/Lungo.Core.coffee

@@ -0,0 +1,204 @@
+###
+Contains all the common functions used in Lungo.
+
+@namespace Lungo
+@class Core
+
+@author Javier Jimenez Villar <javi@tapquo.com> || @soyjavi
+@author Guillermo Pascual <pasku@tapquo.com> || @pasku1
+###
+
+Lungo.Core = do(lng = Lungo, $$ = Quo) ->
+  ARRAY_PROTO = Array::
+  HASHTAG_CHARACTER = "#"
+
+  ###
+  Console system to display messages when you are in debug mode.
+
+  @method log
+
+  @param {number} Severity based in (1)Log, (2)Warn, (>2)Error
+  @param {string} Message to show in console
+  ###
+  log = (severity, message) ->
+    unless lng.Core.isMobile()
+      console[(if (severity is 1) then "log" else (if (severity is 2) then "warn" else "error"))] message
+    else
+
+
+  # @todo : send to the server
+
+  ###
+  Executes callbacks based on the parameters received.
+
+  @method execute
+
+  @param {Function} callback to execute
+  ###
+  execute = ->
+    args = toArray(arguments)
+    callback = args.shift()
+    callback.apply null, args  if toType(callback) is "function"
+
+
+  ###
+  Creates a new function that, when called, itself calls this function in
+  the context of the provided this value, with a given sequence of arguments
+  preceding any provided when the new function was called.
+
+  @method bind
+
+  @param {object} object to which the 'this' can refer in the new function when the new function is called.
+  @param {Function} method A function object.
+  ###
+  bind = (object, method) ->
+    ->
+      method.apply object, toArray(arguments)
+
+
+  ###
+  Copy from any number of objects and mix them all into a new object.
+  The implementation is simple; just loop through arguments and
+  copy every property of every object passed to the function.
+
+  @method mix
+
+  @param {object} arguments to mix them all into a new object.
+  @return {object} child a new object with all the objects from the arguments mixed.
+  ###
+  mix = ->
+    child = child or {}
+    arg = 0
+    len = arguments.length
+
+    while arg < len
+      argument = arguments[arg]
+      for prop of argument
+        child[prop] = argument[prop]  if isOwnProperty(argument, prop)
+      arg++
+    child
+
+
+  ###
+  Every object descended from Object inherits the hasOwnProperty method.
+  This method can be used to determine whether an object has the specified property
+  as a direct property of that object.
+
+  @param {object} object to test for a property's existence inside itself.
+  @param {string} property the name of the property to test.
+  @return {boolean} indicating whether the object has the specified property.
+  ###
+  isOwnProperty = (object, property) ->
+    $$.isOwnProperty object, property
+
+
+  ###
+  Determine the internal JavaScript [[Class]] of an object.
+
+  @param {object} obj to get the real type of itself.
+  @return {string} with the internal JavaScript [[Class]] of itself.
+  ###
+  toType = (obj) ->
+    $$.toType obj
+
+
+  ###
+  Convert an array-like object into a true JavaScript array.
+
+  @param {object} obj Any object to turn into a native Array.
+  @return {object} The object is now a plain array.
+  ###
+  toArray = (obj) ->
+    ARRAY_PROTO.slice.call obj, 0
+
+
+  ###
+  Determine if the current environment is a mobile environment
+
+  @method isMobile
+
+  @return {boolean} true if is mobile environment, false if not.
+  ###
+  isMobile = ->
+    $$.isMobile()
+
+
+  ###
+  Returns information of execute environment
+
+  @method environment
+
+  @return {object} Environment information
+  ###
+  environment = ->
+    $$.environment()
+
+
+  ###
+  Returns a ordered list of objects by a property
+
+  @method orderByProperty
+
+  @param {list} List of objects
+  @param {string} Name of property
+  @param {string} Type of order: asc (ascendent) or desc (descendent)
+  @return {list} Ordered list
+  ###
+  orderByProperty = (data, property, order) ->
+    order_operator = (if (order is "desc") then -1 else 1)
+    data.sort (a, b) ->
+      (if (a[property] < b[property]) then -order_operator else (if (a[property] > b[property]) then order_operator else 0))
+
+
+
+  ###
+  Returns a correct URL using hashtag character
+
+  @method parseUrl
+
+  @param {string} Url
+  @return {string} Url parsed
+  ###
+  parseUrl = (href) ->
+    href_hashtag = href.lastIndexOf(HASHTAG_CHARACTER)
+    if href_hashtag > 0
+      href = href.substring(href_hashtag)
+    else href = HASHTAG_CHARACTER + href  if href_hashtag is -1
+    href
+
+
+  ###
+  Returns a Object in a list by a property value
+
+  @method objectInListByProperty
+
+  @param {list} List of objects
+  @param {string} Name of property
+  @param {var} Value for comparision
+  @return {object} Instance of object founded (if exists)
+  ###
+  findByProperty = (list, property, value) ->
+    search = null
+    i = 0
+    len = list.length
+
+    while i < len
+      element = list[i]
+      if element[property] is value
+        search = element
+        break
+      i++
+    search
+
+  log: log
+  execute: execute
+  bind: bind
+  mix: mix
+  isOwnProperty: isOwnProperty
+  toType: toType
+  toArray: toArray
+  isMobile: isMobile
+  environment: environment
+  orderByProperty: orderByProperty
+  parseUrl: parseUrl
+  findByProperty: findByProperty

+ 17 - 0
src/modules/Lungo.Dom.coffee

@@ -0,0 +1,17 @@
+###
+LungoJS Dom Handler
+
+@namespace Lungo
+@class Dom
+
+@author Javier Jimenez Villar <javi@tapquo.com> || @soyjavi
+###
+
+###
+Add an event listener
+@method dom
+@param  {string} DOM selector
+@return {Object} QuoJS instance
+###
+Lungo.dom = (selector) ->
+  $$ selector

+ 20 - 0
src/modules/Lungo.Events.coffee

@@ -0,0 +1,20 @@
+###
+?
+
+@namespace Lungo
+@class Fallback
+
+@author Javier Jimenez Villar <javi@tapquo.com> || @soyjavi
+###
+
+Lungo.Events = do(lng = Lungo) ->
+  SPACE_CHAR = " "
+  init = (events) ->
+    for event of events
+      index_of = event.indexOf(SPACE_CHAR)
+      if index_of > 0
+        event_name = event.substring(0, index_of)
+        element = event.substring(index_of + 1)
+        lng.dom(element).on event_name, events[event]
+
+  init: init

+ 17 - 0
src/modules/Lungo.Fallback.coffee

@@ -0,0 +1,17 @@
+###
+?
+
+@namespace Lungo
+@class Fallback
+
+@author Javier Jimenez Villar <javi@tapquo.com> || @soyjavi
+###
+Lungo.Fallback = do(lng = Lungo) ->
+  fixPositionInAndroid = ->
+    env = lng.Core.environment()
+    _data "position", (if (env.isMobile and env.os.name is "Android" and env.os.version < "3") then "absolute" else "fixed")
+
+  _data = (key, value) ->
+    lng.dom(document.body).data key, value
+
+  fixPositionInAndroid: fixPositionInAndroid

+ 14 - 0
src/modules/Lungo.Init.coffee

@@ -0,0 +1,14 @@
+###
+Instance initializer
+
+@namespace Lungo
+@class Init
+
+@author Javier Jimenez Villar <javi@tapquo.com> || @soyjavi
+###
+Lungo.init = (config) ->
+  Lungo.Resource.load config.resources  if config and config.resources
+  Lungo.Boot.Device.init()
+  Lungo.Boot.Events.init()
+  Lungo.Boot.Data.init()
+  Lungo.Boot.Layout.init()

+ 141 - 0
src/modules/Lungo.Notification.coffee

@@ -0,0 +1,141 @@
+###
+Notification system in CSS3
+
+@namespace Lungo
+@class Notification
+
+@author Javier Jimenez Villar <javi@tapquo.com> || @soyjavi
+###
+
+Lungo.Notification = do(lng = Lungo) ->
+  _options = []
+  _el = null
+  _window = null
+  DELAY_TIME = 1
+  ANIMATION_MILISECONDS = 200
+  ATTRIBUTE = lng.Constants.ATTRIBUTE
+  BINDING = lng.Constants.BINDING
+  SELECTOR =
+    BODY: "body"
+    NOTIFICATION: ".notification"
+    MODAL: ".notification .window"
+    MODAL_HREF: ".notification .window a"
+    WINDOW_CLOSABLE: ".notification [data-action=close], .notification > .error, .notification > .success"
+    CONFIRM_BUTTONS: ".notification .confirm a.button"
+
+  STYLE =
+    MODAL: "modal"
+    VISIBLE: "visible"
+    SHOW: "show"
+    WORKING: "working"
+    INPUT: "input"
+
+  CALLBACK_HIDE = "Lungo.Notification.hide()"
+  MARKUP_NOTIFICATION = "<div class=\"notification\"><div class=\"window\"></div></div>"
+
+  ###
+  ###
+  show = (title, icon, seconds, callback) ->
+    markup = undefined
+    if title isnt undefined
+      markup = _markup(title, null, icon)
+    else
+      data_loading = lng.Attributes.loading.html
+      markup = data_loading.replace(BINDING.START + BINDING.KEY + BINDING.END, "icon white")
+    _show markup, "growl"
+    _hide seconds, callback
+
+
+  ###
+  ###
+  hide = ->
+    _window.removeClass "show"
+    setTimeout (->
+      _el.style("display", "none").removeClass("html").removeClass("confirm").removeClass("notify").removeClass "growl"
+    ), ANIMATION_MILISECONDS - 50
+
+
+  ###
+  ###
+  confirm = (options) ->
+    _options = options
+    markup = _markup(options.title, options.description, options.icon)
+    markup += _button_markup(options.accept, "accept")
+    markup += _button_markup(options.cancel, "cancel")
+    _show markup, "confirm"
+
+
+  ###
+  ###
+  success = (title, description, icon, seconds, callback) ->
+    _notify title, description, icon, "success", seconds, callback
+
+
+  ###
+  ###
+  error = (title, description, icon, seconds, callback) ->
+    _notify title, description, icon, "error", seconds, callback
+
+
+  ###
+  ###
+  _notify = (title, description, icon, stylesheet, seconds, callback) ->
+    _show _markup(title, description, icon), stylesheet
+    _hide seconds, callback  if seconds
+
+
+  ###
+  ###
+  html = (markup, button) ->
+    markup += (if (button) then "<a href=\"#\" class=\"button large anchor\" data-action=\"close\">" + button + "</a>" else "")
+    _show markup, "html"
+
+  _init = ->
+    lng.dom(SELECTOR.BODY).append MARKUP_NOTIFICATION
+    _el = lng.dom(SELECTOR.NOTIFICATION)
+    _window = _el.children(".window")
+    _subscribeEvents()
+
+  _show = (html, stylesheet) ->
+    _el.show()
+    _window.removeClass STYLE.SHOW
+    _window.removeClass("error").removeClass("success").removeClass("html").removeClass "growl"
+    _window.addClass stylesheet
+    _window.html html
+    setTimeout (->
+      _window.addClass STYLE.SHOW
+    ), DELAY_TIME
+
+  _hide = (seconds, callback) ->
+    if seconds isnt undefined and seconds isnt 0
+      miliseconds = seconds * 1000
+      setTimeout (->
+        hide()
+
+        # if (callback) callback.apply(callback);
+        setTimeout callback, ANIMATION_MILISECONDS  if callback
+      ), miliseconds
+
+  _markup = (title, description, icon) ->
+    description = (if not description then "&nbsp;" else description)
+    "<span class=\"icon " + icon + "\"></span><strong class=\"text bold\">" + title + "</strong><small>" + description + "</small>"
+
+  _button_markup = (options, callback) ->
+    "<a href=\"#\" data-callback=\"" + callback + "\" class=\"button anchor large text thin\">" + options.label + "</a>"
+
+  _subscribeEvents = ->
+    lng.dom(SELECTOR.CONFIRM_BUTTONS).tap (event) ->
+      button = lng.dom(this)
+      callback = _options[button.data("callback")].callback
+      callback.call callback  if callback
+      hide()
+
+    lng.dom(SELECTOR.WINDOW_CLOSABLE).tap hide
+
+  _init()
+  show: show
+  hide: hide
+  error: error
+  success: success
+  confirm: confirm
+  html: html

+ 53 - 0
src/modules/Lungo.Resource.coffee

@@ -0,0 +1,53 @@
+###
+Load Resources
+
+@namespace Lungo
+@class Resource
+
+@author Javier Jimenez Villar <javi@tapquo.com> || @soyjavi
+###
+
+
+Lungo.Resource = do(lng = Lungo, $$ = Quo) ->
+  ELEMENT = lng.Constants.ELEMENT
+  ERROR = lng.Constants.ERROR
+
+  ###
+  Start loading async sections (local & remote)
+
+  @method start
+  ###
+  load = (resource) ->
+    if lng.Core.toType(resource) is "array"
+      i = 0
+      len = resource.length
+
+      while i < len
+        _load resource[i]
+        i++
+    else
+      _load resource
+
+
+  ###
+  ###
+  _load = (resource) ->
+    try
+      response = _loadSyncResource(resource)
+      _pushResourceInBody response
+    catch error
+      lng.Core.log 3, error.message
+
+  _loadSyncResource = (url) ->
+    $$.ajax
+      url: url
+      async: false
+      dataType: "html"
+      error: ->
+        console.error ERROR.LOADING_RESOURCE + url
+
+
+  _pushResourceInBody = (section) ->
+    lng.dom(ELEMENT.BODY).append section  if lng.Core.toType(section) is "string"
+
+  load: load

+ 337 - 0
src/modules/Lungo.Scroll.coffee

@@ -0,0 +1,337 @@
+#! Overthrow v.0.1.0. An overflow:auto polyfill for responsive design. (c) 2012: Scott Jehl, Filament Group, Inc. http://filamentgroup.github.com/Overthrow/license.txt
+((w, undefined_) ->
+  doc = w.document
+  docElem = doc.documentElement
+  classtext = "scroll-enabled"
+
+  # Touch events are used in the polyfill, and thus are a prerequisite
+  canBeFilledWithPoly = "ontouchmove" of doc
+
+  # The following attempts to determine whether the browser has native overflow support
+  # so we can enable it but not polyfill
+
+  # Features-first. iOS5 overflow scrolling property check - no UA needed here. thanks Apple :)
+
+  # Touch events aren't supported and screen width is greater than X
+  # ...basically, this is a loose "desktop browser" check.
+  # It may wrongly opt-in very large tablets with no touch support.
+
+  # Hang on to your hats.
+  # Whitelist some popular, overflow-supporting mobile browsers for now and the future
+  # These browsers are known to get overlow support right, but give us no way of detecting it.
+  overflowProbablyAlreadyWorks = "WebkitOverflowScrolling" of docElem.style or (not canBeFilledWithPoly and w.screen.width > 1200) or (->
+    ua = w.navigator.userAgent
+
+    # Webkit crosses platforms, and the browsers on our list run at least version 534
+    webkit = ua.match(/AppleWebKit\/([0-9]+)/)
+    wkversion = webkit and webkit[1]
+    wkLte534 = webkit and wkversion >= 534
+
+    # Android 3+ with webkit gte 534
+    #                    ~: Mozilla/5.0 (Linux; U; Android 3.0; en-us; Xoom Build/HRI39) AppleWebKit/534.13 (KHTML, like Gecko) Version/4.0 Safari/534.13
+
+    # Blackberry 7+ with webkit gte 534
+    #                    ~: Mozilla/5.0 (BlackBerry; U; BlackBerry 9900; en-US) AppleWebKit/534.11+ (KHTML, like Gecko) Version/7.0.0 Mobile Safari/534.11+
+
+    # Blackberry Playbook with webkit gte 534
+    #                    ~: Mozilla/5.0 (PlayBook; U; RIM Tablet OS 1.0.0; en-US) AppleWebKit/534.8+ (KHTML, like Gecko) Version/0.0.1 Safari/534.8+
+
+    # Firefox Mobile (Fennec) 4 and up
+    #                    ~: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.7; rv:2.1.1) Gecko/ Firefox/4.0.2pre Fennec/4.0.
+
+    # WebOS 3 and up (TouchPad too)
+    #                    ~: Mozilla/5.0 (hp-tablet; Linux; hpwOS/3.0.0; U; en-US) AppleWebKit/534.6 (KHTML, like Gecko) wOSBrowser/233.48 Safari/534.6 TouchPad/1.0
+
+    # Nokia Browser N8
+    #                    ~: Mozilla/5.0 (Symbian/3; Series60/5.2 NokiaN8-00/012.002; Profile/MIDP-2.1 Configuration/CLDC-1.1 ) AppleWebKit/533.4 (KHTML, like Gecko) NokiaBrowser/7.3.0 Mobile Safari/533.4 3gpp-gba
+    #                    ~: Note: the N9 doesn't have native overflow with one-finger touch. wtf
+    ua.match(/Android ([0-9]+)/) and RegExp.$1 >= 3 and wkLte534 or ua.match(RegExp(" Version\\/([0-9]+)")) and RegExp.$1 >= 0 and w.blackberry and wkLte534 or ua.indexOf(/PlayBook/) > -1 and RegExp.$1 >= 0 and wkLte534 or ua.match(/Fennec\/([0-9]+)/) and RegExp.$1 >= 4 or ua.match(/wOSBrowser\/([0-9]+)/) and RegExp.$1 >= 233 and wkLte534 or ua.match(/NokiaBrowser\/([0-9\.]+)/) and parseFloat(RegExp.$1) is 7.3 and webkit and wkversion >= 533
+  )()
+
+  # Easing can use any of Robert Penner's equations (http://www.robertpenner.com/easing_terms_of_use.html). By default, overthrow includes ease-out-cubic
+  # arguments: t = current iteration, b = initial value, c = end value, d = total iterations
+  # use w.overthrow.easing to provide a custom function externally, or pass an easing function as a callback to the toss method
+  defaultEasing = (t, b, c, d) ->
+    c * ((t = t / d - 1) * t * t + 1) + b
+
+  enabled = false
+  timeKeeper = undefined
+
+  # Keeper of intervals
+
+  # toss scrolls and element with easing
+  #
+  #        // elem is the element to scroll
+  #        // options hash:
+  #            * left is the desired horizontal scroll. Default is "+0". For relative distances, pass a string with "+" or "-" in front.
+  #            * top is the desired vertical scroll. Default is "+0". For relative distances, pass a string with "+" or "-" in front.
+  #            * duration is the number of milliseconds the throw will take. Default is 100.
+  #            * easing is an optional custom easing function. Default is w.overthrow.easing. Must follow the easing function signature
+  #
+  toss = (elem, options) ->
+    i = 0
+    sLeft = elem.scrollLeft
+    sTop = elem.scrollTop
+    o =
+
+      # Toss defaults
+      top: "+0"
+      left: "+0"
+      duration: 100
+      easing: w.overthrow.easing
+
+    endLeft = undefined
+    endTop = undefined
+
+    # Mixin based on predefined defaults
+    if options
+      for j of o
+        o[j] = options[j]  if options[j] isnt undefined
+
+    # Convert relative values to ints
+    # First the left val
+    if typeof o.left is "string"
+      o.left = parseFloat(o.left)
+      endLeft = o.left + sLeft
+    else
+      endLeft = o.left
+      o.left = o.left - sLeft
+
+    # Then the top val
+    if typeof o.top is "string"
+      o.top = parseFloat(o.top)
+      endTop = o.top + sTop
+    else
+      endTop = o.top
+      o.top = o.top - sTop
+    timeKeeper = setInterval(->
+      if i++ < o.duration
+        elem.scrollLeft = o.easing(i, sLeft, o.left, o.duration)
+        elem.scrollTop = o.easing(i, sTop, o.top, o.duration)
+      else
+        elem.scrollLeft = endLeft  if endLeft isnt elem.scrollLeft
+        elem.scrollTop = endTop  if endTop isnt elem.scrollTop
+        intercept()
+    , 1)
+
+    # Return the values, post-mixin, with end values specified
+    top: endTop
+    left: endLeft
+    duration: o.duration
+    easing: o.easing
+
+
+  # find closest overthrow (elem or a parent)
+  closest = (target, ascend) ->
+    not ascend and target.className and target.className.indexOf("scroll") > -1 and target or closest(target.parentNode)
+
+
+  # Intercept any throw in progress
+  intercept = ->
+    clearInterval timeKeeper
+
+
+  # Enable and potentially polyfill overflow
+  enable = ->
+
+    # If it's on,
+    return  if enabled
+
+    # It's on.
+    enabled = true
+
+    # If overflowProbablyAlreadyWorks or at least the element canBeFilledWithPoly, add a class to cue CSS that assumes overflow scrolling will work (setting height on elements and such)
+    docElem.className += " " + classtext  if overflowProbablyAlreadyWorks or canBeFilledWithPoly
+
+    # Destroy everything later. If you want to.
+    w.overthrow.forget = ->
+
+      # Strip the class name from docElem
+      docElem.className = docElem.className.replace(classtext, "")
+
+      # Remove touch binding (check for method support since this part isn't qualified by touch support like the rest)
+      doc.removeEventListener "touchstart", start, false  if doc.removeEventListener
+
+      # reset easing to default
+      w.overthrow.easing = defaultEasing
+
+      # Let 'em know
+      enabled = false
+
+
+    # If overflowProbablyAlreadyWorks or it doesn't look like the browser canBeFilledWithPoly, our job is done here. Exit viewport left.
+    return  if overflowProbablyAlreadyWorks or not canBeFilledWithPoly
+
+    # Fill 'er up!
+    # From here down, all logic is associated with touch scroll handling
+    # elem references the overthrow element in use
+    elem = undefined
+
+    # The last several Y values are kept here
+    lastTops = []
+
+    # The last several X values are kept here
+    lastLefts = []
+    lastDown = undefined
+    lastRight = undefined
+
+    # lastDown will be true if the last scroll direction was down, false if it was up
+
+    # lastRight will be true if the last scroll direction was right, false if it was left
+
+    # For a new gesture, or change in direction, reset the values from last scroll
+    resetVertTracking = ->
+      lastTops = []
+      lastDown = null
+
+    resetHorTracking = ->
+      lastLefts = []
+      lastRight = null
+
+
+    # After releasing touchend, throw the overthrow element, depending on momentum
+    finishScroll = ->
+
+      # Come up with a distance and duration based on how
+      # Multipliers are tweaked to a comfortable balance across platforms
+      top = (lastTops[0] - lastTops[lastTops.length - 1]) * 8
+      left = (lastLefts[0] - lastLefts[lastLefts.length - 1]) * 8
+      duration = Math.max(Math.abs(left), Math.abs(top)) / 8
+
+      # Make top and left relative-style strings (positive vals need "+" prefix)
+      top = ((if top > 0 then "+" else "")) + top
+      left = ((if left > 0 then "+" else "")) + left
+
+      # Make sure there's a significant amount of throw involved, otherwise, just stay still
+      if not isNaN(duration) and duration > 0 and (Math.abs(left) > 80 or Math.abs(top) > 80)
+        toss elem,
+          left: left
+          top: top
+          duration: duration
+
+
+    inputs = undefined
+
+    # On webkit, touch events hardly trickle through textareas and inputs
+    # Disabling CSS pointer events makes sure they do, but it also makes the controls innaccessible
+    # Toggling pointer events at the right moments seems to do the trick
+    # Thanks Thomas Bachem http://stackoverflow.com/a/5798681 for the following
+    setPointers = (val) ->
+      inputs = elem.querySelectorAll("textarea, input")
+      i = 0
+      il = inputs.length
+
+      while i < il
+        inputs[i].style.pointerEvents = val
+        i++
+
+
+    # For nested overthrows, changeScrollTarget restarts a touch event cycle on a parent or child overthrow
+    changeScrollTarget = (startEvent, ascend) ->
+      if doc.createEvent
+        newTarget = (not ascend or ascend is undefined) and elem.parentNode or elem.touchchild or elem
+        tEnd = undefined
+        if newTarget isnt elem
+          tEnd = doc.createEvent("HTMLEvents")
+          tEnd.initEvent "touchend", true, true
+          elem.dispatchEvent tEnd
+          newTarget.touchchild = elem
+          elem = newTarget
+          newTarget.dispatchEvent startEvent
+
+
+    # Touchstart handler
+    # On touchstart, touchmove and touchend are freshly bound, and all three share a bunch of vars set by touchstart
+    # Touchend unbinds them again, until next time
+    start = (e) ->
+
+      # Stop any throw in progress
+      intercept()
+
+      # Reset the distance and direction tracking
+      resetVertTracking()
+      resetHorTracking()
+      elem = closest(e.target)
+      return  if not elem or elem is docElem or e.touches.length > 1
+      setPointers "none"
+      touchStartE = e
+      scrollT = elem.scrollTop
+      scrollL = elem.scrollLeft
+      height = elem.offsetHeight
+      width = elem.offsetWidth
+      startY = e.touches[0].pageY
+      startX = e.touches[0].pageX
+      scrollHeight = elem.scrollHeight
+      scrollWidth = elem.scrollWidth
+
+      # Touchmove handler
+      move = (e) ->
+        ty = scrollT + startY - e.touches[0].pageY
+        tx = scrollL + startX - e.touches[0].pageX
+        down = ty >= ((if lastTops.length then lastTops[0] else 0))
+        right = tx >= ((if lastLefts.length then lastLefts[0] else 0))
+
+        # If there's room to scroll the current container, prevent the default window scroll
+        if (ty > 0 and ty < scrollHeight - height) or (tx > 0 and tx < scrollWidth - width)
+          e.preventDefault()
+
+        # This bubbling is dumb. Needs a rethink.
+        else
+          changeScrollTarget touchStartE
+
+        # If down and lastDown are inequal, the y scroll has changed direction. Reset tracking.
+        resetVertTracking()  if lastDown and down isnt lastDown
+
+        # If right and lastRight are inequal, the x scroll has changed direction. Reset tracking.
+        resetHorTracking()  if lastRight and right isnt lastRight
+
+        # remember the last direction in which we were headed
+        lastDown = down
+        lastRight = right
+
+        # set the container's scroll
+        elem.scrollTop = ty
+        elem.scrollLeft = tx
+        lastTops.unshift ty
+        lastLefts.unshift tx
+        lastTops.pop()  if lastTops.length > 3
+        lastLefts.pop()  if lastLefts.length > 3
+
+
+      # Touchend handler
+      end = (e) ->
+
+        # Apply momentum based easing for a graceful finish
+        finishScroll()
+
+        # Bring the pointers back
+        setPointers "auto"
+        setTimeout (->
+          setPointers "none"
+        ), 450
+        elem.removeEventListener "touchmove", move, false
+        elem.removeEventListener "touchend", end, false
+
+      elem.addEventListener "touchmove", move, false
+      elem.addEventListener "touchend", end, false
+
+
+    # Bind to touch, handle move and end within
+    doc.addEventListener "touchstart", start, false
+
+
+  # Expose overthrow API
+  w.overthrow =
+    set: enable
+    forget: ->
+
+    easing: defaultEasing
+    toss: toss
+    intercept: intercept
+    closest: closest
+    support: (if overflowProbablyAlreadyWorks then "native" else canBeFilledWithPoly and "polyfilled" or "none")
+
+
+  # Auto-init
+  enable()
+) this

+ 118 - 0
src/modules/Lungo.Service.coffee

@@ -0,0 +1,118 @@
+###
+External Data & Services Manager
+
+@namespace Lungo
+@class Service
+@requires QuoJS
+
+@author Javier Jimenez Villar <javi@tapquo.com> || @soyjavi
+@author Guillermo Pascual <pasku@tapquo.com> || @pasku1
+###
+
+Lungo.Service = do(lng = Lungo, $$ = Quo) ->
+  URL_CACHE_INDEX_KEY = "lungojs_service_cache"
+  DATE_PATTERN =
+    MINUTE: "minute"
+    HOUR: "hour"
+    DAY: "day"
+
+
+  ###
+  Load data from the server using a HTTP GET request.
+
+  @method get
+
+  @param  {string} Containing the URL to which the request is sent
+  @param  {object} A map or string that is sent to the server with the request
+  @param  {Function} Callback function after the request [OPTIONAL]
+  @param  {string} Mime-Type: json, xml, html, or text [OPTIONAL]
+  ###
+  get = (url, data, success, dataType) ->
+    $$.get url, data, success, dataType
+
+
+  ###
+  Load data from the server using a HTTP POST request.
+
+  @method post
+
+  @param  {string} Containing the URL to which the request is sent
+  @param  {object} A map or string that is sent to the server with the request
+  @param  {Function} Callback function after the request [OPTIONAL]
+  @param  {string} Mime-Type: json, xml, html, or text [OPTIONAL]
+  ###
+  post = (url, data, success, dataType) ->
+    $$.post url, data, success, dataType
+
+
+  ###
+  Load data from the server using a HTTP GET request.
+
+  @method json
+
+  @param  {string} Containing the URL to which the request is sent
+  @param  {object} A map or string that is sent to the server with the request
+  @param  {Function} [OPTIONAL] Callback function after the request
+  ###
+  json = (url, data, success) ->
+    $$.json url, data, success
+
+
+  ###
+  Auto-caching system with date pattern.
+
+  @method cache
+
+  @param  {string} Containing the URL to which the request is sent
+  @param  {object} A map or string that is sent to the server with the request
+  @param  {string} Date pattern (example: 15 minutes, 1 hour, 3 days)
+  @param  {Function} [OPTIONAL] Callback function after the request
+  @param  {string} Mime-Type: json, xml, html, or text [OPTIONAL]
+  ###
+  cache = (url, data, date_pattern, callback, dataType) ->
+    url_key = url + $$.serializeParameters(data)
+    if _urlCached(url_key, date_pattern)
+      value = lng.Data.Storage.persistent(url_key)
+      callback.call callback, value  if value
+    else
+      $$.get url, data, ((result) ->
+        _saveServiceInCache url_key, result
+        callback.call callback, result
+      ), dataType
+
+  _urlCached = (url, date_pattern) ->
+    in_cache = false
+    url_cache_index = lng.Data.Storage.persistent(URL_CACHE_INDEX_KEY)
+    if url_cache_index
+      time_between = _calculateTimeSpent(url_cache_index[url])
+      in_cache = _checkIsValidPattern(time_between, date_pattern)
+    in_cache
+
+  _calculateTimeSpent = (url_last_access) ->
+    now = new Date().getTime()
+    service_last_access = new Date(url_last_access).getTime()
+    now - service_last_access
+
+  _checkIsValidPattern = (time_between, date_pattern) ->
+    pattern = date_pattern.split(" ")
+    diference_time = _calculateDiferenceTime(pattern[1], time_between)
+    (if (diference_time < pattern[0]) then true else false)
+
+  _calculateDiferenceTime = (pattern_name, time_between) ->
+    diference = (time_between / 1000) / 60
+    if pattern_name.indexOf(DATE_PATTERN.HOUR) >= 0
+      diference = diference / 60
+    else diference = (diference / 60) / 24  if pattern_name.indexOf(DATE_PATTERN.DAY) >= 0
+    diference
+
+  _saveServiceInCache = (url, result) ->
+    service_cache_index = lng.Data.Storage.persistent(URL_CACHE_INDEX_KEY) or {}
+    service_cache_index[url] = new Date()
+    lng.Data.Storage.persistent URL_CACHE_INDEX_KEY, service_cache_index
+    lng.Data.Storage.persistent url, result
+
+  get: get
+  post: post
+  json: json
+  cache: cache
+  Settings: $$.ajaxSettings

+ 47 - 0
src/router/Lungo.Router.History.coffee

@@ -0,0 +1,47 @@
+###
+Stores the displayed <sections> as a historical.
+
+@namespace Lungo.Router
+@class History
+
+@author Javier Jimenez Villar <javi@tapquo.com> || @soyjavi
+@author Guillermo Pascual <pasku@tapquo.com> || @pasku1
+###
+
+
+Lungo.Router.History = do ->
+  _history = []
+
+  ###
+  Create a new element to the browsing history based on the current section id.
+
+  @method add
+
+  @param  {string} Id of the section
+  ###
+  add = (section_id) ->
+    _history.push section_id  if section_id isnt current()
+
+
+  ###
+  Returns the current browsing history section id.
+
+  @method current
+
+  @return {string} Current section id
+  ###
+  current = ->
+    _history[_history.length - 1]
+
+
+  ###
+  Removes the current item browsing history.
+
+  @method removeLast
+  ###
+  removeLast = ->
+    _history.length -= 1
+
+  add: add
+  current: current
+  removeLast: removeLast

+ 133 - 0
src/router/Lungo.Router.coffee

@@ -0,0 +1,133 @@
+###
+Handles the <sections> and <articles> to show
+
+@namespace Lungo
+@class Router
+
+@author Javier Jimenez Villar <javi@tapquo.com> || @soyjavi
+@author Guillermo Pascual <pasku@tapquo.com> || @pasku1
+###
+
+Lungo.Router = do(lng = Lungo) ->
+  CLASS = lng.Constants.CLASS
+  ELEMENT = lng.Constants.ELEMENT
+  ERROR = lng.Constants.ERROR
+  TRIGGER = lng.Constants.TRIGGER
+  ATTRIBUTE = lng.Constants.ATTRIBUTE
+  DEVICE = lng.Constants.DEVICE
+  HASHTAG_CHARACTER = "#"
+
+  ###
+  Navigate to a <section>.
+
+  @method section
+
+  @param {string} Id of the <section>
+  ###
+  section = (section_id) ->
+    section_id = lng.Core.parseUrl(section_id)
+    current = lng.Element.Cache.section
+    if _notCurrentTarget(section_id, current)
+      query = ELEMENT.SECTION + section_id
+      target = (if (current) then current.siblings(query) else lng.dom(query))
+      if target.length > 0
+        if lng.DEVICE is DEVICE.PHONE
+          if current
+            _defineTransition target, current
+            current.removeClass(CLASS.SHOW).addClass CLASS.HIDE
+          _showPhoneSection target
+        else
+          _showTabletSection current, target
+        _cacheView current, target
+        lng.Router.History.add section_id
+
+
+  ###
+  Return to previous section.
+
+  @method back
+  ###
+  back = ->
+    lng.View.Aside.hide()  if lng.DEVICE is DEVICE.PHONE and lng.Element.Cache.aside and lng.Element.Cache.aside.hasClass(CLASS.SHOW)
+    lng.Router.History.removeLast()
+    current = lng.Element.Cache.section
+    target = current.siblings(ELEMENT.SECTION + lng.Router.History.current())
+    if lng.DEVICE is DEVICE.PHONE
+      current.removeClass CLASS.SHOW
+      setTimeout (->
+        current.removeClass CLASS.HIDING
+      ), lng.Constants.TRANSITION.DURATION
+      _assignTransition target, target.data("transition-origin")
+      _showPhoneSection target
+    else
+      _showTabletSection current, target
+    _cacheView current, target
+
+
+  ###
+  Displays the <article> in a particular <section>.
+
+  @method article
+
+  @param {string} <section> Id
+  @param {string} <article> Id
+  ###
+  article = (section_id, article_id, element) ->
+    article_id = lng.Core.parseUrl(article_id)
+    current = lng.Element.Cache.article
+    if _notCurrentTarget(article_id, current)
+      section section_id
+      target = lng.Element.Cache.section.find(ELEMENT.ARTICLE + article_id)
+      if target.length > 0
+        current = lng.Element.Cache.section.children(ELEMENT.ARTICLE)  if _sectionId(current) isnt _sectionId(target)
+        current.removeClass(CLASS.ACTIVE).trigger TRIGGER.UNLOAD
+        target.addClass(CLASS.ACTIVE).trigger TRIGGER.LOAD
+        lng.Element.Cache.article = target
+        lng.View.Article.switchNavItems article_id
+        lng.View.Article.switchReferenceItems article_id, lng.Element.Cache.section
+        lng.View.Article.title element.data(ATTRIBUTE.TITLE)  if element
+
+  _showPhoneSection = (target) ->
+    target.removeClass(CLASS.HIDE).addClass CLASS.SHOW
+
+  _showTabletSection = (current, target) ->
+    if current and not current.data("child")
+      current.addClass CLASS.HIDE
+      setTimeout (->
+        current.removeClass(CLASS.SHOW).removeClass CLASS.HIDE
+      ), lng.Constants.TRANSITION.DURATION
+    setTimeout (->
+      target.addClass CLASS.SHOW
+    ), lng.Constants.TRANSITION.DURATION
+
+  _cacheView = (current, target) ->
+    lng.Element.Cache.section = target
+    lng.Element.Cache.article = target.find(ELEMENT.ARTICLE + "." + CLASS.ACTIVE)
+    lng.Element.Cache.aside = lng.View.Aside.active(target)
+    _sectionTriggers current, target
+
+  _notCurrentTarget = (target, element) ->
+    (if (not element or target isnt HASHTAG_CHARACTER + element.attr("id")) then true else false)
+
+  _sectionId = (element) ->
+    element.parent("section").attr "id"
+
+  _sectionTriggers = (current, target) ->
+    current.trigger TRIGGER.UNLOAD  if current
+    target.trigger TRIGGER.LOAD
+
+  _defineTransition = (target, current) ->
+    target_transition = target.data("transition")
+    if target_transition
+      _assignTransitionOrigin current
+      _assignTransition current, target_transition
+
+  _assignTransition = (section, transitionName) ->
+    section.data "transition", transitionName
+
+  _assignTransitionOrigin = (section) ->
+    section.data "transition-origin", section.data("transition")
+
+  section: section
+  article: article
+  back: back

+ 45 - 0
src/view/Lungo.View.Article.coffee

@@ -0,0 +1,45 @@
+###
+Initialize the <articles> layout of a certain <section>
+
+@namespace Lungo.View
+@class Article
+
+@author Javier Jimenez Villar <javi@tapquo.com> || @soyjavi
+@author Guillermo Pascual <pasku@tapquo.com> || @pasku1
+###
+
+Lungo.View.Article = do(lng = Lungo) ->
+  ELEMENT = lng.Constants.ELEMENT
+  CLASS = lng.Constants.CLASS
+  ATTRIBUTE = lng.Constants.ATTRIBUTE
+  SELECTORS =
+    NAVIGATION_ITEM: "a[href][data-router=\"article\"]"
+    REFERENCE_LINK: " a[href][data-article]"
+    TITLE_OF_ARTICLE: "header .title, footer .title"
+    ASIDE_REFERENCE_LIST: "li a.active, li.active"
+
+
+  ###
+  ?
+
+  @method show
+  ###
+  title = (value) ->
+    lng.Element.Cache.section.find(SELECTORS.TITLE_OF_ARTICLE).text value  if value
+
+  switchNavItems = (article_id) ->
+    lng.Element.Cache.section.find(SELECTORS.NAVIGATION_ITEM).removeClass CLASS.ACTIVE
+    active_nav_items = "a[href=\"" + article_id + "\"][data-router=\"article\"]"
+    lng.Element.Cache.section.find(active_nav_items).addClass CLASS.ACTIVE
+    if lng.Element.Cache.aside
+      aside = lng.Element.Cache.aside
+      aside.find(SELECTORS.ASIDE_REFERENCE_LIST).removeClass CLASS.ACTIVE
+      aside.find(active_nav_items).addClass(CLASS.ACTIVE).parent().addClass CLASS.ACTIVE
+
+  switchReferenceItems = (article_id, section) ->
+    reference = "[data-article=" + article_id.replace("#", "") + "]"
+    section.find(SELECTORS.REFERENCE_LINK).hide().siblings(reference).show()
+
+  title: title
+  switchReferenceItems: switchReferenceItems
+  switchNavItems: switchNavItems

+ 142 - 0
src/view/Lungo.View.Aside.coffee

@@ -0,0 +1,142 @@
+###
+Initialize the <articles> layout of a certain <section>
+
+@namespace Lungo.View
+@class Aside
+
+@author Javier Jimenez Villar <javi@tapquo.com> || @soyjavi
+###
+
+Lungo.View.Aside = do(lng = Lungo) ->
+  ELEMENT = lng.Constants.ELEMENT
+  CLASS = lng.Constants.CLASS
+  ATTRIBUTE = lng.Constants.ATTRIBUTE
+  DEVICE = lng.Constants.DEVICE
+  QUERY = lng.Constants.QUERY
+  TRANSITION = lng.Constants.TRANSITION.DURATION
+
+  ###
+  Active aside for a determinate section
+
+  @method active
+
+  @param  {object} Section element
+  ###
+  active = (section) ->
+    aside_id = section.data("aside")
+    current_aside = lng.Element.Cache.aside
+
+    # Deactive
+    if (current_aside and aside_id isnt current_aside.attr("id")) or aside_id is null
+      current_aside.removeClass(CLASS.SHOW).removeClass CLASS.ACTIVE
+      lng.Element.Cache.aside = null
+
+    # Active
+    if aside_id
+      lng.Element.Cache.aside = lng.dom(ELEMENT.ASIDE + "#" + aside_id)
+      lng.Element.Cache.aside.addClass CLASS.ACTIVE
+      lng.View.Aside.show aside_id  unless lng.DEVICE is DEVICE.PHONE
+    lng.Element.Cache.aside
+
+
+  ###
+  Toggle an aside element
+
+  @method toggle
+
+  @param  {string} Aside id
+  ###
+  toggle = ->
+    if lng.Element.Cache.aside
+      is_visible = lng.Element.Cache.aside.hasClass(CLASS.SHOW)
+      if is_visible
+        lng.View.Aside.hide()
+      else
+        lng.View.Aside.show()
+
+
+  ###
+  Display an aside element with a particular <section>
+
+  @method show
+  ###
+  show = (aside) ->
+    if lng.Element.Cache.aside?
+      setTimeout (->
+        lng.Element.Cache.aside.addClass CLASS.SHOW
+      ), TRANSITION
+      if lng.DEVICE is DEVICE.PHONE
+        lng.Element.Cache.aside.addClass CLASS.SHOW
+        aside_stylesheet = _asideStylesheet()
+        lng.Element.Cache.section.addClass(aside_stylesheet).addClass CLASS.ASIDE
+
+
+  ###
+  Hide an aside element with a particular section
+
+  @method hide
+  ###
+  hide = ->
+    if lng.Element.Cache.aside?
+      if lng.DEVICE is DEVICE.PHONE
+        aside_stylesheet = _asideStylesheet()
+        console.error lng.Element.Cache
+
+        lng.Element.Cache.section.removeClass CLASS.ASIDE
+        setTimeout (->
+          lng.Element.Cache.aside.removeClass CLASS.SHOW
+        ), TRANSITION
+
+  ###
+  @todo
+
+  @method suscribeEvents
+  ###
+  draggable = ->
+    MIN_XDIFF = parseInt(document.body.getBoundingClientRect().width / 3, 10)
+    MIN_XDIFF = 128
+    lng.dom(QUERY.HREF_ASIDE).each ->
+      STARTED = false
+      el = lng.dom(this)
+      section = el.closest("section")
+      aside = lng.dom("aside#" + el.data("aside"))
+      section.swiping (gesture) ->
+        unless section.hasClass("aside")
+          xdiff = gesture.currentTouch.x - gesture.iniTouch.x
+          ydiff = Math.abs(gesture.currentTouch.y - gesture.iniTouch.y)
+          STARTED = (if STARTED then true else xdiff > 3 * ydiff and xdiff < 50)
+          if STARTED
+            xdiff = (if xdiff > 256 then 256 else (if xdiff < 0 then 0 else xdiff))
+            aside.addClass CLASS.SHOW
+            section.vendor "transform", "translateX(" + xdiff + "px)"
+            section.vendor "transition-duration", "0s"
+          else
+            section.attr "style", ""
+
+      section.swipe (gesture) ->
+        diff = gesture.currentTouch.x - gesture.iniTouch.x
+        ydiff = Math.abs(gesture.currentTouch.y - gesture.iniTouch.y)
+        section.attr "style", ""
+        if diff > MIN_XDIFF and STARTED
+          show aside
+        else
+          hide aside
+        STARTED = false
+
+
+
+  _asideStylesheet = ->
+    aside_stylesheet = lng.Element.Cache.aside.attr(ATTRIBUTE.CLASS)
+    classes = ""
+
+    #@todo: Refactor
+    if aside_stylesheet
+      classes += (if (aside_stylesheet.indexOf(CLASS.RIGHT) > -1) then CLASS.RIGHT + " " else "")
+      classes += (if (aside_stylesheet.indexOf(CLASS.SMALL) > -1) then CLASS.SMALL + " " else "")
+    classes
+
+  active: active
+  toggle: toggle
+  show: show
+  hide: hide
+  draggable: draggable