/* The toy table: paper and cardboard on a wooden tabletop.
   Content sits on clean paper; the toy lives around it. */

:root {
  --wall: #f1e2c6;
  --ink: #2c241d;
  --ink-soft: #5d5045;
  --ink-faint: #8a7b6c;
  --page: #fffdf7;
  --line: #e6dac4;
  --orange: #ff7a1f;
  /* Orange dark enough to read as text on paper */
  --accent: #b8500c;
  --accent-rgb: 184, 80, 12;
  --blue: #2f6fd6;
  --red: #d9584a;
  --yellow: #f2c14e;
  --green: #6fae66;
  --col: max(min(1180px, 66vw), min(100vw - 32px, 760px));
  /* The project board runs a little past the bottom of the screen: its
     bottom border only shows once the page is scrolled on */
  --board-h: max(540px, calc(100vh + 12px));
  --head: "Figtree", "Nunito", system-ui, sans-serif;
  --body: "Nunito", system-ui, sans-serif;
  color-scheme: light;
}

* {
  box-sizing: border-box;
}

html {
  background: var(--wall);
}

/* Darts and clicks land all over the page, so nothing should highlight by
   accident; form fields stay selectable */
body {
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
}

input,
textarea {
  -webkit-user-select: text;
  user-select: text;
}

body {
  margin: 0;
  color: var(--ink);
  font-family: var(--body);
  font-size: 16px;
  line-height: 1.55;
  background: var(--wall);
}

h1,
h2,
h3,
h4 {
  font-family: var(--head);
  font-weight: 600;
  line-height: 1.2;
  margin: 0;
}

img {
  max-width: 100%;
}

a {
  color: var(--accent);
}

:focus-visible {
  outline: 3px solid var(--blue);
  outline-offset: 2px;
}

#table {
  position: fixed;
  inset: 0;
  width: 100vw;
  height: 100vh;
  display: block;
  z-index: 0;
}

#page {
  position: relative;
  z-index: 1;
  padding-bottom: 48px;
}

/* ------------------------------------------------------------- panels */

/* A content panel is a sheet laid on top of the table. Its paper is drawn
   on a canvas underneath, so a dart can punch a hole through to the table. */
.panel {
  position: relative;
  border-radius: 12px;
  background: var(--page);
  box-shadow: 0 14px 30px rgba(70, 40, 16, 0.28), 0 2px 4px rgba(70, 40, 16, 0.2);
}

.panel.has-sheet {
  background: transparent;
}

.panel-canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: 0;
  pointer-events: none;
}

.panel > :not(.panel-canvas) {
  position: relative;
  z-index: 1;
}

/* Room past the last page, where the table is all there is */
.table-room {
  height: 100vh;
  padding-top: 36px;
  text-align: center;
}

.table-tag {
  display: inline-block;
  margin: 0;
  padding: 0.45em 1em;
  border-radius: 6px;
  background: var(--page);
  box-shadow: 0 0 0 2px #5c4330, 0 6px 12px rgba(70, 40, 16, 0.25);
  font-family: var(--head);
  font-size: 0.95rem;
  color: var(--ink-soft);
  transform: rotate(-1.5deg);
}

/* Only on a touch screen, where aiming the toy is clumsy */
.table-tag-pc {
  display: none;
  margin-top: 0.15em;
  font-size: 0.78em;
  opacity: 0.8;
}

@media (hover: none) and (pointer: coarse) {
  .table-tag-pc {
    display: block;
  }
}

/* ------------------------------------------------------------- splash */

/* The first screen is the table alone. The intro lies on it as a real
   sheet of paper in the 3D scene, and the panel here stays see-through
   until that sheet has come up into its place (main.js). */
.splash {
  height: 100vh;
  height: 100svh;
}

/* Everything below the intro stays out of sight while the sheet is still
   coming up off the table, and fades in once the intro is upright */
main {
  transition: opacity 0.45s ease, visibility 0s;
}

body.sheet-flying main {
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.2s ease, visibility 0s 0.2s;
}

/* Once the sheet has landed, the intro holds still at its rest on the
   screen (set in main.js) while a stretch of scrolling passes under it,
   then the page moves on */
/* Keeps the intro's margin inside, so main.js finds where the intro sits */
.intro-stage {
  display: flow-root;
}

.has-hold .intro-flip {
  position: sticky;
  top: var(--intro-rest, 28px);
}

.has-hold .intro-stage::after {
  content: "";
  display: block;
  height: 35vh;
}

/* The page holds still, and stays out of sight under the mist, while the
   table and the intro sheet are still on their way */
html.sheet-loading {
  overflow: hidden;
}

#page {
  transition: opacity 0.6s ease 0.3s;
}

html.sheet-loading #page {
  opacity: 0;
}

/* On a phone the table is too small to look at, so the page starts at the
   intro; so it does when the load is too slow for the splash (main.js) */
html.no-splash .splash {
  display: none;
}

@media (max-width: 760px), (hover: none) and (pointer: coarse) {
  .splash {
    display: none;
  }
}

/* On the splash, the sun lands on the intro sheet: a warm glow round it,
   and the table round that in shade, so the sheet is where the eye goes.
   The shade stops at the table's far edge, so the sky stays sunny. main.js
   puts its middle on the sheet and fades it as the sheet comes up. */
.splash-light {
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  opacity: 0;
}

.splash-light::before,
.splash-light::after {
  content: "";
  position: absolute;
  inset: 0;
}

