]> git.draconx.ca Git - homepage.git/commitdiff
cdecl99-1.3 bash-5 hotfix master
authorNick Bowler <nbowler@draconx.ca>
Wed, 17 Apr 2024 02:34:19 +0000 (22:34 -0400)
committerNick Bowler <nbowler@draconx.ca>
Wed, 17 Apr 2024 02:40:49 +0000 (22:40 -0400)
Turns out that prior to release the package was not tested on any
systems where /bin/sh is bash-5, and the configure script hits a
parser bug in this shell regarding for loops in case statements.

A workaround is to set CONFIG_SHELL environment variable to some
other shell without this bug.  But if that's not possible, this
patch can be applied to avoid the bug.

95 files changed:
.gitmodules
Rules
content/archive/cdecl99/cdecl99-1.2.tar.gz [new symlink]
content/archive/cdecl99/cdecl99-1.2.tar.gz.sig [new symlink]
content/archive/cdecl99/cdecl99-1.2.tar.xz [new symlink]
content/archive/cdecl99/cdecl99-1.2.tar.xz.sig [new symlink]
content/archive/cdecl99/cdecl99-1.3-bash5-fix.patch [new file with mode: 0644]
content/archive/cdecl99/cdecl99-1.3.tar.gz [new symlink]
content/archive/cdecl99/cdecl99-1.3.tar.gz.sig [new symlink]
content/archive/cdecl99/cdecl99-1.3.tar.lz [new symlink]
content/archive/cdecl99/cdecl99-1.3.tar.lz.sig [new symlink]
content/archive/rarpd-dx/rarpd-dx-1.tar.gz [new symlink]
content/archive/rarpd-dx/rarpd-dx-1.tar.gz.sig [new symlink]
content/archive/rarpd-dx/rarpd-dx-1.tar.lz [new symlink]
content/archive/rarpd-dx/rarpd-dx-1.tar.lz.sig [new symlink]
content/icons/down.svg
content/icons/folder.svg
content/icons/return.svg
content/icons/up.svg
content/images/eclipse-20220515-223224.jpg [new symlink]
content/images/eclipse-20220515-223224.yaml [new file with mode: 0644]
content/images/eclipse-20220515-223904.jpg [new symlink]
content/images/eclipse-20220515-223904.yaml [new file with mode: 0644]
content/images/eclipse-20220515-225442.jpg [new symlink]
content/images/eclipse-20220515-225442.yaml [new file with mode: 0644]
content/images/eclipse-20220515-230507.jpg [new symlink]
content/images/eclipse-20220515-230507.yaml [new file with mode: 0644]
content/images/eclipse-20220515-231141.jpg [new symlink]
content/images/eclipse-20220515-231141.yaml [new file with mode: 0644]
content/images/eclipse-20220515-231939.jpg [new symlink]
content/images/eclipse-20220515-231939.yaml [new file with mode: 0644]
content/images/eclipse-20220515-232447.jpg [new symlink]
content/images/eclipse-20220515-232447.yaml [new file with mode: 0644]
content/images/eclipse-20220515-232709.jpg [new symlink]
content/images/eclipse-20220515-232709.yaml [new file with mode: 0644]
content/images/eclipse-20220515-233454.jpg [new symlink]
content/images/eclipse-20220515-233454.yaml [new file with mode: 0644]
content/images/eclipse-20220515-233803.jpg [new symlink]
content/images/eclipse-20220515-233803.yaml [new file with mode: 0644]
content/images/eclipse-20220515-234126.jpg [new symlink]
content/images/eclipse-20220515-234126.yaml [new file with mode: 0644]
content/images/eclipse-20220515-234942.jpg [new symlink]
content/images/eclipse-20220515-234942.yaml [new file with mode: 0644]
content/images/eclipse-20220515-235136.jpg [new symlink]
content/images/eclipse-20220515-235136.yaml [new file with mode: 0644]
content/images/eclipse-20220516-000739.jpg [new symlink]
content/images/eclipse-20220516-000739.yaml [new file with mode: 0644]
content/images/eclipse-20240408-143755.jpg [new symlink]
content/images/eclipse-20240408-143755.yaml [new file with mode: 0644]
content/images/eclipse-20240408-151623.jpg [new symlink]
content/images/eclipse-20240408-151623.yaml [new file with mode: 0644]
content/images/eclipse-20240408-152435.jpg [new symlink]
content/images/eclipse-20240408-152435.yaml [new file with mode: 0644]
content/images/eclipse-20240408-152639.jpg [new symlink]
content/images/eclipse-20240408-152639.yaml [new file with mode: 0644]
content/images/eclipse-20240408-152802.jpg [new symlink]
content/images/eclipse-20240408-152802.yaml [new file with mode: 0644]
content/images/eclipse-20240408-152846.jpg [new symlink]
content/images/eclipse-20240408-152846.yaml [new file with mode: 0644]
content/images/m48t59y-fixed.jpg [new symlink]
content/images/m48t59y-fixed.yaml [new file with mode: 0644]
content/images/m48t59y-installed.jpg [new symlink]
content/images/m48t59y-installed.yaml [new file with mode: 0644]
content/images/m48t59y-solder.jpg [new symlink]
content/images/m48t59y-solder.yaml [new file with mode: 0644]
content/images/m48t59y-terminals.jpg [new symlink]
content/images/m48t59y-terminals.yaml [new file with mode: 0644]
content/images/m48t59y.jpg [new symlink]
content/images/m48t59y.yaml [new file with mode: 0644]
content/projects.md
content/projects/cdecl99.md
content/projects/rarpd-dx.md [new file with mode: 0644]
content/projects/rrace.md [new file with mode: 0644]
content/pubring.gpg [new file with mode: 0644]
content/style.scss
content/weblog/eclipse-20220515.md [new file with mode: 0644]
content/weblog/eclipse-20240408.md [new file with mode: 0644]
content/weblog/responsive-tables.md
content/weblog/ultra60-nvram.md [new file with mode: 0644]
gitmodules/cdecl99
gitmodules/rarpd-dx [new submodule]
gitmodules/rrace [new submodule]
layouts/default.xml
layouts/default.xsl
layouts/embed-svg.xsl
layouts/functions.xsl
layouts/whitespace.xsl [new file with mode: 0644]
lib/colourmap.scss [new file with mode: 0644]
lib/compiledcontent.rb [new file with mode: 0644]
lib/css-darkmode.rb [new file with mode: 0644]
lib/gpg-wkd.rb [new file with mode: 0644]
lib/helpers.rb
lib/project-readme.rb
lib/scss-var.rb
tools/weblog-update.rb

index d345e88f3f97574a85a92d3c00aaf546522f22bc..f24b460ec36277bc41598ea93e6d9ce69100bd7d 100644 (file)
@@ -4,3 +4,9 @@
 [submodule "gitmodules/cdecl99"]
        path = gitmodules/cdecl99
        url = https://git.draconx.ca/cdecl99.git
+[submodule "gitmodules/rrace"]
+       path = gitmodules/rrace
+       url = https://git.draconx.ca/rrace.git
+[submodule "gitmodules/rarpd-dx"]
+       path = gitmodules/rarpd-dx
+       url = https://git.draconx.ca/rarpd-dx.git
diff --git a/Rules b/Rules
index 26bcb40ac7b1a8f0dbe86ba9f22b5edbc44d6300..1aa5907bcd4185432355a52907218eea35f4b4d2 100644 (file)
--- a/Rules
+++ b/Rules
@@ -1,6 +1,6 @@
 #!/usr/bin/env ruby
 #
-# Copyright © 2018-2020 Nick Bowler
+# Copyright © 2018-2022 Nick Bowler
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -68,20 +68,24 @@ preprocess do
 
         @items.create("", attrs, "#{dir}/index.lst")
     end
+
+    create_wkd_items(@items["/pubring.gpg"])
+    create_hkp_items(@items["/pubring.gpg"])
 end
 
 postprocess do
     # Gzip all text items for nginx http_static
-    if !ENV['GZIP_SITE'].to_s.empty?
+    gzip_site = !ENV['GZIP_SITE'].to_s.empty?
+    if gzip_site
         reps = @items.flat_map(&:reps).each do |rep|
-            file = rep.raw_path
-            next if !file or rep.binary?
-
-            if system("gzip", "-ck9", file, [:out]=>[file + ".gz.tmp", "wb"])
-                system("touch", "-r", file, file + ".gz.tmp")
-                File.rename(file + ".gz.tmp", file + ".gz")
-            else
-                File.unlink(file + ".gz.tmp")
+            next if rep.binary?
+            rep.instance_variable_get(:@item_rep).raw_paths[:last].each do |f|
+                if system("gzip", "-nck9", f, [:out]=>["#{f}.gz.tmp", "wb"])
+                    system("touch", "-r", f, "#{f}.gz.tmp")
+                    File.rename("#{f}.gz.tmp", "#{f}.gz")
+                else
+                    File.unlink("#{f}.gz.tmp")
+                end
             end
         end
     end
@@ -206,16 +210,39 @@ compile '/images/*.jpg', rep: :info do
 end
 
 compile '/**/*.scss' do
-    filter :sass, syntax: :scss
+    filter :sass, syntax: :scss, load_paths: ["."]
     filter :css_source, uribase: \
         "https://git.draconx.ca/gitweb/homepage.git/blob/" +
         @item[:gitrev] + ":"
     filter :css_clean_selectors, \
         preserve_comments: true, \
         preserve_hacks: true
+    snapshot :before_darkmode
+    filter :css_darkmode
     write @item.identifier.without_ext + '.css'
 end
 
+compile '/style.scss', rep: :dark do
+    filter :compiled_content, snapshot: :before_darkmode
+    filter :css_darkmode, alternate: true
+    write "/dark.css"
+end
+
+compile '/gpg/*' do
+    filter :wkd_export_armor
+    write "/pubring/#{@item.identifier.components.last}.asc"
+    write "/pubring/#{@item[:id64]}.asc" if @item[:id64]
+    write "/pubring/#{@item[:id32]}.asc" if @item[:id32]
+end
+
+compile '/gpg/*', rep: :hu do
+    if @item[:wkd_hash]
+        filter :wkd_export
+        write "/pubring/wkd/" + @item[:wkd_hash]
+    end
+end
+ignore '/*.gpg'
+
 compile '/**/*.svg' do
     filter :scour, comment_stripping: true
     write @item.identifier.to_s
