/* ─────────────────────────────────────────────────────────────
 * AutoPoster Pro — shared motion layer
 *
 * Load LAST on every page, after the page's <style> and after shell.css /
 * nav.css / form.css:
 *     <link rel="stylesheet" href="/public/motion.css?v=1">
 *
 * Scope: easing, entrance, hover and press feedback only. Nothing in here
 * changes layout, colour or size at rest — remove the file and every page still
 * looks and works exactly as it does now, just without the movement.
 *
 * Two rules kept throughout:
 *
 *  1. Never animate `opacity` on an element that carries a resting opacity.
 *     Animations beat normal declarations, so a `both`-filled fade to 1 would
 *     permanently cancel `.tl-item.skipped{opacity:.6}` and
 *     `tr.is-banned{opacity:.62}`. Where a container is dimmed, the entrance
 *     animation goes on an inner element instead (`.tl-card`, not `.tl-item`).
 *
 *  2. No JS. Entrance animations here can never leave content invisible,
 *     because every keyframe ends at the element's resting state and nothing
 *     depends on a script running to reveal it.
 *
 * Everything is switched off wholesale by the prefers-reduced-motion block at
 * the bottom of the file.
 * ───────────────────────────────────────────────────────────── */

:root{
  --ease:cubic-bezier(.22,1,.36,1);          /* decelerate — entrances        */
  --ease-spring:cubic-bezier(.34,1.36,.5,1); /* slight overshoot — press/pop  */
  --m-fast:.14s;
  --m:.22s;
  --m-slow:.38s;
}

/* ═══ KEYFRAMES ══════════════════════════════════════════════ */
@keyframes mRise  {from{opacity:0;transform:translateY(10px);}to{opacity:1;transform:none;}}
@keyframes mDrop  {from{opacity:0;transform:translateY(-8px);} to{opacity:1;transform:none;}}
@keyframes mFade  {from{opacity:0;}                            to{opacity:1;}}
@keyframes mPop   {from{opacity:0;transform:scale(.96);}       to{opacity:1;transform:none;}}
@keyframes mSlideL{from{opacity:0;transform:translateX(-10px);}to{opacity:1;transform:none;}}
/* transform-only — safe on anything that has a resting opacity */
@keyframes mNudge {from{transform:translateY(8px);}            to{transform:none;}}
@keyframes mSpin  {to{transform:rotate(360deg);}}
@keyframes mGlow  {0%,100%{opacity:.85;}50%{opacity:1;}}

/* ═══ PAGE ENTRANCE ══════════════════════════════════════════
   The bar drops in, then the content stack rises in sequence. The selector list
   is explicit rather than `.content > *` on purpose: `.tab-panel` already owns
   its own animation in shell.css, and the posting pages' `.section` blocks get
   reparented into those panels by form-tabs.js — animating them here would
   restart mid-flight and flash. */
.topbar{animation:mDrop var(--m-slow) var(--ease) both;}

.content > .card,
.content > .panel,
.content > .toolbar,
.content > .controls,
.content > .page-tabs,
.content > .stats-grid,
.content > .day-card,
.content > .export-card,
.content > .hero,
.content > .cluster,
.content > .two-col,
.content > .filter-bar,
.content > .h-wrap,
.content > .preview-banner,
.content > .cmd,
.content > #viewSchedule,
.content > #viewHistory{
  animation-name:mRise;
  animation-duration:.44s;
  animation-timing-function:var(--ease);
  animation-fill-mode:both;
}
/* Delays live in their own rules so the block above can stay on longhands and
   not reset them. Harmless on children that aren't animated.
   `:not(.tab-panel)` is load-bearing: shell.css's `.tab-panel.on` has the same
   specificity as `.content > *:nth-child(n)` and this file loads later, so
   without the exclusion these delays would win and put up to 340ms of dead air
   in front of every tab switch on the posting pages. */
.content > *:not(.tab-panel):nth-child(1){animation-delay:.02s;}
.content > *:not(.tab-panel):nth-child(2){animation-delay:.07s;}
.content > *:not(.tab-panel):nth-child(3){animation-delay:.12s;}
.content > *:not(.tab-panel):nth-child(4){animation-delay:.17s;}
.content > *:not(.tab-panel):nth-child(5){animation-delay:.21s;}
.content > *:not(.tab-panel):nth-child(6){animation-delay:.25s;}
.content > *:not(.tab-panel):nth-child(7){animation-delay:.28s;}
.content > *:not(.tab-panel):nth-child(8){animation-delay:.31s;}
.content > *:not(.tab-panel):nth-child(n+9){animation-delay:.34s;}

