/* Application styles — keep most styling in Tailwind utilities */

/* ── Motion tokens (Apple-like) ──────────────────────────────────────────── */
:root {
  --ease-out: cubic-bezier(0.23, 1, 0.32, 1);
  --ease-in-out: cubic-bezier(0.77, 0, 0.175, 1);
  --ease-drawer: cubic-bezier(0.32, 0.72, 0, 1);
  --duration-press: 160ms;
  --duration-pop: 180ms;
  --duration-panel: 280ms;
  --duration-modal: 220ms;
}

/* Turbo ships no CSS for its own custom element, so <turbo-frame> defaults to
   display: inline. Every list that draws its borders with divide-y or
   :first-child/:last-child over frames needs them to be block boxes, or the
   dividers render as inline fragments instead of full-width rules.
   /tasks is built on exactly that (see app/views/tasks/index.html.erb). */
turbo-frame {
  display: block;
}

/* In-review segment on the client overview bar: green, with a light stripe
   so it reads as handed off, not accepted. Width still transitions on the
   element; the stripe does not move. */
.progress-review {
  background-image: repeating-linear-gradient(
    -45deg,
    transparent 0 3px,
    rgba(255, 255, 255, 0.42) 3px 5px
  );
}

/* Shake animation for invalid magic link code */
@keyframes shake {
  0%, 100% { transform: translateX(0); }
  25% { transform: translateX(-8px); }
  75% { transform: translateX(8px); }
}
.animate-shake {
  animation: shake 0.3s ease-in-out;
}

/* Done refused while a required answer is empty (taskCard#shakeDone): a
   short shake that settles, a few pixels, like a wrong password on a Mac. */
@keyframes ui-shake {
  0%, 100% { transform: translateX(0); }
  15% { transform: translateX(-5px); }
  35% { transform: translateX(5px); }
  55% { transform: translateX(-3px); }
  75% { transform: translateX(2px); }
}
.ui-shake {
  animation: ui-shake 300ms ease-out;
}

/* The card the quick-add box just added (utils/scroll_to_added): a soft
   ring and tint that fade over about 1.5 s. Reduced motion: a still ring,
   until the next click. */
@keyframes ui-just-added {
  0%, 35% {
    background-color: rgb(99 102 241 / 0.14);
    box-shadow: inset 0 0 0 2px rgb(99 102 241 / 0.75);
  }
  100% {
    background-color: rgb(99 102 241 / 0);
    box-shadow: inset 0 0 0 2px rgb(99 102 241 / 0);
  }
}
turbo-frame.ui-just-added > div {
  animation: ui-just-added 1.5s ease-out;
}

/* Answers after Done (plan 26, tasks/_field read_only:, and the fields
   form while Done is on its way, taskCard#markDone): the same boxes as the
   inputs, so nothing on the card moves. Only the border and the fill
   change; the size, padding, and line height stay. The wrapper is inert,
   so nothing in it takes a click or a tab. */
.ui-answers-readonly :is(textarea, input:not([type="radio"]):not([type="checkbox"])) {
  border-color: transparent;
  background-color: rgb(249 250 251);
  box-shadow: none;
  color: rgb(17 24 39);
}
.ui-answers-readonly :is(textarea, input)::placeholder {
  color: rgb(156 163 175);
}
.ui-answers-readonly [data-choice-row] {
  cursor: default;
  background-color: transparent;
}
.dark .ui-answers-readonly :is(textarea, input:not([type="radio"]):not([type="checkbox"])) {
  border-color: transparent;
  background-color: rgb(31 41 55 / 0.5);
  color: rgb(243 244 246);
}
.ui-answers-readonly button {
  visibility: hidden;
}
@media (prefers-reduced-motion: no-preference) {
  .ui-answers :is(textarea, input:not([type="radio"]):not([type="checkbox"])) {
    transition: background-color 200ms ease-out, border-color 200ms ease-out;
  }
}

/* The empty required answer (tasks/_field, aria-invalid): a red outline
   on the input or the radio/checkbox group, red focus too. */
