/* Mobile Adaptations - All Devices */

/* Set global device type variables */
:root {
  --is-mobile: 0;
  --device-type: 'desktop';
  --font-scale: 1;
}

@media (max-width: 1024px) {
:root.mobile-pointer:not(.desktop-runtime) {
    --is-mobile: 1;
    --device-type: 'mobile';
    --font-scale: 0.9;
  }
}

/* Utility classes for device-specific styling */
.desktop-only {
  display: block;
}

.mobile-only {
  display: none;
}

@media (max-width: 1024px) {
  :root.mobile-pointer:not(.desktop-runtime) .desktop-only {
    display: none !important;
  }

  :root.desktop-runtime .mobile-only {
    display: none !important;
  }

  :root.mobile-pointer:not(.desktop-runtime) .mobile-only {
    display: block !important;
  }
}

/* General mobile improvements for all mobile devices */
@media (max-width: 1024px) {
  /* Override CSS custom properties for mobile */
  :root.mobile-pointer:not(.desktop-runtime) {
    --text-markdown: 1rem;
    --text-code: 0.875rem;
    --text-ui-header: 0.9375rem;
    --text-ui-label: 0.875rem;
    --text-meta: 0.875rem;
    --text-micro: 0.8125rem;
  }

  /* Force override with higher specificity */
  :root.mobile-pointer:not(.desktop-runtime) * {
    --text-markdown: 1rem !important;
    --text-code: 0.875rem !important;
    --text-ui-header: 0.9375rem !important;
    --text-ui-label: 0.875rem !important;
    --text-meta: 0.875rem !important;
    --text-micro: 0.8125rem !important;
  }

  /* Additional override for elements using typography classes */
  :root.mobile-pointer:not(.desktop-runtime) .typography-markdown,
  :root.mobile-pointer:not(.desktop-runtime) .typography-code,
  :root.mobile-pointer:not(.desktop-runtime) .typography-ui-header,
  :root.mobile-pointer:not(.desktop-runtime) .typography-ui-label,
  :root.mobile-pointer:not(.desktop-runtime) .typography-meta,
  :root.mobile-pointer:not(.desktop-runtime) .typography-micro {
    font-size: unset !important;
  }

  /* Fix font size for tool displays */
  :root.mobile-pointer:not(.desktop-runtime) [class*="tool"],
  :root.mobile-pointer:not(.desktop-runtime) [class*="code"] {
    --text-code: 0.875rem !important;
    font-size: var(--text-code) !important;
  }

  /* Improve touch targets for mobile */
  :root.mobile-pointer:not(.desktop-runtime) button:not([role="radio"]):not([role="checkbox"]):not([role="switch"]),
  :root.mobile-pointer:not(.desktop-runtime) .btn,
  :root.mobile-pointer:not(.desktop-runtime) [role="button"] {
    min-height: 36px;
    min-width: 36px;
  }

  /* Composer footer action buttons (sessions / attach / auto-accept): hug the
     icon so the group stays tight. The container only renders in the mobile
     JSX, so no pointer/runtime gating is needed; !important overrides both the
     w-8 utility and the global mobile touch-target min-width regardless of
     cascade layer or specificity. */
  .composer-mobile-actions button {
    width: 1.5rem !important;
    min-width: 0 !important;
  }

  /* Improve input field touch targets */
  :root.mobile-pointer:not(.desktop-runtime) input,
  :root.mobile-pointer:not(.desktop-runtime) textarea,
  :root.mobile-pointer:not(.desktop-runtime) select {
    min-height: 36px;
  }

  /* Fix mobile text inputs */
  :root.mobile-pointer:not(.desktop-runtime) input[type="text"],
  :root.mobile-pointer:not(.desktop-runtime) input[type="search"],
  :root.mobile-pointer:not(.desktop-runtime) input[type="email"],
  :root.mobile-pointer:not(.desktop-runtime) input[type="password"],
  :root.mobile-pointer:not(.desktop-runtime) textarea {
    font-size: 16px !important; /* Prevents iOS zoom */
  }

  /* Prevent keyboard on non-input elements */
  :root.mobile-pointer:not(.desktop-runtime)
    button:not([type="submit"]):not([type="button"]):not([type="reset"]),
  :root.mobile-pointer:not(.desktop-runtime) .btn,
  :root.mobile-pointer:not(.desktop-runtime) [role="button"] {
    -webkit-touch-callout: none;
    -webkit-user-select: none;
    user-select: none;
  }

  /* Specific fix for session buttons */
  :root.mobile-pointer:not(.desktop-runtime) button[inputmode="none"] {
    -webkit-touch-callout: none;
    -webkit-user-select: none;
    user-select: none;
    pointer-events: auto;
  }

  /* Prevent system context menu on message content when using custom selection menu */
  :root.mobile-pointer:not(.desktop-runtime) .message-content-text {
    -webkit-touch-callout: none;
    -webkit-user-select: text;
    user-select: text;
  }


  /* Allow system context menu only when explicitly requested via the "More" button */
  :root.mobile-pointer:not(.desktop-runtime) .message-content-text.show-system-menu {
    -webkit-touch-callout: default;
  }

  /* Improve mobile spacing */
  :root.mobile-pointer:not(.desktop-runtime) .px-4 {
    padding-left: 1rem !important;
    padding-right: 1rem !important;
  }

  :root.mobile-pointer:not(.desktop-runtime) .py-2 {
    padding-top: 0.75rem !important;
    padding-bottom: 0.75rem !important;
  }

  /* Fix mobile scroll containers */
  :root.mobile-pointer:not(.desktop-runtime) .overflow-hidden {
    overflow-x: hidden !important;
    overflow-y: auto !important;
    -webkit-overflow-scrolling: touch;
  }

  :root.mobile-pointer:not(.desktop-runtime) [data-page-scroll-lock="true"] {
    overflow: hidden !important;
    overscroll-behavior: none;
  }

  /* Keep markdown code blocks horizontally scrollable on mobile. */
  :root.mobile-pointer:not(.desktop-runtime) .markdown-content [data-markdown="code-block-body"] {
    overflow-x: auto !important;
    overflow-y: hidden !important;
    -webkit-overflow-scrolling: touch;
  }

  /* Fix mobile viewport height */
  :root.mobile-pointer:not(.desktop-runtime) {
    height: 100%;
    height: -webkit-fill-available;
    overflow: hidden;
  }

  /* Fix main layout container */
  :root.mobile-pointer:not(.desktop-runtime) .flex.flex-col.h-screen {
    height: 100vh;
    height: -webkit-fill-available;
    min-height: 0;
  }

  /* Fix main content area */
  :root.mobile-pointer:not(.desktop-runtime) .flex-1.overflow-hidden {
    min-height: 0;
    flex: 1;
  }

  /* Fix chat container */
  :root.mobile-pointer:not(.desktop-runtime) .flex.flex-col.h-full {
    height: 100%;
    min-height: 0;
  }

  /* Ensure proper flex behavior */
  :root.mobile-pointer:not(.desktop-runtime) .flex.flex-col {
    min-height: 0;
  }

  /* Mobile sidebar positioning */
  :root.mobile-pointer:not(.desktop-runtime) .mobile-sidebar-top {
    top: var(--header-height, 3rem);
    height: calc(100vh - var(--header-height, 3rem));
  }

  /* Set header height variable */
  :root.mobile-pointer:not(.desktop-runtime) .header-safe-area {
    --header-height: 3rem;
  }

  /* For mobile devices with larger header */
  @media (max-width: 768px) {
    :root.mobile-pointer:not(.desktop-runtime) .header-safe-area {
      --header-height: 3.5rem;
    }
  }

  /* For tablet devices */
  @media (min-width: 769px) and (max-width: 1024px) {
    :root.mobile-pointer:not(.desktop-runtime) .header-safe-area {
      --header-height: 3.25rem;
    }
  }
}

