@layer utilities {
      .line-clamp-2 {
        display: -webkit-box;
        -webkit-line-clamp: 2;
        -webkit-box-orient: vertical;
        overflow: hidden;
      }
    .line-clamp-3 {
      display: -webkit-box;
      -webkit-line-clamp: 3;
      -webkit-box-orient: vertical;
      overflow: hidden;
    }
  }

  :root {
    --base-font-size: 16px;
    --body-line-height: 1.5;
    --body-letter-spacing: normal;
    --focus-ring-color: #0ea5e9;
    --app-font-stack: 'Space Grotesk', 'ui-sans-serif', 'system-ui';
  }

  html {
    font-size: var(--base-font-size);
  }

  body {
    line-height: var(--body-line-height);
    letter-spacing: var(--body-letter-spacing);
    -webkit-text-size-adjust: 100%;
  }

  .font-sans {
    font-family: var(--app-font-stack) !important;
  }

  /* OpenDyslexic via fontsource (the old antijingoist/open-dyslexic@master GitHub path
     404s — the dyslexia font option silently failed to load before this). */
  @font-face {
    font-family: 'OpenDyslexic';
    src:
      url('https://cdn.jsdelivr.net/npm/@fontsource/opendyslexic@5/files/opendyslexic-latin-400-normal.woff2') format('woff2'),
      url('https://cdn.jsdelivr.net/npm/@fontsource/opendyslexic@5/files/opendyslexic-latin-400-normal.woff') format('woff');
    font-weight: 400;
    font-style: normal;
    font-display: swap;
  }

  @font-face {
    font-family: 'OpenDyslexic';
    src:
      url('https://cdn.jsdelivr.net/npm/@fontsource/opendyslexic@5/files/opendyslexic-latin-700-normal.woff2') format('woff2'),
      url('https://cdn.jsdelivr.net/npm/@fontsource/opendyslexic@5/files/opendyslexic-latin-700-normal.woff') format('woff');
    font-weight: 700;
    font-style: normal;
    font-display: swap;
  }

  /* Screen-reader-only content. Defined explicitly (not just via Tailwind's CDN JIT)
     because the live-region elements are created with the DOM API, not in markup. */
  .sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
  }

  .high-contrast {
    filter: contrast(1.25) saturate(1.1);
  }

  /* Always show a visible focus indicator for keyboard users, regardless of the optional
     "focus ring always on" setting. 3px outline clears the 3:1 non-text contrast minimum. */
  :focus-visible {
    outline: 3px solid var(--focus-ring-color);
    outline-offset: 2px;
  }

  .focus-ring :focus {
    outline: 3px solid var(--focus-ring-color);
    outline-offset: 2px;
  }

  /* Windows High Contrast / forced-colors: defer to the user's system palette and keep
     focus, the switch highlight, and modal edges visible against it. */
  @media (forced-colors: active) {
    *:focus-visible,
    .focus-ring :focus {
      outline: 3px solid CanvasText;
      outline-offset: 2px;
    }
    .switch-highlight {
      outline: 3px solid Highlight;
      box-shadow: none;
    }
    .high-contrast {
      filter: none;
    }
    dialog.app-dialog > [data-modal] {
      border: 1px solid CanvasText;
    }
  }

  .switch-highlight {
    outline: 3px solid #f59e0b;
    outline-offset: 3px;
    box-shadow: 0 0 0 3px rgba(245, 158, 11, 0.25);
  }

  /* Large-target mode: 44px minimum controls (WCAG 2.5.5), 48px on small screens.
     min-width is kept off inline <a> so text links in prose are not distorted; they
     still get the taller hit area. */
  .large-targets button,
  .large-targets [role="button"],
  .large-targets [role="menuitem"],
  .large-targets input,
  .large-targets textarea,
  .large-targets select {
    min-height: 44px;
    min-width: 44px;
  }

  .large-targets a[href] {
    min-height: 44px;
    display: inline-flex;
    align-items: center;
  }

  @media (max-width: 640px) {
    .large-targets button,
    .large-targets [role="button"],
    .large-targets [role="menuitem"] {
      min-height: 48px;
      min-width: 48px;
    }
  }

  .reduce-motion *,
  .reduce-motion *::before,
  .reduce-motion *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }

  /* Belt-and-suspenders for the transform-heavy flourishes: remove them outright (the rule
     above only near-zeroes the duration) and drop the heart-burst overlay entirely so there
     is no sub-frame flash for vestibular-sensitive users. */
  .reduce-motion [class*="animate-"] {
    animation: none !important;
  }
  .reduce-motion .heart-animation {
    display: none !important;
  }

  .reduce-transparency .a11y-overlay {
    backdrop-filter: none !important;
    background-color: rgba(15, 23, 42, 0.85) !important;
  }

  /* Native <dialog> modals (the Dialog wrapper). The element is a transparent, centered
     shell; its child panel keeps the Tailwind styling. The backdrop is the dimming layer. */
  dialog.app-dialog {
    padding: 0;
    border: 0;
    background: transparent;
    max-width: 100vw;
    max-height: 100dvh;
    max-height: 100vh;
    color: inherit;
    overflow: visible;
  }

  dialog.app-dialog::backdrop {
    background: rgba(0, 0, 0, 0.5);
    backdrop-filter: blur(2px);
  }

  dialog.app-dialog[open] {
    animation: scale-in 0.18s ease-out;
  }

  .reduce-transparency dialog.app-dialog::backdrop {
    backdrop-filter: none;
    background: rgba(15, 23, 42, 0.9);
  }

  .reduce-motion dialog.app-dialog[open] {
    animation: none;
  }

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

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

    @keyframes heart-burst {
      0% {
        transform: scale(0.3) translateY(0);
        opacity: 0;
      }
      50% {
        transform: scale(1.5) translateY(-10px);
        opacity: 1;
      }
      100% {
        transform: scale(1) translateY(-20px);
        opacity: 0;
      }
    }

    .heart-animation {
      animation: heart-burst 0.6s ease-out;
      pointer-events: none;
    }

    @keyframes slide-in-right {
      from {
        transform: translateX(100%);
        opacity: 0;
      }
      to {
        transform: translateX(0);
        opacity: 1;
      }
    }

    .animate-slide-in-right {
      animation: slide-in-right 0.3s ease-out;
    }

    /* iOS Safari viewport fix */
    html, body {
      height: 100%;
      height: 100dvh;
      overflow: hidden;
      position: fixed;
      width: 100%;
      -webkit-overflow-scrolling: touch;
    }

    #root {
      height: 100%;
      height: 100dvh;
      overflow: hidden;
    }

    /* Safe height utility */
    .h-safe-screen {
      height: 100vh;
      height: 100dvh;
    }

    .min-h-safe-screen {
      min-height: 100vh;
      min-height: 100dvh;
    }

    /* Safe area inset for notched devices */
    .pb-safe {
      padding-bottom: env(safe-area-inset-bottom, 0px);
    }
