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 ()
main = putCss defaultStyleEverything
Next, we define the high-level function which calls all the other functons generating the stylesheet.
defaultStyle :: Css
defaultStyle = doWe want the generic styles to be generated first, because they should be overridden by later styles in specific contexts.
genericStyleWe then generate the styles which are only relevant to specific contexts.
codeStyle
articleStyle
tableOfContents
figuresFinally, 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.
mediaStylesGeneric 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
genericStyle = doI 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.
html ? do
fontFamily ["Georgia", "Garamond"] [serif, sansSerif, monospace]
textRendering optimizeLegibility
textAlign justify
lineHeight $ unitless 1.5In 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 autoThe 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.
body ? do
marginTop $ em 2
marginLeft $ pct 10
marginRight $ pct 10
marginBottom $ em 2
headings
footer ? do
fontFamily ["Courier New"] [monospace, sansSerif]
fontSize $ em 0.75
marginTop $ em 1
footer |> nav ? do
paddingTop $ em 1.5
a ? paddingRight (em 1)
dt ? do
fontWeight bold
marginBottom $ em 0.25
dd ? do
marginBottom $ em 1One 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 |+ li ? marginTop (em 0.75)
li |> (ul <> ol) ? marginTop (em 0.75)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
headings = do
h1 <> h2 <> h3 <> h4 <> h5 <> h6 ? do
fontFamily ["Courier New"] [monospace, sansSerif]
textAlign $ alignSide sideLeft
h1 ? do
fontSize $ em 2
fontStyle italic
h2 <> h3 <> h4 <> h5 <> h6 ? do
textDecorationLine underline
textDecorationStyle dotted
h2 ? fontSize (em 1.8)
h3 ? fontSize (em 1.5)
h4 ? fontSize (em 1.25)
h5 ? fontSize (em 1.1)Content-specific styles
Code
Style for code snippets.
codeStyle :: Css
codeStyle = do
div # ".sourceCode" ? do
borderStyle solid
borderWidth $ px 1
marginRight $ em 1
marginLeft $ em 1
sym padding $ em 0.5Articles
Style for article elements.
articleStyle :: Css
articleStyle = do
article |> section # ".byline" ? do
fontFamily ["Verdana"] [sansSerif, serif, monospace]
fontSize $ em 0.7
p ? do
marginTop $ em 0.2
marginBottom $ em 0.2Table of contents
Style for table of contents.
tableOfContents :: Css
tableOfContents = do
nav # ".toc" ? do
marginTop $ em 1
marginBottom $ em 1
sym padding $ em 1
borderStyle solid
borderWidth $ px 1
h2 ? do
fontFamily ["Georgia", "Garamond"] [serif, sansSerif, monospace]
fontSize $ em 1.2
fontStyle normal
fontWeight bold
textDecorationLine none
textDecorationStyle none
marginTop $ em 0.1
marginBottom $ em 0.25
ul <> (ul ** (ul <> li)) ? do
paddingLeft $ em 0.75
marginTop $ em 0.1
marginBottom $ em 0.1
listStyleType noneImages
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
figures = do
figure ? do
display block
paddingTop $ em 0.5
paddingBottom $ em 0.5
marginTop $ em 1
marginBottom $ em 1
marginLeft auto
marginRight auto
textAlign center
img ? do
display block
marginLeft auto
marginRight auto
maxWidth $ pct 95
maxHeight $ vh 60
figcaption ? do
display block
fontFamily ["Verdana"] [sansSerif, serif, monospace]
fontSize $ em 0.9
marginTop $ em 0.5
marginLeft auto
marginRight autoMedia-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
mediaStyles = do
query Media.all [Media.maxWidth narrowWidth] $ do
body ? sym margin (em 1)
ul <> ol ? do
marginLeft $ em 0.5
paddingLeft $ em 0.5
query Media.all [Media.minWidth wideWidth] $ do
body ? do
width wideWidth
marginRight auto
marginLeft auto
footerPosition
query Media.all [Media.prefersColorScheme Media.light] lightColorScheme
query Media.all [Media.prefersColorScheme Media.dark] darkColorSchemeWe 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
headingColors mapColor = do
h1 ? fontColor (mapColor 1)
h2 ? fontColor (mapColor 2)
h3 ? fontColor (mapColor 3)
h4 ? fontColor (mapColor 4)
h5 ? fontColor (mapColor 5)
h6 ? fontColor (mapColor 6)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
footerPosition = do
query Media.all [Media.minHeight $ em 24] $ do
footer ? do
position sticky
bottom nil
paddingBottom $ em 0.5Light mode
Color scheme to use in light mode.
lightColorScheme :: Css
lightColorScheme = do
html ? do
color black
backgroundColor white
footer ? do
backgroundColor white
borderTop (px 1) solid black
a # link ? color blue
a # visited ? color purple
headingColors headingColor
nav # ".toc" ? do
borderColor lightgrey
backgroundColor $ rgb 240 240 240
div # ".sourceCode" ? borderColor lightgrey
where
headingColor n = rgb (n * 20) (n * 20) (100 + n * 10)Dark mode
Color scheme to use in dark mode.
darkColorScheme :: Css
darkColorScheme = do
html ? do
color white
backgroundColor black
footer ? do
backgroundColor black
borderTop (px 1) solid white
a # link ? color cyan
a # visited ? color pink
headingColors headingColor
nav # ".toc" ? do
borderColor dimgrey
backgroundColor $ rgb 20 20 20
figure |> img # (not (byClass "keep-colors") <> "src" $= ".svg") ? do
filter (invert $ pct 100)
div # ".sourceCode" ? borderColor dimgrey
where
headingColor n = rgb (255 - n * 20) (255 - n * 20) (155 - n * 10)