.splash-light::before {
  background: radial-gradient(
    calc(var(--rx, 300px) * 2.1) calc(var(--ry, 200px) * 3.4) at var(--x, 50%) var(--y, 70%),
    rgba(58, 32, 14, 0) 30%,
    rgba(58, 32, 14, 0.44) 100%
  );
  -webkit-mask-image: linear-gradient(to bottom, transparent calc(50vh - 3vw), #000 calc(50vh + 9vw));
  mask-image: linear-gradient(to bottom, transparent calc(50vh - 3vw), #000 calc(50vh + 9vw));
}

.splash-light::after {
  background: radial-gradient(
    calc(var(--rx, 300px) * 0.95) calc(var(--ry, 200px) * 1.6) at var(--x, 50%) var(--y, 70%),
    rgba(255, 244, 214, 0.3) 0%,
    rgba(255, 244, 214, 0.26) 50%,
    rgba(255, 244, 214, 0) 100%
  );
}

/* The sunbeam: a shaft of light from the hanging sun down onto the sheet.
   main.js lays the shaft from the sun to past the sheet; it is narrow at
   the sun and as wide as the sheet where it lands, brightest there. */
.splash-beam {
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  opacity: 0;
  filter: blur(10px);
}

.splash-beam > div {
  position: absolute;
  left: 0;
  top: 0;
  transform-origin: 0 0;
  clip-path: polygon(0 calc(50% - var(--near, 30px)), 100% 0, 100% 100%, 0 calc(50% + var(--near, 30px)));
  background: linear-gradient(
    to right,
    rgba(255, 246, 220, 0.36) 0%,
    rgba(255, 246, 220, 0.26) 22%,
    rgba(255, 246, 220, 0.28) calc(var(--land, 75%) - 12%),
    rgba(255, 246, 220, 0.42) var(--land, 75%),
    rgba(255, 246, 220, 0.4) 92%,
    rgba(255, 246, 220, 0) 100%
  );
}

/* A note scribbled on the table just past the sheet, with an arrow down at
   it. It writes itself out once and then stays still. */
.splash-cue {
  position: fixed;
  z-index: 1;
  margin: 0;
  display: flex;
  align-items: flex-end;
  gap: 4px;
  pointer-events: none;
  opacity: 0;
  font-family: "Shantell Sans", var(--head);
  color: #3d2b20;
  text-shadow: 0 1px 0 rgba(255, 250, 240, 0.9), 0 0 10px rgba(255, 246, 228, 0.95), 0 0 22px rgba(255, 246, 228, 0.7);
  white-space: nowrap;
}

.cue-words {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  margin-bottom: 14px;
  transform: rotate(-2deg);
  opacity: 0;
  transition: opacity 0.4s ease 0.7s;
}

.cue-big {
  font-size: 1.7rem;
  font-weight: 600;
  line-height: 1.1;
}

.cue-small {
  font-size: 1.02rem;
  font-weight: 400;
}

.cue-arrow {
  width: 46px;
  height: 43px;
  flex: none;
  overflow: visible;
  fill: none;
  stroke: #3d2b20;
  stroke-width: 3.2;
  stroke-linecap: round;
  stroke-linejoin: round;
  filter: drop-shadow(0 0 3px rgba(255, 246, 228, 0.95));
  /* Drawn pointing up; turned to point down and to the left, onto the sheet */
  transform: rotate(-135deg);
}

.cue-arrow path {
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
  transition: stroke-dashoffset 0.5s ease-in-out 0.3s;
}

.cue-arrow path + path {
  transition-delay: 0.75s;
  transition-duration: 0.2s;
}

.splash-cue.is-on .cue-words {
  opacity: 1;
}

.splash-cue.is-on .cue-arrow path {
  stroke-dashoffset: 0;
}

@media (prefers-reduced-motion: reduce) {
  .cue-words,
  .cue-arrow path {
    transition: none;
  }
}

/* ------------------------------------------------------------- blocks */

/* Buttons are painted wooden blocks: they press in when hit */
.block-btn {
  --face: var(--yellow);
  --edge: #b98a2a;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.4em;
  min-height: 40px;
  padding: 0.45em 1em;
  border: 0;
  border-radius: 7px;
  background:
    linear-gradient(180deg, rgba(255, 255, 255, 0.35), rgba(255, 255, 255, 0) 45%),
    var(--face);
  color: var(--ink);
  font-family: var(--head);
  font-size: 0.98rem;
  font-weight: 600;
  line-height: 1.1;
  text-decoration: none;
  box-shadow: 0 4px 0 var(--edge), 0 6px 8px rgba(60, 38, 16, 0.25);
  transform: translateY(0);
  transition: transform 0.08s ease-out, box-shadow 0.08s ease-out, filter 0.12s;
  cursor: pointer;
}

.block-btn:hover {
  filter: brightness(1.05);
}

.block-btn:active,
.block-btn.is-hit {
  transform: translateY(3px);
  box-shadow: 0 1px 0 var(--edge), 0 2px 3px rgba(60, 38, 16, 0.25);
}

.block-btn:disabled {
  opacity: 0.45;
  cursor: default;
}

.block-btn--blue {
  --face: #8fb0de;
  --edge: #4a6fa8;
}

.block-btn--link {
  --face: #f3e5c9;
  --edge: #bfa47a;
}

.block-btn--green {
  --face: #aac68a;
  --edge: #6b8a4c;
}

/* -------------------------------------------------------------- intro */

/* The intro is a card with the résumé on its back. Both faces share one
   grid cell; the card turns over, and grows to nearly a screen tall while
   the résumé is showing. */
/* As wide as the project board below it. Lying on the table, the sheet is
   capped in scene.js so it still clears the props beside it. */
.intro-flip {
  width: var(--col);
  margin: 28px auto 30px;
  scroll-margin-top: 28px;
  display: grid;
  perspective: 2400px;
  min-height: 0;
  transition: min-height 0.6s ease;
}

.intro-flip > .panel {
  grid-area: 1 / 1;
  backface-visibility: hidden;
  transition: transform 0.75s cubic-bezier(0.45, 0.05, 0.2, 1);
}

.intro-back {
  transform: rotateY(180deg);
  display: flex;
  flex-direction: column;
  gap: 14px;
  padding: 24px 28px 28px;
}

.intro-flip.is-flipped {
  min-height: calc(100vh - 56px);
}

.intro-flip.is-flipped > .intro {
  transform: rotateY(-180deg);
}

.intro-flip.is-flipped > .intro-back {
  transform: rotateY(0deg);
}

.intro-back-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}

.intro-back-bar h2 {
  font-size: 1.9rem;
}

.intro-back-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
}

