/* Home page only. Loaded after site.css. */

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

.hero {
    min-height: calc(100svh - var(--base-padding-y) * 4 - 2em);
    align-content: end;
}

/* Scoped with .site so it outranks the `.site h1` reset above */
.site .hero-lede {
    /* Placed rather than spanned: the character shares this row, and an
       auto-placed lede would be pushed past it into implicit columns */
    grid-column: 1 / span 12;
    grid-row: 1;
    font-size: clamp(1.6em, 4.2vw, 3.6em);
    font-weight: 700;
    line-height: 1.05;
    margin: 0 0 2em;
    max-width: 18em;
}

.hero-lede .line {
    display: block;
    overflow: hidden;
}

.hero-lede .line > span {
    display: block;
    transform: translateY(105%);
}

.site.is-ready .hero-lede .line > span {
    transform: translateY(0);
    transition: transform 1.05s cubic-bezier(0.16, 1, 0.3, 1);
}

.site.is-ready .hero-lede .line:nth-child(1) > span { transition-delay: 0.05s; }
.site.is-ready .hero-lede .line:nth-child(2) > span { transition-delay: 0.12s; }
.site.is-ready .hero-lede .line:nth-child(3) > span { transition-delay: 0.19s; }
.site.is-ready .hero-lede .line:nth-child(4) > span { transition-delay: 0.26s; }
.site.is-ready .hero-lede .line:nth-child(5) > span { transition-delay: 0.33s; }

/* The clickable character. The container is a zero-height grid cell pinned to
   the bottom of the lede's row; the canvas hangs off it upwards, absolute so it
   can never grow the row and shove the meta line down the page. Pointer events
   are off throughout — character.js resolves hits against the character's
   projected silhouette instead, so the empty space around it stays inert. */
.hero-character {
    grid-column: 7 / span 6;
    grid-row: 1;
    align-self: end;
    position: relative;
    height: 0;
    pointer-events: none;

    /* The container is zero-height, so percentages are useless inside it —
       the prompt positions itself against this instead */
    --stage-h: min(60svh, 32em);
}

.hero-character canvas {
    position: absolute;
    right: 0;
    bottom: 0;
    width: 100%;
    height: var(--stage-h);
    opacity: 0;
    transition: opacity 1.4s ease;
    /* character.js draws into a framebuffer a third of this size; the blow-up
       has to stay nearest-neighbour or the PSX pixels turn to mush */
    image-rendering: pixelated;
}

/* --------------------------------------------------------- Annoy prompt */

/* Bundled locally like Commit Mono, rather than pulled from Google — the page
   has to work opened straight off disk. Licence in fonts/ */
@font-face {
    font-family: just-me-again;
    src: url('fonts/just-me-again-down-here-latin-400-normal.woff2') format('woff2');
    font-weight: 400;
    font-style: normal;
    font-display: swap;
}

/* Sits to the character's left, at shoulder height, so the arrow — which
   curves down and to the right — lands on the torso.
   Scoped with .site so the font-size outranks the `.site p` reset above, the
   same reason .hero-lede is scoped. */
.site .hero-prompt {
    position: absolute;
    right: 60%;
    bottom: calc(var(--stage-h) * 0.52);
    margin: 0;
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: 0.1em;

    font-family: just-me-again, cursive;
    font-size: clamp(1.5em, 2.4vw, 2.3em);
    line-height: 1;
    text-transform: none;      /* .site sets uppercase on everything */
    white-space: nowrap;
    color: var(--color-primary);

    opacity: 0;
    transform: translateY(0.4em);
}

/* Only once there is something on screen to point at. character.js bails out
   under reduced motion, on coarse pointers and below 1024px, so in all three
   cases is-live never lands and the prompt correctly never shows. */
.hero-character.is-live .hero-prompt {
    opacity: 1;
    transform: translateY(0);
    transition: opacity 0.9s ease 0.6s, transform 0.9s cubic-bezier(0.16, 1, 0.3, 1) 0.6s;
}

