/* ==========================================================================
   mn-motion.css — the motion layer of the public site.

   A SEPARATE FILE, linked only from _Layout.cshtml. The admin panel renders
   through _LayoutAdmin and never downloads a byte of this: a staff screen that
   moves while its reader is working a moderation queue is a worse screen, and
   the client's contract treats the panel as a tool rather than a shopfront.
   Every rule here is therefore scoped to `body.mn-public` as a second belt,
   so a stray import cannot leak motion into `body.mn-admin`.

   Three states matter and they are deliberately different:

     body.mn-public   the sheet is loaded. Hover, focus and press feedback only.
                      All of it works with JavaScript off.
     body.mn-motion   mn-motion.js is running AND the reader has not asked for
                      reduced motion. Only this state hides anything before it
                      is revealed, which is why a scroll reveal can never leave
                      a page blank: with no script the class is never added and
                      every .mn-reveal is simply a normal element.
     reduce           the media query at the foot cuts every duration to nil.

   RTL: the document is `dir="rtl"` on <html>, always. Logical properties are
   used throughout except where a transform is involved — `transform` has no
   logical form, so `translateX(-3px)` here means "forward along the line",
   which on this site is leftward, and `transform-origin: 100%` means the
   start edge. Each such case is commented where it appears.
   ========================================================================== */

/* ---- motion tokens ------------------------------------------------------- */
body.mn-public {
    /* Two curves do almost all the work. The first is for anything the pointer
       drives: it has to arrive quickly or the control feels laggy. The second
       is a long expo tail for scroll reveals, where the eye is following the
       page rather than a finger. */
    --mn-ease: cubic-bezier(.2, .7, .3, 1);
    --mn-ease-out: cubic-bezier(.16, 1, .3, 1);
    --mn-ease-pop: cubic-bezier(.34, 1.4, .64, 1);

    --mn-t-1: .13s;   /* colour and tint: below the threshold of feeling slow */
    --mn-t-2: .24s;   /* lift, scale, the ordinary hover */
    --mn-t-3: .42s;   /* image zoom inside a card */
    --mn-t-4: .70s;   /* scroll reveal */

    --mn-lift: -3px;
    --mn-lift-lg: -5px;

    /* The ink a title takes when its card is hovered. Steel on the light
       public pages; on the dark dashboard steel disappears, so it is the
       brand amber there instead. */
    --mn-hover-ink: var(--mn-accent-700);
    --mn-hover-line: var(--mn-brand-line);
}

body.mn-public.mn-dash {
    --mn-hover-ink: var(--mn-brand);
}

/* ==========================================================================
   1. Scroll reveal
   ========================================================================== */

/* The pre-state lives behind body.mn-motion and nowhere else. mn-motion.js
   adds that class only after it has confirmed IntersectionObserver exists and
   that the reader has not asked for reduced motion, so the failure mode of
   every branch below is "the content is just there", never "the content is
   invisible and nothing arrives to reveal it".

   --mn-delay carries the stagger. It is set per element by the script rather
   than by nth-child, because the same grid holds three cards on one page and
   twenty-four on another. */
body.mn-motion .mn-reveal {
    opacity: 0;
    transform: translate3d(0, 20px, 0);
    transition:
        opacity var(--mn-t-4) var(--mn-ease-out) var(--mn-delay, 0ms),
        transform var(--mn-t-4) var(--mn-ease-out) var(--mn-delay, 0ms);
}

/* No will-change on the reveal, on purpose. A results page marks two dozen
   cards at once and the homepage sixty-nine elements; promising the
   compositor a layer for each of them costs more memory than the hint saves,
   and an opacity/transform transition is composited while it runs anyway. */

body.mn-motion .mn-reveal.is-in {
    opacity: 1;
    transform: none;
}

/* Variants. The script picks one from the element's role; a view can also ask
   for one by hand with class="mn-reveal mn-reveal-start". */
