/* Megan Hillman Donovan
   Ported from the Drupal theme's Sass. Values carried over as-is unless noted.
   pt units from the original are converted at 1pt = 1.333px. */

:root {
  /* Open Sans has no 900 — it tops out at 800, ExtraBold. The original asked
     for 900 from a CDN that served the heaviest it had. The four places that
     said 900 now say what actually renders, so the file means what it says.
     Both faces are variable, so the in-between weights below are real. */
  --serif: Literata, Georgia, serif;
  --sans: "Open Sans", -apple-system, BlinkMacSystemFont, sans-serif;

  --teal: #85d0ca;
  /* The brand teal at a 50% tint, as --k50 is black at 50%. The 404's
     ground, header rule to footer. */
  --teal50: #c2e8e5;
  --black: #000;
  --k50: #808080;
  --k30: #b2b2b2;
  --k20: #ccc;
  --k10: #e6e6e6;
  --k3: #f7f7f7;   /* the awards panel; lighter than anything the original had */

  /* The panel behind artwork. It is one level off --k10 on purpose: the clips
     carry this grey in their own pixels where the GIFs were transparent, and
     H.264 cannot round-trip 230 — it comes back as 231, which showed as a seam
     against the panel. 229 and 231 both survive the round trip exactly, so the
     panel is set to the one the clips already land on. */
  --panel: #e7e7e7;

  --gutter: 85vw;
  --header-height: 80px;

  /* As the original had it. It only ever applied to the link, so nothing
     visible is actually 3 seconds long. */
  --tile-fade: 3s;

  /* Search. The crossing runs longer than the fade on purpose: a travelling
     object needs more time than a fading one or it reads as a jump. */
  --search-travel: 500ms;
  --search-fade: 350ms;
  --search-ease: cubic-bezier(.65, 0, .35, 1);

  /* The crop marks are drawn on, not faded on. Both grids share these.
     The beat lets the artwork darken first, so the marks are ruled onto a
     picture that has already been dimmed rather than racing it. */
  --mark-draw: 450ms;
  --mark-beat: 75ms;
  /* The sweep, in mask-size. Every stroke sits outside a 10% trim, so a
     centred mask grown past 84% reveals each one from the end nearest the
     picture outward. 84% is exactly where the mask edge meets the inner tip
     of all eight; 94% is where it clears the outer tips. Going on to 100%
     would spend 169ms of the 450 revealing nothing, so it stops at 94.4 --
     a fifth of a unit past the tips, which keeps them from being clipped by
     the mask's own antialiasing. */
  --mark-from: 84%;
  --mark-to: 94.4%;
}

/* Below 780 the margin is asked for in points rather than as a share of the
   screen: 70px each side, then 40. Stated as 100% minus twice the margin, not
   100vw minus it -- vw counts the scrollbar, so on a narrow desktop window
   100vw is wider than the room actually available and the page would scroll
   sideways by the width of the bar. % is measured against the parent, which
   has already had the bar taken off it. */
@media (max-width: 780px) { :root { --gutter: calc(100% - 140px); } }
@media (max-width: 600px) { :root { --gutter: calc(100% - 80px);  } }

*, *::before, *::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  font-family: var(--sans);
  font-weight: 400;
  font-size: 12pt;
  position: relative;
  background: #fff;
  color: var(--black);
  /* The original anchored the footer with position:absolute against a body
     padded 55px at the foot. That pins it to the bottom of the content, not
     the bottom of the window, so filtering down to one row of tiles left it
     floating halfway up. A column flexbox at full viewport height lets main
     take up the slack instead. */
  display: flex;
  flex-direction: column;
  min-height: 100vh;
  min-height: 100dvh;
}

.header-lead, #site-header, footer { flex: none; }

/* Takes the leftover height on a short page. This is the gap between the
   content and the footer; it stood at the 55px the old body padding made,
   and Megan set it to 50 on 2026-08-31 when the page feet were tightened. */
main { flex: 1 0 auto; padding-bottom: 50px; }
@media (min-width: 2500px) { main { padding-top: 50px; } }
@media (max-width: 1024px) { main { padding-bottom: 20px; } }

img { max-width: 100%; height: auto; }
ul { list-style: none; margin: 0; padding: 0; }
button { font: inherit; }

::selection { background: var(--teal); }

/* ---------- type ---------- */

h1 { font-family: var(--sans); font-size: 21pt; line-height: 22.5pt; font-weight: 325; color: var(--black); margin: 0; }
@media (min-width: 2500px) { h1 { font-size: 25pt; } }
@media (max-width: 780px) { h1 { font-size: 13pt; } }

/* balance, not pretty: at 30pt a title is two or three lines, and the fault to
   avoid is the last one carrying a single word. `balance` evens the measure
   across all of them, so "Zion National Park Lodge" breaks 2/2 rather than 3/1.
   Browsers stop balancing past a handful of lines, which is why body copy below
   gets `pretty` instead -- it only lifts the widow on the final line. */
h2 {
  font-family: var(--sans); font-size: 30pt; line-height: 32pt; font-weight: 700;
  color: var(--teal); letter-spacing: .5pt; margin: 0; text-wrap: balance;
}
@media (max-width: 600px) { h2 { font-size: 24pt; line-height: 25pt; letter-spacing: .75pt; } }
@media (max-width: 340px) { h2 { font-size: 18pt; line-height: 20pt; } }

h3 { font-family: var(--sans); font-size: 18pt; line-height: 20pt; font-weight: 400; color: var(--k50); letter-spacing: .5pt; margin: 0; text-wrap: balance; }
@media (max-width: 340px) { h3 { font-size: 13pt; line-height: 15pt; } }

h4 {
  font-family: var(--serif); font-size: 9pt; line-height: 18pt; font-weight: 700;
  text-transform: uppercase; color: var(--k30); letter-spacing: 1.75pt; margin: 0;
}
@media (min-width: 2500px) { h4 { font-size: 12pt; } }
@media (max-width: 1024px) { h4 { line-height: 13pt; text-wrap: pretty; } }
@media (max-width: 600px) { h4 { line-height: 12pt; letter-spacing: 1.75pt; } }

/* One label style, four places: the project view-more, the About labels, the
   RIP marker and the search count. Each keeps only what is its own below. */
h5, .boxes h6, .rip, .search-count {
  font-family: var(--serif); font-size: 11pt; line-height: 15pt; font-weight: 450;
  font-style: italic; text-transform: lowercase; letter-spacing: .75pt;
  color: var(--k30); margin: 20px 0 2px;
}
@media (min-width: 2500px) { h5, .boxes h6, .rip, .search-count { font-size: 12pt; line-height: 17pt; } }

/* Credits, the view-more terms, the blog list and the award descriptions. */
h6, .terms, #blogs p, .awards .award .desc p {
  font-family: var(--sans); font-size: 11pt; line-height: 14pt; font-weight: 350;
  text-transform: lowercase; letter-spacing: .25pt; color: var(--black); margin: 0;
}

/* The base is the copy style, not a leftover default. It used to be 300 at
   .5pt on 24px -- Drupal's, which every scoped block on the site was written
   to escape, and which leaked into any paragraph that forgot to. */
p { font-family: var(--sans); font-weight: 350; font-size: 12.5pt; line-height: 18pt; letter-spacing: .25pt; margin: 0 0 1em; text-wrap: pretty; }
@media (max-width: 780px) { p { font-size: 12pt; line-height: 17pt; } }

a { color: var(--black); text-decoration: none; border-bottom: 1px solid var(--teal); padding-bottom: 1px; }
a:hover { color: var(--teal); }
a:focus-visible { outline: 2px solid var(--teal); outline-offset: 3px; }

/* ---------- header ----------
   The original swapped position:relative for position:fixed on scroll, which
   pulled it out of flow and dropped everything below by the header's height.
   The rule meant to absorb that only targeted `section`, so the home page —
   a nav and a ul — got no compensation at all, and the 1px scroll threshold
   let the resulting shift re-trigger the handler.

   Sticky never leaves the flow, so there is nothing to compensate. The height
   stays constant and the teal bar is painted by a pseudo-element, so pinning
   changes no geometry whatsoever. */

.header-lead { height: 60px; }
@media (max-width: 1024px) { .header-lead { height: 40px; } }

/* A listing page's row sets its own distance from the rule, and so does the
   search count, which can be opened from any page. The header's rule sits 9px
   above the header's bottom edge, so 11 here makes 20 to the row. */
body.listing #site-header,
body.searching #site-header { margin-bottom: 11px; }

#site-header {
  position: sticky;
  top: 0;
  z-index: 10;
  background: #fff;
  height: var(--header-height);
  display: flex;
  align-items: center;
  /* Space below the rule, before the page starts. On the header rather than
     the container, whose height is fixed by the header and whose padding the
     pinned state already uses. */
  margin-bottom: 20px;
}

#site-header::after {
  content: "";
  position: absolute;
  inset: auto 0 0 0;
  height: 8px;
  background: var(--teal);
  transform: scaleY(0);
  transform-origin: bottom;
  transition: transform .18s ease-out;
}

