This commit is contained in:
rhiannon morris 2021-07-24 03:35:02 +02:00
commit 77a53e06a5
21 changed files with 1070 additions and 0 deletions

3
.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
dist-newstyle
_build
_tmp

3
.gitmodules vendored Normal file
View file

@ -0,0 +1,3 @@
[submodule "lang"]
path = lang
url = https://git.rhiannon.website/rhi/lang

112
Makefile Normal file
View file

@ -0,0 +1,112 @@
HOST ?= rhiannon.website
REMOTE_USER ?= www-data
IDFILE ?= ~/.ssh/xyz
REMOTE_DIR ?= blog
TMPDIR ?= _tmp
BUILDDIR ?= _build
POSTSDIR ?= posts
TEMPLATEDIR ?= templates
STATICEXTS := yaml
POSTS != find $(POSTSDIR) -name '*.md'
STATICS != parallel find $(POSTSDIR) -name '\*.{}' ::: $(STATICEXTS)
OUTPUTPOSTS = \
$(patsubst $(POSTSDIR)/%.md,$(BUILDDIR)/%.html,$(POSTS)) \
$(patsubst $(POSTSDIR)/%,$(BUILDDIR)/%,$(STATICS)) \
$(BUILDDIR)/all-posts.html
STYLE != find style -type f
OUTPUTSTYLE = $(patsubst %,$(BUILDDIR)/%,$(STYLE))
OUTPUT = $(OUTPUTPOSTS) $(OUTPUTSTYLE)
LANGFILTER = $(TMPDIR)/langfilter
LAANTAS_SCRIPT = $(TMPDIR)/laantas-script
ALL_TAGS = $(TMPDIR)/all-tags
POST_LISTS = $(TMPDIR)/post-lists
NICE_DATE = $(TMPDIR)/nice-date
EXECS = \
$(LANGFILTER) $(LAANTAS_SCRIPT) \
$(ALL_TAGS) $(POST_LISTS) $(NICE_DATE)
CABAL_FLAGS ?= -O -v0
.PHONY: all
all: build
.PHONY: build
build: $(EXECS) $(OUTPUT)
POSTDEPS = $(TEMPLATEDIR)/* $(LANGFILTER) $(LAANTAS_SCRIPT)
$(TMPDIR)/all-tags.md $(TMPDIR)/tags.mk &: $(POSTS) $(ALL_TAGS)
@echo "[all-tags]"
$(ALL_TAGS) $(POSTSDIR) $(TMPDIR)/all-tags.md $(TMPDIR)/tags.mk
build: $(BUILDDIR)/all-tags.html
include $(TMPDIR)/tags.mk
$(BUILDDIR)/%.html: $(POSTSDIR)/%.md $(POSTDEPS) ; $(call pandoc,post)
$(BUILDDIR)/%.html: $(TMPDIR)/%.md $(POSTDEPS) ; $(call pandoc,meta)
define pandoc
@echo "[pandoc] $<"
mkdir -p $(dir $@)
mkdir -p $(basename $@)
LAANTAS_SCRIPT="$(LAANTAS_SCRIPT)" \
DIRNAME="$(basename $@)" \
FILENAME="$@" \
pandoc -s --toc --template $(TEMPLATEDIR)/$(1).html -o $@ $< \
--filter $(LANGFILTER) --filter $(NICE_DATE) --mathjax
endef
$(BUILDDIR)/%: $(POSTSDIR)/% ; $(copy)
$(BUILDDIR)/%: $(TMPDIR)/% ; $(copy)
$(BUILDDIR)/%: % ; $(copy)
define copy
@echo "[copy] $<"
mkdir -p $(dir $@)
cp $< $@
endef
BLOG_META_DEPS != find blog-meta -type f
$(LANGFILTER): lang/langfilter/* ; $(call cabal-exe)
$(LAANTAS_SCRIPT): lang/laantas-script/* ; $(call cabal-exe)
$(ALL_TAGS): $(BLOG_META_DEPS) ; $(call cabal-exe,blog-meta:)
$(POST_LISTS): $(BLOG_META_DEPS) ; $(call cabal-exe,blog-meta:)
$(NICE_DATE): $(BLOG_META_DEPS) ; $(call cabal-exe,blog-meta:)
define cabal-exe
@echo "[build] $(notdir $@)"
cabal build $(1)$(notdir $@) $(CABAL_FLAGS)
mkdir -p $(dir $@)
find dist-newstyle -name $(notdir $@) -type f -exec cp {} $(TMPDIR) \;
endef
upload: build
@echo "[upload]"
@rsync --recursive --partial --progress --copy-links \
--compress --human-readable --hard-links --size-only \
--delete --delete-after \
--rsh='ssh -l $(REMOTE_USER) -i $(IDFILE)' \
$(BUILDDIR)/ $(HOST):$(REMOTE_DIR)/
.PHONY: clean distclean
clean:
@echo "[clean]"
rm -rf $(BUILDDIR)
rm -rf $(TMPDIR)
distclean: clean
@echo "[distclean]"
cabal clean
.SILENT:

108
blog-meta/all-tags.hs Normal file
View file

@ -0,0 +1,108 @@
{-# LANGUAGE NamedFieldPuns #-}
module Main (main) where
import qualified Data.ByteString.Lazy as LazyBS
import Data.Foldable
import Data.Function ((&))
import qualified Data.Map.Strict as Map
import Data.Set (Set)
import qualified Data.Set as Set
import Data.Text (Text)
import Misc
import qualified System.FilePath.Find as Find
import YAML ((##=), (.!=), (.:))
import qualified YAML
import System.Environment
import qualified Data.Text.IO as Text
import qualified Data.Text as Text
import Data.Char
main :: IO ()
main = do
Opts {dir, yaml, make} <- getOptions
files <- Find.findL True (pure True) (Find.extension Find.==? ".md") dir
tags <- traverse getTags files
LazyBS.writeFile yaml $ makeYAML tags
Text.writeFile make $ makeMake tags
getTags :: FilePath -> IO (Set Text)
getTags file = do
yaml <- YAML.readHeader file
list <- unwrap file $ YAML.parseEither $
yaml & YAML.withMap "yaml header" \m -> m .: "tags" .!= []
pure $ Set.fromList list
makeYAML :: [Set Text] -> LazyBS.ByteString
makeYAML tags = "---\n" <> yaml <> "\n...\n" where
yaml = YAML.encode1 $ YAML.obj
[("title" ##= YAML.str "all tags"),
("tags" ##= collate tags)]
makeMake :: [Set Text] -> Text
makeMake tags' = Text.unlines $ build : allPosts : map makeRule tags where
build = Text.unwords $
"build:" : ["$(BUILDDIR)/" <> t <> ".html" |
t <- ["all-tags", "all-posts"] <> map slug' tags]
makeRule' opt title file =
"$(TMPDIR)/" <> file <> ".md : $(POSTS) $(POST_LISTS)\n\
\\t@echo \"[post-lists] $<\"\n\
\\t$(POST_LISTS) " <> opt <> " --out $@ \\\n\
\\t $(POSTSDIR) \"" <> title <> "\""
allPosts = makeRule' "" "all posts" "all-posts"
makeRule t =
makeRule' ("--tag \"" <> name t <> "\"")
("posts tagged " <> name t <> "")
(slug' t)
slug' (Tag {slug}) = "tag-" <> slug
tags = collate tags'
data Tag =
Tag {
name :: !Text,
slug :: !Text,
count :: !Int
}
deriving Show
instance YAML.ToYAML Tag where
toYAML (Tag {name, slug, count}) = YAML.obj $
[("name" ##= name), ("slug" ##= slug), ("count" ##= count)]
collate :: [Set Text] -> [Tag]
collate tags =
toList $ fst $ foldl' add1 (mempty, mempty) $ foldMap toList tags
where
add1 (tags, slugs) name
| Map.member name tags =
(Map.adjust incrCount name tags, slugs)
| otherwise =
let tag = makeTag slugs name in
(Map.insert name tag tags,
Set.insert (slug tag) slugs)
makeTag slugs name =
Tag {name, slug = makeSlug slugs name, count = 1}
makeSlug slugs name = head $ filter (`notElem` slugs) candidates where
slug = Text.map toSlugChar name
toSlugChar c
| isAlphaNum c && isAscii c || c == '-' = toLower c
| otherwise = '_'
candidates = slug : [slug <> Text.pack (show i) | i <- [(0 :: Int) ..]]
incrCount t@(Tag {count}) = t {count = succ count}
data Options =
Opts {
dir :: !FilePath,
yaml :: !FilePath,
make :: !FilePath
}
getOptions :: IO Options
getOptions = do
args <- getArgs
prog <- getProgName
case args of
[dir, yaml, make] -> pure $ Opts {dir, yaml, make}
_ -> fail $
"usage: " <> prog <> " DIR YAML MAKE ---\n\
\ get all tags from posts in DIR and put the results in the given files"

51
blog-meta/blog-meta.cabal Normal file
View file

@ -0,0 +1,51 @@
cabal-version: 2.2
name: blog-meta
version: 0.1
author: rhiannon morris <rhi@rhiannon.website>
maintainer: rhiannon morris <rhi@rhiannon.website>
common deps
default-language: Haskell2010
default-extensions:
BlockArguments,
OverloadedStrings,
OverloadedLists,
NondecreasingIndentation,
ViewPatterns
build-depends:
base ^>= 4.14.2.0,
HsYAML ^>= 0.2.1.0,
bytestring ^>= 0.10.12.0,
containers ^>= 0.6.4.1,
filemanip,
pandoc-types ^>= 1.22,
text ^>= 1.2.4.1,
time ^>= 1.9.3
ghc-options: -Wall
common exe
build-depends: blog-meta
ghc-options: -threaded -rtsopts -with-rtsopts=-N
library
import: deps
hs-source-dirs: lib
exposed-modules:
YAML,
Misc
executable post-lists
import: deps, exe
hs-source-dirs: .
main-is: post-lists.hs
executable all-tags
import: deps, exe
hs-source-dirs: .
main-is: all-tags.hs
executable nice-date
import: deps, exe
hs-source-dirs: .
main-is: nice-date.hs

22
blog-meta/lib/Misc.hs Normal file
View file

@ -0,0 +1,22 @@
module Misc where
import qualified System.Console.GetOpt as GetOpt
import System.Environment
import System.Exit
-- | exception on 'Left'
unwrap :: Show a => FilePath -> Either a b -> IO b
unwrap file = either (\x -> fail $ file <> ":" <> show x) return
getOptionsWith :: (String -> String) -> ([String] -> Maybe a)
-> [GetOpt.OptDescr (a -> a)] -> IO a
getOptionsWith hdr mkDef descrs = do
res <- GetOpt.getOpt GetOpt.Permute descrs <$> getArgs
case res of
(fs, rest, []) | Just def <- mkDef rest ->
return $ foldl (flip ($)) def fs
_ -> do
prog <- getProgName
putStrLn $ GetOpt.usageInfo (hdr prog) descrs
exitFailure

52
blog-meta/lib/YAML.hs Normal file
View file

@ -0,0 +1,52 @@
module YAML (module YAML) where
import Data.YAML as YAML
import Data.YAML.Event as YAML (untagged)
import Data.Text (Text)
import qualified Data.Text as Text
import Misc
import Data.ByteString (ByteString)
import qualified Data.ByteString as BS
import qualified Data.ByteString.Lazy as LazyBS
import qualified System.IO as IO
str' :: String -> Node ()
str' = str . Text.pack
str :: Text -> Node ()
str = Scalar () . SStr
obj :: Mapping () -> Node ()
obj = Mapping () untagged
(##=) :: (ToYAML b) => Text -> b -> (Node (), Node ())
(##=) = (#=)
(#=) :: (ToYAML a, ToYAML b) => a -> b -> (Node (), Node ())
k #= v = (toYAML k, toYAML v)
list :: ToYAML a => [a] -> Node ()
list = Sequence () untagged . map toYAML
-- | read a chunk from the beginning of the file between a
-- @---@ and a @...@. throw an exception if there isn't one
readHeader :: FilePath -> IO (YAML.Node YAML.Pos)
readHeader file = IO.withFile file IO.ReadMode \h -> do
ln <- BS.hGetLine h
if (ln /= "---") then
fail $ file <> ": no header"
else
unwrap file . YAML.decode1 =<< linesUntil "..." h
-- | read all the lines from a handle until the given terminator. return the
-- lines read, excluding the terminator
linesUntil :: ByteString -> IO.Handle -> IO LazyBS.ByteString
linesUntil end h = go [] where
go acc = do
l <- BS.hGetLine h
if l == end then
return $ LazyBS.fromChunks $ reverse acc
else
go (l <> "\n" : acc)

45
blog-meta/nice-date.hs Normal file
View file

@ -0,0 +1,45 @@
import Text.Pandoc.Definition
import Data.Map.Strict (Map)
import qualified Data.Map.Strict as Map
import Data.Time
import Text.Pandoc.JSON
import Data.Text (Text, unpack, pack)
import Data.Char (toLower)
main :: IO ()
main = toJSONFilter \(Pandoc (Meta m) body) -> do
m' <- niceDate m
pure $ Pandoc (Meta m') body
niceDate :: Map Text MetaValue -> IO (Map Text MetaValue)
niceDate = Map.alterF reformat "date"
reformat :: Maybe MetaValue -> IO (Maybe MetaValue)
reformat Nothing = pure Nothing
reformat (Just (toText -> Just txt)) = do
-- extra '-'s in %-m and %-d to allow leading zeroes to be skipped
date <- parseTimeM True defaultTimeLocale "%Y-%-m-%-d" $ unpack txt
let str = formatTime defaultTimeLocale "%A %-e %B %Y" (date :: Day)
pure $ Just $ MetaString $ pack $ map toLower str
reformat (Just d) = fail $ "date is\n" <> show d <> "\nwanted a string"
toText :: MetaValue -> Maybe Text
toText (MetaString str) = Just str
toText (MetaInlines is) = foldMap inlineText is
toText (MetaBlocks bs) = foldMap blockText bs
toText _ = Nothing
inlineText :: Inline -> Maybe Text
inlineText (Str txt) = Just txt
inlineText Space = Just " "
inlineText SoftBreak = Just " "
inlineText LineBreak = Just " "
inlineText (RawInline _ txt) = Just txt
inlineText _ = Nothing
blockText :: Block -> Maybe Text
blockText (Plain is) = foldMap inlineText is
blockText (Para is) = foldMap inlineText is
blockText Null = Just ""
blockText (RawBlock _ txt) = Just txt
blockText _ = Nothing

100
blog-meta/post-lists.hs Normal file
View file

@ -0,0 +1,100 @@
import qualified Data.ByteString.Lazy as LazyBS
import Data.Char (toLower)
import Data.Function ((&))
import Data.List (sortBy)
import Data.Ord (comparing)
import Data.Text (Text)
import qualified Data.Text as Text
import Data.Time
import qualified YAML
import YAML ((.:), (.!=), (##=))
import qualified System.Console.GetOpt as GetOpt
import qualified System.FilePath.Find as Find
import Misc
import Data.Char (toLower)
main :: IO ()
main = do
Opts title dir tag out <- getOptions
files <- Find.findL True (pure True) (Find.extension Find.==? ".md") dir
infos <- filter (checkTag tag) <$> traverse getInfo files
let content = makeContent title infos
case out of
Nothing -> LazyBS.putStr content
Just fn -> LazyBS.writeFile fn content
makeContent :: Text -> [PostInfo] -> LazyBS.ByteString
makeContent title is' = "---\n" <> YAML.encode1 val <> "...\n" where
is = sortBy (flip $ comparing infoDate) is'
val = YAML.obj [("title" ##= title), ("posts" ##= is)]
checkTag :: Maybe Text -> PostInfo -> Bool
checkTag Nothing _ = True
checkTag (Just t) i = t `elem` infoTags i
data Options =
Opts {
optsTitle :: !Text,
optsDir :: !FilePath,
optsTag :: !(Maybe Text),
optsOut :: !(Maybe FilePath)
}
getOptions :: IO Options
getOptions = getOptionsWith hdr defOpts optDescrs where
hdr prog = "usage: " <> prog <> " [OPTION...] DIR TITLE\n\
\ --- get info about posts in DIR and use given title"
optDescrs :: [GetOpt.OptDescr (Options -> Options)]
optDescrs =
[GetOpt.Option "t" ["tag"]
(GetOpt.ReqArg (\t o -> o {optsTag = Just $ Text.pack t}) "TAG")
"list only posts with the given tag",
GetOpt.Option "o" ["out"]
(GetOpt.ReqArg (\f o -> o {optsOut = Just f}) "FILE")
"write output to FILE"]
defOpts :: [String] -> Maybe Options
defOpts [dir, title] =
Just $ Opts {optsDir = dir, optsTitle = Text.pack title,
optsTag = Nothing, optsOut = Nothing}
defOpts _ = Nothing
getInfo :: FilePath -> IO PostInfo
getInfo file = do
yaml <- YAML.readHeader file
unwrap file $ YAML.parseEither $
yaml & YAML.withMap "title, date, tags" \m ->
Info <$> return (Text.pack file)
<*> m .: "title"
<*> m .: "date"
<*> m .: "tags" .!= []
-- | the front matter info we care about
data PostInfo =
Info {
_nfoFile :: Text,
_nfoTitle :: Text,
infoDate :: BlogDate,
infoTags :: [Text]
}
instance YAML.ToYAML PostInfo where
toYAML (Info file title date tags) = YAML.obj
[("date" ##= date),
("title" ##= title),
("tags" ##= tags),
("file" ##= file)]
newtype BlogDate = D Day deriving (Eq, Ord)
instance YAML.FromYAML BlogDate where
parseYAML = YAML.withStr "YYYY-MM-DD" $
fmap D . parseTimeM True defaultTimeLocale "%F" . Text.unpack
instance YAML.ToYAML BlogDate where
toYAML (D d) = YAML.str $ Text.pack $ map toLower $
formatTime defaultTimeLocale "%a %-d %B %Y" d

8
cabal.project Normal file
View file

@ -0,0 +1,8 @@
packages:
lang/**/*.cabal,
./blog-meta/blog-meta.cabal
source-repository-package
type: git
location: https://git.rhiannon.website/rhi/filemanip.git
tag: 0edef8f7bbfe8e210f546e3222b735a32e6055e3

