diff --git a/README.md b/README.md
index 8f9cb16..c7859d5 100644
--- a/README.md
+++ b/README.md
@@ -22,4 +22,42 @@ All other information like details on groups, projects and recurring events shou
## Usage
-TODO
+### Build and Deploy
+
+Running the hugo command without and parameters will re-generate the site in the `public` directory.
+To deploy the website, just copy the whole folder to a directory which is servered by the webserver of your preference.
+
+Please note, that the website should be re-deployed at least daily to update the "announcement" section on the front page even if there were no changed to the content.
+
+### Add Pages
+
+To run a local version, just install HUGO by following the [instructions](https://gohugo.io/installation/) for your operating system.
+To build the website and run a development webserver, execute:
+```shell
+hugo server
+```
+
+To also build posts in the future or in draft state, run this instead:
+```shell
+hugo server -D
+```
+
+#### Add an Event Announcement
+
+There are two basic types of posts: Events and articles.
+Events will be shown on the home page from their publishing date until they have happened.
+
+To create a new event blog post, run a command like this:
+```shell
+hugo new content --king event blog/your-event-title.md
+```
+
+#### Add a Blog Entry
+
+As mentioned before, you can also create blog posts for things which aren't events.
+They will only be shown in the "blog" section and posted to the RSS feeds.
+
+To create a new general blog post, run a command like this:
+```shell
+hugo new content --king article blog/your-article-title.md
+```
diff --git a/content/_index.md b/content/_index.md
new file mode 100644
index 0000000..3fb14ce
--- /dev/null
+++ b/content/_index.md
@@ -0,0 +1,8 @@
+---
+title: "Home"
+date: 2022-11-20T09:03:20-08:00
+draft: false
+---
+TODO: Hallo, wir sind der CCC Hamburg...
+
+TOOD: Was wir machen und wer wir sind
diff --git a/content/blog/_index.md b/content/blog/_index.md
new file mode 100644
index 0000000..4c429de
--- /dev/null
+++ b/content/blog/_index.md
@@ -0,0 +1,7 @@
+---
+title: "Blog"
+draft: false
+menu: main
+---
+
+TODO: Provide filters by type (announcement, post)
diff --git a/hugo.toml b/hugo.toml
new file mode 100644
index 0000000..5f6c940
--- /dev/null
+++ b/hugo.toml
@@ -0,0 +1,4 @@
+baseURL = 'https://hamburg.ccc.de/'
+languageCode = 'de-de'
+title = 'CCC Hansestadt Hamburg e.V.'
+theme = 'ccchh'
diff --git a/themes/ccchh/LICENSE b/themes/ccchh/LICENSE
new file mode 100644
index 0000000..17993f6
--- /dev/null
+++ b/themes/ccchh/LICENSE
@@ -0,0 +1,20 @@
+The MIT License (MIT)
+
+Copyright (c) 2023 YOUR_NAME_HERE
+
+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/themes/ccchh/archetypes/article.md b/themes/ccchh/archetypes/article.md
new file mode 100644
index 0000000..8aa30b2
--- /dev/null
+++ b/themes/ccchh/archetypes/article.md
@@ -0,0 +1,8 @@
+---
+kind: article
+title: '{{ replace .File.ContentBaseName `-` ` ` | title }}'
+date: '{{ .Date }}'
+draft: true
+---
+
+TODO: Write your blog post
diff --git a/themes/ccchh/archetypes/default.md b/themes/ccchh/archetypes/default.md
new file mode 100644
index 0000000..26f317f
--- /dev/null
+++ b/themes/ccchh/archetypes/default.md
@@ -0,0 +1,5 @@
+---
+title: "{{ replace .Name "-" " " | title }}"
+date: {{ .Date }}
+draft: true
+---
diff --git a/themes/ccchh/archetypes/event.md b/themes/ccchh/archetypes/event.md
new file mode 100644
index 0000000..287f8ec
--- /dev/null
+++ b/themes/ccchh/archetypes/event.md
@@ -0,0 +1,9 @@
+---
+kind: event
+title: '{{ replace .File.ContentBaseName `-` ` ` | title }}'
+date: '{{ .Date }}' # date of the event
+publishDate: '{{ .Date }}' # when to publish
+draft: true
+---
+
+TODO: Describe your event here
diff --git a/themes/ccchh/assets/images/logo.svg b/themes/ccchh/assets/images/logo.svg
new file mode 100644
index 0000000..7198c3d
--- /dev/null
+++ b/themes/ccchh/assets/images/logo.svg
@@ -0,0 +1,59 @@
+
+
diff --git a/themes/ccchh/assets/js/roomstate.js b/themes/ccchh/assets/js/roomstate.js
new file mode 100644
index 0000000..a70e878
--- /dev/null
+++ b/themes/ccchh/assets/js/roomstate.js
@@ -0,0 +1,84 @@
+import timeDistance, { deLocale } from 'js/util/timeDistance'
+
+const spaceapiUrl = "https://www.hamburg.ccc.de/dooris/status.json"
+const interval_ms = 60000
+
+const classNameOpen = "open"
+const classNameClosed = "closed"
+
+
+function updateRoomState(openState, lastChangeTimestamp) {
+ console.debug("SpaceAPI: Setting room " + openState + " since " + lastChangeTimestamp)
+
+ const roomstate = document.querySelector('#roomstate')
+ var statusItem = roomstate.querySelector(".state")
+ var durationItem = roomstate.querySelector(".duration")
+
+ if (openState === null || lastChangeTimestamp === null) {
+ console.warn("SpaceAPI: Room state unknown");
+ statusItem.textContent = "unbekannt"
+ roomstate.classList.remove(classNameOpen)
+ roomstate.classList.remove(classNameClosed)
+ durationItem.textContent = "(seit unbekannt)"
+
+ } else {
+ if (openState) {
+ statusItem.textContent = "offen"
+ roomstate.classList.remove(classNameClosed)
+ roomstate.classList.add(classNameOpen)
+
+ } else {
+ statusItem.textContent = "geschlossen"
+ roomstate.classList.remove(classNameOpen)
+ roomstate.classList.add(classNameClosed)
+
+ }
+
+ const changeDate = new Date(lastChangeTimestamp * 1000)
+ const changeText = timeDistance(deLocale).inWords(changeDate)
+
+ durationItem.textContent = "(" + changeText + ")"
+ }
+}
+
+function parseResponse(apiJson) {
+ console.debug('SpaceAPI got:', apiJson)
+
+ if (typeof apiJson.api === "undefined") {
+ console.error("SpaceAPI JSON invalid: 'api' item not present")
+ return [null, null]
+ }
+ if (typeof apiJson.state === "undefined") {
+ console.error("SpaceAPI JSON invalid: 'state' item not present")
+ return [null, null]
+ }
+ if (typeof apiJson.state.open === "undefined") {
+ console.error("SpaceAPI JSON invalid: 'state.open' item not present")
+ return [null, null]
+ }
+
+ console.debug('SpaceAPI state:', apiJson.state)
+ var openState = (apiJson.state.open == true)
+ var lastchange = null
+
+ if (apiJson.state.lastchange !== "undefined") {
+ lastchange = apiJson.state.lastchange
+ }
+
+ return [openState, lastchange]
+}
+
+function update() {
+ fetch(spaceapiUrl)
+ .then(resp => resp.json())
+ .then(function (data) {
+ var state = parseResponse(data)
+ updateRoomState(state[0], state[1])
+ })
+ .catch(err => { throw err });
+}
+
+window.onload = function () {
+ update()
+ window.setTimeout(update, interval_ms)
+}
diff --git a/themes/ccchh/assets/js/util/timeDistance.js b/themes/ccchh/assets/js/util/timeDistance.js
new file mode 100644
index 0000000..ab578c3
--- /dev/null
+++ b/themes/ccchh/assets/js/util/timeDistance.js
@@ -0,0 +1,104 @@
+export default ((locale) => {
+ const self = {}
+
+ if (locale === undefined) {
+ self.locales = {
+ pastPrefix: '',
+ pastSufix: 'ago',
+
+ futurePrefix: 'in',
+ futureSufix: '',
+
+ seconds: '%p less than a minutei %s',
+ minute: '%p about a minute %s',
+ minutes: '%p %d minutes %s',
+ hour: '%p about an hour %s',
+ hours: '%p about %d%r hours %s',
+ day: '%p a day %s',
+ days: '%p %d%r days %s',
+ month: '%p about a month %s',
+ months: '%p %d%r months %s',
+ year: '%p about a year %s',
+ years: '%p %d%r years %s'
+ }
+ } else {
+ self.locales = locale
+ }
+
+ self.inWords = (timeDistance, start) => {
+ if (start === undefined) {
+ start = new Date()
+ } else {
+ start = start instanceof(Date) ? start : parseInt(start)
+ }
+ timeDistance = timeDistance instanceof(Date) ? timeDistance : parseInt(timeDistance)
+ const prefix = timeDistance < new Date() ? self.locales.pastPrefix : self.locales.futurePrefix
+ const sufix = timeDistance < new Date() ? self.locales.pastSufix : self.locales.futureSufix
+
+ let seconds = Math.abs(Math.floor((start - timeDistance) / 1000)),
+ interval = 0,
+ rest = 0,
+ intervals = {
+ year: seconds / 31536000,
+ month: seconds / 2592000,
+ day: seconds / 86400,
+ hour: seconds / 3600,
+ minute: seconds / 60
+ }
+
+ let distance = self.locales.seconds
+ let key
+
+ for (key in intervals) {
+ interval = Math.floor(intervals[key])
+ rest = intervals[key] - interval
+
+ if (interval >= 1) {
+ distance = self.locales[key + 's']
+ break
+ } else if (interval === 1) {
+ distance = self.locales[key]
+ break
+ }
+ }
+
+ if (rest > 0.5) {
+ distance = distance.replace(/%r/, "½")
+ } else if (rest > 0.25) {
+ distance = distance.replace(/%r/, "¼")
+ } else {
+ if (interval === 1) {
+ distance = self.locales[key]
+ }
+ distance = distance.replace(/\s*%r/, "")
+ }
+
+ distance = distance.replace(/%d/i, interval)
+ distance = distance.replace(/%p/i, prefix)
+ distance = distance.replace(/%s/i, sufix)
+
+ return distance.trim()
+ }
+
+ return self
+})
+
+export const deLocale = {
+ pastPrefix: 'seit',
+ pastSufix: '',
+
+ futurePrefix: 'in',
+ futureSufix: '',
+
+ seconds: '%p %d Sekunden',
+ minute: '%p einer Minute',
+ minutes: '%p %d Minuten',
+ hour: '%p einer Stunde',
+ hours: '%p %d%r Stunden',
+ day: '%p einem Tag',
+ days: '%p %d%r Tagen',
+ month: '%p einem Monat',
+ months: '%p %d%r Monaten',
+ year: '%p einem Jahr',
+ years: '%p %d%r Jahren'
+}
diff --git a/themes/ccchh/assets/sass/main.scss b/themes/ccchh/assets/sass/main.scss
new file mode 100644
index 0000000..3de3c01
--- /dev/null
+++ b/themes/ccchh/assets/sass/main.scss
@@ -0,0 +1,78 @@
+$roomstate_color_unknown: #dda218;
+$roomstate_color_open: var(--ins-color);
+$roomstate_color_closed: var(--del-color);
+
+
+// Room State in Menu
+#roomstate {
+ font-size: 0.9em;
+ line-height: 1.0em;
+ max-width: 8em;
+ padding: 5px;
+
+ color: $roomstate_color_unknown;
+
+ &.open {
+ color: $roomstate_color_open;
+ }
+
+ &.closed {
+ color: $roomstate_color_closed;
+ }
+
+ span.duration {
+ font-size: 0.7em;
+ }
+}
+
+// CCCHH Icon in Menu
+@media only screen and (prefers-color-scheme: light) {
+ .invert-on-light {
+ filter: invert(1);
+ }
+}
+
+// Home page Announcements
+.announcement {
+ border-radius: var(--border-radius);
+ background: var(--code-background-color);
+ padding: 10px 15px;
+
+ margin-bottom: var(--typography-spacing-vertical);
+
+ p:last-child {
+ margin-bottom: 0;
+ }
+}
+
+@media only screen and (prefers-color-scheme: dark) {
+ .announcement {
+ background: var(--code-background-color);
+ }
+}
+
+// Home Friends&Family Gallery
+.flex-grid {
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: center;
+
+ div {
+ width: 250px;
+
+ // 2*250px + 2*var(--spacing) + 1px
+ @media only screen and (max-width: 533px) {
+ width: 150px;
+ }
+ }
+}
+
+.img-link {
+ a {
+ img {
+ display: block;
+ padding: var(--grid-spacing-horizontal);
+ margin: 0 auto;
+ }
+ }
+}
\ No newline at end of file
diff --git a/themes/ccchh/hugo.toml b/themes/ccchh/hugo.toml
new file mode 100644
index 0000000..938865a
--- /dev/null
+++ b/themes/ccchh/hugo.toml
@@ -0,0 +1 @@
+# Theme config.
diff --git a/themes/ccchh/layouts/404.html b/themes/ccchh/layouts/404.html
new file mode 100644
index 0000000..c4413b4
--- /dev/null
+++ b/themes/ccchh/layouts/404.html
@@ -0,0 +1,8 @@
+{{ define "main" }}
+ 404 Page not Found
+ Back Home
+
{{ .Date.Format "2006-01-02" }}: {{ .Title }} + {{ .Summary }} + {{- if .Truncated }} + Read More… + {{- end }} +
+