/* ==================================================================
   ORBIX POLISH — the last motion layer, shared by all ten apps.
   Loaded after every other stylesheet. It only touches *how* things
   move (timing, easing, which properties animate), never colours,
   sizes or layout, so each app keeps its own look.

   Timing comes from Animate UI's components (animate-ui.com), turned
   into CSS springs with linear():
     switch thumb   spring 300 / 25      tab highlight  spring 200 / 25
     menu highlight spring 350 / 35      popover/toast  spring 300 / 25
     radio dot      spring 200 / 16      number roll    spring 200 / 20 / .4
   Only transform, opacity and colours animate, so phones stay smooth.
   Everything turns off with Reduce Motion or Settings → Smooth transitions.
   ================================================================== */

:root {
  --op-out: cubic-bezier(.22, 1, .36, 1);          /* entrances, responses */
  --op-in: cubic-bezier(.4, 0, 1, 1);              /* exits */
  --op-std: cubic-bezier(.4, 0, .2, 1);            /* colour changes */
  --op-spring-switch: cubic-bezier(.3, 1.3, .6, 1);
  --op-spring-tab: cubic-bezier(.22, 1, .36, 1);
  --op-spring-snappy: cubic-bezier(.2, .9, .3, 1);
  --op-spring-radio: cubic-bezier(.34, 1.56, .64, 1);
  --op-fast: .16s;
  --op-base: .22s;
  --op-slow: .38s;
}
@supports (animation-timing-function: linear(0, 1)) {
  :root {
    --op-spring-switch: linear(0, 0.04 4.2%, 0.137 8.3%, 0.263 12.5%, 0.399 16.7%, 0.532 20.8%, 0.652 25%, 0.755 29.2%, 0.84 33.3%, 0.907 37.5%, 0.956 41.7%, 0.992 45.8%, 1.015 50%, 1.029 54.2%, 1.036 58.3%, 1.038 62.5%, 1.036 66.7%, 1.032 70.8%, 1.028 75%, 1.022 79.2%, 1.017 83.3%, 1.013 87.5%, 1.009 91.7%, 1.006 95.8%, 1);
    --op-spring-tab: linear(0, 0.021 4.2%, 0.075 8.3%, 0.149 12.5%, 0.233 16.7%, 0.321 20.8%, 0.408 25%, 0.49 29.2%, 0.567 33.3%, 0.636 37.5%, 0.697 41.7%, 0.751 45.8%, 0.797 50%, 0.837 54.2%, 0.87 58.3%, 0.898 62.5%, 0.921 66.7%, 0.94 70.8%, 0.955 75%, 0.967 79.2%, 0.976 83.3%, 0.984 87.5%, 0.989 91.7%, 0.993 95.8%, 1);
    --op-spring-snappy: linear(0, 0.031 4.2%, 0.104 8.3%, 0.2 12.5%, 0.303 16.7%, 0.404 20.8%, 0.5 25%, 0.586 29.2%, 0.661 33.3%, 0.726 37.5%, 0.78 41.7%, 0.826 45.8%, 0.863 50%, 0.894 54.2%, 0.918 58.3%, 0.937 62.5%, 0.953 66.7%, 0.964 70.8%, 0.974 75%, 0.981 79.2%, 0.986 83.3%, 0.99 87.5%, 0.993 91.7%, 0.995 95.8%, 1);
    --op-spring-radio: linear(0, 0.068 4.2%, 0.228 8.3%, 0.427 12.5%, 0.626 16.7%, 0.8 20.8%, 0.936 25%, 1.031 29.2%, 1.088 33.3%, 1.113 37.5%, 1.114 41.7%, 1.101 45.8%, 1.08 50%, 1.056 54.2%, 1.034 58.3%, 1.016 62.5%, 1.002 66.7%, 0.993 70.8%, 0.988 75%, 0.987 79.2%, 0.987 83.3%, 0.989 87.5%, 0.992 91.7%, 0.995 95.8%, 1);
  }
}

