/* ═══════════════════════════════════════════════════════════════════════════
 * Motion
 *
 * Motion is part of how this reads, not decoration on top of it. Three rules
 * hold it together:
 *
 *   1. Everything moves the same way. One spring, one exit curve, one set of
 *      durations, expressed as tokens. A sheet, a card and a button are the
 *      same physical world.
 *   2. Motion explains what happened. A card lifts because you are about to
 *      touch it; a tick draws because something completed just now; a sheet
 *      rises from the bottom because that is where you pressed. Nothing moves
 *      to be seen moving.
 *   3. It costs nothing to turn off. Every animation here is transform and
 *      opacity only, so it runs on the compositor, and the whole system stops
 *      under prefers-reduced-motion.
 * ═══════════════════════════════════════════════════════════════════════════ */

:root{
  /* one spring, used by anything that arrives or is pressed */
  --m-spring:cubic-bezier(.34,1.32,.5,1);
  --m-out:cubic-bezier(.22,1,.36,1);
  --m-sharp:cubic-bezier(.4,0,.2,1);
  --m-fast:.16s;
  --m-base:.28s;
  --m-slow:.46s;
}

/* ── arrival ──────────────────────────────────────────────────────────────
   A list does not appear all at once; it settles, nearest first. The delay is
   capped so a long list never feels like it is loading slowly. */
@keyframes m-rise{
  from{opacity:0;transform:translateY(14px) scale(.985)}
  to{opacity:1;transform:none}
}
@keyframes m-fade{from{opacity:0}to{opacity:1}}


/* ── drawn marks ──────────────────────────────────────────────────────────
   A tick that is simply revealed says "this was always true". A tick that
   draws says "this happened just now", which is the only reason to show one.
   The ring goes round first, then the mark runs inside it. */
.m-draw{--m-len:100}
.m-draw .ring{
  stroke-dasharray:var(--m-len);stroke-dashoffset:var(--m-len);
  animation:m-stroke .5s var(--m-out) forwards;
  transform-origin:center;transform:rotate(-90deg);
}
.m-draw .mark{
  stroke-dasharray:var(--m-len);stroke-dashoffset:var(--m-len);
  animation:m-stroke .32s var(--m-out) .34s forwards;
}
@keyframes m-stroke{to{stroke-dashoffset:0}}

/* a mark that lands with weight rather than fading in */
@keyframes m-pop{
  0%{transform:scale(.4);opacity:0}
  60%{transform:scale(1.08);opacity:1}
  100%{transform:scale(1);opacity:1}
}


/* ── numbers that change ──────────────────────────────────────────────────
   A count rolling to a new value is the difference between noticing a job
   arrived and not. */
@keyframes m-roll{
  0%{transform:translateY(0);opacity:1}
  45%{transform:translateY(-70%);opacity:0}
  55%{transform:translateY(70%);opacity:0}
  100%{transform:translateY(0);opacity:1}
}


/* ── the sheet ────────────────────────────────────────────────────────────
   Rises from the bottom edge and takes the page out of focus behind it, so the
   thing you opened is unmistakably the only thing you are looking at. */
.m-scrim{
  position:fixed;inset:0;z-index:80;
  background:rgba(6,12,20,.55);
  opacity:0;visibility:hidden;
  /* the blur is applied only when open: a backdrop-filter on a hidden element
     is still composited, which leaves the page hazy with nothing on top of it */
  backdrop-filter:none;-webkit-backdrop-filter:none;
  transition:opacity var(--m-base) var(--m-sharp),visibility var(--m-base),
             backdrop-filter var(--m-base) var(--m-sharp);
  display:flex;align-items:flex-end;justify-content:center;
}
.m-scrim.open{opacity:1;visibility:visible;
  backdrop-filter:blur(10px) saturate(.9);-webkit-backdrop-filter:blur(10px) saturate(.9)}
.m-sheet{
  /* Full width, rising from the bottom edge, but never the whole screen: a
     strip of the page stays visible above it so the sheet reads as something
     lying on top of where you were, not a new screen. */
  width:100%;max-width:none;
  max-height:min(86vh,860px);overflow-y:auto;overscroll-behavior:contain;
  border-radius:28px 28px 0 0;
  transform:translateY(30px);opacity:.6;
  transition:transform .42s var(--m-spring),opacity .26s var(--m-sharp);
  padding-bottom:max(24px,env(safe-area-inset-bottom));
  will-change:transform;
}
.m-scrim.open .m-sheet{transform:none;opacity:1}
@media (max-width:680px){
  .m-sheet{max-height:88vh;border-radius:22px 22px 0 0}
}
/* the grab handle, so it reads as something you can pull down */
.m-grab{width:44px;height:4px;border-radius:99px;margin:10px auto 4px;
  background:currentColor;opacity:.22}

