├── .gitignore ├── .vscode └── settings.json ├── Pipfile ├── Pipfile.lock ├── README.md ├── Requirements.in ├── app ├── README.md ├── __init__.py ├── app.py ├── config.py ├── gitmanager.py ├── helper.py ├── loader.py ├── logger.py ├── static │ ├── fonts │ │ ├── lato.woff2 │ │ └── quicksand.woff2 │ ├── images │ │ ├── bg.jpg │ │ ├── email.png │ │ ├── facebook.png │ │ ├── favicon.png │ │ ├── github.png │ │ ├── github_cute.png │ │ ├── keybase.png │ │ ├── linkedin.png │ │ ├── location.png │ │ ├── profile.png │ │ ├── snehesh.jpeg │ │ ├── snehesh_sq.jpg │ │ └── twitter.png │ ├── js │ │ └── app.js │ ├── public │ │ ├── app.js │ │ ├── bg.jpg │ │ ├── email.png │ │ ├── facebook.png │ │ ├── favicon.png │ │ ├── github.png │ │ ├── github_cute.png │ │ ├── keybase.png │ │ ├── lato.woff2 │ │ ├── linkedin.png │ │ ├── location.png │ │ ├── profile.png │ │ ├── quicksand.woff2 │ │ ├── snehesh.jpeg │ │ ├── snehesh_sq.jpg │ │ ├── styles.css │ │ └── twitter.png │ ├── robots.txt │ └── sass │ │ ├── backup.scss.bak │ │ ├── button.scss │ │ ├── fonts.scss │ │ ├── media-queries.scss │ │ ├── normalize.scss │ │ ├── responsive.scss │ │ ├── rfs.scss.bak │ │ ├── styles.scss │ │ └── theme.scss ├── templates │ ├── about.html │ ├── base.html │ ├── blog.html │ ├── footer.html │ ├── header.html │ ├── index.html │ ├── post.html │ ├── projects.html │ └── sitemap.xml └── views.py ├── docker ├── Dockerfile └── start_server.sh ├── gulpfile.js ├── package.json ├── yarn-error.log └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # Python 2 | __pycache__ 3 | *.pyc 4 | 5 | # Sass 6 | .sass-cache 7 | .map 8 | .gitignore 9 | node_modules/ 10 | 11 | env/ 12 | blogposts 13 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "python.pythonPath": "/home/warlock/.local/share/virtualenvs/blog-AAp6Teun/bin/python", 3 | "python.linting.enabled": false 4 | } -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | url = "https://pypi.python.org/simple" 3 | verify_ssl = true 4 | name = "pypi" 5 | 6 | [packages] 7 | arrow = "==0.10.0" 8 | cffi = "==1.9.1" 9 | click = "==6.6" 10 | dateparser = "==0.6.0" 11 | itsdangerous = "==0.24" 12 | misaka = "==2.0.0" 13 | mistune = "==0.7.3" 14 | pycparser = "==2.17" 15 | python-dateutil = "==2.6.0" 16 | pytz = "==2017.2" 17 | regex = "==2017.6.7" 18 | "ruamel.yaml" = "==0.15.9" 19 | six = "==1.10.0" 20 | tornado = "==4.5.1" 21 | tzlocal = "==1.4" 22 | Markdown = "==2.6.6" 23 | MarkupSafe = "==0.23" 24 | Pigments = "==1.6" 25 | Werkzeug = "==0.11.10" 26 | flask = "*" 27 | 28 | [dev-packages] 29 | -------------------------------------------------------------------------------- /Pipfile.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "hash": { 4 | "sha256": "d085453c08d1d0dbb6ffcee0de9149a370c26ac95f2ae3dc4ecf4cb719f84109" 5 | }, 6 | "pipfile-spec": 6, 7 | "requires": {}, 8 | "sources": [ 9 | { 10 | "name": "pypi", 11 | "url": "https://pypi.python.org/simple", 12 | "verify_ssl": true 13 | } 14 | ] 15 | }, 16 | "default": { 17 | "arrow": { 18 | "hashes": [ 19 | "sha256:805906f09445afc1f0fc80187db8fe07670e3b25cdafa09b8d8ac264a8c0c722" 20 | ], 21 | "index": "pypi", 22 | "version": "==0.10.0" 23 | }, 24 | "cffi": { 25 | "hashes": [ 26 | "sha256:04b133ef629ae2bc05f83d0b079a964494a9cd17914943e690c57209b44aae20", 27 | "sha256:0f1b3193c17b93c75e73eeac92f22eec4c98a021d9969b1c347d1944fae0d26b", 28 | "sha256:1fb1cf40c315656f98f4d3acfb1bd031a14a9a69d155e9a180d5f9b52eaf745a", 29 | "sha256:20af85d8e154b50f540bc8d517a0dbf6b1c20b5d06e572afda919d5dafd1d06b", 30 | "sha256:2570f93b42c61013ab4b26e23aa25b640faf5b093ad7dd3504c3a8eadd69bc24", 31 | "sha256:2f4e2872833ee3764dfc168dea566b7dd83b01ac61b377490beba53b5ece57f7", 32 | "sha256:31776a37a67424e7821324b9e03a05aa6378bbc2bccc58fa56402547f82803c6", 33 | "sha256:353421c76545f1d440cacc137abc865f07eab9df0dd3510c0851a2ca04199e90", 34 | "sha256:36d06de7b09b1eba54b1f5f76e2221afef7489cc61294508c5a7308a925a50c6", 35 | "sha256:3f1908d0bcd654f8b7b73204f24336af9f020b707fb8af937e3e2279817cbcd6", 36 | "sha256:5268de3a18f031e9787c919c1b9137ff681ea696e76740b1c6c336a26baaa58a", 37 | "sha256:563e0bd53fda03c151573217b3a49b3abad8813de9dd0632e10090f6190fdaf8", 38 | "sha256:5e1368d13f1774852f9e435260be19ad726bbfb501b80472f61c2dc768a0692a", 39 | "sha256:60881c79eb72cb75bd0a4be5e31c9e431739146c4184a2618cabea3938418984", 40 | "sha256:6120b62a642a40e47eb6c9ff00c02be69158fc7f7c5ff78e42a2c739d1c57cd6", 41 | "sha256:65c223e77f87cb463191ace3398e0a6d84ce4ac575d42eb412a220b099f593d6", 42 | "sha256:6fbf8db55710959344502b58ab937424173ad8b5eb514610bcf56b119caa350a", 43 | "sha256:74aadea668c94eef4ceb09be3d0eae6619e28b4f1ced4e29cd43a05bb2cfd7a4", 44 | "sha256:7be1efa623e1ed91b15b1e62e04c536def1d75785eb930a0b8179ca6b65ed16d", 45 | "sha256:83266cdede210393889471b0c2631e78da9d4692fcca875af7e958ad39b897ee", 46 | "sha256:86c68a3f8246495962446c6f96f6a27f182b91208187b68f1e87ec3dfd29fa32", 47 | "sha256:9163f7743cf9991edaddf9cf886708e288fab38e1b9fec9c41c15c85c8f7f147", 48 | "sha256:97d9f338f91b7927893ea6500b953e4b4b7e47c6272222992bb76221e17056ff", 49 | "sha256:a7930e73a4359b52323d09de6d6860840314aa09346cbcf4def8875e1b07ebc7", 50 | "sha256:ada8a42c493e4934a1a8875c2bc9efcb1b88c09883f70375bfa053ab32d6a118", 51 | "sha256:b0bc2d83cc0ba0e8f0d9eca2ffe07f72f33bec7d84547071e7e875d4cca8272d", 52 | "sha256:b5412a65605c642adf3e1544b59b8537daf5696dedadd2b3cbebc42e24da45ed", 53 | "sha256:ba6b5205fced1625b6d9d55f9ef422f9667c5d95f18f07c0611eb964a3355331", 54 | "sha256:bcaf3d86385daaab0ae51c9c53ebe70a6c1c5dfcb9e311b13517e04773ddf6b6", 55 | "sha256:cfa15570ecec1ea6bee089e86fd4deae6208c96a811344ce246de5e5c9ac824a", 56 | "sha256:d3e3063af1fa6b59e255da9a812891cdaf24b90fbaf653c02797871069b7c4c9", 57 | "sha256:d9cfe26ecea2fec320cd0cac400c9c2435328994d23596ee6df63945fe7292b0", 58 | "sha256:e5ef800ef8ef9ee05ae9a5b7d7d9cf7d6c936b32e312e54823faca3034ee16ab", 59 | "sha256:f1366150acf611d09d37ffefb3559ed3ffeb1713643d3cd10716d6c5da3f83fb", 60 | "sha256:f4eb9747a37120b35f59c8e96265e87b0c432ff010d32fc0772992aa14659502", 61 | "sha256:f8264463cc08cd696ad17e4bf3c80f3344628c04c11ffdc545ddf0798bc17316", 62 | "sha256:f8ba54848dfe280b1be0d6e699544cee4ba10d566f92464538063d9e645aed3e", 63 | "sha256:f93d1edcaea7b6a7a8fbf936f4492a9a0ee0b4cb281efebd5e1dd73e5e432c71", 64 | "sha256:fc8865c7e0ac25ddd71036c2b9a799418b32d9acb40400d345b8791b6e1058cb", 65 | "sha256:fce6b0cb9ade1546178c031393633b09c4793834176496c99a94de0bfa471b27", 66 | "sha256:fde17c52d7ce7d55a9fb263b57ccb5da6439915b5c7105617eb21f636bb1bd9c" 67 | ], 68 | "index": "pypi", 69 | "version": "==1.9.1" 70 | }, 71 | "click": { 72 | "hashes": [ 73 | "sha256:cc6a19da8ebff6e7074f731447ef7e112bd23adf3de5c597cf9989f2fd8defe9", 74 | "sha256:fcf697e1fd4b567d817c69dab10a4035937fe6af175c05fd6806b69f74cbc6c4" 75 | ], 76 | "index": "pypi", 77 | "version": "==6.6" 78 | }, 79 | "dateparser": { 80 | "hashes": [ 81 | "sha256:e2629d2f8361722c6047138ca085256c9f2cf5cc657fd66122aa0564afa4dc33", 82 | "sha256:f8c24317120b06f71691d28076764ec084a132be2a250a78fdf54f6b427cac95" 83 | ], 84 | "index": "pypi", 85 | "version": "==0.6.0" 86 | }, 87 | "itsdangerous": { 88 | "hashes": [ 89 | "sha256:cbb3fcf8d3e33df861709ecaf89d9e6629cff0a217bc2848f1b41cd30d360519" 90 | ], 91 | "index": "pypi", 92 | "version": "==0.24" 93 | }, 94 | "jinja2": { 95 | "hashes": [ 96 | "sha256:1cc03ef32b64be19e0a5b54578dd790906a34943fe9102cfdae0d4495bd536b4", 97 | "sha256:bc1ff2ff88dbfacefde4ddde471d1417d3b304e8df103a7a9437d47269201bf4" 98 | ], 99 | "index": "pypi", 100 | "version": "==2.8" 101 | }, 102 | "markdown": { 103 | "hashes": [ 104 | "sha256:022239550fb4a84bcc3b3b42dff9a41efc56d773ef17c4f28016dd8f265c82d0", 105 | "sha256:9a292bb40d6d29abac8024887bcfc1159d7a32dc1d6f1f6e8d6d8e293666c504" 106 | ], 107 | "index": "pypi", 108 | "version": "==2.6.6" 109 | }, 110 | "markupsafe": { 111 | "hashes": [ 112 | "sha256:a4ec1aff59b95a14b45eb2e23761a0179e98319da5a7eb76b56ea8cdc7b871c3" 113 | ], 114 | "index": "pypi", 115 | "version": "==0.23" 116 | }, 117 | "misaka": { 118 | "hashes": [ 119 | "sha256:336ef1381ab840046b7da9f95c4c28af17e636aed8dcfcf1efe972db05f73604", 120 | "sha256:4a15e96a58b2d5732edc2bc7513d4e85cc134cc76a1e2d09bf4d405ecedb5026" 121 | ], 122 | "index": "pypi", 123 | "version": "==2.0.0" 124 | }, 125 | "mistune": { 126 | "hashes": [ 127 | "sha256:21d0e869df3b9189f248e022f1c9763cf9069e1a2f00676f1f1852bd7f98b713", 128 | "sha256:ee7447aadcf1962b5af767ff0443dcb0499c16bf73ad36dc99d230e7574571e5" 129 | ], 130 | "index": "pypi", 131 | "version": "==0.7.3" 132 | }, 133 | "pigments": { 134 | "hashes": [ 135 | "sha256:8f927e03d397854faca0bd147a97303dd42aa9d83f7bca385b12901be0906902" 136 | ], 137 | "index": "pypi", 138 | "version": "==1.6" 139 | }, 140 | "pycparser": { 141 | "hashes": [ 142 | "sha256:0aac31e917c24cb3357f5a4d5566f2cc91a19ca41862f6c3c22dc60a629673b6" 143 | ], 144 | "index": "pypi", 145 | "version": "==2.17" 146 | }, 147 | "python-dateutil": { 148 | "hashes": [ 149 | "sha256:3acbef017340600e9ff8f2994d8f7afd6eacb295383f286466a6df3961e486f0", 150 | "sha256:537bf2a8f8ce6f6862ad705cd68f9e405c0b5db014aa40fa29eab4335d4b1716", 151 | "sha256:62a2f8df3d66f878373fd0072eacf4ee52194ba302e00082828e0d263b0418d2" 152 | ], 153 | "index": "pypi", 154 | "version": "==2.6.0" 155 | }, 156 | "pytz": { 157 | "hashes": [ 158 | "sha256:d1d6729c85acea5423671382868627129432fba9a89ecbb248d8d1c7a9f01c67", 159 | "sha256:f5c056e8f62d45ba8215e5cb8f50dfccb198b4b9fbea8500674f3443e4689589" 160 | ], 161 | "index": "pypi", 162 | "version": "==2017.2" 163 | }, 164 | "regex": { 165 | "hashes": [ 166 | "sha256:0108d0cc20d3f52538524e672272de848c5040e80eeeb30db92aef1adca85018", 167 | "sha256:0943225e6283fb02aa48701ad49e3de7fea9f9651e57a8836d114a6990c96692", 168 | "sha256:1513a41c76930043ea71715b9329804c5120440bb6c3d9c726cab153c369b67e", 169 | "sha256:159c22e2bedec21a41e67832451a042911e63eec580e7bd6f4e1d105e4ba33d4", 170 | "sha256:1a812f66ea0d0a47d4aeb50ea6e8f0267451cafd2ad1f51c8c8f66ba4e2eb1e0", 171 | "sha256:21bc64f5cf4a1a3adf3bfa912f92473a29cb556ac820576e4af0a998952f77b6", 172 | "sha256:2e71f63d505b6c9855656dd419d8e7504f99512da36dd241cf7629c168a55d4e", 173 | "sha256:43babf481fded33a14961d8f866c509e5bec562497006f9d3bb56a3cb188083e", 174 | "sha256:479eea16637be3f7e093ad5c97e26cdd74b708130e2be9ca8dc4194bf1d58807", 175 | "sha256:50c8dd40ec938a105f12fd2cf56ab462052b088dfde2baa5f6c4998edbb1841a", 176 | "sha256:6d0303e73abecfce91c5ea77e7436fa4f2c9367f271a4af1758521e72c7b3d24", 177 | "sha256:6ee29a3f292182da1f1e219d9d0ac0f9bd037146241779fdfb9ca9601da8ea66", 178 | "sha256:abb41bf0fb3a1b027af1038737c09b1e686a13b596b282397cd84dd51bba519e", 179 | "sha256:c0003ff071dfd114a28dfa02cd003d7605e5055f63a126ae5c3fdaddaa628456", 180 | "sha256:c57a4bf7a4fa7c1c4d11f9a48cb86fa4d6fa0902ecde718e22b69cef40267379", 181 | "sha256:cc8712ec515208266346ed7bed61cb5f565247dda4cea9d968c1c24de1903138", 182 | "sha256:db02c609e0354d8806b1dc94033677f645115c1b0d9c9be6b5845a0789c848b0", 183 | "sha256:e45784bbe5a0ce4a954fbc5e0f72909798257241147271d4906bc617fd59261b", 184 | "sha256:f323c270c046d43910d0458cf1002c21651133afbe93b0c62d10c15ac3e50229" 185 | ], 186 | "index": "pypi", 187 | "version": "==2017.6.7" 188 | }, 189 | "ruamel.yaml": { 190 | "hashes": [ 191 | "sha256:1ee3c0108e7f697d5f45276416191c9c45dcfb54e0a3181f65022b6875964102", 192 | "sha256:20974865ae6bb500de6675185bc5512ba8a7dd1ab9c82c2264acba219005ddec", 193 | "sha256:350496f6fdd8c2bb17a0fa3fd2ec98431280cf12d72dae498b19ac0119c2bbad", 194 | "sha256:373b9722e2e0a54dde7c1f0f965a4bcb47662618588af33b8b2a7bb82cc16197", 195 | "sha256:5d48b6a63b982b68f2cef5a48895078161dd205ada2cb5357385295a4f65b106", 196 | "sha256:6c3bb9019353acb6390e786047480b6d10e1b24f8b95c1cb3138dd5c64e75794", 197 | "sha256:d543d3999a007f0f3038bb70dfc430e1ccad4402838d3653f302faeffe5a9507" 198 | ], 199 | "index": "pypi", 200 | "version": "==0.15.9" 201 | }, 202 | "six": { 203 | "hashes": [ 204 | "sha256:0ff78c403d9bccf5a425a6d31a12aa6b47f1c21ca4dc2573a7e2f32a97335eb1", 205 | "sha256:105f8d68616f8248e24bf0e9372ef04d3cc10104f1980f54d57b2ce73a5ad56a" 206 | ], 207 | "index": "pypi", 208 | "version": "==1.10.0" 209 | }, 210 | "tornado": { 211 | "hashes": [ 212 | "sha256:0a6894559e62a186d6cc20f1c0e7318f83ae8e28922d75c4b6f83d42cc91d8b1", 213 | "sha256:2b4618010625bcf01f187eec3148c6a3db76a2a900f7dde416330fc413274df1", 214 | "sha256:c3f4456f0ab9e62dab7ede834dcf4c89082238e97b3e67a9196572c076e716aa", 215 | "sha256:db0904a28253cfe53e7dedc765c71596f3c53bb8a866ae50123320ec1a7b73fd", 216 | "sha256:fb0883ae46da958d06fda2e3be6c033825a0b8603c493df8a69c0237055308d9" 217 | ], 218 | "index": "pypi", 219 | "version": "==4.5.1" 220 | }, 221 | "tzlocal": { 222 | "hashes": [ 223 | "sha256:05a2908f7fb1ba8843f03b2360d6ad314dbf2bce4644feb702ccd38527e13059" 224 | ], 225 | "index": "pypi", 226 | "version": "==1.4" 227 | }, 228 | "werkzeug": { 229 | "hashes": [ 230 | "sha256:cc64dafbacc716cdd42503cf6c44cb5a35576443d82f29f6829e5c49264aeeee", 231 | "sha256:f22b9762589decfde50149c7ee080713cbf6129b49ce2b398f59b709b161a8d3" 232 | ], 233 | "index": "pypi", 234 | "version": "==0.11.10" 235 | } 236 | }, 237 | "develop": {} 238 | } 239 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Minimalist Blog Engine [snehesh.me](https://snehesh.me) 2 | ====================== 3 | Discuss on [Hacker News](https://news.ycombinator.com/item?id=12244475) 4 | 5 | _TL;DR_ 6 | 7 | Publish a blog post with ` git push ` 8 | 9 |  10 | 11 | My motivation to build this blog engine was based on 3 factors. 12 | - Write on any device ( i.e Mobile, Laptop) using CLI or text editor 13 | - Never worry about database backups and keep track of all changes I made on an article 14 | - Make publishing simple without touching the server 15 | 16 | This project uses git to track all changes made on an article, as all the writing is done in markdown format, I can write posts on multiple devices ( CLI (Termux) on Android, VIM or Sublime on Arch ) switching back and forth. Finally I can publish those writings by adding metadata to the metadata.json file. In other words the posts are visible on blog only when they are enabled in `metadata.json`. 17 | 18 | 19 |  20 | 21 | 22 | 23 | ToDo 24 | ---- 25 | - Multiprocess support for DataStore() 26 | - Support for .rst format 27 | - Social Buttons ( HN, Twitter ) 28 | - Filtered views based on Tags 29 | 30 | 31 | Usage 32 | ----- 33 | docker pull snehesh/blogengine:latest 34 | ``` 35 | Environmental Variables 36 | POSTS_GIT_REPO 37 | POSTS_GIT_REPO_SECRET 38 | ``` 39 | 40 | ``` 41 | docker run -d \ 42 | -e POSTS_GIT_REPO="https://github.com/snehesht/BLOGPOSTS_REPO" \ 43 | -e POSTS_GIT_REPO_SECRET="SECRET HERE" \ 44 | -p 5000:5000 \ 45 | --name be1 snehesh/blogengine:latest 46 | ``` 47 | 48 | Stack 49 | ----- 50 | > - Python 3 51 | > - Flask 52 | > - Markdown 53 | > - Github Webhook 54 | 55 | License 56 | ------- 57 | 58 | The MIT License (MIT) 59 | 60 | Copyright (c) 2016 Snehesh Thalapaneni 61 | 62 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 63 | 64 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 65 | 66 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 67 | -------------------------------------------------------------------------------- /Requirements.in: -------------------------------------------------------------------------------- 1 | arrow==0.10.0 2 | cffi==1.9.1 3 | click==6.6 4 | dateparser==0.6.0 5 | Flask==1.0.2 6 | itsdangerous==0.24 7 | Jinja2==2.10 8 | Markdown==2.6.6 9 | MarkupSafe==0.23 10 | misaka==2.0.0 11 | mistune==0.7.3 12 | Pigments==1.6 13 | pkg-resources==0.0.0 14 | pycparser==2.17 15 | python-dateutil==2.6.0 16 | pytz==2017.2 17 | regex==2017.6.7 18 | ruamel.yaml==0.15.9 19 | six==1.10.0 20 | tornado==4.5.1 21 | tzlocal==1.4 22 | Werkzeug==0.14.1 23 | -------------------------------------------------------------------------------- /app/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snehesht/blog/e579fce1d4f4a17fb6c9c7adfa3e05bbdc24aae6/app/README.md -------------------------------------------------------------------------------- /app/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snehesht/blog/e579fce1d4f4a17fb6c9c7adfa3e05bbdc24aae6/app/__init__.py -------------------------------------------------------------------------------- /app/app.py: -------------------------------------------------------------------------------- 1 | from flask import Flask 2 | from config import * 3 | import os 4 | 5 | # Initilize Flask App 6 | app = Flask(__name__,static_folder='static', static_url_path='') 7 | 8 | # Import views after the app is initilized 9 | from views import * 10 | 11 | ENV = 'production' 12 | DEBUG_STATUS = False 13 | if ('ENV' in os.environ): 14 | if (os.environ['ENV'] == 'dev'): 15 | DEBUG_STATUS = True 16 | 17 | print(ENV) 18 | 19 | @app.after_request 20 | def add_header(response): 21 | if (DEBUG_STATUS): 22 | response.cache_control.no_store = True 23 | response.cache_control.no_cache = True 24 | else: 25 | response.cache_control.max_age = 360000 26 | return response 27 | 28 | if __name__=="__main__": 29 | app.run(debug=DEBUG_STATUS,host=HOST,port=PORT) 30 | -------------------------------------------------------------------------------- /app/config.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | SITE_URL = "https://snehesh.me" 4 | 5 | # Accepted file formats regex 6 | # markdown accepted 7 | # reStructuredText not accepted for now 8 | ACCEPTED_FILE_FORMATS = "^.*\.md$" # .md 9 | 10 | # Sitemap Blacklist URL's 11 | SITEMAP_BLACKLIST=['/blog/', '/sitemap.xml'] 12 | 13 | # Git Repo Config 14 | # Fetched via environmental variables 15 | if os.environ.get('POSTS_GIT_REPO') != None: 16 | GIT_REPO_URL = os.environ.get('POSTS_GIT_REPO') 17 | else: 18 | print('Check your environmental vars - POSTS_GIT_REPO is null ') 19 | GIT_REPO_URL = "https://github.com/snehesht/blogposts.git" 20 | 21 | GIT_REPO_DIR_NAME = GIT_REPO_URL.split('/')[-1].replace('.git','') 22 | 23 | if os.environ.get('POSTS_GIT_REPO_SECRET') != None: 24 | GIT_REPO_SECRET = os.environ.get('POSTS_GIT_REPO_SECRET') 25 | else: 26 | print('Check your environmental vars - POSTS_GIT_REPO_SECRET is null ') 27 | GIT_REPO_SECRET = "YOUR SECRET" 28 | 29 | # Server Config 30 | PORT=5000 31 | HOST='0.0.0.0' 32 | DEBUG_STATUS=True 33 | 34 | # Welcome Message 35 | WELCOME_MESSAGE="Hello there, I'm Snehesh and I love Python and OSS" 36 | 37 | # About Me 38 | # HTML 39 | ABOUT_ME_CONTENT=""" 40 |
41 | I'm Snehesh Thalapaneni, a full stack developer living in New York City. While I was doing my undergrad in Electronics, I developed keen interest in computers and I started to teach my self C and then Python. After my bachelors I moved to US and got my master's degree in Computer Engineering. Fast forward, I'm living in New York and working as a generalistic software developer. 42 |
43 | 44 |45 | My interests align with product development, system architecture, IoT, general machine learning, infosec and online privacy. I spend most of my free time turning my ideas into side projects. Apart from the technical aspects of software development, I'm interested in product management, User Interfaces and Robotics. 46 |
47 | 48 |49 | One of most interesting projects I worked on is for my bachelor's thesis, Along with Deepak Muppiri I built a road scanner to map irregularities on indian roads such as speed breakers, path holes, dangerous curves etc. using an array of sensors and then map the data onto Google Maps with ~ 1 meter resolution. Thus the data can be integrated into the navigation software to alert drivers about possible dangers up ahead and this data can also be used by the Govt. to prioritize fixing the roads. 50 |
51 | 52 |53 | Check out my github profile for my side projects, and feel free to use then in anyway you see fit. If you wish to connect, drop an email at mail@snehesh.me 54 |
55 | """ 56 | 57 | 58 | -------------------------------------------------------------------------------- /app/gitmanager.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | import os 3 | from config import * 4 | 5 | # Update the GIT database repo 6 | def git_update(): 7 | current_dir = os.getcwd().split('/')[-1] 8 | try: 9 | if check_repo_exist(): 10 | # Checks if the directory is GIT_REPO_DIR_NAME or not, if not changes to that dir 11 | # This is to avoid recursive chdir calls 12 | if current_dir != GIT_REPO_DIR_NAME: 13 | print("Changing directory") 14 | os.chdir(GIT_REPO_DIR_NAME) 15 | proc = subprocess.run(['git','pull']) 16 | # Sleeps for 10 secs, waits till the pull is completed 17 | # time.sleep(10) 18 | else: 19 | print('Some problem with the repo, repo doesnt exist') 20 | except Exception as e: 21 | raise e 22 | finally: 23 | os.chdir("../") 24 | 25 | 26 | def get_current_dir(): 27 | tmp = os.getcwd().split('/') 28 | return tmp[-1] 29 | 30 | 31 | # GIT DATA repo doesn't exist, pull the repo 32 | def git_clone(): 33 | try: 34 | os.chdir(GIT_REPO_DIR_NAME) 35 | proc = subprocess.run(['git','clone',GIT_REPO_URL]) 36 | except Exception as e: 37 | raise e 38 | finally: 39 | return 0 40 | 41 | 42 | # Check if git data repo exist 43 | def check_repo_exist(): 44 | try: 45 | repo_exists = False 46 | # List dir 47 | for item in os.listdir(): 48 | # print(item) 49 | if item == GIT_REPO_DIR_NAME: 50 | repo_exists = True 51 | current_dir = os.getcwd().split('/')[-1] 52 | # If curr directory is GIT_REPO_DIR_NAME 53 | if current_dir == GIT_REPO_DIR_NAME: 54 | repo_exists = True 55 | if repo_exists == False: 56 | proc = subprocess.run(['git','clone',GIT_REPO_URL]) 57 | except Exception as e: 58 | raise 59 | finally: 60 | return True 61 | -------------------------------------------------------------------------------- /app/helper.py: -------------------------------------------------------------------------------- 1 | import hashlib 2 | import hmac 3 | import datetime 4 | import misaka 5 | from app import app 6 | from loader import DataStore 7 | from config import SITE_URL , SITEMAP_BLACKLIST 8 | 9 | # Generate sitemap.xml 10 | def gen_sitemap(blog_metadata): 11 | ''' Generate sitemap.xml file ''' 12 | today = datetime.datetime.now().date().isoformat() 13 | sitemap_urllist = [] 14 | 15 | # compile a list of all static urls like /about, /blog, / etc. 16 | # and append them to the sitemap_urllist. 17 | for rule in app.url_map.iter_rules(): 18 | if rule.endpoint != 'static' and "GET" in rule.methods and len( 19 | rule.arguments) == 0 and rule.rule not in SITEMAP_BLACKLIST: 20 | tmp_dic = {} 21 | tmp_dic['url'] = SITE_URL + rule.rule 22 | # Ignoring the lastmod for constant URL's 23 | #tmp_dic['lastmod'] = today 24 | tmp_dic['priority'] = 0.3 25 | tmp_dic['changefreq'] = "daily" 26 | sitemap_urllist.append(tmp_dic) 27 | 28 | for bp in blog_metadata: 29 | tmp_dic = {} 30 | tmp_dic['url'] = SITE_URL +"/blog/" + bp['url'] 31 | tmp_dic['lastmod'] = bp['date'] 32 | tmp_dic['priority'] = 0.5 33 | tmp_dic['changefreq'] = "weekly" 34 | sitemap_urllist.append(tmp_dic) 35 | 36 | return sitemap_urllist 37 | 38 | 39 | # Convert markdown --> html 40 | def md2html(markdown_content): 41 | ''' Convert markdown to html using misaka ''' 42 | html_content = misaka.html( 43 | markdown_content, 44 | extensions=misaka.EXT_NO_INTRA_EMPHASIS | misaka.EXT_FENCED_CODE | 45 | misaka.EXT_AUTOLINK | misaka.EXT_TABLES | misaka.EXT_STRIKETHROUGH | 46 | misaka.EXT_QUOTE | misaka.EXT_UNDERLINE | misaka.EXT_HIGHLIGHT, 47 | render_flags=misaka.HTML_USE_XHTML | misaka.HTML_HARD_WRAP) 48 | return html_content 49 | 50 | 51 | # Verify Github Webhook hash 52 | def hash_check(gh_sha1, gh_payload, secret): 53 | ''' Verify Github webhook payload and hash ''' 54 | calc_hmac_sha1 = hmac.new(secret.encode(), gh_payload, hashlib.sha1) 55 | if gh_sha1.split('=')[1] == calc_hmac_sha1.hexdigest(): 56 | return True 57 | else: 58 | return False 59 | -------------------------------------------------------------------------------- /app/loader.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | import json 4 | from config import * 5 | from gitmanager import * 6 | import time 7 | import arrow 8 | 9 | # Global Config 10 | DATA_DIR = GIT_REPO_DIR_NAME 11 | POSTS_DATA_DIR = DATA_DIR + "/public" 12 | PROJECTS_DATA_DIR = DATA_DIR + "/projects" 13 | FILE_DOT_MD_REGEX = ACCEPTED_FILE_FORMATS 14 | """ 15 | Class to access data 16 | """ 17 | class DataStore(object): 18 | """docstring for DataStore""" 19 | def __init__(self): 20 | super(DataStore, self).__init__() 21 | self.data = {} 22 | self.metadata = {} 23 | check_repo_exist() 24 | self.load_files() 25 | self.load_metadata() 26 | """ 27 | Load files from data_dir with markdown format and return a map with key as filename and value as contents 28 | """ 29 | def load_files(self): 30 | print("loading files ...") 31 | data = {} 32 | files_in_dir = os.listdir(POSTS_DATA_DIR) 33 | for f in files_in_dir: 34 | # safegaurd to load only *.md files 35 | if re.match(FILE_DOT_MD_REGEX,f) is not None: 36 | # Read each file and save it's content in a dict with key as filename and value as content 37 | with open(POSTS_DATA_DIR+"/"+f) as fp: 38 | key = f.replace(".md","") 39 | value = fp.read() 40 | data[key] = value 41 | self.data = data 42 | 43 | # Meta data for the posts 44 | # Rule, the filename must match the key in metadata 45 | def load_metadata(self): 46 | with open(DATA_DIR+"/"+"metadata.json","r") as fp: 47 | parsed_data = json.load(fp) 48 | parsed_metadata = []; 49 | for el in parsed_data: 50 | el['human_time'] = arrow.get(el['date']).humanize() 51 | el['year'] = arrow.get(el['date']).year 52 | parsed_metadata.append(el) 53 | self.metadata = parsed_metadata 54 | 55 | 56 | #Function to access data, this is usually called 57 | def get_data(self): 58 | return self.data 59 | 60 | 61 | #Function to fetch metadata 62 | def get_metadata(self): 63 | return self.metadata 64 | 65 | 66 | #Reload data from DATA_DIR, usually called when changes are made 67 | def reload_data(self): 68 | self.load_files() 69 | self.load_metadata() 70 | 71 | #Reload data 72 | def reload(self): 73 | try: 74 | git_update() 75 | except Exception as e: 76 | print("Reload failed") 77 | finally: 78 | self.reload_data() 79 | -------------------------------------------------------------------------------- /app/logger.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snehesht/blog/e579fce1d4f4a17fb6c9c7adfa3e05bbdc24aae6/app/logger.py -------------------------------------------------------------------------------- /app/static/fonts/lato.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snehesht/blog/e579fce1d4f4a17fb6c9c7adfa3e05bbdc24aae6/app/static/fonts/lato.woff2 -------------------------------------------------------------------------------- /app/static/fonts/quicksand.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snehesht/blog/e579fce1d4f4a17fb6c9c7adfa3e05bbdc24aae6/app/static/fonts/quicksand.woff2 -------------------------------------------------------------------------------- /app/static/images/bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snehesht/blog/e579fce1d4f4a17fb6c9c7adfa3e05bbdc24aae6/app/static/images/bg.jpg -------------------------------------------------------------------------------- /app/static/images/email.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snehesht/blog/e579fce1d4f4a17fb6c9c7adfa3e05bbdc24aae6/app/static/images/email.png -------------------------------------------------------------------------------- /app/static/images/facebook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snehesht/blog/e579fce1d4f4a17fb6c9c7adfa3e05bbdc24aae6/app/static/images/facebook.png -------------------------------------------------------------------------------- /app/static/images/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snehesht/blog/e579fce1d4f4a17fb6c9c7adfa3e05bbdc24aae6/app/static/images/favicon.png -------------------------------------------------------------------------------- /app/static/images/github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snehesht/blog/e579fce1d4f4a17fb6c9c7adfa3e05bbdc24aae6/app/static/images/github.png -------------------------------------------------------------------------------- /app/static/images/github_cute.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snehesht/blog/e579fce1d4f4a17fb6c9c7adfa3e05bbdc24aae6/app/static/images/github_cute.png -------------------------------------------------------------------------------- /app/static/images/keybase.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snehesht/blog/e579fce1d4f4a17fb6c9c7adfa3e05bbdc24aae6/app/static/images/keybase.png -------------------------------------------------------------------------------- /app/static/images/linkedin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snehesht/blog/e579fce1d4f4a17fb6c9c7adfa3e05bbdc24aae6/app/static/images/linkedin.png -------------------------------------------------------------------------------- /app/static/images/location.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snehesht/blog/e579fce1d4f4a17fb6c9c7adfa3e05bbdc24aae6/app/static/images/location.png -------------------------------------------------------------------------------- /app/static/images/profile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snehesht/blog/e579fce1d4f4a17fb6c9c7adfa3e05bbdc24aae6/app/static/images/profile.png -------------------------------------------------------------------------------- /app/static/images/snehesh.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snehesht/blog/e579fce1d4f4a17fb6c9c7adfa3e05bbdc24aae6/app/static/images/snehesh.jpeg -------------------------------------------------------------------------------- /app/static/images/snehesh_sq.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snehesht/blog/e579fce1d4f4a17fb6c9c7adfa3e05bbdc24aae6/app/static/images/snehesh_sq.jpg -------------------------------------------------------------------------------- /app/static/images/twitter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snehesht/blog/e579fce1d4f4a17fb6c9c7adfa3e05bbdc24aae6/app/static/images/twitter.png -------------------------------------------------------------------------------- /app/static/js/app.js: -------------------------------------------------------------------------------- 1 | 2 | // Nav Button Toggle 3 | // 4 | 5 | 6 | // Toggle the Navigation Menubar based on previous state 7 | function toggleNavbar() { 8 | if (document.querySelector('.navbar').style.display != "block") { 9 | document.querySelector(".navbar").style.display = "block" 10 | } else { 11 | document.querySelector(".navbar").style.display = "none" 12 | } 13 | } 14 | 15 | // Attach an EventListener to the "Menu" icon, to listen for mouse clicks 16 | // document.querySelector("#mobile-menu").addEventListener("click", function () { 17 | // console.log('Clicked Menu') 18 | // // this.classList.toggle("open"); 19 | // // toggleNavbar(); 20 | // }); 21 | 22 | 23 | // Execute the script once the webpage is loaded 24 | document.addEventListener('DOMContentLoaded', function () { 25 | Array.from(document.querySelectorAll(".post-content a")).forEach(function (a) { 26 | console.log(a.href) 27 | a.target = "_blank"; 28 | }); 29 | }); 30 | 31 | // Adding target="_blank" to URL's in the blogpost 32 | // Array.from(document.querySelectorAll(".content a")).forEach(function (a) { 33 | // a.target = "_blank"; 34 | // }); 35 | -------------------------------------------------------------------------------- /app/static/public/app.js: -------------------------------------------------------------------------------- 1 | function toggleNavbar(){"block"!=document.querySelector(".navbar").style.display?document.querySelector(".navbar").style.display="block":document.querySelector(".navbar").style.display="none"}document.addEventListener("DOMContentLoaded",function(){Array.from(document.querySelectorAll(".post-content a")).forEach(function(e){console.log(e.href),e.target="_blank"})}); -------------------------------------------------------------------------------- /app/static/public/bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snehesht/blog/e579fce1d4f4a17fb6c9c7adfa3e05bbdc24aae6/app/static/public/bg.jpg -------------------------------------------------------------------------------- /app/static/public/email.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snehesht/blog/e579fce1d4f4a17fb6c9c7adfa3e05bbdc24aae6/app/static/public/email.png -------------------------------------------------------------------------------- /app/static/public/facebook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snehesht/blog/e579fce1d4f4a17fb6c9c7adfa3e05bbdc24aae6/app/static/public/facebook.png -------------------------------------------------------------------------------- /app/static/public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snehesht/blog/e579fce1d4f4a17fb6c9c7adfa3e05bbdc24aae6/app/static/public/favicon.png -------------------------------------------------------------------------------- /app/static/public/github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snehesht/blog/e579fce1d4f4a17fb6c9c7adfa3e05bbdc24aae6/app/static/public/github.png -------------------------------------------------------------------------------- /app/static/public/github_cute.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snehesht/blog/e579fce1d4f4a17fb6c9c7adfa3e05bbdc24aae6/app/static/public/github_cute.png -------------------------------------------------------------------------------- /app/static/public/keybase.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snehesht/blog/e579fce1d4f4a17fb6c9c7adfa3e05bbdc24aae6/app/static/public/keybase.png -------------------------------------------------------------------------------- /app/static/public/lato.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snehesht/blog/e579fce1d4f4a17fb6c9c7adfa3e05bbdc24aae6/app/static/public/lato.woff2 -------------------------------------------------------------------------------- /app/static/public/linkedin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snehesht/blog/e579fce1d4f4a17fb6c9c7adfa3e05bbdc24aae6/app/static/public/linkedin.png -------------------------------------------------------------------------------- /app/static/public/location.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snehesht/blog/e579fce1d4f4a17fb6c9c7adfa3e05bbdc24aae6/app/static/public/location.png -------------------------------------------------------------------------------- /app/static/public/profile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snehesht/blog/e579fce1d4f4a17fb6c9c7adfa3e05bbdc24aae6/app/static/public/profile.png -------------------------------------------------------------------------------- /app/static/public/quicksand.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snehesht/blog/e579fce1d4f4a17fb6c9c7adfa3e05bbdc24aae6/app/static/public/quicksand.woff2 -------------------------------------------------------------------------------- /app/static/public/snehesh.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snehesht/blog/e579fce1d4f4a17fb6c9c7adfa3e05bbdc24aae6/app/static/public/snehesh.jpeg -------------------------------------------------------------------------------- /app/static/public/snehesh_sq.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snehesht/blog/e579fce1d4f4a17fb6c9c7adfa3e05bbdc24aae6/app/static/public/snehesh_sq.jpg -------------------------------------------------------------------------------- /app/static/public/styles.css: -------------------------------------------------------------------------------- 1 | button,hr,input{overflow:visible}progress,sub,sup{vertical-align:baseline}html,legend{max-width:100%}.button,.footer,.post-content img{text-align:center}@font-face{font-family:Lato;font-style:normal;font-weight:400;src:local("Lato Regular"),local("Lato-Regular"),url(lato.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Quicksand;font-style:normal;font-weight:500;src:local("Quicksand Medium"),local("Quicksand-Medium"),url(quicksand.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}/*! normalize.css v8.0.0 | MIT License | github.com/necolas/normalize.css *//*! normalize.css v8.0.0 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-webkit-text-size-adjust:100%}h1{font-size:2em;margin:.67em 0}hr{-webkit-box-sizing:content-box;box-sizing:content-box;height:0}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}a{background-color:transparent}abbr[title]{border-bottom:none;-webkit-text-decoration:underline dotted;text-decoration:underline dotted}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}img{border-style:none}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,select{text-transform:none}[type=submit],[type=button],[type=reset],button{-webkit-appearance:button}[type=submit]::-moz-focus-inner,[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=submit]:-moz-focusring,[type=button]:-moz-focusring,[type=reset]:-moz-focusring,button:-moz-focusring{outline:ButtonText dotted 1px}fieldset{padding:.35em .75em .625em}legend{-webkit-box-sizing:border-box;box-sizing:border-box;color:inherit;display:table;padding:0;white-space:normal}.button,.button:focus,.button:hover{color:#fff;background-color:#212121}textarea{overflow:auto}[type=checkbox],[type=radio]{-webkit-box-sizing:border-box;box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details{display:block}summary{display:list-item}[hidden],template{display:none}@font-face{font-family:Lato;font-style:normal;font-weight:400;src:local("Lato Regular"),local("Lato-Regular"),url(lato.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Quicksand;font-style:normal;font-weight:500;src:local("Quicksand Medium"),local("Quicksand-Medium"),url(quicksand.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@-webkit-keyframes scale-up{from{opacity:1;-webkit-transform:translate(-50%,-50%) scale(0);transform:translate(-50%,-50%) scale(0)}to{opacity:0;-webkit-transform:translate(-50%,-50%) scale(1);transform:translate(-50%,-50%) scale(1)}}@keyframes scale-up{from{opacity:1;-webkit-transform:translate(-50%,-50%) scale(0);transform:translate(-50%,-50%) scale(0)}to{opacity:0;-webkit-transform:translate(-50%,-50%) scale(1);transform:translate(-50%,-50%) scale(1)}}.button{border-radius:4px;-webkit-transition:background-color;transition:background-color;-webkit-transition-duration:.3s;transition-duration:.3s;overflow:hidden}.button:focus,.button:hover{border:1px solid #212121}.button::before{content:"";display:block;width:100%;height:auto;padding-bottom:100%;position:absolute;top:50%;left:50%;z-index:30;background:rgba(0,0,0,.4);border-radius:50%;-webkit-transform:translate(-50%,-50%) scale(0);transform:translate(-50%,-50%) scale(0);visibility:hidden}.header,body{display:grid}.button:not(:active)::before{-webkit-animation:scale-up .3s ease-out 0s;animation:scale-up .3s ease-out 0s}.button:focus::before{visibility:visible}html{-webkit-box-sizing:border-box;box-sizing:border-box;height:100%;overflow:auto;margin:0 auto;padding:1rem;font-size:16px}html ::-webkit-scrollbar{background:rgba(33,33,33,.1);border-radius:4px;height:10px;width:10px}html ::-webkit-scrollbar-thumb{background:rgba(33,33,33,.3)}html *{outline:0;text-decoration:none}html ol{margin:0;padding:0;list-style:none}@media (min-width:300px) and (max-width:767px){html{font-size:calc(.8rem + ((1vw - .2em)/ 1.37142857));cursor:pointer}}@media (min-width:768px) and (max-width:1023px){html{font-size:1rem;cursor:pointer}}@media (min-width:1024px){html{font-size:1rem;max-width:1024px}}body{margin:0;min-height:100%;grid-template-columns:minmax(10%,-webkit-max-content) auto minmax(10%,-webkit-max-content);grid-template-columns:minmax(10%,max-content) auto minmax(10%,max-content);grid-template-rows:-webkit-max-content auto minmax(10%,-webkit-max-content);grid-template-rows:max-content auto minmax(10%,max-content);grid-gap:1em;grid-template-areas:"header header header" "container container container" "footer footer footer"}.header{grid-area:header;grid-template-columns:-webkit-max-content auto -webkit-max-content;grid-template-columns:max-content auto max-content;grid-template-areas:"logo . navbar";padding-bottom:1rem;margin:2rem 0}@media (max-width:1024px){.header{margin:.5rem 0}}.post,.post-year{margin-bottom:1.5rem}.logo{grid-area:logo;text-transform:uppercase;font-family:Quicksand,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";font-size:1rem;letter-spacing:1.1px;cursor:pointer;padding:.25rem .5rem;border-left:8px solid rgba(33,33,33,.75)}@media (max-width:1024px){.logo{font-size:1.2rem;padding:0;border-left:0 solid rgba(33,33,33,.75)}}@media (min-width:1024px){.navbar-wrapper{grid-area:navbar}.navbarmenu{display:none}.navbar{font-family:Quicksand,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";font-size:.8rem;text-transform:uppercase;letter-spacing:1.1px;place-self:center}.navbar ol{display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex}.navitem{color:#212121;padding:.4rem 1rem}.navitem:hover{color:#fff;background-color:rgba(33,33,33,.95);border-radius:2px;-webkit-transition:opacity .3s cubic-bezier(.694,0,.335,1),background-color .3s cubic-bezier(.694,0,.335,1),color .3s cubic-bezier(.694,0,.335,1);transition:opacity .3s cubic-bezier(.694,0,.335,1),background-color .3s cubic-bezier(.694,0,.335,1),color .3s cubic-bezier(.694,0,.335,1)}}@media (max-width:1024px){.navbar-wrapper{grid-area:navbar;display:-webkit-box;display:-ms-flexbox;display:flex}.navbarmenu{display:initial;padding:.5rem 1rem;cursor:pointer}.navbar{font-family:Quicksand,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";font-size:.8rem;text-transform:uppercase;letter-spacing:1.1px;display:none}.navbar ol{display:inline-table}.navbar ol li{color:#fff;padding:.5rem}.navitem{color:#212121;padding:.5rem 1rem}.navitem:focus,.navitem:hover{color:#fff;background-color:rgba(33,33,33,.95);border-radius:2px;-webkit-transition:opacity .3s cubic-bezier(.694,0,.335,1),background-color .3s cubic-bezier(.694,0,.335,1),color .3s cubic-bezier(.694,0,.335,1);transition:opacity .3s cubic-bezier(.694,0,.335,1),background-color .3s cubic-bezier(.694,0,.335,1),color .3s cubic-bezier(.694,0,.335,1)}.post-content img{overflow-x:auto;max-width:90vw}}.footer,.post-year{color:#212121;font-family:Quicksand,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol"}.footer{grid-area:footer;font-size:1.2rem}.footer .emoji{font-size:2.4rem}.container{grid-area:container}.post-year{letter-spacing:1.2px;font-size:1rem}.post-year span{background-color:rgba(33,33,33,.95);color:#fff;padding:.2em .5em}.post-title,.post-title-single{font-family:Quicksand,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";font-size:1.5rem;font-weight:400;color:#212121}.post-metadata{display:grid;grid-template-columns:-webkit-max-content auto;grid-template-columns:max-content auto;grid-template-areas:"post-date post-tags";margin-top:.3rem;font-family:Lato,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";font-size:.8rem;color:#212121}.post-date{grid-area:post-date;margin-right:.5rem}.post-tags{grid-area:post-tags}.post-tags .post-tag{font-size:.72rem;padding:.2rem .4rem;margin:0 .3rem;border-radius:2px;color:#212121;background-color:rgba(33,33,33,.2)}@media (min-width:300px) and (max-width:767px){.post-metadata{display:grid;grid-template-columns:auto;grid-template-rows:-webkit-max-content auto;grid-template-rows:max-content auto;grid-row-gap:3px;grid-template-areas:"post-date" "post-tags"}.post-tags .post-tag{font-size:.8rem;padding:.1rem .2rem;margin:0 .1rem;border-radius:2px;color:#212121;background-color:rgba(33,33,33,.2)}.post-content{max-width:90vw}}.post-content{margin-top:2rem;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";font-size:1rem;line-height:1.35rem;color:#212121}.post-content code,.post-content pre code{font-size:.8rem;font-family:Consolas,"Andale Mono WT","Andale Mono","Lucida Console","Lucida Sans Typewriter","DejaVu Sans Mono","Bitstream Vera Sans Mono","Liberation Mono","Nimbus Mono L",Monaco,"Courier New",Courier,monospace;color:#212121}.post-content img{margin:0 auto;display:-webkit-box;display:-ms-flexbox;display:flex;cursor:pointer}.post-content pre{border:1px solid rgba(33,33,33,.3);border-radius:3px;padding:.5rem;background-color:rgba(33,33,33,.0625);color:#212121;overflow:auto;max-width:100%;overflow-wrap:normal}.post-content pre code{background-color:unset;padding:0;overflow:auto}.post-content code{padding:3px 5px;background-color:rgba(144,238,144,.15);border-radius:3px;place-content:center}.post-content a{color:#212121;text-decoration:underline;text-underline-position:auto;padding:0 2px}.post-content a:focus,.post-content a:hover{color:#fff;text-decoration:none;background-color:rgba(33,33,33,.95);border-radius:2px;-webkit-transition:opacity .3s cubic-bezier(.694,0,.335,1),background-color .3s cubic-bezier(.694,0,.335,1),color .3s cubic-bezier(.694,0,.335,1);transition:opacity .3s cubic-bezier(.694,0,.335,1),background-color .3s cubic-bezier(.694,0,.335,1),color .3s cubic-bezier(.694,0,.335,1)}.post-content p{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";margin:.5rem 0}.post-content h1,.post-content h2,.post-content h3,.post-content h4,.post-content h5,.post-content h6,.post-content strong{font-family:Quicksand,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";margin:1rem 0}.post-content h1,.post-content h2,.post-content h3,.post-content h4,.post-content h5,.post-content h6{font-weight:400;padding:0 .5rem;border-left:5px solid rgba(33,33,33,.95);color:#212121}.post-content h1{font-size:1.44rem}.post-content h2{font-size:1.38rem}.post-content h3{font-size:1.32rem}.post-content h4{font-size:1.26rem}.post-content strong{padding:3px .5rem;background-color:rgba(33,33,33,.85);border-radius:3px;color:#fff} -------------------------------------------------------------------------------- /app/static/public/twitter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snehesht/blog/e579fce1d4f4a17fb6c9c7adfa3e05bbdc24aae6/app/static/public/twitter.png -------------------------------------------------------------------------------- /app/static/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | 4 | SITEMAP: https://snehesh.me/sitemap.xml 5 | -------------------------------------------------------------------------------- /app/static/sass/backup.scss.bak: -------------------------------------------------------------------------------- 1 | @import "normalize"; 2 | @import "fonts"; 3 | @import "theme"; 4 | @import "button"; 5 | 6 | html { 7 | box-sizing: border-box; 8 | height: 100%; 9 | overflow: auto; 10 | 11 | ::-webkit-scrollbar { 12 | background: rgba($primary, 0.1); 13 | border-radius: 4px; 14 | height: 10px; 15 | width: 10px; 16 | } 17 | 18 | ::-webkit-scrollbar-thumb { 19 | background:rgba($primary, 0.3); 20 | } 21 | 22 | a { 23 | outline: 0; 24 | text-decoration: none; 25 | } 26 | } 27 | 28 | body { 29 | color: $primary; 30 | font-family: $fontFamily; 31 | font-size: $regularSize; 32 | display: grid; 33 | // min-height: 100%; 34 | max-width: 1440px; 35 | margin: 0 auto; 36 | } 37 | 38 | // CSS Grid 39 | .wrapper { 40 | display: grid; 41 | grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); 42 | grid-template-rows: max-content minmax(85vh, max-content) max-content; 43 | } 44 | .header { 45 | grid-row: 1; 46 | // grid-column: span 13; 47 | margin: 2 * $spaceUnit; 48 | }; 49 | 50 | .container { 51 | grid-row: 2; 52 | grid-column: span 10; 53 | padding: 5 * $spaceUnit 0px; 54 | }; 55 | 56 | .footer { 57 | grid-row: 3; 58 | grid-column: 1/13; 59 | background-color: rgba($white, 0.1); 60 | text-align: center; 61 | .emoji { 62 | font-size: 2 * $bigSize; 63 | } 64 | .text { 65 | font-size: $regularSize; 66 | } 67 | }; 68 | 69 | @keyframes scale-up { 70 | from { 71 | opacity: 0; 72 | } 73 | to { 74 | opacity: 1; 75 | } 76 | } 77 | .navbar { 78 | display: grid; 79 | grid-template-columns: max-content auto max-content; 80 | grid-auto-rows: 100%; 81 | } 82 | 83 | .logo { 84 | padding: 0.5 * $spaceUnit $spaceUnit; 85 | margin: auto; 86 | grid-column: 1; 87 | font-family: Lato, $fontFamily; 88 | font-size: $regularSize; 89 | text-transform: uppercase; 90 | letter-spacing: 1.5px; 91 | color: $primary; 92 | border-left: 5px solid $primary; 93 | cursor: pointer; 94 | &:hover, &:focus, &:active { 95 | display: block; 96 | background-image: linear-gradient(to left, rgba($secondary, 0.1) 50%, rgba($secondary, 0.1) 0%, transparent 50%, transparent); 97 | background-position: 100% 0; 98 | background-size: 200% 100%; 99 | transition: all .25s ease-in-out; 100 | border-left: 5px solid rgba($secondary, 0.6); 101 | } 102 | } 103 | .nav { 104 | grid-column: 3; 105 | float: right; 106 | ol { 107 | display: inline-flex; 108 | list-style: none; 109 | } 110 | } 111 | 112 | .navButton { 113 | font-size: $smallSize; 114 | font-family: Lato, $fontFamily; 115 | text-transform: uppercase; 116 | text-decoration: none; 117 | color: rgba($primary, 0.8); 118 | border: 1px solid $white; 119 | border-radius: 2px; 120 | padding: 0.25*$spaceUnit 0.8*$spaceUnit; 121 | margin: 0 $spaceUnit; 122 | letter-spacing: 1.2px; 123 | 124 | &:hover, &:focus, &:active { 125 | border: 1px solid $primary; 126 | background-color: $primary; 127 | color: $white; 128 | animation: 200ms ease-in 0s; 129 | box-shadow: 0 10px 20px rgba(0,0,0,0.19), 0 6px 6px rgba(0,0,0,0.23); 130 | } 131 | } 132 | 133 | .post { 134 | margin: 2 * $spaceUnit 0; 135 | } 136 | 137 | .post-year { 138 | // width: 6.5vw;/* */ 139 | padding: 2 * $spaceUnit 0; 140 | span { 141 | margin: 0.5*$spaceUnit 0.3* $spaceUnit; 142 | font-size: 0.8 * $bigSize; 143 | color: $primary; 144 | border-left: 5px solid rgba($secondary, 0.6); 145 | background-color: rgba($primary, 0.1); 146 | padding: 0.2 * $spaceUnit $spaceUnit; 147 | } 148 | } 149 | 150 | .post-title { 151 | a { 152 | font-family: Quicksand, $fontFamily; 153 | color: $primary; 154 | text-decoration: none; 155 | padding: 0.1*$spaceUnit 0.3*$spaceUnit; 156 | font-size: $bigSize; 157 | position: relative; 158 | // Underline animation 159 | // &:before { 160 | // content: ""; 161 | // position: absolute; 162 | // width: 100%; 163 | // height: 2px; 164 | // bottom: 0; 165 | // left: 0; 166 | // background-color: rgba($secondary, 0.5); 167 | // visibility: hidden; 168 | // -webkit-transform: scaleX(0); 169 | // transform: scaleX(0); 170 | // -webkit-transition: all 0.3s ease-in-out 0s; 171 | // transition: all 0.3s ease-in-out 0s; 172 | // } 173 | // &:hover:before { 174 | // visibility: visible; 175 | // -webkit-transform: scaleX(1); 176 | // transform: scaleX(1); 177 | // } 178 | // &:hover { 179 | // animation: 100ms ease-in 0s; 180 | // // background-color: rgba($secondary, 0.1); 181 | // } 182 | } 183 | } 184 | 185 | .post-metadata { 186 | margin: 0.5*$spaceUnit 0* $spaceUnit; 187 | font-family: Lato, $fontFamily; 188 | 189 | ol { 190 | list-style: none; 191 | display: inline-flex; 192 | margin: 0 auto; 193 | padding: 0; 194 | li { 195 | padding: 0px 0.2*$spaceUnit; 196 | } 197 | } 198 | .date { 199 | font-size: 0.8 * $regularSize; 200 | color: $primary; 201 | background-color: rgba($primary, 0.1); 202 | padding: 0.15*$spaceUnit 0.3* $spaceUnit; 203 | } 204 | 205 | .tags { 206 | margin: 0px 0.4 * $spaceUnit; 207 | a { 208 | color: $primary; 209 | text-decoration: none; 210 | padding: 3px 5px; 211 | font-size: 0.7 * $regularSize; 212 | letter-spacing: 1.1px; 213 | color: $primary; 214 | background-color: rgba($secondary, 0.15); 215 | text-transform: uppercase; 216 | margin: 0.15 * $spaceUnit; 217 | &:hover { 218 | // box-shadow: 0 8px 15px rgba(0,0,0,0.19), 0 4px 4px rgba(0,0,0,0.23); 219 | animation: 200ms ease-in 0s; 220 | } 221 | } 222 | } 223 | } 224 | 225 | .post-content { 226 | margin: 3*$spaceUnit 0.3* $spaceUnit; 227 | line-height: 1.5 * $regularSize; 228 | a { 229 | color: $primary; 230 | text-decoration: none; 231 | color: rgba($secondary, 1); 232 | position: relative; 233 | &:before { 234 | content: ""; 235 | position: absolute; 236 | width: 100%; 237 | height: 2px; 238 | bottom: 0; 239 | left: 0; 240 | background-color: rgba($secondary, 0.7); 241 | visibility: hidden; 242 | -webkit-transform: scaleX(0); 243 | transform: scaleX(0); 244 | -webkit-transition: all 0.3s ease-in-out 0s; 245 | transition: all 0.3s ease-in-out 0s; 246 | } 247 | &:hover:before { 248 | visibility: visible; 249 | -webkit-transform: scaleX(1); 250 | transform: scaleX(1); 251 | } 252 | &:hover { 253 | animation: 100ms ease-in 0s; 254 | // background-color: rgba($secondary, 0.1); 255 | } 256 | } 257 | 258 | img { 259 | display: flex; 260 | margin: 0 auto; 261 | padding: 2 * $spaceUnit 0; 262 | &:after { 263 | content: attr(alt); 264 | } 265 | } 266 | 267 | pre { 268 | margin: $spaceUnit 0; 269 | border: 1px solid rgba($primary, 0.1); 270 | border-radius: 2px; 271 | background-color: rgba($primary, 0.05); 272 | padding: $spaceUnit; 273 | overflow: auto; 274 | 275 | code { 276 | color: $primary; 277 | background-color: rgba($primary, 0); 278 | padding: 0.1 * $spaceUnit 0 * $spaceUnit; 279 | } 280 | } 281 | 282 | code { 283 | color: $red; 284 | background-color: rgba($red, 0.05); 285 | padding: 0.1 * $spaceUnit 0.5 * $spaceUnit; 286 | } 287 | } 288 | 289 | .aboutme { 290 | .profile { 291 | .profile-img { 292 | text-align: center; 293 | 294 | img { 295 | border-radius: 2px; 296 | vertical-align: center; 297 | box-shadow: 0 18px 30px rgba(0,0,0,0.19), 0 4px 4px rgba(0,0,0,0.23); 298 | } 299 | } 300 | 301 | .accounts { 302 | display: inline-flex; 303 | grid-auto-columns: auto; 304 | background-color: $lightGreen; 305 | padding: $spaceUnit; 306 | width: 100%; 307 | justify-content: center; 308 | .account { 309 | margin: $spaceUnit; 310 | 311 | a { 312 | font-size: $regularSize; 313 | text-decoration: none; 314 | color: $white; 315 | display: flex; 316 | background-color: rgba($primary, 1); 317 | border-radius: 2px; 318 | padding: 0.3 * $spaceUnit; 319 | &:hover, &:focus { 320 | box-shadow: 0 18px 30px rgba(0,0,0,0.19), 0 4px 4px rgba(0,0,0,0.23); 321 | } 322 | img { 323 | width: 2 * $regularSize; 324 | height: 2 * $regularSize; 325 | padding: 6px; 326 | margin: 0px; 327 | 328 | } 329 | i { 330 | font-size: $bigSize; 331 | color: $white; 332 | background-color: rgba($primary, 0.5); 333 | padding: 2px 10px 2px 5px; 334 | border-right: 1px solid $white; 335 | } 336 | span { 337 | font-size: $regularSize; 338 | padding: 0px 10px; 339 | margin: auto; 340 | line-height: $bigSize; 341 | text-transform: uppercase; 342 | letter-spacing: 1.2px; 343 | } 344 | } 345 | } 346 | } 347 | } 348 | .bio { 349 | line-height: 1.5* $regularSize; 350 | color: $primary; 351 | a { 352 | text-decoration: none; 353 | color: rgba($secondary, 1); 354 | } 355 | } 356 | } -------------------------------------------------------------------------------- /app/static/sass/button.scss: -------------------------------------------------------------------------------- 1 | @import "theme"; 2 | 3 | // ----- Variables ----- 4 | $btn-color-dark: $primary; 5 | $btn-color: $primary; 6 | $ripple-color: rgba(0, 0, 0, 0.4) !default; 7 | 8 | $animation-duration: 0.3s !default; 9 | $animation-duration-large: $animation-duration*1.5; 10 | $animation-duration-small: $animation-duration/1.5; 11 | 12 | // ----- Mixins ----- 13 | @mixin ripple-element() { 14 | content: ""; 15 | display: block; 16 | width: 100%; 17 | height: auto; 18 | padding-bottom: 100%; 19 | position: absolute; 20 | top: 50%; 21 | left: 50%; 22 | z-index: 30; 23 | background: $ripple-color; 24 | border-radius: 50%; 25 | transform: translate(-50%, -50%) scale(0); // Initial state, should hide the effect when not animating 26 | visibility: hidden; // Hides element so it doesnt animate on page load 27 | } 28 | 29 | @mixin ripple-element-active() { 30 | visibility: visible; // Shows element when it is focused 31 | } 32 | 33 | // ----- Animation ----- 34 | @keyframes scale-up { 35 | from { 36 | opacity: 1; 37 | transform: translate(-50%, -50%) scale(0); 38 | } 39 | 40 | to { 41 | opacity: 0; 42 | transform: translate(-50%, -50%) scale(1); 43 | } 44 | } 45 | 46 | // ----- Button ripple ----- 47 | .button { 48 | color: #fff; 49 | text-align: center; 50 | background-color: $primary; 51 | border-radius: 4px; 52 | transition: background-color; 53 | transition-duration: $animation-duration; 54 | overflow: hidden; 55 | 56 | &:hover, &:focus { 57 | color: #fff; 58 | background-color: $primary; 59 | border: 1px solid $primary; 60 | } 61 | 62 | 63 | &::before { 64 | @include ripple-element(); 65 | } 66 | 67 | &:not(:active)::before { 68 | animation: scale-up $animation-duration ease-out 0s; 69 | } 70 | 71 | &:focus::before { 72 | @include ripple-element-active(); 73 | } 74 | } -------------------------------------------------------------------------------- /app/static/sass/fonts.scss: -------------------------------------------------------------------------------- 1 | /* latin */ 2 | @font-face { 3 | font-family: 'Lato'; 4 | font-style: normal; 5 | font-weight: 400; 6 | src: local('Lato Regular'), local('Lato-Regular'), url(lato.woff2) format('woff2'); 7 | unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; 8 | } 9 | 10 | /* latin */ 11 | @font-face { 12 | font-family: 'Quicksand'; 13 | font-style: normal; 14 | font-weight: 500; 15 | src: local('Quicksand Medium'), local('Quicksand-Medium'), url(quicksand.woff2) format('woff2'); 16 | unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; 17 | } 18 | -------------------------------------------------------------------------------- /app/static/sass/media-queries.scss: -------------------------------------------------------------------------------- 1 | $phone : '(max-width: 480px)'; 2 | $tablet-portrait: '(max-width: 767px)'; 3 | $tablet-landscape: '(min-width: 768px) and (max-width: 979px)'; 4 | $large-desktop: '(min-width: 1200px)'; 5 | $non-retina: 'screen and (-webkit-max-device-pixel-ratio: 1)'; 6 | $retina: '(min--moz-device-pixel-ratio: 1.5), 7 | (-o-min-device-pixel-ratio: 3/2), 8 | (-webkit-min-device-pixel-ratio: 1.5), 9 | (min-device-pixel-ratio: 1.5), 10 | (min-resolution: 144dpi), 11 | (min-resolution: 1.5dppx)'; 12 | 13 | @mixin size($media) { 14 | @media only screen and #{$media} { 15 | @content; 16 | } 17 | }; -------------------------------------------------------------------------------- /app/static/sass/normalize.scss: -------------------------------------------------------------------------------- 1 | /*! normalize.css v8.0.0 | MIT License | github.com/necolas/normalize.css */ 2 | 3 | /* Document 4 | ========================================================================== */ 5 | 6 | /** 7 | * 1. Correct the line height in all browsers. 8 | * 2. Prevent adjustments of font size after orientation changes in iOS. 9 | */ 10 | 11 | html { 12 | line-height: 1.15; /* 1 */ 13 | -webkit-text-size-adjust: 100%; /* 2 */ 14 | } 15 | 16 | /* Sections 17 | ========================================================================== */ 18 | 19 | /** 20 | * Remove the margin in all browsers. 21 | */ 22 | 23 | body { 24 | margin: 0; 25 | } 26 | 27 | /** 28 | * Correct the font size and margin on `h1` elements within `section` and 29 | * `article` contexts in Chrome, Firefox, and Safari. 30 | */ 31 | 32 | h1 { 33 | font-size: 2em; 34 | margin: 0.67em 0; 35 | } 36 | 37 | /* Grouping content 38 | ========================================================================== */ 39 | 40 | /** 41 | * 1. Add the correct box sizing in Firefox. 42 | * 2. Show the overflow in Edge and IE. 43 | */ 44 | 45 | hr { 46 | box-sizing: content-box; /* 1 */ 47 | height: 0; /* 1 */ 48 | overflow: visible; /* 2 */ 49 | } 50 | 51 | /** 52 | * 1. Correct the inheritance and scaling of font size in all browsers. 53 | * 2. Correct the odd `em` font sizing in all browsers. 54 | */ 55 | 56 | pre { 57 | font-family: monospace, monospace; /* 1 */ 58 | font-size: 1em; /* 2 */ 59 | } 60 | 61 | /* Text-level semantics 62 | ========================================================================== */ 63 | 64 | /** 65 | * Remove the gray background on active links in IE 10. 66 | */ 67 | 68 | a { 69 | background-color: transparent; 70 | } 71 | 72 | /** 73 | * 1. Remove the bottom border in Chrome 57- 74 | * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari. 75 | */ 76 | 77 | abbr[title] { 78 | border-bottom: none; /* 1 */ 79 | text-decoration: underline; /* 2 */ 80 | text-decoration: underline dotted; /* 2 */ 81 | } 82 | 83 | /** 84 | * Add the correct font weight in Chrome, Edge, and Safari. 85 | */ 86 | 87 | b, 88 | strong { 89 | font-weight: bolder; 90 | } 91 | 92 | /** 93 | * 1. Correct the inheritance and scaling of font size in all browsers. 94 | * 2. Correct the odd `em` font sizing in all browsers. 95 | */ 96 | 97 | code, 98 | kbd, 99 | samp { 100 | font-family: monospace, monospace; /* 1 */ 101 | font-size: 1em; /* 2 */ 102 | } 103 | 104 | /** 105 | * Add the correct font size in all browsers. 106 | */ 107 | 108 | small { 109 | font-size: 80%; 110 | } 111 | 112 | /** 113 | * Prevent `sub` and `sup` elements from affecting the line height in 114 | * all browsers. 115 | */ 116 | 117 | sub, 118 | sup { 119 | font-size: 75%; 120 | line-height: 0; 121 | position: relative; 122 | vertical-align: baseline; 123 | } 124 | 125 | sub { 126 | bottom: -0.25em; 127 | } 128 | 129 | sup { 130 | top: -0.5em; 131 | } 132 | 133 | /* Embedded content 134 | ========================================================================== */ 135 | 136 | /** 137 | * Remove the border on images inside links in IE 10. 138 | */ 139 | 140 | img { 141 | border-style: none; 142 | } 143 | 144 | /* Forms 145 | ========================================================================== */ 146 | 147 | /** 148 | * 1. Change the font styles in all browsers. 149 | * 2. Remove the margin in Firefox and Safari. 150 | */ 151 | 152 | button, 153 | input, 154 | optgroup, 155 | select, 156 | textarea { 157 | font-family: inherit; /* 1 */ 158 | font-size: 100%; /* 1 */ 159 | line-height: 1.15; /* 1 */ 160 | margin: 0; /* 2 */ 161 | } 162 | 163 | /** 164 | * Show the overflow in IE. 165 | * 1. Show the overflow in Edge. 166 | */ 167 | 168 | button, 169 | input { /* 1 */ 170 | overflow: visible; 171 | } 172 | 173 | /** 174 | * Remove the inheritance of text transform in Edge, Firefox, and IE. 175 | * 1. Remove the inheritance of text transform in Firefox. 176 | */ 177 | 178 | button, 179 | select { /* 1 */ 180 | text-transform: none; 181 | } 182 | 183 | /** 184 | * Correct the inability to style clickable types in iOS and Safari. 185 | */ 186 | 187 | button, 188 | [type="button"], 189 | [type="reset"], 190 | [type="submit"] { 191 | -webkit-appearance: button; 192 | } 193 | 194 | /** 195 | * Remove the inner border and padding in Firefox. 196 | */ 197 | 198 | button::-moz-focus-inner, 199 | [type="button"]::-moz-focus-inner, 200 | [type="reset"]::-moz-focus-inner, 201 | [type="submit"]::-moz-focus-inner { 202 | border-style: none; 203 | padding: 0; 204 | } 205 | 206 | /** 207 | * Restore the focus styles unset by the previous rule. 208 | */ 209 | 210 | button:-moz-focusring, 211 | [type="button"]:-moz-focusring, 212 | [type="reset"]:-moz-focusring, 213 | [type="submit"]:-moz-focusring { 214 | outline: 1px dotted ButtonText; 215 | } 216 | 217 | /** 218 | * Correct the padding in Firefox. 219 | */ 220 | 221 | fieldset { 222 | padding: 0.35em 0.75em 0.625em; 223 | } 224 | 225 | /** 226 | * 1. Correct the text wrapping in Edge and IE. 227 | * 2. Correct the color inheritance from `fieldset` elements in IE. 228 | * 3. Remove the padding so developers are not caught out when they zero out 229 | * `fieldset` elements in all browsers. 230 | */ 231 | 232 | legend { 233 | box-sizing: border-box; /* 1 */ 234 | color: inherit; /* 2 */ 235 | display: table; /* 1 */ 236 | max-width: 100%; /* 1 */ 237 | padding: 0; /* 3 */ 238 | white-space: normal; /* 1 */ 239 | } 240 | 241 | /** 242 | * Add the correct vertical alignment in Chrome, Firefox, and Opera. 243 | */ 244 | 245 | progress { 246 | vertical-align: baseline; 247 | } 248 | 249 | /** 250 | * Remove the default vertical scrollbar in IE 10+. 251 | */ 252 | 253 | textarea { 254 | overflow: auto; 255 | } 256 | 257 | /** 258 | * 1. Add the correct box sizing in IE 10. 259 | * 2. Remove the padding in IE 10. 260 | */ 261 | 262 | [type="checkbox"], 263 | [type="radio"] { 264 | box-sizing: border-box; /* 1 */ 265 | padding: 0; /* 2 */ 266 | } 267 | 268 | /** 269 | * Correct the cursor style of increment and decrement buttons in Chrome. 270 | */ 271 | 272 | [type="number"]::-webkit-inner-spin-button, 273 | [type="number"]::-webkit-outer-spin-button { 274 | height: auto; 275 | } 276 | 277 | /** 278 | * 1. Correct the odd appearance in Chrome and Safari. 279 | * 2. Correct the outline style in Safari. 280 | */ 281 | 282 | [type="search"] { 283 | -webkit-appearance: textfield; /* 1 */ 284 | outline-offset: -2px; /* 2 */ 285 | } 286 | 287 | /** 288 | * Remove the inner padding in Chrome and Safari on macOS. 289 | */ 290 | 291 | [type="search"]::-webkit-search-decoration { 292 | -webkit-appearance: none; 293 | } 294 | 295 | /** 296 | * 1. Correct the inability to style clickable types in iOS and Safari. 297 | * 2. Change font properties to `inherit` in Safari. 298 | */ 299 | 300 | ::-webkit-file-upload-button { 301 | -webkit-appearance: button; /* 1 */ 302 | font: inherit; /* 2 */ 303 | } 304 | 305 | /* Interactive 306 | ========================================================================== */ 307 | 308 | /* 309 | * Add the correct display in Edge, IE 10+, and Firefox. 310 | */ 311 | 312 | details { 313 | display: block; 314 | } 315 | 316 | /* 317 | * Add the correct display in all browsers. 318 | */ 319 | 320 | summary { 321 | display: list-item; 322 | } 323 | 324 | /* Misc 325 | ========================================================================== */ 326 | 327 | /** 328 | * Add the correct display in IE 10+. 329 | */ 330 | 331 | template { 332 | display: none; 333 | } 334 | 335 | /** 336 | * Add the correct display in IE 10. 337 | */ 338 | 339 | [hidden] { 340 | display: none; 341 | } -------------------------------------------------------------------------------- /app/static/sass/responsive.scss: -------------------------------------------------------------------------------- 1 | $mobile-width: 300px; 2 | $tablet-width: 768px; 3 | $desktop-width: 1024px; 4 | 5 | @mixin mobile { 6 | @media (min-width: #{$mobile-width}) and (max-width: #{$tablet-width - 1px}) { 7 | @content; 8 | } 9 | } 10 | 11 | @mixin tablet { 12 | @media (min-width: #{$tablet-width}) and (max-width: #{$desktop-width - 1px}) { 13 | @content; 14 | } 15 | } 16 | 17 | @mixin predesktop { 18 | @media (max-width: #{$desktop-width}) { 19 | @content; 20 | } 21 | } 22 | 23 | @mixin desktop { 24 | @media (min-width: #{$desktop-width}) { 25 | @content; 26 | } 27 | } -------------------------------------------------------------------------------- /app/static/sass/rfs.scss.bak: -------------------------------------------------------------------------------- 1 | // stylelint-disable declaration-property-value-blacklist 2 | 3 | // SCSS RFS mixin 4 | // 5 | // Automated font-resizing 6 | // 7 | // See https://github.com/MartijnCuppens/rfs 8 | 9 | // Configuration 10 | 11 | // Minimum font size 12 | $rfs-minimum-font-size: 1rem !default; 13 | $rfs-font-size-unit: rem !default; 14 | 15 | // Breakpoint at where font-size starts decreasing if screen width is smaller 16 | $rfs-breakpoint: 1200px !default; 17 | $rfs-breakpoint-unit: px !default; 18 | 19 | // Resize font-size based on screen height and width 20 | $rfs-two-dimensional: false !default; 21 | 22 | // Factor of decrease 23 | $rfs-factor: 5 !default; 24 | 25 | // Generate enable or disable classes. Possibilities: false, "enable" or "disable" 26 | $rfs-class: false !default; 27 | 28 | // 1 rem = $rfs-rem-value px 29 | $rfs-rem-value: 16 !default; 30 | 31 | // Disable RFS by setting $enable-responsive-font-sizes to false 32 | $enable-responsive-font-sizes: true !default; 33 | 34 | @if $enable-responsive-font-sizes == false { 35 | // If $rfs-factor is set to 1, fluid font-resizing is disabled 36 | $rfs-factor: 1; 37 | } 38 | 39 | // Remove px-unit from $rfs-minimum-font-size for calculations 40 | @if unit($rfs-minimum-font-size) == "px" { 41 | $rfs-minimum-font-size: $rfs-minimum-font-size / ($rfs-minimum-font-size * 0 + 1); 42 | } 43 | @else if unit($rfs-minimum-font-size) == "rem" { 44 | $rfs-minimum-font-size: $rfs-minimum-font-size / ($rfs-minimum-font-size * 0 + 1 / $rfs-rem-value); 45 | } 46 | 47 | // Remove unit from $rfs-breakpoint for calculations 48 | @if unit($rfs-breakpoint) == "px" { 49 | $rfs-breakpoint: $rfs-breakpoint / ($rfs-breakpoint * 0 + 1); 50 | } 51 | @else if unit($rfs-breakpoint) == "rem" or unit($rfs-breakpoint) == "em" { 52 | $rfs-breakpoint: $rfs-breakpoint / ($rfs-breakpoint * 0 + 1 / $rfs-rem-value); 53 | } 54 | 55 | // Responsive font-size mixin 56 | @mixin rfs($fs, $important: false) { 57 | $rfs-suffix: ""; 58 | 59 | // Add !important suffix if needed 60 | @if $important { 61 | $rfs-suffix: " !important"; 62 | } 63 | 64 | // If $fs isn't a number (like inherit) or $fs has a unit (not px or rem, like 1.5em) or $ is 0, just print the value 65 | @if type-of($fs) != "number" or not unitless($fs) and unit($fs) != "px" and unit($fs) != "rem" or $fs == 0 { 66 | font-size: #{$fs}#{$rfs-suffix}; 67 | } 68 | @else { 69 | // Variables for storing static and fluid rescaling 70 | $rfs-static: null; 71 | $rfs-fluid: null; 72 | 73 | // Remove px-unit from $fs for calculations 74 | @if unit($fs) == "px" { 75 | $fs: $fs / ($fs * 0 + 1); 76 | } 77 | @else if unit($fs) == "rem" { 78 | $fs: $fs / ($fs * 0 + 1 / $rfs-rem-value); 79 | } 80 | 81 | // Set default font-size 82 | @if $rfs-font-size-unit == rem { 83 | $rfs-static: #{$fs / $rfs-rem-value}rem#{$rfs-suffix}; 84 | } 85 | @else if $rfs-font-size-unit == px { 86 | $rfs-static: #{$fs}px#{$rfs-suffix}; 87 | } 88 | @else { 89 | @error "`#{$rfs-font-size-unit}` is not a valid unit for $rfs-font-size-unit. Use `px` or `rem`."; 90 | } 91 | 92 | @if type-of($rfs-factor) != "number" or $rfs-factor < 1 { 93 | @error "`#{$rfs-factor}` is not a valid $rfs-factor, it must be greater or equal to 1."; 94 | } 95 | 96 | // Only add media query if font-size is bigger as the minimum font-size 97 | // If $rfs-factor == 1, no rescaling will take place 98 | @if $fs > $rfs-minimum-font-size and $rfs-factor != 1 { 99 | $min-width: null; 100 | $variable-unit: null; 101 | 102 | // Calculate minimum font-size for given font-size 103 | $fs-min: $rfs-minimum-font-size + ($fs - $rfs-minimum-font-size) / $rfs-factor; 104 | 105 | // Calculate difference between given font-size and minimum font-size for given font-size 106 | $fs-diff: $fs - $fs-min; 107 | 108 | // Minimum font-size formatting 109 | // No need to check if the unit is valid, because we did that before 110 | @if $rfs-font-size-unit == rem { 111 | $min-width: #{$fs-min / $rfs-rem-value}rem; 112 | } 113 | @else { 114 | $min-width: #{$fs-min}px; 115 | } 116 | 117 | // If two-dimensional, use smallest of screen width and height 118 | @if $rfs-two-dimensional { 119 | $variable-unit: vmin; 120 | } 121 | @else { 122 | $variable-unit: vw; 123 | } 124 | 125 | // Calculate the variable width between 0 and $rfs-breakpoint 126 | $variable-width: #{$fs-diff * 100 / $rfs-breakpoint}#{$variable-unit}; 127 | 128 | // Set the calculated font-size. 129 | $rfs-fluid: calc(#{$min-width} + #{$variable-width}) #{$rfs-suffix}; 130 | } 131 | 132 | // Rendering 133 | @if $rfs-fluid == null { 134 | // Only render static font-size if no fluid font-size is available 135 | font-size: $rfs-static; 136 | } 137 | @else { 138 | $mq-value: null; 139 | 140 | // RFS breakpoint formatting 141 | @if $rfs-breakpoint-unit == em or $rfs-breakpoint-unit == rem { 142 | $mq-value: #{$rfs-breakpoint / $rfs-rem-value}#{$rfs-breakpoint-unit}; 143 | } 144 | @else if $rfs-breakpoint-unit == px { 145 | $mq-value: #{$rfs-breakpoint}px; 146 | } 147 | @else { 148 | @error "`#{$rfs-breakpoint-unit}` is not a valid unit for $rfs-breakpoint-unit. Use `px`, `em` or `rem`."; 149 | } 150 | 151 | @if $rfs-class == "disable" { 152 | // Adding an extra class increases specificity, 153 | // which prevents the media query to override the font size 154 | &, 155 | .disable-responsive-font-size &, 156 | &.disable-responsive-font-size { 157 | font-size: $rfs-static; 158 | } 159 | } 160 | @else { 161 | font-size: $rfs-static; 162 | } 163 | 164 | @if $rfs-two-dimensional { 165 | @media (max-width: #{$mq-value}), (max-height: #{$mq-value}) { 166 | @if $rfs-class == "enable" { 167 | .enable-responsive-font-size &, 168 | &.enable-responsive-font-size { 169 | font-size: $rfs-fluid; 170 | } 171 | } 172 | @else { 173 | font-size: $rfs-fluid; 174 | } 175 | } 176 | } 177 | @else { 178 | @media (max-width: #{$mq-value}) { 179 | @if $rfs-class == "enable" { 180 | .enable-responsive-font-size &, 181 | &.enable-responsive-font-size { 182 | font-size: $rfs-fluid; 183 | } 184 | } 185 | @else { 186 | font-size: $rfs-fluid; 187 | } 188 | } 189 | } 190 | } 191 | } 192 | } 193 | 194 | // The responsive-font-size mixin uses RFS to rescale font sizes 195 | @mixin responsive-font-size($fs, $important: false) { 196 | @include rfs($fs, $important); 197 | } -------------------------------------------------------------------------------- /app/static/sass/styles.scss: -------------------------------------------------------------------------------- 1 | @import "normalize"; 2 | @import "fonts"; 3 | @import "theme"; 4 | @import "button"; 5 | @import "media-queries"; 6 | @import "responsive"; 7 | 8 | html { 9 | box-sizing: border-box; 10 | height: 100%; 11 | overflow: auto; 12 | margin: 0 auto; 13 | max-width: 100%; 14 | padding: $spaceUnit; 15 | 16 | ::-webkit-scrollbar { 17 | background: rgba($primary, 0.1); 18 | border-radius: 4px; 19 | height: 10px; 20 | width: 10px; 21 | } 22 | 23 | ::-webkit-scrollbar-thumb { 24 | background:rgba($primary, 0.3); 25 | } 26 | 27 | * { 28 | outline: 0; 29 | text-decoration: none; 30 | } 31 | 32 | ol { 33 | margin: 0; 34 | padding: 0; 35 | list-style: none; 36 | } 37 | 38 | font-size: 16px; 39 | 40 | @include mobile { 41 | font-size: calc(0.8rem + ((1vw - .2em) / 1.37142857)); 42 | cursor: pointer; 43 | } 44 | 45 | @include tablet { 46 | font-size: $baseSize; 47 | cursor: pointer; 48 | } 49 | 50 | @include desktop { 51 | font-size: $baseSize; 52 | max-width: 1024px; 53 | } 54 | 55 | } 56 | 57 | body { 58 | display: grid; 59 | min-height: 100%; 60 | grid-template-columns: minmax(10%, max-content) auto minmax(10%, max-content); 61 | grid-template-rows: max-content auto minmax(10%, max-content); 62 | grid-gap: 1em; 63 | grid-template-areas: 64 | "header header header" 65 | "container container container" 66 | "footer footer footer"; 67 | 68 | } 69 | 70 | // Header 71 | .header { 72 | grid-area: header; 73 | display: grid; 74 | grid-template-columns: max-content auto max-content; 75 | grid-template-areas: 76 | "logo . navbar"; 77 | padding-bottom: $spaceUnit; 78 | margin: 2 * $spaceUnit 0em; 79 | @include predesktop { 80 | margin: 0.5* $spaceUnit 0em; 81 | } 82 | } 83 | 84 | .logo { 85 | grid-area: logo; 86 | text-transform: uppercase; 87 | font-family: $headerFont; 88 | font-size: $baseSize; 89 | letter-spacing: 1.1px; 90 | cursor: pointer; 91 | padding: 0.25 * $spaceUnit 0.5 * $spaceUnit; 92 | border-left: 8px solid rgba($primary, 0.75); 93 | @include predesktop { 94 | font-size: $regularSize; 95 | padding: 0 * $spaceUnit 0 * $spaceUnit; 96 | border-left: 0px solid rgba($primary, 0.75); 97 | } 98 | 99 | } 100 | 101 | // Navigation 102 | @include desktop { 103 | .navbar-wrapper { 104 | grid-area: navbar; 105 | } 106 | .navbarmenu { 107 | display: none; 108 | } 109 | 110 | .navbar { 111 | font-family: $headerFont; 112 | font-size: $smallSize; 113 | text-transform: uppercase; 114 | letter-spacing: 1.1px; 115 | place-self: center; 116 | ol { 117 | display: inline-flex; 118 | li {} 119 | } 120 | } 121 | 122 | .navitem { 123 | color: $black; 124 | padding: 0.4 * $spaceUnit 1 * $spaceUnit; 125 | &:hover { 126 | color: white; 127 | background-color: rgba($primary, 0.95); 128 | border-radius: 2px; 129 | transition: opacity 300ms cubic-bezier(.694,0,.335,1), 130 | background-color 300ms cubic-bezier(.694,0,.335,1), 131 | color 300ms cubic-bezier(.694,0,.335,1); 132 | } 133 | } 134 | } 135 | 136 | @include predesktop { 137 | .navbar-wrapper { 138 | grid-area: navbar; 139 | display: flex; 140 | } 141 | .navbarmenu { 142 | display: initial; 143 | padding: 0.5 * $spaceUnit $spaceUnit; 144 | cursor: pointer; 145 | } 146 | .navbar { 147 | font-family: $headerFont; 148 | font-size: $smallSize; 149 | text-transform: uppercase; 150 | letter-spacing: 1.1px; 151 | display: none; 152 | ol { 153 | display: inline-table; 154 | // border: 1px solid $primary; 155 | // padding: 0.5 * $spaceUnit 0.1 * $spaceUnit; 156 | // border-radius: 5px; 157 | li { 158 | // float: right; 159 | color: $white; 160 | padding: 0.5 * $spaceUnit; 161 | } 162 | } 163 | } 164 | 165 | .navitem { 166 | color: $black; 167 | padding: 0.5 * $spaceUnit $spaceUnit; 168 | &:hover, &:focus { 169 | color: white; 170 | background-color: rgba($primary, 0.95); 171 | border-radius: 2px; 172 | transition: opacity 300ms cubic-bezier(.694,0,.335,1), 173 | background-color 300ms cubic-bezier(.694,0,.335,1), 174 | color 300ms cubic-bezier(.694,0,.335,1); 175 | } 176 | } 177 | } 178 | 179 | // Mobile Navigation 180 | 181 | // Footer 182 | .footer { 183 | text-align: center; 184 | grid-area: footer; 185 | font-family: $headerFont; 186 | font-size: $regularSize; 187 | color: $primary; 188 | 189 | .emoji { 190 | font-size: 2 * $regularSize; 191 | } 192 | } 193 | 194 | // Content 195 | .container { 196 | grid-area: container; 197 | } 198 | 199 | .post { 200 | margin-bottom: 1.5 * $spaceUnit; 201 | } 202 | 203 | .post-year { 204 | margin-bottom: 1.5 * $spaceUnit; 205 | font-family: $headerFont; 206 | letter-spacing: 1.2px; 207 | font-size: $baseSize; 208 | color: $black; 209 | span { 210 | background-color: rgba($primary, 0.95); 211 | color: $white; 212 | padding: 0.2em 0.5em; 213 | } 214 | } 215 | 216 | .post-title { 217 | font-family: $headerFont; 218 | font-size: $bigSize; 219 | font-weight: 400; 220 | color: $primary; 221 | } 222 | 223 | .post-title-single { 224 | font-family: $headerFont; 225 | font-size: $bigSize; 226 | font-weight: 400; 227 | color: $primary; 228 | } 229 | 230 | .post-metadata { 231 | display: grid; 232 | grid-template-columns: max-content auto; 233 | grid-template-areas: 234 | "post-date post-tags"; 235 | margin-top: 0.3 * $spaceUnit; 236 | font-family: $metaFont; 237 | font-size: $smallSize; 238 | color: $black; 239 | @include mobile { 240 | display: grid; 241 | grid-template-columns: auto; 242 | grid-template-rows: max-content auto; 243 | grid-row-gap: 3px; 244 | grid-template-areas: 245 | "post-date" 246 | "post-tags"; 247 | } 248 | } 249 | 250 | .post-date { 251 | grid-area: post-date; 252 | margin-right: 0.5 * $spaceUnit; 253 | } 254 | 255 | .post-tags { 256 | grid-area: post-tags; 257 | .post-tag { 258 | font-size: 0.9 * $smallSize; 259 | padding: 0.2 * $spaceUnit 0.4 * $spaceUnit; 260 | margin: 0 0.3 * $spaceUnit; 261 | border-radius: 2px; 262 | color: $black; 263 | background-color: rgba($primary, 0.2); 264 | 265 | @include mobile { 266 | font-size: $smallSize; 267 | padding: 0.1 * $spaceUnit 0.2 * $spaceUnit; 268 | margin: 0 0.1 * $spaceUnit; 269 | border-radius: 2px; 270 | color: $black; 271 | background-color: rgba($primary, 0.2); 272 | } 273 | } 274 | } 275 | 276 | .post-content { 277 | margin-top: 2 * $spaceUnit; 278 | font-family: $baseFonts; 279 | font-size: $baseSize; 280 | line-height: 1.35 * $baseSize; 281 | color: $primary; 282 | 283 | @include mobile { 284 | max-width: 90vw; 285 | } 286 | 287 | 288 | img { 289 | margin: 0 auto; 290 | text-align: center; 291 | display: flex; 292 | cursor: pointer; 293 | @include predesktop { 294 | overflow-x:auto; 295 | max-width: 90vw; 296 | } 297 | } 298 | 299 | pre { 300 | border: 1px solid rgba($primary, 0.3); 301 | border-radius: 3px; 302 | padding: 0.5 * $spaceUnit; 303 | background-color: rgba($primary, 0.0625); 304 | color: $primary; 305 | overflow: auto; 306 | max-width: 100%; 307 | overflow-wrap: normal; 308 | 309 | code { 310 | font-family: $monospaceFont; 311 | font-size: $smallSize; 312 | background-color: unset; 313 | color: $primary; 314 | padding: 0px; 315 | overflow: auto; 316 | } 317 | } 318 | 319 | code { 320 | color: $primary; 321 | padding: 3px 5px; 322 | background-color: rgba($green, 0.15); 323 | border-radius: 3px; 324 | font-size: $smallSize; 325 | font-family: $monospaceFont; 326 | place-content: center; 327 | } 328 | 329 | a { 330 | color: $primary; 331 | text-decoration: underline; 332 | text-underline-position: auto; 333 | padding: 0px 2px; 334 | &:hover, &:focus { 335 | color: $white; 336 | text-decoration: none; 337 | background-color: rgba($primary, 0.95); 338 | border-radius: 2px; 339 | transition: opacity 300ms cubic-bezier(.694,0,.335,1), 340 | background-color 300ms cubic-bezier(.694,0,.335,1), 341 | color 300ms cubic-bezier(.694,0,.335,1); 342 | } 343 | } 344 | 345 | p { 346 | font-family: $baseFonts; 347 | margin: 0.5 * $spaceUnit 0; 348 | } 349 | 350 | h1, h2, h3, h4, h5, h6 { 351 | font-family: $headerFont; 352 | font-weight: 400; 353 | padding: 0 0.5 * $spaceUnit; 354 | border-left: 5px solid rgba($primary, 0.95); 355 | color: $primary; 356 | margin: $spaceUnit 0; 357 | } 358 | h1 { 359 | font-size: 1.2 * $regularSize; 360 | } 361 | 362 | h2 { 363 | font-size: 1.15 * $regularSize; 364 | } 365 | 366 | h3 { 367 | font-size: 1.1 * $regularSize; 368 | } 369 | 370 | h4 { 371 | font-size: 1.05 * $regularSize; 372 | } 373 | 374 | strong { 375 | font-family: $headerFont; 376 | padding: 3px 0.5*$spaceUnit; 377 | margin: $spaceUnit 0; 378 | background-color: rgba($primary, 0.85); 379 | border-radius: 3px; 380 | color: $white; 381 | } 382 | } -------------------------------------------------------------------------------- /app/static/sass/theme.scss: -------------------------------------------------------------------------------- 1 | 2 | // Colors 3 | $grey: #555555; 4 | $black: #212121; 5 | $lightBlack: #424242; 6 | $blue: #3185FC; 7 | $white: #FFFFFF; 8 | $red: #F44336; 9 | $green: #90EE90; 10 | $lightGreen: #E8F3EC; 11 | 12 | $primary: $black; 13 | $secondary: $blue; 14 | 15 | // Fonts 16 | $baseFonts: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; 17 | $headerFont: 'Quicksand', $baseFonts; 18 | $metaFont: 'Lato', $baseFonts; 19 | $monospaceFont: Consolas, "Andale Mono WT", "Andale Mono", "Lucida Console", "Lucida Sans Typewriter", "DejaVu Sans Mono", "Bitstream Vera Sans Mono", "Liberation Mono", "Nimbus Mono L", Monaco, "Courier New", Courier, monospace; 20 | 21 | $baseSize: 1rem; 22 | $bigSize: 1.5rem; 23 | $regularSize: 1.2rem; 24 | $smallSize: 0.8rem; 25 | 26 | // Spacing 27 | $spaceUnit: 1rem; -------------------------------------------------------------------------------- /app/templates/about.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %} 4 | About Me - 5 | {% endblock %} 6 | 7 | {% block container %} 8 | 9 | 10 | 11 |