body.mn-motion .mn-reveal-start { transform: translate3d(28px, 0, 0); }   /* enters from the start edge (right, RTL) */
body.mn-motion .mn-reveal-end   { transform: translate3d(-28px, 0, 0); }
body.mn-motion .mn-reveal-zoom  { transform: scale(.955); }
body.mn-motion .mn-reveal-fade  { transform: none; }
body.mn-motion .mn-reveal-start.is-in,
body.mn-motion .mn-reveal-end.is-in,
body.mn-motion .mn-reveal-zoom.is-in { transform: none; }

/* A printed page has no scroll, so nothing would ever be revealed. */
@media print {
    body.mn-motion .mn-reveal {
        opacity: 1 !important;
        transform: none !important;
    }
}

/* ==========================================================================
   2. Hover, focus and press
   Everything in this block works without JavaScript.
   ========================================================================== */

/* ---- cards --------------------------------------------------------------- */
body.mn-public .mn-card {
    transition:
        transform var(--mn-t-2) var(--mn-ease),
        box-shadow var(--mn-t-2) var(--mn-ease),
        border-color var(--mn-t-2) var(--mn-ease);
}

/* :focus-within, not only :hover. The card's link and its heart are both
   focusable, and a keyboard reader deserves the same "this one" cue that a
   pointer gets. */
body.mn-public .mn-card:hover,
body.mn-public .mn-card:focus-within {
    transform: translate3d(0, var(--mn-lift), 0);
    box-shadow: var(--mn-shadow-md);
    border-color: var(--mn-hover-line);
}

body.mn-public .mn-card-media img,
body.mn-public .mn-rowcard-media img,
body.mn-public .mn-band-thumb img,
body.mn-public .mn-projgallery-thumb img,
body.mn-public .mn-photogrid img {
    transition: transform var(--mn-t-3) var(--mn-ease);
}

/* .mn-card-media already carries overflow:hidden, so the crop holds. */
body.mn-public .mn-card:hover .mn-card-media img,
body.mn-public .mn-card:focus-within .mn-card-media img,
body.mn-public .mn-rowcard:hover .mn-rowcard-media img,
body.mn-public .mn-photogrid a:hover img {
    transform: scale(1.06);
}

body.mn-public .mn-card-title,
body.mn-public .mn-rowcard-body strong,
body.mn-public .mn-home-list-title,
body.mn-public .mn-opp-name {
    transition: color var(--mn-t-1) linear;
}

body.mn-public .mn-card:hover .mn-card-title,
body.mn-public .mn-card:focus-within .mn-card-title,
body.mn-public .mn-rowcard:hover .mn-rowcard-body strong,
body.mn-public .mn-home-list a:hover .mn-home-list-title {
    color: var(--mn-hover-ink);
}

/* The mode chip rides the photograph; lifting it with the card keeps it
   attached to the image rather than sliding across it. */
body.mn-public .mn-card-media .mn-tag-mode {
    transition: transform var(--mn-t-2) var(--mn-ease);
}
body.mn-public .mn-card:hover .mn-card-media .mn-tag-mode {
    transform: translateY(-2px);
}

/* ---- «نشان‌شده‌ها», the heart ----------------------------------------------- */
body.mn-public .mn-card-fav {
    transition:
        transform var(--mn-t-2) var(--mn-ease-pop),
        color var(--mn-t-1) linear,
        background var(--mn-t-1) linear;
}
body.mn-public .mn-card-fav:hover,
body.mn-public .mn-card-fav:focus-visible { transform: scale(1.16); }
body.mn-public .mn-card-fav:active { transform: scale(.92); }

/* Favouriting posts and reloads the page, so there is no moment during which
   the heart could animate from empty to full. It plays on arrival instead,
   which is the confirmation the round trip otherwise swallows. */
body.mn-motion .mn-card-fav.is-on svg { animation: mn-heart .5s var(--mn-ease-pop); }

/* ---- buttons ------------------------------------------------------------- */
body.mn-public .mn-btn {
    transition:
        background var(--mn-t-1) ease,
        border-color var(--mn-t-1) ease,
        color var(--mn-t-1) ease,
        transform var(--mn-t-1) var(--mn-ease),
        box-shadow var(--mn-t-2) var(--mn-ease);
}
body.mn-public .mn-btn:hover { transform: translateY(-1px); box-shadow: var(--mn-shadow-sm); }