/* iOS PWA Adaptations */

/* iOS safe area defaults (applies to PWA + Safari) */
@supports (-webkit-touch-callout: none) {
  :root.device-mobile:not(.desktop-runtime),
  :root.device-tablet:not(.desktop-runtime),
  :root.mobile-pointer:not(.desktop-runtime) {
    --oc-safe-area-top: env(safe-area-inset-top, 0);
    --oc-safe-area-right: env(safe-area-inset-right, 0);
    --oc-safe-area-bottom: env(safe-area-inset-bottom, 0);
    --oc-safe-area-bottom-visual: clamp(0px, calc(var(--oc-safe-area-bottom) * 0.05), 4px);
    --oc-safe-area-left: env(safe-area-inset-left, 0);
  }

  :root.device-mobile:not(.desktop-runtime) .header-safe-area,
  :root.device-tablet:not(.desktop-runtime) .header-safe-area,
  :root.mobile-pointer:not(.desktop-runtime) .header-safe-area {
    padding-top: var(--oc-safe-area-top);
  }

  :root.oc-capacitor-app [data-sonner-toaster][data-y-position='top'] {
    top: calc(env(safe-area-inset-top, 0px) + 16px) !important;
  }
}

/* Phase 1: iOS PWA safe area handling - Enhanced positioning approach */
@media (display-mode: standalone) {
  /* iOS-specific safe area handling with CSS variable support */
  @supports (-webkit-touch-callout: none) {
    /* Safe area handling for fixed positioned elements */
    :root.device-mobile:not(.desktop-runtime),
    :root.device-tablet:not(.desktop-runtime),
    :root.mobile-pointer:not(.desktop-runtime) {
      --oc-safe-area-top: env(safe-area-inset-top, 0);
      --oc-safe-area-right: env(safe-area-inset-right, 0);
      --oc-safe-area-bottom: env(safe-area-inset-bottom, 0);
      --oc-safe-area-bottom-visual: clamp(0px, calc(var(--oc-safe-area-bottom) * 0.05), 4px);
      --oc-safe-area-left: env(safe-area-inset-left, 0);
    }

    :root.device-mobile:not(.desktop-runtime) .header-safe-area,
    :root.device-tablet:not(.desktop-runtime) .header-safe-area,
    :root.mobile-pointer:not(.desktop-runtime) .header-safe-area {
      padding-top: var(--oc-safe-area-top);
    }

    :root.device-mobile:not(.desktop-runtime) .main-content-safe-area,
    :root.device-tablet:not(.desktop-runtime) .main-content-safe-area,
    :root.mobile-pointer:not(.desktop-runtime) .main-content-safe-area {
      padding-top: 0;
      padding-bottom: 0;
      padding-left: var(--oc-safe-area-left);
      padding-right: var(--oc-safe-area-right);
    }

    /* Safe area for bottom fixed/bottom-0 elements */
    :root.device-mobile:not(.desktop-runtime) .bottom-safe-area,
    :root.device-tablet:not(.desktop-runtime) .bottom-safe-area,
    :root.mobile-pointer:not(.desktop-runtime) .bottom-safe-area {
      padding-bottom: var(--oc-safe-area-bottom-visual) !important;
    }

    /* Drawer safe area - top already offset by header height */
    :root.device-mobile:not(.desktop-runtime) .drawer-safe-area,
    :root.device-tablet:not(.desktop-runtime) .drawer-safe-area,
    :root.mobile-pointer:not(.desktop-runtime) .drawer-safe-area {
      padding-top: 0;
      padding-bottom: var(--oc-safe-area-bottom-visual);
    }

    /* Fix iOS viewport issues */
    :root.device-mobile:not(.desktop-runtime) .flex.flex-col.h-screen,
    :root.device-tablet:not(.desktop-runtime) .flex.flex-col.h-screen,
    :root.mobile-pointer:not(.desktop-runtime) .flex.flex-col.h-screen {
      min-height: 100vh;
      min-height: -webkit-fill-available;
    }

    /* Prevent content overlap in iOS */
    :root.device-mobile:not(.desktop-runtime) .flex-1.overflow-hidden,
    :root.device-tablet:not(.desktop-runtime) .flex-1.overflow-hidden,
    :root.mobile-pointer:not(.desktop-runtime) .flex-1.overflow-hidden {
      position: relative;
      overflow-y: auto;
      -webkit-overflow-scrolling: touch;
    }

    /* Update header height for iOS */
    :root.device-mobile:not(.desktop-runtime) .header-safe-area,
    :root.device-tablet:not(.desktop-runtime) .header-safe-area,
    :root.mobile-pointer:not(.desktop-runtime) .header-safe-area {
      --header-height: 4.5rem;
    }

    :root.device-mobile:not(.desktop-runtime) .mobile-sidebar-top,
    :root.device-tablet:not(.desktop-runtime) .mobile-sidebar-top,
    :root.mobile-pointer:not(.desktop-runtime) .mobile-sidebar-top {
      --header-height: 4.5rem;
    }

    /* Mobile typography improvements */
    .typography-markdown {
      font-size: 1rem !important;
    }

    .typography-code {
      font-size: 0.875rem !important;
    }

    .typography-ui-header {
      font-size: 0.9375rem !important;
    }

    .typography-ui-label {
      font-size: 0.875rem !important;
    }

    .typography-meta {
      font-size: 0.875rem !important;
    }

    .typography-micro {
      font-size: 0.8125rem !important;
    }

    /* Fix font size for tool displays */
    [class*="tool"], [class*="code"] {
      font-size: 0.875rem !important;
    }
  }

  /* Fallback for non-iOS standalone mode */
  @supports not (-webkit-touch-callout: none) {
    :root.device-mobile:not(.desktop-runtime),
    :root.device-tablet:not(.desktop-runtime),
    :root.mobile-pointer:not(.desktop-runtime) {
      --oc-safe-area-top: env(safe-area-inset-top, 0);
      --oc-safe-area-right: env(safe-area-inset-right, 0);
      --oc-safe-area-bottom: env(safe-area-inset-bottom, 0);
      --oc-safe-area-bottom-visual: clamp(0px, calc(var(--oc-safe-area-bottom) * 0.05), 4px);
      --oc-safe-area-left: env(safe-area-inset-left, 0);
    }

    :root.device-mobile:not(.desktop-runtime) .header-safe-area,
    :root.device-tablet:not(.desktop-runtime) .header-safe-area,
    :root.mobile-pointer:not(.desktop-runtime) .header-safe-area {
      padding-top: var(--oc-safe-area-top);
    }

    :root.device-mobile:not(.desktop-runtime) .main-content-safe-area,
    :root.device-tablet:not(.desktop-runtime) .main-content-safe-area,
    :root.mobile-pointer:not(.desktop-runtime) .main-content-safe-area {
      padding-top: 0;
      padding-bottom: 0;
      padding-left: var(--oc-safe-area-left);
      padding-right: var(--oc-safe-area-right);
    }

    :root.device-mobile:not(.desktop-runtime) .bottom-safe-area,
    :root.device-tablet:not(.desktop-runtime) .bottom-safe-area,
    :root.mobile-pointer:not(.desktop-runtime) .bottom-safe-area {
      padding-bottom: var(--oc-safe-area-bottom-visual) !important;
    }
  }
}

