/* ═══════════════════════════════════════════════════════════════════════════
   NeoBot's companion pet — `.pet`
   ═══════════════════════════════════════════════════════════════════════════

   The sprite element itself is almost styleless on purpose: js/pet.js writes
   width, height, background-size and background-position in px, because those
   numbers come from whichever pet is active and the stylesheet is not allowed
   to know which one that is. Everything here is either true of every pet
   (it never eats a click, it never gets dragged) or is a PLACEMENT.

   Placements live here rather than in the six stylesheets that host a pet, so
   "where can the pet appear" is one list you can read in one place — and so
   removing a mount point is deleting a rule next to its siblings instead of
   hunting for it. Each is named for the surface: `.pet-hero`, `.pet-sso`, …

   Standing rules this obeys (2025 design session, still in force): no glow, no
   gradient, no glassmorphism, no hover lift on something informational. The pet
   is artwork sitting ON the panel — it never gets a panel of its own, a border,
   or a backdrop. Entrances use `animation-fill-mode: backwards`, never `both`:
   with `both` the resting state is the last keyframe, so a throttled tab or a
   dropped rule parks the pet at `opacity: 0`.
   ═══════════════════════════════════════════════════════════════════════════ */

.pet {
    display: block;
    flex: none;
    background-repeat: no-repeat;
    background-position: 0 0;
    /* Default for a smooth-edged pet, which is a thing a future pet may be. */
    image-rendering: auto;
    /* A decoration that can swallow a click is a decoration that can break a
       form. Only a mount that opted into `poke` turns this back on, and pet.js
       does it inline on that one element. */
    pointer-events: none;
    user-select: none;
    -webkit-user-drag: none;
}

/* ⚠️ PIXEL ART. Nibi is drawn at 48x52 and scaled by whole integers, so every
   drawn pixel must land on an exact block of device pixels — smooth sampling
   would average the twenty-colour palette into hundreds and turn a hard visor
   edge into a grey smear. `crisp-edges` is the older spelling and stays as the
   fallback for engines that never took `pixelated`.

   This class is set by pet.js from the REGISTRY's `pixelated` flag, not by a
   selector on the pet's id — it is a property of the artwork, and the next pet
   may not share it. */
/* Order matters: `crisp-edges` is the older spelling and is left first so an
   engine that only knows that one still gets nearest-neighbour, while anything
   modern takes `pixelated` — the later declaration it also understands. */
.pet.is-pixel {
    image-rendering: crisp-edges;
    image-rendering: pixelated;
}

/* ── The click target ───────────────────────────────────────────────────────
   Clicking the pet plays a reaction, and that is the only thing it ever does
   to the page. Added by pet.js when a mount opts into `poke`, so hit-testing
   is off everywhere else by default.

   ⚠️ There is no `cursor: pointer`. A pointer cursor is a promise that
   something will happen when you click, and what happens here is that a cat
   looks startled — a promise worth keeping is one the user can act on. The
   poke is a thing you find, not a control you are offered.

   Note the two switches below. A sprite hit-tests on its whole rectangle,
   including the transparent margin around the artwork, so anywhere the pet
   overlaps something the user needs, poke has to lose. */
.pet.is-pokeable {
    pointer-events: auto;
    -webkit-tap-highlight-color: transparent;
}

/* Inside a picker the pet IS the control's label — a click there must select
   the option, not poke the cat that is standing on the radio button. */
.set-pet-mini .pet.is-pokeable,
.set-swatch .pet.is-pokeable { pointer-events: none; }

/* The host is whatever element the caller handed to mount(). It only ever gets
   the `.has-pet` marker — never layout — because a host is usually an element
   that already had a job. Placement classes below go on the SPRITE, passed as
   `className` to mount(). */

/* A declared host with nothing in it takes no space. The pet can be switched
   off, and the home hero is a flex row with a 2rem gap — an empty host there
   would hold a gap open around a cat that is not coming. This does NOT hide
   the host while the sheet loads: the sprite exists by then, sized from the
   registry and merely invisible, so the layout is settled before the image
   lands. See render() in js/pet.js. */
[data-pet]:not(.has-pet) { display: none; }

/* ⚠️ There was a head-crop mechanism here (`is-face`: the host became a
   window and the sprite was offset inside it) so the assistant surfaces could
   fit the pet into a 30-38px icon tile. It is gone, and it should not come
   back: Nibi has no neck, so every horizontal cut lands mid-body and reads as a
   decapitated cat. pet-catalog.js carries the row-by-row silhouette that proves
   it. The surfaces that can hold 48x52 now show the whole animal; the one that
   cannot — a 30px button in a 50px navbar — kept its line icon. */