1
lang Submodule

@ -0,0 +1 @@
Subproject commit 3a878590c2a15764d5b6e8d4c80bfbc92714f2ae

16
posts/index.md Normal file
View file

@ -0,0 +1,16 @@
---
title: test
tags: [a, b]
date: 2021-07-25
conlang: laantas
...
# hello
im gecs
:::example
`{#kášńḿł | size=100 ; stroke=4}`
:::
$$\mathbb{wow},\, \mathcal{MATH}$$

66
style/counters.css Normal file
View file

@ -0,0 +1,66 @@
:root {
--section-prefix: '';
}
h1::before, h2::before, h3::before, h4::before, h5::before, h6::before {
padding-right: 1ex;
}
main h1 {
counter-increment: h1;
counter-reset: h2 h3 h4 h5 h6;
}
main h1::before {
content: var(--section-prefix) counter(h1);
}
main h2 {
counter-increment: h2;
counter-reset: h3 h4 h5 h6;
}
main h2::before {
content: var(--section-prefix) counter(h1) '.' counter(h2);
}
main h3 {
counter-increment: h3;
counter-reset: h4 h5 h6;
}
main h3::before {
content: var(--section-prefix) counter(h1) '.' counter(h2) '.' counter(h3);
}
main h4 {
counter-increment: h4;
counter-reset: h5 h6;
}
main h4::before {
content: var(--section-prefix)
counter(h1) '.' counter(h2) '.' counter(h3) '.' counter(h4);
}
main h5 {
counter-increment: h5;
counter-reset: h6;
}
main h5::before {
content: var(--section-prefix)
counter(h1) '.' counter(h2) '.' counter(h3) '.' counter(h4) '.'
counter(h5);
}
main h6 {
counter-increment: h6;
}
main h6::before {
content: var(--section-prefix)
counter(h1) '.' counter(h2) '.' counter(h3) '.' counter(h4) '.'
counter(h5) '.' counter(h6);
}

