/* ==========================================================
   Nathan Foerger Psychology
   Horizontal scroll on desktop, stacked on mobile. Small scripts live in site.js.
   Sizes that scale with the window are rounded to whole pixels with round(),
   so 1px lines always land on a full pixel and stay crisp.
   ========================================================== */

:root {
  --bg:   #f6f2e8;   /* cream background */
  --fg:   #2a1c18;   /* very dark brown text and lines */
  --soft: #6e5f59;   /* muted brown text */
  --line:  1px solid var(--fg);
  --line-soft: 1px solid rgba(42, 28, 24, 0.3);  /* dividers inside a section */
  --ease-snappy: cubic-bezier(0.32, 0.72, 0, 1);  /* quick start, soft landing */
  /* Hover fade speeds, set separately since each effect animates a different amount */
  --hover-button: 0.3s ease;                      /* button background fill */
  --hover-link: 0.1s ease;                        /* text link underline */
  --hover-row: 0.4s ease;                         /* accordion row shading */

  --nav:  64px;
  --pad:  round(clamp(24px, 3vw, 56px), 1px);     /* vertical spacing */
  --gutter: round(clamp(32px, 4.5vw, 80px), 1px); /* space between divider lines and content */
  --top:  round(calc(var(--pad) * 1.5), 1px);
  --breathe: round(calc(var(--gutter) * 2.5), 1px); /* extra room on the right of text columns: 2.5 × the gutter, in whole pixels */
  --measure: 480px;                   /* hero intro width on desktop */
  --measure-wide: 520px;              /* About quote and bio, and the "Who I work with" intro, on desktop */

  /* Type scale: 16px body, steps of x1.25 */
  --fs-xs:   11px;   /* uppercase labels */
  --fs-base: 16px;   /* body copy */
  --fs-md:   20px;
  --fs-lg:   25px;   /* subheaders: pull quote, audience titles, address */
  --fs-2xl:  49px;   /* section headlines */
  /* Hero headline, 3 lines on desktop: 8.5vw of the width (its longest
     line, "things feel scattered.", is about 7.7 times the font size, so it
     spans about 65% of the window), but never so big that the space above it
     (nav to the top of the letters) is smaller than the space below it
     (baseline to the divider). The height part: the window height, minus
     the nav, up to 5 lines of intro and the spacing, shared between 3 lines
     at 1.08 line height plus that extra room on top (3.34 in total). On
     very short windows it stops shrinking at 12vh. */
  --fs-display: round(min(8.5vw, max(12vh, (100vh - 195px - 2 * var(--top) - 2 * var(--pad)) / 3.34)), 1px);

  /* Line heights in whole pixels, so stacked lines and table rows never
     end on half a pixel */
  --lh-base: 26px;   /* 16px text */
  --lh-md:   26px;   /* 20px text */
  --lh-lg:   32px;   /* 25px text */

  --serif: "Newsreader", Georgia, serif;
  --sans:  "Inter", "Helvetica Neue", Arial, sans-serif;
}

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

html, body {
  margin: 0;
  -webkit-tap-highlight-color: transparent;   /* no grey flash when tapping links and buttons */
  background: var(--bg);
  color: var(--fg);
  font-family: var(--sans);
  font-size: var(--fs-base);
  line-height: var(--lh-base);
  -webkit-font-smoothing: antialiased;
}

/* A fixed frame around the whole viewport, so the outer edges of the
   scrolling content are never visible. Not on phones (see the phone styles). */
body::after {
  content: "";
  position: fixed;
  inset: 0;
  border: var(--line);
  pointer-events: none;
  z-index: 20;
}

img { display: block; width: 100%; height: 100%; object-fit: cover; }
a { color: inherit; }
p { margin: 0; }

/* Rebalance the last lines of a paragraph so none ends on a single word
   (browsers without support just wrap normally) */
p, dd { text-wrap: pretty; }
figure { margin: 0; }

/* ---------- Type ---------- */

