/* =============================================================
 * feedback.css — global click / tap visual feedback
 *
 * Paired with assets/controllers/global_feedback_controller.js.
 * The controller toggles classes; this stylesheet handles the
 * visual side so the JS stays small and the CSS stays cache-able
 * independently.
 *
 * Conventions:
 *   .is-debouncing     — the button was clicked and is frozen for
 *                         ~1.5 s. Shown with an inline spinner and
 *                         reduced opacity so the user knows their
 *                         click landed.
 *   .tap-feedback      — opt-in scale-down on press for any tappable
 *                         element. Extended via [data-tap] below so
 *                         all buttons pick it up without manual
 *                         classes on each one.
 * ============================================================= */

/* ───────────────────────── Button debounce feedback ─────────── */

.is-debouncing {
    position: relative;
    opacity: 0.7;
    cursor: wait !important;
    pointer-events: none;
}

/* Inline spinner next to the existing label, so the user sees a
 * visual "something is happening" cue without us having to touch
 * the button's textContent (which would break buttons containing
 * icons or nested markup). Uses a pure CSS conic-gradient spinner
 * — no SVG, no image fetch. */
.is-debouncing::after {
    content: "";
    display: inline-block;
    width: 0.9em;
    height: 0.9em;
    margin-left: 0.5em;
    vertical-align: -0.15em;
    border-radius: 50%;
    border: 2px solid currentColor;
    border-top-color: transparent;
    animation: debounce-spin 600ms linear infinite;
}

@keyframes debounce-spin {
    to { transform: rotate(360deg); }
}

/* Respect reduced-motion users: no spinner animation, just static. */
@media (prefers-reduced-motion: reduce) {
    .is-debouncing::after {
        animation: none;
    }
}

/* ───────────────────────── Tap feedback (press ripple) ───────── */

/*
 * Every interactive element gets a tiny scale-down on press so the
 * user feels the touch even before the network responds. We target
 * buttons, submit inputs, and anchor elements with role="button" or
 * data-turbo-method — i.e. anything the user expects to behave as
 * a tap target.
 *
 * Scoped with :not(.is-debouncing) so the debounce style (opacity
 * + spinner) takes precedence once the click is registered.
 */
@media (hover: none), (pointer: coarse) {
    button:not(:disabled):not(.is-debouncing),
    input[type="submit"]:not(:disabled):not(.is-debouncing),
    input[type="button"]:not(:disabled):not(.is-debouncing),
    [role="button"]:not(.is-debouncing),
    a[data-turbo-method]:not(.is-debouncing) {
        transition: transform 80ms ease-out, opacity 80ms ease-out;
    }

    button:not(:disabled):not(.is-debouncing):active,
    input[type="submit"]:not(:disabled):not(.is-debouncing):active,
    input[type="button"]:not(:disabled):not(.is-debouncing):active,
    [role="button"]:not(.is-debouncing):active,
    a[data-turbo-method]:not(.is-debouncing):active {
        transform: scale(0.97);
        opacity: 0.92;
    }
}

/* ───────────────────────── Skeleton loading cards ───────────── */

/*
 * Placeholder shapes shown in the bottom sheet of the map search
 * while /api/stations/nearby is in flight. Match the real station
 * card structure so the swap doesn't shift the layout. Pulsing
 * animation degrades to a static grey block under
 * prefers-reduced-motion.
 *
 * The skeleton card uses the same .station-card wrapper class as
 * the real card so margins, padding and borders stay consistent.
 */

.station-card--skeleton {
    pointer-events: none;
}

.skeleton-line {
    background: linear-gradient(
        90deg,
        var(--grey-200, #e7e8ec) 0%,
        var(--grey-100, #f2f3f7) 50%,
        var(--grey-200, #e7e8ec) 100%
    );
    background-size: 200% 100%;
    border-radius: 6px;
    animation: skeleton-shimmer 1.4s ease-in-out infinite;
}

.skeleton-line--name { height: 16px; width: 70%; margin-bottom: 6px; }
.skeleton-line--hardware { height: 12px; width: 45%; }
.skeleton-line--label { height: 10px; width: 60%; margin: 0 auto 6px; }
.skeleton-line--value { height: 14px; width: 55%; margin: 0 auto; }
.skeleton-line--cta { height: 14px; width: 40%; }
.skeleton-line--header-title { height: 18px; width: 140px; margin: 6px 0; }

.skeleton-chip {
    width: 48px;
    height: 22px;
    border-radius: 11px;
    background: linear-gradient(
        90deg,
        var(--grey-200, #e7e8ec) 0%,
        var(--grey-100, #f2f3f7) 50%,
        var(--grey-200, #e7e8ec) 100%
    );
    background-size: 200% 100%;
    animation: skeleton-shimmer 1.4s ease-in-out infinite;
}

@keyframes skeleton-shimmer {
    0%   { background-position: 200% 0; }
    100% { background-position: -200% 0; }
}

@media (prefers-reduced-motion: reduce) {
    .skeleton-line,
    .skeleton-chip {
        animation: none;
        background: var(--grey-200, #e7e8ec);
    }
}

/* ───────────────────────── View Transitions ─────────────────── */

/*
 * Enable a subtle crossfade when the browser runs a View Transition
 * for a Turbo navigation. We only customize the duration — the
 * default fade looks good enough, just faster.
 */
@supports (view-transition-name: root) {
    ::view-transition-old(root),
    ::view-transition-new(root) {
        animation-duration: 180ms;
    }
}

/*
 * Optimistic chat bubble — rendered instantly by chat_controller.js
 * before the server confirms the send. The slight opacity tells the
 * user "we've got it, we're just finalising" without looking broken.
 * When the POST response lands, the .is-optimistic class is removed
 * and the bubble snaps to full opacity.
 */
.is-optimistic {
    opacity: 0.65;
    transition: opacity 150ms ease-out;
}