.resume-frame {
  flex: 1;
  width: 100%;
  min-height: 0;
  border: 0;
  border-radius: 6px;
  background: #fff;
  box-shadow: 0 0 0 2px var(--line);
}

/* The intro is only focused from script, to carry keyboard focus on from
   the splash, so it never shows a focus ring */
.intro:focus {
  outline: none;
}

.intro {
  padding: 30px 36px 32px;
  display: grid;
  grid-template-columns: minmax(140px, 200px) minmax(0, 1fr);
  gap: 32px;
  align-items: start;
}

.intro-portrait {
  margin: 0;
}

.intro-portrait img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: 10px;
  box-shadow: 0 4px 0 var(--line);
}

.intro-copy h1 {
  font-size: clamp(1.9rem, 3vw, 2.6rem);
}

.intro-copy > p {
  margin: 0.55em 0;
  max-width: 62ch;
}

.intro-role {
  margin-top: 2px !important;
  color: var(--accent);
  font-family: var(--head);
  font-size: 1.1rem;
}

.intro-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin-top: 18px;
}

/* -------------------------------------------------------------- board */

/* Cardboard round the carousel. Only its frame is cardboard: the middle
   is open, so a hole in a slide looks through to the table. */
.board {
  width: var(--col);
  height: var(--board-h);
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  border: 20px solid #cfa56c;
  border-radius: 14px;
  /* Inked at the edge, like the intro and More work sheets */
  box-shadow: 0 0 0 2.5px #5c4330, 0 14px 30px rgba(70, 40, 16, 0.28), 0 2px 4px rgba(70, 40, 16, 0.2);
}

.board-track {
  flex: 1;
  min-height: 0;
  display: flex;
  overflow-x: auto;
  overflow-y: hidden;
  scroll-snap-type: x mandatory;
  overscroll-behavior-x: contain;
  scrollbar-width: none;
}

.board-track::-webkit-scrollbar {
  display: none;
}

/* The projects are bookmarks sticking up from behind the board's top
   edge, so every project is in reach as soon as the board comes into view.
   They sit under the board, which hides their tucked-in ends. */
.board {
  position: relative;
  margin-top: 50px;
}

.board-tabs {
  --tuck: 16px;
  position: absolute;
  z-index: -1;
  bottom: calc(100% + 20px - var(--tuck));
  left: 10px;
  right: 10px;
  display: flex;
  flex-wrap: wrap-reverse;
  align-items: flex-end;
  gap: 6px;
}

.bookmark {
  --face: #f3e5c9;
  --edge: #bfa47a;
  /* Room at the bottom for the tucked-in end and the drop of a closed bookmark */
  padding: 0.5em 0.85em calc(var(--tuck) + 6px + 0.4em);
  border: 0;
  border-radius: 9px 9px 0 0;
  background:
    linear-gradient(180deg, rgba(255, 255, 255, 0.35), rgba(255, 255, 255, 0) 45%),
    var(--face);
  color: var(--ink);
  font-family: var(--head);
  font-size: 0.92rem;
  font-weight: 600;
  line-height: 1.1;
  white-space: nowrap;
  box-shadow: 0 0 0 2px #5c4330, 0 4px 0 2px var(--edge), 0 6px 10px rgba(60, 38, 16, 0.25);
  transform: translateY(6px);
  transition: transform 0.16s cubic-bezier(0.3, 1.5, 0.5, 1), filter 0.12s;
  cursor: pointer;
}

.bookmark:hover {
  filter: brightness(1.05);
  transform: translateY(0);
}

/* The open project's bookmark sticks up furthest */
.bookmark[aria-current="true"] {
  --face: var(--orange);
  --edge: #b04f0c;
  color: #fff;
  transform: translateY(-6px);
}

.bookmark:active,
.bookmark.is-hit {
  transform: translateY(10px);
  transition-duration: 0.06s;
}

/* Phones: all four share the top edge, a long name on two lines */
@media (max-width: 640px) {
  .board {
    margin-top: 58px;
  }

  .board-tabs {
    left: -6px;
    right: -6px;
    flex-wrap: nowrap;
    align-items: stretch;
  }

  .bookmark {
    flex: 1 1 0;
    min-width: 0;
    padding-inline: 0.4em;
    font-size: 0.8rem;
    white-space: normal;
  }
}

.slide {
  flex: 0 0 100%;
  height: 100%;
  border-radius: 4px;
  box-shadow: none;
  scroll-snap-align: start;
  scroll-snap-stop: always;
}

.slide-scroll {
  height: 100%;
  overflow-y: auto;
  padding: 30px 40px 40px;
  scrollbar-color: #cdb894 transparent;
}

.slide-head {
  margin-bottom: 18px;
}

.slide-count {
  display: block;
  font-family: var(--head);
  font-size: 0.8rem;
  color: var(--ink-faint);
  letter-spacing: 0.08em;
}

.slide-head h2 {
  margin-top: 4px;
  font-size: clamp(1.8rem, 2.8vw, 2.5rem);
}

.slide-status {
  display: inline-block;
  vertical-align: middle;
  margin-left: 6px;
  padding: 0.2em 0.6em;
  border-radius: 999px;
  background: rgba(47, 111, 214, 0.12);
  color: #23539e;
  font-size: 0.85rem;
  font-weight: 600;
}

