├── .gitignore ├── README.md ├── elm-package.json ├── examples ├── article │ ├── .DS_Store │ ├── dist │ │ ├── favicon.ico │ │ ├── index.html │ │ ├── js │ │ │ └── main.2ee6e429.js │ │ └── static │ │ │ └── media │ │ │ └── panda.12f77371.jpg │ ├── elm-package.json │ └── src │ │ ├── .DS_Store │ │ ├── Main.elm │ │ ├── Ports.elm │ │ ├── favicon.ico │ │ ├── index.html │ │ ├── index.js │ │ ├── main.css │ │ └── panda.jpg └── document │ ├── .DS_Store │ ├── dist │ ├── favicon.ico │ ├── index.html │ ├── js │ │ └── main.eab5c38b.js │ └── static │ │ └── media │ │ └── panda.12f77371.jpg │ ├── elm-package.json │ └── src │ ├── .DS_Store │ ├── Main.elm │ ├── Ports.elm │ ├── favicon.ico │ ├── index.html │ ├── index.js │ ├── main.css │ └── panda.jpg └── src └── ScrollProgress.elm /.gitignore: -------------------------------------------------------------------------------- 1 | elm-stuff 2 | elm.js 3 | examples 4 | .DS_Store -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # elm-scroll-progress 2 | 3 | Add a scroll progress indicator and track the `scrollTop` position of your app or a specific element. 4 | 5 | ```shell 6 | elm package install chrisbuttery/elm-scroll-progress 7 | ``` 8 | 9 | ### Examples 10 | You can scope to a specific element (e.g. and article) or to the document. 11 | 12 | [Article](http://chrisbuttery.github.io/elm-scroll-progress/examples/article/dist/index.html) | [Document](http://chrisbuttery.github.io/elm-scroll-progress/examples/document/dist/index.html) 13 | 14 | ## Usage 15 | 16 | 1. Import the module. 17 | 2. Create a new parent Msg type referencing the `ScrollProgress` `Msg`. 18 | 3. Subscribe to an incoming port, ensuring incoming data is passed back to `ScrollProgress.Progress`. 19 | 4. Cater for the ProgressMsg in your `update` function. 20 | 5. Map emitted messages from `ScrollProgress.view` to the type teh parent view expects (Msg). 21 | 22 | 23 | ```elm 24 | module Main exposing (..) 25 | import ScrollProgress exposing (..) 26 | 27 | -- MESSAGES 28 | 29 | type Msg 30 | = ProgressMsg ScrollProgress.Msg 31 | 32 | -- SUBSCRIPTIONS 33 | 34 | subscriptions : Model -> Sub Msg 35 | subscriptions model = 36 | Ports.onScroll (ProgressMsg << ScrollProgress.Progress) 37 | 38 | -- UPDATE 39 | 40 | update : Msg -> AppModel -> ( AppModel, Cmd Msg ) 41 | update message model = 42 | case message of 43 | ProgressMsg subMsg -> 44 | let 45 | ( updatedProgessModel, progressCmd ) = 46 | ScrollProgress.update subMsg model.progressModel 47 | in 48 | ( { model | progressModel = updatedProgessModel }, Cmd.map ProgressMsg progressCmd ) 49 | 50 | --VIEW 51 | 52 | view : AppModel -> Html Msg 53 | view model = 54 | Html.div [] 55 | [ Html.App.map ProgressMsg (ScrollProgress.view model.progressModel) 56 | , Html.h1 [] [ text "Read this " ] 57 | , Html.div [] [ text "Some article..." ] 58 | ] 59 | ``` 60 | 61 | Define an incoming port in your `Ports.elm` so JavaScript can pass in scrolling attributes. 62 | 63 | ```elm 64 | port module Ports exposing (..) 65 | import ProgressBar exposing (ScrollAttributes) 66 | 67 | port onScroll : (ScrollAttributes -> msg) -> Sub msg 68 | ``` 69 | 70 | In your JavaScript, add an eventListener to listen for the "scroll" event on the `window`. 71 | You can choose to target the document or a specifc element's `targetScrollHeight`. 72 | 73 | ```js 74 | var app = Elm.Main.embed(root); 75 | 76 | var target = document.querySelector('.some-article'); 77 | // or target the full page height with: 78 | // target = document.documentElement; 79 | 80 | window.addEventListener('scroll', function() { 81 | app.ports.onScroll.send({ 82 | scrollTop: document.documentElement.scrollTop || document.body.scrollTop, 83 | targetScrollHeight: target.scrollHeight, 84 | clientHeight: document.documentElement.clientHeight 85 | }); 86 | }); 87 | ``` 88 | Note: For this to work in Firefox, we need to check for `document.documentElement.scrollTop` otherwise fallback to `document.body.scrollTop`. 89 | 90 | ## Overrides 91 | 92 | All types for defining colors are `Maybe String`. 93 | By default, the color of the progress scale is defined as `Just #1684f6` however these `String` values can be whatever CSS colors you wish e.g: `"#336699"`, `"honeydew"`, `"rgba(0,0,0,0.5)"`, etc. 94 | 95 | You can choose to overide this color or include a linear gradient. 96 | 97 | To override the color of the element, define a new model for the Child inside of the Parent. 98 | 99 | ```elm 100 | module Main exposing (..) 101 | import ScrollProgress exposing (..) 102 | 103 | type alias AppModel = 104 | { progressModel : ScrollProgress.Model 105 | } 106 | 107 | newChildModel : ScrollProgress.Model 108 | newChildModel = 109 | { progress = 0 110 | , color = Just "hotpink" 111 | , from = Nothing 112 | , to = Nothing 113 | } 114 | 115 | initialModel : AppModel 116 | initialModel = 117 | { progressModel = newChildModel } 118 | ``` 119 | 120 | To replace a flat color for a linear gradient, populate the `from` and `to` properties. 121 | 122 | ```elm 123 | newChildModel : ScrollProgress.Model 124 | newChildModel = 125 | { progress = 0 126 | , color = Nothing 127 | , from = Just "lightseagreen" 128 | , to = Just "lightsalmon" 129 | } 130 | ``` 131 | 132 | # Building examples 133 | 134 | Install [Create Elm App](https://github.com/halfzebra/create-elm-app) and run `elm-app build` or `elm-app start` inside of `examples/article` & `examples/body`. 135 | 136 | Thanks goes to [Michael Troy](https://github.com/michaeltroy) for creating the examples. 137 | 138 | > [chrisbuttery.com](http://chrisbuttery.com) · 139 | > GitHub [@chrisbuttery](https://github.com/chrisbuttery) · 140 | > Twitter [@buttahz](https://twitter.com/buttahz) · 141 | > elm-lang slack [@butters](http://elmlang.herokuapp.com/) 142 | -------------------------------------------------------------------------------- /elm-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.3", 3 | "summary": "A scroll progress indicator", 4 | "repository": "https://github.com/chrisbuttery/elm-scroll-progress.git", 5 | "license": "BSD3", 6 | "source-directories": [ 7 | "src" 8 | ], 9 | "exposed-modules": [ 10 | "ScrollProgress" 11 | ], 12 | "dependencies": { 13 | "elm-lang/core": "4.0.5 <= v < 5.0.0", 14 | "elm-lang/html": "1.1.0 <= v < 2.0.0" 15 | }, 16 | "elm-version": "0.17.1 <= v < 0.18.0" 17 | } 18 | -------------------------------------------------------------------------------- /examples/article/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrisbuttery/elm-scroll-progress/3e4dd2c1812812f878ba4bf3435b2380b43c6c1c/examples/article/.DS_Store -------------------------------------------------------------------------------- /examples/article/dist/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrisbuttery/elm-scroll-progress/3e4dd2c1812812f878ba4bf3435b2380b43c6c1c/examples/article/dist/favicon.ico -------------------------------------------------------------------------------- /examples/article/dist/index.html: -------------------------------------------------------------------------------- 1 |
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
Story written by The Happy Panda
'+t+""}function a(t,r){return function(e,n,a){try{var c=u(t,r);return a||(c.renderer=i),d(t,c,e,n)}catch(l){throw e.innerHTML=o(l.message),l}}}function i(){return{update:function(){}}}function u(t,e){var n=e.main;if("undefined"==typeof n.init){var o=k(R.Nil),a=p.Tuple2(p.Tuple0,o);return Zt({init:function(){return a},view:function(){return n},update:r(function(){return a}),subscriptions:function(){return o}})}var i=e.flags,u=i?s(t,n.init,i):c(t,n.init);return Zt({init:u,view:n.view,update:n.update,subscriptions:n.subscriptions})}function c(t,r){return function(e){if("undefined"!=typeof e)throw new Error("You are giving module `"+t+"` an argument in JavaScript.\nThis module does not take arguments though! You probably need to change the\ninitialization code to something like `Elm."+t+".fullscreen()`"); 2 | return r()}}function s(t,r,e){return function(n){var o=l(Ht.run,e,n);if("Err"===o.ctor)throw new Error("You are trying to initialize module `"+t+"` with an unexpected argument.\nWhen trying to convert it to a usable Elm value, I run into this problem:\n\n"+o._0);return r(o._0)}}function d(t,r,e,n){function o(t,r){return $.nativeBinding(function(e){var n=l(c,t,r);r=n._0,i.update(_(r));var o=n._1,a=f(r);w(d,o,a),e($.succeed(r))})}function a(t){$.rawSend(g,t)}var i,u=r.init,c=r.update,f=r.subscriptions,_=r.view,s=r.renderer,d={},p=$.nativeBinding(function(t){var r=u(n),o=r._0;i=s(e,a,_(o));var c=r._1,l=f(o);w(d,c,l),t($.succeed(o))}),g=b(p,o),v=h(d,a);return v?{ports:v}:{}}function h(t,r){var e;for(var n in C){var o=C[n];o.isForeign&&(e=e||{},e[n]="cmd"===o.tag?S(n):L(n,r)),t[n]=g(o,r)}return e}function g(t,r){function e(t,r){if("self"===t.ctor)return f(i,n,t._0,r);var e=t._0;switch(o){case"cmd":return f(a,n,e.cmds,r);case"sub":return f(a,n,e.subs,r);case"fx":return _(a,n,e.cmds,e.subs,r)}}var n={main:r,self:void 0},o=t.tag,a=t.onEffects,i=t.onSelfMsg,u=b(t.init,e);return n.self=u,u}function v(t,r){return $.nativeBinding(function(e){t.main(r),e($.succeed(p.Tuple0))})}function m(t,r){return l($.send,t.self,{ctor:"self",_0:r})}function b(t,r){function e(t){var o=$.receive(function(e){return r(e,t)});return l(n,o,e)}var n=$.andThen,o=l(n,t,e);return $.rawSpawn(o)}function y(t){return function(r){return{type:"leaf",home:t,value:r}}}function k(t){return{type:"node",branches:t}}function T(t,r){return{type:"map",tagger:t,tree:r}}function w(t,r,e){var n={};B(!0,r,n,null),B(!1,e,n,null);for(var o in t){var a=o in n?n[o]:{cmds:R.Nil,subs:R.Nil};$.rawSend(t[o],{ctor:"fx",_0:a})}}function B(t,r,e,n){switch(r.type){case"leaf":var o=r.home,a=x(t,o,n,r.value);return void(e[o]=N(t,a,e[o]));case"node":for(var i=r.branches;"[]"!==i.ctor;)B(t,i._0,e,n),i=i._1;return;case"map":return void B(t,r.tree,e,{tagger:r.tagger,rest:n})}}function x(t,r,e,n){function o(t){for(var r=e;r;)t=r.tagger(t),r=r.rest;return t}var a=t?C[r].cmdMap:C[r].subMap;return l(a,o,n)}function N(t,r,e){return e=e||{cmds:R.Nil,subs:R.Nil},t?(e.cmds=R.Cons(r,e.cmds),e):(e.subs=R.Cons(r,e.subs),e)}function A(t){if(t in C)throw new Error("There can only be one port named `"+t+"`, but your program has multiple.")}function E(t,r){return A(t),C[t]={tag:"cmd",cmdMap:M,converter:r,isForeign:!0},y(t)}function S(t){function r(t,r,e){for(;"[]"!==r.ctor;){for(var n=i(r._0),o=0;o
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod 15 | tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, 16 | quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo 17 | consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse 18 | cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non 19 | proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod 20 | tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, 21 | quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo 22 | consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse 23 | cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non 24 | proident, sunt in culpa qui officia deserunt mollit anim id est laborum. 25 |
26 |Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod 27 | tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, 28 | quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo 29 | consequat.
30 |Duis aute irure dolor in reprehenderit in voluptate velit esse 31 | cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non 32 | proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod 33 | tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, 34 | quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo 35 | consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse 36 | cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non 37 | proident, sunt in culpa qui officia deserunt mollit anim id est laborum. 38 |
39 |Story written by The Happy Panda
40 |Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
Story written by The Happy Panda
'+t+""}function a(t,r){return function(e,n,a){try{var c=u(t,r);return a||(c.renderer=i),d(t,c,e,n)}catch(l){throw e.innerHTML=o(l.message),l}}}function i(){return{update:function(){}}}function u(t,e){var n=e.main;if("undefined"==typeof n.init){var o=k(R.Nil),a=p.Tuple2(p.Tuple0,o);return Zt({init:function(){return a},view:function(){return n},update:r(function(){return a}),subscriptions:function(){return o}})}var i=e.flags,u=i?s(t,n.init,i):c(t,n.init);return Zt({init:u,view:n.view,update:n.update,subscriptions:n.subscriptions})}function c(t,r){return function(e){if("undefined"!=typeof e)throw new Error("You are giving module `"+t+"` an argument in JavaScript.\nThis module does not take arguments though! You probably need to change the\ninitialization code to something like `Elm."+t+".fullscreen()`"); 2 | return r()}}function s(t,r,e){return function(n){var o=l(Ht.run,e,n);if("Err"===o.ctor)throw new Error("You are trying to initialize module `"+t+"` with an unexpected argument.\nWhen trying to convert it to a usable Elm value, I run into this problem:\n\n"+o._0);return r(o._0)}}function d(t,r,e,n){function o(t,r){return $.nativeBinding(function(e){var n=l(c,t,r);r=n._0,i.update(_(r));var o=n._1,a=f(r);w(d,o,a),e($.succeed(r))})}function a(t){$.rawSend(g,t)}var i,u=r.init,c=r.update,f=r.subscriptions,_=r.view,s=r.renderer,d={},p=$.nativeBinding(function(t){var r=u(n),o=r._0;i=s(e,a,_(o));var c=r._1,l=f(o);w(d,c,l),t($.succeed(o))}),g=b(p,o),v=h(d,a);return v?{ports:v}:{}}function h(t,r){var e;for(var n in C){var o=C[n];o.isForeign&&(e=e||{},e[n]="cmd"===o.tag?O(n):L(n,r)),t[n]=g(o,r)}return e}function g(t,r){function e(t,r){if("self"===t.ctor)return f(i,n,t._0,r);var e=t._0;switch(o){case"cmd":return f(a,n,e.cmds,r);case"sub":return f(a,n,e.subs,r);case"fx":return _(a,n,e.cmds,e.subs,r)}}var n={main:r,self:void 0},o=t.tag,a=t.onEffects,i=t.onSelfMsg,u=b(t.init,e);return n.self=u,u}function v(t,r){return $.nativeBinding(function(e){t.main(r),e($.succeed(p.Tuple0))})}function m(t,r){return l($.send,t.self,{ctor:"self",_0:r})}function b(t,r){function e(t){var o=$.receive(function(e){return r(e,t)});return l(n,o,e)}var n=$.andThen,o=l(n,t,e);return $.rawSpawn(o)}function y(t){return function(r){return{type:"leaf",home:t,value:r}}}function k(t){return{type:"node",branches:t}}function T(t,r){return{type:"map",tagger:t,tree:r}}function w(t,r,e){var n={};B(!0,r,n,null),B(!1,e,n,null);for(var o in t){var a=o in n?n[o]:{cmds:R.Nil,subs:R.Nil};$.rawSend(t[o],{ctor:"fx",_0:a})}}function B(t,r,e,n){switch(r.type){case"leaf":var o=r.home,a=x(t,o,n,r.value);return void(e[o]=N(t,a,e[o]));case"node":for(var i=r.branches;"[]"!==i.ctor;)B(t,i._0,e,n),i=i._1;return;case"map":return void B(t,r.tree,e,{tagger:r.tagger,rest:n})}}function x(t,r,e,n){function o(t){for(var r=e;r;)t=r.tagger(t),r=r.rest;return t}var a=t?C[r].cmdMap:C[r].subMap;return l(a,o,n)}function N(t,r,e){return e=e||{cmds:R.Nil,subs:R.Nil},t?(e.cmds=R.Cons(r,e.cmds),e):(e.subs=R.Cons(r,e.subs),e)}function A(t){if(t in C)throw new Error("There can only be one port named `"+t+"`, but your program has multiple.")}function E(t,r){return A(t),C[t]={tag:"cmd",cmdMap:M,converter:r,isForeign:!0},y(t)}function O(t){function r(t,r,e){for(;"[]"!==r.ctor;){for(var n=i(r._0),o=0;o
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod 15 | tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, 16 | quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo 17 | consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse 18 | cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non 19 | proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod 20 | tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, 21 | quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo 22 | consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse 23 | cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non 24 | proident, sunt in culpa qui officia deserunt mollit anim id est laborum. 25 |
26 |Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod 27 | tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, 28 | quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo 29 | consequat.
30 |Duis aute irure dolor in reprehenderit in voluptate velit esse 31 | cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non 32 | proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod 33 | tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, 34 | quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo 35 | consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse 36 | cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non 37 | proident, sunt in culpa qui officia deserunt mollit anim id est laborum. 38 |
39 |Story written by The Happy Panda
40 |