/* Avatar builder + "Currently Wearing" shelf.
 *
 * LIGHT THEME IS THE DEFAULT, dark is an override under .av-dark. Not the other
 * way round: the apex renders light by default, and a page whose variables only
 * exist for dark chrome is unreadable there — that is exactly how the
 * leaderboard ended up broken. Every colour below resolves in both modes.
 */

.av-page {
    --av-bg: #faf8f3;
    --av-surface: #ffffff;
    --av-border: #e5e0d2;
    --av-text: #1a1610;
    --av-muted: #6a6458;
    --av-accent: var(--primary, #d4af37);
    --av-locked: #a89e88;

    background: var(--av-bg);
    color: var(--av-text);
}

.av-page.av-dark {
    --av-bg: #0d0b08;
    --av-surface: #1a1610;
    --av-border: #332e25;
    --av-text: #f3f0e8;
    --av-muted: #a89e88;
    --av-locked: #56503f;
}

.av-wrap {
    max-width: 1100px;
    margin: 0 auto;
    padding: 2rem 1.25rem 4rem;
}

.av-head {
    display: flex;
    flex-wrap: wrap;
    gap: 1rem;
    align-items: flex-start;
    justify-content: space-between;
    margin-bottom: 2rem;
}
.av-head h1 { font-size: clamp(1.6rem, 3vw, 2.2rem); margin: 0 0 .35rem; }
.av-sub { color: var(--av-muted); margin: 0; max-width: 56ch; }

.av-grid {
    display: grid;
    grid-template-columns: 280px 1fr;
    gap: 2rem;
    align-items: start;
}

/* ─── Preview ─────────────────────────────────────────────────────── */
.av-preview {
    position: sticky;
    top: 1.5rem;
    background: var(--av-surface);
    border: 1px solid var(--av-border);
    border-radius: 16px;
    padding: 1.25rem;
}
.av-stage {
    aspect-ratio: 1;
    display: grid;
    place-items: center;
    margin-bottom: 1rem;
}
/* Circular mask, matching how avatars are shown everywhere else on the site —
   the frames are rendered inset specifically so this crop does not clip them. */
.av-stage img {
    width: 100%;
    border-radius: 50%;
    image-rendering: pixelated;
    background: transparent;
}
.av-actions { display: grid; gap: .6rem; }
.av-status { margin: 0; min-height: 1.2em; font-size: .875rem; color: var(--av-muted); }
.av-note {
    margin: 1rem 0 0;
    font-size: .8125rem;
    color: var(--av-muted);
    line-height: 1.5;
}

/* ─── Buttons ─────────────────────────────────────────────────────── */
.av-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: 44px;
    padding: .6rem 1.2rem;
    border-radius: 999px;
    border: 1px solid transparent;
    font-weight: 600;
    cursor: pointer;
    text-decoration: none;
    transition: filter .2s, border-color .2s;
}
.av-btn-primary { background: var(--av-accent); color: #0d0b08; }
.av-btn-primary:hover { filter: brightness(1.08); }
.av-btn-primary:disabled { opacity: .6; cursor: default; }
.av-btn-ghost {
    background: transparent;
    border-color: var(--av-border);
    color: var(--av-text);
}
.av-btn-ghost:hover { border-color: var(--av-accent); color: var(--av-text); }

/* ─── Tabs + panels ───────────────────────────────────────────────── */
.av-tabs { display: flex; gap: .5rem; margin-bottom: 1.25rem; flex-wrap: wrap; }
.av-tab {
    min-height: 44px;
    padding: .5rem 1.1rem;
    border-radius: 999px;
    border: 1px solid var(--av-border);
    background: transparent;
    color: var(--av-muted);
    font-weight: 600;
    cursor: pointer;
}
.av-tab.is-active {
    border-color: var(--av-accent);
    color: var(--av-text);
    background: color-mix(in srgb, var(--av-accent) 12%, transparent);
}

.av-panel { display: none; }
.av-panel.is-active { display: block; }
.av-empty { color: var(--av-muted); padding: 1.5rem 0; }

.av-section-title { font-size: 1.1rem; margin: 2rem 0 1rem; }

/* ─── Choice grids ────────────────────────────────────────────────── */
.av-choices {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(84px, 1fr));
    gap: .75rem;
}
.av-choice {
    position: relative;
    padding: .35rem;
    border-radius: 14px;
    border: 2px solid var(--av-border);
    background: var(--av-surface);
    cursor: pointer;
    transition: border-color .15s, transform .15s;
}
.av-choice:hover { transform: translateY(-2px); border-color: var(--av-accent); }
.av-choice.is-selected { border-color: var(--av-accent); box-shadow: 0 0 0 3px color-mix(in srgb, var(--av-accent) 30%, transparent); }
.av-choice img { width: 100%; border-radius: 10px; display: block; image-rendering: pixelated; }

