/* Consumer layer over catppuccin-neu. Unlayered, so it wins over the
   package's cascade layers. Mobile first: the base rules are the phone
   page, and min-width queries add the desktop one on top.

   On a phone the page is one column: the intro with a single row of
   actions, then Experience and Projects as accordion lists. Every project
   is a row with a thumbnail. From 761px the projects with a scene turn
   into image cards, and from 1000px the intro becomes a column that stays
   put beside the content. */

.app-shell { --page-gutter: clamp(16px, 3vw, var(--space-6)); }
main { --page-pad: 32px 0 56px; }
/* No rubber-band past the ends, so the avocado lands where the page stops. */
html { overscroll-behavior: none; }

/* ── Intro ────────────────────────────────────────────────────────────────
   Phone: the name and tagline, then Resume and the four icon buttons on
   one row, Resume taking what the icons leave. */
.intro {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  column-gap: 10px;
}
.intro > :not(.btn-primary, .hero-actions) { grid-column: 1 / -1; }
.intro h1 { font-size: clamp(32px, 3.4vw, 36px); }
.intro .lede { margin: 12px 0 24px; font-size: var(--cn-type-lede-size); line-height: 1.5; }
.intro > .btn-primary {
  min-height: var(--control-h);
  padding-inline: 16px;
  justify-content: center;
}
.intro .hero-actions { margin-top: 0; gap: 10px; flex-wrap: nowrap; }

/* Hero actions and project links compose the package's .btn.is-icon with
   .cn-tone-mauve, .cn-raised-soft and .cn-pressable. The buttons read a step
   brighter than the recipe's overlay-1, and the hover hairline stays off: the
   raised shadow already gives them an edge. */
:is(.hero-actions, .project-links) .btn.is-icon { color: var(--subtext-0); }
:is(.hero-actions, .project-links) .btn.is-icon:hover { border-color: transparent; }

/* ── Sections ───────────────────────────────────────────────────────────── */
.split-content { margin-top: 40px; }
.split-content > section + section { margin-top: 16px; }

/* The stack sheds its side padding and pulls out by 8px, so row titles land
   near the column edge while the open row's wash keeps 8px from the screen
   edge. */
.accordion-stack {
  padding-inline: 0;
  margin-inline: -8px;
}

/* Each accordion holds one sentence, so the rows and the copy get more type
   than the package's 13px label and 14px body voice, with the row title a
   clear step above the copy. */
.accordion > label { font-size: 16px; }
.accordion .fold p { font-size: 14px; line-height: 1.6; }

/* Under 760px the recipe wraps the label so its meta drops under the title.
   A title longer than the row then pushed the chevron onto a line of its
   own, so the title shrinks to share its line with the chevron. */
.accordion > label > p { flex: 1 1 0; min-width: 0; }

/* ── Projects ─────────────────────────────────────────────────────────────
   Phone: one list. Each row is the project's favicon beside the name and
   its stack, with the chevron on the right. The favicons are the apps' own
   mauve tiles, still and sized to the two lines of text, since a scene
   shrunk to a thumbnail is unreadable. A project without a card has no
   icon, so its name and stack start at the row's edge. The cards and the
   "More projects" label are for wider screens. */
.project-grid,
.more-projects { display: none; }

.project-row > label {
  display: grid;
  grid-template-columns: 40px minmax(0, 1fr) 15px;
  column-gap: 12px;
  row-gap: 2px;
}
.project-row > label > .project-thumb { grid-row: 1 / 3; }
.project-row > label > p { grid-column: 2; grid-row: 1; align-self: end; }
.project-row > label > .cn-meta { grid-column: 2; grid-row: 2; align-self: start; }
.project-row > label:not(:has(.project-thumb)) > :is(p, .cn-meta) { grid-column: 1 / 3; }
.project-row > label::after { grid-column: 3; grid-row: 1 / 3; }

.project-thumb {
  display: block;
  width: 40px;
  height: 40px;
  object-fit: contain;
}

/* ── Scroll cue ───────────────────────────────────────────────────────────
   Phone "more below" cue: a small overlay-0 chevron pinned to the bottom of
   the screen that drifts 3px and fades out over the first 120px of scroll.
   The fade needs scroll timelines, so browsers without them never show it. */
