From 59b8d969df7b3580417bff4f6ee226af5694915c Mon Sep 17 00:00:00 2001 From: Emma Thorpe Date: Thu, 11 Jun 2026 15:59:39 +0100 Subject: [PATCH 1/7] feat: add page structure and DLR colour schemes Single-page layout with a centred message, a colour-scheme toggle, and a "Check again" button. styles.css defines two palettes selected via the data-theme attribute: modern DLR turquoise/teal and the original 1987 red-and-blue livery. Co-Authored-By: Claude Opus 4.8 (1M context) --- index.html | 31 ++++++++++++ styles.css | 141 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 172 insertions(+) create mode 100644 index.html create mode 100644 styles.css diff --git a/index.html b/index.html new file mode 100644 index 0000000..b299cb1 --- /dev/null +++ b/index.html @@ -0,0 +1,31 @@ + + + + + + Why is the DLR shut today? + + + +
+
+ Colour scheme + + +
+
+ +
+

Why is the DLR shut today?

+

+ +
+ + + + + diff --git a/styles.css b/styles.css new file mode 100644 index 0000000..36d33aa --- /dev/null +++ b/styles.css @@ -0,0 +1,141 @@ +/* Why is the DLR shut today? + Two colour schemes selected via the [data-theme] attribute on : + - modern: current DLR turquoise/teal branding + - original: 1987 DLR red-and-blue livery */ + +:root { + --font-stack: "Helvetica Neue", Helvetica, Arial, sans-serif; +} + +/* Modern DLR — turquoise/teal */ +[data-theme="modern"] { + --bg: #00afaa; + --bg-accent: #007e7a; + --surface: #ffffff; + --text: #ffffff; + --message: #ffffff; + --button-bg: #ffffff; + --button-text: #007e7a; + --button-active-bg: #00302e; + --button-active-text: #ffffff; +} + +/* Original DLR — 1987 red and blue */ +[data-theme="original"] { + --bg: #002b5c; + --bg-accent: #c8102e; + --surface: #f5f0e1; + --text: #f5f0e1; + --message: #f5f0e1; + --button-bg: #c8102e; + --button-text: #f5f0e1; + --button-active-bg: #f5f0e1; + --button-active-text: #002b5c; +} + +* { + box-sizing: border-box; +} + +body { + margin: 0; + min-height: 100vh; + display: flex; + flex-direction: column; + font-family: var(--font-stack); + color: var(--text); + background: linear-gradient(135deg, var(--bg) 0%, var(--bg-accent) 100%); + transition: background 0.4s ease, color 0.4s ease; +} + +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; +} + +.controls { + display: flex; + justify-content: flex-end; + padding: 1.5rem; +} + +.theme-toggle { + display: flex; + gap: 0.5rem; + border: none; + margin: 0; + padding: 0; +} + +.theme-button { + font-family: inherit; + font-size: 0.9rem; + font-weight: 600; + padding: 0.5rem 1rem; + border: 2px solid var(--button-bg); + border-radius: 999px; + background: transparent; + color: var(--text); + cursor: pointer; + transition: background 0.2s ease, color 0.2s ease; +} + +.theme-button[aria-pressed="true"] { + background: var(--button-active-bg); + color: var(--button-active-text); + border-color: var(--button-active-bg); +} + +.stage { + flex: 1; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + text-align: center; + padding: 2rem; + gap: 2rem; +} + +.title { + font-size: clamp(1.5rem, 4vw, 2.5rem); + font-weight: 700; + margin: 0; + letter-spacing: 0.02em; +} + +.message { + font-size: clamp(1.75rem, 6vw, 4rem); + font-weight: 800; + line-height: 1.2; + margin: 0; + max-width: 22ch; + color: var(--message); +} + +.refresh-button { + font-family: inherit; + font-size: 1rem; + font-weight: 600; + padding: 0.75rem 1.75rem; + border: 2px solid var(--button-bg); + border-radius: 999px; + background: var(--button-bg); + color: var(--button-text); + cursor: pointer; + transition: transform 0.1s ease, opacity 0.2s ease; +} + +.refresh-button:hover { + opacity: 0.9; +} + +.refresh-button:active { + transform: scale(0.97); +} -- 2.52.0 From 3b5c304002506ac31aee0574595e33ddcc4fe77d Mon Sep 17 00:00:00 2001 From: Emma Thorpe Date: Thu, 11 Jun 2026 15:59:39 +0100 Subject: [PATCH 2/7] feat: add random message selection and theme toggle script.js picks a random entry from MESSAGES on load and on each "Check again" click, and persists the chosen colour scheme in localStorage. messages.js holds the MESSAGES array as a placeholder template to be filled in with content. Co-Authored-By: Claude Opus 4.8 (1M context) --- messages.js | 18 ++++++++++++++++ script.js | 59 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+) create mode 100644 messages.js create mode 100644 script.js diff --git a/messages.js b/messages.js new file mode 100644 index 0000000..d3dcf15 --- /dev/null +++ b/messages.js @@ -0,0 +1,18 @@ +/* Messages for "Why is the DLR shut today?" + * + * Fill this array with your own reasons. One string per entry. + * On every page load (and every "Check again" click) one entry is + * picked at random and shown in the centre of the screen. + * + * Keep them short — roughly a sentence — so they fit the large display + * text. HTML is NOT rendered; entries are inserted as plain text. + * + * Replace the placeholders below with your own content. + */ + +const MESSAGES = [ + "PLACEHOLDER: write your first reason here", + "PLACEHOLDER: write another reason here", + // Add as many entries as you like, one per line: + // "Your reason here", +]; diff --git a/script.js b/script.js new file mode 100644 index 0000000..55e67fe --- /dev/null +++ b/script.js @@ -0,0 +1,59 @@ +/* Why is the DLR shut today? + * Picks a random message on load and on demand, and handles the + * colour-scheme toggle. Messages live in messages.js (MESSAGES). */ + +(function () { + "use strict"; + + const FALLBACK = "Add some reasons in messages.js"; + const messageEl = document.getElementById("message"); + const refreshButton = document.getElementById("refresh"); + const themeButtons = document.querySelectorAll("[data-set-theme]"); + const THEME_KEY = "dlr-theme"; + + function pickMessage() { + if (!Array.isArray(MESSAGES) || MESSAGES.length === 0) { + return FALLBACK; + } + const index = Math.floor(Math.random() * MESSAGES.length); + return MESSAGES[index]; + } + + function showMessage() { + messageEl.textContent = pickMessage(); + } + + function applyTheme(theme) { + document.body.setAttribute("data-theme", theme); + themeButtons.forEach(function (button) { + const isActive = button.dataset.setTheme === theme; + button.setAttribute("aria-pressed", String(isActive)); + }); + try { + localStorage.setItem(THEME_KEY, theme); + } catch (err) { + /* localStorage unavailable; theme simply won't persist. */ + } + } + + function initTheme() { + let saved = null; + try { + saved = localStorage.getItem(THEME_KEY); + } catch (err) { + /* ignore */ + } + applyTheme(saved === "original" ? "original" : "modern"); + } + + themeButtons.forEach(function (button) { + button.addEventListener("click", function () { + applyTheme(button.dataset.setTheme); + }); + }); + + refreshButton.addEventListener("click", showMessage); + + initTheme(); + showMessage(); +})(); -- 2.52.0 From a2c6408277b0c4fffa876c9db07e667889b2496f Mon Sep 17 00:00:00 2001 From: Emma Thorpe Date: Thu, 11 Jun 2026 15:59:39 +0100 Subject: [PATCH 3/7] docs: add README Co-Authored-By: Claude Opus 4.8 (1M context) --- README.md | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..37a5dac --- /dev/null +++ b/README.md @@ -0,0 +1,37 @@ +# Why is the DLR shut today? + +A single-page site that displays one randomly chosen message in the centre of +the screen. The message changes on every page load and whenever the +**Check again** button is pressed. + +The site is themed around the Docklands Light Railway colour scheme, with a +toggle between: + +- **Modern colours** — the current DLR turquoise/teal branding. +- **Original colours** — the 1987 DLR red-and-blue livery. + +The chosen theme is remembered between visits via `localStorage`. + +## Adding messages + +Edit `messages.js` and fill the `MESSAGES` array with your own reasons — one +string per entry. Entries are inserted as plain text. Until you add some, the +page shows a fallback prompt. + +## Running + +It is a static site with no build step. Open `index.html` in a browser, or +serve the directory with any static file server, for example: + +```sh +python3 -m http.server +``` + +## Files + +| File | Purpose | +| ------------- | ------------------------------------------------ | +| `index.html` | Page structure. | +| `styles.css` | Both colour schemes, selected via `data-theme`. | +| `messages.js` | The list of messages (fill this in). | +| `script.js` | Random message selection and the theme toggle. | -- 2.52.0 From d2bac6b8d39ffa25d9403f575247963c51a58006 Mon Sep 17 00:00:00 2001 From: Emma Thorpe Date: Thu, 11 Jun 2026 16:07:10 +0100 Subject: [PATCH 4/7] feat: containerise the site with nginx-unprivileged Serve the static site from a non-root nginx image listening on 8080, with cache headers, gzip and a /healthz endpoint. Designed to run behind an external reverse proxy that terminates TLS. Co-Authored-By: Claude Opus 4.8 (1M context) --- .dockerignore | 12 ++++++++++++ Dockerfile | 9 +++++++++ default.conf | 42 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 63 insertions(+) create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100644 default.conf diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..3a88f48 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,12 @@ +# Keep the build context minimal; only the site assets and nginx config +# referenced by the Dockerfile are needed. +.git +.gitea +.github +.idea +.vscode +*.md +renovate.json +.renovaterc* +Dockerfile +.dockerignore diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..7525657 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,9 @@ +# Lightweight, non-root nginx serving the static site. +# Runs as user "nginx" and listens on 8080, ready to sit behind an +# external reverse proxy that terminates TLS and forwards requests. +FROM nginxinc/nginx-unprivileged:1.27-alpine-slim + +COPY default.conf /etc/nginx/conf.d/default.conf +COPY --chown=nginx:nginx index.html styles.css script.js messages.js /usr/share/nginx/html/ + +EXPOSE 8080 diff --git a/default.conf b/default.conf new file mode 100644 index 0000000..449930a --- /dev/null +++ b/default.conf @@ -0,0 +1,42 @@ +# Static file serving for "Why is the DLR shut today?". +# Intended to run behind an external reverse proxy (e.g. NGINX) which +# handles TLS, host routing and any X-Forwarded-* headers. + +server { + listen 8080; + listen [::]:8080; + server_name _; + + root /usr/share/nginx/html; + index index.html; + + gzip on; + gzip_vary on; + gzip_types text/css application/javascript; + + location / { + try_files $uri $uri/ =404; + } + + # HTML should always be revalidated so deploys are picked up promptly. + location = /index.html { + add_header Cache-Control "no-cache"; + } + + # The message list is edited frequently; do not cache it. + location = /messages.js { + add_header Cache-Control "no-cache"; + } + + # Other static assets may be cached for a short period. + location ~* \.(?:css|js)$ { + add_header Cache-Control "public, max-age=3600"; + } + + # Health endpoint for the proxy / orchestrator. + location = /healthz { + access_log off; + default_type text/plain; + return 200 "ok\n"; + } +} -- 2.52.0 From b28ec41ccb3a037c422c3e3e32f00b74e90cf807 Mon Sep 17 00:00:00 2001 From: Emma Thorpe Date: Thu, 11 Jun 2026 16:07:10 +0100 Subject: [PATCH 5/7] ci: build and publish the container via Gitea Actions Build the image on pushes to main, version tags and pull requests, and push to the Gitea container registry (except on PRs) using the auto-provided GITEA_TOKEN. Tags are derived with docker/metadata-action. Co-Authored-By: Claude Opus 4.8 (1M context) --- .gitea/workflows/build-and-publish.yml | 53 ++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 .gitea/workflows/build-and-publish.yml diff --git a/.gitea/workflows/build-and-publish.yml b/.gitea/workflows/build-and-publish.yml new file mode 100644 index 0000000..c58d334 --- /dev/null +++ b/.gitea/workflows/build-and-publish.yml @@ -0,0 +1,53 @@ +name: Build and publish container + +on: + push: + branches: [main] + tags: ["v*"] + pull_request: + branches: [main] + +jobs: + build: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Determine registry host + run: echo "REGISTRY=${GITHUB_SERVER_URL#*://}" >> "$GITHUB_ENV" + + - name: Set up Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to the Gitea container registry + if: github.event_name != 'pull_request' + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITEA_TOKEN }} + + - name: Extract image metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ github.repository }} + tags: | + type=ref,event=branch + type=ref,event=pr + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=sha + type=raw,value=latest,enable={{is_default_branch}} + + - name: Build and push + uses: docker/build-push-action@v6 + with: + context: . + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} -- 2.52.0 From eb0186a5b8a54dacc013001dbe5fabb5bd244d6c Mon Sep 17 00:00:00 2001 From: Emma Thorpe Date: Thu, 11 Jun 2026 16:07:10 +0100 Subject: [PATCH 6/7] build: add Renovate configuration Track the Dockerfile base image, the actions used in the Gitea workflow, and versioned front-end dependencies referenced in HTML (via renovate comment annotations or jsDelivr/unpkg npm URLs). Co-Authored-By: Claude Opus 4.8 (1M context) --- renovate.json | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 renovate.json diff --git a/renovate.json b/renovate.json new file mode 100644 index 0000000..f0af024 --- /dev/null +++ b/renovate.json @@ -0,0 +1,39 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": [ + "config:recommended", + ":dependencyDashboard", + ":semanticCommits" + ], + "labels": ["renovate"], + "github-actions": { + "fileMatch": ["^\\.gitea/workflows/[^/]+\\.ya?ml$"] + }, + "packageRules": [ + { + "description": "Group nginx base image updates", + "matchManagers": ["dockerfile"], + "groupName": "docker base image" + } + ], + "customManagers": [ + { + "customType": "regex", + "description": "Update HTML dependencies annotated with a renovate comment, e.g. before the versioned URL", + "fileMatch": ["\\.html$"], + "matchStrings": [ + "datasource=(?\\S+) depName=(?\\S+)( versioning=(?\\S+))?[\\s\\S]*?(?v?\\d+\\.\\d+\\.\\d+[\\w.-]*)" + ] + }, + { + "customType": "regex", + "description": "Auto-detect versioned jsDelivr / unpkg npm assets in HTML", + "fileMatch": ["\\.html$"], + "matchStrings": [ + "https://cdn\\.jsdelivr\\.net/npm/(?@?[^@/]+(?:/[^@/]+)?)@(?\\d[^/\"']+)", + "https://unpkg\\.com/(?@?[^@/]+(?:/[^@/]+)?)@(?\\d[^/\"']+)" + ], + "datasourceTemplate": "npm" + } + ] +} -- 2.52.0 From e5dd090c45b9a860218b3a19c9451d3d12d56b34 Mon Sep 17 00:00:00 2001 From: Emma Thorpe Date: Thu, 11 Jun 2026 16:07:10 +0100 Subject: [PATCH 7/7] docs: document container, CI and dependency updates Co-Authored-By: Claude Opus 4.8 (1M context) --- README.md | 60 +++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 54 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 37a5dac..6abb542 100644 --- a/README.md +++ b/README.md @@ -27,11 +27,59 @@ serve the directory with any static file server, for example: python3 -m http.server ``` +## Container + +The site is packaged as a container based on `nginxinc/nginx-unprivileged`. It +runs as a non-root user and listens on port **8080**, serving the static files +and exposing a `/healthz` endpoint. It is designed to sit behind an external +reverse proxy that terminates TLS and routes by host. + +Build and run locally: + +```sh +docker build -t dlr . +docker run --rm -p 8080:8080 dlr +# then browse http://localhost:8080 +``` + +## CI + +`.gitea/workflows/build-and-publish.yml` builds the container with Gitea Actions +and publishes it to this Gitea instance's container registry on pushes to `main` +and on `v*` tags. Pull requests build the image but do not push. Authentication +uses the automatically provided `GITEA_TOKEN`; the registry host is derived from +the Gitea server URL. + +The published image is `//`, tagged by branch, semver +(for `v*` tags), commit SHA, and `latest` on the default branch. + +## Dependency updates + +`renovate.json` configures Renovate to keep dependencies current: + +- the Dockerfile base image, +- the actions used in the Gitea workflow, +- versioned front-end dependencies referenced in HTML. + +There are currently no external front-end dependencies. When one is added via a +CDN, Renovate will track it if it is either annotated with a comment, e.g. + +```html + + +``` + +or referenced through a versioned jsDelivr / unpkg npm URL, which is detected +automatically. + ## Files -| File | Purpose | -| ------------- | ------------------------------------------------ | -| `index.html` | Page structure. | -| `styles.css` | Both colour schemes, selected via `data-theme`. | -| `messages.js` | The list of messages (fill this in). | -| `script.js` | Random message selection and the theme toggle. | +| File | Purpose | +| ------------------------------------- | ------------------------------------------------ | +| `index.html` | Page structure. | +| `styles.css` | Both colour schemes, selected via `data-theme`. | +| `messages.js` | The list of messages (fill this in). | +| `script.js` | Random message selection and the theme toggle. | +| `Dockerfile` / `default.conf` | Container image and nginx static-serving config. | +| `.gitea/workflows/` | Gitea Actions build-and-publish pipeline. | +| `renovate.json` | Renovate dependency-update configuration. | -- 2.52.0