/* ============================================================
   Medieval Solutions Online — shared components
   The building blocks first made for the interactive example, used by
   the example, every in-game phone screen and the TV stage. Colours
   and fonts come from theme.css; a player's colours arrive as
   --bub (fill) and --acc (accent) on the element or an ancestor.
   ============================================================ */

/* ── Avatar ──────────────────────────────────────────────────────────
   The box is the disc (fill + ring, drawn here in the player's colours).
   The image is a transparent sprite layer from scripts/make_avatars.py,
   1.75× the disc (radius 8 + 6px pop-out margin), centred over it, so
   hats, ears and held items hang over the ring without affecting layout. */
.av {
    --av: 40px;
    position: relative; flex-shrink: 0;
    width: var(--av); height: var(--av); border-radius: 50%;
    display: inline-flex; align-items: center; justify-content: center;
    background: var(--bub, #fff);
    border: max(2px, calc(var(--av) * 0.06)) solid var(--acc, var(--ink));
    font-family: var(--font-pixel); color: var(--acc, var(--ink)); font-size: calc(var(--av) * 0.45);
}
.av img {
    position: absolute; left: 50%; top: 50%;
    width: calc(var(--av) * 1.75); height: calc(var(--av) * 1.75);
    transform: translate(-50%, -50%); max-width: none; display: block;
    image-rendering: pixelated; pointer-events: none;
    transition: transform .35s cubic-bezier(.3, 1.4, .5, 1);
}
/* Sprites face left; whoever has the floor turns to face right, with a little flip. */
.av.facing img { transform: translate(-50%, -50%) scaleX(-1); animation: av-turn .35s cubic-bezier(.3, 1.4, .5, 1); }
@keyframes av-turn { from { transform: translate(-50%, -50%) scaleX(1); } }
@media (prefers-reduced-motion: reduce) { .av img { transition: none; } .av.facing img { animation: none; } }
.av.ghost { visibility: hidden; }

/* ── Pixel icons (img/icons/, from scripts/make_icons.py) ── */
/* Pixel art comes in every shape (19×24, 28×19…): contain keeps its true proportions inside any square box. */
.icon { width: 1.15em; height: 1.15em; image-rendering: pixelated; object-fit: contain; vertical-align: -0.2em; display: inline-block; }
.icon.lg { width: 1.6em; height: 1.6em; }

/* ── Banner: the town, plus the art of the trouble on the table ──
   Two short lines that wrap rather than truncate (some names are long). */
.ms-banner {
    display: flex; align-items: center; gap: 8px;
    background: var(--parch); border: 2px solid var(--parch-brd); border-radius: 6px;
    padding: 3px 8px 4px; font-size: 0.75rem; line-height: 1.25;
}
.ms-banner[hidden] { display: none; }
.ms-banner .tb-text { flex: 1; min-width: 0; text-align: center; overflow-wrap: anywhere; }
.ms-banner b { display: block; font-family: var(--font-pixel); font-weight: normal; font-size: 0.95rem; color: var(--red); }
.ms-banner .tb-text span { display: block; color: var(--ink2); }
.ms-banner .tb-art {
    flex-shrink: 0; width: 34px; height: 34px;
    background: var(--card-bg); border: 2px solid var(--red); border-radius: 5px;
}
.ms-banner .tb-art img { width: 100%; height: 100%; image-rendering: pixelated; display: block; }
.ms-banner .tb-art.land { animation: land .6s .5s cubic-bezier(.3, 1.6, .5, 1) both; }
.ms-banner.unroll { animation: unroll .7s cubic-bezier(.3, 1.4, .5, 1) both; transform-origin: top; }

/* ── Score bar: everyone's avatar with their points as pips ── */
.ms-scorebar { display: flex; justify-content: center; flex-wrap: wrap; gap: 8px 22px; margin-top: 8px; }
.ms-seat { display: flex; flex-direction: column; align-items: center; gap: 3px; position: relative; }
.ms-seat .av { --av: 30px; }
.ms-seat.troubler .av { box-shadow: 0 0 0 2px var(--paper), 0 0 0 4px var(--red); }
.ms-seat.troubler::after {
    content: ''; position: absolute; top: -8px; right: -12px; width: 16px; height: 16px;
    background: url('../img/icons/troubler.png') center / contain no-repeat; image-rendering: pixelated;
}
/* A small status stamp in the corner: sealed (locked in / presented) or presenting. */
.ms-seat .stamp {
    position: absolute; bottom: 10px; left: -9px; width: 16px; height: 16px;
    image-rendering: pixelated; animation: stamp .45s cubic-bezier(.3, 1.6, .5, 1) both;
}
.ms-seat.waiting .av { opacity: .55; }
/* Whoever has the floor: a gold glow and a little lift (the TV's .st-seat too). */
.ms-seat .av, .st-seat .av { transition: box-shadow .3s, translate .3s cubic-bezier(.3, 1.6, .5, 1); }
.ms-seat.speaking .av, .st-seat.speaking .av {
    box-shadow: 0 0 0 2px var(--paper), 0 0 0 4px var(--gold), 0 0 14px 4px var(--glow);
    translate: 0 -3px;
}
.ms-pips { display: flex; gap: 3px; }
.ms-pip { width: 8px; height: 8px; border-radius: 50%; border: 2px solid var(--acc); background: #fff; }
.ms-pip.on { background: #ecbc34; border-color: #8b6a10; }
.ms-seat.pop { animation: pop .7s ease; }

/* ── Cards ── */
.card { background: var(--card-bg); border: 2px solid var(--card-brd); border-radius: var(--card-radius); width: 100%; }
.card.unknown { border-color: var(--red); color: var(--red); display: flex; align-items: center; justify-content: center; text-align: center; padding: 8px; font-size: 0.8rem; }

/* The trouble card slamming onto the table, with a puff of dust. */
.slam { position: relative; animation: slam .55s cubic-bezier(.5, 0, .75, 0) both, shake .35s .55s ease both; }
.slam::after {
    content: ''; position: absolute; left: -20%; right: -20%; bottom: -10px; height: 26px;
    background: radial-gradient(ellipse at center, rgba(160, 120, 70, .55), rgba(160, 120, 70, 0) 70%);
    opacity: 0; animation: dust .8s .5s ease-out both; pointer-events: none;
}

/* Face-down card that turns over in 3D (add .flipped). */
.flip-card { width: 96px; aspect-ratio: 5/7; perspective: 900px; transform: rotate(var(--tilt, 0deg)); }
.flip-inner { position: relative; width: 100%; height: 100%; transition: transform .7s cubic-bezier(.3, 1.3, .5, 1); transform-style: preserve-3d; }
.flip-card.flipped .flip-inner { transform: rotateY(180deg); }
.flip-face { position: absolute; inset: 0; backface-visibility: hidden; -webkit-backface-visibility: hidden; }
.flip-face .card-back { width: 100%; height: 100%; object-fit: cover; border-radius: var(--card-radius); image-rendering: pixelated; }
.flip-front { transform: rotateY(180deg); }
.flip-front .card { height: 100%; border-color: var(--acc, var(--card-brd)); box-shadow: 0 3px 12px color-mix(in srgb, var(--acc, #b8903a) 45%, transparent); }

/* A card flying to whoever won it. */
.fly-in { animation: fly .9s cubic-bezier(.3, 1.2, .5, 1) both; }

/* ── Speech bubble (a player's colour via --bub / --acc) ── */
.bubble {
    font-size: 16.5px; line-height: 1.38; padding: 9px 14px; border-radius: 18px;
    background: var(--bub); box-shadow: 0 2px 0 color-mix(in srgb, var(--acc) 35%, transparent);
}
.typing span {
    display: inline-block; width: 7px; height: 7px; margin: 0 2px; border-radius: 50%;
    background: var(--acc); animation: dot 1s infinite ease-in-out;
}
.typing span:nth-child(2) { animation-delay: .15s; }
.typing span:nth-child(3) { animation-delay: .3s; }

/* ── Choice button: avatar + who + label + their cards ── */
.option {
    display: flex; align-items: center; gap: 10px; width: 100%; margin: 0;
    font-family: var(--font-ui); font-size: 0.95rem; text-align: left; color: var(--ink);
    background: var(--bub); border: 2px solid var(--acc); border-radius: 14px; padding: 8px 10px; cursor: pointer;
    transition: transform .15s, box-shadow .15s, opacity .2s;
}
.option:hover:not(:disabled) { transform: translateY(-2px); box-shadow: 0 4px 0 var(--acc); filter: none; }
.option:disabled { cursor: default; opacity: .4; }
.option.picked { opacity: 1; box-shadow: 0 0 0 3px #ecbc34, 0 0 16px rgba(236, 188, 52, .8); }
.option .thumbs { display: flex; gap: 4px; flex-shrink: 0; margin-left: auto; }
.option .thumbs .card { width: 42px; }
.option .who-l { font-weight: bold; color: var(--acc); }

/* ── Wiggling emphasis (**text** in story/rules copy) ── */
.wiggle { font-weight: 900; color: var(--red); white-space: nowrap; }
.wiggle span { display: inline-block; animation: sineWave 1.5s infinite ease-in-out; }   /* the example and demo: a touch gentler than it was (1s, 8%) */

/* ── Signposts: parchment tabs hanging off the screen edges, pointing to the
   neighbouring places in the world (js/world.js). Same size and the same spot
   on every page (beside the title's table), so a pan carries one tab off and
   the next one lands exactly where it was. They scroll with the page rather
   than stay fixed, so they never sit over a long page's text. ── */
.signpost {
    position: absolute; top: 340px; z-index: 20;
    display: flex; flex-direction: column; align-items: center; gap: 6px;
    padding: 14px 7px; text-decoration: none;
    font-family: var(--font-pixel); font-size: 1rem; color: var(--red);
    background: var(--parch); border: 2px solid var(--parch-brd);
    box-shadow: 0 3px 0 var(--tan);
    transition: translate .25s cubic-bezier(.3, 1.6, .5, 1), scale .15s, background .2s, box-shadow .2s;
}
.signpost .icon { width: 22px; height: 22px; order: 2; }
/* Both labels read the same way (bottom to top, like book spines), the icon stays
   upright at the foot of the tab, and the box itself never rotates — so the
   shadow and the hover lift sit on the same edges on both sides. */
.signpost span { writing-mode: vertical-rl; rotate: 180deg; }
.signpost.left  { left: 0;  border-left: none;  border-radius: 0 10px 10px 0; transform: translateY(-50%); }
.signpost.right { right: 0; border-right: none; border-radius: 10px 0 0 10px; transform: translateY(-50%); }
.signpost:hover, .signpost.pulling { background: var(--parch-2); }
.signpost.left:hover  { translate: 5px 0; }
.signpost.right:hover { translate: -5px 0; }
.signpost:active { scale: .92; box-shadow: 0 1px 0 var(--tan); }
/* A swipe towards a neighbour stretches its tab out to meet the finger. */
.signpost.left.pulling  { translate: 12px 0; }
.signpost.right.pulling { translate: -12px 0; }
/* Tabs slide in from the edge once a page has loaded on its own (not mid-pan). */
html:not(.arrived) .signpost.left  { animation: tab-in-left .5s .55s cubic-bezier(.3, 1.4, .5, 1) backwards; }
html:not(.arrived) .signpost.right { animation: tab-in-right .5s .65s cubic-bezier(.3, 1.4, .5, 1) backwards; }
@keyframes tab-in-left  { from { translate: -100% 0; } }
@keyframes tab-in-right { from { translate: 100% 0; } }
@media (prefers-reduced-motion: reduce) { .signpost { animation: none !important; transition: none; } }

@media (min-width: 700px) {
    .signpost { padding: 20px 10px; font-size: 1.15rem; }
}
/* ── Keyframes ── */
@keyframes rise { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }
@keyframes unroll { from { transform: scaleY(0); opacity: 0; } to { transform: scaleY(1); opacity: 1; } }
@keyframes pop { 0%, 100% { transform: scale(1); } 40% { transform: scale(1.3); } }
@keyframes land { from { opacity: 0; transform: scale(2) rotate(-12deg); } }
@keyframes stamp { from { opacity: 0; transform: scale(2.4) rotate(-20deg); } }
@keyframes dot { 0%, 100% { transform: translateY(0); opacity: .4; } 50% { transform: translateY(-4px); opacity: 1; } }
@keyframes slam { from { opacity: 0; transform: scale(2.4) rotate(-14deg) translateY(-60px); } to { opacity: 1; transform: scale(1) rotate(0); } }
@keyframes shake { 0%, 100% { translate: 0 0; } 25% { translate: -4px 2px; } 50% { translate: 4px -1px; } 75% { translate: -2px 1px; } }
@keyframes dust { 0% { opacity: 0; transform: scaleX(.4); } 30% { opacity: 1; } 100% { opacity: 0; transform: scaleX(1.4); } }
@keyframes fly { from { opacity: 0; transform: translate(-80px, -90px) rotate(-30deg) scale(1.6); } }
@keyframes bob { 0%, 100% { translate: 0 0; } 50% { translate: 0 -4px; } }
@keyframes beckon { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.05); } }
@keyframes sineWave { 0%, 100% { transform: translateY(0); } 25% { transform: translateY(-5%); } 75% { transform: translateY(5%); } }
/* What the reduced-motion rules (table.css, stage.css) use in place of a slam or a flight: no movement, just a quick fade. */
@keyframes fade-in { from { opacity: 0; } }
@keyframes fade-in-out { 0%, 100% { opacity: 0; } 12%, 70% { opacity: 1; } }

@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after { animation: none !important; transition-duration: 0s !important; }
}

/* ── Thrown artwork (polish.js flyReaction) ── */
.react-fly {
    position: fixed; z-index: 1100; image-rendering: pixelated; pointer-events: none;
    filter: drop-shadow(0 2px 0 rgba(30, 14, 6, .35));
    animation: react-fly 1.9s cubic-bezier(.2, .8, .3, 1) forwards;
}
@keyframes react-fly {
    0%   { transform: scale(.3); opacity: 0; }
    12%  { transform: scale(1.25); opacity: 1; }
    70%  { opacity: 1; }
    100% { transform: translate(var(--dx), var(--dy)) rotate(var(--rot)) scale(.9); opacity: 0; }
}
/* Reduced motion: the thrown artwork appears where it was thrown from and fades, so everyone still sees it. */
@media (prefers-reduced-motion: reduce) { .react-fly { animation: fade-in-out 1.9s ease forwards !important; } }

/* Under a "write to us" button once it's pressed (js/interest.js): the address, in case no email window opened.
   flex-basis puts it on its own line when the button sits in a row of buttons. */
.mail-fallback {
    flex-basis: 100%; display: block; margin: 8px auto 0; max-width: 420px; padding: 8px 12px; text-align: center;
    background: #fff; border: 2px dashed var(--tan); border-radius: 10px; font-size: .9rem; line-height: 1.5; color: var(--ink2);
    animation: mail-in .3s ease both;
}
.mail-fallback b { color: var(--ink); user-select: all; white-space: nowrap; }
.mail-fallback button {
    margin-left: 6px; padding: 2px 10px; font: inherit; font-size: .85rem; color: var(--red); cursor: pointer;
    background: var(--parch); border: 2px solid var(--parch-brd); border-radius: 999px;
}
@keyframes mail-in { from { opacity: 0; translate: 0 -4px; } }
@media (prefers-reduced-motion: reduce) { .mail-fallback { animation: none; } }