.small {
  font-size: var(--fs-xs);
  line-height: 16px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.display,
.headline {
  font-family: var(--serif);
  font-weight: 200;
  font-optical-sizing: auto;
  letter-spacing: -0.035em;
  margin: 0;
}

/* Subheaders: pull quote, audience titles, address, Who intro */
.pull,
.who__toggle,
address,
.who__head .lede {
  font-family: var(--sans);
  font-weight: 400;
  font-size: var(--fs-lg);
  line-height: var(--lh-lg);
  letter-spacing: -0.02em;
  margin: 0;
}

.display {
  font-size: var(--fs-display);
  line-height: round(calc(var(--fs-display) * 1.08), 1px);
}

/* Section headlines are smaller than the hero, so they get a heavier
   weight to keep a similar stroke thickness. */
.headline {
  font-weight: 300;
  font-size: var(--fs-2xl);
  line-height: 52px;
}

.lede { max-width: var(--measure); }

/* ---------- Nav ---------- */

.nav {
  position: fixed;
  inset: 0 0 auto 0;
  height: var(--nav);
  z-index: 10;
  display: flex;
  align-items: center;
  justify-content: space-between;
  background: var(--bg);
  border-bottom: var(--line);
}

.nav__name {
  height: 100%;
  display: flex;
  align-items: center;
  padding: 0 var(--gutter);
  font-weight: 500;
  font-size: var(--fs-base);
  letter-spacing: -0.01em;
  text-decoration: none;
  white-space: nowrap;
}

.nav__book {
  /* 1px taller so it also covers the nav's bottom border; otherwise the
     pointer drops off the button on that line and the hover flickers */
  height: calc(100% + 1px);
  align-self: flex-start;
  display: flex;
  align-items: center;
  padding: 0 var(--gutter);
  border-left: var(--line);
  font-size: var(--fs-xs);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  text-decoration: none;
  white-space: nowrap;
  transition: background-color var(--hover-button), color var(--hover-button);
}
@media (hover: hover) {
  .nav__book:hover { background-color: var(--fg); color: var(--bg); }
}

/* ==========================================================
   Horizontal scroll (desktop)
   With JavaScript (html.js): .track is a real sideways scroller, so trackpad
   swipes work natively, and scroll.js turns scroll-down into sideways.
   Without JavaScript: fall back to rotating the track -90° and the content
   back +90°, so an ordinary scroll-down still moves the page sideways.
   ========================================================== */

@media (min-width: 900px) {
  .track {
    position: fixed;
    top: 0;
    left: 0;
    scrollbar-width: none;
  }
  .track::-webkit-scrollbar { display: none; }

  .panels {
    display: flex;
    width: max-content;
    height: 100vh;
  }

  .panel {
    height: 100vh;
    padding-top: var(--nav);
    flex: none;
  }

  /* JavaScript on: real sideways scroller */
  .js .track {
    width: 100vw;
    height: 100vh;
    overflow-x: auto;
    overflow-y: hidden;
    overscroll-behavior-x: contain;  /* no browser back/forward swipe at the ends */
  }

  /* JavaScript off: rotated fallback */
  html:not(.js) .track {
    width: 100vh;
    height: 100vw;
    overflow-x: hidden;
    overflow-y: auto;
    transform-origin: top left;
    transform: rotate(-90deg) translateX(-100vh);
  }
  html:not(.js) .panels {
    transform-origin: top left;
    transform: rotate(90deg) translateY(-100vh);
  }
}

.panel {
  display: grid;
  border-right: var(--line);
}
.panel:last-child { border-right: 0; }

/* ---------- Hero ---------- */

.panel--hero {
  width: 100vw;
  grid-template-rows: 1fr auto;
  padding-left: var(--gutter);
  padding-right: var(--gutter);
}
/* Headline sits just above the divider; the space above the line matches
   the space below it, and the rest of the room goes above the headline. */
.panel--hero .display { align-self: end; padding-bottom: var(--top); }
@media (min-width: 900px)  { .display .br--narrow { display: none; } }
@media (max-width: 899px)  { .display .br--wide { display: none; } }

.hero__foot {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--pad);
  padding: var(--pad) 0;   /* same space above and below the intro; it ends on the same bottom line as every other section */
  border-top: var(--line-soft);
}

/* "EDMONTON, AB  [leaf]" in the hero's top-right corner. The grid rows
   don't account for it; it's pinned to the corner. */
.panel--hero { position: relative; overflow: clip; }   /* clip: the sand canvas reaches a little past the headline */

/* Hero headline "sand" effect (site.js): a canvas copy of the headline sits
   exactly over the real one, which turns transparent while it's there */