diff --git a/content/archive/cdecl99/cdecl99-1.2.tar.gz b/content/archive/cdecl99/cdecl99-1.2.tar.gz
new file mode 120000 (symlink)
index 0000000..f2992a8
--- /dev/null
@@ -0,0 +1 @@
+../../../.git/annex/objects/2f/p1/SHA512-s966058--01e7f4c09c32beac469900895d0e3115ceb083bc1e273cff30cd9a7b0427359c826e7d20d0d578af92ee88598f66f629f8c756ac8b5fa38f5f930e0f77d2ac76/SHA512-s966058--01e7f4c09c32beac469900895d0e3115ceb083bc1e273cff30cd9a7b0427359c826e7d20d0d578af92ee88598f66f629f8c756ac8b5fa38f5f930e0f77d2ac76
\ No newline at end of file
diff --git a/content/archive/cdecl99/cdecl99-1.2.tar.gz.sig b/content/archive/cdecl99/cdecl99-1.2.tar.gz.sig
new file mode 120000 (symlink)
index 0000000..42a4117
--- /dev/null
@@ -0,0 +1 @@
+../../../.git/annex/objects/pj/JW/SHA512-s310--d32edf921631e3af7fcc7aacb9579c32215f86696e83d4bbead5ed998b1ed786a640e5e16bc7b9bfbf25980907396f8d8510bca70d7039beb908a030b30f32c3/SHA512-s310--d32edf921631e3af7fcc7aacb9579c32215f86696e83d4bbead5ed998b1ed786a640e5e16bc7b9bfbf25980907396f8d8510bca70d7039beb908a030b30f32c3
\ No newline at end of file
diff --git a/content/archive/cdecl99/cdecl99-1.2.tar.xz b/content/archive/cdecl99/cdecl99-1.2.tar.xz
new file mode 120000 (symlink)
index 0000000..7330ce4
--- /dev/null
@@ -0,0 +1 @@
+../../../.git/annex/objects/wg/1j/SHA512-s634148--a24f095fd66e0c7fceb1b089de92d5a96d8d21c2c59d5f91da8374ca1465f65041d806bcd5cd1299778dd4d25204390f437b481f8cb07b9fa0a88cecf19185c2/SHA512-s634148--a24f095fd66e0c7fceb1b089de92d5a96d8d21c2c59d5f91da8374ca1465f65041d806bcd5cd1299778dd4d25204390f437b481f8cb07b9fa0a88cecf19185c2
\ No newline at end of file
diff --git a/content/archive/cdecl99/cdecl99-1.2.tar.xz.sig b/content/archive/cdecl99/cdecl99-1.2.tar.xz.sig
new file mode 120000 (symlink)
index 0000000..d70b59e
--- /dev/null
@@ -0,0 +1 @@
+../../../.git/annex/objects/PV/QZ/SHA512-s310--204a5c7003d96860a28671778099578a2f89d5f0c5d8b0a0bf883762555f0db7ca202248630cf2c07858d86d7faf450f566103e94676cedcafbe0fbcafa7ac8f/SHA512-s310--204a5c7003d96860a28671778099578a2f89d5f0c5d8b0a0bf883762555f0db7ca202248630cf2c07858d86d7faf450f566103e94676cedcafbe0fbcafa7ac8f
\ No newline at end of file
diff --git a/content/archive/cdecl99/cdecl99-1.3-bash5-fix.patch b/content/archive/cdecl99/cdecl99-1.3-bash5-fix.patch
new file mode 100644 (file)
index 0000000..0ffc323
--- /dev/null
@@ -0,0 +1,49 @@
+-----BEGIN PGP SIGNED MESSAGE-----
+Hash: SHA256
+NotDashEscaped: You need gpg to verify this message
+
+The configure script included with the cdecl99-1.3 release fails on
+bash-5 due to a parser bug in this shell.  On affected systems, a
+message similar to the following may be seen when running configure:
+
+  ./configure: line 6631: syntax error near unexpected token `do'
+  ./configure: line 6631: `do'
+
+You can work around the problem by setting the CONFIG_SHELL environment
+variable to some other shell before running configure; otherwise you can
+apply this patch file with a command such as:
+
+  % cd cdecl99-1.3
+  % patch -p1 <cdecl99-1.3-bash5-fix.patch
+
+*** cdecl99-1.3.orig/configure
+--- cdecl99-1.3/configure
+***************
+*** 6626,6633 ****
+    $as_echo_n "(cached) " >&6
+  else
+    dx_cv_have_alignas=no
+! for _dx_alignas
+! in '_Alignas(X)' '__attribute__((__aligned__(X)))' '__declspec(align(X))'
+  do
+  if ac_fn_c_compute_int "$LINENO" "sizeof (struct { char a; char ALIGNAS_TEST(4) b; }) >= 8" "_dx_tmp"        "#include <stddef.h>
+  #define ALIGNAS_TEST(X) $_dx_alignas
+--- 6626,6632 ----
+    $as_echo_n "(cached) " >&6
+  else
+    dx_cv_have_alignas=no
+! for _dx_alignas in '_Alignas(X)' '__attribute__((__aligned__(X)))' '__declspec(align(X))'
+  do
+  if ac_fn_c_compute_int "$LINENO" "sizeof (struct { char a; char ALIGNAS_TEST(4) b; }) >= 8" "_dx_tmp"        "#include <stddef.h>
+  #define ALIGNAS_TEST(X) $_dx_alignas
+-----BEGIN PGP SIGNATURE-----
+
+iQEzBAEBCAAdFiEEJagcgrvBtB9dtMuDW0XT0YW44fYFAmYfNBUACgkQW0XT0YW4
+4famBQf9GhDZJGOvrkDB5dGWwieXqFCWXJBbATVbY8JmY5wJupke5MqvDEQJ3tBr
+A2YIcQZ67+KjA2LwWTTQhQgKkveQs1HWFdTDrHnhTm4dF4jZdlxwVK3K3kHCcref
+uW+vl/6C9YkKcVcbf8OeHT4JVgYv2h7XRKW69XIB6lchdMc6rGNeStuCHyuGKYWG
+7n3WH9bA5OBPmPAd6yUPmU4urWBHjZwxyTaFBYBO7T+9R3Q4KvvyzCfxt1JxTUPS
+SmiQC1B0gg16/N2XJzCmnBsaVXSHdsvt8UrZeIZyWipejqtb2UR1MVnVToz/83NO
+gh5tyhxXnYiQgRqd2/uRg9z+GzziDw==
+=/RXF
+-----END PGP SIGNATURE-----
diff --git a/content/archive/cdecl99/cdecl99-1.3.tar.gz b/content/archive/cdecl99/cdecl99-1.3.tar.gz
new file mode 120000 (symlink)
index 0000000..b948234
--- /dev/null
@@ -0,0 +1 @@
+../../../.git/annex/objects/v9/65/SHA512-s897087--54ab0ce2db34beaee220cbed936b009added5a51b901c2251c7767df6e63a84a8ce6f59b59ec6b00925c2845674a8d5ef02e620bd5eb8074b52cd07441c005cd/SHA512-s897087--54ab0ce2db34beaee220cbed936b009added5a51b901c2251c7767df6e63a84a8ce6f59b59ec6b00925c2845674a8d5ef02e620bd5eb8074b52cd07441c005cd
\ No newline at end of file
diff --git a/content/archive/cdecl99/cdecl99-1.3.tar.gz.sig b/content/archive/cdecl99/cdecl99-1.3.tar.gz.sig
new file mode 120000 (symlink)
index 0000000..57d10b0
--- /dev/null
@@ -0,0 +1 @@
+../../../.git/annex/objects/4v/p3/SHA512-s310--b4b196e4df43c18634ba5dcc8823cf677931ceb0bd2965713db0a078d29e979c601f190e854caed60af7d7249a60f2b39cbd86c1bcdb9b43827945aa5acd5669/SHA512-s310--b4b196e4df43c18634ba5dcc8823cf677931ceb0bd2965713db0a078d29e979c601f190e854caed60af7d7249a60f2b39cbd86c1bcdb9b43827945aa5acd5669
\ No newline at end of file
diff --git a/content/archive/cdecl99/cdecl99-1.3.tar.lz b/content/archive/cdecl99/cdecl99-1.3.tar.lz
new file mode 120000 (symlink)
index 0000000..8382932
--- /dev/null
@@ -0,0 +1 @@
+../../../.git/annex/objects/3j/wf/SHA512-s591522--11f04eeda91ef127f6e3d5ef5e2cbb135e6a1f46a138640decbe80ed687c9fd2110a94b2fd883d8d0125704b36760c89cf41aeb5005f7965721b85fb55826b6c/SHA512-s591522--11f04eeda91ef127f6e3d5ef5e2cbb135e6a1f46a138640decbe80ed687c9fd2110a94b2fd883d8d0125704b36760c89cf41aeb5005f7965721b85fb55826b6c
\ No newline at end of file
diff --git a/content/archive/cdecl99/cdecl99-1.3.tar.lz.sig b/content/archive/cdecl99/cdecl99-1.3.tar.lz.sig
new file mode 120000 (symlink)
index 0000000..fbb9bb9
--- /dev/null
@@ -0,0 +1 @@
+../../../.git/annex/objects/3k/Mp/SHA512-s310--5c1d52ff133aeaaa7be59f5ec394ecf95d29341eb491909a12412f246b1d88251e6449aab9385797d0d3bf23eb6ff8fd8eedf80dbc446c4d61f5dbf43dc45ed4/SHA512-s310--5c1d52ff133aeaaa7be59f5ec394ecf95d29341eb491909a12412f246b1d88251e6449aab9385797d0d3bf23eb6ff8fd8eedf80dbc446c4d61f5dbf43dc45ed4
\ No newline at end of file
diff --git a/content/archive/rarpd-dx/rarpd-dx-1.tar.gz b/content/archive/rarpd-dx/rarpd-dx-1.tar.gz
new file mode 120000 (symlink)
index 0000000..5ed165b
--- /dev/null
@@ -0,0 +1 @@
+../../../.git/annex/objects/fv/p8/SHA512-s105283--742eeeccb8eba6daea7294ab696cd9e2ad92e84c1b81d5512c8654c5d1df258d5584c360569fe41b73b84757f0cfea93d5da1b67de59b4b920610009068b85de/SHA512-s105283--742eeeccb8eba6daea7294ab696cd9e2ad92e84c1b81d5512c8654c5d1df258d5584c360569fe41b73b84757f0cfea93d5da1b67de59b4b920610009068b85de
\ No newline at end of file
diff --git a/content/archive/rarpd-dx/rarpd-dx-1.tar.gz.sig b/content/archive/rarpd-dx/rarpd-dx-1.tar.gz.sig
new file mode 120000 (symlink)
index 0000000..6c7a819
--- /dev/null
@@ -0,0 +1 @@
+../../../.git/annex/objects/g6/j8/SHA512-s310--b40653638706881f135868329359e00c0c55134fadebebb4572f93de79c3d2ff39f6c0af7ad5dcc16f1ed9dd3b2afdca8b9af8dbd071a6b19afabc104658feb6/SHA512-s310--b40653638706881f135868329359e00c0c55134fadebebb4572f93de79c3d2ff39f6c0af7ad5dcc16f1ed9dd3b2afdca8b9af8dbd071a6b19afabc104658feb6
\ No newline at end of file
diff --git a/content/archive/rarpd-dx/rarpd-dx-1.tar.lz b/content/archive/rarpd-dx/rarpd-dx-1.tar.lz
new file mode 120000 (symlink)
index 0000000..cbf3770
--- /dev/null
@@ -0,0 +1 @@
+../../../.git/annex/objects/65/5W/SHA512-s82601--3642314e7390074a50f080c1d3039b1db6d57895efd40a8285a2cc95f5777962282b11edfee3a17caa10fe9c4dc129729fe4b3c6c02ddb411d1b6d57186e1278/SHA512-s82601--3642314e7390074a50f080c1d3039b1db6d57895efd40a8285a2cc95f5777962282b11edfee3a17caa10fe9c4dc129729fe4b3c6c02ddb411d1b6d57186e1278
\ No newline at end of file
diff --git a/content/archive/rarpd-dx/rarpd-dx-1.tar.lz.sig b/content/archive/rarpd-dx/rarpd-dx-1.tar.lz.sig
new file mode 120000 (symlink)
index 0000000..c219a09
--- /dev/null
@@ -0,0 +1 @@
+../../../.git/annex/objects/k5/6V/SHA512-s310--530702e16b139db74862244dde8ddbb8d8618c462b935e3e9bc02fdf7e0fa58ff4d952aa444acfb524e4e4c0f2317a7c20a6cc65ec64ff2f9442b9a5753cfafe/SHA512-s310--530702e16b139db74862244dde8ddbb8d8618c462b935e3e9bc02fdf7e0fa58ff4d952aa444acfb524e4e4c0f2317a7c20a6cc65ec64ff2f9442b9a5753cfafe
\ No newline at end of file
index 95b82afd11f63166b2f07d335b8a35951f667b14..0c05c068d7025860d18449ea55f5cbebd9f96233 100644 (file)
@@ -67,6 +67,7 @@
     <radialGradient
        inkscape:collect="always"
        xlink:href="#linearGradient8662"
+       class="shadow"
        id="radialGradient1444"
        gradientUnits="userSpaceOnUse"
        gradientTransform="matrix(1.000000,0.000000,0.000000,0.536723,1.614716e-15,16.87306)"
        sodipodi:rx="15.644737"
        sodipodi:cy="36.421127"
        sodipodi:cx="24.837126"
+       class="shadow"
        id="path8660"
        style="opacity:0.20454545;color:#000000;fill:url(#radialGradient1444);fill-opacity:1.0000000;fill-rule:evenodd;stroke:none;stroke-width:1.0000000;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:10.000000;stroke-dasharray:none;stroke-dashoffset:0.0000000;stroke-opacity:1.0000000;visibility:visible;display:inline;overflow:visible"
        sodipodi:type="arc" />
index 79b25c305beded4252b7359bda5e67493877ac8f..ff2f8d7312c038ccd714962422877cd7645ea097 100644 (file)
@@ -42,6 +42,7 @@
        r="117.14286" />
     <linearGradient
        inkscape:collect="always"
+       class="shadow"
        id="linearGradient5060">
       <stop
          style="stop-color:black;stop-opacity:1;"
@@ -81,6 +82,7 @@
     <linearGradient
        inkscape:collect="always"
        xlink:href="#linearGradient5048"
+       class="shadow"
        id="linearGradient6715"
        gradientUnits="userSpaceOnUse"
        gradientTransform="matrix(2.774389,0,0,1.969706,-1892.179,-872.8854)"
     <g
        style="display:inline"
        transform="matrix(2.262383e-2,0,0,2.086758e-2,43.38343,36.36962)"
+       class="shadow"
        id="g6707">
       <rect
          style="opacity:0.40206185;color:black;fill:url(#linearGradient6715);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
index d3cce96f1bee79a9a35b2aa72dac530b5f9d9778..81a5b906e03141b3372138c07bc2fbe9c2b9ee18 100644 (file)
@@ -80,6 +80,7 @@
     <radialGradient
        inkscape:collect="always"
        xlink:href="#linearGradient8662"
+       class="shadow"
        id="radialGradient8668"
        cx="24.837126"
        cy="36.421127"
        sodipodi:rx="15.644737"
        sodipodi:cy="36.421127"
        sodipodi:cx="24.837126"
+       class="shadow"
        id="path8660"
        style="opacity:0.14117647;color:#000000;fill:url(#radialGradient8668);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:10;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
        sodipodi:type="arc" />
index 54263df3e53745c408e187863ac8ad67aa803f11..63e8db3274238fa862644108860e3f3041ba1d37 100644 (file)
@@ -89,6 +89,7 @@
     <radialGradient
        inkscape:collect="always"
        xlink:href="#linearGradient8662"
+       class="shadow"
        id="radialGradient1444"
        gradientUnits="userSpaceOnUse"
        gradientTransform="matrix(1.000000,0.000000,0.000000,0.536723,1.614716e-15,16.87306)"
        sodipodi:rx="15.644737"
        sodipodi:cy="36.421127"
        sodipodi:cx="24.837126"
+       class="shadow"
        id="path8660"
        style="opacity:0.29946521;color:#000000;fill:url(#radialGradient1444);fill-opacity:1.0000000;fill-rule:evenodd;stroke:none;stroke-width:1.0000000;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:10.000000;stroke-dasharray:none;stroke-dashoffset:0.0000000;stroke-opacity:1.0000000;visibility:visible;display:inline;overflow:visible"
        sodipodi:type="arc" />
