├── .gitignore ├── ChangeLog.md ├── LICENSE ├── README.md ├── Setup.hs ├── app └── Main.hs ├── docs ├── atom.xml ├── css │ ├── style.css │ └── syntax.css ├── images │ ├── code.jpg │ ├── github-logo.png │ └── twitter-logo.png ├── index.html ├── js │ └── main.js └── posts │ └── sample-post.html ├── package.yaml ├── site-example.png ├── site ├── css │ ├── style.css │ └── syntax.css ├── images │ ├── code.jpg │ ├── github-logo.png │ └── twitter-logo.png ├── js │ └── main.js ├── posts │ └── sample-post.md └── templates │ ├── atom.xml │ ├── footer.html │ ├── header.html │ ├── index.html │ ├── meta-data.html │ ├── post-list.html │ └── post.html ├── slick-template.cabal ├── stack.yaml └── stack.yaml.lock /.gitignore: -------------------------------------------------------------------------------- 1 | .stack-work/ 2 | *~ 3 | .shake 4 | -------------------------------------------------------------------------------- /ChangeLog.md: -------------------------------------------------------------------------------- 1 | # Changelog for my site 2 | 3 | ## Unreleased changes 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright AUTHOR (c) 2018 2 | 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above 12 | copyright notice, this list of conditions and the following 13 | disclaimer in the documentation and/or other materials provided 14 | with the distribution. 15 | 16 | * Neither the name of AUTHOR nor the names of other 17 | contributors may be used to endorse or promote products derived 18 | from this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Slick Template 2 | 3 | A cloneable template for building a static site with [slick](https://github.com/ChrisPenner/slick)! 4 | 5 | For an example of what the site will look like, check out [my blog](https://chrispenner.ca)! 6 | 7 | ![my site](./site-example.png) 8 | 9 | Here's [the configuration](https://github.com/ChrisPenner/ChrisPenner.github.io/blob/site/app/Main.hs) for my site if you're curious. 10 | 11 | Get up and running with slick in no time! 12 | 13 | Here's all you need to do to have your own site: 14 | 15 | 1. Click the green `Use this template` button at the top of this page (next to "clone") 16 | 2. Clone your new site repo 17 | 3. Edit your `siteMeta` inside `Main.hs` 18 | 4. Add some awesome blog posts in `site/posts/` by copying the sample post there 19 | 4. run `stack build`; `stack exec build-site` 20 | 5. Serve your `docs` directory by enabling Github Pages in your repository's settings 21 | 6. ...? 22 | 7. Profit! 23 | 24 | If you want a quick tool for serving your file system during development I recommend using `serve`: 25 | 26 | ```shell 27 | $ npm install -g serve 28 | $ serve docs 29 | ``` 30 | 31 | Then navigate to the port which is serving (usually http://localhost:3000 or http://localhost:5000 ) 32 | 33 | 34 | --- 35 | 36 | # Tips 37 | 38 | * Everything is just html, css, and javascript! Edit things to your heart's content! 39 | * Templates are in `site/tempates`; they use the [Mustache](https://mustache.github.io/) template language. 40 | * You'll need to delete your `.shake` directory when you edit `Main.hs` to avoid stale build caches. 41 | * Slick is good at **updating** and **creating** files, but it doesn't delete stale files. When in doubt you can delete your whole output directory. 42 | 43 | # Caching guide 44 | 45 | Shake takes care of most of the tricky parts, but there're still a few things you need to know. 46 | 47 | Cache-busting in Slick works using [`Development.Shake.Forward`](https://hackage.haskell.org/package/shake/docs/Development-Shake-Forward.html). The idea is that you can wrap actions with [`cacheAction`](https://hackage.haskell.org/package/shake-0.18.3/docs/Development-Shake-Forward.html#v:cacheAction), providing an unique identifier for each time it runs. Shake will track any dependencies which are triggered during the first run of that action and can use them to detect when that particular action must be re-run. Typically you'll want to cache an action for each "thing" you have to load, e.g. when you load a post, or when you build a page. You can also nest these caches if you like. 48 | 49 | When using `cacheAction` Shake will automatically serialize and store the results of that action to disk so that on a later build it can simply 'hydrate' that asset without running the command. For this reason, your data models should probably implement `Binary`. Here's an example data model: 50 | 51 | ```haskell 52 | {-# LANGUAGE DeriveGeneric #-} 53 | {-# LANGUAGE DeriveAnyClass #-} 54 | 55 | import Data.Aeson (ToJSON, FromJSON) 56 | import Development.Shake.Classes (Binary) 57 | import GHC.Generics (Generic) 58 | 59 | -- | Data for a blog post 60 | data Post = 61 | Post { title :: String 62 | , author :: String 63 | , content :: String 64 | , url :: String 65 | , date :: String 66 | , image :: Maybe String 67 | } 68 | deriving (Generic, Eq, Ord, Show, FromJSON, ToJSON, Binary) 69 | ``` 70 | 71 | If you need to run arbitrary shell commands you can use [`cache`](https://hackage.haskell.org/package/shake-0.18.3/docs/Development-Shake-Forward.html#v:cache); it will do its best to track file use during the run of the command and cache-bust on that; results may vary. It's likely better to use explicit tracking commands like `readFile'` when possible, (or even just use `readFile'` on the files you depend on, then throw away the results. It's equivalent to explicitly depending on the file contents). 72 | 73 | Shake has many dependency tracking combinators available; whenever possible you should use the shake variants of these (e.g. `copyFileChanged`, `readFile'`, `writeFile'`, etc.). This will allow shake to detect when and what it needs to rebuild. 74 | 75 | Note: You'll likely need to delete `.shake` in your working directory after editing your `Main.hs` file as shake can get confused if rules change without it noticing. 76 | 77 | ## Example 78 | 79 | Here's a FULL static website! This is the [`Main.hs`](app/Main.hs) for this template repo. 80 | 81 | ```haskell 82 | {-# LANGUAGE DeriveGeneric #-} 83 | {-# LANGUAGE DeriveAnyClass #-} 84 | {-# LANGUAGE OverloadedStrings #-} 85 | 86 | module Main where 87 | 88 | import Control.Lens 89 | import Control.Monad 90 | import Data.Aeson as A 91 | import Data.Aeson.Lens 92 | import Development.Shake 93 | import Development.Shake.Classes (Binary) 94 | import Development.Shake.Forward 95 | import Development.Shake.FilePath 96 | import GHC.Generics (Generic) 97 | import Slick 98 | 99 | import qualified Data.Text as T 100 | 101 | 102 | outputFolder :: FilePath 103 | outputFolder = "docs/" 104 | 105 | -- | Data for the index page 106 | data IndexInfo = 107 | IndexInfo 108 | { posts :: [Post] 109 | } deriving (Generic, Show, FromJSON, ToJSON) 110 | 111 | -- | Data for a blog post 112 | data Post = 113 | Post { title :: String 114 | , author :: String 115 | , content :: String 116 | , url :: String 117 | , date :: String 118 | , image :: Maybe String 119 | } 120 | deriving (Generic, Eq, Ord, Show, FromJSON, ToJSON, Binary) 121 | 122 | -- | given a list of posts this will build a table of contents 123 | buildIndex :: [Post] -> Action () 124 | buildIndex posts' = do 125 | indexT <- compileTemplate' "site/templates/index.html" 126 | let indexInfo = IndexInfo {posts = posts'} 127 | indexHTML = T.unpack $ substitute indexT (toJSON indexInfo) 128 | writeFile' (outputFolder "index.html") indexHTML 129 | 130 | -- | Find and build all posts 131 | buildPosts :: Action [Post] 132 | buildPosts = do 133 | pPaths <- getDirectoryFiles "." ["site/posts//*.md"] 134 | forP pPaths buildPost 135 | 136 | -- | Load a post, process metadata, write it to output, then return the post object 137 | -- Detects changes to either post content or template 138 | buildPost :: FilePath -> Action Post 139 | buildPost srcPath = cacheAction ("build" :: T.Text, srcPath) $ do 140 | liftIO . putStrLn $ "Rebuilding post: " <> srcPath 141 | postContent <- readFile' srcPath 142 | -- load post content and metadata as JSON blob 143 | postData <- markdownToHTML . T.pack $ postContent 144 | let postUrl = T.pack . dropDirectory1 $ srcPath -<.> "html" 145 | withPostUrl = _Object . at "url" ?~ String postUrl 146 | -- Add additional metadata we've been able to compute 147 | let fullPostData = withPostUrl $ postData 148 | template <- compileTemplate' "site/templates/post.html" 149 | writeFile' (outputFolder T.unpack postUrl) . T.unpack $ substitute template fullPostData 150 | -- Convert the metadata into a Post object 151 | convert fullPostData 152 | 153 | -- | Copy all static files from the listed folders to their destination 154 | copyStaticFiles :: Action () 155 | copyStaticFiles = do 156 | filepaths <- getDirectoryFiles "./site/" ["images//*", "css//*", "js//*"] 157 | void $ forP filepaths $ \filepath -> 158 | copyFileChanged ("site" filepath) (outputFolder filepath) 159 | 160 | -- | Specific build rules for the Shake system 161 | -- defines workflow to build the website 162 | buildRules :: Action () 163 | buildRules = do 164 | allPosts <- buildPosts 165 | buildIndex allPosts 166 | copyStaticFiles 167 | 168 | main :: IO () 169 | main = slick buildRules 170 | ``` 171 | -------------------------------------------------------------------------------- /Setup.hs: -------------------------------------------------------------------------------- 1 | import Distribution.Simple 2 | main = defaultMain 3 | -------------------------------------------------------------------------------- /app/Main.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE DeriveGeneric #-} 2 | {-# LANGUAGE DeriveAnyClass #-} 3 | {-# LANGUAGE OverloadedStrings #-} 4 | {-# LANGUAGE DuplicateRecordFields #-} 5 | 6 | module Main where 7 | 8 | import Control.Lens ((?~), at) 9 | import Control.Monad (void) 10 | import Data.Aeson (FromJSON, ToJSON, Value (Object, String), toJSON) 11 | import Data.Aeson.KeyMap (union) 12 | import Data.Aeson.Lens (_Object) 13 | import qualified Data.Text as T 14 | import Data.Time 15 | ( UTCTime, defaultTimeLocale, formatTime, getCurrentTime 16 | , iso8601DateFormat, parseTimeOrError) 17 | import Development.Shake 18 | ( Action, Verbosity(Verbose), copyFileChanged, forP, getDirectoryFiles 19 | , liftIO, readFile', shakeLintInside, shakeOptions, shakeVerbosity 20 | , writeFile') 21 | import Development.Shake.Classes (Binary) 22 | import Development.Shake.FilePath ((), (-<.>), dropDirectory1) 23 | import Development.Shake.Forward (cacheAction, shakeArgsForward) 24 | import GHC.Generics (Generic) 25 | import Slick (compileTemplate', convert, markdownToHTML, substitute) 26 | 27 | 28 | ---Config----------------------------------------------------------------------- 29 | 30 | siteMeta :: SiteMeta 31 | siteMeta = 32 | SiteMeta { siteAuthor = "Me" 33 | , baseUrl = "https://example.com" 34 | , siteTitle = "My Slick Site" 35 | , twitterHandle = Just "myslickhandle" 36 | , githubUser = Just "myslickgithubuser" 37 | } 38 | 39 | outputFolder :: FilePath 40 | outputFolder = "docs/" 41 | 42 | --Data models------------------------------------------------------------------- 43 | 44 | withSiteMeta :: Value -> Value 45 | withSiteMeta (Object obj) = Object $ union obj siteMetaObj 46 | where 47 | Object siteMetaObj = toJSON siteMeta 48 | withSiteMeta _ = error "only add site meta to objects" 49 | 50 | data SiteMeta = 51 | SiteMeta { siteAuthor :: String 52 | , baseUrl :: String -- e.g. https://example.ca 53 | , siteTitle :: String 54 | , twitterHandle :: Maybe String -- Without @ 55 | , githubUser :: Maybe String 56 | } 57 | deriving (Generic, Eq, Ord, Show, ToJSON) 58 | 59 | -- | Data for the index page 60 | data IndexInfo = 61 | IndexInfo 62 | { posts :: [Post] 63 | } deriving (Generic, Show, FromJSON, ToJSON) 64 | 65 | type Tag = String 66 | 67 | -- | Data for a blog post 68 | data Post = 69 | Post { title :: String 70 | , author :: String 71 | , content :: String 72 | , url :: String 73 | , date :: String 74 | , tags :: [Tag] 75 | , description :: String 76 | , image :: Maybe String 77 | } 78 | deriving (Generic, Eq, Ord, Show, FromJSON, ToJSON, Binary) 79 | 80 | data AtomData = 81 | AtomData { title :: String 82 | , domain :: String 83 | , author :: String 84 | , posts :: [Post] 85 | , currentTime :: String 86 | , atomUrl :: String } deriving (Generic, ToJSON, Eq, Ord, Show) 87 | 88 | -- | given a list of posts this will build a table of contents 89 | buildIndex :: [Post] -> Action () 90 | buildIndex posts' = do 91 | indexT <- compileTemplate' "site/templates/index.html" 92 | let indexInfo = IndexInfo {posts = posts'} 93 | indexHTML = T.unpack $ substitute indexT (withSiteMeta $ toJSON indexInfo) 94 | writeFile' (outputFolder "index.html") indexHTML 95 | 96 | -- | Find and build all posts 97 | buildPosts :: Action [Post] 98 | buildPosts = do 99 | pPaths <- getDirectoryFiles "." ["site/posts//*.md"] 100 | forP pPaths buildPost 101 | 102 | -- | Load a post, process metadata, write it to output, then return the post object 103 | -- Detects changes to either post content or template 104 | buildPost :: FilePath -> Action Post 105 | buildPost srcPath = cacheAction ("build" :: T.Text, srcPath) $ do 106 | liftIO . putStrLn $ "Rebuilding post: " <> srcPath 107 | postContent <- readFile' srcPath 108 | -- load post content and metadata as JSON blob 109 | postData <- markdownToHTML . T.pack $ postContent 110 | let postUrl = T.pack . dropDirectory1 $ srcPath -<.> "html" 111 | withPostUrl = _Object . at "url" ?~ String postUrl 112 | -- Add additional metadata we've been able to compute 113 | let fullPostData = withSiteMeta . withPostUrl $ postData 114 | template <- compileTemplate' "site/templates/post.html" 115 | writeFile' (outputFolder T.unpack postUrl) . T.unpack $ substitute template fullPostData 116 | convert fullPostData 117 | 118 | -- | Copy all static files from the listed folders to their destination 119 | copyStaticFiles :: Action () 120 | copyStaticFiles = do 121 | filepaths <- getDirectoryFiles "./site/" ["images//*", "css//*", "js//*"] 122 | void $ forP filepaths $ \filepath -> 123 | copyFileChanged ("site" filepath) (outputFolder filepath) 124 | 125 | formatDate :: String -> String 126 | formatDate humanDate = toIsoDate parsedTime 127 | where 128 | parsedTime = 129 | parseTimeOrError True defaultTimeLocale "%b %e, %Y" humanDate :: UTCTime 130 | 131 | rfc3339 :: Maybe String 132 | rfc3339 = Just "%H:%M:%SZ" 133 | 134 | toIsoDate :: UTCTime -> String 135 | toIsoDate = formatTime defaultTimeLocale (iso8601DateFormat rfc3339) 136 | 137 | buildFeed :: [Post] -> Action () 138 | buildFeed posts' = do 139 | now <- liftIO getCurrentTime 140 | let atomData = 141 | AtomData 142 | { title = siteTitle siteMeta 143 | , domain = baseUrl siteMeta 144 | , author = siteAuthor siteMeta 145 | , posts = mkAtomPost <$> posts' 146 | , currentTime = toIsoDate now 147 | , atomUrl = "/atom.xml" 148 | } 149 | atomTempl <- compileTemplate' "site/templates/atom.xml" 150 | writeFile' (outputFolder "atom.xml") . T.unpack $ substitute atomTempl (toJSON atomData) 151 | where 152 | mkAtomPost :: Post -> Post 153 | mkAtomPost p = p { date = formatDate $ date p } 154 | 155 | -- | Specific build rules for the Shake system 156 | -- defines workflow to build the website 157 | buildRules :: Action () 158 | buildRules = do 159 | allPosts <- buildPosts 160 | buildIndex allPosts 161 | buildFeed allPosts 162 | copyStaticFiles 163 | 164 | main :: IO () 165 | main = do 166 | let shOpts = shakeOptions { shakeVerbosity = Verbose, shakeLintInside = ["\\"]} 167 | shakeArgsForward shOpts buildRules 168 | -------------------------------------------------------------------------------- /docs/atom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | <site-title-here> 4 | 5 | 2020-04-08T20:00:SZ 6 | 7 | <author-name-here> 8 | 9 | https://<your-domain-here>/ 10 | 11 | 12 | Sample Post 13 | 14 | https://<your-domain-here>posts/sample-post.html 15 | 2019-01-01T00:00:SZ 16 | 17 | 18 | My first blog post using slick 19 | Welcome to your first blog post!

]]>
20 |
21 |
22 | -------------------------------------------------------------------------------- /docs/css/style.css: -------------------------------------------------------------------------------- 1 | /* CSS Reset */ 2 | html, body, div, span, object, h1, h2, h3, h4, h5, h6, p, a, abbr, acronym, em, img, ol, ul, li { 3 | border: 0; 4 | font-weight: inherit; 5 | font-style: inherit; 6 | font-size: 100%; 7 | font-family: inherit; 8 | vertical-align: baseline; 9 | margin: 0; 10 | padding: 0; } 11 | 12 | html { 13 | box-sizing: border-box; } 14 | 15 | *, *:before, *:after { 16 | box-sizing: inherit; } 17 | 18 | /* Variables */ 19 | html { 20 | font-family: georgia, serif; 21 | color: #333; 22 | background: #FFFFFC; 23 | background: -webkit-linear-gradient(top, #EEE 0%, #FFF 10%, #FFF 90%, #EEE 100%); 24 | background: linear-gradient(to bottom, #EEE 0%, #FFF 10%, #FFF 90%, #EEE 100%); 25 | background-attachment: fixed; 26 | height: 100%; 27 | width: 100%; 28 | z-index: -10; } 29 | html.dark { 30 | background: #333; 31 | background: -webkit-linear-gradient(top, #2a2a2a 0%, #333 10%, #333 90%, #2a2a2a 100%); 32 | background: linear-gradient(to bottom, #2a2a2a 0%, #333 10%, #333 90%, #2a2a2a 100%); 33 | background-attachment: fixed; 34 | width: 100%; 35 | height: 100%; 36 | color: #DDD; } 37 | 38 | body { 39 | display: -webkit-box; 40 | display: -webkit-flex; 41 | display: -ms-flexbox; 42 | display: flex; 43 | -webkit-flex-flow: column nowrap; 44 | -ms-flex-flow: column nowrap; 45 | flex-flow: column nowrap; 46 | -webkit-box-pack: center; 47 | -webkit-justify-content: center; 48 | -ms-flex-pack: center; 49 | justify-content: center; } 50 | 51 | h2 { 52 | font-family: inherit; 53 | font-size: 1.25em; 54 | font-variant: small-caps; 55 | text-align: center; 56 | width: 80%; 57 | margin: 0 auto 0; 58 | border-top: 1px solid #777; 59 | margin-top: 1em; 60 | padding: 1em 0 0 0; } 61 | 62 | h3 { 63 | font-size: 1.4em; 64 | margin: 0.5em 0; 65 | text-align: center; } 66 | 67 | blockquote { 68 | width: 90%; 69 | margin: 0.7em auto; 70 | text-align: center; 71 | padding-left: 0.5em; 72 | font-size: 1.1em; 73 | font-style: italic; 74 | display: -webkit-box; 75 | display: -webkit-flex; 76 | display: -ms-flexbox; 77 | display: flex; 78 | -webkit-flex-flow: row nowrap; 79 | -ms-flex-flow: row nowrap; 80 | flex-flow: row nowrap; 81 | -webkit-box-pack: center; 82 | -webkit-justify-content: center; 83 | -ms-flex-pack: center; 84 | justify-content: center; } 85 | blockquote > p { 86 | padding-left: 7px; 87 | border-left: 5px solid rgba(76, 76, 76, 0.9); 88 | margin: auto; } 89 | 90 | ul, ol { 91 | list-style-position: inside; 92 | margin: 1em 0 1em 2em; } 93 | 94 | li { 95 | margin-bottom: 0.7em; 96 | } 97 | 98 | 99 | 100 | em { 101 | font-weight: bold; 102 | font-style: italic; } 103 | 104 | img { 105 | margin: auto; } 106 | 107 | a, a:visited, a:hover { 108 | color: #EB005B; 109 | text-decoration: none; 110 | -webkit-transition: color 0.1s; 111 | transition: color 0.1s; } 112 | a:hover, a:visited:hover, a:hover:hover { 113 | color: #EB0978; 114 | text-decoration: underline; } 115 | 116 | header { 117 | position: fixed; 118 | /*color: $night-white;*/ 119 | top: 0; 120 | height: 4em; 121 | width: 100%; } 122 | 123 | footer { 124 | font-size: 0.8em; 125 | min-height: 200px; 126 | padding: 1em; 127 | color: #888; 128 | text-align: center; 129 | clear: both; } 130 | 131 | .photo { 132 | border-radius: 25px; 133 | border: 1px solid black; } 134 | 135 | .blog { 136 | display: table; 137 | width: 100%; 138 | height: 100%; 139 | margin: auto; } 140 | 141 | .title { 142 | font-family: "Quicksand", helvetica, sans-serif; 143 | font-weight: 400; 144 | font-size: 3em; 145 | text-transform: capitalize; } 146 | 147 | .byline { 148 | font-family: helvetica, sans-serif; 149 | font-style: italic; 150 | font-weight: 100; 151 | font-size: 1em; } 152 | 153 | .wrapper { 154 | width: 100%; 155 | display: -webkit-box; 156 | display: -webkit-flex; 157 | display: -ms-flexbox; 158 | display: flex; 159 | -webkit-flex-flow: column nowrap; 160 | -ms-flex-flow: column nowrap; 161 | flex-flow: column nowrap; 162 | -webkit-box-pack: center; 163 | -webkit-justify-content: center; 164 | -ms-flex-pack: center; 165 | justify-content: center; 166 | -webkit-box-align: center; 167 | -webkit-align-items: center; 168 | -ms-flex-align: center; 169 | align-items: center; } 170 | 171 | .masthead { 172 | display: -webkit-box; 173 | display: -webkit-flex; 174 | display: -ms-flexbox; 175 | display: flex; 176 | -webkit-flex-flow: column nowrap; 177 | -ms-flex-flow: column nowrap; 178 | flex-flow: column nowrap; 179 | -webkit-box-pack: center; 180 | -webkit-justify-content: center; 181 | -ms-flex-pack: center; 182 | justify-content: center; 183 | -webkit-box-align: space-around; 184 | -webkit-align-items: space-around; 185 | -ms-flex-align: space-around; 186 | align-items: space-around; 187 | width: 100%; 188 | min-height: 400px; 189 | padding: 2em 10px; 190 | text-align: center; 191 | margin: auto; } 192 | 193 | .post-image { 194 | height: auto; 195 | width: 400px; 196 | max-width: 400px; } 197 | 198 | .metadata { 199 | text-align: center; } 200 | 201 | .date { 202 | color: rgba(128, 128, 128, 0.5); 203 | font-size: 0.9em; 204 | font-style: italic; 205 | line-height: 2em; 206 | font-family: helvetica, sans-serif; } 207 | 208 | .categories, .tags { 209 | color: rgba(128, 128, 128, 0.5); 210 | font-size: 0.9em; 211 | font-style: italic; 212 | line-height: 2em; 213 | font-family: helvetica, sans-serif; 214 | width: 100%; 215 | max-width: 500px; 216 | display: block; 217 | margin: auto; } 218 | 219 | a.tag, a.category { 220 | padding: 0px 8px; 221 | margin: 2px 1px; 222 | border-radius: 4px; 223 | display: inline-block; 224 | color: #333; 225 | border: 1px solid rgba(76, 76, 76, 0.9); 226 | -webkit-transition: 0.3s; 227 | transition: 0.3s; } 228 | a.tag:hover, a.category:hover { 229 | background: #333; 230 | color: #FFFFFC; 231 | text-decoration: none; } 232 | .dark a.tag, .dark a.category { 233 | color: #DDD; } 234 | .dark a.tag:hover, .dark a.category:hover { 235 | background: #DDD; 236 | color: #333; 237 | text-decoration: none; } 238 | 239 | .post { 240 | display: -webkit-box; 241 | display: -webkit-flex; 242 | display: -ms-flexbox; 243 | display: flex; 244 | -webkit-flex-flow: column nowrap; 245 | -ms-flex-flow: column nowrap; 246 | flex-flow: column nowrap; 247 | width: auto; 248 | min-width: 100px; 249 | max-width: 720px; 250 | /*min-height: 100%;*/ 251 | padding: 0px 20px; 252 | margin: 3em auto; 253 | -webkit-transition: color 2s; 254 | transition: color 2s; 255 | font-size: 1.1em; } 256 | .post p { 257 | line-height: 1.6em; 258 | letter-spacing: 0.02em; 259 | margin: 0.5em 0 0 0; 260 | -webkit-hyphens: auto; 261 | -moz-hyphens: auto; 262 | -ms-hyphens: auto; 263 | hyphens: auto; 264 | word-break: break-word; } 265 | .post p + p { 266 | margin-top: 1em; } 267 | .post > p:first-of-type:first-letter { 268 | font-size: 8em; 269 | line-height: 0.1em; 270 | padding-right: 0.06em; } 271 | .post ul { 272 | list-style-type: disc; 273 | text-indent: -1em; } 274 | .post ul li { 275 | line-height: 1.3em; 276 | letter-spacing: 0.05em; 277 | margin: 0.5em 0 0 0; 278 | -webkit-hyphens: auto; 279 | -moz-hyphens: auto; 280 | -ms-hyphens: auto; 281 | hyphens: auto; 282 | word-break: break-word; } 283 | 284 | .post img { 285 | max-width: 100%; 286 | margin: auto; 287 | display: block; 288 | } 289 | 290 | .table-of-contents { 291 | clear: both; 292 | width: 100%; } 293 | .table-of-contents h1 { 294 | font-size: 3em; 295 | text-align: center; 296 | font-family: "Quicksand", helvetica, sans-serif; } 297 | .table-of-contents ul { 298 | margin: 3em 0; 299 | list-style: none; } 300 | .table-of-contents li { 301 | width: 100%; } 302 | .table-of-contents li a { 303 | color: #333; 304 | font-weight: normal; 305 | box-sizing: border-box; 306 | border-top: 1px solid #444; 307 | display: block; 308 | width: 50%; 309 | min-width: 300px; 310 | margin: 0 auto; 311 | padding: 1em 0.5em 1em 0; 312 | -webkit-transition: color 0.7s, background 0.7s, padding 0.5s; 313 | transition: color 0.7s, background 0.7s, padding 0.5s; } 314 | .table-of-contents li a .date { 315 | float: right; 316 | margin-top: -0.25em; 317 | color: rgba(128, 128, 128, 0.8); } 318 | .table-of-contents li a:hover { 319 | background: #333; 320 | color: #FFFFFC; 321 | text-decoration: none; 322 | padding-left: 2em; } 323 | .dark .table-of-contents ul a { 324 | color: #DDD; } 325 | .dark .table-of-contents ul a:hover { 326 | background: #DDD; 327 | color: #333; 328 | padding-left: 2em; } 329 | 330 | .arrow { 331 | display: -webkit-box; 332 | display: -webkit-flex; 333 | display: -ms-flexbox; 334 | display: flex; 335 | -webkit-flex-flow: column nowrap; 336 | -ms-flex-flow: column nowrap; 337 | flex-flow: column nowrap; 338 | -webkit-box-pack: center; 339 | -webkit-justify-content: center; 340 | -ms-flex-pack: center; 341 | justify-content: center; 342 | -webkit-box-align: center; 343 | -webkit-align-items: center; 344 | -ms-flex-align: center; 345 | align-items: center; 346 | font-size: 1.5em; 347 | width: 1.5em; 348 | height: 1.5em; 349 | background: #DDD; 350 | /*display: table-cell;*/ 351 | /*vertical-align: middle;*/ 352 | text-align: center; 353 | /*padding-top: 0.14em;*/ 354 | box-sizing: border-box; 355 | border-radius: 0.75em; 356 | opacity: 0.7; 357 | -webkit-transition: background 0.5s, opacity 0.5s, width 0.5s; 358 | transition: background 0.5s, opacity 0.5s, width 0.5s; 359 | cursor: pointer; } 360 | .arrow:hover { 361 | box-sizing: border-box; 362 | width: 2em; 363 | color: #EB005B; 364 | border: 1px solid #EB005B; 365 | opacity: 0.9; 366 | background: #FFFFFC; 367 | -webkit-transition: background 0.5s, opacity 0.5s, width 0.5s; 368 | transition: background 0.5s, opacity 0.5s, width 0.5s; 369 | text-decoration: none; } 370 | .dark .arrow { 371 | background: #222; } 372 | .dark .arrow:hover { 373 | color: #C00762; 374 | border-color: #C00762; } 375 | 376 | .social-buttons { 377 | display: -webkit-box; 378 | display: -webkit-flex; 379 | display: -ms-flexbox; 380 | display: flex; 381 | -webkit-flex-flow: row nowrap; 382 | -ms-flex-flow: row nowrap; 383 | flex-flow: row nowrap; 384 | -webkit-box-pack: center; 385 | -webkit-justify-content: center; 386 | -ms-flex-pack: center; 387 | justify-content: center; 388 | margin: 1em 0; } 389 | .social-buttons > div { 390 | /*margin: 0 1em;*/ } 391 | .social-buttons .twitter-share-button { 392 | max-width: 90px; 393 | margin-right: 5px; } 394 | 395 | .center { 396 | text-align: center; 397 | margin: auto; } 398 | 399 | .monochrome { 400 | color: #333; } 401 | .dark .monochrome { 402 | color: #DDD; } 403 | 404 | .gem-info { 405 | display: -webkit-box; 406 | display: -webkit-flex; 407 | display: -ms-flexbox; 408 | display: flex; 409 | -webkit-flex-flow: column nowrap; 410 | -ms-flex-flow: column nowrap; 411 | flex-flow: column nowrap; 412 | margin: auto; 413 | -webkit-box-align: center; 414 | -webkit-align-items: center; 415 | -ms-flex-align: center; 416 | align-items: center; } 417 | .gem-info table { 418 | border: 1px solid #333; 419 | border-collapse: collapse; } 420 | .gem-info table td { 421 | border: 1px solid #333; 422 | padding: 0.4em 1em; } 423 | .dark .gem-info table { 424 | border: 1px solid #DDD; } 425 | .dark .gem-info table td { 426 | border: 1px solid #DDD; } 427 | 428 | .pager-title { 429 | display: -webkit-box; 430 | display: -webkit-flex; 431 | display: -ms-flexbox; 432 | display: flex; 433 | -webkit-flex-flow: column nowrap; 434 | -ms-flex-flow: column nowrap; 435 | flex-flow: column nowrap; 436 | -webkit-box-align: center; 437 | -webkit-align-items: center; 438 | -ms-flex-align: center; 439 | align-items: center; } 440 | .pager-title span { 441 | text-align: center; } 442 | 443 | .pager article { 444 | margin-top: 2em; } 445 | 446 | .dark #disqus_thread { 447 | color: #DDD; } 448 | 449 | #page { 450 | margin: 4em 0; } 451 | 452 | #theme-button { 453 | text-transform: uppercase; 454 | font-family: "Quicksand", helvetica, sans-serif; 455 | font-size: 1.5em; 456 | font-weight: bold; 457 | text-align: right; 458 | cursor: pointer; 459 | -webkit-transition: opacity 0.5s, color 2s; 460 | transition: opacity 0.5s, color 2s; 461 | opacity: 0.6; } 462 | #theme-button:hover { 463 | opacity: 1; } 464 | .dark #theme-button { 465 | color: #DDD; } 466 | 467 | #beacon { 468 | font-family: "Oswald", helvetica, sans-serif; 469 | font-size: 4em; 470 | height: 1.5em; 471 | width: 1.5em; 472 | text-align: center; 473 | margin: 0.1em; 474 | position: absolute; 475 | border-radius: 0.1em; 476 | opacity: 0.4; 477 | -webkit-transition: opacity 0.5s; 478 | transition: opacity 0.5s; 479 | color: inherit; } 480 | #beacon path { 481 | fill: currentColor; } 482 | #beacon .logo { 483 | width: 100%; 484 | height: 100%; } 485 | #beacon:hover { 486 | opacity: 1; 487 | text-decoration: none; } 488 | 489 | #home-text { 490 | font-size: 0.8em; 491 | } 492 | 493 | #leftarrow { 494 | position: fixed; 495 | left: 5px; 496 | top: calc(50% - 15px); 497 | text-decoration: none; } 498 | 499 | #rightarrow { 500 | position: fixed; 501 | right: 10px; 502 | top: calc(50% - 15px); 503 | text-decoration: none; } 504 | 505 | @media only screen and (max-width: 750px) { 506 | header { 507 | color: #FFFFFC; 508 | height: 3em; 509 | background: rgba(76, 76, 76, 0.9); } 510 | 511 | #beacon { 512 | color: #FFFFFC; 513 | font-size: 2em; 514 | margin: 0; } 515 | 516 | #theme-button { 517 | color: #FFFFFC; 518 | font-size: 1.5em; 519 | top: 0.5em; 520 | right: 0.2em; 521 | opacity: 0.5; } } 522 | @media only screen and (min-device-width: 320px) and (max-device-width: 480px) { 523 | /* Styles */ 524 | h2 { 525 | border-top: 3px solid #777; } 526 | 527 | .page { 528 | min-width: none; 529 | max-width: none; 530 | width: auto; 531 | padding: 0 1em; 532 | margin: 0; 533 | box-sizing: border-box; } 534 | 535 | .page p { 536 | padding-bottom: 1em; 537 | font-size: 1em; 538 | line-height: 1.8em; } 539 | 540 | .post-image { 541 | height: auto; 542 | width: auto; 543 | max-width: 280px; } 544 | 545 | .page > p:first-of-type:first-letter { 546 | font-size: 4em; 547 | line-height: 0.1em; } } 548 | @media only screen and (max-height: 450px) { 549 | .masthead { 550 | min-height: 350px; } } 551 | 552 | .ext-link img { 553 | width: 32px; 554 | height: 32px; 555 | } 556 | 557 | .right-sidebar { 558 | position: absolute; 559 | top: 0.75em; 560 | right: 0.7em; 561 | display: flex; 562 | } 563 | 564 | .right-sidebar > * { 565 | margin-left: 12px; 566 | } 567 | 568 | /*# sourceMappingURL=style.css.map */ 569 | -------------------------------------------------------------------------------- /docs/css/syntax.css: -------------------------------------------------------------------------------- 1 | pre { 2 | background-color: #F5FCFF; 3 | } 4 | 5 | code { 6 | background-color: #F5FCFF; 7 | color: #268BD2; 8 | } 9 | 10 | /* KeyWordTok */ 11 | .sourceCode .kw { color: #600095; } 12 | /* DataTypeTok */ 13 | .sourceCode .dt { color: #268BD2; } 14 | 15 | /* DecValTok (decimal value), BaseNTok, FloatTok */ 16 | .sourceCode .dv, .sourceCode .bn, .sourceCode .fl { color: #AE81FF; } 17 | /* CharTok */ 18 | .sourceCode .ch { color: #37ad2d; } 19 | /* StringTok */ 20 | .sourceCode .st { color: #37ad2d; } 21 | /* CommentTok */ 22 | .sourceCode .co { color: #7E8E91; } 23 | /* OtherTok */ 24 | .sourceCode .ot { color: #EB005B; } 25 | /* AlertTok */ 26 | .sourceCode .al { color: #A6E22E; font-weight: bold; } 27 | /* FunctionTok */ 28 | .sourceCode .fu { color: #333; } 29 | /* RegionMarkerTok */ 30 | .sourceCode .re { } 31 | /* ErrorTok */ 32 | .sourceCode .er { color: #E6DB74; font-weight: bold; } 33 | -------------------------------------------------------------------------------- /docs/images/code.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChrisPenner/slick-template/8b20af18a6e982b87d15bdd41e85595c2d1c02e1/docs/images/code.jpg -------------------------------------------------------------------------------- /docs/images/github-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChrisPenner/slick-template/8b20af18a6e982b87d15bdd41e85595c2d1c02e1/docs/images/github-logo.png -------------------------------------------------------------------------------- /docs/images/twitter-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChrisPenner/slick-template/8b20af18a6e982b87d15bdd41e85595c2d1c02e1/docs/images/twitter-logo.png -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | My Slick Site 9 | 10 | 11 | 12 | 13 |
14 | 19 | 20 | 30 |
31 |
32 |
33 |

All Posts

34 | 36 | 41 |
42 |
43 | 44 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /docs/js/main.js: -------------------------------------------------------------------------------- 1 | function switchTheme(){ 2 | var button = document.getElementById("theme-button"); 3 | var html = document.getElementsByTagName("html")[0]; 4 | html.classList.toggle("dark"); 5 | 6 | if(button.innerHTML == "DAY"){ 7 | button.innerHTML = "NIGHT"; 8 | // Expire in two months 9 | setCookie("theme", "night", 60*24*60*60*1000); 10 | } else { 11 | button.innerHTML = "DAY"; 12 | // Expire in two months 13 | setCookie("theme", "day", 60*24*60*60*1000); 14 | } 15 | return; 16 | } 17 | 18 | function setCookie(cname,cvalue,extime) 19 | { 20 | var d = new Date(); 21 | d.setTime(d.getTime()+(extime)); 22 | var expires = "expires="+d.toGMTString(); 23 | document.cookie = cname + "=" + cvalue + ";" + expires + ";" + "path=/"; 24 | } 25 | 26 | function getCookie(cname) 27 | { 28 | var name = cname + "="; 29 | var ca = document.cookie.split(';'); 30 | for(var i=0; i 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | Sample Post 15 | 16 | 17 | 18 | 19 |
20 | 25 | 26 | 36 |
37 | 38 |
39 |
40 |
41 | 42 | Sample Post 43 | 44 |
45 | 46 | 47 |
48 | 49 |
50 | Jan 1, 2019 51 |
52 | 54 |
55 | slick 56 | site 57 |
58 |
59 |
60 |
61 |

Welcome to your first blog post!

62 | 63 |
64 |
65 | 66 | 67 | 75 |
76 | 77 |
78 | 79 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /package.yaml: -------------------------------------------------------------------------------- 1 | name: slick-template 2 | version: 0.1.0.0 3 | github: "Me/my-site" 4 | license: BSD3 5 | author: "Me" 6 | maintainer: "example@example.com" 7 | copyright: "20XX Me" 8 | 9 | extra-source-files: 10 | - README.md 11 | - ChangeLog.md 12 | 13 | synopsis: My site built with slick! 14 | category: Slick-Site 15 | 16 | description: My slick Site 17 | 18 | executables: 19 | build-site: 20 | main: Main.hs 21 | source-dirs: app 22 | ghc-options: 23 | - -threaded 24 | - -rtsopts 25 | - -with-rtsopts=-N 26 | - -Wall 27 | dependencies: 28 | - base >= 4.7 && < 5 29 | - shake 30 | - slick 31 | - text 32 | - containers 33 | - unordered-containers 34 | - lens 35 | - aeson 36 | - lens-aeson 37 | - time 38 | -------------------------------------------------------------------------------- /site-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChrisPenner/slick-template/8b20af18a6e982b87d15bdd41e85595c2d1c02e1/site-example.png -------------------------------------------------------------------------------- /site/css/style.css: -------------------------------------------------------------------------------- 1 | /* CSS Reset */ 2 | html, body, div, span, object, h1, h2, h3, h4, h5, h6, p, a, abbr, acronym, em, img, ol, ul, li { 3 | border: 0; 4 | font-weight: inherit; 5 | font-style: inherit; 6 | font-size: 100%; 7 | font-family: inherit; 8 | vertical-align: baseline; 9 | margin: 0; 10 | padding: 0; } 11 | 12 | html { 13 | box-sizing: border-box; } 14 | 15 | *, *:before, *:after { 16 | box-sizing: inherit; } 17 | 18 | /* Variables */ 19 | html { 20 | font-family: georgia, serif; 21 | color: #333; 22 | background: #FFFFFC; 23 | background: -webkit-linear-gradient(top, #EEE 0%, #FFF 10%, #FFF 90%, #EEE 100%); 24 | background: linear-gradient(to bottom, #EEE 0%, #FFF 10%, #FFF 90%, #EEE 100%); 25 | background-attachment: fixed; 26 | height: 100%; 27 | width: 100%; 28 | z-index: -10; } 29 | html.dark { 30 | background: #333; 31 | background: -webkit-linear-gradient(top, #2a2a2a 0%, #333 10%, #333 90%, #2a2a2a 100%); 32 | background: linear-gradient(to bottom, #2a2a2a 0%, #333 10%, #333 90%, #2a2a2a 100%); 33 | background-attachment: fixed; 34 | width: 100%; 35 | height: 100%; 36 | color: #DDD; } 37 | 38 | body { 39 | display: -webkit-box; 40 | display: -webkit-flex; 41 | display: -ms-flexbox; 42 | display: flex; 43 | -webkit-flex-flow: column nowrap; 44 | -ms-flex-flow: column nowrap; 45 | flex-flow: column nowrap; 46 | -webkit-box-pack: center; 47 | -webkit-justify-content: center; 48 | -ms-flex-pack: center; 49 | justify-content: center; } 50 | 51 | h2 { 52 | font-family: inherit; 53 | font-size: 1.25em; 54 | font-variant: small-caps; 55 | text-align: center; 56 | width: 80%; 57 | margin: 0 auto 0; 58 | border-top: 1px solid #777; 59 | margin-top: 1em; 60 | padding: 1em 0 0 0; } 61 | 62 | h3 { 63 | font-size: 1.4em; 64 | margin: 0.5em 0; 65 | text-align: center; } 66 | 67 | blockquote { 68 | width: 90%; 69 | margin: 0.7em auto; 70 | text-align: center; 71 | padding-left: 0.5em; 72 | font-size: 1.1em; 73 | font-style: italic; 74 | display: -webkit-box; 75 | display: -webkit-flex; 76 | display: -ms-flexbox; 77 | display: flex; 78 | -webkit-flex-flow: row nowrap; 79 | -ms-flex-flow: row nowrap; 80 | flex-flow: row nowrap; 81 | -webkit-box-pack: center; 82 | -webkit-justify-content: center; 83 | -ms-flex-pack: center; 84 | justify-content: center; } 85 | blockquote > p { 86 | padding-left: 7px; 87 | border-left: 5px solid rgba(76, 76, 76, 0.9); 88 | margin: auto; } 89 | 90 | ul, ol { 91 | list-style-position: inside; 92 | margin: 1em 0 1em 2em; } 93 | 94 | li { 95 | margin-bottom: 0.7em; 96 | } 97 | 98 | 99 | 100 | em { 101 | font-weight: bold; 102 | font-style: italic; } 103 | 104 | img { 105 | margin: auto; } 106 | 107 | a, a:visited, a:hover { 108 | color: #EB005B; 109 | text-decoration: none; 110 | -webkit-transition: color 0.1s; 111 | transition: color 0.1s; } 112 | a:hover, a:visited:hover, a:hover:hover { 113 | color: #EB0978; 114 | text-decoration: underline; } 115 | 116 | header { 117 | position: fixed; 118 | /*color: $night-white;*/ 119 | top: 0; 120 | height: 4em; 121 | width: 100%; } 122 | 123 | footer { 124 | font-size: 0.8em; 125 | min-height: 200px; 126 | padding: 1em; 127 | color: #888; 128 | text-align: center; 129 | clear: both; } 130 | 131 | .photo { 132 | border-radius: 25px; 133 | border: 1px solid black; } 134 | 135 | .blog { 136 | display: table; 137 | width: 100%; 138 | height: 100%; 139 | margin: auto; } 140 | 141 | .title { 142 | font-family: "Quicksand", helvetica, sans-serif; 143 | font-weight: 400; 144 | font-size: 3em; 145 | text-transform: capitalize; } 146 | 147 | .byline { 148 | font-family: helvetica, sans-serif; 149 | font-style: italic; 150 | font-weight: 100; 151 | font-size: 1em; } 152 | 153 | .wrapper { 154 | width: 100%; 155 | display: -webkit-box; 156 | display: -webkit-flex; 157 | display: -ms-flexbox; 158 | display: flex; 159 | -webkit-flex-flow: column nowrap; 160 | -ms-flex-flow: column nowrap; 161 | flex-flow: column nowrap; 162 | -webkit-box-pack: center; 163 | -webkit-justify-content: center; 164 | -ms-flex-pack: center; 165 | justify-content: center; 166 | -webkit-box-align: center; 167 | -webkit-align-items: center; 168 | -ms-flex-align: center; 169 | align-items: center; } 170 | 171 | .masthead { 172 | display: -webkit-box; 173 | display: -webkit-flex; 174 | display: -ms-flexbox; 175 | display: flex; 176 | -webkit-flex-flow: column nowrap; 177 | -ms-flex-flow: column nowrap; 178 | flex-flow: column nowrap; 179 | -webkit-box-pack: center; 180 | -webkit-justify-content: center; 181 | -ms-flex-pack: center; 182 | justify-content: center; 183 | -webkit-box-align: space-around; 184 | -webkit-align-items: space-around; 185 | -ms-flex-align: space-around; 186 | align-items: space-around; 187 | width: 100%; 188 | min-height: 400px; 189 | padding: 2em 10px; 190 | text-align: center; 191 | margin: auto; } 192 | 193 | .post-image { 194 | height: auto; 195 | width: 400px; 196 | max-width: 400px; } 197 | 198 | .metadata { 199 | text-align: center; } 200 | 201 | .date { 202 | color: rgba(128, 128, 128, 0.5); 203 | font-size: 0.9em; 204 | font-style: italic; 205 | line-height: 2em; 206 | font-family: helvetica, sans-serif; } 207 | 208 | .categories, .tags { 209 | color: rgba(128, 128, 128, 0.5); 210 | font-size: 0.9em; 211 | font-style: italic; 212 | line-height: 2em; 213 | font-family: helvetica, sans-serif; 214 | width: 100%; 215 | max-width: 500px; 216 | display: block; 217 | margin: auto; } 218 | 219 | a.tag, a.category { 220 | padding: 0px 8px; 221 | margin: 2px 1px; 222 | border-radius: 4px; 223 | display: inline-block; 224 | color: #333; 225 | border: 1px solid rgba(76, 76, 76, 0.9); 226 | -webkit-transition: 0.3s; 227 | transition: 0.3s; } 228 | a.tag:hover, a.category:hover { 229 | background: #333; 230 | color: #FFFFFC; 231 | text-decoration: none; } 232 | .dark a.tag, .dark a.category { 233 | color: #DDD; } 234 | .dark a.tag:hover, .dark a.category:hover { 235 | background: #DDD; 236 | color: #333; 237 | text-decoration: none; } 238 | 239 | .post { 240 | display: -webkit-box; 241 | display: -webkit-flex; 242 | display: -ms-flexbox; 243 | display: flex; 244 | -webkit-flex-flow: column nowrap; 245 | -ms-flex-flow: column nowrap; 246 | flex-flow: column nowrap; 247 | width: auto; 248 | min-width: 100px; 249 | max-width: 720px; 250 | /*min-height: 100%;*/ 251 | padding: 0px 20px; 252 | margin: 3em auto; 253 | -webkit-transition: color 2s; 254 | transition: color 2s; 255 | font-size: 1.1em; } 256 | .post p { 257 | line-height: 1.6em; 258 | letter-spacing: 0.02em; 259 | margin: 0.5em 0 0 0; 260 | -webkit-hyphens: auto; 261 | -moz-hyphens: auto; 262 | -ms-hyphens: auto; 263 | hyphens: auto; 264 | word-break: break-word; } 265 | .post p + p { 266 | margin-top: 1em; } 267 | .post > p:first-of-type:first-letter { 268 | font-size: 8em; 269 | line-height: 0.1em; 270 | padding-right: 0.06em; } 271 | .post ul { 272 | list-style-type: disc; 273 | text-indent: -1em; } 274 | .post ul li { 275 | line-height: 1.3em; 276 | letter-spacing: 0.05em; 277 | margin: 0.5em 0 0 0; 278 | -webkit-hyphens: auto; 279 | -moz-hyphens: auto; 280 | -ms-hyphens: auto; 281 | hyphens: auto; 282 | word-break: break-word; } 283 | 284 | .post img { 285 | max-width: 100%; 286 | margin: auto; 287 | display: block; 288 | } 289 | 290 | .table-of-contents { 291 | clear: both; 292 | width: 100%; } 293 | .table-of-contents h1 { 294 | font-size: 3em; 295 | text-align: center; 296 | font-family: "Quicksand", helvetica, sans-serif; } 297 | .table-of-contents ul { 298 | margin: 3em 0; 299 | list-style: none; } 300 | .table-of-contents li { 301 | width: 100%; } 302 | .table-of-contents li a { 303 | color: #333; 304 | font-weight: normal; 305 | box-sizing: border-box; 306 | border-top: 1px solid #444; 307 | display: block; 308 | width: 50%; 309 | min-width: 300px; 310 | margin: 0 auto; 311 | padding: 1em 0.5em 1em 0; 312 | -webkit-transition: color 0.7s, background 0.7s, padding 0.5s; 313 | transition: color 0.7s, background 0.7s, padding 0.5s; } 314 | .table-of-contents li a .date { 315 | float: right; 316 | margin-top: -0.25em; 317 | color: rgba(128, 128, 128, 0.8); } 318 | .table-of-contents li a:hover { 319 | background: #333; 320 | color: #FFFFFC; 321 | text-decoration: none; 322 | padding-left: 2em; } 323 | .dark .table-of-contents ul a { 324 | color: #DDD; } 325 | .dark .table-of-contents ul a:hover { 326 | background: #DDD; 327 | color: #333; 328 | padding-left: 2em; } 329 | 330 | .arrow { 331 | display: -webkit-box; 332 | display: -webkit-flex; 333 | display: -ms-flexbox; 334 | display: flex; 335 | -webkit-flex-flow: column nowrap; 336 | -ms-flex-flow: column nowrap; 337 | flex-flow: column nowrap; 338 | -webkit-box-pack: center; 339 | -webkit-justify-content: center; 340 | -ms-flex-pack: center; 341 | justify-content: center; 342 | -webkit-box-align: center; 343 | -webkit-align-items: center; 344 | -ms-flex-align: center; 345 | align-items: center; 346 | font-size: 1.5em; 347 | width: 1.5em; 348 | height: 1.5em; 349 | background: #DDD; 350 | /*display: table-cell;*/ 351 | /*vertical-align: middle;*/ 352 | text-align: center; 353 | /*padding-top: 0.14em;*/ 354 | box-sizing: border-box; 355 | border-radius: 0.75em; 356 | opacity: 0.7; 357 | -webkit-transition: background 0.5s, opacity 0.5s, width 0.5s; 358 | transition: background 0.5s, opacity 0.5s, width 0.5s; 359 | cursor: pointer; } 360 | .arrow:hover { 361 | box-sizing: border-box; 362 | width: 2em; 363 | color: #EB005B; 364 | border: 1px solid #EB005B; 365 | opacity: 0.9; 366 | background: #FFFFFC; 367 | -webkit-transition: background 0.5s, opacity 0.5s, width 0.5s; 368 | transition: background 0.5s, opacity 0.5s, width 0.5s; 369 | text-decoration: none; } 370 | .dark .arrow { 371 | background: #222; } 372 | .dark .arrow:hover { 373 | color: #C00762; 374 | border-color: #C00762; } 375 | 376 | .social-buttons { 377 | display: -webkit-box; 378 | display: -webkit-flex; 379 | display: -ms-flexbox; 380 | display: flex; 381 | -webkit-flex-flow: row nowrap; 382 | -ms-flex-flow: row nowrap; 383 | flex-flow: row nowrap; 384 | -webkit-box-pack: center; 385 | -webkit-justify-content: center; 386 | -ms-flex-pack: center; 387 | justify-content: center; 388 | margin: 1em 0; } 389 | .social-buttons > div { 390 | /*margin: 0 1em;*/ } 391 | .social-buttons .twitter-share-button { 392 | max-width: 90px; 393 | margin-right: 5px; } 394 | 395 | .center { 396 | text-align: center; 397 | margin: auto; } 398 | 399 | .monochrome { 400 | color: #333; } 401 | .dark .monochrome { 402 | color: #DDD; } 403 | 404 | .gem-info { 405 | display: -webkit-box; 406 | display: -webkit-flex; 407 | display: -ms-flexbox; 408 | display: flex; 409 | -webkit-flex-flow: column nowrap; 410 | -ms-flex-flow: column nowrap; 411 | flex-flow: column nowrap; 412 | margin: auto; 413 | -webkit-box-align: center; 414 | -webkit-align-items: center; 415 | -ms-flex-align: center; 416 | align-items: center; } 417 | .gem-info table { 418 | border: 1px solid #333; 419 | border-collapse: collapse; } 420 | .gem-info table td { 421 | border: 1px solid #333; 422 | padding: 0.4em 1em; } 423 | .dark .gem-info table { 424 | border: 1px solid #DDD; } 425 | .dark .gem-info table td { 426 | border: 1px solid #DDD; } 427 | 428 | .pager-title { 429 | display: -webkit-box; 430 | display: -webkit-flex; 431 | display: -ms-flexbox; 432 | display: flex; 433 | -webkit-flex-flow: column nowrap; 434 | -ms-flex-flow: column nowrap; 435 | flex-flow: column nowrap; 436 | -webkit-box-align: center; 437 | -webkit-align-items: center; 438 | -ms-flex-align: center; 439 | align-items: center; } 440 | .pager-title span { 441 | text-align: center; } 442 | 443 | .pager article { 444 | margin-top: 2em; } 445 | 446 | .dark #disqus_thread { 447 | color: #DDD; } 448 | 449 | #page { 450 | margin: 4em 0; } 451 | 452 | #theme-button { 453 | text-transform: uppercase; 454 | font-family: "Quicksand", helvetica, sans-serif; 455 | font-size: 1.5em; 456 | font-weight: bold; 457 | text-align: right; 458 | cursor: pointer; 459 | -webkit-transition: opacity 0.5s, color 2s; 460 | transition: opacity 0.5s, color 2s; 461 | opacity: 0.6; } 462 | #theme-button:hover { 463 | opacity: 1; } 464 | .dark #theme-button { 465 | color: #DDD; } 466 | 467 | #beacon { 468 | font-family: "Oswald", helvetica, sans-serif; 469 | font-size: 4em; 470 | height: 1.5em; 471 | width: 1.5em; 472 | text-align: center; 473 | margin: 0.1em; 474 | position: absolute; 475 | border-radius: 0.1em; 476 | opacity: 0.4; 477 | -webkit-transition: opacity 0.5s; 478 | transition: opacity 0.5s; 479 | color: inherit; } 480 | #beacon path { 481 | fill: currentColor; } 482 | #beacon .logo { 483 | width: 100%; 484 | height: 100%; } 485 | #beacon:hover { 486 | opacity: 1; 487 | text-decoration: none; } 488 | 489 | #home-text { 490 | font-size: 0.8em; 491 | } 492 | 493 | #leftarrow { 494 | position: fixed; 495 | left: 5px; 496 | top: calc(50% - 15px); 497 | text-decoration: none; } 498 | 499 | #rightarrow { 500 | position: fixed; 501 | right: 10px; 502 | top: calc(50% - 15px); 503 | text-decoration: none; } 504 | 505 | @media only screen and (max-width: 750px) { 506 | header { 507 | color: #FFFFFC; 508 | height: 3em; 509 | background: rgba(76, 76, 76, 0.9); } 510 | 511 | #beacon { 512 | color: #FFFFFC; 513 | font-size: 2em; 514 | margin: 0; } 515 | 516 | #theme-button { 517 | color: #FFFFFC; 518 | font-size: 1.5em; 519 | top: 0.5em; 520 | right: 0.2em; 521 | opacity: 0.5; } } 522 | @media only screen and (min-device-width: 320px) and (max-device-width: 480px) { 523 | /* Styles */ 524 | h2 { 525 | border-top: 3px solid #777; } 526 | 527 | .page { 528 | min-width: none; 529 | max-width: none; 530 | width: auto; 531 | padding: 0 1em; 532 | margin: 0; 533 | box-sizing: border-box; } 534 | 535 | .page p { 536 | padding-bottom: 1em; 537 | font-size: 1em; 538 | line-height: 1.8em; } 539 | 540 | .post-image { 541 | height: auto; 542 | width: auto; 543 | max-width: 280px; } 544 | 545 | .page > p:first-of-type:first-letter { 546 | font-size: 4em; 547 | line-height: 0.1em; } } 548 | @media only screen and (max-height: 450px) { 549 | .masthead { 550 | min-height: 350px; } } 551 | 552 | .ext-link img { 553 | width: 32px; 554 | height: 32px; 555 | } 556 | 557 | .right-sidebar { 558 | position: absolute; 559 | top: 0.75em; 560 | right: 0.7em; 561 | display: flex; 562 | } 563 | 564 | .right-sidebar > * { 565 | margin-left: 12px; 566 | } 567 | 568 | -------------------------------------------------------------------------------- /site/css/syntax.css: -------------------------------------------------------------------------------- 1 | pre { 2 | background-color: #F5FCFF; 3 | } 4 | 5 | code { 6 | background-color: #F5FCFF; 7 | color: #268BD2; 8 | } 9 | 10 | /* KeyWordTok */ 11 | .sourceCode .kw { color: #600095; } 12 | /* DataTypeTok */ 13 | .sourceCode .dt { color: #268BD2; } 14 | 15 | /* DecValTok (decimal value), BaseNTok, FloatTok */ 16 | .sourceCode .dv, .sourceCode .bn, .sourceCode .fl { color: #AE81FF; } 17 | /* CharTok */ 18 | .sourceCode .ch { color: #37ad2d; } 19 | /* StringTok */ 20 | .sourceCode .st { color: #37ad2d; } 21 | /* CommentTok */ 22 | .sourceCode .co { color: #7E8E91; } 23 | /* OtherTok */ 24 | .sourceCode .ot { color: #EB005B; } 25 | /* AlertTok */ 26 | .sourceCode .al { color: #A6E22E; font-weight: bold; } 27 | /* FunctionTok */ 28 | .sourceCode .fu { color: #333; } 29 | /* RegionMarkerTok */ 30 | .sourceCode .re { } 31 | /* ErrorTok */ 32 | .sourceCode .er { color: #E6DB74; font-weight: bold; } 33 | -------------------------------------------------------------------------------- /site/images/code.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChrisPenner/slick-template/8b20af18a6e982b87d15bdd41e85595c2d1c02e1/site/images/code.jpg -------------------------------------------------------------------------------- /site/images/github-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChrisPenner/slick-template/8b20af18a6e982b87d15bdd41e85595c2d1c02e1/site/images/github-logo.png -------------------------------------------------------------------------------- /site/images/twitter-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChrisPenner/slick-template/8b20af18a6e982b87d15bdd41e85595c2d1c02e1/site/images/twitter-logo.png -------------------------------------------------------------------------------- /site/js/main.js: -------------------------------------------------------------------------------- 1 | function switchTheme(){ 2 | var button = document.getElementById("theme-button"); 3 | var html = document.getElementsByTagName("html")[0]; 4 | html.classList.toggle("dark"); 5 | 6 | if(button.innerHTML == "DAY"){ 7 | button.innerHTML = "NIGHT"; 8 | // Expire in two months 9 | setCookie("theme", "night", 60*24*60*60*1000); 10 | } else { 11 | button.innerHTML = "DAY"; 12 | // Expire in two months 13 | setCookie("theme", "day", 60*24*60*60*1000); 14 | } 15 | return; 16 | } 17 | 18 | function setCookie(cname,cvalue,extime) 19 | { 20 | var d = new Date(); 21 | d.setTime(d.getTime()+(extime)); 22 | var expires = "expires="+d.toGMTString(); 23 | document.cookie = cname + "=" + cvalue + ";" + expires + ";" + "path=/"; 24 | } 25 | 26 | function getCookie(cname) 27 | { 28 | var name = cname + "="; 29 | var ca = document.cookie.split(';'); 30 | for(var i=0; i 2 | 3 | {{title}} 4 | 5 | {{currentTime}} 6 | 7 | {{author}} 8 | 9 | {{domain}}/ 10 | 11 | {{#posts}} 12 | 13 | {{title}} 14 | 15 | {{domain}}{{url}} 16 | {{date}} 17 | {{#tags}} 18 | 19 | {{/tags}} 20 | {{description}} 21 | 22 | 23 | {{/posts}} 24 | 25 | -------------------------------------------------------------------------------- /site/templates/footer.html: -------------------------------------------------------------------------------- 1 |
2 | Built with Haskell using slick ❤️ 3 |
4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /site/templates/header.html: -------------------------------------------------------------------------------- 1 |
2 | 7 | 8 | 22 |
23 | -------------------------------------------------------------------------------- /site/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | {{siteTitle}} 9 | 10 | 11 | 12 | 13 | {{>site/templates/header.html}} 14 |
15 |
16 |

All Posts

17 | 19 | {{>site/templates/post-list.html}} 20 |
21 |
22 | 23 | {{>site/templates/footer.html}} 24 | -------------------------------------------------------------------------------- /site/templates/meta-data.html: -------------------------------------------------------------------------------- 1 | 2 | {{#twitterHandle}} 3 | 4 | 5 | {{/twitterHandle}} 6 | 7 | 8 | {{#image}} 9 | 10 | {{/image}} 11 | -------------------------------------------------------------------------------- /site/templates/post-list.html: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /site/templates/post.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | {{>site/templates/meta-data.html}} 9 | {{title}} 10 | 11 | 12 | 13 | 14 | {{>site/templates/header.html}} 15 | 16 |
17 |
18 |
19 | 20 | {{title}} 21 | 22 |
23 | 24 | {{#image}} 25 | 26 | {{/image}} 27 |
28 | {{#author}} 29 | 30 | {{/author}} 31 |
32 | {{date}} 33 |
34 | 36 |
37 | {{#tags}} 38 | {{.}} 39 | {{/tags}} 40 |
41 |
42 |
43 |
44 | {{{content}}} 45 | 46 |
47 |
48 | 49 | 50 | 60 |
61 | 62 |
63 | 64 | {{>site/templates/footer.html}} 65 | -------------------------------------------------------------------------------- /slick-template.cabal: -------------------------------------------------------------------------------- 1 | cabal-version: 1.12 2 | 3 | -- This file has been generated from package.yaml by hpack version 0.33.0. 4 | -- 5 | -- see: https://github.com/sol/hpack 6 | -- 7 | -- hash: 99608593197be2adb4aa74b31a94255716eef02a03cb55c4477261cee7cf3fc2 8 | 9 | name: slick-template 10 | version: 0.1.0.0 11 | synopsis: My site built with slick! 12 | description: My slick Site 13 | category: Slick-Site 14 | homepage: https://github.com/Me/my-site#readme 15 | bug-reports: https://github.com/Me/my-site/issues 16 | author: Me 17 | maintainer: example@example.com 18 | copyright: 20XX Me 19 | license: BSD3 20 | license-file: LICENSE 21 | build-type: Simple 22 | extra-source-files: 23 | README.md 24 | ChangeLog.md 25 | 26 | source-repository head 27 | type: git 28 | location: https://github.com/Me/my-site 29 | 30 | executable build-site 31 | main-is: Main.hs 32 | other-modules: 33 | Paths_slick_template 34 | hs-source-dirs: 35 | app 36 | ghc-options: -threaded -rtsopts -with-rtsopts=-N -Wall 37 | build-depends: 38 | aeson 39 | , base >=4.7 && <5 40 | , containers 41 | , lens 42 | , lens-aeson 43 | , shake 44 | , slick 45 | , text 46 | , time 47 | , unordered-containers 48 | default-language: Haskell2010 49 | -------------------------------------------------------------------------------- /stack.yaml: -------------------------------------------------------------------------------- 1 | resolver: lts-14.22 2 | 3 | packages: 4 | - . 5 | 6 | extra-deps: 7 | - slick-1.0.1.1 8 | -------------------------------------------------------------------------------- /stack.yaml.lock: -------------------------------------------------------------------------------- 1 | # This file was autogenerated by Stack. 2 | # You should not edit this file by hand. 3 | # For more information, please see the documentation at: 4 | # https://docs.haskellstack.org/en/stable/lock_files 5 | 6 | packages: 7 | - completed: 8 | hackage: slick-1.0.1.1@sha256:bd27d2237126f8883b14c4a1e6c19b6640ed8dae5ba18e9eb8877c1ee23d5c1e,1307 9 | pantry-tree: 10 | size: 601 11 | sha256: dbea944bf224db953522e5c95142920aad976610e1d7434b7e71963670329ec3 12 | original: 13 | hackage: slick-1.0.1.1 14 | snapshots: 15 | - completed: 16 | size: 524164 17 | url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/14/22.yaml 18 | sha256: 7ad8f33179b32d204165a3a662c6269464a47a7e65a30abc38d01b5a38ec42c0 19 | original: lts-14.22 20 | --------------------------------------------------------------------------------