◊(Local Yarn Code "Check-in [e52e53c8]")

Overview
Comment:Merge updates from trunk
Timelines: family | ancestors | descendants | both | doc-expansion
Files: files | file ages | folders
SHA3-256: e52e53c80aeef022515d121a19b92ec91bc79ba580e15ff1a080b727287a73ea
User & Date: joel on 2020-02-10 21:15:32
Other Links: branch diff | manifest | tags
Context
2020-02-12
21:12
Don’t clobber the footer when rebuilding code docs check-in: c8354d62 user: joel tags: doc-expansion
2020-02-10
21:15
Merge updates from trunk check-in: e52e53c8 user: joel tags: doc-expansion
17:28
Improved footer check-in: 75502f96 user: joel tags: trunk
2020-01-19
20:49
scribble edits check-in: ead156b1 user: joel tags: doc-expansion
Changes

Modified blog.rkt from [1dda01dc] to [75b9d900].

2
3
4
5
6
7
8
9
10

11
12
13
14
15
16
17
..
34
35
36
37
38
39
40

41
42
43
44
45
46
47

; SPDX-License-Identifier: BlueOak-1.0.0
; This file is licensed under the Blue Oak Model License 1.0.0.

;; Builds the paginated “blog” HTML files (blog-pg1.html ...) from the SQLite cache
;; The files will be written out every time this module is evaluated! (see end)

(require "crystalize.rkt"
         "snippets-html.rkt"

         racket/file
         sugar/list)

(provide main)

;; How many items per blog page
(define per-page 5)
................................................................................
 <nav id="bottom-nav"><ul>◊|page-nav|</ul></nav>

 ◊html$-page-body-close[]
 </html>})