.slide-lead {
  display: grid;
  grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr);
  gap: 26px;
  align-items: start;
}

.slide-hero {
  margin: 0;
}

.slide-hero img,
.slide-hero video {
  display: block;
  background: #1d1812;
  width: 100%;
  aspect-ratio: 16 / 10;
  object-fit: cover;
  border-radius: 4px;
  box-shadow: 0 2px 0 var(--line);
}

.slide-facts {
  display: grid;
  gap: 6px;
  margin: 14px 0 0;
  font-size: 0.92rem;
}

.slide-facts > div {
  display: grid;
  grid-template-columns: 5.5rem minmax(0, 1fr);
  gap: 10px;
}

.slide-facts dt {
  font-family: var(--head);
  font-size: 0.78rem;
  font-weight: 700;
  color: var(--ink-faint);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  line-height: 1.6rem;
}

.slide-facts dd {
  margin: 0;
  font-weight: 600;
}

.slide-duties {
  display: block;
  color: var(--ink-soft);
  font-weight: 400;
}

.slide-desc {
  margin: 12px 0 0;
  color: var(--ink-soft);
}

.slide-links {
  margin-top: 16px;
}

.slide-process {
  margin: 34px 0 4px;
  padding-top: 18px;
  border-top: 2px dashed var(--line);
  font-size: 1.35rem;
}

.slide-note {
  display: inline-block;
  margin: 10px 0 0;
  padding: 0.5em 0.9em;
  background: #fff3a8;
  color: #5b4a00;
  font-weight: 600;
  box-shadow: 0 2px 4px rgba(60, 38, 16, 0.18);
  transform: rotate(-1deg);
}

.slide-trailer h3 {
  margin-bottom: 10px;
}

/* ----------------------------------------------------- project details */

.project-award {
  margin: 4px 0 0;
  color: var(--accent);
  font-weight: 700;
  font-size: 0.95rem;
}

.project-contributions h4 {
  margin: 0 0 6px;
  font-size: 0.85rem;
  color: var(--ink-faint);
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.project-contributions ul {
  margin: 0;
  padding-left: 1.15rem;
}

.project-contributions li + li {
  margin-top: 4px;
}

.project-stack {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin: 10px 0 0;
  padding: 0;
  list-style: none;
}

.project-stack li {
  padding: 0.18em 0.6em;
  border-radius: 999px;
  background: rgba(var(--accent-rgb), 0.1);
  color: var(--accent);
  font-size: 0.8rem;
  font-weight: 700;
}

.project-embed {
  width: min(100%, 44rem);
}

.project-embed iframe,
.project-embed-fallback {
  display: block;
  width: 100%;
  aspect-ratio: 16 / 9;
  border: 0;
  border-radius: 4px;
  background: #1d1812;
}

.project-embed-fallback {
  position: relative;
  overflow: hidden;
  color: #fff;
}

.project-embed-fallback img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0.8;
}

.project-embed-fallback span {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  padding: 0.5em 0.9em;
  border-radius: 6px;
  background: rgba(20, 16, 12, 0.75);
  font-weight: 700;
}

/* --------------------------------------------------------------- beats */

.beat {
  margin-top: 22px;
}

.beat h2 {
  margin: 0 0 10px;
  font-size: 1.2rem;
}

.beat p {
  margin: 0.6em 0;
}

.pass-row {
  display: grid;
  gap: 14px;
  margin: 14px 0 6px;
}

.pass-row.cols-2 {
  grid-template-columns: 1fr 1fr;
}

.pass-row.cols-3 {
  grid-template-columns: 1fr 1fr 1fr;
}

.pass-row--tracks {
  grid-template-columns: var(--row-tracks);
}

.pass-row--narrow {
  max-width: 28rem;
  margin-left: auto;
  margin-right: auto;
}

.pass-row--arrow {
  align-items: center;
}

.pass-arrow {
  display: block;
  width: 1.6rem;
  height: 1.6rem;
  background: var(--orange);
  clip-path: polygon(0 35%, 55% 35%, 55% 10%, 100% 50%, 55% 90%, 55% 65%, 0 65%);
}

.pass {
  display: flex;
  flex-direction: column;
  height: 100%;
  margin: 0;
  min-width: 0;
}

.pass figcaption {
  margin: 0 0 6px;
  font-family: var(--head);
  font-size: 0.88rem;
  color: var(--ink-soft);
}

.pass-box {
  display: flex;
  flex-direction: column;
  flex: 1;
  border: 2px solid var(--line);
  border-radius: 5px;
  overflow: hidden;
  background: #fff;
}

.pass video,
.pass img {
  display: block;
  width: 100%;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  background: #1d1812;
}

.pass img.pass-natural {
  aspect-ratio: auto;
  height: auto;
}

.pass-text {
  flex: 1;
  padding: 8px 10px 10px;
}

.pass .pass-note {
  margin: 0;
  font-size: 0.9rem;
  line-height: 1.45;
  color: var(--ink-soft);
}

.pass .pass-stat {
  margin: 0 0 4px;
  font-family: var(--head);
  color: var(--accent);
}

.beats--log {
  position: relative;
  margin: 18px 0 0 6px;
  padding-left: 26px;
  border-left: 2px dashed var(--line);
  container-type: inline-size;
}

.beat--log {
  position: relative;
  margin-top: 4px;
}

.beat--log::before {
  content: "";
  position: absolute;
  left: -34px;
  top: 16px;
  width: 14px;
  height: 14px;
  border-radius: 3px;
  background: #d8c8ad;
}

.beat--log[open]::before {
  background: var(--orange);
}

