projects
Domeniko Gentner 3 years ago
commit 9ac6e9930e
  1. 8
      .gitignore
  2. 22
      LICENSE.md
  3. 20
      Pipfile
  4. 251
      Pipfile.lock
  5. 130
      README.md
  6. 0
      db/keep
  7. 35
      docs/apache-config.md
  8. 1
      docs/flow-chart.drawio
  9. BIN
      docs/flow-chart.png
  10. 84
      js/labertasche.js
  11. 44
      labertasche.yaml
  12. 26
      labertasche/__init__.py
  13. 12
      labertasche/blueprints/__init__.py
  14. 189
      labertasche/blueprints/bp_comments.py
  15. 201
      labertasche/blueprints/bp_dashboard.py
  16. 46
      labertasche/blueprints/bp_login.py
  17. 12
      labertasche/database/__init__.py
  18. 190
      labertasche/helper/__init__.py
  19. 100
      labertasche/mail/__init__.py
  20. 11
      labertasche/models/__init__.py
  21. 34
      labertasche/models/t_comments.py
  22. 23
      labertasche/models/t_emails.py
  23. 21
      labertasche/models/t_location.py
  24. 30
      labertasche/settings/__init__.py
  25. 9
      mail_credentials.json
  26. 75
      server.py
  27. 16
      server.wsgi
  28. 1
      static/css/Chart.min.css
  29. 10168
      static/css/labertasche.css
  30. BIN
      static/css/open-sans-v18-latin-700.woff2
  31. BIN
      static/css/open-sans-v18-latin-700italic.woff2
  32. BIN
      static/css/open-sans-v18-latin-italic.woff2
  33. BIN
      static/css/open-sans-v18-latin-regular.woff2
  34. 5
      static/css/sass/bulma/base/_all.sass
  35. 143
      static/css/sass/bulma/base/generic.sass
  36. 1
      static/css/sass/bulma/base/helpers.sass
  37. 79
      static/css/sass/bulma/base/minireset.sass
  38. 15
      static/css/sass/bulma/components/_all.sass
  39. 75
      static/css/sass/bulma/components/breadcrumb.sass
  40. 83
      static/css/sass/bulma/components/card.sass
  41. 81
      static/css/sass/bulma/components/dropdown.sass
  42. 77
      static/css/sass/bulma/components/level.sass
  43. 52
      static/css/sass/bulma/components/media.sass
  44. 57
      static/css/sass/bulma/components/menu.sass
  45. 99
      static/css/sass/bulma/components/message.sass
  46. 115
      static/css/sass/bulma/components/modal.sass
  47. 443
      static/css/sass/bulma/components/navbar.sass
  48. 150
      static/css/sass/bulma/components/pagination.sass
  49. 119
      static/css/sass/bulma/components/panel.sass
  50. 174
      static/css/sass/bulma/components/tabs.sass
  51. 16
      static/css/sass/bulma/elements/_all.sass
  52. 24
      static/css/sass/bulma/elements/box.sass
  53. 325
      static/css/sass/bulma/elements/button.sass
  54. 27
      static/css/sass/bulma/elements/container.sass
  55. 155
      static/css/sass/bulma/elements/content.sass
  56. 1
      static/css/sass/bulma/elements/form.sass
  57. 21
      static/css/sass/bulma/elements/icon.sass
  58. 71
      static/css/sass/bulma/elements/image.sass
  59. 50
      static/css/sass/bulma/elements/notification.sass
  60. 39
      static/css/sass/bulma/elements/other.sass
  61. 71
      static/css/sass/bulma/elements/progress.sass
  62. 131
      static/css/sass/bulma/elements/table.sass
  63. 138
      static/css/sass/bulma/elements/tag.sass
  64. 70
      static/css/sass/bulma/elements/title.sass
  65. 9
      static/css/sass/bulma/form/_all.sass
  66. 22
      static/css/sass/bulma/form/checkbox-radio.sass
  67. 182
      static/css/sass/bulma/form/file.sass
  68. 66
      static/css/sass/bulma/form/input-textarea.sass
  69. 87
      static/css/sass/bulma/form/select.sass
  70. 57
      static/css/sass/bulma/form/shared.sass
  71. 215
      static/css/sass/bulma/form/tools.sass
  72. 5
      static/css/sass/bulma/grid/_all.sass
  73. 504
      static/css/sass/bulma/grid/columns.sass
  74. 34
      static/css/sass/bulma/grid/tiles.sass
  75. 12
      static/css/sass/bulma/helpers/_all.sass
  76. 37
      static/css/sass/bulma/helpers/color.sass
  77. 35
      static/css/sass/bulma/helpers/flexbox.sass
  78. 8
      static/css/sass/bulma/helpers/float.sass
  79. 11
      static/css/sass/bulma/helpers/other.sass
  80. 2
      static/css/sass/bulma/helpers/overflow.sass
  81. 5
      static/css/sass/bulma/helpers/position.sass
  82. 31
      static/css/sass/bulma/helpers/spacing.sass
  83. 98
      static/css/sass/bulma/helpers/typography.sass
  84. 122
      static/css/sass/bulma/helpers/visibility.sass
  85. 6
      static/css/sass/bulma/layout/_all.sass
  86. 9
      static/css/sass/bulma/layout/footer.sass
  87. 147
      static/css/sass/bulma/layout/hero.sass
  88. 13
      static/css/sass/bulma/layout/section.sass
  89. 9
      static/css/sass/bulma/utilities/_all.sass
  90. 5
      static/css/sass/bulma/utilities/animations.sass
  91. 50
      static/css/sass/bulma/utilities/controls.sass
  92. 107
      static/css/sass/bulma/utilities/derived-variables.sass
  93. 115
      static/css/sass/bulma/utilities/functions.sass
  94. 78
      static/css/sass/bulma/utilities/initial-variables.sass
  95. 285
      static/css/sass/bulma/utilities/mixins.sass
  96. 87
      static/css/sass/labertasche.scss
  97. 1
      static/css/sass/sass-watch.cmd
  98. 86
      static/css/sass/triangle.scss
  99. 7
      static/js/Chart.bundle.min.js
  100. 21
      static/js/dashboard.js
  101. Some files were not shown because too many files have changed in this diff Show More

8
.gitignore vendored

@ -0,0 +1,8 @@
.idea
__pycache__/
venv
db/labertasche.db-shm
db/labertasche.db-wal
output
/output/
*.sql

@ -0,0 +1,22 @@
MIT License
Copyright (c) 2020 Domeniko Gentner <code@tuxstash.de>
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:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
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.

@ -0,0 +1,20 @@
[[source]]
name = "pypi"
url = "https://pypi.org/simple"
verify_ssl = true
[dev-packages]
[packages]
flask = "*"
pyyaml = "*"
flask-sqlalchemy = "*"
flask-cors = "*"
antispam = "*"
flask-login = "*"
sqlalchemy = "*"
requests = "*"
py3-validate-email = "*"
[requires]
python_version = "3.8"

251
Pipfile.lock generated

@ -0,0 +1,251 @@
{
"_meta": {
"hash": {
"sha256": "57134ef6f8a30aa46c1ab6263e62e14edbb27d6df2911fc6b2140dde8c49d27c"
},
"pipfile-spec": 6,
"requires": {
"python_version": "3.8"
},
"sources": [
{
"name": "pypi",
"url": "https://pypi.org/simple",
"verify_ssl": true
}
]
},
"default": {
"antispam": {
"hashes": [
"sha256:e188b424ea9b76c408a592a5ff60eb1280f45f26b404db4d5e96123f485de39b"
],
"index": "pypi",
"version": "==0.0.10"
},
"certifi": {
"hashes": [
"sha256:1f422849db327d534e3d0c5f02a263458c3955ec0aae4ff09b95f195c59f4edd",
"sha256:f05def092c44fbf25834a51509ef6e631dc19765ab8a57b4e7ab85531f0a9cf4"
],
"version": "==2020.11.8"
},
"chardet": {
"hashes": [
"sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
"sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"
],
"version": "==3.0.4"
},
"click": {
"hashes": [
"sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a",
"sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"
],
"version": "==7.1.2"
},
"dnspython": {
"hashes": [
"sha256:044af09374469c3a39eeea1a146e8cac27daec951f1f1f157b1962fc7cb9d1b7",
"sha256:40bb3c24b9d4ec12500f0124288a65df232a3aa749bb0c39734b782873a2544d"
],
"version": "==2.0.0"
},
"filelock": {
"hashes": [
"sha256:18d82244ee114f543149c66a6e0c14e9c4f8a1044b5cdaadd0f82159d6a6ff59",
"sha256:929b7d63ec5b7d6b71b0fa5ac14e030b3f70b75747cef1b10da9b879fef15836"
],
"version": "==3.0.12"
},
"flask": {
"hashes": [
"sha256:4efa1ae2d7c9865af48986de8aeb8504bf32c7f3d6fdc9353d34b21f4b127060",
"sha256:8a4fdd8936eba2512e9c85df320a37e694c93945b33ef33c89946a340a238557"
],
"index": "pypi",
"version": "==1.1.2"
},
"flask-cors": {
"hashes": [
"sha256:6bcfc100288c5d1bcb1dbb854babd59beee622ffd321e444b05f24d6d58466b8",
"sha256:cee4480aaee421ed029eaa788f4049e3e26d15b5affb6a880dade6bafad38324"
],
"index": "pypi",
"version": "==3.0.9"
},
"flask-login": {
"hashes": [
"sha256:6d33aef15b5bcead780acc339464aae8a6e28f13c90d8b1cf9de8b549d1c0b4b",
"sha256:7451b5001e17837ba58945aead261ba425fdf7b4f0448777e597ddab39f4fba0"
],
"index": "pypi",
"version": "==0.5.0"
},
"flask-sqlalchemy": {
"hashes": [
"sha256:05b31d2034dd3f2a685cbbae4cfc4ed906b2a733cff7964ada450fd5e462b84e",
"sha256:bfc7150eaf809b1c283879302f04c42791136060c6eeb12c0c6674fb1291fae5"
],
"index": "pypi",
"version": "==2.4.4"
},
"idna": {
"hashes": [
"sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6",
"sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"
],
"version": "==2.10"
},
"itsdangerous": {
"hashes": [
"sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19",
"sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749"
],
"version": "==1.1.0"
},
"jinja2": {
"hashes": [
"sha256:89aab215427ef59c34ad58735269eb58b1a5808103067f7bb9d5836c651b3bb0",
"sha256:f0a4641d3cf955324a89c04f3d94663aa4d638abe8f733ecd3582848e1c37035"
],
"version": "==2.11.2"
},
"markupsafe": {
"hashes": [
"sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473",
"sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161",
"sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235",
"sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5",
"sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42",
"sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff",
"sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b",
"sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1",
"sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e",
"sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183",
"sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66",
"sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b",
"sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1",
"sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15",
"sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1",
"sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e",
"sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b",
"sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905",
"sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735",
"sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d",
"sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e",
"sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d",
"sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c",
"sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21",
"sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2",
"sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5",
"sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b",
"sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6",
"sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f",
"sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f",
"sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2",
"sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7",
"sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"
],
"version": "==1.1.1"
},
"py3-validate-email": {
"hashes": [
"sha256:3bbb264b49c0ae09afdb2738956f00b0e8dd7e079e2d079b2e9b6688de474d28"
],
"index": "pypi",
"version": "==0.2.10"
},
"pyyaml": {
"hashes": [
"sha256:06a0d7ba600ce0b2d2fe2e78453a470b5a6e000a985dd4a4e54e436cc36b0e97",
"sha256:240097ff019d7c70a4922b6869d8a86407758333f02203e0fc6ff79c5dcede76",
"sha256:4f4b913ca1a7319b33cfb1369e91e50354d6f07a135f3b901aca02aa95940bd2",
"sha256:69f00dca373f240f842b2931fb2c7e14ddbacd1397d57157a9b005a6a9942648",
"sha256:73f099454b799e05e5ab51423c7bcf361c58d3206fa7b0d555426b1f4d9a3eaf",
"sha256:74809a57b329d6cc0fdccee6318f44b9b8649961fa73144a98735b0aaf029f1f",
"sha256:7739fc0fa8205b3ee8808aea45e968bc90082c10aef6ea95e855e10abf4a37b2",
"sha256:95f71d2af0ff4227885f7a6605c37fd53d3a106fcab511b8860ecca9fcf400ee",
"sha256:b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d",
"sha256:cc8955cfbfc7a115fa81d85284ee61147059a753344bc51098f3ccd69b0d7e0c",
"sha256:d13155f591e6fcc1ec3b30685d50bf0711574e2c0dfffd7644babf8b5102ca1a"
],
"index": "pypi",
"version": "==5.3.1"
},
"requests": {
"hashes": [
"sha256:7f1a0b932f4a60a1a65caa4263921bb7d9ee911957e0ae4a23a6dd08185ad5f8",
"sha256:e786fa28d8c9154e6a4de5d46a1d921b8749f8b74e28bde23768e5e16eece998"
],
"index": "pypi",
"version": "==2.25.0"
},
"six": {
"hashes": [
"sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259",
"sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"
],
"version": "==1.15.0"
},
"sqlalchemy": {
"hashes": [
"sha256:009e8388d4d551a2107632921320886650b46332f61dc935e70c8bcf37d8e0d6",
"sha256:0157c269701d88f5faf1fa0e4560e4d814f210c01a5b55df3cab95e9346a8bcc",
"sha256:0a92745bb1ebbcb3985ed7bda379b94627f0edbc6c82e9e4bac4fb5647ae609a",
"sha256:0cca1844ba870e81c03633a99aa3dc62256fb96323431a5dec7d4e503c26372d",
"sha256:166917a729b9226decff29416f212c516227c2eb8a9c9f920d69ced24e30109f",
"sha256:1f5f369202912be72fdf9a8f25067a5ece31a2b38507bb869306f173336348da",
"sha256:2909dffe5c9a615b7e6c92d1ac2d31e3026dc436440a4f750f4749d114d88ceb",
"sha256:2b5dafed97f778e9901b79cc01b88d39c605e0545b4541f2551a2fd785adc15b",
"sha256:2e9bd5b23bba8ae8ce4219c9333974ff5e103c857d9ff0e4b73dc4cb244c7d86",
"sha256:3aa6d45e149a16aa1f0c46816397e12313d5e37f22205c26e06975e150ffcf2a",
"sha256:4bdbdb8ca577c6c366d15791747c1de6ab14529115a2eb52774240c412a7b403",
"sha256:53fd857c6c8ffc0aa6a5a3a2619f6a74247e42ec9e46b836a8ffa4abe7aab327",
"sha256:5cdfe54c1e37279dc70d92815464b77cd8ee30725adc9350f06074f91dbfeed2",
"sha256:5d92c18458a4aa27497a986038d5d797b5279268a2de303cd00910658e8d149c",
"sha256:632b32183c0cb0053194a4085c304bc2320e5299f77e3024556fa2aa395c2a8b",
"sha256:7c735c7a6db8ee9554a3935e741cf288f7dcbe8706320251eb38c412e6a4281d",
"sha256:7cd40cb4bc50d9e87b3540b23df6e6b24821ba7e1f305c1492b0806c33dbdbec",
"sha256:84f0ac4a09971536b38cc5d515d6add7926a7e13baa25135a1dbb6afa351a376",
"sha256:8dcbf377529a9af167cbfc5b8acec0fadd7c2357fc282a1494c222d3abfc9629",
"sha256:950f0e17ffba7a7ceb0dd056567bc5ade22a11a75920b0e8298865dc28c0eff6",
"sha256:9e379674728f43a0cd95c423ac0e95262500f9bfd81d33b999daa8ea1756d162",
"sha256:b15002b9788ffe84e42baffc334739d3b68008a973d65fad0a410ca5d0531980",
"sha256:b6f036ecc017ec2e2cc2a40615b41850dc7aaaea6a932628c0afc73ab98ba3fb",
"sha256:bad73f9888d30f9e1d57ac8829f8a12091bdee4949b91db279569774a866a18e",
"sha256:bbc58fca72ce45a64bb02b87f73df58e29848b693869e58bd890b2ddbb42d83b",
"sha256:bca4d367a725694dae3dfdc86cf1d1622b9f414e70bd19651f5ac4fb3aa96d61",
"sha256:be41d5de7a8e241864189b7530ca4aaf56a5204332caa70555c2d96379e18079",
"sha256:bf53d8dddfc3e53a5bda65f7f4aa40fae306843641e3e8e701c18a5609471edf",
"sha256:c092fe282de83d48e64d306b4bce03114859cdbfe19bf8a978a78a0d44ddadb1",
"sha256:c3ab23ee9674336654bf9cac30eb75ac6acb9150dc4b1391bec533a7a4126471",
"sha256:ce64a44c867d128ab8e675f587aae7f61bd2db836a3c4ba522d884cd7c298a77",
"sha256:d05cef4a164b44ffda58200efcb22355350979e000828479971ebca49b82ddb1",
"sha256:d2f25c7f410338d31666d7ddedfa67570900e248b940d186b48461bd4e5569a1",
"sha256:d3b709d64b5cf064972b3763b47139e4a0dc4ae28a36437757f7663f67b99710",
"sha256:e32e3455db14602b6117f0f422f46bc297a3853ae2c322ecd1e2c4c04daf6ed5",
"sha256:ed53209b5f0f383acb49a927179fa51a6e2259878e164273ebc6815f3a752465",
"sha256:f605f348f4e6a2ba00acb3399c71d213b92f27f2383fc4abebf7a37368c12142",
"sha256:fcdb3755a7c355bc29df1b5e6fb8226d5c8b90551d202d69d0076a8a5649d68b"
],
"index": "pypi",
"version": "==1.3.20"
},
"urllib3": {
"hashes": [
"sha256:19188f96923873c92ccb987120ec4acaa12f0461fa9ce5d3d0772bc965a39e08",
"sha256:d8ff90d979214d7b4f8ce956e80f4028fc6860e4431f731ea4a8c08f23f99473"
],
"version": "==1.26.2"
},
"werkzeug": {
"hashes": [
"sha256:2de2a5db0baeae7b2d2664949077c2ac63fbd16d98da0ff71837f7d1dea3fd43",
"sha256:6c80b1e5ad3665290ea39320b91e1be1e0d5f60652b964a3070216de83d2e47c"
],
"version": "==1.0.1"
}
},
"develop": {}
}

