/* Based off a modified minima jekyll theme */

/* Breakpoints, in em so they track the user's font size. Custom properties
   cannot be used in media queries, so the values are repeated inline; this
   is the list of what each one means.
     30em   (480px)  bio has room for photo and paragraph side by side
     37.5em (600px)  phone: narrower gutter, list rows stack their date
     50em   (800px)  headings and post titles step down a size
     70em  (1120px)  margin wide enough for the bio photo to hang outside
                     the content column */

/* Every colour on the site resolves through these, so the dark palette below
   is the only place that has to change. Shipping a real dark theme also stops
   extensions like Dark Reader from filtering the page, which mangles the card
   cover images. */
:root {
  color-scheme: light;

  --bg: #fdfdfd;
  --text: #111;
  --text-muted: #6b6b6b;
  --heading: #424242;

  --link: #1f68c9;
  --link-visited: #1756a9;
  --link-hover: #111;

  --border: #e8e8e8;
  --border-strong: #dedede;
  --border-stronger: #c9c9c9;

  --code-bg: #eef;
  --table-text: #3f3f3f;
  --table-stripe: #f7f7f7;
  --table-head-bg: #f0f0f0;

  --icon: #828282;

  /* The lavender that gives the cards and post covers their character. */
  --wash-top: hsla(255, 60%, 95%, 1);
  --wash-bottom: hsla(255, 50%, 90%, 0.3);
  --cover-top: hsla(255, 60%, 95%, 1);
  --cover-bottom: hsla(255, 50%, 90%, 0.4);

  --toggle-track: hsl(255, 35%, 96%);
  --toggle-border: hsl(255, 30%, 90%);
  --toggle-idle: hsl(255, 8%, 45%);
  --toggle-hover: hsl(255, 10%, 25%);
  --toggle-active: hsl(255, 15%, 20%);
  --toggle-active-bg: #fff;
  --toggle-shadow: rgba(40, 30, 80, 0.16);

  --focus-ring: hsl(255, 45%, 65%);
  --card-shadow: rgba(0, 0, 0, 0.2);
  --card-shadow-tight: rgba(0, 0, 0, 0.1);

  /* Page gutter, narrowed on small screens just below. */
  --gutter-size: 1.875rem;
}

@media (prefers-color-scheme: dark) {
  :root {
    color-scheme: dark;

    --bg: #14141a;
    --text: #e6e4ee;
    --text-muted: #a3a0b0;
    --heading: #d8d5e4;

    --link: #8ab4f8;
    --link-visited: #b39ddb;
    --link-hover: #fff;

    --border: #2c2a36;
    --border-strong: #3a3746;
    --border-stronger: #4a4658;

    --code-bg: #1e1c28;
    --table-text: #d2cfdd;
    --table-stripe: #1a1922;
    --table-head-bg: #22202c;

    --icon: #8b879a;

    /* Same hue, inverted lightness, so covers are darkened rather than
       washed out and the cards stay recognisably lavender. */
    --wash-top: hsla(255, 28%, 20%, 1);
    --wash-bottom: hsla(255, 32%, 14%, 0.25);
    --cover-top: hsla(255, 28%, 18%, 0.92);
    --cover-bottom: hsla(255, 32%, 12%, 0.55);

    --toggle-track: hsl(255, 18%, 16%);
    --toggle-border: hsl(255, 15%, 26%);
    --toggle-idle: hsl(255, 10%, 62%);
    --toggle-hover: hsl(255, 15%, 82%);
    --toggle-active: hsl(255, 20%, 93%);
    --toggle-active-bg: hsl(255, 15%, 27%);
    --toggle-shadow: rgba(0, 0, 0, 0.45);

    --focus-ring: hsl(255, 55%, 72%);
    --card-shadow: rgba(0, 0, 0, 0.55);
    --card-shadow-tight: rgba(0, 0, 0, 0.35);
  }
}

@media screen and (max-width: 37.5em) {
  :root {
    --gutter-size: 0.9375rem;
  }
}

* {
  box-sizing: border-box;
}

html {
  /* The list view is short enough to remove the scrollbar that the cards
     need, and its appearing and disappearing shifts the whole page sideways
     on every toggle. Reserving the gutter keeps the width constant. */
  scrollbar-gutter: stable;
}

/** Reset some basic elements */
body, h1, h2, h3, h4, h5, h6, p, blockquote,
pre, hr, dl, dd, ol, ul, figure {
  margin: 0;
  padding: 0;
}