/* Paging control under the face grid. */
.av-more {
    display: flex;
    align-items: center;
    gap: .85rem;
    margin-top: 1rem;
}
.av-count { font-size: .8125rem; color: var(--av-muted); }

/* Frames are wide cards, not thumbnails: the unlock CONDITION is the point, so
   it has to be readable rather than a tooltip. */
.av-frames { grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); }
.av-frame {
    display: grid;
    gap: .25rem;
    text-align: left;
    padding: .85rem 1rem;
    min-height: 76px;
}
.av-frame-name { font-weight: 700; }
.av-frame-hint { font-size: .8125rem; color: var(--av-muted); line-height: 1.35; }

/* Locked frames stay VISIBLE and legible — dimmed, not hidden. A frame you
   cannot see cannot motivate you to earn it. */
.av-frame.is-locked {
    cursor: not-allowed;
    border-style: dashed;
    opacity: .7;
}
.av-frame.is-locked:hover { transform: none; border-color: var(--av-border); }
.av-frame.is-locked .av-frame-name { color: var(--av-locked); }
.av-lock { position: absolute; top: .5rem; right: .6rem; font-size: .8rem; opacity: .7; }

/* ─── Live preview ────────────────────────────────────────────────── */
/* Face and frame are two STACKED inline SVGs, not one composited image: the
   face is regenerated on every click and the frame is geometry that does not
   depend on it, so swapping either costs no round trip. The render pipeline
   composites them for real at save time — this is only the on-screen stand-in. */
.av-live { position: relative; width: 100%; aspect-ratio: 1; }
.av-live-face,
.av-live-frame { position: absolute; inset: 0; }
.av-live-face svg,
.av-live-frame svg { width: 100%; height: 100%; display: block; }
/* The face is inset and circularly masked to match avatarRender's CHARACTER_INSET
   and the circular crop used everywhere else on the site — without it the ring
   would overlap the head on screen but not in the saved PNG. */
.av-live-face {
    inset: 6%;
    border-radius: 50%;
    overflow: hidden;
    image-rendering: pixelated;
}
.av-live-frame { pointer-events: none; }

/* ─── Option groups ───────────────────────────────────────────────── */
.av-group {
    border: 1px solid var(--av-border);
    border-radius: 14px;
    background: var(--av-surface);
    margin-bottom: .6rem;
    overflow: hidden;
}
.av-group-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    width: 100%;
    min-height: 52px;
    padding: .75rem 1rem;
    background: transparent;
    border: 0;
    color: var(--av-text);
    cursor: pointer;
    text-align: left;
}
.av-group-label { font-weight: 700; }
.av-group-value { font-size: .8125rem; color: var(--av-muted); }
/* The caret is the only affordance saying these open, so it must invert when
   they do — an accordion with no state indicator reads as a dead heading. */
.av-group-head::after {
    content: '\25BE';
    color: var(--av-muted);
    transition: transform .15s;
}
.av-group.is-open .av-group-head::after { transform: rotate(180deg); }

.av-group-body { display: none; padding: 0 1rem 1rem; }
.av-group.is-open .av-group-body { display: block; }

/* Tighter than .av-choices: these are 16px faces, and at 84px each a 45-variant
   hair group becomes a scroll rather than a glance. */
.av-options { grid-template-columns: repeat(auto-fill, minmax(64px, 1fr)); gap: .5rem; }
.av-option { padding: .25rem; }
/* Inline SVG, so the .av-choice img rules do not reach it. */
.av-option svg {
    width: 100%;
    height: auto;
    display: block;
    border-radius: 10px;
    image-rendering: pixelated;
}
.av-option-name {
    display: block;
    font-size: .6875rem;
    color: var(--av-muted);
    text-align: center;
    padding-top: .15rem;
}

/* ─── Colour swatches ─────────────────────────────────────────────── */
.av-swatches {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: .5rem;
    margin-bottom: 1rem;
}
.av-swatch-label {
    font-size: .75rem;
    letter-spacing: .06em;
    text-transform: uppercase;
    color: var(--av-muted);
    width: 100%;
}
.av-swatch {
    width: 32px;
    height: 32px;
    padding: 0;
    border-radius: 50%;
    border: 2px solid var(--av-border);
    background: var(--sw, transparent);
    cursor: pointer;
    transition: transform .15s, border-color .15s;
}
.av-swatch:hover { transform: scale(1.1); border-color: var(--av-accent); }
.av-swatch.is-selected {
    border-color: var(--av-accent);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--av-accent) 30%, transparent);
}

/* Swatches are pure colour, so the hex has to reach a screen reader some other
   way — title= is not announced on a button with no accessible name. */
.sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
    border: 0;
}

