diff --git a/.gitignore b/.gitignore
index 4f19501..4cf7aec 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,3 +3,4 @@ _build
 _tmp
 .directory
 *~
+.sass-cache
diff --git a/Makefile b/Makefile
index 05d26f8..21957d6 100644
--- a/Makefile
+++ b/Makefile
@@ -19,16 +19,17 @@ OUTPUTPOSTS = \
   $(BUILDDIR)/index.html $(BUILDDIR)/rss.xml
 
 STYLE != find style -type f
-OUTPUTSTYLE = $(patsubst %,$(BUILDDIR)/%,$(STYLE))
+OUTPUTSTYLE = $(patsubst %.scss,%.css,$(patsubst %,$(BUILDDIR)/%,$(STYLE)))
 
-OUTPUT = $(OUTPUTPOSTS) $(OUTPUTSTYLE)
+SCRIPT != find script -type f
+OUTPUTSCRIPT = $(patsubst %.ts,%.js,$(patsubst %,$(BUILDDIR)/%,$(SCRIPT)))
+
+OUTPUT = $(OUTPUTPOSTS) $(OUTPUTSTYLE) $(OUTPUTSCRIPT)
 
 ALL_TAGS = $(TMPDIR)/all-tags
 POST_LISTS = $(TMPDIR)/post-lists
 COMBO_FILTER = $(TMPDIR)/combo-filter
-EXECS = \
-  $(LAANTAS_SCRIPT) \
-  $(ALL_TAGS) $(POST_LISTS) $(COMBO_FILTER)
+EXECS = $(ALL_TAGS) $(POST_LISTS) $(COMBO_FILTER)
 
 CABAL_FLAGS ?= -O
 