.beat--log > summary {
  position: relative;
  display: block;
  padding: 8px 44px 8px 0;
  list-style: none;
  cursor: pointer;
}

.beat--log > summary::-webkit-details-marker {
  display: none;
}

.beat--log > summary::after {
  content: "+";
  position: absolute;
  right: 0;
  top: 50%;
  display: grid;
  place-items: center;
  width: 30px;
  height: 30px;
  transform: translateY(-50%);
  border-radius: 6px;
  background: #f3e5c9;
  box-shadow: 0 3px 0 #bfa47a;
  font-family: var(--head);
  font-size: 1.1rem;
}

.beat--log[open] > summary::after {
  content: "\2212";
}

.beat--log > summary.is-hit::after {
  transform: translateY(calc(-50% + 2px));
  box-shadow: 0 1px 0 #bfa47a;
}

.beat-step {
  display: block;
  font-family: var(--head);
  font-size: 0.8rem;
  color: var(--accent);
  letter-spacing: 0.06em;
}

.beat--log > summary h2 {
  margin: 2px 0 0;
}

.beat-body {
  padding: 2px 0 16px;
}

.beat-callout {
  margin: 14px 0;
  padding: 12px 14px;
  border-left: 4px solid var(--red);
  border-radius: 0 6px 6px 0;
  background: rgba(217, 88, 74, 0.08);
}

.beat-callout-label {
  display: block;
  margin-bottom: 4px;
  font-family: var(--head);
  font-size: 0.8rem;
  color: #a8392d;
  text-transform: uppercase;
  letter-spacing: 0.06em;
}

.beat-callout p {
  margin: 0;
}

.beat-flow {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(0, 1fr);
  gap: 1.5rem;
  margin: 16px 0;
  padding: 0;
  list-style: none;
}

.beat-flow-step {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 10px 11px 12px;
  border: 2px solid var(--line);
  border-radius: 6px;
  background: #fff;
}

.beat-flow-step.is-host {
  border-color: var(--orange);
  background: rgba(255, 122, 31, 0.07);
}

.beat-flow-step:not(:last-child)::after {
  content: "";
  position: absolute;
  top: 50%;
  right: calc(-1.5rem + 0.2rem);
  width: 1.1rem;
  height: 1.1rem;
  transform: translateY(-50%);
  background: var(--orange);
  clip-path: polygon(0 35%, 55% 35%, 55% 10%, 100% 50%, 55% 90%, 55% 65%, 0 65%);
}

.beat-flow-actor {
  font-family: var(--head);
  font-size: 0.75rem;
  color: var(--ink-faint);
  text-transform: uppercase;
  letter-spacing: 0.06em;
}

.beat-flow-step.is-host .beat-flow-actor {
  color: var(--accent);
}

.beat-flow-step strong {
  display: flex;
  align-items: center;
  gap: 6px;
  font-family: var(--head);
}

.beat-flow-num {
  display: inline-grid;
  place-items: center;
  flex: none;
  width: 1.3rem;
  height: 1.3rem;
  border-radius: 4px;
  background: var(--orange);
  color: #fff;
  font-size: 0.75rem;
}

.beat-flow-text {
  font-size: 0.88rem;
  line-height: 1.4;
  color: var(--ink-soft);
}

/* Four steps read better as two rows of two than as four narrow columns. */
.beat-flow--2 {
  grid-auto-flow: row;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 1.3rem 1.5rem;
}

.beat-flow--2 .beat-flow-step:nth-child(even)::after {
  display: none;
}

.beat-flow--2 .beat-flow-step {
  padding: 12px 14px 14px;
}

@container (max-width: 46rem) {
  .beat-flow {
    grid-auto-flow: row;
    gap: 1.3rem;
  }

  .beat-flow-step:not(:last-child)::after {
    top: auto;
    right: auto;
    left: 50%;
    bottom: calc(-1.3rem + 0.1rem);
    transform: translateX(-50%) rotate(90deg);
  }
}

/* The two-column variant has no devlog container, so it follows the screen.
   On a phone it becomes a single column with arrows pointing down. */
@media (max-width: 40rem) {
  .beat-flow--2 {
    grid-template-columns: none;
    gap: 1.3rem;
  }

  .beat-flow--2 .beat-flow-step:nth-child(even):not(:last-child)::after {
    display: block;
  }

  .beat-flow--2 .beat-flow-step:not(:last-child)::after {
    top: auto;
    right: auto;
    left: 50%;
    bottom: calc(-1.3rem + 0.1rem);
    transform: translateX(-50%) rotate(90deg);
  }
}

/* --------------------------------------------------------- more work */

.other {
  width: var(--col);
  margin: 34px auto 0;
  padding: 30px 36px 36px;
}

.other > h2 {
  font-size: 1.9rem;
}

.thumb-group {
  margin-top: 22px;
}

.thumb-group h3 {
  font-size: 1.15rem;
  color: var(--ink-soft);
}

.thumb-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(170px, 1fr));
  gap: 14px;
  margin: 10px 0 0;
  padding: 0;
  list-style: none;
}

.thumb {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  width: 100%;
  height: 100%;
  padding: 8px 8px 10px;
  border: 0;
  border-radius: 8px;
  background: #fff;
  color: var(--ink);
  font: inherit;
  text-align: left;
  box-shadow: 0 0 0 2px var(--line), 0 4px 0 var(--line);
  transition: transform 0.08s ease-out, box-shadow 0.08s ease-out;
  cursor: pointer;
}

.thumb:hover {
  box-shadow: 0 0 0 2px #d9c39b, 0 4px 0 #d9c39b;
}

.thumb:active,
.thumb.is-hit {
  transform: translateY(3px);
  box-shadow: 0 0 0 2px var(--line), 0 1px 0 var(--line);
}