.panel--hero .display { position: relative; }
.display.is-sand { color: transparent; user-select: none; -webkit-user-select: none; -webkit-touch-callout: none; }   /* no selection box over the canvas, and no iPhone press-and-hold menu (holding stirs the sand) */
/* On load the real text stays hidden while site.js gets the sand ready, so
   it can gather in rather than appear and then scatter. If the script never
   takes over, the text shows by itself after 2.5s. */
@media (prefers-reduced-motion: no-preference) {
  .js .panel--hero .display:not(.is-sand):not(.sand-off) { animation: sand-wait 2.5s step-end; }
}
@keyframes sand-wait { from { color: transparent; } }
.display__sand {
  position: absolute;
  /* its size and position (reaching past the headline) are set by site.js */
  pointer-events: none;
}
.locale {
  position: absolute;
  top: calc(var(--nav) + var(--top));   /* same top line as the other section headlines */
  right: var(--gutter);
  display: flex;
  align-items: center;
  gap: 8px;
  color: rgba(42, 28, 24, 0.8);          /* text at 80% */
  white-space: nowrap;
}
.locale__text { font-size: 10px; }   /* 1px smaller than the other small labels */
.locale__icon { display: block; line-height: 0; color: rgba(42, 28, 24, 0.6); }  /* leaf at 60% */
.locale__icon svg { display: block; width: 15px; height: 15px; }

/* Body text starts at the halfway point of the section */
.hero__foot .lede { margin-left: calc(round(50vw, 1px) - var(--gutter)); }

/* ---------- About ---------- */

.panel--about {
  /* Main column sizes itself around photo + text; the credentials column grows
     if needed so no credential line ever wraps */
  grid-template-columns: auto minmax(round(34vw, 1px), max-content);
}

.about__main,
.creds {
  padding: var(--top) var(--gutter) var(--pad);
}

.about__main {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  gap: var(--pad);
  border-right: var(--line);
}

.about__body {
  display: grid;
  /* Photo column width: site.js sets it to the bio's height × the photo's
     shape, so the photo matches the text height and is never cropped.
     332px is the fallback without JavaScript (418px tall text × 600/756). */
  --portrait-w: 332px;
  grid-template-columns: var(--portrait-w) var(--measure-wide);
  gap: var(--gutter);
  align-items: end;
}

/* The photo takes no height of its own, so it always matches the text beside it */
.portrait { position: relative; align-self: stretch; }
.portrait img { position: absolute; inset: 0; object-position: 50% 25%; } /* keep the face in frame if it's ever cropped */

.about__text {
  display: flex;
  flex-direction: column;
  gap: 1em;
}
.about__text .pull { margin-bottom: 12px; }
.about__text p { max-width: var(--measure-wide); }

.creds {
  margin: 0;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
}
.creds__row {
  display: grid;
  gap: 6px;               /* label sits above its value */
  padding: 24px 0;
  border-top: var(--line-soft);
}
.creds__row:first-child { border-top: 0; padding-top: 0; }  /* lines only between items */
.creds__row:last-child { padding-bottom: 0; }               /* list ends on the same bottom line as other content */
.creds dt { padding-top: 2px; color: var(--soft); }
.creds dd { margin: 0; }
.creds__item { display: block; }
.creds__item + .creds__item { margin-top: 8px; }   /* a little air between degrees */
.nowrap { white-space: nowrap; }

/* Phones and tablets: if the longest degree can't fit on one line (about
   424px at 16px), every degree breaks after its comma, so they all match.
   If a longer degree is added, raise 430px to fit it. */
@media (max-width: 899px) {
  .creds dd { container-type: inline-size; }
  @container (max-width: 430px) {
    .creds__item .nowrap { display: block; }
  }
}

/* ---------- Who I work with: headline + intro on the left, accordion rows on the right ---------- */

.panel--who {
  /* Left column fits the 520px intro plus its left padding and the extra
     right-side space; the right column fits 400px descriptions plus the gap and icon */
  --who-desc: 400px;
  --who-icon: 24px;
  --who-icon-gap: 90px;               /* clear gap between the text and the icon column */
  grid-template-columns: calc(var(--measure-wide) + var(--gutter) + var(--breathe)) calc(var(--who-desc) + var(--who-icon-gap) + var(--who-icon) + 2 * var(--gutter));
}

