 
/** Theme
 *
 * Every colour on the site comes from one of these custom properties, so the
 * dark theme below only has to redefine the palette, not the rules using it.
 * `color-scheme` lets the browser follow along for the things CSS doesn't
 * reach: scrollbars, form controls and the default canvas.
 */

:root {
  color-scheme: light dark;

  --content-width: 820px;
  --page-padding: 25px;

  --bg: #fff;
  --text: #455;
  --link: #122;
  --link-hover: #000;
  --accent: #d00;
  --muted: #ababab;
  --caption: #888;
  --rule: currentColor;

  --code-bg: #334;
  --code-text: #ddd;
  --code-prompt: #ad8;

  --shadow: rgba(0, 0, 0, 0.2);
  --placeholder: rgba(68, 85, 85, 0.04);
  --progress-bar: #ddd;
  --model-dim: none;

  --overlay-bg: rgba(0, 0, 0, 0.62);
  --overlay-title: #fff;
  --overlay-text: #e3e6e6;

  --highlight-red: rgba(255, 76, 76, 0.1);
  --highlight-green: rgba(76, 255, 76, 0.1);
  --highlight-blue: rgba(76, 192, 255, 0.1);
  --text-red: #b44;
  --text-blue: #139;
}

@media (max-width: 640px) {
  :root {
    --page-padding: 22px;
  }
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #14191a;
    --text: #c3d0d0;
    --link: #eaf2f2;
    --link-hover: #fff;
    --accent: #ff7a70;
    --muted: #78868a;
    --caption: #8b9797;

    --code-bg: #222b33;
    --code-text: #d7dee0;

    --shadow: rgba(0, 0, 0, 0.5);
    --placeholder: rgba(195, 208, 208, 0.05);
    --progress-bar: #667;
    --model-dim: brightness(0.85);

    --highlight-red: rgba(255, 76, 76, 0.18);
    --highlight-green: rgba(76, 255, 76, 0.15);
    --highlight-blue: rgba(76, 192, 255, 0.18);
    --text-red: #e58c86;
    --text-blue: #7fb4e8;
  }
}

/** Wobbly avatar */

.wobbly-wrapper {
  width: 300px;
  height: 300px;
  margin: 30px 0px;
}

@media (max-width: 640px) {
  .wobbly-wrapper {
    margin: 20px auto;
  }
}

svg {
  width: 100%;
  height: 100%;
  display: block;
}

