/* Portrait companion — a quiet, breathing presence beside each summoner.
   Opponent: THINKING / MOVES through the authoritative action lifecycle.
   Both sides: short deterministic reactions to meaningful combat changes.
   Design language: gold/violet breathing glows — never strobe. */

/* Sweep angle for the aura's travelling light */
@property --ti-angle {
    syntax: '<angle>';
    inherits: false;
    initial-value: 0deg;
}

.thinking-indicator {
    position: absolute;
    inset: 0;
    z-index: 18;
    pointer-events: none;
    overflow: visible;
}

.thinking-indicator * {
    pointer-events: none;
}

/* The companion lives inside the portrait's isolated paint context. Portraits
   can be mounted directly by Indicators or inside the portrait's optional
   fixed wrapper, so lift whichever element owns the live HUD stacking context.
   Cards remain at 9500; StoryFeed and spell surfaces retain the higher tiers. */
.portrait-container.thinking-hud-active,
#player-portrait.thinking-hud-active,
#opponent-portrait.thinking-hud-active {
    z-index: 9510;
}

/* ===== Aura: breathing glow hugging the portrait frame ===== */
.thinking-indicator::before {
    content: '';
    position: absolute;
    inset: -1.9vmin;
    border-radius: 9.6vmin 9.6vmin 2.5vmin 2.5vmin;
    opacity: 0;
    transition: opacity 0.5s ease;
}

.thinking-indicator.is-active::before {
    opacity: 1;
    animation: thinking-aura-breathe 2.8s ease-in-out infinite alternate;
}

/* Travelling light sweeping the frame band */
.thinking-aura {
    position: absolute;
    inset: -1.9vmin;
    border-radius: 9.6vmin 9.6vmin 2.5vmin 2.5vmin;
    padding: 0.55vmin;
    background:
        conic-gradient(from var(--ti-angle, 0deg),
            transparent 0deg,
            rgba(214, 178, 255, 0) 55deg,
            rgba(214, 178, 255, 0.85) 95deg,
            rgba(255, 219, 145, 0.95) 120deg,
            rgba(214, 178, 255, 0.45) 150deg,
            transparent 205deg),
        linear-gradient(160deg, rgba(190, 150, 255, 0.22), rgba(255, 210, 130, 0.14));
    -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
    -webkit-mask-composite: xor;
    mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
    mask-composite: exclude;
    /* Soften the band into a travelling light so it hugs any frame geometry */
    filter: blur(0.4vmin);
    opacity: 0;
    transition: opacity 0.5s ease;
}

.thinking-indicator.is-active .thinking-aura {
    opacity: 1;
    animation: thinking-aura-sweep 5.5s linear infinite;
}

@keyframes thinking-aura-sweep {
    to {
        --ti-angle: 360deg;
    }
}

@keyframes thinking-aura-breathe {
    from {
        box-shadow:
            0 0 1.6vmin 0.2vmin rgba(157, 113, 242, 0.28),
            0 0 3.2vmin 0.9vmin rgba(255, 205, 112, 0.1);
    }

    to {
        box-shadow:
            0 0 2.6vmin 0.55vmin rgba(157, 113, 242, 0.5),
            0 0 5vmin 1.4vmin rgba(255, 205, 112, 0.2);
    }
}

/* ===== Pill: frosted obsidian glass floating beside the portrait ===== */
.thinking-pill {
    position: absolute;
    right: calc(100% + 2.6vmin);
    top: 50%;
    display: inline-flex;
    align-items: center;
    gap: max(0.9vmin, 6px);
    padding: max(0.9vmin, 6px) max(1.7vmin, 12px);
    border-radius: 999px;
    background: linear-gradient(150deg, rgba(26, 20, 44, 0.78), rgba(12, 9, 24, 0.72));
    -webkit-backdrop-filter: blur(10px) saturate(1.25);
    backdrop-filter: blur(10px) saturate(1.25);
    border: 1px solid rgba(216, 190, 247, 0.28);
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, 0.09),
        0 0.45vmin 1.6vmin rgba(0, 0, 0, 0.5),
        0 0 2.2vmin rgba(157, 113, 242, 0.22);
    white-space: nowrap;
    opacity: 0;
    visibility: hidden;
    /* Enter from the open side so the pill never sweeps across the portrait. */
    transform: translateY(-50%) translateX(-8%) scale(0.94);
    transition: opacity 0.22s ease, transform 0.22s ease, visibility 0s linear 0.22s;
    will-change: opacity, transform;
}

