diff --git a/.gitignore b/.gitignore
index 21345cf835b8d3e03f98561d813f31dce9736463..2ca4fc00325ed6238029d8b6e95c9cc09196ccd1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,5 @@
 /output
 /public
 /content
-/__pycache__
+__pycache__
 *.swp
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index f2ed0f213d068b38e04aa5aafc143dded75dd0f4..6fcd50118a50ac8cc79399e814fef7371342c430 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -12,8 +12,7 @@ site:
   before_script:
     - apt-get update && apt-get install -y --no-install-recommends python3-pip python3 make git wget software-properties-common
     - wget -O pandoc.deb https://github.com/jgm/pandoc/releases/download/2.18/pandoc-2.18-1-amd64.deb && dpkg -i pandoc.deb && rm --interactive=never pandoc.deb
-    - ./installpelican.sh
-    - pip3 install tzlocal markdown pelican-pandoc-reader gitpython typogrify
+    - pip3 install -r requirements.txt
     - git clone https://gitlab.gwdg.de/GAUMI-fginfo/fg-website-data.git content
   script:
     - make publish
diff --git a/Makefile b/Makefile
index 598b3270b3abb0e27795488172a2a38d26d56654..bdfdb460dca80e7e2f615ea6373000b0fdd122e3 100644
--- a/Makefile
+++ b/Makefile
@@ -1,91 +1,9 @@
-PY?=
-PELICAN?=pelican
-PELICANOPTS=
+.PHONY: html
+html: run
 
-BASEDIR=$(CURDIR)
-INPUTDIR=$(BASEDIR)/content
-OUTPUTDIR=$(BASEDIR)/output
-CONFFILE=$(BASEDIR)/pelicanconf.py
-PUBLISHCONF=$(BASEDIR)/publishconf.py
+.PHONY: publish
+publish: run
 
