diff --git a/.forgejo/workflows/deploy.yaml b/.forgejo/workflows/deploy.yaml index db47f8e..ec8cb71 100644 --- a/.forgejo/workflows/deploy.yaml +++ b/.forgejo/workflows/deploy.yaml @@ -78,3 +78,26 @@ jobs: with: name: website-build path: public/ + + # Separate from the build, so that accessibility findings show up on PRs without blocking deploys. + accessibility: + runs-on: docker + container: + image: docker.io/hugomods/hugo:0.165.0 + steps: + - name: Install packages + run: | + apk update + # For CI actions and pa11y. + apk add nodejs-current npm + # For website build. + apk add tzdata git + # For pa11y: a browser, fonts for it, and a web server for the built site. + apk add chromium font-dejavu python3 + + - uses: actions/checkout@v7 + with: + submodules: recursive + + - name: Check accessibility with pa11y + run: ./scripts/pa11y.sh diff --git a/README.md b/README.md index 1df165e..0f5cb3c 100644 --- a/README.md +++ b/README.md @@ -49,4 +49,19 @@ Wide screens get one column per day on an hour grid, with tours that start at th For a different event than the page's section, pass its name: `{{< schedule 39c3 >}}`. -Without a data file, or without any tours in it, the shortcode renders nothing and the build logs a warning. To silence it, for example for an upcoming event, add `warning-schedule-empty` to `ignoreLogs` in `hugo.yaml`. \ No newline at end of file +Without a data file, or without any tours in it, the shortcode renders nothing and the build logs a warning. To silence it, for example for an upcoming event, add `warning-schedule-empty` to `ignoreLogs` in `hugo.yaml`. +## Accessibility check + +CI runs [pa11y-ci](https://github.com/pa11y/pa11y-ci) with the axe and HTML_CodeSniffer rules for WCAG 2 AA on every push and pull request, in its own job (`accessibility`), so findings show up without blocking the deploy. To run the same check locally (needs Node.js, Python 3 and Chrome or Chromium): + +```shell +./scripts/pa11y.sh +``` + +The script builds the site, serves it on `localhost:8080` and checks every page in the sitemaps, except the past events and the individual news posts, which are kept as they were. The list of skipped sections is `EXCLUDE` in [scripts/pa11y.sh](scripts/pa11y.sh); the rules are in [.pa11yci.json](.pa11yci.json). pa11y only checks the light colour scheme. + +Some things to keep in mind when writing pages: + +- Give every image an alt text that says what it shows. The image title is the photo credit (`![Alt text](photo.jpg "© Photographer")`); it is shown below the image. +- Headings in a page start at `##`; `#` is the page title. +- Don't put information only into images, like directions on a map; write it out as text as well. diff --git a/assets/css/_custom.css b/assets/css/_custom.css index bd9d272..cf3ec0b 100644 --- a/assets/css/_custom.css +++ b/assets/css/_custom.css @@ -1,6 +1,7 @@ @import './_schedule.css'; .header__intro_headline { + margin: 0; font: bold var(--fs-xxl) "Arial"; padding-bottom: 10px; } @@ -32,6 +33,7 @@ th, td { --color-header-bg: #242424; --color-text: #faf5f5; --color-link: #66f2ff; + --color-link-visited: #66f2ff; --color-footer-bg: #242424; --color-border: #faf5f5; --color-button: #141414; @@ -49,7 +51,9 @@ th, td { .main-menu a:active { text-decoration: none; } - a { + /* Links in text keep their underline: the link colour alone is too close to the text colour. */ + nav a, + .header a { text-decoration: none; } } @@ -60,4 +64,44 @@ h2 a { .title-post-date { margin-left: 0em; -} \ No newline at end of file +} +/* Visible when focused with the keyboard; the visually-hidden class hides it otherwise. */ +.skip-link:focus { + position: absolute; + inset-block-start: 0.5rem; + inset-inline-start: 0.5rem; + z-index: 100; + padding: 0.5rem 1rem; + background: var(--color-button); + color: var(--color-button-text); +} + +#main-content:focus { + outline: none; +} + +/* Content images with their credit, see layouts/_markup/render-image.html. */ +.content-image { + margin: 1rem 0; + + img { + display: block; + max-width: 100%; + height: auto; + } +} + +span.content-image { + display: inline-block; + vertical-align: top; + max-width: 100%; + margin: 0 0.5rem 0.5rem 0; +} + +.content-image__credit { + display: block; + margin-block-start: 0.25rem; + font-family: var(--ff-alt); + font-size: var(--fs-xs); + color: var(--color-text-meta); +} diff --git a/assets/css/_schedule.css b/assets/css/_schedule.css index 6e812f4..d91766b 100644 --- a/assets/css/_schedule.css +++ b/assets/css/_schedule.css @@ -68,16 +68,16 @@ } } -/* Lined up with the time on the cards. */ +/* Lined up with the time on the cards, but in normal weight to stay in the background. + No transparency: it has to keep text contrast. */ .schedule__hour-label { display: block; padding: calc(0.5rem + 0.25rem) 0.75rem 0.5rem calc(0.75rem + 3px); font-family: var(--ff-alt); font-size: var(--fs-s); - font-weight: bold; + font-weight: normal; font-variant-numeric: tabular-nums; color: var(--color-text-meta); - opacity: 0.45; } .schedule__day-title { @@ -202,6 +202,11 @@ } @container (width < 48rem) { + /* Keeps a focused card from scrolling under the sticky day picker. */ + .schedule__title { + scroll-margin-block-start: 6rem; + } + .schedule__picker:not([hidden]) { position: sticky; inset-block-start: 0; diff --git a/content/de/_index.md b/content/de/_index.md index 6b4a97c..994c7ee 100644 --- a/content/de/_index.md +++ b/content/de/_index.md @@ -15,8 +15,6 @@ cascade: intro: Wir von Hamburg Hackertours zeigen euch die Stadt! --- -## Willkommen bei Hamburg Hackertours! - Für den [39C3](https://events.ccc.de/category/39c3/) haben wir interessante Orte in und um Hamburg gefunden, an denen ihr für ein paar Stunden in andere Welten eintauchen und spannende Dinge erleben könnt. [Wie das funktioniert, erklären wir euch hier in unseren FAQ]({{< relref "faq/_index" >}})! HackerTours ist auch auf **[Mastodon](https://chaos.social/@hackertours)**! @@ -31,4 +29,4 @@ Hackertours-Desk: DECT 8687 "TOUR", +49 40 573 0830 8687 Frühere Veranstaltungen: [37C3](37c3) / [38C3](38c3) / [EH22](eh22) -# News +## News diff --git a/content/en/_index.md b/content/en/_index.md index f64a73f..81acaf3 100644 --- a/content/en/_index.md +++ b/content/en/_index.md @@ -15,8 +15,6 @@ cascade: intro: We at Hamburg Hackertours show you around the city! --- -## Welcome to Hamburg Hackertours! - For [39C3](https://events.ccc.de/category/39c3/) we have found interesting places in and around Hamburg where you can delve into other worlds and experience fascinating things for a few hours. [We are going to explain how this works here in our FAQ article]({{< relref "faq/_index" >}})! HackerTours is also on **[Mastodon](https://chaos.social/@hackertours)**! @@ -31,5 +29,5 @@ Hackertours Desk: DECT 8687 "TOUR", +49 40 573 0830 8687 Previous events: [37C3](37c3) / [38C3](38c3) / [EH22](eh22) -# News +## News \ No newline at end of file diff --git a/hugo.yaml b/hugo.yaml index 3f5235f..c3e043a 100644 --- a/hugo.yaml +++ b/hugo.yaml @@ -44,3 +44,9 @@ languages: pagination: pagerSize: 25 title: "Hamburg Hackertours" + +markup: + goldmark: + parser: + # Lets the image render hook tell images on their own line (.IsBlock) from inline ones. + wrapStandAloneImageWithinParagraph: false diff --git a/i18n/de.yaml b/i18n/de.yaml index 3ad0816..856a4a6 100644 --- a/i18n/de.yaml +++ b/i18n/de.yaml @@ -1,3 +1,6 @@ +logo_alt: "Hamburg Hackertours – Startseite" +map_39c3_title: "Karte der 39C3-Touren mit Wegen vom CCH (uMap)" schedule_day: "Tag {{ .Day }}" schedule_pick_day: "Tag auswählen" schedule_soldout: "ausgebucht" +skip_to_content: "Zum Inhalt springen" diff --git a/i18n/en.yaml b/i18n/en.yaml index 7de61d2..fee4502 100644 --- a/i18n/en.yaml +++ b/i18n/en.yaml @@ -1,3 +1,6 @@ +logo_alt: "Hamburg Hackertours – home page" +map_39c3_title: "Map of the 39C3 tours with routes from the CCH (uMap)" schedule_day: "Day {{ .Day }}" schedule_pick_day: "Choose a day" schedule_soldout: "fully booked" +skip_to_content: "Skip to content" diff --git a/layouts/_partials/lang-badge.html b/layouts/_partials/lang-badge.html index a305135..7698e49 100644 --- a/layouts/_partials/lang-badge.html +++ b/layouts/_partials/lang-badge.html @@ -1,21 +1,25 @@ -{{- /* Language badge for a tour. Params: code ("de", "en" or "deen"), class (optional; without it the badge is styled as an inline superscript). */ -}} +{{- /* Language badge for a tour. Params: code ("de", "en" or "deen"), class (optional; without it the badge is styled as an inline superscript). +The visible label ("DE/EN") is hidden from screen readers, which read a short text instead; the full sentence is a tooltip. */ -}} {{- $code := .code | lower -}} {{- $validCodes := slice "de" "en" "deen" -}} {{- if not (in $validCodes $code) -}} Warning: Invalid or missing language code '{{ $code }}'. Use 'de', 'en', or 'deen'. {{- else -}} - {{- $label := cond (eq $code "de") "DE" (cond (eq $code "en") "EN" (cond (eq $code "deen") "DE/EN" "")) -}} + {{- $de := eq site.Language.Name "de" -}} + {{- $label := cond (eq $code "de") "DE" (cond (eq $code "en") "EN" "DE/EN") -}} {{- $title := "" -}} + {{- $spoken := "" -}} {{- if eq $code "deen" -}} - {{- $title = cond (eq site.Language.Name "de") "Diese Tour wird zweisprachig stattfinden (Deutsch und Englisch)" "This tour will be bilingual (German and English)" -}} + {{- $title = cond $de "Diese Tour wird zweisprachig stattfinden (Deutsch und Englisch)" "This tour will be bilingual (German and English)" -}} + {{- $spoken = cond $de "Sprache: Deutsch und Englisch" "Language: German and English" -}} {{- else if eq $code "de" -}} - {{- $title = cond (eq site.Language.Name "de") "Diese Tour wird auf Deutsch stattfinden" "This tour will be held in German" -}} + {{- $title = cond $de "Diese Tour wird auf Deutsch stattfinden" "This tour will be held in German" -}} + {{- $spoken = cond $de "Sprache: Deutsch" "Language: German" -}} {{- else if eq $code "en" -}} - {{- $title = cond (eq site.Language.Name "de") "Diese Tour wird auf Englisch stattfinden" "This tour will be held in English" -}} - {{- end -}} - {{- with .class -}} - {{ $label }} - {{- else -}} - {{ $label }} + {{- $title = cond $de "Diese Tour wird auf Englisch stattfinden" "This tour will be held in English" -}} + {{- $spoken = cond $de "Sprache: Englisch" "Language: English" -}} {{- end -}} + {{- $style := "font-size: .65em; vertical-align: super;" -}} + {{- with .class }}{{ $style = "" }}{{ end -}} + {{ $spoken }} {{- end -}} diff --git a/layouts/_shortcodes/map-39c3.html b/layouts/_shortcodes/map-39c3.html index 34999ba..ffe1f50 100644 --- a/layouts/_shortcodes/map-39c3.html +++ b/layouts/_shortcodes/map-39c3.html @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/layouts/baseof.html b/layouts/baseof.html index 3f35c5f..1b2fc6e 100644 --- a/layouts/baseof.html +++ b/layouts/baseof.html @@ -17,15 +17,16 @@ +
{{ if $.Param "logo" | default true -}} - {{ end -}}
- +
{{ .Param "intro" }}
@@ -37,6 +38,7 @@ {{ if not ($.Param "menuinheader") }}{{ partial "menu.html" . }}{{ end -}} {{ if $.Param "mobilemenu" }}{{ partial "mobilemenu.html" . }}{{ end -}} +
{{ block "main" . }}{{ end }} {{ if $.Param "sidebar" }}{{ partial "sidebar.html" . }}{{ end -}} diff --git a/layouts/home-baseof.html b/layouts/home-baseof.html index 80661d4..bdbee20 100644 --- a/layouts/home-baseof.html +++ b/layouts/home-baseof.html @@ -17,15 +17,16 @@ +
{{ if $.Param "logo" | default true -}} - {{ end -}}
-
{{ .Param "headline" }}
+

{{ .Param "headline" }}

{{ if .Param "intro" }}
{{ .Param "intro" }}
{{ end }} @@ -39,6 +40,7 @@ {{ if not ($.Param "menuinheader") }}{{ partial "menu.html" . }}{{ end -}} {{ if $.Param "mobilemenu" }}{{ partial "mobilemenu.html" . }}{{ end -}} +
{{ block "main" . }}{{ end }} {{ if $.Param "sidebar" }}{{ partial "sidebar.html" . }}{{ end -}}