/* Glimmerstone design system: Y2K candy revival. Chunky ink outlines, sticker cards with hard offset shadows,
   glossy pill buttons, per-land sky gradients. Reuse these classes; don't invent new ones for the same job:
   .card .btn .btn-alt .btn-small .btn-big .btn-danger .btn-row .grid(.grid-items/.grid-pets/.grid-2/.grid-3)
   .item-tile .pet-card .stat-bar .pill .flash .table .muted .lede .empty .head-row .hint */

/* Fredoka and Nunito, self-hosted: Google Fonts' own CSS for the families, weights and subsets the site used to link
   (Fredoka 400-700, Nunito 400/600/700/800 and italic 400, display=swap), with the woff2 files copied into public/fonts.
   Kept face for face, so every weight picks the same face it did. New file names for a font update (src/assets.js). */
/* hebrew */
@font-face {
  font-family: 'Fredoka';
  font-style: normal;
  font-weight: 400;
  font-stretch: 100%;
  font-display: swap;
  src: url(/fonts/fredoka-v17-hebrew.woff2) format('woff2');
  unicode-range: U+0307-0308, U+0590-05FF, U+200C-2010, U+20AA, U+25CC, U+FB1D-FB4F;
}
/* latin-ext */
@font-face {
  font-family: 'Fredoka';
  font-style: normal;
  font-weight: 400;
  font-stretch: 100%;
  font-display: swap;
  src: url(/fonts/fredoka-v17-latin-ext.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
  font-family: 'Fredoka';
  font-style: normal;
  font-weight: 400;
  font-stretch: 100%;
  font-display: swap;
  src: url(/fonts/fredoka-v17-latin.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* hebrew */
@font-face {
  font-family: 'Fredoka';
  font-style: normal;
  font-weight: 500;
  font-stretch: 100%;
  font-display: swap;
  src: url(/fonts/fredoka-v17-hebrew.woff2) format('woff2');
  unicode-range: U+0307-0308, U+0590-05FF, U+200C-2010, U+20AA, U+25CC, U+FB1D-FB4F;
}
/* latin-ext */
@font-face {
  font-family: 'Fredoka';
  font-style: normal;
  font-weight: 500;
  font-stretch: 100%;
  font-display: swap;
  src: url(/fonts/fredoka-v17-latin-ext.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
  font-family: 'Fredoka';
  font-style: normal;
  font-weight: 500;
  font-stretch: 100%;
  font-display: swap;
  src: url(/fonts/fredoka-v17-latin.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* hebrew */
@font-face {
  font-family: 'Fredoka';
  font-style: normal;
  font-weight: 600;
  font-stretch: 100%;
  font-display: swap;
  src: url(/fonts/fredoka-v17-hebrew.woff2) format('woff2');
  unicode-range: U+0307-0308, U+0590-05FF, U+200C-2010, U+20AA, U+25CC, U+FB1D-FB4F;
}
/* latin-ext */
@font-face {
  font-family: 'Fredoka';
  font-style: normal;
  font-weight: 600;
  font-stretch: 100%;
  font-display: swap;
  src: url(/fonts/fredoka-v17-latin-ext.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
  font-family: 'Fredoka';
  font-style: normal;
  font-weight: 600;
  font-stretch: 100%;
  font-display: swap;
  src: url(/fonts/fredoka-v17-latin.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* hebrew */
@font-face {
  font-family: 'Fredoka';
  font-style: normal;
  font-weight: 700;
  font-stretch: 100%;
  font-display: swap;
  src: url(/fonts/fredoka-v17-hebrew.woff2) format('woff2');
  unicode-range: U+0307-0308, U+0590-05FF, U+200C-2010, U+20AA, U+25CC, U+FB1D-FB4F;
}
/* latin-ext */
@font-face {
  font-family: 'Fredoka';
  font-style: normal;
  font-weight: 700;
  font-stretch: 100%;
  font-display: swap;
  src: url(/fonts/fredoka-v17-latin-ext.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
  font-family: 'Fredoka';
  font-style: normal;
  font-weight: 700;
  font-stretch: 100%;
  font-display: swap;
  src: url(/fonts/fredoka-v17-latin.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
  font-family: 'Nunito';
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url(/fonts/nunito-italic-v32-cyrillic-ext.woff2) format('woff2');
  unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
  font-family: 'Nunito';
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url(/fonts/nunito-italic-v32-cyrillic.woff2) format('woff2');
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* vietnamese */
@font-face {
  font-family: 'Nunito';
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url(/fonts/nunito-italic-v32-vietnamese.woff2) format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
  font-family: 'Nunito';
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url(/fonts/nunito-italic-v32-latin-ext.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
  font-family: 'Nunito';
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url(/fonts/nunito-italic-v32-latin.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
  font-family: 'Nunito';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(/fonts/nunito-v32-cyrillic-ext.woff2) format('woff2');
  unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
  font-family: 'Nunito';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(/fonts/nunito-v32-cyrillic.woff2) format('woff2');
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* vietnamese */
@font-face {
  font-family: 'Nunito';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(/fonts/nunito-v32-vietnamese.woff2) format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
  font-family: 'Nunito';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(/fonts/nunito-v32-latin-ext.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
  font-family: 'Nunito';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(/fonts/nunito-v32-latin.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
  font-family: 'Nunito';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url(/fonts/nunito-v32-cyrillic-ext.woff2) format('woff2');
  unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
  font-family: 'Nunito';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url(/fonts/nunito-v32-cyrillic.woff2) format('woff2');
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* vietnamese */
@font-face {
  font-family: 'Nunito';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url(/fonts/nunito-v32-vietnamese.woff2) format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
  font-family: 'Nunito';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url(/fonts/nunito-v32-latin-ext.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
  font-family: 'Nunito';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url(/fonts/nunito-v32-latin.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
  font-family: 'Nunito';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url(/fonts/nunito-v32-cyrillic-ext.woff2) format('woff2');
  unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
  font-family: 'Nunito';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url(/fonts/nunito-v32-cyrillic.woff2) format('woff2');
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* vietnamese */
@font-face {
  font-family: 'Nunito';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url(/fonts/nunito-v32-vietnamese.woff2) format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
  font-family: 'Nunito';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url(/fonts/nunito-v32-latin-ext.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
  font-family: 'Nunito';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url(/fonts/nunito-v32-latin.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
  font-family: 'Nunito';
  font-style: normal;
  font-weight: 800;
  font-display: swap;
  src: url(/fonts/nunito-v32-cyrillic-ext.woff2) format('woff2');
  unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
  font-family: 'Nunito';
  font-style: normal;
  font-weight: 800;
  font-display: swap;
  src: url(/fonts/nunito-v32-cyrillic.woff2) format('woff2');
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* vietnamese */
@font-face {
  font-family: 'Nunito';
  font-style: normal;
  font-weight: 800;
  font-display: swap;
  src: url(/fonts/nunito-v32-vietnamese.woff2) format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
  font-family: 'Nunito';
  font-style: normal;
  font-weight: 800;
  font-display: swap;
  src: url(/fonts/nunito-v32-latin-ext.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
  font-family: 'Nunito';
  font-style: normal;
  font-weight: 800;
  font-display: swap;
  src: url(/fonts/nunito-v32-latin.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

:root {
  --ink: #2a1b4a;          /* outlines + body text */
  --card-muted: #5d5280;   /* secondary text, AA on white. Use --muted: it is this on a surface and --page-muted on the page */
  --muted: var(--card-muted);
  --paper: #f4efff;        /* page */
  --card: #ffffff;
  --tint: #faf6ff;         /* table stripes, inset panels */
  --pink: #ff4f9a;
  --sun: #ffd23f;
  --sky: #3fb8ff;
  --lime: #8fe03c;
  --grape: #7a4fe0;
  --card-link: #077069;    /* deep lagoon teal, AA on white, paper and --hi (a "yours" row); not pet-site purple. Use --link */
  --link: var(--card-link);
  --ok-bg: #dcffe8;
  --hi: #eedcff;           /* selected, highlighted, "yours": glimmer lilac, not pet-site yellow */
  --hi-soft: #f8f0ff;      /* callouts, unread rows */
  --pick: #c9b3ff;         /* the current tab / pressed toggle */
  --tint-2: #ece5fb;       /* insets, bar tracks, locked things */
  --rule: #d8cdf3;         /* dashed dividers, table row lines */
  --price: #b3246a;        /* prices and jackpots, AA on white */
  --emph: #b3246a;         /* magenta emphasis: kickers, taglines, a hit that lands. Same hue as --price today; prices keep --price */
  --gain: #1b712f;         /* AA on --hi too */
  --loss: #b0243f;
  --err-bg: #ffe1e8;
  --land-from: #ffd6f0;
  --land-to: #bfe6ff;
  --land-accent: #e8338a;
  --page-ink: var(--ink);  /* text that sits straight on the page background */
  --page-link: var(--card-link); --page-muted: var(--card-muted); /* links and secondary text on the page background */
  --night: #2e2452;         /* dark-land surface: the page background under .land-dark */
  --sun-soft: #fff5b0;     /* pale sun: highlight fills, sheens */
  --sun-glow: #ffe680;     /* warm sun: glowing tags, stars, flares */
  --sun-cream: #fff6c2;    /* creamy yellow: notes, paper, soft callouts */
  --pink-soft: #ffdcee;    /* pale pink: quest badges, tinted plates */
  --sky-soft: #dcf3ff;     /* pale sky: shop badges, water tints */
  --parchment: #fdf7e4;    /* aged paper: keepsakes, notes, scrapbook pages */

  /* One-off palette for component art in this file (sky charms, buttons, stat fills), named by role */
  --ui-sky-day-bg: #7fd3ff;
  --ui-sky-day-bg-2: #d6f3ff;
  --ui-sky-dusk-bg: #ff9fc9;
  --ui-sky-dusk-bg-2: #ffd98a;
  --ui-sky-dusk-ink: #6fbf3a;
  --ui-sky-night-bg: #24175e;
  --ui-sky-night-bg-2: #5b3fa6;
  --ui-sky-night-ink: #2f6b3a;
  --ui-btn-alt-b2: #c9f5e4;
  --ui-btn-danger-b1: #ffd0d8;
  --ui-btn-danger-b2: #ff8fa5;
  /* Button fills by role (top, bottom of the gloss gradient). A page theme (public/themes.css) sets these on <body>;
     .btn reads them into its own --b1/--b2, which a one-off button may still set directly. */
  --btn-b1: #f1e6ff; --btn-b2: #a98bff; /* glimmer shimmer */
  --btn-alt-b1: #ffffff; --btn-alt-b2: var(--ui-btn-alt-b2);
  --btn-danger-b1: var(--ui-btn-danger-b1); --btn-danger-b2: var(--ui-btn-danger-b2);
  --ui-pill-sick-bg: #ffe1a8;
  --ui-event-banner-bg: #d9d0ff;
  --ui-ad-bg: #fffff0;
  --ui-stat-hunger-fill: #ffa63d;
  --ui-stat-hp-fill: #3fcf6a;
  --ui-stat-glow-fill: #7fe3f5;
  --ui-land-dark-bg: #180e30;
  --ui-townsfolk-bg: #d8f3ea;

  --line: 3px;
  --r-card: 22px;
  --r-tile: 16px;
  --shadow: 6px 6px 0 var(--ink);
  --shadow-sm: 3px 3px 0 var(--ink);
  --shadow-lift: 5px 5px 0 var(--ink);   /* --shadow-sm tiles on hover */
  --ease-out: cubic-bezier(.2, .8, .3, 1);
  --fs-xs: .82rem;              /* pills, captions, labels */
  --fs-sm: .94rem;         /* secondary text; stays >= 15px */
  --r-sm: 12px;            /* insets, chips inside cards */
  --display: 'Fredoka', 'Nunito', system-ui, sans-serif;
  --body: 'Nunito', system-ui, -apple-system, 'Segoe UI', sans-serif;
  --gap: 18px;
  --gap-sm: 10px;
  /* Gourdlight (October): a cobweb for each top corner of the header, strung with one glimmer dewdrop */
  --oct-web: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='60' height='60'%3E%3Cg%3E%3Cpath d='M0 0L58 0M0 0L53.8 21.7M0 0L41 41M0 0L21.7 53.8M0 0L0 58M16 0Q12.9 2.5 14.8 6Q10.9 7.2 11.3 11.3Q7.2 10.9 6 14.8Q2.5 12.9 0 16M30 0Q24.1 4.7 27.8 11.2Q20.5 13.6 21.2 21.2Q13.6 20.5 11.2 27.8Q4.7 24.1 0 30M44 0Q35.4 6.9 40.8 16.5Q30.1 19.9 31.1 31.1Q19.9 30.1 16.5 40.8Q6.9 35.4 0 44' fill='none' stroke='white' stroke-opacity='.7' stroke-width='1.3' stroke-linecap='round'/%3E%3Cpath d='M24.6 12.2Q25 16 28.6 16.4Q25 16.8 24.6 20.6Q24.2 16.8 20.6 16.4Q24.2 16 24.6 12.2Z' fill='%23fff6a8' stroke='%232a1b4a' stroke-width='.8'/%3E%3C/g%3E%3C/svg%3E");
  --oct-web-r: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='60' height='60'%3E%3Cg transform='matrix(-1 0 0 1 60 0)'%3E%3Cpath d='M0 0L58 0M0 0L53.8 21.7M0 0L41 41M0 0L21.7 53.8M0 0L0 58M16 0Q12.9 2.5 14.8 6Q10.9 7.2 11.3 11.3Q7.2 10.9 6 14.8Q2.5 12.9 0 16M30 0Q24.1 4.7 27.8 11.2Q20.5 13.6 21.2 21.2Q13.6 20.5 11.2 27.8Q4.7 24.1 0 30M44 0Q35.4 6.9 40.8 16.5Q30.1 19.9 31.1 31.1Q19.9 30.1 16.5 40.8Q6.9 35.4 0 44' fill='none' stroke='white' stroke-opacity='.7' stroke-width='1.3' stroke-linecap='round'/%3E%3Cpath d='M24.6 12.2Q25 16 28.6 16.4Q25 16.8 24.6 20.6Q24.2 16.8 20.6 16.4Q24.2 16 24.6 12.2Z' fill='%23fff6a8' stroke='%232a1b4a' stroke-width='.8'/%3E%3C/g%3E%3C/svg%3E");
}

.land-dark {
  --paper: var(--night);
  --page-ink: #f4ecff;
  --page-link: #7ff0e2;
  --page-muted: #c9bde6;
}

*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; padding: 0 env(safe-area-inset-right) 0 env(safe-area-inset-left); } /* viewport-fit=cover: keep landscape pages off the notch */
body {
  /* :root resolves these aliases with lilac's values; restated here they follow a page theme's --card-link/--card-muted.
     .land-dark and the dark theme restate --page-link and --page-muted with more specificity. */
  --link: var(--card-link); --muted: var(--card-muted); --page-link: var(--card-link); --page-muted: var(--card-muted);
  margin: 0;
  font: 400 16px/1.55 var(--body);
  color: var(--ink);
  background-color: var(--paper);
  background-image:
    radial-gradient(circle at 20% 30%, color-mix(in oklab, var(--land-accent) 18%, transparent) 0 1.5px, transparent 2.5px),
    radial-gradient(circle at 70% 80%, color-mix(in oklab, var(--page-dot, var(--grape)) 16%, transparent) 0 1.5px, transparent 2.5px); /* a dark theme lightens --page-dot */
  background-size: 46px 46px, 64px 64px;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}
img, svg { max-width: 100%; }
a { color: var(--link); text-decoration-thickness: 2px; text-underline-offset: 3px; }
@media (hover: hover) and (pointer: fine) { a:hover { text-decoration-thickness: 4px; } } /* hover keeps the link color (pink on white was 3.07:1) and inks a chunkier underline */
:focus-visible { outline: 3px solid var(--ink); outline-offset: 2px; border-radius: 6px; box-shadow: 0 0 0 8px var(--sky); } /* two-tone: the ink ring reads on light surfaces, the sky halo on dark pages and .land-dark */
h1, h2, h3 { font-family: var(--display); line-height: 1.15; margin: 0 0 .5em; letter-spacing: .005em; text-wrap: balance; overflow-wrap: anywhere; } /* long usernames ("the_real_glimmerlord's keepsakes") break instead of widening the page */
h1 { font-size: clamp(1.8rem, 1.3rem + 2vw, 2.5rem); font-weight: 700; }
h2 { font-size: 1.45rem; font-weight: 600; }
h3 { font-size: 1.15rem; font-weight: 600; }
p { margin: 0 0 .8em; text-wrap: pretty; }
code { font-size: .9em; background: var(--tint); padding: .1em .35em; border-radius: 6px; }
.muted { color: var(--muted); }
.lede { font-size: 1.12rem; max-width: 62ch; }
.skip { position: absolute; left: -999px; top: 8px; z-index: 10; background: var(--sun); padding: 8px 14px; border: var(--line) solid var(--ink); border-radius: 999px; }
.skip:focus { left: 8px; }

/* ---------- Header ---------- */
.site-head {
  position: relative;
  display: flex; align-items: center; gap: 16px; flex-wrap: wrap;
  padding: 14px clamp(16px, 3vw, 32px) 18px;
  background:
    radial-gradient(circle at 12% 30%, #fff 0 2px, transparent 3px),
    radial-gradient(circle at 80% 20%, #fff 0 1.5px, transparent 2.5px),
    radial-gradient(circle at 55% 75%, #fff 0 1.5px, transparent 2.5px),
    linear-gradient(115deg, var(--land-from), var(--land-to));
  background-size: 120px 80px, 90px 70px, 150px 90px, auto;
  border-bottom: var(--line) solid var(--ink);
}
/* Gourdlight (October, html.october from src/layout.js): cobwebs in the header's top corners. They are background layers, so they
   never take space or sit over a control. The other four layers repeat .site-head's own. */
.october .site-head {
  background-image: var(--oct-web), var(--oct-web-r),
    radial-gradient(circle at 12% 30%, #fff 0 2px, transparent 3px),
    radial-gradient(circle at 80% 20%, #fff 0 1.5px, transparent 2.5px),
    radial-gradient(circle at 55% 75%, #fff 0 1.5px, transparent 2.5px),
    linear-gradient(115deg, var(--land-from), var(--land-to));
  background-size: 60px 60px, 60px 60px, 120px 80px, 90px 70px, 150px 90px, auto;
  background-position: left top, right top, 0 0, 0 0, 0 0, 0 0;
  background-repeat: no-repeat, no-repeat, repeat, repeat, repeat, repeat;
}
/* SVG logo: paint-order puts every glyph's outline under every fill, so thick strokes never cover the neighboring letter */
.logo { display: inline-flex; text-decoration: none; filter: drop-shadow(0 4px 0 var(--ink)); }
.logo svg { height: clamp(44px, 34px + 2vw, 58px); width: auto; overflow: visible; }
.logo text { font: 700 60px var(--display); letter-spacing: -.5px; stroke: var(--ink); stroke-width: 7px; stroke-linejoin: round; paint-order: stroke fill; }
.logo:hover { filter: drop-shadow(0 4px 0 var(--ink)) brightness(1.05); }
.land-chip {
  font: 600 var(--fs-sm) var(--display);
  padding: 4px 14px;
  border: 2px solid var(--ink);
  border-radius: 999px;
  background: color-mix(in oklab, var(--land-accent) 18%, #fff); /* style-ok: land paint: the chip sits on the land header */
  color: var(--ink);
  text-decoration: none;
  box-shadow: var(--shadow-sm);
}
.land-dark .land-chip { background: var(--land-accent); }
.who { margin-left: auto; display: flex; align-items: center; gap: 12px; }
.who form { margin: 0; }
/* Account menu: the avatar opens a small panel (a native <details>) */
.account { position: relative; }
.account summary { list-style: none; cursor: pointer; display: block; border: 2px solid var(--ink); border-radius: 50%; background: var(--card); box-shadow: var(--shadow-sm); }
.account summary::-webkit-details-marker { display: none; }
.account summary svg { display: block; border-radius: 50%; }
.account[open] summary { background: var(--pick); }
.account-panel {
  position: absolute; right: 0; top: calc(100% + 10px); z-index: 30; width: 220px; padding: 8px; display: grid; gap: 2px;
  background: var(--card); border: var(--line) solid var(--ink); border-radius: var(--r-sm); box-shadow: var(--shadow-sm);
}
.account-panel a, .account-panel button {
  display: grid; width: 100%; padding: 8px 12px; text-align: left; font: 600 1rem var(--display); color: var(--ink);
  text-decoration: none; background: none; border: 0; border-radius: 8px; cursor: pointer; overflow-wrap: anywhere;
}
.account-panel small { font: 400 var(--fs-xs) var(--body); color: var(--muted); }
.acct-me .title-tag { justify-self: start; margin: 4px 0 0; white-space: normal; } /* sized to its words, not stretched across the menu */
.account-panel a:hover, .account-panel button:hover { background: var(--hi); color: var(--ink); }
.account-panel form { margin: 0; padding-top: 4px; border-top: 2px dashed var(--rule); }
/* Header status cluster: coins, mail, bell and the sky clock, all 40px pills */
.charms { display: flex; align-items: center; gap: 8px; }
.charm { display: inline-flex; align-items: center; gap: 3px; min-width: 40px; min-height: 40px; justify-content: center; padding: 0 8px; color: var(--ink); text-decoration: none; background: var(--card); border: 2px solid var(--ink); border-radius: 999px; box-shadow: var(--shadow-sm); font: 700 .95rem var(--display); }
.charm.has-new { background: var(--sun); }
/* Desktop bell dropdown (shell.js fills it from /bell/panel); phones and tablets follow the link instead. */
.bell-wrap { position: relative; display: flex; }
.who-bell[aria-expanded="true"] { background: var(--pick); }
.bell-panel {
  position: absolute; right: 0; top: calc(100% + 10px); z-index: 30; width: 360px; padding: 10px 12px; display: grid; gap: 6px;
  background: var(--card); border: var(--line) solid var(--ink); border-radius: var(--r-sm); box-shadow: var(--shadow-sm);
}
.bell-panel[hidden] { display: none; }
.bell-head { font: 600 1.05rem var(--display); }
.bell-list { list-style: none; margin: 0; padding: 0; }
.bell-list li { display: grid; gap: 2px; padding: 8px 0; font-size: var(--fs-sm); overflow-wrap: anywhere; }
.bell-list li + li { border-top: 2px dashed var(--rule); }
.bell-list li.unread { font-weight: 800; }
.bell-list small, .bell-empty { color: var(--muted); font-size: var(--fs-xs); font-weight: 400; }
.bell-empty { margin: 0; font-size: var(--fs-sm); }
.bell-all { justify-self: end; font: 600 var(--fs-sm) var(--display); }
.charm:hover { color: var(--ink); background: var(--hi); }
.sky-charm { display: block; flex: none; width: 58px; height: 40px; border: 2px solid var(--ink); border-radius: 999px; overflow: hidden; box-shadow: var(--shadow-sm); cursor: help; }
.sky-charm svg { display: block; }
.sky-day { background: linear-gradient(var(--ui-sky-day-bg), var(--ui-sky-day-bg-2)); color: var(--lime); }
.sky-dusk { background: linear-gradient(var(--ui-sky-dusk-bg), var(--ui-sky-dusk-bg-2)); color: var(--ui-sky-dusk-ink); }
.sky-night { background: linear-gradient(var(--ui-sky-night-bg), var(--ui-sky-night-bg-2)); color: var(--ui-sky-night-ink); }

/* ---------- Nav ---------- */
/* Sticker tabs hanging off the header's bottom edge, tinted with the land's accent. */
.site-nav { padding: 0 clamp(16px, 3vw, 32px); display: flex; align-items: flex-start; gap: 16px; }
.site-nav ul { list-style: none; margin: 0; padding: 0 4px 8px; display: flex; gap: 8px; overflow-x: auto; scrollbar-width: thin; flex: 1; min-width: 0; }
.site-nav a {
  display: block; white-space: nowrap;
  padding: 8px 14px 8px;
  font: 600 1rem var(--display); color: var(--ink); text-decoration: none;
  background: var(--nav-tab, color-mix(in oklab, var(--land-accent) 14%, var(--card))); /* a page theme sets --nav-tab */
  border: var(--line) solid var(--ink); border-top: 0; border-radius: 0 0 14px 14px;
  box-shadow: var(--shadow-sm);
}
.site-nav a:hover { color: var(--ink); background: var(--card); }
.site-nav a[aria-current="page"] { background: var(--pick); }
.site-nav a:focus-visible { outline-offset: -9px; box-shadow: var(--shadow-sm), inset 0 0 0 3px var(--sky); } /* the tab row scrolls (overflow), which would clip an outside ring */
@media (max-width: 767px) { .site-nav a:focus-visible, .site-nav button:focus-visible { outline-offset: -8px; box-shadow: inset 0 0 0 3px var(--sky); } }

/* ---------- Page body ---------- */
.site-body {
  flex: 1;
  width: 100%; max-width: 1180px; margin: 0 auto;
  padding: 16px clamp(16px, 3vw, 32px) 40px;
}
/* Signed in: the pet panel (after <main> in the DOM) sits in a left column; below 1000px it's a strip above the content. */
.has-side { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--gap); align-content: start; }
.has-side > .pet-panel { order: -1; }
/* Desktop width. --page-max caps .site-body, and the header and nav rows are padded in to the same edge so the logo, nav tabs and coins line up
   with the content column instead of hugging the window edges. Wide monitors (1700px+) get 1480px: 1320px left 300px empty either side at 1920. */
:root { --page-max: 1320px; }
@media (min-width: 1700px) { :root { --page-max: 1480px; } }
@media (min-width: 1000px) {
  .site-head, .site-nav { padding-inline: max(clamp(16px, 3vw, 32px), calc((100% - var(--page-max)) / 2 + 28px)); }
  .site-body { max-width: var(--page-max); }
  .has-side { grid-template-columns: 232px minmax(0, 1fr); align-items: start; }
  .has-side > .pet-panel { grid-column: 1; grid-row: 1; position: sticky; top: 16px; max-height: calc(100vh - 32px); overflow-y: auto; } /* short windows: the switcher stays reachable */
  .has-side > main { grid-column: 2; grid-row: 1; }
}
main { min-width: 0; display: grid; gap: var(--gap); align-content: start; }
/* Text straight on the page takes the page tokens (light under a dark land or Plum night): ink, links and muted text. Every
   surface drawn on the page sets the card ones back. Zero specificity, so a component's own color still wins. A new light
   surface that holds text joins this list (the all-routes contrast scan in Plum night finds one that is missing). */
main { color: var(--page-ink); --link: var(--page-link); --muted: var(--page-muted); }
:where(.card, .flash, .event-banner, .item-tile, .pet-card, .table-wrap, .table, .pill, .speech, .hint, .ad, .home-hero, .gem-sniff,
  .land-banner-text, .land-card, .place, .map-pop-card, .album-tabs a, .btn, .pick, .swatch, .tile, .alert, dialog, [popover]) {
  color: var(--ink); --link: var(--card-link); --muted: var(--card-muted);
}

/* ---------- Cards ---------- */
.card {
  background: var(--card);
  border: var(--line) solid var(--ink);
  border-radius: var(--r-card);
  box-shadow: var(--shadow);
  padding: clamp(16px, 2.4vw, 24px);
  min-width: 0;
}
.card > :last-child { margin-bottom: 0; }
.head-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; margin-bottom: .6em; }
.head-row h1, .head-row h2 { margin: 0; }
/* A place's own page (layout.js placeHead): the plain word under the themed title, and a "← Land" pill above it on phones. */
.place-plain { display: block; margin-top: 4px; font: 700 var(--fs-sm)/1.3 var(--body); color: var(--muted); letter-spacing: 0; }
.land-back { display: none; }
/* "?" links to Nanny's Handbook (Home's tagged rows, the pet panel's meters): a 44px target around a small yellow coin. */
.hb-q { display: inline-grid; place-items: center; width: 44px; height: 44px; flex: none; text-decoration: none; color: var(--ink); }
.hb-q > span[aria-hidden] { display: grid; place-items: center; width: 26px; height: 26px; border: 2px solid var(--ink); border-radius: 50%; background: var(--sun-cream); font: 700 .85rem/1 var(--display); }
@media (hover: hover) and (pointer: fine) { .hb-q:hover > span[aria-hidden] { background: var(--sun); } }
/* The meters' "?" ends the Health row, after "11 / 11" (layout.js petPanel, 2026-10-16 pick P2). Still a 44px target; the negative
   margins keep the row as tall as the others (the target overlaps the gap, where there is nothing else to tap). */
.pet-meters li.stat-hp { grid-template-columns: auto 1fr auto auto; }
.pet-meters li.stat-hp > .hb-q { margin: -12px -10px -12px -8px; }
.pet-meters li.stat-hp > .hb-q > span[aria-hidden] { width: 22px; height: 22px; font-size: .78rem; }
/* Friends today rows (routes/friends.js todayList), on Home and /friends: a small pet face, the line, how long ago */
.fr-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 10px; }
.fr-list li { display: flex; gap: 10px; align-items: flex-start; font-size: var(--fs-sm); line-height: 1.4; }
.fr-list svg { flex: none; width: 32px; height: 32px; border-radius: 50%; box-shadow: 0 0 0 2px var(--ink); }
.fr-list small { color: var(--muted); white-space: nowrap; }
.empty { text-align: center; padding: 18px 8px; color: var(--muted); }
.empty .btn { margin-top: 6px; }
.empty .btn-row { justify-content: center; margin-top: 6px; }
/* An .empty card's scene bleeds to its own 18px/8px padding, not the card's (the Hatchery's "spots full" card stuck out 16px each side);
   its paragraphs center under the desktop 75ch cap. */
.card.empty > .scene { margin: -18px -8px 16px; }
.empty p { margin-inline: auto; }
.hint { background: var(--tint); border: 2px dashed color-mix(in oklab, var(--grape) 50%, var(--card)); border-radius: var(--r-sm); padding: 8px 12px; }

/* ---------- Buttons ---------- */
.btn {
  --b1: var(--btn-b1); --b2: var(--btn-b2);
  position: relative; display: inline-flex; align-items: center; justify-content: center; gap: 6px; text-align: center; /* a label that wraps ("Season 2 (locked)") stays centered */
  min-height: 44px; padding: 8px 24px;
  font: 600 1.02rem/1.1 var(--display); color: var(--ink); text-decoration: none; cursor: pointer;
  background: linear-gradient(180deg, var(--b1), var(--b2));
  border: var(--line) solid var(--ink); border-radius: 999px;
  box-shadow: 0 4px 0 var(--ink);
  transition: transform .08s, box-shadow .08s, filter .15s;
}
.btn::before { /* the gloss */
  content: ''; position: absolute; left: 12px; right: 12px; top: 3px; height: 38%;
  border-radius: 999px; background: linear-gradient(180deg, rgb(255 255 255 / .85), rgb(255 255 255 / .1));
  pointer-events: none;
}
.btn:focus-visible { box-shadow: 0 4px 0 var(--ink), 0 0 0 8px var(--sky); } /* keeps its hard shadow under the halo */
@media (hover: hover) and (pointer: fine) { /* touch keeps no sticky hover */
  .btn:hover { color: var(--ink); filter: brightness(1.05) saturate(1.1);
    background: linear-gradient(115deg, color-mix(in srgb, var(--pink) 20%, transparent), transparent 45%, color-mix(in srgb, var(--sky) 22%, transparent)), linear-gradient(180deg, var(--b1), var(--b2)); } /* glimmerstone sheen */
}
.btn:active { transform: translateY(3px); box-shadow: 0 1px 0 var(--ink); }
.btn:disabled { opacity: .5; cursor: not-allowed; transform: none; box-shadow: 0 4px 0 var(--ink); }
.btn-alt { --b1: var(--btn-alt-b1); --b2: var(--btn-alt-b2); }
.btn-danger { --b1: var(--btn-danger-b1); --b2: var(--btn-danger-b2); }
.btn-small { min-height: 36px; padding: 6px 14px; font-size: var(--fs-sm); box-shadow: 0 3px 0 var(--ink); }
.btn-small:focus-visible { box-shadow: 0 3px 0 var(--ink), 0 0 0 8px var(--sky); }
.btn-small:active { transform: translateY(2px); box-shadow: 0 1px 0 var(--ink); }
.btn-big { min-height: 54px; padding: 12px 32px; font-size: 1.2rem; }
.btn-row { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; }
.btn-row form { margin: 0; }
.card > form.btn-row + p { margin-top: 12px; } /* a note under a lone action button */

/* ---------- Pills, flash, events ---------- */
.pill {
  display: inline-block; padding: 2px 12px;
  font: 600 var(--fs-xs)/1.5 var(--display);
  background: color-mix(in oklab, var(--land-accent) 14%, var(--card));
  border: 2px solid var(--ink); border-radius: 999px; margin: 0;
}
:where(.pill) { color: var(--ink); } /* light pill, dark text even inside a dark card; zero specificity so link pills keep the link color */
.pill-sick { background: var(--ui-pill-sick-bg); }
.flash {
  border: var(--line) solid var(--ink); border-radius: var(--r-tile);
  padding: 12px 16px 12px 48px; position: relative; font-weight: 700;
  background: var(--ok-bg); box-shadow: var(--shadow-sm);
}
.flash::before { content: '✓'; position: absolute; left: 14px; top: 10px; font: 700 1.2rem var(--display); }
.flash-err { background: var(--err-bg); }
/* Delight: a pet's mood went up -> the flash badge becomes a heart and throws a little sparkle ring (so does the pet on its own page) */
.flash-joy { background: linear-gradient(110deg, var(--ok-bg), var(--hi) 80%); }
.flash-joy::before { content: '♥'; left: 15px; color: var(--pink); }
.flash-joy::after, .pet-joy .pet-profile-art::after {
  content: ''; position: absolute; left: 21px; top: 23px; width: 6px; height: 6px; margin: -3px; border-radius: 50%;
  pointer-events: none; opacity: 0; animation: sparkle-ring .75s var(--ease-out) .1s;
  box-shadow: 0 -18px var(--sun), 13px -13px var(--pink), 18px 0 var(--sky), 13px 13px var(--sun), 0 18px var(--pink), -13px 13px var(--sky), -18px 0 var(--sun), -13px -13px var(--pink);
}
.pet-joy .pet-profile-art::after {
  left: 50%; top: 45%; width: 12px; height: 12px; margin: -6px;
  box-shadow: 0 -80px var(--sun), 57px -57px var(--pink), 80px 0 var(--sky), 57px 57px var(--sun), 0 80px var(--pink), -57px 57px var(--sky), -80px 0 var(--sun), -57px -57px var(--pink);
}
.pet-joy .pet-profile-art > svg { animation: joy-hop .6s var(--ease-out) .1s; }
@keyframes sparkle-ring { from { opacity: 1; transform: scale(.35); } 55% { opacity: 1; } to { opacity: 0; transform: scale(1.5); } }
@keyframes joy-hop { 35% { transform: translateY(-14px); } }
.flash-err::before { content: '!'; left: 20px; }
.event-banner {
  position: relative;
  border: var(--line) solid var(--ink); border-radius: var(--r-card);
  padding: 16px 20px 14px 80px;
  background:
    radial-gradient(circle at 92% 25%, #fff 0 2px, transparent 3px),
    radial-gradient(circle at 80% 75%, #fff 0 1.5px, transparent 2.5px),
    linear-gradient(110deg, var(--sun-soft), var(--pink-soft) 55%, var(--ui-event-banner-bg));
  box-shadow: var(--shadow);
  animation: pop-in .5s cubic-bezier(.2, 1.6, .4, 1) both;
}
.event-banner::before {
  content: '★'; position: absolute; left: 16px; top: 50%; translate: 0 -50%;
  width: 46px; height: 46px; display: grid; place-items: center;
  font-size: 1.6rem; color: var(--ink); background: var(--sun);
  border: var(--line) solid var(--ink); border-radius: 50%;
}
.event-banner b { font: 700 1.25rem var(--display); display: block; }
.event-banner p { margin: 2px 0 0; font-weight: 600; }
@keyframes pop-in { from { transform: scale(.85); opacity: 0; } }
@keyframes bob { to { transform: translateY(-6px); } } /* sprites and pocket critters */

/* ---------- Coins (header charm) ---------- */
.coins {
  display: inline-flex; align-items: center; min-height: 40px; white-space: nowrap;
  font-variant-numeric: tabular-nums;
  font: 700 1.05rem var(--display); color: var(--ink); text-decoration: none;
  background: linear-gradient(180deg, var(--sun-soft), var(--sun)); border: 2px solid var(--ink); border-radius: 999px; padding: 0 12px;
  box-shadow: var(--shadow-sm);
}
.coins:hover { color: var(--ink); }
/* Money: gl(n) in src/html.js prints <span class="gl">1,234<i></i></span>; the <i> is the glimmer shard (the pets' glim mark). */
.gl { white-space: nowrap; font-variant-numeric: tabular-nums; }
.gl > i {
  display: inline-block; width: .72em; height: .95em; margin-left: .18em; vertical-align: -.12em;
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='-6 -8 12 15'%3E%3Cpath d='M0 -7L5 -2L3 6L-3 6L-5 -2Z' fill='%238ff0ff' stroke='%232a1b4a' stroke-width='1.4' stroke-linejoin='round'/%3E%3Cpath d='M-5 -2H5M-2 -2L0 6L2 -2' fill='none' stroke='%233fb6d8' stroke-width='.8'/%3E%3Cpath d='M-3.6 -2L0 -5.6L-1.4 -2Z' fill='%23fff'/%3E%3C/svg%3E") center / contain no-repeat;
}
.coins .gl > i { width: .85em; height: 1.1em; }
/* Delight: Glimmers went up since the last page -> the pill pops, the coin flips, the gain floats off (tiny script in layout.js).
   The pill sits in the header, so the gain floats down into the page. */
.coins { position: relative; }
.coins-up { animation: coin-pop .45s var(--ease-out); }
.coins-up .gl > i { animation: coin-flip .7s var(--ease-out); }
.coins-up::after {
  content: attr(data-gain); position: absolute; right: 10px; bottom: -6px; pointer-events: none; opacity: 0;
  font: 700 var(--fs-sm) var(--display); color: var(--gain); text-shadow: 0 1px 0 #fff, 1px 0 0 #fff, -1px 0 0 #fff, 0 -1px 0 #fff;
  animation: coin-rise 1.6s var(--ease-out) .15s;
}
@keyframes coin-pop { 40% { transform: scale(1.07); } }
@keyframes coin-flip { to { transform: rotateY(360deg); } }
@keyframes coin-rise { 12%, 70% { opacity: 1; } to { opacity: 0; transform: translateY(22px); } }

/* ---------- Ad flyer: taped up at the foot of the page content ---------- */
.ad {
  --ad-hue: 300;
  position: relative; justify-self: center; width: min(100%, 620px); margin: 14px 0 4px;
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px 16px;
  padding: 18px 20px 16px;
  background: repeating-linear-gradient(135deg, hsl(var(--ad-hue) 100% 97%) 0 12px, hsl(var(--ad-hue) 100% 93%) 12px 24px); /* style-ok: color computed from a per-element custom property */
  border: 2px solid var(--ink); border-radius: 6px; box-shadow: var(--shadow-sm);
  rotate: -1deg;
}
.ad::before, .ad::after { /* two strips of tape */
  content: ''; position: absolute; top: -11px; width: 70px; height: 22px;
  background: color-mix(in srgb, var(--ui-ad-bg) 70%, transparent); border: 1px solid color-mix(in srgb, var(--ink) 20%, transparent); rotate: -7deg;
}
.ad::before { left: 22px; }
.ad::after { right: 22px; rotate: 5deg; }
.ad > svg { flex: none; }
.ad div { flex: 1 1 240px; min-width: 0; }
.ad small { display: block; font-size: var(--fs-xs); font-weight: 800; letter-spacing: .08em; text-transform: uppercase; color: var(--muted); }
.ad b { font: 700 1.15rem/1.2 var(--display); }
.ad p { font-size: var(--fs-sm); margin: 2px 0 0; }
.ad .pill { background: hsl(var(--ad-hue) 90% 80%); } /* style-ok: color computed from a per-element custom property */
.ad form { margin: 0; }
/* The CTA: a link to the place, or a button that answers back (public/shell.js swaps in data-ad-reply and wobbles the flyer) */
.ad .ad-cta { display: inline-flex; align-items: center; min-height: 44px; padding: 0 16px; color: var(--ink); text-decoration: none; cursor: pointer; }
span.ad-cta { cursor: default; }
/* A link CTA names where it goes in a white tag inside the pill; a CTA that only answers back is a dashed, unfilled tear-off. */
.ad .ad-go { flex-wrap: wrap; justify-content: center; gap: 4px 8px; padding-right: 6px; } /* a long place name drops under the words on a narrow phone */
.ad .ad-to { display: inline-flex; align-items: center; min-height: 32px; padding: 0 10px; background: var(--card); border: 2px solid var(--ink); border-radius: 999px;
  font: 700 var(--fs-xs) var(--display); text-align: left; }
.ad .ad-to > [aria-hidden] { margin-right: 4px; }
.ad button.ad-tap { background: transparent; border-style: dashed; }
@media (hover: hover) and (pointer: fine) { .ad :is(a, button).ad-cta:hover { box-shadow: var(--shadow-sm); } }
.ad.ad-wobble { animation: ad-wobble .5s var(--ease-out); }
@keyframes ad-wobble { 25% { transform: rotate(2deg) scale(1.02); } 60% { transform: rotate(-1.5deg); } }
/* Looks (src/data/ads.js kind), all the .ad footprint: a notice pinned on parchment, a lost poster with tear-off tabs, a coupon with a cut line */
.ad-notice { background: var(--parchment); border-radius: 0; rotate: .8deg; }
.ad-notice::after { display: none; }
.ad-notice::before { /* the pushpin */
  left: calc(50% - 10px); top: -10px; width: 20px; height: 20px; rotate: 0deg; border: 2px solid var(--ink); border-radius: 50%;
  background: radial-gradient(circle at 35% 35%, #fff 0 3px, var(--pink) 4px); /* style-ok: pin head art */
}
.ad-lost { background: var(--card); border-radius: 0; rotate: -.6deg; padding-bottom: 56px; }
.ad-lost b { text-transform: uppercase; letter-spacing: .04em; }
.ad-lost::before { left: calc(50% - 35px); rotate: 2deg; }
.ad-lost::after { /* the tear-off tabs: a dashed tear line over a row of strips */
  top: auto; bottom: 0; left: 0; right: 0; width: auto; height: 40px; rotate: 0deg; border: 0; border-top: 2px dashed var(--ink);
  background: repeating-linear-gradient(90deg, transparent 0 38px, var(--rule) 38px 40px);
}
.ad-coupon { background: hsl(var(--ad-hue) 100% 95%); border-radius: var(--r-sm); outline: 2px dashed var(--ink); outline-offset: -8px; rotate: .5deg; } /* style-ok: color computed from a per-element custom property */
.ad-coupon::before, .ad-coupon::after { display: none; }
/* The golden flyer: about 1 page in 200 (src/layout.js). Three glints twinkle a few times, then hold still */
.ad-golden { background: linear-gradient(135deg, var(--sun-soft), var(--sun) 55%, var(--sun-glow)); box-shadow: var(--shadow-sm), 0 0 18px var(--sun-glow); }
.ad-golden small { color: var(--ink); }
.ad-glints { position: absolute; bottom: 6px; left: 10px; width: 64px; height: 40px; color: var(--ink); pointer-events: none; }
.ad-glints path { transform-box: fill-box; transform-origin: 50% 50%; animation: ad-glint 1.4s var(--ease-out) var(--d) 4 backwards; }
@keyframes ad-glint { 0%, 100% { opacity: .2; transform: scale(.4) rotate(0deg); } 50% { opacity: 1; transform: scale(1) rotate(45deg); } }

/* ---------- Pet panel: a sticky sidebar card on wide screens, a strip above the content below 1000px (see Responsive) ---------- */
.pet-panel {
  display: grid; gap: 12px; padding: 14px; color: var(--ink); align-content: start;
  background: var(--card); border: var(--line) solid var(--ink); border-radius: var(--r-card); box-shadow: var(--shadow);
}
.pet-stage { /* the land-tinted backdrop, with a pedestal ellipse under the pet's feet */
  display: grid; place-items: center; padding: 14px 0 24px; border: 2px solid var(--ink); border-radius: var(--r-sm); position: relative;
  overflow: clip visible; /* sides clip the halo; the top stays open so a tall hat or balloon that reaches past the art box (petSvg) isn't cut off */
  background: radial-gradient(ellipse 60% 14% at 50% 86%, color-mix(in oklab, var(--land-accent) 55%, #fff) 0 98%, transparent), linear-gradient(180deg, var(--land-from), var(--land-to)); /* style-ok: pet stage spotlight art */
}
.pet-stage svg { position: relative; display: block; max-width: 100%; height: auto; }
/* petSvg's overflow="visible" also gave every pet svg a flex/grid min size of its full box (only clipped boxes get 0). In the phone
   pet circle that grew the grid row to 132px, so the art top-aligned and the circle showed the hat instead of the face. Back to 0. */
.pet-svg { min-width: 0; min-height: 0; }
/* The pet's glow (logic/pets.js glowOf): a halo over the pedestal, --glow 0-1. A sick pet's glow flickers, fizzes or storms. */
.pet-stage::before, .pet-stage.glow-stormy::after {
  content: ''; position: absolute; left: 50%; bottom: 4%; width: 150px; aspect-ratio: 1; translate: -50% 0; border-radius: 50%;
  background: radial-gradient(circle, rgb(255 250 205 / calc(var(--glow, 0) * .95)) 0 22%, rgb(143 240 255 / calc(var(--glow, 0) * .55)) 48%, transparent 70%); /* style-ok: color computed from a per-element custom property */
}
.glow-flickery::before { animation: glow-flicker 1.7s steps(1) infinite; }
.glow-fizzy::before { animation: glow-fizz .35s ease-in-out infinite alternate; }
/* Stormy: a gray halo with a flash of full color now and then. The gray copy (a still filter) and the colored copy (::after)
   swap by opacity; animating the filter itself repainted the halo every frame. At rest (reduced motion) only the gray one shows. */
.glow-stormy::before { filter: grayscale(.8) brightness(.8); animation: glow-storm-gray 4s steps(1) infinite; }
.glow-stormy:not(.is-clash, .is-pale)::after { opacity: 0; animation: glow-storm-flash 4s steps(1) infinite; }
:is(.pet-stage, .room-pet).glow-stormy { isolation: isolate; } /* their flash layer sits under the pet (z-index -1) */
:is(.pet-stage, .room-pet).glow-stormy::after { z-index: -1; }
:is(.pet-stage, .room-pet).glow-stormy::before { z-index: -1; }
.pc-stage:is(.is-clash, .is-pale).glow-stormy::before { animation: glow-storm 4s steps(1) infinite; } /* ::after is the clash pool here: the old flash */
@keyframes glow-flicker { 20%, 60% { opacity: .35; } 40%, 80% { opacity: 1; } }
@keyframes glow-fizz { to { scale: 1.08 .94; opacity: .7; } }
@keyframes glow-storm { 88% { filter: none; } 90% { filter: grayscale(.8) brightness(.8); } 93% { filter: none; } }
@keyframes glow-storm-gray { 88% { opacity: 0; } 90% { opacity: 1; } 93% { opacity: 0; } }
@keyframes glow-storm-flash { 88% { opacity: 1; } 90% { opacity: 0; } 93% { opacity: 1; } }
@keyframes glow-fade-out { to { opacity: 0; } } /* two glow layers crossfading: the old one out, */
@keyframes glow-fade-in { to { opacity: 1; } } /* the new one in (start it at opacity 0) */
.reduce-motion .pet-stage::before, .reduce-motion .pet-stage::after { animation: none; }
.pet-panel { position: relative; }
.pet-level { /* the level, on a little fallen star in the stage's corner */
  position: absolute; top: 20px; right: 20px; display: grid; place-items: center; width: 34px; height: 34px;
  font: 700 .85rem var(--display); color: var(--ink); background: var(--sun-glow);
  clip-path: polygon(50% 0, 63% 33%, 98% 36%, 71% 58%, 80% 94%, 50% 74%, 20% 94%, 29% 58%, 2% 36%, 37% 33%);
}
.pet-id { text-align: center; }
.pet-id p { margin: 0; font-size: var(--fs-sm); color: var(--muted); }
.pet-name { display: block; font: 700 1.3rem/1.2 var(--display); color: var(--ink); text-decoration: none; overflow-wrap: anywhere; }
.pet-name:hover { color: var(--ink); text-decoration: underline; }
.pet-id .pill-sick { margin-top: 6px; }
.pet-meters { list-style: none; margin: 0; padding: 10px 12px; display: grid; gap: 8px; background: var(--tint); border: 2px solid var(--ink); border-radius: var(--r-sm); }
.pet-meters li { display: grid; grid-template-columns: auto 1fr auto; align-items: center; column-gap: 6px; row-gap: 3px; color: var(--fill); font-size: var(--fs-sm); }
.pet-meters svg path { stroke: var(--ink); }
.pet-meters b { color: var(--ink); font: 700 var(--fs-sm) var(--display); }
.pet-meters span:not(.stat-bar) { color: var(--ink); font-weight: 700; text-align: right; }
.pet-meters .stat-bar { grid-column: 1 / -1; height: 11px; }
.pet-actions { display: grid; gap: 8px; }
.pet-actions .btn { text-align: center; }
.pet-switch { display: flex; flex-wrap: wrap; gap: 6px; justify-content: center; padding-top: 10px; border-top: 2px dashed var(--rule); }
.pet-switch { align-items: center; }
.pet-switch small { flex: 1 0 100%; text-align: center; font-weight: 800; color: var(--muted); }
.pet-switch form { margin: 0; }
.pet-switch button { display: block; padding: 0; line-height: 0; cursor: pointer; background: var(--card); border: 2px solid var(--ink); border-radius: 50%; box-shadow: var(--shadow-sm); }
.pet-switch button:hover { background: var(--hi); }
.pet-panel-empty { text-align: center; justify-items: center; background: var(--hi-soft); }
.pet-panel-empty b { font: 700 1.2rem var(--display); }
.pet-panel-empty p { margin: 0; }
.land-dark .pet-panel { color: var(--ink); } /* the card stays white in dark lands, so its text stays ink */
/* Desktop sidebar: compact enough that "Switch Glimling" shows without scrolling the card on a 1280x800 laptop. Smaller art,
   tighter bubble, meters and buttons. Narrower screens restyle it below. */
@media (min-width: 1000px) {
  .pet-panel { grid-template-columns: minmax(0, 1fr); gap: 8px; padding: 12px; } /* minmax: long names and looks wrap instead of widening the card */
  .pet-panel .pet-stage { padding: 16px 0 8px; }
  .pet-panel .pet-level { top: 16px; right: 16px; width: 30px; height: 30px; font-size: .8rem; }
  .pet-panel .pet-name { font-size: 1.15rem; }
  .pet-panel .pet-id p { text-wrap: balance; } /* wraps to two lines; cutting it off hid the glow word */
  .pet-panel .speech.pl-say { padding: 6px 10px; line-height: 1.35; } /* beats petlife.css, which loads later */
  .pet-panel .pet-meters { padding: 6px 10px; gap: 4px; }
  .pet-panel .pet-meters li { row-gap: 1px; }
  .pet-panel .pet-meters .stat-bar { height: 8px; }
  .pet-panel .pet-actions { gap: 6px; }
  .pet-panel .pet-actions .btn-small { min-height: 32px; padding-block: 4px; }
  /* One 40px line at every count: "Switch" and up to four 32px faces fit the 232px card, so the row stays above the 1280x800 fold */
  .pet-panel .pet-switch { flex-wrap: nowrap; gap: 4px; padding-top: 6px; }
  .pet-panel .pet-switch small { flex: 0 0 auto; margin-right: 4px; }
  .pet-panel .pet-switch .sw-more { display: none; } /* phones keep "Switch Glimling" */
  .pet-panel .pet-switch svg { width: 28px; height: 28px; }
}

/* ---------- Stat bars ---------- */
/* Word + bar rows on pet cards (the name is left over from the old sidebar). */
.side-stats { display: grid; grid-template-columns: auto 1fr; gap: 2px 10px; margin: 10px 0 12px; text-align: left; font-size: var(--fs-sm); }
.side-stats dt { font-weight: 800; }
.side-stats dd { margin: 0; text-align: right; }
.side-stats .stat-bar { grid-column: 1 / -1; margin-bottom: 6px; }
.stat-bar { display: block; height: 13px; border: 2px solid var(--ink); border-radius: 999px; background: var(--tint-2); overflow: hidden; }
.stat-bar > span {
  display: block; height: 100%; border-radius: 999px;
  background: linear-gradient(180deg, rgb(255 255 255 / .55) 0 35%, transparent 36%), var(--fill, var(--lime));
}
.stat-hunger { --fill: var(--ui-stat-hunger-fill); }
.stat-mood { --fill: var(--pink); }
.stat-hp { --fill: var(--ui-stat-hp-fill); }
.stat-xp { --fill: var(--sky); }
.stat-glow { --fill: var(--ui-stat-glow-fill); }

/* ---------- Grids and tiles ---------- */
.grid { display: grid; gap: 14px; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); }
.grid-items { grid-template-columns: repeat(auto-fill, minmax(124px, 1fr)); }
.grid-pets { grid-template-columns: repeat(auto-fill, minmax(210px, 1fr)); }
/* Desktop: the Hatchery's five spots fit one row (a fifth Glimling sat alone on a second row); the card's Visit and Wardrobe stack.
   The stack selector carries .pet-card so it outranks the row rule further down (.pet-card .pet-card-foot .btn-row), which won before.
   /glimlings (.pets-roll, no pet panel) always has five even columns, so two Glimlings get cards the size of five, not two stretched ones. */
@media (min-width: 1024px) {
  .grid-pets { grid-template-columns: repeat(auto-fill, minmax(156px, 1fr)); gap: 12px; }
  .grid-pets.pets-roll { grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 14px; }
  .grid-pets .pet-card { padding-inline: 8px; } /* 8px more room, so a 20-letter name fits a 171px card at its smallest size */
  .grid-pets .pet-card .pet-card-foot .btn-row { flex-direction: column; align-items: stretch; }
  .grid-pets .pet-card .pet-card-foot .btn-row > * { width: 100%; }
}
.grid-species { grid-template-columns: repeat(auto-fill, minmax(104px, 1fr)); gap: 10px; }
.grid-places { grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); }
.grid-2 { grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: var(--gap); }
.grid-3 { grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: var(--gap); }
.item-tile {
  position: relative; display: flex; flex-direction: column; align-items: center; gap: 2px;
  padding: 12px 8px 10px; text-align: center; width: 100%;
  font: inherit; color: var(--ink); text-decoration: none;
  background: var(--card); border: 2px solid var(--ink); border-radius: var(--r-tile);
  box-shadow: var(--shadow-sm); cursor: pointer;
}
/* One hover lift for every clickable sticker: tiles, places, lots, daily and game cards */
a.item-tile, button.item-tile, .place, .lot, .daily-card {
  transition: transform .15s var(--ease-out), box-shadow .15s var(--ease-out);
}
@media (hover: hover) and (pointer: fine) {
  a.item-tile:hover, button.item-tile:hover, .place:hover, .lot:hover { transform: translate(-2px, -2px); box-shadow: var(--shadow-lift); color: var(--ink); }
  a.daily-card:hover { transform: translate(-2px, -2px); box-shadow: 8px 8px 0 var(--ink); color: var(--ink); }
}
a.item-tile:active, button.item-tile:active, .place:active, .lot:active, a.daily-card:active { transform: translate(1px, 1px); box-shadow: 1px 1px 0 var(--ink); }
.item-tile svg { display: block; }
.item-tile b { font-weight: 800; line-height: 1.25; font-size: var(--fs-sm); }
.item-tile small { color: var(--muted); font-size: var(--fs-xs); }
.item-tile .qty { position: absolute; top: 6px; right: 8px; font: 700 var(--fs-xs) var(--display); background: var(--sun); border: 2px solid var(--ink); border-radius: 999px; padding: 0 8px; }
.item-tile .price { font: 700 1rem var(--display); color: var(--price); }
.item-tile form { margin-top: 4px; }
.item-use .btn { margin-top: auto; }
.item-use b { margin-bottom: 6px; }
.item-use { cursor: default; }
.item-cell { display: grid; } /* backpack tiles in a row share one height */

/* Item popover (native [popover], no JS) */
.item-pop { width: min(440px, calc(100vw - 32px)); margin: auto; color: var(--ink); overflow: visible; }
.item-pop::backdrop { background: color-mix(in srgb, var(--ink) 40%, transparent); }
.item-pop-head { display: flex; gap: 14px; align-items: center; margin-bottom: 8px; }
.item-pop-head h3 { margin: 0 0 4px; font-size: 1.35rem; }
.item-pop-actions { display: flex; flex-wrap: wrap; gap: 8px; align-items: end; margin-top: 12px; padding-top: 12px; border-top: 2px dashed var(--rule); }
.item-pop-actions label { font-weight: 700; display: grid; font-size: var(--fs-sm); }
.item-pop-actions input { width: 5.5em; }
.pop-close { position: absolute; top: -14px; right: -14px; width: 40px; height: 40px; border-radius: 50%; border: var(--line) solid var(--ink); background: var(--pink); color: #fff; font: 700 1.4rem/1 var(--display); cursor: pointer; box-shadow: 0 3px 0 var(--ink); }

/* ---------- Pet card ---------- */
/* petCard() in src/layout.js. The pet stands on its backdrop (src/art/habitats.js: the Look Book backdrop picked for it, else
   its species' habitat) in a framed stage with its level on a star; under it the name, look + species, glow and quirk chips,
   the pocket critter and any illness, then the bars. The card is a size container so the name can shrink with the card. */
.pet-card {
  container-type: inline-size;
  background: var(--card); border: var(--line) solid var(--ink); border-radius: var(--r-card);
  box-shadow: var(--shadow-sm); padding: 12px 12px 14px; text-align: center;
}
.pc-stage {
  position: relative; display: grid; place-items: end center; height: 150px; padding-bottom: 2px;
  border: 2px solid var(--ink); border-radius: var(--r-sm); background: var(--tint);
}
.pc-bd { position: absolute; inset: 0; z-index: 0; display: block; width: 100%; height: 100%; object-fit: cover; object-position: center bottom; border-radius: 10px; }
/* the pet's glow halo over the backdrop, as on the sidebar's stage (.glow-* make it flicker or fizz for a sick pet) */
.pc-stage::before, .pc-stage.glow-stormy:not(.is-clash, .is-pale)::after {
  content: ''; position: absolute; z-index: 1; left: 50%; bottom: 2%; width: 140px; aspect-ratio: 1; translate: -50% 0; border-radius: 50%;
  background: radial-gradient(circle, rgb(255 250 205 / calc(var(--glow, 0) * .9)) 0 22%, rgb(143 240 255 / calc(var(--glow, 0) * .5)) 48%, transparent 70%); /* style-ok: color computed from a per-element custom property */
}
/* A look that nearly matches its habitat (a green Fluffkin on the meadow, a Velvet Starfling on the night sky; clashOf in
   src/art/habitats.js): a pale pool of light behind the pet and a white die-cut edge, like a sticker's, so it never melts in. */
.pc-stage.is-clash::after {
  content: ''; position: absolute; z-index: 1; left: 50%; bottom: 0; width: 150px; height: 120px; translate: -50% 0; border-radius: 50%;
  background: radial-gradient(closest-side, rgb(255 255 255 / .8), rgb(255 255 255 / .45) 60%, rgb(255 255 255 / 0));
}
.pc-stage.is-clash .pet-svg { filter: drop-shadow(0 0 1px #fff) drop-shadow(0 0 1px #fff) drop-shadow(0 0 1px #fff); }
.pc-stage.is-pale::after { background: radial-gradient(closest-side, color-mix(in srgb, var(--ink) 16%, transparent), color-mix(in srgb, var(--ink) 6%, transparent) 60%, transparent); }
.pc-stage.is-pale .pet-svg { filter: drop-shadow(0 0 1px color-mix(in srgb, var(--ink) 45%, transparent)); }
.pc-stage .pet-card-art { position: relative; z-index: 2; }
.pet-card-art { display: block; }
.pet-card-art svg { display: block; margin: 0 auto; }
.pc-stage .pet-svg { max-width: 100%; height: auto; }
/* The level on a fallen star, like the sidebar's .pet-level, with an ink edge so it reads on a yellow or a night backdrop.
   A chubby star (inner radius .57 of the outer) so two digits fit inside the ink at 34px; the 5% top padding moves the
   number onto the star's middle (y 21.2 of 40), not the box's. The thin star let "3" touch the edge on a phone.
   The nudge is in px: a % padding is a share of the stage's width (330px on desktop), which sank the number 16px. */
.pc-level {
  position: absolute; z-index: 3; top: 4px; right: 4px; display: grid; place-items: center; width: 40px; height: 40px; padding-top: 2px;
  font: 700 .85rem/1 var(--display); color: var(--ink); letter-spacing: -.02em;
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 40 40'%3E%3Cpath d='M20 2.6L26.2 12.6L37.7 15.5L30.1 24.5L30.9 36.2L20 31.8L9.1 36.2L9.9 24.5L2.3 15.5L13.8 12.6Z' fill='%23ffe680' stroke='%232a1b4a' stroke-width='2.6' stroke-linejoin='round'/%3E%3C/svg%3E") center / contain no-repeat;
}
.pc-level.pc-level-2 { font-size: .74rem; letter-spacing: -.05em; } /* two digits (levels 10-30) fit inside the ink too */
/* Name: never splits mid-word. nameHtml() puts a <wbr> after each "_", the only break; the size shrinks with the name's
   length (--len, at least 8) against the card's width, from 1.3rem down to .95rem (15.2px); overflow-wrap is a last resort.
   1.6: the size at which the widest name per letter measured (Pummet_Daibun, 116px at 15.2px, needs 1.7) still fits in one line. */
.pet-card h3 {
  margin: 8px 0 0; line-height: 1.15; letter-spacing: -.01em; text-wrap: balance; overflow-wrap: anywhere;
  font-size: clamp(.95rem, calc(100cqi / var(--len, 8) * 1.6), 1.3rem);
}
.pet-card h3.pc-solid { hyphens: auto; }
.pet-card h3 a { color: var(--ink); text-decoration: none; }
@media (hover: hover) and (pointer: fine) { .pet-card h3 a:hover { text-decoration: underline; } }
.pc-look { margin: 2px 0 0; font-size: var(--fs-sm); font-weight: 700; color: var(--muted); text-wrap: balance; }
.pc-chips { display: flex; flex-wrap: wrap; justify-content: center; gap: 4px; margin: 8px 0 0; }
.pc-chip { display: inline-flex; align-items: center; gap: 4px; padding: 0 8px; font-size: .78rem; white-space: nowrap; background: var(--tint); }
.pc-glow i { flex: none; width: 10px; height: 10px; border: 2px solid var(--ink); border-radius: 50%;
  background: color-mix(in oklab, var(--ui-stat-glow-fill) calc(var(--glow, 0) * 100%), #fff); } /* style-ok: glow meter fill */
.pc-row { margin: 6px 0 0; }
.pc-row .pill { margin: 0; }
.pc-critter { padding-left: 4px; }
.pc-critter svg { flex: none; }
/* The level star beside a pet's name (src/art/pet.js levelStarSvg): always numbered, so it never reads as the Glimmers shard */
.lv-star { display: inline-block; vertical-align: -.3em; margin-left: .3em; flex: none; }
/* Level card on the pet page: bar, today's XP per source against its cap, next gift and unlock */
.pet-xp h2 { display: flex; align-items: center; gap: 8px; }
.pet-xp h2 .lv-star { margin: 0; }
.pet-xp h3 { font-size: 1.05rem; margin: 14px 0 6px; }
.pet-xp-bar { display: grid; gap: 4px; font-weight: 700; }
.pet-xp-today { list-style: none; margin: 0 0 12px; padding: 0; display: grid; grid-template-columns: repeat(auto-fill, minmax(min(100%, 170px), 1fr)); gap: 6px 12px; }
.pet-xp-today li { display: flex; justify-content: space-between; gap: 8px; padding: 6px 10px; background: var(--tint); border: 2px solid var(--ink); border-radius: var(--r-sm); font-size: var(--fs-sm); }
.pet-xp-today .is-full { background: var(--ok-bg); }
@media (max-width: 560px) { .pet-xp h2 { justify-content: center; } .pet-xp h3, .pet-xp-bar span { text-align: center; } }
.pet-card .muted { margin: 0; }
.pet-card .btn-row { justify-content: center; }
.pet-card .btn-row + .btn-row { margin-top: 12px; }
.pet-card-hp { font-size: var(--fs-sm); font-weight: 700; text-align: left; }
/* The bars (and on /glimlings the buttons) sit at the card bottom so every card in a row lines up; two even rows (Make active or the Active pill, then Visit + Wardrobe) */
.grid-pets .pet-card, .pet-card:has(.pet-card-foot) { display: flex; flex-direction: column; }
.pet-card > .side-stats:first-of-type { padding-top: 12px; }
.grid-pets .pet-card > .side-stats:first-of-type, .pet-card:has(.pet-card-foot) > .side-stats:first-of-type { margin-top: auto; }
.pet-card-foot > .pet-card-hp { margin-top: -8px; } /* Health reads as the third bar, as when it sat right after Mood */
.pet-card .pet-card-foot .btn-row { flex-direction: row; flex-wrap: nowrap; align-items: stretch; }
.pet-card-foot .btn-row > :is(.btn, form, .pill) { flex: 1 1 0; min-width: 0; }
.pet-card-foot .btn { padding-inline: 8px; } /* "Wardrobe" fits half a 210px card, so both halves stay even */
.pet-card-foot form > .btn { width: 100%; }
.pet-card-foot .btn, .pet-card-foot .pill { justify-content: center; }
.pet-card-foot .pill { display: flex; align-items: center; min-height: 36px; margin: 0; }
/* Phones: the card turns sideways, the stage on the left and the name and chips on the right, so five cards take
   about 2,200px of scroll instead of 2,600px. The bars and buttons run full width underneath. */
@media (max-width: 560px) {
  .grid-pets .pet-card { display: grid; grid-template-columns: 116px minmax(0, 1fr); column-gap: 12px; align-items: center; text-align: left; }
  .grid-pets .pet-card > .pc-stage { grid-column: 1; grid-row: 1; height: 116px; }
  .grid-pets .pc-stage .pet-svg { width: 104px; }
  .grid-pets .pc-stage .pc-level { top: -8px; right: -8px; width: 34px; height: 34px; font-size: .78rem; }
  .grid-pets .pc-stage .pc-level.pc-level-2 { font-size: .66rem; }
  .grid-pets .pc-stage::before { width: 100px; }
  .grid-pets .pc-stage.is-clash::after { width: 110px; height: 96px; }
  .grid-pets .pet-card > .pc-id { grid-column: 2; grid-row: 1; }
  .grid-pets .pet-card > :not(.pc-stage, .pc-id) { grid-column: 1 / -1; }
  .grid-pets .pet-card h3 { margin-top: 0; font-size: clamp(.95rem, calc((100cqi - 128px) / var(--len, 8) * 1.6), 1.3rem); }
  .grid-pets .pc-chips { justify-content: flex-start; }
  .grid-pets .pet-card .pc-row { text-align: left; } /* beats the phone rule that centers a lone pill's paragraph */
  .grid-pets .pet-card > .side-stats:first-of-type { margin-top: 4px; }
}
/* Narrow phones (320px): the column beside the stage is too narrow for a 20-letter name, so the name runs across the top. */
@media (max-width: 374px) {
  .grid-pets .pet-card { grid-template-columns: 96px minmax(0, 1fr); }
  .grid-pets .pet-card > .pc-id { display: contents; }
  .grid-pets .pet-card h3 { grid-column: 1 / -1; grid-row: 1; margin-bottom: 8px; font-size: clamp(.95rem, calc(100cqi / var(--len, 8) * 1.6), 1.3rem); }
  .grid-pets .pet-card > .pc-stage { grid-row: 2; height: 104px; }
  .grid-pets .pc-meta { grid-column: 2; grid-row: 2; }
  .grid-pets .pc-meta .pc-look { margin-top: 0; }
  .grid-pets .pc-stage .pet-svg { width: 88px; }
}

/* ---------- Tables ---------- */
.table { width: 100%; border-collapse: separate; border-spacing: 0; border: 2px solid var(--ink); border-radius: var(--r-tile); overflow: hidden; }
.table th, .table td { padding: 10px 12px; text-align: left; vertical-align: middle; }
.table thead th { background: var(--ink); box-shadow: 1px 0 0 var(--ink); color: #fff; /* the shadow covers the hairline seam between fractional-width cells */ font: 600 .98rem var(--display); }
.table tbody tr:nth-child(even) { background: var(--tint); }
.table tbody tr + tr > * { border-top: 1px solid var(--rule); }
.table { font-variant-numeric: tabular-nums; }
.table td:has(> svg:only-child) { width: 1%; } /* item icon columns hug their art */
.table-wrap { overflow-x: auto; position: relative; } /* relative: keeps absolute .visually-hidden labels inside the scroller */
.table-wrap .table { min-width: 460px; }
:where(.card, form) > :is(.grid, .table, .table-wrap) + * { margin-top: 14px; } /* breathing room under a grid or table, before its footer link/buttons */
.table .row-hi { background: var(--hi) !important; font-weight: 800; }
.table svg { display: block; max-width: none; }

/* ---------- Forms ---------- */
label { font-weight: 700; }
:where(input[type=checkbox], input[type=radio]) { accent-color: var(--grape); inline-size: 1.15em; block-size: 1.15em; vertical-align: middle; }
input:not([type=radio], [type=checkbox], [type=hidden]), select, textarea {
  display: block; width: 100%; margin-top: 4px;
  font: 600 1rem var(--body); color: var(--ink);
  padding: 10px 14px; background: var(--card);
  border: var(--line) solid var(--ink); border-radius: var(--r-sm);
  box-shadow: inset 0 3px 0 var(--tint-2);
}
select { overflow: hidden; text-overflow: ellipsis; } /* Safari counts a long option's text past the box as page width: the sideways wiggle on phones */
select { -webkit-appearance: none; appearance: none; padding-right: 40px; /* Safari's native menulist ignores padding and draws 31px tall; this one is 44px+ like Chrome's */
  background: var(--card) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5l5 5 5-5' fill='none' stroke='%232a1b4a' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") no-repeat right 14px center / 12px 8px; }
::placeholder { color: var(--card-muted); opacity: 1; } /* fields are always card-white; WebKit's own #a9a9a9 is 2.35:1 */
input:focus-visible, select:focus-visible, textarea:focus-visible { outline: 3px solid var(--ink); outline-offset: 2px; box-shadow: inset 0 3px 0 var(--tint-2), 0 0 0 8px var(--sky); }
fieldset { border: 2px dashed var(--rule); border-radius: var(--r-tile); padding: 12px 14px 14px; margin: 0 0 16px; min-width: 0; }
legend { font: 600 1.15rem var(--display); padding: 0 6px; }
.inline-form, .search-row { display: flex; gap: 8px; align-items: end; flex-wrap: wrap; }
.inline-form label { display: grid; font-size: var(--fs-sm); }
.inline-form input { width: 5.5em; margin: 0; }
.inline-form input:where(:not([type]), [type=text]) { width: 12em; max-width: 100%; } /* names and items, not quantities */
.search-row { margin-bottom: 14px; }
.search-row label { flex: 1 1 200px; }
.visually-hidden, .pick input, .swatch input { position: absolute; opacity: 0; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }

/* Radio "sticker" pickers */
.pick {
  position: relative; display: grid; justify-items: center; gap: 2px; padding: 8px 6px;
  border: 2px solid var(--ink); border-radius: var(--r-tile); background: var(--card); cursor: pointer;
  font: 600 var(--fs-sm) var(--display); box-shadow: var(--shadow-sm);
}
.pick:has(input:checked) { background: var(--hi); box-shadow: 0 0 0 3px var(--pink), var(--shadow-sm); }
.pick:has(input:focus-visible), .swatch:has(input:focus-visible) { outline: 3px solid var(--ink); outline-offset: 3px; box-shadow: 0 0 0 9px var(--sky); }
.pick:has(input:focus-visible) { box-shadow: var(--shadow-sm), 0 0 0 9px var(--sky); }
.pick:has(input:checked):has(input:focus-visible) { box-shadow: 0 0 0 3px var(--pink), var(--shadow-sm), 0 0 0 9px var(--sky); }
.pick-row { display: flex; flex-wrap: wrap; gap: 10px; }
.swatch {
  --swatch: #fff;
  position: relative; display: inline-flex; align-items: center; gap: 8px; cursor: pointer;
  padding: 6px 16px 6px 8px; border: 2px solid var(--ink); border-radius: 999px; background: var(--card); font: 600 1rem var(--display);
}
.swatch::before { content: ''; width: 24px; height: 24px; border-radius: 50%; background: var(--swatch); border: 2px solid var(--ink); }
.swatch:has(input:checked) { background: var(--hi); box-shadow: 0 0 0 3px var(--pink); }
/* Non-color swatches (difficulty, gender) have a plain white dot, so give the chosen one a filled center. */
.swatch:not([style]):has(input:checked)::before { background: var(--ink); box-shadow: inset 0 0 0 5px #fff; } /* style-ok: ring inside a color swatch */
.pick > span { text-align: center; } /* long names wrap centered, like the caption under them */

/* ---------- Auth ---------- */
.auth-wrap { display: grid; place-items: center; padding: 12px 0; }
.auth-card { width: min(440px, 100%); display: grid; gap: 14px; }
.auth-card h1, .auth-card p { margin: 0; }

/* ---------- Pets ---------- */
.create-form { display: grid; gap: 4px; }
.create-preview { display: grid; justify-items: center; gap: 4px; margin-bottom: 12px; padding: 12px; border-radius: var(--r-card);
  background: radial-gradient(ellipse 60% 55% at 50% 45%, #fff, color-mix(in oklab, var(--land-to) 60%, #fff)); border: 2px solid var(--ink); } /* style-ok: pet art plate */
.pet-profile { display: grid; grid-template-columns: minmax(0, 300px) minmax(0, 1fr); gap: 24px; align-items: start; }
.pet-profile-art { position: relative; display: grid; justify-items: center; padding: 10px; border-radius: var(--r-card);
  background: radial-gradient(ellipse 65% 50% at 50% 42%, #fff, color-mix(in oklab, var(--land-from) 80%, #fff)); border: 2px solid var(--ink); } /* style-ok: pet art plate */
.pet-petpet { position: absolute; right: 6px; bottom: 6px; display: grid; justify-items: center; font-weight: 800; }
.stats-table { margin: 8px 0 14px; }
.stats-table th { width: 32%; }
.stats-table td:last-child { width: 38%; }

/* ---------- Shops ---------- */
.shop-head { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 16px; align-items: center; }
.keeper { display: flex; align-items: center; gap: 4px; }
.keeper svg { flex: none; }
.speech {
  position: relative; background: var(--tint); border: 2px solid var(--ink); border-radius: 18px; padding: 10px 14px; max-width: 280px;
  animation: speech-in .3s var(--ease-out) .12s both; transform-origin: 0 40%;
}
/* Delight: every keeper line is freshly said: the keeper gives a little nod as the bubble pops out beside them */
.keeper > svg { transform-origin: 50% 100%; animation: keeper-nod .5s var(--ease-out); }
@keyframes speech-in { from { opacity: 0; transform: scale(.88) translateX(-8px); } }
@keyframes keeper-nod { 45% { transform: rotate(-4deg) translateY(-4px); } }
.speech::before { content: ''; position: absolute; left: -11px; top: 22px; border: 10px solid transparent; border-right-color: var(--ink); border-left: 0; }
.speech p { margin: 2px 0 0; }
/* Scene banners (src/art/scene.js): a full-bleed illustrated strip across the top of a shop's or daily's head card.
   The svg crops its sides below 560px wide. Only the classes below move; both reduced-motion rules stop them. */
.card > .scene { grid-column: 1 / -1; margin: calc(-1 * clamp(16px, 2.4vw, 24px)) calc(-1 * clamp(16px, 2.4vw, 24px)) 0;
  border-bottom: var(--line) solid var(--ink); border-radius: calc(var(--r-card) - var(--line)) calc(var(--r-card) - var(--line)) 0 0; overflow: hidden; }
.card:not(.shop-head) > .scene { margin-bottom: 16px; } /* .shop-head is a grid; its gap already spaces it */
.scene > :is(svg, img) { display: block; width: 100%; aspect-ratio: 4; min-height: 140px; }
.scene .sway { transform-origin: 50% 100%; animation: scene-sway 3.8s ease-in-out infinite alternate; }
.scene .bob { animation: scene-bob 2.4s ease-in-out infinite alternate; }
.scene .drift { animation: scene-drift 16s ease-in-out infinite alternate; }
.scene .twinkle { transform-origin: 50% 50%; animation: scene-twinkle 1.8s ease-in-out infinite alternate; }
.scene .steam { animation: scene-steam 2.8s ease-out infinite; }
/* Keep the delay after the animation shorthands, which otherwise reset it to zero. */
.scene :is(.sway, .bob, .drift, .twinkle, .steam) { transform-box: fill-box; animation-delay: var(--d, 0s); }
/* These sit on SVG parts, so they animate transform: Chrome runs scale/translate/rotate on any SVG element on the main thread. */
@keyframes scene-sway { from { transform: rotate(-3deg); } to { transform: rotate(3deg); } }
@keyframes scene-bob { to { transform: translate(0, -5px); } }
@keyframes scene-drift { to { transform: translate(28px, 0); } }
/* A twinkle can contain a whole constellation or an attached highlight. Keep it in place. */
@keyframes scene-twinkle { to { opacity: .45; } }
@keyframes scene-steam { from { opacity: .9; transform: translate(0, 4px); } to { opacity: 0; transform: translate(0, -14px); } }
/* Banner layers (src/art/layers.js). Moving bits inside one big SVG repaint the whole SVG on the main thread every frame, so
   sceneSvg and landBannerHtml lift each moving piece into its own small <svg> (or a <div> frame when pieces move inside it),
   stacked over the still base in paint order. The animation sits on that outer box, so the GPU runs it. The stage is the
   viewBox, sized like preserveAspectRatio="xMidYMid slice" (cover, centered), and every layer is placed in % of it.
   --U is one banner unit in px; --ma..--md carry a piece's own transform (a scaled pet, a mirrored prop), --sg its mirroring.
   The rules above stay for anything still drawn inside the SVG. Both reduced-motion rules and the battery pause stop these too. */
.scene { position: relative; }
.fx-box { position: absolute; inset: 0; clip-path: inset(0); pointer-events: none; container-type: size; } /* clip-path, not overflow: the battery observer still sees a piece that drifts in from the cropped edge */
.fx-stage { position: absolute; left: 50%; top: 50%; width: max(100cqw, 100cqh * var(--vw) / var(--vh)); aspect-ratio: var(--vw) / var(--vh); translate: -50% -50%; container-type: inline-size; }
.fx-stage .fx { position: absolute; display: block; margin: 0; min-width: 0; max-width: none; min-height: 0; max-height: none; overflow: visible; --U: calc(100cqw / var(--vw)); } /* beats img, svg { max-width: 100% }, which squeezed wide layers */
.fx-stage div.fx > svg { display: block; width: 100%; height: 100%; max-width: none; overflow: visible; } /* a moving piece's art fills its <div> */
.fx-sway { animation: fx-sway 3.8s ease-in-out var(--d, 0s) infinite alternate; }
.fx-bob { animation: fx-bob 2.4s ease-in-out var(--d, 0s) infinite alternate; }
.fx-drift { animation: fx-drift 16s ease-in-out var(--d, 0s) infinite alternate; }
.fx-twinkle { animation: fx-twinkle 1.8s ease-in-out var(--d, 0s) infinite alternate; }
.fx-steam { animation: fx-steam 2.8s ease-out var(--d, 0s) infinite; }
@keyframes fx-sway { from { rotate: calc(-3deg * var(--sg, 1)); } to { rotate: calc(3deg * var(--sg, 1)); } }
@keyframes fx-bob { to { translate: calc(-5 * var(--mc, 0) * var(--U)) calc(-5 * var(--md, 1) * var(--U)); } }
@keyframes fx-drift { to { translate: calc(28 * var(--ma, 1) * var(--U)) calc(28 * var(--mb, 0) * var(--U)); } }
@keyframes fx-twinkle { to { opacity: .45; } }
@keyframes fx-steam { from { opacity: .9; translate: calc(4 * var(--mc, 0) * var(--U)) calc(4 * var(--md, 1) * var(--U)); } to { opacity: 0; translate: calc(-14 * var(--mc, 0) * var(--U)) calc(-14 * var(--md, 1) * var(--U)); } }
/* land banners (src/art/land-scenes.js; the same motion as its STYLE, which still drives the standalone banner images) */
.land-banner { position: absolute; inset: 0; }
.land-banner > svg { display: block; width: 100%; height: 100%; }
.fx-lb-sway { animation: fx-sway 4s ease-in-out var(--d, 0s) infinite alternate; }
.fx-lb-bob { animation: fx-lb-bob 2.6s ease-in-out var(--d, 0s) infinite alternate; }
.fx-lb-drift { animation: fx-lb-drift 18s ease-in-out var(--d, 0s) infinite alternate; }
.fx-lb-twinkle { animation: fx-lb-twinkle 1.8s ease-in-out var(--d, 0s) infinite alternate; }
.fx-lb-blink { animation: fx-lb-blink 2.4s ease-in-out var(--d, 0s) infinite alternate; }
.fx-lb-flicker { animation: fx-lb-flicker 1.1s ease-in-out var(--d, 0s) infinite alternate; }
.fx-lb-steam { animation: fx-lb-steam 3s ease-out var(--d, 0s) infinite; }
.fx-lb-wobble { animation: fx-lb-wobble 2.2s ease-in-out var(--d, 0s) infinite alternate; }
.fx-lb-rise { animation: fx-lb-rise 16s linear var(--d, 0s) infinite; }
.fx-lb-fall { animation: fx-lb-fall 16s linear var(--d, 0s) infinite; }
.fx-lb-shoot { animation: fx-lb-shoot 7s ease-in var(--d, 0s) infinite; }
.fx-lb-spin { animation: fx-lb-spin 16s linear var(--d, 0s) infinite; }
.fx-lb-spin-back { animation: fx-lb-spin 9.4s linear var(--d, 0s) infinite reverse; }
@keyframes fx-lb-bob { to { translate: calc(-6 * var(--mc, 0) * var(--U)) calc(-6 * var(--md, 1) * var(--U)); } }
@keyframes fx-lb-drift { to { translate: calc(36 * var(--ma, 1) * var(--U)) calc(36 * var(--mb, 0) * var(--U)); } }
@keyframes fx-lb-twinkle { to { opacity: .35; scale: .7; } }
@keyframes fx-lb-blink { to { opacity: .35; } }
@keyframes fx-lb-flicker { to { opacity: .6; } }
@keyframes fx-lb-steam { from { opacity: .9; translate: calc(6 * var(--mc, 0) * var(--U)) calc(6 * var(--md, 1) * var(--U)); } to { opacity: 0; translate: calc(-30 * var(--mc, 0) * var(--U)) calc(-30 * var(--md, 1) * var(--U)); } }
@keyframes fx-lb-wobble { to { scale: 1.03 .97; } }
@keyframes fx-lb-rise { to { translate: calc(-360 * var(--mc, 0) * var(--U)) calc(-360 * var(--md, 1) * var(--U)); } }
@keyframes fx-lb-fall { to { translate: calc(360 * var(--mc, 0) * var(--U)) calc(360 * var(--md, 1) * var(--U)); } }
@keyframes fx-lb-spin { to { rotate: calc(360deg * var(--sg, 1)); } }
@keyframes fx-lb-shoot { 0% { opacity: 0; translate: 0 0; } 8% { opacity: 1; } 30%, 100% { opacity: 0; translate: calc((-260 * var(--ma, 1) + 130 * var(--mc, 0)) * var(--U)) calc((-260 * var(--mb, 0) + 130 * var(--md, 1)) * var(--U)); } }
/* stickers and pets standing in a banner: the same loops as .holo-sweep / .holo-twinkle below and the idle pet rules */
.fx-holo-sweep { opacity: .75; animation: fx-holo-sweep 7s ease-in-out var(--holo, 0s) infinite; }
body:has(.sky-charm.sky-night) .fx-holo-sweep { opacity: 1; }
.fx-holo-twinkle { animation: fx-holo-twinkle 7s ease-in-out calc(var(--holo, 0s) + var(--holo-i, 0) * .3s) infinite; }
.fx-pet-idle { animation: fx-pet-idle 3.6s ease-in-out var(--idle, 0s) infinite alternate; }
.fx-pet-eyes { animation: fx-pet-eyes 5.3s linear calc(var(--idle, 0s) * 1.7) infinite; }
.fx-pet-glint { animation: fx-pet-glint 5s ease-in-out calc(var(--idle, 0s) * 1.25) infinite; }
.fx-gl-fk1 { animation: gl-fk 2.2s ease-in-out calc(var(--idle, 0s) * .9) infinite; }
.fx-gl-fk2 { animation: gl-fk 3.1s ease-in-out calc(var(--idle, 0s) * 1.2) infinite; }
.fx-gl-fk3 { animation: gl-fk 3.9s ease-in-out calc(var(--idle, 0s) * 1.5) infinite; }
@keyframes fx-holo-sweep { 0%, 55% { translate: 0 0; } 100% { translate: calc(200 * var(--ma, 1) * var(--U)) calc(200 * var(--mb, 0) * var(--U)); } }
@keyframes fx-holo-twinkle { 0%, 62%, 100% { scale: 1; rotate: 0deg; opacity: 1; } 78% { scale: .35; rotate: calc(45deg * var(--sg, 1)); opacity: .4; } }
@keyframes fx-pet-idle { to { scale: .99 1.025; } }
@keyframes fx-pet-eyes { 0%, 95%, 100% { scale: 1; } 97.5% { scale: 1 .12; } }
@keyframes fx-pet-glint { 0% { translate: 0 0; } 35%, 100% { translate: calc(42 * var(--ma, 1) * var(--U)) calc(42 * var(--mb, 0) * var(--U)); } }
.haggle { display: grid; gap: 18px; }
.haggle-item { display: flex; gap: 18px; align-items: center; flex-wrap: wrap; }
.haggle-item h1 { margin-bottom: 6px; }
.haggle-item .pill { margin-bottom: 10px; }
.haggle-item svg { flex: none; }
.haggle-item > div { flex: 1 1 240px; }
.haggle-form { display: flex; flex-wrap: wrap; gap: 12px; align-items: end; }
.haggle-form label { flex: 0 1 240px; }
.haggle-form input { min-height: 54px; font-size: 1.15rem; } /* same height as the btn-big beside it */

/* ---------- Bank ---------- */
.bank-figures { display: grid; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); gap: 12px; margin: 14px 0; }
.bank-figures > div { display: grid; border: 2px solid var(--ink); border-radius: var(--r-tile); padding: 12px 16px; background: var(--tint); }
.bank-figures small { font-weight: 800; color: var(--muted); }
.bank-figures b { font: 700 1.6rem var(--display); }
.interest-box { border: var(--line) dashed var(--ink); border-radius: var(--r-tile); padding: 14px 18px; background: var(--hi-soft); }
.interest-box p { margin: 0 0 8px; }
.grid-2 > form { display: grid; gap: 10px; align-content: start; }
.grid-2 > form h2 { margin: 0; }

/* ---------- World (/world: map + popover, passport, land cards) ---------- */
.world-map { display: block; width: 100%; height: auto; border: var(--line) solid var(--ink); border-radius: var(--r-card); overflow: hidden; } /* the sea is square; the frame rounds it at every size */
.map-wrap { position: relative; margin: 6px 0 0; container-type: inline-size; } /* the map's own phone labels ask this container how wide it is */
.map-land, .map-mark { cursor: pointer; }
.map-landmark { transition: transform .2s cubic-bezier(.3, 1.6, .5, 1); transform-box: fill-box; transform-origin: center bottom; }
.map-mark:hover .map-landmark { transform: scale(1.12) translateY(-4px); } /* a label lifts its landmark from inside the map's own style (:has) */
.map-land:focus-visible { outline: none; }
.map-land:focus-visible rect:not(.wm-hit) { stroke: var(--sun); stroke-width: 5; }
.map-clouds { animation: drift 40s ease-in-out infinite alternate; }
@keyframes drift { to { transform: translateX(40px); } }

/* The popover: public/world.js fills it from a <template> and pins it under (or over) the land. */
.map-pop { position: absolute; z-index: 5; width: min(300px, calc(100% - 16px)); animation: map-pop-in .16s var(--ease-out); }
@keyframes map-pop-in { from { opacity: 0; transform: translateY(6px) scale(.97); } } /* own name: a second pop-in used to override the event banner's */
.map-pop-card {
  padding: 0 14px 14px; background: var(--card); border: var(--line) solid var(--ink); border-radius: var(--r-tile);
  box-shadow: var(--shadow-sm); overflow: hidden;
}
.map-pop-name {
  display: flex; align-items: center; justify-content: space-between; gap: 8px; margin: 0 -14px 8px; padding: 8px 14px;
  font: 600 1.25rem/1.2 var(--display); background: linear-gradient(115deg, var(--land-from), var(--land-to)); border-bottom: 2px solid var(--ink);
}
.map-pop-card.is-dark .map-pop-name { color: #fff; }
.map-pop-tag { font-size: var(--fs-sm); color: var(--muted); margin-bottom: 8px; }
.map-pop-places { list-style: none; margin: 0 0 10px; padding: 0; display: grid; gap: 2px; font-weight: 700; }
.map-pop-places a::before, .land-card-places a::before { content: '→ '; color: var(--land-accent); }
.map-pop .btn { width: 100%; }

/* What's-up badges: a glyph plus words, so they never rely on color. */
.badges { list-style: none; display: flex; flex-wrap: wrap; gap: 6px; margin: 0 0 10px; padding: 0; }
.badge {
  display: inline-flex; align-items: center; gap: 4px; padding: 1px 10px 1px 6px; color: var(--ink);
  font: 600 var(--fs-xs)/1.5 var(--display); border: 2px solid var(--ink); border-radius: 999px; background: var(--hi);
}
.badge svg { flex: none; fill: none; stroke: var(--ink); stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
.badge-daily svg { fill: var(--sun); stroke-width: 1.6; }
.badge-shop { background: var(--sky-soft); }
.badge-quest { background: var(--pink-soft); }

/* Passport: a stamp per land; its art fills in (--fill, 0-1) as the land's places are stamped, and a finished land gets the double ring and tilt. */
.passport .stamps { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(auto-fit, minmax(84px, 1fr)); gap: 10px 8px; }
.stamp a {
  display: grid; justify-items: center; align-content: start; gap: 4px; height: 100%; padding: 10px 6px 8px;
  font: 600 var(--fs-xs)/1.2 var(--display); text-align: center; color: var(--muted); text-decoration: none;
  border: 2px dashed var(--rule); border-radius: 18px; transition: transform .15s var(--ease-out);
}
.stamp-art { width: 100%; max-width: 60px; height: auto; filter: grayscale(calc(1 - var(--fill, 0))); opacity: calc(.3 + .7 * var(--fill, 0)); }
.stamp.is-stamped a {
  color: var(--ink); border: 3px solid var(--land-accent); transform: rotate(var(--tilt));
  background: radial-gradient(circle at 50% 40%, #fff 30%, color-mix(in oklab, var(--land-from) 40%, #fff)); /* style-ok: passport stamp art */
  box-shadow: inset 0 0 0 3px #fff, inset 0 0 0 5px color-mix(in oklab, var(--land-accent) 55%, #fff); /* style-ok: passport stamp art */
}
.stamp.is-stamped .stamp-art { filter: none; opacity: 1; }
@media (hover: hover) and (pointer: fine) { .stamp a:hover { transform: rotate(0) scale(1.05); color: var(--ink); } }

/* Cover tiers (cover-0 plain ... cover-4 fully inked): stickers, foil edge, then glimshard ink that glows once the passport is full. */
.passport-count { margin: 0 0 10px; text-align: center; }
.passport-foot { margin: 10px 0 0; text-align: center; font-size: var(--fs-sm); }
.stamp span:not(.visually-hidden) { font-weight: 700; font-size: var(--fs-xs); }
.passport { --foil: color-mix(in oklab, var(--sun) 30%, var(--card)); }
.passport.cover-1, .passport.cover-2, .passport.cover-3, .passport.cover-4 { background-image: radial-gradient(circle at 12px 12px, var(--sun) 0 6px, transparent 7px), radial-gradient(circle at calc(100% - 12px) 12px, var(--pink) 0 6px, transparent 7px); }
.passport.cover-2, .passport.cover-3, .passport.cover-4 { border-color: var(--sun); box-shadow: inset 0 0 0 3px var(--foil), var(--shadow); }
.passport.cover-3, .passport.cover-4 { border-style: double; border-width: 6px; }
/* The full passport's glow: a small and a big sun glow on two layers the size of the card, crossfaded (opacity only). */
.passport.cover-4 { position: relative; box-shadow: inset 0 0 0 3px var(--foil); }
.passport.cover-4::before, .passport.cover-4::after { content: ''; position: absolute; inset: -6px; border-radius: var(--r-card); pointer-events: none; }
.passport.cover-4::before { box-shadow: 0 0 6px 1px var(--sun); animation: glow-fade-out 3s ease-in-out infinite alternate; }
.passport.cover-4::after { box-shadow: 0 0 18px 4px var(--sun); opacity: 0; animation: glow-fade-in 3s ease-in-out infinite alternate; }
@media (prefers-reduced-motion: reduce) { .passport.cover-4::before { box-shadow: 0 0 14px 3px var(--sun); } }
.passport-land h2 { display: flex; align-items: center; gap: 8px; margin: 0; }
.passport-land .stamp-art { width: 32px; filter: none; opacity: 1; }
.passport-places { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(auto-fill, minmax(min(100%, 220px), 1fr)); gap: 4px 12px; }
.passport-places li { display: flex; align-items: center; gap: 8px; min-height: 44px; color: var(--muted); }
.passport-places li::before { content: ""; flex: none; width: 16px; height: 16px; border: 2px dashed var(--rule); border-radius: 50%; }
.passport-places li.is-stamped { color: var(--ink); font-weight: 700; }
.passport-places li.is-stamped::before { border: 3px solid var(--land-accent); background: radial-gradient(circle, var(--land-accent) 30%, #fff 35%); } /* style-ok: passport stamp art */
.passport-places a { color: inherit; }

/* Land cards: the land's sky on top, its news and shortlist below. */
.lands-h { color: var(--page-ink); margin-top: 4px; }
.land-cards { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(auto-fill, minmax(min(100%, 290px), 1fr)); gap: var(--gap); }
.land-card {
  display: flex; flex-direction: column; min-width: 0; background: var(--card); overflow: hidden;
  border: var(--line) solid var(--ink); border-radius: var(--r-card); box-shadow: var(--shadow);
}
.land-card.is-here { box-shadow: 0 0 0 4px var(--sun), var(--shadow); }
.land-card-head {
  position: relative; display: grid; grid-template-columns: 72px minmax(0, 1fr); gap: 10px; align-items: center;
  padding: 12px 14px; background: linear-gradient(120deg, var(--land-from), var(--land-to)); border-bottom: var(--line) solid var(--ink);
}
.land-card-head .landmark { width: 72px; height: auto; overflow: visible; }
.land-card-head h3 { margin: 0 0 2px; font-size: 1.3rem; }
.land-card-head h3 a { color: var(--ink); text-decoration: none; }
.land-card-head h3 a:hover { text-decoration: underline; }
.land-card-head p { margin: 0; font-size: var(--fs-sm); line-height: 1.35; }
.land-card.is-dark .land-card-head, .land-card.is-dark .land-card-head h3 a { color: #fff; }
.here-flag {
  flex: none; padding: 1px 10px; font: 600 var(--fs-xs)/1.5 var(--display); color: var(--ink); white-space: nowrap;
  background: var(--sun); border: 2px solid var(--ink); border-radius: 8px; box-shadow: 2px 2px 0 var(--ink); transform: rotate(3deg);
}
.land-card-head .here-flag { grid-column: 2; justify-self: start; margin-top: -4px; }
.land-card > .badges { margin: 12px 14px 0; }
.land-card-places { list-style: none; margin: 0; padding: 10px 14px 12px; display: grid; gap: 3px; font-weight: 700; }
.land-card-foot { margin-top: auto; display: flex; align-items: center; gap: 8px; min-height: 52px; padding: 8px 14px; border-top: 2px dashed var(--rule); font-size: var(--fs-sm); font-weight: 700; }
.around { display: flex; margin-left: auto; }
.around-face { display: block; border-radius: 50%; }
.around-face svg { display: block; border: 2px solid var(--ink); border-radius: 50%; background: #fff; } /* style-ok: face art plate */
.around .around-face + .around-face { margin-left: -8px; }
@media (hover: hover) and (pointer: fine) { .around-face { transition: transform .15s var(--ease-out); } .around-face:hover { transform: translateY(-3px); } }

/* ---------- Land pages (/land/:id) ---------- */
/* Banner: the land's wide scene, with a scrim on the left third under the name. */
.land-banner-wrap {
  position: relative; width: 100%; aspect-ratio: 4; min-height: 260px; margin-bottom: var(--gap); overflow: hidden; /* wider cards grow a little taller, so the 1200x360 art never crops more than ~30 off top and bottom */
  border: var(--line) solid var(--ink); border-radius: var(--r-card); box-shadow: var(--shadow);
  background: linear-gradient(120deg, var(--land-from), var(--land-to));
}
.land-banner-wrap > .land-banner { z-index: 0; } /* landBannerHtml (src/art/land-scenes.js): the still svg and its moving layers */
.land-banner-wrap::before {
  content: ''; position: absolute; inset: 0; z-index: 1;
  background: linear-gradient(90deg, color-mix(in oklab, var(--land-from) 94%, transparent) 0 24%, color-mix(in oklab, var(--land-from) 55%, transparent) 42%, transparent 62%);
}
.land-dark .land-banner-wrap::before { background: linear-gradient(90deg, color-mix(in srgb, var(--ui-land-dark-bg) 88%, transparent) 0 24%, color-mix(in srgb, var(--ui-land-dark-bg) 50%, transparent) 42%, transparent 62%); }
.land-banner-text {
  position: relative; z-index: 2; height: 100%; max-width: min(48%, 460px);
  display: flex; flex-direction: column; justify-content: center; padding: 0 clamp(18px, 3vw, 34px);
}
.land-banner-text h1 { font-size: clamp(2rem, 1.3rem + 2.6vw, 3.2rem); margin-bottom: .15em; }
.land-banner-text p { margin: 0 0 10px; font-weight: 700; }
.land-banner-text .badges { margin: 0; }
.land-dark .land-banner-text { color: #fff; }
.land-dark .land-banner-text h1 { text-shadow: 0 3px 0 var(--ink); }

.land-places .kind + .kind { margin-top: 24px; padding-top: 18px; border-top: 2px dashed var(--rule); }
.kind h2 { display: flex; align-items: center; gap: 8px; }
.kind h2 small { font: 600 var(--fs-xs)/1.5 var(--display); padding: 0 8px; border: 2px solid var(--ink); border-radius: 999px; background: color-mix(in oklab, var(--land-accent) 14%, var(--card)); }
.place {
  display: flex; flex-direction: column; gap: 2px; padding: 14px 16px;
  border: 2px solid var(--ink); border-radius: var(--r-tile); background: var(--card); color: var(--ink); text-decoration: none; box-shadow: var(--shadow-sm);
}
/* Places with a shopkeeper: keeper on the left so every card's title lines up at the top */
.place:has(> svg) { display: grid; grid-template-columns: 56px minmax(0, 1fr); column-gap: 12px; align-content: start; }
.place > svg { grid-row: span 2; width: 56px; height: 56px; margin: -4px 0 0 -4px; }
.place:has(> svg) > .badges { grid-column: 2; }
.place b { font: 600 1.15rem var(--display); color: var(--link); }
.place span { color: var(--muted); font-size: var(--fs-sm); }
.place > .badges { margin: 8px 0 0; }
.place.has-news { background: var(--hi-soft); }
/* /glimlings: the lone Cottage tile runs the full card width instead of a narrow column under the pet cards */
.pets-places { grid-template-columns: 1fr; }
/* The Wardrobe: Thimble & Thread's card under the title, and a piece picked out by ?wear= at the top. */
.wd-tailor { margin-bottom: var(--gap); }
.wd-try-row { display: flex; align-items: center; gap: 16px; }
.wd-try-row svg { flex: none; }
.wd-try-row p { margin: 0; }
.wd-try .btn-row { margin-top: 12px; }
.shops-back { margin-top: 24px; padding-top: 18px; border-top: 2px dashed var(--rule); }
.wear-pick.is-picked { background: var(--hi-soft); box-shadow: 0 0 0 3px var(--hi), var(--shadow-sm); }
.land-nav { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 10px; margin-top: var(--gap); }

@media (max-width: 560px) {
  .map-pop { position: static; width: auto; margin-top: 10px; } /* phones: a strip under the map, so it never covers the map */
  .land-banner-wrap { height: 180px; min-height: 0; border-radius: 22px; }
  .land-banner-wrap::before { background: linear-gradient(90deg, color-mix(in oklab, var(--land-from) 94%, transparent) 0 40%, color-mix(in oklab, var(--land-from) 50%, transparent) 62%, transparent 82%); }
  .land-dark .land-banner-wrap::before { background: linear-gradient(90deg, color-mix(in srgb, var(--ui-land-dark-bg) 88%, transparent) 0 40%, color-mix(in srgb, var(--ui-land-dark-bg) 50%, transparent) 62%, transparent 82%); }
  .land-banner-text { max-width: 72%; }
  .land-banner-text h1 { font-size: min(2rem, 7.6vw); } /* one-word names (Whisperwood, Gloomhollow) fit 72% of a 320px phone instead of breaking mid-word */
  .land-banner-text p { font-size: var(--fs-sm); line-height: 1.35; }
  .land-banner-text .badges { display: none; } /* the place cards below still show them */
  .passport .stamps { grid-template-columns: repeat(3, minmax(0, 1fr)); } /* eight lands: three, three, two */
}

/* ---------- Admin (Back Office) ---------- */
.admin-tools { grid-template-columns: repeat(auto-fit, minmax(min(100%, 340px), 1fr)); gap: var(--gap); }
.admin-tools .card { display: grid; gap: 12px; align-content: start; }
.admin-tools .card > * { margin: 0; }
.admin-tools form.inline-form:not(:first-of-type) { padding-top: 12px; border-top: 2px dashed var(--rule); }
.admin-note { font-size: var(--fs-sm); }
.admin-doctor .search-row label { flex: 0 1 auto; }
.admin-form input[type=number] { width: 8em; } /* the 10000 default clipped in the 5.5em quantity width */
.admin-db { display: grid; grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); gap: 0 18px; margin: 0; font-variant-numeric: tabular-nums; }
.admin-db div { display: flex; justify-content: space-between; align-items: baseline; gap: 8px; padding: 6px 2px; border-bottom: 1px dashed var(--rule); }
.admin-db dd { margin: 0; font-weight: 800; }
.danger-zone { background: var(--err-bg); border-style: dashed; }
.danger-zone h2 { color: var(--loss); }
.danger-zone p { max-width: 62ch; }

/* ---------- Footer ---------- */
.site-foot { text-align: center; padding: 18px 16px 32px; color: var(--page-ink); font-size: var(--fs-sm); }
.site-foot p { margin: 0 auto 6px; max-width: 46rem; }
.site-foot a { margin: 0 8px; color: inherit; font-weight: 700; }

/* ---------- Responsive ---------- */
@media (max-width: 900px) {
  /* Tablet header: shrink the logo and pills so logo, land chip and status cluster keep one row down to ~700px. */
  .site-head { gap: 10px; }
  .logo svg { height: 38px; }
  .who { gap: 8px; flex-wrap: nowrap; }
  .charms { gap: 6px; }
  .charm { min-width: 36px; min-height: 36px; padding: 0 6px; }
  .coins { font-size: .9rem; }
  .sky-charm { width: 50px; height: 36px; }
  .pet-profile, .shop-head { grid-template-columns: minmax(0, 1fr); }
  .pet-profile-art { max-width: 320px; justify-self: center; width: 100%; }
}
/* Below 1000px the pet panel is a horizontal strip: the pet's face, name and level, the meters inline, actions at the end. */
@media (max-width: 999px) {
  .pet-panel {
    grid-template-columns: auto minmax(0, 1fr) auto; grid-template-areas: "face id act" "face meters act" "sw sw sw";
    align-items: center; gap: 4px 14px; padding: 10px 14px;
  }
  .pet-panel .pet-stage { grid-area: face; width: 84px; height: 84px; padding: 0; overflow: hidden; border-radius: 50%; align-self: center; }
  .pet-panel .pet-stage svg { width: 132px; max-width: none; height: auto; margin: 4px 0 0 -24px; }
  .pet-panel .pet-stage::before, .pet-panel .pet-stage.glow-stormy::after { width: 90px; bottom: -10%; }
  .pet-level { position: static; width: 28px; height: 28px; font-size: .75rem; align-self: center; }
  .pet-id { grid-area: id; text-align: left; display: flex; flex-wrap: wrap; align-items: baseline; gap: 2px 10px; }
  .pet-name { font-size: 1.15rem; }
  .pet-meters { grid-area: meters; display: flex; flex-wrap: wrap; gap: 4px 16px; padding: 0; background: none; border: 0; }
  .pet-meters li { grid-template-columns: auto auto auto; column-gap: 6px; white-space: nowrap; }
  .pet-meters .stat-bar { display: none; }
  .pet-actions { grid-area: act; grid-auto-flow: row; }
  .pet-actions .btn-alt { display: none; }
  .pet-switch small { flex: none; }
  .pet-switch { grid-area: sw; justify-content: flex-start; border-top: 2px dashed var(--rule); margin-top: 6px; }
  .pet-panel-empty { grid-template-columns: none; grid-template-areas: none; display: flex; flex-wrap: wrap; justify-content: center; gap: 4px 14px; }
}
@media (max-width: 767px) {
  .site-head { padding-inline: 12px; gap: 8px; }
  .logo svg { height: 32px; }
  .land-chip { padding: 3px 10px; font-size: var(--fs-xs); }
}
@media (max-width: 560px) {
  /* Phone strip: face beside name and meters, "Feed & play" full width underneath. */
  .pet-panel { grid-template-columns: auto minmax(0, 1fr); grid-template-areas: "face id" "face meters" "act act" "sw sw"; }
  .pet-actions .btn { padding-block: 6px; }
  body { font-size: var(--fs-sm); }
  /* Phone header, tab bar and pet bar: shell.css. */
  .keeper { flex-direction: column; align-items: flex-start; }
  .keeper svg { width: 96px; height: 96px; }
  .speech::before { display: none; }
  .table th, .table td { padding: 8px; }
}
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation: none !important; transition: none !important; }
}

/* ---------- Idle pets: every petSvg breathes and blinks (src/art/pet.js adds .pet-idle / .pet-eyes and a per-pet --idle delay).
   Only the inner groups move, so outer svg animations (joy hop, keeper nod, parade) still apply.
   The global prefers-reduced-motion rule above switches both off. ---------- */
.pet-idle { transform-box: fill-box; transform-origin: 50% 100%; animation: pet-breathe 3.6s ease-in-out var(--idle, 0s) infinite alternate; }
.pet-eyes { transform-box: fill-box; transform-origin: 50% 50%; animation: pet-blink 5.3s linear calc(var(--idle, 0s) * 1.7) infinite; }
@keyframes pet-breathe { to { transform: scale(.99, 1.025); } }
@keyframes pet-blink { 0%, 95%, 100% { transform: none; } 97.5% { transform: scaleY(.12); } }
/* The glimmer mark's glint: a white streak sweeps across the gem and its gold setting every 5 s (src/art/pet.js glim()), phased
   off the pet's --idle delay. Same two rules as GLINT_CSS in pet.js, which goes inside the served /art/pet SVGs. */
@media (prefers-reduced-motion: no-preference) { .pet-glint { animation: pet-glint 5s ease-in-out calc(var(--idle, 0s) * 1.25) infinite; } }
@keyframes pet-glint { 35%, 100% { transform: translateX(42px); } }
/* Glittery's flicker: three groups of bigger flakes over the coat fade and come back on their own beats (src/art/pet.js front()),
   phased off --idle. Same rules as GLITTER_CSS in pet.js; at rest every flake shows, so still renders keep the glitter. */
@media (prefers-reduced-motion: no-preference) {
  .gl-fk1 { animation: gl-fk 2.2s ease-in-out calc(var(--idle, 0s) * .9) infinite; }
  .gl-fk2 { animation: gl-fk 3.1s ease-in-out calc(var(--idle, 0s) * 1.2) infinite; }
  .gl-fk3 { animation: gl-fk 3.9s ease-in-out calc(var(--idle, 0s) * 1.5) infinite; }
}
@keyframes gl-fk { 50% { opacity: .1; } }

/* ---------- Wardrobe touches: the few moving bits on worn pieces (src/art/wear.js): sparkles, a phone screen, a sparkler, a
   lamp flame, a watch hand. Small elements, transform and opacity only. The prefers-reduced-motion rule above and the
   reduce-motion setting stop them, leaving each bit in its resting look. ---------- */
.wear-twinkle, .wear-spark, .wear-flicker, .wear-tick, .wear-whirl, .wear-twirl { transform-box: fill-box; transform-origin: 50% 50%; }
.wear-twinkle { animation: wear-twinkle 2.6s ease-in-out infinite; }
.wear-blink { animation: wear-blink 2.2s steps(1) infinite; }
.wear-spark { animation: wear-spark .45s ease-in-out infinite alternate; }
.wear-flicker { transform-origin: 50% 100%; animation: wear-flicker 1.1s ease-in-out infinite alternate; }
.wear-tick { transform-origin: 50% 100%; animation: wear-tick 60s steps(60) infinite; }
@keyframes wear-twinkle { 0%, 55%, 100% { transform: scale(.2) rotate(0deg); opacity: 0; } 75% { transform: scale(1.15) rotate(45deg); opacity: 1; } }
@keyframes wear-blink { 50% { opacity: 0; } }
@keyframes wear-spark { from { transform: scale(.8) rotate(-8deg); opacity: .55; } to { transform: scale(1.1) rotate(8deg); opacity: 1; } }
@keyframes wear-flicker { 0% { transform: none; } 40% { transform: scale(.9, 1.1); } 70% { transform: scale(1.06, .9); } 100% { transform: scale(.96, 1.05); } }
@keyframes wear-tick { to { transform: rotate(360deg); } }
.wear-whirl { animation: wear-whirl .9s linear infinite; } /* the Spinning-Top Hat's top: its stripe wobbles as it spins */
.wear-twirl { animation: wear-tick 4s linear infinite; }   /* the Brass Pinwheel's sails */
@keyframes wear-whirl { 50% { transform: scaleX(.86) skewX(4deg); } }
/* Neon set: slow, soft glow changes only (no hard flashing). pulse breathes a glow, buzz dims once every few seconds like an
   old sign, scan slides a highlight sideways, hover bobs a drone or halo. */
.wear-pulse { animation: wear-pulse 2.4s ease-in-out infinite; }
.wear-buzz { animation: wear-buzz 4.4s ease-in-out infinite; }
.wear-scan { animation: wear-scan 3s ease-in-out infinite alternate; }
.wear-hover { animation: wear-hover 2.6s ease-in-out infinite alternate; }
@keyframes wear-pulse { 50% { opacity: .35; } }
@keyframes wear-buzz { 0%, 78%, 96%, 100% { opacity: 1; } 84% { opacity: .5; } 88% { opacity: .85; } 91% { opacity: .55; } }
@keyframes wear-scan { from { transform: translateX(-14px); } to { transform: translateX(14px); } }
@keyframes wear-hover { to { transform: translateY(-4px); } }
.wear-glide { animation: wear-glide 2.6s linear infinite; } /* Y2K set: a CRT scanline, a visor sheen; distance in --gx / --gy */
@keyframes wear-glide { 0% { transform: none; } 70%, 100% { transform: translate(var(--gx, 0px), var(--gy, 0px)); } }
/* Terminal set: a screen-colored cover shrinks off its text a letter at a time (steps(n) set inline), then the screen clears;
   cords swing from where they are fixed (the bottom). */
.wear-type, .wear-sway { transform-box: fill-box; }
.wear-type { transform-origin: 100% 50%; animation: wear-type 3.2s steps(3) infinite; }
.wear-sway { transform-origin: 50% 100%; animation: wear-sway 1.8s ease-in-out infinite alternate; }
@keyframes wear-type { 0% { transform: scaleX(1); } 60%, 100% { transform: scaleX(0); } }
@keyframes wear-sway { from { transform: rotate(-6deg); } to { transform: rotate(6deg); } }
/* Hacker set: code rain falls one period (each column is drawn twice, so 50% is one period), a loading bar fills in steps,
   glows throb, a spoon's top half bends from its kink. */
.wear-coderain, .wear-loadbar, .wear-wobble { transform-box: fill-box; transform-origin: 50% 50%; }
.wear-coderain { animation: wear-coderain 2s linear infinite; }
.wear-loadbar { transform-origin: 0 50%; animation: wear-loadbar 3.6s infinite; }
.wear-throb { animation: wear-throb 1.1s ease-in-out infinite alternate; }
.wear-wobble { transform-origin: 50% 100%; animation: wear-wobble 1.4s ease-in-out infinite alternate; }
@keyframes wear-coderain { to { transform: translateY(50%); } }
@keyframes wear-loadbar { 0% { transform: scaleX(.06); animation-timing-function: steps(6); } 75%, 100% { transform: none; } }
@keyframes wear-throb { from { opacity: .12; } to { opacity: .85; } }
@keyframes wear-wobble { from { transform: rotate(-9deg); } to { transform: rotate(12deg); } }
/* Throwback set (2026-10-10): lava blobs and a yo-yo drift by --gy and back, bubbles, notes and drips rise or fall by --gx / --gy
   and fade, speaker cones thump, a ringing phone and clicky beads shake in short bursts. */
.wear-float, .wear-fizz, .wear-thump, .wear-jiggle { transform-box: fill-box; transform-origin: 50% 50%; }
.wear-float { animation: wear-float 3s ease-in-out infinite alternate; }
.wear-fizz { animation: wear-fizz 2s ease-in infinite; }
.wear-thump { animation: wear-thump .6s ease-out infinite; }
.wear-jiggle { transform-origin: 50% 100%; animation: wear-jiggle 2.4s linear infinite; }
@keyframes wear-float { to { transform: translate(var(--gx, 0px), var(--gy, -6px)); } }
@keyframes wear-fizz { 0% { transform: none; opacity: 0; } 15% { opacity: 1; } 100% { transform: translate(var(--gx, 0px), var(--gy, -10px)); opacity: 0; } }
@keyframes wear-thump { 0% { transform: scale(1.14); } 60%, 100% { transform: none; } }
@keyframes wear-jiggle { 0%, 32%, 100% { transform: none; } 4% { transform: rotate(-6deg); } 8% { transform: rotate(6deg); } 12% { transform: rotate(-5deg); } 16% { transform: rotate(5deg); } 20% { transform: rotate(-3deg); } 24% { transform: rotate(3deg); } 28% { transform: rotate(-1deg); } }
.reduce-motion :is(.wear-twinkle, .wear-blink, .wear-spark, .wear-flicker, .wear-tick, .wear-whirl, .wear-twirl, .wear-pulse, .wear-buzz, .wear-scan, .wear-hover, .wear-glide, .wear-type, .wear-sway, .wear-coderain, .wear-loadbar, .wear-throb, .wear-wobble, .wear-float, .wear-fizz, .wear-thump, .wear-jiggle, .gl-fk1, .gl-fk2, .gl-fk3) { animation: none; }
/* end wardrobe touches */

/* ---------- Rare item shimmer (tileClass() in src/layout.js) ----------
   Rare (rarity 80+) tiles get a gold ring and a slow light sweep; ultra-rare (95+) a candy ring, a quicker sweep and a
   twinkling star. Under reduced motion the rule above stops the sweep and twinkle, leaving the ring as a static glow.
   The sweep moves on the GPU: the light band is a strip 2.6 tiles wide that slides across by transform, clipped to the
   tile's rounded box (overflow: clip, which leaves the rings and shadows outside alone). It used to slide its
   background-position, which repainted the tile on the main thread every frame. Same geometry: the strip starts 2.56
   tiles left of the tile (the old 160% position) and travels 3.52 tiles (to the old -60%), 3.52 / 2.6 of its own width. */
.tile-rare { overflow: clip; box-shadow: 0 0 0 3px var(--sun), var(--shadow-sm); }
.tile-ultra { overflow: clip; box-shadow: 0 0 0 3px var(--pink), 0 0 0 6px var(--sun), var(--shadow-sm); }
@media (hover: hover) and (pointer: fine) {
  :is(a, button).tile-rare:hover { box-shadow: 0 0 0 3px var(--sun), var(--shadow-lift); }
  :is(a, button).tile-ultra:hover { box-shadow: 0 0 0 3px var(--pink), 0 0 0 6px var(--sun), var(--shadow-lift); }
}
.tile-rare::after, .tile-ultra::after {
  content: ''; position: absolute; top: 0; bottom: 0; left: -256%; width: 260%; pointer-events: none;
  background: linear-gradient(110deg, transparent 38%, rgb(255 255 255 / .75) 50%, transparent 62%);
  animation: tile-shimmer 4.5s ease-in-out infinite;
}
.tile-ultra::after { animation-duration: 3s; }
.tile-ultra::before {
  content: '✦'; position: absolute; top: 5px; left: 8px; pointer-events: none;
  font: 700 1.15rem/1 var(--display); color: var(--pink); text-shadow: 0 0 6px var(--sun);
  animation: tile-twinkle 1.8s ease-in-out infinite;
}
@keyframes tile-shimmer { 0%, 55% { transform: none; } 100% { transform: translateX(135.3846%); } }
@keyframes tile-twinkle { 50% { transform: scale(.6) rotate(45deg); opacity: .5; } }

/* ---------- Glimmery stickers (holo() in src/art/item.js, inside every itemSvg) ----------
   A light band sweeps across the foil once every 7s and the glitter specks twinkle as it passes; --holo staggers each sticker.
   Only small transformed groups, idle most of the cycle, so a page full of them stays smooth. Reduced motion (the global rules)
   stops both: the band rests off the sticker and the specks stay lit, a still rainbow foil. Glimshard catches starlight:
   the band is brighter while the header's sky clock shows night (.sky-charm.sky-night, src/layout.js). */
.holo-sweep { opacity: .75; animation: holo-sweep 7s ease-in-out var(--holo, 0s) infinite; }
body:has(.sky-charm.sky-night) .holo-sweep { opacity: 1; }
.holo-twinkle { transform-box: fill-box; transform-origin: center; animation: holo-twinkle 7s ease-in-out calc(var(--holo, 0s) + var(--holo-i, 0) * .3s) infinite; }
@keyframes holo-sweep { 0%, 55% { transform: translateX(0); } 100% { transform: translateX(200px); } }
@keyframes holo-twinkle { 0%, 62%, 100% { transform: none; opacity: 1; } 78% { transform: scale(.35) rotate(45deg); opacity: .4; } }

/* A title worn next to a player's name (services/trophies.js titleTag) */
.town-note { margin: 16px auto 0; max-width: 60ch; text-align: center; font-size: .85rem; color: var(--muted); } /* src/html.js townNote */
.town-note .townsfolk { margin: 0 4px 0 0; }
.townsfolk { display: inline-block; margin-left: 4px; padding: 0 8px; border: 1.5px solid var(--ink); border-radius: 999px; background: var(--ui-townsfolk-bg); font: 600 .68rem/1.5 var(--display); color: var(--ink); vertical-align: middle; white-space: nowrap; }
/* Brass nameplate: a rivet in each corner, a faint inner line, ink shadow, spaced capitals (picked B1 in the 2026-10-03 mockup) */
.title-tag {
  display: inline-block; margin-left: 4px; padding: 1px 12px; vertical-align: middle; white-space: nowrap;
  font: 600 .68rem/1.72 var(--display); text-transform: uppercase; letter-spacing: .08em; color: var(--ink);
  --rivet: radial-gradient(circle, var(--ink) 1.4px, transparent 1.9px);
  background: var(--rivet) left 0 top 0 / 6px 6px no-repeat, var(--rivet) right 0 top 0 / 6px 6px no-repeat,
    var(--rivet) left 0 bottom 0 / 6px 6px no-repeat, var(--rivet) right 0 bottom 0 / 6px 6px no-repeat, var(--sun-cream);
  border: 1.5px solid var(--ink); border-radius: 6px; box-shadow: 2px 2px 0 var(--ink);
  outline: 1px solid color-mix(in srgb, var(--ink) 30%, transparent); outline-offset: -4px; /* the plaque's inner line */
}

/* ---------- Phones (max 767px): 44px targets, 12px gaps, button rows, alignment by content type ---------- */
@media (max-width: 767px) {
  .btn-small, .pop-close, a.pill, .pet-card-foot .pill { min-height: 44px; }
  a.pill { display: inline-flex; align-items: center; }
  .pop-close { width: 44px; height: 44px; }
  :where(details:not(.account)) > summary { min-height: 44px; padding-block: 10px; box-sizing: border-box; cursor: pointer; }
  label:has(> input[type=checkbox], > input[type=radio]):not(.pick, .swatch, .wall-swatch, .th-thumb, .set-row, :has(svg, img)) { display: inline-flex; align-items: center; gap: 10px; min-height: 44px; }
  :where(input[type=checkbox], input[type=radio]) { inline-size: 22px; block-size: 22px; flex: none; }

  /* gaps between stacked controls */
  .inline-form, .search-row, .item-pop-actions, .pick-row, .land-nav, .btn-row { gap: 12px; }
  .create-form, .grid-2 > form { gap: 12px; }
  .card > :is(p, form) + form { margin-top: 12px; }
  h2:has(+ :is(form, .btn-row, .inline-form, .search-row)) { margin-bottom: 14px; }

  /* button rows: the primary spans the row, the rest share the next one; small buttons just share evenly */
  .btn-row { gap: 12px; }
  .btn-row:has(> .btn-big) { align-items: stretch; }
  .btn-row:not(:is(table, .item-tile, .pet-card, .head-row) .btn-row) > :is(.btn, form) { flex: 1 1 40%; }
  .btn-row:not(:is(table, .item-tile, .pet-card, .head-row) .btn-row) > form > .btn:only-of-type { width: 100%; } /* hidden inputs beside it are fine */
  .btn-row:not(:is(table, .item-tile, .pet-card, .head-row) .btn-row) > .btn:not(.btn-small, .btn-alt, .btn-danger):is(:first-child:has(~ .btn, ~ form), :only-child) { flex-basis: 100%; }

  /* centered: intro titles, the shop keeper, wrapped page headers */
  .card > .scene:first-child + h1, .card > .scene:first-child + h1 + .lede { text-align: center; margin-inline: auto; }
  .shop-head { text-align: center; }
  .keeper:not(.plot-say) { flex-direction: column; align-items: center; } /* every keeper: art centered above a centered bubble */
  .keeper:not(.plot-say) .speech { margin-inline: auto; text-align: center; }
  .keeper:not(.plot-say) .speech :is(p, b, .pill) { text-align: center; }
  .keeper:not(.plot-say) .speech::before { display: none; }
  .shop-head .lede { margin-inline: auto; }
  .shop-head :is(form, ul, ol, dl, table) { text-align: left; } /* short text centers with the head; forms and lists stay left */
  .land-back { display: flex; justify-content: center; margin: 0 0 8px; }
  .head-row > .land-back { margin: 0; }
  .land-back .pill { min-height: 44px; padding-inline: 16px; display: inline-flex; align-items: center; gap: 6px; font-weight: 700; color: var(--link); text-decoration: none; }
  .land-back + h1 { text-align: center; }
  .head-row { justify-content: center; text-align: center; }
  .head-row:has(> :nth-child(2)) { flex-direction: column; align-items: center; gap: 8px; } /* title, then its pill or button on its own row */
  .card > :is(h1, h2):first-child, .card > .scene:first-child + :is(h1, h2) { text-align: center; } /* a card's own title, even without a head-row */
  .pet-profile-info > h1, .head-row + :is(.lede, p), h1 + .lede, h2 + .lede, p:has(> .pill:only-child) + .lede, .phone-center { text-align: center; margin-inline: auto; } /* the intro under a header or its pill; .phone-center for other short notes */
  p:has(> .pill:only-child) { text-align: center; }
  .album-tabs { justify-content: center; }
  .pet-card .btn-row { flex-direction: column; align-items: center; } /* Active Glimling pill and Visit on separate rows */
  .card > .scene:first-child + .head-row + .lede, .card > .scene:first-child + .head-row + .lede + p { text-align: center; margin-inline: auto; }
  .haggle-item .btn-row, .ad { justify-content: center; text-align: center; } /* Owlmanac pills; the flyer's text and its pill */
  .ad .pill { margin-left: auto; } /* the flyer's pill sits bottom right, like a tear-off tab */
  .poll > :is(.muted, h1, h2, .lede, p), .poll > form, .poll-answers li, .poll-reveal > h2, .poll-town > h2, .poll-voted, .poll-prize { text-align: center; } /* Town Poll: everything above the results, the answers, the two headings, the vote count and the prize */
  .poll-ring .btn { width: 100%; } /* the bell is the page's one action: full width on phones */
  .stack-phone > :is(label, .btn) { flex: 1 1 100%; } /* a field and its button on separate rows (Settings recovery) */
  .stack-phone input { width: 100%; box-sizing: border-box; }
  .haggle-item { justify-content: center; text-align: center; } /* an Owlmanac entry's hero: art, name, badges */
  .admin-form :is(label, .btn) { flex: 1 1 100%; } /* admin tools: every field its own row, the submit full width */
  .admin-form input { width: 100%; box-sizing: border-box; }
  .search-row > .btn:last-child { flex: 1 1 100%; } /* the lone submit under a search field spans the row */
}

/* Set progress card (src/services/sets.js setCard): the four prizes for finishing, then the missing members as silhouettes. */
.set-prizes { list-style: none; padding: 0; margin: 12px 0 0; display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 12px; }
.set-prizes li { display: flex; flex-direction: column; align-items: center; text-align: center; gap: 4px; padding: 10px; background: var(--tint); border-radius: var(--r-sm); }
.set-prizes small { color: var(--muted); font-size: var(--fs-xs); }
.set-won li { background: var(--hi-soft); }
.set-credit { font-family: var(--display); font-size: 1.45rem; line-height: 56px; color: var(--gain); }
.set-missing { background: var(--tint); border-style: dashed; }
.set-missing svg { filter: brightness(0) opacity(.22); }
.set-missing form { margin-top: auto; }

/* Desktop pet profile: art beside name/bubble, the long stats table spans the full card underneath (it used to leave the art column empty for 500px). */
@media (min-width: 1100px) {
  .pet-profile { grid-template-rows: auto auto 1fr; }
  .pet-profile-info { display: contents; }
  .pet-profile-info > .speech { align-self: start; }
  .pet-profile-art { grid-row: 1 / span 3; }
  .pet-profile-info > .stats-table, .pet-profile-info > .btn-row { grid-column: 1 / -1; }
  .pet-profile-info > .speech { max-width: 520px; }
}

/* Desktop reading width: plain paragraphs and .muted notes inside cards stop at 75ch. On wide screens they ran 90-140 characters per line
   (paragraphs with their own class, like .lede or the centered .empty, keep their own rules). */
@media (min-width: 1100px) { main .card p:not([class]), main .card p.muted { max-width: 75ch; } }

/* .wear-glow (src/art/pet.js wraps a lit split glimshard half in it) has no style: the A3 pendant draws its own warm light
   (wear.js shardHalfPiece). A CSS filter there also showed nothing in WebKit, which ignores filters on elements inside an SVG. */