369
style/page.css Normal file
View file

@ -0,0 +1,369 @@
@import url(fonts/muller/muller.css);
@import url(fonts/junius/junius.css);
@import url(fonts/pragmatapro/pragmatapro.css);
:root {
--root-col: hsl(30deg, 5%, 26%);
--fg-col: hsl(336deg, 17%, 11%);
--bg-col: hsl(40deg, 91%, 98%);
--link-col: hsl(355deg, 52%, 48%);
--ipa-font: JuniusX;
}
:root {
background: var(--root-col);
font-family: Muller;
font-size: 16pt;
height: 100vh;
}
body {
background: url(paper.png), var(--bg-col);
background-blend-mode: multiply;
color: var(--fg-col);
box-shadow: 0 0 5em var(--fg-col);
max-width: 50em;
min-height: 100%;
margin: 0 auto;
padding: 1em 2em 3em;
box-sizing: border-box;
}
header {
text-align: center;
}
header h1 {
font-size: 350%;
font-weight: 100;
margin-top: 0;
}
h1, h2, h3, h4, h5, h6 {
margin: 1em 0 0.25em;
}
h1 {
font-size: 200%;
font-weight: 200;
}
h2 {
font-size: 180%;
font-weight: 200;
}
h3 {
font-size: 160%;
font-weight: 200;
}
h4 {
font-size: 140%;
font-weight: 300;
}
h5 {
font-size: 120%;
font-weight: 300;
}
h6 {
font-size: 100%;
font-weight: 400;
}
header h1 {
margin-bottom: 0.5rem;
}
hr {
border: 1px solid var(--root-col);
width: 80%;
height: 0;
}
.meta {
display: flex;
column-gap: 1em;
align-items: baseline;
justify-content: center;
}
.date {
font-size: 80%;
font-weight: 400;
font-style: italic;
margin-top: 0;
}
.tags {
font-size: 80%;
}
.tags ul {
display: inline;
padding: 0;
}
.tags li {
list-style: none;
display: inline;
}
.tags li + li {
margin-left: 0.75ex;
}
a {
color: var(--link-col);
}
b, strong {
font-weight: 600;
}
ul li {
list-style: '— ';
}
table {
margin: auto;
border-spacing: 0;
border-top: 2px solid var(--root-col);
border-bottom: 2px solid var(--root-col);
}
th {
font-weight: 500;
}
thead th {
border-bottom: 1px solid var(--root-col);
}
td, th {
padding: 0 0.5em;
vertical-align: text-bottom;
}
code {
font-family: PragmataPro;
font-size: 90%;
}
pre {
width: min-content;
margin: auto;
}
.ipa, .lang, .ebnf-t {
font-family: var(--ipa-font);
font-feature-settings:
"ccmp", "calt", "liga", "loca", "mark", "mkmk", "ss01";
/* ss01 to use modern Þþð design, others to make juniusx go brrr
* maybe i can just turn loca off but idk what other regional forms
* it does that i actually want */
text-underline-offset: 0.125em;
}
.ipa {
font-weight: 500;
}
.lang, .ebnf-t {
font-weight: 700;
}
.abbr {
font-size: 70%;
font-weight: 500;
}
.scr {
height: 1.5em;
vertical-align: -40%;
padding-right: 0.5ex;
}
.gloss .scr {
height: 2em;
}
:is(.splash, .example) .scr {
height: unset;
display: block;
margin: auto;
}
.example {
margin: auto;
}
.example .text {
display: block;
text-align: center;
width: 80%;
margin: 0.75em auto 0;
}
blockquote {
font-style: italic;
}
.letter-list {
margin: 1.25em auto 0;
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: space-around;
width: 80%;
row-gap: 0.5em;
column-gap: 1.5em;
}
.letter-list + .letter-list {
margin-top: 2.5em;
}
.letter-list .lang {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.letter-list .scr {
height: 3em;
margin-left: 0.5em;
}
.letter-list .text {
font-size: 125%;
}
main nav {
text-align: center;
font-size: 125%;
}
#toc > h2 {
font-size: 120%;
font-weight: 300;
}
#toc > ul {
columns: 2;
margin-left: 2em;
margin-right: 2em;
}
figure:not(.left) {
text-align: center;
}
figure:not(.left) table {
display: inline-table;
}
figure table {
margin: 1em 0.5em;
}
.gloss {
border: none;
text-align: left;
}
figure ul {
text-align: left;
margin-left: 5em;
margin-right: 5em;
columns: 2;
}
figure li {
break-inside: avoid;
}
dt { font-weight: 500; float: left; clear: left; }
dd { margin-left: 4em; }
dt {
break-after: avoid;
}
dd {
break-before: avoid;
}
u u {
text-decoration: double underline;
}
:is(.twocol, .threecol):not(:is(ul, ol) *) {
margin-top: 1em;
}
.twocol {
columns: 2;
}
.threecol {
columns: 3;
}
.twocol :first-child, .threecol :first-child {
margin-top: 0;
}
footer {
margin: 1.5em auto 1em;
padding-top: 0.5em;
font-size: 80%;
font-weight: 500;
text-align: center;
}
.ebnf {
border: none;
}
.ebnf td {
padding: 0 0.15em;
}
.ebnf-nt {
font-weight: 500;
color: hsl(155deg, 80%, 30%);
white-space: nowrap;
}
.ebnf-punc {
color: hsl(25deg, 40%, 30%);
}
.ebnf-sub, .ebnf-brack {
color: hsl(210deg, 80%, 35%);
font-weight: 500;
}
.ebnf-brack {
padding: 0 0.05em;
}
.ebnf-s {
font-style: italic;
color: hsl(330deg, 80%, 30%);
}
blockquote {
max-width: 70%;
border-left: 1px solid black;
padding-left: 1em;
margin: auto;
}