/* Phase 6: Home indicator blur gradient fix */
@media (display-mode: standalone) and (max-width: 768px) {
  .pwa-dialog-content {
    --pwa-dialog-padding: clamp(0.75rem, 3vw, 1.25rem);
    top: auto;
    /* Browsers that don't understand dvh ignore the second declaration and
       keep the vh-based fallback. Newer browsers use 100dvh so Android
       Chrome's collapsible URL bar doesn't push the dialog footer off-screen. */
    max-height: calc(
      100vh
      - var(--oc-safe-area-top, 0px)
      - var(--oc-safe-area-bottom-visual, 0px)
      - 20px
    );
    max-height: calc(
      100dvh
      - var(--oc-safe-area-top, 0px)
      - var(--oc-safe-area-bottom-visual, 0px)
      - 20px
    );
    padding: var(--pwa-dialog-padding);
    padding-top: calc(var(--pwa-dialog-padding) + (var(--oc-safe-area-top, 0px) * 0.35));
    row-gap: clamp(0.75rem, 2vw, 1.25rem);
  }

  .pwa-dialog-content.pwa-compact {
    --pwa-dialog-padding: clamp(0.5rem, 2vw, 0.85rem);
    row-gap: clamp(0.45rem, 1.5vw, 0.8rem);
  }

  .pwa-dialog-content [data-slot="dialog-close"] {
    top: calc(0.25rem + (var(--oc-safe-area-top, 0px) * 0.35));
    right: var(--pwa-dialog-padding);
  }

  .pwa-dialog-content.pwa-compact [data-slot="dialog-close"] {
    top: calc(0.2rem + (var(--oc-safe-area-top, 0px) * 0.3));
  }

  .pwa-dialog-content .settings-page-body {
    max-width: 100%;
    padding: 0.75rem 1rem;
    row-gap: 0;
  }

  .pwa-dialog-content .settings-page-body > :not([hidden]) ~ :not([hidden]) {
    margin-top: 0.85rem;
  }

  .pwa-dialog-content .directory-dialog-body {
    padding: 0.5rem 0.75rem;
  }

  .pwa-dialog-content .directory-dialog-body .directory-grid {
    gap: 0.6rem;
  }

  .pwa-overlay-panel {
    margin-bottom: 0;
  }

  .pwa-overlay-panel .pwa-overlay-scroll {
    padding-bottom: calc(var(--oc-safe-area-bottom-visual, 0px) + 12px);
  }
}