.thinking-indicator.is-active .thinking-pill {
    opacity: 1;
    visibility: visible;
    transform: translateY(-50%) translateX(0) scale(1);
    transition:
        opacity 0.38s cubic-bezier(0.22, 1, 0.36, 1) 0.05s,
        transform 0.38s cubic-bezier(0.22, 1, 0.36, 1) 0.05s,
        visibility 0s linear 0.05s;
}

/* ===== Face: the kaomoji companion ===== */
.thinking-face {
    position: relative;
    font-family: 'Hiragino Sans', 'Yu Gothic', 'Meiryo', 'Segoe UI', sans-serif;
    font-size: max(1.9vmin, 12px);
    font-weight: 600;
    line-height: 1;
    color: #ffe9ad;
    text-shadow: 0 0 max(0.8vmin, 5px) rgba(255, 217, 140, 0.45);
    white-space: nowrap;
}

.thinking-indicator.is-active .thinking-face {
    animation: thinking-face-sway 2.6s ease-in-out infinite;
}

@keyframes thinking-face-sway {

    0%,
    100% {
        transform: rotate(-3deg) translateY(0);
    }

    50% {
        transform: rotate(3deg) translateY(-6%);
    }
}

/* The move flash: the companion springs to life */
.thinking-indicator.is-acting .thinking-face {
    color: #ffd166;
    text-shadow: 0 0 max(1.2vmin, 8px) rgba(255, 209, 102, 0.85);
    animation: thinking-face-pop 0.45s cubic-bezier(0.34, 1.56, 0.64, 1);
}

/* Combat reactions own the expression without inheriting the thinking sway. */
.thinking-indicator.is-reacting .thinking-face {
    color: #fff0c7;
    text-shadow:
        0 0 max(1.15vmin, 7px) rgba(255, 224, 158, 0.8),
        0 0 max(2vmin, 12px) rgba(192, 144, 255, 0.38);
    animation: thinking-face-pop 0.45s cubic-bezier(0.34, 1.56, 0.64, 1);
}

@keyframes thinking-face-pop {
    0% {
        transform: scale(0.6) rotate(-8deg);
    }

    60% {
        transform: scale(1.18) rotate(4deg);
    }

    100% {
        transform: scale(1) rotate(0deg);
    }
}

/* A little sparkle over its head when it moves */
.thinking-face::after {
    content: '✦';
    position: absolute;
    top: -0.7em;
    right: -0.55em;
    font-size: 0.62em;
    color: #ffe9ad;
    opacity: 0;
}

.thinking-indicator.is-acting .thinking-face::after {
    animation: thinking-sparkle 1s ease-out;
}

@keyframes thinking-sparkle {
    0% {
        opacity: 0;
        transform: scale(0.3) rotate(0deg);
    }

    30% {
        opacity: 1;
        transform: scale(1.15) rotate(35deg);
    }

    100% {
        opacity: 0;
        transform: scale(0.5) rotate(90deg) translateY(-60%);
    }
}

.thinking-indicator.is-acting .thinking-pill {
    animation: thinking-pill-pop 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
    border-color: rgba(255, 219, 145, 0.55);
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, 0.12),
        0 0.45vmin 1.6vmin rgba(0, 0, 0, 0.5),
        0 0 3vmin rgba(255, 209, 102, 0.4);
}

.thinking-indicator.is-reacting .thinking-pill {
    animation: thinking-pill-pop 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
    border-color: rgba(229, 203, 255, 0.52);
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, 0.13),
        0 0.45vmin 1.6vmin rgba(0, 0, 0, 0.5),
        0 0 3.2vmin rgba(187, 137, 255, 0.35),
        0 0 1.6vmin rgba(255, 216, 139, 0.2);
}

/* Deckout uses the portrait as an emoji interaction, while its complete text
   remains exclusively in the storyboard. The live-region description stays
   available to assistive technology. */
.thinking-indicator.is-expression-only .thinking-label,
.thinking-indicator.is-expression-only .thinking-dots {
    display: none;
}

/* A stalled storyteller sentence is intentionally complete in the polite live
   region, but it cannot become a screen-wide battle object. Likewise, when a
   summoner already owns a status rail, the companion becomes a compact face so
   the two messages never cover cards or each other. */