.scroll-cue { display: none; }
@keyframes scroll-cue-out { to { opacity: 0; visibility: hidden; } }
@keyframes scroll-cue-bob { 50% { transform: translateY(3px); } }
@supports (animation-timeline: scroll()) {
  .scroll-cue {
    display: block;
    position: fixed;
    left: 50%;
    bottom: max(14px, env(safe-area-inset-bottom));
    z-index: 60;
    translate: -50% 0;
    color: var(--overlay-0);
    pointer-events: none;
    animation: scroll-cue-out linear both;
    animation-timeline: scroll(root);
    animation-range: 0 120px;
  }
  .scroll-cue svg {
    display: block;
    width: 18px;
    height: 18px;
    animation: scroll-cue-bob 2.4s ease-in-out infinite;
  }
}
@media (prefers-reduced-motion: reduce) {
  .scroll-cue svg { animation: none; }
}

/* ── 521px and up: the type steps up a size ─────────────────────────────── */
@media (min-width: 521px) {
  main { --page-pad: 72px 0 72px; }
  .accordion > label { font-size: 18px; }
  .accordion .fold p { font-size: 15px; }
}

/* ── 761px and up: cards ──────────────────────────────────────────────────
   The intro goes back to a block: Resume on its own line at its label's
   width, the icons under it. The stack pulls out by the label's own 12px,
   capped 4px short of the screen edge. Projects with a scene become image
   cards, and the rest sit under "More projects" as plain accordion rows. */
@media (min-width: 761px) {
  .scroll-cue { display: none; }

  .intro { display: block; }
  .intro .lede { margin: var(--space-5) 0; }
  .intro > .btn-primary {
    min-height: calc(var(--control-h) + var(--space-3));
    padding-inline: var(--space-5);
  }
  .intro .hero-actions { margin-top: 24px; gap: 16px; flex-wrap: wrap; }

  .split-content { margin-top: 0; }
  .split-content > section + section { margin-top: 24px; }
  .accordion-stack { margin-inline: calc(-1 * min(12px, var(--page-gutter) - 4px)); }
  .accordion > label > p { flex: initial; min-width: auto; }

  .project-grid { display: grid; margin-top: 24px; }
  .more-projects { display: block; margin: 48px 0 0; }
  .project-row.is-hosted,
  .project-thumb { display: none; }
  .project-row > label { display: flex; }
  .project-row > label > :is(p, .cn-meta) { align-self: auto; }
}

/* Project art. Each card shows a still of its scene (art/<name>.html),
   rendered by scripts/art_stills.py. Hovering a card loads the scene in a
   frame over the still and plays it (the script after the accordions); the
   frame shows once it holds the still's own first frame, so the swap
   doesn't flash. Neither takes the pointer, so clicks reach the card's
   link. */
.project-art {
  position: relative;
  overflow: hidden;
  border: 1px solid var(--surface-0);
  background: var(--base);
}
.project-art > * {
  display: block;
  width: 100%;
  height: 100%;
  border: 0;
  object-fit: cover;
  pointer-events: none;
}
.project-art > iframe { position: absolute; inset: 0; opacity: 0; transition: opacity .15s; }
.project-art > iframe.is-ready { opacity: 1; }

/* Each card is the package's .accent-card with its art on top, at the
   control radius, with the aspect ratio pinned so the grid does not reflow
   as the frame loads. */
.project-art {
  width: 100%;
  aspect-ratio: 16 / 10;
  border-radius: var(--cn-radius-control);
}

/* A click anywhere on a card opens its site, or its repo when there is no
   site. The card holds its own link buttons, so it can't sit inside an
   <a>; the name's link stretches its ::after over the card instead, and
   the buttons stack above it. The recipe only lifts and presses a card
   inside a link (a > .accent-card), so the lift, press and focus ring are
   restated here off the stretched link.
   Package gap: the recipe could key them off a stretched link too. */
.project-card { position: relative; }
.project-card .card-link { color: inherit; text-decoration: none; }
.project-card .card-link::after { content: ""; position: absolute; inset: 0; }
.project-card .card-link:focus-visible { outline: none; }
.project-card .project-links { position: relative; z-index: 1; }
.project-card:has(.card-link:hover) strong { color: var(--mauve); }
.project-card:has(.card-link:hover) {
  transform: translate(-1px, -1px);
  box-shadow: calc(var(--hard-offset) + 1px) calc(var(--hard-offset) + 1px) 0 var(--plate), var(--hard-extra);
}
.project-card:has(.card-link:active) {
  transform: translate(calc(var(--hard-offset) / 2), calc(var(--hard-offset) / 2));
  box-shadow: calc(var(--hard-offset) / 2) calc(var(--hard-offset) / 2) 0 var(--plate), var(--hard-extra);
  transition-timing-function: var(--ease-out);
  transition-duration: var(--t-press);
}
.project-card:has(.card-link:focus-visible) { outline: 2px solid var(--mauve); outline-offset: 3px; }