[data-required-control][aria-invalid="true"] {
  border-color: rgb(220 38 38);
  box-shadow: 0 0 0 1px rgb(220 38 38);
}
[data-required-control][aria-invalid="true"]:focus {
  border-color: rgb(220 38 38);
  box-shadow: 0 0 0 3px rgb(220 38 38 / 0.25), 0 0 0 1px rgb(220 38 38);
}
div[data-required-control][aria-invalid="true"] {
  box-shadow: none;
  outline: 1.5px solid rgb(220 38 38);
  outline-offset: 6px;
}

/* ── UI polish utilities ─────────────────────────────────────────────────── */

/* Pressable controls: subtle scale on :active (skip disabled / aria-disabled). */
.ui-press {
  transition: transform var(--duration-press) var(--ease-out);
}
.ui-press:active:not(:disabled):not([aria-disabled="true"]) {
  transform: scale(0.97);
}

/* A 20 px control that takes taps 44 px wide (plan 21 F07): the ::before
   reaches 12 px past each side. Rows keep 16 px of padding, so it never
   leaves the row. */
.ui-tap {
  position: relative;
}
.ui-tap::before {
  content: "";
  position: absolute;
  inset: -12px;
}

/* The card page has no why line (plan 26): the description is right below.
   The partial leaves it out there (single:); this covers a card re-rendered
   in place without it (a refused write). */
[data-single-task] [data-row-why] {
  display: none;
}

/* A Projects list (boards/_task_list, plan 26): a line above each row but
   the first, so done rows hidden at the end leave no stray line. Done rows
   hide until "Show N done" (data-show-done on the project section); a card
   acted on in this page view stays (data-board-keep). */
.ui-list-rows > * + * {
  border-top: 1px solid rgb(0 0 0 / 0.1);
}
.dark .ui-list-rows > * + * {
  border-top-color: rgb(255 255 255 / 0.1);
}
[data-board-project]:not([data-show-done]) [data-board-done]:not([data-board-keep]) {
  display: none;
}

/* The shared toast (utils/toast, plan 26): bottom center on a phone, above
   the safe area; bottom right on a computer. Never takes a tap. */
.ui-toast-region {
  position: fixed;
  z-index: 70;
  left: 50%;
  bottom: calc(1rem + env(safe-area-inset-bottom));
  transform: translateX(-50%);
  pointer-events: none;
}
@media (min-width: 640px) {
  .ui-toast-region {
    left: auto;
    right: 1.5rem;
    bottom: 1.5rem;
    transform: none;
  }
}
.ui-toast {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  white-space: nowrap;
  border-radius: 9999px;
  padding: 0.5rem 1rem;
  font-size: 0.875rem;
  line-height: 1.25rem;
  font-weight: 500;
  color: #fff;
  background: rgb(17 24 39 / 0.92);
  box-shadow: 0 8px 24px rgb(0 0 0 / 0.18);
  opacity: 0;
  transform: translateY(6px);
  transition: opacity 180ms var(--ease-out), transform 180ms var(--ease-out);
}
.ui-toast-in {
  opacity: 1;
  transform: none;
}
.ui-toast-error {
  background: rgb(220 38 38 / 0.95);
  white-space: normal;
  max-width: calc(100vw - 2rem);
}
.dark .ui-toast:not(.ui-toast-error) {
  color: #111827;
  background: rgb(243 244 246 / 0.95);
}
@media (prefers-reduced-motion: reduce) {
  .ui-toast {
    transform: none;
    transition: opacity 120ms linear;
  }
}

/* Soft focus glow (Apple-style) instead of a hard offset ring. */
.ui-focus-ring:focus {
  outline: none;
}
.ui-focus-ring:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px rgba(0, 113, 227, 0.28), 0 0 0 1px #0071e3;
}
.dark .ui-focus-ring:focus-visible,
:where(.dark) .ui-focus-ring:focus-visible {
  box-shadow: 0 0 0 3px rgba(10, 132, 255, 0.35), 0 0 0 1px #0a84ff;
}

/* Separator / hairline borders (grouped-list style). */
.ui-hairline {
  border-color: rgba(60, 60, 67, 0.18);
}
.dark .ui-hairline,
:where(.dark) .ui-hairline {
  border-color: rgba(255, 255, 255, 0.1);
}