/* ═══ BUTTONS ════════════════════════════════════════════════
   Everything clickable gets the same easing and the same press response, so the
   app feels like one surface. */
.btn,.act,.tl-btn,.pg-btn,.day-nav-btn,.fb-btn,.li-btn,.page-tab,.search-x,
.big-btn,.btn-sec,.collapse-btn,.tl-action-btn{
  transition:
    background var(--m-fast) ease,
    border-color var(--m-fast) ease,
    color var(--m-fast) ease,
    box-shadow var(--m) ease,
    filter var(--m-fast) ease,
    transform var(--m-fast) var(--ease-spring);
}
.btn:hover,.act:hover,.tl-btn:hover,.pg-btn:hover:not(:disabled),
.day-nav-btn:hover,.fb-btn:hover,.li-btn:hover{
  transform:translateY(-1px);
}
/* Press wins over hover: land the button back down and shrink slightly. */
.btn:active,.act:active,.tl-btn:active,.pg-btn:active:not(:disabled),
.day-nav-btn:active,.fb-btn:active,.li-btn:active,.big-btn:active,.btn-sec:active{
  transform:translateY(0) scale(.972);
  transition-duration:.06s;
}
.page-tab:active{transform:scale(.98);transition-duration:.06s;}
.btn:disabled,.pg-btn:disabled{transform:none;}

/* focus ring grows in rather than snapping */
.btn:focus-visible,.act:focus-visible,.page-tab:focus-visible,
input:focus-visible,select:focus-visible,textarea:focus-visible{
  transition:outline-offset var(--m-fast) var(--ease-spring),box-shadow var(--m-fast) ease;
}

/* ═══ INPUTS ═════════════════════════════════════════════════ */
input,select,textarea{
  transition:
    border-color var(--m-fast) ease,
    box-shadow var(--m-fast) ease,
    background var(--m-fast) ease;
}

/* ═══ CARDS & PANELS ═════════════════════════════════════════ */
.card,.biz,.tl-card,.stat-card,.export-card,.mtile,.glass,.ctile,.section,
.gc,.h-wrap,.toolbar,.controls,.day-card{
  transition:
    transform var(--m) var(--ease),
    box-shadow var(--m) var(--ease),
    border-color var(--m-fast) ease,
    background var(--m-fast) ease;
}
/* Lift only the small, list-like surfaces. A full-width container rising on
   hover reads as a glitch, not as feedback. */
.stat-card:hover,.tl-card:hover,.mtile:hover,.glass:hover,.ctile:hover{
  transform:translateY(-2px);
}

/* ═══ TABLES ═════════════════════════════════════════════════
   No row entrance animation by design: `tr.is-banned` and the filtered lists
   re-render on every keystroke, so a per-row fade would both fight the dimming
   and strobe while typing. Rows get state transitions and a hover nudge. */
tbody tr{transition:background var(--m-fast) ease;}
tbody tr.prow > td:first-child{transition:padding-left var(--m) var(--ease);}
tbody tr.prow:hover > td:first-child{padding-left:18px;}

/* ═══ DISCLOSURE ═════════════════════════════════════════════
   `<tr>` and display-toggled blocks can't transition, so the panel that appears
   inside them is what animates. */
tr.row-detail.on .detail{animation:mRise .28s var(--ease) both;}
.biz.is-open .biz-body{animation:mRise .3s var(--ease) both;}
.biz-chev,.row-chev{transition:transform var(--m) var(--ease-spring),color var(--m-fast) ease,background var(--m-fast) ease;}

/* ═══ STAGGERED LISTS ════════════════════════════════════════
   Render templates set `style="--i:N"`; the delay is derived from it. Capped so
   a 60-row list doesn't take two seconds to finish arriving. */
.biz,.tl-card{
  animation-name:mRise;
  animation-duration:.4s;
  animation-timing-function:var(--ease);
  animation-fill-mode:both;
  animation-delay:calc(min(var(--i,0),10) * 45ms);
}
/* Filtering repaints the list on a 120ms debounce, so the entrance is suppressed
   while a filter is being typed — otherwise the results strobe. */
body.is-filtering .biz,
body.is-filtering .tl-card{animation:none;}

