main
rra 2023-06-12 10:53:53 +02:00
rodzic 5e22155f46
commit 61e005fdd7
96 zmienionych plików z 5984 dodań i 0 usunięć

22
.gitignore vendored 100644
Wyświetl plik

@ -0,0 +1,22 @@
# ---> Hugo
# Generated files by hugo
/public/
/resources/_gen/
.hugo_build.lock
# Executable may be added to repository
hugo.exe
hugo.darwin
hugo.linux
.DS_Store
# solar_v2 theme specific
content/*/*/images/dithers/
content/*/images/dithers/
#Python stuff
__pycache__/
*.py[cod]
*$py.class

220
README.md 100644
Wyświetl plik

@ -0,0 +1,220 @@
# Solar v.2
- Rebuild of Low-tech Magazine's Solar theme with Hugo
- Builds the entire site in minutes rather than hours :)
- Makes use of additional taxonomies that are possible in Hugo
Requires Hugo 0.10x or newer!
## Local Development
```
hugo server
```
## Organizing content
Content is organized as [Hugo Page Bundles](https://gohugo.io/content-management/page-bundles/).
That means that each post is a directory which contains:
* the article (`index.md`)
* the translations (`index.lang.md`)
* the images in the article (`images/`)
* dithered versions of the images (`images/dithers/`)
* comments in various languages (`comments.en.md`)
Example:
```
how-to-build-a-low-tech-internet/
├── comments.en.md
├── images
│   ├── air-jaldi-epostman.png
│   ├── dithers
│   │   ├── air-jaldi-epostman_dithered.png
│   │   ├── freifunk-wifi-node_dithered.png
│   │   ├── node-air-jaldi-network_dithered.png
│   │   ├── node-spanish-guifi-network_dithered.png
│   │   ├── node-tegola_dithered.png
│   │   ├── sneakernet-on-rails_dithered.png
│   │   ├── tegola-project-low-tech-internet_dithered.png
│   │   ├── wifi-link_dithered.png
│   │   └── wireless-links-spanish-guifi-network_dithered.png
│   ├── freifunk-wifi-node.jpg
│   ├── node-air-jaldi-network.png
│   ├── node-spanish-guifi-network.png
│   ├── node-tegola.jpg
│   ├── sneakernet-on-rails.jpg
│   ├── tegola-project-low-tech-internet.png
│   ├── wifi-link.jpg
│   └── wireless-links-spanish-guifi-network.jpg
├── index.de.md
├── index.en.md
├── index.es.md
└── index.fr.md
```
At least one article is required: `index.md` or `index.lang.md`.
## Formatting articles
The design relies on the following [front matter](https://gohugo.io/content-management/front-matter/) fields:
```
---
title: "How to Build a Low-tech Internet"
date: "2015-10-26"
summary: "If we want the internet to keep working in circumstances where access to energy is more limited, we can learn important lessons from alternative network technologies."
slug: "how-to-build-a-low-tech-internet"
lang: "en"
authors: ["Kris De Decker" ]
categories: ["Low-tech Solutions"]
tags: ["ICT" ]
featured_image: "tegola-project-low-tech-internet.png"
draft: False
---
```
In the case of a translation you can specify the translators as well:
__!! Careful, only some metadata should to be translated, the other needs to be left intact.__
Specifically, the metadata keys (`title`, `date`, `summary` etc) should remain intact wheras the metadata values can be translated (such as the contents of `title` or `summary`).
However do __not__ translate the values of `slug`, `categories`, `tags` and `featured_image`.
```
---
title: "Cómo construir una Internet de Baja Tecnología" #TO TRANSLATE
date: "2015-10-26"
summary: "Si queremos que internet siga funcionando en circunstancias en que el acceso a la energía es más limitado, entonces podemos aprender lecciones importantes de las tecnologías de red alternativas." #TO TRANSLATE
slug: "how-to-build-a-low-tech-internet"
lang: "es" #ADD THE CORRECT LANG code (fr, es,etc.)
authors: ["Kris De Decker" ]
categories: ["Low-tech Solutions"]
tags: ["ICT" ]
featured_image: "tegola-project-low-tech-internet.png"
translators: ["Colectivo Disonancia"] #ADD TRANSLATOR FOR THIS LANGUAGE
draft: False
---
```
To add several authors or several tags, we use the following syntax:
```
---
authors: ["Kris De Decker","Marie Verdeil" ]
tags: ["ICT", "another tag", "another other tag"]
---
```
### Image shortcodes
The design relies on shortcodes for images rather than markdown image tags:
`{{% figure src="yutampo2.png" %}} Una borsa dacqua calda giapponese detta yutampo, fatta di plastica rigida. Fonte: All About Japan. [https://allabout-japan.com/en/article/6244/](https://allabout-japan.com/en/article/6244/) {{% /figure %}}
`
### Reader comments
If there are any comments to be rendered under an article they should be in a file called `comments.lang.md` and each comment rendered as such:
```
{{< comment name="Lord Byron" >}}
As the Liberty lads o'er the sea
Bought their freedom, and cheaply, with blood
So we, boys, we
Will die fighting, or live free,
And down with all kings but King Ludd”
{{</ comment >}}
```
## Author & Translator pages
This site builds custom taxonomies for `Authors` and `Translators` which can be accessed via `http://localhost:1313/authors/` and `http://localhost:1313/translators/` respectively. Individual data about each author or translator can be written in `content/authors/authorname/index.md`
# Additional utilities
In `utils` there are various utilities to be used before or after site rendering.
## dithering tool
`dither_images.py` recursively traverses folders and creates dithered versions of the images it finds. These are stored in the same folder as the images in a folder called "dithers".
### Installation & Depedencies
depends on [Pillow](https://pillow.readthedocs.io) and [hitherdither](https://github.com/hbldh/hitherdither)
`pip install Pillow git+https://www.github.com/hbldh/hitherdither`
### Usage
Dither all the images found in the subdirectories of `content`
`python3 utils/dither_images.py --directory content/`
Colorize the dithers as well based on the LTM categories:
`python3 utils/dither_images.py --directory content/ --colorize`
Run the script with more debug output:
`python3 utils/dither_images.py --directory content/ --colorize --verbose`
Remove all dithered files in the subdirectories of `content`:
`python3 utils/dither_images.py --remove --directory content/`
## Page Size Calculator
This script recursively traverses folders and enumerates the file size of all html pages and associated media.
The calculated total file size is then added to the HTML page. The script looks for a `div` with class `page-size` to add the page metadata in to. This div is currently found in `layouts/partials/footer.html`
#### Installation & Dependencies
Relies on BeautifulSoup
`pip install bs4`
#### Usage
This script should be run *after* the site has been generated on the resulting files. It is a post-processing step.
In the case of Hugo, this is usually the directory called `public`. Add the baseurl that you also use in production:
`python3 utils/calculate_size.py --directory public/ --baseURL https://solar.lowtechmagazine.com`
## build_site.sh
This is a script to build the hugo site and run the various support scripts. It assumes you generate and deploy the site on the same machine.
It can be used in `cron` to make a daily build at 12:15 and log the output.
`15 12 * * * /bin/bash /path/to/repo/utils/build_site.sh > /path/to/build.log 2>&1`
## pelican to hugo converter
`convert_to_hugo.py` converts posts of the Pelican format of Solar v1 to Hugo Page Bundles. Needs to be run only **once and never again** because it will _overwrite whatever you have in your content folder!_
You need to edit the file to set the input and output paths etc.
**N.B. this tool will do 95.3% of the work but you will need to manually fix a few individual files**
### Installation & Dependencies
depends on [jinja2](https://jinja.palletsprojects.com/en/3.1.x/)
`pip install jinja2`
### Usage
You need to first edit the file to set the input and output paths. These can be found around line 20.
`python3 utils/convert_to_hugo.py`
# Contributions
The Solar v.2 theme was made by
* [Marie Otsuka](https://motsuka.com/)[^1]
* [Roel Roscam Abbing](https://test.roelof.info)[^1]
* [Marie Verdeil](https://verdeil.net/)
With contributions by
* [Erhard Maria Klein](http://www.weitblick.de/)
[^1]: Marie and Roel created the [original Pelican theme](https://github.com/lowtechmag/solar) for the first version of https://solar.lowtechmagazine.com

Wyświetl plik

@ -0,0 +1,84 @@
@media print {
html {
font-size: 10.5pt;
}
body {
background: none;
}
h1, footer h2 {
font-size: 1.2rem;
margin: 0;
display: inline-block;
}
.subtitle {
display: inline-block;
margin: 0;
.icon {
height: 1rem;
}
}
h1.entry-title {
font-size: 2rem;
margin-top: 2rem;
}
p.summary {
margin-bottom: 1rem;
}
.entry-content {
columns: 2;
column-gap: 20pt;
a {
text-decoration: none;
}
a:after {
content:" (" attr(href) ") ";
font-size: var(--font-small);
font-weight: normal;
}
h2 {
font-size: 1.5rem;
margin-bottom: 0.5rem;
}
figure {
max-width: none;
margin: 1rem auto 0.5rem auto;
background-color: white !important;
mix-blend-mode: normal !important;
page-break-inside: avoid;
img {
max-width: 100%;
mix-blend-mode: normal !important;
}
}
.caption {
margin-bottom: 1rem;
}
h2, h3, p, .footnote {
max-width: none;
width: 100%;
}
p {
margin: 0;
line-height: 1.2;
text-indent: 2rem;
}
h2+p, .caption, .caption+p, .footnote p {
text-indent: 0;
}
blockquote p {
line-height: 1.1;
text-indent: 0;
margin: 1rem 0;
page-break-inside: avoid;
}
}
#battery_data {
display: none;
}
nav, #battery, #comment-list, #related, #post-nav, ul.cols .featured-img, footer .dashboard {
display: none;
}
ul.cols li {
max-width: 50%;
}
}

1270
assets/css/style.css 100644

Plik diff jest za duży Load Diff

File diff suppressed because one or more lines are too long

1312
assets/css/style.scss 100644

Plik diff jest za duży Load Diff

163
config/config.toml 100644
Wyświetl plik

@ -0,0 +1,163 @@
baseURL = "/"
languageCode = "en-us"
title = "LOW←TECH MAGAZINE"
buildFuture = true
buildDrafts = false
paginate = 12
DefaultContentLanguage = "en"
pluralizelisttitles = "false"
refLinksErrorLevel = "WARNING"
refLinksNotFoundURL = "/"
timeout = 60000
rssLimit = 3
[markup.goldmark.renderer]
unsafe = true
[markup.goldmark.parser.attribute]
block = true
[markup]
[markup.highlight]
anchorLineNos = false
codeFences = true
guessSyntax = false
hl_Lines = ''
hl_inline = false
lineAnchors = ''
lineNoStart = 1
lineNos = false
lineNumbersInTable = true
noClasses = true
noHl = false
style = 'onesenterprise'
tabWidth = 4
enableInlineShortcodes = true
[permalinks]
posts = '/:year/:month/:slugorfilename/'
categories = '/:slug/'
[params]
author = "Kris De Decker"
newsletter = "https://d69baa34.sibforms.com/serve/MUIEAJWIw9w82Dl4ua6FQArPaI-3Qb-zVTwPNabHQgFH51MiGF69Smy9LOC_HPoUmBj0emaXsXT87gcQXDPvtu-AZsJCHWhkkv21CdrcQu4GdnYAhZ-MrIPhwGDecagLzYxqfvkaqXg2ODcbJU4ByoDmzJK3ZTczDo2jcWtfn-En0MGKLVkgxx9TgdHqYoPabMJCMF-agLEclEwv"
description = "This is a solar-powered website, which means it sometimes goes offline"
[menu]
[[menu.main]]
identifier = "about"
name = "About"
url = "/about/"
weight = 1
[[menu.main]]
identifier = "lowtechtitle"
name = "Low-tech Solutions"
url = "/low-tech-solutions/"
weight = 2
[[menu.main]]
identifier = "hightechtitle"
name = "High-tech Problems"
url = "/high-tech-problems/"
weight = 3
[[menu.main]]
identifier = "obsoletetitle"
name = "Obsolete Technology"
url = "/obsolete-technology/"
weight = 4
[[menu.main]]
identifier = "offline"
name = "Offline Reading"
url = "/offline-reading/"
weight = 5
[[menu.main]]
identifier = "archives"
name = "Archive"
url = "/archives/"
weight = 6
[[menu.main]]
identifier = "donate"
name = "Donate"
url = "/donate/"
weight = 7
[[menu.main]]
identifier = "ntm"
name = "NTM"
url = "https://www.notechmagazine.com/"
weight = 8
defaultContentLanguage = 'en'
[languages]
[languages.en]
languageName = 'English'
weight = 1
[languages.fr]
languageName = 'Français'
description = ''
weight = 2
[languages.de]
languageName = 'Deutsch'
weight = 3
[languages.nl]
languageName = 'Nederlands'
weight = 4
[languages.es]
languageName = 'Español'
weight = 5
[languages.it]
languageName = 'Italiano'
weight = 6
[languages.pt]
languageName = 'Português'
weight = 7
[languages.pl]
languageName = 'Polski'
weight = 8
[languages.ar]
languageName = 'العربية'
languagedirection = 'rtl'
weight = 9
[languages.vn]
languageName = 'Tiếng Việt'
weight = 10
[languages.ko]
languageName = '한국어'
weight = 11
[taxonomies]
author = "authors"
tag = "tags"
category = "categories"
translator = "translators"
lang = "languages"
[related]
includeNewer = true
threshold = 80
toLower = false
[[related.indices]]
name = 'tags'
weight = 100
[[related.indices]]
name = 'lang'
weight = 80

Wyświetl plik

@ -0,0 +1,11 @@
---
title: "Nicht gefunden"
summary: "Diese Seite konnte nicht gefunden werden"
noindex: true
lang: de
url: "404"
categories: [""]
---
Es scheint, dass etwas schief gelaufen ist.... Möglicherweise wurden einige Beiträge verschoben und dieser Link wurde noch nicht aktualisiert. Oder vielleicht ist dieser Artikel noch nicht übersetzt?
Bitte konsultieren Sie unser [Archiv](de/archives/), um schnell alle Inhalte wiederzufinden.

Wyświetl plik

@ -0,0 +1,11 @@
---
title: "Not Found"
summary: "This page could not be found"
noindex: true
lang: en
url: "404"
categories: [""]
---
It seems that something went wrong.... Some posts may have been moved and this link has not been updated yet. Or maybe this article isn't yet translated?
Please consult our [archives](/archives/) to quickly find back all content.

Wyświetl plik

@ -0,0 +1,11 @@
---
title: "Erreur 404"
summary: "La page que vous recherchez est introuvable"
noindex: true
lang: fr
url: "404"
categories: [""]
---
Il semble que la page que vous recherchez n'existe pas, ou n'existe plus. Il se peut qu'elle n'est pas encore été traduite ou qu'elle ai changé d'emplacement.
Consultez notre [archive](/fr/archives/) pour retrouver facilment tout le contenu disponible.

Wyświetl plik

@ -0,0 +1,12 @@
---
title: "Niet gevonden"
summary: "Deze pagina kon niet worden gevonden"
noindex: true
lang: nl
url: "404"
categories: [""]
---
We zijn nog bezig met het overzetten van posts en wellicht is deze link nog niet overgezet of vertaald.
Raadpleeg het [archief](/nl/archives/) om snel artikelen te kunnen terugvinden.

Wyświetl plik

@ -0,0 +1,5 @@
---
cascade:
_build:
publishResources: false
---

Wyświetl plik

@ -0,0 +1,5 @@
---
cascade:
_build:
publishResources: false
---

Wyświetl plik

@ -0,0 +1,5 @@
---
cascade:
_build:
publishResources: false
---

Wyświetl plik

@ -0,0 +1,5 @@
---
cascade:
_build:
publishResources: false
---

Wyświetl plik

@ -0,0 +1,5 @@
---
cascade:
_build:
publishResources: false
---

Wyświetl plik

@ -0,0 +1,5 @@
---
cascade:
_build:
publishResources: false
---

Wyświetl plik

@ -0,0 +1,5 @@
---
cascade:
_build:
publishResources: false
---

Wyświetl plik

@ -0,0 +1,5 @@
---
cascade:
_build:
publishResources: false
---

Wyświetl plik

@ -0,0 +1,5 @@
---
cascade:
_build:
publishResources: false
---

Wyświetl plik

@ -0,0 +1,5 @@
---
cascade:
_build:
publishResources: false
---

Wyświetl plik

@ -0,0 +1,5 @@
---
cascade:
_build:
publishResources: false
---

Wyświetl plik

@ -0,0 +1,9 @@
---
title: Archive
slug: archives
lang: ar
summary: archive intro
layout: archive
---
Archive page content

Wyświetl plik

@ -0,0 +1,9 @@
---
title: Archiv
slug: archives
lang: de
summary: archive intro
layout: archive
---
Archive page content

Wyświetl plik

@ -0,0 +1,9 @@
---
title: Archivo
slug: archives
lang: es
summary: archive intro
layout: archive
---
Archive page content

Wyświetl plik

@ -0,0 +1,9 @@
---
title: Archive
slug: archives
lang: fr
summary: archive intro
layout: archive
---
Archive page content

Wyświetl plik

@ -0,0 +1,7 @@
---
title: Archivio
slug: archives
lang: it
layout: archive
---

Wyświetl plik

@ -0,0 +1,9 @@
---
title: Archive
slug: archives
lang: ko
summary: archive intro
layout: archive
---
Archive page content

Wyświetl plik

@ -0,0 +1,9 @@
---
title: Archive
slug: archives
lang: en
summary: archive intro
layout: archive
---
Archive page content

Wyświetl plik

@ -0,0 +1,9 @@
---
title: Archief
slug: archives
lang: nl
summary: archive intro
layout: archive
---
Archive page content

Wyświetl plik

@ -0,0 +1,9 @@
---
title: Archiwum
slug: archives
lang: pl
summary: archive intro
layout: archive
---
Archive page content

Wyświetl plik

@ -0,0 +1,9 @@
---
title: Archive
slug: archives
lang: pt
summary: archive intro
layout: archive
---
Archive page content

Wyświetl plik

@ -0,0 +1,9 @@
---
title: Archive
slug: archives
lang: vn
summary: archive intro
layout: archive
---
Archive page content

Wyświetl plik

@ -0,0 +1,7 @@
---
---
{{< comment name="Name" >}}
This is the comment text.
{{</ comment >}}

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 40 KiB

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 201 KiB

Wyświetl plik

@ -0,0 +1,393 @@
---
title: "Article Template: How to write articles and translations in Markdown for Hugo?"
date: ""
summary: "This page goes over the specific markdown syntax that should be used to write articles, add translations and comments in the new hugo solar web. "
lang: "en"
authors: ["Marie Verdeil" ]
categories: [""]
tags: [""]
unlisted: true
draft: false
featured_image: "image.png"
---
{{% figure src="image.png" %}}
A screenshot of the markdown file for this page.
{{% /figure %}}
## Table of Contents
- [Files](#files)
- [Structure](#structure)
- [Creating a new article](#folder-name)
- [Index](#index)
- [Comments file](#comments-files)
- [Article Syntax](#syntax)
- [Front Matter](#front-matter)
- [Syntax rules](#rules)
- [Main rules](#main-rules)
- [Internal links](#internal-links)
- [Image shortcodes](#syntax-images)
- [Comments](#syntax-comments)
- [Translations](#translations)
- [Translations front matter](#translations-front-matter)
- [Translations internal links](#translations-internal-links)
- [Translations of site metadata](#translations-meta)
## Files {#files}
Each articles lives in the folder `posts` on Gitlab, with the following structure.
#### **Structure** {#structure}
Each post/article is a folder which contains:
* the article in english (`index.md`) or (`index.en.md`)
* the translations (`index.lang.md`)
* the images in the article (`images/`)
* dithered versions of the images (`images/dithers/`)
* comments in various languages (`comments.en.md`)
Example:
```
how-to-build-a-low-tech-internet/
├── comments.en.md
├── images
│   ├── air-jaldi-epostman.png
│   ├── dithers
│   │   ├── air-jaldi-epostman_dithered.png
│   │   ├── freifunk-wifi-node_dithered.png
│   │   ├── node-air-jaldi-network_dithered.png
│   │   ├── node-spanish-guifi-network_dithered.png
│   │   ├── node-tegola_dithered.png
│   │   ├── sneakernet-on-rails_dithered.png
│   │   ├── tegola-project-low-tech-internet_dithered.png
│   │   ├── wifi-link_dithered.png
│   │   └── wireless-links-spanish-guifi-network_dithered.png
│   ├── freifunk-wifi-node.jpg
│   ├── node-air-jaldi-network.png
│   ├── node-spanish-guifi-network.png
│   ├── node-tegola.jpg
│   ├── sneakernet-on-rails.jpg
│   ├── tegola-project-low-tech-internet.png
│   ├── wifi-link.jpg
│   └── wireless-links-spanish-guifi-network.jpg
├── index.de.md
├── index.en.md
├── index.es.md
└── index.fr.md
```
#### **Folder Name** {#folder-name}
To create on new article, be sure to create a new folder in `posts/` with the name of the article, containing at least:
- 1 index file
- 1 comments file (see below)
- 1 `images/` folder where your images will live.
To create an new page that isn't an article, place the folder directly in `content/` or in `content/about/` for the about section.
By default, your folder name is the article slug. The slug should match the title, but with the following rules. Use `"-"`instead of `" "` spaces and **don't** include special character (no `,=;/%?! &.@` etc.)
```
/posts/my-article-name/index.en.md
```
will become:
```
https://solar.lowtechmagazine.com/YYYY/MM/my-article-name/
```
### **Index** {#index}
The article content should be in a file named `index.lang.md`. For an english article it would be `index.en.md`, for french translation `index.fr.md`, etc.
Regarding the syntax of the index files see the [Syntax section below](#syntax)..
### **Comments file** {#comments-files}
The comments should now be placed in a different file in the article folder. The different comments are separated by languages and will appear in the corresponding article version. Each comments files should be named "comments.lang.md". So for english it would be `comments.en.md`, for dutch `comments.nl.md`, etc.
The Comments will appear automatically at the end of the article, no need to add anything in the article file.
Regarding the syntax of the comments files see the [Comments section below](#syntax-comments).
### **Output**
The folder should look like this at start:
```
my-article-name/
├── comments.en.md
├── images/
│   └── images goes here.
└── index.lang.md
```
## Syntax {#syntax}
The syntax of the markdown file has a few changes and additions in Hugo, which we will go over in this section. The specific changes related to Translations can be found in the[Translations section below](#translations).
## Front matter data {#front-matter}
The Front matter data at the top of each article should follow the following syntax:
```yaml
---
title: "Article Title"
date: "2015-10-26"
summary: "Article Summary"
lang: "en"
authors: ["Kris De Decker" ]
categories: ["Low-tech Solutions"]
tags: ["tag", "tag2" ]
translators: [""]
featured_image: "image.png"
draft: false
---
```
**_!Do not forget the `---` at the first and last line of the front matter!_**
- Date should use the following YYYY-MM-DD syntax.
```yaml
date: "2015-10-26"
```
- Language should be using on the following: en (english), nl (dutch), fr (french) pl (polish), pt (portuguese), es (spanish), de (german), it (italian ), vn (vietnamese), ar (arabic), ko (korean).
To add a different language translations changes in the config file are necessary.
```yaml
lang: "en"
```
- The authors, tags and translators fields support several entry, using this syntax:
```yaml
authors: ["Kris De Decker" ]
authors: ["Kris De Decker", "Roel Roscam Abbing" ]
tags: ["ICT", "transportation" ]
```
- The correct spelling for categories is:
`"Low-tech Solutions"` (Blue), `"High-tech Problems"` (Red), `"Obsolete Technology"` (Green), `"About"` or `" "` (BW)
```yaml
categories: ["Low-tech Solutions"]
```
- The featured image will appear as a thumbnail on the category page. Make sure the image is placed inside the `images/` folder. Do not include the file path, just the image with the correct extension (.png, .jpg).
```yaml
featured_image: "image.png"
```
- `draft: false` is the default. Setting this to `draft: true` will not generate the article. It will not be visible on the site anymore, only on gitlab.
```yaml
draft: false
```
_**Always include at least:**_ `title: "", date: "", summary: "Article Summary", lang: "en"`
Other metadata fields are available:
- `slug: ""` : By default, the slug is the filename but you can overwrite this by adding a slug.
```yaml
slug: "this-is-a-slug"
```
- `unlisted: true` : Include this field to mark the article as unlisted: it will still be accessible via the url but won't be listed in the index page.
```yaml
unlisted: true
```
- `translators: [""]` : see [Translations section below](#translations)
```yaml
translators: [""]
```
## Syntax Rules {#rules}
The rest of of the document uses [regular markdown syntax](https://www.markdownguide.org/cheat-sheet), with a few exception. Markup conventions as follows:
### **Main rules**
- `## Big headers are h2` and render as:
## (Big headers are h2)
- `### Sub-header are h3` and render as:
### Sub-header are h3
- `> Quotes` render as:
> Quotes
- `* Lists` / ` - Lists` render as this list.
* _Footnote references_ use this syntax: `[^number]` and render as [^1]
* _Footnotes_ appear the bottom of the document. The syntax is `[^1]: text`
[^1]: Footnotes appear here the bottom of the document.
- `[Hyperlinks](url)` linking to other websites render as: [Hyperlinks](url)
### **Internal Links**
To link to other articles on the solar website, we use a hugo specific shortcode to call the article folder. This has several advantages:
1. The url will not break if the article slug changes, since we are calling the file itself.
2. We don't need to change the url when translating an article, it's automatic: see [translations section](#translations-internal-links).
- _Shortcode is written as follow and looks like this:_ [Text](/).
```go
[Text]({{</* ref "/path-to-folder" /*>}})
```
The file path should start from within the content folder and link to the article or page folder, not the slug!
- _Examples:_
```go
[Donate]({{</* ref "/donate" */>}})
[here]({{</* ref "/posts/power-water-networks/" */>}})
```
* _To link to a section in the article_ (render as: [Link to section](#section).)
```go
[Link to Section](#section)
### Section Header{#section}
```
## Images shortcodes {#syntax-images}
Images now use specific shortcodes instead of the classic markdown syntax. This allows t include a toggle linking to the original images and to embed the caption within the image and better control its styling.
The shortcode is written:
``` go
{{%/* figure src="image-1.png" %}}
Here goes the image caption.
You can include footnotes [^1],
[Hyperlinks](https://solar.lowtechmagazine.com),
and *regular* __markdown__ syntax.
{{% /figure */%}}
```
and render as:
{{% figure src="image-1.png" %}}
This is an image of the shortcode that generated it. You can include footnotes [^1],
[Hyperlinks](https://solar.lowtechmagazine.com),
and *regular* __markdown__ syntax.
{{% /figure %}}
Captions are handy to include sources and additional info but are also useful for screen-readers users (people who cannot see images). Describing the image is thoughtful of them.
To render uncompressed images (not dithered and not compressed in `.webp`), use the normal markdown syntax. This comes in handy for comic pages, for example. Please pre-compress the images to prevent overcrowding the server with big files.
```markdown
![here goes your alt text ](image-filename.png)
```
## Comments {#syntax-comments}
Comments are now added in a dedicated `comments.lang.md` file, as explained above.
The file should start with the following lines:
```yaml
---
---
```
Each comment is then added:
```go
{{</* comment name="Name" >}}
This is the comment text.
{{</ comment */>}}
```
Check out the result [at the bottom](#comments-title)
## Translations {#translations}
To translate an article in a different language, another `index.lang.md` file should be created in the article folder as detailed above.
### **Front matter** {#translations-front-matter}
Not all front matter should be translated, or the website might give an error.
- _Front-matter that should be translated:_
```yaml
---
title: "Translate the title"
date: "YYYY-MM-DD" #of the translation
summary: "Translate the Article Summary"
lang: "en" #add the language code (fr, nl, etc.)
translators: ["add translator name", "other translator"]
---
```
- _Front matter that shouldn't change, no matter the language:_
```yaml
---
authors: ["Kris De Decker" ]
categories: ["Low-tech Solutions"]
tags: ["tag", "tag2" ]
featured_image: "image.png"
draft: false
---
```
### **Internal Links** {#translations-internal-links}
When linking to articles on the website, the shortcode will handle directing to the correct translation automatically:
On a french article `index.fr.md` the link will redirect to the french [donate](/fr/donate) page.
```go
[Donate]({{</* ref "/donate" */>}})
```
Another example: this article [Bring back the Horses]({{< ref "/posts/bring-back-the-horses" >}}) isn't yet available in dutch. The shortcode below in a `index.nl.md_ file would lead to the english version, until the dutch translation is available:
```go
[Bring back the Horses]({{</* ref "/posts/bring-back-the-horses" */>}})
```
[^1]: Footnote that are correctly linked appear here at the bottom of the document. You should use the following syntax for the footnotes:
### **Translating Site Metadata** {#translations-meta}
Another thing that needs to be translated is the many metadata words used in the website. Such as:
- "Translated by"
- "Written by"
- "View Original Image / View Dithered Image"
- "Subscribe to our Newsletter"
- etc.
This metadata is stored in configuration files called `lang.toml` (`pl.toml`, `fr.toml`, etc.). Find this folder in `solar > i18n > lang.toml`
_The syntax is (here for `nl.toml`):_
```toml
[pagesize]
other = 'Fill in here the word for page size'
[written_by]
other = 'Door'
[translated_by]
other = 'Vertaald door'
```
The `[key]` should not be changed and be the same in every language.
The most complete files are the french (`fr.toml`) and dutch (`nl.toml`) one, refer to those to know what expressions need translation. Untranslated expressions will default back to english.
Please reach out _marie @ verdeil . net_ if you have any remaining questions.

Wyświetl plik

@ -0,0 +1,10 @@
---
title: Mitwirkende
slug: contributors
lang: de
summary: "Autor*innen und Übersetzer*innen von LOW←TECH MAGAZINE"
layout: contributors
---

Wyświetl plik

@ -0,0 +1,10 @@
---
title: Contributors
slug: contributors
lang: en
summary: "Authors and translators for LOW←TECH MAGAZINE"
layout: contributors
---

Wyświetl plik

@ -0,0 +1,11 @@
---
title: Contributeurs
slug: contributors
lang: fr
summary: "Auteur.rices et traducteur.rices (en français) de LOW←TECH MAGAZINE"
layout: contributors
---

Wyświetl plik

@ -0,0 +1,11 @@
---
title: Contributors
slug: contributors
lang: nl
summary: "Authors and translators for LOW←TECH MAGAZINE"
layout: contributors
---

Wyświetl plik

@ -0,0 +1,67 @@
---
title: "Feeds"
date: ""
summary: "The website is available via feeds."
slug: "feeds"
aliases: "rss"
lang: "en"
authors: ["" ]
categories: [""]
tags: []
url: /feeds/
---
We provide multiple feeds for feed readers. There is one feed for each language.
## Arabic
- [Arabic full feed (RSS)](/ar/posts/index.xml)
## Dutch
- [Dutch full feed (RSS)](/nl/posts/index.xml)
## English
- [English full feed (RSS)](/posts/index.xml)
## French
- [French full feed (RSS)](/fr/posts/index.xml)
## German
- [German full feed (RSS)](/de/posts/index.xml)
## Italian
- [Italian full feed (RSS)](/it/posts/index.xml)
## Korean
- [Korean full feed (RSS)](/ko/posts/index.xml)
## Polish
- [Polish full feed (RSS)](/pl/posts/index.xml)
## Portuguese
- [Portuguese full feed (RSS)](/pt/posts/index.xml)
## Spanish
- [Spanish full feed (RSS)](/es/posts/index.xml)
## Vietnamese
- [Vietnamese full feed (RSS)](/vn/posts/index.xml)

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 519 KiB

Wyświetl plik

@ -0,0 +1,42 @@
---
title: "Power"
date: ""
summary: "This website runs on a [solar powered server](/de/about/the-solar-website) located in Barcelona, and will go off-line during longer periods of bad weather. This page shows live data relating to power supply, power demand, and energy storage."
slug: "power"
lang: "de"
authors: ["" ]
categories: [""]
tags: []
featured_image: "solar-powered-server-weather-2.png"
---
## Power supply
This is a forecast for the coming days, updated daily:
<p class="forecast">
</p>
This weather forecast is [powered by BrightSky](https://brightsky.dev/).
## Power demand
These are live power statistics of the solar powered server:
<dl id="server">
</dl>
(* load average per 15 minutes)
## Battery meter
The background of this website is a battery meter, designed to always display the relationship of the energy powering the website and the visitor traffic consuming it. Since 12 January 2020, the website runs on a 30W solar panel and a 168 Wh lead-acid battery.
We also installed a new battery meter, which simply represents the voltage of our battery. For example, a storage capacity of 68% converts to 12.68V, and the other way around. Showing a correct representation of the storage capacity requires calibration and algorithms in relation to a specific battery. This is troublesome, because we [keep experimenting with different sizes of batteries and solar panels]({{< ref "/posts/how-sustainable-is-a-solar-powered-website" >}}) to find the optimal balance between uptime and sustainability.
The voltage reading is the "naked" data on which each battery meter relies to display a percentage. The voltage of the battery doesn't always correlate with the energy storage capacity -- it's also influenced by the electric load (which lowers the voltage) and the solar insolation (which increases the voltage).
Because our load (the server) has a rather constant power use, during the day our battery meter reflects the local solar conditions. If the panel receives full sun, the voltage will most likely raise above 13V, colouring the whole website in yellow. However, if it's cloudy enough, the battery meter will decrease and the blue background is revealed.
Because our load is constant, during the night the battery meter reflects the storage capacity of the battery accurately. When the voltage of the battery drops below 12V, and the whole page is coloured in blue, the solar charge controller shuts down the system and the website goes offline.
{{% figure src="solar-powered-server-weather-2.png" %}} The accessibility of this website depends on the weather in Barcelona, Spain. {{% /figure %}}

Wyświetl plik

@ -0,0 +1,43 @@
---
title: "Power"
date: ""
summary: "This website runs on a [solar powered server](/about/the-solar-website) located in Barcelona, and will go off-line during longer periods of bad weather. This page shows live data relating to power supply, power demand, and energy storage."
slug: "power"
lang: "en"
authors: ["" ]
categories: [""]
tags: []
featured_image: "solar-powered-server-weather-2.png"
---
## Power supply
This is a forecast for the coming days, updated daily:
<p class="forecast">
</p>
This weather forecast is [powered by BrightSky](https://brightsky.dev/).
## Power demand
These are live power statistics of the solar powered server:
<dl id="server">
</dl>
(* load average per 15 minutes)
## Battery meter
The background of the top of every page is a battery meter, designed to display the relationship of the energy powering the website and the visitor traffic consuming it.
The battery meter simply represents the voltage of our battery. For example, a storage capacity of 68% equals 12.68V, and a storage capacity of 8% equals 12.08V. The voltage reading is the "naked" data on which each battery meter relies to display a percentage. It doesn't always correlate with the energy storage capacity, because it's also influenced by the electric load (which lowers the voltage) and the solar insolation (which increases the voltage).
Because our load (the server) has a rather constant power use, during the day our battery meter reflects the local solar conditions. If the panel receives full sun, the voltage will raise above 13V, coloring the whole website in yellow. However, if it gets cloudy, the battery meter will decrease and the blue background is revealed. During the night, the battery meter reflects the storage capacity of the battery accurately.
When the voltage of the battery drops below 12V, and the whole page is coloured in blue, the solar charge controller shuts down the system and the website goes offline. It will come back when the panel receives full sun again.
Showing a "correct" representation of the storage capacity requires calibration and algorithms in relation to a specific battery. This is troublesome, because we [keep experimenting with different sizes of batteries and solar panels]({{< ref "/posts/how-sustainable-is-a-solar-powered-website" >}}) to find the optimal balance between uptime and sustainability. Furthermore, the naked data are more informative for people who understand how a solar PV system works. For example, the battery meter also reveals the state and age of the battery.
Since 12 January 2020, the website runs on a 30W solar panel and a (new) 168 Wh lead-acid battery. From September 2018 to January 2020, the website was powered by a 50W solar panel and an (old) 86 Wh battery.
{{% figure src="solar-powered-server-weather-2.png" %}} The accessibility of this website depends on the weather in Barcelona, Spain. {{% /figure %}}

Wyświetl plik

@ -0,0 +1,44 @@
---
title: "Power"
date: ""
summary: "This website runs on a [solar powered server](/about/the-solar-website) located in Barcelona, and will go off-line during longer periods of bad weather. This page shows live data relating to power supply, power demand, and energy storage."
slug: "power"
lang: "es"
authors:
categories: [""]
tags: []
featured_image: "solar-powered-server-weather-2.png"
---
## Power supply
This is a forecast for the coming days, updated daily:
<p class="forecast">
</p>
This weather forecast is [powered by BrightSky](https://brightsky.dev/).
## Power demand
These are live power statistics of the solar powered server:
<dl id="server">
</dl>
(* load average per 15 minutes)
## Battery meter
The background of this website is a battery meter, designed to always display the relationship of the energy powering the website and the visitor traffic consuming it. Since 12 January 2020, the website runs on a 30W solar panel and a 168 Wh lead-acid battery.
We also installed a new battery meter, which simply represents the voltage of our battery. For example, a storage capacity of 68% converts to 12.68V, and the other way around. Showing a correct representation of the storage capacity requires calibration and algorithms in relation to a specific battery. This is troublesome, because we [keep experimenting with different sizes of batteries and solar panels]({{< ref "/posts/how-sustainable-is-a-solar-powered-website" >}}) to find the optimal balance between uptime and sustainability.
The voltage reading is the "naked" data on which each battery meter relies to display a percentage. The voltage of the battery doesn't always correlate with the energy storage capacity -- it's also influenced by the electric load (which lowers the voltage) and the solar insolation (which increases the voltage).
Because our load (the server) has a rather constant power use, during the day our battery meter reflects the local solar conditions. If the panel receives full sun, the voltage will most likely raise above 13V, colouring the whole website in yellow. However, if it's cloudy enough, the battery meter will decrease and the blue background is revealed.
Because our load is constant, during the night the battery meter reflects the storage capacity of the battery accurately. When the voltage of the battery drops below 12V, and the whole page is coloured in blue, the solar charge controller shuts down the system and the website goes offline.
{{% figure src="solar-powered-server-weather-2.png" %}} The accessibility of this website depends on the weather in Barcelona, Spain. {{% /figure %}}

Wyświetl plik

@ -0,0 +1,41 @@
---
title: "Power"
date: ""
summary: "This website runs on a [solar powered server](/fr/about/the-solar-website) located in Barcelona, and will go off-line during longer periods of bad weather. This page shows live data relating to power supply, power demand, and energy storage."
slug: "power"
lang: "fr"
authors: ["" ]
categories: [""]
tags: []
featured_image: "solar-powered-server-weather-2.png"
---
## Power supply
This is a forecast for the coming days, updated daily:
<p class="forecast">
</p>
This weather forecast is [powered by BrightSky](https://brightsky.dev/).
## Power demand
These are live power statistics of the solar powered server:
<dl id="server">
</dl>
(* load average per 15 minutes)
## Battery meter
The background of this website is a battery meter, designed to always display the relationship of the energy powering the website and the visitor traffic consuming it. Since 12 January 2020, the website runs on a 30W solar panel and a 168 Wh lead-acid battery.
We also installed a new battery meter, which simply represents the voltage of our battery. For example, a storage capacity of 68% converts to 12.68V, and the other way around. Showing a correct representation of the storage capacity requires calibration and algorithms in relation to a specific battery. This is troublesome, because we [keep experimenting with different sizes of batteries and solar panels]({{< ref "/posts/how-sustainable-is-a-solar-powered-website" >}}) to find the optimal balance between uptime and sustainability.
The voltage reading is the "naked" data on which each battery meter relies to display a percentage. The voltage of the battery doesn't always correlate with the energy storage capacity -- it's also influenced by the electric load (which lowers the voltage) and the solar insolation (which increases the voltage).
Because our load (the server) has a rather constant power use, during the day our battery meter reflects the local solar conditions. If the panel receives full sun, the voltage will most likely raise above 13V, colouring the whole website in yellow. However, if it's cloudy enough, the battery meter will decrease and the blue background is revealed.
Because our load is constant, during the night the battery meter reflects the storage capacity of the battery accurately. When the voltage of the battery drops below 12V, and the whole page is coloured in blue, the solar charge controller shuts down the system and the website goes offline.
{{% figure src="solar-powered-server-weather-2.png" %}} The accessibility of this website depends on the weather in Barcelona, Spain. {{% /figure %}}

Wyświetl plik

@ -0,0 +1,44 @@
---
title: "Power"
date: ""
summary: "This website runs on a [solar powered server](/it/about/the-solar-website) located in Barcelona, and will go off-line during longer periods of bad weather. This page shows live data relating to power supply, power demand, and energy storage."
slug: "power"
lang: "it"
authors:
categories: [""]
tags: []
featured_image: "solar-powered-server-weather-2.png"
---
## Power supply
This is a forecast for the coming days, updated daily:
<p class="forecast">
</p>
This weather forecast is [powered by BrightSky](https://brightsky.dev/).
## Power demand
These are live power statistics of the solar powered server:
<dl id="server">
</dl>
(* load average per 15 minutes)
## Battery meter
The background of this website is a battery meter, designed to always display the relationship of the energy powering the website and the visitor traffic consuming it. Since 12 January 2020, the website runs on a 30W solar panel and a 168 Wh lead-acid battery.
We also installed a new battery meter, which simply represents the voltage of our battery. For example, a storage capacity of 68% converts to 12.68V, and the other way around. Showing a correct representation of the storage capacity requires calibration and algorithms in relation to a specific battery. This is troublesome, because we [keep experimenting with different sizes of batteries and solar panels]({{< ref "/posts/how-sustainable-is-a-solar-powered-website" >}}) to find the optimal balance between uptime and sustainability.
The voltage reading is the "naked" data on which each battery meter relies to display a percentage. The voltage of the battery doesn't always correlate with the energy storage capacity -- it's also influenced by the electric load (which lowers the voltage) and the solar insolation (which increases the voltage).
Because our load (the server) has a rather constant power use, during the day our battery meter reflects the local solar conditions. If the panel receives full sun, the voltage will most likely raise above 13V, colouring the whole website in yellow. However, if it's cloudy enough, the battery meter will decrease and the blue background is revealed.
Because our load is constant, during the night the battery meter reflects the storage capacity of the battery accurately. When the voltage of the battery drops below 12V, and the whole page is coloured in blue, the solar charge controller shuts down the system and the website goes offline.
{{% figure src="solar-powered-server-weather-2.png" %}} The accessibility of this website depends on the weather in Barcelona, Spain. {{% /figure %}}

Wyświetl plik

@ -0,0 +1,39 @@
---
title: "Energie"
date: ""
summary: "Deze website draait op [zonne-energie](/nl/about/the-solar-website). De server staat opgesteld in Barcelona en gaat offline gedurende langere periodes van slecht weer. Deze pagina geeft \"live\" informatie weer over de productie van zonne-energie, de energieopslag, en het energieverbruik van de website."
slug: "power"
lang: "nl"
authors: ["" ]
categories: [""]
tags: []
featured_image: "solar-powered-server-weather-2.png"
---
## Energieproductie
Dit is de lokale weersverwachting voor de komende dagen (dagelijkse update):
<p class="forecast">
</p>
Dit weerbericht [powered by BrightSky](https://brightsky.dev/).
## Energievraag
Dit zijn de data die door de server worden geregistreerd:
<dl id="server">
</dl>
(* gemiddelde per 15 minuten)
## Energieopslag
De achtergrond van deze website is een batterijmeter, die aangeeft over hoeveel energieopslag de zonne-installatie beschikt. Sinds 12 januari 2020 draait de website op haar huidige configuratie: een zonnepaneel van 30W en een loodzuurbatterij van 168 watt-uur.
De batterijmeter toont het voltage van de batterij. Bijvoorbeeld een opslagcapaciteit van 68% komt overeen met een voltage van 12.68 volt. Het voltage komt echter niet altijd overeen met de capaciteit van de energieopslag.
De elektrische lading van de server (die het voltage van de batterij verlaagt) kan makkelijk worden ingecalculeerd omdat het elektriciteitsverbruik van de server vrijwel constant is. Maar de zonnestraling (die het voltage van de batterij doet toenemen) is daarentegen heel veranderlijk.
Tijdens de nacht reflecteert het voltage accuraat de opslagcapaciteit van de batterij: de batterijmeter is dan heel precies en zal langzaam zakken. Overdag weerspiegelt de batterijmeter de lokale weersomstandigheden. Als het zonnepaneel volle zon ontvangt, stijgt het voltage boven de 13 volt en wordt de hele website in het geel gekleurd. Als het bewolkt genoeg is, dan zakt de batterijmeter en wordt de blauwe achtergrond onthuld. Als het voltage van de batterij onder de 12V zakt, en de hele pagina in het blauw is gekleurd, sluit de zonneregelaar het systeem af en gaat de website offline. Ze komt weer online als de zon opnieuw schijnt.
{{% figure src="solar-powered-server-weather-2.png" %}} De toegankelijkheid van deze website hangt af van het weer in Barcelona, Spanje. {{% /figure %}}

Wyświetl plik

@ -0,0 +1,41 @@
---
title: "Power"
date: ""
summary: "This website runs on a [solar powered server](/pl/about/the-solar-website/) located in Barcelona, and will go off-line during longer periods of bad weather. This page shows live data relating to power supply, power demand, and energy storage."
slug: "power"
lang: "pl"
authors: ["" ]
categories: [""]
tags: []
featured_image: "solar-powered-server-weather-2.png"
---
## Power supply
This is a forecast for the coming days, updated daily:
<p class="forecast">
</p>
This weather forecast is [powered by BrightSky](https://brightsky.dev/).
## Power demand
These are live power statistics of the solar powered server:
<dl id="server">
</dl>
(* load average per 15 minutes)
## Battery meter
The background of this website is a battery meter, designed to always display the relationship of the energy powering the website and the visitor traffic consuming it. Since 12 January 2020, the website runs on a 30W solar panel and a 168 Wh lead-acid battery.
We also installed a new battery meter, which simply represents the voltage of our battery. For example, a storage capacity of 68% converts to 12.68V, and the other way around. Showing a correct representation of the storage capacity requires calibration and algorithms in relation to a specific battery. This is troublesome, because we [keep experimenting with different sizes of batteries and solar panels]({{< ref "/posts/how-sustainable-is-a-solar-powered-website" >}}) to find the optimal balance between uptime and sustainability.
The voltage reading is the "naked" data on which each battery meter relies to display a percentage. The voltage of the battery doesn't always correlate with the energy storage capacity -- it's also influenced by the electric load (which lowers the voltage) and the solar insolation (which increases the voltage).
Because our load (the server) has a rather constant power use, during the day our battery meter reflects the local solar conditions. If the panel receives full sun, the voltage will most likely raise above 13V, colouring the whole website in yellow. However, if it's cloudy enough, the battery meter will decrease and the blue background is revealed.
Because our load is constant, during the night the battery meter reflects the storage capacity of the battery accurately. When the voltage of the battery drops below 12V, and the whole page is coloured in blue, the solar charge controller shuts down the system and the website goes offline.
{{% figure src="solar-powered-server-weather-2.png" %}} The accessibility of this website depends on the weather in Barcelona, Spain. {{% /figure %}}

Wyświetl plik

@ -0,0 +1,44 @@
---
title: "Power"
date: ""
summary: "This website runs on a [solar powered server](/pt/about/the-solar-website) located in Barcelona, and will go off-line during longer periods of bad weather. This page shows live data relating to power supply, power demand, and energy storage."
slug: "power"
lang: "pt"
authors:
categories: [""]
tags: []
featured_image: "solar-powered-server-weather-2.png"
---
## Power supply
This is a forecast for the coming days, updated daily:
<p class="forecast">
</p>
This weather forecast is [powered by BrightSky](https://brightsky.dev/).
## Power demand
These are live power statistics of the solar powered server:
<dl id="server">
</dl>
(* load average per 15 minutes)
## Battery meter
The background of this website is a battery meter, designed to always display the relationship of the energy powering the website and the visitor traffic consuming it. Since 12 January 2020, the website runs on a 30W solar panel and a 168 Wh lead-acid battery.
We also installed a new battery meter, which simply represents the voltage of our battery. For example, a storage capacity of 68% converts to 12.68V, and the other way around. Showing a correct representation of the storage capacity requires calibration and algorithms in relation to a specific battery. This is troublesome, because we [keep experimenting with different sizes of batteries and solar panels]({{< ref "/posts/how-sustainable-is-a-solar-powered-website" >}}) to find the optimal balance between uptime and sustainability.
The voltage reading is the "naked" data on which each battery meter relies to display a percentage. The voltage of the battery doesn't always correlate with the energy storage capacity -- it's also influenced by the electric load (which lowers the voltage) and the solar insolation (which increases the voltage).
Because our load (the server) has a rather constant power use, during the day our battery meter reflects the local solar conditions. If the panel receives full sun, the voltage will most likely raise above 13V, colouring the whole website in yellow. However, if it's cloudy enough, the battery meter will decrease and the blue background is revealed.
Because our load is constant, during the night the battery meter reflects the storage capacity of the battery accurately. When the voltage of the battery drops below 12V, and the whole page is coloured in blue, the solar charge controller shuts down the system and the website goes offline.
{{% figure src="solar-powered-server-weather-2.png" %}} The accessibility of this website depends on the weather in Barcelona, Spain. {{% /figure %}}

Wyświetl plik

@ -0,0 +1,33 @@
---
title: "Privacy"
date: ""
summary: "This website does not use cookies, advertising services, or tracking codes. We collect server logs to understand traffic on the server. This information is viewed only by us and is not used to make user profiles."
slug: "privacy"
lang: "en"
categories: [""]
tags: []
---
Example of server logs below:
109.69.14.162 - - [13/Sep/2018:01:59:35 +0200] "GET
/dithers/heated-sock.png HTTP/2.0" 200 30190
"https://solar.lowtechmagazine.com/category/low-tech-solutions2.html"
"Mozilla/5.0 (X11; Linux i686) AppleWebKit/537.36 (KHTML, like Gecko)
Chrome/65.0.3325.183 Safari/537.36 Vivaldi/1.96.1147.55"
109.69.14.162 - - [13/Sep/2018:01:59:35 +0200] "GET
/dithers/XYZ-cargo-Trike.png HTTP/2.0" 200 45829
"https://solar.lowtechmagazine.com/category/low-tech-solutions2.html"
"Mozilla/5.0 (X11; Linux i686) AppleWebKit/537.36 (KHTML, like Gecko)
Chrome/65.0.3325.183 Safari/537.36 Vivaldi/1.96.1147.55"
109.69.14.162 - - [13/Sep/2018:01:59:35 +0200] "GET
/dithers/fireless-cooker-in-kitchen.png HTTP/2.0" 200 44844
"https://solar.lowtechmagazine.com/category/low-tech-solutions2.html"
"Mozilla/5.0 (X11; Linux i686) AppleWebKit/537.36 (KHTML, like Gecko)
Chrome/65.0.3325.183 Safari/537.36 Vivaldi/1.96.1147.55"
109.69.14.162 - - [13/Sep/2018:01:59:36 +0200] "GET /favicon.ico
HTTP/2.0" 200 6481
"https://solar.lowtechmagazine.com/category/low-tech-solutions2.html"
"Mozilla/5.0 (X11; Linux i686) AppleWebKit/537.36 (KHTML, like Gecko)
Chrome/65.0.3325.183 Safari/537.36 Vivaldi/1.96.1147.55"

Wyświetl plik

@ -0,0 +1,9 @@
---
title: Translators
lang: en
summary: "Authors and translators for LOW←TECH MAGAZINE"
layout: contributors
---

Wyświetl plik

@ -0,0 +1,8 @@
---
title: Traducteurs
lang: fr
summary: "Authors and translators for LOW←TECH MAGAZINE"
---

47
i18n/de.toml 100644
Wyświetl plik

@ -0,0 +1,47 @@
[sitesubtitle]
other = 'Diese Website ist solarbetrieben und geht daher manchmal offline.'
[about]
other = 'Über'
[lowtechtitle]
other = 'Low-Tech Lösungen'
[lowtechdescription]
other = 'Interessante Möglichkeiten ergeben sich, entweder wenn man (eine) alte Technologie mit neuer Erkenntnis und neuen Materialien kombiniert oder alte Konzepte und traditionelle Erkenntnis auf moderne Technologie überträgt.'
[hightechtitle]
other = 'High-Tech Probleme'
[hightechdescription]
other = 'Technologie ist innerhalb unserer Gesellschaft zum Gegenstand von Idolatrie geworden, aber technologische Fortschritt zielt – meistens – darauf ab, Probleme zu lösen, die von früheren technischen Erfindungen ausgelöst wurden.'
[obsoletetitle]
other = 'Vergessene Technologie'
[obsoletedescription]
other = 'Vergangenes Wissen und oft vergessene Kenntnisse und Technologien enthalten ein großes Potenzial für die Gestaltung einer nachhaltigen Gesellschaft.'
[offline]
other = 'Offline Lesen'
[archives]
other = 'Archiv'
[donate]
other = 'Spenden'
[trans]
other = 'Übersetzt von'
[pagesize]
other = 'Seitengröße'
[written_by]
other = 'Von'
[translated_by]
other = 'Übersetzt von'
[authors]
other = ''
[translators]
other = ''
[contributors]
other = ''
[translations]
other= ''
# post-footer
[subscribeNEWS]
other = 'Erhalten Sie unseren Newsletter'
[supportLTM]
other = 'Unterstützen Sie das Low-tech Magazine mit'
[or]
other = 'oder'
[readoffline]
other = 'Kaufen Sie die gedruckte Webseite'

53
i18n/es.toml 100644
Wyświetl plik

@ -0,0 +1,53 @@
[sitesubtitle]
other = 'Este sitio web funciona con energía solar, lo que significa que en ocasiones estará fuera de línea.'
[about]
other = 'Acerca de'
[lowtechtitle]
other = 'Soluciones de Baja Tecnología'
[lowtechdescription]
other = 'Posibilidades interesantes surgen cuando se combina la tecnología antigua con nuevos conocimientos y nuevos materiales, o cuando se aplican conceptos tradicionales a la tecnología moderna.'
[hightechtitle]
other = 'Problemas de Alta Tecnología'
[hightechdescription]
other = 'La tecnología se ha convertido en el ídolo de nuestra sociedad, pero el progreso tecnológico está, en la mayoría de los casos, dirigido a resolver problemas causados por las mismas invenciones técnicas anteriores.'
[obsoletetitle]
other = 'Tecnologías Obsoletas'
[obsoletedescription]
other = 'Existe un gran potencial en el conocimiento y las tecnologías pasadas, las cuales son a menudo olvidadas a la hora de diseñar una sociedad sostenible'
[offline]
other = 'Lectura fuera de línea'
[archives]
other = 'Archivo'
[donate]
other = 'Donaciones'
[trans]
other = 'Traducido por'
[newsletterlink]
other = 'https://d69baa34.sibforms.com/serve/MUIEAEZUWQsqfc4iKwh6GDA0LS_6AE98wa1FP5PR4GpWzonWKBB5kuC2lPxvZDfq3TFEMX0TRy6KUp0QmzTaqBvvisJ5zpgu6FeI2lTw-8WjgPZBWxio3IKivik9Pd-EyiEzPwXuAkwkw0jhIXWwx2mYOuSPW06G1aOktFLZ2oV8YP58E2eMWj1AG-FK7PWiZXGE28K8WvV-ZPfT'
[pagesize]
other = 'Tamaño de página'
[written_by]
other = ''
[translated_by]
other = 'Traducido por'
[authors]
other = ''
[translators]
other = ''
[contributors]
other = ''
[translations]
other= ''
[vieworig]
other = ''
[viewdither]
other = ''
#post-footer
[subscribeNEWS]
other = 'Suscríbete a nuestro boletín'
[supportLTM]
other = 'Apoye a Low-tech Magazine a través de'
[or]
other = 'o'
[readoffline]
other = 'Lea Low-tech Magazine sin conexión'

56
i18n/fr.toml 100644
Wyświetl plik

@ -0,0 +1,56 @@
[sitesubtitle]
other = 'Ce site fonctionne à lénergie solaire, et se retrouve parfois hors-ligne'
[about]
other = 'À propos'
[lowtechtitle]
other = 'Solutions Low-tech'
[lowtechdescription]
other = 'Des possibilités intéressantes se présentent lors de la combinaison de lancienne technologie avec de nouvelles connaissances et de nouveaux matériaux, ou lors de lapplication danciens concepts et de connaissances traditionnelles à la technologie moderne.'
[hightechtitle]
other = 'Problèmes High-tech'
[hightechdescription]
other = 'Le progrès technologique est devenu lidole de notre société, mais il crée plus de problèmes quil en résout.'
[obsoletetitle]
other = 'Technologie Ancienne'
[obsoletedescription]
other = 'Il y a beaucoup de potentiel dans les connaissances et technologies anciennes et souvent oubliées lorsquil sagit de concevoir une société durable.'
[offline]
other = 'Lire le magazine hors-ligne'
[archives]
other = 'Archive'
[donate]
other = 'Faire un don'
[trans]
other = 'Traduit par'
[newsletterlink]
other = 'https://d69baa34.sibforms.com/serve/MUIEANc2lrp0ZlxefJj9bGWkRWAP8XKI8G25tXyMryhx1Q6iKLoxg-A9u3QuJxksFS7rQuYNdNjVBqcJfwig9kXB6QzKRFg0KK2ZhiJjarVqjLKhFw2Ej58I5aLFMcgBWzD0MrDKgWiQgF_qMW1-rhMF_nsEY44QyiGRITSt0oJGZGZMjXkhgKH6t_x5-HgMgcnO1J4fSoQ_2iw-'
[pagesize]
other = 'Taille de la page'
[written_by]
other = 'Ecrit par'
[translated_by]
other = 'Traduit par'
[authors]
other = 'Auteurs'
[translators]
other = 'Traducteurs'
[contributors]
other = 'Contributeurs'
[theme]
other = 'Thème'
[translations]
other = 'Traductions'
[vieworig]
other = "Voir l'original"
[viewdither]
other = 'Voir la version compressée'
#post-footer
[subscribeNEWS]
other = 'Abonnez-vous à notre newsletter'
[supportLTM]
other = 'Soutenez Low-tech Magazine sur'
[or]
other = 'ou'
[readoffline]
other = 'Lire le magazine sur papier'

51
i18n/it.toml 100644
Wyświetl plik

@ -0,0 +1,51 @@
[sitesubtitle]
other = 'Questo e un sito a energia solare, il che vuol dire che a volte va offline.'
[about]
other = 'Riguardo'
[lowtechtitle]
other = 'Siluzioni low tech'
[lowtechdescription]
other = 'Si manifestano possibilità interessanti quando vecchie tecnologie si combinano insieme a nuove conoscenze e nuovi materiali, o quando vecchi concetti e il sapere tradizionale vengono applicati alla tecnologia moderna.'
[hightechtitle]
other = 'Problemi dellhi-tech'
[hightechdescription]
other = 'Lhi-tech è diventato lidolo della nostra società, ma il progresso tecnologico è mirato, il più delle volte, a risolvere problemi causati da invenzioni tecniche precedenti.'
[obsoletetitle]
other = 'Tecnologia obsoleta'
[obsoletedescription]
other = 'Quando si tratta di progettare una società sostenibile, cè un grande potenziale nelle conoscenze e tecnologie passate e spesso dimenticate.'
[offline]
other = 'Lettura offline'
[archives]
other = 'Archivio'
[donate]
other = 'Donare'
[written_by]
other = ''
[translated_by]
other = ''
[trans]
other = 'Tradotto da'
[authors]
other = ''
[translators]
other = ''
[contributors]
other = ''
[translations]
other= ''
[pagesize]
other = 'Dimensione pagina'
[vieworig]
other = ''
[viewdither]
other = ''
# post-footer
[subscribeNEWS]
other = ''
[supportLTM]
other = ''
[or]
other = ''
[readoffline]
other = ''

57
i18n/nl.toml 100644
Wyświetl plik

@ -0,0 +1,57 @@
[sitesubtitle]
other = 'Deze website draait op zonne-energie, wat betekent dat ze af en toe uit de lucht gaat'
[about]
other = 'Over ons'
[lowtechtitle]
other = 'Lowtech Oplossingen'
[lowtechdescription]
other = 'Er ontstaan interessante mogelijkheden als oude technologie wordt gecombineerd met nieuwe inzichten en materialen, of wanneer traditionele denkwijzen worden toegepast op nieuwe technologie.'
[hightechtitle]
other = 'Hightech Problemen'
[hightechdescription]
other = 'Technologische vooruitgang is de heilige koe van de moderne maatschappij, maar creëert steeds vaker problemen in plaats van oplossingen.'
[obsoletetitle]
other = 'Vergeten Technologie'
[obsoletedescription]
other = 'Oude en vaak vergeten kennis en technologie kan veel inspiratie bieden voor een duurzame samenleving.'
[offline]
other = 'Offline Lezen'
[archives]
other = 'Archief'
[donate]
other = 'Nieuwsbrief'
[trans]
other = 'Vertaald door'
[newsletterlink]
other = 'https://d69baa34.sibforms.com/serve/MUIEAHQzQiVLl_sv5NX9Sii_mfBoFQThNwPv_rtFv0ABEc9OFnwTy_OUeTB7iy-sFym7LiQipYTbtM3PUqohPJDtydCCgro4hClGkznEtvMbbhXU8NLAkLOowbKx_ToeTDsoEjF6m0FvWskEbgmT9t40R1SYOw8Hb5sk6dEpF_G91Lm-c1BbIIlmyl59CZCGMjkfPRF0IuzmQCas'
[pagesize]
other = 'Paginagrootte'
[written_by]
other = 'Door'
[translated_by]
other = 'Vertaald door'
[authors]
other = ''
[translators]
other = ''
[contributors]
other = ''
[theme]
other = ''
[commentstitle]
other = 'Reacties'
[commentsdescription]
other = 'Als je op dit artikel wil reageren, stuur dan een mailtje naar solar (at) lowtechmagazine (dot) com.'
[vieworig]
other = ''
[viewdither]
other = ''
# post-footer
[subscribeNEWS]
other = 'Schrijf je in op onze nieuwsbrief'
[supportLTM]
other = 'Steun Low-tech Magazine via'
[or]
other = 'of'
[readoffline]
other = 'Lees Low-tech Magazine op papier'

51
i18n/pl.toml 100644
Wyświetl plik

@ -0,0 +1,51 @@
[sitesubtitle]
other = 'Ta strona zasilana jest energią słoneczną co oznacza, że czasami może być niedostępna.'
[about]
other = 'O nas'
[lowtechtitle]
other = 'Proste technologie, proste rozwiązania'
[lowtechdescription]
other = 'Wiele współczesnych problemów można rozwiązać łącząc dawne technologie ze współczesną wiedzą i nowoczesnymi materiałami, lub aplikując tradycyjne podejście i materiały do nowoczesnych technologii.'
[hightechtitle]
other = 'Problemy zaawansowanych technologii'
[hightechdescription]
other = 'Technologia stała się bogiem naszego społeczeństwa, jednak rozwój technologiczny niesie ze sobą nie tylko korzyści, ale również zagrożenia.'
[obsoletetitle]
other = 'Zapomniane technologie'
[obsoletedescription]
other = 'W zapomnianych technologiach minionych czasów tkwi wielki potencjał do stworzenia nowego, zrównoważonego społeczeństwa.'
[offline]
other = 'Czytanie offline'
[archives]
other = 'Archiwum'
[donate]
other = 'Wspomóż nas'
[trans]
other = 'Przekład'
[written_by]
other = ''
[translated_by]
other = 'Przekład '
[authors]
other = ''
[translators]
other = ''
[contributors]
other = ''
[translations]
other= ''
[pagesize]
other = 'Rozmiar strony'
[vieworig]
other = ''
[viewdither]
other = ''
# post-footer
[subscribeNEWS]
other = 'Zapisz się do naszego newslettera'
[supportLTM]
other = 'Wesprzyj Low-tech Magazine przez'
[or]
other = 'lub'
[readoffline]
other = 'Czytaj Low-tech Magazine off-line'

50
i18n/pt.toml 100644
Wyświetl plik

@ -0,0 +1,50 @@
[sitesubtitle]
other = 'Este site é movido a energia solar, o que significa que, às vezes, ele fica fora do ar.'
[lowtechtitle]
other = 'Soluções de baixa tecnologia'
[lowtechdescription]
other = 'Possibilidades interessantes surgem quando você combina tecnologias antigas com conhecimentos e materiais novos, ou quando você aplica antigos conceitos e conhecimentos tradicionais a tecnologias modernas.'
[hightechtitle]
other = 'Problemas de alta tecnologia'
[hightechdescription]
other = 'A tecnologia se tornou o ídolo de nossa sociedade, mas o progresso tecnológico é -- na maioria das vezes -- direcionado à resolução de problemas causados por invenções técnicas anteriores.'
[obsoletetitle]
other = 'Tecnologias obsoletas'
[obsoletedescription]
other = 'Há muito potencial em conhecimentos e tecnologias passadas -- e muitas vezes esquecidas -- quando se trata de projetar uma sociedade sustentável.'
[offline]
other = 'Lendo Offline'
[donate]
other = 'Faça uma doação'
[trans]
other = ''
[newsletterlink]
other = 'https://d69baa34.sibforms.com/serve/MUIEANc2lrp0ZlxefJj9bGWkRWAP8XKI8G25tXyMryhx1Q6iKLoxg-A9u3QuJxksFS7rQuYNdNjVBqcJfwig9kXB6QzKRFg0KK2ZhiJjarVqjLKhFw2Ej58I5aLFMcgBWzD0MrDKgWiQgF_qMW1-rhMF_nsEY44QyiGRITSt0oJGZGZMjXkhgKH6t_x5-HgMgcnO1J4fSoQ_2iw-'
[pagesize]
other = ''
[written_by]
other = ''
[translated_by]
other = ''
[authors]
other = ''
[translators]
other = ''
[contributors]
other = ''
[translations]
other= ''
[vieworig]
other = ''
[viewdither]
other = ''
# post-footer
[subscribeNEWS]
other = 'Inscreva-se em nossa newsletter (english)'
[supportLTM]
other = 'Apoie a Low-tech Magazine via'
[or]
other = 'or'
[readoffline]
other = 'Compre a versão impressa do site (english)'

31
layouts/404.html 100644
Wyświetl plik

@ -0,0 +1,31 @@
{{ define "main" }}
<main class='article {{ range .Params.categories }} {{ . | urlize }} {{ end }}'>
<article id="{{ .Title | urlize }}">
<section id="content" class="article">
<header class="entry-header">
<h1 class="entry-title">
{{ .Title }}</h1>
<p class="summary">
<p class="summary">
{{ .Summary }}
</p>
</p>
</header>
<div class="entry-content">
<p class="summary">
{{ .Content }}
</p>
</div>
</section>
</article>
</main>
{{ end }}

Wyświetl plik

@ -0,0 +1,10 @@
{{- $img := .Page.Resources.GetMatch .Destination -}}
{{- if and (not $img) .Page.File -}}
{{ $path := path.Join .Page.File.Dir .Destination }}
{{- $img = resources.Get $path -}}
{{- end -}}
{{- with $img -}}
<img class="uncompressed" src="{{ $img.RelPermalink }}" alt="{{ $.Text }}" />
{{- else -}}
<img class="uncompressed" src="{{ .Destination | safeURL }}" alt="{{ $.Text }}" />{{- end -}}

Wyświetl plik

@ -0,0 +1 @@
<a href="{{ .Destination | safeURL }}"{{ with .Title}} title="{{ . }}"{{ end }}{{ if or (strings.HasPrefix .Destination "https") (strings.HasPrefix .Destination "http")}} target="_blank"{{ end }} >{{ .Text | safeHTML }}</a>

Wyświetl plik

@ -0,0 +1,81 @@
{{ define "main" }}
<main id="archive-list">
<header>
<h1 class="entry-title">{{i18n "archives" | default .Title}}</h1>
</header>
<div id="filters">
<div class="filter active desc" id="date">
Date
</div>
<div class="filter asc" id="title">
Title
</div>
<div class="filter asc" id="cat">
Theme
</div>
</div>
<ul>
{{ $posts := where .Site.RegularPages "Type" "in" "posts" }} {{ $notunlisted := where .Site.RegularPages ".Params.unlisted" "!=" "true" }} {{ $archive := $posts | intersect $notunlisted }} {{ range $archive.ByDate.Reverse }}
<li class="{{ range .Params.categories }} {{ . | urlize }} {{ end }}">
<article>
<time class="published" datetime="{{.Date }}">{{.Date | time.Format ":date_long"}}</time>
<div class="article-title"><a href="{{.Permalink}}" rel="bookmark" title="{{.Title}}">{{.Title}}</a></div>
<div class="category">
{{ range .Params.categories }} {{ . }} {{ end }}
</div>
</article>
</li>
{{end}}
</ul>
<!-- /#archive-list -->
</main>
<!-- LIST SORTING-------------->
<script src="{{ .Site.BaseURL }}js/tinysort.min.js"></script>
<script>
var listElements = document.querySelectorAll('#archive-list ul li');
var filters = document.getElementsByClassName('filter');
for (var i = 0; i < filters.length; i++) {
filters[i].addEventListener('click', sort, false);
}
function sort() {
for (var j = 0; j < filters.length; j++) {
filters[j].classList.remove('active');
}
this.classList.add('active');
this.classList.toggle('desc');
this.classList.toggle('asc');
var type = (this).id;
switch (type) {
case "title":
tinysort(listElements, {
selector: 'div.article-title a',
attr: "title",
order: (this.isAsc = !this.isAsc) ? 'asc' : 'desc'
});
break;
case "date":
tinysort(listElements, {
selector: 'time.published',
attr: 'datetime',
order: (this.isAsc = !this.isAsc) ? 'asc' : 'desc'
});
break;
case "cat":
tinysort(listElements, {
selector: '.category',
order: (this.isAsc = !this.isAsc) ? 'asc' : 'desc'
});
break;
}
}
</script>
{{end}}

Wyświetl plik

@ -0,0 +1,29 @@
<!DOCTYPE html>
<html lang="{{ .Site.LanguageCode | default "en-us" }}">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>{{ if not .IsHome }}{{ .Title }} | {{ end }}{{ .Site.Title }}</title>
{{ with .Site.Params.description }}<meta name="description" content="{{ . }}">{{ end }}
{{ with .Site.Params.author }}<meta name="author" content="{{ . }}">{{ end }}
<link rel="icon" href="{{ .Site.BaseURL }}icons/sun.svg">
{{ $style := resources.Get "/css/style.scss" | resources.ToCSS }}
<link rel="stylesheet" href="{{ $style.Permalink }}">
{{- partial "feeds" . -}}
{{- partial "opengraph" . -}}
</head>
<body id="{{ .Title | urlize }}">
{{ partial "battery" . }}
{{ partial "header" . }}
{{ block "main" . }}{{ end }}
{{ partial "footer" . }}
<script src="{{ .Site.BaseURL }}js/script.js"></script>
</body>
</html>

Wyświetl plik

@ -0,0 +1,57 @@
{{ define "main" }}
<main>
<section class="article" id="authors">
<header class="entry-header">
<h1 class="entry-title">{{ i18n "contributors" | default "Contributors"}}</h1>
<p class="summary">
{{ .Summary }}
</p>
{{ if .IsTranslated }}
<div class="metadata">
<div class="translations">
{{i18n "translations" | default "Translations" }} {{ range .Translations }}
<a href="{{ .Permalink }}">{{ .Lang }}</a> {{ end }}
</div>
</div>
{{ end }}
</header>
<div class="entry-content">
<p>
{{ .Content }}
</p>
{{/* {{printf "%#v" .Site.Taxonomies }} */}}
<h2>{{i18n "authors" | default "Authors" }}</h2>
{{ range $key, $author := .Site.Taxonomies.authors.ByCount }}
<details>
<summary>{{ .Page.Title }} ({{ .Count }})</summary>
<ul class="page-list">
{{ range $author.Pages }}
{{ if not (in .Params.categories "About") }}
<li hugo-nav="{{ .RelPermalink}}"><a href="{{ .Permalink}}">{{ .LinkTitle }}</a></li>
{{ end }}
{{ end }}
</ul>
</details>
{{ end }}
<h2>{{i18n "translators" | default "Translators" }} <span>({{ .Language.LanguageName }})</span></h2>
{{ if .Site.Taxonomies.translators }} {{ range $trans, $translator := .Site.Taxonomies.translators.ByCount }}
<details>
<summary>{{ .Page.Title }} ({{ .Count }})</summary>
<ul class="page-list">
{{ range $translator.Pages }}
<li hugo-nav="{{ .RelPermalink}}"><a href="{{ .Permalink}}">{{ .LinkTitle }}</a></li>
{{ end }}
</ul>
</details>
{{ end }} {{ else }}
<p>{{i18n "notrans" | default "There are no translators for this language. The author(s) handled the translations directly." }}</p>
{{ end }}
</div>
</section>
</main>
{{ end }}

Wyświetl plik

@ -0,0 +1,25 @@
{{ define "main" }} {{ $categoryTitle := .Title }} {{ $categoryDescription := ' ' }}
{{ if (eq .Data.Term "Low-tech Solutions") }} {{ $categoryTitle = i18n "lowtechtitle" }}
{{ $categoryDescription = i18n "lowtechdescription" | default "Interesting possibilities arise when you combine old technology with new knowledge and new materials, or when you apply old concepts and traditional knowledge to modern technology." }}
{{ else if (eq .Data.Term "High-tech Problems")}} {{ $categoryTitle = i18n "hightechtitle" }} {{ $categoryDescription = i18n "hightechdescription" | default "Technology has become the idol of our society, but technological progress is—more often than not—aimed at solving problems caused by earlier technical inventions." }}
{{ else if (eq .Data.Term "Obsolete Technology")}} {{ $categoryTitle = i18n "obsoletetitle" }} {{ $categoryDescription = i18n "obsoletedescription" | default "There is a lot of potential in past and often forgotten knowledge and technologies when it comes to designing a sustainable society." }}
{{ else if (eq .Data.Term "About")}} {{ $categoryTitle = i18n "about" }} {{ $categoryDescription = i18n "aboutdescription" | default "" }}{{ end }}
<main class="article-list">
<header>
{{ if (eq .Data.Singular "author")}} {{ i18n "written_by" | default "Written by"}} {{ else if (eq .Data.Singular "translator")}} {{ i18n "translated_by" | default "Translated by"}} {{ else if (eq .Data.Singular "tag")}} {{ i18n "theme" | default "Theme"}}{{ end }}
<h1 class="entry-title">{{ $categoryTitle | default .Title }}</h1>
{{ if not (eq $categoryDescription ' ') }}
<p class="summary">{{$categoryDescription}}
</p>
{{ end }}
</header>
<section id="list" class="grid">
{{ $allposts := .Pages }}
{{ $notunlisted := where .RegularPages ".Params.unlisted" "!=" "true" }}
{{ $posts := $allposts | intersect $notunlisted }}
{{ $paginator := .Paginate $posts }}
{{ range $paginator.Pages }} {{ partial "article-list/default" . }} {{ end }}
</section>
{{ if gt $paginator.TotalPages 1}} {{ partial "pagination" . }} {{ end }}
</main>
{{ end }}

Wyświetl plik

@ -0,0 +1,48 @@
{{- $pctx := . -}}
{{- if .IsHome -}}{{ $pctx = .Site }}{{- end -}}
{{- $pages := slice -}}
{{- if or $.IsHome $.IsSection -}}
{{- $pages = $pctx.RegularPages -}}
{{- else -}}
{{- $pages = $pctx.Pages -}}
{{- end -}}
{{- $limit := .Site.Config.Services.RSS.Limit -}}
{{- if ge $limit 1 -}}
{{- $pages = $pages | first $limit -}}
{{- end -}}
{{- printf "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\"?>" | safeHTML }}
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title>{{ .Site.Title }} {{.Site.Language.LanguageName}}</title>
<link>{{ .Permalink }}</link>
<description>{{i18n "sitesubtitle" | default .Site.Params.description}}</description>
<generator>Hugo {{hugo.Version}}</generator>{{ with .Site.Language.Lang }}
<language>{{.}}</language>{{end}}{{ with .Site.Author.email }}
<managingEditor>{{.}}{{ with $.Site.Author.name }} ({{.}}){{end}}</managingEditor>{{end}}{{ with .Site.Author.email }}
<webMaster>{{.}}{{ with $.Site.Author.name }} ({{.}}){{end}}</webMaster>{{end}}{{ with .Site.Copyright }}
<copyright>{{.}}</copyright>{{end}}{{ if not .Date.IsZero }}
<lastBuildDate>{{ .Date.Format "Mon, 02 Jan 2006 15:04:05 -0700" | safeHTML }}</lastBuildDate>{{ end }}
{{- with .OutputFormats.Get "RSS" -}}
{{ printf "<atom:link href=%q rel=\"self\" type=%q />" .Permalink .MediaType | safeHTML }}
{{- end -}}
{{ range $pages }}
<item>
<title>{{ .Title }}</title>
<link>{{ .Permalink }}</link>
{{- if .Params.featured_image -}}
{{- $img := strings.TrimSuffix (path.Ext .Params.featured_image) .Params.featured_image }}
{{ $dithered := (print "images/dithers/" $img "_dithered.png") -}}
{{ $dithered_image := (.Page.Resources.ByType "image").GetMatch $dithered }}
<enclosure url="{{with $dithered_image }}{{ .Permalink }}{{end}}" type="image/png" length="{{ (os.Stat (path.Join (path.Dir .Page.File.Path) $dithered_image)).Size }}" ></enclosure>
{{ else }}
<enclosure url="{{ .Site.BaseURL }}icons/sun.svg }}" type="image/png" length=""></enclosure>
{{- end -}}
<pubDate>{{ .Date.Format "Mon, 02 Jan 2006 15:04:05 -0700" | safeHTML }}</pubDate>
{{ with .Site.Author.email }}<author>{{.}}{{ with $.Site.Author.name }} ({{.}}){{end}}</author>{{end}}
<guid>{{ .Permalink }}</guid>
<description>{{ .Content | html }}</description>
</item>
{{ end }}
</channel>
</rss>

Wyświetl plik

@ -0,0 +1,109 @@
{{ define "main" }}
<main class='article {{ range .Params.categories }} {{ . | urlize }} {{ end }}'>
<article>
<section id="content" class="article">
<header class="entry-header">
<h1 class="entry-title">
{{ .Title }}</h1>
<p class="summary">
{{ .Summary }}
</p>
<div class="metadata">
{{/* disable metadata author for about category */}}
{{ if not (in .Params.categories "About") }}
{{if (.GetTerms "authors")}}
<div class="authors">
<span class="byline">{{i18n "written_by" | default "Written by" }}</span>
{{ range (.GetTerms "authors") }}
<span class="author"><a href="{{ .Permalink }}">{{ .Title }}</a></span>
{{ end }}
</div>
{{ end }}
{{ end }}
{{ if (.GetTerms "translators") }}
<div class="translations">
<span class="byline">{{i18n "translated_by" | default "translated by"}}</span>
{{ range (.GetTerms "translators") }}
<span class="author"><a href="{{ .Permalink }}">{{ .Title }}</a></span>
{{ end }}
</div>
{{ end }}
{{ if .IsTranslated }}
<div class="translations">
<span class="byline">{{i18n "translations" | default "Translations"}}</span>
{{ range .Translations }}
<a href="{{ .Permalink }}">{{ .Lang }}</a>
{{ end }}
</div>
{{ end }}
</div>
</header>
<div class="entry-content">
{{- $contents := split .Content `<div class="footnotes" role="doc-endnotes">` -}}
{{ index $contents 0 | safeHTML }}
</div>
{{ if not (in .Params.categories "About") }}
{{ if not (in .Params.categories "") }}
{{ partial "post-footer" }}
{{ end }}
{{ end }}
</section>
{{ $commentsfile := printf "comments.%s.md" .Lang }}
{{ with .Resources.GetMatch $commentsfile }}
<section class="comments" id="comments">
<h2 id="comments-title">{{ i18n "commentstitle" | default "Comments"}} </h2>
<p><em>{{ i18n "commentsdescription" | default "To make a comment, please send an e-mail to solar (at) lowtechmagazine (dot) com. Your e-mail address is not used for other purposes, and will be deleted after the comment is published. If you dont want your real name to be published, sign the e-mail with the name you want to appear."}}</em></p>
<details>
<summary><span id="comment-count"></span> {{ i18n "commentstitle" | default "Reactions"}}</summary>
<div id="comments-list">
{{ .Content }}
</div>
</details>
</section>
{{ end }}
<section id="reference">
<div class="footnotes" role="doc-endnotes">
{{ index $contents 1 | safeHTML }}
</section>
{{$tags := (.GetTerms "tags")}}
{{ if $tags }}
<section id="related" class="article-list">
<h3 class="related">Related Articles</h3>
<div class="post-info gray">
<p class="tags">{{ i18n "theme" | default "Themes"}}:
{{ range $tags }}
<a href="{{ .Permalink }}" class="tag">{{ .LinkTitle }}</a>
{{ end }}
</p>
</div>
<div class="grid">
{{ $allposts := where .Site.RegularPages "Type" "in" "posts" }}
{{ $notunlisted := where site.RegularPages ".Params.unlisted" "!=" "true" }}
{{ $posts := $allposts | intersect $notunlisted }}
{{ $related := $posts.RelatedIndices . "tags" "lang" | first 4}}
{{ range $related }}
{{ if isset .Params ("categories") }}
{{ partial "article-list/default" . }}
{{ end }}
{{ end }}
</div>
</section>
{{ end }}
</article>
</main>
{{ end }}

Wyświetl plik

@ -0,0 +1,25 @@
{{ define "main" }}
<main class="author-list">
<header>
<h1 class="entry-title">{{.Title }}</h1>
</header>
<section id="list">
<div class="entry-content">
{{ range .Data.Terms.ByCount }}
<ul class="page-list">
<li class="author-item">
<a href="{{ .Page.Permalink }}">
{{ .Page.Title }}
</a> ({{ .Count }})
</li>
</ul>
{{ end }}
{{ if or (eq .Data.Plural "authors") (eq .Data.Plural "translators")}}
<h4>See all <a href="{{ absLangURL "contributors" }}">{{i18n "contributors" | default "Contributors" }}</a> ({{ .Language.LanguageName}})</h4>
{{end}}
</div>
</section>
</main>
{{ end }}

26
layouts/index.html 100644
Wyświetl plik

@ -0,0 +1,26 @@
{{ define "main" }}
<main class="home article-list" >
<!-- Recent Articles -->
{{ $allposts := where .Site.RegularPages "Type" "in" "posts" }}
{{ $notunlisted := where .Site.RegularPages ".Params.unlisted" "!=" "true" }}
{{ $posts := $allposts | intersect $notunlisted }}
{{ $pages := after 1 $posts.ByDate.Reverse }}
{{ $recent := first 1 $posts.ByDate.Reverse }}
<section id="home-listing" class="grid">
{{ $paginator := .Paginate $pages }}
{{ if not $paginator.HasPrev }}
{{ range $recent }}
{{ partial "article-list/featured" . }}
{{ end }}
{{ end }}
{{ range $paginator.Pages }}
{{ partial "article-list/default" . }}
{{ end }}
</section>
{{ if gt $paginator.TotalPages 1}}
{{ partial "pagination" . }}
{{ end }}
</main>
{{ end }}

Wyświetl plik

@ -0,0 +1,14 @@
<div class="article {{ range .Params.categories }} {{ . | urlize }} {{ end }}">
<a href="{{.Permalink}}">
{{$url := .Permalink}}
{{$filename := strings.Replace .Params.featured_image (path.Ext .Params.featured_image) "_dithered.png"}}
{{ $imgurl := path.Join "images" "dithers" $filename }}
{{ with .Resources.GetMatch $imgurl }}
<div class="featured-img" style="background-image: url('{{ .Permalink }}');"></div>
{{ end }}
<h3 class="entry-title">{{.Title}}</h3>
<p class="index-summary">{{.Summary}}</p>
<time class="published">{{.Date | time.Format ":date_long"}}</time>
</a>
</div>

Wyświetl plik

@ -0,0 +1,19 @@
<div class="cover {{ range .Params.categories }} {{ . | urlize }} {{ end }}">
<a href="{{.Permalink}}">
<div class="text">
<h2 class="entry-title">{{ .Title }}</h2>
<p class="index-summary">
{{ .Summary}}
</p>
<time class="published">{{.Date | time.Format ":date_long"}}</time>
</div>
<div class="image">
{{$url := .Permalink}}
{{$filename := strings.Replace .Params.featured_image (path.Ext .Params.featured_image) "_dithered.png"}}
{{ $imgurl := path.Join "images" "dithers" $filename }}
{{ with .Resources.GetMatch $imgurl }}
<div class="featured-img" style="background-image: url('{{ .Permalink }}');"></div>
{{ end }}
</div>
</a>
</div>

Wyświetl plik

@ -0,0 +1,13 @@
<div class="battery_bg">
<div id="battery_data" class="bat_status" data-charging="no">
<a href="{{ absLangURL "/power/" }}" title="Power">
<span id="charge_icon">
<svg class="icon battery-svg" viewBox="0 0 500 500"><title>Battery used</title><polygon class="svg_stroke" points="327.01 127.16 327.01 47.31 172.99 47.31 172.99 127.16 115.52 127.16 115.52 452.69 384.48 452.69 384.48 127.16 327.01 127.16"/><polygon class="svg_fill" points="357.64 408.86 158.26 408.86 158.26 209.49 357.64 408.86"/></svg>
<svg class="icon sun-svg" viewBox="0 0 500 500"><title>Battery charging</title><circle class="svg_fill" cx="248.48" cy="252.55" r="97.03"/><rect class="svg_fill" x="234.53" y="17.45" width="27.9" height="112.39"/><rect class="svg_fill" x="234.53" y="375.25" width="27.9" height="112.39"/><rect class="svg_fill" x="413.42" y="196.35" width="27.9" height="112.39" transform="translate(679.92 -174.83) rotate(90)"/><rect class="svg_fill" x="55.63" y="196.35" width="27.9" height="112.39" transform="translate(322.12 182.97) rotate(90)"/><rect class="svg_fill" x="361.03" y="69.85" width="27.9" height="112.39" transform="translate(198.96 -228.23) rotate(45)"/><rect class="svg_fill" x="108.03" y="322.85" width="27.9" height="112.39" transform="translate(303.75 24.77) rotate(45)"/><rect class="svg_fill" x="361.03" y="322.85" width="27.9" height="112.39" transform="translate(908.15 381.93) rotate(135)"/><rect class="svg_fill" x="108.03" y="69.85" width="27.9" height="112.39" transform="translate(297.35 128.93) rotate(135)"/></svg>
</span>
<span id="level"></span></a>
</div>
<div id="battery">
</div>
</div>

Wyświetl plik

@ -0,0 +1,3 @@
{{ range .AlternativeOutputFormats -}}
{{ printf `<link rel="%s" type="%s" href="%s" title="%s" />` .Rel .MediaType.Type .Permalink $.Site.Title | safeHTML }}
{{ end -}}

Wyświetl plik

@ -0,0 +1,42 @@
<footer class="site-footer">
<div id=page-size>{{ i18n "pagesize" | default "Page Size"}}: <span id="size-value"> $PLACEHOLDER</span></div>
<div id=back-to-top><a id="" href="#top" title="Back to top"></a></div>
<h2>
<a href="{{ .Site.BaseURL }}">{{ .Site.Title }}</a>
</h2>
<div class="dashboard">
<ul class="grid">
<li>
<h3>Server Stats</h3>
<dl id="stats">
</dl>
</li>
<li>
<h3>Forecast</h3>
<div class="forecast"></div>
</li>
<li>
<h3>Info</h3>
{{ partial "site-footer" }}
</li>
<li>
<h3>Contact</h3>
<p>
&copy; Kris De Decker<br> solar [at] lowtechmagazine [dot] com</p>
<div class="social">
<!-- RSS FEED -->
<a href="/feeds" type="application/atom+xml" rel="alternate" target="_blank"><svg viewBox="0 0 80 80" class="icon"><title>RSS Feed</title><circle cx="19.91" cy="58.23" r="6.86"/><path d="M67.89,65.72H55.7A41.86,41.86,0,0,0,13.89,23.91V11.73A54.06,54.06,0,0,1,67.89,65.72Z"/><path d="M48.93,65.72H36.75A22.88,22.88,0,0,0,13.89,42.87V30.68A35.08,35.08,0,0,1,48.93,65.72Z"/></svg></a>
<a href="https://twitter.com/lowtechmagazine" target="_blank"><svg viewBox="0 0 80 80" class="icon"><title>Twitter</title><path d="M65.05,29.23c0,.56,0,1.12,0,1.68,0,17.1-13,36.8-36.8,36.8A36.54,36.54,0,0,1,8.44,61.91a26.68,26.68,0,0,0,3.12.16,25.9,25.9,0,0,0,16.06-5.53,13,13,0,0,1-12.09-9,16.28,16.28,0,0,0,2.44.2,13.67,13.67,0,0,0,3.4-.44A12.93,12.93,0,0,1,11,34.64v-.16a13,13,0,0,0,5.85,1.64,13,13,0,0,1-4-17.3A36.76,36.76,0,0,0,39.51,32.36a14.58,14.58,0,0,1-.32-3,12.95,12.95,0,0,1,22.38-8.85,25.45,25.45,0,0,0,8.21-3.12,12.9,12.9,0,0,1-5.69,7.13,25.93,25.93,0,0,0,7.45-2A27.79,27.79,0,0,1,65.05,29.23Z"/></svg></a>
<a href="https://www.patreon.com/lowtechmagazine" target="_blank"><svg viewBox="0 -4.5 256 256" transform="scale(0.85)" preserveAspectRatio="xMidYMid" class="icon"><title>Patreon</title><path d="M45.1355837,0 L45.1355837,246.35001 L0,246.35001 L0,0 L45.1355837,0 Z M163.657111,0 C214.65668,0 256,41.3433196 256,92.3428889 C256,143.342458 214.65668,184.685778 163.657111,184.685778 C112.657542,184.685778 71.3142222,143.342458 71.3142222,92.3428889 C71.3142222,41.3433196 112.657542,0 163.657111,0 Z"> </path></svg></a>
<a href='{{ i18n "newsletterlink" | default .Site.Params.Newsletter }}' target="_blank"><svg viewBox="0 0 80 80" class="icon"><title>Newsletter</title><path d="M4.95,13.14v55H74.33v-55Zm54.34,8.12L39.64,39.74,20,21.26ZM13.07,60V25.91l26.56,25,26.57-25V60Z"/></svg></a>
</div>
<!-- /.social -->
</li>
</ul>
</div>
</footer>

Wyświetl plik

@ -0,0 +1,9 @@
<header class="site-header">
<h1><a id="top" href="{{ absLangURL "/" }}">{{ site.Title }}</a></h1>
<div class="subtitle">
<a href="{{ absLangURL "power" }}">{{i18n "sitesubtitle" | default .Site.Params.description}}
</a>
</div>
{{ partial "nav" . }}
</header>

Wyświetl plik

@ -0,0 +1,12 @@
<div id="lang-menu" title="languages">
<svg id="globe" class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 19.97 19.97"><circle class="stroke" cx="9.99" cy="9.99" r="9.24"/><ellipse class="stroke" cx="9.99" cy="9.99" rx="4.63" ry="9.24"/><line class="stroke" x1="9.99" y1="0.75" x2="9.99" y2="19.22"/><line class="stroke" x1="18.39" y1="12.99" x2="1.58" y2="12.99"/><line class="stroke" x1="18.39" y1="6.99" x2="1.58" y2="6.99"/></svg>
</div>
<nav id="languages">
<ul>
{{ range $.Site.Home.AllTranslations }}
<li>
<a class="lang-option" href="{{ .Permalink }}">{{ .Language.LanguageName}}</a>
</li>
{{ end }}
</ul>
</nav>

Wyświetl plik

@ -0,0 +1,20 @@
<nav id="menu">
<div id="menu-s">
{{ partial "lang-nav" . }}
<span id="m-btn">menu</span>
</div>
<ul id="menu-list">
{{ $currentPage := . }} {{ range .Site.Menus.main }}
<li class="{{ if $currentPage.HasMenuCurrent " main " . }}active {{ end }}">
<a href="{{ if i18n .Identifier }} {{ absLangURL .URL }} {{else}} {{.URL}} {{ end }}" data-nav="{{.Name | urlize}}" {{if strings.HasPrefix .URL "https" }} target="_blank" {{end}}>
<span>{{ i18n .Identifier | default .Name }}</span>
</a>
</li>
{{ end }}
<li class="rss">
<a href='{{ i18n "newsletterlink" | default .Site.Params.Newsletter }}' target="_blank"><svg viewBox="0 0 80 80" class="icon"><title>Newsletter</title><path d="M4.95,13.14v55H74.33v-55Zm54.34,8.12L39.64,39.74,20,21.26ZM13.07,60V25.91l26.56,25,26.57-25V60Z"/></svg></a>
<a href="/feeds" type="application/atom+xml" rel="alternate" target="_blank"><svg viewBox="0 0 80 80" class="icon"><title>RSS Feed</title><circle cx="19.91" cy="58.23" r="6.86"/><path d="M67.89,65.72H55.7A41.86,41.86,0,0,0,13.89,23.91V11.73A54.06,54.06,0,0,1,67.89,65.72Z"/><path d="M48.93,65.72H36.75A22.88,22.88,0,0,0,13.89,42.87V30.68A35.08,35.08,0,0,1,48.93,65.72Z"/></svg></a>
</li>
</ul>
</nav>

Wyświetl plik

@ -0,0 +1,64 @@
<meta property="og:title" content="{{ if not .IsHome }}{{ .Title }}{{ else }}{{ .Site.Title }}{{end }}" />
<meta name="twitter:title" content="{{ if not .IsHome }}{{ .Title }}{{ else }}{{ .Site.Title }}{{end }}"/>
<meta property="og:description" content="{{ with .Description }}{{ . }}{{ else }}{{if .IsPage}}{{ .Summary }}{{ else }}{{ with site.Params.description }}{{ . }}{{ end }}{{ end }}{{ end }}" />
<meta name="twitter:description" content="{{ with .Description }}{{ . }}{{ else }}{{if .IsPage}}{{ .Summary }}{{ else }}{{ with .Site.Params.description }}{{ . }}{{ end }}{{ end }}{{ end -}}"/>
<meta property="og:type" content="{{ if .IsPage }}article{{ else }}website{{ end }}" />
<meta property="og:url" content="{{ .Permalink }}" />
{{- if .Params.featured_image -}}
{{ $img := strings.TrimSuffix (path.Ext .Params.featured_image) .Params.featured_image }}
{{ $dithered := (print "images/dithers/" $img "_dithered.png") }}
<meta property="og:image" itemprop="image" content="{{ (path.Join .RelPermalink $dithered ) | absURL }}" />
<meta property="og:image:secure_url" content="{{ (path.Join .RelPermalink $dithered ) | absURL }}" />
<meta property="og:image:type" content="image/png">
<meta name="twitter:card" content="summary_large_image"/>
<meta name="twitter:image" content="{{ (path.Join .RelPermalink $dithered ) | absURL }}"/>
{{ else }}
<meta property="og:image" content="{{ .Site.BaseURL }}icons/sun.svg" />
<meta property="og:image:secure_url" content="{{ .Site.BaseURL }}icons/sun.svg" />
<meta property="og:image:type" content="image/svg+xml">
<meta property="og:image:width" content="300" />
<meta property="og:image:height" content="300" />
<meta name="twitter:card" content="summary"/>
{{- end}}
{{- if .IsPage }}
{{- $iso8601 := "2006-01-02T15:04:05-07:00" -}}
<meta property="article:section" content="{{ .Section }}" />
{{ with .PublishDate }}<meta property="article:published_time" {{ .Format $iso8601 | printf "content=%q" | safeHTMLAttr }} />{{ end }}
{{ with .Lastmod }}<meta property="article:modified_time" {{ .Format $iso8601 | printf "content=%q" | safeHTMLAttr }} />{{ end }}
{{- end -}}
{{- with .Site.Title }}<meta property="og:site_name" content="{{ . }}" />{{ end }}
{{- /* If it is part of a series, link to related articles */}}
{{- $permalink := .Permalink }}
{{- $siteSeries := site.Taxonomies.series }}
{{ with .Params.series }}{{- range $name := . }}
{{- $series := index $siteSeries ($name | urlize) }}
{{- range $page := first 6 $series.Pages }}
{{- if ne $page.Permalink $permalink }}<meta property="og:see_also" content="{{ $page.Permalink }}" />{{ end }}
{{- end }}
{{ end }}{{ end }}
{{- if .IsPage }}
{{ if .Params.authors }}
{{- range .Params.authors}}
<meta property="article:author" content="{{ . }}" />
<meta name="citation_author" content="{{ . }}">
{{ end }}
{{ else }}
<meta property="article:author" content="{{ .Site.Params.Author }}" />
<meta name="citation_author" content="{{ .Site.Params.Author }}">
{{ end }}
{{- with .Params.tags }}{{ range first 6 . }}
<meta property="article:tag" content="{{ . }}" />{{ end }}
{{- end }}{{ end }}

Wyświetl plik

@ -0,0 +1,9 @@
<nav class="pagination">
{{ if .Paginator.HasPrev }}
<a href="{{ .Paginator.Prev.URL }}">&laquo;</a>
{{ end }}
{{ .Paginator.PageNumber }} / {{ .Paginator.TotalPages }}
{{ if .Paginator.HasNext }}
<a href="{{ .Paginator.Next.URL }}">&raquo;</a>
{{ end }}
</nav>

Wyświetl plik

@ -0,0 +1,9 @@
{{- $newsletterlink := i18n "newsletterlink" | default "https://d69baa34.sibforms.com/serve/MUIEAJWIw9w82Dl4ua6FQArPaI-3Qb-zVTwPNabHQgFH51MiGF69Smy9LOC_HPoUmBj0emaXsXT87gcQXDPvtu-AZsJCHWhkkv21CdrcQu4GdnYAhZ-MrIPhwGDecagLzYxqfvkaqXg2ODcbJU4ByoDmzJK3ZTczDo2jcWtfn-En0MGKLVkgxx9TgdHqYoPabMJCMF-agLEclEwv"
-}}
<div class="post-footer">
<ul>
<li><a href="{{$newsletterlink}}">{{i18n "subscribeNEWS" | default "Subscribe to our newsletter"}}</a>.</li>
<li> {{i18n "supportLTM" | default "Support Low-tech Magazine via"}} <a href="https://www.paypal.me/lowtechmagazine">Paypal</a> {{i18n "or" | default "or"}} <a href="https://www.patreon.com/lowtechmagazine">Patreon</a>.</li>
<li><a href="{{ absLangURL "offline-reading/" }}">{{i18n "readoffline" | default "Read Low-tech Magazine offline"}}</a>.</li>
</ul>
</div>

Wyświetl plik

@ -0,0 +1,17 @@
<div class="footer-links">
<a href="{{ absLangURL "about" }}">
{{ i18n "about"| default "About"}}
</a>
<a href="{{ absLangURL "power" }}">
{{ i18n "power"| default "Power"}}
</a>
<a href="{{ absLangURL "about/team" }}">
{{ i18n "team"| default "Colophon"}}
</a>
<a href="{{ absLangURL "donate" }}">
{{ i18n "donate"| default "Donate"}}
</a>
<a href="/privacy">
{{ i18n "privacy"| default "Privacy Policy"}}
</a>
</div>

Wyświetl plik

@ -0,0 +1,5 @@
{{ $optBlock := dict "display" "block" }}
<div class="comment">
<h5>{{ .Get "name" }}</h5>
{{ .Inner | $.Page.RenderString $optBlock }}
</div>

Wyświetl plik

@ -0,0 +1,58 @@
{{- $old_filename := .Get "src" -}}
{{- $img := (.Page.Resources.ByType "image").GetMatch (path.Join "images" $old_filename ) -}}
{{- if $img -}}
{{- $thumb := $img.Fit "800x800 q90 webp" -}}
{{- $new_filename := replace $old_filename (path.Ext $old_filename) "" -}}
{{- $dithered := printf "images/dithers/%s_dithered.png" $new_filename -}}
{{ $dithered_image := (.Page.Resources.ByType "image").GetMatch $dithered }}
{{- $optBlock := dict "display" "block" -}}
{{- $alt := .Inner -}}
<div class="article-img {{if le $img.Width (mul $img.Height 1.2)}} vertical{{end}}">
<figure data-imgstate="dither">
<img src="{{with $dithered_image }}{{ .Permalink }}{{end}}" alt='{{ with $alt }}{{ $alt | markdownify| plainify }}{{ else }}{{ .Get "alt" }}{{ . | markdownify| plainify }}{{ end }}' data-original="{{if $thumb}}{{$thumb.Permalink}}{{else}}images/{{$old_filename }}{{end}}" data-dither="{{with $dithered_image }}{{ .RelPermalink }}{{end}}" loading="lazy"/> </figure>
<div class="figure-controls">
<figcaption class="caption">
{{ .Inner }}<svg class="dither-toggle icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<rect class="svg_fill" x="13.51" y="13.58" width="24.28" height="24.28"/><rect class="svg_fill" x="37.93" y="37.86" width="24.28" height="24.28"/><rect class="svg_fill" x="62.21" y="13.58" width="24.28" height="24.28"/><rect class="svg_fill" x="13.51" y="62.14" width="24.28" height="24.28"/><rect class="svg_fill" x="62.21" y="62.14" width="24.28" height="24.28"/>
</svg>
<div class="imgindicator">
<span class="tooltip view-orig">
{{ i18n "vieworig" | default "View original image"}}
</span>
<span class="tooltip view-dither">
{{ i18n "viewdither" | default "View dithered image"}}
</span>
</div>
</figcaption>
</div>
</div>
{{- else -}}
{{ $new_filename := replace $old_filename (path.Ext $old_filename) ""}}
{{ $dithered := printf "images/dithers/%s_dithered.png" $new_filename }}
{{ $dithered_image := (.Page.Resources.ByType "image").GetMatch $dithered }}
{{- $alt := .Inner -}}
<div class="article-img">
<figure data-imgstate="dither">
<img src="{{with $dithered_image }}{{ .Permalink }}{{end}}" alt='{{ with $alt }}{{ $alt | markdownify| plainify }}{{ else }}{{ .Get "alt" }}{{ . | markdownify| plainify }}{{ end }}' data-original="images/{{$old_filename }}" data-dither="{{with $dithered_image }}{{ .Permalink }}{{end}}" loading="lazy"/> </figure>
<div class="figure-controls">
<figcaption class="caption">
{{ .Inner }}<svg class="dither-toggle icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<rect class="svg_fill" x="13.51" y="13.58" width="24.28" height="24.28"/><rect class="svg_fill" x="37.93" y="37.86" width="24.28" height="24.28"/><rect class="svg_fill" x="62.21" y="13.58" width="24.28" height="24.28"/><rect class="svg_fill" x="13.51" y="62.14" width="24.28" height="24.28"/><rect class="svg_fill" x="62.21" y="62.14" width="24.28" height="24.28"/>
</svg>
<div class="imgindicator">
<span class="tooltip view-orig">
{{ i18n "vieworig" | default "View original image"}}
</span>
<span class="tooltip view-dither">
{{ i18n "viewdither" | default "View dithered image"}}
</span>
</div>
</figcaption>
</div>
</div>
{{end}}

Wyświetl plik

@ -0,0 +1,16 @@
{{- $old_filename := .Get "src" -}}
{{- $img := (.Page.Resources.ByType "image").GetMatch (path.Join "images" $old_filename ) -}}
{{- if $img -}}
{{- $alt := .Inner -}}
{{- $new_filename := replace $old_filename (path.Ext $old_filename) "" -}}
{{- $dithered := printf "images/dithers/%s_dithered.png" $new_filename -}}
{{ $dithered_image := (.Page.Resources.ByType "image").GetMatch $dithered }}
<div class="article-img {{if le $img.Width $img.Height}} vertical{{end}}">
<figure data-imgstate="dither">
<img src="{{with $dithered_image }}{{ .Permalink }}{{end}}" alt='{{ with $alt }}{{ $alt | markdownify| plainify }}{{ else }}{{ .Get "alt" }}{{ . | markdownify| plainify }}{{ end }}' loading="lazy"/></figure>
<figcaption class="caption">
{{ .Inner }}
</figcaption>
</div>
</div>
{{end}}

Wyświetl plik

@ -0,0 +1,12 @@
{{- $path := .Get 0 -}}
{{- with site.GetPage $path -}}
{{- .Permalink -}}
{{- else -}}
{{- with .Page.Sites.First.GetPage $path -}}
{{- .Permalink -}}
{{/* code to produce errors on not found */}}
{{- else -}}
{{/* {{- warnf "The %q shortcode was unable to resolve %q to a page. See %s" .Name $path .Position -}} */}}
{{- end -}}
{{- end -}}

Wyświetl plik

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 24.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 500 500" style="enable-background:new 0 0 500 500;" xml:space="preserve">
<title>bat</title>
<circle cx="248.5" cy="252.6" r="97"/>
<rect x="234.5" y="17.5" width="27.9" height="112.4"/>
<rect x="234.5" y="375.2" width="27.9" height="112.4"/>
<rect x="371.2" y="238.6" width="112.4" height="27.9"/>
<rect x="13.4" y="238.6" width="112.4" height="27.9"/>
<rect x="318.8" y="112.1" transform="matrix(0.7071 -0.7071 0.7071 0.7071 20.7095 302.0763)" width="112.4" height="27.9"/>
<rect x="65.8" y="365.1" transform="matrix(0.7071 -0.7071 0.7071 0.7071 -232.2856 197.2768)" width="112.4" height="27.9"/>
<rect x="361" y="322.9" transform="matrix(0.7071 -0.7071 0.7071 0.7071 -158.2369 376.1901)" width="27.9" height="112.4"/>
<rect x="108" y="69.9" transform="matrix(0.7071 -0.7071 0.7071 0.7071 -53.4381 123.1909)" width="27.9" height="112.4"/>
</svg>

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 1.1 KiB

Wyświetl plik

@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg id="weather" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 671.36 101.66"><g><circle cx="51.34" cy="50.83" r="19.81"/><rect x="48.49" y="2.82" width="5.7" height="22.95"/><rect x="48.49" y="75.89" width="5.7" height="22.95"/><rect x="85.03" y="39.36" width="5.7" height="22.95" transform="translate(138.71 -37.04) rotate(90)"/><rect x="11.96" y="39.36" width="5.7" height="22.95" transform="translate(65.64 36.02) rotate(90)"/><rect x="74.33" y="13.52" width="5.7" height="22.95" transform="translate(40.28 -47.25) rotate(45)"/><rect x="22.66" y="65.19" width="5.7" height="22.95" transform="translate(61.68 4.42) rotate(45)"/><rect x="74.33" y="65.19" width="5.7" height="22.95" transform="translate(185.95 76.3) rotate(135)"/><rect x="22.66" y="13.52" width="5.7" height="22.95" transform="translate(61.22 24.64) rotate(135)"/></g><g><g><rect x="155.07" y="2.82" width="5.7" height="22.95"/><rect x="118.54" y="39.36" width="5.7" height="22.95" transform="translate(172.21 -70.55) rotate(90)"/><rect x="180.9" y="13.52" width="5.7" height="22.95" transform="translate(71.5 -122.61) rotate(45)"/><rect x="129.24" y="65.19" width="5.7" height="22.95" transform="translate(92.9 -70.94) rotate(45)"/><rect x="129.24" y="13.52" width="5.7" height="22.95" transform="translate(243.16 -50.72) rotate(135)"/><path d="M157.99,51.51c.08,0,.16,.01,.24,.01,1.14-7.25,6.59-13.06,13.64-14.74-3.58-3.56-8.52-5.76-13.96-5.76-10.94,0-19.81,8.87-19.81,19.81,0,4.16,1.29,8.02,3.48,11.21,2.86-6.21,9.13-10.53,16.41-10.53Z"/></g><path d="M222.23,70.84c0-7.91-5.47-14.53-12.82-16.33-1.03-6.2-6.4-10.94-12.89-10.94-1.98,0-3.85,.45-5.53,1.24-3.05-3.07-7.27-4.98-11.94-4.98-8.39,0-15.33,6.15-16.6,14.19-.08,0-.15-.01-.23-.01-9.29,0-16.82,7.53-16.82,16.82,0,3.75,1.24,7.2,3.32,10l-9.78,6.82h67.75l-.35-.05c8.87-.47,15.92-7.79,15.92-16.78Z"/></g><path d="M335.68,59.53c0-9.32-6.44-17.11-15.1-19.23-1.21-7.31-7.54-12.88-15.19-12.88-2.33,0-4.54,.53-6.52,1.46-3.59-3.62-8.56-5.86-14.07-5.86-9.89,0-18.06,7.25-19.55,16.71-.09,0-.18-.01-.27-.01-10.94,0-19.81,8.87-19.81,19.81,0,4.42,1.46,8.48,3.91,11.78l-11.52,8.04h79.79l-.42-.05c10.44-.56,18.74-9.18,18.74-19.76Z"/><path d="M419.66,69.78c-13.86,0-25.1-11.24-25.1-25.1s11.24-25.1,25.1-25.1c.68,0,1.34,.03,2.01,.09-5.69-3.59-12.42-5.68-19.64-5.68-20.34,0-36.84,16.49-36.84,36.84s16.49,36.84,36.84,36.84c16.07,0,29.73-10.3,34.76-24.65-4.49,4.19-10.51,6.76-17.13,6.76Z"/><g><path d="M567.53,70.84c0-7.91-5.47-14.53-12.82-16.33-1.03-6.2-6.4-10.94-12.89-10.94-1.98,0-3.85,.45-5.53,1.24-3.05-3.07-7.27-4.98-11.94-4.98-8.39,0-15.33,6.15-16.6,14.19-.08,0-.15-.01-.23-.01-9.29,0-16.82,7.53-16.82,16.82,0,3.75,1.24,7.2,3.32,10l-9.78,6.82h67.75l-.35-.05c8.87-.47,15.92-7.79,15.92-16.78Z"/><path d="M497.7,44.39c-7.48,0-13.54-6.06-13.54-13.54s6.06-13.54,13.54-13.54c.36,0,.72,.02,1.08,.05-3.07-1.94-6.7-3.06-10.59-3.06-10.97,0-19.87,8.89-19.87,19.87s8.89,19.87,19.87,19.87c8.67,0,16.03-5.55,18.75-13.29-2.42,2.26-5.67,3.65-9.24,3.65Z"/></g><path d="M667.75,55.79c0-20.44-15.69-37.21-35.68-38.95V5.55h-6v11.22c-20.41,1.32-36.55,18.28-36.55,39.02h36.55v20.46c0,11-4.6,11-6.81,11-3.5,0-6.81,0-6.81-10.08h-6c0,11.12,3.95,16.08,12.81,16.08,5.85,0,12.81-2.95,12.81-17v-20.46h35.68Z"/></svg>

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 3.2 KiB

157
static/js/script.js 100644
Wyświetl plik

@ -0,0 +1,157 @@
// console.log('script loaded');
let url = "/api/stats.json";
let data;
let solar_stats = [];
let battery_stats = [];
let general_stats = [];
loadJSON();
async function loadJSON() {
const response = await fetch(url);
const data = await response.json();
setupBatteryMeter(data);
populateDashboard(data);
populateForecast(data);
if (window.location.href.indexOf('/power/') > -1) {
//load general stats on power page
populateData(data);
}
}
function setupBatteryMeter(data) {
//setup visible battery level
let level = parseInt(data.charge);
let indicator = document.getElementById('battery_data');
document.getElementById('battery').style.height = (100-level) + '%';
// indicator.style.top = 100 - parseInt(level) + "vh";
indicator.style.top = (100-level) + "vh";
if (data.charging == "no") {
// battery is draining, show battery level
document.getElementById('level').textContent = level;
} else {
// sun is out!
indicator.setAttribute('data-charging', 'yes');
}
}
function pushData(arr) {
// returns a list of dt/dd pairs from a two-dimensional array
let stats = [];
for (i = 0; i < arr.length; i++) {
stats.push("<dt>" + arr[i][0] + "</dt><dd>" + arr[i][1] + "</dd>");
}
return stats;
}
function populateData(data) {
let load = ((data.load_15 / 2) * 100).toFixed(2) + '%';
let general_stats = [
["Local time", data.local_time],
["Uptime", data.uptime],
["Power usage", data.W],
["Current draw", data.A],
["Voltage", data.V],
["CPU temperature", data.temperature + "°C"],
["CPU load average *", load],
["Solar panel active", data.charging],
["Battery capacity", data.charge + "%"]
];
let dl = document.getElementById('server');
dl.innerHTML = pushData(general_stats).join("");
}
function populateForecast(data) {
const weather_ignore = ["snow", "sleet", "wind", "fog"]; //because Barcelona is paradise
const weather_data = ["today_icon", "tomorrow_icon", "day_after_t_icon"];
const weather_days = ["today", "tomorrow", "day after tomorrow"];
let forecast = "";
for (let i = 0; i < weather_data.length; i++) {
let icon_name = weather_data[i]
let text = data[icon_name].replace(/-/g, " ");
let weather_icon;
//use cloud icon for all overcast weather
if (weather_ignore.includes(data[icon_name])) {
weather_icon = "cloudy";
} else {
weather_icon = data[icon_name];
}
forecast += '<span class="weather_day" id="' + weather_days[i] + '" title="' + text + '">' + weather_days[i] + '</span><span class="weather_icon ' + weather_icon + '"> </span><span class="weather_text"> ' + text + '</span>';
}
let weatherinfo = document.querySelectorAll('.forecast');
[].forEach.call(weatherinfo, function(target) {
target.innerHTML = forecast;
});
}
function populateDashboard(data) {
let bat_text = "";
if(data.charging=='no'){
bat_text = data.charge + "%, not charging";
}else{
bat_text = "charging";
}
let footer_data = [
['Location', 'Barcelona'],
['Time', data.local_time],
['Battery status', bat_text],
['Power used', data.W],
['Uptime', data.uptime]
];
document.getElementById('stats').innerHTML = pushData(footer_data).join("");
}
// language menu toggle
const langmenu = document.getElementById('lang-menu');
langmenu.addEventListener('click', function() {
console.log('togglelanguages');
document.getElementById('languages').classList.toggle("lang-expanded");
});
//mobile menu toggle
const mobilemenu = document.getElementById('m-btn');
mobilemenu.addEventListener('click', function() {
console.log('togglemenu');
document.getElementById('menu-list').classList.toggle("show");
});
const comments = document.querySelectorAll('.comment');
if ( comments.length > 0 ){
//update comment count
document.getElementById('comment-count').innerText = comments.length;
}
const dither_icons = document.querySelectorAll('.dither-toggle');
dither_icons.forEach(icon => {
icon.addEventListener('click', function() {
let figure = icon.closest('.figure-controls').previousElementSibling;
let img = figure.querySelector('img');
if( figure.getAttribute('data-imgstate') == "dither"){
figure.setAttribute('data-imgstate', 'undither');
let original = img.getAttribute('data-original');
img.src = original;
}else{
figure.setAttribute('data-imgstate', 'dither');
let dither= img.getAttribute('data-dither');
img.src = dither;
}
});
});

10
static/js/tinysort.min.js vendored 100644
Wyświetl plik

@ -0,0 +1,10 @@
/**
* TinySort is a small script that sorts HTML elements. It sorts by text- or attribute value, or by that of one of it's children.
* @summary A nodeElement sorting script.
* @version 2.3.6
* @license MIT
* @author Ron Valstar <ron@ronvalstar.nl>
* @copyright Ron Valstar <ron@ronvalstar.nl>
* @namespace tinysort
*/
!function(e,t){"use strict";function r(){return t}"function"==typeof define&&define.amd?define("tinysort",r):e.tinysort=t}(this,function(){"use strict";function e(e,n){function s(){0===arguments.length?v({}):t(arguments,function(e){v(x(e)?{selector:e}:e)}),d=$.length}function v(e){var t=!!e.selector,n=t&&":"===e.selector[0],o=r(e||{},m);$.push(r({hasSelector:t,hasAttr:!(o.attr===l||""===o.attr),hasData:o.data!==l,hasFilter:n,sortReturnNumber:"asc"===o.order?1:-1},o))}function S(){t(e,function(e,t){M?M!==e.parentNode&&(k=!1):M=e.parentNode;var r=$[0],n=r.hasFilter,o=r.selector,a=!o||n&&e.matchesSelector(o)||o&&e.querySelector(o),l=a?R:V,s={elm:e,pos:t,posn:l.length};B.push(s),l.push(s)}),D=R.slice(0)}function y(e,t,r){for(var n=r(e.toString()),o=r(t.toString()),a=0;n[a]&&o[a];a++)if(n[a]!==o[a]){var l=Number(n[a]),s=Number(o[a]);return l==n[a]&&s==o[a]?l-s:n[a]>o[a]?1:-1}return n.length-o.length}function N(e){for(var t,r,n=[],o=0,a=-1,l=0;t=(r=e.charAt(o++)).charCodeAt(0);){var s=46==t||t>=48&&57>=t;s!==l&&(n[++a]="",l=s),n[a]+=r}return n}function C(e,r){var n=0;for(0!==p&&(p=0);0===n&&d>p;){var l=$[p],s=l.ignoreDashes?f:u;if(t(h,function(e){var t=e.prepare;t&&t(l)}),l.sortFunction)n=l.sortFunction(e,r);else if("rand"==l.order)n=Math.random()<.5?1:-1;else{var c=a,g=w(e,l),m=w(r,l),v=""===g||g===o,S=""===m||m===o;if(g===m)n=0;else if(l.emptyEnd&&(v||S))n=v&&S?0:v?1:-1;else{if(!l.forceStrings){var C=x(g)?g&&g.match(s):a,b=x(m)?m&&m.match(s):a;if(C&&b){var A=g.substr(0,g.length-C[0].length),F=m.substr(0,m.length-b[0].length);A==F&&(c=!a,g=i(C[0]),m=i(b[0]))}}n=g===o||m===o?0:l.natural&&(isNaN(g)||isNaN(m))?y(g,m,N):m>g?-1:g>m?1:0}}t(h,function(e){var t=e.sort;t&&(n=t(l,c,g,m,n))}),n*=l.sortReturnNumber,0===n&&p++}return 0===n&&(n=e.pos>r.pos?1:-1),n}function b(){var e=R.length===B.length;if(k&&e)O?R.forEach(function(e,t){e.elm.style.order=t}):M?M.appendChild(A()):console.warn("parentNode has been removed");else{var t=$[0],r=t.place,n="org"===r,o="start"===r,a="end"===r,l="first"===r,s="last"===r;if(n)R.forEach(F),R.forEach(function(e,t){E(D[t],e.elm)});else if(o||a){var c=D[o?0:D.length-1],i=c&&c.elm.parentNode,u=i&&(o&&i.firstChild||i.lastChild);u&&(u!==c.elm&&(c={elm:u}),F(c),a&&i.appendChild(c.ghost),E(c,A()))}else if(l||s){var f=D[l?0:D.length-1];E(F(f),A())}}}function A(){return R.forEach(function(e){q.appendChild(e.elm)}),q}function F(e){var t=e.elm,r=c.createElement("div");return e.ghost=r,t.parentNode.insertBefore(r,t),e}function E(e,t){var r=e.ghost,n=r.parentNode;n.insertBefore(t,r),n.removeChild(r),delete e.ghost}function w(e,t){var r,n=e.elm;return t.selector&&(t.hasFilter?n.matchesSelector(t.selector)||(n=l):n=n.querySelector(t.selector)),t.hasAttr?r=n.getAttribute(t.attr):t.useVal?r=n.value||n.getAttribute("value"):t.hasData?r=n.getAttribute("data-"+t.data):n&&(r=n.textContent),x(r)&&(t.cases||(r=r.toLowerCase()),r=r.replace(/\s+/g," ")),null===r&&(r=g),r}function x(e){return"string"==typeof e}x(e)&&(e=c.querySelectorAll(e)),0===e.length&&console.warn("No elements to sort");var D,M,q=c.createDocumentFragment(),B=[],R=[],V=[],$=[],k=!0,z=e.length&&e[0].parentNode,L=z.rootNode!==document,O=e.length&&(n===o||n.useFlex!==!1)&&!L&&-1!==getComputedStyle(z,null).display.indexOf("flex");return s.apply(l,Array.prototype.slice.call(arguments,1)),S(),R.sort(C),b(),R.map(function(e){return e.elm})}function t(e,t){for(var r,n=e.length,o=n;o--;)r=n-o-1,t(e[r],r)}function r(e,t,r){for(var n in t)(r||e[n]===o)&&(e[n]=t[n]);return e}function n(e,t,r){h.push({prepare:e,sort:t,sortBy:r})}var o,a=!1,l=null,s=window,c=s.document,i=parseFloat,u=/(-?\d+\.?\d*)\s*$/g,f=/(\d+\.?\d*)\s*$/g,h=[],d=0,p=0,g=String.fromCharCode(4095),m={selector:l,order:"asc",attr:l,data:l,useVal:a,place:"org",returns:a,cases:a,natural:a,forceStrings:a,ignoreDashes:a,sortFunction:l,useFlex:a,emptyEnd:a};return s.Element&&function(e){e.matchesSelector=e.matchesSelector||e.mozMatchesSelector||e.msMatchesSelector||e.oMatchesSelector||e.webkitMatchesSelector||function(e){for(var t=this,r=(t.parentNode||t.document).querySelectorAll(e),n=-1;r[++n]&&r[n]!=t;);return!!r[n]}}(Element.prototype),r(n,{loop:t}),r(e,{plugin:n,defaults:m})}());

Wyświetl plik

@ -0,0 +1,51 @@
#!/bin/bash
# © 2023 Roel Roscam Abbing, released as AGPLv3
# see https://www.gnu.org/licenses/agpl-3.0.html
# Support your local low-tech magazine: https://solar.lowtechmagazine.com/donate/
now=`date`
baseURL="" #the URL of the website e.g. htttps://solar.lowtechmagazine.com/
contentDir="" #the directory where your HUGO articles are e.g. /path/to/repo/solar_v2/content/
repoDir="" #the full path to the repository
outputDir="" # the directory where you export the site to.
while getopts f flag
do
case "${flag}" in
f) updated="forced rebuild";;
esac
done
if [[ $updated != "forced rebuild" ]]; then
echo "Checking for update $now"
updated=$(git -C $repoDir pull origin main)
fi
if echo $updated | grep -q "Already up to date";
then
echo "Git up to date $now"
else
echo "Git was not up to date"
echo $updated
echo "Rebuilding the site"
cd $repoDir
echo "Dithering new images"
/usr/bin/python3 utils/dither_images.py -d $contentDir --colorize
echo "Generating site"
hugo -b $baseURL --destination $outputDir
echo "Calculating page sizes"
/usr/bin/python3 utils/calculate_size.py --directory $outputDir --baseURL $baseURL
echo "Removing original media from" $outputDir
/usr/bin/python3 utils/clean_output.py --directory $outputDir
after=`date`
echo "Site regeneration started $now"
echo "Site regeneration finished $after"
fi

Wyświetl plik

@ -0,0 +1,176 @@
# Page Metadata
# © 2022 Roel Roscam Abbing, released as AGPLv3
# see https://www.gnu.org/licenses/agpl-3.0.html
# Support your local low-tech magazine: https://solar.lowtechmagazine.com/donate.html
import os
import argparse
import shutil
from bs4 import BeautifulSoup
import logging
import sys
parser = argparse.ArgumentParser(
"""
This script recursively traverses folders and enumerates the file size of all html pages and associated media.
The calculated total file size is then added to the HTML page.
"""
)
parser.add_argument(
'-d', '--directory', help="Set the directory to traverse", default="."
)
parser.add_argument(
'-rm', '--remove', help="Removes all the folders with dithers and their contents", action="store_true"
)
parser.add_argument(
'-b', '--baseURL', help="hostname (and path) to the root, e.g. https://solar.lowtechmagazine.com"
)
parser.add_argument(
'-v', '--verbose', help="Print out more detailed information about what this script is doing", action="store_true"
)
args = parser.parse_args()
if args.verbose:
logging.basicConfig(level=logging.DEBUG, stream=sys.stdout)
else:
logging.basicConfig(level=logging.INFO, stream=sys.stdout)
content_dir = args.directory
base_url = args.baseURL
def get_printable_size(byte_size):
"""
Pretty file sizes.
Thanks Pobux!
https://gist.github.com/Pobux/0c474672b3acd4473d459d3219675ad8
"""
BASE_SIZE = 1024.00
MEASURE = ["B", "KB", "MB", "GB", "TB", "PB"]
def _fix_size(size, size_index):
if not size:
return "0"
elif size_index == 0:
return str(size)
else:
return "{:.2f}".format(size)
current_size = byte_size
size_index = 0
while current_size >= BASE_SIZE and len(MEASURE) != size_index:
current_size = current_size / BASE_SIZE
size_index = size_index + 1
size = _fix_size(current_size, size_index)
measure = MEASURE[size_index]
return size + measure
def get_assets(soup):
"""
Lists all the page assets such as scripts and icons in a given HMTL page.
"""
assets = []
for a in soup.findAll('link', {'rel':['apple-touch-icon','icon','stylesheet']}):
a = a['href'].split('?')[0]
if a not in assets:
assets.append(a)
for s in soup.findAll('script'):
if ['src'] in s:
s = s['src']
if s not in assets:
assets.append(s)
return assets
def get_media(html_file):
"""
Lists all the images on a given HTML page.
"""
html_file = open(html_file).read()
soup = BeautifulSoup(html_file, 'html.parser')
media = []
for img in soup(['img', 'object']):
media.append(img['src'])
featured_images = soup.findAll('div', {'class':'featured-img'})
for fi in featured_images:
fi = fi['style']
start = fi.find("url('")
end = fi.find("');")
url = fi[start+len("url('"):end]
media.append(url)
assets = get_assets(soup)
#assets = list(set(assets))
media = list(set(media+assets)) # duplicate media don't increase page size
return media, soup
def insert_metadata(output_file, metadata, soup):
"""
Adds page metadata to a given HTML page and saves it.
"""
tag = soup.find('div', {'id':'page-size'})
if tag:
with open(output_file,'w') as f:
tag.string = '{}'.format(metadata)
f.write(str(soup))
logging.info("Checking file sizes for '{}'".format(content_dir))
for root, dirs, files in os.walk(os.path.abspath(content_dir), topdown=True):
for fname in files:
if fname.endswith(".html"):
logging.debug("Checking file size for '{}'".format(os.path.join(root, fname)))
media_size = 0
media, soup = get_media(os.path.join(root, fname))
for m in media:
file_name = m.replace(base_url, '')
# current problematic with pagebundles is images are in the same folder as html file
# but html file might link to images in other page bundles so we need to determine
# whether the image is in this page bundle or another:
media_this_page = os.path.join(root, file_name.strip('/'))
media_other_page = os.path.join(content_dir, file_name.strip('/'))
try:
if os.path.exists(media_this_page):
m = media_this_page
except:
pass
try:
if os.path.exists(media_other_page):
m = media_other_page
except:
pass
if os.path.exists(m):
item_size = os.path.getsize(m)
media_size = media_size + item_size
logging.debug("Found {} {}".format(m, get_printable_size(item_size)))
else:
# if the file path can't be found it might actually not be there at all..
logging.debug("{} not found!".format(m))
current_file = os.path.join(root, fname)
file_size = os.path.getsize(current_file)
file_size = file_size + media_size
metadata = get_printable_size(file_size)
metadata = get_printable_size(file_size+len(metadata)) # count the extra metadata as well
insert_metadata(os.path.join(root, fname), metadata, soup)
logging.debug("{} is {}".format(os.path.join(root,fname), metadata))
logging.info("Done checking filesizes")

Wyświetl plik

@ -0,0 +1,96 @@
# clean_output
# © 2023 Roel Roscam Abbing, released as AGPLv3
# see https://www.gnu.org/licenses/agpl-3.0.html
# Support your local low-tech magazine: https://solar.lowtechmagazine.com/donate.html
import os
import argparse
import logging
parser = argparse.ArgumentParser(
"""
This script recursively traverses folders and deletes files not in "dithers" or containing hugos resizing pattern in the filname.
This catches media in your content repository which is not used but still copied over to the final site.
Use with caution and adjust the pattern to your own situation!
"""
)
parser.add_argument(
'-d', '--directory', help="Set the directory to traverse", default="."
)
parser.add_argument(
'-v', '--verbose', help="Print out more detailed information about what this script is doing", action="store_true"
)
args = parser.parse_args()
content_dir = args.directory
def get_printable_size(byte_size):
"""
Pretty file sizes.
Thanks Pobux!
https://gist.github.com/Pobux/0c474672b3acd4473d459d3219675ad8
"""
BASE_SIZE = 1024.00
MEASURE = ["B", "KB", "MB", "GB", "TB", "PB"]
def _fix_size(size, size_index):
if not size:
return "0"
elif size_index == 0:
return str(size)
else:
return "{:.2f}".format(size)
current_size = byte_size
size_index = 0
while current_size >= BASE_SIZE and len(MEASURE) != size_index:
current_size = current_size / BASE_SIZE
size_index = size_index + 1
size = _fix_size(current_size, size_index)
measure = MEASURE[size_index]
return size + measure
def calculate_dir_size(content_dir):
size = 0
for path, dirs, files in os.walk(os.path.abspath(content_dir)):
for f in files:
fp = os.path.join(path, f)
size += os.path.getsize(fp)
return(size)
exclude_dirs = set(["dithers"])
image_ext = [".jpg", ".JPG", ".jpeg", ".png", ".gif", ".webp", ".tiff", ".bmp"]
pattern = "_800x800_fit_q90"
if args.verbose:
logging.basicConfig(level=logging.DEBUG)
else:
logging.basicConfig(level=logging.INFO)
logging.info("Deleting images in {} and lower".format(os.path.abspath(content_dir)))
count = 0
size = calculate_dir_size(content_dir)
logging.info("Directory is {} before cleanup".format(get_printable_size(size)))
for root, dirs, files in os.walk(os.path.abspath(content_dir), topdown=True):
dirs[:] = [d for d in dirs if d not in exclude_dirs]
for fname in files:
if fname.endswith(tuple(image_ext)):
if pattern not in fname:
f = os.path.join(root, fname)
count+=1
os.remove(f)
logging.debug("🗑 {}".format(fname))
logging.info("Deleted {} original images".format(count))
size = calculate_dir_size(content_dir)
logging.info("Directory is {} after cleanup".format(get_printable_size(size)))

Wyświetl plik

@ -0,0 +1,183 @@
# image dithering script
# © 2022 Roel Roscam Abbing, released as AGPLv3
# see https://www.gnu.org/licenses/agpl-3.0.html
# Support your local low-tech magazine: https://solar.lowtechmagazine.com/donate.html
import hitherdither
import os
import argparse
import shutil
from PIL import Image
import logging
parser = argparse.ArgumentParser(
"""
This script recursively traverses folders and creates dithered versions of the images it finds.
These are stored in the same folder as the images in a folder called "dithers".
"""
)
parser.add_argument(
'-d', '--directory', help="Set the directory to traverse", default="."
)
parser.add_argument(
'-rm', '--remove', help="Removes all the folders with dithers and their contents", action="store_true"
)
parser.add_argument(
'-c', '--colorize', help="Colorizes the dithered images", action="store_true"
)
parser.add_argument(
'-v', '--verbose', help="Print out more detailed information about what this script is doing", action="store_true"
)
args = parser.parse_args()
image_ext = [".jpg", ".JPG", ".jpeg", ".png", ".gif", ".webp", ".tiff", ".bmp"]
content_dir = args.directory
if args.verbose:
logging.basicConfig(level=logging.DEBUG)
else:
logging.basicConfig(level=logging.INFO)
exclude_dirs = set(["dithers"])
logging.info("Dithering all images in {} and subfolders".format(content_dir))
logging.debug("excluding directories: {}".format("".join(exclude_dirs)))
def colorize(source_image, category):
"""
Picks a colored dithering palette based on the post category.
"""
colors = {
'low-tech': hitherdither.palette.Palette([(30,32,40), (11,21,71),(57,77,174),(158,168,218),(187,196,230),(243,244,250)]),
'obsolete': hitherdither.palette.Palette([(9,74,58), (58,136,118),(101,163,148),(144,189,179),(169,204,195),(242,247,246)]),
'high-tech': hitherdither.palette.Palette([(86,9,6), (197,49,45),(228,130,124),(233,155,151),(242,193,190),(252,241,240)]),
'grayscale': hitherdither.palette.Palette([(25,25,25), (75,75,75),(125,125,125),(175,175,175),(225,225,225),(250,250,250)])
}
if category:
for i in colors.keys():
if i in category.lower():
color = colors[i]
logging.info("Applying color palette '{}' for {}".format(i, category))
break
else:
logging.info("No category for {}, {}".format(source_image, category))
print("No category for {}, {}".format(source_image, category))
color = colors['grayscale']
else:
logging.info("No category for {}, {}".format(source_image, category))
print("No category for {}, {}".format(source_image, category))
color = colors['grayscale']
return color
def dither_image(source_image, output_image, category ='grayscale'):
#see hitherdither docs for different dithering algos and settings
if args.colorize:
palette = colorize(source_image, category)
else:
palette = hitherdither.palette.Palette([(25,25,25), (75,75,75),(125,125,125),(175,175,175),(225,225,225),(250,250,250)])
try:
img= Image.open(source_image).convert('RGB')
img.thumbnail((800,800), Image.LANCZOS)
#palette = palettes[category]
threshold = [96, 96, 96]
img_dithered = hitherdither.ordered.bayer.bayer_dithering(img, palette, threshold, order=8)
#if args.colorize:
# img_dithered = colorize(img_dithered, category)
# logging.debug("Created {} in category {}".format(img_dithered, category))
img_dithered.save(output_image, optimize=True)
except Exception as e:
logging.debug("❌ failed to convert {}".format(source_image))
logging.debug(e)
def delete_dithers(content_dir):
logging.info("Deleting 'dither' folders in {} and below".format(content_dir))
for root, dirs, files in os.walk(content_dir, topdown=True):
if root.endswith('dithers'):
shutil.rmtree(root)
logging.info("Removed {}".format(root))
def parse_front_matter(md):
with open(md) as f:
contents = f.readlines()
cat = None
for l in contents:
if l.startswith("categories: "):
cat = l.split("categories: ")[1]
cat = cat.strip("[")
cat = cat.strip()
cat = cat.strip("]")
logging.debug("Categories: {} from {}".format(cat, l.strip()))
return cat
prev_root = None
if args.remove:
delete_dithers(
os.path.abspath(content_dir)
)
else:
for root, dirs, files in os.walk(os.path.abspath(content_dir), topdown=True):
logging.debug("Checking next folder {}".format(root))
dirs[:] = [d for d in dirs if d not in exclude_dirs]
category = None
if prev_root is None:
prev_root = root
if prev_root is not root:
if files:
if any(x.endswith(tuple(image_ext)) for x in files):
if not os.path.exists(os.path.join(root,'dithers')):
os.mkdir(os.path.join(root,'dithers'))
logging.info("📁 created in {}".format(root))
if args.colorize:
#iterate over md files to find one with a category
if not category:
for i in os.listdir(root):
if i.startswith('index'):
category2 = parse_front_matter(os.path.join(root,i))
break
for fname in files:
if fname.endswith(tuple(image_ext)):
file_, ext = os.path.splitext(fname)
source_image= os.path.join(root,fname)
output_image = os.path.join(os.path.join(root, 'dithers'), file_+'_dithered.png')
if not os.path.exists(output_image):
if not args.colorize:
category2 = "grayscale"
dither_image(source_image,output_image, category2)
logging.info("🖼 converted {}".format(fname))
logging.debug(output_image)
else:
logging.debug("Dithered version of {} found, skipping".format(fname))
prev_root = root
logging.info("Done dithering")