pelican-subtle-giggi: update release v1
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..cc1ec0b
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,143 @@
+PY?=python3
+PELICAN?=pelican
+PELICANOPTS=
+
+BASEDIR=$(CURDIR)
+INPUTDIR=$(BASEDIR)/content
+OUTPUTDIR=$(BASEDIR)/output
+CONFFILE=$(BASEDIR)/pelicanconf.py
+PUBLISHCONF=$(BASEDIR)/publishconf.py
+
+FTP_HOST=localhost
+FTP_USER=anonymous
+FTP_TARGET_DIR=/
+
+SSH_HOST=localhost
+SSH_PORT=22
+SSH_USER=root
+SSH_TARGET_DIR=/var/www
+
+S3_BUCKET=my_s3_bucket
+
+CLOUDFILES_USERNAME=my_rackspace_username
+CLOUDFILES_API_KEY=my_rackspace_api_key
+CLOUDFILES_CONTAINER=my_cloudfiles_container
+
+DROPBOX_DIR=~/Dropbox/Public/
+
+GITHUB_PAGES_BRANCH=gh-pages
+
+INSTALLDIR=
+UID="www-data"
+GID="www-data"
+MOD="664"
+
+DEBUG ?= 0
+ifeq ($(DEBUG), 1)
+	PELICANOPTS += -D
+endif
+
+RELATIVE ?= 0
+ifeq ($(RELATIVE), 1)
+	PELICANOPTS += --relative-urls
+endif
+
+help:
+	@echo 'Makefile for a pelican Web site                                           '
+	@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]          start/restart develop_server.sh    '
+	@echo '   make stopserver                     stop local server                  '
+	@echo '   make ssh_upload                     upload the web site via SSH        '
+	@echo '   make rsync_upload                   upload the web site via rsync+ssh  '
+	@echo '   make dropbox_upload                 upload the web site via Dropbox    '
+	@echo '   make ftp_upload                     upload the web site via FTP        '
+	@echo '   make s3_upload                      upload the web site via S3         '
+	@echo '   make cf_upload                      upload the web site via Cloud Files'
+	@echo '   make github                         upload the web site via gh-pages   '
+	@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:
+ifdef PORT
+	cd $(OUTPUTDIR) && $(PY) -m pelican.server $(PORT)
+else
+	cd $(OUTPUTDIR) && $(PY) -m pelican.server
+endif
+
+serve-global:
+ifdef SERVER
+	cd $(OUTPUTDIR) && $(PY) -m pelican.server 80 $(SERVER)
+else
+	cd $(OUTPUTDIR) && $(PY) -m pelican.server 80 0.0.0.0
+endif
+
+
+devserver:
+ifdef PORT
+	$(BASEDIR)/develop_server.sh restart $(PORT)
+else
+	$(BASEDIR)/develop_server.sh restart
+endif
+
+stopserver:
+	$(BASEDIR)/develop_server.sh stop
+	@echo 'Stopped Pelican and SimpleHTTPServer processes running in background.'
+
+publish:
+	$(PELICAN) $(INPUTDIR) -o $(OUTPUTDIR) -s $(PUBLISHCONF) $(PELICANOPTS)
+
+ssh_upload: publish
+	scp -P $(SSH_PORT) -r $(OUTPUTDIR)/* $(SSH_USER)@$(SSH_HOST):$(SSH_TARGET_DIR)
+
+rsync_upload: publish
+	rsync -e "ssh -p $(SSH_PORT)" -P -rvzc --delete $(OUTPUTDIR)/ $(SSH_USER)@$(SSH_HOST):$(SSH_TARGET_DIR) --cvs-exclude
+
+dropbox_upload: publish
+	cp -r $(OUTPUTDIR)/* $(DROPBOX_DIR)
+
+ftp_upload: publish
+	lftp ftp://$(FTP_USER)@$(FTP_HOST) -e "mirror -R $(OUTPUTDIR) $(FTP_TARGET_DIR) ; quit"
+
+s3_upload: publish
+	s3cmd sync $(OUTPUTDIR)/ s3://$(S3_BUCKET) --acl-public --delete-removed --guess-mime-type --no-mime-magic --no-preserve
+
+cf_upload: publish
+	cd $(OUTPUTDIR) && swift -v -A https://auth.api.rackspacecloud.com/v1.0 -U $(CLOUDFILES_USERNAME) -K $(CLOUDFILES_API_KEY) upload -c $(CLOUDFILES_CONTAINER) .
+
+github: publish
+	ghp-import -m "Generate Pelican site" -b $(GITHUB_PAGES_BRANCH) $(OUTPUTDIR)
+	git push origin $(GITHUB_PAGES_BRANCH)
+
+install:
+	@command [ -z "$(INSTALLDIR)" ] && echo "INSTALLDIR unset. i.e. make install INSTALLDIR=\"/path/to/dir\"" && exit 1 ||:
+	@command [ ! -d $(OUTPUTDIR) ] && echo "OUTPUTDIR unset. i.e. make html" && exit 1 ||:
+	@echo "Copying $(OUTPUTDIR) to $(INSTALLDIR)"
+	@command sudo cp -ra $(OUTPUTDIR)/* $(INSTALLDIR) ||:
+	@echo "Setting chown to $(UID):$(GID)"
+	@command sudo chown -R $(UID):$(GID) $(INSTALLDIR) ||:
+	@echo "Setting chmod $(MOD) to files"
+	@command find $(INSTALLDIR) -type f | sudo xargs -i{} chmod $(MOD) {} ||:
+	@echo "Installed files:"
+	@command find $(INSTALLDIR) -type f ||:
+	@echo "Done."
+
+
+.PHONY: html help clean regenerate serve serve-global devserver stopserver publish ssh_upload rsync_upload dropbox_upload ftp_upload s3_upload cf_upload github install
diff --git a/content/pages/about.md b/content/pages/about.md
new file mode 100644
index 0000000..74d7bea
--- /dev/null
+++ b/content/pages/about.md
@@ -0,0 +1,3 @@
+title: About
+date: 01.01.01
+
diff --git a/content/pages/ftp.md b/content/pages/ftp.md
new file mode 100644
index 0000000..72f3fe9
--- /dev/null
+++ b/content/pages/ftp.md
@@ -0,0 +1,3 @@
+title: Ftp
+
+
diff --git a/content/pages/git.md b/content/pages/git.md
new file mode 100644
index 0000000..5b1329c
--- /dev/null
+++ b/content/pages/git.md
@@ -0,0 +1,17 @@
+title: Git
+
+### Avrdude-6.3-fork
+
+[Public AVRDUDE fork](https://bitbucket.org/luigi_s/avrdude-6.3-fork/src/master/ "Avrdude-6.3")
+
+### Grbl-v1.1h-fork
+
+[Public GRBL fork](https://bitbucket.org/luigi_s/grbl-v1.1h-fork/src/master/ "Grbl-v1.1h")
+
+### Emacs init.el
+
+[Public EMACS init](https://bitbucket.org/luigi_s/emacstore/src/master/ "Emacstore")
+
+### Pattigrass
+
+[Private PATTIGRASS repo](https://bitbucket.org/luigi_s/pattigrass/src/master/ "Pattigrass")
diff --git a/content/pages/home.md b/content/pages/home.md
new file mode 100644
index 0000000..9c64a66
--- /dev/null
+++ b/content/pages/home.md
@@ -0,0 +1,20 @@
+title: 
+date: 09.02.2019
+slug: static-home-page
+URL:
+save_as: index.html
+
+
+*subject*: some   [basics](https://theweek.com/articles/462065/brief-history-yippeekiyay)
+first.
+
+Hello,
+
+As much as obsolete and unlike its  original author, I still love the concept of
+[slow web](https://jackcheng.com/the-slow-web/).   Hence, if we know  each other
+you maybe can access  already what you are looking for  here. Otherwise, drop me
+an email [^1] and you maybe will.
+
+Thanks, see you.
+
+[^1]: Italian, English and I'd love to practise my Spanish.
diff --git a/content/pages/invite.md b/content/pages/invite.md
new file mode 100644
index 0000000..0d8d347
--- /dev/null
+++ b/content/pages/invite.md
@@ -0,0 +1,6 @@
+title: Invite
+date: 14.02.2019
+Template: invite
+
+Fill up this form and send it will request an account on giggi.me. It will have
+then to be authorized and activated, you will get an email when that happens.
diff --git a/content/pages/login.md b/content/pages/login.md
new file mode 100644
index 0000000..2a3adb9
--- /dev/null
+++ b/content/pages/login.md
@@ -0,0 +1,6 @@
+title: Login
+date: 14.02.2019
+Template: login
+
+Welcome, goto to invite if you'd like an account.
+
diff --git a/content/pages/mail.md b/content/pages/mail.md
new file mode 100644
index 0000000..52b158c
--- /dev/null
+++ b/content/pages/mail.md
@@ -0,0 +1,2 @@
+title: Mail
+
diff --git a/content/posts/making-giggi-me.md b/content/posts/making-giggi-me.md
new file mode 100644
index 0000000..697bb82
--- /dev/null
+++ b/content/posts/making-giggi-me.md
@@ -0,0 +1,18 @@
+title: Giggi.me instructions.
+subtitle: Sharing how to bring quickly a static site up.
+date: 02.10.19
+summary: *Some brief notes on how Giggi.me was made*
+slug: making-giggi-me
+no: 5
+
+This website is based on [Pelican](https://github.com/getpelican/pelican), which
+uses python  for generating  the final  html output.   Pelican supports  lots of
+themes and tweaks, below are available useful links and some changes made on top
+of it.
+
+1. [Jinja docs](http://jinja.pocoo.org/docs/2.10/templates/#sort)
+2. [Pelican 3.6.3 docs](http://docs.getpelican.com/en/3.6.3/content.html)
+3. [Pelican, using a static page as home](http://docs.getpelican.com/en/3.6.3/faq.html?highlight=faq#how-can-i-use-a-static-page-as-my-home-page)
+4. [Markdown docs](https://markdown-guide.readthedocs.io/en/latest/)
+5. [Markdown docs](https://daringfireball.net/projects/markdown/syntax)
+
diff --git a/content/posts/meet-patti.md b/content/posts/meet-patti.md
new file mode 100644
index 0000000..07acffc
--- /dev/null
+++ b/content/posts/meet-patti.md
@@ -0,0 +1,37 @@
+title: Demo Patti moves its first steps
+subtitle: Epic vise for iconic granddad
+date: 09.12.2019
+summary: *Patti baby steps*
+slug: meet-patti
+no: 10
+
+Here is a quick view of the very first test. It was to check whether the frame
+properly aligned worm screw and gears. The reduction ratio in place is 100:1.
+I am driving this with a bare metal RC transmitter/receiver, no MCU at all.
+
+<script src="http://vjs.zencdn.net/4.0/video.js"></script>
+<video id="patti-video-1" class="video-js vjs-default-skin" controls
+preload="auto" width="384" height="683" poster="https://www.giggi.me/videos/demo-patti/videos-1.png" 
+data-setup="{}">
+<source src="https://www.giggi.me/videos/demo-patti/video-1.mp4" type='video/mp4'>
+</video>
+
+Here is with the frame fully assembled. I am driving it using Firefox running on an
+Android tablet. Patti is powered by a Pic32 with a 6LowPan WiFi on-board.
+
+<script src="http://vjs.zencdn.net/4.0/video.js"></script>
+<video id="patti-video-2" class="video-js vjs-default-skin" controls
+preload="auto" width="384" height="683" poster="https://www.giggi.me/videos/demo-patti/videos-2.png" 
+data-setup="{}">
+<source src="https://www.giggi.me/videos/demo-patti/video-2.mp4" type='video/mp4'>
+</video>
+
+Here it's also carrying a brush cutter, which makes it weight about 30 kilos. It's
+driven from Chrome running on a laptop.
+
+<script src="http://vjs.zencdn.net/4.0/video.js"></script>
+<video id="patti-video-3" class="video-js vjs-default-skin" controls
+preload="auto" width="384" height="683" poster="https://www.giggi.me/videos/demo-patti/videos-3.png" 
+data-setup="{}">
+<source src="https://www.giggi.me/videos/demo-patti/video-3.mp4" type='video/mp4'>
+</video>
diff --git a/content/posts/pattigrass-ci40.md b/content/posts/pattigrass-ci40.md
new file mode 100644
index 0000000..c7efe34
--- /dev/null
+++ b/content/posts/pattigrass-ci40.md
@@ -0,0 +1,10 @@
+category: PattiGrass
+title: 6LowPan server from Ci40 and OpenWRT
+subtitle: There's no help out there
+summary: *Overview of how to setup the Ci40 for Pattigrass*
+slug: pattigrass-ci40
+date: 02.10.19
+no: 2
+
+PattiGrass needs  a server, meaning somebody  who's in charge and  tells what to
+do. Let us see how Imagination's Ci40 does about that.
diff --git a/content/posts/pattigrass-general.md b/content/posts/pattigrass-general.md
new file mode 100644
index 0000000..1febaa2
--- /dev/null
+++ b/content/posts/pattigrass-general.md
@@ -0,0 +1,19 @@
+title: PattiGrass
+subtitle: How everything fits together
+date: 02.10.19
+summary: *An overview of PattiGrass*
+slug: pattigrass-general
+no: 1
+
+Definition attempt as follows:
+
+> PattiGrass is an excuse for getting  several things to work together. It comes
+> in the final form of a *lawn mower* and a GUI.
+
+Long  story short,  this project  started _officially_  somewhere about  October
+2017, or at least so it seems to be dated its initial commit.
+
+This page is a general bucket, meaning will try to put together things unrelated
+to any specific  component of PattiGrass.  Hopefully I will  get myself to write
+something down at some point.
+
diff --git a/content/posts/pattigrass-pic32.md b/content/posts/pattigrass-pic32.md
new file mode 100644
index 0000000..57f79fb
--- /dev/null
+++ b/content/posts/pattigrass-pic32.md
@@ -0,0 +1,8 @@
+title: pic32 as onboard MCU
+subtitle: 6LowPan Clicker by Michrochip(r)
+date: 02.10.19
+summary: *Onboard MCU - pic32*
+slug: pattigrass-pic32
+no: 3
+
+PattiGrass needs an onboard MCU. Let us see how pic32 does about it.
diff --git a/content/posts/pattigrass-webui.md b/content/posts/pattigrass-webui.md
new file mode 100644
index 0000000..645160e
--- /dev/null
+++ b/content/posts/pattigrass-webui.md
@@ -0,0 +1,10 @@
+title: Graphics UI, take the wheels.
+subtitle: Web browser for a responsive GUI
+date: 02.10.19
+summary: *GUI web based for Patti*
+slug: pattigrass-webui
+no: 4
+
+PattiGrass needs  an user  interface. It  doesn't need to  be a  *graphics* user
+interface, it can though. What follows is  about how that is achieved with a web
+browser.
diff --git a/content/posts/routing-giggi-me.md b/content/posts/routing-giggi-me.md
new file mode 100644
index 0000000..e6a8c6d
--- /dev/null
+++ b/content/posts/routing-giggi-me.md
@@ -0,0 +1,89 @@
+title: Routing hell with Apache and CodeIgniter
+subtitle: When it does not really make sense
+date: 22.06.2019
+summary: *Some notes on to get it to work*
+slug: routing-giggi-me
+no: 6
+
+Taken by the frustration of a  ridicolous amount of time wasted in understanding
+why this thing did not want to run  as expected I will write down how finally it
+did it.
+
+Admittedly I do not  know anything about Apache, PHP and  CodeIgniter as well as
+any viable alternative to them. However, I needed some tool for hosting personal
+contents and I gave them a go.
+
+The goal was to  get something extremely simple running in  a robust manner. I'd
+like to:
+
+1. display friendly URLs
+2. do **not** use .htaccess files
+3. only use virtual hosts
+4. only use rewrite rules as URL manipulation mechanism
+
+The first point above translates in CodeIgniter with routing and removing
+index.php from the requested URI.  Google '*hide index.php CodeIgniter*' to get
+a flavour of it. Once you have done that carry on reading this.
+
+From the official docs[^1] this is what you've got to do:
+
+```
+RewriteEngine On
+RewriteCond %{REQUEST_FILENAME} !-f
+RewriteCond %{REQUEST_FILENAME} !-d
+RewriteRule ^(.*)$ index.php/$1 [L]
+```
+
+It assumes you are using a .htaccess file, it also assumes you are willing to
+scatter dot-hidden-files across the file system, which is also discouraged by
+Apache folks[^2].
+
+I don't want to use it, I'll move it to the vhost. In order to get it right
+though there are a few caveats to be noted. .htaccess lives in a *directory
+context*, so make sure this will be replicated in the vhost, as:
+
+```
+<Directory "/var/www/html">
+	Require all granted # This assumes that /var/www/html can be safely accessed
+</Directory>
+```
+
+Then, RewriteEngine & Co. can be added, but one more thing must be clarified:
+**DO NOT REDIRECT**.  This means, make sure `R` isn't one of the flags you are
+passing to the rewrite rule. This is because you want the rewrite engine to be
+silently manipulating the requested URI server side only, definitely you do not
+want the client to send a new request that includes `index.php/whatever`.
+
+```
+<Directory "/var/www/html">
+	Require all granted
+	RewriteEngine On
+	RewriteCond %{REQUEST_FILENAME} !-f
+	RewriteCond %{REQUEST_FILENAME} !-d
+	RewriteRule ^(.*)$ index.php/$1 [L]
+</Directory>
+```
+
+Now you can tell CodeIgniter not to expect any prefix before the controller is
+invoked, in `config/config.php` remove `index.php`:
+
+```
+- $config['index_page'] = 'index.php';
++ $config['index_page'] = '';
+```
+
+Last thing to do is to set up some route in order to map a specific requested
+token to the right controller for your case. This is done in `config/routes.php`
+as follows[^3]:
+
+```
+$route['blog/joe'] = 'blogs/users/34';
+$route['product/(:any)'] = 'catalog/product_lookup';
+$route['product/(:num)'] = 'catalog/product_lookup_by_id/$1';
+```
+
+HTH
+
+[^1]: [Removing the index.php file](https://www.codeigniter.com/user_guide/general/urls.html#removing-the-index-php-file)
+[^2]: [Apache htaccess files](https://httpd.apache.org/docs/2.4/howto/htaccess.html#page-header)
+[^3]: [CodeIgniter routing](https://www.codeigniter.com/user_guide/general/routing.html)
diff --git a/content/posts/shocks-abs.md b/content/posts/shocks-abs.md
new file mode 100644
index 0000000..84f1454
--- /dev/null
+++ b/content/posts/shocks-abs.md
@@ -0,0 +1,32 @@
+title: Patti shock absorbers
+subtitle: To cut threads on lathe, you must become the lathe
+date: 9.09.2019
+summary: *Working on Patti suspension system*
+slug: patti-shock-abs
+gallery: {photo}shockabs{Assembly parts}
+no: 8
+
+Christmas time, time to wrap Patti's shock absorbers up. Fortunately I had to
+split the work across two holidays, so I did some thinking in the
+meantime. Pretty happy with how they came up.
+
+They allow some tweaking, there is an adjustable pre-load screw and the weight
+force on the spring coils[^1] can be increased or decreased by varying the
+distance that the unit has from the pivot point - overall this mechanism is
+exactly like a nutcracker.
+
+Anyone will notice there is no hydraulics involved. For the time being I did not
+want to over engineer the design, besides there is no real need yet for an
+actual absorbing action. However this might change in future.
+
+I also had to split the rear block into two independent units, including wheel,
+motor and assembly parts.
+
+Pictures do not show batteries, battery trays and electronics box, I still got
+to figure out how to place all of it. They used to sit in the middle of the old
+monolithic rear block.
+
+Material utilised are music wire (for the spring coils), aluminium, brass, then
+bearing, nuts and bolts.
+
+[^1]: [Custom spring retailer](https://www.compressionspring.com/pc169-1156-21000-mw-7250-cg-n-in.html?unit_measure=me)
diff --git a/content/posts/vise-restore.md b/content/posts/vise-restore.md
new file mode 100644
index 0000000..b86f09b
--- /dev/null
+++ b/content/posts/vise-restore.md
@@ -0,0 +1,17 @@
+title: 1954 Stahl-Titan vise restoration
+subtitle: Epic vise for iconic granddad
+date: 29.08.2019
+summary: *A visaul story-telling*
+slug: vise-restoration
+gallery: {photo}visesbs{Side by side}, {photo}vise{All of them}
+no: 7
+
+This year my granddad turned 90 and I loved to bring this iconic item of my
+childhood back to shine. Youtube was my main source of inspiration, must say,
+*Hand tool rescue* is a great channel and those guys really know what they are
+doing.
+
+I won't be verbose, I am not a professional, only an enthusiast enjoying
+spending his time in the garage.
+
+I plan to improve this article, time permitting.
diff --git a/fabfile.py b/fabfile.py
new file mode 100644
index 0000000..b3a0222
--- /dev/null
+++ b/fabfile.py
@@ -0,0 +1,92 @@
+from fabric.api import *
+import fabric.contrib.project as project
+import os
+import shutil
+import sys
+import SocketServer
+
+from pelican.server import ComplexHTTPRequestHandler
+
+# Local path configuration (can be absolute or relative to fabfile)
+env.deploy_path = 'output'
+DEPLOY_PATH = env.deploy_path
+
+# Remote server configuration
+production = 'root@localhost:22'
+dest_path = '/var/www'
+
+# Rackspace Cloud Files configuration settings
+env.cloudfiles_username = 'my_rackspace_username'
+env.cloudfiles_api_key = 'my_rackspace_api_key'
+env.cloudfiles_container = 'my_cloudfiles_container'
+
+# Github Pages configuration
+env.github_pages_branch = "gh-pages"
+
+# Port for `serve`
+PORT = 8000
+
+def clean():
+    """Remove generated files"""
+    if os.path.isdir(DEPLOY_PATH):
+        shutil.rmtree(DEPLOY_PATH)
+        os.makedirs(DEPLOY_PATH)
+
+def build():
+    """Build local version of site"""
+    local('pelican -s pelicanconf.py')
+
+def rebuild():
+    """`build` with the delete switch"""
+    local('pelican -d -s pelicanconf.py')
+
+def regenerate():
+    """Automatically regenerate site upon file modification"""
+    local('pelican -r -s pelicanconf.py')
+
+def serve():
+    """Serve site at http://localhost:8000/"""
+    os.chdir(env.deploy_path)
+
+    class AddressReuseTCPServer(SocketServer.TCPServer):
+        allow_reuse_address = True
+
+    server = AddressReuseTCPServer(('', PORT), ComplexHTTPRequestHandler)
+
+    sys.stderr.write('Serving on port {0} ...\n'.format(PORT))
+    server.serve_forever()
+
+def reserve():
+    """`build`, then `serve`"""
+    build()
+    serve()
+
+def preview():
+    """Build production version of site"""
+    local('pelican -s publishconf.py')
+
+def cf_upload():
+    """Publish to Rackspace Cloud Files"""
+    rebuild()
+    with lcd(DEPLOY_PATH):
+        local('swift -v -A https://auth.api.rackspacecloud.com/v1.0 '
+              '-U {cloudfiles_username} '
+              '-K {cloudfiles_api_key} '
+              'upload -c {cloudfiles_container} .'.format(**env))
+
+@hosts(production)
+def publish():
+    """Publish to production via rsync"""
+    local('pelican -s publishconf.py')
+    project.rsync_project(
+        remote_dir=dest_path,
+        exclude=".DS_Store",
+        local_dir=DEPLOY_PATH.rstrip('/') + '/',
+        delete=True,
+        extra_opts='-c',
+    )
+
+def gh_pages():
+    """Publish to GitHub Pages"""
+    rebuild()
+    local("ghp-import -b {github_pages_branch} {deploy_path} -p".format(**env))
diff --git a/pelicanconf.py b/pelicanconf.py
new file mode 100644
index 0000000..39a0e23
--- /dev/null
+++ b/pelicanconf.py
@@ -0,0 +1,213 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*- #
+from __future__ import unicode_literals
+
+# For an exhaustive list of available variables and how to use them refer to
+# http://docs.getpelican.com/en/stable/settings.html
+
+# Reference for signing off posts
+AUTHOR = 'Luigi'
+
+# Reference to the site name
+SITENAME = 'Giggi.me'
+
+# Custom variable, introduced for keeping requests cosistent
+INDEX_LINK_AS = 'blog.html'
+
+# If set, stop treating index.html as default posts binder
+INDEX_SAVE_AS = INDEX_LINK_AS
+
+# URL path to the root
+SITEURL = 'https://www.giggi.me'
+
+# URL path to the theme folder
+THEMEURL = SITEURL
+
+# Local path to the markdown source folder
+PATH = 'content'
+
+# Local path to the current theme folder
+THEME = 'theme'
+
+# Default time zone
+TIMEZONE = 'Europe/London'
+
+# The default date format you want to use.
+DEFAULT_DATE_FORMAT = '%a %B %d %Y'
+
+# The extensions to use when looking up template files from template names.
+TEMPLATE_EXTENSION = [ '.html' ]
+
+# Default language
+DEFAULT_LANG = 'en'
+
+# 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
+
+# Social widget
+SOCIAL = (
+    ('Linux', 'https://www.debian.org'),
+    ('Mail', 'mailto:luigi.santivetti@gmail.com'),
+)
+
+#
+DEFAULT_PAGINATION = False
+
+# List of plugins paths utilised by this site
+PLUGIN_PATHS = [
+    'theme/plugins',
+]
+
+# List of plugins names
+PLUGINS = [
+    'assets',
+    'photos',
+]
+
+# Derive categories from the folder name
+USE_FOLDER_AS_CATEGORY = False
+
+# Show shortcuts to categories
+DISPLAY_CATEGORIES_ON_MENU = True
+
+# Show shortcuts to static pages (i.e. non articles)
+DISPLAY_PAGES_ON_MENU = False
+
+# Use cached html
+LOAD_CONTENT_CACHE = False
+
+# List of menu items
+MENUITEMS = [
+    ('Git', 'page/git.html'),
+    ('Mail', 'page/mail.html'),
+    ('Ftp', 'page/ftp.html'),
+    ('About', 'page/about.html'),
+    ('Invite', 'page/invite.html'),
+    ('Login', 'page/login.html'),
+]
+
+# Enable line numbers
+MARKDOWN = {
+    'extension_configs': {
+        'markdown.extensions.codehilite': {'css_class': 'highlight', 'linenums' : True},
+        'markdown.extensions.extra': {},
+        'markdown.extensions.meta': {},
+    },
+    'output_format': 'html5',
+}
+
+#
+# Defines whether Pelican should use document-relative URLs or not. Only set
+# this to True when developing/testing# and only if you fully understand the
+# effect it can have on links/feeds.
+#RELATIVE_URLS = True
+
+# The URL to refer to an article.
+ARTICLE_URL = 'blog/{slug}.html'
+
+# The place where we will save an article.
+ARTICLE_SAVE_AS = 'blog/{slug}.html'
+
+# The URL to refer to an article which doesn’t use the default language.
+ARTICLE_LANG_URL = 'blog/{slug}-{lang}.html'
+
+# The place where we will save an article which doesn’t use the default
+# language.
+ARTICLE_LANG_SAVE_AS = 'blog/{slug}-{lang}.html'
+
+# The URL to refer to an article draft.
+DRAFT_URL = 'draft/blog/{slug}.html'
+
+# The place where we will save an article draft.
+DRAFT_SAVE_AS = 'draft/blog/{slug}.html'
+
+# The URL to refer to an article draft which doesn’t use the default language.
+DRAFT_LANG_URL = 'draft/blog/{slug}-{lang}.html'
+
+# The place where we will save an article draft which doesn’t use the default
+# language.
+DRAFT_LANG_SAVE_AS = 'draft/blog/{slug}-{lang}.html'
+
+# The URL we will use to link to a page.
+PAGE_URL = 'page/{slug}.html'
+
+# The location we will save the page. This value has to be the same as PAGE_URL
+# or you need to use a rewrite in your server config.
+PAGE_SAVE_AS = 'page/{slug}.html'
+
+# The URL we will use to link to a page which doesn’t use the default language.
+PAGE_LANG_URL = 'page/{slug}-{lang}.html'
+
+#The location we will save the page which doesn’t use the default language.
+PAGE_LANG_SAVE_AS = 'page/{slug}-{lang}.html'
+
+# The URL used to link to a page draft.
+DRAFT_PAGE_URL = 'draft/page/{slug}.html'
+
+# The actual location a page draft is saved at.
+DRAFT_PAGE_SAVE_AS = 'draft/page/{slug}.html'
+
+# The URL used to link to a page draft which doesn’t use the default language.
+DRAFT_PAGE_LANG_URL = 'draft/page/{slug}-{lang}.html'
+
+# The actual location a page draft which doesn’t use the default language is
+# saved at.
+DRAFT_PAGE_LANG_SAVE_AS = 'draft/page/{slug}-{lang}.html'
+
+# The URL to use for a category.
+CATEGORY_URL = 'category/{slug}.html'
+
+# The location to save a category.
+CATEGORY_SAVE_AS = 'category/{slug}.html'
+
+# The URL to use for a tag.
+TAG_URL = 'tag/{slug}.html'
+
+# The location to save the tag page.
+TAG_SAVE_AS = 'tag/{slug}.html'
+
+# The URL to use for an author.
+AUTHOR_URL = 'author/{slug}.html'
+
+# The location to save an author.
+AUTHOR_SAVE_AS = 'author/{slug}.html'
+
+# The location to save per-year archives of your posts.
+YEAR_ARCHIVE_SAVE_AS = 'blog/{date:%Y}/index.html'
+
+# The URL to use for per-year archives of your posts. Used only if you have the
+# {url} placeholder in PAGINATION_PATTERNS.
+YEAR_ARCHIVE_URL = ''
+
+# The location to save per-month archives of your posts.
+MONTH_ARCHIVE_SAVE_AS = 'blog/{date:%Y}/{date:%b}/index.html'
+
+# The URL to use for per-month archives of your posts. Used only if you have the
+# {url} placeholder in PAGINATION_PATTERNS.
+MONTH_ARCHIVE_URL = ''
+
+# The location to save per-day archives of your posts.
+DAY_ARCHIVE_SAVE_AS = 'blog/{date:%Y}/{date:%b}/{date:%d}/index.html'
+
+# The URL to use for per-day archives of your posts. Used only if you have the
+# {url} placeholder in PAGINATION_PATTERNS.
+DAY_ARCHIVE_URL = ''
+
+# Gallery plugin
+PHOTO_LIBRARY = '/home/luigi/giggi.me.c/multimedia/gallery'
+PHOTO_GALLERY = (2000, 1333, 100)
+PHOTO_ARTICLE = (2000, 1333, 100)
+PHOTO_THUMB = (300, 200, 100)
+PHOTO_SQUARE_THUMB = False
diff --git a/plugins/assets b/plugins/assets
deleted file mode 160000
index 2e3e7fc..0000000
--- a/plugins/assets
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 2e3e7fc5cc679b56a1405badbfe6451542a443af
diff --git a/publishconf.py b/publishconf.py
new file mode 100644
index 0000000..ce7a890
--- /dev/null
+++ b/publishconf.py
@@ -0,0 +1,24 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*- #
+from __future__ import unicode_literals
+
+# 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 *
+
+SITEURL = 'http://giggi.me'
+RELATIVE_URLS = False
+
+FEED_ALL_ATOM = 'feeds/all.atom.xml'
+CATEGORY_FEED_ATOM = 'feeds/%s.atom.xml'
+
+DELETE_OUTPUT_DIRECTORY = True
+
+# Following items are often useful when publishing
+
+#DISQUS_SITENAME = ""
+#GOOGLE_ANALYTICS = ""
diff --git a/screenshot.png b/screenshot.png
deleted file mode 100644
index 3430405..0000000
--- a/screenshot.png
+++ /dev/null
Binary files differ
diff --git a/static/js/lw-timeago b/static/js/lw-timeago
deleted file mode 160000
index 5adac4a..0000000
--- a/static/js/lw-timeago
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 5adac4ab06d16afed7e1ff041c87219c4e86125c
diff --git a/README.md b/theme/README.md
similarity index 100%
rename from README.md
rename to theme/README.md
diff --git a/theme/plugins/assets/__init__.py b/theme/plugins/assets/__init__.py
new file mode 100644
index 0000000..67b75dd
--- /dev/null
+++ b/theme/plugins/assets/__init__.py
@@ -0,0 +1 @@
+from .assets import *
diff --git a/theme/plugins/assets/__pycache__/__init__.cpython-35.pyc b/theme/plugins/assets/__pycache__/__init__.cpython-35.pyc
new file mode 100644
index 0000000..581a1c8
--- /dev/null
+++ b/theme/plugins/assets/__pycache__/__init__.cpython-35.pyc
Binary files differ
diff --git a/theme/plugins/assets/__pycache__/assets.cpython-35.pyc b/theme/plugins/assets/__pycache__/assets.cpython-35.pyc
new file mode 100644
index 0000000..fde1266
--- /dev/null
+++ b/theme/plugins/assets/__pycache__/assets.cpython-35.pyc
Binary files differ
diff --git a/theme/plugins/assets/assets.py b/theme/plugins/assets/assets.py
new file mode 100644
index 0000000..e204dd6
--- /dev/null
+++ b/theme/plugins/assets/assets.py
@@ -0,0 +1,75 @@
+# -*- coding: utf-8 -*-
+"""
+Asset management plugin for Pelican
+===================================
+
+This plugin allows you to use the `webassets`_ module to manage assets such as
+CSS and JS files.
+
+The ASSET_URL is set to a relative url to honor Pelican's RELATIVE_URLS
+setting. This requires the use of SITEURL in the templates::
+
+    <link rel="stylesheet" href="{{ SITEURL }}/{{ ASSET_URL }}">
+
+.. _webassets: https://webassets.readthedocs.org/
+
+"""
+from __future__ import unicode_literals
+
+import os
+import logging
+
+from pelican import signals
+logger = logging.getLogger(__name__)
+
+try:
+    import webassets
+    from webassets import Environment
+    from webassets.ext.jinja2 import AssetsExtension
+except ImportError:
+    webassets = None
+
+def add_jinja2_ext(pelican):
+    """Add Webassets to Jinja2 extensions in Pelican settings."""
+
+    if 'JINJA_ENVIRONMENT' in pelican.settings: # pelican 3.7+
+        pelican.settings['JINJA_ENVIRONMENT']['extensions'].append(AssetsExtension)
+    else:
+        pelican.settings['JINJA_EXTENSIONS'].append(AssetsExtension)
+
+
+def create_assets_env(generator):
+    """Define the assets environment and pass it to the generator."""
+
+    theme_static_dir = generator.settings['THEME_STATIC_DIR']
+    assets_destination = os.path.join(generator.output_path, theme_static_dir)
+    generator.env.assets_environment = Environment(
+        assets_destination, theme_static_dir)
+
+    if 'ASSET_CONFIG' in generator.settings:
+        for item in generator.settings['ASSET_CONFIG']:
+            generator.env.assets_environment.config[item[0]] = item[1]
+
+    if 'ASSET_BUNDLES' in generator.settings:
+        for name, args, kwargs in generator.settings['ASSET_BUNDLES']:
+            generator.env.assets_environment.register(name, *args, **kwargs)
+
+    if 'ASSET_DEBUG' in generator.settings:
+        generator.env.assets_environment.debug = generator.settings['ASSET_DEBUG']
+    elif logging.getLevelName(logger.getEffectiveLevel()) == "DEBUG":
+        generator.env.assets_environment.debug = True
+
+    for path in (generator.settings['THEME_STATIC_PATHS'] +
+                 generator.settings.get('ASSET_SOURCE_PATHS', [])):
+        full_path = os.path.join(generator.theme, path)
+        generator.env.assets_environment.append_path(full_path)
+
+
+def register():
+    """Plugin registration."""
+    if webassets:
+        signals.initialized.connect(add_jinja2_ext)
+        signals.generator_init.connect(create_assets_env)
+    else:
+        logger.warning('`assets` failed to load dependency `webassets`.'
+                       '`assets` plugin not loaded.')
diff --git a/theme/plugins/photos/README.md b/theme/plugins/photos/README.md
new file mode 100644
index 0000000..411265f
--- /dev/null
+++ b/theme/plugins/photos/README.md
@@ -0,0 +1,291 @@
+# Photos
+
+Use Photos to add a photo or a gallery of photos to an article, or to include photos in the body text. Photos are kept separately, as an organized library of high resolution photos, and resized as needed.
+
+## How to install and configure
+
+The plug-in requires `Pillow`: the Python Imaging Library and optionally `Piexif`, whose installation are outside the scope of this document.
+
+The plug-in resizes the referred photos, and generates thumbnails for galleries and associated photos, based on the following configuration and default values:
+
+`PHOTO_LIBRARY = "~/Pictures"`
+:	Absolute path to the folder where the original photos are kept, organized in sub-folders.
+
+`PHOTO_GALLERY = (1024, 768, 80)`
+:	For photos in galleries, maximum width and height, plus JPEG quality as a percentage. This would typically be the size of the photo displayed when the reader clicks a thumbnail.
+
+`PHOTO_ARTICLE = (760, 506, 80)`
+:	For photos associated with articles, maximum width, height, and quality. The maximum size would typically depend on the needs of the theme. 760px is suitable for the theme `notmyidea`.
+
+`PHOTO_THUMB = (192, 144, 60)`
+:	For thumbnails, maximum width, height, and quality.
+
+`PHOTO_SQUARE_THUMB = False`
+:	Crops thumbnails to make them square.
+
+`PHOTO_RESIZE_JOBS = 5`
+: Number of parallel resize jobs to be run. Defaults to 1.
+
+`PHOTO_WATERMARK = True`
+: Adds a watermark to all photos in articles and pages. Defaults to using your site name.
+
+`PHOTO_WATERMARK_TEXT' = SITENAME`
+: Allow the user to change the watermark text or remove it completely. By default it uses [SourceCodePro-Bold](http://www.adobe.com/products/type/font-information/source-code-pro-readme.html) as the font.
+
+`PHOTO_WATERMARK_IMG = ''`
+: Allows the user to add an image in addition to or as the only watermark. Set the variable to the location.
+
+**The following features require the piexif library**
+`PHOTO_EXIF_KEEP = True`
+: Keeps the exif of the input photo.
+
+`PHOTO_EXIF_REMOVE_GPS = True`
+: Removes any GPS information from the files exif data.
+
+`PHOTO_EXIF_COPYRIGHT = 'COPYRIGHT'`
+: Attaches an author and a license to the file. Choices include:
+	- `COPYRIGHT`: Copyright
+	- `CC0`: Public Domain
+	- `CC-BY-NC-ND`: Creative Commons Attribution-NonCommercial-NoDerivatives
+	- `CC-BY-NC-SA`: Creative Commons Attribution-NonCommercial-ShareAlike
+	- `CC-BY`: Creative Commons Attribution
+	- `CC-BY-SA`: Creative Commons Attribution-ShareAlike
+	- `CC-BY-NC`: Creative Commons Attribution-NonCommercial
+	- `CC-BY-ND`: Creative Commons Attribution-NoDerivatives
+
+`PHOTO_EXIF_COPYRIGHT_AUTHOR = 'Your Name Here'`
+: Adds an author name to the photo's exif and copyright statement. Defaults to `AUTHOR` value from the `pelicanconf.py`
+
+The plug-in automatically resizes the photos and publishes them to the following output folder:
+
+    ./output/photos
+
+**WARNING:** The plug-in can take hours to resize 40,000 photos, therefore, photos and thumbnails are only generated once. Clean the output folders to regenerate the resized photos again.
+
+## How to use
+
+Maintain an organized library of high resolution photos somewhere on disk, using folders to group related images. The default path `~/Pictures` is convenient for Mac OS X users.
+
+* To create a gallery of photos, add the metadata field `gallery: {photo}folder` to an article. To simplify the transition from the plug-in Gallery, the syntax `gallery: {filename}folder` is also accepted.
+* You can now have multiple galleries. The galleries need to be seperated by a comma in the metadata field. The syntax is gallery: `{photo}folder, {photo}folder2`. You can also add titles to your galleries. The syntax is: `{photo}folder, {photo}folder2{This is a title}`. Using the following example the first gallery would have the title of the folder location and the second would have the title `This is a tile.`
+* To use an image in the body of the text, just use the syntax `{photo}folder/image.jpg` instead of the usual `{filename}/images/image.jpg`.
+* To use an image in the body of the text, which can be used with [Lightbox](http://lokeshdhakar.com/projects/lightbox2/) just use the syntax `{lightbox}folder/image.jpg`. For use with other implementations, the gallery and caption attribute names can be set with `PHOTO_LIGHTBOX_GALLERY_ATTR` and `PHOTO_LIGHTBOX_CAPTION_ATTR`.
+* To associate an image with an article, add the metadata field `image: {photo}folder/image.jpg` to an article. Use associated images to improve navigation. For compatibility, the syntax `image: {filename}/images/image.jpg` is also accepted.
+
+### Exif, Captions, and Blacklists
+Folders of photos may optionally have three text files, where each line describes one photo. You can use the `#` to comment out a line. Generating these optional files is left as an exercise for the reader (but consider using Phil Harvey's [exiftool](http://www.sno.phy.queensu.ca/~phil/exiftool/)). See below for one method of extracting exif data.
+
+`exif.txt`
+:	Associates compact technical information with photos, typically the camera settings. For example:
+
+	best.jpg: Canon EOS 5D Mark II - 20mm f/8 1/250s ISO 100
+	night.jpg: Canon EOS 5D Mark II - 47mm f/8 5s ISO 100
+	# new.jpg: Canon EOS 5D Mark II - 47mm f/8 5s ISO 100
+
+`captions.txt`
+:	Associates comments with photos. For example:
+
+	best.jpg: My best photo ever! How lucky of me!
+	night.jpg: Twilight over the dam.
+	# new.jpg: My new photo blog entry is not quite ready.
+
+`blacklist.txt`
+: Skips photos the user does not want to include. For example:
+
+	this-file-will-be-skipped.jpg
+	this-one-will-be-skipped-too.jpg
+	# but-this-file-will-NOT-be-skipped.jpg
+	this-one-will-be-also-skipped.jpg
+
+
+Here is an example Markdown article that shows the four use cases:
+
+	title: My Article
+	gallery: {photo}favorite
+	image: {photo}favorite/best.jpg
+
+	Here are my best photos, taken with my favorite camera:
+	![]({photo}mybag/camera.jpg).
+	![]({lightbox}mybag/flash.jpg).
+
+The default behavior of the Photos plugin removes the exif information from the file. If you would like to keep the exif information, you can install the `piexif` library for python and add the following settings to keep some or all of the exif information. This feature is not a replacement for the `exif.txt` feature but in addition to that feature. This feature currently only works with jpeg input files.
+
+## How to change the Jinja templates
+
+The plugin provides the following variables to your templates:
+
+`article.photo_image`
+:	For articles with an associated photo, a tuple with the following information:
+
+* The filename of the original photo.
+* The output path to the generated photo.
+* The output path to the generated thumbnail.
+
+For example, modify the template `article.html` as shown below to display the associated image before the article content:
+
+```html
+<div class="entry-content">
+	{% if article.photo_image %}<img src="{{ SITEURL }}/{{ article.photo_image[1] }}" />{% endif %}
+	{% include 'article_infos.html' %}
+	{{ article.content }}
+</div><!-- /.entry-content -->
+```
+
+`article.photo_gallery`
+:	For articles with a gallery, a list of the photos in the gallery. Each item in the list is a tuple with five elements:
+
+* The title of the gallery
+* The filename of the original photo.
+* The output path to the generated photo.
+* The output path to the generated thumbnail.
+* The EXIF information of the photo, as read from the file `exif.txt`.
+* The caption of the photo, as read from `captions.txt`.
+
+For example, add the following to the template `article.html` to add the gallery as the end of the article:
+
+```html
+{% if article.photo_gallery %}
+<div class="gallery">
+		{% for title, gallery in article.photo_gallery %}
+			<h1>{{ title }}</h1>
+				{% for name, photo, thumb, exif, caption in gallery %}
+						<a href="{{ SITEURL }}/{{ photo }}" title="{{ name }}" exif="{{ exif }}" caption="{{ caption }}"><img src="{{ SITEURL }}/{{ thumb }}"></a>
+				{% endfor %}
+		{% endfor %}
+</div>
+{% endif %}
+```
+
+For example, add the following to the template `index.html`, inside the `entry-content`, to display the thumbnail with a link to the article:
+
+```html
+{% if article.photo_image %}<a href="{{ SITEURL }}/{{ article.url }}"><img src="{{ SITEURL }}/{{ article.photo_image[2] }}"
+	style="display: inline; float: right; margin: 2px 0 2ex 4ex;" /></a>
+{% endif %}
+```
+
+## How to make the gallery lightbox
+
+There are several JavaScript libraries that display a list of images as a lightbox. The example below uses [Magnific Popup](http://dimsemenov.com/plugins/magnific-popup/), which allows the more complex initialization needed to display both the filename, the compact technical information, and the caption. The solution would be simpler if photos did not show any extra information.
+
+Copy the files `magnific-popup.css` and `magnific-popup.js` to the root of your Pelican template.
+
+Add the following to the template `base.html`, inside the HTML `head` tags:
+
+```html
+{% if (article and article.photo_gallery) or (articles_page and articles_page.object_list[0].photo_gallery) %}
+	<link rel="stylesheet" href="{{ SITEURL }}/{{ THEME_STATIC_DIR }}/magnific-popup.css">
+{% endif %}
+```
+
+Add the following to the template `base.html`, before the closing HTML `</body>` tag:
+
+```JavaScript
+{% if (article and article.photo_gallery) or (articles_page and articles_page.object_list[0].photo_gallery) %}
+<!-- jQuery 1.7.2+ or Zepto.js 1.0+ -->
+<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
+
+<!-- Magnific Popup core JS file -->
+<script src="{{ SITEURL }}/{{ THEME_STATIC_DIR }}/magnific-popup.js"></script>
+<script>
+$('.gallery').magnificPopup({
+	delegate: 'a',
+	type: 'image',
+	gallery: {
+		enabled: true,
+		navigateByImgClick: true,
+		preload: [1,2]
+	},
+	image: {
+		titleSrc: function(item) {
+			if (item.el.attr('caption') && item.el.attr('exif')) {
+				return (item.el.attr('caption').replace(/\\n/g, '<br />') +
+					'<small>' + item.el.attr('title') + ' - ' + item.el.attr('exif') + '</small>');
+			}
+		return item.el.attr('title') + '<small>' + item.el.attr('exif') + '</small>';
+	} }
+});
+</script>
+{% endif %}
+```
+
+## How to make a Bootstrap Carousel
+
+If you are using bootstrap, the following code is an example of how one could create a carousel.
+
+```html
+{% if article.photo_gallery %}
+  {% for title, gallery in article.photo_gallery %}
+    <h1>{{ title }}</h1>
+    <div id="carousel-{{ loop.index }}" class="carousel slide">
+      <ol class="carousel-indicators">
+          {% for i in range(0, gallery|length) %}
+          <li data-target="#carousel-{{ loop.index }}" data-slide-to="{{ i }}" {% if i==0 %} class="active" {% endif %}></li>
+          {% endfor %}
+      </ol>
+      <div class="carousel-inner">
+        {% for name, photo, thumb, exif, caption in gallery %}
+          {% if loop.first %}
+            <div class="item active">
+          {% else %}
+            <div class="item">
+          {% endif %}
+          <img src="{{ SITEURL }}/{{ photo }}" exif="{{ exif }}" alt="{{ caption }}">
+          <div class="carousel-caption">
+              <h5>{{ caption }}</h5>
+          </div> <!-- carousel-caption -->
+        </div> <!-- item -->
+        {% endfor %}
+      </div> <!-- carousel-inner -->
+      <a class="left carousel-control" href="#carousel-{{ loop.index }}" data-slide="prev">
+        <span class="glyphicon glyphicon-chevron-left"></span>
+      </a>
+      <a class="right carousel-control" href="#carousel-{{ loop.index }}" data-slide="next">
+        <span class="glyphicon glyphicon-chevron-right"></span>
+      </a>
+    </div> <!-- closes carousel-{{ loop.index }} -->
+    {% endfor %}
+{% endif %}
+```
+
+## Exiftool example
+
+You can add the following stanza to your fab file if you are using `fabric` to generate the appropriate text files for your galleries. You need to set the location of `Exiftool` control files.
+
+```Python
+def photo_gallery_gen(location):
+    """Create gallery metadata files."""
+    local_path = os.getcwd() + 'LOCATION OF YOUR EXIF CONTROL FILES'
+    with lcd(location):
+        local("exiftool -p {fmt_path}/exif.fmt . > exif.txt".format(
+            fmt_path=local_path))
+        local("exiftool -p {fmt_path}/captions.fmt . > captions.txt".format(
+            fmt_path=local_path))
+
+```
+
+`captions.fmt` example file
+
+```
+$FileName: $Description
+```
+
+`exif.fmt` example file
+
+```
+$FileName: $CreateDate - $Make $Model Stats:(f/$Aperture, ${ShutterSpeed}s, ISO $ISO Flash: $Flash) GPS:($GPSPosition $GPSAltitude)
+```
+
+## Known use cases
+
+[pxquim.pt](http://pxquim.pt/) uses Photos and the plug-in Sub-parts to publish 600 photo galleries with 40,000 photos. Photos keeps the high-resolution photos separate from the site articles.
+
+[pxquim.com](http://pxquim.com/) uses sub-parts to cover conferences, where it makes sense to have a sub-part for each speaker.
+
+## Alternatives
+
+Gallery
+:	Galleries are distinct entities, without the organizational capabilities of articles. Photos must be resized separately, and must be kept with the source of the blog. Gallery was the initial inspiration for Photos.
+
+Image_process
+:	Resize and process images in the article body in a more flexible way (based on the CSS class of the image), but without the ability to create galleries. The source photos must be kept with the source of the blog.
diff --git a/theme/plugins/photos/SourceCodePro-Bold.otf b/theme/plugins/photos/SourceCodePro-Bold.otf
new file mode 100644
index 0000000..f4e576c
--- /dev/null
+++ b/theme/plugins/photos/SourceCodePro-Bold.otf
Binary files differ
diff --git a/theme/plugins/photos/SourceCodePro-Regular.otf b/theme/plugins/photos/SourceCodePro-Regular.otf
new file mode 100644
index 0000000..4e3b9d0
--- /dev/null
+++ b/theme/plugins/photos/SourceCodePro-Regular.otf
Binary files differ
diff --git a/theme/plugins/photos/__init__.py b/theme/plugins/photos/__init__.py
new file mode 100644
index 0000000..9832420
--- /dev/null
+++ b/theme/plugins/photos/__init__.py
@@ -0,0 +1 @@
+from .photos import *
diff --git a/theme/plugins/photos/licenses.json b/theme/plugins/photos/licenses.json
new file mode 100644
index 0000000..c727e21
--- /dev/null
+++ b/theme/plugins/photos/licenses.json
@@ -0,0 +1,30 @@
+{
+	"CC-BY-NC-ND": {
+		"URL": "https://creativecommons.org/licenses/by-nc-nd/4.0/legalcode",
+		"Text": "Copyleft license, {Author} {Year}. Content licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License, except where indicated otherwise. See {URL} for more information."
+	},
+	"CC-BY-NC-SA": {
+		"URL": "https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode",
+		"Text": "Copyleft license, {Author} {Year}. Content licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License, except where indicated otherwise. See {URL} for more information."
+	},
+	"CC-BY": {
+		"URL": "https://creativecommons.org/licenses/by/4.0/legalcode",
+		"Text": "Copyleft license, {Author} {Year}. Content licensed under a Creative Commons Attribution 4.0 International License, except where indicated otherwise. See {URL} for more information."
+	},
+	"CC-BY-SA": {
+		"URL": "https://creativecommons.org/licenses/by-sa/4.0/legalcode",
+		"Text": "Copyleft license, {Author} {Year}. Content licensed under a Creative Commons Attribution-ShareAlike 4.0 International License, except where indicated otherwise. See {URL} for more information."
+	},
+	"CC0": {
+		"URL": "https://creativecommons.org/publicdomain/zero/1.0/",
+		"Text": "CC0 Copyleft license, {Author} {Year}. To the extent possible under law, the author(s) have dedicated all copyright and related and neighboring rights to this software to the public domain worldwide. This software is distributed without any warranty. See {URL} for more information."
+	},
+	"CC-BY-NC": {
+		"URL": "https://creativecommons.org/licenses/by-nc/4.0/legalcode",
+		"Text": "Copyleft license, {Author} {Year}. Content licensed under a Creative Commons Attribution-NonCommercial 4.0 International License, except where indicated otherwise. See {URL} for more information."
+	},
+	"CC-BY-ND": {
+		"URL": "https://creativecommons.org/licenses/by-nd/4.0/legalcode",
+		"Text": "Copyleft license, {Author} {Year}. Content licensed under a Creative Commons Attribution-NoDerivatives 4.0 International License, except where indicated otherwise. See {URL} for more information."
+	}
+}
diff --git a/theme/plugins/photos/photos.py b/theme/plugins/photos/photos.py
new file mode 100644
index 0000000..d0c1af1
--- /dev/null
+++ b/theme/plugins/photos/photos.py
@@ -0,0 +1,589 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+import datetime
+import itertools
+import json
+import logging
+import multiprocessing
+import os
+import pprint
+import re
+import sys
+
+from pelican.generators import ArticlesGenerator
+from pelican.generators import PagesGenerator
+from pelican.settings import DEFAULT_CONFIG
+from pelican import signals
+from pelican.utils import pelican_open
+
+logger = logging.getLogger(__name__)
+
+try:
+    from PIL import Image
+    from PIL import ImageDraw
+    from PIL import ImageEnhance
+    from PIL import ImageFont
+    from PIL import ImageOps
+except ImportError:
+    logger.error('PIL/Pillow not found')
+
+try:
+    import piexif
+except ImportError:
+    ispiexif = False
+    logger.warning('piexif not found! Cannot use exif manipulation features')
+else:
+    ispiexif = True
+    logger.debug('piexif found.')
+
+
+def initialized(pelican):
+    p = os.path.expanduser('~/Pictures')
+
+    DEFAULT_CONFIG.setdefault('PHOTO_LIBRARY', p)
+    DEFAULT_CONFIG.setdefault('PHOTO_GALLERY', (1024, 768, 80))
+    DEFAULT_CONFIG.setdefault('PHOTO_ARTICLE', (760, 506, 80))
+    DEFAULT_CONFIG.setdefault('PHOTO_THUMB', (192, 144, 60))
+    DEFAULT_CONFIG.setdefault('PHOTO_SQUARE_THUMB', False)
+    DEFAULT_CONFIG.setdefault('PHOTO_GALLERY_TITLE', '')
+    DEFAULT_CONFIG.setdefault('PHOTO_ALPHA_BACKGROUND_COLOR', (255, 255, 255))
+    DEFAULT_CONFIG.setdefault('PHOTO_WATERMARK', False)
+    DEFAULT_CONFIG.setdefault('PHOTO_WATERMARK_THUMB', False)
+    DEFAULT_CONFIG.setdefault('PHOTO_WATERMARK_TEXT', DEFAULT_CONFIG['SITENAME'])
+    DEFAULT_CONFIG.setdefault('PHOTO_WATERMARK_TEXT_COLOR', (255, 255, 255))
+    DEFAULT_CONFIG.setdefault('PHOTO_WATERMARK_IMG', '')
+    DEFAULT_CONFIG.setdefault('PHOTO_WATERMARK_IMG_SIZE', False)
+    DEFAULT_CONFIG.setdefault('PHOTO_RESIZE_JOBS', 1)
+    DEFAULT_CONFIG.setdefault('PHOTO_EXIF_KEEP', False)
+    DEFAULT_CONFIG.setdefault('PHOTO_EXIF_REMOVE_GPS', False)
+    DEFAULT_CONFIG.setdefault('PHOTO_EXIF_AUTOROTATE', True)
+    DEFAULT_CONFIG.setdefault('PHOTO_EXIF_COPYRIGHT', False)
+    DEFAULT_CONFIG.setdefault('PHOTO_EXIF_COPYRIGHT_AUTHOR', DEFAULT_CONFIG['SITENAME'])
+    DEFAULT_CONFIG.setdefault('PHOTO_LIGHTBOX_GALLERY_ATTR', 'data-lightbox')
+    DEFAULT_CONFIG.setdefault('PHOTO_LIGHTBOX_CAPTION_ATTR', 'data-title')
+
+    DEFAULT_CONFIG['queue_resize'] = {}
+    DEFAULT_CONFIG['created_galleries'] = {}
+    DEFAULT_CONFIG['plugin_dir'] = os.path.dirname(os.path.realpath(__file__))
+
+    if pelican:
+        pelican.settings.setdefault('PHOTO_LIBRARY', p)
+        pelican.settings.setdefault('PHOTO_GALLERY', (1024, 768, 80))
+        pelican.settings.setdefault('PHOTO_ARTICLE', (760, 506, 80))
+        pelican.settings.setdefault('PHOTO_THUMB', (192, 144, 60))
+        pelican.settings.setdefault('PHOTO_SQUARE_THUMB', False)
+        pelican.settings.setdefault('PHOTO_GALLERY_TITLE', '')
+        pelican.settings.setdefault('PHOTO_ALPHA_BACKGROUND_COLOR', (255, 255, 255))
+        pelican.settings.setdefault('PHOTO_WATERMARK', False)
+        pelican.settings.setdefault('PHOTO_WATERMARK_THUMB', False)
+        pelican.settings.setdefault('PHOTO_WATERMARK_TEXT', pelican.settings['SITENAME'])
+        pelican.settings.setdefault('PHOTO_WATERMARK_TEXT_COLOR', (255, 255, 255))
+        pelican.settings.setdefault('PHOTO_WATERMARK_IMG', '')
+        pelican.settings.setdefault('PHOTO_WATERMARK_IMG_SIZE', False)
+        pelican.settings.setdefault('PHOTO_RESIZE_JOBS', 1)
+        pelican.settings.setdefault('PHOTO_EXIF_KEEP', False)
+        pelican.settings.setdefault('PHOTO_EXIF_REMOVE_GPS', False)
+        pelican.settings.setdefault('PHOTO_EXIF_AUTOROTATE', True)
+        pelican.settings.setdefault('PHOTO_EXIF_COPYRIGHT', False)
+        pelican.settings.setdefault('PHOTO_EXIF_COPYRIGHT_AUTHOR', pelican.settings['AUTHOR'])
+        pelican.settings.setdefault('PHOTO_LIGHTBOX_GALLERY_ATTR', 'data-lightbox')
+        pelican.settings.setdefault('PHOTO_LIGHTBOX_CAPTION_ATTR', 'data-title')
+
+
+def read_notes(filename, msg=None):
+    notes = {}
+    try:
+        with pelican_open(filename) as text:
+            for line in text.splitlines():
+                if line.startswith('#'):
+                    continue
+                m = line.split(':', 1)
+                if len(m) > 1:
+                    pic = m[0].strip()
+                    note = m[1].strip()
+                    if pic and note:
+                        notes[pic] = note
+                else:
+                    notes[line] = ''
+    except Exception as e:
+        if msg:
+            logger.info('{} at file {}'.format(msg, filename))
+        logger.debug('read_notes issue: {} at file {}. Debug message:{}'.format(msg, filename, e))
+    return notes
+
+
+def enqueue_resize(orig, resized, spec=(640, 480, 80)):
+    if resized not in DEFAULT_CONFIG['queue_resize']:
+        DEFAULT_CONFIG['queue_resize'][resized] = (orig, spec)
+    elif DEFAULT_CONFIG['queue_resize'][resized] != (orig, spec):
+        logger.error('photos: resize conflict for {}, {}-{} is not {}-{}'.format(resized, DEFAULT_CONFIG['queue_resize'][resized][0], DEFAULT_CONFIG['queue_resize'][resized][1], orig, spec))
+
+
+def isalpha(img):
+    return True if img.mode in ('RGBA', 'LA') or (img.mode == 'P' and 'transparency' in img.info) else False
+
+
+def remove_alpha(img, bg_color):
+    background = Image.new("RGB", img.size, bg_color)
+    background.paste(img, mask=img.split()[3])  # 3 is the alpha channel
+    return background
+
+
+def ReduceOpacity(im, opacity):
+    """Reduces Opacity.
+
+    Returns an image with reduced opacity.
+    Taken from http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/362879
+    """
+    assert opacity >= 0 and opacity <= 1
+    if isalpha(im):
+        im = im.copy()
+    else:
+        im = im.convert('RGBA')
+
+    alpha = im.split()[3]
+    alpha = ImageEnhance.Brightness(alpha).enhance(opacity)
+    im.putalpha(alpha)
+    return im
+
+
+def watermark_photo(image, settings):
+    margin = [10, 10]
+    opacity = 0.6
+
+    watermark_layer = Image.new("RGBA", image.size, (0, 0, 0, 0))
+    draw_watermark = ImageDraw.Draw(watermark_layer)
+    text_reducer = 32
+    image_reducer = 8
+    text_size = [0, 0]
+    mark_size = [0, 0]
+    text_position = [0, 0]
+
+    if settings['PHOTO_WATERMARK_TEXT']:
+        font_name = 'SourceCodePro-Bold.otf'
+        default_font = os.path.join(DEFAULT_CONFIG['plugin_dir'], font_name)
+        font = ImageFont.FreeTypeFont(default_font, watermark_layer.size[0] // text_reducer)
+        text_size = draw_watermark.textsize(settings['PHOTO_WATERMARK_TEXT'], font)
+        text_position = [image.size[i] - text_size[i] - margin[i] for i in [0, 1]]
+        draw_watermark.text(text_position, settings['PHOTO_WATERMARK_TEXT'], settings['PHOTO_WATERMARK_TEXT_COLOR'], font=font)
+
+    if settings['PHOTO_WATERMARK_IMG']:
+        mark_image = Image.open(settings['PHOTO_WATERMARK_IMG'])
+        mark_image_size = [watermark_layer.size[0] // image_reducer for size in mark_size]
+        mark_image_size = settings['PHOTO_WATERMARK_IMG_SIZE'] if settings['PHOTO_WATERMARK_IMG_SIZE'] else mark_image_size
+        mark_image.thumbnail(mark_image_size, Image.ANTIALIAS)
+        mark_position = [watermark_layer.size[i] - mark_image.size[i] - margin[i] for i in [0, 1]]
+        mark_position = tuple([mark_position[0] - (text_size[0] // 2) + (mark_image_size[0] // 2), mark_position[1] - text_size[1]])
+
+        if not isalpha(mark_image):
+            mark_image = mark_image.convert('RGBA')
+
+        watermark_layer.paste(mark_image, mark_position, mark_image)
+
+    watermark_layer = ReduceOpacity(watermark_layer, opacity)
+    image.paste(watermark_layer, (0, 0), watermark_layer)
+
+    return image
+
+
+def rotate_image(img, exif_dict):
+    if "exif" in img.info and piexif.ImageIFD.Orientation in exif_dict["0th"]:
+        orientation = exif_dict["0th"].pop(piexif.ImageIFD.Orientation)
+        if orientation == 2:
+            img = img.transpose(Image.FLIP_LEFT_RIGHT)
+        elif orientation == 3:
+            img = img.rotate(180)
+        elif orientation == 4:
+            img = img.rotate(180).transpose(Image.FLIP_LEFT_RIGHT)
+        elif orientation == 5:
+            img = img.rotate(-90).transpose(Image.FLIP_LEFT_RIGHT)
+        elif orientation == 6:
+            img = img.rotate(-90, expand=True)
+        elif orientation == 7:
+            img = img.rotate(90).transpose(Image.FLIP_LEFT_RIGHT)
+        elif orientation == 8:
+            img = img.rotate(90)
+
+    return (img, exif_dict)
+
+
+def build_license(license, author):
+    year = datetime.datetime.now().year
+    license_file = os.path.join(DEFAULT_CONFIG['plugin_dir'], 'licenses.json')
+
+    with open(license_file) as data_file:
+        licenses = json.load(data_file)
+
+    if any(license in k for k in licenses):
+        return licenses[license]['Text'].format(Author=author, Year=year, URL=licenses[license]['URL'])
+    else:
+        return 'Copyright {Year} {Author}, All Rights Reserved'.format(Author=author, Year=year)
+
+
+def manipulate_exif(img, settings):
+    try:
+        exif = piexif.load(img.info['exif'])
+    except Exception:
+        logger.debug('EXIF information not found')
+        exif = {}
+
+    if settings['PHOTO_EXIF_AUTOROTATE']:
+        img, exif = rotate_image(img, exif)
+
+    if settings['PHOTO_EXIF_REMOVE_GPS']:
+        exif.pop('GPS')
+
+    if settings['PHOTO_EXIF_COPYRIGHT']:
+
+        # We want to be minimally destructive to any preset exif author or copyright information.
+        # If there is copyright or author information prefer that over everything else.
+        if not exif['0th'].get(piexif.ImageIFD.Artist):
+            exif['0th'][piexif.ImageIFD.Artist] = settings['PHOTO_EXIF_COPYRIGHT_AUTHOR']
+            author = settings['PHOTO_EXIF_COPYRIGHT_AUTHOR']
+
+        if not exif['0th'].get(piexif.ImageIFD.Copyright):
+            license = build_license(settings['PHOTO_EXIF_COPYRIGHT'], author)
+            exif['0th'][piexif.ImageIFD.Copyright] = license
+
+    return (img, piexif.dump(exif))
+
+
+def resize_worker(orig, resized, spec, settings):
+    logger.info('photos: make photo {} -> {}'.format(orig, resized))
+    im = Image.open(orig)
+
+    if ispiexif and settings['PHOTO_EXIF_KEEP'] and im.format == 'JPEG':  # Only works with JPEG exif for sure.
+        try:
+            im, exif_copy = manipulate_exif(im, settings)
+        except:
+            logger.info('photos: no EXIF or EXIF error in {}'.format(orig))
+            exif_copy = b''
+    else:
+        exif_copy = b''
+
+    icc_profile = im.info.get("icc_profile", None)
+
+    if settings['PHOTO_SQUARE_THUMB'] and spec == settings['PHOTO_THUMB']:
+        im = ImageOps.fit(im, (spec[0], spec[1]), Image.ANTIALIAS)
+
+    im.thumbnail((spec[0], spec[1]), Image.ANTIALIAS)
+    directory = os.path.split(resized)[0]
+
+    if isalpha(im):
+        im = remove_alpha(im, settings['PHOTO_ALPHA_BACKGROUND_COLOR'])
+
+    if not os.path.exists(directory):
+        try:
+            os.makedirs(directory)
+        except Exception:
+            logger.exception('Could not create {}'.format(directory))
+    else:
+        logger.debug('Directory already exists at {}'.format(os.path.split(resized)[0]))
+
+    if settings['PHOTO_WATERMARK']:
+        isthumb = True if spec == settings['PHOTO_THUMB'] else False
+        if not isthumb or (isthumb and settings['PHOTO_WATERMARK_THUMB']):
+            im = watermark_photo(im, settings)
+
+    im.save(resized, 'JPEG', quality=spec[2], icc_profile=icc_profile, exif=exif_copy)
+
+
+def resize_photos(generator, writer):
+    if generator.settings['PHOTO_RESIZE_JOBS'] == -1:
+        debug = True
+        generator.settings['PHOTO_RESIZE_JOBS'] = 1
+    else:
+        debug = False
+
+    pool = multiprocessing.Pool(generator.settings['PHOTO_RESIZE_JOBS'])
+    logger.debug('Debug Status: {}'.format(debug))
+    for resized, what in DEFAULT_CONFIG['queue_resize'].items():
+        resized = os.path.join(generator.output_path, resized)
+        orig, spec = what
+        if (not os.path.isfile(resized) or os.path.getmtime(orig) > os.path.getmtime(resized)):
+            if debug:
+                resize_worker(orig, resized, spec, generator.settings)
+            else:
+                pool.apply_async(resize_worker, (orig, resized, spec, generator.settings))
+
+    pool.close()
+    pool.join()
+
+
+def detect_content(content):
+    hrefs = None
+
+    def replacer(m):
+        what = m.group('what')
+        value = m.group('value')
+        tag = m.group('tag')
+        output = m.group(0)
+
+        if what in ('photo', 'lightbox'):
+            if value.startswith('/'):
+                value = value[1:]
+
+            path = os.path.join(
+                os.path.expanduser(settings['PHOTO_LIBRARY']),
+                value
+            )
+
+            if os.path.isfile(path):
+                photo_prefix = os.path.splitext(value)[0].lower()
+
+                if what == 'photo':
+                    photo_article = photo_prefix + 'a.jpg'
+                    enqueue_resize(
+                        path,
+                        os.path.join('photos', photo_article),
+                        settings['PHOTO_ARTICLE']
+                    )
+
+                    output = ''.join((
+                        '<',
+                        m.group('tag'),
+                        m.group('attrs_before'),
+                        m.group('src'),
+                        '=',
+                        m.group('quote'),
+                        os.path.join(settings['SITEURL'], 'photos', photo_article),
+                        m.group('quote'),
+                        m.group('attrs_after'),
+                    ))
+
+                elif what == 'lightbox' and tag == 'img':
+                    photo_gallery = photo_prefix + '.jpg'
+                    enqueue_resize(
+                        path,
+                        os.path.join('photos', photo_gallery),
+                        settings['PHOTO_GALLERY']
+                    )
+
+                    photo_thumb = photo_prefix + 't.jpg'
+                    enqueue_resize(
+                        path,
+                        os.path.join('photos', photo_thumb),
+                        settings['PHOTO_THUMB']
+                    )
+
+                    lightbox_attr_list = ['']
+
+                    gallery_name = value.split('/')[0]
+                    lightbox_attr_list.append('{}="{}"'.format(
+                        settings['PHOTO_LIGHTBOX_GALLERY_ATTR'],
+                        gallery_name
+                    ))
+
+                    captions = read_notes(
+                        os.path.join(os.path.dirname(path), 'captions.txt'),
+                        msg = 'photos: No captions for gallery'
+                    )
+                    caption = captions.get(os.path.basename(path)) if captions else None
+                    if caption:
+                        lightbox_attr_list.append('{}="{}"'.format(
+                            settings['PHOTO_LIGHTBOX_CAPTION_ATTR'],
+                            caption
+                        ))
+
+                    lightbox_attrs = ' '.join(lightbox_attr_list)
+
+                    output = ''.join((
+                        '<a href=',
+                        m.group('quote'),
+                        os.path.join(settings['SITEURL'], 'photos', photo_gallery),
+                        m.group('quote'),
+                        lightbox_attrs,
+                        '><img',
+                        m.group('attrs_before'),
+                        'src=',
+                        m.group('quote'),
+                        os.path.join(settings['SITEURL'], 'photos', photo_thumb),
+                        m.group('quote'),
+                        m.group('attrs_after'),
+                        '</a>'
+                    ))
+
+            else:
+                logger.error('photos: No photo %s', path)
+
+        return output
+
+    if hrefs is None:
+        regex = r"""
+            <\s*
+            (?P<tag>[^\s\>]+)  # detect the tag
+            (?P<attrs_before>[^\>]*)
+            (?P<src>href|src)  # match tag with src and href attr
+            \s*=
+            (?P<quote>["\'])  # require value to be quoted
+            (?P<path>{0}(?P<value>.*?))  # the url value
+            (?P=quote)
+            (?P<attrs_after>[^\>]*>)
+        """.format(
+            content.settings['INTRASITE_LINK_REGEX']
+        )
+        hrefs = re.compile(regex, re.X)
+
+    if content._content and ('{photo}' in content._content or '{lightbox}' in content._content):
+        settings = content.settings
+        content._content = hrefs.sub(replacer, content._content)
+
+
+def galleries_string_decompose(gallery_string):
+    splitter_regex = re.compile(r'[\s,]*?({photo}|{filename})')
+    title_regex = re.compile(r'{(.+)}')
+    galleries = map(unicode.strip if sys.version_info.major == 2 else str.strip, filter(None, splitter_regex.split(gallery_string)))
+    galleries = [gallery[1:] if gallery.startswith('/') else gallery for gallery in galleries]
+    if len(galleries) % 2 == 0 and ' ' not in galleries:
+        galleries = zip(zip(['type'] * len(galleries[0::2]), galleries[0::2]), zip(['location'] * len(galleries[0::2]), galleries[1::2]))
+        galleries = [dict(gallery) for gallery in galleries]
+        for gallery in galleries:
+            title = re.search(title_regex, gallery['location'])
+            if title:
+                gallery['title'] = title.group(1)
+                gallery['location'] = re.sub(title_regex, '', gallery['location']).strip()
+            else:
+                gallery['title'] = DEFAULT_CONFIG['PHOTO_GALLERY_TITLE']
+        return galleries
+    else:
+        logger.error('Unexpected gallery location format! \n{}'.format(pprint.pformat(galleries)))
+
+
+def process_gallery(generator, content, location):
+    content.photo_gallery = []
+
+    galleries = galleries_string_decompose(location)
+
+    for gallery in galleries:
+
+        if gallery['location'] in DEFAULT_CONFIG['created_galleries']:
+            content.photo_gallery.append((gallery['location'], DEFAULT_CONFIG['created_galleries'][gallery]))
+            continue
+
+        if gallery['type'] == '{photo}':
+            dir_gallery = os.path.join(os.path.expanduser(generator.settings['PHOTO_LIBRARY']), gallery['location'])
+            rel_gallery = gallery['location']
+        elif gallery['type'] == '{filename}':
+            base_path = os.path.join(generator.path, content.relative_dir)
+            dir_gallery = os.path.join(base_path, gallery['location'])
+            rel_gallery = os.path.join(content.relative_dir, gallery['location'])
+
+        if os.path.isdir(dir_gallery):
+            logger.info('photos: Gallery detected: {}'.format(rel_gallery))
+            dir_photo = os.path.join('photos', rel_gallery.lower())
+            dir_thumb = os.path.join('photos', rel_gallery.lower())
+            exifs = read_notes(os.path.join(dir_gallery, 'exif.txt'),
+                               msg='photos: No EXIF for gallery')
+            captions = read_notes(os.path.join(dir_gallery, 'captions.txt'), msg='photos: No captions for gallery')
+            blacklist = read_notes(os.path.join(dir_gallery, 'blacklist.txt'), msg='photos: No blacklist for gallery')
+            content_gallery = []
+
+            title = gallery['title']
+            for pic in sorted(os.listdir(dir_gallery)):
+                if pic.startswith('.'):
+                    continue
+                if pic.endswith('.txt'):
+                    continue
+                if pic in blacklist:
+                    continue
+                photo = os.path.splitext(pic)[0].lower() + '.jpg'
+                thumb = os.path.splitext(pic)[0].lower() + 't.jpg'
+                content_gallery.append((
+                    pic,
+                    os.path.join(dir_photo, photo),
+                    os.path.join(dir_thumb, thumb),
+                    exifs.get(pic, ''),
+                    captions.get(pic, '')))
+
+                enqueue_resize(
+                    os.path.join(dir_gallery, pic),
+                    os.path.join(dir_photo, photo),
+                    generator.settings['PHOTO_GALLERY'])
+                enqueue_resize(
+                    os.path.join(dir_gallery, pic),
+                    os.path.join(dir_thumb, thumb),
+                    generator.settings['PHOTO_THUMB'])
+
+            content.photo_gallery.append((title, content_gallery))
+            logger.debug('Gallery Data: '.format(pprint.pformat(content.photo_gallery)))
+            DEFAULT_CONFIG['created_galleries']['gallery'] = content_gallery
+        else:
+            logger.error('photos: Gallery does not exist: {} at {}'.format(gallery['location'], dir_gallery))
+
+
+def detect_gallery(generator, content):
+    if 'gallery' in content.metadata:
+        gallery = content.metadata.get('gallery')
+        if gallery.startswith('{photo}') or gallery.startswith('{filename}'):
+            process_gallery(generator, content, gallery)
+        elif gallery:
+            logger.error('photos: Gallery tag not recognized: {}'.format(gallery))
+
+
+def image_clipper(x):
+    return x[8:] if x[8] == '/' else x[7:]
+
+
+def file_clipper(x):
+    return x[11:] if x[10] == '/' else x[10:]
+
+
+def process_image(generator, content, image):
+    if image.startswith('{photo}'):
+        path = os.path.join(os.path.expanduser(generator.settings['PHOTO_LIBRARY']), image_clipper(image))
+        image = image_clipper(image)
+    elif image.startswith('{filename}'):
+        path = os.path.join(generator.path, content.relative_dir, file_clipper(image))
+        image = file_clipper(image)
+
+    if os.path.isfile(path):
+        photo = os.path.splitext(image)[0].lower() + 'a.jpg'
+        thumb = os.path.splitext(image)[0].lower() + 't.jpg'
+        content.photo_image = (
+            os.path.basename(image).lower(),
+            os.path.join('photos', photo),
+            os.path.join('photos', thumb))
+        enqueue_resize(
+            path,
+            os.path.join('photos', photo),
+            generator.settings['PHOTO_ARTICLE'])
+        enqueue_resize(
+            path,
+            os.path.join('photos', thumb),
+            generator.settings['PHOTO_THUMB'])
+    else:
+        logger.error('photo: No photo for {} at {}'.format(content.source_path, path))
+
+
+def detect_image(generator, content):
+    image = content.metadata.get('image', None)
+    if image:
+        if image.startswith('{photo}') or image.startswith('{filename}'):
+            process_image(generator, content, image)
+        else:
+            logger.error('photos: Image tag not recognized: {}'.format(image))
+
+
+def detect_images_and_galleries(generators):
+    """Runs generator on both pages and articles."""
+    for generator in generators:
+        if isinstance(generator, ArticlesGenerator):
+            for article in itertools.chain(generator.articles, generator.translations, generator.drafts):
+                detect_image(generator, article)
+                detect_gallery(generator, article)
+        elif isinstance(generator, PagesGenerator):
+            for page in itertools.chain(generator.pages, generator.translations, generator.hidden_pages):
+                detect_image(generator, page)
+                detect_gallery(generator, page)
+
+
+def register():
+    """Uses the new style of registration based on GitHub Pelican issue #314."""
+    signals.initialized.connect(initialized)
+    try:
+        signals.content_object_init.connect(detect_content)
+        signals.all_generators_finalized.connect(detect_images_and_galleries)
+        signals.article_writer_finalized.connect(resize_photos)
+    except Exception as e:
+        logger.exception('Plugin failed to execute: {}'.format(pprint.pformat(e)))
diff --git a/requirements.txt b/theme/requirements.txt
similarity index 76%
rename from requirements.txt
rename to theme/requirements.txt
index fccedf6..b6f5391 100644
--- a/requirements.txt
+++ b/theme/requirements.txt
@@ -3,3 +3,5 @@
 pyScss==1.3.5
 slimit==0.8.1
 webassets==0.12.1
+Pillow
+piexif>=1.0.5
diff --git a/static/css/pygments.css b/theme/static/css/pygments.css
similarity index 100%
rename from static/css/pygments.css
rename to theme/static/css/pygments.css
diff --git a/static/css/style.scss b/theme/static/css/style.scss
similarity index 100%
rename from static/css/style.scss
rename to theme/static/css/style.scss
diff --git a/static/js/email.js b/theme/static/js/email.js
similarity index 100%
rename from static/js/email.js
rename to theme/static/js/email.js
diff --git a/theme/static/js/lw-timeago/README.md b/theme/static/js/lw-timeago/README.md
new file mode 100644
index 0000000..7d9a9eb
--- /dev/null
+++ b/theme/static/js/lw-timeago/README.md
@@ -0,0 +1,60 @@
+lw-timeago
+==========
+
+A lightweight implementation of the [Timeago jQuery plugin](http://timeago.yarp.com/), allowing fuzzy timestamps to be dynamically generated and displayed.
+
+Example
+-------
+
+#### Original
+```html
+<time datetime="2012-02-11T22:05:00-05:00">Feb 11, 2012</time>
+```
+
+#### Outputs
+```html
+<time title="Feb 11, 2012" datetime="2012-02-11T22:05:00-05:00">3 years ago</time>
+<time title="2/11/2012, 10:05:00 PM" datetime="2012-02-11T22:05:00-05:00">3 years ago on Feb 11, 2012</time>
+```
+
+Configuration
+-------------
+In the `config` section of the script there are a few options that can be configured.
+- `whitelist`: Only process `<time>` tags with this attribute. Setting it to `null` disables whitelisting (all `<time>` tags are processed).
+- `keepDate`: If `true`, don't replace the date in the `<time>` tags, prepend the fuzzy time to it (see the second example above).
+
+Additionally, the text can all be customized.
+
+Usage
+-----
+Step 2 can be skipped if `whitelist` is set to `null` in the config.
+
+1. Markup times in the HTML source with `<time>` tags, making sure they have a `datetime` attribute. The datetime attribute *MUST* be ISO 8601 formatted.
+2. For all `<time>` tags that should be converted to fuzzy times, add `data-timeago` attributes to them.
+3. Include the `lw-timeago.js` file in the html head (`<script src="lw-timeago.js" type="text/javascript"></script>`).
+4. Call the `lw_timeago()` function when the page loads (`<script type="text/javascript">window.addEventListener("load", lw_timeago);</script>`).
+
+License
+-------
+```
+Copyright (c) 2008-2015, Ryan McGeary
+Copyright (c) 2015 Carey Metcalfe
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+```
diff --git a/theme/static/js/lw-timeago/lw-timeago.js b/theme/static/js/lw-timeago/lw-timeago.js
new file mode 100644
index 0000000..04a5c12
--- /dev/null
+++ b/theme/static/js/lw-timeago/lw-timeago.js
@@ -0,0 +1,91 @@
+var lw_timeago = function() {
+
+  var config = {
+    whitelist: "data-timeago", // Set to null to disable whitelisting
+
+    keepDate: true, // If true, appends the original date after the fuzzy one
+
+    suffixAgo: "ago",
+    suffixFromNow: "from now",
+    on: "on",
+
+    seconds: "less than a minute",
+    minute: "about a minute",
+    minutes: "%d minutes",
+    hour: "about an hour",
+    hours: "%d hours",
+    day: "about a day",
+    days: "%d days",
+    month: "about a month",
+    months: "%d months",
+    year: "about a year",
+    years: "%d years",
+  }
+
+  function inWords(distanceMillis) {
+    // Produce a string representing the milliseconds in a human-readable way
+
+    var suffix = distanceMillis < 0 ? config.suffixFromNow : config.suffixAgo;
+    var seconds = Math.abs(distanceMillis) / 1000;
+    var minutes = seconds / 60;
+    var hours = minutes / 60;
+    var days = hours / 24;
+    var years = days / 365;
+
+    function substitute(string, number) {
+      return string.replace(/%d/i, number);
+    }
+
+    var words =
+      seconds < 45 && substitute(config.seconds, Math.round(seconds)) ||
+      seconds < 90 && substitute(config.minute, 1) ||
+      minutes < 45 && substitute(config.minutes, Math.round(minutes)) ||
+      minutes < 90 && substitute(config.hour, 1) ||
+      hours < 24 && substitute(config.hours, Math.round(hours)) ||
+      hours < 42 && substitute(config.day, 1) ||
+      days < 30 && substitute(config.days, Math.round(days)) ||
+      days < 45 && substitute(config.month, 1) ||
+      days < 365 && substitute(config.months, Math.round(days / 30)) ||
+      years < 1.5 && substitute(config.year, 1) ||
+      substitute(config.years, Math.round(years));
+
+    return words + " " + suffix;
+  }
+
+  function diff(timestamp) {
+    // Get the number of milliseconds distance from the current time
+    return Date.now() - timestamp;
+  }
+
+  function doReplace(){
+    // Go over all <time> elements, grab the datetime attribute, then calculate
+    // and display a fuzzy representation of it.
+
+    var times = document.getElementsByTagName("time")
+    for (var i = 0; i < times.length; i++){
+
+      if (config.whitelist && !times[i].hasAttribute(config.whitelist))
+        break;
+
+      var datetime = times[i].getAttribute("datetime");
+      if (!datetime)
+        break;
+
+      var parsed = new Date(datetime);
+      if (!parsed)
+        break;
+
+      var words = inWords(diff(parsed.getTime()));
+      var title = times[i].innerHTML;
+      if (config.keepDate){
+        words += " " + config.on + " " + times[i].innerHTML;
+        title = parsed.toLocaleString()
+      }
+
+      times[i].title = title;
+      times[i].innerHTML = words;
+    }
+  }
+
+  return doReplace;
+}();
diff --git a/static/js/scroll.js b/theme/static/js/scroll.js
similarity index 100%
rename from static/js/scroll.js
rename to theme/static/js/scroll.js
diff --git a/templates/archives.html b/theme/templates/archives.html
similarity index 100%
rename from templates/archives.html
rename to theme/templates/archives.html
diff --git a/templates/article.html b/theme/templates/article.html
similarity index 81%
rename from templates/article.html
rename to theme/templates/article.html
index 103931e..11e32c5 100644
--- a/templates/article.html
+++ b/theme/templates/article.html
@@ -18,10 +18,23 @@
   {% endif %}
   <div class="article_title">
     <h1><a href="{{ SITEURL }}/{{ article.url }}" class="nohover">{{ article.title }}</a></h1>
+  {% if DISPLAY_SUBTITLE %}
+    <h4>{{ article.subtitle }}</a></h4>
+  {% endif %}
   </div>
   <div class="article_text">
     {{ article.content }}
   </div>
+  {% if article.photo_gallery %}
+  <div class="gallery">
+  {% for title, gallery in article.photo_gallery %}
+    <h1>{{ title }}</h1>
+    {% for name, photo, thumb, exif, caption in gallery %}
+      <a href="{{ SITEURL }}/{{ photo }}" title="{{ name }}" exif="{{ exif }}" caption="{{ caption }}"><img src="{{ SITEURL }}/{{ thumb }}"></a>
+    {% endfor %}
+  {% endfor %}
+  </div>
+  {% endif %}
   {% if not DISPLAY_META_ABOVE_ARTICLE %}
     {% include "modules/article_meta.html" %}
   {% endif %}
diff --git a/templates/author.html b/theme/templates/author.html
similarity index 100%
rename from templates/author.html
rename to theme/templates/author.html
diff --git a/templates/authors.html b/theme/templates/authors.html
similarity index 100%
rename from templates/authors.html
rename to theme/templates/authors.html
diff --git a/templates/base.html b/theme/templates/base.html
similarity index 88%
rename from templates/base.html
rename to theme/templates/base.html
index 3d69679..b59631a 100644
--- a/templates/base.html
+++ b/theme/templates/base.html
@@ -9,11 +9,11 @@
 
   {% block base %}{% endblock %}
   {% assets filters="pyscss,cssmin", output="css/styles.%(version)s.min.css", "css/style.scss", "css/pygments.css" %}
-  <link rel="stylesheet" type="text/css" href="{{ SITEURL }}/{{ ASSET_URL }}">
+  <link rel="stylesheet" type="text/css" href="{{ THEMEURL }}/{{ ASSET_URL }}">
   {% endassets %}
 
   {% assets filters="slimit", output="js/scripts.%(version)s.min.js", "js/lw-timeago/lw-timeago.js", "js/scroll.js", "js/email.js" %}
-  <script src="{{ SITEURL }}/{{ ASSET_URL }}" type="text/javascript"></script>
+  <script src="{{ THEMEURL }}/{{ ASSET_URL }}" type="text/javascript"></script>
   {% endassets%}
 
   <link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.6.3/css/font-awesome.min.css" rel="stylesheet">
@@ -88,6 +88,7 @@
 
   {%- elif '.stackexchange.com' in temp %}{% set class = 'fa-stack-exchange' %}
   {% elif '.tumblr.com' in temp %}{% set class = 'fa-tumblr' %}
+  {% elif 'debian.org' in temp %}{% set class = 'fa fa-linux fa-lg' %}
   {% endif -%}
 
   <i class="fa {{ class }} fa-lg"></i>
@@ -110,17 +111,21 @@
         {% endif %}
       </a>
       <h2><a href="{{ SITEURL }}" class="nohover">{{ SITENAME }}</a></h2>
-      <p>{{ TAGLINE }}</p>
+      {{ TAGLINE }}
+      {% if SOCIAL|length > 1 %}
       <div class="social">
         {% for name, link in SOCIAL %}
-          {{ display_link(name, link, "") }}
+        {{ display_link(name, link, "") }}
         {% endfor %}
       </div>
+      {% endif %}
+      {% if LINKS|length > 1 %}
       <ul>
         {% for name, link in LINKS %}
         <li>{{ display_link(name, link, name) }}</li>
         {% endfor %}
       </ul>
+      {% endif %}
     </div>
   </aside>
 
@@ -138,7 +143,7 @@
       {% endfor %}
       {% endif %}
       {% if INDEX_SAVE_AS and INDEX_SAVE_AS != "index.html" %}
-      &#124; <a href="{{ SITEURL }}/{{ INDEX_SAVE_AS }}">Blog</a>
+      &#124; <a href="{{ SITEURL }}/{{ INDEX_LINK_AS }}">Blog</a>
       {% endif %}
       {% if FEED_ALL_ATOM %}
       &#124; <a href="{{ FEED_DOMAIN }}/{{ FEED_ALL_ATOM }}">Atom Feed</a>
@@ -146,6 +151,9 @@
       {% if FEED_ALL_RSS %}
       &#124; <a href="{{ FEED_DOMAIN }}/{{ FEED_ALL_RSS }}">RSS Feed</a>
       {% endif %}
+      {% for title, link in MENUITEMS %}
+            &#124; <a href="{{ SITEURL }}/{{ link }}">{{ title }}</a>
+      {% endfor %}
       </p>
       {% endblock header %}
       {% block subheader %}{% include "modules/blogsubheader.html" %}{% endblock subheader %}
@@ -158,7 +166,7 @@
     {% endblock %}
 
     <div id="ending_message">
-        <p>&copy; {{ AUTHOR }}. Built using <a href="http://getpelican.com">Pelican</a>. Theme is <a href="https://github.com/pR0Ps/pelican-subtle">subtle</a> by <a href="http://cmetcalfe.ca">Carey Metcalfe</a>. Based on <a href="https://github.com/giulivo/pelican-svbhack">svbhack</a> by Giulio Fidente.</p>
+        <p>{{ SITENAME }}. Built using <a href="http://getpelican.com">Pelican</a>. Theme is <a href="https://github.com/pR0Ps/pelican-subtle">subtle</a> by <a href="http://cmetcalfe.ca">Carey Metcalfe</a>. Based on <a href="https://github.com/giulivo/pelican-svbhack">svbhack</a> by Giulio Fidente.</p>
     </div>
   </main>
 
diff --git a/templates/categories.html b/theme/templates/categories.html
similarity index 100%
rename from templates/categories.html
rename to theme/templates/categories.html
diff --git a/templates/category.html b/theme/templates/category.html
similarity index 100%
rename from templates/category.html
rename to theme/templates/category.html
diff --git a/templates/error.html b/theme/templates/error.html
similarity index 100%
rename from templates/error.html
rename to theme/templates/error.html
diff --git a/templates/index.html b/theme/templates/index.html
similarity index 65%
rename from templates/index.html
rename to theme/templates/index.html
index 1d1066f..099ed0f 100644
--- a/templates/index.html
+++ b/theme/templates/index.html
@@ -3,12 +3,10 @@
 {% block title %}Blog | {{ SITENAME }}{% endblock %}
 
 {% block content %}
-{% for article in articles_page.object_list %}
+{% for article in articles_page.object_list|sort(attribute='no') %}
 <article>
   <div class="article_title">
-    <h1><a href="{{ SITEURL }}/{{ article.url }}">{{ article.title }}</a></h1>
-  </div>
-  <div class="article_text">
+    <h3><a href="{{ BLOGURL }}/{{ article.url }}">{{ article.title }}</a></h3>
     {{ article.summary }}
   </div>
 </article>
@@ -18,8 +16,10 @@
 {% endfor %}
 {% endblock %}
 
+{% if BLOCK_FOOTER %}
 {% block footer %}
 <footer>
 {% include "modules/pagination.html" %}
 </footer>
 {% endblock %}
+{% endif %}
diff --git a/theme/templates/invite.html b/theme/templates/invite.html
new file mode 100644
index 0000000..e73aede
--- /dev/null
+++ b/theme/templates/invite.html
@@ -0,0 +1,53 @@
+{% extends "base.html" %}
+{% block title %}{{ page.title|striptags|escape }} | {{ SITENAME }}{% endblock %}
+
+{% block subheader %}{% endblock %}
+
+{% block content %}
+
+<article>
+  <div class="article_title">
+    <h1><a href="{{ SITEURL }}/{{ page.url }}" class="nohover">{{ page.title }}</a></h1>
+  </div>
+  <div class="article_text">
+    {{ page.content }}
+  </div>
+  <div class="article_text">
+    <?php echo form_open('invite/view', 'id = inviteform'); ?>
+    <fieldset class="field">
+      <div class="form_errors"><?php echo form_error('username'); ?></div>
+      <input class="input" type="text" name="username" placeholder="Username" id="username">
+    </fieldset>
+
+    <fieldset class="field">
+      <div class="form_errors"><?php echo form_error('password'); ?></div>
+      <input class="input" type="password" name="password" placeholder="Password" id="password">
+    </fieldset>
+
+    <fieldset class="field">
+      <div class="form_errors"><?php echo form_error('passconf'); ?></div>
+      <input class="input" type="password" name="passconf" placeholder="Repeat password" id="passconf">
+    </fieldset>
+
+    <fieldset class="field">
+      <div class="form_errors"><?php echo form_error('email'); ?></div>
+      <input class="input" type="email" name="email" placeholder="example@domain.com" id="email">
+    </fieldset>
+
+    <fieldset class="field">
+      <input class="button submit" type="submit" value="Invite me">
+    </fieldset>
+
+    <fieldset class="field">
+      <div class="form_errors"><?php echo form_error('submit_msg'); ?></div>
+      <div class="submit_msg"><?php echo form_message(); ?></div>
+      <input type="hidden" name="submit_msg" id="submit_msg">
+    </fieldset>
+    </form>
+  </div>
+</article>
+{% endblock %}
+
+{% block scripts %}
+{{ super() }}
+{% endblock scripts %}
diff --git a/theme/templates/login.html b/theme/templates/login.html
new file mode 100644
index 0000000..1d5dba0
--- /dev/null
+++ b/theme/templates/login.html
@@ -0,0 +1,43 @@
+{% extends "base.html" %}
+{% block title %}{{ page.title|striptags|escape }} | {{ SITENAME }}{% endblock %}
+
+{% block subheader %}{% endblock %}
+
+{% block content %}
+
+<article>
+  <div class="article_title">
+    <h1><a href="{{ SITEURL }}/{{ page.url }}" class="nohover">{{ page.title }}</a></h1>
+  </div>
+  <div class="article_text">
+    {{ page.content }}
+  </div>
+  <div class="article_text">
+    <?php echo form_open('login/view', 'id = loginform'); ?>
+    <fieldset class="field">
+      <div class="form_errors"><?php echo form_error('username'); ?></div>
+      <input class="input" type="text" name="username" placeholder="Username" id="username">
+    </fieldset>
+
+    <fieldset class="field">
+      <div class="form_errors"><?php echo form_error('password'); ?></div>
+      <input class="input" type="password" name="password" placeholder="Password" id="password">
+    </fieldset>
+
+    <fieldset class="field">
+      <input class="button submit" type="submit" value="Login">
+    </fieldset>
+
+    <fieldset class="field">
+      <div class="form_errors"><?php echo form_error('submit_msg'); ?></div>
+      <div class="submit_msg"><?php echo form_message(); ?></div>
+      <input type="hidden" name="submit_msg" id="submit_msg">
+    </fieldset>
+    </form>
+  </div>
+</article>
+{% endblock %}
+
+{% block scripts %}
+{{ super() }}
+{% endblock scripts %}
diff --git a/templates/modules/analytics.html b/theme/templates/modules/analytics.html
similarity index 100%
rename from templates/modules/analytics.html
rename to theme/templates/modules/analytics.html
diff --git a/templates/modules/article_meta.html b/theme/templates/modules/article_meta.html
similarity index 100%
rename from templates/modules/article_meta.html
rename to theme/templates/modules/article_meta.html
diff --git a/templates/modules/blogsubheader.html b/theme/templates/modules/blogsubheader.html
similarity index 70%
rename from templates/modules/blogsubheader.html
rename to theme/templates/modules/blogsubheader.html
index 3bb3fbf..9ba047d 100644
--- a/templates/modules/blogsubheader.html
+++ b/theme/templates/modules/blogsubheader.html
@@ -1,9 +1,9 @@
 <p>
-{% if INDEX_SAVE_AS %}
-<a href="{{ SITEURL }}/{{ INDEX_SAVE_AS }}">Posts</a>
+<!--{% if INDEX_SAVE_AS %}
+<a href="{{ SITEURL }}/{{ INDEX_SAVE_AS }}">Blog</a>
 {% else %}
-<a href="{{ SITEURL }}">Posts</a>
-{% endif %}
+<a href="{{ SITEURL }}">Blog</a>
+{% endif %} NOTE: This will be redundant now. -->
 {% if TAGS_URL %}
 &#124; <a href="{{ SITEURL }}/{{ TAGS_URL }}">Tags</a>
 {% endif %}
diff --git a/templates/modules/pagination.html b/theme/templates/modules/pagination.html
similarity index 100%
rename from templates/modules/pagination.html
rename to theme/templates/modules/pagination.html
diff --git a/templates/page.html b/theme/templates/page.html
similarity index 100%
rename from templates/page.html
rename to theme/templates/page.html
diff --git a/templates/tag.html b/theme/templates/tag.html
similarity index 100%
rename from templates/tag.html
rename to theme/templates/tag.html
diff --git a/templates/tags.html b/theme/templates/tags.html
similarity index 100%
rename from templates/tags.html
rename to theme/templates/tags.html