.thumb-shot {
  display: block;
  aspect-ratio: 16 / 10;
  border-radius: 5px;
  overflow: hidden;
  background: #efe5d2;
}

.thumb-shot img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.thumb-name {
  margin-top: 8px;
  font-family: var(--head);
  font-weight: 600;
  line-height: 1.2;
}

.thumb-line {
  margin-top: 3px;
  color: var(--accent);
  font-size: 0.82rem;
  font-weight: 700;
}

.thumb-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  margin-top: 6px;
}

.thumb-tags span {
  padding: 0.12em 0.55em;
  border-radius: 999px;
  border: 1.5px solid rgba(var(--accent-rgb), 0.55);
  background: transparent;
  color: var(--accent);
  font-size: 0.72rem;
  font-weight: 700;
}

.about-marks {
  margin: 0.6em 0;
  padding-left: 1.15rem;
}

.skill-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin: 12px 0 0;
  padding: 0;
  list-style: none;
}

.skill-tags li {
  padding: 0.2em 0.65em;
  border-radius: 5px;
  background: #f3e5c9;
  box-shadow: 0 2px 0 #d6c19b;
  font-size: 0.88rem;
  font-weight: 700;
}

.skill-tags--inline {
  display: none;
}

.contact-links {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin: 14px 0 0;
  padding: 0;
  list-style: none;
}

.contact-links a {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 0.5em 0.9em;
  border-radius: 7px;
  background: #fff;
  box-shadow: 0 0 0 2px var(--line);
  color: var(--ink);
  font-weight: 700;
  text-decoration: none;
}

.contact-links a:hover {
  color: var(--accent);
}

/* --------------------------------------------------------------- peek */

.peek {
  width: min(880px, 92vw);
  max-height: 88vh;
  padding: 0;
  border: 0;
  border-radius: 8px;
  background: var(--page);
  color: var(--ink);
  box-shadow: 0 24px 60px rgba(40, 26, 12, 0.4);
}

.peek::backdrop {
  background: rgba(44, 30, 16, 0.45);
}

#peek-body {
  max-height: 88vh;
  overflow-y: auto;
  padding: 30px 36px 36px;
}

#peek-body h1 {
  font-size: 2rem;
  padding-right: 90px;
}

#peek-body .project-detail > * + * {
  margin-top: 14px;
}

#peek-body .project-detail-media,
#peek-body .project-card-media {
  width: 100%;
  border-radius: 5px;
}

#peek-body .project-detail-photos {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
}

#peek-body .project-detail-photo {
  width: min(100%, 18rem);
  margin: 0;
}

#peek-body .project-detail-photo img {
  border-radius: 5px;
}

.peek-close {
  position: absolute;
  top: 18px;
  right: 22px;
  z-index: 2;
}

/* ---------------------------------------------------------------- toy */

#fx {
  position: fixed;
  inset: 0;
  z-index: 21;
  pointer-events: none;
  overflow: hidden;
}

#aim {
  position: fixed;
  left: 0;
  top: 0;
  z-index: 40;
  width: 28px;
  height: 28px;
  margin: -14px 0 0 -14px;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.12s, width 0.12s, height 0.12s, margin 0.12s;
}

/* The ring is its own layer, so it can pop on a shot without moving */
.aim-ring {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  border: 2px solid #fff;
  box-shadow: 0 0 0 1.5px rgba(44, 30, 16, 0.75), inset 0 0 0 1.5px rgba(44, 30, 16, 0.75);
  transition: border-color 0.12s;
}

.aim-ring::after {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  width: 4px;
  height: 4px;
  margin: -2px 0 0 -2px;
  border-radius: 50%;
  background: var(--orange);
  box-shadow: 0 0 0 1.5px rgba(44, 30, 16, 0.75);
}

body.toy-on #aim.is-shown {
  opacity: 1;
}

#aim.is-target {
  width: 38px;
  height: 38px;
  margin: -19px 0 0 -19px;
}

#aim.is-target .aim-ring {
  border-color: var(--orange);
}

/* Over the clock, the crosshair turns into a mouse wheel, with the clock's
   line and what the wheel does beside it */
.aim-wheel,
.aim-note {
  position: absolute;
  opacity: 0;
  transition: opacity 0.15s;
}

.aim-wheel {
  left: 50%;
  top: 50%;
  width: 30px;
  height: 42px;
  margin: -21px 0 0 -15px;
  overflow: visible;
  fill: none;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.wheel-body {
  fill: #fff;
  stroke: rgba(44, 30, 16, 0.85);
  stroke-width: 2.5;
}

.wheel-roller {
  fill: var(--orange);
  stroke: rgba(44, 30, 16, 0.85);
  stroke-width: 1.5;
}

.wheel-arrow {
  stroke: #fff;
  stroke-width: 3;
  filter: drop-shadow(0 0 1.5px rgba(44, 30, 16, 0.9));
}

.aim-note {
  left: calc(50% + 26px);
  top: 50%;
  translate: 0 -50%;
  display: grid;
  gap: 2px;
  font-family: "Shantell Sans", var(--head);
  color: #3d2b20;
  white-space: nowrap;
  text-shadow: 0 1px 0 rgba(255, 250, 240, 0.9), 0 0 10px rgba(255, 246, 228, 0.95), 0 0 22px rgba(255, 246, 228, 0.7);
}

.aim-quote {
  font-size: 17px;
  font-weight: 600;
}

#aim.is-clock .aim-ring {
  opacity: 0;
}

#aim.is-clock .aim-wheel,
#aim.is-clock .aim-note {
  opacity: 1;
}

/* While time winds, the roller turns the way it is going, and that arrow lights up */
html.time-back .wheel-roller {
  animation: roll-down 0.28s linear infinite;
}