@media (display-mode: standalone) {
  /* Home indicator overlay */
  :root.device-mobile:not(.desktop-runtime) body::after,
  :root.device-tablet:not(.desktop-runtime) body::after,
  :root.mobile-pointer:not(.desktop-runtime) body::after {
    content: '';
    position: fixed;
    bottom: 0;
    left: 0;
    right: 0;
    height: var(--oc-safe-area-bottom-visual, var(--oc-safe-area-bottom, env(safe-area-inset-bottom, 0)));
    background: linear-gradient(
      to top,
      var(--background) 0%,
      var(--background) 30%,
      rgba(from var(--background) r g b / 0.8) 60%,
      rgba(from var(--background) r g b / 0.3) 85%,
      transparent 100%
    );
    z-index: 1000;
    pointer-events: none;
  }

  /* Fallback for browsers without rgba(from ...) support */
  @supports not (color: rgba(from white r g b / 0.5)) {
    :root.device-mobile:not(.desktop-runtime) body::after,
    :root.device-tablet:not(.desktop-runtime) body::after,
    :root.mobile-pointer:not(.desktop-runtime) body::after {
      background: linear-gradient(
        to top,
        var(--background) 0%,
        var(--background) 30%,
        rgba(21, 19, 19, 0.8) 60%,  /* Dark theme fallback */
        rgba(21, 19, 19, 0.3) 85%,
        transparent 100%
      );
    }

    /* Light theme fallback */
    :root body::after {
      background: linear-gradient(
        to top,
        var(--background) 0%,
        var(--background) 30%,
        rgba(248, 247, 243, 0.8) 60%,  /* Light theme fallback */
        rgba(248, 247, 243, 0.3) 85%,
        transparent 100%
      );
    }
  }
}