/* ═══ NAV RAIL ═══════════════════════════════════════════════ */
.nav-item{transition:background var(--m-fast) ease,color var(--m-fast) ease;}
.nav-icon svg{transition:transform var(--m) var(--ease-spring);}
.nav-item:hover .nav-icon svg{transform:scale(1.14);}
.nav-item:active .nav-icon svg{transform:scale(.96);transition-duration:.06s;}
.nav-item.active::before{animation:mGlow 3.4s ease-in-out infinite;}
.logo-mark{transition:transform var(--m) var(--ease-spring),box-shadow var(--m) ease;}
.sidebar:hover .logo-mark{transform:scale(1.05);}
.user-row{transition:background var(--m-fast) ease;}

/* ═══ MODALS ═════════════════════════════════════════════════
   Display-toggled from JS. A display:none element runs no animation, so the
   keyframes fire the moment it's shown — no transition or extra class needed. */
.modal{animation:mFade .18s ease both;}
.modal > .card{animation:mPop .3s var(--ease-spring) both;}

/* ═══ BADGES, CHIPS, PILLS ═══════════════════════════════════
   Transitions only. These aren't clickable, so they get no hover response —
   they just need to ease when their state changes underneath them. */
.badge,.pchip,.tl-badge,.h-badge,.mini,.keychip,.status-pill,.live-pill,
.logo-badge,.today-badge,.grp-count,.biz-tally{
  transition:background var(--m-fast) ease,color var(--m-fast) ease,border-color var(--m-fast) ease;
}
.status-dot,.live-dot{transition:background var(--m-fast) ease,box-shadow var(--m) ease;}

/* ═══ LINKS ══════════════════════════════════════════════════ */
a{transition:color var(--m-fast) ease,opacity var(--m-fast) ease;}
.tl-link svg,.h-link svg{transition:transform var(--m) var(--ease-spring);}
.tl-link:hover svg,.h-link:hover svg{transform:translate(2px,-2px);}

/* ═══ PROGRESS & METERS ══════════════════════════════════════
   Widths are set from JS after fetch, so these ease from 0 to their value on
   first paint for free. */
.track i,.rail-done,.prog-fill{transition:width .9s var(--ease);}

/* ═══ ALERTS / TOASTS ════════════════════════════════════════ */
.alert.show{animation:mDrop .26s var(--ease) both;}
.toast{transition:opacity var(--m) ease,transform var(--m) var(--ease-spring);}

/* ═══ LOADING ════════════════════════════════════════════════
   Three states, so "waiting" never has to be spelled out in prose again:
     .sk*          skeleton placeholders shaped like the content that's coming
     .is-loading   a button that has been pressed and is waiting on the server
     .route-bar    top-of-window indeterminate bar, driven by loading.js
   ──────────────────────────────────────────────────────────── */
@keyframes mShimmer{from{background-position:100% 0;}to{background-position:-100% 0;}}
@keyframes mBar{0%{transform:translateX(-100%);}100%{transform:translateX(340%);}}

.sk{
  display:block;border-radius:7px;
  background:linear-gradient(90deg,
    rgba(30,37,51,.055) 25%,
    rgba(30,37,51,.115) 37%,
    rgba(30,37,51,.055) 63%);
  background-size:300% 100%;
  animation:mShimmer 1.25s ease-in-out infinite;
}
/* shapes — pick the one that matches what will land there */
.sk-title {height:14px;width:38%;}
.sk-line  {height:10px;}
.sk-line + .sk-line{margin-top:8px;}
.sk-num   {height:22px;width:68px;}
.sk-pill  {height:20px;width:76px;border-radius:99px;}
.sk-chip  {height:18px;width:58px;border-radius:99px;}
.sk-tile  {height:38px;width:38px;border-radius:11px;}
.sk-avatar{height:34px;width:34px;border-radius:50%;}
.sk-w90{width:90%;} .sk-w75{width:75%;} .sk-w60{width:60%;} .sk-w45{width:45%;} .sk-w30{width:30%;}
/* Successive placeholders breathe out of phase, so a stack of them reads as
   several pending things rather than one flashing block. */
.sk-stagger > *:nth-child(2) .sk,.sk-stagger > *:nth-child(2).sk{animation-delay:.12s;}
.sk-stagger > *:nth-child(3) .sk,.sk-stagger > *:nth-child(3).sk{animation-delay:.24s;}
.sk-stagger > *:nth-child(4) .sk,.sk-stagger > *:nth-child(4).sk{animation-delay:.36s;}
.sk-stagger > *:nth-child(5) .sk,.sk-stagger > *:nth-child(5).sk{animation-delay:.48s;}
.sk-stagger > *:nth-child(n+6) .sk,.sk-stagger > *:nth-child(n+6).sk{animation-delay:.6s;}

/* Skeleton rows go inside the real <table> so the columns don't jump when the
   data replaces them. */