.thinking-indicator.is-stalled .thinking-pill,
#player-portrait.portrait--has-status-effects .thinking-pill,
#opponent-portrait.portrait--has-status-effects .thinking-pill {
    width: 48px;
    min-width: 0;
    max-width: 48px;
    box-sizing: border-box;
    justify-content: center;
    gap: 0;
    padding: 3px 2px;
}

.thinking-indicator.is-stalled .thinking-label,
.thinking-indicator.is-stalled .thinking-dots,
#player-portrait.portrait--has-status-effects .thinking-label,
#player-portrait.portrait--has-status-effects .thinking-dots,
#opponent-portrait.portrait--has-status-effects .thinking-label,
#opponent-portrait.portrait--has-status-effects .thinking-dots {
    display: none;
}

.thinking-indicator.is-stalled .thinking-face,
#player-portrait.portrait--has-status-effects .thinking-face,
#opponent-portrait.portrait--has-status-effects .thinking-face {
    font-size: clamp(9px, 1.3vmin, 13px);
}

/* The companion is ONE object whose content changes — it must not become a
   different, smaller object when it swaps a kaomoji for an emoji. Measured at
   1400x640 the reaction pill was 42x32 against THINKING's 172x27: a quarter of
   the width and, contradictorily, taller. Pin the height across every state and
   give the reaction a presence in the same family as the text states. */
.thinking-indicator .thinking-pill {
    min-height: var(--ti-pill-height, calc(2 * max(0.9vmin, 6px) + max(2.1vmin, 15px)));
    box-sizing: border-box;
}

.thinking-indicator.is-expression-only .thinking-pill {
    justify-content: center;
    min-width: max(7.4vmin, 52px);
    padding-inline: max(1.25vmin, 9px);
}

.thinking-indicator.is-expression-only .thinking-face {
    font-family: 'Apple Color Emoji', 'Segoe UI Emoji', 'Noto Color Emoji', sans-serif;
    font-size: max(2.9vmin, 21px);
    /* The glyph is deliberately larger than the pill's text line box. Pinning
       the face's own box to that line box and centring the glyph inside lets it
       overflow symmetrically, so the emoji reads at full size without the pill
       growing around it. */
    display: inline-flex;
    align-items: center;
    justify-content: center;
    height: max(2.1vmin, 15px);
    line-height: 1;
    overflow: visible;
}

@keyframes thinking-pill-pop {
    0% {
        transform: translateY(-50%) translateX(0) scale(1);
    }

    45% {
        transform: translateY(-50%) translateX(0) scale(1.07);
    }

    100% {
        transform: translateY(-50%) translateX(0) scale(1);
    }
}

.thinking-indicator.is-acting::before {
    animation-duration: 0.9s;
}

.thinking-indicator.is-acting .thinking-aura {
    animation-duration: 1.6s;
}

.thinking-indicator.is-reacting::before {
    animation-duration: 1.15s;
}

.thinking-indicator.is-reacting .thinking-aura {
    animation-duration: 2s;
}