/* ── 1. Buttons, chips, nav: animate only what changes ────────────────
   Most apps used `transition: all`, which also animates padding, width
   and font weight (the "wobble" when a tab turns bold). These lists keep
   the feel and drop the side effects. The press squish itself is the
   shared spring in orbix-ui.js. */
.btn, .btn-primary, .btn-secondary, .btn-ghost, .btn-outline, .btn-dark, .btn-danger,
.btn-icon, .icon-btn, .chip, .tab, .nav-item, .modal-close, .om-btn {
  transition-property: transform, background-color, color, border-color, box-shadow, opacity, filter;
  transition-duration: var(--op-base);
  transition-timing-function: var(--op-out);
}
/* Disabled things fade, they don't snap */
.btn:disabled, button:disabled, .btn[aria-disabled="true"] { transition-duration: var(--op-base); }

/* Hover lifts belong to mice. On touch screens a tapped button kept its
   "hover" lift until you tapped elsewhere; now it stays put. */
@media (hover: none) {
  .btn:hover, .btn-primary:hover, .btn-secondary:hover, .btn-ghost:hover, .btn-outline:hover,
  .btn-dark:hover, .btn-icon:hover, .icon-btn:hover, .chip:hover, .accent-btn:hover:not(.active) {
    transform: none;
  }
}

/* The sidebar's active bar grows with a soft spring */
.nav-item::before {
  transition: transform var(--op-slow) var(--op-spring-snappy), opacity var(--op-base) var(--op-out);
}

/* ── 2. Keyboard focus ring eases in instead of popping ──────────────── */
:focus-visible { transition: outline-offset var(--op-fast) var(--op-out); }

/* ── 3. Text fields and selects ──────────────────────────────────────── */
input:not([type="checkbox"]):not([type="radio"]):not([type="range"]),
textarea, select, .form-input, .form-select, .form-textarea, .select {
  transition: border-color var(--op-base) var(--op-std), box-shadow var(--op-base) var(--op-out),
              background-color var(--op-base) var(--op-std), color var(--op-base) var(--op-std);
}

/* ── 4. Switches: spring thumb + iOS "stretch" while held ────────────
   Animate UI's switch widens its thumb while pressed and lands with a
   300/25 spring. Each app's own sizes are kept; the stretch grows towards
   the side the thumb will travel. */