/* Done its job */
.hero-character.is-poked .hero-prompt {
    opacity: 0;
    transform: translateY(-0.3em);
    transition: opacity 0.45s ease, transform 0.45s ease;
}

.hero-prompt-text {
    transform: rotate(-4deg);
}

.hero-prompt-arrow {
    width: 2.4em;
    height: auto;
    margin-right: -0.7em;
    animation: prompt-point 2.4s ease-in-out infinite;
}

/* A nudge along the arrow's own axis rather than a bob, so it reads as
   pointing at the character instead of just floating */
@keyframes prompt-point {
    0%, 100% { transform: translate(0, 0) rotate(0deg); }
    50%      { transform: translate(0.3em, 0.16em) rotate(4deg); }
}

@media (prefers-reduced-motion: reduce) {
    .hero-prompt-arrow { animation: none; }
}

.hero-character canvas.is-loaded {
    opacity: 1;
}

/* Below 1024px the section grid drops to four columns and the hero has no
   spare width; character.js bails out at the same breakpoint. */
@media only screen and (max-width: 1024px) {
    .hero-character { display: none; }
}

/* The only hover affordance available — the custom cursor never shows its
   label pill on this page, and the canvas cannot take a CSS cursor while it
   is passing pointer events through. */
.custom-cursor.is-over-character {
    width: 44px;
    height: 44px;
}

.hero-meta {
    grid-column: span 12 / span 12;
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 1em;
    padding-top: 1em;
    border-top: 1px solid var(--hairline);
    opacity: 0;
    transform: translateY(0.7em);
}

.site.is-ready .hero-meta {
    opacity: 1;
    transform: translateY(0);
    transition: opacity 0.9s ease 0.5s, transform 0.9s cubic-bezier(0.16, 1, 0.3, 1) 0.5s;
}

.meta-item {
    margin: 0;
    display: flex;
    flex-direction: column;
    gap: 0.3em;
}

.meta-key { color: var(--color-faint); }

/* The second line under Based. Quieter than the value above it — it is an
   aside, not another field, and giving it the same weight would read as one. */
.meta-aside { color: var(--color-faint); }

@media only screen and (max-width: 1024px) {
    .hero-lede,
    .hero-meta { grid-column: span 4 / span 4; }
}

/* --------------------------------------------------------------- Work index */

/* A list of rows rather than a card grid, with a preview panel that trails the
   cursor. Sizing/colour/grid come from the shared token system in site.css. */

.work-head {
    grid-column: span 12 / span 12;
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 1em;
    margin-bottom: 2em;
}

.work-label,
.work-count {
    margin: 0;
}

.work-count {
    color: var(--color-faint);
}

.work-list {
    grid-column: span 12 / span 12;
    display: flex;
    flex-direction: column;
}

.work-row {
    position: relative;
    display: grid;
    grid-template-columns: repeat(12, minmax(0, 1fr));
    gap: 1em;
    align-items: baseline;
    padding: 1.2em 0;
    border-top: 1px solid var(--hairline);
    text-decoration: none;
    color: var(--color-primary);
    cursor: none;
}

.work-row:last-child {
    border-bottom: 1px solid var(--hairline);
}

/* Wash that wipes up behind the row, bleeding past the page gutter */
.work-row::before {
    content: '';
    position: absolute;
    left: calc(var(--base-padding-x) * -1);
    right: calc(var(--base-padding-x) * -1);
    top: 0;
    bottom: 0;
    background: var(--color-primary);
    opacity: 0.05;
    transform: scaleY(0);
    transform-origin: bottom center;
    transition: transform 0.45s cubic-bezier(0.65, 0, 0.35, 1);
    pointer-events: none;
}

.work-row:hover::before {
    transform: scaleY(1);
}

.row-title { grid-column: span 5 / span 5; }
.row-role  { grid-column: span 5 / span 5; }
.row-year  { grid-column: span 2 / span 2; text-align: right; }

.row-role,
.row-year {
    color: var(--color-dim);
    transition: color 0.35s ease;
}

