/* Ashton Z. Shu, portfolio site
   Layout after the Daikoku Design Institute site: a full-bleed slideshow on the home page,
   a quiet header, image grids that show names on hover, one centered text column.
   Inter at regular weight throughout; Poppins 800 only for "Ashton is" on the first slide. */

:root {
  --ink: #111;
  --ink-2: #444;     /* secondary text, e.g. résumé details */
  --muted: #6b6b6b;
  --paper: #fff;
  --soft: #f2f2f2;
  --gutter: clamp(16px, 3.5vw, 50px);
  --head-top: clamp(20px, 3.5vw, 50px);
  --frame: 1240px;   /* home grid, footer, breadcrumb */
  --wide: 1080px;    /* project images */
  --text: 704px;     /* text column */
  --fs: clamp(16px, .19vw + 15.3px, 18px);
  --fs-ui: clamp(15px, .65vw + 12.6px, 22px);
  --fs-mid: clamp(20px, .77vw + 16.9px, 28px);
  --fs-big: clamp(26px, 1.53vw + 20px, 42px);
  --g: clamp(12px, 1.7vw, 24px);
  --head-h: calc(var(--head-top) + 1.2 * var(--fs-ui) + 14px);   /* js/site.js replaces it with the measured height */
  --ease: cubic-bezier(.4, 0, .2, 1);
  color-scheme: light;
}

/* ---------- light / dark ---------- */
:root[data-theme="dark"] { --ink: #f2f2f2; --ink-2: #c6c6c6; --muted: #a3a3a3; --paper: #0d0d0d; --soft: #1a1a1a; color-scheme: dark; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) { --ink: #f2f2f2; --ink-2: #c6c6c6; --muted: #a3a3a3; --paper: #0d0d0d; --soft: #1a1a1a; color-scheme: dark; }
  :root:not([data-theme="light"]) img[src$="mark.png"] { filter: invert(1); }
}
:root[data-theme="dark"] img[src$="mark.png"] { filter: invert(1); }

*, *::before, *::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: 'Inter', system-ui, -apple-system, 'Segoe UI', sans-serif;
  font-size: var(--fs);
  line-height: 1.5;
  text-wrap: pretty;
  orphans: 3;
  widows: 3;
  overflow-wrap: break-word;
}

h1, h2, h3, h4 { margin: 0; font-size: inherit; font-weight: 400; text-wrap: balance; }
p { margin: 0; }
ul, ol { margin: 0; padding: 0; list-style: none; }
figure { margin: 0; }
/* Hero video shares the image defaults and artwork geometry. */
img, .slide video { display: block; max-width: 100%; height: auto; }
button { font: inherit; color: inherit; }

a { color: inherit; text-decoration-thickness: 1px; text-underline-offset: .22em; transition: opacity .3s var(--ease); }
:focus-visible { outline: 1.5px solid currentColor; outline-offset: 4px; }