/* The press has to be faster than the release or the button feels spongy. */
body.mn-public .mn-btn:active {
    transform: translateY(0) scale(.985);
    transition-duration: .06s;
    box-shadow: none;
}
body.mn-public .mn-btn:disabled,
body.mn-public .mn-btn[aria-disabled="true"] { transform: none; box-shadow: none; }

/* A single sheen across the amber button. It is the one control on the page
   the client wants pressed, and it is the only element given an animation it
   did not have to be hovered twice to notice. */
body.mn-public .mn-btn-brand { position: relative; overflow: hidden; }
body.mn-public .mn-btn-brand > * { position: relative; z-index: 1; }
body.mn-public .mn-btn-brand::after {
    content: "";
    position: absolute;
    inset-block: -40%;
    inset-inline-start: -60%;
    width: 42%;
    background: linear-gradient(100deg, transparent, rgba(255, 255, 255, .55), transparent);
    transform: skewX(-16deg);
    pointer-events: none;
    opacity: 0;
}
body.mn-motion .mn-btn-brand:hover::after { animation: mn-sheen .72s var(--mn-ease) 1; }

/* ---- links --------------------------------------------------------------- */

/* The header. The existing rule draws the current-page marker with an inset
   box-shadow; it is replaced here by a rule that can be animated, and the
   shadow is switched off so the two do not stack into a 4px line. */
body.mn-public .mn-nav-links a {
    position: relative;
    transition: color var(--mn-t-1) ease;
}
body.mn-public .mn-nav-links a:hover,
body.mn-public .mn-nav-links a[aria-current="page"] { box-shadow: none; }
body.mn-public .mn-nav-links a::after {
    content: "";
    position: absolute;
    inset-inline: 0;
    inset-block-end: 0;
    height: 2px;
    background: var(--mn-brand);
    transform: scaleX(0);
    /* 100% is the start edge under dir="rtl": the rule grows the way the
       words are read. */
    transform-origin: 100% 50%;
    transition: transform var(--mn-t-2) var(--mn-ease);
}
body.mn-public .mn-nav-links a:hover::after,
body.mn-public .mn-nav-links a:focus-visible::after,
body.mn-public .mn-nav-links a[aria-current="page"]::after { transform: scaleX(1); }

/* The footer columns. translateX(-3px) is forward along an RTL line. */
body.mn-public .mn-footer-col a,
body.mn-public .mn-footer-legal a {
    display: inline-block;
    transition: color var(--mn-t-1) ease, transform var(--mn-t-2) var(--mn-ease);
}
body.mn-public .mn-footer-col a:hover,
body.mn-public .mn-footer-legal a:hover { transform: translateX(-3px); }

body.mn-public .mn-topbar a,
body.mn-public .mn-crumb a,
body.mn-public .mn-sechead-all,
body.mn-public .mn-home-more,
body.mn-public .mn-band-more {
    transition: color var(--mn-t-1) ease, transform var(--mn-t-2) var(--mn-ease);
}
body.mn-public .mn-sechead-all:hover,
body.mn-public .mn-home-more:hover,
body.mn-public .mn-band-more:hover { transform: translateX(-3px); }

/* The mark. A logo that answers the pointer is the cheapest signal that the
   header is a live thing rather than a printed banner. */
body.mn-public .mn-nav img[alt="معدنچی"] {
    transition: transform var(--mn-t-2) var(--mn-ease-pop);
}
body.mn-public .mn-nav a:hover img[alt="معدنچی"] { transform: scale(1.06) rotate(-2deg); }