.who__head {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  padding: var(--top) var(--gutter) var(--pad);
  border-right: var(--line-soft);
}

/* Intro uses the larger sans subheader style (set with .pull above) */
.who__head .lede { max-width: var(--measure-wide); }

/* "Individual therapy can be in-person or virtual." in body text, below the intro */
.who__note { margin-top: 24px; max-width: var(--measure-wide); }

/* Audiences: a vertical stack of rows, each running from divider to divider
   so the whole band is clickable. On desktop the rows fill the column's full
   height: the spare height is shared equally, so closed rows are equal and
   every row has the same space above and below its centred content. An open
   row grows to fit its content and the others shrink evenly to make room. */
.who__grid {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
}
@media (min-width: 900px) {
  .who__grid { height: calc(100vh - var(--nav)); }   /* a definite height to share out */
}

.who__item {
  /* Two columns: the text (title + body) and, on its own, the icon */
  display: grid;
  grid-template-columns: minmax(0, 1fr) var(--who-icon);
  column-gap: var(--who-icon-gap);
  align-content: center;
  flex: 1 1 auto;                      /* spare height shared equally, so every row gets the same space around its content */
  padding: 0 var(--gutter);
  transition: padding 0.35s var(--ease-snappy), background-color var(--hover-row);
}
.who__title { grid-column: 1; grid-row: 1; }
.who__panel { grid-column: 1; grid-row: 2; }
.who__icon  { grid-column: 2; grid-row: 1; align-self: center; }   /* level with the title */
.who__item + .who__item { border-top: var(--line-soft); }   /* the dividers */

/* An open row gets a little extra room above and below (desktop) */
@media (min-width: 900px) {
  .who__item.is-open { padding-top: 20px; padding-bottom: 20px; }
}

/* Whole row is clickable (accordion.js); shade it slightly on hover */
.js .who__item { cursor: pointer; }
@media (hover: hover) {
  .js .who__item:hover { background-color: rgba(42, 28, 24, 0.05); }
}

/* ----- Accordion rows ----- */

.who__title { margin: 0; }

/* The title is the button (for keyboards); its type style is set with .pull above */
.who__toggle {
  display: block;
  width: 100%;
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  text-align: left;
  cursor: pointer;
}

/* Plus icon: two 1px lines. Open, it spins round to become an X. */
.who__icon {
  position: relative;
  width: var(--who-icon);
  height: var(--who-icon);
  /* Close to the panel's own timing, so they move together. It starts
     straight away and slows into place, more gently than the snappy curve,
     so the turn reads as a turn rather than a jump. */
  transition: transform 0.4s ease-out;
}
.who__icon::before,
.who__icon::after {
  content: "";
  position: absolute;
  left: 0;
  top: 50%;
  width: 100%;
  height: 1px;
  background: currentColor;
}
.who__icon::after { transform: rotate(90deg); }
.who__item.is-open .who__icon { transform: rotate(135deg); }   /* + turns into × */

/* Body slides open: the panel's single row animates from 0 to its full height.
   Closed panels are also hidden from keyboard and screen readers. */
.who__panel {
  display: grid;
  grid-template-rows: 0fr;
  visibility: hidden;
  transition: grid-template-rows 0.35s var(--ease-snappy) 0.05s, visibility 0s 0.4s;   /* closing starts as the text fades */
}
.who__item.is-open .who__panel {
  grid-template-rows: 1fr;
  visibility: visible;
  transition: grid-template-rows 0.45s var(--ease-snappy), visibility 0s;   /* a touch slower opening */
}
.who__panel-inner {
  min-height: 0;
  overflow: hidden;
  opacity: 0;
  transition: opacity 0.12s ease;                       /* closing: fade out first, then the row shrinks */
}
/* Opening: the row grows, and the content fades in over the last part of that,
   so it's never wiped into view but it doesn't feel like two separate steps */
.who__item.is-open .who__panel-inner {
  opacity: 1;
  transition: opacity 0.4s ease 0.36s;                 /* starts once the row is open, whatever its height, then carries on */
}
.who__panel-inner > :first-child { margin-top: 24px; }   /* title to description */

.who__item p { max-width: var(--who-desc); margin: 0; }