@@ -58,8 +59,17 @@ $(BUILDDIR)/%.html: $(TMPDIR)/%.md   $(POSTDEPS) ; $(call pandoc-simple,meta.htm
 $(BUILDDIR)/rss.xml: $(TMPDIR)/index.md $(POSTDEPS)
 	$(call pandoc,rss.xml,--metadata-file rss.yaml --write html)
 
-.PHONY: langfilter laantas-script
-langfilter laantas-script: %: ; cabal build $@
+$(BUILDDIR)/%.css: %.scss
+	@echo "[sass]" $@
+	mkdir -p $(dir $@)
+	sass --no-source-map -I style $< $@
+
+$(BUILDDIR)/%.js: %.ts
+	@echo "[tsc]" $@
+	mkdir -p $(dir $@)
+	tsc --strict --noUncheckedIndexedAccess --noEmitOnError \
+	    --lib dom,es2021 --target es2015 \
+	    --outDir $(dir $@) $^
 
 # $(1): template file
 # $(2): extra flags
diff --git a/blog-meta/all-tags.hs b/blog-meta/all-tags.hs
index 9bbc6ec..e2e7c02 100644
--- a/blog-meta/all-tags.hs
+++ b/blog-meta/all-tags.hs
@@ -61,8 +61,8 @@ getTags file = do
 makeYAML :: [Set Text] -> LazyBS.ByteString
 makeYAML tags = "---\n" <> yaml <> "\n...\n" where
   yaml = YAML.encode1 $ YAML.obj
-    [("title"    ##= YAML.str "all tags"),
-     ("all-tags" ##= collate tags)]
+    ["title"    ##= YAML.str "all tags", "meta" ##= True,
+     "all-tags" ##= collate tags]
 
 -- | generates a makefile to include that generates the index and individual
 -- tag pages. example:
@@ -112,7 +112,7 @@ data Tag =
 
 instance YAML.ToYAML Tag where
   toYAML (Tag {name, slug, count}) = YAML.obj $
-    [("name" ##= name), ("slug" ##= slug), ("count" ##= count)]
+    ["name" ##= name, "slug" ##= slug, "count" ##= count]
 
 -- | counts occurrences of each tag, and checks they all have distinct slugs
 collate :: [Set Text] -> [Tag]
diff --git a/blog-meta/post-lists.hs b/blog-meta/post-lists.hs
index cbdbe9a..01d61f9 100644
--- a/blog-meta/post-lists.hs
+++ b/blog-meta/post-lists.hs
@@ -64,7 +64,7 @@ makeTagInfo infos title basename outdir = do
 makeContent :: Text -> [PostInfo] -> LazyBS.ByteString
 makeContent title is' = "---\n" <> YAML.encode1 val <> "...\n" where
   is = sortBy (flip $ comparing date) is'
-  val = YAML.obj [("title" ##= title), ("posts" ##= is)]
+  val = YAML.obj ["title" ##= title, "posts" ##= is, "meta" ##= True]
 
 allTags :: Foldable t => t PostInfo -> Set Text
 allTags = foldMap (Set.fromList . tags)
diff --git a/cabal.project b/cabal.project
index 485c837..15865bd 100644
--- a/cabal.project
+++ b/cabal.project
@@ -4,13 +4,13 @@ packages:
 source-repository-package
   type: git
   location: https://git.rhiannon.website/rhi/lang.git
-  tag: ed54ec4c5af5b2b8ea25f0274e11c2f8c87714c3
+  tag: c4a430facbeaa55a9de8edd69145243475c94087
   subdir: langfilter
 
 source-repository-package
   type: git
   location: https://git.rhiannon.website/rhi/lang.git
-  tag: ed54ec4c5af5b2b8ea25f0274e11c2f8c87714c3
+  tag: c4a430facbeaa55a9de8edd69145243475c94087
   subdir: laantas-script
 
 source-repository-package
diff --git a/posts/new-new-theme.md b/posts/new-new-theme.md
new file mode 100644
index 0000000..8ef62b9
--- /dev/null
+++ b/posts/new-new-theme.md
@@ -0,0 +1,14 @@
+---
+title: <em>new</em> new theme
+date: 2024-12-05
+summary: for real this time. probably
+tags: [december adventure]
+...
+
+yeah i redid it.
+
+it's more _on brand_ now, i think. and the actual css is nice and organised now. i am grudgingly using [sass] now because i really want those mixins. and functions.
+
+so yeah! honestly i don't think there is much to talk about with something like this. you can see the results.
+
+bye
diff --git a/posts/new-theme.md b/posts/new-theme.md
new file mode 100644
index 0000000..f003eb5
--- /dev/null
+++ b/posts/new-theme.md
@@ -0,0 +1,18 @@
+---
+title: new theme
+date: 2024-12-04
+tags: [december adventure]
+summary: wow!
+...
+
+hi today's `@december adventure@` was…
+
+:::banner
+redesigning this page
+:::
+
+maybe you noticed when you opened it. i know it's pretty subtle.
+
+:::aside
+**note to readers from the future**: it was not subtle.
+:::
diff --git a/posts/quorientation.md b/posts/quorientation.md
index d40f526..9c06c34 100644
--- a/posts/quorientation.md
+++ b/posts/quorientation.md
@@ -29,6 +29,8 @@ so. one sentence at a time.
 
 # one
 
+you know! the thing! from the game!
+
 :::glosses
 - suatł pattalal šilbari gulaigúšḿ fulla
 - [ˌsuətɫ̩ ˈpatːɐləlʲ ˈʃilʲbɐɾɪ ɡɔˈlaɪɣɔːʃm̩ fʊɫːɑ]
@@ -37,18 +39,13 @@ so. one sentence at a time.
 - use your warps to skip some floors
 :::
 
-you know! the thing! from the game!
-
-<figure class='floating right'>
-<figcaption> well. from the fan translation </figcaption>
-<img src=quorientation/skip-some-floors.png class=pixel alt=''>
-</figure>
+![well. from the fan translation](quorientation/skip-some-floors.png){.floating .right .pixel}
 
 - out of context, `{!patta}` means door. i suppose a portal would be a magic
   door, an `{!ustaitł patta}`. but in the context of someone making one right
   in front of you, it is clear what kind of door we're talking about.
 
-  :::{.aside .no-line}
+  :::aside
   the word `{!ustail}` also means "songs". did you know there's an old english
   word for magic, `{ġealdor}`, that comes from the proto-germanic `{galdr}`,
   meaning both "song" and "incantation"? i thought it was cute.
@@ -88,7 +85,7 @@ babylon no longer exists, so it doesn't need protecting. go have fun
   mean something like "is possible/necessary".
   "{flowers grow here} is possible".
 
-  :::{.aside .no-line}
+  :::aside
   the similarity of `{!fulla}` and `{!bulla}` is just a weird accident, in case
   you're wondering. i made the words at different times. no etymology puzzle
   here, sorry
@@ -112,7 +109,7 @@ babylon no longer exists, so it doesn't need protecting. go have fun
 
 # abbreviation list
 
-:::{.twocol .abbr-list}
+:::{.abbr-list}
 DEF
 : [definite](https://lang.niss.website/laantas/nouns.html#definiteness)
   ["the"]{.note}
diff --git a/posts/rainbow-quox.md b/posts/rainbow-quox.md
index e71b0bc..17c51f3 100644
--- a/posts/rainbow-quox.md
+++ b/posts/rainbow-quox.md
@@ -6,17 +6,18 @@ summary: q.t. colour scheme generator
 header-includes: |
   <style>
     #relcolor {
-      max-width: 20em; margin: auto;
-      display: grid; grid-template-columns: 1fr 1fr; gap: .5em;
+      max-width: 20em; margin: 1em auto auto;
+      display: grid; grid-template-columns: 1fr 1fr; gap: 3em;
       font-weight: bold;
       --hi: #ea9aa1; --wow: oklch(from var(--hi) l c calc(h + 180));
     }
     #relcolor div {
       text-align: center; font-size: 125%;
-      padding: .4em; border-radius: .5em;
+      padding: .4em;
       color: black;
       background: var(--bg);
       border: 4px solid oklch(from var(--bg) .25 75% h);
+      box-shadow: 0.5em 0.5em 0 color-mix(in srgb, var(--bg) 50%, transparent);
     }
     #hi { --bg: var(--hi); }
     #wow { --bg: var(--wow); }
@@ -69,7 +70,7 @@ if you look at [mdn], you might see this interesting [`hue-rotate()`][hr] thing
 
 well that doesn't sound very encouraging. but maybe it'll be fine.
 
-i shoved [all of the colours][jofo] to hue 0° (using krita's _hue HSL_ blend mode), and used `hue-rotate()` to change it back to the 'main' colour of each region.
+i shoved [all of the colours][jofo] to hue 0° (using krita's `@hue HSL@` blend mode), and used `hue-rotate()` to change it back to the 'main' colour of each region.
 
 it won't look exact, but it'll be close. right?
 
@@ -171,27 +172,27 @@ a good place to start is the same place everyone does: with complementary and tr
 using this as a starting point, i pick some evenly-spaced colours for each bit. the final palettes come out like this!
 
 :::{#result}
-- fins:
+- fins
   <svg viewBox='-0.1 -0.1 8.7 1.2'>
   <rect fill=#770084 width=2 height=1 />
   <rect fill=#9d0058 width=2 height=1 x=2 />
   <rect fill=#a11916 width=2 height=1 x=4 />
   <rect fill=#eead91 width=2 height=1 x=6.5 />
   </svg>
-- body:
+- body
   <svg viewBox='-0.1 -0.1 6.7 1.2'>
   <rect fill=#00709b width=2 height=1 />
   <rect fill=#008fca width=2 height=1 x=2 />
   <rect fill=#7dd1f1 width=2 height=1 x=4.5 />
   </svg>
-- belly:
+- belly
   <svg viewBox='-0.1 -0.1 8.7 1.2'>
   <rect fill=#dc8d7b width=2 height=1 />
   <rect fill=#efa4b0 width=2 height=1 x=2 />
   <rect fill=#f9aba1 width=2 height=1 x=4.5 />
   <rect fill=#e3adbc width=2 height=1 x=6.5 />
   </svg>
-- mask/claws/socks:
+- mask/claws/socks
   <svg viewBox='-0.1 -0.1 9.2 1.2'>
   <rect fill=#ebc1c8 width=2 height=1 />
   <rect fill=#b4aaae width=2 height=1 x=2.5 />
diff --git a/script/hue.ts b/script/hue.ts
new file mode 100644
index 0000000..2d4f459
--- /dev/null
+++ b/script/hue.ts
@@ -0,0 +1,6 @@
+export {}
+
+document.addEventListener('DOMContentLoaded', () => {
+  const hue = `${Math.random() * 360}`;
+  document.documentElement.style.setProperty('--base-hue', hue);
+});
diff --git a/style/colours.scss b/style/colours.scss
new file mode 100644
index 0000000..35c4393
--- /dev/null
+++ b/style/colours.scss
@@ -0,0 +1,43 @@
+
+$lighter: oklch(95% 8% var(--base-hue));
+$light:   oklch(90% 12% var(--base-hue));
+$mid:     oklch(40% 20% var(--base-hue));
+$dark:    oklch(20% 45% var(--base-hue));
+$darker:  oklch(10% 40% var(--base-hue));
+
+$dark-accent:  oklch(56% 40% calc(var(--base-hue) - 180));
+$light-accent: oklch(90% 30% calc(var(--base-hue) - 180));
+
+@function halfalpha($col) {
+  @return color-mix(in srgb, #{$col} 50%, transparent);
+}
+
+
+@mixin color-var($name, $light, $dark: null) {
+  @property #{$name} {
+    syntax: "<color>";
+    inherits: true;
+    initial-value: #{$light};
+  }
+  :root {
+    #{$name}: #{$light};
+    @if $dark != null {
+      @media (prefers-color-scheme: dark) { #{$name}: #{$dark}; }
+    }
+  }
+}
+
+@mixin apply-colors($fg, $bg, $accent: null) {
+  --fg: #{$fg};
+  --bg: #{$bg};
+  @if $accent { --accent: #{$accent}; }
+
+  color:      var(--fg);
+  background: var(--bg);
+}
+
+@mixin inverted($padding: 0.5em) {
+  @include apply-colors($fg: var(--counter-fg), $bg: var(--counter-bg),
+                        $accent: var(--counter-accent));
+  padding:    $padding;
+}
diff --git a/style/counters.css b/style/counters.css
index 6821348..53ceb1a 100644
--- a/style/counters.css
+++ b/style/counters.css
@@ -16,53 +16,14 @@
   suffix: " ";
 }
 
-main :is(h1, h2, h3, h4, h5, h6):not(.unnumbered)::before {
-  padding-right: 1ex;
-}
+main h1 {
+  &:not(.unnumbered) { counter-increment: h1; }
 
-main h1:not(.unnumbered) { counter-increment: h1; }
-main h1 { counter-reset: h2 h3 h4 h5 h6; }
-main h1:not(.unnumbered)::before {
-  content: counter(h1, inv-circled);
+  &::before {
+    font-family: PragmataPro;
+    font-style: normal;
+    content: counter(h1, inv-circled);
+    padding-right: 1ex;
+  }
+  &.unnumbered::before { content: "\2B8A"; }
 }
-main h1.unnumbered::before {
-  content: "\2B8A  ";
-}
-
-/*
-
-main h2:not(.unnumbered) { counter-increment: h2; }
-main h2:not(.unnumbered)::before {
-  content: counter(h1) '.' counter(h2);
-}
-main h2 { counter-reset: h3 h4 h5 h6; }
-
-main h3:not(.unnumbered) { counter-increment: h3; }
-main h3 { counter-reset: h4 h5 h6; }
-main h3:not(.unnumbered)::before {
-  content: counter(h1) '.' counter(h2) '.' counter(h3);
-}
-
-main h4:not(.unnumbered) { counter-increment: h4; }
-main h4 { counter-reset: h5 h6; }
-main h4:not(.unnumbered)::before {
-  content:
-    counter(h1) '.' counter(h2) '.' counter(h3) '.' counter(h4);
-}
-
-main h5:not(.unnumbered) { counter-increment: h5; }
-main h5 { counter-reset: h6; }
-main h5:not(.unnumbered)::before {
-  content:
-    counter(h1) '.' counter(h2) '.' counter(h3) '.' counter(h4) '.'
-    counter(h5);
-}
-
-main h6:not(.unnumbered) { counter-increment: h6; }
-main h6:not(.unnumbered)::before {
-  content:
-    counter(h1) '.' counter(h2) '.' counter(h3) '.' counter(h4) '.'
-    counter(h5) '.' counter(h6);
-}
-
-*/
diff --git a/style/fonts.scss b/style/fonts.scss
new file mode 100644
index 0000000..30b9891
--- /dev/null
+++ b/style/fonts.scss
@@ -0,0 +1,51 @@
+@import url(fonts/pragmatapro/pragmatapro.css);
+@import url(fonts/muller/muller.css);
+@import url(fonts/junicodevf/junicodevf.css);
+
+
+$normal-weight:       400;
+$bold-weight:         600;
+$boldbold-weight:     700;
+$main-heading-weight: 300;
+$heading-weight:      $bold-weight;
+$small-weight:        500;
+
+$body-size:  14pt;
+$small-size: $body-size * 0.75;
+$code-font-scale: 0.85;
+
+@mixin main-font { font-family: Muller, sans-serif; }
+
+@mixin heading-font { @include main-font; }
+
+@mixin code-font {
+  font-family: PragmataPro, monospace;
+  font-size:   100% * $code-font-scale;
+}
+
+@mixin ipa-font {
+  font-family: JunicodeVF, serif;
+  font-feature-settings: "ccmp", "calt", "liga", "loca", "mark", "mkmk", "ss01";
+  font-weight: 550;
+  font-stretch: 110%;
+}
+
+@mixin small-font {
+  font-size:   80%;
+  font-weight: $small-weight;
+}
+
+@mixin note-font {
+  @include small-font;
+  font-style:  italic;
+}
+
+@mixin symbol-font {
+  font-family: PragmataPro, sans-serif;
+}
+
+@mixin heading-size($level) {
+  $step: 1.2;
+  font-size: #{pow($step, max(4 - $level, 0))}rem;
+}
+
diff --git a/style/meta.scss b/style/meta.scss
new file mode 100644
index 0000000..cd16243
--- /dev/null
+++ b/style/meta.scss
@@ -0,0 +1,34 @@
+@use 'colours' as *;
+@use 'fonts' as *;
+@import url(page.css);
+
+.tag-list {
+  columns: 2;
+}
+
+.post-list li + li {
+  margin-top: 0.5em;
+}
+
+.post-desc {
+  margin: 0;
+  line-height: 125%;
+
+  p { margin: 0; }
+  p + p { margin-top: .5em; }
+}
+
+.post-list > li { list-style: '✎ '; }
+.tag-list > li { list-style: '🏷 '; }
+
+.post-list .date, .tag-list .count, .post-desc {
+  @include note-font;
+}
+
+#toc > ul {
+  columns: 2;
+  margin-left:  2em;
+  margin-right: 2em;
+  > li { list-style: circled; }
+}
+
diff --git a/style/niss.png b/style/niss.png
deleted file mode 100644
index 0e2009e..0000000
Binary files a/style/niss.png and /dev/null differ
diff --git a/style/page.css b/style/page.css
deleted file mode 100644
index 6a16d69..0000000
--- a/style/page.css
+++ /dev/null
@@ -1,708 +0,0 @@
-@import url(counters.css);
-@import url(fonts/schola/schola.css);
-@import url(fonts/muller/muller.css);
-@import url(fonts/junius/junius.css);
-@import url(fonts/pragmatapro/pragmatapro.css);
-
-:root {
-  --ipa-font: JuniusX;
-
-  --light: #feb;
-  --dark:  #325;
-  --mid:   #83869e;
-  --red1:  #f42;
-  --red2:  #d16;
-
-  --fg:  var(--dark);
-  --bg:  var(--light);
-  --accent: var(--red1);
-  --other-accent: var(--red2);
-}
-
-:root {
-  background: var(--bg);
-  color: var(--fg);
-
-  font-family: PragmataPro;
-  font-size: 13pt;
-}
-
-@media (prefers-color-scheme: dark) {
-  :root {
-    --fg: var(--light);
-    --bg: var(--dark);
-    --accent: var(--red2);
-    --other-accent: var(--red1);
-  }
-}
-
-:root, body { margin: 0; padding: 0; }
-
-main, footer { max-width: 50rem; }
-
-main {
-  min-height: 100%;
-  margin: 1em auto 0;
-  padding: 0.5em 2em;
-
-  line-height: 150%;
-
-  box-sizing: border-box;
-}
-
-header {
-  text-align: center;
-  text-wrap: balance;
-  background: var(--fg);
-  color: var(--bg);
-  --accent: var(--other-accent);
-  padding: 1em;
-}
-
-header h1 {
-  font-size: 200%;
-  margin: 0;
-  font-weight: normal;
-  text-transform: uppercase;
-  letter-spacing: 0.4ch;
-}
-
-/* h1, h2, h3, h4, h5, h6 { */
-main h1 {
-  margin: 1em 0 0.25em;
-
-  font-weight: normal;
-  font-variant-east-asian: full-width;
-  -moz-font-feature-settings: "fwid";
-  -webkit-font-feature-settings: "fwid";
-  font-feature-settings: "fwid";
-
-  clear: both;
-
-  font-size: 100%;
-}
-
-h1 small {
-  font-variant-east-asian: normal;
-  -moz-font-feature-settings: normal;
-  -webkit-font-feature-settings: normal;
-  font-feature-settings: normal;
-}
-
-
-h1 { font-size: 125%; }
-h2 { font-size: 125%; }
-h3 { font-size: 120%; }
-h4 { font-size: 110%; }
-h5 { font-size: 100%; }
-h6 { font-size: 100%; }
-
-main h1 {
-  background: var(--fg);
-  color: var(--bg);
-  padding: 0.25rem 0.75rem;
-  margin-bottom: 1rem;
-}
-
-main :is(h2, h3, h4, h5, h6) {
-  padding: 0.25rem 0.75rem;
-  border-bottom: 2px dotted var(--fg);
-  margin-bottom: 1rem;
-}
-
-hr {
-  border: none;
-  border-bottom: 2px dotted var(--fg);
-  height: 0;
-}
-
-pre + hr {
-  margin-top: 1.5em;
-}
-
-.meta {
-  margin-top: 1rem;
-  display: flex;
-  column-gap: 2em;
-  align-items: baseline;
-  justify-content: center;
-}
-
-header .date {
-  font-variant-east-asian: normal;
-  -moz-font-feature-settings: normal;
-  -webkit-font-feature-settings: normal;
-  font-feature-settings: normal;
-  font-size: 100%;
-
-  margin: 0;
-}
-
-.tags ul {
-  display: inline;
-  padding: 0;
-}
-
-.tags li {
-  list-style: none;
-  display: inline;
-}
-
-.tags li + li {
-  margin-left: 0.75ex;
-}
-
-.tag-list {
-  columns: 2;
-}
-
-.post-list .date, .tag-list .count {
-  font-size: 85%;
-}
-
-.post-list li + li {
-  margin-top: 0.5em;
-}
-
-.post-desc {
-  margin: 0;
-  font-size: small;
-  font-style: italic;
-  line-height: 125%;
-}
-
-.post-desc p {
-  margin: 0;
-}
-.post-desc p + p {
-  margin-top: .5em;
-}
-
-
-a {
-  color: inherit;
-  text-decoration: 2px dotted underline var(--accent);
-}
-a:hover { color: var(--accent); }
-
-b, strong {
-  font-weight: 600;
-}
-
-dfn {
-  font-style: normal;
-  font-weight: 500;
-}
-
-ul li { list-style: '❀ '; }
-.post-list li { list-style: '✒ '; }
-.tag-list li { list-style: ' '; } /* a tag in pragmatapro */
-
-table {
-  margin: auto;
-  border-spacing: 0;
-  border-top: 2px solid var(--fg);
-  border-bottom: 2px solid var(--fg);
-}
-
-th {
-  font-weight: 500;
-}
-
-thead th {
-  border-bottom: 1px solid var(--fg);
-}
-
-td, th {
-  padding: 0 0.5em;
-  vertical-align: text-bottom;
-}
-
-
-pre, code {
-  font-family: PragmataPro;
-  font-size:   inherit;
-}
-
-pre, :not(pre) > code {
-  background:  hsla(0deg 0% 100% / 40%);
-  border:      2px dotted color-mix(in srgb, currentcolor 75%, transparent);
-}
-
-@media (prefers-color-scheme: dark) {
-  pre, :not(pre) > code { background: hsla(0deg 0% 00% / 30%); }
-}
-
-:not(pre) > code { padding: 0 5px; white-space: nowrap; }
-
-pre {
-  clear:   both;
-  width:   min-content;
-  margin:  0.5em auto;
-  padding: 0.5em 0.8em;
-}
-
-
-.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;
-}
-
-.lang, .ebnf-t { font-weight: 500; }
-
-.abbr-list dt {
-  font-family: PragmataPro;
-  font-weight: normal;
-}
-
-.abbr, .abbr-list dt {
-  font-variant: all-small-caps;
-}
-
-.scr {
-  height: 1.5em;
-  vertical-align: -40%;
-}
-
-.gloss-split, .gloss-gloss {
-  font-size: 80%;
-}
-
-:is(.gloss, .bigscr) .scr {
-  height: 2em;
-}
-
-.hugescr .scr {
-  height: 4em;
-}
-
-: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%;
-}
-
-
-#toc > ul {
-  columns: 2;
-  margin-left:  2em;
-  margin-right: 2em;
-  > li { list-style: circled; }
-}
-
-figure img {
-  max-width: 100%;
-}
-figure:not(.nobg, .hasborder) img:not(.scr) {
-  border: 3px solid currentcolor;
-}
-figure.nobg a img {
-  /* fake outline */
-  filter:
-    drop-shadow(1px 1px 0 currentcolor)
-    drop-shadow(1px -1px 0 currentcolor)
-    drop-shadow(-1px 1px 0 currentcolor)
-    drop-shadow(-1px -1px 0 currentcolor)
-    drop-shadow(1px 0 0 currentcolor)
-    drop-shadow(-1px 0 0 currentcolor)
-    drop-shadow(0 1px 0 currentcolor)
-    drop-shadow(0 -1px 0 currentcolor) ;
-}
-figure.lightbg img {
-  background: var(--light);
-}
-figure:not(.left) {
-  text-align: center;
-}
-figure:not(.left) table {
-  display: inline-table;
-}
-.twocol-grid figure {
-  margin: 0;
-}
-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;
-}
-
-figcaption {
-  font-size: 80%;
-  line-height: 125%;
-  font-style: italic;
-  margin: 0 auto;
-  text-align: center;
-  text-wrap: balance;
-}
-figcaption em {
-  font-style: normal;
-}
-:not(.floating) > figcaption {
-  width: 75%;
-}
-
-dt {
-  font-weight: 500;
-  display: inline;
-  break-after: avoid;
-}
-
-dd {
-  margin-left: 1em;
-  display: inline;
-  break-before: avoid;
-}
-
-dd::after {
-  content: '';
-  display: block;
-}
-
-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;
-}
-
-.twocol-grid {
-  display: grid;
-  grid-template-columns: 1fr 1fr;
-  gap: 1em;
-  margin: 1em 0;
-}
-
-.twocol-grid .gloss {
-  margin-left: 0;
-}
-
-footer {
-  clear: both;
-  border-top: 2px dotted var(--fg);
-  margin: 1.5em auto 3em;
-
-  font-size: 80%;
-  font-weight: 500;
-  text-align: center;
-}
-
-footer ul::before {
-  margin: 0;
-  content: '➸ ';
-}
-
-footer ul::after {
-  margin: 0;
-  content: ' 🐌︎';
-}
-
-footer li {
-  display: inline;
-  list-style: none;
-}
-footer li + li::before { content: ' · '; }
-
-
-.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: 85%;
-  border-left: 1px solid black;
-  padding-left: 1em;
-  margin: auto;
-}
-
-
-.note, aside {
-  font-size: 90%;
-}
-
-.note {
-  font-style: italic;
-}
-
-.banner {
-  font-size: 125%;
-  font-weight: bolder;
-  text-align: center;
-  text-wrap: balance;
-}
-.banner p:first-child::before { content: '☛ '; }
-.banner p:last-child::after { content: ' ☚'; }
-
-:is(h1, h2) .note {
-  font-size: 75%;
-}
-
-aside {
-  margin: 0.5em 3em;
-}
-aside:not(.no-line) {
-  padding-left: 1em;
-  border-left: 2px solid var(--fg);
-}
-
-aside > details summary {
-  font-weight: 600;
-}
-
-aside :is(h1, h2, h3, h4, h5, h6) {
-  margin-top: 0.25em;
-  font-weight: 600;
-}
-aside h1 { font-size: 115%; }
-aside :is(h2, h3, h4, h5, h6) { font-size: 100%; }
-
-:is(aside, figure).floating {
-  max-width: 33%;
-}
-aside.floating {
-  padding: 0.25em 0.75em;
-  margin: 0.15em 1em 0;
-}
-figure.floating {
-  margin: 0 0.5em;
-}
-
-aside.floating :first-child { margin-top: 0; }
-aside.floating :last-child { margin-bottom: 0; }
-
-.kw { color: hsl(300deg, 60%,  30%); }
-.pp { color: hsl(343deg, 100%, 40%); /* font-weight: 500; */ }
-.dt { color: hsl(173deg, 100%, 24%); /* font-weight: 500; */ }
-.fu, .at { color: hsl(34deg,  100%, 30%); /* font-weight: 500; */ }
-.va { color: hsl(203deg, 100%, 30%); /* font-weight: 500; */ }
-.cf { color: hsl(276deg, 75%,  35%); /* font-weight: 500; */ }
-.op { color: hsl(220deg, 40%,  33%); }
-.co { color: hsl(221deg, 10%,  39%); /* font-style: italic; */ }
-.bu { color: hsl(120deg, 85%,  25%); }
-.st, .fl, .dv, .bn, .sc, .ss { color: hsl(143deg, 100%, 20%); }
-.wa { color: hsl(350deg, 80%,  25%); text-decoration: wavy 1.5px underline; }
-.al { color: hsl(350deg, 80%,  25%); }
-.cn { color: hsl(343deg, 100%, 30%); }
-
-@media (prefers-color-scheme: dark) {
-  .kw { color: hsl(300deg, 60%,  80%); }
-  .pp { color: hsl(343deg, 100%, 85%); /* font-weight: 500; */ }
-  .dt { color: hsl(193deg, 60%, 74%); /* font-weight: 500; */ }
-  .fu, .at { color: hsl(34deg,  100%, 70%); /* font-weight: 500; */ }
-  .va { color: hsl(203deg, 100%, 75%); /* font-weight: 500; */ }
-  .cf { color: hsl(276deg, 75%,  85%); /* font-weight: 500; */ }
-  .op { color: hsl(220deg, 40%,  70%); }
-  .co { color: hsl(221deg, 10%,  80%); /* font-style: italic; */ }
-  .bu { color: hsl(120deg, 55%,  75%); }
-  .st, .fl, .dv, .bn, .sc, .ss { color: hsl(143deg, 70%, 80%); }
-  .wa { color: hsl(350deg, 80%,  77%); text-decoration: wavy 1.5px underline; }
-  .al { color: hsl(350deg, 80%,  77%); }
-  .cn { color: hsl(343deg, 80%, 70%); }
-}
-
-.floating {
-  float: right;
-  margin: 0.5em 1em 0.5em 2em;
-}
-
-.floating.left {
-  float: left;
-  margin: 0.5em 2em 0.5em 1em;
-}
-
-.shaped {
-  /* maybe one day... */
-  /* shape-outside: attr(src url); */
-  shape-margin: 1em;
-}
-
-.shadow { filter: drop-shadow(5px 5px 8px #0006); }
-
-.pixel {
-  image-rendering: crisp-edges;
-  image-rendering: pixelated;
-}
-
-.emoji {
-  height: 1em;
-  width:  1em;
-  vertical-align: -0.1em;
-}
-
-.bigemoji {
-  height: 2em;
-  width:  2em;
-  vertical-align: -0.2em;
-}
-
-.emojiseq { white-space: nowrap; }
-
-
-.citation {
-  font-size: 90%;
-}
-
-#refs {
-  margin-top: 0.75em;
-}
-.csl-entry {
-  margin-left: 2em;
-  text-indent: -2em;
-}
-/*
-.csl-entry {
-  display: grid;
-  grid-template-columns: 4ch auto;
-  grid-gap: 1ex;
-}
-.csl-left-margin {
-  justify-self: end;
-}
-*/
-
-math[display=block] {
-  border: 2px dotted var(--fg);
-
-  padding: 1em 3em;
-  margin: auto;
-  width: min-content;
-
-}
-.rulebox math[display=block] {
-  border: none;
-  padding: 0;
-}
-
-.texdefs {
-  display: none;
-}
-
-.rulebox {
-  float: right;
-  border: 1px solid var(--fg);
-  background: #ffffff66;
-  padding: .4em 1.2em;
-  margin-left: 3em;
-}
-
-/* the last thing in the :is is for priority fuckery */
-.rulebox :is(p, .math, mjx-container, #asd) {
-  margin: 0;
-  padding: 0;
-}
-
-.clear { clear: both; }
-
-mark {
-  mix-blend-mode: multiply;
-  background: #fbc;
-  padding: 0 0.35ch;
-}
-
-.light {
-  background: var(--light);
-  color: var(--dark);
-}
diff --git a/style/page.scss b/style/page.scss
new file mode 100644
index 0000000..dc76bd8
--- /dev/null
+++ b/style/page.scss
@@ -0,0 +1,617 @@
+@use 'sass:math';
+@use 'colours' as *;
+@use 'fonts' as *;
+
+@import url(counters.css);
+
+
+@layer colordefs {
+  @property --base-hue {
+    syntax: "<number>";
+    inherits: true;
+    initial-value: 336;
+  }
+
+  @include color-var(--fg, $darker, $light);
+  @include color-var(--bg, $light, $darker);
+  @include color-var(--counter-fg, $lighter, $lighter);
+  @include color-var(--counter-bg, $dark, $mid);
+  @include color-var(--accent, $dark-accent, $light-accent);
+  @include color-var(--counter-accent, $light-accent, $dark-accent);
+  @include color-var(--box-base, $light, $mid);
+  @include color-var(--box-fg, $darker, $lighter);
+  @include color-var(--box-shadow, halfalpha($mid));
+
+  :root {
+    --gradient: linear-gradient(
+      120deg in oklch,
+      oklch(93% 27.1% 96deg),
+      oklch(84.03% 22% 15deg),
+      oklch(80% 29.3% 303deg),
+      oklch(84% 23% 233deg),
+      oklch(89% 25% 161deg)
+    );
+
+    @media (prefers-color-scheme: dark) {
+      // todo oklch
+      --gradient:
+        linear-gradient(20deg in oklch,
+          hsl(300deg 30% 20%),
+          hsl(220deg 30% 20%),
+          hsl(150deg 30% 20%),
+          hsl(30deg  30% 20%),
+          hsl(350deg 30% 20%));
+    }
+  }
+}
+
+
+@mixin boxy-margin { margin: 1rem 0; }
+
+@mixin boxy-shadow($dst: 0.5rem) {
+  box-shadow: 0.5rem 0.5rem 0 var(--box-shadow);
+}
+
+@function border-style($fg: var(--fg)) { @return 3px solid $fg; }
+
+@mixin boxy-unpadded($bg, $fg: var(--box-fg), $border: $fg, $dst: 0.5em)
+{
+  color:      $fg;
+  background: $bg;
+  border:     border-style($border);
+  @include boxy-shadow;
+}
+
+@mixin boxy($bg, $clear: true, $args...) {
+  @include boxy-unpadded($bg, $args...);
+  @include boxy-margin;
+  padding:  0.5rem 0.8rem;
+  @if $clear {
+    clear: both;
+  } @else {
+    overflow: hidden; // makes the box dodge floats?????? idk why
+  }
+  :first-child { margin-top:    0; }
+  :last-child  { margin-bottom: 0; }
+}
+
+@mixin fake-border {
+  border: none;
+  filter:
+    drop-shadow(1px 1px 0 currentcolor)
+    drop-shadow(1px -1px 0 currentcolor)
+    drop-shadow(-1px 1px 0 currentcolor)
+    drop-shadow(-1px -1px 0 currentcolor)
+    drop-shadow(1px 0 0 currentcolor)
+    drop-shadow(-1px 0 0 currentcolor)
+    drop-shadow(0 1px 0 currentcolor)
+    drop-shadow(0 -1px 0 currentcolor);
+}
+
+@mixin decorations($before, $after: $before, $size: null, $color: null) {
+  &::before, &::after {
+    @include symbol-font;
+    font-style:  normal;
+    @if $size  { font-size: $size; }
+    @if $color { color: $color;    }
+    margin: 0 0.5rem;
+  }
+  &::before { content: $before; }
+  &::after  { content: $after;  }
+}
+
+
+@mixin inline-lists {
+  ul, menu, li { display: inline; }
+  ul, menu { padding-left: 0; }
+  li       { list-style: none;    }
+  li + li  { margin-left: 0.75ex; }
+}
+
+@mixin center {
+  text-align: center;
+  text-wrap: balance;
+}
+
+
+@layer outer {
+  * { box-sizing: border-box; }
+
+  :root {
+    background: var(--gradient) fixed;
+    color:      var(--fg);
+
+    @include main-font;
+    font-size: $body-size;
+    line-height: 150%;
+  }
+
+  body {
+    @include boxy-unpadded(var(--bg));
+    margin: 2em auto;
+
+    $darkbg: oklch(from var(--bg) calc(l * 0.9) calc(c * 1.25) h);
+    background: linear-gradient(to bottom in oklch, var(--bg), $darkbg);
+
+    width: min(54rem, 85vw);
+  }
+
+  main {
+    margin: 0 2rem;
+  }
+}
+
+@layer header {
+  header {
+    @include inverted;
+    @include inline-lists;
+
+    text-align: center;
+    text-wrap: balance;
+    padding: 1em;
+
+    h1 {
+      @include decorations('✿', $size: 60%);
+      @include heading-size(0);
+      @include heading-font;
+      font-weight: $main-heading-weight;
+      font-style: italic;
+      letter-spacing: 0.1ch;
+      margin: { top: 0; bottom: 0.5rem; }
+
+      em { font-weight: $normal-weight; }
+    }
+
+    .meta {
+      @include small-font;
+      display:         flex;
+      gap:             2em;
+      justify-content: center;
+
+      * { display: inline; }
+    }
+  }
+}
+
+@layer footer {
+  footer {
+    @include inverted(0.75rem);
+    @include small-font;
+    @include inline-lists;
+
+    clear: both;
+    margin: 2rem 0 0 0;
+
+    display: flex;
+    justify-content: center;
+
+    menu { @include decorations('➸', ''); }
+  }
+}
+
+@layer headings {
+  main {
+    h1, h2, h3, h4, h5, h6 { @include heading-font; }
+
+    h1 {
+      @include inverted;
+      @include boxy-margin;
+      @include boxy-shadow;
+      @include heading-size(1);
+      letter-spacing: 0.075ch;
+      clear: both;
+    }
+
+    h2 { @include heading-size(2); }
+    h3 { @include heading-size(3); }
+    h4, h5, h6 { font-size: 1rem; }
+
+    h2, h3, h4, h5, h6 {
+      padding: 0.25rem 0.75rem;
+      border-bottom: border-style();
+      margin-bottom: 1rem;
+    }
+  }
+}
+
+@layer links {
+  a {
+    color: inherit;
+    text-decoration: 2px solid underline var(--accent);
+    text-underline-offset: 0.175em;
+    &:hover { color: var(--accent); }
+  }
+}
+
+@layer banner {
+  .banner {
+    @include boxy(var(--accent), $fg: var(--banner-fg),
+                  $border: var(--banner-border));
+    @include decorations('☛', '☚');
+    @include heading-size(2);
+
+    font-weight: $boldbold-weight;
+    width:       fit-content;
+    margin:      auto;
+
+    p { display: inline; }
+
+    &:has(a) {
+      background: var(--accent);
+      a { text-decoration: none; }
+      a:hover { color: var(--counter-accent); }
+    }
+
+    --banner-fg:     #{$lighter};
+    --banner-border: #{$dark};
+    @media (prefers-color-scheme: dark) {
+      --banner-fg:     #{$darker};
+      --banner-border: #{$lighter};
+    }
+  }
+}
+
+
+@layer code {
+  pre {
+    @include code-font;
+    font-size: 1rem * $code-font-scale;
+  }
+
+  :where(:not(pre)) > code {
+    @include code-font;
+    white-space: nowrap;
+  }
+
+  /* syntax highlighting */
+  .kw { color: hsl(300deg, 60%,  30%); }
+  .pp { color: hsl(343deg, 100%, 40%); }
+  .dt { color: hsl(173deg, 100%, 24%); }
+  .fu, .at { color: hsl(34deg,  100%, 30%); }
+  .va { color: hsl(203deg, 100%, 30%); }
+  .cf { color: hsl(276deg, 75%,  35%); }
+  .op { color: hsl(220deg, 40%,  33%); }
+  .co { color: hsl(221deg, 10%,  39%); }
+  .bu { color: hsl(120deg, 85%,  25%); }
+  .st, .fl, .dv, .bn, .sc, .ss { color: hsl(143deg, 100%, 20%); }
+  .wa { color: hsl(350deg, 80%,  25%); text-decoration: wavy 1.5px underline; }
+  .al { color: hsl(350deg, 80%,  25%); }
+  .cn { color: hsl(343deg, 100%, 30%); }
+
+  @media (prefers-color-scheme: dark) {
+    .kw { color: hsl(300deg, 60%,  80%); }
+    .pp { color: hsl(343deg, 100%, 85%); }
+    .dt { color: hsl(193deg, 60%, 74%); }
+    .fu, .at { color: hsl(34deg,  100%, 70%); }
+    .va { color: hsl(203deg, 100%, 75%); }
+    .cf { color: hsl(276deg, 75%,  85%); }
+    .op { color: hsl(220deg, 40%,  70%); }
+    .co { color: hsl(221deg, 10%,  80%); }
+    .bu { color: hsl(120deg, 55%,  75%); }
+    .st, .fl, .dv, .bn, .sc, .ss { color: hsl(143deg, 70%, 80%); }
+    .wa { color: hsl(350deg, 80%,  77%); text-decoration: wavy 1.5px underline; }
+    .al { color: hsl(350deg, 80%,  77%); }
+    .cn { color: hsl(343deg, 80%, 70%); }
+  }
+}
+
+
+
+@layer figures {
+  figure {
+    img { max-width: 100%; }
+
+    &:where(:not(.nobg, .hasborder)) {
+      a       img { border: border-style(); }
+      a:hover img { border: border-style(var(--accent)); }
+    }
+
+    &.nobg a img { @include fake-border; }
+    &.lightbg img { background: var(--light); }
+  }
+
+  figcaption {
+    @include note-font;
+    @include center;
+
+    em { font-style: normal; }
+  }
+
+  :not(.floating) > figcaption { width: 75%; }
+}
+
+
+@layer tables {
+  table {
+    margin: auto;
+    border-spacing: 0;
+    border: { top: border-style(); bottom: border-style(); }
+  }
+
+  th { font-weight: $bold-weight; }
+
+  thead th {
+    border-bottom: border-style(halfalpha(var(--fg)));
+  }
+
+  td, th {
+    padding: 0 0.5em;
+    vertical-align: text-bottom;
+  }
+}
+
+
+@layer boxes {
+  pre, aside, blockquote {
+    $hue: calc(h + var(--hue-nudge));
+    --box-bg: oklch(from var(--box-base) var(--luma) c #{$hue});
+    @include boxy(var(--box-bg), $clear: false);
+
+    --luma: calc(l * 1.05);
+    @media (prefers-color-scheme: dark) { --luma: calc(l / 1.05); }
+  }
+
+  :is(pre, .sourceCode, aside, blockquote) {
+    & + & { margin-top: 1.5em; }
+  }
+
+  pre {
+    --hue-nudge: 60;
+  }
+
+  aside {
+    @include note-font;
+    --hue-nudge: -80;
+  }
+
+  blockquote {
+    @include note-font;
+    --hue-nudge: 180;
+  }
+
+  .light {
+    @include apply-colors($fg: $darker, $bg: $lighter);
+  }
+}
+
+
+@layer conlangs {
+  .ipa { @include ipa-font; }
+
+  .lang, .ebnf-t {
+    @include ipa-font;
+    font-weight: 700;
+    font-variation-settings: "ENLA" 25;
+      /* ENLA (increased x-height) not defined for IPA characters */
+  }
+
+  .scr {
+    vertical-align: -40%;
+    height: 1.5em;
+
+    :is(.gloss, .bigscr) & { height: 2em; }
+    .hugescr & { height: 4em; }
+  }
+
+  .gloss {
+    border: none;
+    text-align: left;
+    & + & { margin-top: 2em; }
+  }
+
+  .gloss-split, .gloss-gloss { @include small-font; }
+
+  .glosses.left .gloss { margin-left: 0; }
+
+  .abbr-list {
+    dl {
+      display: grid;
+      grid-template-columns: 1fr 8fr 1fr 8fr;
+    }
+    dt { @extend .abbr; }
+  }
+
+}
+
+
+@layer spans {
+
+  dfn, b, strong, dt { font-weight: $bold-weight; }
+
+  dfn { font-style: normal; }
+
+  abbr, .abbr { font-variant: all-small-caps; }
+
+  u u { text-decoration: double underline; }
+
+  .note { @include note-font; }
+  :is(h1, h2) .note { font-size: 75%; }
+
+  mark {
+    --mark-hue: calc(var(--hue) + 90)
+    padding: 0 0.35ch;
+    mix-blend-mode: multiply;
+    background: oklch(95% 40% var(--mark-hue) / 30%);
+
+    @media (prefers-color-scheme: dark) {
+      mix-blend-mode: screen;
+    }
+  }
+
+}
+
+@layer lists {
+  :not(ol) > li {
+    list-style: '☀ ';
+
+    &::marker {
+      @include symbol-font;
+      font-size: 80%;
+    }
+  }
+
+}
+
+
+@layer dl {
+
+  dt {
+    font-weight: bold;
+    display: inline;
+    break-after: avoid;
+  }
+
+  dd {
+    margin-left: 1em;
+    display: inline;
+    break-before: avoid;
+  }
+
+  dd::after {
+    content: '';
+    display: block;
+  }
+
+}
+
+@layer twocol {
+  .twocol {
+    columns: 2;
+    > :first-child { margin-top: 0; }
+  }
+
+  .twocol-grid {
+    display:               grid;
+    grid-template-columns: 1fr 1fr;
+    gap:                   1em;
+  }
+}
+
+@layer ebnf {
+
+  .ebnf {
+    border: none;
+  }
+
+  .ebnf td {
+    padding: 0 0.15em;
+  }
+
+  .ebnf-nt {
+    font-weight: bold;
+    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: bold;
+  }
+
+  .ebnf-brack {
+    padding: 0 0.05em;
+  }
+
+  .ebnf-s {
+    font-style: italic;
+    color: hsl(330deg, 80%, 30%);
+  }
+}
+
+
+@layer floats {
+  .floating {
+    max-width: 33%;
+    margin: 0.5em 1em;
+
+    &:not(.left) {
+      float: right;
+      margin-right: 0;
+    }
+
+    &.left {
+      float: left;
+      margin-left: 0;
+    }
+  }
+
+  .shaped { shape-margin: 1em; }
+}
+
+
+@layer images {
+
+  .pixel {
+    image-rendering: crisp-edges;
+    image-rendering: pixelated;
+  }
+
+  .emoji {
+    height: 1em;
+    width:  1em;
+    vertical-align: -0.1em;
+  }
+
+  .bigemoji {
+    height: 2em;
+    width:  2em;
+    vertical-align: -0.2em;
+  }
+
+  .emojiseq { white-space: nowrap; }
+}
+
+@layer citations {
+
+.citation {
+  font-size: 90%;
+}
+
+#refs {
+  margin-top: 0.75em;
+}
+.csl-entry {
+  margin-left: 2em;
+  text-indent: -2em;
+}
+
+}
+
+@layer math {
+
+  math[display=block] {
+    border: 2px solid var(--fg);
+
+    padding: 1em 3em;
+    margin: auto;
+    width: min-content;
+
+  }
+  .rulebox math[display=block] {
+    border: none;
+    padding: 0;
+  }
+
+  .texdefs {
+    display: none;
+  }
+
+  .rulebox {
+    float: right;
+    border: 1px solid var(--fg);
+    background: #ffffff66;
+    padding: .4em 1.2em;
+    margin-left: 3em;
+
+    // "#asd" for specificity
+    :is(p, .math, mjx-container, #asd) {
+      margin: 0;
+      padding: 0;
+    }
+  }
+
+}
diff --git a/style/paper.png b/style/paper.png
deleted file mode 100644
index 5977961..0000000
Binary files a/style/paper.png and /dev/null differ
diff --git a/templates/foot.html b/templates/foot.html
index 2af6f48..63b964e 100644
--- a/templates/foot.html
+++ b/templates/foot.html
@@ -1,10 +1,10 @@
 $if(hide-footer)$$else$
 <footer>
-  <ul>
+  <menu>
     <li><a href=/index.html>all posts</a>
     <li><a href=/all-tags.html>all tags</a>
     <li><a href=/rss.xml>rss</a>
-  </ul>
+  </menu>
 </footer>
 $endif$
 
diff --git a/templates/head.html b/templates/head.html
index 7e6b37c..2cbac9f 100644
--- a/templates/head.html
+++ b/templates/head.html
@@ -4,11 +4,16 @@
 <meta name=viewport content="width=device-width, initial-scale=1">
 
 <link rel=stylesheet href=/style/page.css>
+$if(meta)$
+<link rel=stylesheet href=/style/meta.css>
+$endif$
 $for(css)$
 <link rel=stylesheet href=/style/$css$>
 $endfor$
 <link rel=alternate href=/rss.xml type=application/rss+xml>
 
+<script src=/script/hue.js type=module></script>
+
 $for(header-includes)$
 $header-includes$
 $endfor$
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..309f42d
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,9 @@
+{
+  "compilerOptions": {
+    "strict": true,
+    "noUncheckedIndexedAccess": true,
+    "noEmitOnError": true,
+    "lib": ["ES2021", "dom"],
+    "target": "ES2015"
+  }
+}