Create CCCHH theme

This commit is contained in:
jtbx 2023-10-02 12:08:18 +02:00
commit 2d267ba9fe
23 changed files with 629 additions and 1 deletions

20
themes/ccchh/LICENSE Normal file
View file

@ -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.

View file

@ -0,0 +1,8 @@
---
kind: article
title: '{{ replace .File.ContentBaseName `-` ` ` | title }}'
date: '{{ .Date }}'
draft: true
---
TODO: Write your blog post

View file

@ -0,0 +1,5 @@
---
title: "{{ replace .Name "-" " " | title }}"
date: {{ .Date }}
draft: true
---

View file

@ -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

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 86 KiB

View file

@ -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)
}

View file

@ -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'
}

View file

@ -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;
}
}
}

1
themes/ccchh/hugo.toml Normal file
View file

@ -0,0 +1 @@
# Theme config.

View file

@ -0,0 +1,8 @@
{{ define "main" }}
<main class="container" aria-role="main">
<div>
<h1 id="title">404 Page not Found</h1>
<a href="{{ "" | relURL }}">Back Home</a>
</div>
</main>
{{ end }}

View file

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html>
<head>
{{- partial "head.html" . }}
</head>
<body>
{{- partial "header.html" . }}
{{- block "main" . }}{{- end }}
{{- partial "footer.html" . }}
</body>
</html>

View file

@ -0,0 +1,13 @@
{{ define "main" }}
<main class="container" aria-role="main">
<header class="header">
<h1>{{ .Title }}</h1>
{{ with .Params.subtitle }}
<span class="subtitle">{{ . }}</span>
{{ end }}
</header>
<div class="content">
{{ .Content }}
</div>
</main>
{{ end }}

View file

@ -0,0 +1,33 @@
{{ define "main" }}
<main class="container" aria-role="main">
<header class="header">
<h1>{{ .Title }}</h1>
{{ with .Params.subtitle }}
<span class="subtitle">{{ . }}</span>
{{ end }}
</header>
<div class="content">
{{ .Content }}
</div>
<ul>
{{ range .Pages }}
<li>
<a href="{{ .Permalink }}">{{ .Date.Format "2006-01-02" }} | {{ .Title }}</a>
</li>
{{ end }}
</ul>
{{ range .Pages.GroupByParam "kind" }}
<h3>{{ .Key }}</h3>
<ul>
{{ range .Pages }}
<li>
<a href="{{ .Permalink }}">{{ .Date.Format "2006-01-02" }} | {{ .Title }}</a>
</li>
{{ end }}
</ul>
{{ end }}
<!-- TODO: Pagination: see https://gohugo.io/templates/pagination/ -->
</main>
{{ end }}

View file

@ -0,0 +1,50 @@
{{ define "main" }}
<main class="container" aria-role="main">
<!-- <header class="homepage-header">
<h1>{{ .Title }}</h1>
{{ with .Params.subtitle }}
<span class="subtitle">{{ . }}</span>
{{ end }}
</header> -->
<div class="homepage-content">
<!-- Note that the content for index.html, as a sort of list page, will pull from content/_index.md -->
{{ .Content }}
</div>
{{- $events := where (.Site.GetPage "blog").Pages ".Params.kind" "event" }}
{{- $upcoming := where $events ".Params.date" "ge" now }}
{{ if $upcoming}}
<div class="announcements">
<h3>Neuigkeiten</h3>
{{- range $upcoming.ByDate }}
<div class="announcement">
<p><a href="{{ .Permalink }}">{{ .Date.Format "2006-01-02" }}: {{ .Title }}</a>
{{ .Summary }}
{{- if .Truncated }}
<a href="{{ .RelPermalink }}">Read More…</a>
{{- end }}
</p>
</div>
{{- end }}
</div>
{{- end }}
{{- $home_sections := .Site.GetPage "/home" }}
{{- range sort ($home_sections.Resources.Match "*.md") ".Name" }}
<div class="section">
<h3>{{ .Title }}</h3>
{{ .Content -}}
{{- with .Params.Resources }}
<div class="flex-grid">
{{- range . }}
{{- $img := $home_sections.Resources.GetMatch (printf "%s" .src) }}
<div class="img-link"><a href="{{ .params.url }}"><img src="{{ $img.Permalink }}" class="box-image" alt=""></a></div>
{{- end }}
</div>
{{- end }}
</div>
{{ end }}
</main>
{{ end }}

