/* playful.css - Playful Design System
 * Loaded before user styles.css. Provides Playful design tokens.
 */

/* Declare the cascade-layer order up front so it matches Tailwind v4's own
 * `@layer theme, base, components, utilities`. Generated apps load Tailwind v4
 * (@tailwindcss/browser@4), which puts every utility in `@layer utilities`.
 * The base resets below therefore live in `@layer base`, so app-authored
 * Tailwind spacing utilities (p-*, m-*, …) still win. An UNLAYERED reset would
 * beat every layered utility regardless of specificity and silently zero them
 * out — see the padding/margin collapse this replaced. */
@layer theme, base, components, utilities;

/* ============================================
 * 0. Base reset
 * ============================================
 * 83/99 recent prod projects re-wrote this verbatim. Centralizing here lets
 * apps skip the boilerplate without sacrificing Tailwind compatibility
 * (Tailwind Preflight does its own reset, but it does NOT zero margin/padding
 * on every element — apps still had to).
 */
@layer base {
  *,
  *::before,
  *::after {
    box-sizing: border-box;
    margin: 0;
    padding: 0;
    -webkit-tap-highlight-color: transparent;
  }
}

/* ============================================
 * 1. Typography
 * ============================================
 * Inter is loaded via Google Fonts in the preamble.
 */
:root {
  /* Safe-area insets — non-zero when viewport-fit=cover is active (iOS Safari).
   * Use these to offset fixed/absolute elements away from the notch, home
   * indicator, or address-bar overlap zone. */
  --playful-safe-top: env(safe-area-inset-top, 0px);
  --playful-safe-right: env(safe-area-inset-right, 0px);
  --playful-safe-bottom: env(safe-area-inset-bottom, 0px);
  --playful-safe-left: env(safe-area-inset-left, 0px);

  /* Font stacks */
  --playful-font-sans: 'Inter', system-ui, -apple-system, sans-serif;
  --playful-font-mono: ui-monospace, 'Cascadia Code', 'Fira Code', monospace;

  /* Type scale (~1.25 ratio) */
  --playful-text-xs: 0.75rem; /* 12px */
  --playful-text-sm: 0.875rem; /* 14px */
  --playful-text-base: 1rem; /* 16px */
  --playful-text-lg: 1.125rem; /* 18px */
  --playful-text-xl: 1.25rem; /* 20px */
  --playful-text-2xl: 1.5rem; /* 24px */
  --playful-text-3xl: 1.875rem; /* 30px */
  --playful-text-4xl: 2.25rem; /* 36px */
  --playful-text-5xl: 3rem; /* 48px */

  /* Default size for text-like form controls (<input>, <textarea>, <select>).
   * 16px is the iOS Safari threshold below which a focused input triggers
   * viewport auto-zoom that does not restore on blur — see the input floor
   * at the bottom of this file and Tailwind's `text-input` utility below. */
  --playful-text-input: 1rem; /* 16px */

  /* Line heights */
  --playful-leading-none: 1;
  --playful-leading-tight: 1.25;
  --playful-leading-snug: 1.375;
  --playful-leading-normal: 1.5;
  --playful-leading-relaxed: 1.625;

  /* Font weights */
  --playful-font-light: 300;
  --playful-font-normal: 400;
  --playful-font-medium: 500;
  --playful-font-semibold: 600;
  --playful-font-bold: 700;
}

/* ============================================
 * 1b. Tailwind @theme integration
 * ============================================
 * Tailwind v4 (@tailwindcss/browser@4) generates utility classes from the
 * tokens declared in `@theme`. Re-pointing the built-in `--text-*` keys at
 * our `--playful-text-*` variables means any future change to the type
 * scale (here in `:root`) flows through `text-xs`, `text-sm`, … without
 * touching individual apps.
 *
 * The custom `--text-input` token generates a `text-input` utility class
 * the agent should reach for on `<input>`, `<textarea>`, and `<select>`.
 * It is the platform default for text-like form controls and is sized at
 * 16px to avoid iOS Safari's focused-input auto-zoom behaviour.
 */