/* ---- tiles, panels and rows ---------------------------------------------- */
body.mn-public .mn-cattiles a,
body.mn-public .mn-cattiles-photo a,
body.mn-public .mn-roletiles a,
body.mn-public .mn-tile,
body.mn-public .mn-prop,
body.mn-public .mn-panel-link,
body.mn-public .mn-rowcard,
body.mn-public .mn-oppcard,
body.mn-public .mn-pricecard,
body.mn-public .mn-bandcard,
body.mn-public .mn-minecard,
body.mn-public .mn-thread,
body.mn-public .mn-offer,
body.mn-public .mn-sellercard,
body.mn-public .mn-choice {
    transition:
        transform var(--mn-t-2) var(--mn-ease),
        box-shadow var(--mn-t-2) var(--mn-ease),
        border-color var(--mn-t-2) var(--mn-ease),
        background var(--mn-t-1) ease;
}
body.mn-public .mn-cattiles a:hover,
body.mn-public .mn-cattiles-photo a:hover,
body.mn-public .mn-roletiles a:hover,
body.mn-public .mn-panel-link:hover,
body.mn-public .mn-rowcard:hover,
body.mn-public .mn-oppcard:hover,
body.mn-public .mn-pricecard:hover,
body.mn-public .mn-bandcard:hover,
body.mn-public .mn-thread:hover,
body.mn-public .mn-sellercard:hover,
body.mn-public .mn-choice:hover {
    transform: translate3d(0, var(--mn-lift), 0);
    box-shadow: var(--mn-shadow-md);
    border-color: var(--mn-hover-line);
}

/* .mn-tile and .mn-prop are small; a 3px lift on a 90px box reads as a jump,
   so they get half of it and a tint instead. */
body.mn-public .mn-tile:hover,
body.mn-public .mn-prop:hover {
    transform: translate3d(0, -2px, 0);
    box-shadow: var(--mn-shadow-sm);
}
body.mn-public .mn-prop-icon,
body.mn-public .mn-ctastat-icon,
body.mn-public .mn-cattile-icon {
    transition: transform var(--mn-t-2) var(--mn-ease-pop), color var(--mn-t-1) ease;
}
body.mn-public .mn-prop:hover .mn-prop-icon,
body.mn-public .mn-cattiles a:hover .mn-cattile-icon { transform: scale(1.12) translateY(-2px); }

/* The five links inside a province panel and the province rail on the map. */
body.mn-public .mn-minemap-stat,
body.mn-public .mn-minemap-rail-row,
body.mn-public .mn-facet-item,
body.mn-public .mn-doc-row,
body.mn-public .mn-panel-row {
    transition: background var(--mn-t-1) ease, transform var(--mn-t-2) var(--mn-ease);
}
body.mn-public .mn-minemap-stat:hover,
body.mn-public .mn-minemap-rail-row:hover,
body.mn-public .mn-facet-item:hover,
body.mn-public .mn-doc-row:hover { transform: translateX(-2px); }

/* ---- tags, pills and chips ----------------------------------------------- */
body.mn-public a.mn-tag,
body.mn-public .mn-pill,
body.mn-public .mn-spectab,
body.mn-public .mn-sort,
body.mn-public .mn-syn,
body.mn-public .mn-hero-chips a {
    transition:
        background var(--mn-t-1) ease,
        border-color var(--mn-t-1) ease,
        color var(--mn-t-1) ease,
        transform var(--mn-t-2) var(--mn-ease-pop),
        box-shadow var(--mn-t-2) var(--mn-ease);
}
body.mn-public a.mn-tag:hover,
body.mn-public .mn-pill:hover,
body.mn-public .mn-spectab:hover,
body.mn-public .mn-sort:hover { transform: translateY(-1px); }

/* The chips sit on the dark hero band, where a shadow is what reads as lift. */
body.mn-public .mn-hero-chips a:hover {
    transform: translateY(-2px);
    box-shadow: 0 8px 20px rgba(0, 0, 0, .34);
}
body.mn-public .mn-pill-x { transition: transform var(--mn-t-1) var(--mn-ease-pop); }
body.mn-public .mn-pill:hover .mn-pill-x { transform: rotate(90deg); }

/* ---- tables -------------------------------------------------------------- */
body.mn-public .mn-table tbody tr {
    transition: background var(--mn-t-1) ease;
}
body.mn-public .mn-table tbody tr:hover { background: var(--mn-hover-tint); }