/* Without JavaScript every row shows open and there's nothing to toggle */
html:not(.js) .who__panel { grid-template-rows: 1fr; visibility: visible; }
html:not(.js) .who__panel-inner { opacity: 1; }
html:not(.js) .who__icon { display: none; }
html:not(.js) .who__toggle { cursor: default; }

/* Links under a description (Group therapy), each on its own line */
.who__links { display: flex; flex-direction: column; gap: 8px; margin-top: 24px; }

@media (prefers-reduced-motion: reduce) {
  .who__icon,
  .who__panel,
  .who__panel-inner,
  .who__item,
  .who__item.is-open .who__panel,
  .who__item.is-open .who__panel-inner { transition: none; }
}

/* ---------- Visit ---------- */

.panel--visit {
  /* Both columns are just as wide as their content: the location column
     fits its widest line (the one-line email sentence) plus the usual
     breathing room, the hours column fits its one-line headline */
  grid-template-columns: max-content max-content;
}

.visit__find,
.visit__hours {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  gap: var(--pad);
  padding: var(--top) var(--gutter) var(--pad);
}
.visit__find { border-right: var(--line); }

.find__body {
  display: grid;
  grid-template-columns: round(18vw, 1px) max-content;
  gap: var(--pad);
  align-items: end;
}

.building img {
  height: auto;
  aspect-ratio: 3 / 4;
  max-height: 52vh;
}

.find__address {
  display: flex;
  flex-direction: column;
  gap: 8px;                          /* same small gap between name, address and "Get directions" */
  align-self: stretch;               /* as tall as the photo: address at its top, email at its bottom */
}
address { font-style: normal; }

.link {
  text-decoration: none;
  border-bottom: var(--line);
  align-self: flex-start;
  padding-bottom: 2px;
  transition: border-color var(--hover-link);
}
@media (hover: hover) {
  .link:hover { border-bottom-color: transparent; }  /* text stays solid, underline fades out */
}

/* "To get in touch, email me at …": pushed to the bottom of the address
   column (level with the bottom of the photo), never closer than 58px */
.contact { margin-top: auto; padding-top: 58px; }

.hours {
  width: 100%;
  border-collapse: separate;   /* 'collapse' splits each 1px line across two rows, landing them on half pixels */
  border-spacing: 0;
  margin-bottom: round(clamp(24px, 6vh, 48px), 1px); /* 48px on most screens, less on short ones */
}
.hours th,
.hours td {
  padding: 8px 0;
  border-top: var(--line-soft);
  font-weight: 400;
  text-align: left;
}
.hours th { font-size: var(--fs-base); }
.hours tr:first-child th,
.hours tr:first-child td { border-top: 0; }  /* lines only between days */
.hours td { text-align: right; font-variant-numeric: tabular-nums; }
/* Closed days: the text fades back. Lines touching an open day stay at full
   strength; only the line between two closed days fades too. */
.hours .closed th,
.hours .closed td { color: rgba(246, 242, 232, 0.35); }   /* cream at 35% */
.hours .closed + .closed th,
.hours .closed + .closed td { border-top-color: rgba(246, 242, 232, 0.105); }   /* the 30% line at 35% */


/* Hours column in dark mode: swap the colour tokens inside it only */
.visit__hours {
  --bg: #2a1c18;
  --fg: #f6f2e8;
  --line-soft: 1px solid rgba(246, 242, 232, 0.3);
  background: var(--bg);
  color: var(--fg);
}
/* "Book a session" in the hours card: a cream outline button that fills
   cream on hover, like the nav button */
.button {
  display: block;
  padding: 18px 20px;
  border: 1px solid var(--fg);
  color: var(--fg);
  text-align: center;
  font-size: var(--fs-xs);
  line-height: 18px;  /* whole-pixel height keeps the edges crisp */
  letter-spacing: 0.06em;
  text-transform: uppercase;
  text-decoration: none;
  transition: background-color var(--hover-button), color var(--hover-button);
}
@media (hover: hover) {
  .button:hover { background-color: var(--fg); color: var(--bg); }
}

/* Keyboard focus: a thin outline in the text colour, only for keyboard users */
:focus-visible { outline: 1px solid currentColor; outline-offset: 4px; }
.nav a:focus-visible { outline-offset: -6px; }   /* inside, since the nav touches the window edge */
.who__toggle:focus-visible { outline-offset: 6px; }