#site-header.pinned::after { transform: scaleY(1); }
@media (max-width: 600px) { #site-header::after { height: 6px; } }

.header-container {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
  width: var(--gutter);
  margin: 0 auto;
  padding-bottom: 20px;
  border-bottom: 1px solid var(--k10);
  /* The search icon and the search field are positioned against this box.
     Without it they resolve against the sticky #site-header, which is full
     width and starts higher, so both land in the wrong place. */
  position: relative;
  transition: border-color .18s ease-out;
}
#site-header.pinned .header-container { border-bottom-color: transparent; padding-bottom: 0; }

/* While searching, the header holds its unpinned height. The field and the
   travelling icon are both positioned from this box, so letting it change
   underneath them -- which pinning does, by dropping 20px of padding -- pulls
   the two apart by 10px. */
body.searching #site-header.pinned .header-container { padding-bottom: 20px; }

.logo { display: flex; align-items: center; gap: 10px; }
.logo a { border: 0; display: block; line-height: 0; }
/* flex:none on both, or the mark distorts rather than the row overflowing.
   .logo and the image inside it are flex items, and a flex item shrinks from
   its width while a stated height holds: below about 342 the header runs out of
   room and the whole shortfall came out of the mark's width, squashing a square
   logo to an oval (21x35 at 320). Nothing about a logo should be elastic. */
.logo, .logo img { flex: none; }
.logo img { height: 40px; width: 40px; }
@media (max-width: 600px) { .logo img { height: 35px; width: 35px; } .logo { gap: 0; } }
@media (max-width: 700px) { .logo h1 { display: none; } }

.primary-navigation ul { display: flex; align-items: center; }
.primary-navigation li { padding-left: 25px; font-size: 12pt; }
.primary-navigation a {
  border: none; text-transform: uppercase; color: var(--k50);
  font-weight: 600; letter-spacing: 2pt; transition: color .5s ease;
}
.primary-navigation a:hover, .primary-navigation .active a { color: var(--teal); }
@media (min-width: 2500px) { .primary-navigation li { font-size: 14pt; } }
@media (max-width: 780px) { .primary-navigation li { font-size: 12pt; line-height: 17pt; } }
@media (max-width: 600px) { .primary-navigation li { font-size: 9.75pt; padding-left: 10px; letter-spacing: 0pt; } }
/* With the mark rigid the room has to come from somewhere else, and the header
   is 262.5px of content against a 240px gutter at 320. Taken from the spacing
   rather than the type: the nav's own size is set at 780 and 600 and is not to
   be undercut here. */
@media (max-width: 375px) { .primary-navigation li { padding-left: 9px; } }
@media (max-width: 330px) { .primary-navigation a { letter-spacing: 1.5pt; } }

/* ---------- footer ---------- */

footer {
  padding: 0 40px 10px;
  border-bottom: 8px solid var(--teal);
}
@media (max-width: 600px) { footer { padding: 0 0 10px; border-bottom-width: 10px; } }
@media (max-width: 340px) { footer { padding: 20px 0 5px; } }

.footer-text { text-align: right; font-size: 8pt; line-height: 18pt; font-weight: 350; letter-spacing: 1.5pt; text-transform: uppercase; color: var(--k50); margin: 0; }
@media (min-width: 2500px) { .footer-text { font-size: 10pt; } }
@media (max-width: 600px) { .footer-text { font-size: 7pt; letter-spacing: .25pt; text-align: center; } }
@media (max-width: 340px) { .footer-text { font-size: 6pt; letter-spacing: .5pt; } }

/* ---------- search ----------
   One icon, moved. It rests in the nav beside BLOG and crosses the header to
   become the field's own icon, rather than one icon fading out while a second
   fades in somewhere else.

   Its resting spot and its open spot are both measured in JS, because the icon
   has to align to the cap band of the nav words and neither the font metrics
   nor the SVG's ink extents are knowable from the stylesheet. */

.search-slot { display: inline-block; vertical-align: baseline; }

.header-nav {
  display: flex; align-items: center; justify-content: space-between;
  gap: 20px; width: 100%;
  transition: opacity var(--search-fade) ease;
}
/* Below .header-nav, not up with the nav's other breakpoints: these override
   the gap declared just above, at the same specificity, so they only take
   effect after it. Placed among the nav rules they were silently dead.

   The gap goes entirely at 340 rather than the page margin coming in: the
   nav's first item already carries 9px of its own padding, so a flex gap on
   top of it was spacing the mark off the words twice. */
@media (max-width: 375px) { .header-nav { gap: 10px; } }
@media (max-width: 340px) { .header-nav { gap: 0; } }

body.searching .header-nav { opacity: 0; pointer-events: none; }

.header-search {
  position: absolute; inset: 0 0 20px 0;
  display: flex; align-items: center;
  opacity: 0; visibility: hidden;
  transition: opacity var(--search-fade) ease, visibility 0s var(--search-fade);
}
body.searching .header-search {
  opacity: 1; visibility: visible;
  transition: opacity var(--search-fade) ease calc(var(--search-travel) * .45), visibility 0s;
}

.search-field {
  flex: 1; min-width: 0; border: 0; outline: 0; background: none;
  font-family: var(--sans); font-weight: 350; font-size: 22pt; line-height: 24pt;
  color: var(--black); padding: 0 0 0 36px;   /* clears the arriving icon */
  -webkit-appearance: none; appearance: none;
}
.search-field::placeholder { color: var(--k20); }
.search-field::-webkit-search-cancel-button { display: none; }

.search-close {
  flex: none; border: 0; background: none; cursor: pointer; padding: 0 0 0 8px;
  font-family: var(--sans); font-size: 16.5pt; line-height: 1;
  color: var(--k30); transition: color .5s ease;
}
.search-close:hover { color: var(--teal); }

.search-open {
  position: absolute; left: 0; top: 0; z-index: 4;
  width: 20px; height: 20px; padding: 0; border: 0; background: none;
  cursor: pointer; color: var(--k50); line-height: 0;
  opacity: 0;                                  /* until measured */
  transform-origin: 0 0;
  transform: translate3d(var(--rest-x, 0), var(--rest-y, 0), 0) scale(var(--rest-scale, .85));
  /* No transition until the icon has been put where it belongs. It starts at
     the left with nothing measured yet, and again when the web font lands and
     the nav changes width -- both would otherwise glide across the header on
     every single page load. */
  transition: none;
}
body.search-animated .search-open {
  transition: transform var(--search-travel) var(--search-ease),
              color var(--search-travel) var(--search-ease);
}
.search-open svg { width: 20px; height: 20px; overflow: visible; }
.search-open:hover { color: var(--teal); }
body.search-ready .search-open { opacity: 1; }
body.searching .search-open {
  transform: translate3d(0, var(--open-y, 0), 0) scale(1);
  color: var(--teal); pointer-events: none;
}

/* Results stand in for whatever the page was showing. */
body.searching main > :not(.search-results) { display: none; }

.search-results { width: var(--gutter); margin: 0 auto; }

/* The count sits where the filter row sits: the same band, the same 20px of
   visible space above and below. Unlike the grid, .search-hits carries no tile
   border, so nothing has to be subtracted below. */
.search-count {
  height: 22px;
  line-height: 1;
  display: flex;
  align-items: center;
  margin: 0 0 20px;
  color: var(--k50);
}
.search-hits {
  display: grid; grid-template-columns: repeat(6, 1fr); gap: 26px 16px;
  list-style: none; margin: 0; padding: 0;
}
@media (min-width: 1800px) { .search-hits { grid-template-columns: repeat(7, 1fr); } }
@media (max-width: 1024px) { .search-hits { grid-template-columns: repeat(4, 1fr); } }
@media (max-width: 780px)  { .search-hits { grid-template-columns: repeat(3, 1fr); } }
@media (max-width: 600px)  { .search-hits { grid-template-columns: repeat(2, 1fr); } }

.search-hits a { display: block; border: 0; }
.search-hits img {
  display: block; width: 100%; aspect-ratio: 1; object-fit: cover;
  background: var(--k10);
}

/* Hovering a result reads as marking it up for the crop: everything outside
   the trim drops away and the crop marks come up white in it.

   The dark is 65%, which is the same arithmetic as the brightness(.35) the
   home grid has used since the original theme -- both leave 35% of the pixel,
   so the two grids darken by exactly the same amount.

   The marks are a background image rather than markup, so a search returning
   139 results does not carry 139 copies of the same eight strokes. */
.search-hits .shot { position: relative; display: block; overflow: hidden; }

.search-hits .shot::before,
.search-hits .shot::after {
  content: ""; position: absolute; opacity: 0;
  transition: opacity .28s ease; pointer-events: none;
}
.search-hits .shot::before {
  inset: 10%; z-index: 1;
  box-shadow: 0 0 0 9999px rgba(0, 0, 0, .65);
}
.search-hits .shot::after {
  inset: 0; z-index: 2;
  background: url("data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20viewBox%3D%270%200%20100%20100%27%3E%3Cg%20fill%3D%27none%27%20stroke%3D%27%23fff%27%20stroke-width%3D%27.6%27%20stroke-linecap%3D%27butt%27%3E%3Cpath%20d%3D%27M3%2010H8M10%203V8%27%2F%3E%3Cpath%20d%3D%27M92%2010H97M90%203V8%27%2F%3E%3Cpath%20d%3D%27M3%2090H8M10%2092V97%27%2F%3E%3Cpath%20d%3D%27M92%2090H97M90%2092V97%27%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E") center / 100% 100% no-repeat;
}
.search-hits a:hover .shot::before, .search-hits a:focus-visible .shot::before,
.search-hits a:hover .shot::after,  .search-hits a:focus-visible .shot::after { opacity: 1; }

/* Same draw as the home grid -- only the marks; the ::before darkening keeps
   its own fade so the result dims at once and the marks follow. */
@supports (mask-size: 100% 100%) or (-webkit-mask-size: 100% 100%) {
  .search-hits .shot::after {
    /* Held transparent at rest, not just masked. At 84% the mask edge lies
       exactly on the inner tip of every stroke, and that edge is antialiased,
       so a hairline of all eight showed through before the pointer arrived.
       Masking alone cannot fix that without starting the sweep short of the
       tips and wasting the first frames of the draw on nothing. */
    opacity: 0;
    -webkit-mask-image: linear-gradient(#000, #000); mask-image: linear-gradient(#000, #000);
    -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
    -webkit-mask-position: center; mask-position: center;
    -webkit-mask-size: var(--mark-from) var(--mark-from);
            mask-size: var(--mark-from) var(--mark-from);
    /* Declared on the hover state, not here: with no transition at rest,
       leaving the tile takes the marks off at once instead of un-drawing
       them. That matches the artwork, which un-dims instantly too. */
    transition: none;
  }
  .search-hits a:hover .shot::after, .search-hits a:focus-visible .shot::after {
    /* Opacity flips at the instant the sweep starts -- no duration, just the
       beat as its delay -- so it is never visible while the mask is parked. */
    opacity: 1;
    -webkit-mask-size: var(--mark-to) var(--mark-to);
            mask-size: var(--mark-to) var(--mark-to);
    transition: opacity 0s linear var(--mark-beat),
                -webkit-mask-size var(--mark-draw) ease-out var(--mark-beat),
                        mask-size var(--mark-draw) ease-out var(--mark-beat);
  }
}

@media (prefers-reduced-motion: reduce) {
  .search-hits .shot::before, .search-hits .shot::after,
  .search-hits a:hover .shot::after, .search-hits a:focus-visible .shot::after {
    transition-duration: .01ms; transition-delay: 0s;
  }
}
.search-hits b {
  display: block; margin-top: 7px;
  font-family: var(--sans); font-weight: 650; font-size: 8.25pt; letter-spacing: .5pt;
  line-height: 10.5pt; text-transform: uppercase; color: var(--black);
}
.search-hits mark { background: rgba(133, 208, 202, .4); color: inherit; padding: 0 1px; }

/* The one delicate italic outside h5 -- same face, but a size of its own. */
.search-hits .why {
  display: block; margin-top: 4px;
  font-family: var(--serif); font-style: italic; font-weight: 350; font-size: 8.25pt;
  line-height: 10.48pt; letter-spacing: .5pt; color: var(--k30); text-transform: lowercase;
}

.search-empty { font-family: var(--serif); font-style: italic; font-size: 11pt; color: var(--k50); }

@media (max-width: 600px) { .search-field { font-size: 15pt; } }
@media (prefers-reduced-motion: reduce) {
  .search-open, .header-nav, .header-search {
    transition-duration: .01ms !important; transition-delay: 0s !important;
  }
}

/* ---------- home: filter ---------- */

/* The filter row and the tag page's viewing row share a band, so the two pages
   space identically. Both centre their contents in a fixed height rather than
   letting the line box decide, which is what made them differ: the tag row's
   close cross is 20px type and was pushing its band taller than the filter's.

   20px of visible space above and below, measured from what the eye actually
   sees: the header's rule, and the artwork inside the tiles. Both sides
   subtract what sits between -- the 9px the rule is inset into the header, and
   the 4px white border on every tile. */
.filter, .viewmore {
  width: var(--gutter);
  margin: 0 auto 16px;   /* 16 + the grid's 4px tile border = 20 to the artwork */
  padding: 0;
  /* A fixed height and a hugged line box, so both rows present the same band
     whatever is inside them -- the tag row's 20px close cross would otherwise
     make its band taller than the filter's, and the two pages would space
     differently. */
  height: 22px;
  line-height: 1;
  display: flex;
  align-items: center;
}
.filter { color: var(--k20); }
.filter h5 { display: inline-block; }
.filter ul { display: inline; }
.filter li { display: inline-block; }
.filter li::after { content: "|"; margin: 0 5px; color: var(--k20); }
.filter li:last-child::after { display: none; }
/* The pipe on an item that ends a wrapped row. .row-end is put on by mh.js --
   see the note there for why it is hidden rather than collapsed. */
.filter li.row-end::after { visibility: hidden; }

.filter-button {
  color: var(--k20); border: none; background: none; text-transform: uppercase;
  letter-spacing: 1.5pt; font-weight: 800; font-size: 11pt; line-height: 1;
  cursor: pointer; padding: 0;
}
.filter-button:hover, .filter-button.active { color: var(--teal); }
.filter-button:focus-visible { outline: 2px solid var(--teal); outline-offset: 2px; }

/* The filter is shown again below 1024, and at these widths it cannot hold a
   single line: the nine categories need 864px at 9pt, so at the 780 gutter it
   would take 5pt to fit on one and below 700 no size fits at all. It wraps
   instead, which means the fixed 22px band has to go with it -- a second row
   had nowhere to sit and pushed out of the band into the grid. height:auto and
   a line-height do the spacing; display:block lets the label and the list flow
   together as one run of inline content rather than two flex items. .viewmore
   keeps the fixed band: it is one short row on a tag page and never wraps. */
@media (max-width: 1024px) {
  .filter { height: auto; display: block; line-height: 14pt; text-wrap: balance; }
  .filter-button { font-size: 9pt; }
  /* The pipe is on the li, not the button, so it was still taking body's 12pt
     while the categories beside it dropped to 9. */
  .filter li::after { font-size: 10.5pt; }
}
@media (max-width: 780px) { .filter-button { font-size: 10pt; letter-spacing: 1.125pt; } }
@media (max-width: 600px) { .filter-button { font-size: 9pt; letter-spacing: 0.75pt; } .filter li::after { margin: 0 1px; } }
@media (max-width: 340px) { .filter { line-height: 12pt; } .filter-button { font-size: 7pt; } }



/* ---------- the square grid ----------
   Inline-block with percentage widths, as the original had it. Grid would be
   the tidier choice, but the filter depends on this: hiding a tile collapses
   its width to nothing and the remaining ones flow up into the space. A grid
   item cannot do that — removing it from the track is instantaneous. */

.home-squares {
  display: block;
  /* Every tile carries a 4px white border, which used to hold the artwork 4px
     inside the gutter while the header's rule and the filter row sat on it.
     Widening by both borders puts the artwork itself on the gutter, so the
     grid lines up with everything above it. */
  width: calc(var(--gutter) + 8px);
  margin: 0 auto;
  text-align: left;
  font-size: 0;
  padding-bottom: 0;
}

.home-squares li {
  display: inline-block;
  width: 16.66667%;
  /* The height comes from padding-bottom, which resolves against the row's
     width -- but the 4px borders sit outside it, so every tile came out 8px
     taller than it was wide. Taking both borders off the padding squares it.
     The percentage has to stay: it is what holds the height steady while the
     filter animates the width to zero. */
  padding-bottom: calc(16.66667% - 8px);
  height: 0;
  border: 4px solid #fff;
  position: relative;
  vertical-align: top;
  /* The original animated these three over 400ms with jQuery. Visibility rides
     along at the end so a hidden tile leaves the tab order once it has gone. */
  transition: width .4s ease, opacity .4s ease, border-width .4s ease, visibility 0s;
}

@media (min-width: 1800px) { .home-squares li { width: 14.28571%; padding-bottom: calc(14.28571% - 8px); } }
@media (max-width: 1024px) { .home-squares li { width: 25%;       padding-bottom: calc(25% - 8px); } }
@media (max-width: 780px)  { .home-squares li { width: 33.33333%; padding-bottom: calc(33.33333% - 8px); } }
@media (max-width: 600px)  { .home-squares li { width: 50%;       padding-bottom: calc(50% - 8px); } }

.home-squares li.is-hidden {
  width: 0;
  opacity: 0;
  border-width: 0;
  visibility: hidden;
  transition: width .4s ease, opacity .4s ease, border-width .4s ease, visibility 0s .4s;
}

.home-squares a { display: block; border: 0; }

/* The original declares this on the li's direct children, which is the link and
   nothing else. Transitions do not inherit, so the image and caption inside it
   have none and their hover change is instant — which is how the rollover is
   meant to feel. Putting the 3s on them directly is what made it crawl. */
.home-squares li > * { transition: var(--tile-fade) all ease-in; }

/* No object-fit, as the original had it. That is what makes the filter unfold
   rather than zoom: with the image simply filling the box, narrowing a tile
   squashes it and the row closes like a concertina. object-fit:cover keeps the
   aspect ratio and re-crops instead, which reads as the picture zooming.

   Nothing distorts at rest, because these thumbnails are generated square --
   Kirby crops them to 500x500 for this grid. */
.home-squares img {
  height: 100%;
  width: 100%;
  position: absolute;
}

/* The caption sits on the bottom-left corner of the crop, so the type and the
   marks share a line instead of ignoring each other. The 5% is breathing room
   inside the trim; being a percentage it holds its proportion whether the grid
   is one square across on a phone or seven on a wide screen. */
/* Pinned, not inherited. These used to take their tracking and leading from
   the base p, so resetting the base moved them -- the caption is uppercase
   display type where tracking shows. */
.home-squares p {
  font-family: var(--sans); font-weight: 775; letter-spacing: .5pt; max-width: 72%;
  position: absolute; bottom: 10%; left: 10%;
  padding: 0 0 5% 5%;
  text-align: left; z-index: 3; color: transparent; text-transform: uppercase;
  line-height: 16pt; margin: 0;
}
@media (min-width: 2500px) { .home-squares p { font-size: 14pt; line-height: 17pt; } }
@media (max-width: 1024px) { .home-squares p { font-size: 8.5pt; line-height: 10pt; } }
@media (max-width: 780px) { .home-squares p { font-size: 12pt; line-height: 16pt; } }
@media (max-width: 700px) { .home-squares p { font-size: 9pt;  line-height: 11pt; } }
@media (max-width: 600px) { .home-squares p { font-size: 8pt;  line-height: 10pt; } }
@media (max-width: 340px) { .home-squares p { font-size: 7pt;  line-height: 9pt;  } }

/* Same marks as the search results, over the same darkening. The transition is
   declared here because .home-squares li > * puts 3s on the link, and a
   pseudo-element does not inherit it -- which is what keeps the rollover
   instant rather than crawling. */
.home-squares a::after {
  content: ""; position: absolute; inset: 0; z-index: 2; pointer-events: none;
  background: url("data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20viewBox%3D%270%200%20100%20100%27%3E%3Cg%20fill%3D%27none%27%20stroke%3D%27%23fff%27%20stroke-width%3D%27.6%27%3E%3Cpath%20d%3D%27M3%2010H8M10%203V8%27%2F%3E%3Cpath%20d%3D%27M92%2010H97M90%203V8%27%2F%3E%3Cpath%20d%3D%27M3%2090H8M10%2092V97%27%2F%3E%3Cpath%20d%3D%27M92%2090H97M90%2092V97%27%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E") center / 100% 100% no-repeat;
  opacity: 0; transition: opacity .3s ease;
}
.home-squares a:hover::after, .home-squares a:focus-visible::after { opacity: 1; }

/* Draw them instead of fading them, where the browser can. The fade above
   stays as the fallback, so nothing is lost if mask-size is unsupported. */
@supports (mask-size: 100% 100%) or (-webkit-mask-size: 100% 100%) {
  .home-squares a::after {
    /* Held transparent at rest, not just masked. At 84% the mask edge lies
       exactly on the inner tip of every stroke, and that edge is antialiased,
       so a hairline of all eight showed through before the pointer arrived.
       Masking alone cannot fix that without starting the sweep short of the
       tips and wasting the first frames of the draw on nothing. */
    opacity: 0;
    -webkit-mask-image: linear-gradient(#000, #000); mask-image: linear-gradient(#000, #000);
    -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
    -webkit-mask-position: center; mask-position: center;
    -webkit-mask-size: var(--mark-from) var(--mark-from);
            mask-size: var(--mark-from) var(--mark-from);
    /* Declared on the hover state, not here: with no transition at rest,
       leaving the tile takes the marks off at once instead of un-drawing
       them. That matches the artwork, which un-dims instantly too. */
    transition: none;
  }
  .home-squares a:hover::after, .home-squares a:focus-visible::after {
    /* Opacity flips at the instant the sweep starts -- no duration, just the
       beat as its delay -- so it is never visible while the mask is parked. */
    opacity: 1;
    -webkit-mask-size: var(--mark-to) var(--mark-to);
            mask-size: var(--mark-to) var(--mark-to);
    transition: opacity 0s linear var(--mark-beat),
                -webkit-mask-size var(--mark-draw) ease-out var(--mark-beat),
                        mask-size var(--mark-draw) ease-out var(--mark-beat);
  }
}
@media (prefers-reduced-motion: reduce) {
  .home-squares a::after,
  .home-squares a:hover::after, .home-squares a:focus-visible::after {
    transition-duration: .01ms; transition-delay: 0s;
  }
}

.home-squares a:hover p, .home-squares a:focus-visible p { color: #fff; }
.home-squares a:hover img, .home-squares a:focus-visible img { filter: brightness(.35); }

/* ---------- tag pages ---------- */

/* The three sit on one baseline, not as three centred boxes. The band is a
   flex row, and flex blockifies its children -- so the inline-block and
   vertical-align this rule used to carry never did anything, and the alignment
   was really coming from the band's align-items: center. Centring the boxes of
   11pt Literata, 11pt Open Sans and a 20px cross lands three different
   baselines; and because the base h5 keeps its 20px 0 2px margin here, centring
   the *margin* box dropped "viewing:" by half that difference again -- 9.5px of
   baseline spread in all. Baseline alignment plus the margin reset closes it.
   The band keeps its fixed 22px, so the tag row and the filter row still
   present the same height to the grid below. */
.viewmore { align-items: baseline; }
.viewmore h5, .viewmore h6, .viewmore a { line-height: 1; margin: 0; }
/* 800, not the 900 this said: Open Sans stops at 800 and 900 was silently
   clamping to it -- the same correction the :root note describes. */
.viewmore h6 { margin: 0 8px; color: var(--teal); text-transform: uppercase; letter-spacing: 1.5pt; font-weight: 800; }
.viewmore a { border: 0; color: var(--k30); font-size: 15pt; line-height: 1;
  /* The band aligns on the baseline so the two type sizes sit together, but the
     cross is a symbol, not a word. Centring its box is not enough: the glyph
     sits on the maths axis, low in its own line box, which measured 3px below
     the cap-height centre of the name beside it. Set to 2 by eye: a full 3
     over-corrected against the lighter weight of the cross. */
  align-self: center; position: relative; top: -2px; }
.viewmore a:hover { color: var(--teal); }
/* The label goes on a narrow screen and the tag name starts at the gutter --
   its 8px left margin was only ever spacing it off "viewing:". */
@media (max-width: 450px) {
  .viewmore h5 { display: none; }
  .viewmore h6 { margin-left: 0; font-size: 8.5pt; }
  /* The cross comes down with the name -- at 15pt beside 8.5pt type it was
     nearly twice the height of the words. The -2px above was measured against
     a 20px cross by an 11pt name and neither holds here, so the nudge is
     measured again: 5px lands the glyph's ink centre on the name's cap centre,
     0.17px under it. */
  .viewmore a { font-size: 10pt; top: -5px; }
}

/* ---------- project page ----------
   Ported from _portfolio-item.scss. The two columns are inline-block against a
   font-size:0 parent, which is how the original removed the whitespace between
   them, and each piece of artwork sits in its own grey panel. */

.portfolio { width: var(--gutter); margin: 0 auto; font-size: 0; text-align: center; }

/* The description holds still while the artwork scrolls past it. Sticky rather
   than fixed so it stays inside its own column and stops at the bottom of the
   set; the offset clears the sticky header with the same 30px of air the page
   opens with. It keeps display:inline-block -- the two columns are still an
   inline-block pair against font-size:0, and sticky does not disturb that. */
.portfolio-description {
  display: inline-block; width: 30vw; vertical-align: top;
  text-align: left; padding-right: 100px;
  position: sticky; top: calc(var(--header-height) + 30px);
  /* A handful of projects -- cheesecake-csr is the tallest at ~970px -- have a
     description taller than the screen. Pinned by the top alone their tags
     would sit below the fold and never come back, so the column is capped to
     the viewport and scrolls itself. Short ones never reach the cap and never
     show a bar. */
  max-height: calc(100vh - var(--header-height) - 60px);
  overflow-y: auto;
  scrollbar-width: thin;
  /* A line of air at the foot of the scroll box. Without it the last row of
     tags ends flush with the clip edge and its link underlines are shaved off. */
  padding-bottom: 14pt;
}
/* Above 2000 the copy column stops growing with the screen -- at 30vw it had
   already reached 67 characters a line and would only get longer -- and what it
   gives up goes to the artwork beside it. The page keeps the site gutter, so
   the panel still ends on the header's rule; the width comes out of the copy,
   not out of the margin. Nothing is upscaled by the wider panel: the artwork is
   width:auto, so a small file renders at its own size with more grey around it
   and only the larger ones grow. */
@media (min-width: 2000px) { .portfolio-description { width: 520px; padding-right: 60px; } }

/* Below 1024 the columns stack, so there is nothing to hold still against, and
   the description takes the whole gutter. display:block is what makes
   width:auto fill -- left inline-block it would shrink to fit its content. */
@media (max-width: 1024px) { .portfolio-description { padding: 0 0 30px; width: auto; margin: 0;
                                                       position: static; max-height: none; overflow: visible;
                                                       display: block; } }

/* One band only. Between 781 and 1024 the description is wide enough to set its
   two halves side by side -- the copy at five eighths, the awards, credits and
   tags at three -- with a rule down the trough between them. At 780 the block
   above is all that is left and the column goes back to one.

   The rule is a border on the copy, not on the meta column: with 25px of gap
   and 25px of padding it lands dead centre of a 51px trough, and the 26px it
   costs comes out of the roomier of the two. The items are left to stretch, so
   it runs the full height of the taller column rather than stopping at the end
   of the copy. min-width:0 so a long tag cannot push its column past its
   share. */
@media (min-width: 781px) and (max-width: 1024px) {
  .portfolio-description { display: flex; gap: 25px; }
  .description-main { flex: 5 1 0; min-width: 0; border-right: 1px solid var(--teal); padding-right: 25px; }
  .description-meta { flex: 3 1 0; min-width: 0; }
  /* The h5 that opens the column carries the 20px it uses to stand off the
     copy when the two are stacked. Side by side there is nothing above it,
     and the margin cannot collapse away because a flex item is its own
     formatting context, so it is taken off here and the column starts on
     the rule. :first-child rather than h5 -- awards and credits are both
     conditional, so which label leads depends on the project. */
  .description-meta > :first-child { margin-top: 0; }
}

.portfolio-description h2 { margin-bottom: 5px; }
@media (min-width: 2000px) { .portfolio-description h2 { margin-bottom: 10px; font-size: 40pt; line-height: 42pt; } }
@media (max-width: 1024px) { .portfolio-description h2 { margin-bottom: 8px; } }
.portfolio-description h4 { margin-bottom: 20px; }
.portfolio-description p  { margin-bottom: 10px; }


/* The award has no mark of its own: the h5 label above it does the work, the
   same as the credits and the view-more rows. It was a ribbon hung in the
   margin until 29 Aug -- the artwork is still at assets/img/ribbon.svg, traced
   from ribbon.ai, if it is ever wanted back. The .award class stays on the h6
   as a hook; nothing styles it now. */

/* The tags read as highlights rather than links: the teal at its 50% tint
   behind the word, the full teal on hover. The comma stays outside the box --
   it is on .term::after, which is on the span, not the anchor.

   The leading opens from 22px to 28px, and it has to. A highlight is as tall as
   the type's own content box plus its padding, about 22px at 11pt, and padding
   on an inline element does not push lines apart: at 22px the boxes on a
   wrapped line met edge to edge. 28px (21pt) leaves 6px of ground between them,
   and this row wraps to two or three lines on most projects at the 332px
   column.

   `.terms a` outranks the base `a` rule, which is where the teal underline and
   the 1px bottom padding come from. The hover needs `.terms a:hover` to outrank
   `a:hover` -- that rule turns a link teal, which on a teal ground would take
   the word away. */
/* The credits sit lighter than the rest of the column. She asked for 100;
   Open Sans' weight axis floors at 300, so 300 is the lightest it can render
   and asking for less would silently clamp to exactly this. The <p> is named as
   well as the h6: kirbytext wraps the credits in one, and the base `p` rule
   sets weight on it directly, which inheritance from the h6 cannot outrank. */
.portfolio-description .credits,
.portfolio-description .credits p { font-weight: 300; }

/* A pencil in front of the blog links. It is a mask rather than a background
   image, so the colour comes from the teal token instead of being baked into
   the file; the pseudo-element carries no text, so masking it is safe -- which
   it would not be on the tag highlights, where the mask would have clipped the
   words to the shape.

   The class is put on by the template, and only on links whose label is "off
   the pasteboard" or "behind the scenes" -- never on a credit, whatever it
   points at. Matching the label rather than the address also means this
   survives the blog moving into Kirby, which is on the docket: the URLs change
   then, the words do not. Sized in em so it tracks the copy. */
.portfolio-description a.blog-link::before {
  content: "";
  display: inline-block;
  width: 1em; height: 1em;
  margin-right: .35em;
  vertical-align: -.12em;
  background-color: var(--teal);
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='black' stroke-width='1.4' stroke-linejoin='round'%3E%3Cpath d='M11.2 1.9 14.1 4.8 5.6 13.3 1.9 14.1 2.7 10.4z'/%3E%3Cpath d='M9.6 3.5 12.5 6.4'/%3E%3C/svg%3E"); mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='black' stroke-width='1.4' stroke-linejoin='round'%3E%3Cpath d='M11.2 1.9 14.1 4.8 5.6 13.3 1.9 14.1 2.7 10.4z'/%3E%3Cpath d='M9.6 3.5 12.5 6.4'/%3E%3C/svg%3E");
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
  -webkit-mask-size: contain; mask-size: contain;
  -webkit-mask-position: center; mask-position: center;
}

.terms { line-height: 24pt; }
.terms a {
  border: 0;
  padding: 0 4px 4px 4px;
  color: var(--black);
  background: var(--teal50);
  /* A tag long enough to break across lines keeps its box on both halves. */
  -webkit-box-decoration-break: clone;
          box-decoration-break: clone;
}
.terms a:hover { color: var(--black); background: var(--teal); }
.terms .term { display: inline-block; }
.terms .term::after { display: inline-block; content: ","; padding: 0 5px 0 1px; }
.terms .term:last-child::after { content: ""; }

/* The tag index at /tags. One full-width column on the same gutter as the
   filter band above it and the grid on every other listing page, so the chips
   start where the page starts. .terms does the rest. */
.tag-index { width: var(--gutter); margin: 0 auto; }

.portfolio-content {
  display: inline-block; width: 55vw; max-width: 1000px; min-width: 400px;
  vertical-align: top; text-align: left;
}
@media (min-width: 2000px) { .portfolio-content { width: calc(100% - 520px); max-width: none; } }
@media (max-width: 1024px) { .portfolio-content { display: block; width: auto; min-width: 0; } }

.portfolio-content .item {
  margin-bottom: 25px;
  padding: 40px;
  background-color: var(--panel);
  font-size: 0;
  text-align: center;
}
@media (min-width: 2000px) { .portfolio-content .item { padding: 60px; } }
@media (max-width: 1024px) { .portfolio-content .item { padding: 25px; } }
@media (max-width: 600px)  { .portfolio-content .item { padding: 8px; margin-bottom: 10px; } }

.portfolio-content .item img,
.portfolio-content .item .clip {
  max-height: 80vh;
  width: auto;
  max-width: 100%;
  display: inline-block;
}
.portfolio-content .item .clip[controls] { cursor: pointer; }

/* An optional caption, inside the panel under the artwork. The figure shrinks
   to the picture, so the caption lands on the IMAGE's left edge and not the
   panel's -- artwork is centred in the box and is usually narrower than it, so
   those are two different lines.

   Equal air above and below: 20px over the caption, and the captioned panel
   drops its own bottom padding to 20px to match. The panel's sides narrow to
   25px and 8px on their own, and 8px under a caption would crowd it, so the
   pair is set here rather than left to follow. Below 600 they come down
   together to 8 and 8 -- still equal, which is the point of them. The class is set in the template rather
   than with :has(), so it does not depend on support for that. */
.portfolio-content .item .shot { display: inline-block; margin: 0; text-align: left; max-width: 100%; }
.portfolio-content .item.has-caption { padding-bottom: 20px; }
.portfolio-content .item figcaption {
  margin-top: 20px;
  font-family: var(--sans); font-weight: 650; font-size: 8.25pt; line-height: 10.5pt;
  letter-spacing: .5pt; text-transform: uppercase; color: var(--k50);
}
@media (max-width: 600px) {
  .portfolio-content .item.has-caption { padding-bottom: 8px; }
  .portfolio-content .item figcaption { margin-top: 8px; font-size: 7pt; line-height: 8pt; }
}

/* Infographics and the like are exempt from the cap above: at eight times their
   own width, 80vh crushes them into an unreadable sliver. The old site did this
   in meganhillman.js, on the height attribute alone past 3000px. Height alone
   also caught every high-resolution landscape spread, where max-width binds
   first and the cap was never reaching anyway, so the template asks for twice
   as tall as wide as well and the class now appears only where it does
   something -- seven pieces, the longest 16042px.

   The 3000 floor stays for a second reason: a handful of promo assets are long
   in ratio but small in pixels, and without it a 488px-wide social ad would be
   stretched across the column at half again its own resolution.

   Set in the template rather than on ready, so a long image never paints
   squeezed first and it still holds with JavaScript off. */
.portfolio-content .item img.tall { max-height: none; }

.vimeo-container {
  position: relative; padding-bottom: 56.25%; padding-top: 25px;
  height: 0; margin-bottom: 40px;
}
.vimeo-video { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }

/* The original wrapped each arrow in a figure and coloured it through
   `#portfolio-content figure a { color: $teal }`, so both sat teal at rest
   with no hover state of their own. */
.arrow { position: fixed; top: 235px; font-size: 45pt; margin: 0; z-index: 5; }
.arrow a { border: 0; color: var(--teal); line-height: 1; }
.arrow-left { left: 2%; }
.arrow-right { right: 2%; }
@media (max-width: 780px) { .arrow { font-size: 26.25pt; } }
@media (max-width: 600px) { .arrow { top: 50%; } }


/* ---------- about ---------- */

.about-page { width: var(--gutter); margin: 0 auto; }

.about-info { display: flex; gap: 50px; align-items: flex-start; }
.about-image { flex: 0 0 480px; padding-top: 10px; position: relative; }
/* The picture at the large setting. flex:1 0 auto lets it grow with the row
   but never shrink below the file's own width, so it takes whatever the row
   has spare once the copy has the measure it needs. width:100% on the image
   itself because the box can outgrow the file it holds -- without it the
   picture stops at its own width and the frame marks hang off nothing.

   (The block used to be capped at 1400 and centred, back when the favourites
   sat underneath and the copy ran to 100 characters a line. The three-column
   grid at 1850 took that job over and the cap came out.) */
@media (min-width: 2500px) {
  .about-image { flex: 1 0 auto; }
  .about-image img { width: 100%; }
  /* Two columns rather than one long measure: the copy fills the width the
     headline needs, and each column reads at about 46 characters. No max-width
     -- the cap was there to keep a single column readable, and the columns do
     that job now. */
  .about-body { columns: 2; column-gap: 60px; }
}
.about-image img { display: block; }   /* the marks hug the picture, not a baseline gap */

/* ---------- the picture box, selected ----------
   A placed frame with its selection showing: a hairline edge and eight solid
   handles, four corners and four midpoints. The handles sit centred on the
   edge, so they overhang the picture by half of --handle on every side. */
.about-image { --handle: 10px; }
.frame-marks { position: absolute; inset: 10px 0 0 0; outline: 1px solid var(--teal); pointer-events: none; }
.frame-marks .handle { position: absolute; width: var(--handle); height: var(--handle);
  transform: translate(-50%, -50%); background: var(--teal); border: 1px solid var(--teal); }
.frame-marks .tl { left: 0;    top: 0; }
.frame-marks .tc { left: 50%;  top: 0; }
.frame-marks .tr { left: 100%; top: 0; }
.frame-marks .ml { left: 0;    top: 50%; }
.frame-marks .mr { left: 100%; top: 50%; }
.frame-marks .bl { left: 0;    top: 100%; }
.frame-marks .bc { left: 50%;  top: 100%; }
.frame-marks .br { left: 100%; top: 100%; }
@media (max-width: 1024px) { .about-image { flex-basis: 25vw; } }
/* align-items is restated because the direction flips here. It is flex-start
   on the row above, where it holds the picture and the copy to the top; turned
   to a column it governs the horizontal instead, and flex-start there sizes
   each child to its own content rather than to the column -- the copy came out
   at its max-content 276px inside a 240px gutter and hung 36px past the picture.
   The picture escaped it only by way of the width:100% below, which was
   covering for the same thing. */
@media (max-width: 780px)  { .about-info { flex-direction: column; gap: 20px; align-items: stretch; } .about-image { flex-basis: auto; width: 100%; } }

.about-text { flex: 1; }
/* The headline is sized off this column between 781 and 1440 -- see
   .about-title below. Scoped to that range and not left on everywhere:
   container-type stops an element's width depending on its contents, and above
   1440 the headline's own min-content is exactly what widens this column enough
   to hold it. Contained up there, the column would collapse to its flex share
   and the fixed sizes would overrun it. */
@media (min-width: 781px) and (max-width: 1440px) {
  .about-text { container-type: inline-size; }
}
@media (min-width: 2500px) { .about-text { flex: 4; } }
/* The page's own heading, under the site name in the header. letter-spacing
   is declared because h2 sets .5pt and this never did -- as a div that never
   reached it, and without this the title would loosen on becoming one. */
.about-title { font-family: var(--sans); font-size: 60pt; line-height: 55pt; font-weight: 800; letter-spacing: 0; color: var(--teal); margin-bottom: 30px; width: 500px; max-width: 100%; }
/* From 781 to 2499 the headline sits beside the picture, so what constrains it
   is the copy column -- gutter less the picture and the gap -- and not the
   window. That column swings from 341px at 1025 to 1042px at 1849 while the
   type was a flat 60pt, which is why "PERFECTIONIST." ran 138px past its box
   at every width in the range and scrolled the page below about 1374.

   cqw measures that column. The word is very close to eight times the type
   size, so an eighth of the column is the largest that always fits: 12.5cqw,
   no steps. It replaces the old 42pt and 60pt both.

   Above 2500 the fixed size stays. There the headline's own min-content is
   what widens the column to 1009px -- the title sizing its own container --
   and taking that pressure away would settle the column at 900 and the type at
   112px, smaller than the 95pt chosen for that width. The seam is quiet:
   130px at 2499 against 127px at 2500. */
@media (min-width: 781px) and (max-width: 1440px) { .about-title { font-size: 12.5cqw; line-height: .9em; width: auto; margin-bottom: 20px; } }
@media (min-width: 2500px) { .about-title { font-size: 95pt; line-height: 85pt; margin-bottom: 30px; width: auto; } }
/* width:auto from here down, not just no width: the 1024 rule above still
   applies at these sizes, so its 460px has to be undone rather than omitted.
   Below 780 .about-info is a column and the picture takes the full gutter --
   the title now measures the same. */
/* Below 780 the picture and the copy stack, so the headline has the whole
   gutter to itself and can be sized off the screen instead of stepped down by
   hand. One rule replaces the old 780/700/600/375 ladder.

   9.4 rather than 10, measured: the page margin is a fixed 140px (80 under
   600), so as the screen narrows the gutter loses width faster than the type
   does, and the tightest points are just above each margin change. At 10vw
   "PERFECTIONIST." runs 18px past the gutter at 601 and 15px at 320; 9.4 is
   the largest that clears every width in the range. Line-height keeps the same
   nine-tenths of the size. */
@media (max-width: 780px)  { .about-title { font-size: 9.4vw; line-height: 8.5vw; margin-bottom: 30px; width: auto; } }
@media (max-width: 600px)  { .about-title { margin-bottom: 10px; } }

.about-body { font-family: var(--sans); font-weight: 350; font-size: 13.5pt; line-height: 18.75pt; letter-spacing: .25pt; }
.about-body p { font-size: inherit; line-height: inherit; letter-spacing: inherit; margin-bottom: 15px; }
/* Named rather than left to inherit from .about-body: the rule above sets
   font-size on the paragraph directly, and a value on the container cannot
   outrank it. */
@media (min-width: 2500px) { .about-body p { font-size: 16pt; line-height: 23pt; } }
@media (max-width: 1024px) { .about-body p { font-size: 12pt; line-height: 17pt; letter-spacing: .25pt; } }
@media (max-width: 700px)  { .about-body p { font-size: 11pt; line-height: 15pt; } }

.favorites { display: flex; gap: 40px; margin-top: 50px; }
@media (max-width: 780px) { .favorites { gap: 25px; } }
.favorites h4 { color: var(--teal); margin-bottom: 10px; }
@media (max-width: 600px) { .favorites h4 { margin-bottom: 10px; font-size: 9pt; line-height: 16pt; letter-spacing: 2.5pt; } }
.boxes { flex: 1; font-family: var(--sans); font-weight: 350; font-size: 11pt; letter-spacing: .25pt; }
/* The three columns stop stretching and take their own widths, and the group
   they make is centred on the page. Each column keeps its own left-aligned
   type -- it is the grouping that centres, not the text inside it. */
@media (min-width: 2500px) {
  .favorites { justify-content: center; }
  .boxes { flex: none; }
}
/* The address is kept whole. Left to break, it split as "meghillman at /
   gmail", stranding the second half on its own line with its teal underline
   shaved off; held together it moves to the next line entire, under its label,
   whenever it will not fit beside it.

   On the link and not on .boxes: from the container this holds the label and
   the address on one line together, which wants 263px and pushes the three
   columns past the gutter below about 880.

   The rows are not in a paragraph, whatever the template reads like -- h6 is
   block-level and cannot sit inside <p>, so the parser closes the <p> first and
   hands back <p></p><h6>..</h6> <a>..</a><p></p>. The label and link are direct
   children of .boxes. It is why .boxes h6 is set to inline below, and why a
   .boxes p rule would never reach any of this. */
@media (max-width: 1024px) { .boxes a { text-wrap: nowrap; } }
@media (max-width: 340px) { .boxes { font-size: 9pt; } }
.boxes p { font-size: inherit; margin: 0 0 8px; }
#blogs p { display: block; margin-bottom: 8px; }
.boxes h6 { display: inline; }   /* type comes from h5 */
/* The template writes <p><h6>label</h6> keys</p>. h6 cannot sit inside a p, so
   the parser closes the paragraph first and leaves an empty <p> in front of
   every row -- those empties are what break the rows apart, and their 8px is
   the space between them. The one that opens the box carries that 8px into the
   top of the first column, while a second column begins straight on an h6 and
   has none, which set the two columns 8px out of step. Dropping it from the
   opener costs nothing anywhere else: the h4 above already sets the space. */
#shortcuts > p:empty:first-of-type { margin-bottom: 0; }
#shortcuts, #blogs { border-left: 1px solid var(--teal); padding-left: 40px; }
/* Each shortcut holds its own line, so the app and its keys are never split.
   Set on the column because the rows are not in a paragraph -- see .boxes.
   Bounded at the bottom: holding them together puts a 220px floor under the
   column, which the three-column row can afford at 780 and at 700 but not
   below, where it pushes the row past the gutter. Under 600 .favorites is a
   single column and nothing needs holding. */
@media (min-width: 701px) and (max-width: 780px) { #shortcuts { text-wrap: nowrap; } }
/* .boxes .rip, not .rip: `.boxes h6` two rules up is (0,1,1) and outranks a bare
   class, so it was winning both the display and the margin. The label was
   therefore inline, vertical margins did not apply to it at all, and the gap
   above was only the 8px the blog above carries -- every number written here
   was being dropped. The blog's own 8px collapses into this one, so 20 is the
   gap you see. */
.boxes .rip { display: block; margin: 20px 0 8px; text-transform: uppercase; }

/* Below 728 the three columns stop dividing the row and stack instead, each
   one taking the full gutter. With that much width to itself a group is short
   and wide, so the lists inside divide in two -- the opposite trade to the one
   above 2000, and for the same reason: match the number of columns to the
   width the content actually has. The teal rules move from between the groups
   to above each one, since they are separating rows now rather than columns. */
@media (max-width: 728px) {
  .favorites { flex-direction: column; gap: 0; margin-top: 20px; }
  .boxes { padding: 20px 0 10px; border-top: 1px solid var(--teal); }
  /* Shortcuts and blogs only. Chit chat stays one column at every width: its
     rows are a label and a link together and want the whole measure, where the
     other two are short entries that pair up happily. */
  #shortcuts, #blogs { border-left: 0; padding-left: 0;
                       columns: 2; column-gap: 30px; }
  /* Headings sit over both columns, and the retired blogs under them, exactly
     as they do in the wide layout. */
  .boxes h4 { column-span: all; }
  .rip-group { column-span: all; }
}

/* And back to one below 450. The measure is the blog names: they are held on
   one line by .boxes a, so "things organized neatly" needs 163px, and two
   columns stop giving it that at about 436. 450 clears it with room. */
@media (max-width: 450px) {
  #shortcuts, #blogs { columns: auto; }
}

/* ---------- awards, the year rail ----------
   Letter-spacing here is in points, like the rest of the stylesheet. It was in
   pixels because I wrote this block; the values that stayed in pixels -- 2, 3,
   4 and 5 -- are the original's own and are left as they were found.
   One year per group. A new year rules across both columns; repeats within the
   same year sit under a rule that starts at the copy column. */

/* Scoped to .awards throughout: the project page has an unrelated h6.award --
   just the award copy under its label -- and an unscoped `.award { display: grid }`
   here was reaching across and setting that line in the 78px year column. */
/* Panelled on k3, at the 40px the project page's artwork panel uses -- light
   enough that the k20 year rules and the k30 honour labels both keep the
   contrast they were drawn for on white. */
.awards { margin: 50px 0 0; background: var(--k3); padding: 40px; }
@media (max-width: 600px)  { .awards { padding: 20px; } }
/* h3, not h2: h2 is the project title, a different animal entirely, and this
   was the only other thing wearing that tag. h3 was going spare. */
.awards h3 {
  font-family: var(--serif); font-size: 11.25pt; font-weight: 700; letter-spacing: 2.25pt;
  text-transform: uppercase; color: var(--teal); margin-bottom: 22px; line-height: 1;
}

/* Two columns: the list reads down the first, then on at the top of the second.
   A year group is kept whole, so a repeat is never left standing under nothing. */
.awards .award-list { columns: 2; column-gap: 50px; }
@media (min-width: 2500px) { .awards .award-list { columns: 3; } }
.awards .award-group { break-inside: avoid; }
@media (max-width: 1024px) { .awards .award-list { columns: 1; } }

.awards .award { display: grid; grid-template-columns: 78px 1fr; gap: 0 18px; align-items: start; }
.awards .award-year { border-top: 1px solid var(--k20); }
.awards .award-repeat .award-copy { border-top: 1px solid var(--k20); }
.awards .award > .year, .awards .award-copy { padding: 15px 0 17px; }

/* 3.6px, measured rather than eyeballed: it hangs the year's cap line off the
   org's x-height. Within its own line box Literata's cap top sits 7.35px down
   and Open Sans' x-height top 10.98px, so the year needs 3.63px to meet it.
   The same number holds at 600px, where the org drops to 18px -- it wants
   3.77px there, a tenth of a pixel away. */
.awards .award .year { font-family: var(--serif); font-weight: 750; font-size: 13pt; line-height: 18pt; color: var(--teal); letter-spacing: 1.75pt; margin: 3.6px 0 0; }
/* Deck copy: a line of secondary display under a heading, in both places. */
.awards .award .org { font-family: var(--sans); font-size: 16.5pt; font-weight: 400; color: var(--k50); letter-spacing: .25pt; line-height: 20.63pt; margin: 0; }
@media (max-width: 1024px) { .awards .award .org { font-size: 15pt; line-height: 17pt; } }
/* A plain h4. An honour that runs to two lines sets each as its own block and
   lets h4's own 18pt leading do the separating. */
.awards .award .honor { margin: 3px 0 0; }
.awards .award .honor span { display: block; }
/* h4's leading closes to 13pt at this width, which is what used to hold two
   honour lines apart. The gap is stated here instead. */
@media (max-width: 1024px) { .awards .award .honor span { margin-bottom: 4px; } }
/* Leading set here, not on the shared rule above: 11pt/14pt is also the
   credits, the view-more terms and the blog list, and none of those asked to
   open up. */
.awards .award .desc p { text-transform: none; line-height: 16pt; margin: 7px 0 0; }   /* keeps its capitals */
@media (max-width: 1024px) { .awards .award .desc p { margin: 5px 0 0; } }

@media (max-width: 600px) {
  .awards .award { grid-template-columns: 52px 1fr; gap: 0 12px; }
  .awards .award .org { font-size: 13.5pt; }
}

/* The year rail goes at this width. Even at 52px it left the copy about 140px,
   which broke every org name over four or five lines and hung the year alone in
   a column of its own. One column instead: the year, then the award under it,
   both on the full width.

   The year's 3.6px nudge goes with the rail. It existed to hang the year's cap
   line off the org's x-height while the two sat side by side, and there is no
   side by side left -- kept, it would only push the year off its own rule.

   Stacked, both rules run the full width, so thickness is what tells a new year
   from a second award within one -- 2px over a year, 1px over a repeat, both in
   k20. 2 rather than 1.5: a half pixel rounds on a 1x screen, and it rounded
   towards the repeats, which left the two rules reading almost alike there.

   The empty year is taken out of the repeats. The template prints a <p class=
   "year"> either way and it carried the 15px that stands the year off its rule,
   so on a repeat that 15px was landing above the rule instead of below it: the
   copy then sat 2px under its own line while a year sat 15px under its own.
   Dropped here, both open on the same 15px. */
@media (max-width: 340px) {
  .awards .award { grid-template-columns: 1fr; gap: 0; }
  .awards .award > .year { padding: 15px 0 0; margin: 0; }
  .awards .award-copy { padding: 0 0 17px; }
  .awards .award-repeat > .year { display: none; }
  .awards .award-repeat .award-copy { padding-top: 15px; }
  .awards .award-year { border-top-width: 2px; }
}

/* ---------- about, on a large display ----------
   From 1850 the page becomes three columns: picture and copy as they were,
   with the favourites brought up beside them instead of sitting underneath.
   .about-info and .favorites are siblings, so the page itself does the
   arranging -- a two-track grid, with the awards spanning both underneath.

   This block sits here, after every rule it overrides, on purpose. .favorites,
   .boxes and #shortcuts/#blogs are all declared further up the file, and a
   media query at the same specificity only wins if it comes later; written
   beside .about-page it would have been silently dead.

   The favourites stack rather than sitting three abreast -- the column is not
   wide enough for three, and stacked they read down the page like the copy
   beside them. The teal rules move with them: off the two inner columns, where
   they divided three abreast, and onto the group's left edge, where the whole
   block now needs standing off the copy.

   Type is untouched here. Everything in this band is at the sizes it has at
   1849 -- only the arrangement changes. The larger setting starts at 2500. */
@media (min-width: 1850px) {
  .about-page {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 340px;
    column-gap: 40px;
    align-items: start;
  }
  .about-info    { grid-column: 1; max-width: none; margin: 0; }
  /* align-self overrides the grid's align-items:start for this cell alone, so
     the column fills the row and its teal rule runs the whole depth of the
     picture beside it. The content stays at the top -- justify-content holds
     it there, the box just gets taller underneath it. */
  .favorites     { grid-column: 2; grid-row: 1; margin-top: 0; align-self: stretch;
                   flex-direction: column; gap: 34px; justify-content: flex-start;
                   border-left: 1px solid var(--teal); padding-left: 40px; }
  .awards        { grid-column: 1 / -1; }
  #shortcuts, #blogs { border-left: 0; padding-left: 0; }
  /* A hairline between the groups, the same weight the awards year rules use.
     On the second and third only -- the first sits under the teal rule's top
     and needs nothing above it. */
  #shortcuts, #blogs { border-top: .5px solid var(--k20); padding-top: 30px; }
}

/* At 2000 the shortcuts and the blogs divide in two. Both are long lists of
   short lines and read better paired than run down; chit chat stays one column,
   its lines being label-plus-link and much longer. The rail widens here to pay
   for it -- at the 340 it has from 1850 the two halves would be 134px each,
   which the longer shortcuts do not fit. */
@media (min-width: 2000px) {
  .about-page { grid-template-columns: minmax(0, 1fr) 440px; }
  #shortcuts, #blogs { columns: 2; column-gap: 30px; }
  /* The heading belongs to both columns, not to the first one. Without this it
     flows as ordinary content and the left column starts two lines lower than
     the right. */
  #shortcuts h4, #blogs h4 { column-span: all; }
  /* The retired blogs sit under the pair of columns rather than in them, so
     RIP reads as a footer to the list and not as another entry in it. */
  .rip-group { column-span: all; }
}

/* At 2500 the columns freeze.
   Past that width the extra goes into the trough, not into the columns: the
   copy is already at the measure it wants and the picture does not need to
   keep growing. Column 1 is held at the width it reaches at 2500 -- picture
   596, copy 1009 -- and everything beyond that is air.

   The two halves of the trough are the same expression, because they come from
   different properties: the gap is the air to the LEFT of the teal rule, the
   favourites' own padding-left is the air to its right. 2024 is the frozen
   column plus the favourites' 368 of content, so the remainder splits evenly
   and the rule stays centred however wide the screen gets. vw rather than %,
   since a percentage padding on a grid item resolves against its own track and
   not against the page.

   Shortcuts and blogs take two columns of their own here because both are long
   lists of short lines; chit chat stays one, its lines being label-plus-link
   and much longer. Below 2500 the rail is too narrow to divide at all. */
/* At 2500 the columns freeze and everything past it goes into the trough
   rather than into the content: the copy is at the measure it wants and the
   picture does not need to keep growing. Column 1 holds the width it reaches
   at 2500 -- picture 596, copy 1009.

   The two halves of the trough are the same expression, because they come from
   different properties: the gap is the air to the LEFT of the teal rule, the
   favourites' own padding-left is the air to its right. 2024 is the frozen
   column plus the favourites' 368 of content, so the remainder splits evenly
   and the rule stays centred however wide the screen gets. vw rather than %,
   since a percentage padding on a grid item resolves against its own track and
   not against the page.

   Freezing here rather than at 2000 is what lets the headline be 95pt: column 1
   grows to 1655 before it locks, where at 2000 it would lock at 1220 and cap
   the copy column at 690. */
@media (min-width: 2500px) {
  .about-page {
    grid-template-columns: 1655px minmax(0, 1fr);
    column-gap: calc((85vw - 2024px) / 2);
  }
  .favorites { padding-left: calc((85vw - 2024px) / 2); }
}

/* ---------- 404 ---------- */

/* The teal starts at the header's rule. That rule is the header container's
   bottom border, and it sits 9px above the header's own bottom edge -- the
   container is 62px tall (40px logo + 20px padding + 1px border) centred in an
   80px header. So the header is painted white down to the rule and teal below
   it, and main, pulled up over its own 20px gap, carries the gradient on from
   there with no white in between. Recompute the 9px if --header-height or the
   logo size changes. */
body.error #site-header {
  background: linear-gradient(#fff 0 calc(100% - 9px), var(--teal50) calc(100% - 9px));
}

/* Flat, header rule to footer -- the footer included, so the 8px teal band at
   the very bottom sits on the tint rather than on white. */
body.error main {
  display: flex; flex-direction: column;
  margin-top: -20px; padding-top: 20px;
  background: var(--teal50);
}
body.error footer { background: var(--teal50); }

/* Opening search replaces everything inside main, so the ground goes back to
   white with it -- and the pull has to go too, or the results ride up into the
   11px gap the searching header leaves. */
body.error.searching #site-header,
body.error.searching footer { background: #fff; }
body.error.searching main {
  display: block; background: none; margin-top: 0; padding-top: 0;
}

.page-404 { flex: 1; display: flex; align-items: center; justify-content: center; padding: 30px 20px; }

/* ---- the dialog ---- */

.dialog-404 {
  width: 100%; max-width: 640px;
  /* 80vw below 780 so the card keeps a margin of its own on a narrow screen. */
  background: #fff; border: 1px solid #dcdcdc; border-radius: 12px; overflow: hidden;
  /* Kept short enough to land inside the page. The long ambient it had before
     ran past the foot of main, where the footer cut it off in a hard line. */
  box-shadow: 0 2px 4px rgba(0, 0, 0, .08), 0 14px 28px rgba(0, 0, 0, .10),
              0 36px 60px rgba(0, 0, 0, .12);
}
@media (max-width: 780px) { .dialog-404 { max-width: 80vw; } }
.body-404 { padding: 36px 40px 30px; }
.body-404 img { display: block; width: 48px; height: auto; margin: 0 0 18px; }
.body-404 h2 { margin-bottom: 10px; }
.body-404 .copy p { margin: 0; }   /* the rest is the base p */

.links-404 { width: 100%; border-collapse: collapse; margin-top: 22px; table-layout: fixed; text-align: left; }
.links-404 th { padding: 0 10px 6px 0; border-bottom: 1px solid var(--k20); }
.links-404 td { padding: 7px 10px 7px 0; border-bottom: 1px solid var(--k10); vertical-align: middle; }
.links-404 th:first-child { width: 46%; }
.links-404 th:first-child + th { width: 38%; }
.links-404 .num { width: 16%; text-align: right; padding-right: 0; }
.links-404 .num h6 { font-variant-numeric: tabular-nums; color: var(--k50); }
.links-404 tr.missing td { background: rgba(231, 76, 60, .06); }
.links-404 tr.missing h6 { font-weight: 600; }

/* Both marks occupy the same 18px, so every label in the column starts at the
   same place whichever mark is beside it. */
.links-404 .status { display: flex; align-items: center; gap: 10px; }
.links-404 .linked, .links-404 .lost { width: 18px; height: 18px; flex: none; }
.links-404 .linked { position: relative; }
.links-404 .linked::before {
  content: ""; position: absolute; inset: 4px; border-radius: 50%; background: var(--teal);
}

/* A stop sign, drawn as a true octagon so the question mark has a flat top to
   sit under rather than a point. */
.links-404 .lost {
  background: #e74c3c;
  clip-path: polygon(29.3% 0, 70.7% 0, 100% 29.3%, 100% 70.7%,
                     70.7% 100%, 29.3% 100%, 0 70.7%, 0 29.3%);
  display: flex; align-items: center; justify-content: center;
  font-family: var(--sans); font-weight: 800; font-size: 9pt; line-height: 1; color: #fff;
}

.acts-404 {
  display: flex; justify-content: flex-end; align-items: center; gap: 10px;
  padding: 14px 20px; background: #f1f1f1; border-top: 1px solid #e0e0e0;
}
.acts-404 h5 { margin: 0 auto 0 0; white-space: nowrap; }

.button-404 {
  font-family: var(--sans); font-size: 10.5pt; font-weight: 600; letter-spacing: .75pt;
  line-height: 1; text-transform: uppercase; white-space: nowrap;
  padding: 11px 16px; border-radius: 6px; cursor: pointer;
  border: 1px solid #cfcfcf; background: #fff; color: #3a3a3a;
  box-shadow: 0 1px 1px rgba(0, 0, 0, .06);
}
a.button-404 { display: inline-block; }
.button-404:hover { border-color: var(--teal); color: var(--black); }
.button-404.go {
  background: var(--teal); border-color: #6cc2bb; color: #fff; font-weight: 800;
  box-shadow: 0 1px 2px rgba(0, 0, 0, .14), inset 0 1px 0 rgba(255, 255, 255, .4);
}
.button-404.go:hover { background: #76c8c1; color: #fff; }
.button-404:focus-visible { outline: 3px solid rgba(133, 208, 202, .55); outline-offset: 2px; }

@media (max-width: 600px) {
  /* The logo drops to 35px here, which makes the container 57px and the gap
     below the rule 11.5 rather than 9. */
  body.error #site-header {
    background: linear-gradient(#fff 0 calc(100% - 11.5px), var(--teal50) calc(100% - 11.5px));
  }

  .dialog-404 { border-radius: 10px; }
  .body-404 .copy p { font-size: 11pt; }
  .body-404 { padding: 32px 22px 26px; }
  .body-404 img { width: 40px; margin-bottom: 14px; }
  .links-404 { margin-top: 18px; }
  /* The octagon's corners are a pixel or two each at this size and read as a
     smudge, and the question mark inside them is unreadable. Both marks become
     the same dot, and the row's own tint carries the meaning. */
  .links-404 .lost {
    background: none; clip-path: none; color: transparent; position: relative;
  }
  .links-404 .lost::before {
    content: ""; position: absolute; inset: 4px; border-radius: 50%; background: #e74c3c;
  }

  /* Stacked and centred, recommended action first. Each button is sized to its
     own label rather than stretched -- full width reads as a bar, not a button. */
  .acts-404 { flex-direction: column; align-items: center; gap: 8px; padding: 12px 14px; }
  .acts-404 h5 { order: 1; margin: 0 0 2px; text-align: center; }
  .button-404 { padding: 12px 22px; font-size: 10pt; letter-spacing: .5pt; text-align: center; }
  .button-404.go { order: 2; }
  a.button-404 { order: 3; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { transition-duration: .01ms !important; }
}