@theme {
  /* Type scale → text-xs … text-5xl utilities. */
  --text-xs: var(--playful-text-xs);
  --text-sm: var(--playful-text-sm);
  --text-base: var(--playful-text-base);
  --text-lg: var(--playful-text-lg);
  --text-xl: var(--playful-text-xl);
  --text-2xl: var(--playful-text-2xl);
  --text-3xl: var(--playful-text-3xl);
  --text-4xl: var(--playful-text-4xl);
  --text-5xl: var(--playful-text-5xl);

  /* Default font-size for text-like form controls. 16px on purpose — iOS
   * Safari auto-zooms focused inputs below 16px and never restores. */
  --text-input: var(--playful-text-input);

  /* Shadow scale → shadow-xs … shadow-2xl utilities, backed by the
   * playful shadow tokens so a future tweak (softer ambient, deeper
   * elevation, …) flows through every app. */
  --shadow-xs: var(--playful-shadow-xs);
  --shadow-sm: var(--playful-shadow-sm);
  --shadow-md: var(--playful-shadow-md);
  --shadow-lg: var(--playful-shadow-lg);
  --shadow-xl: var(--playful-shadow-xl);
  --shadow-2xl: var(--playful-shadow-2xl);

  /* Hue-neutral surfaces → bg-overlay / bg-glass / bg-scrim utilities.
   * These are semi-transparent grayscale colors meant for modal
   * backdrops, glassmorphic cards, and dimmer scrims — palette-agnostic,
   * so they read against any app theme. */
  --color-overlay: var(--playful-surface-overlay);
  --color-glass: var(--playful-surface-glass);
  --color-scrim: var(--playful-surface-scrim);

  /* Animation utilities → animate-* classes. Tailwind 4 ships
   * animate-spin / animate-ping / animate-pulse / animate-bounce by
   * default; we add common entrance + toast animations and override
   * pulse with a tighter timing curve.
   *
   * Keyframe names keep the `playful-` prefix so apps that reference
   * them directly via the CSS `animation:` shorthand (and any older
   * app the platform later re-seeds runtime files into) continue to
   * resolve. The agent reaches for `animate-*` classes; the prefix is
   * an internal implementation detail. */
  --animate-fade-in: playful-fade-in 0.3s ease both;
  --animate-rise-in: playful-rise-in 0.4s ease both;
  --animate-toast-in: playful-toast-in 0.25s ease forwards;
  --animate-toast-out: playful-toast-out 0.25s ease forwards;
  --animate-pulse: playful-pulse 1.5s ease-in-out infinite;

  @keyframes playful-spin {
    to {
      transform: rotate(360deg);
    }
  }
  @keyframes playful-fade-in {
    from {
      opacity: 0;
    }
    to {
      opacity: 1;
    }
  }
  @keyframes playful-rise-in {
    from {
      opacity: 0;
      transform: translateY(16px);
    }
    to {
      opacity: 1;
      transform: translateY(0);
    }
  }
  @keyframes playful-toast-in {
    from {
      opacity: 0;
      transform: translateY(12px);
    }
    to {
      opacity: 1;
      transform: translateY(0);
    }
  }
  @keyframes playful-toast-out {
    from {
      opacity: 1;
      transform: translateY(0);
    }
    to {
      opacity: 0;
      transform: translateY(12px);
    }
  }
  @keyframes playful-pulse {
    0%,
    100% {
      opacity: 1;
    }
    50% {
      opacity: 0.5;
    }
  }
}

@layer base {
  html,
  body {
    /* Disable both scroll-chaining and the iOS elastic "rubber-band" bounce.
       Without this, tapping and dragging on a non-scrollable spot inside the
       iframe causes the host viewport to rubber-band on iOS. */
    overscroll-behavior: none;
  }

  body {
    font-family: var(--playful-font-sans);
    /* Subpixel font rendering on macOS / iOS (46/99 apps set this manually). */
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    /* Keep normal-flow content above the iOS home indicator / address-bar
       overlap zone when viewport-fit=cover is active. Fixed-position
       elements must handle their own offset via --playful-safe-bottom. */
    padding-bottom: env(safe-area-inset-bottom, 0px);
  }

  /* Interactive controls: avoid the 300ms tap delay and accidental pinch-zoom
     on mobile. 40/99 prod apps set this manually on individual buttons. */
  button {
    touch-action: manipulation;
  }
}

/* ============================================
 * 2. Shadow / Elevation
 * ============================================ */