/** Basic styling */
body {
  font: 400 1.125rem/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
    Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji",
    "Segoe UI Symbol";
  background-color: var(--bg);
  display: flex;
  /* dvh so mobile browser chrome does not push the footer below the fold;
     the vh line is the fallback for browsers without dvh. */
  min-height: 100vh;
  min-height: 100dvh;
  flex-direction: column;
}

/* Completes the column above: without a growing child the footer rides up
   under the content on any page shorter than the viewport. */
main {
  flex: 1;
}

/** Set `margin-bottom` to maintain vertical rhythm */
h1, h2, h3, h4, h5, h6, p, blockquote,
pre, ul, ol, dl, figure {
  margin-bottom: 15px;
}

h1, h2, h3, h4, h5, h6 {
  font-weight: 400;
}

img {
  max-width: 100%;
  /* paired with the width/height attributes so scaled-down images keep
     their aspect ratio instead of stretching to the declared height */
  height: auto;
  vertical-align: middle;
  border-radius: 4px;
}

figure > img {
  display: block;
}

figcaption {
  font-size: 0.875rem;
}

/** Lists */
ul, ol {
  margin-left: 30px;
}

li > ul,
li > ol {
  margin-bottom: 0;
}

/** Links */
a {
  color: var(--link);
  text-decoration: none;
}

a:visited {
  color: var(--link-visited);
}

a:hover {
  color: var(--link-hover);
  text-decoration: underline;
}

pre,
code {
  font-size: 0.9375rem;
  border: 1px solid var(--border);
  border-radius: 3px;
  background-color: var(--code-bg);
}

code {
  padding: 1px 5px;
}

pre {
  padding: 8px 12px;
  overflow-x: auto;
}

pre > code {
  border: 0;
  padding-right: 0;
  padding-left: 0;
}

/** Tables */
table {
  margin-bottom: 30px;
  width: 100%;
  text-align: left;
  color: var(--table-text);
  border-collapse: collapse;
  border: 1px solid var(--border);
}

table tr:nth-child(even) {
  background-color: var(--table-stripe);
}

table th,
table td {
  padding: 10px 15px;
}

table th {
  background-color: var(--table-head-bg);
  border: 1px solid var(--border-strong);
  border-bottom-color: var(--border-stronger);
}

table td {
  border: 1px solid var(--border);
}

/** Wrapper */
.small-wrapper {
  --wrapper-width: 50rem;
}
.large-wrapper {
  --wrapper-width: 65rem;
}

.wrapper {
  max-width: var(--wrapper-width);
  margin-right: auto;
  margin-left: auto;
  padding-left: var(--gutter-size);
  padding-right: var(--gutter-size);
}

/** Site header */
.site-header {
  border-bottom: 1px solid var(--border);
  min-height: 3.497rem;
  position: relative;
}

.site-title {
  font-size: 1.625rem;
  font-weight: 300;
  line-height: 3.375rem;
  letter-spacing: -1px;
  margin-bottom: 0;
}

.site-title,
.site-title:visited {
  color: var(--heading);
}

/** Site footer */
.site-footer {
  border-top: 1px solid var(--border);
  padding: 30px 0;
}


/** Page content */
.post-meta {
  font-size: 0.875rem;
}

/** Posts */
.post-title {
  font-size: 2.625rem;
  letter-spacing: -1px;
  line-height: 1;
}

@media screen and (max-width: 50em) {
  .post-title {
    font-size: 2.25rem;
  }
}

.post-content {
  margin-bottom: 30px;
}

.post-content h2 {
  font-size: 2rem;
}

@media screen and (max-width: 50em) {
  .post-content h2 {
    font-size: 1.75rem;
  }
}

.post-content h3 {
  font-size: 1.625rem;
}

@media screen and (max-width: 50em) {
  .post-content h3 {
    font-size: 1.375rem;
  }
}

.post-content h4 {
  font-size: 1.25rem;
}

@media screen and (max-width: 50em) {
  .post-content h4 {
    font-size: 1.125rem;
  }
}

/* <picture> is only a wrapper for <source> elements; keep it out of layout so
   the inner <img> keeps whatever positioning its own class gives it. */
picture {
  display: contents;
}

/* Footer social icons. <use> references have no intrinsic size, so without an
   explicit one the browser falls back to the 300x150 default for <svg>. */