/* ---- form controls ------------------------------------------------------- */
body.mn-public .mn-input,
body.mn-public .mn-select,
body.mn-public textarea.mn-input,
body.mn-public .mn-otp-input {
    transition:
        border-color var(--mn-t-1) ease,
        background var(--mn-t-1) ease,
        box-shadow var(--mn-t-2) var(--mn-ease);
}
body.mn-public .mn-input:focus,
body.mn-public .mn-select:focus,
body.mn-public textarea.mn-input:focus,
body.mn-public .mn-otp-input:focus {
    box-shadow: 0 0 0 3px var(--mn-brand-tint);
}

/* The search pill is the widest single control on the site and the one the
   client asked twice to make prominent. It rises when it takes focus. */
body.mn-public .mn-searchpill {
    transition: transform var(--mn-t-2) var(--mn-ease), box-shadow var(--mn-t-2) var(--mn-ease);
}
body.mn-public .mn-searchpill:focus-within {
    transform: translateY(-2px);
    box-shadow: var(--mn-shadow-lg);
}

body.mn-public .mn-upload-drop { transition: border-color var(--mn-t-1) ease, background var(--mn-t-1) ease; }

/* ---- gallery ------------------------------------------------------------- */
body.mn-public .mn-gallery-thumb,
body.mn-public .mn-projgallery-thumb,
body.mn-public .mn-media-tile {
    transition:
        opacity var(--mn-t-1) ease,
        transform var(--mn-t-2) var(--mn-ease),
        border-color var(--mn-t-1) ease;
}
body.mn-public .mn-gallery-thumb:hover,
body.mn-public .mn-projgallery-thumb:hover,
body.mn-public .mn-media-tile:hover { transform: translateY(-2px); }

/* ---- the map ------------------------------------------------------------- */

/* The pin's own transform is the centring translate; a scale that dropped it
   would move every pin half its width off the coordinate it marks. */
body.mn-public .mn-minemap-pin {
    transition: transform var(--mn-t-2) var(--mn-ease-pop);
}
body.mn-public .mn-minemap-pin:hover,
body.mn-public .mn-minemap-pin:focus-visible {
    transform: translate(-50%, -50%) scale(1.3);
}
body.mn-public .mn-minemap-dot { transition: box-shadow var(--mn-t-2) var(--mn-ease); }
body.mn-public .mn-minemap-pin:hover .mn-minemap-dot {
    box-shadow: 0 0 0 6px rgba(255, 179, 0, .22);
}

/* The province outlines already transition their fill. The stroke is added
   here so a hovered province firms up rather than only tinting. */
body.mn-public .mn-minemap-provinces path {
    transition: fill var(--mn-t-1) ease, stroke var(--mn-t-1) ease, stroke-width var(--mn-t-1) ease;
}

/* ---- steppers ------------------------------------------------------------ */

/* One dot breathes: the stage the project has actually reached. The rest are
   static, so the moving one is the answer to "where is it now". */
body.mn-motion .mn-projsteps li.is-done:last-child .mn-projsteps-dot,
body.mn-motion .mn-projsteps li.is-done:has(+ li:not(.is-done)) .mn-projsteps-dot,
body.mn-motion .mn-step-current .mn-step-mark,
body.mn-motion .mn-wstep-current .mn-wstep-mark {
    animation: mn-pulse 2.6s var(--mn-ease) infinite;
}

/* ---- the bell and the user menu ------------------------------------------ */
body.mn-motion .mn-bell:hover svg { animation: mn-swing .62s var(--mn-ease); }

/* .mn-bell-dot marks unread notifications. It draws attention once, on
   arrival, and then stops: a badge that pulses forever is a badge the reader
   learns to look past. */
body.mn-motion .mn-bell-dot { animation: mn-ping 1.6s var(--mn-ease) 3; }

/* Alpine toggles these with display, and a CSS animation runs when an element
   goes from display:none to shown. That is what makes an entry animation
   possible here without an x-transition attribute on the markup. */