:root {
  --playful-shadow-xs: 0 1px 2px oklch(0% 0 0 / 0.05);
  --playful-shadow-sm: 0 1px 3px oklch(0% 0 0 / 0.1), 0 1px 2px oklch(0% 0 0 / 0.06);
  --playful-shadow-md: 0 4px 6px oklch(0% 0 0 / 0.1), 0 2px 4px oklch(0% 0 0 / 0.06);
  --playful-shadow-lg: 0 10px 15px oklch(0% 0 0 / 0.1), 0 4px 6px oklch(0% 0 0 / 0.05);
  --playful-shadow-xl: 0 20px 25px oklch(0% 0 0 / 0.1), 0 8px 10px oklch(0% 0 0 / 0.04);
  --playful-shadow-2xl: 0 25px 50px oklch(0% 0 0 / 0.25);
}

/* ============================================
 * 3. Surface Utilities
 * ============================================
 * Hue-neutral utilities for overlays, scrims, glassmorphic cards.
 * Safe to use regardless of the app's palette.
 */
:root {
  --playful-surface-overlay: oklch(0% 0 0 / 0.5);
  --playful-surface-glass: oklch(100% 0 0 / 0.8);
  --playful-surface-scrim: oklch(0% 0 0 / 0.3);
}

/* ============================================
 * 4. Dialog primitive
 * ============================================
 * Native <dialog> positioning is fragile: the UA stylesheet centers
 * via `margin: auto`, but Tailwind Preflight and utility classes can
 * neutralize that and pin the dialog to the top-left. This class
 * locks in a safe centered layout plus a default backdrop.
 *
 * Usage: <dialog class="playful-dialog"> ... </dialog>
 * Open with dialog.showModal(); close with dialog.close().
 *
 * These rules live in `@layer components` (declared before `utilities` in the
 * layer statement at the top of this file) so they act as OVERRIDABLE defaults:
 * Tailwind emits utilities into `@layer utilities`, which sorts after
 * `components`, so any utility on the element — `max-h-*`, `overflow-hidden`,
 * `open:grid`, etc. — beats these rules regardless of specificity. An UNLAYERED
 * rule would instead win over utilities; keep protective rules (e.g. the
 * touch-input 16px floor) unlayered so they can't be overridden.
 */
@layer components {
  .playful-dialog {
    margin: auto;
    padding: 0.5rem;
    border: none;
    background: white;
    border-radius: 1rem;
    max-width: min(92vw, 560px);
    /* dvh, not vh: on iOS Safari `vh` is the *large* viewport (address bar
       hidden), so a 90vh dialog can exceed the currently visible area and its
       top becomes unreachable. `dvh` tracks the visible viewport. */
    max-height: 90dvh;
    overflow: visible;
  }
  .playful-dialog::backdrop {
    background-color: var(--playful-surface-overlay);
  }

  /* Backdrop fade for the animated open/close path.
   *
   * `playful.ui.showDialog()` drives the dialog box itself with Motion (JS), but
   * the `::backdrop` pseudo-element can't be targeted from JS — so its fade is
   * CSS, gated on the `data-pf-dialog` attribute the helper toggles. Because the
   * helper keeps the dialog OPEN (in the top layer) for the whole close
   * animation and only calls native `.close()` afterwards, this is an ordinary
   * property transition on a live pseudo-element — no `@starting-style`,
   * `overlay`, or `allow-discrete` — so it plays identically in every browser
   * (unlike the top-layer discrete transitions, which Safari does not honor).
   *
   * A plain `.playful-dialog` opened via native `showModal()` has no
   * `data-pf-dialog` attribute, so it keeps the instant, solid backdrop above —
   * the animation is entirely opt-in through the helper. */
  .playful-dialog[data-pf-dialog]::backdrop {
    transition: background-color 0.25s ease;
  }
  /* Hold the box hidden + offset for the one frame between `showModal()` and
   * Motion's first frame, so it can't flash at full opacity/position before the
   * entrance animation takes over. Motion writes inline styles that win from
   * then on. */
  .playful-dialog[data-pf-dialog='opening'] {
    opacity: 0;
    transform: translateY(16px);
  }
  /* `opening` (pre-first-frame) and `closing` hold the backdrop transparent; the
   * helper flips to `open` on the next frame to fade in, and to `closing` to
   * fade out. `open` matches neither, so it falls through to the solid default. */
  .playful-dialog[data-pf-dialog='opening']::backdrop,
  .playful-dialog[data-pf-dialog='closing']::backdrop {
    background-color: transparent;
  }
  @media (prefers-reduced-motion: reduce) {
    .playful-dialog[data-pf-dialog]::backdrop {
      transition: none;
    }
  }
}

