Week 19 of 2026
Development log of Better Tech Club website
5 items
Create a page for Hilversum and a navigation
On by
Cards on the home page are smaller now, with a photo of the location, name and country. The whole card is a link to a detailed local group page. From local groups' pages there is a link back in the header. It's probably not intuitive enough. We need to figure out how to make the back navigation more obvious.
new file mode 100644
index 0000000..723e921
Binary files /dev/null and b/bussum-bibliotheek.jpg differindex e282566..131a74a 100644
--- a/bussum.html
+++ b/bussum.html
@@ -75,7 +75,7 @@
= }
=
= .local-group-page {
- #big-icon {
+ #big-logo {
= height: 6rem;
= align-self: center;
= }
@@ -150,6 +150,11 @@
= color: var(--accent-color);
= font-size: 1.5rem;
= rotate: -4deg;
+
+ a {
+ color: inherit !important;
+ text-decoration: none;
+ }
= }
= p {
= margin: -0.8rem 0px;
@@ -262,7 +267,7 @@
=
= <header>
= <hgroup>
- <small>Better Tech Club</small>
+ <small><a href="index.html">Better Tech Club</a></small>
= <h1> Bussum</h1>
= <p>The Netherlands</p>
= </hgroup>new file mode 100644
index 0000000..23ddb21
Binary files /dev/null and b/duurzaamheidscentrum-hilversum.jpg differnew file mode 100644
index 0000000..cc05e68
--- /dev/null
+++ b/hilversum.html
@@ -0,0 +1,323 @@
+<!doctype html>
+<html class="no-js local-group-page" lang="en">
+ <head>
+ <meta charset="utf-8">
+ <meta http-equiv="x-ua-compatible" content="ie=edge">
+ <title>Better Tech Club Hilversum</title>
+ <meta name="description" content="A network of local groups for the promotion and mutual support of free and open source software use.">
+ <meta name="viewport" content="width=device-width, initial-scale=1">
+ <link rel="icon" type="image/png" href="/favicon-96x96.png" sizes="96x96" />
+ <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
+ <link rel="shortcut icon" href="/favicon.ico" />
+ <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
+ <meta name="apple-mobile-web-app-title" content="Better Tech Club" />
+ <link rel="manifest" href="/site.webmanifest" />
+ <style type="text/css" media="screen">
+
+ @import url("fonts/caveat/caveat.css");
+ @import url("fonts/cooper-hewitt/cooper-hewitt.css");
+
+ html {
+ --content-width: 60rch;
+ --bg-color: oklch(35% 30% 260deg);
+ --fg-color: oklch(80% 0% 0deg);
+ --accent-color: #fac036; /* This is the yellow color taken from the logo */
+ --contrast-bg-color: oklch(20% 0% 0deg);
+ --contrast-fg-color: oklch(80% 0% 0deg);
+ --popup-bg-color: oklch(from var(--fg-color) 75% c h);
+ --popup-fg-color: oklch(from var(--fg-color) 10% c h);;
+
+ font-family: "Cooper Hewitt", sans-serif;
+ font-weight: 500;
+
+ font-size: 12px; /* <-- 1rem */
+
+ @media(width > 480px) {
+ font-size: 16px; /* <-- 1rem */
+ }
+
+ @media(width > 560px) {
+ font-size: 20px; /* <-- 1rem */
+ }
+
+ @media(width > 780px) {
+ font-size: 24px; /* <-- 1rem */
+ }
+ }
+
+ html, body {
+ background: var(--bg-color);
+ min-height: 100vh;
+ color: var(--fg-color);
+ margin: 0;
+ padding: 0;
+ }
+
+ body {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: space-between;
+ background: linear-gradient(
+ var(--bg-color),
+ oklch(from var(--bg-color) 25% 35% h)
+ );
+ }
+
+
+ a:link, a:visited {
+ color: var(--fg-color);
+ }
+
+ #big-logo {
+ max-width: 60%;
+ max-height: 30vh;
+ }
+
+ .local-group-page {
+ #big-logo {
+ height: 6rem;
+ align-self: center;
+ }
+
+ main {
+ display: grid;
+ grid-template-columns: 1fr;
+ gap: 2rem;
+ align-items: start;
+
+ font-size: 2rem;
+
+ @media(width > 560px) {
+ grid-template-columns: 1fr 1fr;
+ font-size: 1rem;
+ }
+ }
+
+ .members ul {
+ display: grid;
+ grid-template-columns: 1fr;
+ padding: 0;
+ gap: 0.2rem 1rem;
+
+ li {
+ display: block;
+ }
+
+ @media(width > 780px) {
+ grid-template-columns: 1fr 1fr;
+ }
+ }
+ }
+
+
+ #hosts {
+ width: 100%;
+ }
+
+ p {
+ line-height: 1.5;
+ hyphens: auto;
+ }
+
+ h2 {
+ font-size: 1.4em;
+ align-self: start;
+ color: var(--accent-color);
+ font-family: "Caveat";
+ margin-bottom: 0;
+ }
+
+ header {
+ display: flex;
+ justify-content: space-between;
+
+ h1 {
+ font-size: 4rem;
+ &:has(#big-logo) > span {
+ display: none;
+ }
+ }
+
+ hgroup {
+ h1 {
+ margin: 0
+ }
+ small {
+ display: block;
+ font-family: "Caveat";
+ font-weight: bold;
+ color: var(--accent-color);
+ font-size: 1.5rem;
+ rotate: -4deg;
+
+ a {
+ color: inherit !important;
+ text-decoration: none;
+ }
+ }
+
+
+
+ p {
+ margin: -0.8rem 0px;
+ font-weight: bold;
+ }
+ }
+ }
+
+
+ /* Containers */
+ main, header {
+ width: 100%;
+ max-width: var(--content-width);
+ padding: 2rem;
+ box-sizing: border-box;
+ }
+
+ main {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ }
+
+ footer {
+ background: var(--contrast-bg-color);
+ color: var(--contrast-fg-color);
+ width: 100%;
+ padding: 2rem 0;
+ font-size: 0.8rem;
+
+ p {
+ text-align: center;
+ }
+ }
+
+ .local-group {
+ /* Parameters to tweak */
+ --vertical-padding: 2rem;
+ --max-horizontal-padding: 2rem;
+
+ /* Intermediate calculations */
+ --margin-width: calc((100vw - 100%) / 2); /* The width between content and the edge of a viewport */
+ --horizontal-padding: min(
+ var(--margin-width), /* Take the whole margin... */
+ var(--max-horizontal-padding) /* ...but no more than this */
+ );
+
+ background: var(--popup-bg-color);
+ color: var(--popup-fg-color);
+ margin: 1rem 0;
+ width: 100%;
+ border-radius: min(
+ 0.4rem,
+ var(--margin-width)
+ );
+
+ padding:
+ var(--vertical-padding)
+ var(--horizontal-padding)
+ ;
+
+ strong {
+ font-weight: bold;
+ text-decoration: none;
+ }
+
+ a:link, a:visited {
+ color: inherit;
+ }
+
+ h3 {
+ font-size: 2rem;
+ font-weight: 800;
+ }
+
+ dt {
+ float: left;
+ clear: both;
+ }
+
+ dd:has(+ dt) {
+ margin-bottom: 1rem;
+ }
+ }
+
+ #reasons-why {
+ width: 100%;
+ padding: 0;
+ font-size: max(
+ 1.4rem, /* Never less than this */
+ min(
+ 2.1rem, /* Never more than this */
+ 5.5vw /* Otherwise scale with the viewport width */
+ )
+ );
+ font-weight: 100;
+ text-align: left;
+ margin-top: 1rem;
+ }
+ </style>
+ </head>
+ <body>
+ <!--[if lt IE 8]>
+ <p class="browserupgrade">
+ You are using an <strong>outdated</strong> browser. Please
+ <a href="http://browsehappy.com/">upgrade your browser</a> to improve
+ your experience.
+ </p>
+ <![endif]-->
+
+ <header>
+ <hgroup>
+ <small><a href="index.html">Better Tech Club</a></small>
+ <h1>Hilversum</h1>
+ <p>The Netherlands</p>
+ </hgroup>
+ <img
+ id="big-logo"
+ src="better-tech-club_icon.svg"
+ alt="Better Tech Club icon"
+ title="Better Tech Club"
+ />
+ </header>
+
+ <main>
+ <section class="time-and-place">
+ <h2>Time and place</h2>
+
+ <p>
+ See <a href="https://www.dchilversum.nl/agenda/">dchilversum.nl/agenda/</a><br />
+ (search for <em>Techtuinieren</em> and <em>Duurzaam Digitaal Spreekuur</em>).
+ </p>
+
+ <p>
+ <strong>Duur­zaam­heids­cen­trum Hilversum,</strong><br/>
+ Bus­sum­er­stra­at 69C<br/>
+ 1211 BJ Hilversum
+ </p>
+
+ </section>
+ <section class="members">
+ <h2>Regular members</h2>
+ <ul id="hosts">
+ <li>Hiske</li>
+ <li>Geert</li>
+ </ul>
+ </section>
+ </main>
+
+ <footer>
+ <p>
+ <strong>Better Tech Club</strong>
+ formerly known as
+ <img
+ src="foss-for-normies-icon.svg"
+ alt="FOSS for Normies icon"
+ style="vertical-align: -12px"
+ />
+ FOSS for Normies.
+ </p>
+ </footer>
+ </body>
+</html>index d7d9ab5..b939583 100644
--- a/index.html
+++ b/index.html
@@ -81,6 +81,29 @@
= margin-bottom: 0;
= }
=
+ hgroup {
+ h1, h2, h3, h4, h5, h6 {
+ margin: 0
+ }
+ small {
+ display: block;
+ font-family: "Caveat";
+ font-weight: bold;
+ color: var(--accent-color);
+ font-size: 1.5rem;
+ rotate: -4deg;
+
+ a {
+ color: inherit !important;
+ text-decoration: none;
+ }
+ }
+ p {
+ margin: -0.8rem 0px;
+ font-weight: bold;
+ }
+ }
+
= header {
= h1 {
= font-size: 4rem;
@@ -113,8 +136,8 @@
=
= .local-group {
= /* Parameters to tweak */
- --vertical-padding: 2rem;
- --max-horizontal-padding: 2rem;
+ --vertical-padding: 1rem;
+ --max-horizontal-padding: 1rem;
=
= /* Intermediate calculations */
= --margin-width: calc((100vw - 100%) / 2); /* The width between content and the edge of a viewport */
@@ -122,7 +145,9 @@
= var(--margin-width), /* Take the whole margin... */
= var(--max-horizontal-padding) /* ...but no more than this */
= );
-
+
+ display: flex;
+ gap: 1rem;
= background: var(--popup-bg-color);
= color: var(--popup-fg-color);
= margin: 1rem 0;
@@ -142,12 +167,18 @@
= text-decoration: none;
= }
=
- a:link, a:visited {
- color: inherit;
+ &:link, &:visited {
+ text-decoration: none;
+ }
+
+ > img {
+ height: 5rem;
+ float: left;
+ border-radius: 0.3rem;
= }
=
= h3 {
- font-size: 2rem;
+ font-size: 3rem;
= font-weight: 800;
= }
=
@@ -229,51 +260,24 @@
=
= <h2>Local groups</h2>
=
- <section class="local-group" id="foss-for-bussum">
- <h3>Bussum, NL</h3>
- <dl>
- <dt title="time">🕕</dt>
- <dd>Every Tuesday, 18:00 - 20:00</dd>
-
- <dt title="place">📍</dt>
- <dd>
- <strong>Bibliotheek Bussum</strong><br/>
- Wil­helm­ina­plant­soen 18<br/>
- 1404 JB Bussum
- </dd>
-
- <dt title="hosts">🫂</dt>
- <dd><a href="https://tad-lispy.com/">Tad Lispy</a></dd>
- <dd><a href="https://jewiet.com/">Fana Mehari</a></dd>
- <dd>Arleen</dd>
- <dd>Daniel Agorander</dd>
- <dd>Hans</dd>
- <dd>Marianne</dd>
- <dd>Jorrit Linnert</dd>
- </dl>
- </section>
+ <a class="local-group" id="foss-for-bussum" href="bussum.html">
+ <img src="bussum-bibliotheek.jpg" alt="Bibliotheek Bussum" />
+ <hgroup>
+ <h3>Bussum</h3>
+ <p>The Netherlands</p>
+ </hgroup>
+ </a>
=
- <section class="local-group" id="duurzaamheidscentrum-hilversum">
- <h3>Hilversum, NL</h3>
- <dl>
- <dt title="time">🕑</dt>
- <dd>
- See <a href="https://www.dchilversum.nl/agenda/">dchilversum.nl/agenda/</a><br />
- (search for <em>Techtuinieren</em> and <em>Duurzaam Digitaal Spreekuur</em>).
- </dd>
-
- <dt title="place">📍</dt>
- <dd>
- <strong>Duur­zaam­heids­cen­trum Hilversum,</strong><br/>
- Bus­sum­er­stra­at 69C<br/>
- 1211 BJ Hilversum
- </dd>
-
- <dt title="hosts">🫂</dt>
- <dd>Hiske</dd>
- <dd>Geert</dd>
- </dl>
- </section>
+ <a class="local-group" id="duurzaamheidscentrum-hilversum" href="hilversum.html">
+ <img
+ src="duurzaamheidscentrum-hilversum.jpg"
+ alt="Duurzaamheidscentrum Hilversum"
+ />
+ <hgroup>
+ <h3>Hilversum</h3>
+ <p>The Netherlands</p>
+ </hgroup>
+ </a>
=
= <p>Currently there are only two groups. Do you want to join the network? Get in touch.</p>
=Add a title to each back link for accessibility
On by
index 131a74a..3e01b07 100644
--- a/bussum.html
+++ b/bussum.html
@@ -267,7 +267,7 @@
=
= <header>
= <hgroup>
- <small><a href="index.html">Better Tech Club</a></small>
+ <small><a href="index.html" title="Home page">Better Tech Club</a></small>
= <h1> Bussum</h1>
= <p>The Netherlands</p>
= </hgroup>index cc05e68..72b87f7 100644
--- a/hilversum.html
+++ b/hilversum.html
@@ -270,7 +270,7 @@
=
= <header>
= <hgroup>
- <small><a href="index.html">Better Tech Club</a></small>
+ <small><a href="index.html" title="Home page">Better Tech Club</a></small>
= <h1>Hilversum</h1>
= <p>The Netherlands</p>
= </hgroup>Update the spec with the new Hilversum page
On by
Also write some TODO comments to make the assetions more robust. I'll implement them when I have more time.
index 97a1a2f..e4cbbc8 100644
--- a/spec/layout.md
+++ b/spec/layout.md
@@ -4,11 +4,16 @@ interpreter: bb spec/interpreters/web_automation.clj
=
=# Basic Layout of the Page
=
+TODO: Always check for an OK status code
+TODO: Reuse pages list for viewport and title
+TODO: Discover pages and error if there is an unlisted page
+
=## All pages fit in a viewport
=
= * Serve the `.` directory on port `1234`
= * Test if page at `http://localhost:1234/` fits in a viewport
= * Test if page at `http://localhost:1234/bussum.html` fits in a viewport
+ * Test if page at `http://localhost:1234/hilversum.html` fits in a viewport
=
=
=## The Width of the Body Doesn't Exceed the Viewport
@@ -24,6 +29,7 @@ parameters:
= * Open the `firefox` browser
= * Set the browser window size to `320` x `480`
= * Navigate to `http://localhost:1234/`
+ * // TODO: The status code should be `200`
= * The content is not wider than the viewport
=
=
@@ -32,6 +38,7 @@ parameters:
= * Serve the `.` directory on port `1234`
= * Test if page at `http://localhost:1234/` has title `Better Tech Club`
= * Test if page at `http://localhost:1234/bussum.html` has title `Better Tech Club Bussum`
+ * Test if page at `http://localhost:1234/hilversum.html` has title `Better Tech Club Hilversum`
=
=
=## The page title is correctRefactor the spec: Use TBB delegation
On by
Using the delegation API of TBB we can now list all pages together with their expected titles in a table, and run scenarios on each page. This reduces repetition in the spec document, thus making it more robust.
Personally I'm very excited about it, because it's the first time I use double delegation, i.e. a delegated scenario delegates further. In this case
-
"There are following pages" step delegates multiple times to "The Page is All Right" scenario (once for each page in the table)
-
"The Page Is All Right" scenario delegates to two specialized scenarios:
- The Page Title Is Correct
- The Width of the Body Doesn't Exceed the Viewport
Each time passing the parameters originally provided by "There are following pages" scenario.
Notice how each of these fixture can be executed on it's own, because of the parameters have default (placeholder) values that are actually correct.
index db80f45..3c51a56 100644
--- a/spec/interpreters/web_automation.clj
+++ b/spec/interpreters/web_automation.clj
@@ -2,13 +2,14 @@
= (:require
= [babashka.fs :as fs]
= [babashka.process :refer [destroy-tree process]]
- [taoensso.timbre :as timbre]
= [etaoin.api :as e]
+ [taoensso.timbre :as timbre]
= [tbb]))
=
=(def server-log-file (str (fs/create-temp-file {:prefix "btc-miniserve" :suffix ".log"})))
=(def miniserve-process (atom nil))
=(def web-driver (atom nil))
+(def expected-pages (atom []))
=
=(tbb/implement-step
= "Serve the {0} directory on port {1}"
@@ -27,6 +28,18 @@
= (slurp)
= (tbb/send-text))))
=
+(tbb/implement-step
+ "There are following pages"
+ (fn [data]
+ (let [pages (flatten (map tbb/table->maps (:tables data)))]
+ (tbb/send-snippet "edn" (tbb/prettify pages))
+ (reset! expected-pages (into-array pages))
+ (doall (map #(tbb/delegate "spec/layout.md"
+ "The Page Is All Right"
+ {"url" (:URL %)
+ "title" (:Title %)})
+ pages)))))
+
=(tbb/implement-step
= "Test if page at {0} fits in a viewport"
= (fn [url data]
@@ -38,7 +51,7 @@
= "Test if page at {0} has title {1}"
= (fn [url title _data]
= (tbb/delegate "spec/layout.md"
- "The page title is correct"
+ "The Page Title Is Correct"
= {:url url
= :title title})))
=index e4cbbc8..89fd5dc 100644
--- a/spec/layout.md
+++ b/spec/layout.md
@@ -2,46 +2,47 @@
=interpreter: bb spec/interpreters/web_automation.clj
=---
=
-# Basic Layout of the Page
+# Basic Layout of the Website
=
-TODO: Always check for an OK status code
-TODO: Reuse pages list for viewport and title
-TODO: Discover pages and error if there is an unlisted page
=
-## All pages fit in a viewport
+
+## All Pages Are Allright
=
= * Serve the `.` directory on port `1234`
- * Test if page at `http://localhost:1234/` fits in a viewport
- * Test if page at `http://localhost:1234/bussum.html` fits in a viewport
- * Test if page at `http://localhost:1234/hilversum.html` fits in a viewport
+ * There are following pages
=
+ | URL | Title |
+ |--------------------------------------|----------------------------|
+ | http://localhost:1234/ | Better Tech Club |
+ | http://localhost:1234/bussum.html | Better Tech Club Bussum |
+ | http://localhost:1234/hilversum.html | Better Tech Club Hilversum |
=
-## The Width of the Body Doesn't Exceed the Viewport
+ This step runs "The Page is All Right" scenario once for every page above.
=
-This scenario asserts that page content fits inside the viewport, i.e. there is no need for horizontal scrolling
+ * // TODO: There are no other pages
+
+ Discover pages by following links from the entry-points given in the previous step, and error if there is an unlisted page.
+
+## The Page Is All Right
=
=``` yaml tbb
=tags: [ fixture ]
=parameters:
= url: http://localhost:1234/
+ title: Better Tech Club
=```
=
- * Open the `firefox` browser
- * Set the browser window size to `320` x `480`
- * Navigate to `http://localhost:1234/`
- * // TODO: The status code should be `200`
- * The content is not wider than the viewport
+ * Test if page at `http://localhost:1234/` has title `Better Tech Club`
=
+ This step runs "The Page Title is Correct" with appropriate parameters.
=
-## All pages have correct titles
+ * Test if page at `http://localhost:1234/` fits in a viewport
=
- * Serve the `.` directory on port `1234`
- * Test if page at `http://localhost:1234/` has title `Better Tech Club`
- * Test if page at `http://localhost:1234/bussum.html` has title `Better Tech Club Bussum`
- * Test if page at `http://localhost:1234/hilversum.html` has title `Better Tech Club Hilversum`
+ This step runs "The Width of the Body Doesn't Exceed the Viewport" with appropriate parameters.
=
+ TODO: List viewport widths to test and test each of them, not just 320 x 480.
=
-## The page title is correct
+## The Page Title Is Correct
=
=``` yaml tbb
=tags: [ fixture ]
@@ -53,3 +54,19 @@ parameters:
= * Open the `firefox` browser
= * Navigate to `http://localhost:1234/`
= * The title should be `Better Tech Club`
+
+
+## The Width of the Body Doesn't Exceed the Viewport
+
+This scenario asserts that page content fits inside the viewport, i.e. there is no need for horizontal scrolling
+
+``` yaml tbb
+tags: [ fixture ]
+parameters:
+ url: http://localhost:1234/
+```
+
+ * Open the `firefox` browser
+ * Set the browser window size to `320` x `480`
+ * Navigate to `http://localhost:1234/`
+ * The content is not wider than the viewportFix a typo
On by
Thanks Harper!
index 89fd5dc..bc3bd11 100644
--- a/spec/layout.md
+++ b/spec/layout.md
@@ -6,7 +6,7 @@ interpreter: bb spec/interpreters/web_automation.clj
=
=
=
-## All Pages Are Allright
+## All Pages Are Alright
=
= * Serve the `.` directory on port `1234`
= * There are following pages