;; Grabs all the articles+notes from the cache and writes out all the blog page files
(define (build-blog)

  (define arts-n-notes (slice-at (listing-htmls (articles+notes 'full #:series #f)) per-page))
  (define pagecount (length arts-n-notes))
  
  (for ([pagenum (in-range 1 (+ 1 pagecount))]
        [page    (in-list arts-n-notes)])
    (define filename (format "blog-pg~a.html" pagenum))
    (displayln (format "Writing: ~a" filename))







|

>







 







>







2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
..
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49

; SPDX-License-Identifier: BlueOak-1.0.0
; This file is licensed under the Blue Oak Model License 1.0.0.

;; Builds the paginated “blog” HTML files (blog-pg1.html ...) from the SQLite cache
;; The files will be written out every time this module is evaluated! (see end)

(require "cache.rkt"
         "snippets-html.rkt"
         "dust.rkt"
         racket/file
         sugar/list)

(provide main)

;; How many items per blog page
(define per-page 5)
................................................................................
 <nav id="bottom-nav"><ul>◊|page-nav|</ul></nav>

 ◊html$-page-body-close[]
 </html>})

;; Grabs all the articles+notes from the cache and writes out all the blog page files
(define (build-blog)
  (listing-context 'blog) ; honor conceal directives for the blog
  (define arts-n-notes (slice-at (listing-htmls (articles+notes 'full #:series #f)) per-page))
  (define pagecount (length arts-n-notes))
  
  (for ([pagenum (in-range 1 (+ 1 pagecount))]
        [page    (in-list arts-n-notes)])
    (define filename (format "blog-pg~a.html" pagenum))
    (displayln (format "Writing: ~a" filename))

Added cache.rkt version [42c939a6].











































































































































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
#lang racket/base

; SPDX-License-Identifier: BlueOak-1.0.0
; This file is licensed under the Blue Oak Model License 1.0.0.

(require deta
         db/base
         db/sqlite3
         threading
         pollen/setup
         racket/match
         (rename-in racket/list
                    (group-by group-list-by))
         "dust.rkt"
         (except-in pollen/core select))

(provide init-cache-db!
         cache-conn                     ; The most eligible bachelor in Neo Yokyo
         (schema-out cache:article)
         (schema-out cache:note)
         (schema-out cache:series)
         (schema-out cache:index-entry)
         delete-article!
         delete-notes!
         current-plain-title
         articles
         articles+notes
         listing-htmls
         <listing-full>
         <listing-excerpt>
         <listing-short>
         unfence
         series-grouped-list)

;; Cache DB and Schemas

(define DBFILE (build-path (current-project-root) "vitreous.sqlite"))
(define cache-conn (sqlite3-connect #:database DBFILE #:mode 'create))

(define current-plain-title (make-parameter "void"))

(define-schema cache:article #:table "articles"
  ([id                   id/f #:primary-key #:auto-increment]
   [page                 symbol/f]
   [title-plain          string/f]
   [title-html-flow      string/f]
   [title-specified?     boolean/f]
   [published            string/f]
   [updated              string/f]
   [author               string/f]
   [conceal              string/f]
   [series-page          symbol/f]
   [noun-singular        string/f]
   [note-count           integer/f]
   [doc-html             string/f]
   [disposition          string/f]
   [disp-html-anchor     string/f]
   [listing-full-html    string/f]   ; full content but without notes
   [listing-excerpt-html string/f]   ; Not used for now
   [listing-short-html   string/f])) ; Date and title only

(define-schema cache:note #:table "notes"
  ([id                   id/f #:primary-key #:auto-increment]
   [page                 symbol/f]
   [html-anchor          string/f]
   [title-html-flow      string/f] ; No block-level HTML elements
   [title-plain          string/f]
   [author               string/f]
   [author-url           string/f]
   [published            string/f]
   [disposition          string/f]
   [content-html         string/f]
   [series-page          symbol/f]
   [conceal              string/f]
   [listing-full-html    string/f]
   [listing-excerpt-html string/f]   ; Not used for now
   [listing-short-html   string/f])) ; Date and title only

(define-schema cache:series #:table "series"
  ([id            id/f #:primary-key #:auto-increment]
   [page          symbol/f]
   [title         string/f]
   [published     string/f]
   [noun-plural   string/f]
   [noun-singular string/f]))

(define-schema cache:index-entry #:table "index_entries"
  ([id          id/f #:primary-key #:auto-increment]
   [entry       string/f]
   [subentry    string/f]
   [page        symbol/f]
   [html-anchor string/f]))

(define-schema listing
  #:virtual
  ([html        string/f]
   [published   date/f]
   [series-page symbol/f]))

(define (init-cache-db!)
  (create-table! cache-conn 'cache:article)
  (create-table! cache-conn 'cache:note)
  (create-table! cache-conn 'cache:series)
  (create-table! cache-conn 'cache:index-entry))

(define (delete-article! page)
  (query-exec cache-conn
              (~> (from cache:article #:as a)
                  (where (= a.page ,(format "~a" page)))
                  delete)))

(define (delete-notes! page)
  (query-exec cache-conn
              (~> (from cache:note #:as n)
                  (where (= n.page ,(format "~a" page)))
                  delete)))

;;
;;  ~~~ Fetching articles and notes ~~~
;;

;; (Private use) Conveniece function for the WHERE `series-page` clause
(define (where-series q s)
  (define (s->p x) (format "~a/~a.html" series-folder x))
  (match s
    [(list series ...)
     (where q (in a.series-page ,(map s->p series)))] ; WHERE series-page IN (item1 ...)
    [(or (? string? series) (? symbol? series))
     (where q (= a.series-page ,(s->p series)))]      ; WHERE series-page = "item"
    [#t
     (where q (= a.series-page ,(path->string (here-output-path))))]
    [_ q]))

;; (Private use) Convenience for the WHERE `conceal` NOT LIKE clause
(define (where-not-concealed q)
  (define base-clause (where q (not (like a.conceal "%all%"))))
  (match (listing-context)
    ["" base-clause]
    [(var context) (where base-clause (not (like a.conceal ,(format "%~a%" context))))]))

;; Needed to "parameterize" column names
;; see https://github.com/Bogdanp/deta/issues/14#issuecomment-573344928
(require (prefix-in ast: deta/private/ast))

;; Builds a query to fetch articles
(define (articles type #:series [s #t] #:limit [lim -1] #:order [ord 'desc])
  (define html-field (format "listing_~a_html" type))
  (~> (from cache:article #:as a)
      (select (fragment (ast:as (ast:qualified "a" html-field) "html"))
              a.published
              a.series-page
              a.conceal)
      (where-series s)
      (where-not-concealed)
      (limit ,lim)
      (order-by ([a.published ,ord]))
      (project-onto listing-schema)))

;; Builds a query that returns articles and notes intermingled chronologically
(define (articles+notes type #:series [s #t] #:limit [lim -1] #:order [ord 'desc])
  (define html-field (format "listing_~a_html" type))
  (~> (from (subquery
             (~> (from cache:article #:as A)
                 (select (fragment (ast:as (ast:qualified "A" html-field) "html"))
                         A.published
                         A.series-page
                         A.conceal)
                 (union
                  (~> (from cache:note #:as N)
                      (select (fragment (ast:as (ast:qualified "N" html-field) "html"))
                              N.published
                              N.series-page
                              N.conceal)))))
            #:as a)
      (where-series s)
      (where-not-concealed)
      (limit ,lim)
      (order-by ([a.published ,ord]))
      (project-onto listing-schema)))

;; Get all the a list of the HTML all the results in a query
(define (listing-htmls list-query)
  (for/list ([l (in-entities cache-conn list-query)])
    (listing-html l)))

;; Return cached HTML of articles and/or notes, fenced within a style txexpr to prevent it being
;; escaped by ->html. See also: definition of `unfence`

;; E.g.: (<listing-full> articles+notes)
(define (<listing-full> query-func #:series [s #t] #:limit [lim -1] #:order [ord 'desc])
  `(style ,@(listing-htmls (query-func 'full #:series s #:limit lim #:order ord))))
;;                                     ^^^^^

(define (<listing-excerpt> query-func #:series [s #t] #:limit [lim -1] #:order [ord 'desc])
  `(style ,@(listing-htmls (query-func 'excerpt #:series s #:limit lim #:order ord))))
;;                                     ^^^^^^^^

(define (<listing-short> query-func #:series [s #t] #:limit [lim -1] #:order [ord 'desc])
  `(style "<ul class=\"article-list\">"
          ,@(listing-htmls (query-func 'short #:series s #:limit lim #:order ord))
          "</ul>")) ;;                 ^^^^^^

;; Remove "<style>" and "</style>" introduced by using ->html on docs containing output from
;; listing functions
(define (unfence html-str)
  (regexp-replace* #px"<[\\/]{0,1}style>" html-str ""))

;;
;;  ~~~ Fetching series ~~~
;;
(define (series-grouped-list)
  (~> (for/list ([row (in-entities cache-conn (from cache:series #:as s))]) row)
      (group-list-by cache:series-noun-plural _ string-ci=?)))

Modified crystalize.rkt from [cb633ea2] to [c41782ea].

1
2
3
4
5
6
7



8
9
10

11
12
13
14

15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
...
127
128
129
130
131
132
133

134
135
136
137
138
139
140
...
253
254
255
256
257
258
259

260
261
262
263
264
265
266
...
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
#lang racket/base

; SPDX-License-Identifier: BlueOak-1.0.0
; This file is licensed under the Blue Oak Model License 1.0.0.

(require deta db/base db/sqlite3 threading txexpr gregor)




(require racket/match
         racket/string
         pollen/pagetree

         pollen/template
         (except-in pollen/core select) ; avoid conflict with deta
         pollen/setup)


(require "dust.rkt" "snippets-html.rkt")

(provide init-cache-db!
         cache-conn            ; The most eligible bachelor in Neo Yokyo
         parse-and-cache-article!
         cache-series!
         current-plain-title
         (schema-out cache:article)
         (schema-out cache:note)
         (schema-out cache:series)
         (schema-out cache:index-entry)
         articles
         articles+notes
         listing-htmls
         <listing-full>
         <listing-excerpt>
         <listing-short>
         unfence)

;; Cache DB and Schemas

(define DBFILE (build-path (current-project-root) "vitreous.sqlite"))
(define cache-conn (sqlite3-connect #:database DBFILE #:mode 'create))

(define current-plain-title (make-parameter "void"))

(define-schema cache:article #:table "articles"
  ([id                   id/f #:primary-key #:auto-increment]
   [page                 symbol/f]
   [title-plain          string/f]
   [title-html-flow      string/f]
   [title-specified?     boolean/f]
   [published            string/f]
   [updated              string/f]
   [author               string/f]
   [conceal              string/f]
   [series-page          symbol/f]
   [noun-singular        string/f]
   [note-count           integer/f]
   [doc-html             string/f]
   [disposition          string/f]
   [disp-html-anchor     string/f]
   [listing-full-html    string/f]   ; full content but without notes
   [listing-excerpt-html string/f]   ; Not used for now
   [listing-short-html   string/f])) ; Date and title only

(define-schema cache:note #:table "notes"
  ([id                   id/f #:primary-key #:auto-increment]
   [page                 symbol/f]
   [html-anchor          string/f]
   [title-html-flow      string/f] ; No block-level HTML elements
   [title-plain          string/f]
   [author               string/f]
   [author-url           string/f]
   [published            string/f]
   [disposition          string/f]
   [content-html         string/f]
   [series-page          symbol/f]
   [listing-full-html    string/f]
   [listing-excerpt-html string/f]   ; Not used for now
   [listing-short-html   string/f])) ; Date and title only

(define-schema cache:series #:table "series"
  ([id            id/f #:primary-key #:auto-increment]
   [page          symbol/f]
   [title         string/f]
   [published     string/f]
   [noun-plural   string/f]
   [noun-singular string/f]))

(define-schema cache:index-entry #:table "index_entries"
  ([id          id/f #:primary-key #:auto-increment]
   [entry       string/f]
   [subentry    string/f]
   [page        symbol/f]
   [html-anchor string/f]))

(define-schema listing
  #:virtual
  ([html        string/f]
   [published   date/f]
   [series-page symbol/f]))

(define (init-cache-db!)
  (create-table! cache-conn 'cache:article)
  (create-table! cache-conn 'cache:note)
  (create-table! cache-conn 'cache:series)
  (create-table! cache-conn 'cache:index-entry))

;; Save an article and its notes (if any) to the database, and return the
;; rendered HTML of the complete article.
;;
(define (parse-and-cache-article! pagenode doc)
  (define-values (doc-no-title maybe-title)
    (splitf-txexpr doc (make-tag-predicate 'title)))
  (define-values (body-txpr note-txprs)
    (splitf-txexpr doc-no-title (make-tag-predicate 'note)))
  (define-values (disposition disp-note-id)
    (notes->last-disposition-values note-txprs))
................................................................................
                                               disp-note-id
                                               (length note-txprs))]
         [footer (html$-article-close footertext)]
         [listing-short (html$-article-listing-short pagenode pubdate title-html)]
         [notes-section-html (cache-notes! pagenode title-plain note-txprs)])
    (cache-index-entries! pagenode doc) ; note original doc is used here
    (current-plain-title title-plain)

    (insert-one! cache-conn
                 (make-cache:article
                  #:page pagenode
                  #:title-plain title-plain
                  #:title-html-flow title-html
                  #:title-specified? title-specified?
                  #:published pubdate
................................................................................
                  #:title-html-flow title-html
                  #:title-plain (tx-strs title-tx)
                  #:published note-date
                  #:author author
                  #:author-url author-url
                  #:disposition disposition-attr
                  #:series-page (metas-series-pagenode)

                  #:content-html content-html
                  #:listing-full-html (html$-note-listing-full pagenode
                                                               note-id
                                                               title-html
                                                               note-date
                                                               content-html
                                                               author
................................................................................
                                     (where (= entry.page ,(symbol->string pagenode))))))
  (unless (null? entry-txs)
    (void
     (apply insert! cache-conn
            (for/list ([etx (in-list entry-txs)])
              (txexpr->index-entry etx pagenode))))))

;;
;;  ~~~ Fetching articles and notes ~~~
;;

;; (Private use) Conveniece function for the WHERE `series-page` clause
(define (where-series q s)
  (define (s->p x) (format "~a/~a.html" series-folder x))
  (match s
    [(list series ...)
     (where q (in a.series-page ,(map s->p series)))] ; WHERE series-page IN (item1 ...)
    [(or (? string? series) (? symbol? series))
     (where q (= a.series-page ,(s->p series)))]      ; WHERE series-page = "item"
    [#t
     (where q (= a.series-page ,(path->string (here-output-path))))]
    [_ q]))

;; Needed to "parameterize" column names
;; see https://github.com/Bogdanp/deta/issues/14#issuecomment-573344928
(require (prefix-in ast: deta/private/ast))

;; Builds a query to fetch articles
(define (articles type #:series [s #t] #:limit [lim -1] #:order [ord 'desc])
  (define html-field (format "listing_~a_html" type))
  (~> (from cache:article #:as a)
      (select (fragment (ast:as (ast:qualified "a" html-field) "html"))
              a.published
              a.series-page)
      (where-series s)
      (limit ,lim)
      (order-by ([a.published ,ord]))
      (project-onto listing-schema)))

;; Builds a query that returns articles and notes intermingled chronologically
(define (articles+notes type #:series [s #t] #:limit [lim -1] #:order [ord 'desc])
  (define html-field (format "listing_~a_html" type))
  (~> (from (subquery
             (~> (from cache:article #:as A)
                 (select (fragment (ast:as (ast:qualified "A" html-field) "html"))
                         A.published
                         A.series-page)
                 (union
                  (~> (from cache:note #:as N)
                      (select (fragment (ast:as (ast:qualified "N" html-field) "html"))
                              N.published
                              N.series-page)))))
            #:as a)
      (where-series s)
      (limit ,lim)
      (order-by ([a.published ,ord]))
      (project-onto listing-schema)))

;; Get all the a list of the HTML all the results in a query
(define (listing-htmls list-query)
  (for/list ([l (in-entities cache-conn list-query)])
    (listing-html l)))

;; Return cached HTML of articles and/or notes, fenced within a style txexpr to prevent it being
;; escaped by ->html. See also: definition of `unfence`

;; E.g.: (<listing-full> articles+notes)
(define (<listing-full> query-func #:series [s #t] #:limit [lim -1] #:order [ord 'desc])
  `(style ,@(listing-htmls (query-func 'full #:series s #:limit lim #:order ord))))
;;                                     ^^^^^

(define (<listing-excerpt> query-func #:series [s #t] #:limit [lim -1] #:order [ord 'desc])
  `(style ,@(listing-htmls (query-func 'excerpt #:series s #:limit lim #:order ord))))
;;                                     ^^^^^^^^

(define (<listing-short> query-func #:series [s #t] #:limit [lim -1] #:order [ord 'desc])
  `(style "<ul class=\"article-list\">"
          ,@(listing-htmls (query-func 'short #:series s #:limit lim #:order ord))
          "</ul>")) ;;                 ^^^^^^

;; Remove "<style>" and "</style>" introduced by using ->html on docs containing output from
;; listing functions
(define (unfence html-str)
  (regexp-replace* #px"<[\\/]{0,1}style>" html-str ""))

;; Save the current article to the `series` table of the SQLite cache
;; Should be called from a template for series pages
(define (cache-series!)
  (define here-page (path->string (here-output-path)))
  (query-exec cache-conn
              (delete (~> (from cache:series #:as s)





|
<
>
>
>
|

<
>


<
|
>
|

<
<
|
|
<
<
<
<
<
<
<
<
<
<
<
<

<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<


<







 







>







 







>







 







<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







1
2
3
4
5
6

7
8
9
10
11

12
13
14

15
16
17
18


19
20












21






































































22
23

24
25
26
27
28
29
30
..
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
...
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
...
228
229
230
231
232
233
234













































































235
236
237
238
239
240
241
#lang racket/base

; SPDX-License-Identifier: BlueOak-1.0.0
; This file is licensed under the Blue Oak Model License 1.0.0.

(require deta

         db/base
         db/sqlite3
         threading
         racket/match
         racket/string

         txexpr
         pollen/template
         (except-in pollen/core select) ; avoid conflict with deta

)

(require "dust.rkt" "cache.rkt" "snippets-html.rkt")



(provide parse-and-cache-article!
         cache-series!)



















































































;; Save an article and its notes (if any) to the database, and return the
;; rendered HTML of the complete article.

(define (parse-and-cache-article! pagenode doc)
  (define-values (doc-no-title maybe-title)
    (splitf-txexpr doc (make-tag-predicate 'title)))
  (define-values (body-txpr note-txprs)
    (splitf-txexpr doc-no-title (make-tag-predicate 'note)))
  (define-values (disposition disp-note-id)
    (notes->last-disposition-values note-txprs))
................................................................................
                                               disp-note-id
                                               (length note-txprs))]
         [footer (html$-article-close footertext)]
         [listing-short (html$-article-listing-short pagenode pubdate title-html)]
         [notes-section-html (cache-notes! pagenode title-plain note-txprs)])
    (cache-index-entries! pagenode doc) ; note original doc is used here
    (current-plain-title title-plain)
    (delete-article! pagenode)
    (insert-one! cache-conn
                 (make-cache:article
                  #:page pagenode
                  #:title-plain title-plain
                  #:title-html-flow title-html
                  #:title-specified? title-specified?
                  #:published pubdate
................................................................................
                  #:title-html-flow title-html
                  #:title-plain (tx-strs title-tx)
                  #:published note-date
                  #:author author
                  #:author-url author-url
                  #:disposition disposition-attr
                  #:series-page (metas-series-pagenode)
                  #:conceal (or (maybe-attr 'conceal attrs #f) (maybe-meta 'conceal))
                  #:content-html content-html
                  #:listing-full-html (html$-note-listing-full pagenode
                                                               note-id
                                                               title-html
                                                               note-date
                                                               content-html
                                                               author
................................................................................
                                     (where (= entry.page ,(symbol->string pagenode))))))
  (unless (null? entry-txs)
    (void
     (apply insert! cache-conn
            (for/list ([etx (in-list entry-txs)])
              (txexpr->index-entry etx pagenode))))))















































































;; Save the current article to the `series` table of the SQLite cache
;; Should be called from a template for series pages
(define (cache-series!)
  (define here-page (path->string (here-output-path)))
  (query-exec cache-conn
              (delete (~> (from cache:series #:as s)

Modified dust.rkt from [c096eb0e] to [dffdbbe2].

17
18
19
20
21
22
23

24
25
26
27
28
29
30
..
62
63
64
65
66
67
68


69
70
71
72
73
74
75

;; Provides common helper functions used throughout the project

(provide maybe-meta     ; Select from (current-metas) or default value ("") if not available
         maybe-attr     ; Return an attribute’s value or a default ("") if not available
         here-output-path
         here-id

         series-metas-noun    ; Retrieve noun-singular from current 'series meta, or ""
         series-metas-title   ; Retrieve title of series in current 'series meta, or ""
         metas-series-pagenode
         invalidate-series
         make-tag-predicate
         tx-strs
         ymd->english
................................................................................
(define (here-output-path)
  (cond [(current-metas)
         (define-values (_ rel-path-parts)
           (drop-common-prefix (explode-path (current-project-root))
                               (explode-path (string->path (select-from-metas 'here-path (current-metas))))))
         (->output-path (apply build-path rel-path-parts))]
        [else (string->path ".")]))



;; Checks current-metas for a 'series meta and returns the pagenode of that series,
;; or '|| if no series is specified.
(define (metas-series-pagenode)
  (define maybe-series (or (select-from-metas 'series (current-metas)) ""))
  (cond
    [(non-empty-string? maybe-series)







>







 







>
>







17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
..
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78

;; Provides common helper functions used throughout the project

(provide maybe-meta     ; Select from (current-metas) or default value ("") if not available
         maybe-attr     ; Return an attribute’s value or a default ("") if not available
         here-output-path
         here-id
         listing-context
         series-metas-noun    ; Retrieve noun-singular from current 'series meta, or ""
         series-metas-title   ; Retrieve title of series in current 'series meta, or ""
         metas-series-pagenode
         invalidate-series
         make-tag-predicate
         tx-strs
         ymd->english
................................................................................
(define (here-output-path)
  (cond [(current-metas)
         (define-values (_ rel-path-parts)
           (drop-common-prefix (explode-path (current-project-root))
                               (explode-path (string->path (select-from-metas 'here-path (current-metas))))))
         (->output-path (apply build-path rel-path-parts))]
        [else (string->path ".")]))

(define listing-context (make-parameter ""))

;; Checks current-metas for a 'series meta and returns the pagenode of that series,
;; or '|| if no series is specified.
(define (metas-series-pagenode)
  (define maybe-series (or (select-from-metas 'series (current-metas)) ""))
  (cond
    [(non-empty-string? maybe-series)

Modified index.html.pp from [6ce20187] to [511f6fe5].

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
..
51
52
53
54
55
56
57
58

59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
..
85
86
87
88
89
90
91

92
93
94
95
96
97
98
#lang pollen

◊; SPDX-License-Identifier: BlueOak-1.0.0
◊; This file is licensed under the Blue Oak Model License 1.0.0.

◊(require pollen/template db/base racket/list racket/match)

◊(define (fetch-series)
  (define q "SELECT noun_plural, page, title FROM series ORDER BY noun_plural DESC")
  (query-rows cache-conn q))

◊(define (series-item->txpr s)
  (match-define (list n pagenode title) s)
  `(li (a [[href ,pagenode]] (i ,title))))

◊(define (series-grouped-list)
  ;; Produces '((("noun1" "p.html" "Title") ("noun1" "q.html" "Title")) (("noun2" ...) ...))
  (define init-group
    (group-by first (map vector->list (fetch-series)) string-ci=?))
  
  (define series-list-items
    (for/list ([group (in-list init-group)])
      `(div (h2 ,(first (first group))) (ul ,@(map series-item->txpr group)))))
  `(section [[class "series-list"] [style "margin-top: 1.3rem"]] ,@series-list-items))
            

<!DOCTYPE html>
<html lang="en">
◊html$-page-head["The Local Yarn" #f]
<style>
  header#front-page {
    text-align: center;
  }
................................................................................
  @keyframes slideFromRight {
    0%   { margin-right: -200%; }
    100% { margin-right: 0%; }
  }

  #front-page-logo {
    shape-outside: url(web-extra/mark.svg);
    filter: brightness(0) saturate(100%) invert(20%) sepia(16%) saturate(903%) hue-rotate(153deg) brightness(93%) contrast(90%);

    shape-margin: 1rem;
    float: left;
    margin-left: -20vw;
    margin-right: 1rem;
    z-index: -100;
  }

  main {
    background: transparent;
    font-size: 1.3em;
    line-height: 1.2em;
  }
</style>
</head>

<body style="overflow-x:hidden; hyphens: auto">
 <header id="front-page"><div>
................................................................................
(almost) is ◊link[1]{arranged in time order, newest first}. There are also a few arranged into named
collections:

◊url[1]{/blog-pg1.html}

}) 
◊; stop for now: (crystalize-index-entries! '|index.html| front-page-body)


<main> 
  ◊(->html front-page-body #:splice? #t)
  ◊(->html (series-grouped-list))
</main> 
</body>
</html>





|
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







 







|
>









<







 







>



|



1
2
3
4
5
6
7



















8
9
10
11
12
13
14
..
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49

50
51
52
53
54
55
56
..
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
#lang pollen

◊; SPDX-License-Identifier: BlueOak-1.0.0
◊; This file is licensed under the Blue Oak Model License 1.0.0.

◊(require pollen/template racket/file)
            



















<!DOCTYPE html>
<html lang="en">
◊html$-page-head["The Local Yarn" #f]
<style>
  header#front-page {
    text-align: center;
  }
................................................................................
  @keyframes slideFromRight {
    0%   { margin-right: -200%; }
    100% { margin-right: 0%; }
  }

  #front-page-logo {
    shape-outside: url(web-extra/mark.svg);
    /* filter: brightness(0) saturate(100%) invert(20%) sepia(16%) saturate(903%) hue-rotate(153deg)
     * brightness(93%) contrast(90%); */
    shape-margin: 1rem;
    float: left;
    margin-left: -20vw;
    margin-right: 1rem;
    z-index: -100;
  }

  main {
    background: transparent;

    line-height: 1.2em;
  }
</style>
</head>

<body style="overflow-x:hidden; hyphens: auto">
 <header id="front-page"><div>
................................................................................
(almost) is ◊link[1]{arranged in time order, newest first}. There are also a few arranged into named
collections:

◊url[1]{/blog-pg1.html}

}) 
◊; stop for now: (crystalize-index-entries! '|index.html| front-page-body)
◊(display-to-file (html$-page-footer) "scribbled/site-footer.html" #:exists 'replace)

<main> 
  ◊(->html front-page-body #:splice? #t)
  ◊(html$-series-list)
</main> 
</body>
</html>

Modified keyword-index.rkt from [50258609] to [d37c4752].

10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
         racket/list
         racket/file
         racket/string
         db/base
         net/uri-codec
         pollen/template)

(require "crystalize.rkt"
         "dust.rkt"
         "snippets-html.rkt")

(provide main)

;; Terminology (because these things get confusing fast)
;;







|







10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
         racket/list
         racket/file
         racket/string
         db/base
         net/uri-codec
         pollen/template)

(require "cache.rkt"
         "dust.rkt"
         "snippets-html.rkt")

(provide main)

;; Terminology (because these things get confusing fast)
;;

Modified makefile from [f9d9c092] to [9872e7c1].

3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
..
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53

54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
..
90
91
92
93
94
95
96

97
98
99
100
101
102
103
...
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125

SHELL = /bin/bash

# ~~~ Variables used by rules ~~~
#

core-files := pollen.rkt dust.rkt
html-deps  := snippets-html.rkt tags-html.rkt crystalize.rkt

article-sources := $(wildcard articles/*.poly.pm)
articles-html   := $(patsubst %.poly.pm, %.html, $(article-sources))
articles-pdf    := $(patsubst %.poly.pm, %.pdf,  $(article-sources))

series-sources  := $(wildcard series/*.poly.pm)
series-html     := $(patsubst %.poly.pm, %.html, $(series-sources))
................................................................................
# the Pollen cache watchlist (see pollen.rkt)
vitreous.sqlite: $(core-files) $(html-deps) template.html.p
	raco pollen setup -p articles/
	raco pollen render -p -t html articles/*.poly.pm
	raco pollen setup -p series/
	raco pollen render -p -t html series/*.poly.pm
	rm -f template.html series/template.html
	# tidy -quiet -modify -indent --wrap 100 --wrap-attributes no --tidy-mark no articles/*.html || true
	# tidy -quiet -modify -indent --wrap 100 --wrap-attributes no --tidy-mark no series/*.html || true

# If the rule for vitreous.sqlite was triggered, all the article HTML files will already have been
# re-rendered.  (That rule comes before this one in the list of dependencies for "all") But if not,
# any individual files that have been edited will get re-rendered.
$(articles-html): %.html: %.poly.pm 
	raco pollen render $@
	# tidy -quiet -modify -indent --wrap 100 --wrap-attributes no --tidy-mark no $@ || true

# Note that if any article is part of a series, it will touch its series .poly.pm file during its
# render, triggering this rule for that series.
$(series-html): %.html: %.poly.pm
	raco pollen render $@
	# tidy -quiet -modify -indent --wrap 100 --wrap-attributes no --tidy-mark no $@ || true

index.html: $(core-files) $(html-deps) $(series-html) index.html.pp
	raco pollen render index.html


# This target will also rebuild pg2, pg3, etc. as needed
blog-pg1.html: $(core-files) $(html-deps) $(articles-html) blog.rkt
	rm -f blog*.html
	racket -tm blog.rkt
	# tidy -quiet -modify -indent --wrap 100 --wrap-attributes no --tidy-mark no blog*.html || true

keyword-index.html: $(core-files) $(html-deps) $(articles-html) keyword-index.rkt
	racket -tm keyword-index.rkt
	# tidy -quiet -modify -indent --wrap 100 --wrap-attributes no --tidy-mark no $@ || true

web-extra/martin.css: web-extra/martin.css.pp
	raco pollen render $@

feed.xml: vitreous.sqlite rss-feed.rkt
	racket -tm rss-feed.rkt

................................................................................
		--exclude=.fslckout \
		--exclude='*.ltx' \
		--exclude='*.swp' \
		--exclude=.DS_Store \
	    --exclude='template*.*' \
		--exclude=makefile 
	rm -rf ~/Desktop/publish


scribble: ## Rebuild code documentation and update Fossil repo
	scribble --htmls +m --redirect https://docs.racket-lang.org/local-redirect/ code-docs/main.scrbl
	fossil uv rm scribbled/*
	rm -rf scribbled/*
	mv main/* scribbled/
	cp code-docs/scribble-iframe.html scribbled/scribble.html
................................................................................
# Self-documenting makefile (http://marmelab.com/blog/2016/02/29/auto-documented-makefile.html)
help: ## Displays this help screen
	@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-15s\033[0m %s\n", $$1, $$2}'

article: ## Start a new article from a template
	racket -tm util/newpost.rkt

.PHONY: all scribble help zap article publish check-env

.DEFAULT_GOAL := help

check-env:
ifndef LOCALYARN_SRV
	$(error LOCALYARN_SRV env variable not set, should be a destination valid for rsync)
endif
ifndef WEB_SRV_PORT
	$(error WEB_SRV_PORT env variable not set, should be SSH port number for web server)
endif







|







 







|
|


|



|





|



>





|



|







 







>







 







|










3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
..
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
..
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
...
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127

SHELL = /bin/bash

# ~~~ Variables used by rules ~~~
#

core-files := pollen.rkt dust.rkt
html-deps  := snippets-html.rkt tags-html.rkt crystalize.rkt cache.rkt

article-sources := $(wildcard articles/*.poly.pm)
articles-html   := $(patsubst %.poly.pm, %.html, $(article-sources))
articles-pdf    := $(patsubst %.poly.pm, %.pdf,  $(article-sources))

series-sources  := $(wildcard series/*.poly.pm)
series-html     := $(patsubst %.poly.pm, %.html, $(series-sources))
................................................................................
# the Pollen cache watchlist (see pollen.rkt)
vitreous.sqlite: $(core-files) $(html-deps) template.html.p
	raco pollen setup -p articles/
	raco pollen render -p -t html articles/*.poly.pm
	raco pollen setup -p series/
	raco pollen render -p -t html series/*.poly.pm
	rm -f template.html series/template.html
	tidy -quiet -modify -indent --wrap 100 --wrap-attributes no --tidy-mark no articles/*.html || true
	tidy -quiet -modify -indent --wrap 100 --wrap-attributes no --tidy-mark no series/*.html || true

# If the rule for vitreous.sqlite was triggered, all the article HTML files will already have been
# re-rendered.  (That rule comes before this one in the list of dependencies for "web") But if not,
# any individual files that have been edited will get re-rendered.
$(articles-html): %.html: %.poly.pm 
	raco pollen render $@
	tidy -quiet -modify -indent --wrap 100 --wrap-attributes no --tidy-mark no $@ || true

# Note that if any article is part of a series, it will touch its series .poly.pm file during its
# render, triggering this rule for that series.
$(series-html): %.html: %.poly.pm
	raco pollen render $@
	tidy -quiet -modify -indent --wrap 100 --wrap-attributes no --tidy-mark no $@ || true

index.html: $(core-files) $(html-deps) $(series-html) index.html.pp
	raco pollen render index.html
	fossil uv add scribbled/site-footer.html

# This target will also rebuild pg2, pg3, etc. as needed
blog-pg1.html: $(core-files) $(html-deps) $(articles-html) blog.rkt
	rm -f blog*.html
	racket -tm blog.rkt
	tidy -quiet -modify -indent --wrap 100 --wrap-attributes no --tidy-mark no blog*.html || true

keyword-index.html: $(core-files) $(html-deps) $(articles-html) keyword-index.rkt
	racket -tm keyword-index.rkt
	tidy -quiet -modify -indent --wrap 100 --wrap-attributes no --tidy-mark no $@ || true

web-extra/martin.css: web-extra/martin.css.pp
	raco pollen render $@

feed.xml: vitreous.sqlite rss-feed.rkt
	racket -tm rss-feed.rkt

................................................................................
		--exclude=.fslckout \
		--exclude='*.ltx' \
		--exclude='*.swp' \
		--exclude=.DS_Store \
	    --exclude='template*.*' \
		--exclude=makefile 
	rm -rf ~/Desktop/publish
	fossil uv sync

scribble: ## Rebuild code documentation and update Fossil repo
	scribble --htmls +m --redirect https://docs.racket-lang.org/local-redirect/ code-docs/main.scrbl
	fossil uv rm scribbled/*
	rm -rf scribbled/*
	mv main/* scribbled/
	cp code-docs/scribble-iframe.html scribbled/scribble.html
................................................................................
# Self-documenting makefile (http://marmelab.com/blog/2016/02/29/auto-documented-makefile.html)
help: ## Displays this help screen
	@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-15s\033[0m %s\n", $$1, $$2}'

article: ## Start a new article from a template
	racket -tm util/newpost.rkt

.PHONY: web scribble help zap article publish check-env

.DEFAULT_GOAL := help

check-env:
ifndef LOCALYARN_SRV
	$(error LOCALYARN_SRV env variable not set, should be a destination valid for rsync)
endif
ifndef WEB_SRV_PORT
	$(error WEB_SRV_PORT env variable not set, should be SSH port number for web server)
endif

Modified pollen.rkt from [ec6e30b5] to [5f7205ca].

9
10
11
12
13
14
15

16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

35
36
37
38
39

40
41
42
43
44
45
46
                     racket/syntax
                     syntax/parse
                     pollen/setup))

(require pollen/tag
         pollen/setup
         racket/function

         "tags-html.rkt"
         "snippets-html.rkt"
         "crystalize.rkt")

(provide (all-defined-out)
         (all-from-out "crystalize.rkt" "snippets-html.rkt"))

(module setup racket/base
  (require syntax/modresolve
           racket/runtime-path
           pollen/setup)
  (provide (all-defined-out))
  (define poly-targets '(html))
  (define block-tags (append '(title style dt note) default-block-tags))

  (define-runtime-path tags-html.rkt     "tags-html.rkt")
  (define-runtime-path snippets-html.rkt "snippets-html.rkt")
  (define-runtime-path dust.rkt          "dust.rkt")
  (define-runtime-path crystalize.rkt    "crystalize.rkt")

  (define cache-watchlist
    (map resolve-module-path
         (list tags-html.rkt
               snippets-html.rkt
               dust.rkt

               crystalize.rkt))))

(case (current-poly-target)
  [(html) (init-cache-db!)])

;; Macro for defining tag functions that automatically branch based on the 
;; current output format and the list of poly-targets in the setup module.







>





|













>





>







9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
                     racket/syntax
                     syntax/parse
                     pollen/setup))

(require pollen/tag
         pollen/setup
         racket/function
         "cache.rkt"
         "tags-html.rkt"
         "snippets-html.rkt"
         "crystalize.rkt")

(provide (all-defined-out)
         (all-from-out "crystalize.rkt" "snippets-html.rkt" "cache.rkt"))

(module setup racket/base
  (require syntax/modresolve
           racket/runtime-path
           pollen/setup)
  (provide (all-defined-out))
  (define poly-targets '(html))
  (define block-tags (append '(title style dt note) default-block-tags))

  (define-runtime-path tags-html.rkt     "tags-html.rkt")
  (define-runtime-path snippets-html.rkt "snippets-html.rkt")
  (define-runtime-path dust.rkt          "dust.rkt")
  (define-runtime-path crystalize.rkt    "crystalize.rkt")
  (define-runtime-path cache.rkt         "cache.rkt")
  (define cache-watchlist
    (map resolve-module-path
         (list tags-html.rkt
               snippets-html.rkt
               dust.rkt
               cache.rkt
               crystalize.rkt))))

(case (current-poly-target)
  [(html) (init-cache-db!)])

;; Macro for defining tag functions that automatically branch based on the 
;; current output format and the list of poly-targets in the setup module.

Modified rss-feed.rkt from [9efceb86] to [502871b7].

8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
..
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
(require txexpr
         racket/match
         racket/file
         racket/date
         racket/string
         db/base
         "dust.rkt"
         "crystalize.rkt")

(provide main)

(define feed-author default-authorname)
(define feed-author-email "joel@jdueck.net")
(define feed-title "The Local Yarn (Beta)")
(define feed-site-url "https://thelocalyarn.com")
................................................................................
     SELECT `path`, `title`, `published`, `updated`, `author`, `entry_contents` FROM
       (SELECT `page` AS `path`,
               `title_plain` AS `title`,
               `published`,
               `updated`,
               `author`,
               `doc_html` AS `entry_contents`
        FROM `articles`
        UNION
        SELECT `page` || '#' || `html_anchor` AS `path`,
               `title_plain` AS `title`,
               `published`,
               "" AS `updated`,
               `author`,
               `content_html` as `entry_contents`
        FROM `notes`)
        ORDER BY `published` DESC LIMIT ~a
---
    )
  (query-rows cache-conn (format select feed-item-limit)))

(define (vector->rss-item vec)
  (match-define







|







 







|







|







8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
..
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
(require txexpr
         racket/match
         racket/file
         racket/date
         racket/string
         db/base
         "dust.rkt"
         "cache.rkt")

(provide main)

(define feed-author default-authorname)
(define feed-author-email "joel@jdueck.net")
(define feed-title "The Local Yarn (Beta)")
(define feed-site-url "https://thelocalyarn.com")
................................................................................
     SELECT `path`, `title`, `published`, `updated`, `author`, `entry_contents` FROM
       (SELECT `page` AS `path`,
               `title_plain` AS `title`,
               `published`,
               `updated`,
               `author`,
               `doc_html` AS `entry_contents`
        FROM `articles` WHERE (NOT (`conceal` LIKE "%all%")) AND (NOT (`conceal` LIKE "%feed%"))
        UNION
        SELECT `page` || '#' || `html_anchor` AS `path`,
               `title_plain` AS `title`,
               `published`,
               "" AS `updated`,
               `author`,
               `content_html` as `entry_contents`
        FROM `notes` WHERE (NOT (`conceal` LIKE "%all%")) AND (NOT (`conceal` LIKE "%feed%")))
        ORDER BY `published` DESC LIMIT ~a
---
    )
  (query-rows cache-conn (format select feed-item-limit)))

(define (vector->rss-item vec)
  (match-define

Modified snippets-html.rkt from [9386bc7c] to [f524862a].

9
10
11
12
13
14
15

16
17
18
19

20
21
22

23
24
25
26
27
28
29
..
69
70
71
72
73
74
75
76












77
78


79
80
81
82
83
84
85
86
...
194
195
196
197
198
199
200










         pollen/decode
         pollen/private/version
         racket/string
         racket/function
         racket/list
         txexpr
         openssl/sha1

         "dust.rkt")

(provide html$-page-head
         html$-page-body-open

         html$-article-open
         html$-article-close
         html$-article-listing-short

         html$-page-body-close
         html$-note-contents
         html$-note-listing-full
         html$-note-in-article
         html$-notes-section
         html$-paginate-navlinks)

................................................................................

(define (html$-article-listing-short pagenode pubdate title)
  ◊string-append{
 <li><a href="/◊(symbol->string pagenode)">
 <div class="article-list-date caps">◊(ymd->english pubdate)</div>
 <div class="article-list-title">◊|title|</div>
 </a></li>})













(define (html$-page-body-close)
  ◊string-append{<footer>By Joel Dueck</footer>


 </main></body>})

;; Notes
;;
(define (html$-note-contents disposition-mark disposition-verb elems)
  (define disposition
    (cond [(non-empty-string? disposition-mark)
           `(abbr [[class "disposition-mark-in-note"]
................................................................................

  (define next-link
    (if (eq? pagecount pagenum)
        "<li class=\"nav-text inactive-link\">Older&rarr;</li>"
        (page-func (+ pagenum 1) "Older&thinsp;&rarr;" "nav-text")))

  (string-join `(,prev-link ,@page-group ,next-link)))

















>




>



>







 








>
>
>
>
>
>
>
>
>
>
>
>

|
>
>
|







 







>
>
>
>
>
>
>
>
>
>
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
..
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
...
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
         pollen/decode
         pollen/private/version
         racket/string
         racket/function
         racket/list
         txexpr
         openssl/sha1
         "cache.rkt"
         "dust.rkt")

(provide html$-page-head
         html$-page-body-open
         html$-series-list
         html$-article-open
         html$-article-close
         html$-article-listing-short
         html$-page-footer
         html$-page-body-close
         html$-note-contents
         html$-note-listing-full
         html$-note-in-article
         html$-notes-section
         html$-paginate-navlinks)

................................................................................

(define (html$-article-listing-short pagenode pubdate title)
  ◊string-append{
 <li><a href="/◊(symbol->string pagenode)">
 <div class="article-list-date caps">◊(ymd->english pubdate)</div>
 <div class="article-list-title">◊|title|</div>
 </a></li>})

(define (html$-page-footer)
  ◊string-append{
<footer id="main">
 <p class="title">The Local Yarn</p>
 <nav><a href="/">Home</a> •
    <a href="/blog-pg1.html">Blog</a> •
    <a href="/keyword-index.html">Keyword Index</a> •
    <a href="/code"><i><code>◊"◊"(Source&nbsp;Code)</code></i></a>
 </nav>
 ◊(html$-series-list)
 </footer>})

(define (html$-page-body-close)
  ◊string-append{
 </main>
 ◊(html$-page-footer)
 </body>})

;; Notes
;;
(define (html$-note-contents disposition-mark disposition-verb elems)
  (define disposition
    (cond [(non-empty-string? disposition-mark)
           `(abbr [[class "disposition-mark-in-note"]
................................................................................

  (define next-link
    (if (eq? pagecount pagenum)
        "<li class=\"nav-text inactive-link\">Older&rarr;</li>"
        (page-func (+ pagenum 1) "Older&thinsp;&rarr;" "nav-text")))

  (string-join `(,prev-link ,@page-group ,next-link)))

(define (series->txpr s)
  `(li (a [[href ,(symbol->string (cache:series-page s))]]
          (i ,(cache:series-title s)))))

(define (html$-series-list)
  (define series-list-items
    (for/list ([group (in-list (series-grouped-list))])
      `(div (h2 ,(cache:series-noun-plural (first group))) (ul ,@(map series->txpr group)))))
  (->html `(section [[class "column-list"] [style "margin-top: 1.3rem"]] ,@series-list-items)))

Modified util/newpost.rkt from [89e8b78f] to [d7b70e83].

31
32
33
34
35
36
37

38
39
40
41
42
43
44
45
(define (make-template-contents title)
  ◊string-append{
 #lang pollen

 ◊comment{Copyright ◊(substring date-string 0 4) by ◊|default-authorname|. All Rights Reserved.}

 ◊"◊"(define-meta published "◊date-string")

 ◊"◊"(define-meta series "seriesname")

 ◊"◊"title{◊title}

 Write here!})

(define (main)
  (display "Enter title: ")







>
|







31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
(define (make-template-contents title)
  ◊string-append{
 #lang pollen

 ◊comment{Copyright ◊(substring date-string 0 4) by ◊|default-authorname|. All Rights Reserved.}

 ◊"◊"(define-meta published "◊date-string")
 ◊"◊"(define-meta conceal   "blog,feed")  ; Edit/delete this line when ready to publish
 ◊"◊;"(define-meta series "seriesname")

 ◊"◊"title{◊title}

 Write here!})

(define (main)
  (display "Enter title: ")

Modified web-extra/font.css from [fb953054] to [81f7d56c].

18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
..
36
37
38
39
40
41
42






  font-weight:  400;
}

@font-face {
  font-family: 'Fabiol';
  src: url('LDFabiolPro-Bold.woff2') format('woff2'),
       url('LDFabiolPro-Bold.woff') format('woff');
  font-style:   bold;
  font-weight:  700;
}

@font-face {
  font-family: 'Triplicate T4c';
  src: url('Triplicate-T4c.woff') format('woff');
  font-style: normal;
................................................................................
@font-face {
  font-family: 'Triplicate T4c';
  src: url('Triplicate-T4c-italic.woff') format('woff');
  font-style: italic;
  font-weight: 400;
  }














|







 







>
>
>
>
>
>
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
..
36
37
38
39
40
41
42
43
44
45
46
47
48
  font-weight:  400;
}

@font-face {
  font-family: 'Fabiol';
  src: url('LDFabiolPro-Bold.woff2') format('woff2'),
       url('LDFabiolPro-Bold.woff') format('woff');
  font-style:   normal;
  font-weight:  700;
}

@font-face {
  font-family: 'Triplicate T4c';
  src: url('Triplicate-T4c.woff') format('woff');
  font-style: normal;
................................................................................
@font-face {
  font-family: 'Triplicate T4c';
  src: url('Triplicate-T4c-italic.woff') format('woff');
  font-style: italic;
  font-weight: 400;
  }

@font-face {
  font-family: 'Triplicate T4c';
  src: url('Triplicate-T4c-bold.woff') format('woff');
  font-style: normal;
  font-weight: bold;
  }

Modified web-extra/martin.css.pp from [62e09f38] to [6da40fde].

25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
..
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692











































693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
...
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
...
859
860
861
862
863
864
865

















/* Let us first address the matter of font size in different screen sizes. */

/* Mobile portrait screens will see a minimum 18px font. */
html { font-size: 22px; } 

/* Start increasing type size dynamically at screen widths of 768px */
@media only screen and (min-width: 768px) { 
    html { font-size: 2.8vw; } 
}

/* Top out at 23px for screens up to 800px TALL */
◊; @media only screen and (min-width: 1000px) and (max-height: 800px) {
◊;    html { font-size: 26px; } /* =  2.6% of 1000px (min-width) */
◊; }

/* Top out at 28px for screens 801px-1000px TALL */
@media only screen and (min-width: 1000px) and (max-height: 920px) { 
   html { font-size: 28px; }     /* =  2.8% of 1000px (min-width) */
}
/* For screens taller than 1000 px, top out at 32px */
@media only screen and (min-width: 1178px) and (min-height: 921px) {
    html { font-size: 33px; }    /* =  2.8% of 1178px (min-width) */
}

◊; Since line height is used in so many places...
◊(define LINEHEIGHT 1.3)
◊(define lineheight (string-append (number->string LINEHEIGHT) "rem"))
◊(define (x-lineheight multiple) 
................................................................................
 **** 1. Mobile-first layout ***
      1.1 All Pages
      1.2 Front page
      1.3 Individual post body markup
      1.4 Journal views (article listings)  
 */

@media screen {
    body {
        margin: 0;
        background: ◊color-background;

        /* Typography: `line-height` is important!
           All verticle rhythm based on this value. */
        line-height: ◊lineheight;
        font-family: ◊body-font;

        font-feature-settings: "calt" on, "liga" on, "clig" on, "dlig" on, "kern" on, "onum" on, "pnum" on;
        color: ◊color-bodytext; /* Japanese Indigo, baby */
    }

    /* This is used to hide certain punctuation and things as long as CSS is available. 
       Turn off CSS and voila: stuff appears. */
    .x {
        display: none;
    }

    .hist p {
        font-variant-alternates: historical-forms;
    }
    .nonhist {
        font-variant-alternates: normal;
    }

    main {
        background: white;
        margin: 0;
        padding: ◊x-lineheight[0.5] ◊x-lineheight[1];
    }

    main > a > header {
        text-align: center;
    }

    main > a > header h1 {
        display: none;
        /* font-size: ◊x-lineheight[1.5];
        line-height: ◊x-lineheight[2];
        margin: 0 0 ◊x-lineheight[0.5] 0;
        font-weight: normal;
        font-style: italic;
        text-transform: lowercase;
        color: ◊color-pagehead;
        transition: color 0.25s ease; */
    }

    img.logo {
        height: ◊x-lineheight[3];
        filter: none;
        transition: filter 0.5s ease;
    }

    header:hover img.logo {
        filter: invert(12%) sepia(87%) saturate(2559%) hue-rotate(348deg) brightness(125%) contrast(88%);
        transition: filter 0.5s ease;
    }

    /* I read somewhere years ago that you should specify styling for links in the
       following order: Link, Visited, Hover, Active. 
           → Remember the mnemonic: Lord Vader Has Arrived.
       
       Not sure if that's relevant advice any more but I still follow it. It can't hurt. */
       
    a:link, a:visited {         /* [L]ord [V]ader… */
        color: ◊color-link;
        text-decoration: none;
    }

    a:hover, a:active {         /* …[H]as [A]rrived */
        color: ◊color-linkhover;
        background: ◊color-linkbackground;
    }

    a.index-link:link, a.index-link:visited {
        color: ◊color-bodytext;
    }

    a.index-link:hover, a.index-link:active {
        color: #006400;
    }

    a.index-link::after {
        content: '\f89b';
        color: #809102;
        position: relative;
        top: -0.3em;
    }

    main>a {
        color: inherit;
    }
    
    span.links-footnote {
        display: inline-block; /* allows keyframe animation to work */
    }
    
    :target {
        animation: hilite 2.5s;
    }
    @keyframes hilite {
        0% {background: transparent;}
        10% {background: #feffc1;}
        100% {background: transparent;}
    }

    main > aside {
        text-align: center;
    }

    nav {
        font-family: 'Triplicate T4c', monospace;
        font-feature-settings: "onum" off;
        font-size: 0.7rem;
        margin: 0;
        margin-top: 0.5rem;
        text-align: center;
    }

    nav ul {
        list-style-type: none;
        margin: 0.2em auto;
        padding: 0;
    }

    nav li {
        display: none; /* Numbers not displayed on mobile */
        color: gray;
    }

    nav li.nav-text {
        display: inline;
        text-transform: uppercase;
        letter-spacing: 0.05rem;
        font-size: 0.6rem;
    }

    nav li.inactive-link {
        color: #545454; /* Accessibility (contrast) */
    }

    nav li.current-page {
        color: ◊color-bodytext;
        padding: 0.2rem 0.5rem;
        border-bottom: dotted ◊color-bodytext 2px;
    }

    nav li a {
        padding: 0.2rem 0.5rem;
    }

    nav li a:link, nav li a:visited {
        color: ◊color-bodytext;
    }

    nav li a:hover, nav li a:active {
        color: ◊color-linkhover;
        background: #ebebeb;
    }
    
    i > em { font-style: normal; }
    
    /* On mobile, an <ARTICLE> is just a box. Later we'll do fancy stuff if grid support is detected. */
    article {
        margin: ◊x-lineheight[2] 0 ◊x-lineheight[1] 0;
        padding-top: ◊x-lineheight[1];
    }
    article:first-of-type {
        margin-top: ◊x-lineheight[1];
    }

    /* Here's my thing these days about article titles: they shouldn't be required. When you write in a
       paper journal, do you think of a title for every entry? No! You just write the date. The date is
       the heading. Makes sense. But, this means I've had to think long and hard about how to present two
       different types of articles (those with titles AND dates, and those with just a date for the title). 

        filter: invert(12%) sepia(87%) saturate(359%) hue-rotate(348deg) brightness(105%) contrast(88%);
       For now: By default, the title-less article is assumed to be the norm. On these, we use <H1> to 
       show the date in italics. */
    article>h1 {
        font-size: ◊x-lineheight[1];
        line-height: ◊x-lineheight[1];
        margin: 0 0 ◊x-lineheight[1] 0;
        font-style: italic;
        font-weight: normal;
    }

    /* Titles non-bold, non-smallcaps by default. This can be overridden in document markup. */
    h1.entry-title {
        margin: 0 0 0 0;
        text-transform: none;
        font-style: normal;
        line-height: 1.7rem;
    }

    h1.entry-title.note-full {
        font-feature-settings: "smcp" on, "liga" on, "clig" on, "dlig" on, "kern" on, "onum" on, "pnum" on;
    }

    /* This <SPAN> class is used in titles for Notes appended to earlier articles */
    h1.entry-title .cross-reference {
        font-feature-settings: "smcp" off, "liga" on, "clig" on, "dlig" on, "kern" on, "onum" on, "pnum" on;
        font-style: italic;
        text-transform: none;
    }

    /* `a.rel-bookmark` is only used in individual article or ‘journal’ views, 
        not in listings; see the design docs.
    */
    a.rel-bookmark:link,        /* Lord  */
    a.rel-bookmark:visited {    /* Vader */
        text-decoration: none;
        background: none;
        color: ◊color-bodytext;
    }

    a.rel-bookmark:hover,       /* Has     */
    a.rel-bookmark:active {     /* Arrived */
        text-decoration: none;
        background: ◊color-linkbackground;
        color: #006400;
    }

    /* Here's where we add the minty fresh maple leaf glyph. */
    a.rel-bookmark::after {
        content: '\f894'; 
        margin-left: 4px;
        font-style: normal;
        color: #809102;
    }
    a.rel-bookmark.note-permlink::after {
        content: '\f897';
        margin-left: 4px;
    }
    div.note a.rel-bookmark.note-permlink::after {
        content: '';
    }
    div.note a.rel-bookmark.note-permlink::before {
       font-family: ◊body-font;
       font-size: 1rem;
       content: '\00b6';
       margin-left: -0.9rem;
       float: left;
       margin-top: -2px;
    }

    a.rel-bookmark:hover::after {
        color: #aaba16;
    }

    a.cross-reference:link,
    a.cross-reference:visited {
        color: ◊color-bodytext;
    }

    a.cross-reference::before {
        content: '☞\00a0';      /* Non-breaking space */
        font-style: normal;
        color: ◊color-xrefmark;
    }

    /* Footnote links */
    sup a {
        font-weight: bold;
        margin-left: 3px;
    }

    p.time {
        margin: 0 0 ◊x-lineheight[1] 0;
    }
    footer.article-info {
        margin: ◊x-lineheight[1] 0;
        text-align: center;
        font-feature-settings: "smcp" on, "liga" on, "clig" on, "dlig" on, "kern" on, "onum" on, "pnum" on;
        color: #555555; /* Accessibility (contrast) */
    }

    footer.article-info::before {
        content: "☞\00a0";
    }
    
    /* Within article info, don’t display series info when on a series page (redundant) */
    body.series-page .series-part {
        display: none;
    }

    p.time::before {
        content: none;
    }

    p.time {
        font-size: ◊x-lineheight[0.75];
        line-height: ◊x-lineheight[1];
        font-feature-settings: "scmp" off, "liga" on, "clig" on, "dlig" on, "kern" on, "onum" on, "pnum" on;
        text-transform: none;
        font-style: italic;
    }
    
    article>:last-child::after {
        content: '\f88d';  /* Round glyph */
        color: ◊color-bodytext;
        font-size: ◊x-lineheight[1];
        line-height: ◊x-lineheight[1];
        text-align: center;
        display: block;
        margin: ◊x-lineheight[1] 0;
    }

    p {
        margin: 0;
        text-indent: 0;
    }

    p + p {
        text-indent: 2em;
    }

    section.entry-content blockquote {
        font-size: ◊x-lineheight[0.7];
        line-height: ◊derive-lineheight[7 #:per-lines 6];
        margin: ◊x-lineheight[1.0] 2em;
    }

    section.entry-content blockquote:first-child {
        margin-top: 0;
    }

    section.entry-content blockquote footer::before {
        content: '—'; /* Em-dash */
    }

    section.entry-content blockquote footer {
        margin-top: ◊x-lineheight[1];
        text-align: right;
        width: calc(100% + 2em);
    }

    section.entry-content h2 {
        font-size: 1.2rem;
        font-style: italic;
        margin: ◊x-lineheight[1] 0;
        font-weight: normal;
    }

    .caps, span.smallcaps, span.newthought {
        font-feature-settings: "smcp" on, "liga" on, "clig" on, "dlig" on, "kern" on, "onum" on, "pnum" on;
        font-style: normal;
    }

    .caps {
        text-transform: lowercase;
        font-size: 1.1em;
    }

    p.pause-before {
        margin-top: ◊x-lineheight[2];
        text-indent: 0;
    }

    section.entry-content ul,
    section.entry-content ol {
        margin: ◊x-lineheight[0.5] 0 ◊x-lineheight[0.5] 0;
        padding: 0;
    }

    section.entry-content li {
        margin: 0 0 ◊x-lineheight[0.5] 0;
        padding: 0;
        text-indent: 0; 
    }

    code {
        font-size: 0.75rem;
        font-family: ◊mono-font;
    }

    pre {
        line-height: ◊derive-lineheight[7 #:per-lines 6];
        max-width: 100%;
        overflow-x: auto;
        tab-size: 4;
    }

    pre code {
        border: 0;
        background: none;
        font-style: italic;
        font-size: 0.75rem;
    }

    pre.code {
        border: dotted #aaa 2px;
        padding-left: 0.2em;
    }

    pre.verse {
        font-family: ◊body-font;
        font-size: 1rem;
        line-height: ◊x-lineheight[1];
        width: auto;
        margin: ◊x-lineheight[1] auto;
        display: table;
        white-space: pre-wrap;
        /* Whitespace is preserved by the browser.
           Text will wrap when necessary, and on line breaks */
    }

    p.verse-heading {
        font-feature-settings: "smcp" on, "liga" on, "clig" on, "dlig" on, "kern" on, "onum" on, "pnum" on;
        text-align: center;
        font-size: 1.3rem;
    }
        
    section.entry-content figure {
        margin: ◊x-lineheight[1] 0;
        padding: 0;
    }

    figure>a {
        margin: 0;
        padding: 0;
        font-family: arial, sans-serif;
    }
    figure img {
        max-width: 100%;
        margin: 0 auto;
    }

    figcaption {
        font-size: 0.8rem;
        line-height: ◊derive-lineheight[4 #:per-lines 3];
        margin-bottom: 0.3rem;
        text-align: left;
    }

    dl {
        margin: ◊x-lineheight[1] 0;
    }

    dl.dialogue dt {
        font-feature-settings: "smcp" on, "liga" on, "clig" on, "dlig" on, "kern" on, "onum" on, "pnum" on;
        font-style: normal;
        text-transform: lowercase;
    }

    section.entry-content p.signoff {
        margin-top: ◊x-lineheight[1];
        font-size: 1.2rem;
    }

    section.footnotes {
        margin-top: ◊x-lineheight[1];
        font-size: 0.9rem;
        line-height: ◊derive-lineheight[7 #:per-lines 6];
    }

    section.footnotes hr {
        border: 0;
        background: ◊color-bodytext;
        height: 1px;
        margin: 0;
        width: 75%;
        text-align: left;
    }

    section.footnotes ol {
        margin: ◊x-lineheight[0.5] 0 0 0;
    }

    /* ******* “Further Notes” added to articles ********
     */

    div.further-notes {
        margin-top: ◊x-lineheight[3];
    }

    div.further-notes>h2 {
        font-style: normal;
        font-feature-settings: "smcp" on;
        border-top: solid 2px ◊color-bodytext;
        text-transform: lowercase;
    }

    div.note h3 {
        margin-top: 0;
        font-size: 1rem;
        font-weight: normal;
        font-family: ◊mono-font;
        font-size: 0.7rem;
        background: #f3f3f3;
        border-top: solid #b6b6b6 1px;
        padding-left: 0.2rem;
        margin-bottom: 0.3rem;
    }

    div.note-meta {
        margin-top: ◊x-lineheight[1];
        color: #888;
        font-size: 1.2rem;
    }

    .by-proprietor .note-meta {
        display: none;
    }

    div.note + div.note {
        margin-top: ◊x-lineheight[2];
    }

    .disposition-mark {
        color: ◊color-xrefmark;
        position: relative;
        top: -0.5em;
        font-size: 0.83em;
    }

    .disposition-mark-in-note {
        background: ◊color-xrefmark;
        color: white;
        border-radius: 0.08em;
        padding: 0 0.1em;
        margin: 0 0.4em 0 0;
        text-decoration: none !important;
    }

    /* ******* (Mobile first) Journal View styling *******
     */

    section.content-block {
    }

    ul.article-list {
        margin-top: ◊x-lineheight[1];
        padding: 0;
    }

    ul.article-list li {
        list-style-type: none;
        display: block;
        margin: 0;
        padding: 0; 
        margin-bottom: ◊x-lineheight[1];
    }

    ul.article-list li>a {
        display: block;
    }

    div.article-list-date {
        color: #999;
    }

    div.article-list-title {
        font-size: 1.2rem;
    }

    /* ******* (Mobile first) Columnar series list styling *******
     */

    .series-list {
        column-width: 9rem;
    }

    .series-list div {
        -webkit-column-break-inside: avoid; /* Chrome, Safari, Opera */
        page-break-inside: avoid; /* Firefox */
        break-inside: avoid; /* IE 10+ */
    }

    .series-list h2 {
        font-feature-settings: "smcp" on;
        text-transform: lowercase;
        font-weight: normal;
        font-size: 1em;
        margin: 0;
    }

    .series-list ul {
        margin-top: 0;
        list-style-type: none;
        padding: 0;
    }

    /* ******* (Mobile first) Keyword Index styling *******
     */

    #keywordindex {
        column-width: 7rem;
        margin-top: ◊x-lineheight[2];
    }
    
    #keywordindex section {
        -webkit-column-break-inside: avoid; /* Chrome, Safari, Opera */
        page-break-inside: avoid; /* Firefox */
        break-inside: avoid; /* IE 10+ */
    }

    #keywordindex h2 {
        margin: 0;
    }

    #keywordindex ul {
        margin-top: 0;
        list-style-type: none;
        padding: 0;
    }

    #keywordindex ul ul {
        margin-left: 0.5em;
        font-size: smaller;
    }

    /* End of mobile-first typography and layout */
}













































/* Here’s where we start getting funky for any viewport wider than mobile portrait.
   An iPhone 6 is 667px wide in landscape mode, so that’s our breakpoint. */

@media only screen and (min-width: 667px) {
    main {
        margin: 0 auto;
        padding-left: 1rem;
        padding-right: 1rem;
        width: 30rem;
        max-width: 90%;
    }

    nav li { display: inline; } /* Display page numbers on larger screens */

    @supports (grid-area: auto) {
        main { 
            width: 42rem;
            background: none;
        }

................................................................................
            border-radius: 2px;
        }
        
        article>h1 {
            grid-area: margin;
            font-size: 1.1rem;
            text-align: right;
            /* padding-right: 1rem; */
            color: ◊color-bodytext;
        }

        article>h1.entry-title {
            grid-area: title;
            font-size: ◊x-lineheight[1];
            text-align: left;
            padding-right: 0;
................................................................................
        }

        div.content-block-main > :first-child {
            margin-top: 0 !important;
        }
    }
}























|









|



|







 







|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>





|








|







 







<
<







 







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
..
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
...
784
785
786
787
788
789
790


791
792
793
794
795
796
797
...
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922

/* Let us first address the matter of font size in different screen sizes. */

/* Mobile portrait screens will see a minimum 18px font. */
html { font-size: 22px; } 

/* Start increasing type size dynamically at screen widths of 768px */
@media (min-width: 768px) { 
    html { font-size: 2.8vw; } 
}

/* Top out at 23px for screens up to 800px TALL */
◊; @media only screen and (min-width: 1000px) and (max-height: 800px) {
◊;    html { font-size: 26px; } /* =  2.6% of 1000px (min-width) */
◊; }

/* Top out at 28px for screens 801px-1000px TALL */
@media (min-width: 1000px) and (max-height: 920px) { 
   html { font-size: 28px; }     /* =  2.8% of 1000px (min-width) */
}
/* For screens taller than 1000 px, top out at 32px */
@media (min-width: 1178px) and (min-height: 921px) {
    html { font-size: 33px; }    /* =  2.8% of 1178px (min-width) */
}

◊; Since line height is used in so many places...
◊(define LINEHEIGHT 1.3)
◊(define lineheight (string-append (number->string LINEHEIGHT) "rem"))
◊(define (x-lineheight multiple) 
................................................................................
 **** 1. Mobile-first layout ***
      1.1 All Pages
      1.2 Front page
      1.3 Individual post body markup
      1.4 Journal views (article listings)  
 */

body {
    margin: 0;
    background: ◊color-background;

    /* Typography: `line-height` is important!
       All verticle rhythm based on this value. */
    line-height: ◊lineheight;
    font-family: ◊body-font;

    font-feature-settings: "calt" on, "liga" on, "clig" on, "dlig" on, "kern" on, "onum" on, "pnum" on;
    color: ◊color-bodytext; /* Japanese Indigo, baby */
}

/* This is used to hide certain punctuation and things as long as CSS is available. 
   Turn off CSS and voila: stuff appears. */
.x {
    display: none;
}

.hist p {
    font-variant-alternates: historical-forms;
}
.nonhist {
    font-variant-alternates: normal;
}

main {
    background: white;
    margin: 0;
    padding: ◊x-lineheight[0.5] ◊x-lineheight[1];
}

main > a > header {
    text-align: center;
}

main > a > header h1 {
    display: none;
    /* font-size: ◊x-lineheight[1.5];
    line-height: ◊x-lineheight[2];
    margin: 0 0 ◊x-lineheight[0.5] 0;
    font-weight: normal;
    font-style: italic;
    text-transform: lowercase;
    color: ◊color-pagehead;
    transition: color 0.25s ease; */
}

img.logo {
    height: ◊x-lineheight[3];
    filter: none;
    transition: filter 0.5s ease;
}

header:hover img.logo {
    filter: invert(12%) sepia(87%) saturate(2559%) hue-rotate(348deg) brightness(125%) contrast(88%);
    transition: filter 0.5s ease;
}

/* I read somewhere years ago that you should specify styling for links in the
   following order: Link, Visited, Hover, Active. 
       → Remember the mnemonic: Lord Vader Has Arrived.
   
   Not sure if that's relevant advice any more but I still follow it. It can't hurt. */
   
a:link, a:visited {         /* [L]ord [V]ader… */
    color: ◊color-link;
    text-decoration: none;
}

a:hover, a:active {         /* …[H]as [A]rrived */
    color: ◊color-linkhover;
    background: ◊color-linkbackground;
}

a.index-link:link, a.index-link:visited {
    color: ◊color-bodytext;
}

a.index-link:hover, a.index-link:active {
    color: #006400;
}

a.index-link::after {
    content: '\f89b';
    color: #809102;
    position: relative;
    top: -0.3em;
}

main>a {
    color: inherit;
}

span.links-footnote {
    display: inline-block; /* allows keyframe animation to work */
}

:target {
    animation: hilite 2.5s;
}
@keyframes hilite {
    0% {background: transparent;}
    10% {background: #feffc1;}
    100% {background: transparent;}
}

main > aside {
    text-align: center;
}

main nav {
    font-family: 'Triplicate T4c', monospace;
    font-feature-settings: "onum" off;
    font-size: 0.7rem;
    margin: 0;
    margin-top: 0.5rem;
    text-align: center;
}

main nav ul {
    list-style-type: none;
    margin: 0.2em auto;
    padding: 0;
}

main nav li {
    display: none; /* Numbers not displayed on mobile */
    color: gray;
}

main nav li.nav-text {
    display: inline;
    text-transform: uppercase;
    letter-spacing: 0.05rem;
    font-size: 0.6rem;
}

main nav li.inactive-link {
    color: #545454; /* Accessibility (contrast) */
}

main nav li.current-page {
    color: ◊color-bodytext;
    padding: 0.2rem 0.5rem;
    border-bottom: dotted ◊color-bodytext 2px;
}

main nav li a {
    padding: 0.2rem 0.5rem;
}

main nav li a:link, nav li a:visited {
    color: ◊color-bodytext;
}

main nav li a:hover, nav li a:active {
    color: ◊color-linkhover;
    background: #ebebeb;
}

i > em { font-style: normal; }

/* On mobile, an <ARTICLE> is just a box. Later we'll do fancy stuff if grid support is detected. */
article {
    margin: ◊x-lineheight[2] 0 ◊x-lineheight[1] 0;
    padding-top: ◊x-lineheight[1];
}
article:first-of-type {
    margin-top: ◊x-lineheight[1];
}

/* Here's my thing these days about article titles: they shouldn't be required. When you write in a
   paper journal, do you think of a title for every entry? No! You just write the date. The date is
   the heading. Makes sense. But, this means I've had to think long and hard about how to present two
   different types of articles (those with titles AND dates, and those with just a date for the title). 

    filter: invert(12%) sepia(87%) saturate(359%) hue-rotate(348deg) brightness(105%) contrast(88%);
   For now: By default, the title-less article is assumed to be the norm. On these, we use <H1> to 
   show the date in italics. */
article>h1 {
    font-size: ◊x-lineheight[1];
    line-height: ◊x-lineheight[1];
    margin: 0 0 ◊x-lineheight[1] 0;
    font-style: italic;
    font-weight: normal;
}

/* Titles non-bold, non-smallcaps by default. This can be overridden in document markup. */
h1.entry-title {
    margin: 0 0 0 0;
    text-transform: none;
    font-style: normal;
    line-height: 1.7rem;
}

h1.entry-title.note-full {
    font-feature-settings: "smcp" on, "liga" on, "clig" on, "dlig" on, "kern" on, "onum" on, "pnum" on;
}

/* This <SPAN> class is used in titles for Notes appended to earlier articles */
h1.entry-title .cross-reference {
    font-feature-settings: "smcp" off, "liga" on, "clig" on, "dlig" on, "kern" on, "onum" on, "pnum" on;
    font-style: italic;
    text-transform: none;
}

/* `a.rel-bookmark` is only used in individual article or ‘journal’ views, 
    not in listings; see the design docs.
*/
a.rel-bookmark:link,        /* Lord  */
a.rel-bookmark:visited {    /* Vader */
    text-decoration: none;
    background: none;
    color: ◊color-bodytext;
}

a.rel-bookmark:hover,       /* Has     */
a.rel-bookmark:active {     /* Arrived */
    text-decoration: none;
    background: ◊color-linkbackground;
    color: #006400;
}

/* Here's where we add the minty fresh maple leaf glyph. */
a.rel-bookmark::after {
    content: '\f894'; 
    margin-left: 4px;
    font-style: normal;
    color: #809102;
}
a.rel-bookmark.note-permlink::after {
    content: '\f897';
    margin-left: 4px;
}
div.note a.rel-bookmark.note-permlink::after {
    content: '';
}
div.note a.rel-bookmark.note-permlink::before {
   font-family: ◊body-font;
   font-size: 1rem;
   content: '\00b6';
   margin-left: -0.9rem;
   float: left;
   margin-top: -2px;
}

a.rel-bookmark:hover::after {
    color: #aaba16;
}

a.cross-reference:link,
a.cross-reference:visited {
    color: ◊color-bodytext;
}

a.cross-reference::before {
    content: '☞\00a0';      /* Non-breaking space */
    font-style: normal;
    color: ◊color-xrefmark;
}

/* Footnote links */
sup a {
    font-weight: bold;
    margin-left: 3px;
}

p.time {
    margin: 0 0 ◊x-lineheight[1] 0;
}
footer.article-info {
    margin: ◊x-lineheight[1] 0;
    text-align: center;
    font-feature-settings: "smcp" on, "liga" on, "clig" on, "dlig" on, "kern" on, "onum" on, "pnum" on;
    color: #555555; /* Accessibility (contrast) */
}

footer.article-info::before {
    content: "☞\00a0";
}

/* Within article info, don’t display series info when on a series page (redundant) */
body.series-page .series-part {
    display: none;
}

p.time::before {
    content: none;
}

p.time {
    font-size: ◊x-lineheight[0.75];
    line-height: ◊x-lineheight[1];
    font-feature-settings: "scmp" off, "liga" on, "clig" on, "dlig" on, "kern" on, "onum" on, "pnum" on;
    text-transform: none;
    font-style: italic;
}

article>:last-child::after {
    content: '\f88d';  /* Round glyph */
    color: ◊color-bodytext;
    font-size: ◊x-lineheight[1];
    line-height: ◊x-lineheight[1];
    text-align: center;
    display: block;
    margin: ◊x-lineheight[1] 0;
}

p {
    margin: 0;
    text-indent: 0;
}

p + p {
    text-indent: 2em;
}

section.entry-content blockquote {
    font-size: ◊x-lineheight[0.7];
    line-height: ◊derive-lineheight[7 #:per-lines 6];
    margin: ◊x-lineheight[1.0] 2em;
}

section.entry-content blockquote:first-child {
    margin-top: 0;
}

section.entry-content blockquote footer::before {
    content: '—'; /* Em-dash */
}

section.entry-content blockquote footer {
    margin-top: ◊x-lineheight[1];
    text-align: right;
    width: calc(100% + 2em);
}

section.entry-content h2 {
    font-size: 1.2rem;
    font-style: italic;
    margin: ◊x-lineheight[1] 0;
    font-weight: normal;
}

.caps, span.smallcaps, span.newthought {
    font-feature-settings: "smcp" on, "liga" on, "clig" on, "dlig" on, "kern" on, "onum" on, "pnum" on;
    font-style: normal;
}

.caps {
    text-transform: lowercase;
    font-size: 1.1em;
}

p.pause-before {
    margin-top: ◊x-lineheight[2];
    text-indent: 0;
}

section.entry-content ul,
section.entry-content ol {
    margin: ◊x-lineheight[0.5] 0 ◊x-lineheight[0.5] 0;
    padding: 0;
}

section.entry-content li {
    margin: 0 0 ◊x-lineheight[0.5] 0;
    padding: 0;
    text-indent: 0; 
}

code {
    font-size: 0.75rem;
    font-family: ◊mono-font;
}

pre {
    line-height: ◊derive-lineheight[7 #:per-lines 6];
    max-width: 100%;
    overflow-x: auto;
    tab-size: 4;
}

pre code {
    border: 0;
    background: none;
    font-style: italic;
    font-size: 0.75rem;
}

pre.code {
    border: dotted #aaa 2px;
    padding-left: 0.2em;
}

pre.verse {
    font-family: ◊body-font;
    font-size: 1rem;
    line-height: ◊x-lineheight[1];
    width: auto;
    margin: ◊x-lineheight[1] auto;
    display: table;
    white-space: pre-wrap;
    /* Whitespace is preserved by the browser.
       Text will wrap when necessary, and on line breaks */
}

p.verse-heading {
    font-feature-settings: "smcp" on, "liga" on, "clig" on, "dlig" on, "kern" on, "onum" on, "pnum" on;
    text-align: center;
    font-size: 1.3rem;
}
    
section.entry-content figure {
    margin: ◊x-lineheight[1] 0;
    padding: 0;
}

figure>a {
    margin: 0;
    padding: 0;
    font-family: arial, sans-serif;
}
figure img {
    max-width: 100%;
    margin: 0 auto;
}

figcaption {
    font-size: 0.8rem;
    line-height: ◊derive-lineheight[4 #:per-lines 3];
    margin-bottom: 0.3rem;
    text-align: left;
}

dl {
    margin: ◊x-lineheight[1] 0;
}

dl.dialogue dt {
    font-feature-settings: "smcp" on, "liga" on, "clig" on, "dlig" on, "kern" on, "onum" on, "pnum" on;
    font-style: normal;
    text-transform: lowercase;
}

section.entry-content p.signoff {
    margin-top: ◊x-lineheight[1];
    font-size: 1.2rem;
}

section.footnotes {
    margin-top: ◊x-lineheight[1];
    font-size: 0.9rem;
    line-height: ◊derive-lineheight[7 #:per-lines 6];
}

section.footnotes hr {
    border: 0;
    background: ◊color-bodytext;
    height: 1px;
    margin: 0;
    width: 75%;
    text-align: left;
}

section.footnotes ol {
    margin: ◊x-lineheight[0.5] 0 0 0;
}

/* ******* “Further Notes” added to articles ********
 */

div.further-notes {
    margin-top: ◊x-lineheight[3];
}

div.further-notes>h2 {
    font-style: normal;
    font-feature-settings: "smcp" on;
    border-top: solid 2px ◊color-bodytext;
    text-transform: lowercase;
}

div.note h3 {
    margin-top: 0;
    font-size: 1rem;
    font-weight: normal;
    font-family: ◊mono-font;
    font-size: 0.7rem;
    background: #f3f3f3;
    border-top: solid #b6b6b6 1px;
    padding-left: 0.2rem;
    margin-bottom: 0.3rem;
}

div.note-meta {
    margin-top: ◊x-lineheight[1];
    color: #888;
    font-size: 1.2rem;
}

.by-proprietor .note-meta {
    display: none;
}

div.note + div.note {
    margin-top: ◊x-lineheight[2];
}

.disposition-mark {
    color: ◊color-xrefmark;
    position: relative;
    top: -0.5em;
    font-size: 0.83em;
}

.disposition-mark-in-note {
    background: ◊color-xrefmark;
    color: white;
    border-radius: 0.08em;
    padding: 0 0.1em;
    margin: 0 0.4em 0 0;
    text-decoration: none !important;
}

/* ******* (Mobile first) Journal View styling *******
 */

section.content-block {
}

ul.article-list {
    margin-top: ◊x-lineheight[1];
    padding: 0;
}

ul.article-list li {
    list-style-type: none;
    display: block;
    margin: 0;
    padding: 0; 
    margin-bottom: ◊x-lineheight[1];
}

ul.article-list li>a {
    display: block;
}

div.article-list-date {
    color: #999;
}

div.article-list-title {
    font-size: 1.2rem;
}

/* ******* (Mobile first) Columnar series list styling *******
 */

.column-list {
    max-width: 17rem;
    margin: 0;
    columns: 8rem auto;
}

@media (min-width: 667px) {
  .column-list {
    margin: 0 auto;
  }
}

.column-list div {
    padding-left: 0.25em; /* Keeps some italic descenders inside the box */
    text-align: left;
    break-inside: avoid;
}

.column-list h2 {
    font-feature-settings: "smcp" on;
    text-transform: lowercase;
    font-weight: normal;
    font-size: 1em;
    margin: 0;
}

.column-list ul {
    margin-top: 0;
    list-style-type: none;
    padding: 0;
    margin-bottom: 0.5rem;
}

/* ******* (Mobile first) Keyword Index styling *******
 */

#keywordindex {
    column-width: 7rem;
    margin-top: ◊x-lineheight[2];
}

#keywordindex section {
    -webkit-column-break-inside: avoid; /* Chrome, Safari, Opera */
    page-break-inside: avoid; /* Firefox */
    break-inside: avoid; /* IE 10+ */
}

#keywordindex h2 {
    margin: 0;
}

#keywordindex ul {
    margin-top: 0;
    list-style-type: none;
    padding: 0;
}

#keywordindex ul ul {
    margin-left: 0.5em;
    font-size: smaller;
}

/* Footer ***** */

footer#main {
  text-align: left;
  font-size: 0.8rem;
  line-height: 1rem;
  width: 90%;
  margin: 0 auto;
}

@media (min-width: 667px) {
  footer#main {
    text-align: center;
    width: 100%;
    background: linear-gradient(◊color-background 5%, #dedede 100%);
  }
}

footer#main p.title {
  font-size: 1rem;
  font-feature-settings: "smcp" on;
  text-transform: lowercase;
  margin-top: ◊x-lineheight[2];
  margin-bottom: ◊x-lineheight[0.5];
}

footer#main nav {
  font-feature-settings: "smcp" on;
  text-transform: lowercase;
}

footer#main nav code {
  font-size: 0.6rem;
}
  

/* End of mobile-first typography and layout */


/* Here’s where we start getting funky for any viewport wider than mobile portrait.
   An iPhone 6 is 667px wide in landscape mode, so that’s our breakpoint. */

@media (min-width: 667px) {
    main {
        margin: 0 auto;
        padding-left: 1rem;
        padding-right: 1rem;
        width: 30rem;
        max-width: 90%;
    }

    main nav li { display: inline; } /* Display page numbers on larger screens */

    @supports (grid-area: auto) {
        main { 
            width: 42rem;
            background: none;
        }

................................................................................
            border-radius: 2px;
        }
        
        article>h1 {
            grid-area: margin;
            font-size: 1.1rem;
            text-align: right;


        }

        article>h1.entry-title {
            grid-area: title;
            font-size: ◊x-lineheight[1];
            text-align: left;
            padding-right: 0;
................................................................................
        }

        div.content-block-main > :first-child {
            margin-top: 0 !important;
        }
    }
}

@media print {
    html { font-size: 13pt; }
    body { background: white; color: black; }
    main { width: 100%; }
    footer.article-info { color: black; }
    article, section.content-block {
        box-shadow: none;
        border: none;
        }
    a:link, a:visited, a:hover, a:active {
        color: black;
        border-bottom: dotted 1px black;
    }
    footer#main { display: none; }
}