.masked-image {
  width: 100%;
  height: 100%;
  object-fit: cover;
  clip-path: url(#wobbleClip);
}

@supports not (clip-path: url(#wobbleClip)) {
  .masked-image {
    clip-path: circle(50%);
  }
}

body {
  font-family: "Spectral", sans-serif;
  max-width: var(--content-width);
  margin: 0 auto;
  padding: var(--page-padding);
  padding-bottom: calc(var(--page-padding) * 2);
  background-color: var(--bg);
  color: var(--text);
}

a {
  font-weight: 500;
  text-decoration: underline;
  text-decoration-thickness: 0.5px;
  text-underline-offset: 2px;
  color: var(--link);
}

a:hover {
  color: var(--link-hover);
  text-decoration-thickness: 1.5px;
}

h1 {
  font-size: 36px;
  font-weight: 600;
  margin-bottom: 28px;
  letter-spacing: -0.75px;
}

@media (max-width: 640px) {
  h1 {
    font-size: 28px;
  }
}

.post-title {
  display: block;
  font-size: 36px;
  font-weight: 600;
  margin-top: 20px;
  margin-bottom: 28px;
  color: var(--accent);
  font-weight: 400;
  word-spacing: 7%;
  margin-bottom: 4px;
  line-height: 40px;
}

post-date {
  font-style: italic;
  margin-left: 20px;
  margin-top: 0;
  margin-bottom: 30px;
  font-size: 20px;
}

@media (max-width: 640px) {
  .post-title {
    font-size: 30px;  
  }

  post-date {
    margin-left: 0px;
  }
}

@media (max-width: 800px) {
  .byline {
    margin-left: 0px;
  }
}

mono {
  font-size: 15px;
  font-family: "Fira Mono", monospace;
}

monospaced {
  font-size: 14px;
  line-height: 19px;
  display: block;
  padding: 20px 30px;
  border-radius: 15px;
  margin: 8px;
  margin-bottom: 16px;
  font-family: "Fira Mono", monospace;
  background-color: var(--code-bg);
  color: var(--code-text);
  white-space: pre;
  overflow-x: auto;
}

.monospaced-wrap {
  position: relative;
  display: block;
  margin: 8px;
  margin-bottom: 16px;
  border-radius: 15px;
  overflow: hidden;
}

.monospaced-wrap monospaced {
  margin: 0;
}

.monospaced-wrap::after {
  content: '';
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  width: 60px;
  background: linear-gradient(to right, transparent, var(--code-bg));
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.15s;
}

.monospaced-wrap.can-scroll::after {
  opacity: 1;
}

h2 {
  font-size: 24px;
  font-weight: 600;
  margin-top: 30px;
  margin-bottom: 25px;
}

h3 {
  font-size: 21px;
  font-weight: 600;
  margin-bottom: 25px;
  margin-left: 12px;
}

hr {
  width: 33%;
  opacity: 33%;
  margin-top: 45px;
  border: 0;
  border-top: 1px solid var(--rule);
}

.hr-short {
  width: 25%;
  opacity: 20%;
  margin-top: 35px;
  margin-bottom: 35px;
}

p {
  font-size: 19px;
  line-height: 30px;
  font-weight: 300;
  margin-left: 20px;
  margin-right: 20px;
  text-align: justify;
}

@media (max-width: 640px) {
  p {
    margin-left: 0px;
    margin-right: 0px;
  }
}

.caption {
  text-align: center;
  color: var(--caption);
  font-style: italic;
  margin-top: -5px;
  margin-bottom: 30px;
}

li {
  font-size: 19px;
  line-height: 30px;
  font-weight: 300;
  margin-bottom: 15px;
}

/* Maximum width */
@media (max-width: 600px) {
  p {
    margin-left: 0px;
    margin-right: 0px;
  }
}

strong {
  font-weight: 600;
}

.muted {
  color: var(--muted);
  margin-bottom: 25px;
}

.full-width {
  width: 100%;
  margin-top: 10px;
  margin-bottom: 10px;
  border-radius: 10px;
}

.soft-shadow {
  box-shadow: 0 10px 18px var(--shadow);
}

/** Image galleries
 *
 * A <div class="gallery"> holding any number of <a><img></a> children. Cells
 * are square and flow into --columns columns, so a gallery needs no markup
 * beyond the images themselves. Any child can claim more room:
 *
 *   .wide   two columns across
 *   .tall   two rows down
 *   .big    two by two
 *
 * That is what gives a gallery its uneven, dynamic look. A feature image on
 * the left with two stacked beside it is <a class="big"> followed by two
 * plain ones; add .flip to the gallery to mirror the whole arrangement
 * without reordering the HTML. Set --columns (inline or via a rule) to make
 * a gallery denser or sparser.
 *
 * Placement is `dense`, so a later small image will backfill a gap that an
 * earlier spanning image left behind. That keeps galleries gapless at any
 * image count, at the cost of images not always appearing in source order.
 *
 * The row height has to equal the column width for cells to come out square,
 * and grid can't express "as tall as a column is wide" on its own. Hence the
 * calc: it reproduces the gallery's own width -- the page's content box,
 * capped by --content-width -- and divides it the same way the columns are.
 */

.gallery {
  --columns: 3;
  --gallery-gap: 14px;

  display: grid;
  grid-template-columns: repeat(var(--columns), 1fr);
  grid-auto-rows: calc(
    (min(100vw - 2 * var(--page-padding), var(--content-width))
      - (var(--columns) - 1) * var(--gallery-gap)) / var(--columns)
  );
  grid-auto-flow: dense;
  gap: var(--gallery-gap);
  margin: 40px 0;
}

.gallery > a {
  display: block;
  position: relative; /* positions the optional <image-info> overlay */
  min-height: 0; /* shrink the cell, not the grid, when space runs out */
  overflow: hidden;
  border-radius: 10px;
  background-color: var(--placeholder); /* holds the shape while loading */
}

.gallery > .wide {
  grid-column: span 2;
}

.gallery > .tall {
  grid-row: span 2;
}

.gallery > .big {
  grid-column: span 2;
  grid-row: span 2;
}

/* Auto-placement follows the inline direction, so flipping it mirrors the
 * arrangement. The children opt back out, leaving their own content alone. */
.gallery.flip {
  direction: rtl;
}

.gallery.flip > * {
  direction: ltr;
}

.gallery img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 0.3s ease;
}

@media (hover: hover) {
  .gallery > a:hover img {
    transform: scale(1.04);
  }
}

.gallery > a.revealed img {
  transform: scale(1.04);
}

/** Image captions (optional)
 *
 * Any gallery cell can carry an <image-info> holding a <strong> title and
 * some caption text. It fades in over the image on hover, on keyboard focus,
 * or -- via the script in template.html -- on the first tap of a touch
 * screen, where a second tap then follows the link.
 *
 * Cells without one behave exactly as before; this costs nothing to skip.
 */

image-info {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  padding: 15px 17px;
  background-color: var(--overlay-bg);
  color: var(--overlay-text);
  font-size: 14px;
  line-height: 19px;
  font-weight: 300;
  opacity: 0;
  transition: opacity 0.2s ease;
  pointer-events: none; /* the link underneath keeps every click */
}

image-info strong {
  display: block;
  margin-bottom: 4px;
  color: var(--overlay-title);
  font-size: 17px;
  line-height: 21px;
}

@media (hover: hover) {
  .gallery > a:hover image-info {
    opacity: 1;
  }
}

.gallery > a:focus-visible image-info,
.gallery > a.revealed image-info {
  opacity: 1;
}

@media (max-width: 640px) {
  image-info {
    padding: 11px 12px;
    font-size: 13px;
    line-height: 17px;
  }

  image-info strong {
    font-size: 15px;
    line-height: 19px;
  }
}

@media (max-width: 640px) {
  /* Two columns keeps a gallery reading as a gallery on a phone. */
  .gallery {
    --columns: 2;
  }

  /* "Two of three columns" has no meaning at two columns -- it would just be
   * full width, which turns a pair of images into two stacked banners. A wide
   * image drops back to a single cell here so it keeps a neighbour beside it.
   * .big still spans both columns: it is meant to be a feature. */
  .gallery > .wide {
    grid-column: auto;
  }
}

/** 3D model viewer
 *
 * <model-viewer> is a web component; template.html only loads it on pages that
 * embed one. Its canvas is transparent, so the page background shows through.
 * The sizing below applies before the component is defined too, which keeps
 * the page from jumping once the script arrives.
 */

model-viewer {
  display: block;
  width: 100%;
  aspect-ratio: 4 / 4;
  max-height: 70vh;
  min-height: 40vh;
  margin: 20px 0 10px;
  border-radius: 10px;
  background-color: transparent;
  --poster-color: transparent;
  --progress-bar-color: var(--progress-bar);
  --progress-mask: transparent;

  /* The .glb is lit for a white page and reads as overexposed against the
   * dark theme. The canvas is transparent and brightness() only scales RGB,
   * so this dims the model and its contact shadow without filling the box. */
  filter: var(--model-dim);
}

model-viewer:not(:defined) {
  /* Faint placeholder while the ~1 MB viewer bundle is still downloading. */
  background-color: var(--placeholder);
}

model-viewer + .caption {
  margin-bottom: 30px;
}

.highlight {
  padding-left: 3px;
  padding-right: 3px;
}

.red {
  background-color: var(--highlight-red);
}

.green {
  background-color: var(--highlight-green);
}

.blue {
  background-color: var(--highlight-blue);
}

.prompt {
  color: var(--code-prompt);
}

.red-text {
  font-weight: 500;
  color: var(--text-red);
}

.blue-text {
  font-weight: 500;
  color: var(--text-blue);
}