/* ============================================
 * 5. Utility classes
 * ============================================
 * Animation keyframes (fade-in / rise-in / toast-in / toast-out, plus
 * our `pulse` override) live in the `@theme` block above so they
 * generate `animate-*` Tailwind utilities. `spin`, `ping`, `pulse`,
 * and `bounce` are provided by Tailwind itself.
 */

/* Single-line ellipsis. Tailwind's `truncate` does the same thing; this is
   for apps that prefer a semantic class on a CSS-styled component. */
.playful-truncate {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Visually hidden, still accessible to screen readers. Standard a11y pattern.
   Use for icon-only buttons that need a text label:
   `<button>Save<span class="playful-sr-only"> document</span></button>`. */
.playful-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;
}

/* Prevent text selection (drag-game pieces, custom controls). */
.playful-no-select {
  -webkit-user-select: none;
  user-select: none;
}

/* ============================================
 * 8. Spinner component
 * ============================================
 * Tiny CSS spinner. Size via `--playful-spinner-size` (default 24px); color
 * inherits via `currentColor`, so set `color: var(--primary)` on the parent.
 *
 * Usage: <div class="playful-spinner" aria-label="Loading"></div>
 */
.playful-spinner {
  width: var(--playful-spinner-size, 24px);
  height: var(--playful-spinner-size, 24px);
  border: 2px solid currentColor;
  border-top-color: transparent;
  border-radius: 50%;
  animation: playful-spin 0.8s linear infinite;
  /* Dim the ring so the spinning gap reads clearly. */
  opacity: 0.6;
}

/* ============================================
 * 9. Scrollbar utilities
 * ============================================ */
.playful-scrollbar-hidden {
  scrollbar-width: none;
}
.playful-scrollbar-hidden::-webkit-scrollbar {
  display: none;
}

.playful-scrollbar-thin {
  scrollbar-width: thin;
  scrollbar-color: currentColor transparent;
}
.playful-scrollbar-thin::-webkit-scrollbar {
  width: 6px;
  height: 6px;
}
.playful-scrollbar-thin::-webkit-scrollbar-thumb {
  background: currentColor;
  border-radius: 3px;
  opacity: 0.4;
}

/* ============================================
 * 10. Toast component
 * ============================================
 * Container is appended to <body> by playful.js when `playful.ui.toast()`
 * is first called. Individual toasts inherit app palette via `currentColor`
 * and the `--playful-surface-*` neutrals.
 *
 * 75/99 recent prod apps hand-rolled this triple (HTML + CSS + JS); use
 * `playful.ui.toast(msg)` instead.
 */
.playful-toast-container {
  position: fixed;
  left: 50%;
  bottom: calc(env(safe-area-inset-bottom, 0px) + 24px);
  transform: translateX(-50%);
  z-index: 2147483000;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  pointer-events: none;
  max-width: calc(100vw - 32px);
}
.playful-toast {
  pointer-events: auto;
  background: #1a1a1a;
  color: #ffffff;
  padding: 10px 18px;
  border-radius: 999px;
  font-family: var(--playful-font-sans);
  font-size: var(--playful-text-sm);
  font-weight: var(--playful-font-medium);
  line-height: var(--playful-leading-snug);
  box-shadow: var(--playful-shadow-lg);
  max-width: 100%;
  text-align: center;
  word-wrap: break-word;
  animation: playful-toast-in 0.25s ease forwards;
}
.playful-toast[data-variant='success'] {
  background: #1f7a4f;
}
.playful-toast[data-variant='error'] {
  background: #b8332f;
}
.playful-toast[data-variant='warning'] {
  background: #b76d0a;
}
.playful-toast[data-closing] {
  animation: playful-toast-out 0.25s ease forwards;
}