/* Nothing animates inside the sheet. The sheet arriving IS the movement; the
   contents staggering in afterwards made it feel slow and drew the eye away
   from what had just been opened. */
.m-scrim .m-in > *{animation:none}

/* ── an icon in a ring ────────────────────────────────────────────────── */
.m-orb{
  width:42px;height:42px;flex:none;border-radius:50%;display:grid;place-items:center;
  /* Solid, not a 6% tint. This lives in motion.css, which loads after cards.css,
     so a fix written there lost to this rule — it has to be corrected here. */
  background:var(--sc-solid,rgba(0,0,0,.06));border:1px solid var(--sc-rule,transparent);
  color:var(--sc-brass-deep,currentColor);
  position:relative;transition:transform var(--m-base) var(--m-spring),background-color var(--m-base);
}
.m-orb svg{width:20px;height:20px;display:block}
/* the ring is the border above; the extra ::after ring doubled it */
.m-orb::after{content:none}
.sc:hover .m-orb{transform:scale(1.06) rotate(-3deg)}
.m-orb.lg{width:52px;height:52px}
.m-orb.lg svg{width:24px;height:24px}

/* ── quiet touches ────────────────────────────────────────────────────── */
/* a bar that fills rather than appearing at its length */
@keyframes m-grow{from{transform:scaleX(0)}to{transform:scaleX(1)}}


/* something leaving: it should go the way it came */
@keyframes m-leave{to{opacity:0;transform:translateY(-8px) scale(.985)}}
.m-leaving{animation:m-leave .26s var(--m-sharp) forwards;pointer-events:none}

/* pressed state for anything tappable that is not a .sc-btn */
.m-tap{transition:transform var(--m-fast) var(--m-spring)}
.m-tap:active{transform:scale(.97)}

@media (prefers-reduced-motion:reduce){
  /* :root repeated three times is a specificity device, not a typo. Both this
     rule and a page's own `.tab{transition:...}` carry !important, and at equal
     importance the more specific selector wins regardless of order — so a plain
     `*` (0,0,0) loses to any class rule. Repeating :root lifts this to (0,3,0),
     above essentially every transition declared on the platform, without
     naming any of them. */
  /* :root itself as well as its descendants — `:root *` selects everything
     INSIDE the root element but not the element, and <html> carries a
     transition of its own once the chrome bar sets .ch-owns-top on it. */
  :root:root:root,
  :root:root:root *,
  :root:root:root *::before,
  :root:root:root *::after{
    animation-duration:.01ms !important;animation-delay:0s !important;
    transition-duration:.01ms !important;
  }
  .m-draw .ring,.m-draw .mark{stroke-dashoffset:0 !important}
  .m-sheet{transform:none !important;opacity:1 !important}
}


/* ── hardware acceleration, explicitly ────────────────────────────────────
 * Animating transform and opacity makes an animation ELIGIBLE for the
 * compositor; it does not guarantee the browser promotes it to its own layer.
 * Safari is conservative here, and an unpromoted transform is painted on the
 * main thread every frame — the stutter this system exists to avoid.
 *
 * Two hints, because no single one covers every engine: translateZ(0) is
 * honoured by older WebKit and Firefox, will-change by everything current.
 *
 * Applied ONLY to things that actually move. My first pass put a layer on every
 * tag and badge on screen — hundreds of layers, which costs memory and can make
 * text render more coarsely. A chip that never animates needs no layer.
 */

/* promoted for the life of the page: these move constantly */
/* The scrim moves and is not frosted, so it can be promoted. The sheet, the
   pill and the tools are all glass — they are left alone. */
.m-scrim{-webkit-backface-visibility:hidden;backface-visibility:hidden}

/* NO perspective on containers, and no will-change on anything that is frosted.
 *
 * This is the mistake that broke the blur. perspective, transform, filter and
 * will-change:transform all make an element a BACKDROP ROOT: a backdrop-filter
 * inside one samples that element instead of the page behind it, so a frosted
 * card blurs an empty box and comes out as flat transparency. I added those
 * hints to make the motion smooth and silently turned the glass off.
 *
 * Compositing and backdrop-filter cannot both be forced on the same subtree.
 * Given the choice, the glass wins: a frosted card is the design, and a
 * transform/opacity animation is already eligible for the compositor without
 * being told twice.
 */