.icon {
  width: 32px;
  height: 32px;
}

.icon + .icon,
a + a > .icon {
  margin-left: 1em;
}

/* Author bio: leads the index, closes every other page. */
.bio {
  display: flex;
  justify-content: center;
  flex-wrap: wrap;
  gap: 1.25rem 2.5rem;
}

.bio-aside {
  flex: none;
  text-align: center;
}

.bio-photo {
  /* eleventy-img stamps width/height from the largest derivative, so pin the
     displayed size here rather than relying on the attributes. */
  width: 9.375rem;
  height: 9.375rem;
  object-fit: cover;
  border-radius: 50%;
  margin-bottom: 1.25rem;
}

.bio-links {
  display: flex;
  justify-content: center;
  margin-bottom: 1.25rem;
}

/* Without this the anchors are inline boxes 8px taller than the glyph they
   wrap, which offsets the row they are supposed to line up with. */
.bio-links a {
  display: flex;
}

.bio-text {
  flex: 1 1 15.625rem;
  min-width: 15.625rem;
  max-width: 31.25rem;
  color: var(--text-muted);
  margin: 0;
  /* Line up with the top of the photo, not the midpoint of the photo and
     the icons stacked beneath it. */
  align-self: flex-start;
}


/* ── Index page: bio header, view toggle, and the post index in both views.
   Only index.html uses these rules; they live here rather than inline so the
   index HTML stays small and the stylesheet is cached once. */
/* Segmented control: one pill, two states, rather than two loose buttons. */
.view-toggle-bar {
  display: none;
  justify-content: flex-end;
  /* Spans the content column, so the pill sits on its right edge and does
     not move when the view changes. */
  margin: 0;
}

/* Bio and view toggle share one block. The rule lives on the post list below
   rather than on this wrapper: the wrapper is pulled left on wide screens, and
   a border here would extend past the content column. */
.index-head {
  padding-top: 2rem;
  margin-bottom: 1rem;
}
:root.js .view-toggle-bar {
  display: flex;
}
.view-toggle {
  display: inline-flex;
  gap: 2px;
  padding: 3px;
  background-color: var(--toggle-track);
  border: 1px solid var(--toggle-border);
  border-radius: 999px;
}

.view-toggle button {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;

  font: inherit;
  font-size: 0.8125rem;
  line-height: 1;
  color: var(--toggle-idle);

  background: none;
  border: 0;
  border-radius: 999px;
  padding: 0.4rem 0.85rem;
  cursor: pointer;

  transition: background-color .15s ease, color .15s ease, box-shadow .15s ease;
}

.view-toggle button svg {
  width: 0.875rem;
  height: 0.875rem;
  flex: none;
}

.view-toggle button:hover {
  color: var(--toggle-hover);
}

.view-toggle button[aria-pressed="true"] {
  color: var(--toggle-active);
  background-color: var(--toggle-active-bg);
  box-shadow: 0 1px 2px var(--toggle-shadow);
}

/* Keyboard users get a ring; mouse users do not. */
.view-toggle button:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 1px;
}

@media (prefers-reduced-motion: reduce) {
  .view-toggle button,
  .post-card {
    transition: none;
  }
}

/* Shared by both views. The border carries the rule that divides the header
   block from the list, because .index-head is pulled left on wide screens
   and a border there would extend past the content column. */
.post-index {
  list-style: none;
  margin: 0;
  padding: 1.5rem 0 0;
  border-top: 1px solid var(--border);

  /* ---- Card view (the default) ---- */
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-start;
  gap: 2.5rem;
}

.post-card {
  position: relative;
  /* Basis small enough that two fit the column before wrapping; they then
     grow to share it evenly. The column caps the width, so no max-width. */
  flex: 1 1 20rem;
  min-width: 18.75rem;
  height: 18.75rem;

  border-radius: 5px;
  overflow: hidden;
  /* On the card, not on :hover, or the shadow eases in and snaps out. */
  transition: box-shadow .2s ease-in-out;
}

.post-card:hover {
  box-shadow: 0 12px 28px 0 var(--card-shadow), 0 2px 4px 0 var(--card-shadow-tight);
}

.post-card img {
  position: absolute;
  width: 100%;
  height: 100%;
  object-fit: cover;
  z-index: -1;
}

a.post-card-link:any-link {
  text-decoration: none;
  color: unset;
  background-image: linear-gradient(var(--wash-top), var(--wash-bottom));

  display: block;
  height: 100%;
  padding: 1.25rem;
}