/* Text for screen readers only (e.g. "Closed" in the hours table) */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

@media (min-width: 900px) {
  /* Section headlines always stay on one line */
  .headline { white-space: nowrap; }

  /* Building name and each address line always stay on one line */
  .building-name,
  .find__address address,
  .contact { white-space: nowrap; }
  /* Each credential line stays on one line */
  .creds dd { white-space: nowrap; }

  /* Generous empty space to the right of each text column, so nothing
     runs right up to a divider */

  .about__main,
  .who__head,
  .visit__find { padding-right: var(--breathe); }

  /* Office photo: up to 18vw wide, but never taller than the About portrait
     (site.js shares its height as --portrait-h) or 52vh. When a cap kicks in
     the photo gets narrower rather than cropped, and its column shrinks to fit. */
  .find__body { grid-template-columns: fit-content(round(18vw, 1px)) max-content; }
  .building img {
    width: auto;
    max-width: 100%;
    max-height: min(52vh, var(--portrait-h, 52vh));
  }
}

/* ==========================================================
   Page-load reveal and smooth scrolling
   ========================================================== */

/* Page-load reveal: a cream panel with a soft gradient edge covers the
   content (under the nav) and slides away: left to right on desktop, top to
   bottom on phones. It's fixed to the screen and only moves with transform,
   so it keeps animating smoothly even while someone scrolls. No JS needed. */
@keyframes reveal-left-to-right {
  from { transform: translateX(-70vw); }
  to   { transform: translateX(100vw); }
}
@keyframes reveal-top-to-bottom {
  from { transform: translateY(-70vh); }
  to   { transform: translateY(100vh); }
}
@media (prefers-reduced-motion: no-preference) {
  body::before {
    content: "";
    position: fixed;
    top: 0;
    left: 0;
    z-index: 5;              /* above the content, below the nav (10) and frame (20) */
    pointer-events: none;
  }
  /* Desktop: two screens wide, transparent at the left edge fading to cream */
  @media (min-width: 900px) {
    body::before {
      width: 200vw;
      height: 100vh;
      background: linear-gradient(to right, transparent 0, var(--bg) 60vw);
      animation: reveal-left-to-right 3s cubic-bezier(0.25, 0.6, 0.3, 1) 0.1s both;
    }
  }
  /* Phones: two screens tall, transparent at the top edge fading to cream */
  @media (max-width: 899px) {
    body::before {
      width: 100vw;
      height: 200vh;
      background: linear-gradient(to bottom, transparent 0, var(--bg) 60vh);
      animation: reveal-top-to-bottom 4s cubic-bezier(0.25, 0.6, 0.3, 1) 0.1s both;
    }
  }
}

/* Smooth scroll for in-page links (the nav name scrolls back to the hero).
   .track is the scroller on desktop, the page itself on phones. */
@media (prefers-reduced-motion: no-preference) {
  html,
  .track { scroll-behavior: smooth; }
}

/* ==========================================================
   Custom cursor (mouse and trackpad only, at any width; site.js adds .has-cursor)
   A 20px circle that slowly cycles through the rainbow, shrinks to 11px
   over links and turns cream over dark brown surfaces.
   ========================================================== */

@property --hue {
  syntax: '<number>';
  inherits: false;
  initial-value: 0;
}
@keyframes cursor-hue { to { --hue: 360; } }

.has-cursor,
.has-cursor * { cursor: none !important; }

.cursor {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 100;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  pointer-events: none;
  opacity: 0;
  /* Fixed lightness keeps every hue at the same contrast against the cream */
  background: oklch(0.55 0.19 var(--hue));  /* 3.5:1 or better on cream at every hue */
  animation: cursor-hue 12s linear infinite paused;   /* only runs while the dot shows (below) */
  /* Colour fades at the same pace as the button fills, so the dot never
     blends into a half-faded button and blinks */
  transition: width 0.2s ease, height 0.2s ease, opacity 0.2s ease, background-color var(--hover-button);
}
.cursor.is-visible { opacity: 1; animation-play-state: running; }
/* Out of the page entirely until a mouse is in use: Safari on iPhone reads
   even invisible fixed elements when colouring its bars, and this one sits
   in the top corner */