.thinking-label {
    font-family: 'Cinzel', Georgia, serif;
    font-size: max(1.55vmin, 12px);
    font-weight: 600;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: #f3ead8;
    background: linear-gradient(100deg, #efe2c4 0%, #ffe9ad 45%, #d9c1ff 90%);
    -webkit-background-clip: text;
    background-clip: text;
    -webkit-text-fill-color: transparent;
    line-height: 1;
    padding-top: 0.1em;
    /* optical centering for Cinzel caps */
}

/* ===== Dots: three moonlit orbs, cascading ===== */
.thinking-dots {
    display: inline-flex;
    align-items: center;
    gap: max(0.55vmin, 3.5px);
}

.thinking-dots i {
    width: max(0.72vmin, 5px);
    height: max(0.72vmin, 5px);
    border-radius: 50%;
    background: radial-gradient(circle at 35% 30%, #fff6df 0%, #ffd98c 38%, #b48cf0 100%);
    box-shadow: 0 0 max(0.7vmin, 4px) rgba(255, 217, 140, 0.55);
    opacity: 0.35;
}

.thinking-indicator.is-active .thinking-dots i {
    animation: thinking-dot 1.5s ease-in-out infinite;
}

.thinking-indicator.is-active .thinking-dots i:nth-child(2) {
    animation-delay: 0.18s;
}

.thinking-indicator.is-active .thinking-dots i:nth-child(3) {
    animation-delay: 0.36s;
}

/* Acting and reactions are complete statements; the thinking ellipsis yields. */
.thinking-indicator.is-acting .thinking-dots,
.thinking-indicator.is-reacting .thinking-dots {
    display: none;
}

@keyframes thinking-dot {

    0%,
    60%,
    100% {
        transform: translateY(0);
        opacity: 0.35;
    }

    30% {
        transform: translateY(-38%);
        opacity: 1;
    }
}

@media (max-width: 600px) {
    .thinking-pill {
        right: calc(100% + 8px);
        width: 64px;
        box-sizing: border-box;
        flex-direction: column;
        gap: 2px;
        padding: 6px;
        border-radius: 16px;
    }

    .thinking-face {
        font-size: 11px;
    }

    .thinking-label {
        width: 100%;
        max-width: none;
        font-size: 12px;
        line-height: 1.12;
        letter-spacing: 0.04em;
        text-align: center;
        white-space: normal;
        overflow-wrap: anywhere;
    }

    .thinking-dots {
        display: none;
    }
}

/* The narrow-phone command banner owns its transient companion UI. Keeping the
   pill and aura inside the frame avoids a floating left-side panel colliding
   with the centred board while still leaving the landscape art intact. */
@media (orientation: landscape) and (max-height: 560px) and (max-width: 932px) {
    :root {
        /* THE BUBBLE'S WIDTH CAP: the status plate's measured side-lane floor
           (status-indicator.css) plus what the pill can safely add to it.
           The plate's floor is a single formula across ten page boxes and is
           conservative on most of them — the real band between slot five and
           the crest, measured in WebKit, is 13px wider than the floor at
           844x376 (bars hidden) and 35-71px wider on every bars-visible box.
           The pill takes 8px of that everywhere (5px to spare at the tightest
           box) and up to 14px on boxes 300px tall or shorter — every
           bars-visible iPhone box, where the slack is 35px or more — which is
           what lets the widest label in the catalogue, "¡Intercambio!", stay
           on one line at 13px on the owner's own 844x280 box. Above 300px the
           board has grown, the slack has shrunk, and the bonus falls back to
           8px. `dvh` because the bar state is exactly what this keys on. */
        --ti-lane: calc(
            var(--phone-status-side-lane, 111px)
            + clamp(8px, calc((330px - 100dvh) * 0.5), 14px)
        );
    }

    .thinking-indicator {
        overflow: hidden;
        border-radius: var(--portrait-radius);
    }

    .thinking-indicator::before,
    .thinking-aura {
        inset: 0;
        border-radius: var(--portrait-radius);
        filter: none;
    }

    /* A COMPACT BUBBLE, NOT A CAPSULE: the face sits ABOVE the label.
       ==================================================================
       Reported: "make 'thinking' indicators summoner text larger at least on
       mobile. text is not readable". On the owner's iPhone 13 the page box is
       844x280, so --mf-dvmin is 2.8px and the old label
       `clamp(8px, calc(2.4 * var(--mf-dvmin)), 10px)` sat on its 8px floor —
       uppercase Cinzel at 8px, the smallest type in the whole battle HUD (the
       status plate floors at 10px, the nameplate at 10px). The face beside it
       was 9px.

       The type could not simply be enlarged in the desktop's one-row capsule.
       The pill hangs `top: 4px` beside the crest, and on every landscape page
       box that band still overlaps the board's fifth slot vertically (by 4px at
       932x300, 26px at 844x280 in WebKit, 32px at 667x275), so the pill's
       WIDTH is what keeps it off the cards: the free lane between slot five
       and the crest measures 100-183px on the boxes above 700px wide. A row of
       kaomoji + 13px label needs 130-175px ("¡Intercambio!" with "(@_@)" is
       170px) and reached slot five on the owner's own box; at the OLD type it
       already did so at 844x376, 852x364 and every 667 box.

       So the phone pill stacks: face on top, label beneath, both centred, the
       whole bubble no wider than its label. Its width is capped at `--ti-lane`
       (declared at the top of this block): the measured side-lane floor the
       status plate uses (status-indicator.css, `--phone-status-side-lane`,
       75-114px across the ten boxes above 700px, both bar states, proven clear
       of every slot by mobile-battle.spec.js and summoner-status-placement.
       spec.js) plus the 8-14px of measured slack the pill may add to it, so it
       cannot reach slot five however tall it grows. A label wider than that
       cap wraps — "MINHA / MANA!" — and only a single word longer than the cap
       breaks mid-word; nothing is ever ellipsised away. Height is free: below
       the pill on the player's side is the status plate, which the owner ruled
       the ephemeral pill may cover, and above the opponent's slots there is
       the deck it may cover too.

       The type is keyed to the lane cap, not to --mf-dvmin: the taller page
       boxes grow the board and NARROW the lane, so scaling the type up with
       the box was the wrong direction. Contract: thinkingIndicatorPhone.test.js;
       rendered proof at every landscape page box: thinking-indicator.spec.js. */
    .thinking-pill {
        top: 4px;
        right: 4px;
        /* `max-content`, not `auto`: an absolutely positioned box whose `right`
           exceeds its containing block (the crest) has NO shrink-to-fit room,
           so `auto` collapses it to its min-content width — and with a label
           that may break anywhere, min-content is one glyph. Measured: a
           38px-wide bubble reading "THINK / ING" in Firefox and WebKit. With
           max-content the bubble is as wide as its longest line and only the
           `max-width` cap below can make a label wrap. */
        width: max-content;
        min-width: 0;
        max-width: calc(100% - 8px);
        box-sizing: border-box;
        flex-direction: column;
        align-items: center;
        justify-content: center;
        gap: 1px;
        padding: 3px 4px;
        border-radius: 12px;
        transform: scale(0.94);
        transform-origin: top right;
    }

    .thinking-indicator.is-active .thinking-pill {
        transform: scale(1);
    }

    .thinking-face {
        font-size: 14px;
        line-height: 1;
    }

    .thinking-label {
        width: auto;
        max-width: 100%;
        min-width: 0;
        /* 13px wherever the cap is 95px or more — the owner's 844x280 box and
           every bars-visible iPhone and Pixel box, measured with real insets
           in Chrome — easing down only on the narrowest caps, which are all
           bars-hidden states of 59px-inset boxes (83px at 844x376, 90px at
           852x364), so that one-word resting labels — "Pensando",
           "Réfléchit" — still sit on one line there instead of breaking. The
           11px floor is the status plate's own size on those boxes; 8px, the
           reported size, was never on this curve. */
        font-size: clamp(11px, calc(var(--ti-lane, 111px) / 7.3), 13px);
        line-height: 1.1;
        letter-spacing: 0.02em;
        text-align: center;
        /* Wrap rather than clip: a label the lane cannot hold in one line is
           still shown whole. A single word wider than the cap ("¡Intercambio!")
           hyphenates in the document's language first; `anywhere` is the last
           resort behind it, for an engine without that dictionary. */
        white-space: normal;
        text-wrap: balance;
        -webkit-hyphens: auto;
        hyphens: auto;
        overflow-wrap: anywhere;
        overflow: visible;
        text-overflow: clip;
    }

    .thinking-dots {
        display: none;
    }

    .thinking-indicator.is-acting .thinking-pill,
    .thinking-indicator.is-reacting .thinking-pill {
        animation: thinking-pill-pop-phone 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
    }

    /* ---- the opponent's pill moves OFF the portrait -----------------------
       "The interaction indicator from the enemy should not be displayed on top
       of the profile picture. It should be displayed to the left just as it is
       on desktop. It doesn't matter if it's on top of the deck — it's better
       like that than drawing it on top of the profile picture."

       The opponent portrait is pinned to the top-right corner on this tier, so
       the space to its left is free and the pill goes there. The player's own
       pill is deliberately not touched: its portrait is bottom-right and it was
       never part of the report. */
    #opponent-portrait .thinking-indicator {
        /* The base rule clips to the rounded portrait frame so the aura hugs
           it. A pill that now lives outside that frame would be cut away
           entirely — invisible rather than merely misplaced. The aura and the
           ::before glow each carry their own border-radius, so nothing depends
           on the parent clipping. */
        overflow: visible;
    }

    #opponent-portrait .thinking-pill {
        /* Right edge flush against the portrait's left edge, with a small gap. */
        right: calc(100% + 6px);
        left: auto;
        top: 4px;
        transform-origin: top right;
        /* Above the deck it is now permitted to cover. */
        z-index: 2;
        /* Never wider than the lane between slot five and the crest — see
           `--ti-lane` at the top of this block. The fallback only exists so an
           unset token cannot invalidate the declaration and let the bubble
           span the board. */
        max-width: min(var(--ti-lane, 46vw), 190px);
    }

    /* THE PLAYER'S FACE LEAVES ITS OWN CREST TOO.
       ==========================================
       The block above fixed the enemy and said so explicitly: "The player's own
       pill is deliberately not touched: its portrait is bottom-right and it was
       never part of the report." It is part of the report now, in the owner's
       words: "self summoner lines are again rendered on top of their profile
       pic rather than to the side like for the enemy", about "the dialog system
       saying things like ouch" -- the reaction pill, not the status plate,
       which he confirmed separately is "perfet".

       So the two crests become the mirrored pair the 667 block below already
       argues they must be: "a face that sits in a different place on each reads
       as a bug". Same off-portrait placement, same top anchor, same overflow
       release, same label cap.

       WHY THIS NO LONGER COLLIDES WITH THE PLATE. The dodge removed above put a
       player pill at x 646..684 against a plate lane of x 580..691 at 844x280 --
       but that was the DODGED pill, shoved inboard to clear a plate that was
       still drawn ON the crest. The plate now lives in the side lane, bottom-
       anchored (`bottom: clamp(24px, 7.4dvmin, 32px)`, status-indicator.css),
       and this pill is top-anchored at `top: 4px`. They occupy opposite ends of
       the same lane, exactly as the opponent's pair already does -- the
       arrangement that measured 32px of clearance at 844x280.

       Contract: tests/unit/styles/reactionPillOffPortrait.test.js */
    #player-portrait .thinking-indicator {
        /* Same reason as the opponent's: the base rule clips to the rounded
           portrait frame, and a pill living outside it would be cut away
           entirely rather than merely misplaced. */
        overflow: visible;
    }

    #player-portrait .thinking-pill {
        /* Right edge flush against the portrait's left edge, with a small gap. */
        right: calc(100% + 6px);
        left: auto;
        top: 4px;
        transform-origin: top right;
        /* Above the deck, matching the opponent's. */
        z-index: 2;
        /* Never wider than the lane between slot five and the crest — see
           `--ti-lane` at the top of this block. The fallback only exists so an
           unset token cannot invalidate the declaration and let the bubble
           span the board. */
        max-width: min(var(--ti-lane, 46vw), 190px);
    }

    /* THE STATUS DODGE IS GONE, BECAUSE WHAT IT DODGED IS GONE.
       ========================================================
       A summoner carrying status effects used to push its reaction pill clear
       of the whole 112px status rail — `right: calc(rail + 1px)` for the
       player, `+ 6px` for the opponent — and shrink it to a bare 38px face,
       hiding the localized label, because the plate was drawn ON the crest and
       overlapped everything the pill could otherwise use.

       The plate now lives in the side lane (status-indicator.css) and the crest
       is free again, so that dodge is not merely unnecessary: it would aim the
       pill straight INTO the new lane. At 844x280 the dodged player pill
       occupies x 646..684 and the plate's lane is x 580..691 — a collision the
       removal is what prevents.

       Without the dodge the two surfaces fall into complementary bands by
       construction. The player's pill returns to the base phone placement,
       `top: 4px; right: 4px` INSIDE the crest, which is exactly where the
       narrow-box rule below already argued a summoner's expression belongs.
       The opponent's keeps its off-portrait placement — `right: calc(100% +
       6px); top: 4px`, the fix for "the interaction indicator from the enemy
       should not be displayed on top of the profile picture" — which puts it at
       the TOP of the lane, while the opponent's plate is bottom-anchored at the
       crest's lower edge and grows upward. Measured clearance between them is
       32px at 844x280 and never less than 15px on any landscape box.

       The label comes back with the pill: it was only hidden because the 38px
       dodge had no room for it. */
}

