/*
 * This is a manifest file that'll be compiled into application.css.
 *
 * With Propshaft, assets are served efficiently without preprocessing steps. You can still include
 * application-wide styles in this file, but keep in mind that CSS precedence will follow the standard
 * cascading order, meaning styles declared later in the document or manifest will override earlier ones,
 * depending on specificity.
 *
 * Consider organizing styles into separate files for maintainability.
 */

/* ============================================
   TOPIC NAVIGATION HIGHLIGHT
   ============================================ */

/* Highlight animation when clicking sidebar navigation */
.topic-highlight {
  animation: topic-pulse 2.5s ease-out;
}

@keyframes topic-pulse {
  0% {
    box-shadow: 0 0 0 0 rgba(60, 141, 147, 0.4), 0 0 20px rgba(60, 141, 147, 0.3);
    border-color: rgba(60, 141, 147, 0.6);
  }
  25% {
    box-shadow: 0 0 0 8px rgba(60, 141, 147, 0), 0 0 30px rgba(60, 141, 147, 0.2);
    border-color: rgba(60, 141, 147, 0.5);
  }
  50% {
    box-shadow: 0 0 0 0 rgba(60, 141, 147, 0.3), 0 0 15px rgba(60, 141, 147, 0.15);
    border-color: rgba(60, 141, 147, 0.4);
  }
  100% {
    box-shadow: none;
    border-color: rgba(226, 232, 240, 0.6);
  }
}

/* Respect reduced motion preference */
@media (prefers-reduced-motion: reduce) {
  .topic-highlight {
    animation: none;
    box-shadow: 0 0 0 3px rgba(60, 141, 147, 0.3);
    transition: box-shadow 0.3s ease;
  }
}

/* Browsers give <button> the default arrow cursor — that is the UA stylesheet, not a Tailwind
   choice — and only ButtonComponent opts into cursor-pointer. Every hand-written button in the app
   (the raspored header's expand/collapse/trash, the duration steppers, the nudge arrows, the menu
   items) therefore looked inert on hover. Restore the cursor people expect, once, centrally.
   :disabled excluded so a disabled control still reads as unavailable. */
button:not(:disabled):not([aria-disabled="true"]),
[role="button"]:not([aria-disabled="true"]),
[role="menuitem"]:not(:disabled) {
  cursor: pointer;
}

/* Modal animations */
dialog[open] {
  animation: fade-in 0.2s ease-out;
}

dialog::backdrop {
  animation: fade-in 0.2s ease-out;
}

@keyframes fade-in {
  from {
    opacity: 0;
    transform: scale(0.95);
  }
  to {
    opacity: 1;
    transform: scale(1);
  }
}

/* Bottom sheet modal on mobile */
@media (max-width: 767px) {
  dialog[open] {
    position: fixed;
    bottom: 0;
    left: 0;
    right: 0;
    top: auto;
    max-width: 100%;
    width: 100%;
    margin: 0;
    border-radius: 1.5rem 1.5rem 0 0;
    max-height: 90vh;
    animation: slide-up 0.3s ease-out;
  }

  @keyframes slide-up {
    from {
      transform: translateY(100%);
    }
    to {
      transform: translateY(0);
    }
  }
}