/* Small app-wide bottom safe area for the native shell. The phone's rounded hardware
   corners clip controls flush against the bottom edge, and the PWA's own safe-area
   padding is gated behind display-mode: standalone — which the Capacitor WebView does
   not match — so nothing reserves bottom room in the native app. Expose it as a token
   so any native surface can consume it; the chat shell does so below. */
:root.oc-capacitor-app {
  --oc-app-bottom-safe: max(16px, calc(env(safe-area-inset-bottom, 0px) * 0.5));
}

/* Paint the document canvas with the theme background in the native app — the same
   thing .desktop-runtime does for body/#root, which the Capacitor shell never got.
   The status bar is overlaid (transparent), and in dark mode `color-scheme: dark`
   makes the bare UA canvas dark, so any sliver not covered by content (notably the
   area behind the status bar) bled through as a dark band at the top. It only showed
   in dark mode, which is why it tracked the system theme. */
:root.oc-capacitor-app,
:root.oc-capacitor-app body,
:root.oc-capacitor-app #root {
  background: var(--background) !important;
  background-color: var(--background) !important;
}

/* Native (Capacitor) keyboard handling.
   The Keyboard plugin runs in `resize: 'none'` mode so the WebView keeps its full
   height; instead we shrink the app shell by the keyboard frame height. Two vars,
   both set by useNativeMobileChrome: --oc-keyboard-inset is the keyboard's target
   height from the moment `keyboardWillShow` fires (overlay surfaces below animate
   against it as before), while --oc-kb-layout is the same height applied to the
   shell's LAYOUT only at the choreography point where a resize is invisible.
   Scoped to .oc-capacitor-app so the browser PWA keeps its dvh / interactive-widget
   behaviour untouched.

   The shell height is NOT transitioned. Animating `height` reflows the whole shell
   every frame and forces the chat's ResizeObserver to chase the bottom on each of
   those frames — the source of the visible micro-jitter on the composer/chat while
   pinned. Instead the visual rise is transform-only (compositor) — a FLIP
   choreography driven by useNativeMobileChrome:

     show: shell keeps its full height for the whole 0.25s; the composer (and, when
           pinned, the chat scroll container — see useChatAutoFollow) slides up via
           inline transforms in sync with the keyboard; at the end the shell snaps to
           its final height (--oc-kb-layout, one reflow) and the shift is removed
           in the same frame — visually identical, so the swap is invisible.
     hide: the shell snaps back to full height immediately (still hidden behind the
           keyboard), the composer/content are FLIP-offset to their raised position
           and slide down with the keyboard.

   `keyboardWillShow` fires at the start of the iOS keyboard animation with the final
   height, and the duration/curve mimic the native keyboard (≈0.25s,
   cubic-bezier(0.38, 0.7, 0.125, 1)). (visualViewport live-tracking would be exact
   but doesn't report under WKWebView's `resize: 'none'`, so this is the best signal.) */