-SSH_HOST=fg.informatik.uni-goettingen.de
-SSH_PORT=22
-SSH_USER=gitlab
-SSH_TARGET_DIR=/home/gitlab/public
-
-
-DEBUG ?= 0
-ifeq ($(DEBUG), 1)
-	PELICANOPTS += -D
-endif
-
-RELATIVE ?= 0
-ifeq ($(RELATIVE), 1)
-	PELICANOPTS += --relative-urls
-endif
-
-SERVER ?= "0.0.0.0"
-
-PORT ?= 0
-ifneq ($(PORT), 0)
-	PELICANOPTS += -p $(PORT)
-endif
-
-.PHONY: default
-default: html
-
-help:
-	@echo 'Makefile für die Webseite der Fachgruppe Informatik.                      '
-	@echo '                                                                          '
-	@echo 'Usage:                                                                    '
-	@echo '   make html                           (re)generate the web site          '
-	@echo '   make clean                          remove the generated files         '
-	@echo '   make regenerate                     regenerate files upon modification '
-	@echo '   make publish                        generate using production settings '
-	@echo '   make serve [PORT=8000]              serve site at http://localhost:8000'
-	@echo '   make serve-global [SERVER=0.0.0.0]  serve (as root) to $(SERVER):80    '
-	@echo '   make devserver [PORT=8000]          serve and regenerate together      '
-	@echo '   make devserver-global               regenerate and serve on 0.0.0.0    '
-	@#echo '   make ssh_upload                     upload the web site via SSH        '
-	@#echo '   make sftp_upload                    upload the web site via SFTP       '
-	@#echo '   make rsync_upload                   upload the web site via rsync+ssh  '
-	@echo '                                                                          '
-	@echo 'Set the DEBUG variable to 1 to enable debugging, e.g. make DEBUG=1 html   '
-	@echo 'Set the RELATIVE variable to 1 to enable relative urls                    '
-	@echo '                                                                          '
-
-html:
-	"$(PELICAN)" "$(INPUTDIR)" -o "$(OUTPUTDIR)" -s "$(CONFFILE)" $(PELICANOPTS)
-
-clean:
-	[ ! -d "$(OUTPUTDIR)" ] || rm -rf "$(OUTPUTDIR)"
-
-regenerate:
-	"$(PELICAN)" -r "$(INPUTDIR)" -o "$(OUTPUTDIR)" -s "$(CONFFILE)" $(PELICANOPTS)
-
-serve:
-	"$(PELICAN)" -l "$(INPUTDIR)" -o "$(OUTPUTDIR)" -s "$(CONFFILE)" $(PELICANOPTS)
-
-serve-global:
-	"$(PELICAN)" -l "$(INPUTDIR)" -o "$(OUTPUTDIR)" -s "$(CONFFILE)" $(PELICANOPTS) -b $(SERVER)
-
-devserver:
-	"$(PELICAN)" -lr "$(INPUTDIR)" -o "$(OUTPUTDIR)" -s "$(CONFFILE)" $(PELICANOPTS)
-
-devserver-global:
-	$(PELICAN) -lr $(INPUTDIR) -o $(OUTPUTDIR) -s $(CONFFILE) $(PELICANOPTS) -b 0.0.0.0
-
-publish:
-	"$(PELICAN)" "$(INPUTDIR)" -o "$(OUTPUTDIR)" -s "$(PUBLISHCONF)" $(PELICANOPTS)
-
-ssh_upload: publish
-	scp -P $(SSH_PORT) -r "$(OUTPUTDIR)"/* "$(SSH_USER)@$(SSH_HOST):$(SSH_TARGET_DIR)"
-
-sftp_upload: publish
-	printf 'put -r $(OUTPUTDIR)/*' | sftp $(SSH_USER)@$(SSH_HOST):$(SSH_TARGET_DIR)
-
-rsync_upload: publish
-	rsync -e "ssh -p $(SSH_PORT)" -P -rvzc --include tags --cvs-exclude --delete "$(OUTPUTDIR)"/ "$(SSH_USER)@$(SSH_HOST):$(SSH_TARGET_DIR)"
-
-
-.PHONY: html help clean regenerate serve serve-global devserver publish ssh_upload rsync_upload
+.PHONY: run
+run:
+	make -C fgs
diff --git a/config.json b/config.json
new file mode 100644
index 0000000000000000000000000000000000000000..9acaf807aa1c485346cf1749283d2d77ccfab50b
--- /dev/null
+++ b/config.json
@@ -0,0 +1,89 @@
+{
+	"relative_urls": true,
+	"static_directories": [
+		"images"
+	],
+	"theme": {
+		"default_tag_color": "hsl(289, 43%, 56%)",
+		"homepage_slug": "index",
+		"static_dir": "theme",
+		"css_file": "main.css",
+		"default_template": "page.html"
+	},
+	"default_status": "published",
+	"toc_depth": 3,
+	"pandoc": {
+		"base": "markdown",
+		"args": [
+			"--table-of-contents"
+		],
+		"extensions": [
+			"+abbreviations",
+			"+all_symbols_escapable",
+			"+ascii_identifiers",
+			"+auto_identifiers",
+			"+autolink_bare_uris",
+			"+backtick_code_blocks",
+			"+bracketed_spans",
+			"+definition_lists",
+			"+emoji",
+			"+escaped_line_breaks",
+			"+example_lists",
+			"+fancy_lists",
+			"+fenced_code_attributes",
+			"+fenced_code_blocks",
+			"+fenced_divs",
+			"+footnotes",
+			"+gfm_auto_identifiers",
+			"+grid_tables",
+			"+hard_line_breaks",
+			"+header_attributes",
+			"+implicit_figures",
+			"+implicit_header_references",
+			"+inline_code_attributes",
+			"+inline_notes",
+			"+intraword_underscores",
+			"+latex_macros",
+			"+line_blocks",
+			"+link_attributes",
+			"+lists_without_preceding_blankline",
+			"+multiline_tables",
+			"+native_divs",
+			"+native_spans",
+			"+pipe_tables",
+			"+raw_attribute",
+			"+raw_html",
+			"+raw_tex",
+			"+shortcut_reference_links",
+			"+simple_tables",
+			"+smart",
+			"+space_in_atx_header",
+			"+startnum",
+			"+strikeout",
+			"+subscript",
+			"+superscript",
+			"+table_captions",
+			"+tex_math_dollars",
+			"+tex_math_single_backslash",
+			"+yaml_metadata_block",
+			"-angle_brackets_escapable",
+			"-blank_before_blockquote",
+			"-blank_before_header",
+			"-citations",
+			"-compact_definition_lists",
+			"-east_asian_line_breaks",
+			"-four_space_rule",
+			"-ignore_line_breaks",
+			"-literate_haskell",
+			"-markdown_attribute",
+			"-markdown_in_html_blocks",
+			"-mmd_header_identifiers",
+			"-mmd_link_attributes",
+			"-mmd_title_block",
+			"-old_dashes",
+			"-pandoc_title_block",
+			"-spaced_reference_links",
+			"-tex_math_double_backslash"
+		]
+	}
+}
diff --git a/fgs/Makefile b/fgs/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..6cfa260bbf2297c11be043f09ddb62a9ada3e30e
--- /dev/null
+++ b/fgs/Makefile
@@ -0,0 +1,4 @@
+
+.PHONY: run
+run:
+	python3 __main__.py
diff --git a/fgs/__main__.py b/fgs/__main__.py
new file mode 100644
index 0000000000000000000000000000000000000000..0d984cd755b893141d14f1ead70d85963dd47e81
--- /dev/null
+++ b/fgs/__main__.py
@@ -0,0 +1,86 @@
+#!/usr/bin/env python3
+
+import json
+import os
+import sys
+
+
+import reader
+import generator
+import writer
+
+CONTENT_DIR = '../content'
+OUTPUT_DIR = '../output'
+THEME_DIR = '../theme'
+
+
+def main():
+    print("Hello World")
+    config = {}
+    with open('../config.json') as f:
+        config.update(json.loads(f.read()))
+    with open('../lang.json') as f:
+        config['lang'] = json.loads(f.read())
+    with open(CONTENT_DIR + '/config.json') as f:
+        config.update(json.loads(f.read()))
+    print(config)
+
+    mdreader = reader.MarkdownReader(config)
+    pages = []
+    directory = os.path.join(CONTENT_DIR, ".")
+    parse_dir(directory, pages, mdreader)
+
+    static_dirs = []
+    for sdir in config['static_directories']:
+        if "." in sdir:
+            raise Exception("Illegal static directory name: ", sdir)
+        static_dirs.append(CONTENT_DIR + "/"+ sdir)
+    static_files = {}
+    for sdir in static_dirs:
+        read_static_dir(sdir, static_files)
+
+    read_static_dir(THEME_DIR + '/static', static_files, [config['theme']['static_dir']])
+
+
+    context = {}
+    gen = generator.Generator(config, context)
+    gen.generate_context(pages, static_files)
+
+    wrt = writer.Writer(config, context, OUTPUT_DIR, THEME_DIR)
+
+    gen.generate_output(wrt)
+
+
+
+def read_static_dir(directory, static_files, subpath = []):
+    print("static_dir: " + directory);
+    for filename in os.listdir(directory):
+        fp = os.path.join(directory, filename)
+        if os.path.isfile(fp) and not filename.startswith("."):
+            lpath = '/'.join(subpath + [filename])
+            with open(fp, "rb") as f:
+                static_files[lpath] = f.read()
+            print("read: ", fp, " as: ", lpath)
+        elif os.path.isdir(fp) and not filename.startswith("."):
+            read_static_dir(fp, static_files, subpath + [filename])
+
+
+def parse_dir(directory, pages, mdreader, subpath = []):
+    print("parse_dir: " + directory);
+    for filename in os.listdir(directory):
+        f = os.path.join(directory, filename)
+        if os.path.isfile(f) and filename.endswith(".md"):
+            pages.append(mdreader.read_and_parse_file(f, subpath))
+        elif os.path.isdir(f) and not filename.startswith("."):
+            parse_dir(f, pages, mdreader, subpath + [filename])
+
+
+
+
+
+
+    
+
+if __name__ == '__main__':
+    main()
+
diff --git a/fgs/generator.py b/fgs/generator.py
new file mode 100644
index 0000000000000000000000000000000000000000..e620c4aa0da86495de7be445d12e81d09b6ad8bb
--- /dev/null
+++ b/fgs/generator.py
@@ -0,0 +1,92 @@
+
+class Generator:
+    def __init__(self, config, context):
+        self.config = config
+        self.context = context
+
+    def generate_context(self, pages, static_files):
+        # static_files
+        self.context['static_files'] = static_files
+
+        published_pages = []
+        for page in pages:
+            if page.status == "published":
+                published_pages.append(page)
+
+
+        # pages
+        all_pages = {}
+        for page in published_pages:
+            if page.lang not in all_pages:
+                all_pages[page.lang] = {}
+            if page.slug in all_pages[page.lang]:
+                raise Exception("duplicate language (",lang,") for slug '", slug ,"'")
+            all_pages[page.lang][page.slug] = page
+        self.context['pages'] = all_pages
+
+        # pages_modified
+        pages_modified = {}
+        for lang in self.config['lang']['supported']:
+            lang_pages = []
+            for page in published_pages:
+                if page.lang == lang:
+                    lang_pages.append(page)
+            lang_pages.sort()
+            pages_modified[lang] = lang_pages
+        self.context['pages_modified'] = pages_modified
+
+        # TODO hidden pages
+        # TODO draft pages
+        # TODO authors
+
+        # categories
+        categories = {}
+        for page in published_pages:
+            if page.lang not in categories:
+                categories[page.lang] = {}
+            if page.category not in categories[page.lang]:
+                categories[page.lang][page.category] = []
+            categories[page.lang][page.category].append(page)
+        self.context['categories'] = categories
+
+        # tags
+        tags = {}
+        for page in published_pages:
+            for tag in page.tags:
+                if page.lang not in tags:
+                    tags[page.lang] = {}
+                if tag not in tags[page.lang]:
+                    tags[page.lang][tag] = []
+                tags[page.lang][tag].append(page)
+        self.context['tags'] = tags
+
+
+
+    def generate_homepage(self, writer, lang, path):
+            page = self.context['pages'][lang][self.config['theme']['homepage_slug']]
+            writer.write_template(page.template, path , lang, {'page': page})
+
+
+    def generate_output(self, writer):
+        for sf, raw in self.context['static_files'].items():
+                print ("writing binary file: ", sf)
+                writer.write_file(sf, raw, mode="wb")
+
+        for lang in self.config['lang']['supported']:
+            # all pages
+            for page in self.context['pages'][lang].values():
+                writer.write_template(page.template, page.url, lang, {'page': page})
+
+            # all tags
+            for tag, tagpages in self.context['tags'][lang].items():
+                writer.write_template('tag.html', lang + '/tag/' + tag + '.html', lang, {'tag': tag, 'tagpages': tagpages})
+
+            # homepages for languages
+            self.generate_homepage(writer, lang, lang + "/index.html")
+        
+            
+        # homepage
+        self.generate_homepage(writer, self.config['lang']['default'], "index.html")
+            
+        
+
diff --git a/fgs/page.py b/fgs/page.py
new file mode 100644
index 0000000000000000000000000000000000000000..801b5959f0795e08ed237509f302746723fe7f96
--- /dev/null
+++ b/fgs/page.py
@@ -0,0 +1,103 @@
+class Page:
+
+    #filename = None
+    #subpath = None
+    #raw = None
+    #metadata = None
+    #content = None
+    #toc = None
+    #title = None
+    #category = None
+    #slug = None
+    #lang = None
+    #date_created = None
+    #date_modified = None
+    #status = None
+    #authors = None
+    #tags = None
+    #template = None
+    
+
+    def __init__(self, filename, subpath, raw, metadata, content, toc, title, category, slug, lang, date_created, date_modified, authors, last_modification_author, status, config):
+        self.filename =  filename
+        self.subpath =  subpath
+        self.raw =  raw
+        self.metadata =  metadata
+        self.content =  content
+        self.toc =  toc
+        self.title =  title
+        self.category =  category
+        self.slug =  slug
+        self.lang =  lang
+        self.date_created =  Date(date_created, config)
+        self.date_modified =  Date(date_modified, config)
+        self.status =  status
+
+        #self.config =  config
+
+        # authors
+        self.authors = []
+        for local_part, domain, name in authors:
+            self.authors.append(Author(local_part, domain, name, config))
+        if last_modification_author:
+            self.last_modification_author = Author(last_modification_author[0], last_modification_author[1], last_modification_author[2], config)
+        else:
+            self.last_modification_author = None
+        #print(authors)
+        #print(last_modification_author)
+
+
+        # tags
+        self.tags = []
+        if 'tags' in metadata:
+            tags = self.tags_str_to_list(metadata['tags'])
+            for t in tags:
+                if (t != category):
+                    self.tags.append(t)
+        self.tags.append(self.category) # the category is also a default tag
+
+        # template
+        if 'template' in metadata:
+            self.template = metadata['template']
+        else:
+            self.template = config['theme']['default_template']
+
+        # url
+        self.url = self.lang + '/' + self.category + '/' + self.slug + ".html"
+
+    def tags_str_to_list(self, s):
+        res = []
+        l = s.split(',')
+        for t in l:
+            t = t.lower()
+            t = t.lstrip()
+            t = t.rstrip()
+            if len(t) > 0:
+                res.append(t)
+        return res
+
+
+
+    def __lt__(self, other):
+        return self.date_modified < other.date_modified
+
+
+class Date:
+    def __init__(self, dt, config):
+        self.dt = dt
+        #self.config = config
+        # TODO various formats
+    def isoformat(self):
+        return self.dt.isoformat()
+
+    def __lt__(self, other):
+        return self.dt.timestamp() < other.dt.timestamp()
+
+class Author:
+    def __init__(self, email_local_part, email_domain, name, config):
+        self.email_local_part = email_local_part
+        self.email_domain = email_domain
+        self.email = email_local_part + '@' + email_domain
+        self.name = name
+        #self.config = config
+        # TODO md5 hash etc.
diff --git a/fgs/pandoc_toc.html b/fgs/pandoc_toc.html
new file mode 100644
index 0000000000000000000000000000000000000000..2fd5a7bd68b1d9474ccc9e42a8dc058a8801e06b
--- /dev/null
+++ b/fgs/pandoc_toc.html
@@ -0,0 +1 @@
+$table-of-contents$
diff --git a/fgs/reader.py b/fgs/reader.py
new file mode 100644
index 0000000000000000000000000000000000000000..9cc14065d9781c638546842e82415482dac9354a
--- /dev/null
+++ b/fgs/reader.py
@@ -0,0 +1,218 @@
+import page
+
+import frontmatter
+
+from datetime import datetime
+from dateutil import parser as dtparser
+
+import subprocess
+import os
+
+class MarkdownReader:
+
+    def __init__(self, config):
+        self.config = config
+
+
+    def read_and_parse_file(self, path, subpath):
+        if not path.endswith(".md"):
+            raise Exception("can only parse markdown files: ", path)
+        elif len(subpath) > 1:
+            raise Exception("markdown file is too deep in directory structure: ", path, subpath)
+
+
+        print("parsing file: ", path, subpath)
+
+        f = open(path)
+        rawcontent = f.read()
+        f.close()
+        metadata, _ = frontmatter.parse(rawcontent)
+
+        #print(metadata)
+
+        category_name = self.get_category_name(metadata, subpath)
+
+        # content
+        content = self.run_pandoc(rawcontent, self.config['pandoc']['base'], self.config['pandoc']['extensions'], "html5")
+        #print(content)
+
+        # TOC
+        toc = self.run_pandoc(rawcontent, self.config['pandoc']['base'], self.config['pandoc']['extensions'], "html5", ["--template", "./pandoc_toc.html", "--toc", "--toc-depth", str(self.config['toc_depth'])])
+        #print((toc))
+    
+        # title
+        if 'title' not in metadata:
+            raise Exception("File is missing title in metadata: ", path, subpath)
+        title = metadata['title']
+
+        # slug and lang
+        pathlist = path.split('/')
+        filename = pathlist[-1]
+        filenamelist = filename.split('.')
+        filenamelist = filenamelist[:-1] # remove .md
+        slug = None
+        lang = None
+        if len(filenamelist) < 1:
+            raise Exception("filename is empty?", path, subpath)
+        elif len(filenamelist) == 1:
+            slug = filenamelist[0]
+        elif len(filenamelist) == 2:
+            slug = filenamelist[0]
+            lang = filenamelist[1]
+        if 'slug' in metadata:
+            slug = metadata['slug']
+        if 'lang' in metadata:
+            lang = metadata['lang']
+
+        if lang == None:
+            lang = self.config['lang']['default']
+
+        if not self.is_supported_lang(lang):
+            raise Exception("language is not supported: ", lang)
+        slug = self.secure_slug(slug)
+        #print("slug: ", slug)
+        #print("lang: ", lang)
+
+        # date_created and date_modified
+        date_modified = datetime.now()
+        date_created = datetime.now()
+        date_changes = self.run_git(path, "log", ["--follow", "--format=%ad", "--date", "iso-strict"]).splitlines()
+        #print("date_changes: ", date_changes)
+        if (len(date_changes) > 0):
+            date_modified = datetime.fromisoformat(date_changes[0])
+            date_created = datetime.fromisoformat(date_changes[-1])
+        if 'date' in metadata:
+            date_created = dtparser.parse(metadata['date'])
+        if 'modified' in metadata:
+            date_modified = dtparser.parse(metadata['modified'])
+        #print("created: ", date_created)
+        #print("last changed: ", date_modified)
+
+        # author
+        # TODO author from metadata
+        authors_raw = self.run_git(path, "log", ["--follow", "--format=%aE@%aN", "--use-mailmap"]).splitlines()
+        authors = []
+        known_author_raws = []
+        for author_raw in authors_raw:
+            if author_raw not in known_author_raws:
+                authors.append(self.extract_author(author_raw))
+                known_author_raws.append(author_raw)
+        if len(authors_raw) > 0:
+            last_modification_author = self.extract_author(authors_raw[0])
+        else:
+            last_modification_author = None
+
+
+
+        # status
+        status = self.config['default_status']
+        if 'status' in metadata:
+            status = metadata['status']
+        valid_status = ["published", "draft", "hidden"]
+        if status not in valid_status:
+            raise Exception("invalid status '", status, "' must be one of ", valid_status)
+
+        # TODO summary
+
+        p = page.Page(
+                filename,
+                subpath,
+                rawcontent,
+                metadata,
+                content,
+                toc,
+                title,
+                category_name,
+                slug,
+                lang,
+                date_created,
+                date_modified,
+                authors,
+                last_modification_author,
+                status,
+                self.config)
+        return p
+
+    def extract_author(self, raw):
+        author_split = raw.split('@')
+        author_local_part = author_split[0]
+        author_domain = author_split[1]
+        author_name = '@'.join(author_split[2:])
+        return (author_local_part, author_domain, author_name)
+
+    def is_supported_lang(self, lang):
+        if not isinstance(lang, str):
+            return False
+        return (lang in self.config['lang']['supported'])
+
+
+    def secure_slug(self, slug):
+        if not isinstance(slug, str):
+            raise Exception("slug is not a string: '", slug, "'")
+        slug = slug.lower()
+        whitelist = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-"
+        res = ""
+        for c in slug:
+            if c in whitelist:
+                #print ("c: '", c,"'")
+                res += c
+        #print("res: '", res, "'")
+        if len(res) == 0:
+            raise Exception("slug is empty")
+        return res
+
+
+    def get_category_name(self, metadata, subpath):
+        if 'category' in metadata:
+            return metadata['category']
+        elif len(subpath) == 1:
+            return subpath[0]
+        else:
+            return 'misc'
+
+    def run_git(self, path, subcmd, extra_args):
+        real_path = os.path.realpath(path)
+        filename = os.path.basename(real_path)
+        dir_path = os.path.dirname(real_path)
+        git_bin = "git"
+        args = [git_bin, subcmd] + extra_args + ["--", filename]
+        p = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, cwd=dir_path)
+        out, _ = p.communicate("".encode('utf-8', errors='strict'))
+        out_str = out.decode('utf-8')
+        return out_str
+
+
+    def run_pandoc(self, source, base="markdown", extensions=[], to="json", extra_args=[]):
+        ext_str = ""
+        if isinstance(extensions, list):
+            for ext in extensions:
+                if ext.startswith('#'):
+                    continue
+                if ext.startswith('+') or ext.startswith('-'):
+                    ext_str = ext_str + ext
+                elif len(ext) > 0:
+                    ext_str = ext_str + '+' + ext
+        elif isinstance(extensions, dict):
+            for ext_key in extensions:
+                # TODO catch 'illegal' ext_keys (containing spaces for example)
+                ext = extensions[ext_key]
+                if "ignore" in ext and ext["ignore"]:
+                    continue
+                flag='+'
+                if "enabled" in ext and not ext["enabled"]:
+                    flag='-'
+                ext_str = ext_str + flag + ext_key
+    
+        #print(ext_str)
+        pandoc_bin = "pandoc"
+        args = [pandoc_bin, "-f", base + ext_str, "-t", to] + extra_args
+        p = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
+        out, _ = p.communicate(source.encode('utf-8', errors='strict'))
+        out_str = out.decode('utf-8')
+        #print("----------------------")
+        #print(out_str)
+        #print("----------------------")
+        #json_dict = json.loads(out.decode('utf-8'))
+        return out_str
+
+
diff --git a/fgs/writer.py b/fgs/writer.py
new file mode 100644
index 0000000000000000000000000000000000000000..a5eb1e6098b5f427b24e4e72d6ad7eddd6e26d25
--- /dev/null
+++ b/fgs/writer.py
@@ -0,0 +1,61 @@
+from jinja2 import Environment, FileSystemLoader
+
+import os
+
+class Writer:
+
+    def __init__(self, config, context, output_dir, theme_dir):
+        self.config = config
+        self.context = context
+        self.output_dir = output_dir
+        self.theme_dir = theme_dir
+
+        self.env = Environment(
+            loader=FileSystemLoader(theme_dir + "/templates"),
+            autoescape=False
+        )
+
+        print("templates: ", self.env.list_templates())
+
+    def write_template(self, template, path, lang, extra_context):
+        tmpl = self.env.get_template(template)
+
+        pathsplit = path.split('/')
+        #pathsplit.remove('.') # TODO remove all not just one
+        #pathsplit.remove('..') # TODO remove all not just one
+
+        siteurl = "."
+        if self.config['relative_urls']:
+            count = len(pathsplit) - 1
+            for i in range(count):
+                siteurl = siteurl + '/..'
+        else:
+            siteurl = config['siteurl']
+
+        # render template
+        context = {}
+        context.update(self.context)
+        context["config"] = self.config
+        context["theme"] = self.config['theme']
+        context["template"] = template
+        context["t"] = self.config["lang"] # translate
+        context["l"] = lang # current language
+        context["path"] = path
+        context["siteurl"] = siteurl
+        context.update(extra_context)
+        out = tmpl.render(context)
+
+        # write file
+        self.write_file(path, out)
+
+    def write_file(self, path, out, mode="w"):
+        # write to file
+        fullpath = self.output_dir + '/' + path
+        directory = os.path.dirname(fullpath)
+        print("fullpath: ", fullpath)
+        print("dir: ", directory)
+        os.makedirs(directory, exist_ok=True)
+        with open(fullpath, mode) as f:
+            f.write(out)
+
+
diff --git a/installpelican.sh b/installpelican.sh
deleted file mode 100755
index 639a843245a3868b73776768d4515dbf354d7b85..0000000000000000000000000000000000000000
--- a/installpelican.sh
+++ /dev/null
@@ -1,9 +0,0 @@
-#!/bin/bash
-
-set -e
-
-# Install Pelican with dependencies
-pip3 install git+https://gitlab.gwdg.de/GAUMI-fginfo/pelican.git
-
-# Reinstall Pelican to make sure it is up-to-date
-pip3 install --force-reinstall --no-cache-dir --no-deps git+https://gitlab.gwdg.de/GAUMI-fginfo/pelican.git
diff --git a/lang.json b/lang.json
index 510c99970da47f20d02524bb6ace12a5c8638ee9..56055dc73d694c7ec93b3c53a18cc55145f2511f 100644
--- a/lang.json
+++ b/lang.json
@@ -1,4 +1,8 @@
 {
+	"default": "de",
+	"supported": [
+		"de"
+	],
 	"de": {
 		"langname": "Deutsch",
 		"sitename": "Fachgruppe Informatik",
diff --git a/pelican-plugins b/pelican-plugins
deleted file mode 160000
index 483215d2e1998684b7f413b6b1c8e26058fd2e6b..0000000000000000000000000000000000000000
--- a/pelican-plugins
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 483215d2e1998684b7f413b6b1c8e26058fd2e6b
diff --git a/pelicanconf.py b/pelicanconf.py
deleted file mode 100644
index 39e4fdc4922c900be343d4fbd306521850bc2c0b..0000000000000000000000000000000000000000
--- a/pelicanconf.py
+++ /dev/null
@@ -1,120 +0,0 @@
-import json
-
-AUTHOR = 'Fachgruppe Informatik'
-SITENAME = 'Dieser Wert wird ignoriert und soll stattdessen in lang.json geändert werden.'
-SITEURL = ''
-
-PATH = 'content'
-
-TIMEZONE = 'Europe/Berlin'
-
-DEFAULT_LANG = 'de'
-
-# Feed generation is usually not desired when developing
-FEED_ALL_ATOM = None
-CATEGORY_FEED_ATOM = None
-TRANSLATION_FEED_ATOM = None
-AUTHOR_FEED_ATOM = None
-AUTHOR_FEED_RSS = None
-
-#DEFAULT_PAGINATION = 10
-DEFAULT_PAGINATION = False
-
-# Uncomment following line if you want document-relative URLs when developing
-RELATIVE_URLS = False
-
-OUTPUT_SOURCES = True
-
-TYPOGRIFY = True
-
-STATIC_PATHS = ['images']
-
-
-PANDOC_ARGS = [
-#    "--mathjax",
-#    "--citeproc",
-    "--table-of-contents",
-]
-
-PANDOC_EXTENSIONS = [
-    "+abbreviations",
-    "+all_symbols_escapable",
-    "+ascii_identifiers",
-    "+auto_identifiers",
-    "+autolink_bare_uris",
-    "+backtick_code_blocks",
-    "+bracketed_spans",
-    "+definition_lists",
-    "+emoji",
-    "+escaped_line_breaks",
-    "+example_lists",
-    "+fancy_lists",
-    "+fenced_code_attributes",
-    "+fenced_code_blocks",
-    "+fenced_divs",
-    "+footnotes",
-    "+gfm_auto_identifiers",
-    "+grid_tables",
-    "+hard_line_breaks",
-    "+header_attributes",
-    "+implicit_figures",
-    "+implicit_header_references",
-    "+inline_code_attributes",
-    "+inline_notes",
-    "+intraword_underscores",
-    "+latex_macros",
-    "+line_blocks",
-    "+link_attributes",
-    "+lists_without_preceding_blankline",
-    "+multiline_tables",
-    "+native_divs",
-    "+native_spans",
-    "+pipe_tables",
-    "+raw_attribute",
-    "+raw_html",
-    "+raw_tex",
-    "+shortcut_reference_links",
-    "+simple_tables",
-    "+smart",
-    "+space_in_atx_header",
-    "+startnum",
-    "+strikeout",
-    "+subscript",
-    "+superscript",
-    "+table_captions",
-    "+tex_math_dollars",
-    "+tex_math_single_backslash",
-    "+yaml_metadata_block",
-    "-angle_brackets_escapable",
-    "-blank_before_blockquote",
-    "-blank_before_header",
-    "-citations",
-    "-compact_definition_lists",
-    "-east_asian_line_breaks",
-    "-four_space_rule",
-    "-ignore_line_breaks",
-    "-literate_haskell",
-    "-markdown_attribute",
-    "-markdown_in_html_blocks",
-    "-mmd_header_identifiers",
-    "-mmd_link_attributes",
-    "-mmd_title_block",
-    "-old_dashes",
-    "-pandoc_title_block",
-    "-spaced_reference_links",
-    "-tex_math_double_backslash",
-]
-
-CALCULATE_READING_TIME = True
-
-
-PLUGIN_PATHS = ['./pelican-plugins']
-PLUGINS = ['filetime_from_git']
-
-THEME = './theme'
-JINJA_GLOBALS = {} 
-with open('./lang.json') as json_file:
-    JINJA_GLOBALS['l'] = json.load(json_file)
-with open(PATH + '/config.json') as json_file:
-    JINJA_GLOBALS['sc'] = json.load(json_file)
-
diff --git a/publishconf.py b/publishconf.py
deleted file mode 100644
index 7ca19fcc6061a8fcfc8df72b5b1a6be5bcaa5b9e..0000000000000000000000000000000000000000
--- a/publishconf.py
+++ /dev/null
@@ -1,21 +0,0 @@
-# This file is only used if you use `make publish` or
-# explicitly specify it as your config file.
-
-import os
-import sys
-sys.path.append(os.curdir)
-from pelicanconf import *
-
-# If your site is available via HTTPS, make sure SITEURL begins with https://
-SITEURL = 'https://fg.informatik.uni-goettingen.de'
-RELATIVE_URLS = False
-
-FEED_ALL_ATOM = 'feeds/all.atom.xml'
-CATEGORY_FEED_ATOM = 'feeds/{slug}.atom.xml'
-
-DELETE_OUTPUT_DIRECTORY = True
-
-# Following items are often useful when publishing
-
-#DISQUS_SITENAME = ""
-#GOOGLE_ANALYTICS = ""
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000000000000000000000000000000000000..82138ee947a568907e0ced324fa2529ed5ef3058
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,4 @@
+python-frontmatter
+pypandoc
+jinja2
+dateutils
diff --git a/tasks.py b/tasks.py
deleted file mode 100644
index 559d52b49ece3ecb47abc1b33046f1aa2b5f150d..0000000000000000000000000000000000000000
--- a/tasks.py
+++ /dev/null
@@ -1,143 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import os
-import shlex
-import shutil
-import sys
-import datetime
-
-from invoke import task
-from invoke.main import program
-from invoke.util import cd
-from pelican import main as pelican_main
-from pelican.server import ComplexHTTPRequestHandler, RootedHTTPServer
-from pelican.settings import DEFAULT_CONFIG, get_settings_from_file
-
-OPEN_BROWSER_ON_SERVE = True
-SETTINGS_FILE_BASE = 'pelicanconf.py'
-SETTINGS = {}
-SETTINGS.update(DEFAULT_CONFIG)
-LOCAL_SETTINGS = get_settings_from_file(SETTINGS_FILE_BASE)
-SETTINGS.update(LOCAL_SETTINGS)
-
-CONFIG = {
-    'settings_base': SETTINGS_FILE_BASE,
-    'settings_publish': 'publishconf.py',
-    # Output path. Can be absolute or relative to tasks.py. Default: 'output'
-    'deploy_path': SETTINGS['OUTPUT_PATH'],
-    # Remote server configuration
-    'ssh_user': 'gitlab',
-    'ssh_host': 'fg.informatik.uni-goettingen.de',
-    'ssh_port': '22',
-    'ssh_path': '/home/gitlab/public',
-    # Host and port for `serve`
-    'host': 'localhost',
-    'port': 8000,
-}
-
-@task
-def clean(c):
-    """Remove generated files"""
-    if os.path.isdir(CONFIG['deploy_path']):
-        shutil.rmtree(CONFIG['deploy_path'])
-        os.makedirs(CONFIG['deploy_path'])
-
-@task
-def build(c):
-    """Build local version of site"""
-    pelican_run('-s {settings_base}'.format(**CONFIG))
-
-@task
-def rebuild(c):
-    """`build` with the delete switch"""
-    pelican_run('-d -s {settings_base}'.format(**CONFIG))
-
-@task
-def regenerate(c):
-    """Automatically regenerate site upon file modification"""
-    pelican_run('-r -s {settings_base}'.format(**CONFIG))
-
-@task
-def serve(c):
-    """Serve site at http://$HOST:$PORT/ (default is localhost:8000)"""
-
-    class AddressReuseTCPServer(RootedHTTPServer):
-        allow_reuse_address = True
-
-    server = AddressReuseTCPServer(
-        CONFIG['deploy_path'],
-        (CONFIG['host'], CONFIG['port']),
-        ComplexHTTPRequestHandler)
-
-    if OPEN_BROWSER_ON_SERVE:
-        # Open site in default browser
-        import webbrowser
-        webbrowser.open("http://{host}:{port}".format(**CONFIG))
-
-    sys.stderr.write('Serving at {host}:{port} ...\n'.format(**CONFIG))
-    server.serve_forever()
-
-@task
-def reserve(c):
-    """`build`, then `serve`"""
-    build(c)
-    serve(c)
-
-@task
-def preview(c):
-    """Build production version of site"""
-    pelican_run('-s {settings_publish}'.format(**CONFIG))
-
-@task
-def livereload(c):
-    """Automatically reload browser tab upon file modification."""
-    from livereload import Server
-
-    def cached_build():
-        cmd = '-s {settings_base} -e CACHE_CONTENT=True LOAD_CONTENT_CACHE=True'
-        pelican_run(cmd.format(**CONFIG))
-
-    cached_build()
-    server = Server()
-    theme_path = SETTINGS['THEME']
-    watched_globs = [
-        CONFIG['settings_base'],
-        '{}/templates/**/*.html'.format(theme_path),
-    ]
-
-    content_file_extensions = ['.md', '.rst']
-    for extension in content_file_extensions:
-        content_glob = '{0}/**/*{1}'.format(SETTINGS['PATH'], extension)
-        watched_globs.append(content_glob)
-
-    static_file_extensions = ['.css', '.js']
-    for extension in static_file_extensions:
-        static_file_glob = '{0}/static/**/*{1}'.format(theme_path, extension)
-        watched_globs.append(static_file_glob)
-
-    for glob in watched_globs:
-        server.watch(glob, cached_build)
-
-    if OPEN_BROWSER_ON_SERVE:
-        # Open site in default browser
-        import webbrowser
-        webbrowser.open("http://{host}:{port}".format(**CONFIG))
-
-    server.serve(host=CONFIG['host'], port=CONFIG['port'], root=CONFIG['deploy_path'])
-
-
-@task
-def publish(c):
-    """Publish to production via rsync"""
-    pelican_run('-s {settings_publish}'.format(**CONFIG))
-    c.run(
-        'rsync --delete --exclude ".DS_Store" -pthrvz -c '
-        '-e "ssh -p {ssh_port}" '
-        '{} {ssh_user}@{ssh_host}:{ssh_path}'.format(
-            CONFIG['deploy_path'].rstrip('/') + '/',
-            **CONFIG))
-
-
-def pelican_run(cmd):
-    cmd += ' ' + program.core.remainder  # allows to pass-through args to pelican
-    pelican_main(shlex.split(cmd))
\ No newline at end of file
diff --git a/theme/templates/Makefile b/theme/templates/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..396366804620681312cf4cb22a4f89294ed09b6f
--- /dev/null
+++ b/theme/templates/Makefile
@@ -0,0 +1,3 @@
+.PHONY: run
+run:
+	make -C ../../fgs
diff --git a/theme/templates/article.html b/theme/templates/article.html
deleted file mode 100644
index 1a2c28ab4adf8e48fd37a66eaa57e7d741091353..0000000000000000000000000000000000000000
--- a/theme/templates/article.html
+++ /dev/null
@@ -1,69 +0,0 @@
-{% extends "base.html" %}
-
-{%- macro article_translation_link(translation, is_current=False) -%}
-<a href="{{ SITEURL }}/{{ translation.url }}" hreflang="{{ translation.lang }}" title="{{ l[translation.lang].langname|e }}">
-	<img alt="{{ l[translation.lang].langname|e }}" src="{{ SITEURL }}/{{ THEME_STATIC_DIR }}/images/flags/{{ translation.lang }}.svg">
-</a>
-{%- endmacro -%}
-
-{% block html_lang %}{{ article.lang }}{% endblock %}
-
-{% block title %}{{ article.title|striptags }}{% endblock %}
-
-{% block extra_head %}
-	{%- for translation in article.translations -%}
-		<link rel="alternate" hreflang="{{ translation.lang }}" href="{{ SITEURL }}/{{ translation.url }}">
-	{%- endfor -%}
-
-	{% if article.summary %}
-		<meta name="description" content="{{ article.summary | striptags | safe | truncate(150) }}" />
-	{% endif %}
-{% endblock %}
-
-{% block content %}
-	<article>
-		<header>
-			<h1>{{ article.title }}</h1>
-		</header>
-		{{ article.content }}
-		<footer>
-			{%- if article.authors -%}
-			<address>
-				{{ l[lang].article.authors_prefix }}
-				{% for author in article.authors %}
-					<a href="{{ SITEURL }}/{{ author.url }}">{{ author }}</a>
-				{% endfor %}
-				{{ l[lang].article.authors_suffix }}
-			</address>
-			{%- endif -%}
-				
-			<span>{{ l[lang].article.published_prefix }}
-			<abbr title="{{ article.date.isoformat() }}">
-				{{ article.locale_date }}
-			</abbr>{{ l[lang].article.published_suffix }}</span>
-
-			{%- if article.modified -%}
-			<br />
-			<span>{{ l[lang].article.modified_prefix }}
-			<abbr title="{{ article.modified.isoformat() }}">{{ article.locale_modified }}</abbr>
-			{{ l[lang].article.modified_suffix }}</span>
-			{%- endif -%}
-
-			<br />
-			<span>{{ l[lang].article.category_prefix }}<a href="{{ SITEURL }}/{{ article.category.url }}">{{ article.category }}</a>{{ l[lang].article.category_suffix }}</span>
-			<br />
-			<span>{{ l[lang].article.languages_prefix }}</span>
-			<ul class="languages">
-			{%- for translation in article.translations -%}
-				<li>
-					{{ article_translation_link(translation) }}
-				</li>
-			{%- endfor -%}
-				<li>
-					{{ article_translation_link(article, True) }}
-				</li>
-			</ul>
-			{{ l[lang].article.languages_suffix }}
-		</footer>
-	</article>
-{% endblock %}
diff --git a/theme/templates/base.html b/theme/templates/base.html
index 6466a8f7566c435c8e4cba8909ab67d03bbc5a38..8a346355e90b1416b3231d6471c7890cf684ffc1 100644
--- a/theme/templates/base.html
+++ b/theme/templates/base.html
@@ -1,42 +1,35 @@
 <!DOCTYPE html>