/* THE NARROWEST PAGE BOX KEEPS ITS FACE ON THE CREST — in EVERY state now,
   because it is the one class with no lane at all.
   ==========================================================================
   Everywhere above 700px the plate lives in the side lane
   (`--phone-status-side-lane`, status-indicator.css), the crest is free, and
   the stacked bubble above fits the lane by construction. The 667-wide class
   is the exception: the band between the board's rightmost slot and the crest
   is 64px at 667x275 and 38px at 667x335, so no lane exists, the plate stays
   ON the crest exactly as it shipped, and no bubble that can hold a word fits
   beside the crest either. The face therefore takes the one part of the crest
   nothing else owns — the strip BETWEEN the two resource discs, on the crest's
   bottom row. It cannot reach the board (it is inside the crest) and it cannot
   reach the ledger (which is anchored `6px + disc` above the crest's lower
   edge, i.e. above this by construction).

   This used to apply only to a crest carrying status effects; without them the
   pill kept the lane placement and, with no lane to sit in, painted across
   slot five — 65 of 72 label/locale pairs at 667x275 and 72 of 72 at 667x335,
   measured on the shipped CSS in Chrome and WebKit. A pill drawn over a card
   is a defect, not a feature, so the on-crest seat is now the ONE placement
   this class has: it cannot jump when a status arrives, and both bar states
   agree. The label is not shown here; the polite live region still carries
   the complete text.

   Both sides move, not just the one that measured red: the two crests are a
   mirrored pair and a face that sits in a different place on each reads as a
   bug. */