/* ─── "Currently Wearing" shelf (profile) ─────────────────────────── */
.av-wearing { margin: 1.25rem 0; }
.av-wearing-title {
    font-size: .75rem;
    letter-spacing: .08em;
    text-transform: uppercase;
    color: var(--av-muted, #6a6458);
    margin: 0 0 .6rem;
}
.av-wearing-items { display: flex; flex-wrap: wrap; gap: .5rem; }
.av-wearing-item {
    display: inline-flex;
    align-items: center;
    gap: .5rem;
    padding: .4rem .85rem;
    border-radius: 999px;
    border: 1px solid var(--av-border, #e5e0d2);
    font-size: .8125rem;
    text-decoration: none;
    color: inherit;
}
.av-wearing-item:hover { border-color: var(--primary, #d4af37); color: inherit; }
.av-wearing-item img { width: 22px; height: 22px; border-radius: 50%; image-rendering: pixelated; }
/* Frames are inlined as SVG rather than a thumbnail image — the art is geometry,
   so there is no file to fetch and it stays crisp at chip size. */
.av-wearing-item svg { width: 22px; height: 22px; display: block; flex: 0 0 auto; }

/* The shelf also renders on the profile, which has no .av-page wrapper and so no
   --av-* vars. The literal fallbacks above cover light; this covers the apex/hub
   dark chrome, where the light muted grey would be unreadable. */
body.fs-dark .av-wearing-title { color: #a89e88; }
body.fs-dark .av-wearing-item { border-color: #332e25; }

@media (max-width: 860px) {
    .av-grid { grid-template-columns: 1fr; }
    .av-preview { position: static; max-width: 320px; }
}

/* ─── Styles (character families) ─────────────────────────────────── */
/* Pixel | Cartoon. Shown only on hubs that offer more than one family. */
.av-styles { display: flex; gap: .4rem; margin-bottom: 1rem; }
.av-style {
    flex: 1;
    padding: .6rem .9rem;
    border: 1px solid var(--av-border);
    border-radius: 999px;
    background: var(--av-surface);
    color: var(--av-text);
    font-weight: 600;
    cursor: pointer;
}
.av-style.is-selected { border-color: var(--av-accent); box-shadow: inset 0 0 0 1px var(--av-accent); }
.av-style-panel[hidden], .av-live-face[hidden], .av-live-cartoon[hidden] { display: none; }

/* The cartoon preview is a canvas the part picker moves in here. Same 6% inset
   as the pixel face — which is avatarRender's CHARACTER_INSET — so what is on
   screen sits where the saved PNG will put it, and never pixelated: this art is
   smooth line work, not a 32px grid. */
.av-live-cartoon {
    position: absolute;
    inset: 6%;
    display: grid;
    place-items: center;
}
.av-live-cartoon canvas {
    width: 100%;
    height: 100%;
    display: block;
    image-rendering: auto;
}

/* ─── Part mixer (public/js/avatar-parts.js) ──────────────────────── */
/* The markup is built in JS and shared by /auth/join, /embed/avatar and here,
   so these rules are keyed to .ap-* and must stay surface-agnostic — anything
   page-specific belongs on the .av-* wrapper around them. */
.av-parts .ap-row { display: flex; flex-wrap: wrap; align-items: center; gap: .35rem; margin-bottom: .6rem; }
.av-parts .ap-label { font-size: .75rem; text-transform: uppercase; letter-spacing: .05em; opacity: .7; margin-right: .25rem; }
.av-parts .ap-group,
.av-parts .ap-tint-reset {
    padding: .35rem .7rem;
    border: 1px solid var(--av-border);
    border-radius: 999px;
    background: var(--av-surface);
    color: var(--av-text);
    font-size: .8rem;
    font-weight: 600;
    cursor: pointer;
}
.av-parts .ap-group.is-selected { border-color: var(--av-accent); box-shadow: inset 0 0 0 1px var(--av-accent); }
.av-parts .ap-options {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(76px, 1fr));
    gap: .5rem;
    margin-bottom: .6rem;
}
.av-parts .ap-option {
    padding: .25rem;
    aspect-ratio: 1;
    border: 1px solid var(--av-border);
    border-radius: 12px;
    background: var(--av-surface);
    cursor: pointer;
}
.av-parts .ap-option.is-selected { border-color: var(--av-accent); box-shadow: inset 0 0 0 1px var(--av-accent); }
/* One rule for both kinds of option art: a live-painted canvas for the part groups,
   a published thumbnail img for the Body grid (which spans every skeleton and so
   cannot be painted from the one loaded atlas). object-fit matters only to the img. */
.av-parts .ap-option-art { width: 100%; height: 100%; display: block; object-fit: contain; }
.av-parts .ap-tint-input { width: 2.4rem; height: 2rem; padding: 0; border: 1px solid var(--av-border); border-radius: 8px; background: none; cursor: pointer; }
.av-parts .ap-status { margin: 0; font-size: .8rem; opacity: .75; }
/* The picker's own stage is removed on this page (the canvas is hoisted into the
   shared preview), so nothing here styles .ap-stage. */