View file

@ -0,0 +1,6 @@
<footer class="container">
<nav class="menu">
{{- partial "menu.html" (dict "menuID" "footer" "page" . ) -}}
</nav>
</footer>

View file

@ -0,0 +1,22 @@
{{- $options := dict "transpiler" "libsass" "targetPath" "css/style.css" -}}
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta charset="utf-8">
{{- with resources.GetRemote "https://cdn.jsdelivr.net/npm/@picocss/pico@1/css/pico.min.css" }}
<link rel="stylesheet" href="{{ .RelPermalink }}" crossorigin="anonymous">
{{ end }}
{{ with resources.Get "sass/main.scss" | toCSS $options | minify | fingerprint -}}
<link rel="stylesheet" href="{{ .RelPermalink }}" integrity="{{ .Data.Integrity }}" crossorigin="anonymous">
{{- end }}
<link rel="stylesheet" href="{{ .Site.BaseURL }}thirdparty/fontawesome6/css/all.min.css" crossorigin="anonymous">
{{ with resources.Get "js/roomstate.js" | js.Build | minify | fingerprint -}}
<script src="{{ .RelPermalink }}" integrity="{{ .Data.Integrity }}" crossorigin="anonymous"></script>
{{- end }}
{{ range .AlternativeOutputFormats -}}
{{ printf `<link rel="%s" type="%s" href="%s" title="%s" />` .Rel .MediaType.Type .Permalink $.Site.Title | safeHTML }}
{{ end -}}
<title>{{ .Title }}</title>
<!-- TODO: Favicon -->

View file

@ -0,0 +1,6 @@
<div class="container header">
<nav class="menu">
{{- partial "menu.html" (dict "menuID" "main" "page" . ) -}}
</nav>
</div>

View file

@ -0,0 +1,32 @@
{{- $page := .page -}}
{{- $menuID := .menuID -}}
{{- $menuContent := index site.Menus .menuID -}}
<ul>
{{- if compare.Eq $menuID "main" -}}
{{ $image := resources.Get "images/logo.svg" }}
<li><a href="/{{ .Site.Home.URL }}"><img src="{{ $image.RelPermalink }}" class="invert-on-light" width="150px" alt="CCCHH Icon"></a></li>
{{- end }}
{{- range $menuContent }}
<li>
{{- $attrs := dict "href" .URL }}
{{- if $page.IsMenuCurrent $menuID . -}}
{{- $attrs = merge $attrs (dict "class" "active" "aria-current" "page") }}
{{- end -}}
<a
{{- range $k, $v := $attrs }}
{{- with $v }}
{{- printf " %s=%q" $k $v | safeHTMLAttr }}
{{- end }}
{{- end -}}
>{{ or (T .Identifier) .Name | safeHTML }}</a></li>
{{- end }}
</ul>
{{- if compare.Eq $menuID "main" -}}
<ul>
<li class="roomstate" id="roomstate">
<span class="state">unbekannt</span><br><span class="duration">(seit&nbsp;)</span>
</li>
</ul>
{{- end }}

21
themes/ccchh/theme.toml Normal file
View file

@ -0,0 +1,21 @@
# theme.toml template for a Hugo theme
# See https://github.com/gohugoio/hugoThemes#themetoml for an example
name = "CCCHH"
license = "MIT"
licenselink = "https://github.com/yourname/yourtheme/blob/master/LICENSE"
description = ""
# homepage = "http://example.com/"
tags = []
features = []
min_version = "0.114.1"
[author]
name = ""
homepage = ""
# If porting an existing theme
[original]
name = ""
homepage = ""
repo = ""