Index: blog.rkt ================================================================== --- blog.rkt +++ blog.rkt @@ -4,12 +4,13 @@ ; 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" +(require "cache.rkt" "snippets-html.rkt" + "dust.rkt" racket/file sugar/list) (provide main) @@ -36,10 +37,11 @@ ◊html$-page-body-close[] }) ;; 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)]) ADDED cache.rkt Index: cache.rkt ================================================================== --- cache.rkt +++ cache.rkt @@ -0,0 +1,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 + + + + 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.: ( articles+notes) +(define ( query-func #:series [s #t] #:limit [lim -1] #:order [ord 'desc]) + `(style ,@(listing-htmls (query-func 'full #:series s #:limit lim #:order ord)))) +;; ^^^^^ + +(define ( query-func #:series [s #t] #:limit [lim -1] #:order [ord 'desc]) + `(style ,@(listing-htmls (query-func 'excerpt #:series s #:limit lim #:order ord)))) +;; ^^^^^^^^ + +(define ( query-func #:series [s #t] #:limit [lim -1] #:order [ord 'desc]) + `(style "
    " + ,@(listing-htmls (query-func 'short #:series s #:limit lim #:order ord)) + "
")) ;; ^^^^^^ + +;; Remove "" 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=?))) Index: crystalize.rkt ================================================================== --- crystalize.rkt +++ crystalize.rkt @@ -1,111 +1,28 @@ #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 +(require deta + db/base + db/sqlite3 + threading + racket/match racket/string - pollen/pagetree + txexpr 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 - - - - 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)) +) + +(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))) @@ -129,10 +46,11 @@ [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 @@ -255,10 +173,11 @@ #: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 @@ -311,87 +230,10 @@ (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.: ( articles+notes) -(define ( query-func #:series [s #t] #:limit [lim -1] #:order [ord 'desc]) - `(style ,@(listing-htmls (query-func 'full #:series s #:limit lim #:order ord)))) -;; ^^^^^ - -(define ( query-func #:series [s #t] #:limit [lim -1] #:order [ord 'desc]) - `(style ,@(listing-htmls (query-func 'excerpt #:series s #:limit lim #:order ord)))) -;; ^^^^^^^^ - -(define ( query-func #:series [s #t] #:limit [lim -1] #:order [ord 'desc]) - `(style "
    " - ,@(listing-htmls (query-func 'short #:series s #:limit lim #:order ord)) - "
")) ;; ^^^^^^ - -;; Remove "" 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))) Index: dust.rkt ================================================================== --- dust.rkt +++ dust.rkt @@ -19,10 +19,11 @@ (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 @@ -64,10 +65,12 @@ (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)) "")) Index: index.html.pp ================================================================== --- index.html.pp +++ index.html.pp @@ -1,31 +1,12 @@ #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)) - - +◊(require pollen/template racket/file) + ◊html$-page-head["The Local Yarn" #f] @@ -87,12 +68,13 @@ ◊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)
◊(->html front-page-body #:splice? #t) - ◊(->html (series-grouped-list)) + ◊(html$-series-list)
Index: keyword-index.rkt ================================================================== --- keyword-index.rkt +++ keyword-index.rkt @@ -12,11 +12,11 @@ racket/string db/base net/uri-codec pollen/template) -(require "crystalize.rkt" +(require "cache.rkt" "dust.rkt" "snippets-html.rkt") (provide main) Index: makefile ================================================================== --- makefile +++ makefile @@ -5,11 +5,11 @@ # ~~~ Variables used by rules ~~~ # core-files := pollen.rkt dust.rkt -html-deps := snippets-html.rkt tags-html.rkt crystalize.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)) @@ -31,38 +31,39 @@ 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 + 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, +# 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 + 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 + 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 + 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 + 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 @@ -92,10 +93,11 @@ --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/* @@ -110,11 +112,11 @@ @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 +.PHONY: web scribble help zap article publish check-env .DEFAULT_GOAL := help check-env: ifndef LOCALYARN_SRV Index: pollen.rkt ================================================================== --- pollen.rkt +++ pollen.rkt @@ -11,16 +11,17 @@ 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")) + (all-from-out "crystalize.rkt" "snippets-html.rkt" "cache.rkt")) (module setup racket/base (require syntax/modresolve racket/runtime-path pollen/setup) @@ -30,15 +31,17 @@ (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!)]) Index: rss-feed.rkt ================================================================== --- rss-feed.rkt +++ rss-feed.rkt @@ -10,11 +10,11 @@ racket/file racket/date racket/string db/base "dust.rkt" - "crystalize.rkt") + "cache.rkt") (provide main) (define feed-author default-authorname) (define feed-author-email "joel@jdueck.net") @@ -52,19 +52,19 @@ `title_plain` AS `title`, `published`, `updated`, `author`, `doc_html` AS `entry_contents` - FROM `articles` + 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`) + 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))) Index: snippets-html.rkt ================================================================== --- snippets-html.rkt +++ snippets-html.rkt @@ -11,17 +11,20 @@ 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 @@ -71,14 +74,28 @@ ◊string-append{
  • ◊(ymd->english pubdate)
    ◊|title|
  • }) + +(define (html$-page-footer) + ◊string-append{ +}) (define (html$-page-body-close) - ◊string-append{
    By Joel Dueck
    - }) + ◊string-append{ + + ◊(html$-page-footer) + }) ;; Notes ;; (define (html$-note-contents disposition-mark disposition-verb elems) (define disposition @@ -196,5 +213,15 @@ (if (eq? pagecount pagenum) "
  • Older→
  • " (page-func (+ pagenum 1) "Older →" "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))) Index: util/newpost.rkt ================================================================== --- util/newpost.rkt +++ util/newpost.rkt @@ -33,11 +33,12 @@ #lang pollen ◊comment{Copyright ◊(substring date-string 0 4) by ◊|default-authorname|. All Rights Reserved.} ◊"◊"(define-meta published "◊date-string") - ◊"◊"(define-meta series "seriesname") + ◊"◊"(define-meta conceal "blog,feed") ; Edit/delete this line when ready to publish + ◊"◊;"(define-meta series "seriesname") ◊"◊"title{◊title} Write here!}) Index: web-extra/font.css ================================================================== --- web-extra/font.css +++ web-extra/font.css @@ -20,11 +20,11 @@ @font-face { font-family: 'Fabiol'; src: url('LDFabiolPro-Bold.woff2') format('woff2'), url('LDFabiolPro-Bold.woff') format('woff'); - font-style: bold; + font-style: normal; font-weight: 700; } @font-face { font-family: 'Triplicate T4c'; @@ -38,5 +38,11 @@ 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; + } Index: web-extra/martin.css.pp ================================================================== --- web-extra/martin.css.pp +++ web-extra/martin.css.pp @@ -27,25 +27,25 @@ /* 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) { +@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 only screen and (min-width: 1000px) and (max-height: 920px) { +@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 only screen and (min-width: 1178px) and (min-height: 921px) { +@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) @@ -72,641 +72,684 @@ 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
    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

    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 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 */ -} +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
    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

    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 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 only screen and (min-width: 667px) { +@media (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 */ + main nav li { display: inline; } /* Display page numbers on larger screens */ @supports (grid-area: auto) { main { width: 42rem; background: none; @@ -743,12 +786,10 @@ 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]; @@ -861,5 +902,21 @@ 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; } +}