BIN
style/paper.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

11
templates/foot.html Normal file
View file

@ -0,0 +1,11 @@
$if(hide-footer)$$else$
<footer>
<hr>
<a href=/all-posts.html>all posts</a> ·
<a href=/all-tags.html>all tags</a>
</footer>
$endif$
$for(include-after)$
$include-after$
$endfor$

24
templates/head.html Normal file
View file

@ -0,0 +1,24 @@
<!DOCTYPE html>
<html$if(lang)$ lang=$lang$$endif$$if(dir)$ dir=$dir$$endif$>
<meta charset=utf-8>
<link rel=stylesheet href=/style/page.css>
$if(show-toc)$
<link rel=stylesheet href=/style/counters.css>
$endif$
$for(css)$
<link rel=stylesheet href=/style/$css$>
$endfor$
$for(header-includes)$
$header-includes$
$endfor$
$if(math)$
$math$
$endif$
<title>$pagetitle$</title>
$for(include-before)$
$include-before$
$endfor$

15
templates/meta.html Normal file
View file

@ -0,0 +1,15 @@
$head()$
$if(title)$
<header>
<h1>$title$</h1>
</header>
$endif$
$if(posts)$
$postlist()$
$elseif(tags)$
$taglist()$
$endif$
$foot()$

46
templates/post.html Normal file
View file

@ -0,0 +1,46 @@
$head()$
<header>
$if(title)$
<h1>$title$</h1>
$endif$
<div class=meta>
$if(date)$
<h2 class=date>$date$</h2>
$endif$
$if(tags)$
<nav class=tags>
tags:
<ul>
$for(tags)$
<li><a href=/tag-$it$.html>$it$</a>
$endfor$
</ul>
</nav>
$endif$
</div>
<hr>
</header>
$if(show-toc)$
<nav id=toc>
<h2>Contents</h2>
$table-of-contents$
</nav>
$endif$
<main>
$body$
$if(blah)$
<dl>
$for(blah)$
<dt>$blah.a$
<dd>$blah.b$
$endfor$
</dl>
$endif$
</main>
$foot()$

9
templates/postlist.html Normal file
View file

@ -0,0 +1,9 @@
<main>
<ul>
$for(posts)$
<li>
<a href=$it.file$.html>$it.title$</a>
($it.date$)
$endfor$
</ul>
</main>

9
templates/taglist.html Normal file
View file

@ -0,0 +1,9 @@
<main>
<ul>
$for(tags)$
<li>
<a href=tag-$it.slug$.html>$it.name$</a>
($it.count$)
$endfor$
</ul>
</main>