.row-year {
    font-variant-numeric: tabular-nums;
}

/* The one place this section steps outside the reference's body-size type —
   the index needs a hierarchy the card grid got from its thumbnails */
.row-title {
    font-size: clamp(1.3em, 2.4vw, 2em);
    font-weight: 700;
    line-height: 1.05;
    transition: transform 0.5s cubic-bezier(0.16, 1, 0.3, 1);
}

.work-row:hover .row-title {
    transform: translateX(0.6em);
}

.work-row:hover .row-role,
.work-row:hover .row-year {
    color: var(--color-primary);
}

/* Floating preview — positioned by home.js, never intercepts pointer events */
.work-preview {
    position: fixed;
    top: 0;
    left: 0;
    width: 18em;
    aspect-ratio: 16 / 9;
    pointer-events: none;
    z-index: 50;
    opacity: 0;
    transform: translate(-50%, -50%) scale(0.94);
    transition: opacity 0.4s ease, transform 0.6s cubic-bezier(0.16, 1, 0.3, 1);
    overflow: hidden;
    background: rgba(255, 255, 227, 0.06);
}

.work-preview.is-active {
    opacity: 1;
    transform: translate(-50%, -50%) scale(1);
}

.preview-item {
    position: absolute;
    inset: 0;
    opacity: 0;
    transition: opacity 0.35s ease;
}

.preview-item.is-showing {
    opacity: 1;
}

.preview-item img {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}

/* The tiger head sits over the still and fades in once the model has loaded.
   model-viewer renders transparent, so the panel background shows through
   rather than a box around the model. */
.preview-model {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    display: block;
    opacity: 0;
    transition: opacity 0.55s ease;
    background: transparent;
    pointer-events: none;
    --poster-color: transparent;
    --progress-bar-color: transparent;
    --progress-mask: transparent;
}

.preview-model.is-loaded {
    opacity: 1;
    /* Drift, so it reads as floating rather than pinned to the panel */
    animation: model-float 4.5s ease-in-out infinite;
}

@keyframes model-float {
    0%, 100% { transform: translateY(-2.5%); }
    50%      { transform: translateY(2.5%); }
}

/* Once the model is up the still would only show through its transparent
   corners, so drop it away */
.preview-item:has(.preview-model.is-loaded) img {
    opacity: 0;
    transition: opacity 0.55s ease;
}

/* The panel's tinted plate and its clipping exist to frame a photo. The model
   renders transparent, so the plate reads as a box drawn around it and the clip
   cuts the float off at the edge. Drop both while the model is the thing shown. */
.work-preview:has(.preview-item.is-showing .preview-model.is-loaded) {
    background: transparent;
    overflow: visible;
}

@media only screen and (max-width: 1024px) {
    .work-head,
    .work-list { grid-column: span 4 / span 4; }

    /* Role and year drop beneath the title rather than squeezing */
    .work-row {
        grid-template-columns: repeat(4, minmax(0, 1fr));
        row-gap: 0.4em;
    }

    .row-title { grid-column: span 4 / span 4; }
    .row-role  { grid-column: 1 / span 4; }
    .row-year  { grid-column: 1 / span 4; text-align: left; }
}

/* ---------------------------------------------------------------- Statement */

.statement-body {
    grid-column: span 7 / span 7;
    line-height: 1.4;
    margin: 0;
}

.statement-link {
    grid-column: span 12 / span 12;
    color: var(--color-primary);
    text-decoration: none;
    cursor: none;
    display: inline-flex;
    flex-direction: column;
    gap: 0.4em;
    width: fit-content;
    margin-top: 2em;
}

.link-rule {
    display: block;
    height: 1px;
    width: 100%;
    background: var(--color-primary);
    transform: scaleX(1);
    transform-origin: right center;
    transition: transform 0.45s cubic-bezier(0.65, 0, 0.35, 1);
}

.statement-link:hover .link-rule { transform: scaleX(0); }

@media only screen and (max-width: 1024px) {
    .statement-body,
    .statement-link { grid-column: span 4 / span 4; }
}