/* ── 1000px and up: side intro ────────────────────────────────────────────
   The intro takes 300 to 360px and the content keeps at least 520px, so
   the row fits at 1000px even with a classic scrollbar taking 17px. Side
   by side, the intro stays put while the content scrolls. Below 1000px the
   row wraps and the intro sits on top. */
.split {
  position: relative;
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  column-gap: 72px;
}
.split > .intro { flex: 1 1 100%; min-width: 0; }
.split > .split-content { flex: 1 1 100%; min-width: 0; }
@media (min-width: 761px) {
  .split { row-gap: 72px; }
}
@media (min-width: 1000px) {
  .split > .intro { flex: 1 1 300px; max-width: 360px; position: sticky; top: 72px; }
  .split > .split-content { flex: 999 1 520px; }
}

/* ── Avocado ──────────────────────────────────────────────────────────────
   A sprite sheet of mauve pixel-art frames: two waving, two falling (the
   same still), and two lying flat with the stars over its head in two
   places. The waving arm and the stars alternate slowly; the fall is a
   still pose. It sits in a rail down the sidebar column and sticks under
   the icons; the scroll script drops it in two-cell jumps and sets the
   pose. The rail ends on top of the computer on the footer rule, so sticky
   stops it there and it rides up with the footer.

   Sizes are in cells; the script picks the cell size (--avo-cell) that
   fits the column and the space above the footer. Each frame is 62x53 with
   the body centred, the waving head starts 6 rows down, and the lying
   pose's lowest lit row is 9 above the bottom edge. The computer is 40x40
   with an unlit top row, so the rail ends 30 cells above the footer. */
.avo-perch { margin-top: 40px; }
.avo-rail {
  position: absolute;
  top: 0;
  left: calc(var(--avo-centre, 150px) - 31 * var(--avo-cell, 4px));
  width: calc(62 * var(--avo-cell, 4px));
  bottom: calc(30 * var(--avo-cell, 4px) - var(--avo-floor, 72px));
  pointer-events: none;
}
.avo, .avo-computer {
  image-rendering: pixelated;
  background-repeat: no-repeat;
  opacity: 0.8;
}
.avo {
  --frame: 0;
  position: sticky;
  top: calc(var(--avo-perch, 372px) - 6 * var(--avo-cell, 4px) + var(--avo-drop, 0px));
  width: calc(62 * var(--avo-cell, 4px));
  height: calc(53 * var(--avo-cell, 4px));
  background-image: url("../img/avo.png?v=2");
  background-size: calc(6 * 62 * var(--avo-cell, 4px)) 100%;
  background-position-x: calc(-1 * var(--frame) * 62 * var(--avo-cell, 4px));
  pointer-events: auto;
  animation: avo-wave 1.3s steps(1, end) infinite;
}
:root[data-avo="fall"] .avo { animation: none; --frame: 2; }
:root[data-avo="flat"] .avo { animation: avo-stars 1.2s steps(1, end) infinite; }
/* Once it lands it sits on the computer, so an accordion opening or closing
   above carries it along with the footer. */
:root[data-avo="flat"] .avo { position: absolute; top: auto; bottom: 0; }
@keyframes avo-wave {
  0% { background-position-x: 0; }
  50%, 100% { background-position-x: calc(-1 * 62 * var(--avo-cell, 4px)); }
}
@keyframes avo-stars {
  0% { background-position-x: calc(-4 * 62 * var(--avo-cell, 4px)); }
  50%, 100% { background-position-x: calc(-5 * 62 * var(--avo-cell, 4px)); }
}

/* The computer: a heart while it falls, "Help" once it lands. */
.avo-computer {
  position: absolute;
  left: calc(var(--avo-centre, 150px) - 20 * var(--avo-cell, 4px));
  bottom: calc(-1 * var(--avo-floor, 72px));
  width: calc(40 * var(--avo-cell, 4px));
  height: calc(40 * var(--avo-cell, 4px));
  background-image: url("../img/avo-computer.png?v=3");
  background-size: 200% 100%;
}
:root[data-avo="flat"] .avo-computer { background-position-x: 100%; }

@media (max-width: 999px) { .avo-perch, .avo-rail, .avo-computer { display: none; } }
@media (prefers-reduced-motion: reduce) {
  .avo { animation: none; }
  .avo-computer { display: none; }
}