diff --git a/content/images/eclipse-20220515-223224.jpg b/content/images/eclipse-20220515-223224.jpg
new file mode 120000 (symlink)
index 0000000..ecd014e
--- /dev/null
@@ -0,0 +1 @@
+../../.git/annex/objects/7V/6K/SHA512-s62206--3c7b5e8560109819d8616fbb77e86dfe275da79bee14a99958b849e2d45fd82196a3aa88d06f7d4bb6a5258a9ee025f5505ee120388c5066dfa1b09e3c61772f/SHA512-s62206--3c7b5e8560109819d8616fbb77e86dfe275da79bee14a99958b849e2d45fd82196a3aa88d06f7d4bb6a5258a9ee025f5505ee120388c5066dfa1b09e3c61772f
\ No newline at end of file
diff --git a/content/images/eclipse-20220515-223224.yaml b/content/images/eclipse-20220515-223224.yaml
new file mode 100644 (file)
index 0000000..6752422
--- /dev/null
@@ -0,0 +1,4 @@
+---
+title: 2022-05-15/16 Lunar Eclipse from Ottawa
+copyright: 2022 Nick Bowler
+license: cc-by-sa-4.0
diff --git a/content/images/eclipse-20220515-223904.jpg b/content/images/eclipse-20220515-223904.jpg
new file mode 120000 (symlink)
index 0000000..8f52052
--- /dev/null
@@ -0,0 +1 @@
+../../.git/annex/objects/z1/gk/SHA512-s67272--ee40a65584c53a7bdf481f66b0633bfcb5612a832b6d033bd0e049d3b4556641168e4842e95a465031dccfb8516635c861ae64065a733cf7392bcbf88df790f3/SHA512-s67272--ee40a65584c53a7bdf481f66b0633bfcb5612a832b6d033bd0e049d3b4556641168e4842e95a465031dccfb8516635c861ae64065a733cf7392bcbf88df790f3
\ No newline at end of file
diff --git a/content/images/eclipse-20220515-223904.yaml b/content/images/eclipse-20220515-223904.yaml
new file mode 100644 (file)
index 0000000..6752422
--- /dev/null
@@ -0,0 +1,4 @@
+---
+title: 2022-05-15/16 Lunar Eclipse from Ottawa
+copyright: 2022 Nick Bowler
+license: cc-by-sa-4.0
diff --git a/content/images/eclipse-20220515-225442.jpg b/content/images/eclipse-20220515-225442.jpg
new file mode 120000 (symlink)
index 0000000..071bd37
--- /dev/null
@@ -0,0 +1 @@
+../../.git/annex/objects/56/0v/SHA512-s62547--acaa868e092acac5329ff17af7b36c6715b0b27c0c7e856199a5c0137fd6d27fbeb3cda7b2e71f6e45538108ecc3dc7782ea7a7ec332d2f903e79b2875d90b7c/SHA512-s62547--acaa868e092acac5329ff17af7b36c6715b0b27c0c7e856199a5c0137fd6d27fbeb3cda7b2e71f6e45538108ecc3dc7782ea7a7ec332d2f903e79b2875d90b7c
\ No newline at end of file
diff --git a/content/images/eclipse-20220515-225442.yaml b/content/images/eclipse-20220515-225442.yaml
new file mode 100644 (file)
index 0000000..6752422
--- /dev/null
@@ -0,0 +1,4 @@
+---
+title: 2022-05-15/16 Lunar Eclipse from Ottawa
+copyright: 2022 Nick Bowler
+license: cc-by-sa-4.0
diff --git a/content/images/eclipse-20220515-230507.jpg b/content/images/eclipse-20220515-230507.jpg
new file mode 120000 (symlink)
index 0000000..0b86aad
--- /dev/null
@@ -0,0 +1 @@
+../../.git/annex/objects/V2/pq/SHA512-s59928--c64ed7765384fd5bd27b61ccf2e927ae38dbce4737d50890c173a7e2b32ab47fea76129d804eb22462582d27e95d1703bb5c29e07e881ee4d91f3c9739f56e9a/SHA512-s59928--c64ed7765384fd5bd27b61ccf2e927ae38dbce4737d50890c173a7e2b32ab47fea76129d804eb22462582d27e95d1703bb5c29e07e881ee4d91f3c9739f56e9a
\ No newline at end of file
diff --git a/content/images/eclipse-20220515-230507.yaml b/content/images/eclipse-20220515-230507.yaml
new file mode 100644 (file)
index 0000000..6752422
--- /dev/null
@@ -0,0 +1,4 @@
+---
+title: 2022-05-15/16 Lunar Eclipse from Ottawa
+copyright: 2022 Nick Bowler
+license: cc-by-sa-4.0
diff --git a/content/images/eclipse-20220515-231141.jpg b/content/images/eclipse-20220515-231141.jpg
new file mode 120000 (symlink)
index 0000000..597a301
--- /dev/null
@@ -0,0 +1 @@
+../../.git/annex/objects/pq/vJ/SHA512-s61153--ca215607a273fee73a79513d2e53ca76225a428f2ee2898e7616798802dac52703b24551644f235eca14660a32f5959b59f0960157748d73d0ccc6513a501c08/SHA512-s61153--ca215607a273fee73a79513d2e53ca76225a428f2ee2898e7616798802dac52703b24551644f235eca14660a32f5959b59f0960157748d73d0ccc6513a501c08
\ No newline at end of file
diff --git a/content/images/eclipse-20220515-231141.yaml b/content/images/eclipse-20220515-231141.yaml
new file mode 100644 (file)
index 0000000..6752422
--- /dev/null
@@ -0,0 +1,4 @@
+---
+title: 2022-05-15/16 Lunar Eclipse from Ottawa
+copyright: 2022 Nick Bowler
+license: cc-by-sa-4.0
diff --git a/content/images/eclipse-20220515-231939.jpg b/content/images/eclipse-20220515-231939.jpg
new file mode 120000 (symlink)
index 0000000..111cfce
--- /dev/null
@@ -0,0 +1 @@
+../../.git/annex/objects/qK/wK/SHA512-s42573--ac7ec2f106c7924a66b021eac1d8e53d0f480fda49adb27ce6dfe715c05d4cd463a41ec4a8ce8dca50b186f0ef719b201b0d757d13e8f4f793d61f24ee68c2bc/SHA512-s42573--ac7ec2f106c7924a66b021eac1d8e53d0f480fda49adb27ce6dfe715c05d4cd463a41ec4a8ce8dca50b186f0ef719b201b0d757d13e8f4f793d61f24ee68c2bc
\ No newline at end of file
diff --git a/content/images/eclipse-20220515-231939.yaml b/content/images/eclipse-20220515-231939.yaml
new file mode 100644 (file)
index 0000000..6752422
--- /dev/null
@@ -0,0 +1,4 @@
+---
+title: 2022-05-15/16 Lunar Eclipse from Ottawa
+copyright: 2022 Nick Bowler
+license: cc-by-sa-4.0
diff --git a/content/images/eclipse-20220515-232447.jpg b/content/images/eclipse-20220515-232447.jpg
new file mode 120000 (symlink)
index 0000000..237113e
--- /dev/null
@@ -0,0 +1 @@
+../../.git/annex/objects/41/06/SHA512-s45985--47c57854f6bf0235a0beceadda5bd3e44e216ca10e4f48f1a15104d48c353405eca7d8b6834b411f2ea4d35e97fed5e83bc090db3902d2cdbbf40b6e025c6d8e/SHA512-s45985--47c57854f6bf0235a0beceadda5bd3e44e216ca10e4f48f1a15104d48c353405eca7d8b6834b411f2ea4d35e97fed5e83bc090db3902d2cdbbf40b6e025c6d8e
\ No newline at end of file
diff --git a/content/images/eclipse-20220515-232447.yaml b/content/images/eclipse-20220515-232447.yaml
new file mode 100644 (file)
index 0000000..6752422
--- /dev/null
@@ -0,0 +1,4 @@
+---
+title: 2022-05-15/16 Lunar Eclipse from Ottawa
+copyright: 2022 Nick Bowler
+license: cc-by-sa-4.0
diff --git a/content/images/eclipse-20220515-232709.jpg b/content/images/eclipse-20220515-232709.jpg
new file mode 120000 (symlink)
index 0000000..2f3f5f7
--- /dev/null
@@ -0,0 +1 @@
+../../.git/annex/objects/7w/J8/SHA512-s46545--ce81c5cf09098e2c3d9566b7a23705d29e2db6c08f2dc3a5129294b546f814f8f4c84a28a4a82a236d327f55bf2e8930fa5afea2f67001ce5e565a35aa89eb39/SHA512-s46545--ce81c5cf09098e2c3d9566b7a23705d29e2db6c08f2dc3a5129294b546f814f8f4c84a28a4a82a236d327f55bf2e8930fa5afea2f67001ce5e565a35aa89eb39
\ No newline at end of file
diff --git a/content/images/eclipse-20220515-232709.yaml b/content/images/eclipse-20220515-232709.yaml
new file mode 100644 (file)
index 0000000..6752422
--- /dev/null
@@ -0,0 +1,4 @@
+---
+title: 2022-05-15/16 Lunar Eclipse from Ottawa
+copyright: 2022 Nick Bowler
+license: cc-by-sa-4.0
diff --git a/content/images/eclipse-20220515-233454.jpg b/content/images/eclipse-20220515-233454.jpg
new file mode 120000 (symlink)
index 0000000..fec7426
--- /dev/null
@@ -0,0 +1 @@
+../../.git/annex/objects/k1/5F/SHA512-s45317--093734cb997e659c34ccceff0899ffa33b42da32c10b8f1a26ca7fc7bebcde982835f85033731c1a17c43c90a695a6e5081721d68a07109bd090fe2dced56021/SHA512-s45317--093734cb997e659c34ccceff0899ffa33b42da32c10b8f1a26ca7fc7bebcde982835f85033731c1a17c43c90a695a6e5081721d68a07109bd090fe2dced56021
\ No newline at end of file
diff --git a/content/images/eclipse-20220515-233454.yaml b/content/images/eclipse-20220515-233454.yaml
new file mode 100644 (file)
index 0000000..6752422
--- /dev/null
@@ -0,0 +1,4 @@
+---
+title: 2022-05-15/16 Lunar Eclipse from Ottawa
+copyright: 2022 Nick Bowler
+license: cc-by-sa-4.0
diff --git a/content/images/eclipse-20220515-233803.jpg b/content/images/eclipse-20220515-233803.jpg
new file mode 120000 (symlink)
index 0000000..34a8b24
--- /dev/null
@@ -0,0 +1 @@
+../../.git/annex/objects/P3/Z3/SHA512-s61296--0fbbe13c91dd55730ab794c173ef89cb67236af9815183b89cb4cb2422d96a69b7642b545a9759c6d701c101468276e59a63fea597b6bdd8cd591b12b5dddede/SHA512-s61296--0fbbe13c91dd55730ab794c173ef89cb67236af9815183b89cb4cb2422d96a69b7642b545a9759c6d701c101468276e59a63fea597b6bdd8cd591b12b5dddede
\ No newline at end of file
diff --git a/content/images/eclipse-20220515-233803.yaml b/content/images/eclipse-20220515-233803.yaml
new file mode 100644 (file)
index 0000000..6752422
--- /dev/null
@@ -0,0 +1,4 @@
+---
+title: 2022-05-15/16 Lunar Eclipse from Ottawa
+copyright: 2022 Nick Bowler
+license: cc-by-sa-4.0
diff --git a/content/images/eclipse-20220515-234126.jpg b/content/images/eclipse-20220515-234126.jpg
new file mode 120000 (symlink)
index 0000000..8a16ccc
--- /dev/null
@@ -0,0 +1 @@
+../../.git/annex/objects/jP/q9/SHA512-s62280--e38d6442ee33be62ca39f1d0976888d1b8dde77fb84043133887190e25febd2dcc7f1c0351880d5879ab542b9db3854e172be60cad1208a859e30c30e1f774dc/SHA512-s62280--e38d6442ee33be62ca39f1d0976888d1b8dde77fb84043133887190e25febd2dcc7f1c0351880d5879ab542b9db3854e172be60cad1208a859e30c30e1f774dc
\ No newline at end of file
diff --git a/content/images/eclipse-20220515-234126.yaml b/content/images/eclipse-20220515-234126.yaml
new file mode 100644 (file)
index 0000000..6752422
--- /dev/null
@@ -0,0 +1,4 @@
+---
+title: 2022-05-15/16 Lunar Eclipse from Ottawa
+copyright: 2022 Nick Bowler
+license: cc-by-sa-4.0
diff --git a/content/images/eclipse-20220515-234942.jpg b/content/images/eclipse-20220515-234942.jpg
new file mode 120000 (symlink)
index 0000000..1dd3e25
--- /dev/null
@@ -0,0 +1 @@
+../../.git/annex/objects/xv/81/SHA512-s75054--65490d25c866e5b275a84133da3305a2df5b31a5a5a83741ee6149d480c2fa1b446cde857487709b34bb7091f31de734b7bdc225250494f8b1ecdd1934957be2/SHA512-s75054--65490d25c866e5b275a84133da3305a2df5b31a5a5a83741ee6149d480c2fa1b446cde857487709b34bb7091f31de734b7bdc225250494f8b1ecdd1934957be2
\ No newline at end of file
diff --git a/content/images/eclipse-20220515-234942.yaml b/content/images/eclipse-20220515-234942.yaml
new file mode 100644 (file)
index 0000000..6752422
--- /dev/null
@@ -0,0 +1,4 @@
+---
+title: 2022-05-15/16 Lunar Eclipse from Ottawa
+copyright: 2022 Nick Bowler
+license: cc-by-sa-4.0
diff --git a/content/images/eclipse-20220515-235136.jpg b/content/images/eclipse-20220515-235136.jpg
new file mode 120000 (symlink)
index 0000000..40f0ebb
--- /dev/null
@@ -0,0 +1 @@
+../../.git/annex/objects/0m/kX/SHA512-s74615--1af33e462be8dcc467fb5a81400438974197918af7caa39eea77113c6e49788df04585ea574efca75699d9a79d0f1f1623d4976c17b8129dbcb6a43a4ba84685/SHA512-s74615--1af33e462be8dcc467fb5a81400438974197918af7caa39eea77113c6e49788df04585ea574efca75699d9a79d0f1f1623d4976c17b8129dbcb6a43a4ba84685
\ No newline at end of file
diff --git a/content/images/eclipse-20220515-235136.yaml b/content/images/eclipse-20220515-235136.yaml
new file mode 100644 (file)
index 0000000..6752422
--- /dev/null
@@ -0,0 +1,4 @@
+---
+title: 2022-05-15/16 Lunar Eclipse from Ottawa
+copyright: 2022 Nick Bowler
+license: cc-by-sa-4.0
diff --git a/content/images/eclipse-20220516-000739.jpg b/content/images/eclipse-20220516-000739.jpg
new file mode 120000 (symlink)
index 0000000..fc0b549
--- /dev/null
@@ -0,0 +1 @@
+../../.git/annex/objects/jW/6f/SHA512-s68109--7e627c88851979ffa02c55369993935c70d7a9e107ddbf334189825c87436a4fbc1d14249ffbc5f9f9b97acf752c27abe557d11b3f5bf385c62b22eee36b18b6/SHA512-s68109--7e627c88851979ffa02c55369993935c70d7a9e107ddbf334189825c87436a4fbc1d14249ffbc5f9f9b97acf752c27abe557d11b3f5bf385c62b22eee36b18b6
\ No newline at end of file
diff --git a/content/images/eclipse-20220516-000739.yaml b/content/images/eclipse-20220516-000739.yaml
new file mode 100644 (file)
index 0000000..6752422
--- /dev/null
@@ -0,0 +1,4 @@
+---
+title: 2022-05-15/16 Lunar Eclipse from Ottawa
+copyright: 2022 Nick Bowler
+license: cc-by-sa-4.0
diff --git a/content/images/eclipse-20240408-143755.jpg b/content/images/eclipse-20240408-143755.jpg
new file mode 120000 (symlink)
index 0000000..36f5d3c
--- /dev/null
@@ -0,0 +1 @@
+../../.git/annex/objects/zX/Px/SHA512-s389810--6d85094e85dbfcc4e73a7861d7992ab8d89d31f369ecc6d977ed89f646a96344dc9361374f8f08ba91f426acf9fdf53bfaad89f3f66563822cfc852b93f4cdef/SHA512-s389810--6d85094e85dbfcc4e73a7861d7992ab8d89d31f369ecc6d977ed89f646a96344dc9361374f8f08ba91f426acf9fdf53bfaad89f3f66563822cfc852b93f4cdef
\ No newline at end of file
diff --git a/content/images/eclipse-20240408-143755.yaml b/content/images/eclipse-20240408-143755.yaml
new file mode 100644 (file)
index 0000000..7eb4d34
--- /dev/null
@@ -0,0 +1,4 @@
+---
+title: 2024-04-08 Solar Eclipse near Cornwall, ON
+copyright: 2024 Nick Bowler
+license: cc-by-sa-4.0
diff --git a/content/images/eclipse-20240408-151623.jpg b/content/images/eclipse-20240408-151623.jpg
new file mode 120000 (symlink)
index 0000000..5b3949f
--- /dev/null
@@ -0,0 +1 @@
+../../.git/annex/objects/7V/zg/SHA512-s302673--ee3080ea9d00aed29a5af1d407e7dfad733a63131a907cb2d52c7b4342a552bf1b2ad9fa4bd062b96d3355162e09220c14134180091db3d3c9fca5b2f2da464a/SHA512-s302673--ee3080ea9d00aed29a5af1d407e7dfad733a63131a907cb2d52c7b4342a552bf1b2ad9fa4bd062b96d3355162e09220c14134180091db3d3c9fca5b2f2da464a
\ No newline at end of file
diff --git a/content/images/eclipse-20240408-151623.yaml b/content/images/eclipse-20240408-151623.yaml
new file mode 100644 (file)
index 0000000..7eb4d34
--- /dev/null
@@ -0,0 +1,4 @@
+---
+title: 2024-04-08 Solar Eclipse near Cornwall, ON
+copyright: 2024 Nick Bowler
+license: cc-by-sa-4.0
diff --git a/content/images/eclipse-20240408-152435.jpg b/content/images/eclipse-20240408-152435.jpg
new file mode 120000 (symlink)
index 0000000..5abb405
--- /dev/null
@@ -0,0 +1 @@
+../../.git/annex/objects/v5/gQ/SHA512-s273018--1a0d88d6b0756be9b23771154e7062da73c442e3f9f8222a6097b8c079382effe64fd7db6f0f763968d983aa9dada88be56932ebee68cbb59e01523ef483662f/SHA512-s273018--1a0d88d6b0756be9b23771154e7062da73c442e3f9f8222a6097b8c079382effe64fd7db6f0f763968d983aa9dada88be56932ebee68cbb59e01523ef483662f
\ No newline at end of file
diff --git a/content/images/eclipse-20240408-152435.yaml b/content/images/eclipse-20240408-152435.yaml
new file mode 100644 (file)
index 0000000..7eb4d34
--- /dev/null
@@ -0,0 +1,4 @@
+---
+title: 2024-04-08 Solar Eclipse near Cornwall, ON
+copyright: 2024 Nick Bowler
+license: cc-by-sa-4.0
diff --git a/content/images/eclipse-20240408-152639.jpg b/content/images/eclipse-20240408-152639.jpg
new file mode 120000 (symlink)
index 0000000..21e8d49
--- /dev/null
@@ -0,0 +1 @@
+../../.git/annex/objects/wf/58/SHA512-s348783--191e38f10a9f3d6c3e2334795e342b6ea92ba37e28502e102bf3034aae65e0c8bd9fdd8af9847e33e54059f4bcb6f6597394e1e76ff27403b4d606375735e62f/SHA512-s348783--191e38f10a9f3d6c3e2334795e342b6ea92ba37e28502e102bf3034aae65e0c8bd9fdd8af9847e33e54059f4bcb6f6597394e1e76ff27403b4d606375735e62f
\ No newline at end of file
diff --git a/content/images/eclipse-20240408-152639.yaml b/content/images/eclipse-20240408-152639.yaml
new file mode 100644 (file)
index 0000000..d162875
--- /dev/null
@@ -0,0 +1,7 @@
+---
+title: 2024-04-08 Total Solar Eclipse near Cornwall, ON
+copyright: 2024 Nick Bowler
+license: cc-by-sa-4.0
+description: |
+  The Sun's corona is visible during totality.  The red spots on the fringes
+  are likely solar prominences.
diff --git a/content/images/eclipse-20240408-152802.jpg b/content/images/eclipse-20240408-152802.jpg
new file mode 120000 (symlink)
index 0000000..1eafa28
--- /dev/null
@@ -0,0 +1 @@
+../../.git/annex/objects/34/ZV/SHA512-s520397--1fb51e2946228e45bffa5b8552ad9243a52e8ba6fb8715a3885c602d0046a84724795717b4674753bf202dceefa2bee263a853909222217405889b5258db14d8/SHA512-s520397--1fb51e2946228e45bffa5b8552ad9243a52e8ba6fb8715a3885c602d0046a84724795717b4674753bf202dceefa2bee263a853909222217405889b5258db14d8
\ No newline at end of file
diff --git a/content/images/eclipse-20240408-152802.yaml b/content/images/eclipse-20240408-152802.yaml
new file mode 100644 (file)
index 0000000..bc26d22
--- /dev/null
@@ -0,0 +1,7 @@
+---
+title: 2024-04-08 Diamond Ring near Cornwall, ON
+copyright: 2024 Nick Bowler
+license: cc-by-sa-4.0
+description: |
+  The sun's disc peeks out again at third contact producing the diamond ring
+  effect.
diff --git a/content/images/eclipse-20240408-152846.jpg b/content/images/eclipse-20240408-152846.jpg
new file mode 120000 (symlink)
index 0000000..f49afd2
--- /dev/null
@@ -0,0 +1 @@
+../../.git/annex/objects/W3/KV/SHA512-s295663--d941929c012be29bbb82e9c22b69280a64b7e9e21321cc7faf2e65088eb251deed5aa2a16b6820473fba40a1057dc72d6b3444e3b916691e8d68322b8d9e1201/SHA512-s295663--d941929c012be29bbb82e9c22b69280a64b7e9e21321cc7faf2e65088eb251deed5aa2a16b6820473fba40a1057dc72d6b3444e3b916691e8d68322b8d9e1201
\ No newline at end of file
diff --git a/content/images/eclipse-20240408-152846.yaml b/content/images/eclipse-20240408-152846.yaml
new file mode 100644 (file)
index 0000000..7eb4d34
--- /dev/null
@@ -0,0 +1,4 @@
+---
+title: 2024-04-08 Solar Eclipse near Cornwall, ON
+copyright: 2024 Nick Bowler
+license: cc-by-sa-4.0
diff --git a/content/images/m48t59y-fixed.jpg b/content/images/m48t59y-fixed.jpg
new file mode 120000 (symlink)
index 0000000..d0f066e
--- /dev/null
@@ -0,0 +1 @@
+../../.git/annex/objects/wK/GG/SHA512-s215590--23ec4d09ab8ed674c51d1fa81145f4983e207ca989ec65c3035ba49c876aa01c37df3ca458b80ab7863607ec80ec8ac950bd03e7d83ec493adc40fef80c55f8d/SHA512-s215590--23ec4d09ab8ed674c51d1fa81145f4983e207ca989ec65c3035ba49c876aa01c37df3ca458b80ab7863607ec80ec8ac950bd03e7d83ec493adc40fef80c55f8d
\ No newline at end of file
diff --git a/content/images/m48t59y-fixed.yaml b/content/images/m48t59y-fixed.yaml
new file mode 100644 (file)
index 0000000..6038682
--- /dev/null
@@ -0,0 +1,6 @@
+---
+title: M48T59Y-70PC1 With Battery Clip
+copyright: 2022 Nick Bowler
+license: cc-by-sa-4.0
+description: |
+  Finished repair of M48T59Y part with external battery clip glued on top.
diff --git a/content/images/m48t59y-installed.jpg b/content/images/m48t59y-installed.jpg
new file mode 120000 (symlink)
index 0000000..2fa0639
--- /dev/null
@@ -0,0 +1 @@
+../../.git/annex/objects/jF/WK/SHA512-s821129--859631534da810c3a29d2246edf97a8d6a61a2ac67c21e18aee2753eb96a38a2856e45278f44db7a7a0ef9bb1c1ae6b00bc32e72b9b143b5d14a8abc7e376a1d/SHA512-s821129--859631534da810c3a29d2246edf97a8d6a61a2ac67c21e18aee2753eb96a38a2856e45278f44db7a7a0ef9bb1c1ae6b00bc32e72b9b143b5d14a8abc7e376a1d
\ No newline at end of file
diff --git a/content/images/m48t59y-installed.yaml b/content/images/m48t59y-installed.yaml
new file mode 100644 (file)
index 0000000..2c74fe6
--- /dev/null
@@ -0,0 +1,6 @@
+---
+title: M48T59Y-70PC1 Repair Complete
+copyright: 2022 Nick Bowler
+license: cc-by-sa-4.0
+description: |
+  Repaired M48T59Y chip installed onto the Sun Ultra 60 motherboard.
diff --git a/content/images/m48t59y-solder.jpg b/content/images/m48t59y-solder.jpg
new file mode 120000 (symlink)
index 0000000..5d8078e
--- /dev/null
@@ -0,0 +1 @@
+../../.git/annex/objects/8M/9g/SHA512-s146926--2dd1f0b920387121d4e1f658f151417d55d65dfa27a6b397343deeebde08636236a0e123a9cfa3fdeef8204425c3f7d00a5e5dcee121126535a140a059f6fb04/SHA512-s146926--2dd1f0b920387121d4e1f658f151417d55d65dfa27a6b397343deeebde08636236a0e123a9cfa3fdeef8204425c3f7d00a5e5dcee121126535a140a059f6fb04
\ No newline at end of file
diff --git a/content/images/m48t59y-solder.yaml b/content/images/m48t59y-solder.yaml
new file mode 100644 (file)
index 0000000..dc6253b
--- /dev/null
@@ -0,0 +1,7 @@
+---
+title: M48T59Y-70PC1 Wire Leads
+copyright: 2022 Nick Bowler
+license: cc-by-sa-4.0
+description: |
+  M48T59Y with battery leads connected, after disconnecting the internal
+  battery tabs.
diff --git a/content/images/m48t59y-terminals.jpg b/content/images/m48t59y-terminals.jpg
new file mode 120000 (symlink)
index 0000000..795475d
--- /dev/null
@@ -0,0 +1 @@
+../../.git/annex/objects/98/V1/SHA512-s186948--c3277cf2aea7e159ee6203e9c7f5330f3a1c79a4d2c347c7637c19f354c7cafe2181b54fd40e84eba434a16340fb9cd3a87e76a636d45278846cb46c3d1f606e/SHA512-s186948--c3277cf2aea7e159ee6203e9c7f5330f3a1c79a4d2c347c7637c19f354c7cafe2181b54fd40e84eba434a16340fb9cd3a87e76a636d45278846cb46c3d1f606e
\ No newline at end of file
diff --git a/content/images/m48t59y-terminals.yaml b/content/images/m48t59y-terminals.yaml
new file mode 100644 (file)
index 0000000..806df38
--- /dev/null
@@ -0,0 +1,8 @@
+---
+title: M48T59Y-70PC1 Battery Terminals
+copyright: 2022 Nick Bowler
+license: cc-by-sa-4.0
+description: |
+  M48T59Y with internal battery connections exposed.  The negative connection
+  is on the left, closest to pin 14.  The positive connection is on the right,
+  closest to pin 16.
diff --git a/content/images/m48t59y.jpg b/content/images/m48t59y.jpg
new file mode 120000 (symlink)
index 0000000..40e10d3
--- /dev/null
@@ -0,0 +1 @@
+../../.git/annex/objects/5f/mJ/SHA512-s141937--ebbd8a2ad4b6d4f1c6d66d9f7ba76685c5281bff3d0fb34f7e00e3016b8422c4935aa9b87caa1c4f8ed9b1b7f727b4f2c97d49991f483317c8248f536c225620/SHA512-s141937--ebbd8a2ad4b6d4f1c6d66d9f7ba76685c5281bff3d0fb34f7e00e3016b8422c4935aa9b87caa1c4f8ed9b1b7f727b4f2c97d49991f483317c8248f536c225620
\ No newline at end of file
diff --git a/content/images/m48t59y.yaml b/content/images/m48t59y.yaml
new file mode 100644 (file)
index 0000000..7258310
--- /dev/null
@@ -0,0 +1,9 @@
+---
+title: M48T59Y-70PC1
+copyright: 2022 Nick Bowler
+license: cc-by-sa-4.0
+description: |
+  <p>The <abbr title="Non-Volatile Random Access Memory">NVRAM</abbr> module
+  used in my Sun Ultra 60 workstation.  This combines a normal memory with a
+  real-time clock and an internal lithium primary cell to maintain everything.
+  However, the internal cell is long dead so this chip is dead too.</p>
index 90413c9153cd7d64d0c037543d4938ad1e11443d..8db0c429b9203360a652e6afb12c3efdbca05782 100644 (file)
@@ -4,6 +4,9 @@ copyright: 2019 Nick Bowler
 license: cc-by-nd-4.0
 ---
 