/* Chevron rotation — Tailwind v4's rotate-180 sets the individual `rotate`
   property (not transform: rotate()). Transition that property so the spin
   animates in sync with x-collapse.duration.280ms. */
.ui-chevron {
  transition: rotate var(--duration-panel) var(--ease-in-out);
}

/* Soft strikethrough for completed task titles. Add .struck when done. */
.ui-title-done {
  transition:
    color var(--duration-panel) var(--ease-out),
    text-decoration-color var(--duration-panel) var(--ease-out);
  text-decoration-line: none;
  text-decoration-color: transparent;
}
.ui-title-done.struck {
  text-decoration-line: line-through;
  text-decoration-color: currentColor;
  color: rgb(156 163 175); /* gray-400 */
}
.dark .ui-title-done.struck,
:where(.dark) .ui-title-done.struck {
  color: rgb(107 114 128); /* gray-500 */
}

/* Open / expanded task row tint (very subtle blue-gray). */
.ui-row-open {
  background: rgba(0, 113, 227, 0.04);
}
.dark .ui-row-open,
:where(.dark) .ui-row-open {
  background: rgba(10, 132, 255, 0.08);
}

/* Top navigation — solid page bg (matches html/body). Not sticky: scrolls away. */
.ui-nav-bar {
  background: #f5f5f7;
  border-bottom: 0.5px solid rgba(0, 0, 0, 0.05);
}
.dark .ui-nav-bar,
.dark.ui-nav-bar,
:where(.dark) .ui-nav-bar {
  background: rgb(3 7 18); /* gray-950 */
  border-bottom-color: rgba(255, 255, 255, 0.05);
}

/* Optional fade helper for reduced-motion-friendly panel content. */
.ui-reduce-motion-fade {
  transition: opacity var(--duration-panel) var(--ease-out);
}

/* ── "Mark done" on a to-do row ──────────────────────────────────────────────
   Three pieces fire together when the status circle flips to done: the circle
   pops, a halo ripples out of it, and the check draws itself.

   These are class-triggered rather than transitioned because the check lives
   inside an x-show element: display:none terminates animations on an element
   and its whole subtree, so revealing it restarts all three from 0% for free.
   The already-done rows the server renders carry no animation class at all,
   so a page load doesn't replay the celebration.

   taskCard#markDone() waits 420ms before submitting, so the 380ms draw always
   finishes before Turbo replaces the row. */

/* The heroicons check path ("m4.5 12.75 6 6 9-13.5") measures ~24.7 user
   units; 26 covers it with a little slack so the stroke fully retracts. */
@keyframes task-check-draw {
  from { stroke-dashoffset: 26; }
  to { stroke-dashoffset: 0; }
}

@keyframes task-check-pop {
  0% { transform: scale(0.6); }
  55% { transform: scale(1.18); }
  100% { transform: scale(1); }
}

/* Capped at 1.9x deliberately: the row sits inside the list's overflow-hidden
   rounded container with only py-3 of vertical breathing room, so a larger
   halo gets sliced square by the clip. */
@keyframes task-check-halo {
  from { transform: scale(1); opacity: 0.45; }
  to { transform: scale(1.9); opacity: 0; }
}

.task-check-draw {
  stroke-dasharray: 26;
  stroke-dashoffset: 26;
  animation: task-check-draw 380ms ease-out forwards;
}

.task-check-pop {
  animation: task-check-pop 380ms cubic-bezier(0.34, 1.56, 0.64, 1);
}

.task-check-halo {
  animation: task-check-halo 520ms ease-out forwards;
}

/* ── Celebrations (utils/celebrate.js) ───────────────────────────────────────
   One fixed canvas and one fixed layer, both pointer-events none, on <html>.
   Overlays animate transform and opacity only, so the board never reflows.
   The rainbow is one slow wash (no flashing); the JS plays it at most once a
   second. */
.celebration-canvas,
.celebration-layer {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  z-index: 9999;
}

.celebration-layer {
  z-index: 10000;
  overflow: hidden;
}