@media (orientation: landscape) and (max-height: 560px) and (max-width: 700px) {
    :root {
        /* The resource discs' phone size, verbatim from indicators.css, so the
           strip between them is derived rather than guessed: they sit at
           `left: 4px` / `right: 4px` inside the crest's lower corners. */
        --ti-crest-disc: clamp(24px, calc(7.4 * var(--mf-dvmin)), 32px);
    }

    #player-portrait .thinking-pill,
    #opponent-portrait .thinking-pill {
        /* Stretched across the strip between the discs, 2px clear of each:
           40px at 667x275, 49px at 667x335. A fixed 38px used to be the seat,
           which the crest outgrows on the taller box. */
        top: auto;
        bottom: 4px;
        left: calc(6px + var(--ti-crest-disc));
        right: calc(6px + var(--ti-crest-disc));
        width: auto;
        min-width: 0;
        max-width: none;
        justify-content: center;
        padding: 3px 0;
        transform-origin: bottom center;
    }

    #player-portrait .thinking-label,
    #opponent-portrait .thinking-label {
        display: none;
    }

    /* The strip cannot hold the lane tier's 14px face — the widest kaomoji is
       55px at that size — so the face is sized to the strip: 9px on the 40px
       strip, up to 11px on the 49px one. Written at the status dodge's
       specificity so a status arriving does not shrink the face. */
    #player-portrait .thinking-indicator .thinking-face,
    #opponent-portrait .thinking-indicator .thinking-face {
        font-size: clamp(9px, calc(3.2 * var(--mf-dvmin)), 11px);
    }
}