/* ── Hosts that swap a fallback for the pet ─────────────────────────────────
   The assistant surfaces (the Architect's mark, Ask Neo, the Explore setup
   card) had a line-icon glyph before the pet existed. That glyph STAYS in the
   markup and the pet hides it — so switching the companion off in Settings
   returns each of them to its icon instead of leaving a hole where an
   assistant's face used to be. The preference is respected and nothing breaks;
   `clear()` drops `.has-pet` and the icon is simply visible again. */
.pet-swap:not(.has-pet) { display: inline-flex; }
.pet-swap.has-pet > svg { display: none; }

/* One entrance, used by every placement that appears after its surface does.
   Opacity and a few px of lift — the pet arrives, it does not perform. */
@keyframes petArrive {
    from { opacity: 0; transform: translateY(6px); }
    to { opacity: 1; transform: none; }
}

/* ⚠️ A PIXEL pet fades and does not travel. Tweening translateY puts the sprite
   on fractional offsets for the length of the entrance, and a nearest-neighbour
   image sampled off-grid shimmers — the one artefact this whole asset exists to
   avoid, played on the pet's first impression. */
@keyframes petArriveFade {
    from { opacity: 0; }
    to { opacity: 1; }
}

.pet.pet-arrive {
    animation: petArrive 320ms cubic-bezier(.22, .61, .36, 1) backwards;
}

.pet.is-pixel.pet-arrive {
    animation: petArriveFade 260ms cubic-bezier(.22, .61, .36, 1) backwards;
}

/* ── Placements ─────────────────────────────────────────────────────────── */

/* Home hero — beside the greeting on the server-selection page. Sits on the
   text baseline block rather than in the stats row, so the first thing on the
   page is "hello <name>" with something alive next to it.

   The negative bottom margin closes the sprite's own floor gap: Nibi's feet end
   at y49 of a 52px cell, so at 2x there are 4px of transparent cell below them.
   Re-measure this if the art changes — it is a fact about the drawing, not a
   nudge until it looked right. */
.pet-hero {
    align-self: flex-end;
    margin: 0 0 -4px 4px;
}

/* Server-switch overlay — under the progress ring while a server loads.
   ⚠️ No opacity here. The sheet's alpha is binary by design and the build
   fails if it is not; painting the element at 90% invents 230 partial alpha
   values at render time and undoes that on the one surface where the pet is
   biggest. If it needs to sit back, that is a colour decision for the artist. */
.pet-sso {
    margin-top: 14px;
}

/* Feature page empty state — inside the preview slot, which is otherwise a
   labelled placeholder. The pet is what makes an unconfigured page look
   unstarted rather than broken. */
.pet-fst {
    margin-bottom: 10px;
}

/* Settings → Appearance — the picker's own live previews. Each swatch shows
   the pet it would select, at the one size the picker uses. */
.pet-swatch {
    margin: 2px auto 0;
}

/* The assistant surfaces. An avatar, not a companion: it does not follow the
   cursor and it is not pokeable, because these sit inside a band you are
   reading and a button you are aiming at. It still blinks, and Ask Neo's face
   changes with what Neo is doing. */
.pet-avatar { display: block; }

/* ── Reduced motion ─────────────────────────────────────────────────────────
   js/pet.js already parks the sprite on its `still` frame (it paints frames
   itself, so body.reduce-motion's blanket animation override never reaches
   it). This only has to stop the entrance. */
@media (prefers-reduced-motion: reduce) {
    .pet.pet-arrive { animation: none; }
}

body.reduce-motion .pet.pet-arrive,
:root.perf-reduced-motion .pet.pet-arrive { animation: none; }

/* ── Small screens ──────────────────────────────────────────────────────────
   The hero pet is the one placement that competes for width: below the tablet
   breakpoint the greeting wraps to two lines and the stats row goes full
   width, so the pet is dropped rather than shrunk to a smudge. Everything else
   is inside a box that is already centred and stays. */
/* ⚠️ Hide the HOST, not the sprite. `#heroPetHost` is a flex item in a row with
   `gap: 2rem`; hiding only the sprite leaves a zero-width item behind and the
   gap is applied on BOTH sides of it, so the band between the greeting and the
   stat tiles silently doubles to 64px of nothing. 768 to match the breakpoint
   dashboard.css stacks the hero at. */
@media (max-width: 768px) {
    #heroPetHost { display: none; }
}