@keyframes fade-in-up {
  from {
    opacity: 0;
    transform: translateY(10px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.animate-fade-in-up {
  animation: fade-in-up 0.3s ease-out;
}

/* Lesson selector drag-and-drop */
[data-lesson-id] {
  transition: transform 0.15s ease, box-shadow 0.15s ease, opacity 0.15s ease;
}

[data-lesson-id].sortable-ghost,
.slot.sortable-ghost {
  opacity: 0.4;
}

[data-lesson-id].sortable-drag,
.slot.sortable-drag {
  box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1);
}

/* Sortable chosen state (element being picked up) */
.slot.sortable-chosen {
  background-color: rgba(60, 141, 147, 0.15) !important;
  border-color: var(--color-primary, #3C8D93) !important;
}

/* Drag handle cursor states */
.drag-handle {
  cursor: grab;
  touch-action: none;
}

.drag-handle:active {
  cursor: grabbing;
}

/* Buttons that only appear in certain lists. The .move-up-btn / .move-down-btn rules that used to
   live here — and the [data-lesson-selector-target="selectedList"] block around them — belonged to
   the lesson selector this calendar grid replaced; that target no longer exists in any view. They
   were worth deleting rather than leaving: a base rule setting .move-up-btn to display:none is an
   ambush for the next person who names a move button the obvious thing. */
.remove-btn,
.add-btn {
  display: none;
  align-items: center;
  justify-content: center;
}

/* Calendar slots: show remove button in filled slots */
.slot-filled .remove-btn {
  display: flex;
}

/* Controls always visible on touch/mobile (no hover capability) */
@media (hover: none) {
  .lesson-item .lesson-controls {
    opacity: 1;
  }
}

/* A moved row briefly re-announces itself. The whole pane is rebuilt on every mutation, so without
   this the only feedback that a nudge worked is that the rows are in a different order. */
.slot-just-moved {
  animation: slot-moved 0.6s ease-out;
}

@keyframes slot-moved {
  0%   { box-shadow: 0 0 0 3px rgba(60, 141, 147, 0.45); }
  100% { box-shadow: 0 0 0 3px rgba(60, 141, 147, 0); }
}

/* The free čas a "Slobodan čas" jump landed on. Same ring as .slot-just-moved, three beats rather
   than one: after a nudge the rows are visibly in a different order, but a jump changes nothing on
   screen except where the grid is scrolled to, so the ring is the only thing that says "here". */
.slot-cued {
  animation: slot-moved 0.6s ease-out 3;
}

/* Nudge arrows sit in the rail and follow the row's hover, like the remove button. Kept visible at
   all times for pointer-less input and so Capybara can drive them. */
.slot-rail .nudge-btn {
  opacity: 0.45;
}
.slot-filled:hover .slot-rail .nudge-btn,
.slot-rail .nudge-btn:focus-visible {
  opacity: 1;
}

/* Calendar Slots - Month Headers (horizontal, like semester headers) */
.month-header {
  /* No vertical styling - horizontal layout from JS Tailwind classes */
}

/* Calendar Slots - Month Group Container (vertical stack: header on top, slots below) */
.month-group {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  margin-bottom: 0.75rem;
}

.month-group:last-child {
  margin-bottom: 0;
}

.month-slots {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}

/* Calendar Slots - Slot States */
.slot {
  transition: all 0.15s ease;
}

/* Between two block rows: a band across the gap above a lesson reveals, on hover, the divider and
   its two pills — „+ Umetni lekciju“ and „Označi kao nenastavni dan“ (helpers/slot_row.js). Sugar
   for the mouse only:
   the ⋮ menu is the keyboard path to both („Umetni lekciju iznad“, „Označi dan kao nenastavni“), so
   the band is invisible rather than absent when nothing points at it. */
.insert-zone {
  position: absolute;
  left: 0;
  right: 0;
  top: -9px;
  height: 18px;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  opacity: 0;
  z-index: 5;
}
.insert-zone::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: 50%;
  height: 2px;
  background-color: var(--color-primary, #3C8D93);
}
.insert-zone:hover,
.insert-zone:focus-within {
  opacity: 1;
}
.insert-pill,
.insert-note {
  position: relative;
}
/* Two pills plus the note come to ~384px, and at the md 3fr/2fr split the band is only ~400px — so
   a three-digit push count ("pomjera 140 lekcija") used to overflow it and the PILLS wrapped, 21px
   of button becoming 54px inside an 18px strip. The pills are the actionable half and never give;
   the note is the nicety and yields first. */
.insert-pill {
  flex: none;
  white-space: nowrap;
}
/* The note sits on the divider line; without its own surface the line strikes through the text. */
.insert-note {
  background-color: var(--color-bg-primary, #fff);
  padding: 0 0.375rem;
  border-radius: 9999px;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Pending-target mode: the row being filled, and the rows the confirmed insert would redate.
   This is the answer to "where is this lesson going", so it is the one row lifted off the pane -
   3px ring plus a wide drop shadow, the same elevation .slot-preview-target uses below. The ring
   stays grey: teal means assigned in this pane, and this cas is not one yet. Lift and arrival
   flash are both box-shadow, so they cannot be split between a Tailwind class and a keyframe -
   the class would be overwritten for the animation's whole 0.6s and then snap in. */
.slot-pending {
  box-shadow: 0 0 0 3px rgba(113, 128, 150, 0.12), 0 8px 32px rgba(45, 55, 72, 0.14);
  animation: pending-arrive 0.6s ease-out;
}

@keyframes pending-arrive {
  0%   { box-shadow: 0 0 0 3px rgba(60, 141, 147, 0.45), 0 8px 32px rgba(45, 55, 72, 0.14); }
  100% { box-shadow: 0 0 0 3px rgba(113, 128, 150, 0.12), 0 8px 32px rgba(45, 55, 72, 0.14); }
}

@media (prefers-reduced-motion: reduce) {
  .slot-pending {
    animation: none;
  }
}
.slot-moving {
  border-color: rgba(245, 166, 35, 0.6) !important;
}

/* Dostupne lekcije: tema groups fold on their header. */
.pool-group.collapsed .pool-items {
  display: none;
}
.pool-group.collapsed .pool-chevron {
  transform: rotate(-90deg);
}

.slot-empty {
  border-style: dashed;
}

.slot-empty:hover {
  border-color: #93c5fd;
  background-color: #eff6ff;
}

.slot-filled {
  border-style: solid;
}

.slot-nonworking {
  cursor: not-allowed;
}

/* Non-working slot stripe patterns */
.slot-nonworking-school {
  background: repeating-linear-gradient(
    -45deg,
    rgba(245, 166, 35, 0.15),
    rgba(245, 166, 35, 0.15) 4px,
    rgba(245, 166, 35, 0.08) 4px,
    rgba(245, 166, 35, 0.08) 8px
  );
  border-color: rgba(245, 166, 35, 0.5);
}

.slot-nonworking-subject {
  background: repeating-linear-gradient(
    -45deg,
    rgba(234, 179, 8, 0.15),
    rgba(234, 179, 8, 0.15) 4px,
    rgba(234, 179, 8, 0.08) 4px,
    rgba(234, 179, 8, 0.08) 8px
  );
  border-color: rgba(234, 179, 8, 0.5);
}

/* ============================================
   HOVER PREVIEW SYSTEM
   ============================================ */

/* Preview target slot - elevated "ghost preview" with pulse */
.slot-preview-target {
  border-color: var(--color-primary) !important;
  border-style: solid !important;
  border-width: 2px !important;
  background: linear-gradient(135deg, rgba(60, 141, 147, 0.12) 0%, rgba(60, 141, 147, 0.06) 100%) !important;
  box-shadow:
    0 0 0 3px rgba(60, 141, 147, 0.2),
    0 4px 12px rgba(60, 141, 147, 0.15);
  animation: slot-ready 0.3s ease-out forwards, slot-pulse 2s ease-in-out 0.3s infinite;
}

@keyframes slot-ready {
  0% { transform: scale(0.98); opacity: 0.7; }
  100% { transform: scale(1); opacity: 1; }
}

@keyframes slot-pulse {
  0%, 100% {
    box-shadow:
      0 0 0 3px rgba(60, 141, 147, 0.2),
      0 4px 12px rgba(60, 141, 147, 0.15);
  }
  50% {
    box-shadow:
      0 0 0 5px rgba(60, 141, 147, 0.15),
      0 6px 16px rgba(60, 141, 147, 0.2);
  }
}

/* Hide original slot content completely */
.slot-preview-target > *:not(.slot-preview-content) {
  visibility: hidden;
}

/* Preview content injected by JS - opaque background */
.slot-preview-content {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  padding: 0.5rem 0.75rem;
  background: rgba(60, 141, 147, 0.08);
  border-radius: inherit;
  animation: preview-fade-in 0.25s ease-out;
}

@keyframes preview-fade-in {
  0% { opacity: 0; transform: translateX(8px); }
  100% { opacity: 1; transform: translateX(0); }
}

.slot-preview-content .preview-icon {
  flex-shrink: 0;
  width: 1.25rem;
  height: 1.25rem;
  color: var(--color-primary);
  animation: preview-icon-bounce 0.4s ease-out;
}

@keyframes preview-icon-bounce {
  0% { transform: translateX(4px); }
  50% { transform: translateX(-2px); }
  100% { transform: translateX(0); }
}

.slot-preview-content .preview-text {
  font-size: 0.8125rem;
  font-weight: 500;
  color: var(--color-primary-dark);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.slot-preview-content .preview-date {
  font-size: 0.6875rem;
  color: var(--color-primary);
  opacity: 0.8;
  margin-left: auto;
  flex-shrink: 0;
}

/* Hovering lesson item - elevated lift effect */
.lesson-item-dragging {
  transform: scale(1.02) translateY(-1px);
  box-shadow: 0 8px 20px rgba(60, 141, 147, 0.2);
  border-color: var(--color-primary) !important;
  background: linear-gradient(135deg, rgba(60, 141, 147, 0.08) 0%, rgba(60, 141, 147, 0.04) 100%) !important;
}

/* No slots available - lesson item feedback */
.lesson-item-no-slots {
  opacity: 0.5;
  cursor: not-allowed !important;
  position: relative;
}

.lesson-item-no-slots::after {
  content: "Nema slobodnih časova";
  position: absolute;
  top: calc(100% + 4px);
  left: 50%;
  transform: translateX(-50%);
  background: var(--color-text-primary);
  color: white;
  font-size: 0.6875rem;
  font-weight: 500;
  padding: 0.25rem 0.5rem;
  border-radius: 0.375rem;
  white-space: nowrap;
  z-index: 50;
  animation: tooltip-appear-down 0.2s ease-out;
  pointer-events: none;
}

.lesson-item-no-slots::before {
  content: "";
  position: absolute;
  top: 100%;
  left: 50%;
  transform: translateX(-50%);
  border: 4px solid transparent;
  border-bottom-color: var(--color-text-primary);
  z-index: 50;
  animation: tooltip-appear-down 0.2s ease-out;
}

@keyframes tooltip-appear-down {
  0% { opacity: 0; transform: translateX(-50%) translateY(-4px); }
  100% { opacity: 1; transform: translateX(-50%) translateY(0); }
}

/* Last item (not first): show tooltip above */
.lesson-item-no-slots:last-child:not(:first-child)::after {
  top: auto;
  bottom: calc(100% + 4px);
  animation: tooltip-appear-up 0.2s ease-out;
}

.lesson-item-no-slots:last-child:not(:first-child)::before {
  top: auto;
  bottom: 100%;
  border-bottom-color: transparent;
  border-top-color: var(--color-text-primary);
  animation: tooltip-appear-up 0.2s ease-out;
}

@keyframes tooltip-appear-up {
  0% { opacity: 0; transform: translateX(-50%) translateY(4px); }
  100% { opacity: 1; transform: translateX(-50%) translateY(0); }
}

/* Collapsed section indicator - shows on collapsed month/semester header */
.collapsed-preview-indicator {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  margin-left: 0.5rem;
  padding: 0.25rem 0.625rem;
  background: var(--color-primary);
  color: white;
  font-size: 0.6875rem;
  font-weight: 600;
  border-radius: 9999px;
  animation: indicator-pop 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
  cursor: pointer;
  transition: background-color 0.15s ease, transform 0.15s ease;
}

.collapsed-preview-indicator:hover {
  background: var(--color-primary-dark);
  transform: scale(1.05);
}

.collapsed-preview-indicator svg {
  width: 0.75rem;
  height: 0.75rem;
}

@keyframes indicator-pop {
  0% { opacity: 0; transform: scale(0.8); }
  100% { opacity: 1; transform: scale(1); }
}

/* Beacon indicator - fixed centered in visible left column */
.slot-beacon {
  position: fixed;
  z-index: 50;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  padding: 0.625rem 1.25rem;
  background: linear-gradient(135deg, var(--color-primary), var(--color-primary-dark));
  color: white;
  font-size: 0.8125rem;
  font-weight: 600;
  border-radius: 9999px;
  box-shadow: 0 4px 16px rgba(60, 141, 147, 0.35);
  cursor: pointer;
  animation: beacon-pulse 1.5s ease-in-out infinite;
  transition: transform 0.15s ease, background 0.15s ease;
  transform: translateX(-50%);
}

.slot-beacon:hover {
  animation: none;
  transform: translateX(-50%) scale(1.05);
  background: linear-gradient(135deg, var(--color-primary-dark), var(--color-primary));
}

.slot-beacon svg {
  width: 1.125rem;
  height: 1.125rem;
}

@keyframes beacon-pulse {
  0%, 100% {
    box-shadow: 0 4px 16px rgba(60, 141, 147, 0.35);
  }
  50% {
    box-shadow: 0 4px 24px rgba(60, 141, 147, 0.5);
  }
}

/* Undo toast animation */
.undo-toast {
  animation: toast-slide-up 0.3s ease-out;
}

@keyframes toast-slide-up {
  from {
    opacity: 0;
    transform: translate(-50%, 20px);
  }
  to {
    opacity: 1;
    transform: translate(-50%, 0);
  }
}

.undo-toast-exit {
  animation: toast-fade-out 0.2s ease-in forwards;
}

@keyframes toast-fade-out {
  to {
    opacity: 0;
    transform: translate(-50%, 10px);
  }
}

/* ============================================
   PREMIUM MESH GRADIENT BACKGROUND
   Stripe-inspired animated gradient effect
   ============================================ */

.mesh-gradient-bg {
  position: relative;
  overflow: hidden;
}

.mesh-gradient-bg::before,
.mesh-gradient-bg::after {
  content: '';
  position: absolute;
  border-radius: 50%;
  filter: blur(100px);
  opacity: 0.4;
  animation: mesh-float 20s ease-in-out infinite;
  pointer-events: none;
  z-index: 0;
}

.mesh-gradient-bg::before {
  width: 70%;
  height: 70%;
  background: var(--color-primary);
  top: -30%;
  left: -20%;
}

.mesh-gradient-bg::after {
  width: 60%;
  height: 60%;
  background: var(--color-accent);
  bottom: -25%;
  right: -15%;
  animation-delay: -10s;
}

/* Third gradient blob via .gradient-blob element */
.mesh-gradient-bg .gradient-blob {
  position: absolute;
  width: 50%;
  height: 50%;
  background: var(--color-primary-light);
  border-radius: 50%;
  filter: blur(80px);
  opacity: 0.3;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  animation: mesh-float 25s ease-in-out infinite reverse;
  pointer-events: none;
  z-index: 0;
}

@keyframes mesh-float {
  0%, 100% {
    transform: translate(0, 0) scale(1);
  }
  25% {
    transform: translate(5%, 8%) scale(1.05);
  }
  50% {
    transform: translate(-3%, 5%) scale(0.95);
  }
  75% {
    transform: translate(8%, -5%) scale(1.02);
  }
}

/* Ensure content sits above gradient */
.mesh-gradient-bg > *:not(.gradient-blob) {
  position: relative;
  z-index: 1;
}

@media (prefers-reduced-motion: reduce) {
  .mesh-gradient-bg::before,
  .mesh-gradient-bg::after,
  .mesh-gradient-bg .gradient-blob {
    animation: none;
  }
}

/* ============================================
   PREMIUM BENTO CARD HOVER CHOREOGRAPHY
   500ms staggered animation sequence
   ============================================ */

.bento-card {
  transition: transform 150ms ease-out,
              box-shadow 200ms ease-out,
              border-color 200ms ease-out;
}

.bento-card:hover {
  transform: translateY(-4px);
  box-shadow: var(--shadow-large);
  border-color: rgba(60, 141, 147, 0.3);
}

/* Optional shimmer sweep effect */
.bento-card::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(
    110deg,
    transparent 20%,
    rgba(255, 255, 255, 0.1) 50%,
    transparent 80%
  );
  transform: translateX(-100%);
  transition: transform 600ms ease-out 100ms;
  pointer-events: none;
  border-radius: inherit;
}

.bento-card:hover::after {
  transform: translateX(100%);
}

@media (prefers-reduced-motion: reduce) {
  .bento-card {
    transition: none;
  }

  .bento-card:hover {
    transform: none;
  }

  .bento-card::after {
    display: none;
  }
}

/* ============================================
   HERO FLOATING PDF BADGE
   ============================================ */

.hero-pdf-badge {
  animation: pdf-float 3s ease-in-out infinite;
}

@keyframes pdf-float {
  0%, 100% { transform: translate(0, 0); }
  50% { transform: translate(0, -8px); }
}

@media (prefers-reduced-motion: reduce) {
  .hero-pdf-badge {
    animation: none;
  }
}

/* ============================================
   SIGNUP PREVIEW FLOATING BADGE
   ============================================ */

.signup-preview-float {
  animation: signup-float 3s ease-in-out infinite;
}

@keyframes signup-float {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(-6px); }
}

@media (prefers-reduced-motion: reduce) {
  .signup-preview-float {
    animation: none;
  }
}

/* ============================================
   FOOTER PREMIUM HOVER EFFECTS
   ============================================ */

/* Footer links with underline slide-in */
.footer-link {
  transition: color 200ms ease-out;
}

.footer-link:hover {
  color: white;
}

/* Mobile: the shared 44px .touch-target box compounds into an over-tall
   column once all four footer groups stack (grid-cols-1). Tighten these
   utility links to 32px on mobile only. `.footer-link.touch-target` (0,2,0)
   outranks `.touch-target` (0,1,0), so this wins regardless of source order.
   See .claude/skills/premium-ui-design/references/principles.md (Touch targets). */
@media (max-width: 767px) {
  .footer-link.touch-target {
    min-height: 32px;
  }
}

/* Footer social icons with glassmorphism hover */
.footer-social-link {
  transition: all 200ms ease-out;
}

.footer-social-link:hover {
  background: rgba(255, 255, 255, 0.15);
  border-color: rgba(255, 255, 255, 0.25);
  color: white;
  transform: translateY(-2px);
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}

@media (prefers-reduced-motion: reduce) {
  .footer-link span span,
  .footer-social-link {
    transition: none;
  }

  .footer-social-link:hover {
    transform: none;
  }
}

/* ============================================
   MOBILE OPTIMIZATIONS
   ============================================ */

/* CRITICAL: No overflow on html/body - it breaks fixed/sticky positioning by creating new containing blocks */
html {
  width: 100%;
  height: 100%;
}

body {
  width: 100%;
  min-height: 100vh;
  margin: 0;
  padding: 0;
  position: relative;
}

/* NOTE: overflow-x: hidden on main breaks sticky positioning
   If horizontal scroll becomes an issue, apply overflow-x: clip instead
   (clip doesn't create a new scroll container) */
main {
  overflow-x: clip;
}

/* iOS smooth scrolling */
body, .overflow-y-auto {
  -webkit-overflow-scrolling: touch;
}

/* Safe area insets moved to specific components:
   - Header handles top inset
   - Bottom tabs handle bottom inset
   - Mobile menu handles its own insets
*/

/* Prevent iOS zoom on input focus (16px minimum).

   This is the whole reason layouts/application and layouts/auth can drop `user-scalable=no`: the
   viewport lock was standing in for this rule, at the cost of pinch-zoom for everyone. date/search/
   tel/url were missing from the list, and each one zooms the same way. */
@media (max-width: 767px) {
  input[type="text"],
  input[type="email"],
  input[type="password"],
  input[type="number"],
  input[type="date"],
  input[type="search"],
  input[type="tel"],
  input[type="url"],
  textarea,
  select {
    font-size: 16px;
  }
}

/* Hide native number-input spinners for clean, centered count fields
   (e.g. weekly schedule day inputs). Arrow keys still increment on focus. */
input[type="number"].no-spinner::-webkit-inner-spin-button,
input[type="number"].no-spinner::-webkit-outer-spin-button {
  -webkit-appearance: none;
  margin: 0;
}
input[type="number"].no-spinner {
  -moz-appearance: textfield;
  appearance: textfield;
}

/* Touch-friendly minimum sizes (44x44px per Apple HIG) */
.touch-target {
  min-width: 44px;
  min-height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

/* Body scroll lock when menu/modal open.

   position: fixed takes the body out of flow, which drops the document's scroll offset to 0 - so
   opening the drawer halfway down a page and dismissing it used to land the teacher back at the
   top. No `top` is declared on purpose: mobile_menu_controller sets it inline to minus the current
   offset while open, and restores the scroll on close. */
body.menu-open {
  overflow: hidden;
  position: fixed;
  width: 100%;
  height: 100%;
}

/* Mobile menu overlay.

   105 puts it over shared/_bottom_tabs, which is pinned at z-index 100 by the
   nav[class*="bottom-0"] rule further down. At 40 the tab bar sat on top of an open drawer,
   undimmed and still tappable, and painted over the drawer's own bottom 64px - which is where
   its sign-out row lives. The two drawers that use this overlay sit at 110, just above it. */
.mobile-menu-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.5);
  z-index: 105;
  opacity: 0;
  transition: opacity 0.3s ease;
  pointer-events: none;
}

.mobile-menu-overlay.active {
  opacity: 1;
  pointer-events: auto;
}

/* ============================================
   CUSTOM SELECT DROPDOWN
   ============================================ */

/* Custom select dropdown scrollbar */
[data-custom-select-target="menu"] {
  scrollbar-width: thin;
  scrollbar-color: var(--color-border, #e2e8f0) transparent;
}

[data-custom-select-target="menu"]::-webkit-scrollbar {
  width: 6px;
}

[data-custom-select-target="menu"]::-webkit-scrollbar-thumb {
  background-color: var(--color-border, #e2e8f0);
  border-radius: 3px;
}

[data-custom-select-target="menu"]::-webkit-scrollbar-track {
  background: transparent;
}

/* Mobile menu slide from right.

   The surface stays here rather than moving to a bg-* utility on the element for the reason the
   rest of this block is here: .mobile-menu is unlayered, so its `background` outranks any utility
   and the panel could not be re-skinned from the markup anyway. Both values are tokens, so a dark
   mode redefines --color-bg-primary / --color-border and this follows; --drawer-shadow is the one
   value it has to set for itself, which is why it is a variable and not a literal rgba().

   The shadow is deep and soft rather than 2px hard: against a 50% dim an 8px shadow is invisible,
   and the panel has to read as a layer above the page. The hairline does the near edge. */
:root {
  --drawer-shadow: rgba(45, 55, 72, 0.22);
}

.mobile-menu {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  width: 80%;
  max-width: 320px;
  background: var(--color-bg-primary);
  border-left: 1px solid var(--color-border);
  z-index: 110;
  transform: translateX(100%);
  transition: transform 0.32s cubic-bezier(0.32, 0.72, 0, 1);
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  box-shadow: -18px 0 44px -12px var(--drawer-shadow);
  padding-top: env(safe-area-inset-top);
  padding-bottom: env(safe-area-inset-bottom);
}

.mobile-menu.active {
  transform: translateX(0);
}

/* The panel arrives first, its contents a beat later - the stagger the premium-ui-design skill
   calls orchestrated rather than abrupt. Grouped, not per row: the drawer's sections are the
   .drawer-body children, so nth-child needs no index threaded through the partials.

   No paired "closed" rule, and `both` rather than `forwards`: the initial state must not apply
   while the drawer is parked, or every row would be invisible to a screen reader that reaches the
   drawer without it ever having been opened. */
@keyframes drawer-section-in {
  from {
    opacity: 0;
    transform: translateX(14px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

.mobile-menu.active .drawer-body > * {
  animation: drawer-section-in 0.3s cubic-bezier(0.32, 0.72, 0, 1) both;
}

.mobile-menu.active .drawer-body > *:nth-child(1) {
  animation-delay: 0.06s;
}

.mobile-menu.active .drawer-body > *:nth-child(2) {
  animation-delay: 0.11s;
}

.mobile-menu.active .drawer-body > *:nth-child(3) {
  animation-delay: 0.16s;
}

.mobile-menu.active .drawer-body > *:nth-child(n + 4) {
  animation-delay: 0.2s;
}

/* This region is the one that scrolls once a short phone runs out of room. Same treatment - and
   the same token - the custom select menus above get. */
.mobile-menu .drawer-body {
  scrollbar-width: thin;
  scrollbar-color: var(--color-border) transparent;
}

.mobile-menu .drawer-body::-webkit-scrollbar {
  width: 6px;
}

.mobile-menu .drawer-body::-webkit-scrollbar-thumb {
  background-color: var(--color-border);
  border-radius: 3px;
}

.mobile-menu .drawer-body::-webkit-scrollbar-track {
  background: transparent;
}

@media (prefers-reduced-motion: reduce) {
  .mobile-menu {
    transition: none;
  }

  .mobile-menu.active .drawer-body > * {
    animation: none;
  }
}

/* Hamburger icon */
.hamburger {
  display: flex;
  flex-direction: column;
  justify-content: space-around;
  width: 24px;
  height: 24px;
  background: transparent;
  border: none;
  cursor: pointer;
  padding: 0;
  min-width: 44px;
  min-height: 44px;
}

.hamburger-line {
  width: 24px;
  height: 2px;
  background: currentColor;
  transition: all 0.3s ease;
}

.hamburger.active .hamburger-line:nth-child(1) {
  transform: translateY(7px) rotate(45deg);
}

.hamburger.active .hamburger-line:nth-child(2) {
  opacity: 0;
}

.hamburger.active .hamburger-line:nth-child(3) {
  transform: translateY(-7px) rotate(-45deg);
}

/* ============================================
   ADMIN SIDEBAR (layouts/admin)
   ============================================ */

/* One element, two states: an off-canvas drawer below lg, a sticky rail at and above it. One
   copy of the nav markup, because the two states carry identical content and a second copy is
   exactly the drift NavigationHelper's comment exists to prevent.

   Driven by mobile_menu_controller with no JS change at all — that controller only ever adds
   and removes the string "active" on its menu/overlay/hamburger targets and never reads a
   class name, so the direction a drawer travels is entirely decided here. .mobile-menu above
   is the same mechanism aimed at the opposite edge.

   Unlayered, like .mobile-menu: nothing here is a default a utility should be able to
   override. Contrast .admin-table below, which is in @layer components for the opposite
   reason. */
.admin-sidebar {
  position: fixed;
  top: 0;
  left: 0;
  bottom: 0;
  z-index: 110;
  transform: translateX(-100%);
  transition: transform 0.3s ease;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  box-shadow: 2px 0 8px rgba(0, 0, 0, 0.1);
  padding-top: env(safe-area-inset-top);
  padding-bottom: env(safe-area-inset-bottom);
}

.admin-sidebar.active {
  transform: translateX(0);
}

/* At lg the rail is permanent: back in flow as a flex item, full height, sticky so a long
   report scrolls under it. Resetting transform is the load-bearing line — with no JS ever
   having run, the drawer is parked at -100% and would otherwise be invisible on a desktop.
   transition: none stops it animating when a window is dragged across the breakpoint. */
@media (min-width: 1024px) {
  .admin-sidebar {
    position: sticky;
    bottom: auto;
    height: 100vh;
    height: 100dvh;
    transform: none;
    transition: none;
    box-shadow: none;
    padding-top: 0;
    padding-bottom: 0;
  }
}

@media (prefers-reduced-motion: reduce) {
  .admin-sidebar {
    transition: none;
  }
}

/* .hamburger puts height: 24px and min-height: 44px on the same flex box, so min-height wins and
   the icon box IS the whole 44px touch target — space-around then spreads the three bars 15px
   apart (measured: tops at 12/27/42) and it reads as a loose stack rather than a hamburger.
   Centring them with a fixed gap keeps the 44px target and gives back a 16px glyph.

   5px is not arbitrary: it puts the bar centres 7px apart, which is exactly the translateY the
   .hamburger.active X-morph above already uses, so the close animation still lands true.

   Scoped to /admin rather than fixed in .hamburger itself — the teacher navbar renders the same
   button and its spacing is not this change's to alter. */
.admin-hamburger {
  justify-content: center;
  gap: 5px;
}

/* ============================================
   ADMIN TABLES (/admin)
   ============================================ */

/* @layer components, NOT unlayered like every block above it. Everything here is a default a
   cell has to be able to override — text-right, font-mono, w-1/3, the muted zero — and
   Tailwind emits its utilities into @layer utilities, which the built tailwind.css declares
   *after* `@layer components;`. So class="text-right" on a <td> wins with no `!` anywhere.
   Unlayered it would silently lose instead, because an unlayered rule outranks any layered one
   regardless of specificity.

   Ten tables each retyped the same six utility strings, and they had already drifted:
   _funnel_table put border-b on its rows where every other table used border-t.

   Deliberately not set: margin-top (Admin::SectionComponent's body wrapper owns it) and
   vertical-align (left at the browser default so schools/index's align-top still behaves). */
@layer components {
  .admin-table {
    width: 100%;
    font-size: 0.875rem;
    line-height: 1.25rem;
  }

  .admin-table th,
  .admin-table td {
    text-align: left;
    padding-block: 0.5rem;
  }

  .admin-table thead th {
    padding-top: 0;
    font-weight: 500;
    white-space: nowrap;
    color: var(--color-text-muted);
  }

  .admin-table tbody tr {
    border-top: 1px solid var(--color-border);
  }

  .admin-table td {
    color: var(--color-text-secondary);
  }
}

/* Remove tap highlight on mobile */
@media (hover: none) {
  * {
    -webkit-tap-highlight-color: transparent;
  }
}

/* ============================================
   TOUCH ACTIVE STATES
   ============================================ */

/* Touch feedback for interactive elements */
@media (hover: none) {
  /* Cards and clickable containers */
  a[class*="bg-white"]:active,
  button:not(:disabled):active,
  [role="button"]:active {
    transform: scale(0.98);
    opacity: 0.9;
    transition: transform 0.1s ease, opacity 0.1s ease;
  }

  /* Bottom tabs already have active state */
  .bottom-tab:active {
    transform: scale(0.95);
  }

  /* FAB (Floating Action Button) */
  a[class*="rounded-full"]:active {
    transform: scale(0.95);
  }

  /* Prevent double-transform on elements with existing active states */
  .hamburger:active {
    /* Keep existing active states */
  }
}

/* ============================================
   HORIZONTAL SCROLL FADE INDICATORS
   ============================================ */

/* Wrapper for scroll container with fade indicators */
.scroll-fade-wrapper {
  position: relative;
}

/* Left fade gradient */
.scroll-fade-left {
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 2rem;
  background: linear-gradient(to right, white, transparent);
  pointer-events: none;
  z-index: 10;
  transition: opacity 0.2s ease;
}

/* Right fade gradient */
.scroll-fade-right {
  position: absolute;
  right: 0;
  top: 0;
  bottom: 0;
  width: 2rem;
  background: linear-gradient(to left, white, transparent);
  pointer-events: none;
  z-index: 10;
  transition: opacity 0.2s ease;
}


/* ============================================
   BOTTOM TAB NAVIGATION
   ============================================ */

/* Bottom nav container - MUST use fixed positioning */
nav[class*="bottom-0"] {
  position: fixed !important;
  bottom: 0 !important;
  left: 0 !important;
  right: 0 !important;
  z-index: 100 !important;
}

/* ── Video lightbox (/video-uputstva) ───────────────────────────────────────────────────────────
   The reason this is CSS and not utility classes: the dialog's width is a three-way min() and one
   of the terms is derived from the viewport HEIGHT, which is what keeps a 16:9 player and a block
   of details inside the screen together.

     90vw      — a phone, and breathing room on any width
     1120px    — the cap. Past this a walkthrough of a UI is just upscaled; YouTube's own watch
                 page tops out around here too, and its default embed is 560px, so every laptop
                 clears that comfortably.
     the calc  — the width at which the player's derived height still leaves room for the details
                 block. Without this term a short laptop viewport gets a player taller than the
                 screen and the details are simply unreachable.

   Desktop only, in effect: the "Bottom sheet modal on mobile" rule near the top of this file
   makes EVERY dialog[open] full-width and bottom-pinned below 768px, and `dialog[open]` (0,1,1)
   outranks `.video-dialog` (0,1,0), so it wins the width there. That is the right phone answer —
   a full-bleed 16:9 sheet beats a 90vw centred box — and it is why measuring the player at 390px
   viewport reports 390 and not 351. Do not "fix" that by adding specificity here.

   --video-chrome is an ESTIMATE of the details block, so the max-height + overflow below are what
   make it safe: if a long summary outgrows the estimate, the details scroll and the player keeps
   its size, rather than the player being squeezed to fit prose. flex-shrink: 0 on the stage is
   what enforces that — a flex item with an aspect-ratio will otherwise give up height first. */
.video-dialog {
  --video-chrome: 11rem;

  flex-direction: column;
  width: min(90vw, 1120px, calc((90dvh - var(--video-chrome)) * 16 / 9));
  max-width: none;
  max-height: 90dvh;
}

/* display goes on [open] and NOT on .video-dialog, which is the whole trick. The UA stylesheet
   hides a closed dialog with `dialog:not([open]) { display: none }`, and any author `display`
   beats it — so declaring `display: flex` unconditionally renders the closed dialog inline in the
   page, with its details block and its "Gledaj na YouTubeu" link sitting in the document. Caught
   by the link count in spec/system/how_tos_spec.rb, which found four where three were expected. */
.video-dialog[open] {
  display: flex;
}

.video-dialog__stage {
  flex: 0 0 auto;
  width: 100%;
  aspect-ratio: 16 / 9;
}

/* min-height: 0 is load bearing, the same way it is in .mobile-menu's scroll region: a flex item
   defaults to min-height: auto and refuses to shrink below its content, so without it a long
   summary pushes the dialog past max-height instead of scrolling inside it. */
.video-dialog__details {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
}

.bottom-tab {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.125rem;
  padding: 0.5rem;
  min-width: 60px;
  min-height: 48px;
  color: var(--color-text-muted, #9ca3af);
  transition: color 0.2s ease, transform 0.1s ease;
  text-decoration: none;
  border: none;
  background: transparent;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

.bottom-tab.active {
  color: var(--color-primary, #3C8D93);
}

.bottom-tab.active svg {
  transform: scale(1.1);
}

.bottom-tab:active {
  transform: scale(0.95);
}

/* ============================================
   FLATPICKR TAILWIND STYLING
   ============================================ */

/* Calendar container */
.flatpickr-calendar {
  background: white;
  box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1);
  border-radius: 0.75rem;
  border: 1px solid var(--color-border, #e5e7eb);
  margin-top: 0.5rem;
}

.flatpickr-calendar.arrowTop:before,
.flatpickr-calendar.arrowTop:after {
  display: none;
}

/* Month navigation */
.flatpickr-months {
  background: white;
  border-radius: 0.75rem 0.75rem 0 0;
  padding: 0.75rem 0.5rem;
}

.flatpickr-month {
  color: var(--color-text-primary, #1f2937);
}

.flatpickr-current-month {
  font-size: 1rem;
  font-weight: 600;
  color: var(--color-text-primary, #1f2937);
}

.flatpickr-monthDropdown-months {
  appearance: none;
  background: white;
  border: 1px solid var(--color-border, #e5e7eb);
  border-radius: 0.5rem;
  padding: 0.25rem 0.5rem;
  font-weight: 600;
  color: var(--color-text-primary, #1f2937);
}

.flatpickr-monthDropdown-months:hover {
  background: var(--color-bg-secondary, #f9fafb);
}

.numInputWrapper:hover {
  background: var(--color-bg-secondary, #f9fafb);
}

/* Navigation arrows */
.flatpickr-prev-month,
.flatpickr-next-month {
  padding: 0.5rem;
  color: var(--color-text-secondary, #6b7280);
  transition: all 0.15s ease;
  border-radius: 0.5rem;
}

.flatpickr-prev-month:hover,
.flatpickr-next-month:hover {
  color: var(--color-primary, #f5a623);
  background: var(--color-bg-secondary, #f9fafb);
}

/* Days container */
.flatpickr-days {
  background: white;
  border-radius: 0 0 0.75rem 0.75rem;
}

.flatpickr-day {
  color: var(--color-text-primary, #1f2937);
  border: none;
  border-radius: 0.5rem;
  transition: all 0.15s ease;
}

.flatpickr-day:hover {
  background: var(--color-bg-secondary, #f9fafb);
  border: none;
}

.flatpickr-day.today {
  border-color: var(--color-primary, #f5a623);
  background: var(--color-primary-light, #fef3e2);
  color: var(--color-primary, #f5a623);
  font-weight: 600;
}

.flatpickr-day.selected,
.flatpickr-day.selected:hover {
  background: var(--color-accent, #f5a623);
  border-color: var(--color-accent, #f5a623);
  color: white;
  font-weight: 600;
}

.flatpickr-day.prevMonthDay,
.flatpickr-day.nextMonthDay {
  color: var(--color-text-muted, #9ca3af);
}

.flatpickr-day.disabled {
  color: var(--color-text-muted, #9ca3af);
  cursor: not-allowed;
  opacity: 0.4;
}

/* Weekdays */
.flatpickr-weekdays {
  background: white;
}

.flatpickr-weekday {
  color: var(--color-text-secondary, #6b7280);
  font-weight: 600;
  font-size: 0.875rem;
}

/* Input styling when flatpickr is active */
.flatpickr-input.active {
  border-color: var(--color-border-focus, #f5a623);
  box-shadow: 0 0 0 3px rgba(245, 166, 35, 0.1);
}

/* Mobile responsiveness */
@media (max-width: 767px) {
  .flatpickr-calendar {
    font-size: 16px;
  }

  .flatpickr-day {
    min-width: 44px;
    min-height: 44px;
  }
}

/* ============================================
   TRIX EDITOR — the card in lessons/_form owns border, background and focus ring;
   the toolbar is the lower half of its white header strip, the editor is bare.
   ============================================ */

/* Toolbar: lower half of the card's white header strip (the tabs sit above it). Its bottom border is
   the card's one divider — chrome above, text below. Trix's own injected style makes it display: block,
   so the border spans the width. */
trix-toolbar {
  border: none !important;
  border-bottom: 1px solid #e2e8f0 !important;
  background: #fff !important;
  padding: 0 8px 8px !important;
}

/* Button row layout */
trix-toolbar .trix-button-row {
  gap: 8px;
  justify-content: flex-start !important;
  flex-wrap: nowrap !important;
  overflow: visible !important;
}

/* Button groups - pill design */
trix-toolbar .trix-button-group {
  border: none !important;
  background: #f1f5f9 !important;
  border-radius: 8px !important;
  padding: 4px !important;
  gap: 2px;
  display: inline-flex !important;
  margin: 0 !important;
  box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.05) !important;
}

trix-toolbar .trix-button-group:not(:first-child) {
  margin-left: 0 !important;
}

/* Individual buttons */
trix-toolbar .trix-button {
  background: transparent !important;
  border: none !important;
  border-radius: 6px !important;
  width: 32px !important;
  height: 32px !important;
  padding: 0 !important;
  margin: 0 !important;
  display: inline-flex !important;
  align-items: center !important;
  justify-content: center !important;
  color: #64748b !important;
  transition: all 0.15s ease !important;
  position: relative !important;
  float: none !important;
}

trix-toolbar .trix-button:hover {
  background: #e2e8f0 !important;
  color: #334155 !important;
}

trix-toolbar .trix-button.trix-active {
  background: #3c8d93 !important;
  color: white !important;
  box-shadow: 0 1px 3px rgba(60, 141, 147, 0.3) !important;
}

trix-toolbar .trix-button:disabled {
  opacity: 0.4 !important;
  cursor: not-allowed !important;
}

/* Button icons - centered */
trix-toolbar .trix-button::before {
  background-size: 16px 16px !important;
  width: 16px !important;
  height: 16px !important;
  opacity: 0.8;
  position: absolute !important;
  top: 50% !important;
  left: 50% !important;
  transform: translate(-50%, -50%) !important;
}

trix-toolbar .trix-button:hover::before,
trix-toolbar .trix-button.trix-active::before {
  opacity: 1;
}

/* Editor area: bare — the card carries frame, background and the focus ring (focus-within); a ring
   drawn here would double up and be clipped by the card's overflow-hidden. */
trix-editor {
  background: transparent !important;
  border: none !important;
  border-radius: 0 !important;
  padding: 12px 16px !important;
  min-height: 100px !important;
  font-size: 0.9375rem !important;
  line-height: 1.6 !important;
  color: #1e293b !important;
  outline: none !important;
}

/* Same token the <input> placeholders in lessons/_form use, so the three hints in the modal are one
   tone rather than two that drift. */
trix-editor:empty::before {
  color: var(--color-text-placeholder, #a3aebf) !important;
}

/* Content styling - lists with visible markers */
trix-editor ul {
  list-style-type: disc !important;
  margin: 0.5em 0 !important;
  padding-left: 1.75em !important;
}

trix-editor ol {
  list-style-type: decimal !important;
  margin: 0.5em 0 !important;
  padding-left: 1.75em !important;
}

trix-editor li {
  margin: 0.25em 0 !important;
  display: list-item !important;
}

trix-editor strong {
  font-weight: 600 !important;
  color: #0f172a !important;
}

/* ============================================
   HIDE UNWANTED TRIX BUTTONS
   ============================================ */

/* Hide: strikethrough, link, heading, quote, code, attach, indent/outdent, undo/redo */
trix-toolbar [data-trix-attribute="strike"],
trix-toolbar [data-trix-attribute="href"],
trix-toolbar [data-trix-attribute="heading1"],
trix-toolbar [data-trix-attribute="quote"],
trix-toolbar [data-trix-attribute="code"],
trix-toolbar [data-trix-action="attachFiles"],
trix-toolbar [data-trix-action="increaseNestingLevel"],
trix-toolbar [data-trix-action="decreaseNestingLevel"],
trix-toolbar [data-trix-action="undo"],
trix-toolbar [data-trix-action="redo"] {
  display: none !important;
}

/* Hide spacer and dialogs */
trix-toolbar .trix-button-group-spacer,
trix-toolbar .trix-dialogs {
  display: none !important;
}

/* Hide button groups that have no visible children */
trix-toolbar .trix-button-group:not(:has(.trix-button[data-trix-attribute="bold"])):not(:has(.trix-button[data-trix-attribute="bullet"])) {
  display: none !important;
}

/* ============================================
   SUCCESS MODAL - ANIMATIONS
   ============================================ */

/* Checkmark pop animation */
.checkmark-pop {
  animation: checkmark-pop 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275) forwards;
}

@keyframes checkmark-pop {
  0% {
    transform: scale(0);
    opacity: 0;
  }
  50% {
    transform: scale(1.2);
  }
  100% {
    transform: scale(1);
    opacity: 1;
  }
}

/* Respect reduced motion preference */
@media (prefers-reduced-motion: reduce) {
  .checkmark-pop {
    animation: none;
    transform: scale(1);
    opacity: 1;
  }

  /* Slot preview - static highlight instead of animations */
  .slot-preview-target {
    animation: none;
    transform: none;
  }

  .slot-preview-content {
    animation: none;
  }

  .slot-preview-content .preview-icon {
    animation: none;
  }

  .lesson-item-dragging {
    transform: none;
  }

  .lesson-item-no-slots::after,
  .lesson-item-no-slots::before {
    animation: none;
  }

  .collapsed-preview-indicator {
    animation: none;
  }

  .slot-beacon {
    animation: none;
  }
}

/* ============================================
   WIZARD STEP TRANSITIONS
   ============================================ */

/* Step enter animation - slide in from right */
.wizard-step-enter {
  animation: wizard-step-enter 0.3s ease-out forwards;
}

@keyframes wizard-step-enter {
  from {
    opacity: 0;
    transform: translateX(20px);
  }
  to {
    opacity: 1;
    transform: translateX(0);
  }
}

/* Step exit animation - fade out to left */
.wizard-step-exit {
  animation: wizard-step-exit 0.2s ease-in forwards;
}

@keyframes wizard-step-exit {
  from {
    opacity: 1;
    transform: translateX(0);
  }
  to {
    opacity: 0;
    transform: translateX(-20px);
  }
}

/* Progress bar pulse when step advances */
.progress-pulse {
  animation: progress-pulse 0.6s ease-out;
}

@keyframes progress-pulse {
  0%, 100% {
    box-shadow: 0 0 0 0 rgba(60, 141, 147, 0);
  }
  50% {
    box-shadow: 0 0 0 8px rgba(60, 141, 147, 0.3);
  }
}

/* Lesson slot success animation */
.slot-success {
  animation: slot-success 0.4s ease-out;
}

@keyframes slot-success {
  0% {
    background-color: rgba(60, 141, 147, 0.3);
    transform: scale(1.02);
  }
  100% {
    background-color: transparent;
    transform: scale(1);
  }
}

/* Auto-assign success burst */
.auto-assign-success {
  animation: auto-assign-burst 0.5s ease-out;
}

@keyframes auto-assign-burst {
  0% {
    box-shadow: 0 0 0 0 rgba(60, 141, 147, 0.4);
  }
  50% {
    box-shadow: 0 0 0 15px rgba(60, 141, 147, 0);
  }
  100% {
    box-shadow: 0 0 0 0 rgba(60, 141, 147, 0);
  }
}

/* Respect reduced motion preference for wizard */
@media (prefers-reduced-motion: reduce) {
  .wizard-step-enter,
  .wizard-step-exit,
  .progress-pulse,
  .slot-success,
  .auto-assign-success {
    animation: none;
  }
}

/* Non-working days calendar: day modal frame */
turbo-frame#nwd-day-frame {
  display: block;
}

turbo-frame#nwd-day-frame[busy] {
  opacity: 0.4;
  pointer-events: none;
  transition: opacity 150ms ease;
}

/* ============================================
   SCHOOL DIRECTORY VIEWS (/skole/:opcina)
   One list of schools, rendered as rows or cards.
   ============================================ */

/* One <ul>, so the page holds a single copy of the school data — it exists to be
   indexed, and a second panel would duplicate every name. This was two views once,
   switched by a Stimulus controller that only flipped [data-view]; the card view is
   gone, but the attribute stays because every rule below is keyed to it and
   .is-filtered-out needs it present. Everything here is CSS. */
.school-directory {
  list-style: none;
  margin: 0;
  padding: 0;
}

/* Both views are the app's own patterns: list mirrors the school rows in
   teaching_subjects/all.html.erb, cards mirror schools/index.html.erb. Neither
   carries the click affordances of the originals — public school entries are not
   links (per-school pages are rejected in seo-improvement.md 2.7), so a lift or a
   hover arrow would promise navigation that does not exist. The border tint on
   hover stays: that reads as row highlighting, not a click target. */
.school-directory > li {
  position: relative;
  overflow: hidden;
  background: var(--color-bg-primary);
  border: 1px solid var(--color-border);
  transition: border-color 200ms ease, box-shadow 200ms ease;
}

.school-directory > li:hover {
  border-color: color-mix(in srgb, var(--color-primary) 40%, transparent);
}

.school-name {
  min-width: 0;
}

/* Vertical in both views; only where the block sits differs. Anything that varies
   between list and cards belongs in the per-view rules below, never in a Tailwind
   utility on the element: every layout loads tailwind and then application, so at
   equal specificity a utility silently loses to a rule in this file. */
.school-contact {
  display: flex;
  flex-direction: column;
  gap: 0.375rem;
}

/* Filtered out by school_filter_controller. The [data-view] qualifier is load
   bearing: the per-view rules below set `display` on the same element at
   (0,2,1), so Tailwind's own .hidden (0,1,0) would lose to them. */
.school-directory[data-view] > li.is-filtered-out {
  display: none;
}

/* The search box lines up with the card grid: it starts at the first card's left
   edge and ends at the second card's right edge, gap included, in both views.
   Derived rather than eyeballed — with n columns and gap g, the grid is
   n·c + (n-1)·g = 100%, so two columns plus one gap is 2c + g.
   n = 2 -> exactly 100%. n = 3 -> 2/3 - g/3, and g is 0.75rem. */
.school-search {
  flex: none;
  width: 100%;
}

@media (min-width: 1024px) {
  .school-search {
    width: calc(200% / 3 - 0.25rem);
  }
}

/* ---- list (default, and what renders without JS) ---- */

/* A stack of row cards rather than one divided panel: icon left, name and its
   metadata to the right on two lines. */
.school-directory[data-view="list"] {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

/* Wraps so the contact block can be a right-hand column on a wide row and a block
   under the body on a narrow one, from one element. align-items stays center: with
   a three-line contact column the row is 68px, and centring keeps the icon nearer
   the name than flex-start would while leaving the two-line rows of a school with
   no contact details exactly as they were. */
.school-directory[data-view="list"] > li {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 1rem;
  padding: 0.875rem 1.5rem;
  border-radius: 1rem;
  box-shadow: var(--shadow-soft);
}

.school-directory[data-view="list"] > li:hover {
  box-shadow: 0 4px 16px color-mix(in srgb, var(--color-primary) 8%, transparent);
}

/* City and address on one line, divided — the same shape as the location and
   subject chips on the authenticated rows. */
.school-directory[data-view="list"] .school-meta {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.25rem 0.75rem;
  margin-top: 0.125rem;
}

.school-directory[data-view="list"] .school-address::before {
  content: "";
  display: inline-block;
  width: 1px;
  height: 0.75rem;
  margin-right: 0.75rem;
  vertical-align: -0.0625rem;
  background: var(--color-border);
}

/* The accent bar is the cards' differentiator; the row pattern it copies has
   none. Decoration only, so hiding it duplicates no content. */
.school-directory[data-view="list"] .school-accent {
  display: none;
}

/* flex-basis 0, not auto. Line breaking measures each item's hypothetical size, so
   an auto basis would measure the school name at max-content — 310px for the
   longest — and drop the body onto its own line on a phone, orphaning the 48px icon
   above it. A zero basis keeps icon and body together at any width, and grow then
   hands the body whatever the contacts leave. */
.school-directory[data-view="list"] .school-body {
  flex: 1 1 0;
}

/* Its own wrapped line below lg. flex-basis is a percentage of the container's
   content box and box-sizing is border-box, so the indent lives inside the 100% and
   cannot overflow; the row's own 1rem gap separates the two lines. */
/* Full width from the card's own edge, not indented under the name. The contacts are
   the row's second line rather than a continuation of the first, and the rule above
   them says so. It spans the card's content box: stopping short left the line ending
   at nothing, which read as a mistake rather than as a deliberate inset. ::before and
   not border-top, because the divider belongs to the gap between the two lines and a
   border would sit inside the contact block's own box. */
.school-directory[data-view="list"] .school-contact {
  flex: 0 0 100%;
  padding-left: 0;
}

.school-directory[data-view="list"] .school-contact::before {
  content: "";
  width: 100%;
  height: 1px;
  margin-bottom: 0.25rem;
  background: var(--color-border);
}

/* Side by side only once the row can hold both, which is 1024 and not 768. At 1024
   the row's content box is 910px, so the name keeps 526px and fits on one line; at
   768 it would keep 290px and wrap, which reads worse than the stacked block.

   A fixed basis rather than shrink-to-fit. Sized to content, every row's column
   started at a different x — a school with a short address next to one with three
   phone numbers made the whole right side ragged. 20rem holds the widest line in
   the data (37 characters, the longest e-mail and the only three-number phone, both
   ~280px at 14px) plus the icon and its gap, with room to spare. No margin-left:auto
   — the body's flex-grow already consumes the free space, and auto margins resolve
   after flexible lengths, so it would be a no-op. */
@media (min-width: 1024px) {
  .school-directory[data-view="list"] .school-contact {
    flex: 0 0 20rem;
    padding-left: 0;
  }

  /* The rule divides a line from the line above it. Once the contacts sit beside the
     body rather than under it there is no line above, only a neighbour. */
  .school-directory[data-view="list"] .school-contact::before {
    display: none;
  }
}

@media (prefers-reduced-motion: reduce) {
  .school-directory > li {
    transition: none;
  }
}

/* Onboarding guide: answering a click that lands on the dimmed page.
   The shades swallow clicks on purpose — dimmed means "not now" — but a click that produces
   absolutely nothing reads as a frozen page rather than as a rule. One pulse on the ring and the
   popover says where the live control is, without dismissing anything: waving off a guide is a
   decision, and it has its own button. */
.onboarding-nudge {
  animation: onboarding-nudge 0.45s ease-out;
}

@keyframes onboarding-nudge {
  0%   { transform: scale(1); }
  35%  { transform: scale(1.03); }
  70%  { transform: scale(0.995); }
  100% { transform: scale(1); }
}

@media (prefers-reduced-motion: reduce) {
  .onboarding-nudge {
    animation: none;
  }
}