:root.oc-capacitor-app .oc-mobile-app-shell {
  height: calc(100dvh - var(--oc-kb-layout, 0px));
  /* Reserve the bottom safe area only while the keyboard is down — when it's up the
     inset cancels it out (the home indicator is hidden and the composer should sit
     flush above the keyboard). The shell keeps its own bg behind this padding. */
  padding-bottom: max(0px, calc(var(--oc-app-bottom-safe, 0px) - var(--oc-kb-layout, 0px)));
}

/* Portal surfaces (bottom sheets, overlay panels) render at <body> level, outside
   the app shell, so they don't inherit the shell's keyboard inset. They're full-
   height `fixed inset-0` scrims with a bottom-anchored (`mt-auto`) sheet, so raising
   their bottom edge by the keyboard height shrinks scrim + sheet together and lifts
   any input above the keyboard instead of hiding it underneath. The opacity term
   preserves the scrim's enter fade (Tailwind's `transition-opacity` would otherwise
   be overridden by this rule's `transition` shorthand). */
:root.oc-capacitor-app .oc-keyboard-inset-surface {
  bottom: var(--oc-keyboard-inset, 0px);
  transition: bottom 0.25s cubic-bezier(0.38, 0.7, 0.125, 1), opacity 0.2s ease-out;
}
/* While a panel is playing its enter animation, its bottom anchor snaps
   instead of transitioning — opening an overlay typically dismisses the
   keyboard in the same moment, and animating both made the entrance jerky. */
:root.oc-capacitor-app .oc-keyboard-inset-surface.oc-keyboard-inset-snap {
  transition: opacity 0.2s ease-out;
}

/* Full-screen scroll views (e.g. the connect/login screen) live outside the app
   shell, so shrink them by the keyboard height the same way the shell does. Capping
   the height (instead of min-height: 100dvh) is what makes overflow-y-auto actually
   scroll, so a field near the bottom lifts above the keyboard rather than staying
   hidden behind it. min-height: 0 neutralises the Tailwind min-h-dvh baseline. */
:root.oc-capacitor-app .oc-keyboard-fill-screen {
  height: calc(100dvh - var(--oc-keyboard-inset, 0px));
  min-height: 0;
  transition: height 0.25s cubic-bezier(0.38, 0.7, 0.125, 1);
}

/* Keyboard slide choreography: the composer (and the draft title, see
   .oc-draft-center note below) are moved with INLINE transform/transition set
   by useNativeMobileChrome — WebKit does not reliably start transitions when a
   transform changes via a CSS custom property, which parked the composer until
   the keyboard finished. Only the sliding backdrop lives here. */
:root.oc-capacitor-app.oc-kb-animating .oc-mobile-composer {
  /* While sliding, the composer passes over chat content that hasn't moved (user
     scrolled up) — give it an opaque backdrop so text doesn't show through the
     form's transparent padding. */
  background: var(--background);
}