tr.sk-row > td{padding:13px 14px;}
tr.sk-row > td .sk{height:11px;}

/* Card mid-refresh: keep the stale content readable but clearly inert. */
.is-busy{position:relative;pointer-events:none;}
.is-busy > *{opacity:.45;transition:opacity var(--m) ease;}
.is-busy::after{
  content:'';position:absolute;inset:0;pointer-events:none;border-radius:inherit;
  background:linear-gradient(90deg,transparent,rgba(255,255,255,.5),transparent);
  background-size:220% 100%;
  animation:mShimmer 1.15s ease-in-out infinite;
}

/* ── pressed-and-waiting buttons ──
   The label stays put and a spinner joins it, so the button doesn't change size
   or lose its meaning while you wait. .btn/.act are already inline-flex with a
   gap, so ::before simply becomes the first flex item. */
.btn.is-loading,.act.is-loading,.fb-btn.is-loading,.li-btn.is-loading,.big-btn.is-loading{
  pointer-events:none;opacity:.8;
}
.btn.is-loading::before,.act.is-loading::before,.fb-btn.is-loading::before,
.li-btn.is-loading::before,.big-btn.is-loading::before{
  content:'';flex:none;width:12px;height:12px;border-radius:50%;
  border:2px solid currentColor;border-top-color:transparent;
  animation:mSpin .62s linear infinite;
}
/* one indicator at a time */
.btn.is-loading svg,.act.is-loading svg,.fb-btn.is-loading svg,
.li-btn.is-loading svg,.big-btn.is-loading svg{display:none;}

/* ── top-of-window progress bar ── */
.route-bar{
  position:fixed;top:0;left:0;right:0;height:2px;z-index:500;
  overflow:hidden;pointer-events:none;
  opacity:0;transition:opacity .22s ease;
}
.route-bar.on{opacity:1;}
.route-bar::after{
  content:'';position:absolute;top:0;left:0;height:100%;width:30%;
  background:linear-gradient(90deg,transparent,#3b6ef8 35%,#7c4ff0 65%,transparent);
  animation:mBar 1.05s ease-in-out infinite;
}

/* ═══ UTILITIES ══════════════════════════════════════════════ */
.spin{animation:mSpin .8s linear infinite;}
.m-fade{animation:mFade var(--m) ease both;}
.m-rise{animation:mRise var(--m-slow) var(--ease) both;}
.m-pop{animation:mPop var(--m) var(--ease-spring) both;}
.m-slide-l{animation:mSlideL var(--m-slow) var(--ease) both;}

/* ═══ REDUCED MOTION ═════════════════════════════════════════
   One switch for the whole layer. Transitions are cut to a duration rather than
   removed, so state changes still register without any travel. */
@media (prefers-reduced-motion:reduce){
  .topbar,
  .content > .card,.content > .panel,.content > .toolbar,.content > .controls,
  .content > .page-tabs,.content > .stats-grid,.content > .day-card,
  .content > .export-card,.content > .hero,.content > .cluster,
  .content > .two-col,.content > .filter-bar,.content > .h-wrap,
  .content > .preview-banner,.content > #viewSchedule,.content > #viewHistory,
  .biz,.tl-card,.modal,.modal > .card,.alert.show,
  tr.row-detail.on .detail,.biz.is-open .biz-body,
  .nav-item.active::before,.spin,
  .m-fade,.m-rise,.m-pop,.m-slide-l{
    animation:none !important;
  }
  *,*::before,*::after{
    transition-duration:.01ms !important;
    animation-duration:.01ms !important;
    animation-iteration-count:1 !important;
  }
  .btn:hover,.act:hover,.stat-card:hover,.tl-card:hover,.mtile:hover,
  .glass:hover,.ctile:hover,.nav-item:hover .nav-icon svg{
    transform:none;
  }
  /* Loading states must stay legible without motion: the shimmer becomes a flat
     tint, and the button spinner a static ring. Nothing is hidden. */
  .sk{animation:none !important;background:rgba(30,37,51,.09) !important;}
  .is-busy::after{animation:none !important;background:none !important;}
  .route-bar::after{animation:none !important;width:100% !important;}
  .btn.is-loading::before,.act.is-loading::before,.fb-btn.is-loading::before,
  .li-btn.is-loading::before,.big-btn.is-loading::before{
    animation:none !important;border-top-color:currentColor;opacity:.5;
  }
  /* Restate the author value — `revert` would drop to the UA default and lose
     the table's own cell padding. */
  tbody tr.prow:hover > td:first-child{padding-left:14px;}
}