.toggle, .switch, .toggle-slider {
  transition: background-color var(--op-base) var(--op-std), border-color var(--op-base) var(--op-std),
              box-shadow var(--op-base) var(--op-out);
}
.toggle::after, .switch::after, .toggle-slider::before {
  transition: transform .42s var(--op-spring-switch), left .42s var(--op-spring-switch),
              width .2s var(--op-out), background-color var(--op-base) var(--op-std);
}
/* AuraSpace / Zephyros (.toggle: 18px thumb, 18px travel) */
.toggle:active::after { width: 22px; }
.toggle.on:active::after { transform: translateX(14px); }
/* Cards (.switch: 18px thumb, 20px travel) */
.switch:active::after { width: 22px; }
.switch.on:active::after { transform: translateX(16px); }
/* Life Tracker (.switch uses left: 3 → 21px, 20px thumb) */
body:has(#toast-wrap) .switch:active::after { width: 24px; transform: none; }
body:has(#toast-wrap) .switch.on:active::after { left: 17px; transform: none; }
/* Finance (.toggle-slider: 16px thumb, 18px travel) */
.toggle-switch:active .toggle-slider::before { width: 20px; }
.toggle-switch:active input:checked + .toggle-slider::before { transform: translateX(14px); }

/* ── 5. Checkboxes and radios ────────────────────────────────────────
   Native controls keep their look; they pick up the app's accent, give
   a tiny press, and a satisfying pop when they turn on (Animate UI's
   radio uses a bouncy 200/16 spring). */
input[type="checkbox"], input[type="radio"] {
  accent-color: var(--accent, var(--om-accent, currentColor));
  transition: transform var(--op-fast) var(--op-out);
}
input[type="checkbox"]:active, input[type="radio"]:active { transform: scale(.88); }
input[type="checkbox"]:checked, input[type="radio"]:checked { animation: op-check .5s var(--op-spring-radio); }
@keyframes op-check { 0% { transform: scale(.82); } 100% { transform: scale(1); } }

/* ── 6. Segmented controls: a sliding highlight (orbix-polish.js) ─────
   Tabs, Today / Tomorrow, timer modes, view switches, filter chips… The JS
   marks the row .op-seg and adds one .op-seg-pill that wears the chosen
   button's own fill, border and shadow, then glides between buttons with
   Animate UI's 200/25 tab spring instead of jumping. The chosen button
   keeps its text colour; only its fill moves to the pill. */
.op-seg { isolation: isolate; }
.op-seg-rel { position: relative; }
.op-seg > :not(.op-seg-pill) { position: relative; z-index: 1; }
.op-seg-ready[data-op-on="active"] > .active,
.op-seg-ready[data-op-on="on"] > .on,
.op-seg-ready[data-op-on="selected"] > .selected,
.op-seg-ready[data-op-on="sel"] > .sel,
.op-seg-ready[data-op-on="current"] > .current,
.op-seg-ready[data-op-on="is-active"] > .is-active,
.op-seg-ready[data-op-on="checked"] > .checked,
.op-seg-ready[data-op-on="aria-selected"] > [aria-selected="true"],
.op-seg-ready[data-op-on="aria-pressed"] > [aria-pressed="true"] {
  background: transparent !important; box-shadow: none !important; border-color: transparent !important;
}
.op-seg-pill {
  position: absolute; top: 0; left: 0; z-index: 0;
  box-sizing: border-box;
  pointer-events: none;
  transform: translate3d(var(--op-x, 0), var(--op-y, 0), 0);
  width: var(--op-w, 0); height: var(--op-h, 0);
  opacity: 0;
  transition: transform .38s var(--op-spring-tab), width .38s var(--op-spring-tab),
              height .38s var(--op-spring-tab), opacity var(--op-fast) var(--op-out),
              background-color .4s var(--op-std), border-color .4s var(--op-std), box-shadow .4s var(--op-std);
}
.op-seg.op-seg-ready > .op-seg-pill { opacity: 1; }
.op-seg-pill.op-instant { transition: none !important; }

/* ── 7. Progress bars, XP bars, chart bars fill in smoothly ──────────── */
.prog-fill, .xp-fill, .xp-bar-fill, .bar-fill, .progress-bar, .habit-prog-fill, .cd-bar-fill,
.sbar-fill, .chart-bar-fill, .loader-fill, .progress-fill, .om-progress-fill {
  transition: width .7s var(--op-out), height .7s var(--op-out), transform .7s var(--op-out),
              background-color var(--op-base) var(--op-std);
}

/* ── 8. Numbers that change roll smoothly (orbix-polish.js tweens them)
   tabular figures keep the width steady while digits change. */
.op-num { font-variant-numeric: tabular-nums; }

/* ── 9. Toasts: arrive with a soft spring, leave quickly and cleanly ─── */
.toast-container > .toast, .toast-stack > .toast, #toast-wrap > .toast, #toast-container > .toast {
  will-change: transform, opacity;
}
/* AuraSpace & Zephyros set `transition: all .3s ease` inline on exit;
   only transform and opacity should move, with an exit curve. */
.toast-container > .toast[style*="opacity"] {
  transition: opacity .24s var(--op-in), transform .3s var(--op-in) !important;
}

/* ── 10. Theme switch: colours cross-fade (orbix-polish.js adds the class
   for half a second only when the theme actually changes) ─────────── */
html.op-theme-fade, html.op-theme-fade body,
html.op-theme-fade *:not(.op-seg-pill), html.op-theme-fade *::before, html.op-theme-fade *::after {
  transition: background-color .45s var(--op-std), color .45s var(--op-std), border-color .45s var(--op-std),
              fill .45s var(--op-std), stroke .45s var(--op-std), box-shadow .45s var(--op-std) !important;
}

/* ── 11. Fold-outs (any <details>, now or later) open with real height ─ */
@supports (interpolate-size: allow-keywords) {
  :root { interpolate-size: allow-keywords; }
  details::details-content {
    height: 0; overflow: clip; opacity: 0;
    transition: height .35s var(--op-out), opacity .25s var(--op-out), content-visibility .35s allow-discrete;
  }
  details[open]::details-content { height: auto; opacity: 1; }
}

/* ── 12. Range sliders: the thumb responds to a grab ─────────────────── */
input[type="range"] { accent-color: var(--accent, var(--om-accent, currentColor)); }
input[type="range"]::-webkit-slider-thumb { transition: transform var(--op-fast) var(--op-out); }
input[type="range"]:active::-webkit-slider-thumb { transform: scale(1.15); }

/* ── 13. Living details, kept smooth ─────────────────────────────────
   The small ambient motions (level halo, floating logos, live dots, the
   calendar "now" dot, buddy moods, music bars) all stay. Where one used to
   animate something the browser has to re-lay out or repaint every frame,
   it now moves with transform / opacity instead, so it stays at 60fps. */

/* Focus timer ring: a real border ring now (before, its card-coloured fill
   sat on top of the timer and washed it out). Hidden at rest; while the
   timer runs it fades in and breathes softly. */
.glow-ring {
  -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 exclude, linear-gradient(#000 0 0);
  opacity: 0; animation: none;
  transition: opacity .6s var(--op-std);
}
.open-focus-display.glowing .glow-ring { opacity: 1; animation: op-ring-breathe 3.2s ease-in-out infinite; }
@keyframes op-ring-breathe { 0%, 100% { opacity: .5; } 50% { opacity: 1; } }

/* Lavender XP shine: glides with transform instead of `left` */
.xp-bar-fill::after { animation-name: op-xp-shine; }
@keyframes op-xp-shine { from { transform: translateX(0); } to { transform: translateX(500%); } }

/* Music bars: transform instead of height */
@keyframes barBounce { 0%, 100% { transform: scaleY(.3); } 50% { transform: scaleY(1); } }
.now-playing-dot { height: 14px !important; transform-origin: 50% 100%; }
@keyframes vizPulse { from { transform: scaleY(.22); } to { transform: scaleY(1); } }
.viz-bar, .viz-bar.paused { height: var(--max-h, 100%); transform-origin: 50% 100%; }

/* ── 14. Hover and press: one smooth feel everywhere ──────────────────
   Cards and buttons keep their lift, but they all rise and settle on the
   same soft spring, and move only transform / shadow / colour (no more
   `transition: all`, which also animated sizes and caused wobble). */
.stat-card, .glass-card, .subject-card-big, .paper-card, .pdf-card, .theme-card, .flash-tile,
.habit-card, .ccard, .lp-app, .ambient-preset, .badge, .cal-block, .heat-cell, .h-cell, .heatmap-cell, .cal-cell {
  transition-property: transform, opacity, background-color, border-color, box-shadow, color;
  transition-duration: .32s;
  transition-timing-function: var(--op-spring-tab);
}
/* A calendar block being dragged lifts a little, even under the pointer */
.cal-block.dragging, .cal-block.op-lifted { transform: scale(1.03); box-shadow: 0 14px 36px rgba(0, 0, 0, .28); z-index: 20; }

/* Cards rise into place one after another when a page opens
   (orbix-polish.js marks the page for a moment, so redraws don't replay it) */
@keyframes op-rise { from { opacity: 0; transform: translate3d(0, 10px, 0) scale(.985); } to { opacity: 1; transform: none; } }
.op-enter :is(.stat-card, .glass-card, .subject-card-big, .paper-card, .pdf-card, .flash-tile, .habit-card, .ccard, .lp-app, .badge) {
  animation: op-rise .5s var(--op-out) backwards;
}
.op-enter :is(.stat-card, .glass-card, .subject-card-big, .paper-card, .pdf-card, .flash-tile, .habit-card, .ccard, .lp-app, .badge):nth-child(2) { animation-delay: .04s; }
.op-enter :is(.stat-card, .glass-card, .subject-card-big, .paper-card, .pdf-card, .flash-tile, .habit-card, .ccard, .lp-app, .badge):nth-child(3) { animation-delay: .08s; }
.op-enter :is(.stat-card, .glass-card, .subject-card-big, .paper-card, .pdf-card, .flash-tile, .habit-card, .ccard, .lp-app, .badge):nth-child(4) { animation-delay: .12s; }
.op-enter :is(.stat-card, .glass-card, .subject-card-big, .paper-card, .pdf-card, .flash-tile, .habit-card, .ccard, .lp-app, .badge):nth-child(5) { animation-delay: .16s; }
.op-enter :is(.stat-card, .glass-card, .subject-card-big, .paper-card, .pdf-card, .flash-tile, .habit-card, .ccard, .lp-app, .badge):nth-child(n+6) { animation-delay: .2s; }

/* ── 15. One rhythm for pages and windows ────────────────────────────
   Every app switches pages in .24s on the same decelerating curve, and
   windows open without the rubber-band overshoot some apps had. */
.page, main.view { animation-duration: .24s; animation-timing-function: cubic-bezier(.2, .8, .2, 1); }
.modal-card, .modal { animation-timing-function: var(--op-out); }

/* ── Reduce Motion (system setting) and Settings → Smooth transitions ──
   Both mean the same thing: changes happen at once. Loading indicators
   keep turning so a busy app never looks frozen. */
@media (prefers-reduced-motion: reduce) {
  .op-seg-pill { transition: opacity .01ms !important; }
  input[type="checkbox"]:checked, input[type="radio"]:checked { animation: none; }
  html.op-theme-fade * { transition-duration: .01ms !important; }
}
@media (prefers-reduced-motion: reduce) {
  :not(.spin, .skeleton, .shimmer, .loader__bar, .loader__ball, .splash-dots i, .ai-typing span, .lp-sync-dot),
  ::before, ::after {
    animation-duration: .01ms !important; animation-iteration-count: 1 !important;
    transition-duration: .01ms !important; scroll-behavior: auto !important;
  }
  #particles { display: none !important; }
}
html.op-no-motion :not(.spin, .skeleton, .shimmer, .loader__bar, .loader__ball, .splash-dots i, .ai-typing span, .lp-sync-dot), html.om-off :not(.spin, .skeleton, .shimmer, .loader__bar, .loader__ball, .splash-dots i, .ai-typing span, .lp-sync-dot),
html.op-no-motion ::before, html.op-no-motion ::after, html.om-off ::before, html.om-off ::after {
  animation-duration: .01ms !important; animation-iteration-count: 1 !important;
  transition-duration: .01ms !important; scroll-behavior: auto !important;
}
html.op-no-motion #particles, html.om-off #particles { display: none !important; }
html.op-no-motion .op-seg-pill, html.om-off .op-seg-pill { transition: none !important; }
html.op-no-motion input[type="checkbox"]:checked, html.op-no-motion input[type="radio"]:checked { animation: none; }

/* Calendar: the hour a carried block will land on */
.cal-hour-cell.op-drop { background: color-mix(in srgb, var(--accent, #3dd6c8) 14%, transparent) !important; transition: background-color .15s var(--op-out); }
.cal-block.op-lifted { cursor: grabbing; }

/* Shared-element morph (orbix-motion.js): the windows being photographed
   hold still, so the morph has the finished window to grow into */
.om-hold, .om-hold::before, .om-hold::after { transition: none !important; animation-duration: 0s !important; animation-delay: 0s !important; }

/* Topic rows (AuraSpace): the arrow turns as the subtopics fold out */
.topic-expand-btn > span { display: inline-block; transition: transform .38s var(--op-spring-tab); }
.topic-expand-btn.open > span { transform: rotate(180deg); }
