This page explains how the default stylesheet used by all pages on this site is defined. It is Haskell code using Clay to generate the CSS stylesheet.
Preamble
As with all Haskell applications, it starts out with defining the main module and the modules it imports.
module Main (main) where
import Clay
import Clay.Media qualified as Media
import Web.Site.Styles (narrowWidth, wideWidth)
A few function names from the Prelude module conflict with HTML element names or CSS keywords. It is more convenient to use the HTML or CSS names directly, so we hide the conflicting names from the Prelude.
import Prelude hiding (div, filter, not, (**))
And of course, we should output our generated CSS stylesheet.
main :: IO ()
= putCss defaultStyle main
Everything
Next, we define the high-level function which calls all the other functons generating the stylesheet.
defaultStyle :: Css
= do defaultStyle
We want the generic styles to be generated first, because they should be overridden by later styles in specific contexts.
genericStyle
We then generate the styles which are only relevant to specific contexts.
codeStyle
articleStyle
tableOfContents figures
Finally, the media-specific styles should be generated last. They may need to override previously generated styles so that they are more appropriate to specific media such as narrower screens.
mediaStyles
Generic styles
These are styles which apply generally. In other words, if there are no styles which need to apply more specifically in a certain context, these are the styles which are applied.
genericStyle :: Css
= do genericStyle
I basically chose the default fonts based on my arbitrary preferences. The only condition I had was that they are expected to be available in any browser. Legibility also takes precedence over anything else such as looking prettier. I can’t stand jagged edges in text, so I specify that text be justified.
? do
html "Georgia", "Garamond"] [serif, sansSerif, monospace]
fontFamily [
textRendering optimizeLegibility
textAlign justify$ unitless 1.25 lineHeight
In narrow screens, it often feels like there is a great waste of empty space without words being hyphenated as appropriate, so automatic hyphenation is enabled. I think hyphenation is fine in general, so I don’t restrict its use to only narrow screens.
hyphens auto
The rest of the generic styles were basically tweaked by me until they achieved a minimal level of acceptability. Unexpectedly, I ended up liking the minimal look, so I will probably stick with it.
? do
body $ em 2
marginTop $ pct 10
marginLeft $ pct 10
marginRight $ em 2
marginBottom
headings
? do
footer "Courier New"] [monospace, sansSerif]
fontFamily [$ em 0.75
fontSize $ em 1
marginTop
|> nav ? do
footer $ em 1.5
paddingTop ? paddingRight (em 1)
a
? do
dt
fontWeight bold$ em 0.25
marginBottom
? do
dd $ em 1 marginBottom
One thing to note is that I want some spacing between list items. So there is a margin between each list item. There is only an extra margin for a list if it is a direct child of a list item, since there would be extra margins from other block elements if the list was not a direct child.
|+ li ? marginTop (em 0.75)
li
|> (ul <> ol) ? marginTop (em 0.75) li
Heading styles
For the sole reason that I use monospace fonts with code, I have the headings in a monospace font as well. The headings are not justified.
Except for the first-level heading which basically serves as the displayed title for the page, I add dotted underlines to distinguish the headings from normal text. Different levels of headings are distinguished by different sizes; the different sizes may be a bit too subtle, but I’m not sure what to do about it. At least the heading levels will be obvious in the table of contents.
Later, heading levels will also be distinguished by fading colors, when the colors are specified for each preferred color scheme for specific media.
headings :: Css
= do
headings <> h2 <> h3 <> h4 <> h5 <> h6 ? do
h1 "Courier New"] [monospace, sansSerif]
fontFamily [$ alignSide sideLeft
textAlign
? do
h1 $ em 2
fontSize
fontStyle italic
<> h3 <> h4 <> h5 <> h6 ? do
h2
textDecorationLine underline
textDecorationStyle dotted
? fontSize (em 1.8)
h2 ? fontSize (em 1.5)
h3 ? fontSize (em 1.25)
h4 ? fontSize (em 1.1) h5
Content-specific styles
Code
Style for code snippets.
codeStyle :: Css
= do
codeStyle div # ".sourceCode" ? do
borderStyle solid$ px 1
borderWidth $ em 1
marginRight $ em 1
marginLeft $ em 0.5 sym padding
Articles
Style for article
elements.
articleStyle :: Css
= do
articleStyle |> section # ".byline" ? do
article "Verdana"] [sansSerif, serif, monospace]
fontFamily [$ em 0.7
fontSize ? do
p $ em 0.2
marginTop $ em 0.2 marginBottom
Table of contents
Style for table of contents.
tableOfContents :: Css
= do
tableOfContents # ".toc" ? do
nav $ em 1
marginTop $ em 1
marginBottom $ em 1
sym padding
borderStyle solid$ px 1
borderWidth
? do
h2 "Georgia", "Garamond"] [serif, sansSerif, monospace]
fontFamily [$ em 1.2
fontSize
fontStyle normal
fontWeight bold
textDecorationLine none
textDecorationStyle none$ em 0.1
marginTop $ em 0.25
marginBottom
<> (ul ** (ul <> li)) ? do
ul $ em 0.75
paddingLeft $ em 0.1
marginTop $ em 0.1
marginBottom listStyleType none
Images
Style for images. One thing to note is that we want images to fit well with everything else. The maximum width is limited to the surrounding content width. The maximum height is limited to 60% of the display area so that some content before and after an image can also be visible, which in some cases would make it easier to keep track of what the image is supposed to explain.
figures :: Css
= do
figures ? do
figure
display block$ em 0.5
paddingTop $ em 0.5
paddingBottom $ em 1
marginTop $ em 1
marginBottom
marginLeft auto
marginRight auto
textAlign center
? do
img
display block
marginLeft auto
marginRight auto$ pct 95
maxWidth $ vh 60
maxHeight
? do
figcaption
display block"Verdana"] [sansSerif, serif, monospace]
fontFamily [$ em 0.9
fontSize $ em 0.5
marginTop
marginLeft auto marginRight auto
Media-specific styles
If the width is too narrow, we reduce the margins and paddings. Conversely, if the width is too wide, we limit the width of a page so that we do not have horrendously wide walls of text. We also set the colors depending on whether the preferred color scheme is light mode or dark mode.
mediaStyles :: Css
= do
mediaStyles $ do
query Media.all [Media.maxWidth narrowWidth] ? sym margin (em 1)
body
<> ol ? do
ul $ em 0.5
marginLeft $ em 0.5
paddingLeft
$ do
query Media.all [Media.minWidth wideWidth] ? do
body
width wideWidth
marginRight auto
marginLeft auto
footerPosition
query Media.all [Media.prefersColorScheme Media.light] lightColorScheme
query Media.all [Media.prefersColorScheme Media.dark] darkColorScheme
We define this function to map each heading level to a color, which makes it convenient to define heading colors when setting the colors programmatically based on the heading level.
headingColors :: (Integer -> Color) -> Css
= do
headingColors mapColor ? fontColor (mapColor 1)
h1 ? fontColor (mapColor 2)
h2 ? fontColor (mapColor 3)
h3 ? fontColor (mapColor 4)
h4 ? fontColor (mapColor 5)
h5 ? fontColor (mapColor 6) h6
If the screen height is large enough so that it would not occupy more than half of the screen, I would like to keep the footer stuck to the bottom. To prevent other content from overlapping with the footer, we will also later explicitly set the background color for the footer.
footerPosition :: Css
= do
footerPosition $ em 24] $ do
query Media.all [Media.minHeight ? do
footer
position sticky
bottom nil$ em 0.5 paddingBottom
Light mode
Color scheme to use in light mode.
lightColorScheme :: Css
= do
lightColorScheme ? do
html
color black
backgroundColor white
? do
footer
backgroundColor white1) solid black
borderTop (px
# link ? color blue
a # visited ? color purple
a
headingColors headingColor
# ".toc" ? do
nav
borderColor lightgrey$ rgb 240 240 240
backgroundColor
div # ".sourceCode" ? borderColor lightgrey
where
= rgb (n * 20) (n * 20) (100 + n * 10) headingColor n
Dark mode
Color scheme to use in dark mode.
darkColorScheme :: Css
= do
darkColorScheme ? do
html
color white
backgroundColor black
? do
footer
backgroundColor black1) solid white
borderTop (px
# link ? color cyan
a # visited ? color pink
a
headingColors headingColor
# ".toc" ? do
nav
borderColor dimgrey$ rgb 20 20 20
backgroundColor
|> img # (not (byClass "keep-colors") <> "src" $= ".svg") ? do
figure filter (invert $ pct 100)
div # ".sourceCode" ? borderColor dimgrey
where
= rgb (255 - n * 20) (255 - n * 20) (155 - n * 10) headingColor n