@ -0,0 +1,130 @@
# Labertasche
A comment system for Hugo, written in Python (and Javascript).
## Feature Set
* Written in Python, utilizing Flask
* Robust Database handling by utilizing SQLAlchemy, which supports all big database engines
* flask-cors for robust security
* Uses Javascript to send comment via POST to the comment server
* Has callbacks for implementing your own notifications during the posting process.
* No IP being logged
* Email confirmation
* EMail Blocklist
* Only outputs JSON, so templates can be done independently, enhancing customization. Using the comments via a partial
template in Hugo is the recommended way. See below for integration code.
## Requirements
* A public webserver capable of running Apache/NGINX and/or gunicorn. This server does not need to be the same as the
server running the site, but it must have access to your CI/CD chain. Same server is of course easier to implement.
## How does it work?
A picture often says more than a thousand words:
![flow chart image](/docs/flow-chart.png "Flow Chart")
In some words, the user sends the comment from your site to the comment system, the comment system does the validation
and confirmation. Then, a json is put into the data directory from where you can load it via Hugo and generate your
template.
## Setup
Run `ssh://git@git.tuxstash.de:1235/gothseidank/labertasche.git` in the directory where you wish to host the comment
system. For example, `/var/www/html`, I also recommend making use of `/srv/` or `/opt/`. It depends on you.
When everything is downloaded, create the directory `/etc/labertasche`. In this directory, we need 2 files:
* labertasche.yaml - you can find an example in the root directory.
* mail_credentials.json - you can find an example in the root directory.
Copy these files from the root directory of this app to the folder `/etc/labertasche`. Make sure to set ownership for
your user that runs your server later. I always do `chmown user:www-data`, so Apache has only group rights and enable read-only
for the Apache user.
Make sure to read the config and replace the values as needed. The mail configuration should need no explanation,
`labertasche.yaml` has comments. Feel free to bug about more documentation regarding this. Pay special attention to
secrets and passwords.
Now, for the server there are several options. I personally always host flask apps with Apache and mod_wsgi.
The config looks like this:
* [Apache](docs/apache-config.md)
Other options:
* [gunicorn](https://gunicorn.org/https://gunicorn.org/) + Apache/Nginx with Proxy Pass
Once you can see the administrative page, you can start integrating it into Hugo.
## Integrating it into Hugo
### Javascript
In the project folder is a small javascript file. You will need to load this into Hugo, I suggest using Hugo's asset
pipeline to integrate it into your site. One thing is important to know: this script only does the bare bones post request
to your comment backend. Anything else must be done by yourself, but don't worry: The function is making use of a callback,
so you can control what happens during the various stages.
TODO: Example using the javascript properly
### Hugo templates
Remember the `labertasche.yaml` file? It asked you where the data folder of Hugo is. What this program does, is to place
various json files into that folder, in folders that describe your sections. So, for each category/section of your blog
where comments can be placed, one folder will be made. And for each page within that section it generates a json file.
Now create a new partial called "comments.html" (or something else). Within that template the following structure is needed:
```
{{ $location := .Scratch.Get "location" }}
{{ if (fileExists $location ) }}
{{ $dataJ := getJSON $location }}
{{ range $dataJ.comments }}
{{ end }}
{{ end }}
```
This loads the json depending on the rel url and walks the list of comments. You can then use the following variables to
access the per-comment data:
* .content => The body of the message the user has sent
* .email => The mail the person used to send the mail
* .created_on => The date and time the comment was posted
* .comment_id => The comment id, great for making anchors
* .gravatar => The md5 hash of the mail for gravatar, if caching is on, prepend e.g. `/images`, otherwise use the gravatar url to integrate it.
You can style around them as needed. You have free reign.
Of course you will also need a few inputs and a button that submits the data.
Here is a base skeleton to start out:
```
<div>
<input type="text" maxlength=100 placeholder="Enter Email" id="labertasche-mail">
<textarea cols="10" rows="10" id="labertasche-text"></textarea>
<input type="button" onclick="labertasche_post_comment(this, labertasche_callback);">
</div>
```
Please take note of the `id` on each element, these are mandatory, as well as the function call for the `onclick` event.
Again, style as needed and add more Javascript to your gusto.
Inside your template `single.html`, or wherever you want to place comments, you qwill also need this:
```
{{ $file := replaceRE "^(.*)[\\/]$" "data$1.json" .Page.RelPermalink }}
{{ .Scratch.Set "location" $file }}
{{ partial "partials/comments" . }}
```
After that and configuring labertasche correctly, the json files should be placed in your data folder and all you got
to do after that, is to rebuild Hugo and the new comment should appear.

@ -0,0 +1,35 @@
This is an example server config for Apache Webserver with mod_wsgi.
If you wish to use pipenv, then please take also a look at the
[WSGIPythonHome](https://modwsgi.readthedocs.io/en/develop/configuration-directives/WSGIPythonHome.html)
directive.
```
<VirtualHost *:80>
ServerAdmin server@example.com
ServerName comments.example.com
Redirect permanent / https://comments.example.com
</VirtualHost>
<VirtualHost *:443>
ServerAdmin server@example.com
ServerName comments.example.com
WSGIDaemonProcess laberflask user=user group=group threads=2
WSGIScriptAlias / /var/www/html/labertasche/server.wsgi
SSLCertificateFile /etc/letsencrypt/live/comments.example.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/comments.example.com/privkey.pem
Include /etc/letsencrypt/options-ssl-apache.conf
<Directory "/var/www/html/labertasche">
WSGIProcessGroup laberflask
WSGIApplicationGroup %{GLOBAL}
Options -Indexes
AllowOverride None
Require all granted
</Directory>
ErrorLog ${APACHE_LOG_DIR}/laberflask.error.log
CustomLog /dev/null common
</VirtualHost>
```

@ -0,0 +1 @@
<mxfile host="app.diagrams.net" modified="2020-11-15T15:20:17.771Z" agent="5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.193 Safari/537.36" etag="1QcwDtM_10p-pEkd1fcO" version="13.9.8" type="device"><diagram id="C5RBs43oDa-KdzZeNtuy" name="Page-1">7Vttc9o4EP41fMyNLdnGfIVAm14uyZX00ny6kW2BXYTFWSJAf/1JtmxeRBsnBQSezjCMtJZsa3ef1b7ILdibLj9kaBb/RSNMWsCKli143QLAdgBoyZ8VrQpK21OEcZZEatCaMEy+Y0W0FHWeRJhtDeSUEp7MtokhTVMc8i0ayjK62B42omT7qTM0xhphGCKiU5+SiMcF1QftNf0jTsZx+WTb6xRXpqgcrFbCYhTRxQYJ9luwl1HKi9Z02cNEMq/ky9PN6oncTrwPn/5m/6Ev3T8f7/65Km42eMuUagkZTvm7b02GV3Nr6PUG7FO3C1CPsvbkylFL46uSXzgS7FNdmvGYjmmKSH9N7WZ0nkZY3tUSvfWYW0pngmgL4jfM+UrpAppzKkgxnxJ1FS8T/lVNl+1n2f7DVb3r5cal61XZSXm2+lreQHY2ZsnuelreK+cV65OL2lGHV3ipxjE6z0L8EwYq3eAoG2P+k3GgUhiBNEynWLykmJdhgnjysv1ySKn8uBq3FqtoKMm+QcqeUSlXkn3euPKKlDcE+7wl1/OWsmtSyuolXxCZqycNCGITQRri7AVnmhKsRSylsogTjoczlDNiITaDbXGOEkJ6lNAsnwuxHbm4LeiMZ3SCN650vDZEXiUU8WCOl+8Qi85GdRfgq5WqHclR3cXavNueosUbpr2cdnDGA43xn7FYNsOC+JDR5aopnHfPjPG2rXH2t2F7k2Fzaxo229qvGKexbK4GMHGbkE6ngglMNFO8aAGPiAV0A2HmvLFsPdwPHw8KvAhhfxTuA54X+jgYndDkVTAzBz2jnqPdeofnaLXOynO06zoVNjSJPdu9lBDhfARm1Nm3dT8wYXJ9MzRtwYFuEmM6DebszeZwNBqBcK85jLzAc4/ph0CwbQ4rM7dhDv1TWkMImu6I5L0HnCWCYTg7FeJATcQBzyji9ADgjh4MZ8Lb9yNnH858EEDvmDizzw5nF+PwHx0asO5m5BuFBtSg8YzZ4fYgP8T796DAdx3XOiI23HPDRpnRPn9sXNYe5F3GHuRpQGPidcUs9m/u+cmyi/zLxGUtVk5SJgDSkkWPJH9Umg8OCA0nJGFcB+yW3lwMaHcdR88yHUcDoztaI+Jovy5AjeawgNkQ4T2CPrNSW21B2x2jltjXLHEefyepfBohdCEECqzcrDYjHN91hZw9hYHTukKw6a7Q0bHWqYs1s15PR8NaMyJv6J0bpMymgRsAKVA3fQwco36Knj5uSMTu7Z4bMI4pv+mYMhKxA6cu0IzuXeVrbgAt9w51qP1CrG1wC/Occ4u14eWcgxOdk8OmbqILGj3l5hgV4psSJnuEeK5JlNrCd8w6J3qW8+Zu2P/8KGg3d4/3embzuqupC4vRTDbDFUmEVmTwdasaFPpzG1QEFE7GuVbdz7m4DVZ0ViiQ7eqm2OC5Pbs8xFGd4ddNcWePJa7qf4dH8e8I/RcRC+uGE9BoARDq4UR/OaN5sSGvMXycj2nxYGB9Y1QmyZKR+EupHFLUMFB1fuWgvpG583zuTnjf3lM8PLFvpJ+xHOA8USlNHU6jprB+JwjsGD9J6ZgtDDTBFNYtDDg/UI0TmUK9MKAMHqEoKrC2KvjCE0SaArhdW9cBxhFna4LozhMiBfCEAyb4KpUpFvwex6LVEwMGvZuDysPkRxw78rAt499xOEaTy00ogjt1c17QaBHc0XNeX5j8Mk1+/jtKsinLm/mHHaU5LI6kHBB7oxH2flApbXcC66jnT+zT+X2iu/42Ob+28YU37P8P</diagram></mxfile>

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

@ -0,0 +1,84 @@
//**********************************************************************************
// * _author : Domeniko Gentner
// * _mail : code@tuxstash.de
// * _repo : https://git.tuxstash.de/gothseidank/labertasche
// * _license : This project is under MIT License
// *********************************************************************************/
/*
Callback example.
Possible messages:
post-min-length
post-max-length
post-invalid-json
post-duplicate
post-internal-server-error
post-success
post-before-fetch
function labertasche_callback(state)
{
if (state === "post-before-fetch"){
}
if (state === "post-min-length"){
}
if (state === "post-success"){
}
if (state === "post-fetch-exception" || state === "post-internal-server-error"){
}
if (state === "post-invalid-email"){
}
}
*/
function labertasche_post_comment(btn, callback)
{
let remote = document.getElementById('labertasche-comment-section').dataset.remote;
let comment = document.getElementById('labertasche-text').value;
let mail = document.getElementById('labertasche-mail').value;
if (mail.length <= 0 || comment.length < 40){
callback('post-min-length');
if(btn) {
btn.preventDefault();
}
return
}
callback('post-before-fetch');
fetch(remote,
{
mode:"cors",
headers: {
'Access-Control-Allow-Origin':'*',
'Accept': 'application/json',
'Content-Type': 'application/json'
},
method: "POST",
// use real location
body: JSON.stringify({ "email": mail,
"content": comment,
"location": window.location.pathname,
"replied_to": null // TODO: future feature: replies?
})
})
.then(async function(response){
let result = await response.json();
callback(result['status']);
})
.catch(function(exc){
console.log(exc);
callback('post-fetch-exception');
})
// Don't reload the page
if (btn) {
btn.preventDefault();
}
}

@ -0,0 +1,44 @@
# /**********************************************************************************
# * _author : Domeniko Gentner
# * _mail : code@tuxstash.de
# * _repo : https://git.tuxstash.de/gothseidank/labertasche
# * _license : This project is under MIT License
# *********************************************************************************/
system:
web_url: "http://dev.localhost:1314/" # Url where the comment system is served
blog_url: "http://dev.localhost:1313/" # Url of your website
cookie-domain: "dev.localhost" # Url where the comment system is served
database_uri: "sqlite:///db/labertasche.db" # Database URI. See documentation. Default is sqlite.
secret: "6Gxvb52bIJCm2vfDsmWKzShKp1omrzVG" # CHANGE ME! THIS IS IMPORTANT!
output: "../../web/tuxstash.de/data/" # Base path for the output json
debug: false # Leave this as is, this is for development.
send_otp_to_publish: true # Disables confirmation w/ OTP via mail
gravatar:
cache: true # Enable caching of gravatar images
static_dir: "../../web/tuxstash.de/static/images/gravatar/" # Where to store cached images
size: 256 # only applies if images are cached,
# otherwise use ?s=size at the end of the gravatar url
dashboard:
username: "admin" # CHANGE ME!
password: "admin" # CHANGE ME!
addons:
smileys: true # Enable smiley replacements, set to false if unwanted
# https://www.w3schools.com/charsets/ref_emoji_smileys.asp
smileys:
":)": "&#128512"
":d": "&#128513"
":D": "&#128513"
";)": "&#128521"
":p": "&#128523"
":P": "&#128523"
":8": "&#128526"
"(:": "&#128579"
"$)": "&#129297"
":o": "&#128562"
":O": "&#128562"

@ -0,0 +1,26 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# /**********************************************************************************
# * _author : Domeniko Gentner
# * _mail : code@tuxstash.de
# * _repo : https://git.tuxstash.de/gothseidank/labertasche
# * _license : This project is under MIT License
# *********************************************************************************/
from labertasche import (
models,
database,
blueprints,
helper,
mail,
settings
)
_all_ = [
models,
database,
blueprints,
helper,
mail,
settings
]

@ -0,0 +1,12 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# /**********************************************************************************
# * _author : Domeniko Gentner
# * _mail : code@tuxstash.de
# * _repo : https://git.tuxstash.de/gothseidank/labertasche
# * _license : This project is under MIT License
# *********************************************************************************/
from .bp_comments import bp_comments
from .bp_login import bp_login
from .bp_dashboard import bp_dashboard

@ -0,0 +1,189 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# /**********************************************************************************
# * _author : Domeniko Gentner
# * _mail : code@tuxstash.de
# * _repo : https://git.tuxstash.de/gothseidank/labertasche
# * _license : This project is under MIT License
# *********************************************************************************/
import re
from sys import stderr
from antispam import is_spam as spam, score
from flask import Blueprint, jsonify, request, make_response, redirect
from flask_cors import cross_origin
from sqlalchemy import exc
from labertasche.database import labertasche_db as db
from labertasche.helper import is_valid_json, default_timestamp, check_gravatar, export_location
from labertasche.mail import mail
from labertasche.models import TComments, TLocation, TEmail
from labertasche.settings import Settings
from secrets import compare_digest
# Blueprint
bp_comments = Blueprint("bp_comments", __name__, url_prefix='/comments')
# Route for adding new comments
@bp_comments.route("/new", methods=['POST'])
@cross_origin()
def check_and_insert_new_comment():
if request.method == 'POST':
settings = Settings()
smileys = settings.smileys
addons = settings.addons
sender = mail()
# Check length of content and abort if too long or too short
if request.content_length > 2048:
return make_response(jsonify(status="post-max-length"), 400)
if request.content_length <= 0:
return make_response(jsonify(status="post-min-length"), 400)
# get json from request
new_comment = request.json
# save and sanitize location, nice try, bitch
location = new_comment['location'].strip().replace('.', '')
# Validate json and check length again
if not is_valid_json(new_comment) or \
len(new_comment['content']) < 40 or \
len(new_comment['email']) < 5:
print("too short", file=stderr)
return make_response(jsonify(status='post-invalid-json'), 400)
# Strip any HTML from message body
tags = re.compile('<.*?>')
special = re.compile('[&].*[;]')
content = re.sub(tags, '', new_comment['content']).strip()
content = re.sub(special, '', content).strip()
# Convert smileys
if addons['smileys']:
for key, value in smileys.items():
content = content.replace(key, value)
# Update values
new_comment.update({"content": content})
new_comment.update({"email": new_comment['email'].strip()})
new_comment.update({"location": location})
new_comment.update({"replied_to": None}) # Not (yet?) implemented
# Check mail
if not sender.validate(new_comment['email']):
return make_response(jsonify(status='post-invalid-email'), 400)
# check for spam
is_spam = spam(new_comment['content'])
has_score = score(new_comment['content'])
# Insert mail into spam if detected, allow if listed as such
email = db.session.query(TEmail).filter(TEmail.email == new_comment['email']).first()
if not email:
if is_spam:
entry = {
"email": new_comment['email'],
"is_blocked": True,
"is_allowed": False
}
db.session.add(TEmail(**entry))
if email:
if not email.is_allowed:
is_spam = True
if email.is_allowed:
is_spam = False
# Look for location
loc_query = db.session.query(TLocation)\
.filter(TLocation.location == new_comment['location'])
if loc_query.first():
# Set existing location id
new_comment.update({'location_id': loc_query.first().id_location})
# TComments does not have this field
new_comment.pop("location")
else:
# Insert new location
loc_table = {
'location': new_comment['location']
}
new_loc = TLocation(**loc_table)
db.session.add(new_loc)
db.session.flush()
db.session.refresh(new_loc)
new_comment.update({'location_id': new_loc.id_location})
# TComments does not have this field
new_comment.pop("location")
# insert comment
try:
new_comment.update({"is_published": False})
new_comment.update({"created_on": default_timestamp()})
new_comment.update({"is_spam": is_spam})
new_comment.update({"spam_score": has_score})
new_comment.update({"gravatar": check_gravatar(new_comment['email'])})
t_comment = TComments(**new_comment)
db.session.add(t_comment)
db.session.commit()
db.session.flush()
db.session.refresh(t_comment)
# Send confirmation link and store returned value
hashes = sender.send_confirmation_link(new_comment['email'])
setattr(t_comment, "confirmation", hashes[0])
setattr(t_comment, "deletion", hashes[1])
db.session.commit()
except exc.IntegrityError as e:
# Comment body exists, because content is unique
print(f"Duplicate from {request.environ['REMOTE_ADDR']}, error is:\n{e}", file=stderr)
return make_response(jsonify(status="post-duplicate"), 400)
except Exception as e: # must be at bottom
# mail(f"check_and_insert_new_comment has thrown an error: {e}", )
print(e, file=stderr)
return make_response(jsonify(status="post-internal-server-error"), 400)
export_location(location)
return make_response(jsonify(status="post-success", comment_id=t_comment.comments_id), 200)
# Route for confirming comments
@bp_comments.route("/confirm/<email_hash>", methods=['GET'])
@cross_origin()
def check_confirmation_link(email_hash):
settings = Settings()
comment = db.session.query(TComments).filter(TComments.confirmation == email_hash).first()
if comment:
location = db.session.query(TLocation).filter(TLocation.id_location == comment.location_id).first()
if compare_digest(comment.confirmation, email_hash):
comment.confirmation = None
if not comment.is_spam:
setattr(comment, "is_published", True)
db.session.commit()
url = f"{settings.system['blog_url']}{location.location}#comment_{comment.comments_id}"
export_location(location.location)
return redirect(url)
return redirect(f"{settings.system['blog_url']}?cnf=true")
# Route for deleting comments
@bp_comments.route("/delete/<email_hash>", methods=['GET'])
@cross_origin()
def check_deletion_link(email_hash):
settings = Settings()
query = db.session.query(TComments).filter(TComments.deletion == email_hash)
comment = query.first()
if comment:
location = db.session.query(TLocation).filter(TLocation.id_location == comment.location_id).first()
if compare_digest(comment.deletion, email_hash):
query.delete()
db.session.commit()
url = f"{settings.system['blog_url']}?deleted=true"
export_location(location.location)
return redirect(url)
return redirect(f"{settings.system['blog_url']}?cnf=true")

@ -0,0 +1,201 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# /**********************************************************************************
# * _author : Domeniko Gentner
# * _mail : code@tuxstash.de
# * _repo : https://git.tuxstash.de/gothseidank/labertasche
# * _license : This project is under MIT License
# *********************************************************************************/
from flask import Blueprint, render_template, request, redirect
from flask_login import login_required
from labertasche.database import labertasche_db as db
from labertasche.models import TLocation, TComments, TEmail
from labertasche.helper import dates_of_the_week
from sqlalchemy import func
import re
# Blueprint
bp_dashboard = Blueprint("bp_dashboard", __name__, url_prefix='/dashboard')
@bp_dashboard.route('/')
@login_required
def dashboard_index():
dates = dates_of_the_week()
spam = list()
published = list()
unpublished = list()
for each in dates:
spam_comments = db.session.query(TComments).filter(func.DATE(TComments.created_on) == each.date())\
.filter(TComments.is_spam == True).all()
pub_comments = db.session.query(TComments).filter(func.DATE(TComments.created_on) == each.date()) \
.filter(TComments.is_spam == False)\
.filter(TComments.is_published == True).all()
unpub_comments = db.session.query(TComments).filter(func.DATE(TComments.created_on) == each.date()) \
.filter(TComments.is_spam == False)\
.filter(TComments.is_published == False).all()
published.append(len(pub_comments))
spam.append(len(spam_comments))
unpublished.append(len(unpub_comments))
return render_template('dashboard.html', dates=dates, spam=spam, published=published, unpublished=unpublished)
@bp_dashboard.route('/review-spam/', methods=["POST", "GET"])
@bp_dashboard.route('/review-spam/<int:location>', methods=["POST", "GET"])
@login_required
def dashboard_review_spam(location=None):
all_locations = db.session.query(TLocation).all()
# Check post
if request.method == "POST":
location = request.form.get('selected_location')
# no parameters found
if location is None:
return render_template("review-spam.html", locations=all_locations, selected=location)
try:
if int(location) >= 1:
spam_comments = db.session.query(TComments).filter(TComments.location_id == location)\
.filter(TComments.is_spam == True)
return render_template("review-spam.html", locations=all_locations, selected=location,
spam_comments=spam_comments)
except ValueError:
pass
return render_template("review-spam.html", locations=all_locations, selected=location)
@bp_dashboard.route('/manage-comments/', methods=["POST", "GET"])
@bp_dashboard.route('/manage-comments/<int:location>', methods=["POST", "GET"])
@login_required
def dashboard_manage_regular_comments(location=None):
all_locations = db.session.query(TLocation).all()
# Check post
if request.method == "POST":
location = request.form.get('selected_location')
# no parameters found
if location is None:
return render_template("manage-comments.html", locations=all_locations, selected=location)
try:
if int(location) >= 1:
spam_comments = db.session.query(TComments).filter(TComments.location_id == location) \
.filter(TComments.is_spam == False)
return render_template("manage-comments.html", locations=all_locations, selected=location,
spam_comments=spam_comments)
except ValueError:
pass
return render_template("manage-comments.html", locations=all_locations, selected=location)
@bp_dashboard.route('/manage-mail/')
@login_required
def dashboard_allow_email():
addresses = db.session.query(TEmail).all()
return render_template("manage_mail_addresses.html", addresses=addresses)
@bp_dashboard.route('/toggle-mail-allowed/<int:id_email>')
@login_required
def dashboard_allow_email_toggle(id_email):
address = db.session.query(TEmail).filter(TEmail.id_email == id_email).first()
if address:
setattr(address, "is_allowed", (not address.is_allowed))
setattr(address, "is_blocked", (not address.is_blocked))
db.session.commit()
return redirect(request.referrer)
@bp_dashboard.route('/reset-mail-reputation/<int:id_email>')
@login_required
def dashboard_reset_mail_reputation(id_email):
db.session.query(TEmail).filter(TEmail.id_email == id_email).delete()
db.session.commit()
return redirect(request.referrer)
@bp_dashboard.route('/delete-comment/<int:location_id>/<int:comment_id>', methods=['GET'])
@login_required
def dashboard_review_spam_delete_comment(location_id, comment_id):
comment = db.session.query(TComments).filter(TComments.comments_id == comment_id).first()
db.session.delete(comment)
db.session.commit()
# Remove after last slash, to keep the location but get rid of the comment id
url = re.match("^(.*[/])", request.referrer)[0]
return redirect(f"{url}/{location_id}")
@bp_dashboard.route('/allow-comment/<int:location_id>/<int:comment_id>', methods=['GET'])
@login_required
def dashboard_review_spam_allow_comment(comment_id, location_id):
comment = db.session.query(TComments).filter(TComments.comments_id == comment_id).first()
if comment:
setattr(comment, 'is_published', True)
setattr(comment, 'is_spam', False)
db.session.commit()
url = re.match("^(.*[/])", request.referrer)[0]
return redirect(f"{url}/{location_id}")
@bp_dashboard.route('/block-mail/<int:location_id>/<int:comment_id>', methods=["GET"])
@login_required
def dashboard_review_spam_block_mail(location_id, comment_id):
comment = db.session.query(TComments).filter(TComments.comments_id == comment_id).first()
if comment:
mail = db.session.query(TEmail).filter(TEmail.email == comment.email).first()
if mail:
setattr(mail, 'is_allowed', False)
setattr(mail, 'is_blocked', True)
else:
new_mail = {
"email": comment.first().email,
"is_allowed": False,
"is_blocked": True
}
db.session.add(TEmail(**new_mail))
# Delete all comments made by this mail address
db.session.query(TComments).filter(TComments.email == comment.email).delete()
db.session.commit()
url = re.match("^(.*[/])", request.referrer)[0]
return redirect(f"{url}/{location_id}")
@bp_dashboard.route('/allow-user/<int:location_id>/<int:comment_id>', methods=["GET"])
@login_required
def dashboard_review_spam_allow_user(location_id, comment_id):
comment = db.session.query(TComments).filter(TComments.comments_id == comment_id).first()
if comment:
mail = db.session.query(TEmail).filter(TEmail.email == comment.email).first()
if mail:
setattr(mail, 'is_allowed', True)
setattr(mail, 'is_blocked', False)
else:
new_mail = {
"email": comment.email,
"is_allowed": True,
"is_blocked": False
}
db.session.add(TEmail(**new_mail))
# Allow all comments made by this mail address
all_comments = db.session.query(TComments).filter(TComments.email == comment.email).all()
if all_comments:
for comment in all_comments:
setattr(comment, 'is_published', True)
setattr(comment, 'is_spam', False)
db.session.commit()
url = re.match("^(.*[/])", request.referrer)[0]
return redirect(f"{url}/{location_id}")

@ -0,0 +1,46 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# /**********************************************************************************
# * _author : Domeniko Gentner
# * _mail : code@tuxstash.de
# * _repo : https://git.tuxstash.de/gothseidank/labertasche
# * _license : This project is under MIT License
# *********************************************************************************/
from flask import Blueprint, render_template, request, redirect, url_for
from flask_cors import cross_origin
from labertasche.helper import check_auth, User
from flask_login import login_user, current_user, logout_user
# Blueprint
bp_login = Blueprint("bp_login", __name__)
@cross_origin()
@bp_login.route('/', methods=['GET'])
def show_login():
if current_user.is_authenticated:
return redirect(url_for('bp_dashboard.dashboard_index'))
return render_template('login.html')
@cross_origin()
@bp_login.route('/login', methods=['POST', 'GET'])
def login():
if request.method == 'POST':
username = request.form['username']
password = request.form['password']
if check_auth(username, password):
login_user(User(0), remember=True)
return redirect(url_for('bp_dashboard.dashboard_index'))
# Redirect get request to the login page
return redirect(url_for('bp_login.show_login'))
@cross_origin()
@bp_login.route('/logout/', methods=["GET"])
def logout():
if current_user.is_authenticated:
logout_user()
return redirect(url_for("bp_login.show_login"))

@ -0,0 +1,12 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# /**********************************************************************************
# * _author : Domeniko Gentner
# * _mail : code@tuxstash.de
# * _repo : https://git.tuxstash.de/gothseidank/labertasche
# * _license : This project is under MIT License
# *********************************************************************************/
from flask_sqlalchemy import SQLAlchemy
# Create SQLAlchemy
labertasche_db = SQLAlchemy()

@ -0,0 +1,190 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# /**********************************************************************************
# * _author : Domeniko Gentner
# * _mail : code@tuxstash.de
# * _repo : https://git.tuxstash.de/gothseidank/labertasche
# * _license : This project is under MIT License
# *********************************************************************************/
import datetime
import json
from labertasche.models import TLocation, TComments
from labertasche.settings import Settings
from labertasche.database import labertasche_db as db
from functools import wraps
from hashlib import md5
from flask import request
from flask_login import UserMixin
from secrets import compare_digest
from pathlib import Path
from sys import stderr
from re import match as re_match
import requests
class User(UserMixin):
def __init__(self, user_id):
self.id = user_id
def is_valid_json(j):
"""
Tries to load the json to test if it is valid.
:param j: The json to test.
:return: True if the json is valid, False on any exception.
"""
try:
json.dumps(j)
return True
except json.JSONDecodeError as e:
print("not valid json")
return False
def default_timestamp():
"""Timestamp used by the project to ensure consistency"""
date = datetime.datetime.now().replace(microsecond=0)
return date
def time_to_js(obj):
""""
Returns a timestring readable by Javascript
"""
if isinstance(obj, (datetime.date, datetime.datetime)):
return obj.isoformat()
def alchemy_query_to_dict(obj):
"""
Used when exporting the data. It truncates the mail, removes the T from the date string, etc.
:param obj: A single query item from sqlalchemy.
:return: a dict with the query
"""
no_mail = re_match("^.*[@]", obj.email)[0]
result = {
"comment_id": obj.comments_id,
"email": no_mail,
"content": obj.content,
"created_on": time_to_js(obj.created_on).replace("T", " "),
"replied_to": obj.replied_to,
"gravatar": obj.gravatar
}
return dict(result)
# Come on, it's a mail hash, don't complain
# noinspection InsecureHash
def check_gravatar(email: str):
"""
Builds the gravatar email hash, which uses md5.
You may use ?size=128 for example to dictate size in the final template.
:param email: the email to use for the hash
:return: the gravatar url of the image
"""
settings = Settings()
options = settings.gravatar
gravatar_hash = md5(email.strip().lower().encode("utf8")).hexdigest()
if options['cache']:
url = f"https://www.gravatar.com/avatar/{gravatar_hash}?s={options['size']}"
response = requests.get(url)
if response.ok:
outfile = Path(f"{options['static_dir']}/{gravatar_hash}.jpg")
if not outfile.exists():
with outfile.open('wb') as fp:
response.raw.decode_content = True
for chunk in response:
fp.write(chunk)
return gravatar_hash
def check_auth(username: str, password: str):
"""
Compares username and password from the settings file in a safe way.
Direct string comparison is vulnerable to timing attacks
https://sqreen.github.io/DevelopersSecurityBestPractices/timing-attack/python
:param username: username entered by the user
:param password: password entered by the user
:return: True if equal, False if not
"""
settings = Settings()
if compare_digest(username, settings.dashboard['username']) and \
compare_digest(password, settings.dashboard['password']):
return True
return False
def basic_login_required(f):
"""
Decorator for basic auth
"""
@wraps(f)
def wrapped_view(**kwargs):
auth = request.authorization
if not (auth and check_auth(auth.username, auth.password)):
return ('Unauthorized', 401, {
'WWW-Authenticate': 'Basic realm="Login Required"'
})
return f(**kwargs)
return wrapped_view
def export_location(location: str) -> bool:
"""
Exports the comments for the location after the comment was accepted
:param location: relative url of the hugo page
"""
try:
# Query
loc_query = db.session.query(TLocation).filter(TLocation.location == location).first()
if loc_query:
comments = db.session.query(TComments).filter(TComments.is_spam != True) \
.filter(TComments.is_published == True) \
.filter(TComments.location_id == loc_query.id_location) \
.filter(TComments.replied_to == None)
bundle = {
"comments": []
}
for comment in comments:
bundle['comments'].append(alchemy_query_to_dict(comment))
path_loc = re_match(".*(?=/)", loc_query.location)[0]
system = Settings().system
out = Path(f"{system['output']}/{path_loc}.json")
out = out.absolute()
print(out)
folder = out.parents[0]
folder.mkdir(parents=True, exist_ok=True)
with out.open('w') as fp:
json.dump(bundle, fp)
return True
except Exception as e:
# mail(f"export_comments has thrown an error: {str(e)}")
print(e, file=stderr)
return False
def dates_of_the_week():
"""
Finds all dates of this week and returns them as a list,
going from midnight on monday to sunday 1 second before midnight
:return: A list containing the dates
"""
date_list = list()
now = datetime.datetime.now()
monday = now - datetime.timedelta(days=now.weekday(), hours=now.hour, minutes=now.minute, seconds=now.second,
microseconds=now.microsecond)
date_list.append(monday)
for each in range(1, 6):
monday = monday + datetime.timedelta(days=1)
date_list.append(monday)
date_list.append((monday + datetime.timedelta(days=1, hours=23, minutes=59, seconds=59)))
return date_list

@ -0,0 +1,100 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# /**********************************************************************************
# * _author : Domeniko Gentner
# * _mail : code@tuxstash.de
# * _repo : https://git.tuxstash.de/gothseidank/labertasche
# * _license : This project is under MIT License
# *********************************************************************************/
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from json import load as j_load
from pathlib import Path
from platform import system
from smtplib import SMTP_SSL, SMTPHeloError, SMTPAuthenticationError, SMTPException
from ssl import create_default_context
from labertasche.settings import Settings
from validate_email import validate_email
from secrets import token_urlsafe
class mail:
def __init__(self):
path = Path("/etc/labertasche/mail_credentials.json")
if system().lower() == "windows":
path = Path("mail_credentials.json")
with path.open("r") as fp:
self.credentials = j_load(fp)
def send(self, txt_what: str, html_what: str, to: str):
if not self.credentials['enable']:
return
txtmail = MIMEText(txt_what, "plain", _charset='utf8')
multimime = MIMEMultipart('alternative')
multimime['Subject'] = "Comment confirmation pending"
multimime['From'] = self.credentials['email-sendfrom']
multimime['To'] = to
multimime.attach(txtmail)
# Only send HTML if needed
if html_what is not None:
htmlmail = MIMEText(html_what, "html", _charset='utf8')
multimime.attach(htmlmail)
try:
with SMTP_SSL(host=self.credentials['smtp-server'],
port=self.credentials['smtp-port'],
context=create_default_context()) as server:
server.login(user=self.credentials['email-user'], password=self.credentials['email-password'])
server.sendmail(to_addrs=to,
msg=multimime.as_string(),
from_addr=self.credentials['email-sendfrom'])
except SMTPHeloError as helo:
print(f"SMTPHeloError: {helo}")
except SMTPAuthenticationError as auth_error:
print(f"Authentication Error: {auth_error}")
except SMTPException as e:
print(f"SMTPException: {e}")
def send_confirmation_link(self, email):
"""
Send confirmation link after entering a comment
:param email: The address to send the mail to
:return: A tuple with the confirmation token and the deletion token, in this order
"""
settings = Settings()
confirm_digest = token_urlsafe(48)
delete_digest = token_urlsafe(48)
confirm_url = f"{settings.system['web_url']}/comments/confirm/{confirm_digest}"
delete_url = f"{settings.system['web_url']}/comments/delete/{delete_digest}"
txt_what = f"Hey there. You have made a comment on {settings.system['blog_url']}. Please confirm it by " \
f"copying this link into your browser:\n{confirm_url}\nIf you want to delete your comment for,"\
f"whatever reason, please use this link:\n{delete_url}"
html_what = f"Hey there. You have made a comment on {settings.system['blog_url']}.<br>Please confirm it by " \
f"clicking on this <a href='{confirm_url}'>link</a>.<br>"\
f"In case you want to delete your comment later, please click <a href='{delete_url}'>here</a>."\
f"<br><br>If you think this is in error or someone made this comment in your name, please "\
f"write me a <a href='mailto:contact@tuxstash.de'>mail</a> to discuss options such as " \
f"blocking your mail from being used."
self.send(txt_what, html_what, email)
return confirm_digest, delete_digest
def validate(self, addr):
# validate email
is_valid = validate_email(email_address=addr,
check_regex=True,
check_mx=False,
dns_timeout=10,
use_blacklist=True,
debug=False)
return is_valid

@ -0,0 +1,11 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# /**********************************************************************************
# * _author : Domeniko Gentner
# * _mail : code@tuxstash.de
# * _repo : https://git.tuxstash.de/gothseidank/labertasche
# * _license : This project is under MIT License
# *********************************************************************************/
from .t_comments import TComments
from .t_location import TLocation
from .t_emails import TEmail

@ -0,0 +1,34 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# /**********************************************************************************
# * _author : Domeniko Gentner
# * _mail : code@tuxstash.de
# * _repo : https://git.tuxstash.de/gothseidank/labertasche
# * _license : This project is under MIT License
# *********************************************************************************/
from labertasche.database import labertasche_db as db
from sqlalchemy import ForeignKey
class TComments(db.Model):
# table name
__tablename__ = "t_comments"
__table_args__ = {'useexisting': True}
# primary key
comments_id = db.Column(db.Integer, primary_key=True)
# foreign keys
location_id = db.Column(db.Text, ForeignKey('t_location.id_location'), nullable=False)
# data
email = db.Column(db.Text, nullable=False)
content = db.Column(db.Text, nullable=False, unique=True)
created_on = db.Column(db.DateTime, nullable=False)
is_published = db.Column(db.Boolean, nullable=False)
is_spam = db.Column(db.Boolean, nullable=False)
spam_score = db.Column(db.Float, nullable=False)
replied_to = db.Column(db.Boolean, nullable=True)
confirmation = db.Column(db.Text, nullable=True)
deletion = db.Column(db.Text, nullable=True)
gravatar = db.Column(db.Text, nullable=True)

@ -0,0 +1,23 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# /**********************************************************************************
# * _author : Domeniko Gentner
# * _mail : code@tuxstash.de
# * _repo : https://git.tuxstash.de/gothseidank/labertasche
# * _license : This project is under MIT License
# *********************************************************************************/
from labertasche.database import labertasche_db as db
class TEmail(db.Model):
# Table name
__tablename__ = 't_email'
__table_args__ = {'useexisting': True}
# primary key
id_email = db.Column(db.Integer, primary_key=True)
# data
email = db.Column(db.Integer, unique=True)
is_blocked = db.Column(db.Boolean)
is_allowed = db.Column(db.Boolean)

@ -0,0 +1,21 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# /**********************************************************************************
# * _author : Domeniko Gentner
# * _mail : code@tuxstash.de
# * _repo : https://git.tuxstash.de/gothseidank/labertasche
# * _license : This project is under MIT License
# *********************************************************************************/
from labertasche.database import labertasche_db as db
class TLocation(db.Model):
# table name
__tablename__ = "t_location"
__table_args__ = {'useexisting': True}
# primary key
id_location = db.Column(db.Integer, primary_key=True)
# data
location = db.Column(db.Text, nullable=False, unique=True)

@ -0,0 +1,30 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# /**********************************************************************************
# * _author : Domeniko Gentner
# * _mail : code@tuxstash.de
# * _repo : https://git.tuxstash.de/gothseidank/labertasche
# * _license : This project is under MIT License
# *********************************************************************************/
import yaml
from pathlib import Path
from platform import system
class Settings:
"""
Automatically loads the settings from /etc/ on Linux and same directory on other OS
"""
def __init__(self):
file = Path("labertasche.yaml")
if system().lower() == "linux":
file = Path("/etc/labertasche/labertasche.yaml")
with file.open('r') as fp:
conf = yaml.safe_load(fp)
self.system = conf['system']
self.dashboard = conf['dashboard']
self.gravatar = conf['gravatar']
self.addons = conf['addons']
self.smileys = conf['smileys']

@ -0,0 +1,9 @@
{
"enable": true,
"smtp-server": "mail server",
"smtp-port": 465,
"email-user": "username for smtp",
"email-sendfrom": "sender mail",
"email-password": "passw0rd",
"email-sendto": "receiving mail"
}

@ -0,0 +1,75 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# /**********************************************************************************
# * _author : Domeniko Gentner
# * _mail : code@tuxstash.de
# * _repo : https://git.tuxstash.de/gothseidank/labertasche
# * _license : This project is under MIT License
# *********************************************************************************/
import logging
from flask import Flask, redirect, url_for
from flask_cors import CORS
from sqlalchemy import event
# noinspection PyProtectedMember
from sqlalchemy.engine import Engine
from labertasche.settings import Settings
from labertasche.database import labertasche_db
from labertasche.blueprints import bp_comments, bp_login, bp_dashboard
from labertasche.helper import User
from flask_login import LoginManager
# Load settings
settings = Settings()
# Flask App
laberflask = Flask(__name__)
laberflask.config.update(dict(
SESSION_COOKIE_DOMAIN=settings.system['cookie-domain'],
DEBUG=settings.system['debug'],
SECRET_KEY=settings.system['secret'],
TEMPLATES_AUTO_RELOAD=True,
SQLALCHEMY_DATABASE_URI=settings.system['database_uri'],
SQLALCHEMY_TRACK_MODIFICATIONS=False
))
# CORS
CORS(laberflask, resources={r"/comments": {"origins": settings.system['blog_url']}})
# Import blueprints
laberflask.register_blueprint(bp_comments)
laberflask.register_blueprint(bp_dashboard)
laberflask.register_blueprint(bp_login)
# Disable Werkzeug's verbosity during development
log = logging.getLogger('werkzeug')
log.setLevel(logging.ERROR)
# Initialize ORM
labertasche_db.init_app(laberflask)
with laberflask.app_context():
labertasche_db.create_all()
# Set up login manager
loginmgr = LoginManager(laberflask)
loginmgr.login_view = 'bp_admin_login.login'
@loginmgr.user_loader
def user_loader(user_id):
if user_id != "0":
return None
return User(user_id)
@loginmgr.unauthorized_handler
def login_invalid():
return redirect(url_for('bp_login.show_login'))
@event.listens_for(Engine, "connect")
def set_sqlite_pragma(dbapi_connection, connection_record):
if settings.system["database_uri"][0:6] == 'sqlite':
cursor = dbapi_connection.cursor()
cursor.execute("PRAGMA journal_mode=WAL;")
cursor.close()

@ -0,0 +1,16 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# """
# /**********************************************************************************
# * _author : Domeniko Gentner
# * _mail : code@tuxstash.de
# * _repo : https://git.tuxstash.de/gothseidank/piradio
# * _license : This project is under GPL.v2
# *********************************************************************************/
from sys import path as sys_path
from os import path as os_path
sys_path.insert(0, os_path.dirname(os_path.realpath(__file__)))
from server import laberflask
application = laberflask

@ -0,0 +1 @@
@keyframes chartjs-render-animation{from{opacity:.99}to{opacity:1}}.chartjs-render-monitor{animation:chartjs-render-animation 1ms}.chartjs-size-monitor,.chartjs-size-monitor-expand,.chartjs-size-monitor-shrink{position:absolute;direction:ltr;left:0;top:0;right:0;bottom:0;overflow:hidden;pointer-events:none;visibility:hidden;z-index:-1}.chartjs-size-monitor-expand>div{position:absolute;width:1000000px;height:1000000px;left:0;top:0}.chartjs-size-monitor-shrink>div{position:absolute;width:200%;height:200%;left:0;top:0}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,5 @@
/* Bulma Base */
@charset "utf-8"
@import "minireset.sass"
@import "generic.sass"

@ -0,0 +1,143 @@
$body-background-color: $scheme-main !default
$body-size: 16px !default
$body-min-width: 300px !default
$body-rendering: optimizeLegibility !default
$body-family: $family-primary !default
$body-overflow-x: hidden !default
$body-overflow-y: scroll !default
$body-color: $text !default
$body-font-size: 1em !default
$body-weight: $weight-normal !default
$body-line-height: 1.5 !default
$code-family: $family-code !default
$code-padding: 0.25em 0.5em 0.25em !default
$code-weight: normal !default
$code-size: 0.875em !default
$small-font-size: 0.875em !default
$hr-background-color: $background !default
$hr-height: 2px !default
$hr-margin: 1.5rem 0 !default
$strong-color: $text-strong !default
$strong-weight: $weight-bold !default
$pre-font-size: 0.875em !default
$pre-padding: 1.25rem 1.5rem !default
$pre-code-font-size: 1em !default
html
background-color: $body-background-color
font-size: $body-size
-moz-osx-font-smoothing: grayscale
-webkit-font-smoothing: antialiased
min-width: $body-min-width
overflow-x: $body-overflow-x
overflow-y: $body-overflow-y
text-rendering: $body-rendering
text-size-adjust: 100%
article,
aside,
figure,
footer,
header,
hgroup,
section
display: block
body,
button,
input,
optgroup,
select,
textarea
font-family: $body-family
code,
pre
-moz-osx-font-smoothing: auto
-webkit-font-smoothing: auto
font-family: $code-family
body
color: $body-color
font-size: $body-font-size
font-weight: $body-weight
line-height: $body-line-height
// Inline
a
color: $link
cursor: pointer
text-decoration: none
strong
color: currentColor
&:hover
color: $link-hover
code
background-color: $code-background
color: $code
font-size: $code-size
font-weight: $code-weight
padding: $code-padding
hr
background-color: $hr-background-color
border: none
display: block
height: $hr-height
margin: $hr-margin
img
height: auto
max-width: 100%
input[type="checkbox"],
input[type="radio"]
vertical-align: baseline
small
font-size: $small-font-size
span
font-style: inherit
font-weight: inherit
strong
color: $strong-color
font-weight: $strong-weight
// Block
fieldset
border: none
pre
+overflow-touch
background-color: $pre-background
color: $pre
font-size: $pre-font-size
overflow-x: auto
padding: $pre-padding
white-space: pre
word-wrap: normal
code
background-color: transparent
color: currentColor
font-size: $pre-code-font-size
padding: 0
table
td,
th
vertical-align: top
&:not([align])
text-align: inherit
th
color: $text-strong

@ -0,0 +1 @@
@warn "The helpers.sass file is DEPRECATED. It has moved into its own /helpers folder. Please import sass/helpers/_all instead."

@ -0,0 +1,79 @@
/*! minireset.css v0.0.6 | MIT License | github.com/jgthms/minireset.css */
// Blocks
html,
body,
p,
ol,
ul,
li,
dl,
dt,
dd,
blockquote,
figure,
fieldset,
legend,
textarea,
pre,
iframe,
hr,
h1,
h2,
h3,
h4,
h5,
h6
margin: 0
padding: 0
// Headings
h1,
h2,
h3,
h4,
h5,
h6
font-size: 100%
font-weight: normal
// List
ul
list-style: none
// Form
button,
input,
select,
textarea
margin: 0
// Box sizing
html
box-sizing: border-box
*
&,
&::before,
&::after
box-sizing: inherit
// Media
img,
video
height: auto
max-width: 100%
// Iframe
iframe
border: 0
// Table
table
border-collapse: collapse
border-spacing: 0
td,
th
padding: 0
&:not([align])
text-align: inherit

@ -0,0 +1,15 @@
/* Bulma Components */
@charset "utf-8"
@import "breadcrumb.sass"
@import "card.sass"
@import "dropdown.sass"
@import "level.sass"
@import "media.sass"
@import "menu.sass"
@import "message.sass"
@import "modal.sass"
@import "navbar.sass"
@import "pagination.sass"
@import "panel.sass"
@import "tabs.sass"

@ -0,0 +1,75 @@
$breadcrumb-item-color: $link !default
$breadcrumb-item-hover-color: $link-hover !default
$breadcrumb-item-active-color: $text-strong !default
$breadcrumb-item-padding-vertical: 0 !default
$breadcrumb-item-padding-horizontal: 0.75em !default
$breadcrumb-item-separator-color: $border-hover !default
.breadcrumb
@extend %block
@extend %unselectable
font-size: $size-normal
white-space: nowrap
a
align-items: center
color: $breadcrumb-item-color
display: flex
justify-content: center
padding: $breadcrumb-item-padding-vertical $breadcrumb-item-padding-horizontal
&:hover
color: $breadcrumb-item-hover-color
li
align-items: center
display: flex
&:first-child a
+ltr-property("padding", 0, false)
&.is-active
a
color: $breadcrumb-item-active-color
cursor: default
pointer-events: none
& + li::before
color: $breadcrumb-item-separator-color
content: "\0002f"
ul,
ol
align-items: flex-start
display: flex
flex-wrap: wrap
justify-content: flex-start
.icon
&:first-child
+ltr-property("margin", 0.5em)
&:last-child
+ltr-property("margin", 0.5em, false)
// Alignment
&.is-centered
ol,
ul
justify-content: center
&.is-right
ol,
ul
justify-content: flex-end
// Sizes
&.is-small
font-size: $size-small
&.is-medium
font-size: $size-medium
&.is-large
font-size: $size-large
// Styles
&.has-arrow-separator
li + li::before
content: "\02192"
&.has-bullet-separator
li + li::before
content: "\02022"
&.has-dot-separator
li + li::before
content: "\000b7"
&.has-succeeds-separator
li + li::before
content: "\0227B"

@ -0,0 +1,83 @@
$card-color: $text !default
$card-background-color: $scheme-main !default
$card-shadow: 0 0.5em 1em -0.125em rgba($scheme-invert, 0.1), 0 0px 0 1px rgba($scheme-invert, 0.02) !default
$card-radius: 0.25rem !default
$card-overflow: hidden !default
$card-header-background-color: transparent !default
$card-header-color: $text-strong !default
$card-header-padding: 0.75rem 1rem !default
$card-header-shadow: 0 0.125em 0.25em rgba($scheme-invert, 0.1) !default
$card-header-weight: $weight-bold !default
$card-content-background-color: transparent !default
$card-content-padding: 1.5rem !default
$card-footer-background-color: transparent !default
$card-footer-border-top: 1px solid $border-light !default
$card-footer-padding: 0.75rem !default
$card-media-margin: $block-spacing !default
.card
background-color: $card-background-color
border-radius: $card-radius
box-shadow: $card-shadow
color: $card-color
max-width: 100%
overflow: $card-overflow
position: relative
.card-header
background-color: $card-header-background-color
align-items: stretch
box-shadow: $card-header-shadow
display: flex
.card-header-title
align-items: center
color: $card-header-color
display: flex
flex-grow: 1
font-weight: $card-header-weight
padding: $card-header-padding
&.is-centered
justify-content: center
.card-header-icon
align-items: center
cursor: pointer
display: flex
justify-content: center
padding: $card-header-padding
.card-image
display: block
position: relative
.card-content
background-color: $card-content-background-color
padding: $card-content-padding
.card-footer
background-color: $card-footer-background-color
border-top: $card-footer-border-top
align-items: stretch
display: flex
.card-footer-item
align-items: center
display: flex
flex-basis: 0
flex-grow: 1
flex-shrink: 0
justify-content: center
padding: $card-footer-padding
&:not(:last-child)
+ltr-property("border", $card-footer-border-top)
// Combinations
.card
.media:not(:last-child)
margin-bottom: $card-media-margin

@ -0,0 +1,81 @@
$dropdown-menu-min-width: 12rem !default
$dropdown-content-background-color: $scheme-main !default
$dropdown-content-arrow: $link !default
$dropdown-content-offset: 4px !default
$dropdown-content-padding-bottom: 0.5rem !default
$dropdown-content-padding-top: 0.5rem !default
$dropdown-content-radius: $radius !default
$dropdown-content-shadow: 0 0.5em 1em -0.125em rgba($scheme-invert, 0.1), 0 0px 0 1px rgba($scheme-invert, 0.02) !default
$dropdown-content-z: 20 !default
$dropdown-item-color: $text !default
$dropdown-item-hover-color: $scheme-invert !default
$dropdown-item-hover-background-color: $background !default
$dropdown-item-active-color: $link-invert !default
$dropdown-item-active-background-color: $link !default
$dropdown-divider-background-color: $border-light !default
.dropdown
display: inline-flex
position: relative
vertical-align: top
&.is-active,
&.is-hoverable:hover
.dropdown-menu
display: block
&.is-right
.dropdown-menu
left: auto
right: 0
&.is-up
.dropdown-menu
bottom: 100%
padding-bottom: $dropdown-content-offset
padding-top: initial
top: auto
.dropdown-menu
display: none
+ltr-position(0, false)
min-width: $dropdown-menu-min-width
padding-top: $dropdown-content-offset
position: absolute
top: 100%
z-index: $dropdown-content-z
.dropdown-content
background-color: $dropdown-content-background-color
border-radius: $dropdown-content-radius
box-shadow: $dropdown-content-shadow
padding-bottom: $dropdown-content-padding-bottom
padding-top: $dropdown-content-padding-top
.dropdown-item
color: $dropdown-item-color
display: block
font-size: 0.875rem
line-height: 1.5
padding: 0.375rem 1rem
position: relative
a.dropdown-item,
button.dropdown-item
+ltr-property("padding", 3rem)
text-align: inherit
white-space: nowrap
width: 100%
&:hover
background-color: $dropdown-item-hover-background-color
color: $dropdown-item-hover-color
&.is-active
background-color: $dropdown-item-active-background-color
color: $dropdown-item-active-color
.dropdown-divider
background-color: $dropdown-divider-background-color
border: none
display: block
height: 1px
margin: 0.5rem 0

@ -0,0 +1,77 @@
$level-item-spacing: ($block-spacing / 2) !default
.level
@extend %block
align-items: center
justify-content: space-between
code
border-radius: $radius
img
display: inline-block
vertical-align: top
// Modifiers
&.is-mobile
display: flex
.level-left,
.level-right
display: flex
.level-left + .level-right
margin-top: 0
.level-item
&:not(:last-child)
margin-bottom: 0
+ltr-property("margin", $level-item-spacing)
&:not(.is-narrow)
flex-grow: 1
// Responsiveness
+tablet
display: flex
& > .level-item
&:not(.is-narrow)
flex-grow: 1
.level-item
align-items: center
display: flex
flex-basis: auto
flex-grow: 0
flex-shrink: 0
justify-content: center
.title,
.subtitle
margin-bottom: 0
// Responsiveness
+mobile
&:not(:last-child)
margin-bottom: $level-item-spacing
.level-left,
.level-right
flex-basis: auto
flex-grow: 0
flex-shrink: 0
.level-item
// Modifiers
&.is-flexible
flex-grow: 1
// Responsiveness
+tablet
&:not(:last-child)
+ltr-property("margin", $level-item-spacing)
.level-left
align-items: center
justify-content: flex-start
// Responsiveness
+mobile
& + .level-right
margin-top: 1.5rem
+tablet
display: flex
.level-right
align-items: center
justify-content: flex-end
// Responsiveness
+tablet
display: flex

@ -0,0 +1,52 @@
$media-border-color: bulmaRgba($border, 0.5) !default
$media-spacing: 1rem
$media-spacing-large: 1.5rem
.media
align-items: flex-start
display: flex
text-align: inherit
.content:not(:last-child)
margin-bottom: 0.75rem
.media
border-top: 1px solid $media-border-color
display: flex
padding-top: 0.75rem
.content:not(:last-child),
.control:not(:last-child)
margin-bottom: 0.5rem
.media
padding-top: 0.5rem
& + .media
margin-top: 0.5rem
& + .media
border-top: 1px solid $media-border-color
margin-top: $media-spacing
padding-top: $media-spacing
// Sizes
&.is-large
& + .media
margin-top: $media-spacing-large
padding-top: $media-spacing-large
.media-left,
.media-right
flex-basis: auto
flex-grow: 0
flex-shrink: 0
.media-left
+ltr-property("margin", $media-spacing)
.media-right
+ltr-property("margin", $media-spacing, false)
.media-content
flex-basis: auto
flex-grow: 1
flex-shrink: 1
text-align: inherit
+mobile
.media-content
overflow-x: auto

@ -0,0 +1,57 @@
$menu-item-color: $text !default
$menu-item-radius: $radius-small !default
$menu-item-hover-color: $text-strong !default
$menu-item-hover-background-color: $background !default
$menu-item-active-color: $link-invert !default
$menu-item-active-background-color: $link !default
$menu-list-border-left: 1px solid $border !default
$menu-list-line-height: 1.25 !default
$menu-list-link-padding: 0.5em 0.75em !default
$menu-nested-list-margin: 0.75em !default
$menu-nested-list-padding-left: 0.75em !default
$menu-label-color: $text-light !default
$menu-label-font-size: 0.75em !default
$menu-label-letter-spacing: 0.1em !default
$menu-label-spacing: 1em !default
.menu
font-size: $size-normal
// Sizes
&.is-small
font-size: $size-small
&.is-medium
font-size: $size-medium
&.is-large
font-size: $size-large
.menu-list
line-height: $menu-list-line-height
a
border-radius: $menu-item-radius
color: $menu-item-color
display: block
padding: $menu-list-link-padding
&:hover
background-color: $menu-item-hover-background-color
color: $menu-item-hover-color
// Modifiers
&.is-active
background-color: $menu-item-active-background-color
color: $menu-item-active-color
li
ul
+ltr-property("border", $menu-list-border-left, false)
margin: $menu-nested-list-margin
+ltr-property("padding", $menu-nested-list-padding-left, false)
.menu-label
color: $menu-label-color
font-size: $menu-label-font-size
letter-spacing: $menu-label-letter-spacing
text-transform: uppercase
&:not(:first-child)
margin-top: $menu-label-spacing
&:not(:last-child)
margin-bottom: $menu-label-spacing

@ -0,0 +1,99 @@
$message-background-color: $background !default
$message-radius: $radius !default
$message-header-background-color: $text !default
$message-header-color: $text-invert !default
$message-header-weight: $weight-bold !default
$message-header-padding: 0.75em 1em !default
$message-header-radius: $radius !default
$message-body-border-color: $border !default
$message-body-border-width: 0 0 0 4px !default
$message-body-color: $text !default
$message-body-padding: 1.25em 1.5em !default
$message-body-radius: $radius !default
$message-body-pre-background-color: $scheme-main !default
$message-body-pre-code-background-color: transparent !default
$message-header-body-border-width: 0 !default
$message-colors: $colors !default
.message
@extend %block
background-color: $message-background-color
border-radius: $message-radius
font-size: $size-normal
strong
color: currentColor
a:not(.button):not(.tag):not(.dropdown-item)
color: currentColor
text-decoration: underline
// Sizes
&.is-small
font-size: $size-small
&.is-medium
font-size: $size-medium
&.is-large
font-size: $size-large
// Colors
@each $name, $components in $message-colors
$color: nth($components, 1)
$color-invert: nth($components, 2)
$color-light: null
$color-dark: null
@if length($components) >= 3
$color-light: nth($components, 3)
@if length($components) >= 4
$color-dark: nth($components, 4)
@else
$color-luminance: colorLuminance($color)
$darken-percentage: $color-luminance * 70%
$desaturate-percentage: $color-luminance * 30%
$color-dark: desaturate(darken($color, $darken-percentage), $desaturate-percentage)
@else
$color-lightning: max((100% - lightness($color)) - 2%, 0%)
$color-light: lighten($color, $color-lightning)
&.is-#{$name}
background-color: $color-light
.message-header
background-color: $color
color: $color-invert
.message-body
border-color: $color
color: $color-dark
.message-header
align-items: center
background-color: $message-header-background-color
border-radius: $message-header-radius $message-header-radius 0 0
color: $message-header-color
display: flex
font-weight: $message-header-weight
justify-content: space-between
line-height: 1.25
padding: $message-header-padding
position: relative
.delete
flex-grow: 0
flex-shrink: 0
+ltr-property("margin", 0.75em, false)
& + .message-body
border-width: $message-header-body-border-width
border-top-left-radius: 0
border-top-right-radius: 0
.message-body
border-color: $message-body-border-color
border-radius: $message-body-radius
border-style: solid
border-width: $message-body-border-width
color: $message-body-color
padding: $message-body-padding
code,
pre
background-color: $message-body-pre-background-color
pre code
background-color: $message-body-pre-code-background-color

@ -0,0 +1,115 @@
$modal-z: 40 !default
$modal-background-background-color: bulmaRgba($scheme-invert, 0.86) !default
$modal-content-width: 640px !default
$modal-content-margin-mobile: 20px !default
$modal-content-spacing-mobile: 160px !default
$modal-content-spacing-tablet: 40px !default
$modal-close-dimensions: 40px !default
$modal-close-right: 20px !default
$modal-close-top: 20px !default
$modal-card-spacing: 40px !default
$modal-card-head-background-color: $background !default
$modal-card-head-border-bottom: 1px solid $border !default
$modal-card-head-padding: 20px !default
$modal-card-head-radius: $radius-large !default
$modal-card-title-color: $text-strong !default
$modal-card-title-line-height: 1 !default
$modal-card-title-size: $size-4 !default
$modal-card-foot-radius: $radius-large !default
$modal-card-foot-border-top: 1px solid $border !default
$modal-card-body-background-color: $scheme-main !default
$modal-card-body-padding: 20px !default
$modal-breakpoint: $tablet !default
.modal
@extend %overlay
align-items: center
display: none
flex-direction: column
justify-content: center
overflow: hidden
position: fixed
z-index: $modal-z
// Modifiers
&.is-active
display: flex
.modal-background
@extend %overlay
background-color: $modal-background-background-color
.modal-content,
.modal-card
margin: 0 $modal-content-margin-mobile
max-height: calc(100vh - #{$modal-content-spacing-mobile})
overflow: auto
position: relative
width: 100%
// Responsiveness
+from($modal-breakpoint)
margin: 0 auto
max-height: calc(100vh - #{$modal-content-spacing-tablet})
width: $modal-content-width
.modal-close
@extend %delete
background: none
height: $modal-close-dimensions
position: fixed
+ltr-position($modal-close-right)
top: $modal-close-top
width: $modal-close-dimensions
.modal-card
display: flex
flex-direction: column
max-height: calc(100vh - #{$modal-card-spacing})
overflow: hidden
-ms-overflow-y: visible
.modal-card-head,
.modal-card-foot
align-items: center
background-color: $modal-card-head-background-color
display: flex
flex-shrink: 0
justify-content: flex-start
padding: $modal-card-head-padding
position: relative
.modal-card-head
border-bottom: $modal-card-head-border-bottom
border-top-left-radius: $modal-card-head-radius
border-top-right-radius: $modal-card-head-radius
.modal-card-title
color: $modal-card-title-color
flex-grow: 1
flex-shrink: 0
font-size: $modal-card-title-size
line-height: $modal-card-title-line-height
.modal-card-foot
border-bottom-left-radius: $modal-card-foot-radius
border-bottom-right-radius: $modal-card-foot-radius
border-top: $modal-card-foot-border-top
.button
&:not(:last-child)
+ltr-property("margin", 0.5em)
.modal-card-body
+overflow-touch
background-color: $modal-card-body-background-color
flex-grow: 1
flex-shrink: 1
overflow: auto
padding: $modal-card-body-padding

@ -0,0 +1,443 @@
$navbar-background-color: $scheme-main !default
$navbar-box-shadow-size: 0 2px 0 0 !default
$navbar-box-shadow-color: $background !default
$navbar-height: 3.25rem !default
$navbar-padding-vertical: 1rem !default
$navbar-padding-horizontal: 2rem !default
$navbar-z: 30 !default
$navbar-fixed-z: 30 !default
$navbar-item-color: $text !default
$navbar-item-hover-color: $link !default
$navbar-item-hover-background-color: $scheme-main-bis !default
$navbar-item-active-color: $scheme-invert !default
$navbar-item-active-background-color: transparent !default
$navbar-item-img-max-height: 1.75rem !default
$navbar-burger-color: $navbar-item-color !default
$navbar-tab-hover-background-color: transparent !default
$navbar-tab-hover-border-bottom-color: $link !default
$navbar-tab-active-color: $link !default
$navbar-tab-active-background-color: transparent !default
$navbar-tab-active-border-bottom-color: $link !default
$navbar-tab-active-border-bottom-style: solid !default
$navbar-tab-active-border-bottom-width: 3px !default
$navbar-dropdown-background-color: $scheme-main !default
$navbar-dropdown-border-top: 2px solid $border !default
$navbar-dropdown-offset: -4px !default
$navbar-dropdown-arrow: $link !default
$navbar-dropdown-radius: $radius-large !default
$navbar-dropdown-z: 20 !default
$navbar-dropdown-boxed-radius: $radius-large !default
$navbar-dropdown-boxed-shadow: 0 8px 8px bulmaRgba($scheme-invert, 0.1), 0 0 0 1px bulmaRgba($scheme-invert, 0.1) !default
$navbar-dropdown-item-hover-color: $scheme-invert !default
$navbar-dropdown-item-hover-background-color: $background !default
$navbar-dropdown-item-active-color: $link !default
$navbar-dropdown-item-active-background-color: $background !default
$navbar-divider-background-color: $background !default
$navbar-divider-height: 2px !default
$navbar-bottom-box-shadow-size: 0 -2px 0 0 !default
$navbar-breakpoint: $desktop !default
$navbar-colors: $colors !default
=navbar-fixed
left: 0
position: fixed
right: 0
z-index: $navbar-fixed-z
.navbar
background-color: $navbar-background-color
min-height: $navbar-height
position: relative
z-index: $navbar-z
@each $name, $pair in $navbar-colors
$color: nth($pair, 1)
$color-invert: nth($pair, 2)
&.is-#{$name}
background-color: $color
color: $color-invert
.navbar-brand
& > .navbar-item,
.navbar-link
color: $color-invert
& > a.navbar-item,
.navbar-link
&:focus,
&:hover,
&.is-active
background-color: bulmaDarken($color, 5%)
color: $color-invert
.navbar-link
&::after
border-color: $color-invert
.navbar-burger
color: $color-invert
+from($navbar-breakpoint)
.navbar-start,
.navbar-end
& > .navbar-item,
.navbar-link
color: $color-invert
& > a.navbar-item,
.navbar-link
&:focus,
&:hover,
&.is-active
background-color: bulmaDarken($color, 5%)
color: $color-invert
.navbar-link
&::after
border-color: $color-invert
.navbar-item.has-dropdown:focus .navbar-link,
.navbar-item.has-dropdown:hover .navbar-link,
.navbar-item.has-dropdown.is-active .navbar-link
background-color: bulmaDarken($color, 5%)
color: $color-invert
.navbar-dropdown
a.navbar-item
&.is-active
background-color: $color
color: $color-invert
& > .container
align-items: stretch
display: flex
min-height: $navbar-height
width: 100%
&.has-shadow
box-shadow: $navbar-box-shadow-size $navbar-box-shadow-color
&.is-fixed-bottom,
&.is-fixed-top
+navbar-fixed
&.is-fixed-bottom
bottom: 0
&.has-shadow
box-shadow: $navbar-bottom-box-shadow-size $navbar-box-shadow-color
&.is-fixed-top
top: 0
html,
body
&.has-navbar-fixed-top
padding-top: $navbar-height
&.has-navbar-fixed-bottom
padding-bottom: $navbar-height
.navbar-brand,
.navbar-tabs
align-items: stretch
display: flex
flex-shrink: 0
min-height: $navbar-height
.navbar-brand
a.navbar-item
&:focus,
&:hover
background-color: transparent
.navbar-tabs
+overflow-touch
max-width: 100vw
overflow-x: auto
overflow-y: hidden
.navbar-burger
color: $navbar-burger-color
+hamburger($navbar-height)
+ltr-property("margin", auto, false)
.navbar-menu
display: none
.navbar-item,
.navbar-link
color: $navbar-item-color
display: block
line-height: 1.5
padding: 0.5rem 0.75rem
position: relative
.icon
&:only-child
margin-left: -0.25rem
margin-right: -0.25rem
a.navbar-item,
.navbar-link
cursor: pointer
&:focus,
&:focus-within,
&:hover,
&.is-active
background-color: $navbar-item-hover-background-color
color: $navbar-item-hover-color
.navbar-item
flex-grow: 0
flex-shrink: 0
img
max-height: $navbar-item-img-max-height
&.has-dropdown
padding: 0
&.is-expanded
flex-grow: 1
flex-shrink: 1
&.is-tab
border-bottom: 1px solid transparent
min-height: $navbar-height
padding-bottom: calc(0.5rem - 1px)
&:focus,
&:hover
background-color: $navbar-tab-hover-background-color
border-bottom-color: $navbar-tab-hover-border-bottom-color
&.is-active
background-color: $navbar-tab-active-background-color
border-bottom-color: $navbar-tab-active-border-bottom-color
border-bottom-style: $navbar-tab-active-border-bottom-style
border-bottom-width: $navbar-tab-active-border-bottom-width
color: $navbar-tab-active-color
padding-bottom: calc(0.5rem - #{$navbar-tab-active-border-bottom-width})
.navbar-content
flex-grow: 1
flex-shrink: 1
.navbar-link:not(.is-arrowless)
+ltr-property("padding", 2.5em)
&::after
@extend %arrow
border-color: $navbar-dropdown-arrow
margin-top: -0.375em
+ltr-position(1.125em)
.navbar-dropdown
font-size: 0.875rem
padding-bottom: 0.5rem
padding-top: 0.5rem
.navbar-item
padding-left: 1.5rem
padding-right: 1.5rem
.navbar-divider
background-color: $navbar-divider-background-color
border: none
display: none
height: $navbar-divider-height
margin: 0.5rem 0
+until($navbar-breakpoint)
.navbar > .container
display: block
.navbar-brand,
.navbar-tabs
.navbar-item
align-items: center
display: flex
.navbar-link
&::after
display: none
.navbar-menu
background-color: $navbar-background-color
box-shadow: 0 8px 16px bulmaRgba($scheme-invert, 0.1)
padding: 0.5rem 0
&.is-active
display: block
// Fixed navbar
.navbar
&.is-fixed-bottom-touch,
&.is-fixed-top-touch
+navbar-fixed
&.is-fixed-bottom-touch
bottom: 0
&.has-shadow
box-shadow: 0 -2px 3px bulmaRgba($scheme-invert, 0.1)
&.is-fixed-top-touch
top: 0
&.is-fixed-top,
&.is-fixed-top-touch
.navbar-menu
+overflow-touch
max-height: calc(100vh - #{$navbar-height})
overflow: auto
html,
body
&.has-navbar-fixed-top-touch
padding-top: $navbar-height
&.has-navbar-fixed-bottom-touch
padding-bottom: $navbar-height
+from($navbar-breakpoint)
.navbar,
.navbar-menu,
.navbar-start,
.navbar-end
align-items: stretch
display: flex
.navbar
min-height: $navbar-height
&.is-spaced
padding: $navbar-padding-vertical $navbar-padding-horizontal
.navbar-start,
.navbar-end
align-items: center
a.navbar-item,
.navbar-link
border-radius: $radius
&.is-transparent
a.navbar-item,
.navbar-link
&:focus,
&:hover,
&.is-active
background-color: transparent !important
.navbar-item.has-dropdown
&.is-active,
&.is-hoverable:focus,
&.is-hoverable:focus-within,
&.is-hoverable:hover
.navbar-link
background-color: transparent !important
.navbar-dropdown
a.navbar-item
&:focus,
&:hover
background-color: $navbar-dropdown-item-hover-background-color
color: $navbar-dropdown-item-hover-color
&.is-active
background-color: $navbar-dropdown-item-active-background-color
color: $navbar-dropdown-item-active-color
.navbar-burger
display: none
.navbar-item,
.navbar-link
align-items: center
display: flex
.navbar-item
&.has-dropdown
align-items: stretch
&.has-dropdown-up
.navbar-link::after
transform: rotate(135deg) translate(0.25em, -0.25em)
.navbar-dropdown
border-bottom: $navbar-dropdown-border-top
border-radius: $navbar-dropdown-radius $navbar-dropdown-radius 0 0
border-top: none
bottom: 100%
box-shadow: 0 -8px 8px bulmaRgba($scheme-invert, 0.1)
top: auto
&.is-active,
&.is-hoverable:focus,
&.is-hoverable:focus-within,
&.is-hoverable:hover
.navbar-dropdown
display: block
.navbar.is-spaced &,
&.is-boxed
opacity: 1
pointer-events: auto
transform: translateY(0)
.navbar-menu
flex-grow: 1
flex-shrink: 0
.navbar-start
justify-content: flex-start
+ltr-property("margin", auto)
.navbar-end
justify-content: flex-end
+ltr-property("margin", auto, false)
.navbar-dropdown
background-color: $navbar-dropdown-background-color
border-bottom-left-radius: $navbar-dropdown-radius
border-bottom-right-radius: $navbar-dropdown-radius
border-top: $navbar-dropdown-border-top
box-shadow: 0 8px 8px bulmaRgba($scheme-invert, 0.1)
display: none
font-size: 0.875rem
+ltr-position(0, false)
min-width: 100%
position: absolute
top: 100%
z-index: $navbar-dropdown-z
.navbar-item
padding: 0.375rem 1rem
white-space: nowrap
a.navbar-item
+ltr-property("padding", 3rem)
&:focus,
&:hover
background-color: $navbar-dropdown-item-hover-background-color
color: $navbar-dropdown-item-hover-color
&.is-active
background-color: $navbar-dropdown-item-active-background-color
color: $navbar-dropdown-item-active-color
.navbar.is-spaced &,
&.is-boxed
border-radius: $navbar-dropdown-boxed-radius
border-top: none
box-shadow: $navbar-dropdown-boxed-shadow
display: block
opacity: 0
pointer-events: none
top: calc(100% + (#{$navbar-dropdown-offset}))
transform: translateY(-5px)
transition-duration: $speed
transition-property: opacity, transform
&.is-right
left: auto
right: 0
.navbar-divider
display: block
.navbar > .container,
.container > .navbar
.navbar-brand
+ltr-property("margin", -.75rem, false)
.navbar-menu
+ltr-property("margin", -.75rem)
// Fixed navbar
.navbar
&.is-fixed-bottom-desktop,
&.is-fixed-top-desktop
+navbar-fixed
&.is-fixed-bottom-desktop
bottom: 0
&.has-shadow
box-shadow: 0 -2px 3px bulmaRgba($scheme-invert, 0.1)
&.is-fixed-top-desktop
top: 0
html,
body
&.has-navbar-fixed-top-desktop
padding-top: $navbar-height
&.has-navbar-fixed-bottom-desktop
padding-bottom: $navbar-height
&.has-spaced-navbar-fixed-top
padding-top: $navbar-height + ($navbar-padding-vertical * 2)
&.has-spaced-navbar-fixed-bottom
padding-bottom: $navbar-height + ($navbar-padding-vertical * 2)
// Hover/Active states
a.navbar-item,
.navbar-link
&.is-active
color: $navbar-item-active-color
&.is-active:not(:focus):not(:hover)
background-color: $navbar-item-active-background-color
.navbar-item.has-dropdown
&:focus,
&:hover,
&.is-active
.navbar-link
background-color: $navbar-item-hover-background-color
// Combination
.hero
&.is-fullheight-with-navbar
min-height: calc(100vh - #{$navbar-height})

@ -0,0 +1,150 @@
$pagination-color: $text-strong !default
$pagination-border-color: $border !default
$pagination-margin: -0.25rem !default
$pagination-min-width: $control-height !default
$pagination-item-font-size: 1em !default
$pagination-item-margin: 0.25rem !default
$pagination-item-padding-left: 0.5em !default
$pagination-item-padding-right: 0.5em !default
$pagination-hover-color: $link-hover !default
$pagination-hover-border-color: $link-hover-border !default
$pagination-focus-color: $link-focus !default
$pagination-focus-border-color: $link-focus-border !default
$pagination-active-color: $link-active !default
$pagination-active-border-color: $link-active-border !default
$pagination-disabled-color: $text-light !default
$pagination-disabled-background-color: $border !default
$pagination-disabled-border-color: $border !default
$pagination-current-color: $link-invert !default
$pagination-current-background-color: $link !default
$pagination-current-border-color: $link !default
$pagination-ellipsis-color: $grey-light !default
$pagination-shadow-inset: inset 0 1px 2px rgba($scheme-invert, 0.2)
.pagination
@extend %block
font-size: $size-normal
margin: $pagination-margin
// Sizes
&.is-small
font-size: $size-small
&.is-medium
font-size: $size-medium
&.is-large
font-size: $size-large
&.is-rounded
.pagination-previous,
.pagination-next
padding-left: 1em
padding-right: 1em
border-radius: $radius-rounded
.pagination-link
border-radius: $radius-rounded
.pagination,
.pagination-list
align-items: center
display: flex
justify-content: center
text-align: center
.pagination-previous,
.pagination-next,
.pagination-link,
.pagination-ellipsis
@extend %control
@extend %unselectable
font-size: $pagination-item-font-size
justify-content: center
margin: $pagination-item-margin
padding-left: $pagination-item-padding-left
padding-right: $pagination-item-padding-right
text-align: center
.pagination-previous,
.pagination-next,
.pagination-link
border-color: $pagination-border-color
color: $pagination-color
min-width: $pagination-min-width
&:hover
border-color: $pagination-hover-border-color
color: $pagination-hover-color
&:focus
border-color: $pagination-focus-border-color
&:active
box-shadow: $pagination-shadow-inset
&[disabled]
background-color: $pagination-disabled-background-color
border-color: $pagination-disabled-border-color
box-shadow: none
color: $pagination-disabled-color
opacity: 0.5
.pagination-previous,
.pagination-next
padding-left: 0.75em
padding-right: 0.75em
white-space: nowrap
.pagination-link
&.is-current
background-color: $pagination-current-background-color
border-color: $pagination-current-border-color
color: $pagination-current-color
.pagination-ellipsis
color: $pagination-ellipsis-color
pointer-events: none
.pagination-list
flex-wrap: wrap
+mobile
.pagination
flex-wrap: wrap
.pagination-previous,
.pagination-next
flex-grow: 1
flex-shrink: 1
.pagination-list
li
flex-grow: 1
flex-shrink: 1
+tablet
.pagination-list
flex-grow: 1
flex-shrink: 1
justify-content: flex-start
order: 1
.pagination-previous
order: 2
.pagination-next
order: 3
.pagination
justify-content: space-between
&.is-centered
.pagination-previous
order: 1
.pagination-list
justify-content: center
order: 2
.pagination-next
order: 3
&.is-right
.pagination-previous
order: 1
.pagination-next
order: 2
.pagination-list
justify-content: flex-end
order: 3

@ -0,0 +1,119 @@
$panel-margin: $block-spacing !default
$panel-item-border: 1px solid $border-light !default
$panel-radius: $radius-large !default
$panel-shadow: 0 0.5em 1em -0.125em rgba($scheme-invert, 0.1), 0 0px 0 1px rgba($scheme-invert, 0.02) !default
$panel-heading-background-color: $border-light !default
$panel-heading-color: $text-strong !default
$panel-heading-line-height: 1.25 !default
$panel-heading-padding: 0.75em 1em !default
$panel-heading-radius: $radius !default
$panel-heading-size: 1.25em !default
$panel-heading-weight: $weight-bold !default
$panel-tabs-font-size: 0.875em !default
$panel-tab-border-bottom: 1px solid $border !default
$panel-tab-active-border-bottom-color: $link-active-border !default
$panel-tab-active-color: $link-active !default
$panel-list-item-color: $text !default
$panel-list-item-hover-color: $link !default
$panel-block-color: $text-strong !default
$panel-block-hover-background-color: $background !default
$panel-block-active-border-left-color: $link !default
$panel-block-active-color: $link-active !default
$panel-block-active-icon-color: $link !default
$panel-icon-color: $text-light !default
$panel-colors: $colors !default
.panel
border-radius: $panel-radius
box-shadow: $panel-shadow
font-size: $size-normal
&:not(:last-child)
margin-bottom: $panel-margin
// Colors
@each $name, $components in $panel-colors
$color: nth($components, 1)
$color-invert: nth($components, 2)
&.is-#{$name}
.panel-heading
background-color: $color
color: $color-invert
.panel-tabs a.is-active
border-bottom-color: $color
.panel-block.is-active .panel-icon
color: $color
.panel-tabs,
.panel-block
&:not(:last-child)
border-bottom: $panel-item-border
.panel-heading
background-color: $panel-heading-background-color
border-radius: $panel-radius $panel-radius 0 0
color: $panel-heading-color
font-size: $panel-heading-size
font-weight: $panel-heading-weight
line-height: $panel-heading-line-height
padding: $panel-heading-padding
.panel-tabs
align-items: flex-end
display: flex
font-size: $panel-tabs-font-size
justify-content: center
a
border-bottom: $panel-tab-border-bottom
margin-bottom: -1px
padding: 0.5em
// Modifiers
&.is-active
border-bottom-color: $panel-tab-active-border-bottom-color
color: $panel-tab-active-color
.panel-list
a
color: $panel-list-item-color
&:hover
color: $panel-list-item-hover-color
.panel-block
align-items: center
color: $panel-block-color
display: flex
justify-content: flex-start
padding: 0.5em 0.75em
input[type="checkbox"]
+ltr-property("margin", 0.75em)
& > .control
flex-grow: 1
flex-shrink: 1
width: 100%
&.is-wrapped
flex-wrap: wrap
&.is-active
border-left-color: $panel-block-active-border-left-color
color: $panel-block-active-color
.panel-icon
color: $panel-block-active-icon-color
&:last-child
border-bottom-left-radius: $panel-radius
border-bottom-right-radius: $panel-radius
a.panel-block,
label.panel-block
cursor: pointer
&:hover
background-color: $panel-block-hover-background-color
.panel-icon
+fa(14px, 1em)
color: $panel-icon-color
+ltr-property("margin", 0.75em)
.fa
font-size: inherit
line-height: inherit

@ -0,0 +1,174 @@
$tabs-border-bottom-color: $border !default
$tabs-border-bottom-style: solid !default
$tabs-border-bottom-width: 1px !default
$tabs-link-color: $text !default
$tabs-link-hover-border-bottom-color: $text-strong !default
$tabs-link-hover-color: $text-strong !default
$tabs-link-active-border-bottom-color: $link !default
$tabs-link-active-color: $link !default
$tabs-link-padding: 0.5em 1em !default
$tabs-boxed-link-radius: $radius !default
$tabs-boxed-link-hover-background-color: $background !default
$tabs-boxed-link-hover-border-bottom-color: $border !default
$tabs-boxed-link-active-background-color: $scheme-main !default
$tabs-boxed-link-active-border-color: $border !default
$tabs-boxed-link-active-border-bottom-color: transparent !default
$tabs-toggle-link-border-color: $border !default
$tabs-toggle-link-border-style: solid !default
$tabs-toggle-link-border-width: 1px !default
$tabs-toggle-link-hover-background-color: $background !default
$tabs-toggle-link-hover-border-color: $border-hover !default
$tabs-toggle-link-radius: $radius !default
$tabs-toggle-link-active-background-color: $link !default
$tabs-toggle-link-active-border-color: $link !default
$tabs-toggle-link-active-color: $link-invert !default
.tabs
@extend %block
+overflow-touch
@extend %unselectable
align-items: stretch
display: flex
font-size: $size-normal
justify-content: space-between
overflow: hidden
overflow-x: auto
white-space: nowrap
a
align-items: center
border-bottom-color: $tabs-border-bottom-color
border-bottom-style: $tabs-border-bottom-style
border-bottom-width: $tabs-border-bottom-width
color: $tabs-link-color
display: flex
justify-content: center
margin-bottom: -#{$tabs-border-bottom-width}
padding: $tabs-link-padding
vertical-align: top
&:hover
border-bottom-color: $tabs-link-hover-border-bottom-color
color: $tabs-link-hover-color
li
display: block
&.is-active
a
border-bottom-color: $tabs-link-active-border-bottom-color
color: $tabs-link-active-color
ul
align-items: center
border-bottom-color: $tabs-border-bottom-color
border-bottom-style: $tabs-border-bottom-style
border-bottom-width: $tabs-border-bottom-width
display: flex
flex-grow: 1
flex-shrink: 0
justify-content: flex-start
&.is-left
padding-right: 0.75em
&.is-center
flex: none
justify-content: center
padding-left: 0.75em
padding-right: 0.75em
&.is-right
justify-content: flex-end
padding-left: 0.75em
.icon
&:first-child
+ltr-property("margin", 0.5em)
&:last-child
+ltr-property("margin", 0.5em, false)
// Alignment
&.is-centered
ul
justify-content: center
&.is-right
ul
justify-content: flex-end
// Styles
&.is-boxed
a
border: 1px solid transparent
+ltr
border-radius: $tabs-boxed-link-radius $tabs-boxed-link-radius 0 0
+rtl
border-radius: 0 0 $tabs-boxed-link-radius $tabs-boxed-link-radius
&:hover
background-color: $tabs-boxed-link-hover-background-color
border-bottom-color: $tabs-boxed-link-hover-border-bottom-color
li
&.is-active
a
background-color: $tabs-boxed-link-active-background-color
border-color: $tabs-boxed-link-active-border-color
border-bottom-color: $tabs-boxed-link-active-border-bottom-color !important
&.is-fullwidth
li
flex-grow: 1
flex-shrink: 0
&.is-toggle
a
border-color: $tabs-toggle-link-border-color
border-style: $tabs-toggle-link-border-style
border-width: $tabs-toggle-link-border-width
margin-bottom: 0
position: relative
&:hover
background-color: $tabs-toggle-link-hover-background-color
border-color: $tabs-toggle-link-hover-border-color
z-index: 2
li
& + li
+ltr-property("margin", -#{$tabs-toggle-link-border-width}, false)
&:first-child a
+ltr
border-top-left-radius: $tabs-toggle-link-radius
border-bottom-left-radius: $tabs-toggle-link-radius
+rtl
border-top-right-radius: $tabs-toggle-link-radius
border-bottom-right-radius: $tabs-toggle-link-radius
&:last-child a
+ltr
border-top-right-radius: $tabs-toggle-link-radius
border-bottom-right-radius: $tabs-toggle-link-radius
+rtl
border-top-left-radius: $tabs-toggle-link-radius
border-bottom-left-radius: $tabs-toggle-link-radius
&.is-active
a
background-color: $tabs-toggle-link-active-background-color
border-color: $tabs-toggle-link-active-border-color
color: $tabs-toggle-link-active-color
z-index: 1
ul
border-bottom: none
&.is-toggle-rounded
li
&:first-child a
+ltr
border-bottom-left-radius: $radius-rounded
border-top-left-radius: $radius-rounded
padding-left: 1.25em
+rtl
border-bottom-right-radius: $radius-rounded
border-top-right-radius: $radius-rounded
padding-right: 1.25em
&:last-child a
+ltr
border-bottom-right-radius: $radius-rounded
border-top-right-radius: $radius-rounded
padding-right: 1.25em
+rtl
border-bottom-left-radius: $radius-rounded
border-top-left-radius: $radius-rounded
padding-left: 1.25em
// Sizes
&.is-small
font-size: $size-small
&.is-medium
font-size: $size-medium
&.is-large
font-size: $size-large

@ -0,0 +1,16 @@
/* Bulma Elements */
@charset "utf-8"
@import "box.sass"
@import "button.sass"
@import "container.sass"
@import "content.sass"
@import "icon.sass"
@import "image.sass"
@import "notification.sass"
@import "progress.sass"
@import "table.sass"
@import "tag.sass"
@import "title.sass"
@import "other.sass"

@ -0,0 +1,24 @@
$box-color: $text !default
$box-background-color: $scheme-main !default
$box-radius: $radius-large !default
$box-shadow: 0 0.5em 1em -0.125em rgba($scheme-invert, 0.1), 0 0px 0 1px rgba($scheme-invert, 0.02) !default
$box-padding: 1.25rem !default
$box-link-hover-shadow: 0 0.5em 1em -0.125em rgba($scheme-invert, 0.1), 0 0 0 1px $link !default
$box-link-active-shadow: inset 0 1px 2px rgba($scheme-invert, 0.2), 0 0 0 1px $link !default
.box
@extend %block
background-color: $box-background-color
border-radius: $box-radius
box-shadow: $box-shadow
color: $box-color
display: block
padding: $box-padding
a.box
&:hover,
&:focus
box-shadow: $box-link-hover-shadow
&:active
box-shadow: $box-link-active-shadow

@ -0,0 +1,325 @@
$button-color: $text-strong !default
$button-background-color: $scheme-main !default
$button-family: false !default
$button-border-color: $border !default
$button-border-width: $control-border-width !default
$button-padding-vertical: calc(0.5em - #{$button-border-width}) !default
$button-padding-horizontal: 1em !default
$button-hover-color: $link-hover !default
$button-hover-border-color: $link-hover-border !default
$button-focus-color: $link-focus !default
$button-focus-border-color: $link-focus-border !default
$button-focus-box-shadow-size: 0 0 0 0.125em !default
$button-focus-box-shadow-color: bulmaRgba($link, 0.25) !default
$button-active-color: $link-active !default
$button-active-border-color: $link-active-border !default
$button-text-color: $text !default
$button-text-decoration: underline !default
$button-text-hover-background-color: $background !default
$button-text-hover-color: $text-strong !default
$button-disabled-background-color: $scheme-main !default
$button-disabled-border-color: $border !default
$button-disabled-shadow: none !default
$button-disabled-opacity: 0.5 !default
$button-static-color: $text-light !default
$button-static-background-color: $scheme-main-ter !default
$button-static-border-color: $border !default
$button-colors: $colors !default
// The button sizes use mixins so they can be used at different breakpoints
=button-small
border-radius: $radius-small
font-size: $size-small
=button-normal
font-size: $size-normal
=button-medium
font-size: $size-medium
=button-large
font-size: $size-large
.button
@extend %control
@extend %unselectable
background-color: $button-background-color
border-color: $button-border-color
border-width: $button-border-width
color: $button-color
cursor: pointer
@if $button-family
font-family: $button-family
justify-content: center
padding-bottom: $button-padding-vertical
padding-left: $button-padding-horizontal
padding-right: $button-padding-horizontal
padding-top: $button-padding-vertical
text-align: center
white-space: nowrap
strong
color: inherit
.icon
&,
&.is-small,
&.is-medium,
&.is-large
height: 1.5em
width: 1.5em
&:first-child:not(:last-child)
+ltr-property("margin", calc(#{-1 / 2 * $button-padding-horizontal} - #{$button-border-width}), false)
+ltr-property("margin", $button-padding-horizontal / 4)
&:last-child:not(:first-child)
+ltr-property("margin", $button-padding-horizontal / 4, false)
+ltr-property("margin", calc(#{-1 / 2 * $button-padding-horizontal} - #{$button-border-width}))
&:first-child:last-child
margin-left: calc(#{-1 / 2 * $button-padding-horizontal} - #{$button-border-width})
margin-right: calc(#{-1 / 2 * $button-padding-horizontal} - #{$button-border-width})
// States
&:hover,
&.is-hovered
border-color: $button-hover-border-color
color: $button-hover-color
&:focus,
&.is-focused
border-color: $button-focus-border-color
color: $button-focus-color
&:not(:active)
box-shadow: $button-focus-box-shadow-size $button-focus-box-shadow-color
&:active,
&.is-active
border-color: $button-active-border-color
color: $button-active-color
// Colors
&.is-text
background-color: transparent
border-color: transparent
color: $button-text-color
text-decoration: $button-text-decoration
&:hover,
&.is-hovered,
&:focus,
&.is-focused
background-color: $button-text-hover-background-color
color: $button-text-hover-color
&:active,
&.is-active
background-color: bulmaDarken($button-text-hover-background-color, 5%)
color: $button-text-hover-color
&[disabled],
fieldset[disabled] &
background-color: transparent
border-color: transparent
box-shadow: none
@each $name, $pair in $button-colors
$color: nth($pair, 1)
$color-invert: nth($pair, 2)
&.is-#{$name}
background-color: $color
border-color: transparent
color: $color-invert
&:hover,
&.is-hovered
background-color: bulmaDarken($color, 2.5%)
border-color: transparent
color: $color-invert
&:focus,
&.is-focused
border-color: transparent
color: $color-invert
&:not(:active)
box-shadow: $button-focus-box-shadow-size bulmaRgba($color, 0.25)
&:active,
&.is-active
background-color: bulmaDarken($color, 5%)
border-color: transparent
color: $color-invert
&[disabled],
fieldset[disabled] &
background-color: $color
border-color: transparent
box-shadow: none
&.is-inverted
background-color: $color-invert
color: $color
&:hover,
&.is-hovered
background-color: bulmaDarken($color-invert, 5%)
&[disabled],
fieldset[disabled] &
background-color: $color-invert
border-color: transparent
box-shadow: none
color: $color
&.is-loading
&::after
border-color: transparent transparent $color-invert $color-invert !important
&.is-outlined
background-color: transparent
border-color: $color
color: $color
&:hover,
&.is-hovered,
&:focus,
&.is-focused
background-color: $color
border-color: $color
color: $color-invert
&.is-loading
&::after
border-color: transparent transparent $color $color !important
&:hover,
&.is-hovered,
&:focus,
&.is-focused
&::after
border-color: transparent transparent $color-invert $color-invert !important
&[disabled],
fieldset[disabled] &
background-color: transparent
border-color: $color
box-shadow: none
color: $color
&.is-inverted.is-outlined
background-color: transparent
border-color: $color-invert
color: $color-invert
&:hover,
&.is-hovered,
&:focus,
&.is-focused
background-color: $color-invert
color: $color
&.is-loading
&:hover,
&.is-hovered,
&:focus,
&.is-focused
&::after
border-color: transparent transparent $color $color !important
&[disabled],
fieldset[disabled] &
background-color: transparent
border-color: $color-invert
box-shadow: none
color: $color-invert
// If light and dark colors are provided
@if length($pair) >= 4
$color-light: nth($pair, 3)
$color-dark: nth($pair, 4)
&.is-light
background-color: $color-light
color: $color-dark
&:hover,
&.is-hovered
background-color: bulmaDarken($color-light, 2.5%)
border-color: transparent
color: $color-dark
&:active,
&.is-active
background-color: bulmaDarken($color-light, 5%)
border-color: transparent
color: $color-dark
// Sizes
&.is-small
+button-small
&.is-normal
+button-normal
&.is-medium
+button-medium
&.is-large
+button-large
// Modifiers
&[disabled],
fieldset[disabled] &
background-color: $button-disabled-background-color
border-color: $button-disabled-border-color
box-shadow: $button-disabled-shadow
opacity: $button-disabled-opacity
&.is-fullwidth
display: flex
width: 100%
&.is-loading
color: transparent !important
pointer-events: none
&::after
@extend %loader
+center(1em)
position: absolute !important
&.is-static
background-color: $button-static-background-color
border-color: $button-static-border-color
color: $button-static-color
box-shadow: none
pointer-events: none
&.is-rounded
border-radius: $radius-rounded
padding-left: calc(#{$button-padding-horizontal} + 0.25em)
padding-right: calc(#{$button-padding-horizontal} + 0.25em)
.buttons
align-items: center
display: flex
flex-wrap: wrap
justify-content: flex-start
.button
margin-bottom: 0.5rem
&:not(:last-child):not(.is-fullwidth)
+ltr-property("margin", 0.5rem)
&:last-child
margin-bottom: -0.5rem
&:not(:last-child)
margin-bottom: 1rem
// Sizes
&.are-small
.button:not(.is-normal):not(.is-medium):not(.is-large)
+button-small
&.are-medium
.button:not(.is-small):not(.is-normal):not(.is-large)
+button-medium
&.are-large
.button:not(.is-small):not(.is-normal):not(.is-medium)
+button-large
&.has-addons
.button
&:not(:first-child)
border-bottom-left-radius: 0
border-top-left-radius: 0
&:not(:last-child)
border-bottom-right-radius: 0
border-top-right-radius: 0
+ltr-property("margin", -1px)
&:last-child
+ltr-property("margin", 0)
&:hover,
&.is-hovered
z-index: 2
&:focus,
&.is-focused,
&:active,
&.is-active,
&.is-selected
z-index: 3
&:hover
z-index: 4
&.is-expanded
flex-grow: 1
flex-shrink: 1
&.is-centered
justify-content: center
&:not(.has-addons)
.button:not(.is-fullwidth)
margin-left: 0.25rem
margin-right: 0.25rem
&.is-right
justify-content: flex-end
&:not(.has-addons)
.button:not(.is-fullwidth)
margin-left: 0.25rem
margin-right: 0.25rem

@ -0,0 +1,27 @@
$container-offset: (2 * $gap) !default
$container-max-width: $fullhd !default
.container
flex-grow: 1
margin: 0 auto
position: relative
width: auto
&.is-fluid
max-width: none !important
padding-left: $gap
padding-right: $gap
width: 100%
+desktop
max-width: $desktop - $container-offset
+until-widescreen
&.is-widescreen:not(.is-max-desktop)
max-width: min($widescreen, $container-max-width) - $container-offset
+until-fullhd
&.is-fullhd:not(.is-max-desktop):not(.is-max-widescreen)
max-width: min($fullhd, $container-max-width) - $container-offset
+widescreen
&:not(.is-max-desktop)
max-width: min($widescreen, $container-max-width) - $container-offset
+fullhd
&:not(.is-max-desktop):not(.is-max-widescreen)
max-width: min($fullhd, $container-max-width) - $container-offset

@ -0,0 +1,155 @@
$content-heading-color: $text-strong !default
$content-heading-weight: $weight-semibold !default
$content-heading-line-height: 1.125 !default
$content-blockquote-background-color: $background !default
$content-blockquote-border-left: 5px solid $border !default
$content-blockquote-padding: 1.25em 1.5em !default
$content-pre-padding: 1.25em 1.5em !default
$content-table-cell-border: 1px solid $border !default
$content-table-cell-border-width: 0 0 1px !default
$content-table-cell-padding: 0.5em 0.75em !default
$content-table-cell-heading-color: $text-strong !default
$content-table-head-cell-border-width: 0 0 2px !default
$content-table-head-cell-color: $text-strong !default
$content-table-foot-cell-border-width: 2px 0 0 !default
$content-table-foot-cell-color: $text-strong !default
.content
@extend %block
// Inline
li + li
margin-top: 0.25em
// Block
p,
dl,
ol,
ul,
blockquote,
pre,
table
&:not(:last-child)
margin-bottom: 1em
h1,
h2,
h3,
h4,
h5,
h6
color: $content-heading-color
font-weight: $content-heading-weight
line-height: $content-heading-line-height
h1
font-size: 2em
margin-bottom: 0.5em
&:not(:first-child)
margin-top: 1em
h2
font-size: 1.75em
margin-bottom: 0.5714em
&:not(:first-child)
margin-top: 1.1428em
h3
font-size: 1.5em
margin-bottom: 0.6666em
&:not(:first-child)
margin-top: 1.3333em
h4
font-size: 1.25em
margin-bottom: 0.8em
h5
font-size: 1.125em
margin-bottom: 0.8888em
h6
font-size: 1em
margin-bottom: 1em
blockquote
background-color: $content-blockquote-background-color
+ltr-property("border", $content-blockquote-border-left, false)
padding: $content-blockquote-padding
ol
list-style-position: outside
+ltr-property("margin", 2em, false)
margin-top: 1em
&:not([type])
list-style-type: decimal
&.is-lower-alpha
list-style-type: lower-alpha
&.is-lower-roman
list-style-type: lower-roman
&.is-upper-alpha
list-style-type: upper-alpha
&.is-upper-roman
list-style-type: upper-roman
ul
list-style: disc outside
+ltr-property("margin", 2em, false)
margin-top: 1em
ul
list-style-type: circle
margin-top: 0.5em
ul
list-style-type: square
dd
+ltr-property("margin", 2em, false)
figure
margin-left: 2em
margin-right: 2em
text-align: center
&:not(:first-child)
margin-top: 2em
&:not(:last-child)
margin-bottom: 2em
img
display: inline-block
figcaption
font-style: italic
pre
+overflow-touch
overflow-x: auto
padding: $content-pre-padding
white-space: pre
word-wrap: normal
sup,
sub
font-size: 75%
table
width: 100%
td,
th
border: $content-table-cell-border
border-width: $content-table-cell-border-width
padding: $content-table-cell-padding
vertical-align: top
th
color: $content-table-cell-heading-color
&:not([align])
text-align: inherit
thead
td,
th
border-width: $content-table-head-cell-border-width
color: $content-table-head-cell-color
tfoot
td,
th
border-width: $content-table-foot-cell-border-width
color: $content-table-foot-cell-color
tbody
tr
&:last-child
td,
th
border-bottom-width: 0
.tabs
li + li
margin-top: 0
// Sizes
&.is-small
font-size: $size-small
&.is-medium
font-size: $size-medium
&.is-large
font-size: $size-large

@ -0,0 +1 @@
@warn "The form.sass file is DEPRECATED. It has moved into its own /form folder. Please import sass/form/_all instead."

@ -0,0 +1,21 @@
$icon-dimensions: 1.5rem !default
$icon-dimensions-small: 1rem !default
$icon-dimensions-medium: 2rem !default
$icon-dimensions-large: 3rem !default
.icon
align-items: center
display: inline-flex
justify-content: center
height: $icon-dimensions
width: $icon-dimensions
// Sizes
&.is-small
height: $icon-dimensions-small
width: $icon-dimensions-small
&.is-medium
height: $icon-dimensions-medium
width: $icon-dimensions-medium
&.is-large
height: $icon-dimensions-large
width: $icon-dimensions-large

@ -0,0 +1,71 @@
$dimensions: 16 24 32 48 64 96 128 !default
.image
display: block
position: relative
img
display: block
height: auto
width: 100%
&.is-rounded
border-radius: $radius-rounded
&.is-fullwidth
width: 100%
// Ratio
&.is-square,
&.is-1by1,
&.is-5by4,
&.is-4by3,
&.is-3by2,
&.is-5by3,
&.is-16by9,
&.is-2by1,
&.is-3by1,
&.is-4by5,
&.is-3by4,
&.is-2by3,
&.is-3by5,
&.is-9by16,
&.is-1by2,
&.is-1by3
img,
.has-ratio
@extend %overlay
height: 100%
width: 100%
&.is-square,
&.is-1by1
padding-top: 100%
&.is-5by4
padding-top: 80%
&.is-4by3
padding-top: 75%
&.is-3by2
padding-top: 66.6666%
&.is-5by3
padding-top: 60%
&.is-16by9
padding-top: 56.25%
&.is-2by1
padding-top: 50%
&.is-3by1
padding-top: 33.3333%
&.is-4by5
padding-top: 125%
&.is-3by4
padding-top: 133.3333%
&.is-2by3
padding-top: 150%
&.is-3by5
padding-top: 166.6666%
&.is-9by16
padding-top: 177.7777%
&.is-1by2
padding-top: 200%
&.is-1by3
padding-top: 300%
// Sizes
@each $dimension in $dimensions
&.is-#{$dimension}x#{$dimension}
height: $dimension * 1px
width: $dimension * 1px

@ -0,0 +1,50 @@
$notification-background-color: $background !default
$notification-code-background-color: $scheme-main !default
$notification-radius: $radius !default
$notification-padding: 1.25rem 2.5rem 1.25rem 1.5rem !default
$notification-padding-ltr: 1.25rem 2.5rem 1.25rem 1.5rem !default
$notification-padding-rtl: 1.25rem 1.5rem 1.25rem 2.5rem !default
$notification-colors: $colors !default
.notification
@extend %block
background-color: $notification-background-color
border-radius: $notification-radius
position: relative
+ltr
padding: $notification-padding-ltr
+rtl
padding: $notification-padding-rtl
a:not(.button):not(.dropdown-item)
color: currentColor
text-decoration: underline
strong
color: currentColor
code,
pre
background: $notification-code-background-color
pre code
background: transparent
& > .delete
+ltr-position(0.5rem)
position: absolute
top: 0.5rem
.title,
.subtitle,
.content
color: currentColor
// Colors
@each $name, $pair in $notification-colors
$color: nth($pair, 1)
$color-invert: nth($pair, 2)
&.is-#{$name}
background-color: $color
color: $color-invert
// If light and dark colors are provided
@if length($pair) >= 4
$color-light: nth($pair, 3)
$color-dark: nth($pair, 4)
&.is-light
background-color: $color-light
color: $color-dark

@ -0,0 +1,39 @@
.block
@extend %block
.delete
@extend %delete
.heading
display: block
font-size: 11px
letter-spacing: 1px
margin-bottom: 5px
text-transform: uppercase
.highlight
@extend %block
font-weight: $weight-normal
max-width: 100%
overflow: hidden
padding: 0
pre
overflow: auto
max-width: 100%
.loader
@extend %loader
.number
align-items: center
background-color: $background
border-radius: $radius-rounded
display: inline-flex
font-size: $size-medium
height: 2em
justify-content: center
margin-right: 1.5rem
min-width: 2.5em
padding: 0.25rem 0.5rem
text-align: center
vertical-align: top

@ -0,0 +1,71 @@
$progress-bar-background-color: $border-light !default
$progress-value-background-color: $text !default
$progress-border-radius: $radius-rounded !default
$progress-indeterminate-duration: 1.5s !default
$progress-colors: $colors !default
.progress
@extend %block
-moz-appearance: none
-webkit-appearance: none
border: none
border-radius: $progress-border-radius
display: block
height: $size-normal
overflow: hidden
padding: 0
width: 100%
&::-webkit-progress-bar
background-color: $progress-bar-background-color
&::-webkit-progress-value
background-color: $progress-value-background-color
&::-moz-progress-bar
background-color: $progress-value-background-color
&::-ms-fill
background-color: $progress-value-background-color
border: none
// Colors
@each $name, $pair in $progress-colors
$color: nth($pair, 1)
&.is-#{$name}
&::-webkit-progress-value
background-color: $color
&::-moz-progress-bar
background-color: $color
&::-ms-fill
background-color: $color
&:indeterminate
background-image: linear-gradient(to right, $color 30%, $progress-bar-background-color 30%)
&:indeterminate
animation-duration: $progress-indeterminate-duration
animation-iteration-count: infinite
animation-name: moveIndeterminate
animation-timing-function: linear
background-color: $progress-bar-background-color
background-image: linear-gradient(to right, $text 30%, $progress-bar-background-color 30%)
background-position: top left
background-repeat: no-repeat
background-size: 150% 150%
&::-webkit-progress-bar
background-color: transparent
&::-moz-progress-bar
background-color: transparent
&::-ms-fill
animation-name: none
// Sizes
&.is-small
height: $size-small
&.is-medium
height: $size-medium
&.is-large
height: $size-large
@keyframes moveIndeterminate
from
background-position: 200% 0
to
background-position: -200% 0

@ -0,0 +1,131 @@
$table-color: $text-strong !default
$table-background-color: $scheme-main !default
$table-cell-border: 1px solid $border !default
$table-cell-border-width: 0 0 1px !default
$table-cell-padding: 0.5em 0.75em !default
$table-cell-heading-color: $text-strong !default
$table-head-cell-border-width: 0 0 2px !default
$table-head-cell-color: $text-strong !default
$table-foot-cell-border-width: 2px 0 0 !default
$table-foot-cell-color: $text-strong !default
$table-head-background-color: transparent !default
$table-body-background-color: transparent !default
$table-foot-background-color: transparent !default
$table-row-hover-background-color: $scheme-main-bis !default
$table-row-active-background-color: $primary !default
$table-row-active-color: $primary-invert !default
$table-striped-row-even-background-color: $scheme-main-bis !default
$table-striped-row-even-hover-background-color: $scheme-main-ter !default
$table-colors: $colors !default
.table
@extend %block
background-color: $table-background-color
color: $table-color
td,
th
border: $table-cell-border
border-width: $table-cell-border-width
padding: $table-cell-padding
vertical-align: top
// Colors
@each $name, $pair in $table-colors
$color: nth($pair, 1)
$color-invert: nth($pair, 2)
&.is-#{$name}
background-color: $color
border-color: $color
color: $color-invert
// Modifiers
&.is-narrow
white-space: nowrap
width: 1%
&.is-selected
background-color: $table-row-active-background-color
color: $table-row-active-color
a,
strong
color: currentColor
&.is-vcentered
vertical-align: middle
th
color: $table-cell-heading-color
&:not([align])
text-align: inherit
tr
&.is-selected
background-color: $table-row-active-background-color
color: $table-row-active-color
a,
strong
color: currentColor
td,
th
border-color: $table-row-active-color
color: currentColor
thead
background-color: $table-head-background-color
td,
th
border-width: $table-head-cell-border-width
color: $table-head-cell-color
tfoot
background-color: $table-foot-background-color
td,
th
border-width: $table-foot-cell-border-width
color: $table-foot-cell-color
tbody
background-color: $table-body-background-color
tr
&:last-child
td,
th
border-bottom-width: 0
// Modifiers
&.is-bordered
td,
th
border-width: 1px
tr
&:last-child
td,
th
border-bottom-width: 1px
&.is-fullwidth
width: 100%
&.is-hoverable
tbody
tr:not(.is-selected)
&:hover
background-color: $table-row-hover-background-color
&.is-striped
tbody
tr:not(.is-selected)
&:hover
background-color: $table-row-hover-background-color
&:nth-child(even)
background-color: $table-striped-row-even-hover-background-color
&.is-narrow
td,
th
padding: 0.25em 0.5em
&.is-striped
tbody
tr:not(.is-selected)
&:nth-child(even)
background-color: $table-striped-row-even-background-color
.table-container
@extend %block
+overflow-touch
overflow: auto
overflow-y: hidden
max-width: 100%

@ -0,0 +1,138 @@
$tag-background-color: $background !default
$tag-color: $text !default
$tag-radius: $radius !default
$tag-delete-margin: 1px !default
$tag-colors: $colors !default
.tags
align-items: center
display: flex
flex-wrap: wrap
justify-content: flex-start
.tag
margin-bottom: 0.5rem
&:not(:last-child)
+ltr-property("margin", 0.5rem)
&:last-child
margin-bottom: -0.5rem
&:not(:last-child)
margin-bottom: 1rem
// Sizes
&.are-medium
.tag:not(.is-normal):not(.is-large)
font-size: $size-normal
&.are-large
.tag:not(.is-normal):not(.is-medium)
font-size: $size-medium
&.is-centered
justify-content: center
.tag
margin-right: 0.25rem
margin-left: 0.25rem
&.is-right
justify-content: flex-end
.tag
&:not(:first-child)
margin-left: 0.5rem
&:not(:last-child)
margin-right: 0
&.has-addons
.tag
+ltr-property("margin", 0)
&:not(:first-child)
+ltr-property("margin", 0, false)
+ltr
border-top-left-radius: 0
border-bottom-left-radius: 0
+rtl
border-top-right-radius: 0
border-bottom-right-radius: 0
&:not(:last-child)
+ltr
border-top-right-radius: 0
border-bottom-right-radius: 0
+rtl
border-top-left-radius: 0
border-bottom-left-radius: 0
.tag:not(body)
align-items: center
background-color: $tag-background-color
border-radius: $tag-radius
color: $tag-color
display: inline-flex
font-size: $size-small
height: 2em
justify-content: center
line-height: 1.5
padding-left: 0.75em
padding-right: 0.75em
white-space: nowrap
.delete
+ltr-property("margin", 0.25rem, false)
+ltr-property("margin", -0.375rem)
// Colors
@each $name, $pair in $tag-colors
$color: nth($pair, 1)
$color-invert: nth($pair, 2)
&.is-#{$name}
background-color: $color
color: $color-invert
// If a light and dark colors are provided
@if length($pair) > 3
$color-light: nth($pair, 3)
$color-dark: nth($pair, 4)
&.is-light
background-color: $color-light
color: $color-dark
// Sizes
&.is-normal
font-size: $size-small
&.is-medium
font-size: $size-normal
&.is-large
font-size: $size-medium
.icon
&:first-child:not(:last-child)
+ltr-property("margin", -0.375em, false)
+ltr-property("margin", 0.1875em)
&:last-child:not(:first-child)
+ltr-property("margin", 0.1875em, false)
+ltr-property("margin", -0.375em)
&:first-child:last-child
+ltr-property("margin", -0.375em, false)
+ltr-property("margin", -0.375em)
// Modifiers
&.is-delete
+ltr-property("margin", $tag-delete-margin, false)
padding: 0
position: relative
width: 2em
&::before,
&::after
background-color: currentColor
content: ""
display: block
left: 50%
position: absolute
top: 50%
transform: translateX(-50%) translateY(-50%) rotate(45deg)
transform-origin: center center
&::before
height: 1px
width: 50%
&::after
height: 50%
width: 1px
&:hover,
&:focus
background-color: darken($tag-background-color, 5%)
&:active
background-color: darken($tag-background-color, 10%)
&.is-rounded
border-radius: $radius-rounded
a.tag
&:hover
text-decoration: underline

@ -0,0 +1,70 @@
$title-color: $text-strong !default
$title-family: false !default
$title-size: $size-3 !default
$title-weight: $weight-semibold !default
$title-line-height: 1.125 !default
$title-strong-color: inherit !default
$title-strong-weight: inherit !default
$title-sub-size: 0.75em !default
$title-sup-size: 0.75em !default
$subtitle-color: $text !default
$subtitle-family: false !default
$subtitle-size: $size-5 !default
$subtitle-weight: $weight-normal !default
$subtitle-line-height: 1.25 !default
$subtitle-strong-color: $text-strong !default
$subtitle-strong-weight: $weight-semibold !default
$subtitle-negative-margin: -1.25rem !default
.title,
.subtitle
@extend %block
word-break: break-word
em,
span
font-weight: inherit
sub
font-size: $title-sub-size
sup
font-size: $title-sup-size
.tag
vertical-align: middle
.title
color: $title-color
@if $title-family
font-family: $title-family
font-size: $title-size
font-weight: $title-weight
line-height: $title-line-height
strong
color: $title-strong-color
font-weight: $title-strong-weight
& + .highlight
margin-top: -0.75rem
&:not(.is-spaced) + .subtitle
margin-top: $subtitle-negative-margin
// Sizes
@each $size in $sizes
$i: index($sizes, $size)
&.is-#{$i}
font-size: $size
.subtitle
color: $subtitle-color
@if $subtitle-family
font-family: $subtitle-family
font-size: $subtitle-size
font-weight: $subtitle-weight
line-height: $subtitle-line-height
strong
color: $subtitle-strong-color
font-weight: $subtitle-strong-weight
&:not(.is-spaced) + .title
margin-top: $subtitle-negative-margin
// Sizes
@each $size in $sizes
$i: index($sizes, $size)
&.is-#{$i}
font-size: $size

@ -0,0 +1,9 @@
/* Bulma Form */
@charset "utf-8"
@import "shared.sass"
@import "input-textarea.sass"
@import "checkbox-radio.sass"
@import "select.sass"
@import "file.sass"
@import "tools.sass"

@ -0,0 +1,22 @@
%checkbox-radio
cursor: pointer
display: inline-block
line-height: 1.25
position: relative
input
cursor: pointer
&:hover
color: $input-hover-color
&[disabled],
fieldset[disabled] &,
input[disabled]
color: $input-disabled-color
cursor: not-allowed
.checkbox
@extend %checkbox-radio
.radio
@extend %checkbox-radio
& + .radio
+ltr-property("margin", 0.5em, false)

@ -0,0 +1,182 @@
$file-border-color: $border !default
$file-radius: $radius !default
$file-cta-background-color: $scheme-main-ter !default
$file-cta-color: $text !default
$file-cta-hover-color: $text-strong !default
$file-cta-active-color: $text-strong !default
$file-name-border-color: $border !default
$file-name-border-style: solid !default
$file-name-border-width: 1px 1px 1px 0 !default
$file-name-max-width: 16em !default
$file-colors: $form-colors !default
.file
@extend %unselectable
align-items: stretch
display: flex
justify-content: flex-start
position: relative
// Colors
@each $name, $pair in $file-colors
$color: nth($pair, 1)
$color-invert: nth($pair, 2)
&.is-#{$name}
.file-cta
background-color: $color
border-color: transparent
color: $color-invert
&:hover,
&.is-hovered
.file-cta
background-color: bulmaDarken($color, 2.5%)
border-color: transparent
color: $color-invert
&:focus,
&.is-focused
.file-cta
border-color: transparent
box-shadow: 0 0 0.5em bulmaRgba($color, 0.25)
color: $color-invert
&:active,
&.is-active
.file-cta
background-color: bulmaDarken($color, 5%)
border-color: transparent
color: $color-invert
// Sizes
&.is-small
font-size: $size-small
&.is-medium
font-size: $size-medium
.file-icon
.fa
font-size: 21px
&.is-large
font-size: $size-large
.file-icon
.fa
font-size: 28px
// Modifiers
&.has-name
.file-cta
border-bottom-right-radius: 0
border-top-right-radius: 0
.file-name
border-bottom-left-radius: 0
border-top-left-radius: 0
&.is-empty
.file-cta
border-radius: $file-radius
.file-name
display: none
&.is-boxed
.file-label
flex-direction: column
.file-cta
flex-direction: column
height: auto
padding: 1em 3em
.file-name
border-width: 0 1px 1px
.file-icon
height: 1.5em
width: 1.5em
.fa
font-size: 21px
&.is-small
.file-icon .fa
font-size: 14px
&.is-medium
.file-icon .fa
font-size: 28px
&.is-large
.file-icon .fa
font-size: 35px
&.has-name
.file-cta
border-radius: $file-radius $file-radius 0 0
.file-name
border-radius: 0 0 $file-radius $file-radius
border-width: 0 1px 1px
&.is-centered
justify-content: center
&.is-fullwidth
.file-label
width: 100%
.file-name
flex-grow: 1
max-width: none
&.is-right
justify-content: flex-end
.file-cta
border-radius: 0 $file-radius $file-radius 0
.file-name
border-radius: $file-radius 0 0 $file-radius
border-width: 1px 0 1px 1px
order: -1
.file-label
align-items: stretch
display: flex
cursor: pointer
justify-content: flex-start
overflow: hidden
position: relative
&:hover
.file-cta
background-color: bulmaDarken($file-cta-background-color, 2.5%)
color: $file-cta-hover-color
.file-name
border-color: bulmaDarken($file-name-border-color, 2.5%)
&:active
.file-cta
background-color: bulmaDarken($file-cta-background-color, 5%)
color: $file-cta-active-color
.file-name
border-color: bulmaDarken($file-name-border-color, 5%)
.file-input
height: 100%
left: 0
opacity: 0
outline: none
position: absolute
top: 0
width: 100%
.file-cta,
.file-name
@extend %control
border-color: $file-border-color
border-radius: $file-radius
font-size: 1em
padding-left: 1em
padding-right: 1em
white-space: nowrap
.file-cta
background-color: $file-cta-background-color
color: $file-cta-color
.file-name
border-color: $file-name-border-color
border-style: $file-name-border-style
border-width: $file-name-border-width
display: block
max-width: $file-name-max-width
overflow: hidden
text-align: inherit
text-overflow: ellipsis
.file-icon
align-items: center
display: flex
height: 1em
justify-content: center
+ltr-property("margin", 0.5em)
width: 1em
.fa
font-size: 14px

@ -0,0 +1,66 @@
$textarea-padding: $control-padding-horizontal !default
$textarea-max-height: 40em !default
$textarea-min-height: 8em !default
$textarea-colors: $form-colors !default
%input-textarea
@extend %input
box-shadow: $input-shadow
max-width: 100%
width: 100%
&[readonly]
box-shadow: none
// Colors
@each $name, $pair in $textarea-colors
$color: nth($pair, 1)
&.is-#{$name}
border-color: $color
&:focus,
&.is-focused,
&:active,
&.is-active
box-shadow: $input-focus-box-shadow-size bulmaRgba($color, 0.25)
// Sizes
&.is-small
+control-small
&.is-medium
+control-medium
&.is-large
+control-large
// Modifiers
&.is-fullwidth
display: block
width: 100%
&.is-inline
display: inline
width: auto
.input
@extend %input-textarea
&.is-rounded
border-radius: $radius-rounded
padding-left: calc(#{$control-padding-horizontal} + 0.375em)
padding-right: calc(#{$control-padding-horizontal} + 0.375em)
&.is-static
background-color: transparent
border-color: transparent
box-shadow: none
padding-left: 0
padding-right: 0
.textarea
@extend %input-textarea
display: block
max-width: 100%
min-width: 100%
padding: $textarea-padding
resize: vertical
&:not([rows])
max-height: $textarea-max-height
min-height: $textarea-min-height
&[rows]
height: initial
// Modifiers
&.has-fixed-size
resize: none

@ -0,0 +1,87 @@
$select-colors: $form-colors !default
.select
display: inline-block
max-width: 100%
position: relative
vertical-align: top
&:not(.is-multiple)
height: $input-height
&:not(.is-multiple):not(.is-loading)
&::after
@extend %arrow
border-color: $input-arrow
+ltr-position(1.125em)
z-index: 4
&.is-rounded
select
border-radius: $radius-rounded
+ltr-property("padding", 1em, false)
select
@extend %input
cursor: pointer
display: block
font-size: 1em
max-width: 100%
outline: none
&::-ms-expand
display: none
&[disabled]:hover,
fieldset[disabled] &:hover
border-color: $input-disabled-border-color
&:not([multiple])
+ltr-property("padding", 2.5em)
&[multiple]
height: auto
padding: 0
option
padding: 0.5em 1em
// States
&:not(.is-multiple):not(.is-loading):hover
&::after
border-color: $input-hover-color
// Colors
@each $name, $pair in $select-colors
$color: nth($pair, 1)
&.is-#{$name}
&:not(:hover)::after
border-color: $color
select
border-color: $color
&:hover,
&.is-hovered
border-color: bulmaDarken($color, 5%)
&:focus,
&.is-focused,
&:active,
&.is-active
box-shadow: $input-focus-box-shadow-size bulmaRgba($color, 0.25)
// Sizes
&.is-small
+control-small
&.is-medium
+control-medium
&.is-large
+control-large
// Modifiers
&.is-disabled
&::after
border-color: $input-disabled-color
&.is-fullwidth
width: 100%
select
width: 100%
&.is-loading
&::after
@extend %loader
margin-top: 0
position: absolute
+ltr-position(0.625em)
top: 0.625em
transform: none
&.is-small:after
font-size: $size-small
&.is-medium:after
font-size: $size-medium
&.is-large:after
font-size: $size-large

@ -0,0 +1,57 @@
$form-colors: $colors !default
$input-color: $text-strong !default
$input-background-color: $scheme-main !default
$input-border-color: $border !default
$input-height: $control-height !default
$input-shadow: inset 0 0.0625em 0.125em rgba($scheme-invert, 0.05) !default
$input-placeholder-color: bulmaRgba($input-color, 0.3) !default
$input-hover-color: $text-strong !default
$input-hover-border-color: $border-hover !default
$input-focus-color: $text-strong !default
$input-focus-border-color: $link !default
$input-focus-box-shadow-size: 0 0 0 0.125em !default
$input-focus-box-shadow-color: bulmaRgba($link, 0.25) !default
$input-disabled-color: $text-light !default
$input-disabled-background-color: $background !default
$input-disabled-border-color: $background !default
$input-disabled-placeholder-color: bulmaRgba($input-disabled-color, 0.3) !default
$input-arrow: $link !default
$input-icon-color: $border !default
$input-icon-active-color: $text !default
$input-radius: $radius !default
=input
@extend %control
background-color: $input-background-color
border-color: $input-border-color
border-radius: $input-radius
color: $input-color
+placeholder
color: $input-placeholder-color
&:hover,
&.is-hovered
border-color: $input-hover-border-color
&:focus,
&.is-focused,
&:active,
&.is-active
border-color: $input-focus-border-color
box-shadow: $input-focus-box-shadow-size $input-focus-box-shadow-color
&[disabled],
fieldset[disabled] &
background-color: $input-disabled-background-color
border-color: $input-disabled-border-color
box-shadow: none
color: $input-disabled-color
+placeholder
color: $input-disabled-placeholder-color
%input
+input

@ -0,0 +1,215 @@
$label-color: $text-strong !default
$label-weight: $weight-bold !default
$help-size: $size-small !default
$label-colors: $form-colors !default
.label
color: $label-color
display: block
font-size: $size-normal
font-weight: $label-weight
&:not(:last-child)
margin-bottom: 0.5em
// Sizes
&.is-small
font-size: $size-small
&.is-medium
font-size: $size-medium
&.is-large
font-size: $size-large
.help
display: block
font-size: $help-size
margin-top: 0.25rem
@each $name, $pair in $label-colors
$color: nth($pair, 1)
&.is-#{$name}
color: $color
// Containers
.field
&:not(:last-child)
margin-bottom: 0.75rem
// Modifiers
&.has-addons
display: flex
justify-content: flex-start
.control
&:not(:last-child)
+ltr-property("margin", -1px)
&:not(:first-child):not(:last-child)
.button,
.input,
.select select
border-radius: 0
&:first-child:not(:only-child)
.button,
.input,
.select select
+ltr
border-bottom-right-radius: 0
border-top-right-radius: 0
+rtl
border-bottom-left-radius: 0
border-top-left-radius: 0
&:last-child:not(:only-child)
.button,
.input,
.select select
+ltr
border-bottom-left-radius: 0
border-top-left-radius: 0
+rtl
border-bottom-right-radius: 0
border-top-right-radius: 0
.button,
.input,
.select select
&:not([disabled])
&:hover,
&.is-hovered
z-index: 2
&:focus,
&.is-focused,
&:active,
&.is-active
z-index: 3
&:hover
z-index: 4
&.is-expanded
flex-grow: 1
flex-shrink: 1
&.has-addons-centered
justify-content: center
&.has-addons-right
justify-content: flex-end
&.has-addons-fullwidth
.control
flex-grow: 1
flex-shrink: 0
&.is-grouped
display: flex
justify-content: flex-start
& > .control
flex-shrink: 0
&:not(:last-child)
margin-bottom: 0
+ltr-property("margin", 0.75rem)
&.is-expanded
flex-grow: 1
flex-shrink: 1
&.is-grouped-centered
justify-content: center
&.is-grouped-right
justify-content: flex-end
&.is-grouped-multiline
flex-wrap: wrap
& > .control
&:last-child,
&:not(:last-child)
margin-bottom: 0.75rem
&:last-child
margin-bottom: -0.75rem
&:not(:last-child)
margin-bottom: 0
&.is-horizontal
+tablet
display: flex
.field-label
.label
font-size: inherit
+mobile
margin-bottom: 0.5rem
+tablet
flex-basis: 0
flex-grow: 1
flex-shrink: 0
+ltr-property("margin", 1.5rem)
text-align: right
&.is-small
font-size: $size-small
padding-top: 0.375em
&.is-normal
padding-top: 0.375em
&.is-medium
font-size: $size-medium
padding-top: 0.375em
&.is-large
font-size: $size-large
padding-top: 0.375em
.field-body
.field .field
margin-bottom: 0
+tablet
display: flex
flex-basis: 0
flex-grow: 5
flex-shrink: 1
.field
margin-bottom: 0
& > .field
flex-shrink: 1
&:not(.is-narrow)
flex-grow: 1
&:not(:last-child)
+ltr-property("margin", 0.75rem)
.control
box-sizing: border-box
clear: both
font-size: $size-normal
position: relative
text-align: inherit
// Modifiers
&.has-icons-left,
&.has-icons-right
.input,
.select
&:focus
& ~ .icon
color: $input-icon-active-color
&.is-small ~ .icon
font-size: $size-small
&.is-medium ~ .icon
font-size: $size-medium
&.is-large ~ .icon
font-size: $size-large
.icon
color: $input-icon-color
height: $input-height
pointer-events: none
position: absolute
top: 0
width: $input-height
z-index: 4
&.has-icons-left
.input,
.select select
padding-left: $input-height
.icon.is-left
left: 0
&.has-icons-right
.input,
.select select
padding-right: $input-height
.icon.is-right
right: 0
&.is-loading
&::after
@extend %loader
position: absolute !important
+ltr-position(0.625em)
top: 0.625em
z-index: 4
&.is-small:after
font-size: $size-small
&.is-medium:after
font-size: $size-medium
&.is-large:after
font-size: $size-large

@ -0,0 +1,5 @@
/* Bulma Grid */
@charset "utf-8"
@import "columns.sass"
@import "tiles.sass"

@ -0,0 +1,504 @@
$column-gap: 0.75rem !default
.column
display: block
flex-basis: 0
flex-grow: 1
flex-shrink: 1
padding: $column-gap
.columns.is-mobile > &.is-narrow
flex: none
.columns.is-mobile > &.is-full
flex: none
width: 100%
.columns.is-mobile > &.is-three-quarters
flex: none
width: 75%
.columns.is-mobile > &.is-two-thirds
flex: none
width: 66.6666%
.columns.is-mobile > &.is-half
flex: none
width: 50%
.columns.is-mobile > &.is-one-third
flex: none
width: 33.3333%
.columns.is-mobile > &.is-one-quarter
flex: none
width: 25%
.columns.is-mobile > &.is-one-fifth
flex: none
width: 20%
.columns.is-mobile > &.is-two-fifths
flex: none
width: 40%
.columns.is-mobile > &.is-three-fifths
flex: none
width: 60%
.columns.is-mobile > &.is-four-fifths
flex: none
width: 80%
.columns.is-mobile > &.is-offset-three-quarters
margin-left: 75%
.columns.is-mobile > &.is-offset-two-thirds
margin-left: 66.6666%
.columns.is-mobile > &.is-offset-half
margin-left: 50%
.columns.is-mobile > &.is-offset-one-third
margin-left: 33.3333%
.columns.is-mobile > &.is-offset-one-quarter
margin-left: 25%
.columns.is-mobile > &.is-offset-one-fifth
margin-left: 20%
.columns.is-mobile > &.is-offset-two-fifths
margin-left: 40%
.columns.is-mobile > &.is-offset-three-fifths
margin-left: 60%
.columns.is-mobile > &.is-offset-four-fifths
margin-left: 80%
@for $i from 0 through 12
.columns.is-mobile > &.is-#{$i}
flex: none
width: percentage($i / 12)
.columns.is-mobile > &.is-offset-#{$i}
margin-left: percentage($i / 12)
+mobile
&.is-narrow-mobile
flex: none
&.is-full-mobile
flex: none
width: 100%
&.is-three-quarters-mobile
flex: none
width: 75%
&.is-two-thirds-mobile
flex: none
width: 66.6666%
&.is-half-mobile
flex: none
width: 50%
&.is-one-third-mobile
flex: none
width: 33.3333%
&.is-one-quarter-mobile
flex: none
width: 25%
&.is-one-fifth-mobile
flex: none
width: 20%
&.is-two-fifths-mobile
flex: none
width: 40%
&.is-three-fifths-mobile
flex: none
width: 60%
&.is-four-fifths-mobile
flex: none
width: 80%
&.is-offset-three-quarters-mobile
margin-left: 75%
&.is-offset-two-thirds-mobile
margin-left: 66.6666%
&.is-offset-half-mobile
margin-left: 50%
&.is-offset-one-third-mobile
margin-left: 33.3333%
&.is-offset-one-quarter-mobile
margin-left: 25%
&.is-offset-one-fifth-mobile
margin-left: 20%
&.is-offset-two-fifths-mobile
margin-left: 40%
&.is-offset-three-fifths-mobile
margin-left: 60%
&.is-offset-four-fifths-mobile
margin-left: 80%
@for $i from 0 through 12
&.is-#{$i}-mobile
flex: none
width: percentage($i / 12)
&.is-offset-#{$i}-mobile
margin-left: percentage($i / 12)
+tablet
&.is-narrow,
&.is-narrow-tablet
flex: none
&.is-full,
&.is-full-tablet
flex: none
width: 100%
&.is-three-quarters,
&.is-three-quarters-tablet
flex: none
width: 75%
&.is-two-thirds,
&.is-two-thirds-tablet
flex: none
width: 66.6666%
&.is-half,
&.is-half-tablet
flex: none
width: 50%
&.is-one-third,
&.is-one-third-tablet
flex: none
width: 33.3333%
&.is-one-quarter,
&.is-one-quarter-tablet
flex: none
width: 25%
&.is-one-fifth,
&.is-one-fifth-tablet
flex: none
width: 20%
&.is-two-fifths,
&.is-two-fifths-tablet
flex: none
width: 40%
&.is-three-fifths,
&.is-three-fifths-tablet
flex: none
width: 60%
&.is-four-fifths,
&.is-four-fifths-tablet
flex: none
width: 80%
&.is-offset-three-quarters,
&.is-offset-three-quarters-tablet
margin-left: 75%
&.is-offset-two-thirds,
&.is-offset-two-thirds-tablet
margin-left: 66.6666%
&.is-offset-half,
&.is-offset-half-tablet
margin-left: 50%
&.is-offset-one-third,
&.is-offset-one-third-tablet
margin-left: 33.3333%
&.is-offset-one-quarter,
&.is-offset-one-quarter-tablet
margin-left: 25%
&.is-offset-one-fifth,
&.is-offset-one-fifth-tablet
margin-left: 20%
&.is-offset-two-fifths,
&.is-offset-two-fifths-tablet
margin-left: 40%
&.is-offset-three-fifths,
&.is-offset-three-fifths-tablet
margin-left: 60%
&.is-offset-four-fifths,
&.is-offset-four-fifths-tablet
margin-left: 80%
@for $i from 0 through 12
&.is-#{$i},
&.is-#{$i}-tablet
flex: none
width: percentage($i / 12)
&.is-offset-#{$i},
&.is-offset-#{$i}-tablet
margin-left: percentage($i / 12)
+touch
&.is-narrow-touch
flex: none
&.is-full-touch
flex: none
width: 100%
&.is-three-quarters-touch
flex: none
width: 75%
&.is-two-thirds-touch
flex: none
width: 66.6666%
&.is-half-touch
flex: none
width: 50%
&.is-one-third-touch
flex: none
width: 33.3333%
&.is-one-quarter-touch
flex: none
width: 25%
&.is-one-fifth-touch
flex: none
width: 20%
&.is-two-fifths-touch
flex: none
width: 40%
&.is-three-fifths-touch
flex: none
width: 60%
&.is-four-fifths-touch
flex: none
width: 80%
&.is-offset-three-quarters-touch
margin-left: 75%
&.is-offset-two-thirds-touch
margin-left: 66.6666%
&.is-offset-half-touch
margin-left: 50%
&.is-offset-one-third-touch
margin-left: 33.3333%
&.is-offset-one-quarter-touch
margin-left: 25%
&.is-offset-one-fifth-touch
margin-left: 20%
&.is-offset-two-fifths-touch
margin-left: 40%
&.is-offset-three-fifths-touch
margin-left: 60%
&.is-offset-four-fifths-touch
margin-left: 80%
@for $i from 0 through 12
&.is-#{$i}-touch
flex: none
width: percentage($i / 12)
&.is-offset-#{$i}-touch
margin-left: percentage($i / 12)
+desktop
&.is-narrow-desktop
flex: none
&.is-full-desktop
flex: none
width: 100%
&.is-three-quarters-desktop
flex: none
width: 75%
&.is-two-thirds-desktop
flex: none
width: 66.6666%
&.is-half-desktop
flex: none
width: 50%
&.is-one-third-desktop
flex: none
width: 33.3333%
&.is-one-quarter-desktop
flex: none
width: 25%
&.is-one-fifth-desktop
flex: none
width: 20%
&.is-two-fifths-desktop
flex: none
width: 40%
&.is-three-fifths-desktop
flex: none
width: 60%
&.is-four-fifths-desktop
flex: none
width: 80%
&.is-offset-three-quarters-desktop
margin-left: 75%
&.is-offset-two-thirds-desktop
margin-left: 66.6666%
&.is-offset-half-desktop
margin-left: 50%
&.is-offset-one-third-desktop
margin-left: 33.3333%
&.is-offset-one-quarter-desktop
margin-left: 25%
&.is-offset-one-fifth-desktop
margin-left: 20%
&.is-offset-two-fifths-desktop
margin-left: 40%
&.is-offset-three-fifths-desktop
margin-left: 60%
&.is-offset-four-fifths-desktop
margin-left: 80%
@for $i from 0 through 12
&.is-#{$i}-desktop
flex: none
width: percentage($i / 12)
&.is-offset-#{$i}-desktop
margin-left: percentage($i / 12)
+widescreen
&.is-narrow-widescreen
flex: none
&.is-full-widescreen
flex: none
width: 100%
&.is-three-quarters-widescreen
flex: none
width: 75%
&.is-two-thirds-widescreen
flex: none
width: 66.6666%
&.is-half-widescreen
flex: none
width: 50%
&.is-one-third-widescreen
flex: none
width: 33.3333%
&.is-one-quarter-widescreen
flex: none
width: 25%
&.is-one-fifth-widescreen
flex: none
width: 20%
&.is-two-fifths-widescreen
flex: none
width: 40%
&.is-three-fifths-widescreen
flex: none
width: 60%
&.is-four-fifths-widescreen
flex: none
width: 80%
&.is-offset-three-quarters-widescreen
margin-left: 75%
&.is-offset-two-thirds-widescreen
margin-left: 66.6666%
&.is-offset-half-widescreen
margin-left: 50%
&.is-offset-one-third-widescreen
margin-left: 33.3333%
&.is-offset-one-quarter-widescreen
margin-left: 25%
&.is-offset-one-fifth-widescreen
margin-left: 20%
&.is-offset-two-fifths-widescreen
margin-left: 40%
&.is-offset-three-fifths-widescreen
margin-left: 60%
&.is-offset-four-fifths-widescreen
margin-left: 80%
@for $i from 0 through 12
&.is-#{$i}-widescreen
flex: none
width: percentage($i / 12)
&.is-offset-#{$i}-widescreen
margin-left: percentage($i / 12)
+fullhd
&.is-narrow-fullhd
flex: none
&.is-full-fullhd
flex: none
width: 100%
&.is-three-quarters-fullhd
flex: none
width: 75%
&.is-two-thirds-fullhd
flex: none
width: 66.6666%
&.is-half-fullhd
flex: none
width: 50%
&.is-one-third-fullhd
flex: none
width: 33.3333%
&.is-one-quarter-fullhd
flex: none
width: 25%
&.is-one-fifth-fullhd
flex: none
width: 20%
&.is-two-fifths-fullhd
flex: none
width: 40%
&.is-three-fifths-fullhd
flex: none
width: 60%
&.is-four-fifths-fullhd
flex: none
width: 80%
&.is-offset-three-quarters-fullhd
margin-left: 75%
&.is-offset-two-thirds-fullhd
margin-left: 66.6666%
&.is-offset-half-fullhd
margin-left: 50%
&.is-offset-one-third-fullhd
margin-left: 33.3333%
&.is-offset-one-quarter-fullhd
margin-left: 25%
&.is-offset-one-fifth-fullhd
margin-left: 20%
&.is-offset-two-fifths-fullhd
margin-left: 40%
&.is-offset-three-fifths-fullhd
margin-left: 60%
&.is-offset-four-fifths-fullhd
margin-left: 80%
@for $i from 0 through 12
&.is-#{$i}-fullhd
flex: none
width: percentage($i / 12)
&.is-offset-#{$i}-fullhd
margin-left: percentage($i / 12)
.columns
margin-left: (-$column-gap)
margin-right: (-$column-gap)
margin-top: (-$column-gap)
&:last-child
margin-bottom: (-$column-gap)
&:not(:last-child)
margin-bottom: calc(1.5rem - #{$column-gap})
// Modifiers
&.is-centered
justify-content: center
&.is-gapless
margin-left: 0
margin-right: 0
margin-top: 0
& > .column
margin: 0
padding: 0 !important
&:not(:last-child)
margin-bottom: 1.5rem
&:last-child
margin-bottom: 0
&.is-mobile
display: flex
&.is-multiline
flex-wrap: wrap
&.is-vcentered
align-items: center
// Responsiveness
+tablet
&:not(.is-desktop)
display: flex
+desktop
// Modifiers
&.is-desktop
display: flex
@if $variable-columns
.columns.is-variable
--columnGap: 0.75rem
margin-left: calc(-1 * var(--columnGap))
margin-right: calc(-1 * var(--columnGap))
.column
padding-left: var(--columnGap)
padding-right: var(--columnGap)
@for $i from 0 through 8
&.is-#{$i}
--columnGap: #{$i * 0.25rem}
+mobile
&.is-#{$i}-mobile
--columnGap: #{$i * 0.25rem}
+tablet
&.is-#{$i}-tablet
--columnGap: #{$i * 0.25rem}
+tablet-only
&.is-#{$i}-tablet-only
--columnGap: #{$i * 0.25rem}
+touch
&.is-#{$i}-touch
--columnGap: #{$i * 0.25rem}
+desktop
&.is-#{$i}-desktop
--columnGap: #{$i * 0.25rem}
+desktop-only
&.is-#{$i}-desktop-only
--columnGap: #{$i * 0.25rem}
+widescreen
&.is-#{$i}-widescreen
--columnGap: #{$i * 0.25rem}
+widescreen-only
&.is-#{$i}-widescreen-only
--columnGap: #{$i * 0.25rem}
+fullhd
&.is-#{$i}-fullhd
--columnGap: #{$i * 0.25rem}

@ -0,0 +1,34 @@
$tile-spacing: 0.75rem !default
.tile
align-items: stretch
display: block
flex-basis: 0
flex-grow: 1
flex-shrink: 1
min-height: min-content
// Modifiers
&.is-ancestor
margin-left: $tile-spacing * -1
margin-right: $tile-spacing * -1
margin-top: $tile-spacing * -1
&:last-child
margin-bottom: $tile-spacing * -1
&:not(:last-child)
margin-bottom: $tile-spacing
&.is-child
margin: 0 !important
&.is-parent
padding: $tile-spacing
&.is-vertical
flex-direction: column
& > .tile.is-child:not(:last-child)
margin-bottom: 1.5rem !important
// Responsiveness
+tablet
&:not(.is-child)
display: flex
@for $i from 1 through 12
&.is-#{$i}
flex: none
width: ($i / 12) * 100%

@ -0,0 +1,12 @@
/* Bulma Helpers */
@charset "utf-8"
@import "color.sass"
@import "flexbox.sass"
@import "float.sass"
@import "other.sass"
@import "overflow.sass"
@import "position.sass"
@import "spacing.sass"
@import "typography.sass"
@import "visibility.sass"

@ -0,0 +1,37 @@
@each $name, $pair in $colors
$color: nth($pair, 1)
.has-text-#{$name}
color: $color !important
a.has-text-#{$name}
&:hover,
&:focus
color: bulmaDarken($color, 10%) !important
.has-background-#{$name}
background-color: $color !important
@if length($pair) >= 4
$color-light: nth($pair, 3)
$color-dark: nth($pair, 4)
// Light
.has-text-#{$name}-light
color: $color-light !important
a.has-text-#{$name}-light
&:hover,
&:focus
color: bulmaDarken($color-light, 10%) !important
.has-background-#{$name}-light
background-color: $color-light !important
// Dark
.has-text-#{$name}-dark
color: $color-dark !important
a.has-text-#{$name}-dark
&:hover,
&:focus
color: bulmaLighten($color-dark, 10%) !important
.has-background-#{$name}-dark
background-color: $color-dark !important
@each $name, $shade in $shades
.has-text-#{$name}
color: $shade !important
.has-background-#{$name}
background-color: $shade !important

@ -0,0 +1,35 @@
$flex-direction-values: row, row-reverse, column, column-reverse
@each $value in $flex-direction-values
.is-flex-direction-#{$value}
flex-direction: $value !important
$flex-wrap-values: nowrap, wrap, wrap-reverse
@each $value in $flex-wrap-values
.is-flex-wrap-#{$value}
flex-wrap: $value !important
$justify-content-values: flex-start, flex-end, center, space-between, space-around, space-evenly, start, end, left, right
@each $value in $justify-content-values
.is-justify-content-#{$value}
justify-content: $value !important
$align-content-values: flex-start, flex-end, center, space-between, space-around, space-evenly, stretch, start, end, baseline
@each $value in $align-content-values
.is-align-content-#{$value}
align-content: $value !important
$align-items-values: stretch, flex-start, flex-end, center, baseline, start, end, self-start, self-end
@each $value in $align-items-values
.is-align-items-#{$value}
align-items: $value !important
$align-self-values: auto, flex-start, flex-end, center, baseline, stretch
@each $value in $align-self-values
.is-align-self-#{$value}
align-self: $value !important
$flex-operators: grow, shrink
@each $operator in $flex-operators
@for $i from 0 through 5
.is-flex-#{$operator}-#{$i}
flex-#{$operator}: $i !important

@ -0,0 +1,8 @@
.is-clearfix
+clearfix
.is-pulled-left
float: left !important
.is-pulled-right
float: right !important

@ -0,0 +1,11 @@
.is-radiusless
border-radius: 0 !important
.is-shadowless
box-shadow: none !important
.is-clickable
cursor: pointer !important
.is-unselectable
@extend %unselectable

@ -0,0 +1,2 @@
.is-clipped
overflow: hidden !important

@ -0,0 +1,5 @@
.is-overlay
@extend %overlay
.is-relative
position: relative !important

@ -0,0 +1,31 @@
.is-marginless
margin: 0 !important
.is-paddingless
padding: 0 !important
$spacing-shortcuts: ("margin": "m", "padding": "p") !default
$spacing-directions: ("top": "t", "right": "r", "bottom": "b", "left": "l") !default
$spacing-horizontal: "x" !default
$spacing-vertical: "y" !default
$spacing-values: ("0": 0, "1": 0.25rem, "2": 0.5rem, "3": 0.75rem, "4": 1rem, "5": 1.5rem, "6": 3rem) !default
@each $property, $shortcut in $spacing-shortcuts
@each $name, $value in $spacing-values
// All directions
.#{$shortcut}-#{$name}
#{$property}: $value !important
// Cardinal directions
@each $direction, $suffix in $spacing-directions
.#{$shortcut}#{$suffix}-#{$name}
#{$property}-#{$direction}: $value !important
// Horizontal axis
@if $spacing-horizontal != null
.#{$shortcut}#{$spacing-horizontal}-#{$name}
#{$property}-left: $value !important
#{$property}-right: $value !important
// Vertical axis
@if $spacing-vertical != null
.#{$shortcut}#{$spacing-vertical}-#{$name}
#{$property}-top: $value !important
#{$property}-bottom: $value !important

@ -0,0 +1,98 @@
=typography-size($target:'')
@each $size in $sizes
$i: index($sizes, $size)
.is-size-#{$i}#{if($target == '', '', '-' + $target)}
font-size: $size !important
+typography-size()
+mobile
+typography-size('mobile')
+tablet
+typography-size('tablet')
+touch
+typography-size('touch')
+desktop
+typography-size('desktop')
+widescreen
+typography-size('widescreen')
+fullhd
+typography-size('fullhd')
$alignments: ('centered': 'center', 'justified': 'justify', 'left': 'left', 'right': 'right')
@each $alignment, $text-align in $alignments
.has-text-#{$alignment}
text-align: #{$text-align} !important
@each $alignment, $text-align in $alignments
+mobile
.has-text-#{$alignment}-mobile
text-align: #{$text-align} !important
+tablet
.has-text-#{$alignment}-tablet
text-align: #{$text-align} !important
+tablet-only
.has-text-#{$alignment}-tablet-only
text-align: #{$text-align} !important
+touch
.has-text-#{$alignment}-touch
text-align: #{$text-align} !important
+desktop
.has-text-#{$alignment}-desktop
text-align: #{$text-align} !important
+desktop-only
.has-text-#{$alignment}-desktop-only
text-align: #{$text-align} !important
+widescreen
.has-text-#{$alignment}-widescreen
text-align: #{$text-align} !important
+widescreen-only
.has-text-#{$alignment}-widescreen-only
text-align: #{$text-align} !important
+fullhd
.has-text-#{$alignment}-fullhd
text-align: #{$text-align} !important
.is-capitalized
text-transform: capitalize !important
.is-lowercase
text-transform: lowercase !important
.is-uppercase
text-transform: uppercase !important
.is-italic
font-style: italic !important
.has-text-weight-light
font-weight: $weight-light !important
.has-text-weight-normal
font-weight: $weight-normal !important
.has-text-weight-medium
font-weight: $weight-medium !important
.has-text-weight-semibold
font-weight: $weight-semibold !important
.has-text-weight-bold
font-weight: $weight-bold !important
.is-family-primary
font-family: $family-primary !important
.is-family-secondary
font-family: $family-secondary !important
.is-family-sans-serif
font-family: $family-sans-serif !important
.is-family-monospace
font-family: $family-monospace !important
.is-family-code
font-family: $family-code !important

@ -0,0 +1,122 @@
$displays: 'block' 'flex' 'inline' 'inline-block' 'inline-flex'
@each $display in $displays
.is-#{$display}
display: #{$display} !important
+mobile
.is-#{$display}-mobile
display: #{$display} !important
+tablet
.is-#{$display}-tablet
display: #{$display} !important
+tablet-only
.is-#{$display}-tablet-only
display: #{$display} !important
+touch
.is-#{$display}-touch
display: #{$display} !important
+desktop
.is-#{$display}-desktop
display: #{$display} !important
+desktop-only
.is-#{$display}-desktop-only
display: #{$display} !important
+widescreen
.is-#{$display}-widescreen
display: #{$display} !important
+widescreen-only
.is-#{$display}-widescreen-only
display: #{$display} !important
+fullhd
.is-#{$display}-fullhd
display: #{$display} !important
.is-hidden
display: none !important
.is-sr-only
border: none !important
clip: rect(0, 0, 0, 0) !important
height: 0.01em !important
overflow: hidden !important
padding: 0 !important
position: absolute !important
white-space: nowrap !important
width: 0.01em !important
+mobile
.is-hidden-mobile
display: none !important
+tablet
.is-hidden-tablet
display: none !important
+tablet-only
.is-hidden-tablet-only
display: none !important
+touch
.is-hidden-touch
display: none !important
+desktop
.is-hidden-desktop
display: none !important
+desktop-only
.is-hidden-desktop-only
display: none !important
+widescreen
.is-hidden-widescreen
display: none !important
+widescreen-only
.is-hidden-widescreen-only
display: none !important
+fullhd
.is-hidden-fullhd
display: none !important
.is-invisible
visibility: hidden !important
+mobile
.is-invisible-mobile
visibility: hidden !important
+tablet
.is-invisible-tablet
visibility: hidden !important
+tablet-only
.is-invisible-tablet-only
visibility: hidden !important
+touch
.is-invisible-touch
visibility: hidden !important
+desktop
.is-invisible-desktop
visibility: hidden !important
+desktop-only
.is-invisible-desktop-only
visibility: hidden !important
+widescreen
.is-invisible-widescreen
visibility: hidden !important
+widescreen-only
.is-invisible-widescreen-only
visibility: hidden !important
+fullhd
.is-invisible-fullhd
visibility: hidden !important

@ -0,0 +1,6 @@
/* Bulma Layout */
@charset "utf-8"
@import "hero.sass"
@import "section.sass"
@import "footer.sass"

@ -0,0 +1,9 @@
$footer-background-color: $scheme-main-bis !default
$footer-color: false !default
$footer-padding: 3rem 1.5rem 6rem !default
.footer
background-color: $footer-background-color
padding: $footer-padding
@if $footer-color
color: $footer-color

@ -0,0 +1,147 @@
$hero-body-padding: 3rem 1.5rem !default
$hero-body-padding-small: 1.5rem !default
$hero-body-padding-medium: 9rem 1.5rem !default
$hero-body-padding-large: 18rem 1.5rem !default
$hero-colors: $colors !default
// Main container
.hero
align-items: stretch
display: flex
flex-direction: column
justify-content: space-between
.navbar
background: none
.tabs
ul
border-bottom: none
// Colors
@each $name, $pair in $hero-colors
$color: nth($pair, 1)
$color-invert: nth($pair, 2)
&.is-#{$name}
background-color: $color
color: $color-invert
a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),
strong
color: inherit
.title
color: $color-invert
.subtitle
color: bulmaRgba($color-invert, 0.9)
a:not(.button),
strong
color: $color-invert
.navbar-menu
+touch
background-color: $color
.navbar-item,
.navbar-link
color: bulmaRgba($color-invert, 0.7)
a.navbar-item,
.navbar-link
&:hover,
&.is-active
background-color: bulmaDarken($color, 5%)
color: $color-invert
.tabs
a
color: $color-invert
opacity: 0.9
&:hover
opacity: 1
li
&.is-active a
opacity: 1
&.is-boxed,
&.is-toggle
a
color: $color-invert
&:hover
background-color: bulmaRgba($scheme-invert, 0.1)
li.is-active a
&,
&:hover
background-color: $color-invert
border-color: $color-invert
color: $color
// Modifiers
@if type-of($color) == 'color'
&.is-bold
$gradient-top-left: darken(saturate(adjust-hue($color, -10deg), 10%), 10%)
$gradient-bottom-right: lighten(saturate(adjust-hue($color, 10deg), 5%), 5%)
background-image: linear-gradient(141deg, $gradient-top-left 0%, $color 71%, $gradient-bottom-right 100%)
+mobile
.navbar-menu
background-image: linear-gradient(141deg, $gradient-top-left 0%, $color 71%, $gradient-bottom-right 100%)
// Sizes
&.is-small
.hero-body
padding: $hero-body-padding-small
&.is-medium
+tablet
.hero-body
padding: $hero-body-padding-medium
&.is-large
+tablet
.hero-body
padding: $hero-body-padding-large
&.is-halfheight,
&.is-fullheight,
&.is-fullheight-with-navbar
.hero-body
align-items: center
display: flex
& > .container
flex-grow: 1
flex-shrink: 1
&.is-halfheight
min-height: 50vh
&.is-fullheight
min-height: 100vh
// Components
.hero-video
@extend %overlay
overflow: hidden
video
left: 50%
min-height: 100%
min-width: 100%
position: absolute
top: 50%
transform: translate3d(-50%, -50%, 0)
// Modifiers
&.is-transparent
opacity: 0.3
// Responsiveness
+mobile
display: none
.hero-buttons
margin-top: 1.5rem
// Responsiveness
+mobile
.button
display: flex
&:not(:last-child)
margin-bottom: 0.75rem
+tablet
display: flex
justify-content: center
.button:not(:last-child)
+ltr-property("margin", 1.5rem)
// Containers
.hero-head,
.hero-foot
flex-grow: 0
flex-shrink: 0
.hero-body
flex-grow: 1
flex-shrink: 0
padding: $hero-body-padding

@ -0,0 +1,13 @@
$section-padding: 3rem 1.5rem !default
$section-padding-medium: 9rem 1.5rem !default
$section-padding-large: 18rem 1.5rem !default
.section
padding: $section-padding
// Responsiveness
+desktop
// Sizes
&.is-medium
padding: $section-padding-medium
&.is-large
padding: $section-padding-large

@ -0,0 +1,9 @@
/* Bulma Utilities */
@charset "utf-8"
@import "initial-variables.sass"
@import "functions.sass"
@import "derived-variables.sass"
@import "animations.sass"
@import "mixins.sass"
@import "controls.sass"

@ -0,0 +1,5 @@
@keyframes spinAround
from
transform: rotate(0deg)
to
transform: rotate(359deg)

@ -0,0 +1,50 @@
$control-radius: $radius !default
$control-radius-small: $radius-small !default
$control-border-width: 1px !default
$control-height: 2.5em !default
$control-line-height: 1.5 !default
$control-padding-vertical: calc(0.5em - #{$control-border-width}) !default
$control-padding-horizontal: calc(0.75em - #{$control-border-width}) !default
=control
-moz-appearance: none
-webkit-appearance: none
align-items: center
border: $control-border-width solid transparent
border-radius: $control-radius
box-shadow: none
display: inline-flex
font-size: $size-normal
height: $control-height
justify-content: flex-start
line-height: $control-line-height
padding-bottom: $control-padding-vertical
padding-left: $control-padding-horizontal
padding-right: $control-padding-horizontal
padding-top: $control-padding-vertical
position: relative
vertical-align: top
// States
&:focus,
&.is-focused,
&:active,
&.is-active
outline: none
&[disabled],
fieldset[disabled] &
cursor: not-allowed
%control
+control
// The controls sizes use mixins so they can be used at different breakpoints
=control-small
border-radius: $control-radius-small
font-size: $size-small
=control-medium
font-size: $size-medium
=control-large
font-size: $size-large

@ -0,0 +1,107 @@
$primary: $turquoise !default
$info: $cyan !default
$success: $green !default
$warning: $yellow !default
$danger: $red !default
$light: $white-ter !default
$dark: $grey-darker !default
// Invert colors
$orange-invert: findColorInvert($orange) !default
$yellow-invert: findColorInvert($yellow) !default
$green-invert: findColorInvert($green) !default
$turquoise-invert: findColorInvert($turquoise) !default
$cyan-invert: findColorInvert($cyan) !default
$blue-invert: findColorInvert($blue) !default
$purple-invert: findColorInvert($purple) !default
$red-invert: findColorInvert($red) !default
$primary-invert: findColorInvert($primary) !default
$primary-light: findLightColor($primary) !default
$primary-dark: findDarkColor($primary) !default
$info-invert: findColorInvert($info) !default
$info-light: findLightColor($info) !default
$info-dark: findDarkColor($info) !default
$success-invert: findColorInvert($success) !default
$success-light: findLightColor($success) !default
$success-dark: findDarkColor($success) !default
$warning-invert: findColorInvert($warning) !default
$warning-light: findLightColor($warning) !default
$warning-dark: findDarkColor($warning) !default
$danger-invert: findColorInvert($danger) !default
$danger-light: findLightColor($danger) !default
$danger-dark: findDarkColor($danger) !default
$light-invert: findColorInvert($light) !default
$dark-invert: findColorInvert($dark) !default
// General colors
$scheme-main: $white !default
$scheme-main-bis: $white-bis !default
$scheme-main-ter: $white-ter !default
$scheme-invert: $black !default
$scheme-invert-bis: $black-bis !default
$scheme-invert-ter: $black-ter !default
$background: $white-ter !default
$border: $grey-lighter !default
$border-hover: $grey-light !default
$border-light: $grey-lightest !default
$border-light-hover: $grey-light !default
// Text colors
$text: $grey-dark !default
$text-invert: findColorInvert($text) !default
$text-light: $grey !default
$text-strong: $grey-darker !default
// Code colors
$code: darken($red, 15%) !default
$code-background: $background !default
$pre: $text !default
$pre-background: $background !default
// Link colors
$link: $blue !default
$link-invert: findColorInvert($link) !default
$link-light: findLightColor($link) !default
$link-dark: findDarkColor($link) !default
$link-visited: $purple !default
$link-hover: $grey-darker !default
$link-hover-border: $grey-light !default
$link-focus: $grey-darker !default
$link-focus-border: $blue !default
$link-active: $grey-darker !default
$link-active-border: $grey-dark !default
// Typography
$family-primary: $family-sans-serif !default
$family-secondary: $family-sans-serif !default
$family-code: $family-monospace !default
$size-small: $size-7 !default
$size-normal: $size-6 !default
$size-medium: $size-5 !default
$size-large: $size-4 !default
// Lists and maps
$custom-colors: null !default
$custom-shades: null !default
$colors: mergeColorMaps(("white": ($white, $black), "black": ($black, $white), "light": ($light, $light-invert), "dark": ($dark, $dark-invert), "primary": ($primary, $primary-invert, $primary-light, $primary-dark), "link": ($link, $link-invert, $link-light, $link-dark), "info": ($info, $info-invert, $info-light, $info-dark), "success": ($success, $success-invert, $success-light, $success-dark), "warning": ($warning, $warning-invert, $warning-light, $warning-dark), "danger": ($danger, $danger-invert, $danger-light, $danger-dark)), $custom-colors) !default
$shades: mergeColorMaps(("black-bis": $black-bis, "black-ter": $black-ter, "grey-darker": $grey-darker, "grey-dark": $grey-dark, "grey": $grey, "grey-light": $grey-light, "grey-lighter": $grey-lighter, "white-ter": $white-ter, "white-bis": $white-bis), $custom-shades) !default
$sizes: $size-1 $size-2 $size-3 $size-4 $size-5 $size-6 $size-7 !default

@ -0,0 +1,115 @@
@function mergeColorMaps($bulma-colors, $custom-colors)
// We return at least Bulma's hard-coded colors
$merged-colors: $bulma-colors
// We want a map as input
@if type-of($custom-colors) == 'map'
@each $name, $components in $custom-colors
// The color name should be a string
// and the components either a single color
// or a colors list with at least one element
@if type-of($name) == 'string' and (type-of($components) == 'list' or type-of($components) == 'color') and length($components) >= 1
$color-base: null
$color-invert: null
$color-light: null
$color-dark: null
$value: null
// The param can either be a single color
// or a list of 2 colors
@if type-of($components) == 'color'
$color-base: $components
$color-invert: findColorInvert($color-base)
$color-light: findLightColor($color-base)
$color-dark: findDarkColor($color-base)
@else if type-of($components) == 'list'
$color-base: nth($components, 1)
// If Invert, Light and Dark are provided
@if length($components) > 3
$color-invert: nth($components, 2)
$color-light: nth($components, 3)
$color-dark: nth($components, 4)
// If only Invert and Light are provided
@else if length($components) > 2
$color-invert: nth($components, 2)
$color-light: nth($components, 3)
$color-dark: findDarkColor($color-base)
// If only Invert is provided
@else
$color-invert: nth($components, 2)
$color-light: findLightColor($color-base)
$color-dark: findDarkColor($color-base)
$value: ($color-base, $color-invert, $color-light, $color-dark)
// We only want to merge the map if the color base is an actual color
@if type-of($color-base) == 'color'
// We merge this colors elements as map with Bulma's colors map
// (we can override them this way, no multiple definition for the same name)
// $merged-colors: map_merge($merged-colors, ($name: ($color-base, $color-invert, $color-light, $color-dark)))
$merged-colors: map_merge($merged-colors, ($name: $value))
@return $merged-colors
@function powerNumber($number, $exp)
$value: 1
@if $exp > 0
@for $i from 1 through $exp
$value: $value * $number
@else if $exp < 0
@for $i from 1 through -$exp
$value: $value / $number
@return $value
@function colorLuminance($color)
@if type-of($color) != 'color'
@return 0.55
$color-rgb: ('red': red($color),'green': green($color),'blue': blue($color))
@each $name, $value in $color-rgb
$adjusted: 0
$value: $value / 255
@if $value < 0.03928
$value: $value / 12.92
@else
$value: ($value + .055) / 1.055
$value: powerNumber($value, 2)
$color-rgb: map-merge($color-rgb, ($name: $value))
@return (map-get($color-rgb, 'red') * .2126) + (map-get($color-rgb, 'green') * .7152) + (map-get($color-rgb, 'blue') * .0722)
@function findColorInvert($color)
@if (colorLuminance($color) > 0.55)
@return rgba(#000, 0.7)
@else
@return #fff
@function findLightColor($color)
@if type-of($color) == 'color'
$l: 96%
@if lightness($color) > 96%
$l: lightness($color)
@return change-color($color, $lightness: $l)
@return $background
@function findDarkColor($color)
@if type-of($color) == 'color'
$base-l: 29%
$luminance: colorLuminance($color)
$luminance-delta: (0.53 - $luminance)
$target-l: round($base-l + ($luminance-delta * 53))
@return change-color($color, $lightness: max($base-l, $target-l))
@return $text-strong
@function bulmaRgba($color, $alpha)
@if type-of($color) != 'color'
@return $color
@return rgba($color, $alpha)
@function bulmaDarken($color, $amount)
@if type-of($color) != 'color'
@return $color
@return darken($color, $amount)
@function bulmaLighten($color, $amount)
@if type-of($color) != 'color'
@return $color
@return lighten($color, $amount)

@ -0,0 +1,78 @@
// Colors
$black: hsl(0, 0%, 4%) !default
$black-bis: hsl(0, 0%, 7%) !default
$black-ter: hsl(0, 0%, 14%) !default
$grey-darker: hsl(0, 0%, 21%) !default
$grey-dark: hsl(0, 0%, 29%) !default
$grey: hsl(0, 0%, 48%) !default
$grey-light: hsl(0, 0%, 71%) !default
$grey-lighter: hsl(0, 0%, 86%) !default
$grey-lightest: hsl(0, 0%, 93%) !default
$white-ter: hsl(0, 0%, 96%) !default
$white-bis: hsl(0, 0%, 98%) !default
$white: hsl(0, 0%, 100%) !default
$orange: hsl(14, 100%, 53%) !default
$yellow: hsl(48, 100%, 67%) !default
$green: hsl(141, 53%, 53%) !default
$turquoise: hsl(171, 100%, 41%) !default
$cyan: hsl(204, 71%, 53%) !default
$blue: hsl(217, 71%, 53%) !default
$purple: hsl(271, 100%, 71%) !default
$red: hsl(348, 86%, 61%) !default
// Typography
$family-sans-serif: BlinkMacSystemFont, -apple-system, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", "Helvetica", "Arial", sans-serif !default
$family-monospace: monospace !default
$render-mode: optimizeLegibility !default
$size-1: 3rem !default
$size-2: 2.5rem !default
$size-3: 2rem !default
$size-4: 1.5rem !default
$size-5: 1.25rem !default
$size-6: 1rem !default
$size-7: 0.75rem !default
$weight-light: 300 !default
$weight-normal: 400 !default
$weight-medium: 500 !default
$weight-semibold: 600 !default
$weight-bold: 700 !default
// Spacing
$block-spacing: 1.5rem !default
// Responsiveness
// The container horizontal gap, which acts as the offset for breakpoints
$gap: 32px !default
// 960, 1152, and 1344 have been chosen because they are divisible by both 12 and 16
$tablet: 769px !default
// 960px container + 4rem
$desktop: 960px + (2 * $gap) !default
// 1152px container + 4rem
$widescreen: 1152px + (2 * $gap) !default
$widescreen-enabled: true !default
// 1344px container + 4rem
$fullhd: 1344px + (2 * $gap) !default
$fullhd-enabled: true !default
// Miscellaneous
$easing: ease-out !default
$radius-small: 2px !default
$radius: 4px !default
$radius-large: 6px !default
$radius-rounded: 290486px !default
$speed: 86ms !default
// Flags
$variable-columns: true !default
$rtl: false !default

@ -0,0 +1,285 @@
@import "initial-variables"
=clearfix
&::after
clear: both
content: " "
display: table
=center($width, $height: 0)
position: absolute
@if $height != 0
left: calc(50% - (#{$width} / 2))
top: calc(50% - (#{$height} / 2))
@else
left: calc(50% - (#{$width} / 2))
top: calc(50% - (#{$width} / 2))
=fa($size, $dimensions)
display: inline-block
font-size: $size
height: $dimensions
line-height: $dimensions
text-align: center
vertical-align: top
width: $dimensions
=hamburger($dimensions)
cursor: pointer
display: block
height: $dimensions
position: relative
width: $dimensions
span
background-color: currentColor
display: block
height: 1px
left: calc(50% - 8px)
position: absolute
transform-origin: center
transition-duration: $speed
transition-property: background-color, opacity, transform
transition-timing-function: $easing
width: 16px
&:nth-child(1)
top: calc(50% - 6px)
&:nth-child(2)
top: calc(50% - 1px)
&:nth-child(3)
top: calc(50% + 4px)
&:hover
background-color: bulmaRgba(black, 0.05)
// Modifers
&.is-active
span
&:nth-child(1)
transform: translateY(5px) rotate(45deg)
&:nth-child(2)
opacity: 0
&:nth-child(3)
transform: translateY(-5px) rotate(-45deg)
=overflow-touch
-webkit-overflow-scrolling: touch
=placeholder
$placeholders: ':-moz' ':-webkit-input' '-moz' '-ms-input'
@each $placeholder in $placeholders
&:#{$placeholder}-placeholder
@content
// Responsiveness
=from($device)
@media screen and (min-width: $device)
@content
=until($device)
@media screen and (max-width: $device - 1px)
@content
=mobile
@media screen and (max-width: $tablet - 1px)
@content
=tablet
@media screen and (min-width: $tablet), print
@content
=tablet-only
@media screen and (min-width: $tablet) and (max-width: $desktop - 1px)
@content
=touch
@media screen and (max-width: $desktop - 1px)
@content
=desktop
@media screen and (min-width: $desktop)
@content
=desktop-only
@if $widescreen-enabled
@media screen and (min-width: $desktop) and (max-width: $widescreen - 1px)
@content
=until-widescreen
@if $widescreen-enabled
@media screen and (max-width: $widescreen - 1px)
@content
=widescreen
@if $widescreen-enabled
@media screen and (min-width: $widescreen)
@content
=widescreen-only
@if $widescreen-enabled and $fullhd-enabled
@media screen and (min-width: $widescreen) and (max-width: $fullhd - 1px)
@content
=until-fullhd
@if $fullhd-enabled
@media screen and (max-width: $fullhd - 1px)
@content
=fullhd
@if $fullhd-enabled
@media screen and (min-width: $fullhd)
@content
=ltr
@if not $rtl
@content
=rtl
@if $rtl
@content
=ltr-property($property, $spacing, $right: true)
$normal: if($right, "right", "left")
$opposite: if($right, "left", "right")
@if $rtl
#{$property}-#{$opposite}: $spacing
@else
#{$property}-#{$normal}: $spacing
=ltr-position($spacing, $right: true)
$normal: if($right, "right", "left")
$opposite: if($right, "left", "right")
@if $rtl
#{$opposite}: $spacing
@else
#{$normal}: $spacing
// Placeholders
=unselectable
-webkit-touch-callout: none
-webkit-user-select: none
-moz-user-select: none
-ms-user-select: none
user-select: none
%unselectable
+unselectable
=arrow($color: transparent)
border: 3px solid $color
border-radius: 2px
border-right: 0
border-top: 0
content: " "
display: block
height: 0.625em
margin-top: -0.4375em
pointer-events: none
position: absolute
top: 50%
transform: rotate(-45deg)
transform-origin: center
width: 0.625em
%arrow
+arrow
=block($spacing: $block-spacing)
&:not(:last-child)
margin-bottom: $spacing
%block
+block
=delete
@extend %unselectable
-moz-appearance: none
-webkit-appearance: none
background-color: bulmaRgba($scheme-invert, 0.2)
border: none
border-radius: $radius-rounded
cursor: pointer
pointer-events: auto
display: inline-block
flex-grow: 0
flex-shrink: 0
font-size: 0
height: 20px
max-height: 20px
max-width: 20px
min-height: 20px
min-width: 20px
outline: none
position: relative
vertical-align: top
width: 20px
&::before,
&::after
background-color: $scheme-main
content: ""
display: block
left: 50%
position: absolute
top: 50%
transform: translateX(-50%) translateY(-50%) rotate(45deg)
transform-origin: center center
&::before
height: 2px
width: 50%
&::after
height: 50%
width: 2px
&:hover,
&:focus
background-color: bulmaRgba($scheme-invert, 0.3)
&:active
background-color: bulmaRgba($scheme-invert, 0.4)
// Sizes
&.is-small
height: 16px
max-height: 16px
max-width: 16px
min-height: 16px
min-width: 16px
width: 16px
&.is-medium
height: 24px
max-height: 24px
max-width: 24px
min-height: 24px
min-width: 24px
width: 24px
&.is-large
height: 32px
max-height: 32px
max-width: 32px
min-height: 32px
min-width: 32px
width: 32px
%delete
+delete
=loader
animation: spinAround 500ms infinite linear
border: 2px solid $grey-lighter
border-radius: $radius-rounded
border-right-color: transparent
border-top-color: transparent
content: ""
display: block
height: 1em
position: relative
width: 1em
%loader
+loader
=overlay($offset: 0)
bottom: $offset
left: $offset
position: absolute
right: $offset
top: $offset
%overlay
+overlay

@ -0,0 +1,87 @@
@charset "utf-8";
$family-sans-serif: "Open Sans", sans-serif;
$family-code: "Fira Code", monospace;
$body-color: white;
$strong-color: white;
$link: #feda5a;
$link-hover: white;
$input-icon-color: #1d1e22;
$input-icon-active-color: black;
$section-padding-large: 10rem 1.5rem;
$navbar-background-color: #feda6a;
$navbar-item-color: black;
$navbar-item-hover-color: white;
$navbar-item-hover-background-color: #feda6a;
$navbar-burger-color: black;
$footer-background-color: #393f4d;
$footer-color: true;
$card-header-color: white;
$table-head-background-color: #feda6a;
$table-body-background-color: white;
$box-background-color: #feda6a;
$box-padding: 0.25rem;
$media-border-color: #feda6a;
$menu-item-color: white;
$menu-item-hover-color: black;
$menu-item-hover-background-color: #feda6a;
$menu-label-font-size: 1em;
@import "bulma/utilities/_all";
@import "bulma/base/_all";
@import "bulma/elements/_all";
@import "bulma/form/_all";
@import "bulma/components/_all";
@import "bulma/grid/_all";
@import "bulma/helpers/_all";
@import "bulma/layout/_all";
/* open-sans-regular - latin */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 400;
font-display: swap;
src: local('Open Sans Regular'), local('OpenSans-Regular'),
url('/static/css/open-sans-v18-latin-regular.woff2') format('woff2'); /* Chrome 26+, Opera 23+, Firefox 39+ */
}
canvas{
width: 100%;
height: auto;
}
.bg-yayellow{
background-color: #feda6a;
}
.bg-deepmatte{
background-color: #393f4d;
}
.bg-darkslate{
background-color: #1d1e22;
}
.brdr-yayellow{
border: 2px solid #feda6a;
}
.bg-compliment{
background-color: #384667;
}
.fg-yellow{
color: #feda6a;
}

@ -0,0 +1 @@
sass labertasche.scss ../labertasche.css --color --watch --no-source-map

@ -0,0 +1,86 @@
@mixin triangle ($size, $color, $direction) {
height: 0;
width: 0;
$width: nth($size, 1);
$height: nth($size, length($size));
$foreground-color: nth($color, 1);
$background-color: transparent !default;
@if (length($color) == 2) {
$background-color: nth($color, 2);
}
@if ($direction == up) or ($direction == down) or ($direction == right) or ($direction == left) {
$width: $width / 2;
@if $direction == up {
border-left: $width solid $background-color;
border-right: $width solid $background-color;
border-bottom: $height solid $foreground-color;
} @else if $direction == right {
border-top: $width solid $background-color;
border-bottom: $width solid $background-color;
border-left: $height solid $foreground-color;
} @else if $direction == down {
border-left: $width solid $background-color;
border-right: $width solid $background-color;
border-top: $height solid $foreground-color;
} @else if $direction == left {
border-top: $width solid $background-color;
border-bottom: $width solid $background-color;
border-right: $height solid $foreground-color;
}
}
@else if ($direction == up-right) or ($direction == up-left) {
border-top: $height solid $foreground-color;
@if $direction == up-right {
border-left: $width solid $background-color;
} @else if $direction == up-left {
border-right: $width solid $background-color;
}
}
@else if ($direction == down-right) or ($direction == down-left) {
border-bottom: $height solid $foreground-color;
@if $direction == down-right {
border-left: $width solid $background-color;
} @else if $direction == down-left {
border-right: $width solid $background-color;
}
}
@else if ($direction == inset-up) {
border-width: $height $width;
border-style: solid;
border-color: $background-color $background-color $foreground-color;
}
@else if ($direction == inset-down) {
border-width: $height $width;
border-style: solid;
border-color: $foreground-color $background-color $background-color;
}
@else if ($direction == inset-right) {
border-width: $width $height;
border-style: solid;
border-color: $background-color $background-color $background-color $foreground-color;
}
@else if ($direction == inset-left) {
border-width: $width $height;
border-style: solid;
border-color: $background-color $foreground-color $background-color $background-color;
}
}

File diff suppressed because one or more lines are too long

@ -0,0 +1,21 @@
// # /**********************************************************************************
// # * _author : Domeniko Gentner
// # * _mail : code@tuxstash.de
// # * _repo : https://git.tuxstash.de/gothseidank/labertasche
// # * _license : This project is under MIT License
// # *********************************************************************************/
function dashboard_mailsearch(search_txt)
{
let el = document.getElementById('mail-table');
let children = el.children;
for (let i = 0; i < children.length; i++ )
{
children[i].style.display = "none";
let iTxt = children[i].innerText.replace(/(\r\n|\n|\r)/gm, "").trim();
if ( search_txt.value === iTxt.slice(0, search_txt.value.length)){
children[i].style.display = "table-row";
}
}
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save