.cursor { display: none; }
.has-cursor .cursor { display: block; }
.cursor.is-link    { width: 11px; height: 11px; }
.cursor.is-dark    { background: #f6f2e8; }  /* cream */
.cursor.is-on-light-button { background: #2a1c18; }  /* hours button turns cream on hover */

@media (prefers-reduced-motion: reduce) {
  .cursor { animation: none; --hue: 280; }
}

/* ==========================================================
   Mobile / narrow: stack vertically, keep the ruled grid
   ========================================================== */

@media (max-width: 899px) {
  /* Phones scroll up and down only: no dragging the page sideways (which
     people would do while playing with the headline). Pinch-zoom still works. */
  html, body { overflow-x: clip; }
  body { touch-action: pan-y pinch-zoom; }

  /* Normal line breaking in the narrow stacked layout. "Pretty" wrapping
     earns its keep on wide desktop blocks, but on a phone column Safari
     rebalances whole paragraphs and leaves a very jagged right edge. */
  p, dd { text-wrap: wrap; }

  /* No frame around the window on phones: Safari's see-through bars make
     its edges look odd. The nav keeps a line above and below instead. Being
     a solid cream bar fixed to the top, the nav is also what Safari uses to
     colour the status bar above it. */
  body::after { display: none; }
  .nav { border-top: var(--line); }

  /* --section-y: one top/bottom padding for every stacked block on phones */
  :root { --nav: 56px; --pad: 20px; --gutter: 24px; --section-y: 64px; --divider-y: 32px; }  /* --divider-y: space either side of a light divider */

  .nav__name { padding: 0 12px 0 var(--gutter); font-size: 13px; } /* a touch larger than the 11px button text, for legibility */
  .nav__book { padding: 0 16px; }

  .panels { padding-top: var(--nav); }
  .panel { scroll-margin-top: var(--nav); }

  .panel,
  .panel--hero {
    display: block;
    width: auto;
    border-right: 0;
    border-bottom: var(--line);
  }
  .panel:last-child { border-bottom: 0; }

  .panel--hero .display { --fs-display: round(15vw, 1px); padding: calc(var(--section-y) * 2) 0 var(--divider-y); }  /* extra room above the hero */
  /* Phones: top-left of the hero, as far from the top as the side margins;
     the headline's top spacing then sits between it and the headline */
  .locale { position: static; padding-top: var(--gutter); }
  .hero__foot { flex-direction: column; align-items: flex-start; padding: var(--divider-y) 0 var(--section-y); }
  .hero__foot .lede { margin-left: 0; }

  .about__main, .creds,
  .who__head, .who__item,
  .visit__find, .visit__hours {
    padding: var(--section-y) var(--gutter);
    border-right: 0;
  }

  .headline { font-size: 39px; line-height: 41px; }

  .about__main { gap: 32px; border-bottom: var(--line); }
  .about__body { grid-template-columns: 1fr; gap: 32px; }
  .portrait { width: 60%; aspect-ratio: 600 / 756; }   /* the photo's own shape, so it isn't cropped */
  .pull, .who__toggle, address, .who__head .lede { font-size: var(--fs-md); line-height: var(--lh-md); }

  .who__head { display: block; border-bottom: var(--line-soft); padding-bottom: var(--divider-y); }
  .who__intro { margin-top: 24px; }
  .who__grid { display: block; }
  .who__item p { max-width: none; }
  .who__panel-inner > :first-child { margin-top: 12px; }
  .who__item { --who-icon-gap: 24px; padding-top: var(--divider-y); padding-bottom: var(--divider-y); }
  .who__item:last-child { padding-bottom: var(--section-y); }

  .visit__find { border-bottom: var(--line); }
  .visit__hours { gap: 40px; }  /* room between the headline and Monday */
  /* Phones: address, then photo, then the email line */
  .find__body { display: flex; flex-direction: column; align-items: flex-start; gap: 8px; }
  .find__address { display: contents; }
  .building { order: 1; margin-top: 40px; }
  .contact { order: 2; margin-top: 40px; padding-top: 0; }
  .building { width: 60%; }            /* same width as the portrait */
  .building img { max-height: none; aspect-ratio: 4 / 5; }  /* same shape as the portrait */
}