body.mn-motion .mn-usermenu-pop { animation: mn-pop .18s var(--mn-ease-out); }
body.mn-motion .mn-minecard { animation: mn-pop .2s var(--mn-ease-out); }
body.mn-motion .mn-toast { animation: mn-toast-in .42s var(--mn-ease-pop); }

/* ==========================================================================
   3. The sticky header and the scroll indicator
   ========================================================================== */

/* The thin support bar scrolls away and the navigation stays. Nothing else on
   the page is sticky, so this is the only new stacking context: 50 sits under
   the toast at 60 and the reading progress at 70. */
body.mn-public .mn-nav {
    position: sticky;
    inset-block-start: 0;
    z-index: 50;
    transition: box-shadow var(--mn-t-2) var(--mn-ease), background var(--mn-t-2) var(--mn-ease);
}

/* Added by the script once the page has scrolled past the support bar. */
body.mn-public.is-scrolled .mn-nav {
    box-shadow: var(--mn-shadow-md);
    border-bottom-color: transparent;
}

/* An in-page jump must not land under the bar now covering the top. On the
   root, not on body: the document's scrolling element is <html>, and
   scroll-padding set anywhere else is ignored. This sheet is linked from the
   public layout alone, so the rule cannot reach the admin panel. */
html { scroll-padding-block-start: 76px; }

@media print {
    body.mn-public .mn-nav { position: static; box-shadow: none; }
}

/* How much of the page is left. Created by the script, so it does not exist
   for a reader without JavaScript and nothing has to reserve space for it. */
.mn-progress {
    position: fixed;
    inset-block-start: 0;
    inset-inline: 0;
    height: 2px;
    z-index: 70;
    pointer-events: none;
    background: transparent;
}
.mn-progress > i {
    display: block;
    height: 100%;
    background: linear-gradient(to left, var(--mn-brand), var(--mn-accent-500));
    /* Grows from the start edge, which is the right under dir="rtl". */
    transform-origin: 100% 50%;
    transform: scaleX(var(--mn-progress, 0));
}
@media print { .mn-progress { display: none; } }

/* ==========================================================================
   4. Parallax
   ========================================================================== */

/* The two heroes carry their photograph as a background-image, so the cheapest
   parallax on the page is the one that moves the background rather than the
   element: no layout, no second layer, and the gradient wash over the photo
   stays exactly where it was. The script writes --mn-par (scroll) and
   --mn-parx (pointer); with no script both fall back to 0 and the position is
   the one madanchi.css already sets. */
body.mn-motion .mn-hero.has-photo {
    background-position: calc(50% + var(--mn-parx, 0px)) calc(38% + var(--mn-par, 0px));
}
body.mn-motion .mn-page-hero {
    background-position: calc(50% + var(--mn-parx, 0px)) calc(42% + var(--mn-par, 0px));
}

/* The closing band's photograph is a real <img>, clipped by the band's own
   overflow:hidden. It is scaled up first so that moving it never uncovers an
   edge of the navy underneath. */
body.mn-motion .mn-home-cta > img {
    transform: scale(1.22) translate3d(0, var(--mn-par, 0px), 0);
    will-change: transform;
}

/* The hero's own words rise once, on load, one after the other. This is the
   first thing the client sees on opening the site and the only place on it
   that animates without being asked to. */
body.mn-motion .mn-hero.has-photo .mn-hero-inner > * {
    animation: mn-rise .78s var(--mn-ease-out) backwards;
}
body.mn-motion .mn-hero.has-photo .mn-hero-inner > *:nth-child(1) { animation-delay: .04s; }
body.mn-motion .mn-hero.has-photo .mn-hero-inner > *:nth-child(2) { animation-delay: .11s; }
body.mn-motion .mn-hero.has-photo .mn-hero-inner > *:nth-child(3) { animation-delay: .18s; }
body.mn-motion .mn-hero.has-photo .mn-hero-inner > *:nth-child(4) { animation-delay: .25s; }
body.mn-motion .mn-hero.has-photo .mn-hero-inner > *:nth-child(5) { animation-delay: .32s; }
body.mn-motion .mn-hero.has-photo .mn-hero-inner > *:nth-child(6) { animation-delay: .39s; }