.vh {
  position: absolute !important; width: 1px; height: 1px; margin: -1px; padding: 0;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

.skip {
  position: absolute; left: var(--gutter); top: -100px; z-index: 30;
  background: var(--ink); color: var(--paper); padding: 10px 16px; text-decoration: none;
}
.skip:focus { top: 12px; }

.frame { width: min(100% - 2 * var(--gutter), var(--frame)); margin-inline: auto; }

/* text link with an arrow that opens up on hover */
.arrow-link { font-size: var(--fs-ui); text-decoration: none; }
.arrow-link::after { content: "\2192"; display: inline-block; margin-left: .4em; transition: margin .3s var(--ease); }
.arrow-link:hover { opacity: .85; }
.arrow-link:hover::after { margin-left: .9em; }

/* ---------- header ---------- */

/* always on screen and see-through on every page: white words in difference mode read dark on a light ground
   and turn light wherever they pass over dark type or a dark image; js/site.js only tightens the padding on scroll */
.site-header {
  position: fixed; top: 0; right: 0; left: 0; z-index: 20;
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 6px 20px;
  padding: var(--head-top) var(--gutter) 14px;
  font-size: var(--fs-ui);
  line-height: 1.2;
  color: #fff;
  mix-blend-mode: difference;
  pointer-events: none;   /* clicks reach the page below, except on the links themselves */
  transition: padding .3s var(--ease);
}
.site-header.is-stuck { padding-top: min(var(--head-top), 16px); }
/* once the page scrolls under the header (.is-stuck), a band in the page colour slides in behind it, so the
   wordmark and links never sit on top of body text; js/site.js keeps --head-stuck-h at the tightened header's height */
body::before {
  content: ""; position: fixed; top: 0; right: 0; left: 0; z-index: 19;
  height: var(--head-stuck-h, 60px);
  background: var(--paper);
  pointer-events: none;
  opacity: 0;
  transition: opacity .3s var(--ease);
}
.head-stuck body::before { opacity: 1; }
.site-header :is(.wordmark, .site-nav a, .theme-toggle) {
  pointer-events: auto;
  white-space: nowrap;
  text-decoration: none;
}
/* hover and keyboard focus: the icon and word grey out and the four corners of the name motion's frame open out
   of its centre to sit round it, then close back into the centre; js/site.js adds the <i class="hb"> corners.
   They draw in #fff, so the header's difference blending carries them over any ground.
   The current page's link keeps the corners in place, shorter and fainter (--ha arm, --ho opacity),
   and they open fully on hover like the others */
@property --hb { syntax: '<number>'; inherits: true; initial-value: 0; }
@property --ha { syntax: '<number>'; inherits: true; initial-value: 1; }
@property --ho { syntax: '<number>'; inherits: true; initial-value: 1; }
.site-header :is(.wordmark, .site-nav a, .theme-toggle) {
  --hb: 0; --ha: 1; --ho: 1;
  position: relative;
  transition: color .3s var(--ease), --hb .45s cubic-bezier(.25, .7, .2, 1), --ha .45s cubic-bezier(.25, .7, .2, 1), --ho .3s var(--ease);
}
.site-nav a[aria-current] { --hb: 1; --ha: .55; --ho: .6; }
.site-header :is(.wordmark, .site-nav a, .theme-toggle):focus-visible { --hb: 1; --ha: 1; --ho: 1; color: #4d4d4d; outline: none; }
@media (hover: hover) and (pointer: fine) {
  .site-header :is(.wordmark, .site-nav a, .theme-toggle):hover { --hb: 1; --ha: 1; --ho: 1; color: #4d4d4d; }
}
.hb {
  --s: .12em;                           /* stroke */
  --L: calc(var(--hb) * var(--ha) * .42em);   /* arm, 0 at the centre */
  --k: min(var(--s), var(--L));
  --px: calc(var(--hb) * (50% + .32em));   /* how far out from the centre the corner sits */
  --py: calc(var(--hb) * (50% + .16em));
  position: absolute; box-sizing: border-box; width: var(--L); height: var(--L);
  border: 0 solid #fff; pointer-events: none;
  opacity: var(--ho);
}
.hb-tl, .hb-bl { left: calc(50% - var(--px)); border-left-width: var(--k); }
.hb-tr, .hb-br { right: calc(50% - var(--px)); border-right-width: var(--k); }
.hb-tl, .hb-tr { top: calc(50% - var(--py)); border-top-width: var(--k); }
.hb-bl, .hb-br { bottom: calc(50% - var(--py)); border-bottom-width: var(--k); }
.hb-tl { border-top-left-radius: min(.14em, var(--L)); }
.hb-tr { border-top-right-radius: min(.14em, var(--L)); }
.hb-bl { border-bottom-left-radius: min(.14em, var(--L)); }
.hb-br { border-bottom-right-radius: min(.14em, var(--L)); }
/* the wordmark is "Ashton" set like the name motion (css/name-motion.css); the link's aria-label keeps the full name */
.wordmark { font-family: 'Poppins', sans-serif; font-weight: 800; letter-spacing: -.01em; }
/* Home (index.html puts .home-dock on <html>): at the top the big "Ashton" of the first slide is the name, so the
   wordmark stays out of sight until index.html docks the big one into its place (.name-docked). Out of sight only:
   the link keeps its place in the tab order and its name for screen readers, and keyboard focus shows it */
.home-dock:not(.name-docked) .site-header .wordmark:not(:focus-visible) { opacity: 0; pointer-events: none; }
/* Home, once the name has docked while the header still lies over the "Ashton is" slide: the band comes in behind the
   header in that slide's colour, as the page-colour band does further down, so "is" and the phrase scrolling up under
   it never run into the wordmark and the links. Over a work image the header stays see-through, before the name docks
   nothing changes, and an open header list (see "header lists") takes the band's colour too */
.home-dock.name-docked:not(.head-stuck):has(.slide-intro.is-active) body::before { height: var(--head-h); background: var(--soft); opacity: 1; }
.home-dock.name-docked:not(.head-stuck):has(.slide-intro.is-active) .site-header.is-over ~ .drop-panel:not([hidden]) {
  background: var(--soft); mix-blend-mode: normal; -webkit-backdrop-filter: none; backdrop-filter: none;
}
.home-dock.name-docked:not(.head-stuck):has(.slide-intro.is-active) .site-header.is-over ~ .drop-panel:not([hidden]) ul { mix-blend-mode: difference; }
.site-nav { min-width: 0; max-width: 100%; }
.site-nav ul { display: flex; flex-wrap: wrap; align-items: center; gap: 6px .9em; }
.site-nav li { flex: none; }
/* Feather icons and labels share one link and one set of hover or current-page corners. */
.site-nav a { display: inline-flex; align-items: center; gap: .35em; }
.site-nav a .ni { width: .85em; height: .85em; flex: none; }

/* Display mode: Auto, Light, Dark. The icon and label show the current choice. Phones use round icons. */
.theme-toggle {
  display: inline-flex; align-items: center; gap: .35em;
  margin-left: .35em; padding: 0; border: 0;
  background: none; cursor: pointer;
}
.theme-toggle svg { width: .85em; height: .85em; flex: none; }
/* theme: Auto, Light, Dark; the icon and the word show the current choice (js/world.js) */
.site-header .theme-toggle .ti { display: none; }
.site-header .theme-toggle[data-mode="auto"] .ti-auto,
.site-header .theme-toggle[data-mode="light"] .ti-sun,
.site-header .theme-toggle[data-mode="dark"] .ti-moon { display: block; }
@media (max-width: 639px) {
  .site-nav a, .theme-toggle {
    justify-content: center;
    width: 30px; height: 30px; padding: 0;
    border: 1px solid color-mix(in srgb, currentColor 35%, transparent);
    border-radius: 50%;
  }
  .theme-toggle { margin-left: .15em; }
  .site-nav a .ni, .theme-toggle svg { width: 15px; height: 15px; }
  .site-nav a .nl, .theme-toggle .tt-label {
    position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
    overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
  }
}
@media (max-width: 379px) {
  .site-header { column-gap: 12px; }
  .site-nav ul { gap: 0 .7em; }
}

/* header lists: data-drop opens a row outside the header. Over a work image, both lists use the same
   frost: an empty drop-veil blurs the picture and adds 78% of the page colour. The separate panel stays clear,
   with normal text and corners in the page's ink, never inside the filter group. Without the needed CSS features,
   or while the name cover is up, both lists use a plain page-colour fill with difference text. Away from a hero
   the lists keep their page colour. The docked home intro keeps its own soft fill.
   The control's upper corners stay around its icon and label; its lower corners travel past --drop-gap to the
   bottom of the list. The panel and veil share their size and clip-path motion; reduced motion skips the unroll.
   List items keep the header's hover, focus and current-page corners. No chevrons are added.
   js/site.js measures --drop-gap and --drop-align (start, end or full), then sets --drop-l, --drop-r and --drop-b. */
@property --drop-l { syntax: '<length>'; inherits: true; initial-value: 0px; }
@property --drop-r { syntax: '<length>'; inherits: true; initial-value: 0px; }
@property --drop-b { syntax: '<length>'; inherits: true; initial-value: 0px; }
.site-header .site-nav .drop-trigger {
  transition: color .3s var(--ease), --hb .45s cubic-bezier(.25, .7, .2, 1), --ha .45s cubic-bezier(.25, .7, .2, 1), --ho .3s var(--ease),
              --drop-l .45s cubic-bezier(.25, .7, .2, 1), --drop-r .45s cubic-bezier(.25, .7, .2, 1), --drop-b .45s cubic-bezier(.25, .7, .2, 1);
}
.site-header .site-nav .drop-trigger[aria-expanded="true"] { --hb: 1; --ha: 1; --ho: 1; color: inherit; outline: none; }   /* the word keeps its colour */
.site-header .drop-trigger .hb-bl { left: calc(50% - var(--px) - var(--drop-l)); bottom: calc(50% - var(--py) - var(--drop-b)); }
.site-header .drop-trigger .hb-br { right: calc(50% - var(--px) - var(--drop-r)); bottom: calc(50% - var(--py) - var(--drop-b)); }
.drop-panel {
  --drop-align: start;
  --drop-gap: .5em;   /* about 11px at 1440px, measured below the hover frame */
  position: fixed; top: 0; left: 0; z-index: 19;
  background: var(--paper); color: #fff;
  font-size: var(--fs-ui); line-height: 1.2;
}
.drop-panel, .drop-veil { transition: clip-path .45s cubic-bezier(.25, .7, .2, 1); }
.drop-veil { display: none; position: fixed; top: 0; left: 0; z-index: 19; pointer-events: none; }
.drop-panel[hidden], .drop-veil[hidden] { display: none; }
.drop-panel ul { padding: .55em .55em .6em; border-top: 1px solid; mix-blend-mode: difference; }   /* the hairline is the row's top edge */
.site-header.is-over ~ .drop-panel { background: var(--paper); mix-blend-mode: normal; }
.site-header.is-over ~ .drop-panel ul { mix-blend-mode: difference; }
/* Start with a plain fill. Frost needs both the filter and a page-colour mix.
   Only the empty sibling gets the filter and tint, never the text or its corners.
   The home intro keeps its existing fills. The name cover keeps the plain fallback. */
@supports selector(:has(*)) and (background: color-mix(in srgb, white 78%, transparent)) and ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) {
  html:not(.nm-cover-on):not(:has(.slide-intro.is-active)) .site-header.is-over ~ .drop-veil:not([hidden]) {
    display: block;
    background: color-mix(in srgb, var(--paper) 78%, transparent);
    -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px);
  }
  html:not(.nm-cover-on):not(:has(.slide-intro.is-active)) .site-header.is-over ~ .drop-panel {
    background: none; color: var(--ink); mix-blend-mode: normal;
  }
  html:not(.nm-cover-on):not(:has(.slide-intro.is-active)) .site-header.is-over ~ .drop-panel ul { mix-blend-mode: normal; }
  html:not(.nm-cover-on):not(:has(.slide-intro.is-active)) .site-header.is-over ~ .drop-panel .hb { border-color: currentColor; }
  html:not(.nm-cover-on):not(:has(.slide-intro.is-active)) .site-header.is-over ~ .drop-panel a:focus-visible { color: var(--ink-2); }
  @media (hover: hover) and (pointer: fine) {
    html:not(.nm-cover-on):not(:has(.slide-intro.is-active)) .site-header.is-over ~ .drop-panel a:hover { color: var(--ink-2); }
  }
}
/* Before the home name docks, keep Work's existing clear panel over the intro.
   Its empty veil still blurs there when supported. The language list keeps its soft fill. */
html.home-dock:not(.name-docked):not(.nm-cover-on):has(.slide-intro.is-active) .site-header.is-over ~ .drop-panel.work-menu { background: none; mix-blend-mode: difference; }
html.home-dock:not(.name-docked):not(.nm-cover-on):has(.slide-intro.is-active) .site-header.is-over ~ .drop-panel.work-menu ul { mix-blend-mode: normal; }
@supports ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) {
  html.home-dock:not(.name-docked):not(.nm-cover-on):has(.slide-intro.is-active) .site-header.is-over ~ .drop-veil:not([hidden]):has(+ .work-menu) {
    display: block; -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px);
  }
}
.drop-panel :is(a, li > span) {
  --hb: 0; --ha: 1; --ho: 1;
  position: relative; display: block;
  white-space: nowrap; text-decoration: none;
  transition: color .3s var(--ease), --hb .45s cubic-bezier(.25, .7, .2, 1), --ha .45s cubic-bezier(.25, .7, .2, 1), --ho .3s var(--ease);
}
.drop-panel a::after { content: ""; position: absolute; inset: -.4em -.2em; }   /* a target larger than the word */
.drop-panel [aria-current] { --hb: 1; --ha: .55; --ho: .6; }
.drop-panel a:focus-visible { --hb: 1; --ha: 1; --ho: 1; color: #4d4d4d; outline: none; }
@media (hover: hover) and (pointer: fine) {
  .drop-panel a:hover { --hb: 1; --ha: 1; --ho: 1; color: #4d4d4d; }
}
/* Work and languages use the same inset on both sides. */
.work-menu ul { display: flex; gap: 0 .9em; }
@media (max-width: 639px) {
  /* phones: the line runs from gutter to gutter, the three spread across it with even room between them */
  .drop-panel { --drop-gap: 8px; }
  .work-menu { --drop-align: full; }
  .work-menu ul { justify-content: space-between; }
}
@media (prefers-reduced-motion: reduce) {
  .site-header .site-nav .drop-trigger, .drop-panel :is(a, li > span) { transition: color .3s var(--ease) !important; }
  .drop-panel, .drop-veil { transition: none !important; }
}

html { scroll-padding-top: calc(var(--head-h) + 16px); }

/* pages without a hero start below the header */
.page { padding-top: calc(var(--head-h) + clamp(32px, 3.2vw, 46px)); }

/* email and social links: a small line icon before the name */
.icon-link { display: inline-flex; align-items: center; gap: .5em; }
.icon-link svg, .big-link svg { width: 1em; height: 1em; flex: none; }

/* ---------- footer ---------- */

.site-footer {
  margin-top: clamp(96px, 10.4vw, 150px);
  padding: clamp(32px, 3.2vw, 46px) var(--gutter);
  background: var(--soft);
  font-size: clamp(15px, .29vw + 13.9px, 18px);
  line-height: 1.5;
}
.crumbs + .site-footer { margin-top: clamp(32px, 3.5vw, 50px); }
.foot {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 24px 32px;
  width: min(100%, var(--frame));
  margin-inline: auto;
}
.foot a { text-decoration: none; }
.foot a:hover { opacity: .85; }
.foot-sig .mark { width: 36px; height: 36px; margin-bottom: 14px; }
.foot-based { margin-top: .35em; color: var(--muted); font-size: .85em; }
.foot-links { display: flex; flex-wrap: wrap; gap: 0 48px; }
.foot-links ul + ul { margin-top: 0; }
.foot-top { margin-top: 8px; }
.foot-top a::before { content: "\2191"; display: inline-block; margin-right: .5em; transition: transform .3s var(--ease); }
.foot-top a:hover::before { transform: translateY(-.3em); }
.copy { color: var(--muted); }

@media (min-width: 768px) {
  .foot { grid-template-columns: minmax(0, 1.25fr) minmax(0, 1.75fr) auto; }
  .foot-links { flex-direction: column; gap: 12px; }
  .foot-top { grid-column: 1; margin-top: 16px; }
  .copy { grid-column: 3; align-self: end; }
}

/* ---------- breadcrumb ---------- */

.crumbs {
  width: min(100% - 2 * var(--gutter), var(--frame));
  margin: clamp(96px, 10.4vw, 150px) auto 0;
  font-size: 16px;
  line-height: 1.3;
}
.crumbs ol { display: flex; flex-wrap: wrap; }
.crumbs li + li::before { content: "/"; padding: 0 .35em; color: var(--muted); opacity: .6; }
.crumbs a { color: var(--muted); text-decoration: underline; }
.crumbs a:hover { color: var(--ink); }

/* ---------- home: slideshow ---------- */

.show {
  --cap-pad: clamp(22px, 3vw, 44px);
  position: relative;
  height: 100vh;
  height: 100svh;
  min-height: 440px;
  overflow: hidden;
  background: var(--soft);
  color: var(--ink);
  transition: color .4s var(--ease);
}
.show[data-tone="dark"] { color: #fff; }
.show[data-tone="light"] { color: #111; }

.slides { position: absolute; inset: 0; }
.slide { position: absolute; inset: 0; opacity: 0; visibility: hidden; background: var(--bg, var(--soft)); }
.slide.is-active { z-index: 2; opacity: 1; visibility: visible; transition: opacity .6s ease-out; }
.slide.is-prev { z-index: 1; opacity: 1; visibility: visible; }
.slide[data-href] { cursor: pointer; }
.slide img, .slide video { width: 100%; height: 100%; object-fit: cover; transform: scale(var(--zoom, 1)); }
/* on tall or square screens the whole artwork shows on its own ground colour instead of being cut,
   enlarged by --pz as far as its own margins allow */
@media (max-aspect-ratio: 155/100) { .slide.is-fit img, .slide.is-fit video { object-fit: contain; transform: scale(var(--pz, 1)); } }

/* the first slide: "Ashton is ..." (index.html script), set as on the earlier home page:
   Poppins 800 over a light Inter phrase, on the page gutter, vertically centred.
   It stays readable to screen readers when hidden. */
.slide-intro {
  display: grid;
  align-items: center;
  justify-items: start;
  padding: 0 var(--gutter);
  visibility: visible;
  background: var(--soft);
}
.is-line {
  font-family: 'Poppins', 'Inter', sans-serif;
  font-size: clamp(44px, 8.2vw, 128px);
  font-weight: 700;
  line-height: 1.04;
  letter-spacing: -.01em;
  color: var(--ink);
}
.is-static { display: block; font-weight: 800; transition: opacity .3s ease, transform .3s ease; }
/* "Ashton" itself: the name cover lands on it, and on scroll index.html scales it into the header wordmark
   (a plain inline box takes no transform). On the way (--dock 0 to 1) it also takes the wordmark's colour there,
   the header's white in difference mode over the hero's --soft; once docked the wordmark stands in for it */
.is-name {
  display: inline-block;
  color: color-mix(in srgb, var(--ink), rgb(from var(--soft) calc(255 - r) calc(255 - g) calc(255 - b)) calc(var(--dock, 0) * 100%));
}
.name-docked .is-name, .name-moving .is-name { opacity: 0; }
/* when the slideshow leaves "Ashton is" or comes back to it, index.html carries the name between the two as a copy drawn
   like the header, above the slides; the big "Ashton" and the wordmark both stay out of sight meanwhile (.name-moving) */
.dock-fly {
  position: fixed; top: 0; left: 0; z-index: 21; margin: 0;
  font-family: 'Poppins', sans-serif; font-weight: 800; letter-spacing: -.01em; line-height: 1; white-space: nowrap;
  color: #fff; mix-blend-mode: difference; pointer-events: none;
}
.is-cycle {
  display: block;
  width: fit-content;
  font-family: 'Inter', sans-serif;
  font-weight: 300;
  letter-spacing: -.02em;
  clip-path: inset(-.3em -.1em -.3em -.1em);
  transition: opacity .3s ease, transform .3s ease, clip-path .35s var(--ease);
}
.is-verb { display: inline-block; transition: opacity .3s ease, transform .3s ease; }
/* a new role drops in from just above, as on the earlier home page ("is" turns into "was" the same way);
   a branch of the same role closes and opens sideways */
.is-static.is-out, .is-verb.is-out, .is-cycle.is-out { opacity: 0; transform: translateY(-.16em); }
.is-cycle.is-wipe { clip-path: inset(-.3em 100% -.3em -.1em); }
.is-line.is-past :is(.is-verb, .is-cycle) { color: var(--muted); }   /* "was an electrical engineer." reads as the past */
/* the face on "Ashton is" (index.html places and opens it): a square photo in a corner frame drawn like the name
   motion's, as tall as the two lines of type, from the top of the capital A to the phrase's first baseline, the
   frame's outer edge on the right margin and the phrase wrapping short of it; on phones it stands above the words.
   Shut, the photo is cut down to its centre and each corner sits there with no arm; it opens once the phrase has
   dropped in and shuts as the show leaves the slide. --fx, --fy, --fb: the frame's box; --fh: the photo's inset
   in it; --fp: the photo; --fs, --fa, --fr: stroke, arm and radius of the corners */
.is-face {
  position: absolute; z-index: 1; left: var(--fx, 0); top: var(--fy, 0); width: var(--fb, 0); height: var(--fb, 0);
  margin: 0; color: var(--ink); pointer-events: none; visibility: hidden;
}
.is-face.is-placed { visibility: visible; }
.slide .is-face img {
  position: absolute; left: var(--fh); top: var(--fh); width: var(--fp); height: var(--fp); max-width: none;
  object-fit: cover; transform: none; clip-path: inset(0); transition: clip-path .6s var(--ease);
}
.is-face i { position: absolute; width: var(--fa); height: var(--fa); border: 0 solid currentColor; transition: transform .6s var(--ease); }
.is-face .f-tl { left: 0; top: 0; border-width: var(--fs) 0 0 var(--fs); border-top-left-radius: var(--fr); transform-origin: 0 0; }
.is-face .f-tr { right: 0; top: 0; border-width: var(--fs) var(--fs) 0 0; border-top-right-radius: var(--fr); transform-origin: 100% 0; }
.is-face .f-bl { left: 0; bottom: 0; border-width: 0 0 var(--fs) var(--fs); border-bottom-left-radius: var(--fr); transform-origin: 0 100%; }
.is-face .f-br { right: 0; bottom: 0; border-width: 0 var(--fs) var(--fs) 0; border-bottom-right-radius: var(--fr); transform-origin: 100% 100%; }
.slide .is-face.is-shut img { clip-path: inset(50%); }
.is-face.is-shut .f-tl { transform: translate(calc(var(--fb) / 2), calc(var(--fb) / 2)) scale(0); }
.is-face.is-shut .f-tr { transform: translate(calc(var(--fb) / -2), calc(var(--fb) / 2)) scale(0); }
.is-face.is-shut .f-bl { transform: translate(calc(var(--fb) / 2), calc(var(--fb) / -2)) scale(0); }
.is-face.is-shut .f-br { transform: translate(calc(var(--fb) / -2), calc(var(--fb) / -2)) scale(0); }

.show-caption {
  position: absolute; z-index: 3; bottom: 0; left: var(--gutter);
  max-width: calc(100% - 2 * var(--gutter) - 176px);
  font-size: clamp(16px, .19vw + 15.3px, 18px);
  line-height: 1.2;
  transition: opacity .3s var(--ease);
}
.show-caption.is-out { opacity: 0; transition-duration: .2s; }
.show-caption a { display: inline-block; padding: var(--cap-pad) 0; text-decoration: none; }
.show-caption a[hidden] { display: none; }
.show-caption a::after { content: "\2192"; margin-left: .3em; transition: margin .3s var(--ease); }   /* inline, so the arrow never wraps alone */
.show-caption a:hover { opacity: .85; }
.show-caption a:hover::after { margin-left: .9em; }

.show-nav { position: absolute; z-index: 3; right: calc(var(--gutter) - 9px); bottom: calc(var(--cap-pad) - 2px); display: flex; }
.show-nav button {
  display: grid; place-items: center;
  width: 24px; height: 24px; padding: 0; border: 0;
  background: none; cursor: pointer;
}
.show-dot::before {
  content: ""; width: 6px; height: 6px; border-radius: 50%;
  background: currentColor;
  transition: background-color .3s var(--ease), box-shadow .3s var(--ease);
}
.show-dot[aria-current="true"]::before { background: transparent; box-shadow: 0 0 0 1px currentColor; }
.show-dot:hover::before { box-shadow: 0 0 0 1px currentColor; }
.show-pause { margin-left: 4px; }
.show-pause::before { content: ""; width: 6px; height: 8px; border-right: 2px solid; border-left: 2px solid; }
.show-pause.is-paused::before { width: 0; height: 0; border-style: solid; border-width: 4.5px 0 4.5px 7px; border-color: transparent transparent transparent currentColor; }

/* ---------- home: statement and selected work ---------- */

.intro { padding: clamp(64px, 6.1vw, 88px) var(--gutter) 0; text-align: center; }
.statement { max-width: 22em; margin-inline: auto; font-size: var(--fs-big); line-height: 1.3; letter-spacing: -.012em; }
.statement-sub { margin-top: clamp(16px, 2.5vw, 36px); }

.home-work { margin-top: clamp(64px, 7.6vw, 110px); }
.more { margin-top: clamp(32px, 3vw, 44px); }

/* ---------- image cards (home, archive, next project) ---------- */

.cards {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: clamp(40px, 4.9vw, 70px) clamp(24px, 5.6vw, 80px);
}
@media (min-width: 600px) { .cards { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

.card a { display: block; text-decoration: none; }
.thumb { display: block; aspect-ratio: 16 / 10; overflow: hidden; background: var(--soft); transition: background-color .3s var(--ease); }
.thumb img { width: 100%; height: 100%; object-fit: cover; transition: opacity .3s var(--ease); }
.card figcaption { margin-top: 12px; }
.card-name { display: block; line-height: 1.3; }
.card-sub { display: block; margin-top: .15em; color: var(--muted); font-size: .875em; }

/* names only on hover, the image fading back to the page */
@media (hover: hover) and (pointer: fine) {
  .is-hover .card figure { position: relative; }
  .is-hover .card figcaption {
    position: absolute; inset: 0;
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    margin: 0; padding: 16px;
    text-align: center;
    opacity: 0;
    transform: translateY(-10px);
    transition: opacity .3s var(--ease), transform .3s var(--ease);
  }
  .is-hover .card-name { font-size: clamp(18px, .6vw + 15.4px, 24px); }
  .is-hover .card-sub { font-size: 15px; }
  .is-hover .card a:is(:hover, :focus-visible) .thumb { background: var(--paper); }
  .is-hover .card a:is(:hover, :focus-visible) img { opacity: .08; }
  .is-hover .card a:is(:hover, :focus-visible) figcaption { opacity: 1; transform: none; }
}

/* ---------- work archive ---------- */

/* Projects · Collections · Personal at the top of the three Work pages */
.w-switch { margin-top: -10px; margin-bottom: clamp(20px, 2.5vw, 36px); }
.w-switch ul { display: flex; flex-wrap: wrap; }
.w-switch li + li::before { content: "\00b7"; content: "\00b7" / ""; padding: 0 .5em; color: var(--muted); }
.w-switch a { display: inline-block; padding: 10px 0; color: var(--muted); text-decoration: none; transition: color .3s var(--ease); }
.w-switch a:hover { color: var(--ink); }
.w-switch a[aria-current] { color: var(--ink); text-decoration: underline; text-decoration-thickness: 1.5px; text-underline-offset: .3em; }

.archive + .archive { margin-top: clamp(80px, 9.2vw, 132px); }
.group { margin-bottom: clamp(20px, 2.5vw, 36px); font-size: var(--fs-mid); font-weight: 600; letter-spacing: -.01em; }
@media (min-width: 600px) { .archive .cards { gap: clamp(40px, 3.5vw, 50px) clamp(24px, 4.2vw, 60px); } }
@media (min-width: 992px) { .archive .cards { grid-template-columns: repeat(3, minmax(0, 1fr)); } }

/* ---------- project pages ---------- */

/* on phones the hero starts below the header; from 768px it runs under it, as on the home page */
.p-hero { position: relative; margin-top: var(--head-h); background: var(--hero-bg, var(--soft)); }
.p-hero img { width: 100%; }
/* artwork that should not be cropped sits whole on a plain band */
.p-hero.is-fit { padding: var(--gutter); }

@media (min-width: 768px) {
  .p-hero { height: min(50vw, 80vh); margin-top: 0; }
  .p-hero img { height: 100%; object-fit: cover; object-position: var(--pos, 50% 50%); }
  .p-hero.is-fit { padding: calc(var(--head-h) + 10px) var(--gutter); }
  .p-hero.is-fit img { object-fit: contain; }
}

.p-head { width: min(100% - 2 * var(--gutter), 924px); margin: clamp(48px, 6.1vw, 88px) auto 0; text-align: center; }
.p-head h1 { font-size: var(--fs-big); line-height: 1.3; letter-spacing: -.012em; }
.p-sub { margin-top: .6em; }
.p-meta { margin-top: .2em; color: var(--muted); }

.p-text, .p-cap { width: min(100% - 2 * var(--gutter), var(--text)); margin-inline: auto; }
.p-text { margin-top: clamp(32px, 3.5vw, 50px); }
.p-text p + p, .p-cap p + p { margin-top: 1em; }

.p-sec { margin-top: clamp(64px, 9.2vw, 132px); }
.p-art { display: flex; flex-direction: column; gap: var(--g); width: min(100% - 2 * var(--gutter), var(--wide)); margin-inline: auto; }
.p-art img { width: 100%; }
.p-cap { padding-top: clamp(20px, 3vw, 44px); }
/* section headings: a size and weight above the text, every word capitalised, and as much room below as between paragraphs */
.p-cap h2 { margin-bottom: .8em; font-size: var(--fs-mid); font-weight: 600; line-height: 1.25; letter-spacing: -.01em; text-transform: capitalize; }

/* rows where every image gets the same height */
.row { display: flex; gap: var(--g); }
.row > img { flex: var(--ar) 1 0%; width: 0; min-width: 0; }
.l1r2 { display: flex; gap: var(--g); }
.l1r2 .l { position: relative; flex: var(--ar) 1 0%; min-width: 0; }
.l1r2 .l img { position: absolute; inset: 0; height: 100%; object-fit: cover; }
.l1r2 .r { display: flex; flex-direction: column; gap: var(--g); flex: var(--ar) 1 0%; min-width: 0; }

.p-more { width: min(100% - 2 * var(--gutter), var(--wide)); margin: clamp(96px, 10.4vw, 150px) auto 0; }
.p-more .cards { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 40px clamp(16px, 4.2vw, 60px); }
.p-more .is-next:only-child { grid-column: 2; }   /* a group of two shows just the other page, on its side */
.p-dir { display: block; color: var(--muted); font-size: .875em; }
.p-all { margin-top: clamp(48px, 5vw, 72px); text-align: center; }

/* ---------- about ---------- */

.a-head { width: min(100% - 2 * var(--gutter), 924px); margin-inline: auto; text-align: center; }
.a-head h1 { font-size: var(--fs-big); line-height: 1.3; letter-spacing: -.012em; }
.a-head p { margin-top: clamp(12px, 2vw, 28px); }

/* the name and the face side by side: two frames of one height, drawn alike with the name motion's stroke, arm, radius
   and clearance in ems of the name (css/name-motion.css: --s .14em, --arm .42em, --r .2em, --pad .34em; three lines
   of .92em with .42em above and .37em below make the name's box 3.55em). The face stands still while the name opens
   and rolls. On phones the face stands above the name, the two centred with the rest of the heading */
.a-id { display: flex; justify-content: center; align-items: flex-start; gap: .55em; font-size: clamp(36px, 5.2vw, 76px); }   /* the name's size (name-motion.css .a-name .nm) */
.a-face { position: relative; flex: none; width: 3.55em; height: 3.55em; margin: 0; }
.a-face img { position: absolute; left: .48em; top: .48em; width: 2.59em; height: 2.59em; max-width: none; object-fit: cover; }
.a-face i { position: absolute; width: .42em; height: .42em; border: 0 solid currentColor; }
.a-face .f-tl { left: 0; top: 0; border-width: .14em 0 0 .14em; border-top-left-radius: .2em; }
.a-face .f-tr { right: 0; top: 0; border-width: .14em .14em 0 0; border-top-right-radius: .2em; }
.a-face .f-bl { left: 0; bottom: 0; border-width: 0 0 .14em .14em; border-bottom-left-radius: .2em; }
.a-face .f-br { right: 0; bottom: 0; border-width: 0 .14em .14em 0; border-bottom-right-radius: .2em; }
@media (max-width: 639px) {
  .a-id { flex-direction: column; align-items: center; gap: .3em; }
  .a-face { order: -1; }
}

.a-bio { display: grid; grid-template-columns: minmax(0, 1fr); gap: 28px; margin-top: clamp(56px, 7.6vw, 110px); }
.a-bio-text p + p { margin-top: 1em; }
/* a quotation on its own line, set like a Markdown block quote: a rule on the left, no quotation marks */
.a-bio-text blockquote { margin: 1em 0; padding-left: 1em; border-left: 2px solid currentColor; break-inside: avoid; }

.resume { margin-top: clamp(80px, 9.2vw, 132px); }
.resume > h2 { margin-bottom: clamp(36px, 4.4vw, 64px); font-size: var(--fs-big); line-height: 1.3; text-align: center; }
/* three levels: section heading on a hairline, item title (role and organisation)
   with place and dates under it, then the detail in a quieter ink. Items sit further
   apart than the lines inside an item, and sections twice as far again. */
.rcols {
  --item-gap: clamp(24px, 2.1vw, 30px);
  --sec-gap: calc(var(--item-gap) * 2.2);
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--sec-gap) clamp(32px, 4.2vw, 60px);
}
.rcols h3 {
  margin-bottom: calc(var(--item-gap) * .85);
  padding-top: .45em;
  border-top: 1px solid var(--ink);
  font-size: clamp(22px, .58vw + 19.6px, 28px);
  line-height: 1.2;
  letter-spacing: -.01em;
}
.rcols h3:not(:first-child) { margin-top: var(--sec-gap); }
.skills { margin: 0; }
.jobs li + li, .skills div + div, .plain li + li { margin-top: var(--item-gap); }
.jobs h4, .skills dt, .plain li { font-size: clamp(17.5px, .24vw + 16.5px, 20px); font-weight: 500; line-height: 1.3; }
.jobs .org { display: block; font-weight: 400; }
.jobs .meta { margin-top: .35em; color: var(--muted); font-size: clamp(14px, .1vw + 13.6px, 15px); }
.jobs p:not(.meta), .skills dd { max-width: 31em; margin: .6em 0 0; color: var(--ink-2); line-height: 1.6; }

@media (min-width: 992px) {
  .a-bio-text { columns: 2; column-gap: clamp(40px, 4.2vw, 60px); }
  .rcols { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* ---------- contact ---------- */

.c-links { margin-top: clamp(40px, 5vw, 72px); text-align: center; }
.c-links h2 { color: var(--muted); }
.c-links h2:not(:first-child) { margin-top: clamp(32px, 3.5vw, 50px); }
.c-links p { margin-top: .3em; }
.big-link { display: inline-flex; align-items: center; gap: .45em; font-size: var(--fs-mid); text-decoration: none; }
.big-link svg { width: .8em; height: .8em; }
.big-link:hover { opacity: .85; }

.contact-form { width: min(100% - 2 * var(--gutter), var(--text)); margin: clamp(80px, 9.2vw, 132px) auto 0; }
.contact-form h2 { margin-bottom: 8px; font-size: var(--fs-mid); text-align: center; }
.contact-form label { display: block; margin: 24px 0 8px; color: var(--muted); }
.contact-form input,
.contact-form textarea {
  display: block;
  width: 100%;
  padding: 12px 14px;
  border: 1px solid var(--ink);
  border-radius: 0;
  background: var(--paper);
  color: var(--ink);
  font: inherit;
}
.contact-form textarea { min-height: 180px; resize: vertical; }
.contact-form input:focus-visible,
.contact-form textarea:focus-visible { outline-offset: 2px; }
.send { margin-top: 32px; padding: 0; border: 0; background: none; cursor: pointer; }
.hint, .status { margin-top: 16px; color: var(--muted); font-size: 15px; }
.status:empty { display: none; }

/* on a computer screen: addresses and links on the left, the form on the right, all in one screen */
@media (min-width: 992px) {
  .c-grid {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1.3fr);
    gap: clamp(56px, 6vw, 110px);
    align-items: start;
    width: min(100% - 2 * var(--gutter), 1180px);
    margin: clamp(36px, 4vw, 60px) auto 0;
  }
  .c-links { margin-top: 0; text-align: left; }
  .contact-form { width: auto; margin: 0; }
  .contact-form h2 { text-align: left; }
  .contact-form h2 + label { margin-top: 12px; }
  .contact-form label { margin: 18px 0 6px; }
  .contact-form textarea { min-height: 0; height: clamp(110px, calc(100vh - 640px), 220px); }   /* shorter on short laptop screens so Send stays in view */
  .send { margin-top: 24px; }
}

/* ---------- smaller screens ---------- */

@media (max-width: 639px) {
  .row, .l1r2 { flex-direction: column; }
  .row > img { flex: none; width: 100%; }
  .l1r2 .l, .l1r2 .r { flex: none; }
  .l1r2 .l img { position: static; height: auto; }
}

@media (prefers-reduced-motion: reduce) {
  .site-header, .slide.is-active, .is-static, .is-verb, .is-cycle, .show-caption, .thumb img, .card figcaption, .is-face :is(img, i) { transition: none !important; }
  .site-header :is(.wordmark, .site-nav a, .theme-toggle) { transition: color .3s var(--ease) !important; }   /* the corners appear in place */
}

/* Language: every page uses this globe and current name. It opens the same header list as Work.
   Shared header lists supply the corners, fill, gap and motion. Phones show the globe alone. */
.site-header .lang-toggle {
  --hb: 0; --ha: 1; --ho: 1;
  position: relative;
  display: inline-flex; align-items: center; gap: .35em;
  margin-left: .35em; padding: 0; border: 0;
  background: none; cursor: pointer;
  pointer-events: auto;
  white-space: nowrap;
  transition: color .3s var(--ease), --hb .45s cubic-bezier(.25, .7, .2, 1), --ha .45s cubic-bezier(.25, .7, .2, 1), --ho .3s var(--ease);
}
.site-header .lang-toggle svg { width: .85em; height: .85em; flex: none; }
.site-header .lang-toggle:focus-visible { --hb: 1; --ha: 1; --ho: 1; color: #4d4d4d; outline: none; }
@media (hover: hover) and (pointer: fine) {
  .site-header .lang-toggle:hover { --hb: 1; --ha: 1; --ho: 1; color: #4d4d4d; }
}
@media (prefers-reduced-motion: reduce) {
  .site-header .lang-toggle { transition: color .3s var(--ease) !important; }   /* the corners appear in place */
}
@media (max-width: 639px) {
  .site-header .lang-toggle {
    justify-content: center;
    width: 30px; height: 30px; margin-left: .15em;
    border: 1px solid color-mix(in srgb, currentColor 35%, transparent);
    border-radius: 50%;
  }
  .site-header .lang-toggle svg { width: 15px; height: 15px; }
  .site-header .lang-toggle .lt-label {
    position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
    overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
  }
}

/* phones: the globe makes five round buttons in the header line, so the gaps close up a little (the tap targets
   keep their size); the header still wraps to a second line rather than overflow when a language's words run long */
@media (max-width: 419px) {
  .site-header { column-gap: 12px; }
  .site-nav ul { gap: 0 .55em; }
}

/* Languages line up with the right edge of the globe's frame, with the same inset as Work. */
.drop-panel.lang-menu { --drop-align: end; }
.lang-menu ul { display: flex; flex-direction: column; gap: .8em; }
/* Work and languages share the same frost over work images. The empty veil carries the blur and tint;
   this list carries normal text and corners. If frost is unavailable, both lists use the plain page-colour fill.
   The docked home intro keeps its soft fill. */
/* on the home page's "Ashton is" slide the fill is that slide's colour, so the list reads as part of it */
.home-dock:has(.slide-intro.is-active) .site-header.is-over ~ .drop-panel.lang-menu { background: var(--soft); }

/* the same list in the footer, for a page read without scripts */
.foot-lang [aria-current] { color: var(--muted); }

/* v131 single-frame arbitration: only visibility changes ownership immediately.
   Keep --hb/--ha/--ho and all existing transition timings; a losing frame cannot
   remain visible during its closing animation. Priority: hover > focus > open > current.
   :where() gives every visibility rule the same specificity (.hb); order decides.
   A closing panel is inert before it becomes hidden, so neither can claim a frame. */
/* F0: hide every header/list frame, including current menu spans. */
:where(.site-header, .drop-panel) .hb { visibility: hidden; }
/* F1: no interaction falls back to the current page, if this page has one. */
:where(.site-header .site-nav a[aria-current]) > .hb { visibility: visible; }
/* F2: an open trigger replaces the current-page frame. */
:where(html:has(.site-header .drop-trigger[aria-expanded="true"])) :where(.site-header, .drop-panel) .hb { visibility: hidden; }
:where(.site-header .drop-trigger[aria-expanded="true"]) > .hb { visibility: visible; }
/* F3: a visible keyboard focus replaces both fallback frames. */
:where(html:has(.site-header :is(.wordmark, .site-nav a, .theme-toggle, .lang-toggle):focus-visible, .drop-panel:not([hidden]):not([inert]) :is(a, li > span):focus-visible)) :where(.site-header, .drop-panel) .hb { visibility: hidden; }
:where(.site-header :is(.wordmark, .site-nav a, .theme-toggle, .lang-toggle):focus-visible, .drop-panel:not([hidden]):not([inert]) :is(a, li > span):focus-visible) > .hb { visibility: visible; }
/* F4: a real pointer hover takes priority even if keyboard focus stays elsewhere.
   Include the current-language span; touch devices keep focus/open/current priority. */
@media (hover: hover) and (pointer: fine) {
  :where(html:has(.site-header :is(.wordmark, .site-nav a, .theme-toggle, .lang-toggle):hover, .drop-panel:not([hidden]):not([inert]) :is(a, li > span):hover)) :where(.site-header, .drop-panel) .hb { visibility: hidden; }
  :where(.site-header :is(.wordmark, .site-nav a, .theme-toggle, .lang-toggle):hover, .drop-panel:not([hidden]):not([inert]) :is(a, li > span):hover) > .hb { visibility: visible; }
  .drop-panel:not([hidden]):not([inert]) li > span:hover { --hb: 1; --ha: 1; --ho: 1; }
}
/* End v131 single-frame arbitration. */

/* Only the requested Logo Motion covers contain moving artwork. */
.thumb.motion-cover { position: relative; background: #000; }
.motion-cover > .motion-poster,
.motion-cover > video {
  display: block; width: 100%; height: 100%; object-fit: contain;
  transform: none; background: #000;
}
.motion-cover > video { position: absolute; inset: 0; }
@media (hover: hover) and (pointer: fine) {
  .thumb.motion-cover > video { transition: opacity .3s var(--ease); }
  .is-hover .card a:is(:hover, :focus-visible) .motion-cover > video { opacity: .08; }
}
@media (prefers-reduced-motion: reduce) {
  .motion-cover > video { display: none; transition: none; }
}

/* Overlay the hero clip on its still; neither layer changes the shared image scaling. */
.slide video[data-hero-motion] { position: absolute; inset: 0; }
.slide video[data-hero-motion][hidden] { display: none; }
@media (prefers-reduced-motion: reduce) {
  .slide video[data-hero-motion] { display: none; }
}

/* A wide clip fills its card like any picture (the square Superlinear loop sat on black). */
.thumb.motion-cover.is-wide { background: var(--soft); }
.motion-cover.is-wide > :is(.motion-poster, video) { object-fit: cover; background: none; }
/* The project ends here: a 1px ink rule, as on the About page, sets Previous / Next apart from the last section. */
.p-more { padding-top: clamp(40px, 4.2vw, 60px); border-top: 1px solid var(--ink); }

/* Minimum independent targets; preserve text metrics and existing mobile circles. */
.site-header :is(.wordmark, .site-nav a, button), .drop-panel a,
.site-footer a {
  min-width: 24px; min-height: 24px;
}
.site-header .wordmark, .site-footer a { display: inline-flex; align-items: center; }
/* The error page keeps the shared frame, fonts and responsive footer. */
.not-found .frame { min-width: 0; overflow-wrap: anywhere; }
.not-found h1 { font-size: var(--fs-big); }
.not-found p { margin-top: 1.5em; }
