/* Homepage interaction pass — September 23. Loaded last; overrides the earlier home layers. */

/* One grid. Content hangs from the same 40px edge as the header (20px on phones), so HARRY WU, the
   introduction, every cover and the footer share one left and right edge up to a 1520px window. Beyond that
   the content stays 1440px wide and centred, while the header keeps its 40px edge on every page, so it never
   moves when you go from the homepage to a project. 20px gutters on a 12-column rhythm. */
.page-width{width:min(1440px,calc(100% - 2 * var(--pf-edge,40px)))}
.project-grid{column-gap:20px}
.project-grid>.project:nth-child(n) .project-cover{aspect-ratio:16 / 9}

/* The header marks where the reader is with a thin rule under WORK or EXPERIMENTS. */
.pf-header .pf-links>a[href*="#"]{background:linear-gradient(currentColor,currentColor) 0 calc(100% - 3px) / 0 1.5px no-repeat;transition:background-size .35s cubic-bezier(.2,.8,.2,1),color .2s}
.pf-header .pf-links>a[aria-current=location]{background-size:100% 1.5px}

/* Experiments / Archives sits in from the page edges, giving the gallery more air: one grid column on smaller
   desktops, and a seventh of the content width on each side once the cards can stay a comfortable size. */
@media(min-width:1001px){.experiments>.page-width{padding-inline:calc((100% - 220px) / 12 + 20px)}}
@media(min-width:1280px){.experiments>.page-width{padding-inline:calc(min(1440px,100vw - 80px) * .14)}}