/* promoted only while moving, then released so the layer is not held for ever */
/* only on things with no backdrop-filter of their own */
.m-pop,.m-roll,.m-grow,.m-leaving,.sc-fab:hover,.fab:hover{
  will-change:transform,opacity;
}

/* The drawn marks animate stroke-dashoffset, which is not a compositor property
   on any engine. They are a 44px box running once, so the cost is a few frames
   rather than a standing repaint — worth it for the one animation that carries
   meaning. */
.m-draw svg{shape-rendering:geometricPrecision}

/* Motion off, chosen by the person on Shiftly Management. Same effect as the
   system preference, but it is their choice rather than the device's. */
.no-motion:root:root,.no-motion:root:root *,
.no-motion:root:root *::before,.no-motion:root:root *::after{
  animation-duration:.01ms !important;animation-delay:0s !important;
  transition-duration:.01ms !important;
}
.no-motion .m-draw .ring,.no-motion .m-draw .mark{stroke-dashoffset:0 !important}
.no-motion .m-sheet{transform:none !important;opacity:1 !important}


/* ═══════════════════════════════════════════════════════════════════════════
 * ARRIVING AND LEAVING
 *
 * The rule everything below follows: a thing leaves the way it came, but not by
 * playing the entrance backwards. Arriving overshoots slightly and takes its
 * time, because you are about to read it. Leaving accelerates away and is
 * quicker, because the decision is already made and waiting for it is the
 * slowest part of any interaction. Same axis, same distance, different curve.
 *
 * Everything here moves transform and opacity only, so it runs on the
 * compositor and never touches layout.
 * ═══════════════════════════════════════════════════════════════════════════ */

:root{
  /* leaving has its own pair: quicker, and accelerating rather than settling */
  --m-in:cubic-bezier(.16,1.12,.3,1);   /* arrives with weight, slight overshoot */
  --m-exit:cubic-bezier(.4,0,1,1);      /* accelerates away, no overshoot */
  --m-leave:.24s;
}

/* ── the menu ─────────────────────────────────────────────────────────────
   Eleven destinations arrived as one block, which is a lot of screen changing
   at once. They settle in sequence instead, nearest the top first, capped so
   the last one is never waiting on the other ten. Scoped through .orb-ov.open
   so it only runs when the menu is actually open — orbit.js injects its own
   stylesheet at runtime, after this file, so the extra classes are what let
   these win without !important. */
@keyframes m-navin{
  from{opacity:0;transform:translateY(10px) scale(.98)}
  to{opacity:1;transform:none}
}
.orb-ov.open .orb-stage .navcard{
  animation:m-navin .34s var(--m-out) both;
}
.orb-ov.open .orb-stage .navcard:nth-child(1){animation-delay:.01s}
.orb-ov.open .orb-stage .navcard:nth-child(2){animation-delay:.03s}
.orb-ov.open .orb-stage .navcard:nth-child(3){animation-delay:.05s}
.orb-ov.open .orb-stage .navcard:nth-child(4){animation-delay:.07s}
.orb-ov.open .orb-stage .navcard:nth-child(5){animation-delay:.09s}
.orb-ov.open .orb-stage .navcard:nth-child(6){animation-delay:.11s}
.orb-ov.open .orb-stage .navcard:nth-child(7){animation-delay:.13s}
.orb-ov.open .orb-stage .navcard:nth-child(8){animation-delay:.145s}
.orb-ov.open .orb-stage .navcard:nth-child(n+9){animation-delay:.16s}
/* closed, it must not hold the finished frame — reopening should replay it */
.orb-ov:not(.open) .orb-stage .navcard{animation:none}

/* ── a list settles, it does not appear ───────────────────────────────────
 * The repeated rows across the platform — tickets, notes, guides, the
 * concierge boards, the room sheets — had no entrance at all on most pages, so
 * a refresh or a filter change swapped the whole list in one frame. That reads
 * as a redraw rather than a result.
 *
 * Capped at eight steps: past that the delay stops growing, so a list of two
 * hundred does not take six seconds to finish arriving.
 *
 * NOT applied to .sc. A frosted card cannot carry a transform — it becomes a
 * backdrop root and the blur switches off for everything inside it. cards.css
 * makes that trade deliberately: the card fades and its contents move. Adding
 * a transform here would silently undo the frosting on every card.
 */