@keyframes thinking-pill-pop-phone {
    0%, 100% { transform: scale(1); }
    45% { transform: scale(1.07); }
}

/* ===== Accessibility ===== */
@media (forced-colors: active) {
    .thinking-label {
        background: none;
        color: ButtonText;
        -webkit-text-fill-color: currentColor;
    }

    .thinking-pill {
        border-color: ButtonText;
    }
}

.thinking-indicator .sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
    border: 0;
}

@media (prefers-reduced-motion: reduce) {

    .thinking-indicator.is-active::before,
    .thinking-indicator.is-active .thinking-aura,
    .thinking-indicator.is-active .thinking-dots i,
    .thinking-indicator.is-active .thinking-face,
    .thinking-indicator.is-acting .thinking-face,
    .thinking-indicator.is-reacting .thinking-face,
    .thinking-indicator.is-acting .thinking-face::after,
    .thinking-indicator.is-acting .thinking-pill,
    .thinking-indicator.is-reacting .thinking-pill {
        animation: none;
    }

    .thinking-indicator.is-active::before {
        box-shadow:
            0 0 2.2vmin 0.4vmin rgba(157, 113, 242, 0.4),
            0 0 4vmin 1.1vmin rgba(255, 205, 112, 0.15);
    }

    .thinking-indicator.is-active .thinking-dots i {
        opacity: 0.85;
    }

    .thinking-pill,
    .thinking-indicator.is-active .thinking-pill {
        transition-duration: 0.01s;
    }
}