-{#- TODO lang irgendwie vernünftig setzen/erkennen -#}
-{%- if lang is not defined -%}
-	{%- set lang = DEFAULT_LANG -%}
-{%- else -%}
-Hurra!!! lang ist definiert als {{ lang }}.
-{{ diese_variable_existiert_nicht_werfe_fehler }}
-{%- endif -%}
 
 {%- import 'macros/getters.html' as get with context -%}
 {%- import 'macros/cards.html' as cards with context -%}
 {%- import 'macros/renderers.html' as render with context -%}
 
-<html lang="{%- block html_lang -%}{{ DEFAULT_LANG }}{%- endblock html_lang -%}">
+<html lang="{%- block html_lang -%}{{ l }}{%- endblock html_lang -%}">
 <head>
 	{% block head %}
 	<meta charset="utf-8" />
 	<meta name="viewport" content="width=device-width, initial-scale=1.0">
-	<title>{% block title %}{{ l[lang].title_prefix }}{{ l[lang].sitename }}{{ l[lang].title_suffix }}{%endblock%}</title>
+	<title>{% block title %}{{ t[l].title_prefix }}{{ t[l].sitename }}{{ t[l].title_suffix }}{%endblock%}</title>
 	<!-- <base target="_blank"> -->
 	<!-- <meta HTTP-EQUIV="REFRESH" content="500; url=#"> -->
-	<link rel="preload" href="{{ SITEURL }}/{{ THEME_STATIC_DIR }}/css/{{ CSS_FILE }}" as="style" />
-	<link rel="stylesheet" type="text/css" href="{{ SITEURL }}/{{ THEME_STATIC_DIR }}/css/{{ CSS_FILE }}" />
+	<link rel="preload" href="{{ siteurl }}/{{ theme.static_dir }}/css/{{ theme.css_file }}" as="style" />
+	<link rel="stylesheet" type="text/css" href="{{ siteurl }}/{{ theme.static_dir }}/css/{{ theme.css_file }}" />
 	{#- TODO load javascript? -#}
 	{#- TODO og: meta tags -#}
 	{#- TODO favicon -#}
 	<!-- <link rel="icon" type="image/png" sizes="192x192" href="static/img/favicon-192x192.png"/>
 	<link rel="icon" type="image/png" sizes="32x32" href="static/img/favicon-32x32.png"/>-->
 	<!-- <link rel="icon" type="image/x-icon" sizes="16x16" href="static/img/favicon.ico"/> -->
-	{%- if FEED_ALL_ATOM %}
-	<link href="{{ FEED_DOMAIN }}/{% if FEED_ALL_ATOM_URL %}{{ FEED_ALL_ATOM_URL }}{% else %}{{ FEED_ALL_ATOM }}{% endif %}" type="application/atom+xml" rel="alternate" title="{{ l[lang].atom.title|e }}" />
-	{% endif -%}
+	{#{%- if FEED_ALL_ATOM %}
+	<link href="{{ FEED_DOMAIN }}/{% if FEED_ALL_ATOM_URL %}{{ FEED_ALL_ATOM_URL }}{% else %}{{ FEED_ALL_ATOM }}{% endif %}" type="application/atom+xml" rel="alternate" title="{{ t[l].atom.title|e }}" />
+	{% endif -%}#}
 	{% block extra_head %}{% endblock extra_head %}
 	{% endblock head %}
 </head>
 <body>
 	<header>
 		{% block header %}
-		<h1><a href="{{ SITEURL }}/" title="{{ l[lang].banner.title|e }}" ><img alt="{{ l[lang].banner.alt|e }}" src="{{ SITEURL }}/{{ THEME_STATIC_DIR }}/images/banner-logo.png"><span>{{ l[lang].banner.prefix|e }}{{ l[lang].sitename|e }}{{ l[lang].banner.suffix|e }}</span></a></h1>
+		<h1><a href="{{ siteurl }}/{{ l }}/" title="{{ t[l].banner.title|e }}" ><img alt="{{ t[l].banner.alt|e }}" src="{{ siteurl }}/{{ theme.static_dir }}/images/banner-logo.png"><span>{{ t[l].banner.prefix|e }}{{ t[l].sitename|e }}{{ t[l].banner.suffix|e }}</span></a></h1>
 		{% block extra_header %}{% endblock extra_header %}
 		{% endblock header %}
 	</header>
@@ -46,19 +39,15 @@ Hurra!!! lang ist definiert als {{ lang }}.
 		<label for="show-header-menu" class="show-header-menu-bg"> </label>
 		<nav>
 			<ul>
-			{% for item in sc.menuitems -%}
-				{%- if item.category is defined -%}
-					{%- call(nativecat, sccat, _) get.category_by_name(item.category) -%}
-						<li style="--category-color: {{ sccat.color }}"><a href="{{ SITEURL }}/{{ nativecat.url }}">{{ sccat[lang]|e }}</a></li>
-					{%- endcall -%}
-				{%- elif item.tag is defined -%}
-					{%- call(nativetag, sccat, _) get.tag_by_name(item.tag) -%}
-						<li style="--category-color: {{ sccat.color }}"><a href="{{ SITEURL }}/{{ nativetag.url }}">{{ sccat[lang]|e }}</a></li>
+			{% for item in config.menuitems -%}
+				{%- if item.tag is defined -%}
+					{%- call(tagtitle, tagcolor, tagurl, tagpage, tagpages) get.tag_by_name(item.tag, l) -%}
+						<li style="--category-color: {{ tagcolor }}"><a href="{{ siteurl }}/{{ tagurl }}">{{ tagtitle|e }}</a></li>
 					{%- endcall -%}
 				{%- elif item.slug is defined -%}
-					{%- call(aop, _) get.article_or_page_by_slug(item.slug, lang) -%}
-					{%- call(__, sccat, ___) get.category_by_name(aop.category.name, True) -%}
-						<li style="--category-color: {{ sccat.color }}"><a href="{{ SITEURL }}/{{ aop.url }}">{{ aop.title }}</a></li>
+					{%- call(page) get.page_by_slug(item.slug, l) -%}
+					{%- call(cattitle, catcolor, caturl, catpage, catpages) get.tag_by_name(page.category, l) -%}
+						<li style="--category-color: {{ catcolor }}"><a href="{{ siteurl }}/{{ page.url }}">{{ page.title|e }}</a></li>
 					{%- endcall -%}
 					{%- endcall -%}
 				{%- else -%}
diff --git a/theme/templates/category.html b/theme/templates/category.html
deleted file mode 100644
index 0c2b6e0ff9f6a25c9462dc5910bfcccf62a7aaf7..0000000000000000000000000000000000000000
--- a/theme/templates/category.html
+++ /dev/null
@@ -1,7 +0,0 @@
-{% extends "base.html" %}
-{% block title %}{{ l[lang].title_prefix }}{{ l[lang].sitename }}{{ l[lang].title_suffix }} - {{ category }}{%endblock%}
-{% block content %}
-	<section>
-		{{ render.section({"type": "category", "category": category.slug, "num": None}) }}
-	</section>
-{% endblock content %}
diff --git a/theme/templates/index.html b/theme/templates/index.html
deleted file mode 100644
index d4f2b290de95d8c0f5e42938e0348405c51773ec..0000000000000000000000000000000000000000
--- a/theme/templates/index.html
+++ /dev/null
@@ -1,11 +0,0 @@
-{% extends "base.html" %}
-{% block content_title %}{% endblock %}
-
-
-{% block content %}
-{%- for s in sc.startpage -%}
-	<section>
-		{{ render.section(s) }}
-	</section>
-{%- endfor -%}
-{% endblock content %}
diff --git a/theme/templates/macros/README.md b/theme/templates/macros/README.md
deleted file mode 100644
index 75ed3122989219278ca7cac37db4a07f01cc2e7a..0000000000000000000000000000000000000000
--- a/theme/templates/macros/README.md
+++ /dev/null
@@ -1,184 +0,0 @@
-# Macros
-
-[TOC]
-
-## [getters.html](getters.html)
-
-```
-{%- import 'macros/getters.html' as get with context -%}
-```
-
-Mit diesen Macros können Informationen abgefragt werden.
-
-### `category_by_name(catname, ignore_native = False)`
-
-**Argumente:**
-
-- `catname` (String): Der Name der Kategorie.
-- `ignore_native` (Boolean): Setzt den ersten und dritten Rückgabewert zu `None`. (Verkürzt die Laufzeit dieser Funktion.)
-
-**Rückgabewerte:**
-
-1. ([Category](https://gaumi-fginfo.pages.gwdg.de/pelican/_build/html/themes.html#object-author-cat-tag)): Die native Kategorie.
-2. (Dict): Der Eintrag zu der Kategorie aus der config.json-Datei aus dem Inhaltsrepo.
-3. (List\<[Article](https://gaumi-fginfo.pages.gwdg.de/pelican/_build/html/themes.html#article)\>): Die Artikel in der Kategorie.
-
-**Beispiel Aufruf:**
-
-```
-{%- call(nativecat, sccat, catarticles) get.category_by_name(article.category.name) -%}
-	<ul>
-		<li>Kategorie: {{ nativecat.name }}</li>
-		<li>Farbe: {{ sccat.color }}</li>
-		<li>Anzahl Artikel: {{ catarticles|length }}</li>
-	</ul>
-{%- endcall -%}
-```
-
-### `tag_by_name(tagname)`
-
-**Argumente:**
-
-- `catname` (String): Der Name des Tags.
-
-**Rückgabewerte:**
-
-1. ([Tag](https://gaumi-fginfo.pages.gwdg.de/pelican/_build/html/themes.html#object-author-cat-tag)): Der native Tag.
-2. (Dict): Falls der Tag gleichnamig zu einer Kategorie ist, dann der Eintrag zu der Kategorie aus der config.json-Datei aus dem Inhaltsrepo, sonst `None`.
-3. (List\<[Article](https://gaumi-fginfo.pages.gwdg.de/pelican/_build/html/themes.html#article)\>): Die Artikel die diesen Tag haben.
-
-**Beispiel Aufruf:**
-
-```
-{%- call(nativetag, sccat, tagarticles) get.tag_by_name("event") -%}
-	<ul>
-		<li>Tag: {{ nativetag.name }}</li>
-		<li>Farbe: {% if sccat %}{{ sccat.color }}{% else %}Dieser Tag ist nicht gleichnamig zu einer Kategorie und hat deshalb keine Farbe.{% endif %}</li>
-		<li>Anzahl Artikel mit diesem Tag: {{ tagarticles|length }}</li>
-	</ul>
-{%- endcall -%}
-```
-
-
-### `article_by_slug(slug, lang)`
-
-**Argumente:**
-
-- `slug` (String): Der Slug von dem Artikel.
-- `lang` (String): Die Sprache von dem Artikel.
-
-**Rückgabewerte:**
-
-1. ([Article](https://gaumi-fginfo.pages.gwdg.de/pelican/_build/html/themes.html#article)): Der Artikel.
-
-### `page_by_slug(slug, lang)`
-
-**Argumente:**
-
-- `slug` (String): Der Slug von der Seite.
-- `lang` (String): Die Sprache von der Seite.
-
-**Rückgabewerte:**
-
-1. ([Page](https://gaumi-fginfo.pages.gwdg.de/pelican/_build/html/themes.html#page)): Die Seite.
-
-### `article_or_page_by_slug(slug, lang)`
-
-**Argumente:**
-
-- `slug` (String): Der Slug von dem Artikel oder der Seite.
-- `lang` (String): Die Sprache von dem Artikel oder der Seite.
-
-**Rückgabewerte:**
-
-1. ([Article](https://gaumi-fginfo.pages.gwdg.de/pelican/_build/html/themes.html#article) | [Page](https://gaumi-fginfo.pages.gwdg.de/pelican/_build/html/themes.html#page)): Der Artikel oder die Seite.
-2. (String): "article" falls es ein Artikel ist. "page" falls es eine Seite ist.
-
-## [cards.html](cards.html)
-
-```
-{%- import 'macros/cards.html' as cards with context -%}
-```
-
-Mit diesen Macros können Kachelblöcke erstellt werden.
-
-In einem Kachelblock dürfen nur Kacheln sein und sonst nichts!
-
-**Beispiel:**
-
-```
-{{ cards.open() }}
-
-{%- for article in all_articles -%}
-	{{ cards.card_from_article_or_page(article) }}
-{%- endfor -%}
-
-{{ cards.card(title="Dies ist auch eine Kachel", url="https://www.youtube.com/watch?v=dQw4w9WgXcQ", catcolor="green") }}
-
-{{ cards.close() }}
-```
-
-### `open(id = None, classes = None)`
-
-Beginnt einen Kachelblock.
-
-**Argumente:**
-
-- `id` (String): Die ID von dem Kachelblock.
-- `classes` (List\<String\>): Zusätzliche Klassen für den Kachelblock.
-
-### `close()`
-
-Schließt einen Kachelblock.
-
-### `card(title, url, catcolor, escape_title = True, is_url_external = True)`
-
-Erstellt eine Kachel.
-
-**Argumente:**
-
-- `title` (String): Der Titel.
-- `url` (String): Der URL.
-- `catcolor` (String): Die Farbe.
-- `escape_title` (Boolean): Ob `title` escaped werden soll.
-- `is_url_external` (Boolean): Ob `url` auf eine externe Resource zeigt, oder als relativen Link interpretiert werden soll.
-
-### `card_from_article_or_page(aop)`
-
-Erstellt eine Kachel von einem Artikel oder einer Seite.
-
-**Argumente:**
-
-1. `aop` ([Article](https://gaumi-fginfo.pages.gwdg.de/pelican/_build/html/themes.html#article) | [Page](https://gaumi-fginfo.pages.gwdg.de/pelican/_build/html/themes.html#page)): Der Artikel oder die Seite.
-
-
-### `cards_from_articles_or_pages(asops, max = None, standalone = True)`
-
-Erstellt mehrere Kacheln oder einen vollständigen Kachelblock von mehreren Artikeln oder Seiten.
-
-**Argumente:**
-
-1. `asops` (List\<[Article](https://gaumi-fginfo.pages.gwdg.de/pelican/_build/html/themes.html#article) | [Page](https://gaumi-fginfo.pages.gwdg.de/pelican/_build/html/themes.html#page)\>): Die Artikel oder Seiten. Kann auch gemischt sein.
-2. `max` (Int | None): Falls vorhanden werden maximal `max` Kacheln gerendert.
-3. `standalone` (Boolean): Ob `open()` und `close()` automatisch mit ausgeführt werden soll.
-
-**Beispiel Aufruf:**
-
-```
-{{ cards_from_articles_or_pages(all_articles, max=5) }}
-```
-
-## [renderers.html](renderers.html)
-
-```
-{%- import 'macros/renderers.html' as render with context -%}
-```
-
-**TODO: ** Die Dokumentation für diese Macros ist abhängig von der Dokumentation von der config.json-Datei im Inhaltsrepo.
-
-### `section_news(s)`
-### `section_custom(s)`
-### `section_iframe(s)`
-### `section_category(s)`
-### `section_tag(s)`
-### `section(s)`
diff --git a/theme/templates/macros/cards.html b/theme/templates/macros/cards.html
index bea528c91af935481ca7b08df074b8c83a0c8876..2360ed07469635ba2ecd8c8afb39b029938caa0a 100644
--- a/theme/templates/macros/cards.html
+++ b/theme/templates/macros/cards.html
@@ -9,23 +9,23 @@
 	</ul>
 {%- endmacro -%}
 
-{%- macro card(title, url, catcolor, escape_title = True, is_url_external = True) -%}
-	<li style="--category-color: {{ catcolor }}"><a href="{% if not is_url_external %}{{ SITEURL }}/{% endif %}{{ url }}">{% if escape_title %}{{ title|e }}{% else %}{{ title }}{% endif %}</a></li>
+{%- macro card(title, url, catcolor, is_url_external = True) -%}
+	<li style="--category-color: {{ catcolor }}"><a href="{% if not is_url_external %}{{ SITEURL }}/{% endif %}{{ url }}">{{ title|e }}</a></li>
 {%- endmacro -%}
 
-{%- macro card_from_article_or_page(aop) -%}
-	{%- call(_, sccat, __) get.category_by_name(aop.category.name, True) -%}
-		{{ card(title=aop.title, url=aop.url, catcolor=sccat.color, escape_title=False, is_url_external = False) }}
+{%- macro card_from_page(page, lang) -%}
+	{%- call(cattitle, catcolor, caturl, catpage, catpages) get.tag_by_name(page.category, l) -%}
+		{{ card(title=page.title, url=page.url, catcolor=catcolor, is_url_external = False) }}
 	{%- endcall -%}
 {%- endmacro -%}
 
-{%- macro cards_from_articles_or_pages(asops, max = None, standalone = True) -%}
+{%- macro cards_from_pages(pages, lang, max = None, standalone = True) -%}
 	{%- if standalone -%}
 		{{ open() }}
 	{%- endif -%}
-	{%- for aop in asops -%}
+	{%- for page in pages -%}
 		{%- if max == None or loop.index < max -%}
-			{{ cards.card_from_article_or_page(aop) }}
+			{{ cards.card_from_page(page) }}
 		{%- endif -%}
 	{%- endfor -%}
 	{%- if standalone -%}
diff --git a/theme/templates/macros/getters.html b/theme/templates/macros/getters.html
index 1c1088d4e26ed72f34c271193e9883acf3d3a986..1684d18c4e3a3e1bb601d94e0d230368a19db20c 100644
--- a/theme/templates/macros/getters.html
+++ b/theme/templates/macros/getters.html
@@ -1,53 +1,26 @@
-{%- macro category_by_name(catname, ignore_native = False) -%}
-	{%- if ignore_native -%}
-		{{- caller(None, sc.categories[catname], None) -}}
-	{%- else -%}
-		{%- for nativecat, catarticles in categories -%}
-			{%- if nativecat.name == catname -%}
-				{{- caller(nativecat, sc.categories[catname], catarticles) -}}
-			{%- endif -%}
-		{%- endfor -%}
-	{%- endif -%}
-{%- endmacro -%}
+{%- macro tag_by_name(tagname, lang) -%}
+	{%- set ns = namespace(tagpage=None, tagcolor=config.theme.default_tag_color, tagtitle=tagname) -%}
+	{%- set tagpageslug = ["tag", tagname]|join('-') -%}
 
-{%- macro tag_by_name(tagname) -%}
-	{%- for nativetag, tagarticles in tags -%}
-		{%- if nativetag.name == tagname -%}
-			{%- if tagname in sc.categories -%}
-				{{- caller(nativetag, sc.categories[tagname], tagarticles) -}}
-			{%- else -%}
-				{{- caller(nativetag, None, tagarticles) -}}
-			{%- endif -%}
-		{%- endif -%}
-	{%- endfor -%}
+	{%- if tagpageslug in pages[lang] -%}
+		{%- set ns.tagpage = pages[lang][tagpageslug] -%}
+		{%- set ns.tagtitle = ns.tagpage.title -%}
+		{%- call(tagcolor) metadata_entry(tagpageslug, lang, 'color') -%}
+			{%- set ns.tagcolor = tagcolor -%}
+		{%- endcall -%}
+	{%- endif -%}
+	
+	{{- caller(ns.tagtitle, ns.tagcolor, [lang, 'tag', tagname + '.html']|join('/') , ns.tagpage, tags[lang][tagname]) -}}
 {%- endmacro -%}
 
-{%- macro article_by_slug(slug, lang) -%}
-	{%- for a in all_articles -%}
-		{%- if a.slug == slug and a.lang == lang -%}
-			{{- caller(a) -}}
-		{%- endif -%}
-	{%- endfor -%}
+{%- macro metadata_entry(slug, lang, key) -%}
+	{%- if key in pages[lang][slug].metadata -%}
+		{{- caller(pages[lang][slug].metadata[key]) -}}
+	{%- elif key in pages[t.default][slug].metadata -%}
+		{{- caller(pages[t.default][slug].metadata[key]) -}}
+	{%- endif -%}
 {%- endmacro -%}
 
 {%- macro page_by_slug(slug, lang) -%}
-	{%- for p in pages -%}
-		{%- if p.slug == slug and p.lang == lang -%}
-			{{- caller(p) -}}
-		{%- endif -%}
-	{%- endfor -%}
+	{{- caller(pages[lang][slug]) -}}
 {%- endmacro -%}
-
-{%- macro article_or_page_by_slug(slug, lang) -%}
-	{%- for a in all_articles -%}
-		{%- if a.slug == slug and a.lang == lang -%}
-			{{- caller(a, "article") -}}
-		{%- endif -%}
-	{%- endfor -%}
-	{%- for p in pages -%}
-		{%- if p.slug == slug and p.lang == lang -%}
-			{{- caller(p, "page") -}}
-		{%- endif -%}
-	{%- endfor -%}
-{%- endmacro -%}
-
diff --git a/theme/templates/macros/renderers.html b/theme/templates/macros/renderers.html
index 698dbcccbd9ad24f5a77b13e2687ebff69bce05d..56351467715cfd0882aceb9394b91c9f9936dc82 100644
--- a/theme/templates/macros/renderers.html
+++ b/theme/templates/macros/renderers.html
@@ -1,11 +1,11 @@
 {%- import 'macros/getters.html' as get with context -%}
 {%- import 'macros/cards.html' as cards with context -%}
 
-{%- macro section_news(s) -%}
-	{{ cards.cards_from_articles_or_pages(all_articles, max=s.num) }}
+{%- macro section_news(s, lang) -%}
+	{{ cards.cards_from_pages(pages_modified, max=s.num) }}
 {%- endmacro -%}
 
-{%- macro section_custom(s) -%}
+{%- macro section_custom(s, lang) -%}
 	{{ cards.open() }}
 	{%- for c in s.content -%}
 		{{ cards.card(title=c[lang], url=c.url, catcolor=c.color) }}
@@ -13,59 +13,50 @@
 	{{ cards.close() }}
 {%- endmacro -%}
 
-{%- macro section_iframe(s) -%}
+{%- macro section_iframe(s, lang) -%}
 	<iframe src="{{ s.url }}"></iframe>
 {%- endmacro -%}
 
-{%- macro section_category(s) -%}
-	{%- call(nativecat, sccat, catarticles) get.category_by_name(s.category) -%}
-	{%- if s.title is not defined -%}
-		<header>
-			<h2 {% if s.id is defined -%}id="{{ s.id }}"{%- endif %}>{{ sccat[lang]|e }}</h2>
-		</header>
-	{%- endif -%}
-	{{ cards.cards_from_articles_or_pages(catarticles, max=s.num) }}
-	{%- endcall -%}
-{%- endmacro -%}
-
-{%- macro section_tag(s) -%}
-	{%- call(nativetag, sccat, tagarticles) get.tag_by_name(s.tag) -%}
-	{%- if s.title is not defined -%}
-		<header>
-			<h2 {% if s.id is defined -%}id="{{ s.id }}"{%- endif %}>
-				{%- if sccat -%}
-					{{- sccat[lang]|e -}}
-				{%- else -%}
-					{{- s.tag|e -}}
-				{%- endif -%}
-			</h2>
-		</header>
-	{%- endif -%}
-	{{ cards.cards_from_articles_or_pages(tagarticles, max=s.num) }}
-
-
+{%- macro section_tag(s, lang) -%}
+	{%- call(tagtitle, tagcolor, tagurl, tagpage, tagpages) get.tag_by_name(s.tag, lang) -%}
+		{%- if s.title is not defined -%}
+			<header>
+				<h2 {% if s.id is defined -%}id="{{ s.id }}"{%- endif %}>{{ tagtitle|e }}</h2>
+			</header>
+		{%- endif -%}
+		{{ cards.cards_from_pages(tagpages, lang, max=s.num) }}
+		
+		{%- if tagpage -%}
+			{{ tagpage.content }}
+		{%- endif -%}
 	{%- endcall -%}
 {%- endmacro -%}
 
-{%- macro section(s) -%}
+{%- macro section(s, lang) -%}
 	{%- if s.title is defined -%}
 		<header>
 			<h2 {% if s.id is defined -%}id="{{ s.id }}"{%- endif %}>{{ s.title[lang]|e }}</h2>
 		</header>
 	{%- endif -%}
 	{%- if s.type == "news" -%}
-		{{ section_news(s) }}
+		{{ section_news(s, lang) }}
 	{%- elif s.type == "iframe" -%}
-		{{ section_iframe(s) }}
+		{{ section_iframe(s, lang) }}
 	{%- elif s.type == "custom" -%}
-		{{ section_custom(s) }}
-	{%- elif s.type == "category" -%}
-		{{ section_category(s) }}
+		{{ section_custom(s, lang) }}
 	{%- elif s.type == "tag" -%}
-		{{ section_tag(s) }}
+		{{ section_tag(s, lang) }}
 	{%- else -%}
 	<br />
 	<strong>ERROR: render.section: Unknown section type: {{ s.type|e }}</strong><br />
 	<br />
 	{%- endif -%}
 {%- endmacro -%}
+
+{%- macro sections(sl, lang) -%}
+	{%- for s in sl -%}
+	<section>
+		{{ section(s, lang) }}
+	</section>
+	{%- endfor -%}
+{%- endmacro -%}
diff --git a/theme/templates/page.html b/theme/templates/page.html
index af4e389ee2929cd5d3155aac3a7f8cd914899e2f..fdbef287039e822a368a5f6e0702340bd424aaaa 100644
--- a/theme/templates/page.html
+++ b/theme/templates/page.html
@@ -1,8 +1,8 @@
 {% extends "base.html" %}
 
 {%- macro page_translation_link(translation, is_current=False) -%}
-<a href="{{ SITEURL }}/{{ translation.url }}" hreflang="{{ translation.lang }}" title="{{ l[translation.lang].langname|e }}">
-	<img alt="{{ l[translation.lang].langname|e }}" src="{{ SITEURL }}/{{ THEME_STATIC_DIR }}/images/flags/{{ translation.lang }}.svg">
+<a href="{{ siteurl }}/{{ translation.url }}" hreflang="{{ translation.lang }}" title="{{ l[translation.lang].langname|e }}">
+	<img alt="{{ l[translation.lang].langname|e }}" src="{{ siteurl }}/{{ THEME_STATIC_DIR }}/images/flags/{{ translation.lang }}.svg">
 </a>
 {%- endmacro -%}
 
@@ -12,7 +12,7 @@
 
 {% block extra_head %}
 	{%- for translation in page.translations -%}
-		<link rel="alternate" hreflang="{{ translation.lang }}" href="{{ SITEURL }}/{{ translation.url }}">
+		<link rel="alternate" hreflang="{{ translation.lang }}" href="{{ siteurl }}/{{ translation.url }}">
 	{%- endfor -%}
 
 	{% if page.summary %}
@@ -25,35 +25,41 @@
 		<header>
 			<h1>{{ page.title }}</h1>
 		</header>
+		{%- call(s) get.metadata_entry(page.slug, l, 'before') -%}
+			{{ render.sections(s, l) }}
+		{%- endcall -%}
 		{{ page.content }}
+		{%- call(s) get.metadata_entry(page.slug, l, 'after') -%}
+			{{ render.sections(s, l) }}
+		{%- endcall -%}
 		<footer>
 			{%- if page.authors -%}
 			<address>
-				{{ l[lang].page.authors_prefix }}
+				{{ t[l].page.authors_prefix }}
 				{% for author in page.authors %}
-					<a href="{{ SITEURL }}/{{ author.url }}">{{ author }}</a>
+					<a href="{{ siteurl }}/{{ author.url }}">{{ author }}</a>
 				{% endfor %}
-				{{ l[lang].page.authors_suffix }}
+				{{ t[l].page.authors_suffix }}
 			</address>
 			{%- endif -%}
 				
-			<span>{{ l[lang].page.published_prefix }}
-			<abbr title="{{ page.date.isoformat() }}">
-				{{ page.locale_date }}
-			</abbr>{{ l[lang].page.published_suffix }}</span>
+			<span>{{ t[l].page.published_prefix }}
+			<abbr title="{{ page.date_created.isoformat() }}">
+				{{ page.date_created.isoformat() }}
+			</abbr>{{ t[l].page.published_suffix }}</span>
 
 			{%- if page.modified -%}
 			<br />
-			<span>{{ l[lang].page.modified_prefix }}
+			<span>{{ t[l].page.modified_prefix }}
 			<abbr title="{{ page.modified.isoformat() }}">{{ page.locale_modified }}</abbr>
-			{{ l[lang].page.modified_suffix }}</span>
+			{{ t[l].page.modified_suffix }}</span>
 			{%- endif -%}
 
 			<br />
-			<span>{{ l[lang].page.category_prefix }}<a href="{{ SITEURL }}/{{ page.category.url }}">{{ page.category }}</a>{{ l[lang].page.category_suffix }}</span>
+			{#<span>{{ t[l].page.category_prefix }}<a href="{{ siteurl }}/{{ page.category.url }}">{{ page.category }}</a>{{ t[l].page.category_suffix }}</span>#}
 			<br />
-			<span>{{ l[lang].page.languages_prefix }}</span>
-			<ul class="languages">
+			<span>{{ t[l].page.languages_prefix }}</span>
+			{#<ul class="languages">
 			{%- for translation in page.translations -%}
 				<li>
 					{{ page_translation_link(translation) }}
@@ -62,8 +68,8 @@
 				<li>
 					{{ page_translation_link(page, True) }}
 				</li>
-			</ul>
-			{{ l[lang].page.languages_suffix }}
+			</ul>#}
+			{{ t[l].page.languages_suffix }}
 		</footer>
 	</article>
 {% endblock %}
diff --git a/theme/templates/tag.html b/theme/templates/tag.html
index 6a636023257c22e38fb88b74bb4d631af4718362..3c0cca1b0d16799bf01dacf967c4f2c742ed4ecd 100644
--- a/theme/templates/tag.html
+++ b/theme/templates/tag.html
@@ -1,7 +1,12 @@
 {% extends "base.html" %}
-{% block title %}{{ l[lang].title_prefix }}{{ l[lang].sitename }}{{ l[lang].title_suffix }} - {{ tag }}{%endblock%}
+{% block title -%}
+	{%- call(tagtitle, tagcolor, tagurl, tagpage, tagpages) get.tag_by_name(tag, l) -%}
+		{{ t[l].title_prefix }}{{ t[l].sitename }}{{ t[l].title_suffix }} - {{ tagtitle|e }}
+	{%- endcall -%}
+{%- endblock -%}
+
 {% block content %}
 	<section>
-		{{ render.section({"type": "tag", "tag": tag.slug, "num": None}) }}
+		{{ render.section({"type": "tag", "tag": tag, "num": None}, l) }}
 	</section>
 {% endblock content %}