/* Resume and Email read as the page's two actions. */
.intro-links{font-size:17px;font-weight:700;gap:30px;margin-top:28px}
.intro-links a{color:#171717}
@media(max-width:640px){.intro-links{font-size:15px;gap:26px;margin-top:22px}}

/* Project captions: discipline tags stay on one line and sit the same way on every card. */
@media(min-width:1001px){.project-grid>.project:nth-child(n) .project-meta{display:flex;justify-content:space-between;gap:24px}.project-grid>.project:nth-child(n) .project-meta>span{display:block;white-space:nowrap;max-width:none;text-align:right;margin-top:0;padding-top:4px}}

/* The skip link was sitting under the glass header when focused; it now appears above it. */
.skip-link{z-index:200;border-radius:4px;box-shadow:0 6px 20px #0002}

/* LiveLarge: a sharp still of the assistant comparing three models (the recordings are too low in frame rate to loop). */
.project-cover.cover-adu>img{object-fit:cover;object-position:50% 100%}

/* ZNTR plays its web-experience recording while in view. */
.cover-motion{position:absolute;inset:0;width:100%;height:100%;border:0;object-fit:cover;pointer-events:none;opacity:0;transition:opacity .45s,transform .45s cubic-bezier(.2,.75,.2,1)}
.is-moving>.cover-motion{opacity:1}
@media(hover:hover) and (pointer:fine){.project a:hover .project-cover>video.cover-motion{transform:scale(1.012)}}

/* Inline photographs: the enlargement takes the photo's own proportions, so the rounded corners sit on the picture. */
.intro-sample .sample-enlarged{width:var(--sample-w,240px);border-radius:8px;overflow:hidden}
.intro-sample .sample-enlarged img{width:100%;height:auto;max-height:none;object-fit:cover;border-radius:0}
.intro-sample.is-end{margin-right:1px}

/* The cloud has a face: calm at rest, crying while pressed or raining. It floats level with the headline,
   above the text its rain falls on. */
@media(min-width:641px){.home-intro>.cloud{align-self:start}}
.cloud .cloud-mark{position:relative;display:block;top:auto;left:auto;right:auto;opacity:1;width:164px;margin:auto;pointer-events:none;user-select:none}
.cloud .cloud-mark img{display:block;width:100%;height:auto;margin:0;filter:none}
/* The crying face fades in over the calm one, which stays solid underneath, so the cloud never turns see-through
   mid-change. The fade follows the press and the release directly. */
.cloud-mark .cloud-rain{position:absolute;inset:0;opacity:0;transition:opacity .28s cubic-bezier(.4,0,.2,1)}
.cloud.is-pressed .cloud-rain,.cloud.is-raining .cloud-rain{opacity:1;transition-duration:.2s}
@media(max-width:1000px){.cloud .cloud-mark{width:150px}}
@media(max-width:640px){.cloud .cloud-mark{width:72px}}

/* Rain falls over the introduction text; the cloud sits above the weather it makes and darkens while it rains. */
.home-intro{position:relative}
.intro-rain{position:absolute;inset:0;width:100%;height:100%;pointer-events:none;z-index:0}
.cloud{z-index:2;transition:filter .45s cubic-bezier(.4,0,.2,1)}
.cloud.is-raining{filter:brightness(.8) contrast(1.08)}
/* After the cloud has rained once, a mouse resting on it finds a small label; hidden again while it rains. */
.cloud span.cloud-hint{display:block;position:absolute;top:100%;left:0;right:0;margin-top:6px;text-align:center;font-size:12px;line-height:1.3;letter-spacing:0;color:#777;white-space:nowrap;opacity:0;transform:translateY(-4px);transition:opacity .25s,transform .25s;pointer-events:none}
@media(hover:hover) and (pointer:fine){.cloud.can-hint:hover:not(.is-pressed,.is-raining) .cloud-hint{opacity:1;transform:none}}
@media(max-width:640px){.cloud span.cloud-hint{display:none}}

/* The viewer has no close icon: click outside (a Close label follows the pointer there) or press Esc.
   The button stays for keyboard and screen-reader users and only shows when it has keyboard focus. */
.dialog-close{position:absolute;top:12px;right:12px;z-index:4;width:auto;height:34px;margin:0;padding:0 12px;border-radius:4px;background:#f4f4f1;color:#101110;font-size:12px;line-height:34px;opacity:0;pointer-events:none;transform:none!important}
.dialog-close:focus-visible{opacity:1;pointer-events:auto;outline:none}
.experiment-dialog .close-hint{z-index:5}
.experiment-dialog.is-animating .dialog-shell{overflow:hidden}

/* The viewer takes each piece's own proportions; any remaining margin stays dark. */
.experiment-dialog{width:min(980px,calc(100% - 72px),max(560px,calc(min(66dvh,720px) * var(--media-ratio,1.7778))))}
.dialog-media{height:auto;aspect-ratio:var(--media-ratio,16 / 9);max-height:min(66dvh,720px)}
.dialog-media>img{background:transparent}
.video-play{place-items:end start;padding:18px}
.video-play span{background:#101110d9;backdrop-filter:blur(6px)}
@media(max-width:640px){.experiment-dialog{width:calc(100% - 28px)}.dialog-media{max-height:52dvh}.video-play{padding:12px}}

/* Experiments / Archives header: count and the Grid / Index switch. */
.lab-intro h2{display:flex;align-items:flex-start;gap:6px}
.lab-count{font-size:11px;line-height:1;color:#8b8b87;font-variant-numeric:tabular-nums;padding-top:2px}
.lab-controls{display:flex;align-items:center;gap:26px}
.view-switch{display:flex;gap:16px}
.view-switch button{border:0;background:none;padding:8px 0;font-size:12px;color:#8b8b87;transition:color .2s}
.view-switch button:hover,.view-switch button[aria-pressed=true]{color:#f4f4f1}
.view-switch button[aria-pressed=true]{text-decoration:underline;text-underline-offset:5px}
.view-switch button:focus-visible{outline:1px solid #bbb;outline-offset:3px}

/* Index view: newest first, the year marked once per group; on hover the other rows recede. */
.experiment-index{list-style:none;margin:0;padding:0;border-top:1px solid #ffffff21}
.experiment-index li{animation:index-in .45s cubic-bezier(.2,.8,.2,1) both;animation-delay:calc(var(--i) * 18ms)}
@keyframes index-in{from{opacity:0;transform:translateY(8px)}}
.index-row{display:grid;grid-template-columns:64px minmax(0,1fr) minmax(0,220px) 96px;align-items:baseline;gap:20px;width:100%;padding:16px 0;border:0;border-bottom:1px solid #ffffff21;background:none;color:#f4f4f1;text-align:left;transition:opacity .25s,padding .35s cubic-bezier(.2,.8,.2,1)}
.index-year{font-size:12px;color:#9a9a96;font-variant-numeric:tabular-nums}
.index-year.is-repeat{color:transparent}
.index-title{font-size:17px;line-height:1.3;letter-spacing:-.015em}
.index-type{font-size:12px;color:#9a9a96}
.index-page{font-size:11px;color:#77776f;text-align:right}
.index-thumb{display:none}
.index-thumb.is-mono{filter:grayscale(1)}
.index-row:focus-visible{outline:1px solid #bbb;outline-offset:-1px}
@media(hover:hover) and (pointer:fine){.experiment-index:hover .index-row{opacity:.36}.experiment-index .index-row:hover{opacity:1;padding-left:12px}}
@media(hover:none),(pointer:coarse){.index-row{grid-template-columns:56px 48px minmax(0,1fr) minmax(0,180px) 90px;align-items:center;padding:10px 0}.index-thumb{display:block;width:56px;height:40px;object-fit:cover;border-radius:4px;background:#1b1c1b}}
@media(max-width:640px){.index-row{grid-template-columns:56px minmax(0,1fr) auto;grid-template-areas:"thumb title title" "thumb type year";column-gap:14px;row-gap:3px;align-items:center;padding:10px 0}.index-thumb{display:block;grid-area:thumb;width:56px;height:40px;object-fit:cover;border-radius:4px;background:#1b1c1b}.index-title{grid-area:title;font-size:15px}.index-type{grid-area:type;font-size:11px}.index-year,.index-year.is-repeat{grid-area:year;font-size:11px;color:#77776f}.index-page{display:none}}

/* Pointer companions: the grid's name label and the index's floating image. */
.cursor-label{position:fixed;left:0;top:0;z-index:40;display:flex;align-items:baseline;gap:8px;padding:6px 9px 7px;border-radius:4px;background:#f4f4f1;color:#101110;font-size:12px;line-height:1.2;white-space:nowrap;pointer-events:none;opacity:0;transition:opacity .15s}
.cursor-label.is-on{opacity:1}
.cl-type{font-size:11px;color:#77776f}
.cl-type:empty{display:none}
.index-peek{position:fixed;left:0;top:0;z-index:40;width:300px;pointer-events:none;opacity:0;transition:opacity .2s}
.index-peek.is-on{opacity:1}
.index-peek img{display:block;width:100%;height:auto;max-height:230px;object-fit:cover;border-radius:6px;background:#1b1c1b;box-shadow:0 18px 40px #0007;transform:scale(.9);transition:transform .35s cubic-bezier(.2,.8,.2,1)}
.index-peek.is-on img{transform:scale(1)}
.index-peek.is-mono img{filter:grayscale(1)}
@media(hover:none),(pointer:coarse){.cursor-label,.index-peek{display:none}}

@media(prefers-reduced-motion:reduce){.experiment-index li{animation:none}.index-row,.index-peek img,.cloud,.cloud .cloud-mark img,.cover-motion,.pf-header .pf-links>a{transition:none}.index-peek img{transform:none}}

/* Experiments / Archives rises like a dark sheet over the white page: its rounded top edge arrives slightly narrower
   and lower, and settles to full width as the section reaches the top of the screen. The motion is bound to scroll
   position and only moves the sheet (transform), so the browser composites it without repainting; browsers without
   scroll-driven animation simply show the sheet in place. */
.experiments{border-radius:28px 28px 0 0}
@media(max-width:640px){.experiments{border-radius:18px 18px 0 0}}
@supports (animation-timeline:view()){
  @media(prefers-reduced-motion:no-preference){
    .experiments{transform-origin:50% 0;animation:sheet-rise linear both;animation-timeline:view();animation-range:entry 0% contain 0%}
    @keyframes sheet-rise{from{transform:translateY(36px) scale(.965)}to{transform:none}}
  }
}
