/* Every path is relative so index.html also opens straight off disk. */

/* Subset to ASCII, Latin-1 and Latin Extended-A: 20KB, and no accent in a name
   can quietly fall back to whatever the system happens to have. url() here
   resolves against the sheet, so /games/ gets it too. font/OFL.txt is the
   licence it ships under. */
@font-face {
  font-family: "Atkinson Hyperlegible";
  src: url("font/atkinson-hyperlegible.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

:root {
  color-scheme: light dark;
  -webkit-text-size-adjust: 100%; /* iOS inflates text on rotate without it */
  scrollbar-gutter: stable; /* the sections differ in height; the column must not shift */
  interpolate-size: allow-keywords; /* 0 <-> auto: the project bodies */

  /* --surface was #1a1a1a in dark, the same value as the tile, so a row had no
     face of its own and read as a bare border. */
  --surface: light-dark(#fff, #212121);
  --raised: light-dark(#f4f4f4, #2a2a2a);
  --open: light-dark(#ebebeb, #2f2f2f);
  --open-raised: light-dark(#e3e3e3, #383838);
  --text: light-dark(#000, #e6e6e6);
  --border: light-dark(#555, #666);
  --muted: light-dark(#555, #999);

  --tile: url("img/bg1.png");
  --column: 860px;

  --ease: cubic-bezier(0.2, 0.7, 0.3, 1);
  --fast: 140ms;
  --slow: 260ms;
}

/* light-dark() takes colours, not images. */
@media (prefers-color-scheme: dark) {
  :root {
    --tile: url("img/bg1-dark.png");
  }
}

/* What the toggle sets. light-dark() resolves against the used color-scheme, so
   naming one pins every pair in the sheet; the tile has to be named again. */
:root[data-theme="light"] {
  color-scheme: light;

  --tile: url("img/bg1.png");
}

:root[data-theme="dark"] {
  color-scheme: dark;

  --tile: url("img/bg1-dark.png");
}

@media (prefers-reduced-motion: no-preference) {
  :root {
    scroll-behavior: smooth;
  }
}

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

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

body {
  margin: 0;
  background: var(--tile) repeat;
  color: var(--text);
  /* 17px, not 16: this face carries a 9px x-height there, the same as Verdana
     at 16, so the text reads the size it always has while setting 7% narrower. */
  font: 17px/1.4 "Atkinson Hyperlegible", Verdana, Geneva, "DejaVu Sans", sans-serif;
}

a {
  color: inherit;
  text-underline-offset: 2px;

  &:hover {
    text-decoration-thickness: 2px;
  }
}

img {
  max-width: 100%;
  height: auto;
}

:focus-visible {
  outline: 2px solid currentColor;
  outline-offset: 1px;
}

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

@keyframes appear {
  from {
    opacity: 0;
  }
}

#game {
  position: fixed;
  inset: 0;
  z-index: -1;
  display: block;
  width: 100%;
  height: 100%;
  pointer-events: none;

  &[hidden] {
    display: none;
  }
}

.wrap {
  position: relative;
  width: var(--column);
  margin: 0 auto;
  padding-top: 45px;

  &.bare {
    padding-top: 0;
  }
}

.side {
  position: absolute;
  top: 49px; /* the first button's text sits level with the first row's */
  left: 100%;
  margin-left: 18px;
  white-space: nowrap;

  .menu {
    transition: opacity var(--fast) var(--ease);
  }

  button {
    position: relative;
    display: block;
    padding: 2px 0 2px 11px; /* the gutter holds the #, so nothing shifts on press */
    border: 0;
    background: none;
    font: inherit;
    color: var(--muted);
    text-align: left;
    cursor: pointer;
    transition: color var(--fast) var(--ease);

    &::before {
      content: "#";
      position: absolute;
      left: 0;
      opacity: 0;
      transform: translateX(-3px);
      transition:
        opacity var(--fast) var(--ease),
        transform var(--fast) var(--ease);
    }

    &:hover,
    &:active {
      color: var(--text);
    }

    &[aria-pressed="true"] {
      color: var(--text);
      cursor: default;

      &::before {
        opacity: 1;
        transform: none;
      }
    }
  }
}

.utils {
  margin-top: 26px;
}

#content > section:not([hidden]) {
  animation: appear var(--fast) var(--ease);
}

.projects {
  --sy: 3px; /* summary padding-block; the marker is centred off it */

  list-style: none;
  margin: 0;
  padding: 0;

  > li {
    margin-top: 2px;

    &[hidden] {
      display: none;
    }

    &:not([hidden]) {
      animation: appear var(--slow) var(--ease);
    }
  }

  details {
    scroll-margin-top: 12px;
  }

  summary {
    position: relative;
    background: var(--surface);
    border: 1px solid var(--border);
    padding: var(--sy) 12px var(--sy) 26px;
    list-style: none;
    cursor: pointer;
    transition: background-color var(--fast) var(--ease);

    &::-webkit-details-marker {
      display: none;
    }

    /* A 6px square showing two sides: a chevron, right when shut, down when open. */
    &::before {
      content: "";
      position: absolute;
      left: 9px;
      top: calc(var(--sy) + 0.5lh);
      width: 6px;
      height: 6px;
      margin-top: -3px;
      border-right: 1px solid var(--muted);
      border-bottom: 1px solid var(--muted);
      transform: rotate(-45deg);
      transition: transform var(--slow) var(--ease);
    }

    &:hover {
      background: var(--raised);
    }

    h4 {
      display: inline;
      margin: 0;
      font: inherit;
    }
  }

  details[open] summary {
    background: var(--open);

    &::before {
      transform: rotate(45deg);
    }

    &:hover {
      background: var(--open-raised);
    }
  }

  /* Without ::details-content the body still opens, it just does not slide. */
  details::details-content {
    block-size: 0;
    overflow: clip;
    transition:
      block-size var(--slow) var(--ease),
      content-visibility var(--slow) allow-discrete;
  }

  details[open]::details-content {
    block-size: auto;
  }
}

.year {
  color: var(--muted);
}

.project-body {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 14px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-top: 0;
  padding: 12px;
}

.shots {
  margin: 0;
  padding: 0;
  text-align: center;

  img {
    display: block;
    margin: 0 auto;

    & + img {
      margin-top: 1em;
    }
  }
}

.prose {
  line-height: 1.55;
  text-wrap: pretty;
  overflow-wrap: break-word;

  p,
  ul {
    margin: 0.9em 0 0;
  }

  ul {
    padding-left: 1.6em;
  }

  > :first-child {
    margin-top: 0;
  }
}

/* An era is a run of years with a name: a plain line over its rows, and a
   note in the margin voice. The first one carries no top gap - the strip
   above it is the border it needs. */
.era + .era {
  margin-top: 34px;
}

.era-head {
  margin: 0 0 2px;
  padding-left: 9px; /* level with the chevrons under it */
  font: inherit;
  font-weight: 400;

  .years {
    color: var(--muted);
  }
}

.era-note {
  margin: 0 0 8px;
  padding-left: 9px;
  color: var(--muted);
}

.era .projects {
  margin-top: 6px;
}

.games {
  list-style: none;
  margin: 1.2em 0;
  padding: 0;

  > li {
    margin-top: 2px;
  }

  a {
    display: block;
    background: var(--surface);
    border: 1px solid var(--border);
    padding: 4px 12px;
    text-decoration: none;
    transition: background-color var(--fast) var(--ease);

    &:hover {
      background: var(--raised);
    }
  }
}

/* flow-root contains the floated portrait; no set height needed. */
#bio_top {
  display: flow-root;
}

#bioImg {
  float: left;
  width: 260px;
  margin: 4px 15px 4px 4px;
}

/* Two icons, sized to a 44px tap target through padding rather than through
   the glyph, centred under the portrait: its width plus its 4px on either side. */
#reach {
  display: flex;
  justify-content: center;
  gap: 8px;
  width: calc(260px + 8px);
  margin: 2em 0 0;
}

#reach a {
  display: inline-flex;
  padding: 10px;
  color: inherit;
  opacity: 0.75;
}

#reach a:hover,
#reach a:focus-visible {
  opacity: 1;
}

/* The swipe layer. Fixed over the canvas and under everything else in the
   document order, so the strip's buttons stay tappable through it. touch-action
   none is what keeps a swipe from scrolling or refreshing the page. */
#touch {
  position: fixed;
  inset: 0;
  touch-action: none;

  &[hidden] {
    display: none;
  }
}

#game-toggle {
  position: fixed;
  right: 10px;
  bottom: 10px;
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  font-size: 10px;
  color: var(--muted);
  cursor: pointer;
  transition: color var(--fast) var(--ease);

  &:hover {
    color: var(--text);
  }
}