/* ---- pointer tilt -------------------------------------------------------- */

/* Only the three showcase grids, and only where there is a real pointer: a
   tilt driven by a finger is a tilt that fires on every scroll. The class is
   added by the script, so the transform below never competes with the plain
   hover lift above on a device that will not tilt. */
/* The selectors are spelled out rather than written against .mn-tilt alone so
   that they match the lift rules of section 2 element for element. At equal
   specificity the later rule wins, and this block is the later one, so a
   tilted tile composes its lift into the tilt instead of losing it. */
@media (hover: hover) and (pointer: fine) {
    body.mn-motion .mn-cattiles a.mn-tilt,
    body.mn-motion .mn-cattiles-photo a.mn-tilt,
    body.mn-motion .mn-roletiles a.mn-tilt,
    body.mn-motion .mn-props-grid .mn-prop.mn-tilt {
        transform:
            perspective(760px)
            rotateX(var(--mn-tilt-x, 0deg))
            rotateY(var(--mn-tilt-y, 0deg))
            translate3d(0, var(--mn-tilt-lift, 0px), 0);
        transition: transform var(--mn-t-2) var(--mn-ease);
    }
    body.mn-motion .mn-cattiles a.mn-tilt:hover,
    body.mn-motion .mn-cattiles-photo a.mn-tilt:hover,
    body.mn-motion .mn-roletiles a.mn-tilt:hover,
    body.mn-motion .mn-props-grid .mn-prop.mn-tilt:hover { --mn-tilt-lift: -4px; }
}

/* ---- the section spy ------------------------------------------------------
   The block the reader is actually in gets its heading underlined. It is the
   only scroll indicator on the page that says WHERE rather than HOW FAR, and
   it needs no in-page navigation to hang off, which no view has. */
body.mn-motion .mn-home-h2 { position: relative; }
body.mn-motion .mn-home-h2::after {
    content: "";
    position: absolute;
    inset-inline-start: 0;
    inset-block-end: -7px;
    width: 38px;
    height: 2px;
    background: var(--mn-brand);
    transform: scaleX(0);
    transform-origin: 100% 50%;   /* the start edge under dir="rtl" */
    transition: transform var(--mn-t-3) var(--mn-ease-out);
}
body.mn-motion .is-current .mn-home-h2::after { transform: scaleX(1); }

/* ==========================================================================
   5. Keyframes
   ========================================================================== */
@keyframes mn-rise {
    from { opacity: 0; transform: translate3d(0, 22px, 0); }
    to   { opacity: 1; transform: none; }
}

@keyframes mn-pop {
    from { opacity: 0; transform: translate3d(0, -6px, 0) scale(.98); }
    to   { opacity: 1; transform: none; }
}

@keyframes mn-toast-in {
    /* Enters from the inline-start edge, where the toast lives. Under dir="rtl"
       that is the left, hence the negative X. */
    from { opacity: 0; transform: translate3d(-24px, 8px, 0); }
    to   { opacity: 1; transform: none; }
}

@keyframes mn-heart {
    0%   { transform: scale(1); }
    35%  { transform: scale(1.42); }
    62%  { transform: scale(.92); }
    100% { transform: scale(1); }
}

@keyframes mn-sheen {
    0%   { opacity: 0; inset-inline-start: -60%; }
    12%  { opacity: 1; }
    88%  { opacity: 1; }
    100% { opacity: 0; inset-inline-start: 118%; }
}

@keyframes mn-pulse {
    0%, 100% { box-shadow: 0 0 0 0 var(--mn-brand-tint); }
    50%      { box-shadow: 0 0 0 7px transparent; }
}

@keyframes mn-ping {
    0%   { box-shadow: 0 0 0 0 rgba(168, 67, 58, .45); }
    70%  { box-shadow: 0 0 0 7px transparent; }
    100% { box-shadow: 0 0 0 0 transparent; }
}