/* ---- List view ---- */
/* padding-top drops so the border above reads as one more row separator: the
   gap above the first row matches the gap below every other separator,
   rather than stacking list padding on top of it. */
:root[data-post-view="list"] .post-index {
  display: block;
  padding-top: 0;

}
:root[data-post-view="list"] .post-card {
  position: static;
  height: auto;
  min-width: 0;
  max-width: none;
  margin: 0;
  border-radius: 0;
  border-bottom: 1px solid var(--border);
}
:root[data-post-view="list"] .post-card:hover {
  box-shadow: none;
}

/* The cover image is decorative here; hiding it also keeps the browser from
   fetching it, since lazy images inside display:none are never in view. */
:root[data-post-view="list"] .post-card picture,
:root[data-post-view="list"] .post-card img {
  display: none;
}

:root[data-post-view="list"] a.post-card-link:any-link {
  background-image: none;
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0 1rem;
  height: auto;
  padding: 0.9rem 0.25rem;
}
:root[data-post-view="list"] a.post-card-link:hover h2 {
  text-decoration: underline;
}

:root[data-post-view="list"] .post-card h2 {
  font-size: 1.125rem;
  font-weight: 400;
  margin: 0;
}

:root[data-post-view="list"] .post-card .post-meta {
  order: -1;
  flex: 0 0 7rem;
  font-size: 0.875rem;
  color: var(--text-muted);
}

@media screen and (max-width: 37.5em) {
  :root[data-post-view="list"] .post-card .post-meta {
    flex-basis: 100%;
  }
  :root[data-post-view="list"] a.post-card-link:any-link {
    padding-top: 0.6rem;
  }
}
/* As soon as there is room for photo and paragraph side by side, the header
   becomes a two-row grid: photo and paragraph on the first row, icons and
   view toggle on the second, so the toggle sits on the icon row. Flattening
   the bio makes its parts items of this grid rather than nesting, which is
   what lets the toggle share a row with the icons at all. The breakpoint is
   where the two-column bio itself gives out: photo 9.375rem + gap 2.5rem +
   the paragraph min-width 15.625rem, plus the gutters. Narrower than that the
   bio stacks and the toggle drops to a row of its own. */
@media screen and (min-width: 30em) {
  .index-head {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    column-gap: 2.5rem;
    align-items: center;
  }

  .page-content .bio,
  .page-content .bio-aside {
    display: contents;
  }

  .page-content .bio-photo {
    grid-area: 1 / 1;
    align-self: start;
  }

  .page-content .bio-links {
    grid-area: 2 / 1;
    margin-bottom: 0;
  }

  .page-content .bio-text {
    grid-area: 1 / 2;
    align-self: start;
    min-width: 0;
    max-width: none;
  }

  .view-toggle-bar {
    grid-area: 2 / 2;
  }
}

/* Only once there is 11.875rem of margin to hang into does the photo move out
   of the column, which is what puts the paragraph on the same left edge as
   the site title, the list and the cards. The paragraph is short at this
   width, so the toggle moves up into its row and sits level with the bottom
   of the photo. */
@media screen and (min-width: 70em) {
  .index-head {
    margin-left: -11.875rem;
    /* The gap between photo and icons moves off the photo and onto the grid,
       so the first row ends exactly at the photo rather than 1.25rem past it,
       and align-self: end lands the toggle on that edge. */
    row-gap: 1.25rem;
    /* Photo and icons both sit in the left margin, so the icon row is given no
       height: the header ends with the photo and the rule and post list move
       up to meet it, while the icons overhang into the empty margin beside
       them. */
    grid-template-rows: auto 0;
    /* With the icon row weightless, the header ends level with the icons, so
       drop the gap and the rule lands on that same line. */
    margin-bottom: 0;
  }

  .page-content .bio-photo {
    margin-bottom: 0;
  }

  /* Anchored to the row start rather than centred on the weightless row, so
     the icons begin exactly where the header ends and the rule begins. */
  .page-content .bio-links {
    align-self: start;
  }

  .view-toggle-bar {
    grid-area: 1 / 2;
    align-self: end;
  }

  /* Toggle and paragraph share a cell, so reserve its height at the foot of
     the text. A longer bio then pushes the toggle down instead of running
     under it. */
  :root.js .page-content .bio-text {
    padding-bottom: 2.75rem;
  }
}