html.time-on .wheel-roller {
  animation: roll-up 0.28s linear infinite;
}

html.time-back .wheel-down,
html.time-on .wheel-up {
  stroke: var(--orange);
}

@keyframes roll-down {
  from { translate: 0 -3px; }
  to { translate: 0 4px; }
}

@keyframes roll-up {
  from { translate: 0 4px; }
  to { translate: 0 -3px; }
}

/* The table goes old-photo warm while time is wound */
#table {
  transition: filter 0.35s ease-out;
}

html.time-winding #table {
  filter: sepia(0.42) saturate(1.25) contrast(1.04) brightness(1.02);
}

html.time-on #table {
  filter: sepia(0.25) saturate(1.45) contrast(1.06) brightness(1.04);
}

/* The crosshair stands in for the pointer everywhere, buttons and links
   included: they are pressed by a dart */
body.toy-on,
body.toy-on * {
  cursor: none !important;
}

body.toy-on input,
body.toy-on textarea,
body.toy-on select,
body.toy-on iframe,
body.toy-on label {
  cursor: auto !important;
}

/* A suction dart seen almost end-on: the cup on the surface, the foam tail
   leaning back toward the blaster */
/* The box is a 24 px hit area around the cup, so a dart can be shot off */
.dart {
  position: absolute;
  z-index: 3;
  width: 24px;
  height: 24px;
  margin: -12px 0 0 -12px;
  border-radius: 50%;
  filter: drop-shadow(3px 5px 2px rgba(50, 30, 12, 0.3));
}

.dart::before {
  content: "";
  position: absolute;
  left: 12px;
  top: 7px;
  width: var(--len, 24px);
  height: 10px;
  border-radius: 5px;
  background:
    linear-gradient(90deg, transparent calc(100% - 5px), #ffc38f 0),
    linear-gradient(180deg, #ffb27a, #ff7a1f 45%, #d75a0a);
  transform-origin: 0 50%;
  transform: rotate(var(--angle, 0.7rad));
}

.dart::after {
  content: "";
  position: absolute;
  left: 5px;
  top: 5px;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: radial-gradient(circle at 40% 38%, #6f9ff0, #2f6fd6 55%, #1f4f9e);
}

.dart--falling {
  position: fixed;
}

@media (prefers-reduced-motion: reduce) {
  .intro-flip,
  .intro-flip > .panel,
  .block-btn,
  .thumb,
  #aim,
  #table {
    transition: none;
  }

  html.time-winding .wheel-roller {
    animation: none;
  }
}

/* Narrow desktop windows: the lead stacks */
@media (max-width: 1000px) {
  .slide-lead {
    grid-template-columns: 1fr;
  }

  .intro {
    grid-template-columns: 1fr;
  }

  .intro-portrait img {
    max-width: 180px;
  }
}

/* Medium windows: the intro is one column, so the tags sit beside the portrait
   instead of under it, filling the space the narrow photo leaves */
@media (min-width: 641px) and (max-width: 1000px) {
  .intro-portrait {
    display: flex;
    align-items: flex-end;
    gap: 24px;
  }

  .intro-portrait img {
    flex: none;
    width: 200px;
    max-width: none;
  }

  .intro-portrait .skill-tags {
    margin: 0;
    gap: 8px;
  }
}

/* Phones: side-by-side passes are too thin to read, so each row stacks
   and its arrows point down */
@media (max-width: 640px) {
  .intro-portrait .skill-tags {
    display: none;
  }

  .skill-tags--inline {
    display: flex;
  }

  .slide-scroll {
    padding: 22px 18px 32px;
  }

  .pass-row.cols-2,
  .pass-row.cols-3,
  .pass-row--tracks {
    grid-template-columns: minmax(0, 1fr);
  }

  .pass-row--arrow {
    justify-items: center;
  }

  .pass-row--arrow .pass {
    width: 100%;
  }

  .pass-arrow {
    transform: rotate(90deg);
  }

  .beats--log {
    padding-left: 18px;
  }

  .beat--log::before {
    left: -26px;
  }
}

/* A dart in flight: a foam dart pointing the way it flies, blue cup at
   the front, light cap at the back. It is drawn long as it leaves you and
   shortens to nearly end-on as it arrives (scaled from toy.js). */
.dart-fly {
  position: fixed;
  left: 0;
  top: 0;
  width: 60px;
  height: 20px;
  margin: -10px 0 0 -30px;
  border-radius: 10px;
  background:
    linear-gradient(180deg, rgba(255, 255, 255, 0.45), rgba(255, 255, 255, 0) 45%, rgba(120, 50, 0, 0.25)),
    linear-gradient(90deg, #ffc38f 0 12%, #ff7a1f 12% 82%, #2f6fd6 82%);
  box-shadow: 0 0 0 1.5px rgba(74, 50, 30, 0.7);
  filter: drop-shadow(3px 6px 4px rgba(50, 30, 12, 0.35));
  will-change: transform;
}

/* ------------------------------------------------------------- curtain */

/* A bank of mist over everything while the table and the intro load. Every
   moving part is a transform or opacity animation, so it stays smooth while
   the page is busy building the table underneath. */
#curtain {
  position: fixed;
  inset: 0;
  z-index: 1000;
  overflow: hidden;
  pointer-events: auto;
}

/* The view the table stands in, before the table is there, and behind the
   pages for good if it never comes. The backdrop is placed where the camera
   sees it (scene.js): its width is fixed by the camera's level field of
   view, and its height runs from vw off the middle. */
.curtain-view {
  position: fixed;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  pointer-events: none;
  background: #9cc3e0;
  transition: opacity 1.2s ease 0.5s, visibility 0s 1.7s;
}

.curtain-backdrop {
  position: absolute;
  left: 50%;
  top: calc(50vh - 50.3vw);
  width: 132.7vw;
  height: 71.1vw;
  margin-left: -66.35vw;
  opacity: 0;
  transition: opacity 0.4s ease;
}

.curtain-view.is-painted .curtain-backdrop {
  opacity: 1;
}

/* The table top, from its far edge to the bottom of the screen */
.curtain-table {
  position: absolute;
  left: 0;
  right: 0;
  top: calc(50vh + 2.5vw);
  bottom: 0;
  background: linear-gradient(180deg, #c48d62, #b47b54);
  filter: blur(6px);
  margin: 0 -12px -12px;
}

#view.is-gone {
  opacity: 0;
  visibility: hidden;
}

/* Thin mist down most of its height, thinning out at the bottom edge, so
   lifting it uncovers the table from the ground up */
.curtain-fog {
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  height: 170vh;
  background: linear-gradient(180deg, rgba(236, 242, 244, 0.55) 0%, rgba(242, 242, 236, 0.4) 45%, rgba(243, 241, 234, 0.3) 58%, rgba(243, 241, 234, 0) 100%);
  will-change: transform;
  transition: transform 2.2s cubic-bezier(0.55, 0, 0.35, 1), opacity 0.9s ease 1.4s;
}

/* Wisps that rise slowly and fade, over and over */
.fog-puff {
  position: absolute;
  left: var(--l);
  top: var(--t);
  width: var(--w);
  aspect-ratio: 1.6;
  border-radius: 50%;
  background: radial-gradient(closest-side, rgba(255, 255, 255, 0.62), rgba(255, 255, 255, 0.25) 55%, rgba(255, 255, 255, 0) 100%);
  animation: fog-rise var(--d) linear calc(var(--i) * -1.7s) infinite;
  will-change: transform, opacity;
}

@keyframes fog-rise {
  0% { transform: translate(calc(var(--x) * -1), 7vh) scale(0.92); opacity: 0; }
  25% { opacity: 1; }
  70% { opacity: 1; }
  100% { transform: translate(var(--x), -9vh) scale(1.1); opacity: 0; }
}

#curtain.is-clearing {
  pointer-events: none;
}

#curtain.is-clearing .curtain-fog {
  transform: translateY(-170vh);
  opacity: 0;
}