@keyframes mn-swing {
    0%, 100% { transform: rotate(0); }
    20%      { transform: rotate(11deg); }
    45%      { transform: rotate(-9deg); }
    70%      { transform: rotate(5deg); }
}

/* ==========================================================================
   7. The cursor ring

   Two parts: a dot that keeps up with the pointer and a ring that lags behind
   it. The lag is what makes it read as a physical thing being dragged along
   rather than a second cursor.

   The native cursor stays. Hiding it costs the reader the one indicator every
   operating system taught them, and takes the text caret and the resize
   cursors with it. This is drawn beside it, which is also why it is small and
   translucent: an accent, not a replacement.

   Created by the script and only under a real pointer, so it costs a phone
   nothing and does not exist at all with JavaScript off.
   ========================================================================== */
/* `left`/`top`, and NOT the logical pair. Everything inside this element is
   placed from a pointer event's clientX/clientY, which is measured from the
   physical left edge of the window in every writing direction. With
   inset-inline-start the container anchored to the RIGHT edge under dir="rtl",
   the children were positioned `left: clientX` from there, and the whole ring
   was drawn one viewport-width off the side of the screen. */
.mn-cursor {
    position: fixed;
    top: 0;
    left: 0;
    z-index: 80;
    pointer-events: none;
    opacity: 0;
    transition: opacity .25s var(--mn-ease);
}
.mn-cursor.is-on { opacity: 1; }

/* The ring. `left`/`top` rather than a logical pair on purpose: these are
   viewport coordinates straight out of a pointer event, and clientX is
   measured from the left edge of the window in every writing direction. */
.mn-cursor::after {
    content: "";
    position: absolute;
    left: var(--mn-ring-x, 0);
    top: var(--mn-ring-y, 0);
    width: 32px;
    height: 32px;
    margin: -16px 0 0 -16px;
    border: 1.5px solid var(--mn-brand);
    border-radius: 50%;
    opacity: .55;
    transition:
        width .22s var(--mn-ease-pop),
        height .22s var(--mn-ease-pop),
        margin .22s var(--mn-ease-pop),
        opacity .22s var(--mn-ease),
        background .22s var(--mn-ease);
}

/* The dot, on the pointer itself. */
.mn-cursor > i {
    position: absolute;
    left: var(--mn-cursor-x, 0);
    top: var(--mn-cursor-y, 0);
    width: 6px;
    height: 6px;
    margin: -3px 0 0 -3px;
    border-radius: 50%;
    background: var(--mn-brand);
    transition: transform .18s var(--mn-ease-pop), opacity .18s var(--mn-ease);
}

/* Over something clickable the ring opens and fills faintly, and the dot gets
   out of the way: the ring is now the thing saying "this is the target". */
.mn-cursor.is-over::after {
    width: 52px;
    height: 52px;
    margin: -26px 0 0 -26px;
    background: rgba(250, 199, 87, .12);
    opacity: .9;
}
.mn-cursor.is-over > i { transform: scale(.4); opacity: .5; }

/* And it presses. */
.mn-cursor.is-down::after {
    width: 24px;
    height: 24px;
    margin: -12px 0 0 -12px;
    opacity: 1;
}

@media print { .mn-cursor { display: none; } }

/* ==========================================================================
   6. Reduced motion

   The reader's setting wins over every rule above. mn-motion.js checks the
   same query and never adds body.mn-motion, so the reveals do not merely
   snap into place: their pre-state is never applied at all.
   ========================================================================== */
@media (prefers-reduced-motion: reduce) {
    body.mn-public *,
    body.mn-public *::before,
    body.mn-public *::after {
        animation-duration: .001ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: .001ms !important;
        scroll-behavior: auto !important;
    }
    body.mn-public .mn-card:hover,
    body.mn-public .mn-btn:hover,
    body.mn-public .mn-cattiles a:hover,
    body.mn-public .mn-roletiles a:hover { transform: none; }

    /* The ring's whole point is the lag, and the lag is motion. */
    .mn-cursor { display: none; }
}