/* ============================================
 * 11. Prose (rendered Markdown)
 * ============================================
 * Markdown converted to HTML (e.g. `marked.parse()` on `playful.ai` chat
 * output) renders flat: marked emits classless <h1>/<ul>/<a>/<code>, and
 * Tailwind Preflight strips heading sizes and list markers. Apply this class
 * to the element you set `.innerHTML` on to restore sensible typographic
 * defaults. Palette-neutral — text color inherits via `currentColor`.
 *
 * Usage:
 *   el.innerHTML = marked.parse(text);
 *   el.classList.add('playful-prose'); // or set class="playful-prose" in HTML
 */
.playful-prose {
  line-height: var(--playful-leading-relaxed);
}
.playful-prose > :first-child {
  margin-top: 0;
}
.playful-prose > :last-child {
  margin-bottom: 0;
}
.playful-prose h1,
.playful-prose h2,
.playful-prose h3,
.playful-prose h4,
.playful-prose h5,
.playful-prose h6 {
  font-weight: var(--playful-font-semibold);
  line-height: var(--playful-leading-snug);
  margin: 1.5em 0 0.5em;
}
.playful-prose h1 {
  font-size: var(--playful-text-2xl);
}
.playful-prose h2 {
  font-size: var(--playful-text-xl);
}
.playful-prose h3 {
  font-size: var(--playful-text-lg);
}
.playful-prose p,
.playful-prose ul,
.playful-prose ol,
.playful-prose blockquote,
.playful-prose pre {
  margin: 0.75em 0;
}
.playful-prose ul {
  list-style: disc;
  padding-left: 1.5rem;
}
.playful-prose ol {
  list-style: decimal;
  padding-left: 1.5rem;
}
.playful-prose li {
  margin: 0.25em 0;
}
.playful-prose a {
  color: currentColor;
  text-decoration: underline;
}
.playful-prose strong {
  font-weight: var(--playful-font-semibold);
}
/* Code tint is derived from `currentColor` so it reads on light AND dark
   surfaces (a fixed black tint vanishes on dark themes). */
.playful-prose code {
  font-family: var(--playful-font-mono);
  font-size: 0.9em;
  background: color-mix(in oklab, currentColor 8%, transparent);
  padding: 0.1em 0.3em;
  border-radius: 0.25rem;
}
.playful-prose pre {
  font-family: var(--playful-font-mono);
  background: color-mix(in oklab, currentColor 8%, transparent);
  padding: 0.75rem 1rem;
  border-radius: 0.5rem;
  overflow-x: auto;
}
.playful-prose pre code {
  background: none;
  padding: 0;
}
.playful-prose blockquote {
  border-left: 3px solid currentColor;
  padding-left: 1rem;
  opacity: 0.8;
}
.playful-prose table {
  border-collapse: collapse;
  width: 100%;
  margin: 0.75em 0;
}
.playful-prose th,
.playful-prose td {
  border: 1px solid color-mix(in oklab, currentColor 20%, transparent);
  padding: 0.375em 0.625em;
  text-align: left;
}
.playful-prose th {
  font-weight: var(--playful-font-semibold);
}

/* ============================================
 * 12. iOS Safari input auto-zoom floor (touch only)
 * ============================================
 * iOS Safari auto-zooms the viewport when a focused text-like form control
 * has a computed font-size < 16px, and does NOT restore the zoom on blur —
 * the app stays stuck in a janky zoomed state. The mini-app viewport meta
 * doesn't set user-scalable=no / maximum-scale=1, so we can't suppress the
 * zoom from the viewport side (and even if we did, iOS overrides those for
 * accessibility).
 *
 * UNLAYERED so this beats any Tailwind utility in `@layer utilities` —
 * `text-sm` (14px) and `text-xs` (12px) would otherwise win. `max(16px, 1em)`
 * makes it a floor, not a clamp: input text already ≥ 16px passes through.
 * Scoped to `pointer: coarse` so desktop apps with mouse input can still use
 * sub-16px input text if they want. See issue #1898.
 */
@media (pointer: coarse) {
  input:not([type='checkbox']):not([type='radio']):not([type='range']):not([type='color']):not(
      [type='button']
    ):not([type='submit']):not([type='reset']):not([type='file']):not([type='hidden']),
  select,
  textarea {
    font-size: max(16px, 1em);
  }
}