+*[RARP]: Reverse Address Resolution Protocol
+*[ARP]: Address Resolution Protocol
+
 Most of the operations here in the workshop are tracked on our local [git
 server](//git.draconx.ca) which can be browsed online.  All the work here is
 [free as in freedom](//www.gnu.org/philosophy/free-sw) which means you are
index c8ef5bad652f7b7bff9b5b4c4515cbbf5d3e8d82..c2fb9f498c9221d27bc4546ce3fce57f55765fed 100644 (file)
@@ -1,9 +1,9 @@
 ---
 title: Cdecl99
-copyright: 2021 Nick Bowler
+copyright: 2021-2024 Nick Bowler
 license: gpl-3 or (at your option) any later version
 module: cdecl99
-release: v1.1
+release: v1.3
 ---
 
 <%= project_readme %>
diff --git a/content/projects/rarpd-dx.md b/content/projects/rarpd-dx.md
new file mode 100644 (file)
index 0000000..9eaf368
--- /dev/null
@@ -0,0 +1,13 @@
+---
+title: RARP Daemon
+copyright: 2023 Nick Bowler
+license: gpl-2 or (at your option) any later version
+module: rarpd-dx
+release: v1
+---
+
+*[RARP]: Reverse Address Resolution Protocol
+*[ARP]: Address Resolution Protocol
+
+<%= project_readme %>
+
diff --git a/content/projects/rrace.md b/content/projects/rrace.md
new file mode 100644 (file)
index 0000000..b5a111f
--- /dev/null
@@ -0,0 +1,8 @@
+---
+title: RRace
+copyright: 2022 Nick Bowler
+license: gpl-3 or (at your option) any later version
+module: rrace
+---
+
+<%= project_readme %>
diff --git a/content/pubring.gpg b/content/pubring.gpg
new file mode 100644 (file)
index 0000000..456deef
Binary files /dev/null and b/content/pubring.gpg differ
index 04736d84fd4f61ade054a2b8d7eeb7a0ae63bc30..17aff0ba0352f3a0ccf5ab9117e82c2c6e27e11e 100644 (file)
@@ -1,7 +1,7 @@
 /*!
  * Nick's web site: default stylesheet
  *
- * Copyright © 2018-2021 Nick Bowler
+ * Copyright © 2018-2022 Nick Bowler
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
 
-// colour definitions
-$backgroundcolour:  #ffffff;
-$foregroundcolour:  #000000;
+@import "lib/colourmap.scss";
 
-$linkdefaultcolour: #0000cd;
-$linkactivecolour:  #ff0000;
-$linkvisitedcolour: #800080;
+@include defcolours
+    ( $background:  #ffffff #000000
+    , $foreground:  #000000 #ffffff
 
-$ruledefaultcolour: #d3d3d3;
-$rulestrongcolour:  #696969;
+    , $linkdefault: #0000cd #a3aaff
+    , $linkactive:  #ff0000
+    , $linkvisited: #800080 #e493f7
+    , $focusring:   #628cb2
 
-$annotationcolour:  #708090;
+    , $annotation:  #708090
+    , $tableshade:  #f5f5f5 #101010
 
-$tableshadecolour:  #f5f5f5;
+    , $ruledefault: #d3d3d3 #494949
+    , $rulestrong:  #696969 #939393
+    );
 
 @mixin header_size($maxwidth, $fontsize) {
     font-size: $fontsize;
     max-width: 1em * ($maxwidth / $fontsize);
 }
 
+html { @include usecolours($background-color: background); }
 body {
-    background-color: $backgroundcolour;
-    color: $foregroundcolour;
+    @include usecolours($color: foreground);
     font-family: sans-serif;
     margin: 1em;
 }
 
-a:link { color: $linkdefaultcolour; border-color: $linkdefaultcolour; }
-a:visited { color: $linkvisitedcolour; border-color: $linkvisitedcolour; }
-a:active { color: $linkactivecolour; border-color: $linkactivecolour; }
+a:link {
+    @include usecolours($color: linkdefault, $border-color: linkdefault);
+}
+a:visited {
+    @include usecolours($color: linkvisited, $border-color: linkvisited);
+}
+a:active {
+    @include usecolours($color: linkactive, $border-color: linkactive);
+}
+
+@supports (outline-style: auto) {
+    a:link { border-width: 0; }
+    a:focus { @include usecolour(outline, focusring, auto); }
+    li, td, dt { &>a:link { border: solid 1px transparent; } }
+}
 
 h1 { @include header_size(60em, 2em); }
 h2 { @include header_size(60em, 1.5em); }
 h5 { @include header_size(60em, 1em); }
 
+h1, h2 { abbr { text-decoration: none; } }
+
 @supports (display: grid) {
     .gallery {
         display: grid;
@@ -61,6 +78,16 @@ h5 { @include header_size(60em, 1em); }
 
         p.img { margin: 0.5em 0; }
     }
+
+    @media (max-width: 45em) {
+        .inline.gallery {
+            display: block;
+            p.img {
+                a { max-width: 24em; }
+                margin: 1em 0;
+            }
+        }
+    }
 }
 
 p.img {
@@ -79,12 +106,22 @@ p.img {
         border: solid 2px;
     }
 
+    a.left { margin: 0 1em 0.5em 0; float: left; clear: left; }
+    a.right { margin: 0 0 0.5em 1em; float: right; clear: right; }
+    a.left, a.right {
+        @media (max-width: 45em) { float: none; margin: 0; }
+
+        max-width: 24em;
+    }
+
     small {
-        color: $foregroundcolour;
-        text-align: justify;
+        @include usecolours($color: foreground);
         @media (max-width: 24em) { text-align: left; }
         padding: 0.5ex;
         display: block;
+        display: -moz-inline-box;
+        display: inline-block;
+        text-align: justify;
         font-size: 0.9em;
     }
 }
@@ -115,17 +152,19 @@ hr {
     clear: both;
     margin: 0.5em 0;
     border: 0;
-    border-top: 1px solid $ruledefaultcolour;
+    @include usecolour(border-top, ruledefault, 1px solid);
 }
 
 kbd {
     font-family: monospace;
     font-size: 0.95em;
     &:before { content: "% "; }
+    &.ok:before { content: "ok "; }
     &>span { white-space: nowrap; }
 
     blockquote & {
         display: block;
+        & + br { display: none; }
         text-align: left;
         padding-left: 3em;
         text-indent: -3em;
@@ -133,17 +172,19 @@ kbd {
 }
 
 .permalink {
+    @include usecolours($color: annotation);
     font-size: small;
-    color: $annotationcolour;
 
     a:link, a:visited { color: inherit; }
+    a:active { @include usecolours($color: linkactive); }
     @media not screen { visibility: hidden; }
 }
 
 // General table styles.
 table {
-    border: 1px solid $ruledefaultcolour;
+    @include usecolour(border-top, ruledefault, 1px solid);
     border-collapse: collapse;
+    clear: both;
     width: 100%;
 }
 
@@ -163,18 +204,20 @@ td, th {
     margin: 0;
 }
 
-thead>tr, tbody>tr { border: solid $ruledefaultcolour; }
-th, thead>tr { border-bottom: 1px solid $rulestrongcolour; }
-tbody+tbody { border-bottom: 1px solid $ruledefaultcolour; }
+thead>tr, tbody>tr { @include usecolour(border, ruledefault, solid); }
+th, thead>tr { @include usecolour(border-bottom, rulestrong, 1px solid); }
+tbody+tbody { @include usecolour(border-bottom, ruledefault, 1px solid); }
 *>table, *>th { border: none; }
 thead>tr { border-width: 1px; }
 tbody>tr { border-width: 0 1px; }
 
-td + td { box-shadow: -1px 0 $backgroundcolour; }
+td + td {
+    @include usecolour(box-shadow, background, -1px 0);
+}
 
 tbody>tr {
-    &:nth-of-type(even) { background-color: $tableshadecolour; }
-    &:last-child { border-bottom: solid 1px $ruledefaultcolour; }
+    &:nth-of-type(even) { @include usecolours($background-color: tableshade); }
+    &:last-child { @include usecolour(border-bottom, ruledefault, solid 1px); }
 }
 
 // Specific table styles
@@ -185,8 +228,8 @@ table.cc {
     }
 }
 
-// CSS rules for stortable clicky table headers: Update the display of
-// the /table based on the current state.  Each column has its own set
+// CSS rules for sortable clicky table headers: Update the display of
+// the table based on the current state.  Each column has its own set
 // nearly-identical rules, only the class names differ.
 //
 // The clickytables.xsl stylesheet generates two inputs for each column.
@@ -275,10 +318,6 @@ $clickynames: name, date, size;
             }
         }
 
-        &:focus ~ table th.clicky-#{$col}>label~label>span:first-child {
-            border-color: $foregroundcolour;
-        }
-
         // Unhide to allow keyboard navigation
         display: block !important;
         pointer-events: none;
@@ -299,15 +338,22 @@ $clickynames: name, date, size;
             }
         }
 
-        &:focus ~ table th.clicky-#{$col}>label~label .svg {
-            border-color: $foregroundcolour;
-        }
-
         pointer-events: none;
         position: absolute;
         opacity: 0;
         z-index: -2;
     }
+
+    $focuslabel: ":focus ~ table th.clicky-#{$col}>label~label";
+    #{"input.clicky-#{$col+$focuslabel}"}>span:first-child
+    , #{"input.clicky-#{$col}-rev#{$focuslabel}"} .svg
+    {
+        @include usecolours($border-color: foreground);
+        @at-root { @supports (outline-style: auto) { & {
+            @include usecolour(outline, focusring, auto);
+            border-color: transparent !important;
+        }}}
+    }
 }
 
 thead.clicky label {
@@ -329,9 +375,13 @@ thead.clicky label {
         padding-right: 2px;
     }
 
-    &:active { color: $linkactivecolour; }
+    &:active { @include usecolours($color: linkactive); }
     &:first-child:active>span, &~label:active>.svg {
-        border-color: $linkactivecolour;
+        @include usecolours($border-color: linkactive);
+        @at-root { @supports (outline-style: auto) { & {
+            @include usecolour(outline, focusring, auto);
+            border-color: transparent;
+        }}}
     }
 
     .svg {
@@ -399,7 +449,7 @@ table.filelist {
     }
 }
 
-#footer p { color: $annotationcolour; }
+#footer p { @include usecolours($color: annotation); }
 #article-info p { font-style: italic; }
 
 .wbr:after { content: "\200b"; }
@@ -425,3 +475,25 @@ ul.ordered > {
     #sitetitle * { float: none; }
     #footer { padding: 0 1em; }
 }
+
+// lighten icon shadows in dark mode
+@media (prefers-color-scheme: dark) {
+    svg.icons { path.shadow, g.shadow>* { opacity: 0.7; } }
+    svg.icons .shadow>stop { stop-color: #aaa; }
+    svg.return path.shadow { opacity: 0.45; }
+}
+
+// page-specific dark mode styles
+@media (prefers-color-scheme: dark) and (min-width: 35em) {
+    #page_weblog_responsive_tables {
+        @each $tN in t6 t7 t8 {
+            ##{$tN}>tbody>tr.#{$tN}-split {
+                @include usecolour_dark_(border-bottom, ruledefault);
+
+                &:nth-of-type(odd) ~ tr:nth-of-type(odd) {
+                    @include usecolour_dark_(background-color, tableshade);
+                }
+            }
+        }
+    }
+}
diff --git a/content/weblog/eclipse-20220515.md b/content/weblog/eclipse-20220515.md
new file mode 100644 (file)
index 0000000..ab471b7
--- /dev/null
@@ -0,0 +1,11 @@
+---
+title: Lunar Eclipse in Ottawa on 2022-05-15
+copyright: 2022 Nick Bowler
+license: cc-by-sa-4.0
+published: 2022-05-16T01:17:29-0400
+---
+
+<% images = @items.find_all("/images/eclipse-202205*.jpg")
+images.sort_by(&:identifier).each do |item| %>
+<%= gallery_img(item, caption: "") %>
+<% end %>
diff --git a/content/weblog/eclipse-20240408.md b/content/weblog/eclipse-20240408.md
new file mode 100644 (file)
index 0000000..0cf01bf
--- /dev/null
@@ -0,0 +1,14 @@
+---
+title: Solar Eclipse near Cornwall on 2024-04-08
+copyright: 2024 Nick Bowler
+license: cc-by-sa-4.0
+published: 2024-04-09T00:44:23-0400
+updated: 2024-04-09T21:04:19-0400
+---
+
+More to come...
+
+<% images = @items.find_all("/images/eclipse-20240408*.jpg")
+images.sort_by(&:identifier).each do |item| %>
+<%= gallery_img(item, caption: "") %>
+<% end %>
index 2063484f7b266bdc1d1934167a90963d10c3edf2..a668e163b60269c569b667e4687ac387bcb7b882 100644 (file)
@@ -272,12 +272,12 @@ are pretty easy to fix in the stylesheet.
     #t6>thead, #t6>tbody { display: grid; }
 
     #t6>tbody>tr.t6-split { border-bottom: 1px solid <%=
-      scss_get_var(:ruledefaultcolour) %>; }
+      scss_get_colour(:ruledefault) %>; }
     #t6>tbody>tr.t6-split:nth-of-type(odd) ~ tr:nth-of-type(even) {
       background-color: initial;
     }
     #t6>tbody>tr.t6-split:nth-of-type(odd) ~ tr:nth-of-type(odd) {
-      background-color: <%= scss_get_var(:tableshadecolour) %>;
+      background-color: <%= scss_get_colour(:tableshade) %>;
     }
   }
 }
@@ -325,12 +325,12 @@ these techniques to [table 1](#t1).
     #t7>thead, #t7>tbody { display: grid; }
 
     #t7>tbody>tr.t7-split { border-bottom: 1px solid <%=
-      scss_get_var(:ruledefaultcolour) %>; }
+      scss_get_colour(:ruledefault) %>; }
     #t7>tbody>tr.t7-split:nth-of-type(odd) ~ tr:nth-of-type(even) {
       background-color: initial;
     }
     #t7>tbody>tr.t7-split:nth-of-type(odd) ~ tr:nth-of-type(odd) {
-      background-color: <%= scss_get_var(:tableshadecolour) %>;
+      background-color: <%= scss_get_colour(:tableshade) %>;
     }
   }
 }
@@ -386,12 +386,12 @@ in a stylesheet.  We will do it with more grids.
     #t8>thead, #t8>tbody { display: grid; }
 
     #t8>tbody>tr.t8-split { border-bottom: 1px solid <%=
-      scss_get_var(:ruledefaultcolour) %>; }
+      scss_get_colour(:ruledefault) %>; }
     #t8>tbody>tr.t8-split:nth-of-type(odd) ~ tr:nth-of-type(even) {
       background-color: initial;
     }
     #t8>tbody>tr.t8-split:nth-of-type(odd) ~ tr:nth-of-type(odd) {
-      background-color: <%= scss_get_var(:tableshadecolour) %>;
+      background-color: <%= scss_get_colour(:tableshade) %>;
     }
   }
 
diff --git a/content/weblog/ultra60-nvram.md b/content/weblog/ultra60-nvram.md
new file mode 100644 (file)
index 0000000..ec96370
--- /dev/null
@@ -0,0 +1,101 @@
+---
+title: M48T59Y-70PC1 NVRAM Battery Replacement
+copyright: 2022 Nick Bowler
+license: cc-by-sa-4.0
+published: 2022-07-09T12:12:21-0400
+---
+
+*[NVRAM]: Non-Volatile Random Access Memory
+*[SRAM]: Static Random-Access Memory
+*[NOS]: New Old Stock
+*[DIP]: Dual In-line Package
+*[SOIC]: Small-Outline Integrated Circuit
+*[MAC]: Media Access Control
+
+Thanks to [Mark Henderson's Sun NVRAM FAQ][nvram-faq] for providing invaluable
+insight regarding this repair.
+{:article-info="article-info"}
+
+[nvram-faq]: http://web.archive.org/web/20150919135835/http://www.squirrel.com/sun-nvram-hostid.faq.html
+
+The Sun Ultra 60 workstation uses an ST M48T59Y-70PC1 battery-backed SRAM with
+real-time clock.  When the embedded lithium primary cell inevitably dies, the
+chip is essentially useless.  The most noticeable negative effect of a dead
+chip is that the workstation always powers up in a default configuration with
+diag-switch?  true, running several minutes of self tests every time, with
+bogus MAC and host ID values.
+
+<%= floating_img(@items["/images/m48t59y.jpg"], caption: <<EOF
+You don't even know you are already dead.
+EOF
+) %>
+
+This device was branded obsolescent by the manufacturer in April of 2008 and
+has presumably been out of production since around that time.  Similar chips
+like the M48T58Y are still made but they are expensive (around $40) and slight
+differences may result in compatibility problems.  It appears that NOS parts
+can be found fairly cheap online but these must be almost 15 years old by now
+and who knows what state the embedded batteries are in.  No thanks.
+
+One option could be to find the SOIC version of the M48T59Y for which NOS also
+seems to be available, although somewhat less readily than the DIP modules, and
+then construct an adapter board to fit into the DIP socket on the motherboard.
+The SOIC package does not include the battery and crystal; instead, it has
+contacts on the top to attach a separate "SNAPHAT" package which are still
+made.  This would be complicated and somewhat expensive.
+
+Fortunately, a straightforward (if time-consuming) repair is possible.  This
+is a literal hackjob: we can dig through the potting to expose the battery
+terminals, disconnect the internal battery, and connect a normal battery clip.
+This enables the use of readily-available CR2032 lithium primary cells that can
+be replaced as needed, just like a normal PC.  Since we already have the chip
+we need, it is not necessary to source any obscure or expensive components.
+
+<%=
+floating_img(@items["/images/m48t59y-terminals.jpg"], left: 1, caption: '')
+%>
+
+<%=
+floating_img(@items["/images/m48t59y-solder.jpg"], left: 1, caption: '')
+%>
+
+The battery contacts are located on the end opposite pin 1, between pins
+14 and 15.  Looking underneath the chip there is a small well at either end
+filled with potting compound.  The battery connections are inside one of
+these.  Using a knife, cut back the plastic outer shell on the battery side,
+then begin scraping away potting compound until the terminals are exposed.
+You can use a suitable DIP socket to help protect the pins from accidental
+damage.
+
+Once the terminals are exposed, the internal battery must be disconnected.
+The thick protruding tabs near the bottom of the module are what we want
+to solder to.  The vertical strips going up and into the module are the
+internal battery terminals.  These can be desoldered from the tabs and
+lifted out of the way, or simply cut off.
+
+With the time-consuming parts completed it is a straightforward matter to
+connect a normal battery clip to the exposed terminals.  As the Ultra 60 has
+no clearance issues I simply glued it to the top of the chip.  Since this
+would obscure the barcode sticker which is useful to restore the original MAC
+address and host ID, I moved this sticker to the side of the chip.
+
+Finally all that remains is to put everything back together and power up
+the system.  The system will run the full self-diagnostic tests again and
+if all goes well, the "NVRAM Battery Detect Test" should no longer display
+any errors.  At the ok prompt, the memory can now be initialized.  Where
+"AA BB CC" are the six hexadecimal digits from the orange barcode sticker:
+
+<kbd class='ok'>set-defaults</kbd><br/>
+<kbd class='ok'>setenv diag-switch? false</kbd><br/>
+<kbd class='ok'>8 0 20 AA BB CC AABBCC mkpl<br/><em>&lt;Ctrl-D&gt;&lt;Ctrl-R&gt;</em></kbd><br/>
+<kbd class='ok'>.idprom</kbd><br/>
+<kbd class='ok'>reset</kbd>
+
+<%= gallery_img(@items["/images/m48t59y-fixed.jpg"], caption: <<EOF
+Just like a bought one!
+EOF
+) %>
+<%= gallery_img(@items["/images/m48t59y-installed.jpg"], caption: <<EOF
+There's no place like home in the Ultra 60.
+EOF
+) %>
index 6cc7c1ad6a714b7ea8ae1445e502ec7ce4ecf4ab..db974c0a690f2044dcbc06446333837c3c3e1cec 160000 (submodule)
@@ -1 +1 @@
-Subproject commit 6cc7c1ad6a714b7ea8ae1445e502ec7ce4ecf4ab
+Subproject commit db974c0a690f2044dcbc06446333837c3c3e1cec
diff --git a/gitmodules/rarpd-dx b/gitmodules/rarpd-dx
new file mode 160000 (submodule)
index 0000000..e560983
--- /dev/null
@@ -0,0 +1 @@
+Subproject commit e5609838c7733c84200f368bfd89bf2fe07b3d9e
diff --git a/gitmodules/rrace b/gitmodules/rrace
new file mode 160000 (submodule)
index 0000000..c1dbd8f
--- /dev/null
@@ -0,0 +1 @@
+Subproject commit c1dbd8fcf932d82b4d77da62288b4a6cdf5c1470
index 2e003aefeb975fd077edf9e3b8848782b06c07a8..2f78b3d8481e3805fddaead2dd771b1da5ff39e1 100644 (file)
@@ -2,7 +2,7 @@
 <!--
   Nick's web site: Intermediate document structure.
 
-  Copyright © 2016-2020 Nick Bowler
+  Copyright © 2016-2022 Nick Bowler
 
   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
@@ -64,9 +64,13 @@ end %>
 %>  <article>
 <%= attribute_to_time(@item[:published]).strftime \
 "    <published>%Y-%m-%d</published>\n" if @item[:published]
+%><%= attribute_to_time(@item[:updated]).strftime \
+"    <updated>%Y-%m-%d</updated>\n" if @item[:updated]
 %>  </article>
 <% end
-%>  <html xmlns="<%= Xmlns['xhtml'] %>">
+%>  <html xmlns="<%= Xmlns['xhtml'] %>" id="<%=
+  "page" + @item.identifier.without_ext.gsub(/[^[:alnum:]]/, "_")
+%>">
 <% if !doc_header then
 %>    <h1><%= @item.fetch(:header, @item[:title]) %></h1>
 <% end %><%= doc_str
index 7325708557a89089d76469a80a5fb11b9f78f610..aa22e8f23460062220cba346f2b7468de96bf1ef 100644 (file)
@@ -2,7 +2,7 @@
 <!--
   Nick's web site: XHTML output stage
 
-  Copyright © 2018-2021 Nick Bowler
+  Copyright © 2018-2022 Nick Bowler
 
   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
 <xsl:param name='site-title' select='"The Citrine Citadel"' />
 <xsl:param name='section-links' select='//document/section-links' />
 
-<func:function name='f:ends-with'>
-  <xsl:param name='a' />
-  <xsl:param name='b' />
-  <func:result
-    select='substring($a, string-length($a)-string-length($b)+1)=$b' />
-</func:function>
-
 <xsl:template match='node()|@*'>
   <xsl:copy><xsl:apply-templates select='node()|@*' /></xsl:copy>
 </xsl:template>
   </xsl:copy>
 </xsl:template>
 
-<!--
-  Nokogiri's pretty-printer is a bit weird.  Regardless of the indentation
-  setting, if an element has no child text nodes then it will be pretty-
-  printed.  This works by adding arbitrary whitespace to that element, and
-  then all of its children are eligible to be pretty-printed.
-
-  If an element has any text nodes at all, then it is not pretty-printed and
-  neither are any of its descendents.
-
-  Adding arbitrary whitespace to <pre> is bad, so we inject zero-width non-
-  breaking spaces to prevent this.  This will render fine but the spaces
-  should be removed before final output to avoid problems with copy+paste.
--->
-<xsl:template match='xhtml:pre'>
-  <xsl:copy>
-    <xsl:apply-templates select='node()|@*' />
-    <xsl:text>&#x2060;</xsl:text>
-  </xsl:copy>
-</xsl:template>
-
-<!--
-  Likewise, adding spaces between consecutive span-level elements where
-  none existed before won't go over well.
--->
-<xsl:template name='glue-preceding-span'>
-  <xsl:if test='f:element-is-span(preceding-sibling::node()[1])'>
-    <xsl:text>&#x2060;</xsl:text>
-  </xsl:if>
-</xsl:template>
-
-<xsl:template match='*[f:element-is-span()]'>
-  <xsl:call-template name='glue-preceding-span' />
-  <xsl:copy>
-    <xsl:apply-templates select='node()|@*' />
-    <xsl:if test='*'>
-      <!-- avoid breaking within a span element -->
-      <xsl:text>&#x2060;</xsl:text>
-    </xsl:if>
-  </xsl:copy>
-</xsl:template>
-
-<!--
-  Manually strip whitespace-only text nodes so the pretty printer can do its
-  thing on remaining elements.
--->
-<xsl:template match='text()[normalize-space(.) = ""]'>
-  <xsl:choose>
-    <!-- preserve anything according to xml:space -->
-    <xsl:when test='ancestor::*[@xml:space][1][@xml:space="preserve"]'>
-      <xsl:copy />
-    </xsl:when>
-    <!-- preserve anything under <pre> -->
-    <xsl:when test='ancestor::xhtml:pre'><xsl:copy /></xsl:when>
-    <!-- preserve whitespace which is the only child node of an element -->
-    <xsl:when test='count(../node()) = 1'><xsl:copy /></xsl:when>
-    <!-- preserve whitespace between consecutive span-level elements
-         which have at least one non-whitespace sibling text element -->
-    <xsl:when test='f:element-is-span(preceding-sibling::node()[1])
-                    and f:element-is-span(following-sibling::node()[1])
-                    and ../text()[normalize-space(.) != ""]'>
-      <xsl:copy />
-    </xsl:when>
-  </xsl:choose>
-</xsl:template>
-
-<!-- Clean up whitespace where harmless to do so -->
-<xsl:template match='xhtml:p/node()[1][self::text()]'>
-  <xsl:value-of select='f:strip-leading()' />
-</xsl:template>
-<xsl:template match='xhtml:p/node()[position()=last()][self::text()]'>
-  <xsl:value-of select='f:strip-trailing()' />
-</xsl:template>
-
 <!-- Add rel attributes to external links -->
 <xsl:template match='xhtml:a[starts-with(@href,"http://")
                           or starts-with(@href,"https://")
   </xsl:copy>
 </xsl:template>
 
+<!--
+  Allow abbr to apply to document titles too, since these are generated
+  and kramdown's abbr support won't influence them. We do this by just
+  checking each word of the heading to see if is identical to an existing
+  abbr tag, and just substituting that in its place.
+  -->
+<xsl:key name='abbr' match='xhtml:abbr' use='string(.)' />
+<xsl:template name='insert-abbr' match='xhtml:h1/text()'>
+  <xsl:param name='string' select='normalize-space(.)' />
+
+  <xsl:variable name='head'
+    select='substring-before(concat($string, " "), " ")' />
+  <xsl:variable name='tail' select='substring-after($string, " ")' />
+  <xsl:variable name='match' select='key("abbr", $head)[1]' />
+
+  <xsl:choose>
+    <xsl:when test='$match'><xsl:apply-templates select='$match' /></xsl:when>
+    <xsl:otherwise><xsl:value-of select='$head' /></xsl:otherwise>
+  </xsl:choose>
+  <xsl:if test='$tail'>
+    <xsl:text> </xsl:text>
+    <xsl:call-template name='insert-abbr'>
+      <xsl:with-param name='string' select='$tail' />
+    </xsl:call-template>
+  </xsl:if>
+</xsl:template>
+
 <!--
   Convert caption attribute on tables into proper caption elements, to allow
   a simple way to add captions to kramdown tables.
 </xsl:template>
 
 <!-- For paragraphs containing only kbd elements, wrap in blockquote. -->
-<xsl:template match='xhtml:p[*[last()=count(../xhtml:kbd)]]'>
+<xsl:template match='xhtml:p[*[last()=count(../xhtml:kbd|../xhtml:br)]]'>
   <blockquote>
     <xsl:copy>
       <xsl:apply-templates select='node()|@*' />
   <xsl:variable name='node' select='.' />
   <p>
     <xsl:choose>
-      <xsl:when test='/document/image[license != $node]'>
+      <xsl:when test='/document/image[license/identifier != $node/identifier]'>
         <xsl:text>Except as otherwise noted, copying</xsl:text>
       </xsl:when>
       <xsl:otherwise>Copying</xsl:otherwise>
   </p>
 </xsl:template>
 
+<!-- Article info block inserted between heading and main contents -->
 <xsl:template match='xhtml:h1[not(preceding::xhtml:h1)]'>
+  <xsl:variable name='nodes'
+    select='/document/article/published|//xhtml:*[@article-info]' />
+
   <xsl:copy><xsl:apply-templates select='node()|@*' /></xsl:copy>
-  <xsl:if test='/document/article/published'>
+
+  <xsl:if test='$nodes'>
     <div id='article-info'>
-      <p>
-        <xsl:text>Posted </xsl:text>
-        <xsl:value-of select='/document/article/published' />
-      </p>
+      <xsl:apply-templates mode='article-info' select='$nodes'>
+        <xsl:sort data-type='number'
+          select='number(generate-id(..)=generate-id(/document/article))' />
+      </xsl:apply-templates>
     </div>
   </xsl:if>
 </xsl:template>
 
+<xsl:template mode='article-info' match='/document/article/published'>
+  <p>
+    <xsl:text>Posted </xsl:text>
+    <xsl:value-of select='/document/article/published' />
+    <xsl:if test='/document/article/updated'>
+      <xsl:if test='/document/article/updated != /document/article/published'>
+        <xsl:text>, last updated </xsl:text>
+        <xsl:value-of select='/document/article/updated' />
+      </xsl:if>
+    </xsl:if>
+  </p>
+</xsl:template>
+
+<xsl:template match='*[@article-info]' />
+<xsl:template mode='article-info' match='*[@article-info]'>
+  <xsl:copy>
+    <xsl:apply-templates select='@*[local-name() != "article-info"]' />
+    <xsl:apply-templates select='node()' />
+  </xsl:copy>
+</xsl:template>
+
 <xsl:template name='imgpara' match='xhtml:p[count(*)=1]
                                            [normalize-space(text())=""]
                                            [descendant::xhtml:img]'>
   <xsl:variable name='images' select='key("gallery", generate-id(.))' />
   <xsl:choose>
     <xsl:when test='count($images) > 1'>
-      <div class='gallery'>
+      <div>
+        <xsl:attribute name='class'>
+          <xsl:text>gallery</xsl:text>
+          <xsl:if test='//xhtml:a[f:contains-token(@class, "left")
+                                  or f:contains-token(@class, "right")]'>
+            <xsl:text> inline</xsl:text>
+          </xsl:if>
+        </xsl:attribute>
         <xsl:for-each select='$images'>
           <xsl:call-template name='imgpara' />
         </xsl:for-each>
     <head>
       <meta name='viewport' content='width=device-width, initial-scale=1' />
       <link rel='stylesheet' type='text/css' href='/style.css' />
+      <link rel='alternate stylesheet' type='text/css' href='/dark.css'
+        title='Dark Style' />
       <link rel="icon" href="data:," />
       <title>
         <xsl:variable name='page-title' select='string(/document/title)' />
       <div id='footer'>
         <xsl:apply-templates select='/document/copyright' />
         <xsl:apply-templates select='/document/license' />
-        <xsl:if test='/document/image'>
+        <xsl:if test='/document/image[copyright != /document/copyright
+                      or license/identifier != /document/license/identifier]'>
           <xsl:call-template name='image-attribution' />
         </xsl:if>
         <xsl:apply-templates select='/document/source' />
   </html>
 </xsl:template>
 
+<xsl:include href='layouts/whitespace.xsl' />
 <xsl:include href='layouts/clickytable.xsl' />
 
 </xsl:stylesheet>
index 2b3f8e2a7dcd187d0fcab8c9d188927468049226..0aee04d9dcc7ef7ee7b0156b125eda24b1f7cdab 100644 (file)
@@ -2,7 +2,7 @@
 <!--
   Nick's web site: SVG embedding.
 
-  Copyright © 2021 Nick Bowler
+  Copyright © 2021-2022 Nick Bowler
 
   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
         <xsl:value-of select='concat("es-", generate-id($idnode))' />
       </xsl:attribute>
     </xsl:if>
-    <xsl:apply-templates mode='embed-svg' select='@*[local-name()!="id"]' />
+    <xsl:choose>
+      <xsl:when test='$idnode/@src and not(parent::*)'>
+        <!-- remove .svg suffix -->
+        <xsl:variable name='raw'
+          select='substring($idnode/@src, 1, string-length($idnode/@src)-4)' />
+
+        <xsl:attribute name='class'>
+          <xsl:value-of select='normalize-space(concat(@class, " embed ",
+            translate($raw, "/", " ")))' />
+        </xsl:attribute>
+      </xsl:when>
+      <xsl:otherwise>
+        <xsl:apply-templates mode='embed-svg' select='@class' />
+      </xsl:otherwise>
+    </xsl:if>
+    <xsl:apply-templates mode='embed-svg'
+      select='@*[local-name()!="id" and local-name()!="class"]' />
     <xsl:apply-templates mode='embed-svg' select='node()'>
       <xsl:sort select='-count(self::svg:metadata)' data-type='number' />
     </xsl:apply-templates>
index a88c3380cc88f3bb0d59491bcb6e33f8316f8830..bbaf90e34bbf971bbddf399fd77e73be398b4bb8 100644 (file)
@@ -1,7 +1,7 @@
 <!--
   Nick's web site: XSLT helper functions.
 
-  Copyright © 2019-2021 Nick Bowler
+  Copyright © 2019-2022 Nick Bowler
 
   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   </func:result>
 </func:function>
 
+<!--
+  f:contains-token(list, token)
+  Returns true iff the space-separated list contains the given token
+-->
+<func:function name='f:contains-token'>
+  <xsl:param name='haystack' />
+  <xsl:param name='needle' />
+
+  <func:result select='contains(
+                         concat(" ", normalize-space($haystack), " "),
+                         concat(" ", normalize-space($needle), " ")
+                       )' />
+</func:function>
+
 </xsl:stylesheet>
diff --git a/layouts/whitespace.xsl b/layouts/whitespace.xsl
new file mode 100644 (file)
index 0000000..c15639a
--- /dev/null
@@ -0,0 +1,116 @@
+<!--
+  Nick's web site: white-space normalization for Nokogiri.
+
+  Copyright © 2019, 2022 Nick Bowler
+
+  Nokogiri's pretty-printer seems a bit weird.  Regardless of the indentation
+  setting, if an element has no child text nodes then it will be pretty-
+  printed.  This works by adding arbitrary whitespace to that element, and
+  then all of its children are eligible to be pretty-printed.
+
+  If an element has any text nodes at all, then it is not pretty-printed and
+  neither are any of its descendents.
+
+  In general, adding or removing whitespace from an XHTML document is unsafe
+  (changes the meaning) around span-level elements, but it is OK around other
+  kinds of elements.
+
+  These templates exploit the Nokogiri behaviour in two ways:
+
+  - by explicitly stripping whitespace wherever it is safe to do so, attempting
+    to allow pretty printing as much as possible without changing the meaning
+    of the document.
+
+  - by explicitly adding text nodes to suppress pretty printing in
+    situations where it would otherwise change the meaning of the document.
+
+  The text nodes which are added consist of U+2060 word joiner characters.
+  These should be removed from the final document in a separate pass.
+
+  This program is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program.  If not, see <https://www.gnu.org/licenses/>
+-->
+<xsl:stylesheet version='1.0'
+  xmlns='http://www.w3.org/1999/xhtml'
+  xmlns:xhtml='http://www.w3.org/1999/xhtml'
+  xmlns:xsl='http://www.w3.org/1999/XSL/Transform'
+  xmlns:f='http://draconx.ca/my-functions'>
+
+<xsl:import href='functions.xsl' />
+
+<!--
+  Adding arbitrary whitespace to <pre> is bad, so we inject zero-width non-
+  breaking spaces to prevent this.  This will render fine but the spaces
+  should be removed before final output to avoid problems with copy+paste.
+-->
+<xsl:template match='xhtml:pre'>
+  <xsl:copy>
+    <xsl:apply-templates select='node()|@*' />
+    <xsl:text>&#x2060;</xsl:text>
+  </xsl:copy>
+</xsl:template>
+
+<!--
+  Likewise, adding spaces between consecutive span-level elements where
+  none existed before won't go over well.
+-->
+<xsl:template name='glue-preceding-span'>
+  <xsl:if test='f:element-is-span(preceding-sibling::node()[1])'>
+    <xsl:text>&#x2060;</xsl:text>
+  </xsl:if>
+</xsl:template>
+
+<xsl:template match='*[f:element-is-span()]'>
+  <xsl:call-template name='glue-preceding-span' />
+  <xsl:copy>
+    <xsl:apply-templates select='node()|@*' />
+    <xsl:if test='*'>
+      <!-- avoid breaking within a span element -->
+      <xsl:text>&#x2060;</xsl:text>
+    </xsl:if>
+  </xsl:copy>
+</xsl:template>
+
+<!--
+  Manually strip whitespace-only text nodes so the pretty printer can do its
+  thing on remaining elements.
+-->
+<xsl:template match='text()[normalize-space(.) = ""]'>
+  <xsl:choose>
+    <!-- preserve anything according to xml:space -->
+    <xsl:when test='ancestor::*[@xml:space][1][@xml:space="preserve"]'>
+      <xsl:copy />
+    </xsl:when>
+    <!-- preserve anything under <pre> -->
+    <xsl:when test='ancestor::xhtml:pre'><xsl:copy /></xsl:when>
+    <!-- preserve whitespace which is the only child node of an element -->
+    <xsl:when test='count(../node()) = 1'><xsl:copy /></xsl:when>
+    <!-- preserve whitespace between consecutive span-level elements
+         which have at least one non-whitespace sibling text element -->
+    <xsl:when test='f:element-is-span(preceding-sibling::node()[1])
+                    and f:element-is-span(following-sibling::node()[1])
+                    and ../text()[normalize-space(.) != ""]'>
+      <xsl:copy />
+    </xsl:when>
+  </xsl:choose>
+</xsl:template>
+
+<!-- Clean up whitespace where harmless to do so -->
+<xsl:template match='xhtml:p/node()[1][self::text()]'>
+  <xsl:value-of select='f:strip-leading()' />
+</xsl:template>
+<xsl:template match='xhtml:p/node()[position()=last()][self::text()]'>
+  <xsl:value-of select='f:strip-trailing()' />
+</xsl:template>
+
+</xsl:stylesheet>
diff --git a/lib/colourmap.scss b/lib/colourmap.scss
new file mode 100644 (file)
index 0000000..e4d8d65
--- /dev/null
@@ -0,0 +1,113 @@
+// Nick's web site: SCSS colourmap helpers for automatic light/dark styles.
+//
+// Copyright © 2022 Nick Bowler
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+// database of colour names
+$colourmap: ();
+
+// database of combination CSS properties that should be translated to
+// colour-only properties (e.g., border-top -> border-top-color).
+$_colourpropmap:
+    ( border-top: border-top-color
+    , border-bottom: border-bottom-color
+    , border-left: border-left-color
+    , border-right: border-right-color
+    , border: border-color
+    );
+
+// Define a named set of colours.  Pass keyword arguments with each keyword
+// being a colour name and the argument is a list of one or two colours.
+//
+// When two colours are specified, the first should be for "light" backgrounds
+// and the second for "dark".
+//
+// For example:
+//
+//   @include defcolours($bg: white black, $fg: black white);
+@mixin defcolours($args...) {
+    @each $colour, $list in keywords($args) {
+        $colourmap: map-merge($colourmap, ($colour: $list)) !global;
+    }
+}
+
+// Obtain the colour value for a previously-defined colour name.  By
+// default, its primary (light) colour value is returned; this can be
+// changed with the $num keyword parameter.
+//
+// The $pre and $post keyword arguments may be used to supplement the
+// result with additional tokens either before or after the colour
+// value, respectively, as might be used for combined properties
+// such as border, outline, etc.
+@function getcolour($colour, $pre: (), $post: (), $num: 1) {
+    @return join(append($pre, nth(map-get($colourmap, $colour), $num)), $post);
+}
+
+@mixin usecolour_dark_($prop, $colour, $pre: (), $post: ()) {
+    @if (length(map-get($colourmap, $colour)) > 1) {
+        $transprop: map-get($_colourpropmap, $prop);
+        @if $transprop {
+            #{$transprop}: getcolour($colour, $num: 2)
+        } @else {
+            #{$prop}: getcolour($colour, $pre, $post, 2);
+        }
+    }
+}
+
+// Sets the given CSS property to the specified colour name.  The $pre
+// and $post keyword arguments may be used to supplement the property
+// value, as with the getcolour function.
+//
+// For two-value colours, the property is set using CSS variables to
+// adapt the colour based on the user's preference for a light or dark
+// background.  The rules will fall back to the static (light) colour
+// if CSS variables are not supported.
+//
+// For example:
+//
+//   @include usecolour(border, fg, $pre: solid 1px);
+@mixin usecolour($prop, $colour, $pre: (), $post: ()) {
+    #{$prop}: getcolour($colour, $pre, $post);
+    @at-root {
+        @media (prefers-color-scheme: dark) {
+            & { @include usecolour_dark_($prop, $colour, $pre, $post); }
+        }
+    }
+}
+
+// Convenience helper to assign multiple colour properties at once, for
+// the common case where the $pre and $post arguments to usecolour are
+// not required.
+//
+// Takes any number of keyword arguments with the keyword being the
+// property name and the value being the colour name.
+//
+// For example:
+//
+//   @include usecolours($background-color: bg, $color: fg);
+@mixin usecolours($args...) {
+    @each $prop, $colour in keywords($args) {
+        #{$prop}: getcolour($colour);
+    }
+    @at-root {
+        @media (prefers-color-scheme: dark) {
+            & {
+                @each $prop, $colour in keywords($args) {
+                    @include usecolour_dark_($prop, $colour);
+                }
+            }
+        }
+    }
+}
diff --git a/lib/compiledcontent.rb b/lib/compiledcontent.rb
new file mode 100644 (file)
index 0000000..80e7911
--- /dev/null
@@ -0,0 +1,25 @@
+# Nick's web site: compiled_content filter.  Simply calls the compiled_content
+# method on the current item to retrieve the text from a named rep or snapshot.
+#
+# Copyright © 2022-2023 Nick Bowler
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+class CompiledContentFilter < Nanoc::Filter
+    identifier :compiled_content
+
+    def run(content, params = {})
+        return @item.compiled_content(**params)
+    end
+end
diff --git a/lib/css-darkmode.rb b/lib/css-darkmode.rb
new file mode 100644 (file)
index 0000000..f15f64d
--- /dev/null
@@ -0,0 +1,207 @@
+# Nick's web site: css_darkmode filter.  Cut out dark-mode media queries
+# from a stylesheet and either re-insert them at the end of the same
+# stylesheet, or produce a separate stylesheet for use as an alternate.
+#
+# Copyright © 2022 Nick Bowler
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+class CssDarkModeFilter < Nanoc::Filter
+    identifier :css_darkmode
+
+    require 'crass'
+
+    def filter_whitespace(nodes)
+        return nodes.reject {|x| x[:node] == :whitespace }
+    end
+
+    # Return a new list of nodes where consecutive whitespace nodes have been
+    # replaced with a single whitespace node, and if the whitespace contains
+    # one or more newlines, everything before the final newline is removed.
+    def simplify_whitespace(nodes)
+        nodes.slice_when do |a,b|
+            a[:node] != :whitespace or b[:node] != :whitespace
+        end.map do |x|
+            if x[0][:node] == :whitespace
+                combined = x.map{|y| y[:raw]}.join.sub(/.*\n/m, "\n")
+                x[0].merge({ raw: combined})
+            else
+                x[0]
+            end
+        end
+    end
+
+    def is_media_dark_block(x)
+        return false unless x[:node] == :simple_block
+
+        list = filter_whitespace(x[:value])
+        list.length == 3 and
+            list[0][:node] == :ident and
+            list[0][:value] == "prefers-color-scheme" and
+            list[1][:node] == :colon and
+            list[2][:node] == :ident and
+            list[2][:value] == "dark"
+    end
+
+    def is_media_dark(x)
+        return false unless x[:node] == :at_rule and x[:name] == "media"
+
+        x[:prelude].index {|y| is_media_dark_block(y)}
+    end
+
+    def is_supports_block(x)
+        true if x[:node] == :at_rule and x[:name] == "supports"
+    end
+
+    # Remove (prefers-color-scheme: dark) conditions from a media query.
+    # If the resulting query is empty, returns the query's block alone.
+    # Otherwise, returns the modified query.
+    def prune_media_dark(x)
+        start_index = 0
+        end_index = 0
+        state = :init
+
+        x[:prelude].each do |y|
+            case state
+            when :init
+                end_index += 1
+                if is_media_dark_block(y)
+                    state = :post
+                elsif y[:node] == :ident and y[:value] == "and"
+                    state = :pre
+                else
+                    start_index = end_index
+                    state = :init
+                end
+            when :pre
+                end_index += 1
+                if is_media_dark_block(y)
+                    state = :post
+                elsif y[:node] == :whitespace
+                    state = :pre
+                else
+                    start_index = end_index
+                    state = :init
+                end
+            when :post
+                if y[:node] == :whitespace
+                    end_index += 1
+                    state = :post
+                elsif y[:node] == :ident and y[:value] == "and"
+                    end_index += 1
+                    state = :post
+                else
+                    break
+                end
+            end
+        end
+
+        x[:prelude].slice!(start_index..end_index-1)
+        return x[:block] if filter_whitespace(x[:prelude]).empty?
+        x
+    end
+
+    def equiv_query(a, b)
+        return false unless a.class == b.class
+
+        case a
+        when Array
+            a = filter_whitespace(a)
+            b = filter_whitespace(b)
+
+            return false unless a.length == b.length
+            return a.map.with_index{|x,i| equiv_query(x, b[i])}.all?
+        when Hash
+            return false unless a[:node] == b[:node]
+            case a[:node]
+            when :at_rule
+                return equiv_query(a[:prelude], b[:prelude])
+            when :simple_block
+                return equiv_query(a[:value], b[:value])
+            else
+                return (a[:value] == b[:value] and a[:unit] == b[:unit])
+            end
+        end
+    end
+
+    def process(tree, params)
+        last_visited = {}
+        darknodes = []
+
+        tree.delete_if do |x|
+            sep = { node: :whitespace, raw: "\n" }
+            if last_visited[:node] == :whitespace
+                # Try to maintain indentation
+                sep[:raw] += last_visited[:raw].sub(/[^\n]*\z|.*\n/m, "")
+            end
+            last_visited = x
+
+            if is_supports_block(x)
+                # Re-parse the block as a list of rules
+                s = Crass::Parser.stringify(x[:block])
+                x[:block] = Crass::Parser.parse_rules(s, params)
+
+                block = process(x[:block], params)
+                unless block.empty?
+                    block << { node: :whitespace, raw: "\n" }
+                    darknodes << [sep, x.merge({ block: block })]
+                end
+
+                Crass::Parser.stringify(x[:block]).strip.empty?
+            elsif is_media_dark(x)
+                if params[:alternate]
+                    x = prune_media_dark(x)
+                end
+                darknodes << [sep, x]
+            end
+        end
+
+        # Combine consecutive equivalent media queries into a single query
+        result = darknodes.slice_when do |a,b|
+            !equiv_query(a[1], b[1])
+        end.each.map do |x|
+            case x[0][1]
+            when Hash
+                g = x.map{ |sep, node| node[:block] }.flatten
+                [ x[0][0], x[0][1].merge({ block: simplify_whitespace(g) }) ]
+            else
+                x
+            end
+        end.flatten
+
+        simplify_whitespace(result)
+    end
+
+    def run(content, params = {})
+        params = {
+            preserve_comments: true,
+            preserve_hacks: true
+            }.merge(params)
+
+        tree = Crass.parse(content, params)
+
+        prologue = tree.take_while do |x|
+            x[:node] == :comment or x[:node] == :whitespace
+        end
+        tree.slice!(0, prologue.length)
+
+        output = "#{Crass::Parser.stringify(prologue).rstrip}\n"
+        darknodes = process(tree, params)
+        unless params[:alternate]
+            tree = simplify_whitespace(tree)
+            output += "#{Crass::Parser.stringify(tree).rstrip}\n"
+        end
+        output += "#{Crass::Parser.stringify(darknodes).rstrip}\n"
+    end
+end
diff --git a/lib/gpg-wkd.rb b/lib/gpg-wkd.rb
new file mode 100644 (file)
index 0000000..b2379f1
--- /dev/null
@@ -0,0 +1,196 @@
+# Nick's web site: Export GPG public keys for HTTP Keyserver and the
+# Web Key Directory
+#
+# Copyright © 2022 Nick Bowler
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+module WKD
+    require 'open3'
+
+    @@gpg2 = "/usr/bin/gpg"
+    @@wksclient = "/usr/libexec/gpg-wks-client"
+
+    def WKD.gpg2; @@gpg2 end
+    def WKD.gpg2=(x); @@gpg2 = x end
+    def WKD.wksclient; @@wksclient end
+    def WKD.wksclient=(x); @@wksclient = x end
+
+    # Convert a list of keyring filenames into GPG keyring arguments
+    def WKD.keyring_args(args)
+        return [ "--no-default-keyring",
+            *args.map { |x| "--keyring=" + (x['/'] ? x : "./" + x) } ]
+    end
+
+    # Helper for implementing export filters below
+    def WKD.export(item, id, *args)
+        data, result = Open3.capture2(@@gpg2, "--export", *args,
+            *WKD.keyring_args(item[:keyrings]), id.chomp)
+        raise "gpg failed" unless result.success?
+        return data
+    end
+
+    # Return a list of all key fingerprints known from the given GPG keyrings.
+    def WKD.keys_from_keyrings(*args)
+        fps = {}
+
+        Open3.popen2(@@gpg2,
+            "--with-colons", "--list-keys", *WKD.keyring_args(args)
+        ) do |stdin, stdout, result|
+            stdin.close
+            stdout.each do |line|
+                fields = line.split(":")
+                next if fields[0] != "fpr"
+                fps[fields[9]] = true
+            end
+            stdout.close
+
+            raise "gpg failed" unless result.value.success?
+        end
+
+        return fps.keys
+    end
+
+    # Return a list of all UIDs known from the given GPG keyrings.
+    def WKD.uids_from_keyrings(*args)
+        uids = {}
+
+        Open3.popen2(@@gpg2,
+            "--with-colons", "--list-keys", *WKD.keyring_args(args)
+        ) do |stdin, stdout, result|
+            stdin.close
+            stdout.each do |line|
+                fields = line.split(":")
+                next if fields[0] != "uid"
+                fields[9].gsub!(/\\x../) { |x| x[2..].hex.chr }
+                uids[fields[9]] = true
+            end
+            stdout.close
+
+            raise "gpg failed" unless result.value.success?
+        end
+
+        return uids.keys
+    end
+
+    # Given a list of UIDs, return a dictionary where the keys are UIDs
+    # and the values are the WKS hash.
+    def WKD.hashes_from_uids(*args)
+        wkd_hash = {}
+
+        consume_output = Proc.new do |s|
+            while l = s.slice!(/([^\n]*)\n/) do
+                hash, uid = l.chomp.split(nil, 2)
+                wkd_hash[uid] = hash
+            end
+        end
+
+        Open3.popen2(@@wksclient,
+            "--print-wkd-hash"
+        ) do |stdin, stdout, result|
+            buf = ""
+            args.flatten.each do |uid|
+                stdin.puts(uid)
+                stdin.flush
+
+                loop do
+                    buf += stdout.read_nonblock(100)
+                    consume_output.call(buf)
+                rescue EOFError, IO::WaitReadable
+                    break
+                end
+            end
+            stdin.close
+
+            loop do
+                buf += stdout.readpartial(100)
+                consume_output.call(buf)
+            rescue EOFError
+                break
+            end
+            stdout.close
+
+            raise "gpg-wks-client failed" unless result.value.success?
+        end
+
+        return wkd_hash
+    end
+
+end
+
+# Call during preprocessing to create items for each unique UID found in the
+# given keyring items.  The items have the identifier /gpg/UID and the content
+# is the same UID.  The items are created with the :keyrings attribute set to
+# the list of keyring files and :wkd_hash is for the Web Key Directory.
+def create_wkd_items(keyring_items)
+    keyring_files = {}
+    [*keyring_items].each { |item| keyring_files[item.raw_filename] = true }
+
+    wkd = WKD.hashes_from_uids(WKD.uids_from_keyrings(*keyring_files.keys))
+    wkd.each do |uid, hash|
+        attrs = {
+            keyrings: keyring_files.keys,
+            wkd_hash: hash
+        }
+        @items.create(uid, attrs, "/gpg/" + uid)
+    end
+end
+
+def create_hkp_items(keyring_items)
+    keyring_files = {}
+    [*keyring_items].each { |item| keyring_files[item.raw_filename] = true }
+
+    fps = WKD.keys_from_keyrings(*keyring_files.keys)
+    keyids_64 = {}
+    keyids_32 = {}
+
+    fps.each do |fp|
+        id64 = fp[-16..]
+        id32 = fp[-8..]
+
+        keyids_64[id64] = keyids_64[id64].to_i + 1
+        keyids_32[id32] = keyids_32[id32].to_i + 1
+    end
+
+    fps.each do |fp|
+        id64 = fp[-16..]
+        id32 = fp[-8..]
+
+        attrs = { keyrings: keyring_files.keys }
+        attrs[:id64] = id64 if keyids_64[id64] == 1
+        attrs[:id32] = id32 if keyids_32[id32] == 1
+
+        @items.create("0x"+fp, attrs, "/gpg/" + fp)
+    end
+end
+
+# Convert items created by create_wkd_items into real GPG keyrings.
+class WKDExport < Nanoc::Filter
+    identifier :wkd_export
+    type :text => :binary
+
+    def run(content, params = {})
+        WKD.export(item, content, "--output=" + output_filename)
+    end
+end
+
+class WKDExportArmor < Nanoc::Filter
+    identifier :wkd_export_armor
+    type :text
+
+    def run(content, params = {})
+        data = WKD.export(item, content, "--armor")
+        return data
+    end
+end
index 77916dd29492582da2081c4a4a116b7e32105ee1..24d37e4ef33407db32c07153b055ccbb60f8ad6f 100644 (file)
@@ -1,6 +1,6 @@
 # Nick's web site: Ruby helpers for processing
 #
-# Copyright © 2018-2021 Nick Bowler
+# Copyright © 2018-2022 Nick Bowler
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -77,26 +77,31 @@ def counter(name = :default, item = @item)
     name.to_s.capitalize + " " + ($counters[item][name] += 1).to_s
 end
 
-def img_rep_fallback(item, rep)
-    return rep unless item.reps[rep].raw_path.nil?
-    return :large
+# Return a hash containing :src, :width and :height based on an image item rep.
+def img_rep_attrs(item, rep)
+    rep = :large if item.reps[rep].raw_path.nil?
+    attrs = {}
+
+    attrs[:src] ||= item_uri(item, rep: rep)
+    attrs[:width], attrs[:height] = FastImage.size(item.reps[rep].raw_path)
+
+    return attrs
 end
 
-def gallery_img(item, rep: :large, alt: nil, caption: nil)
+def embed_img(item, rep: :large, caption: nil, block_attrs: {}, img_attrs: {})
     return "[image not found]" unless item
 
-    alt ||= item[:title]
-    caption ||= alt
+    img_attrs[:alt] ||= item[:title]
+    caption ||= img_attrs[:alt]
     caption = caption.strip
     caption.gsub!(/\s+/, " ")
 
-    rep = img_rep_fallback(item, rep)
-    attrs = { :src => item_uri(item, rep: rep), :alt => item[:title] }
-    attrs[:width], attrs[:height] = FastImage.size(item.reps[rep].raw_path)
+    img_attrs = img_rep_attrs(item, rep).merge(img_attrs)
+    block_attrs[:href] = item_uri(item, rep: :info)
 
     b = Nokogiri::XML::Builder.new do |xml|
-        xml.a(:href => item_uri(item, rep: :info)) {
-            xml.img(attrs, "generate-gallery" => "generate-gallery")
+        xml.a(block_attrs) {
+            xml.img(img_attrs)
             unless caption.empty?
                 xml << " &#x2060;"
                 xml.small { xml << caption }
@@ -106,6 +111,19 @@ def gallery_img(item, rep: :large, alt: nil, caption: nil)
     b.to_xml(:save_with => Nokogiri::XML::Node::SaveOptions::NO_DECLARATION)
 end
 
+def gallery_img(item, rep: :medium, caption: nil, alt: nil)
+    attrs = { alt: alt, "generate-gallery" => "generate-gallery" }
+    embed_img(item, rep: rep, caption: caption, img_attrs: attrs)
+end
+
+def floating_img(item, rep: :medium, caption: nil, alt: nil, left: nil)
+    battrs = { class: if left then "left" else "right" end }
+    attrs = { alt: alt }
+
+    embed_img(item, rep: rep, caption: caption,
+                    block_attrs: battrs, img_attrs: attrs)
+end
+
 def expand_copyright(copyright)
     result = { :years => {} }
 
index 3338e8a436558759a81a129abdc937d1d2142506..079640e28d3b3d196c531781da2642dafcf16c14 100644 (file)
@@ -1,6 +1,6 @@
 # Nick's web site: Generate a project page from a submodule README.md
 #
-# Copyright © 2021 Nick Bowler
+# Copyright © 2021-2022 Nick Bowler
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -82,21 +82,25 @@ def project_readme(item = @item)
             obtaining += line.lstrip
         end
 
+        gpg_keyid = "5B45D3D185B8E1F6"
         obtaining += "\n\n" + <<~EOF
             [gpg]: https://gnupg.org/
+            [keyring]: /pubring/#{gpg_keyid}.asc
 
             Use the signature file to verify that the corresponding source
             bundle is intact.  After downloading both files, if [GnuPG][gpg]
             is installed, the signature can be verified with a command like:
 
-            <kbd>gpg --verify #{targz}.sig</kbd>
+            <kbd>gpg --verify #{targz}.sig #{targz}</kbd>
 
             If the verification fails because you don't have the required
             public key, that key can be imported with a command such as:
 
-            <kbd>gpg --keyserver keys.gnupg.net --recv-keys 5B45D3D185B8E1F6</kbd>
+            <kbd>gpg --keyserver keys.draconx.ca --recv-keys #{gpg_keyid}</kbd>
 
-            Then run the verify command again.
+            Then run the verify command again.  Alternatively, you can
+            [download the public keyring][keyring] manually and import it
+            using `gpg --import`.
         EOF
     end
 
index c6cf65464f415e750460e41c0b130bb1422112e7..b15680f6787deae166c5572b06e2a34f3368920e 100644 (file)
@@ -1,6 +1,6 @@
 # Nick's web site: Helper to retrieve global style variables in ruby.
 #
-# Copyright © 2020 Nick Bowler
+# Copyright © 2020, 2022 Nick Bowler
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -22,11 +22,9 @@ class GetSCSSGlobals < Sass::Tree::Visitors::Perform
         x = new(nil)
         x.send(:visit, root)
         result = x.instance_variable_get(:@globals)
-        if name.nil?
-            return result.freeze
-        else
-            return result[name]
-        end
+        return result.freeze if name.nil?
+
+        x.send(:rubify_result, result[name])
     end
 
     protected
@@ -40,15 +38,35 @@ class GetSCSSGlobals < Sass::Tree::Visitors::Perform
 
     def visit_variable(node)
         super
-        
+
         x = @environment.global_env.var(node.name)
-        if !x.nil?
-            @globals[node.name] = x
+        @globals[node.name] = x unless x.nil?
+    end
+
+    # Convert SASS maps and lists to ruby equivalents that are actually usable.
+    def rubify_result(val)
+        case val
+        when Sass::Script::Value::Map
+            val.to_h.each_with_object({}) do |(k, v), h|
+                h[k.to_s.to_sym] = rubify_result(v)
+            end
+        when Sass::Script::Value::List
+            val.to_a.map { |v| rubify_result(v) }
+        else
+            val
         end
     end
 end
 
 def scss_get_var(variable, item = @items["/style.scss"])
-    engine = Sass::Engine.for_file(item.raw_filename, { :syntax => :scss })
+    engine = Sass::Engine.for_file(item.raw_filename, {
+        :syntax => :scss,
+        :load_paths => ["."],
+    })
     return GetSCSSGlobals.visit(engine.to_tree, variable)
 end
+
+def scss_get_colour(colour, index = 0, item = @items["/style.scss"])
+    cmap = scss_get_var(:colourmap, item);
+    [cmap[colour]].flatten[index]
+end
index f955f245d9ff3a4f861ea292f2922df40fd33ae7..677f3678166223e1921a0655cf72ba8429dc9b09 100755 (executable)
@@ -29,7 +29,7 @@ if content =~ /\A---(--)?\s*$/
 end
 
 if metadata
-    meta = YAML.load(metadata)
+    meta = YAML.load(metadata, permitted_classes: [Time])
     timefmt = "%FT%T%z"
     updatestr = updatetime.round.strftime(timefmt)
     autoset = nil
@@ -49,7 +49,7 @@ if metadata
 
     if autoset
         # Revalidate YAML
-        meta = YAML.load(metadata)
+        meta = YAML.load(metadata, permitted_classes: [Time])
         unless meta[autoset] == updatetime.round
             raise "failed to auto-insert " + autoset
         end