/* A line sketch of the table with the intro on it and a pencil at work, with
   the note under it. They float up and away as the mist lifts. */
.curtain-card {
  position: absolute;
  left: 50%;
  top: 46%;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 18px;
  transform: translate(-50%, -50%);
  transition: opacity 0.4s ease, transform 0.6s ease;
}

#curtain.is-clearing .curtain-card {
  opacity: 0;
  transform: translate(-50%, -50%) translateY(-6vh);
}

.sketch {
  position: relative;
  width: 202px;
  height: 171px;
}

/* The table and the blank paper, then the intro's parts, each its own layer
   so it can fade in on its own; opacity alone, so it stays smooth while the
   page is busy */
.sketch-base,
.sketch-layer {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  fill: none;
  stroke: #4a3526;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* The intro's own colours: its paper, picture frame, tags, ink, the orange
   of the role, and two of its block buttons for the row of them */
.sketch-paper {
  fill: #fffdf7;
  stroke-width: 1.6;
}

/* main.js brings each part in as the load gets along, and the rest once it is all in */
.sketch-layer {
  opacity: 0;
  transition: opacity 0.32s ease;
}

.sketch-layer.is-in {
  opacity: 1;
}

.sketch-photo {
  fill: #eadfca;
  stroke: none;
}

.sketch-tag {
  fill: #f3e5c9;
  stroke: #d6c19b;
  stroke-width: 0.8;
}

.sketch-name {
  stroke: #2c241d;
  stroke-width: 2.6;
}

.sketch-role {
  stroke: #b8500c;
  stroke-width: 1.8;
}

.sketch-text {
  stroke: rgba(44, 36, 29, 0.5);
  stroke-width: 1.3;
}

.sketch-button {
  stroke-width: 1;
}

.sketch-button.is-mustard {
  fill: #f2c14e;
  stroke: #b98a2a;
}

.sketch-button.is-blue {
  fill: #8fb0de;
  stroke: #4a6fa8;
}

/* A pencil beside the note, scribbling in place, as a transform, so it
   stays smooth while the page is busy */
.note-pencil {
  display: block;
  transform-origin: 50% 100%;
  animation: sketch-scribble 0.32s ease-in-out infinite alternate;
}

.note-pencil svg {
  display: block;
  overflow: visible;
  fill: #fffdf7;
  stroke: #4a3526;
  stroke-width: 1.6;
  stroke-linejoin: round;
  transform: rotate(28deg);
  transform-origin: 50% 100%;
}

.note-lead {
  fill: #4a3526;
}

@keyframes sketch-scribble {
  from { transform: translate(-1px, 0) rotate(-2deg); }
  to { transform: translate(1.5px, -2px) rotate(3deg); }
}

@media (max-width: 420px) {
  .sketch {
    scale: 0.8;
  }
}

.curtain-note {
  display: flex;
  align-items: flex-end;
  gap: 16px;
  margin: 0;
  white-space: nowrap;
  font-family: "Shantell Sans", var(--head);
  font-size: 1.2rem;
  font-weight: 600;
  color: #4a3526;
  text-shadow: 0 1px 0 rgba(255, 250, 240, 0.9), 0 0 14px rgba(255, 250, 240, 0.95);
}

@media (prefers-reduced-motion: reduce) {
  .fog-puff, .note-pencil {
    animation: none;
  }
  .sketch-layer {
    transition: none;
  }
  .curtain-fog, .curtain-card {
    transition: opacity 0.3s ease;
  }
  #curtain.is-clearing .curtain-fog {
    transform: none;
  }
}