.tk,.note,.gcard,.rcard,.cl-card,.clcard,.ev,.rec,.tblock,.famrow,.board-card{
  animation:m-rise .38s var(--m-out) both;
}
.tk:nth-child(1),.note:nth-child(1),.gcard:nth-child(1){animation-delay:.01s}
.tk:nth-child(2),.note:nth-child(2),.gcard:nth-child(2){animation-delay:.04s}
.tk:nth-child(3),.note:nth-child(3),.gcard:nth-child(3){animation-delay:.07s}
.tk:nth-child(4),.note:nth-child(4),.gcard:nth-child(4){animation-delay:.10s}
.tk:nth-child(5),.note:nth-child(5),.gcard:nth-child(5){animation-delay:.12s}
.tk:nth-child(6),.note:nth-child(6),.gcard:nth-child(6){animation-delay:.14s}
.tk:nth-child(7),.note:nth-child(7),.gcard:nth-child(7){animation-delay:.16s}
.tk:nth-child(n+8),.note:nth-child(n+8),.gcard:nth-child(n+8){animation-delay:.17s}

/* ── leaving, for anything a page removes ─────────────────────────────────
   .m-leaving was defined here and never used by anything, because nothing set
   it. Chrome.leave() sets it now and waits for the animation before removing
   the node, so a row being deleted goes somewhere instead of blinking out. */
@keyframes m-leave{
  from{opacity:1;transform:none}
  to{opacity:0;transform:translateY(-8px) scale(.97)}
}
.m-leaving{animation:m-leave var(--m-leave) var(--m-exit) both !important;
  pointer-events:none}

/* ── one press, everywhere ────────────────────────────────────────────────
   transition:all appears 43 times across the page stylesheets. It animates
   whatever happens to change — including width, padding and border-width,
   which are layout properties and cannot be composited, so a hover repaints
   and reflows the element every frame. These are the classes that used it;
   the properties they actually change are colour and transform. */
.abtn,.check,.pchip,.achip,.ctchip,.cchip,.fchip,.taskcard{
  transition:transform var(--m-fast) var(--m-out),
             opacity var(--m-fast) linear,
             background-color var(--m-fast) linear,
             border-color var(--m-fast) linear,
             color var(--m-fast) linear !important;
}

/* ── reduced motion ───────────────────────────────────────────────────────
 * My first attempt at this named the classes I had added and stopped there,
 * which missed everything the pages animate themselves: pageFade, cardIn,
 * popIn, slideIn, fabIn, secIn, rise, sc-riseIn, pbWordUp. Measured with the
 * preference set, five pages were still running six animations each. Naming
 * things is the wrong shape for this rule — the answer is "all of it", and any
 * list will be out of date the next time somebody adds a keyframe.
 *
 * Near-zero rather than `none`, deliberately. `animation:none` never fires
 * animationend, and code waits on that event — Chrome.leave() removes a node
 * when it hears it. Killing the event would strand every row it is asked to
 * remove. At 0.001ms the animation completes instantly, lands on its final
 * frame because of the fill mode, and still fires. Nothing moves; nothing
 * silently stops working either.
 */
@media (prefers-reduced-motion:reduce){
  /* :root repeated three times is a specificity device, not a typo. Both this
     rule and a page's own `.tab{transition:...}` carry !important, and at equal
     importance the more specific selector wins regardless of order — so a plain
     `*` (0,0,0) loses to any class rule. Repeating :root lifts this to (0,3,0),
     above essentially every transition declared on the platform, without
     naming any of them. */
  /* :root itself as well as its descendants — `:root *` selects everything
     INSIDE the root element but not the element, and <html> carries a
     transition of its own once the chrome bar sets .ch-owns-top on it. */
  :root:root:root,
  :root:root:root *,
  :root:root:root *::before,
  :root:root:root *::after{
    animation-duration:.001ms !important;
    animation-delay:0s !important;
    animation-iteration-count:1 !important;
    transition-duration:.001ms !important;
    transition-delay:0s !important;
    scroll-behavior:auto !important;
  }
}

/* The same, for the in-app switch on Shiftly Management, which sets .no-motion
   on <html> rather than asking the operating system. */
.no-motion:root:root,.no-motion:root:root *,
.no-motion:root:root *::before,.no-motion:root:root *::after{
  animation-duration:.001ms !important;
  animation-delay:0s !important;
  animation-iteration-count:1 !important;
  transition-duration:.001ms !important;
  transition-delay:0s !important;
  scroll-behavior:auto !important;
}

/* The button-corner sweep that used to live here is gone with the square-button
   change it existed to enforce. Each button keeps the radius its own stylesheet
   gives it again — pills stay pills, the round icon buttons stay round. */