/* Constant-viewport trick for the chat scroller: when the keyboard settles the
   shell shrinks by --oc-kb-layout, but the scroller keeps its full height by
   extending that far below its (shrunken) region — behind the composer and the
   keyboard — and converts the covered strip into its own bottom padding. For
   the virtualizer NOTHING resizes across keyboard transitions: clientHeight is
   constant, every row that can become visible is already mounted, and the
   open/close reposition is a single cheap scrollTop change (the settle re-pin /
   the clamp) instead of a viewport resize that mounts rows with a visible
   pause on long chats. */
:root.oc-capacitor-app .oc-mobile-app-shell .chat-scroll {
  /* The shell shrinks by the keyboard height BUT also gives up its bottom
     safe-area padding at the same moment (see the shell's padding-bottom
     formula) — so the scroller's parent only loses (keyboard − safe). Extend
     by exactly that, or the settle grows clientHeight by the safe inset and
     the pinned chat clamps one row down a beat after the keyboard opens. */
  bottom: calc(-1 * max(var(--oc-kb-layout, 0px) - var(--oc-app-bottom-safe, 0px), 0px));
  /* The padding leg is driven by its own variable so it can be applied at the
     very START of the keyboard rise (with an immediate re-pin — the bottom of
     the chat moves up as the keyboard begins, not after it finishes), while
     the bottom-extension leg still tracks the shell's settle-time shrink. Both
     legs equal the keyboard height once settled, so the settle snap is
     geometry-neutral for the scroller. */
  padding-bottom: var(--oc-kb-scroll-inset, var(--oc-kb-layout, 0px));
}

/* WKWebView draws the text caret as a native layer that ignores CSS transforms:
   after the keyboard rise it visibly flies from the pre-keyboard position to
   the final one. Hide it during the transition (+ UIKit's reposition lag,
   see oc-kb-caret-hold timing in useNativeMobileChrome) and pop it back in. */
:root.oc-capacitor-app.oc-kb-caret-hold textarea,
:root.oc-capacitor-app.oc-kb-caret-hold input {
  caret-color: transparent;
}

/* The composer keeps its 1rem bottom padding while the keyboard is down (breathing
   room above the home indicator), but that gap looks artificial sitting right above
   the keyboard — so tighten it while the keyboard is open. Snaps at the start of the
   show animation (a ~10px pre-slide change; transitioning it would reflow the chat
   every frame). */
:root.oc-capacitor-app.oc-keyboard-open .oc-mobile-composer {
  padding-bottom: 12px;
}

/* Draft starter chips leave the moment the keyboard starts rising
   (oc-keyboard-open lands at keyboardWillShow) and return when it's gone —
   with the keyboard up there is only room for the draft title. Instant
   show/hide (no squish animation); the title's own keyboard compensation
   (.oc-draft-center below) carries the smooth motion. */
:root.oc-capacitor-app.oc-keyboard-open .oc-draft-starters,
:root.oc-browser-keyboard-open .oc-draft-starters {
  display: none;
}

/* The draft title (.oc-draft-center) is vertically centered, so it has no
   scroll-pinning to compensate the keyboard like the chat does. It rides the
   keyboard choreography with an INLINE transform of half the keyboard shift
   (exactly how far the center moves after the shell snap) — see the kb-movers
   list in useNativeMobileChrome. No CSS rules needed here; the class only
   marks the element for the mover query. */

/* Mobile-browser fullscreen composer (visual-viewport pinned form in
   ChatInput): the header would otherwise paint above it — the form can't
   out-stack it from inside the composer wrapper's z-10 context. */
:root.oc-browser-kb-fullscreen .oc-mobile-header {
  visibility: hidden;
}

@keyframes oc-composer-morph-fade {
  from { opacity: 0; }
  to { opacity: 1; }
}

/* Voice overlay variant: the overlay BACKGROUND appears immediately (it rides
   the morphing shape), but its content stays hidden until the shape has mostly
   finished growing, then fades in — no clipped controls mid-morph. Timings
   follow the 340ms wrapper morph in ChatInput. */
.oc-composer-morph-content-fade > * {
  animation: oc-composer-morph-fade 0.18s ease-out 0.26s both;
}