.celebration-counter {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  text-align: center;
  color: #111827;
  animation: celebration-counter-in 420ms cubic-bezier(0.34, 1.56, 0.64, 1) both;
  transition: opacity 450ms ease-out, transform 450ms ease-in;
}

.dark .celebration-counter { color: #f9fafb; }

.celebration-counter__number {
  font-size: clamp(4.5rem, 22vw, 9rem);
  font-weight: 900;
  line-height: 1;
  font-variant-numeric: tabular-nums;
  background: linear-gradient(135deg, #2563eb, #8b5cf6 55%, #ec4899);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  filter: drop-shadow(0 4px 14px rgb(0 0 0 / 0.18));
}

.celebration-counter__label {
  margin-top: 0.25rem;
  font-size: clamp(0.9rem, 4vw, 1.25rem);
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  text-shadow: 0 0 10px rgb(255 255 255 / 0.9);
}

.dark .celebration-counter__label { text-shadow: 0 0 10px rgb(0 0 0 / 0.9); }

.celebration-counter.is-done .celebration-counter__number {
  animation: celebration-number-pop 520ms cubic-bezier(0.34, 1.56, 0.64, 1);
}

.celebration-counter.is-leaving {
  opacity: 0;
  transform: translate(-50%, -50%) scale(0.9);
}

.celebration-counter--huge { top: 56%; }

.celebration-counter--huge .celebration-counter__number {
  font-size: clamp(7rem, 42vw, 18rem);
  background: linear-gradient(90deg, #ef4444, #f97316, #eab308, #22c55e, #06b6d4, #3b82f6, #8b5cf6, #ec4899, #ef4444);
  background-size: 200% 100%;
  -webkit-background-clip: text;
  background-clip: text;
  filter: drop-shadow(0 6px 18px rgb(0 0 0 / 0.25));
  animation: celebration-gradient 2.4s linear infinite, celebration-throb 1.2s ease-in-out infinite;
}

.celebration-banner {
  position: absolute;
  left: 50%;
  top: 9%;
  transform: translateX(-50%);
  animation: celebration-banner-in 700ms cubic-bezier(0.34, 1.56, 0.64, 1) both;
  transition: opacity 500ms ease-out;
}

.celebration-banner span {
  display: inline-block;
  padding: 0.35em 1em;
  border-radius: 999px;
  background: linear-gradient(90deg, #f59e0b, #fde047, #f59e0b);
  color: #451a03;
  font-size: clamp(1.25rem, 7vw, 3rem);
  font-weight: 900;
  letter-spacing: 0.2em;
  white-space: nowrap;
  box-shadow: 0 10px 40px rgb(245 158 11 / 0.45);
}

.celebration-banner.is-leaving { opacity: 0; }

.celebration-rainbow {
  position: absolute;
  inset: 0;
  background: linear-gradient(135deg, #ef4444, #f97316, #eab308, #22c55e, #06b6d4, #3b82f6, #8b5cf6, #ec4899);
  animation: celebration-rainbow 1500ms ease-in-out both;
}

.celebration-shake {
  animation: celebration-shake 650ms cubic-bezier(0.36, 0.07, 0.19, 0.97) both;
}

.celebration-counter--calm {
  animation: celebration-fade-in 300ms ease-out both;
}

.celebration-counter--calm .celebration-counter__number {
  animation: none;
}

@keyframes celebration-counter-in {
  from { opacity: 0; transform: translate(-50%, -50%) scale(0.4); }
  to { opacity: 1; transform: translate(-50%, -50%) scale(1); }
}

@keyframes celebration-number-pop {
  0% { transform: scale(1); }
  45% { transform: scale(1.25); }
  100% { transform: scale(1); }
}

@keyframes celebration-gradient {
  to { background-position: 200% 0; }
}

@keyframes celebration-throb {
  0%, 100% { transform: scale(1) rotate(-2deg); }
  50% { transform: scale(1.08) rotate(2deg); }
}

@keyframes celebration-banner-in {
  from { opacity: 0; transform: translateX(-50%) scale(2.6) rotate(-8deg); }
  to { opacity: 1; transform: translateX(-50%) scale(1) rotate(0); }
}

/* One wash: up and down in 1.5 s, never above 30% opacity. */
@keyframes celebration-rainbow {
  0% { opacity: 0; filter: hue-rotate(0deg); }
  35% { opacity: 0.3; }
  100% { opacity: 0; filter: hue-rotate(180deg); }
}

@keyframes celebration-shake {
  0%, 100% { transform: translate3d(0, 0, 0); }
  10% { transform: translate3d(-10px, 4px, 0) rotate(-0.6deg); }
  25% { transform: translate3d(10px, -4px, 0) rotate(0.6deg); }
  40% { transform: translate3d(-8px, 3px, 0) rotate(-0.4deg); }
  55% { transform: translate3d(7px, -2px, 0) rotate(0.3deg); }
  70% { transform: translate3d(-4px, 1px, 0); }
  85% { transform: translate3d(2px, 0, 0); }
}

@keyframes celebration-fade-in {
  from { opacity: 0; }
  to { opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  turbo-frame.ui-just-added > div {
    animation: none;
    box-shadow: inset 0 0 0 2px rgb(99 102 241 / 0.75);
  }

  .animate-shake,
  .ui-shake,
  .task-check-pop,
  .celebration-shake,
  .celebration-rainbow,
  .celebration-banner,
  .celebration-counter__number {
    animation: none;
  }

  .celebration-rainbow { display: none; }

  /* No draw — show the finished check immediately. */
  .task-check-draw {
    animation: none;
    stroke-dashoffset: 0;
  }

  /* No ripple at all rather than a static green disc over the circle. */
  .task-check-halo {
    animation: none;
    opacity: 0;
  }

  /* Press / chevron / panel helpers: no motion, settle immediately.
     Only kill transitions — do not force rotate/transform to none on
     .ui-chevron or open-state rotate-180 never applies under reduced motion. */
  .ui-press,
  .ui-press:active:not(:disabled):not([aria-disabled="true"]),
  .ui-chevron,
  .ui-title-done,
  .ui-reduce-motion-fade {
    transition: none !important;
  }

  .ui-press,
  .ui-press:active:not(:disabled):not([aria-disabled="true"]) {
    transform: none !important;
  }

  /* Alpine x-collapse sets height transition inline on the directive host. */
  [x-collapse] {
    transition-duration: 0.01ms !important;
    animation-duration: 0.01ms !important;
  }
}

/* Trix editor styling */
trix-editor {
  min-height: 12rem;
}

/* Nesting controls rarely needed on task descriptions; hide to declutter. */
trix-toolbar .trix-button--icon-increase-nesting-level,
trix-toolbar .trix-button--icon-decrease-nesting-level {
  display: none;
}

trix-editor.trix-content {
  overflow-y: auto;
}

/* trix.css zeroes all margins inside .trix-content; restore vertical rhythm
   when the content layout also carries Tailwind prose classes. */
.trix-content.prose p,
.trix-content.prose ul,
.trix-content.prose ol,
.trix-content.prose blockquote,
.trix-content.prose pre,
.trix-content.prose h1 {
  margin-top: 0.75em;
  margin-bottom: 0.75em;
}

.trix-content.prose > :first-child {
  margin-top: 0;
}

.trix-content.prose > :last-child {
  margin-bottom: 0;
}

.trix-content.prose li {
  margin-top: 0.25em;
  margin-bottom: 0.25em;
}

/* Dark mode polish for the Trix toolbar on the admin form */
.dark trix-toolbar .trix-button-group {
  border-color: rgb(55 65 81);
}

.dark trix-toolbar .trix-button {
  background: rgb(17 24 39);
  color: rgb(209 213 219);
}

.dark trix-toolbar .trix-button.trix-active {
  background: rgb(31 41 55);
}

.dark trix-editor {
  background: rgb(17 24 39);
  border-color: rgb(55 65 81);
  color: rgb(243 244 246);
}

.dark trix-toolbar .trix-dialog {
  background: rgb(31 41 55);
  border-color: rgb(55 65 81);
  color: rgb(243 244 246);
}