@media (width <= 1180px) {
  .wrap {
    width: auto;
    max-width: var(--column);
    padding: 0;
  }

  /* .wrap hands its padding to main: the strip has to span the column, or
     scrolled rows show down its sides. */
  .wrap > main {
    padding: 12px;
  }

  /* Not a flex column. As flex items the rows carry min-height: auto, which
     pins the collapsing one at content height until the transition ends. */
  .side {
    position: sticky;
    top: 0;
    z-index: 2;
    margin: 0;
    padding: 4px 12px 5px;
    background: var(--surface);
    border-bottom: 1px solid var(--border);
    white-space: normal;
    transition: transform var(--slow) var(--ease);

    /* Out of the way while reading down the page, back on the first flick up. */
    &.tucked {
      transform: translateY(-101%);
    }

    .menu {
      display: flex;
      flex-wrap: wrap;
      gap: 0 12px;

      /* display here outranks the one the hidden attribute carries. */
      &[hidden] {
        display: none;
      }
    }

    button {
      padding-block: 6px;
    }
  }

  /* Off to the side of the first row rather than on a row of their own: the
     strip is already two rows tall before the page has said anything. */
  .utils {
    position: absolute;
    top: 4px;
    right: 12px;
    display: flex;
    gap: 14px;
    margin-top: 0;
  }

  /* Nothing left to head, and the runner is behind it. */
  .wrap.bare .side {
    background: none;
    border-bottom: 0;
  }

  .projects details {
    scroll-margin-top: 5.5rem;
  }
}

/* Bare 19px text is under the 24px WCAG 2.2 target minimum. Coarse only, so a
   mouse keeps the original spacing. */
@media (pointer: coarse) {
  .side button {
    padding-block: 7px;
  }

  .projects {
    --sy: 7px;
  }

  .games a {
    padding-block: 8px;
  }
}

/* Narrower than this the two of them would sit on top of the section names. */
@media (width <= 340px) {
  .utils {
    position: static;
    justify-content: flex-end;
  }
}

@media (width <= 640px) {
  .project-body {
    grid-template-columns: minmax(0, 1fr);
    padding: 10px;
  }

  .prose ul {
    padding-left: 1.4em;
  }

  #bioImg {
    float: none;
    display: block;
    width: min(260px, 100%);
    margin: 0 auto 1em;
  }

  /* The portrait is centred in the column here, so the icons take the column. */
  #reach {
    width: auto;
  }
}
