webui: migrate mobile components to SketchTailwindElement

Complete migration of all mobile web components from LitElement to
SketchTailwindElement base class with Tailwind CSS styling:

Components migrated:
- mobile-chat-input.ts: Chat input with file upload, textarea auto-resize
- mobile-chat.ts: Message display with markdown rendering and tool calls
- mobile-diff.ts: Git diff viewer with Monaco editor integration
- mobile-shell.ts: Main container coordinating mobile UI layout
- mobile-title.ts: Header with connection status and view switching

Key changes:
- Replaced LitElement inheritance with SketchTailwindElement
- Converted all CSS-in-JS styles to Tailwind utility classes
- Removed static styles blocks and shadow DOM styling
- Added custom animations via document.head for non-Tailwind effects
- Preserved all existing functionality and component interactions

Technical improvements:
- Consistent iOS safe area support with env() CSS functions
- Proper flexbox layouts for mobile responsive design
- Maintained accessibility with proper ARIA labels and focus states
- Enhanced hover and active states using Tailwind modifiers
- Optimized touch interactions with -webkit-overflow-scrolling

The mobile components now follow the established SketchTailwindElement
pattern while maintaining full feature parity with the original
shadow DOM implementations.

Co-Authored-By: sketch <hello@sketch.dev>
Change-ID: s21f840091392b02ek
diff --git a/webui/src/web-components/mobile-chat-input.ts b/webui/src/web-components/mobile-chat-input.ts
index c41cdff..ff208d5 100644
--- a/webui/src/web-components/mobile-chat-input.ts
+++ b/webui/src/web-components/mobile-chat-input.ts
@@ -1,10 +1,11 @@
 /* eslint-disable @typescript-eslint/no-explicit-any */
-import { css, html, LitElement } from "lit";
+import { html } from "lit";
 import { customElement, property, state } from "lit/decorators.js";
 import { createRef, ref } from "lit/directives/ref.js";
+import { SketchTailwindElement } from "./sketch-tailwind-element";
 
 @customElement("mobile-chat-input")
-export class MobileChatInput extends LitElement {
+export class MobileChatInput extends SketchTailwindElement {
   @property({ type: Boolean })
   disabled = false;
 
@@ -19,131 +20,6 @@
 
   private textareaRef = createRef<HTMLTextAreaElement>();
 
-  static styles = css`
-    :host {
-      display: block;
-      background-color: #ffffff;
-      border-top: 1px solid #e9ecef;
-      padding: 12px 16px;
-      /* Enhanced iOS safe area support */
-      padding-bottom: max(12px, env(safe-area-inset-bottom));
-      padding-left: max(16px, env(safe-area-inset-left));
-      padding-right: max(16px, env(safe-area-inset-right));
-      /* Prevent iOS Safari from covering the input */
-      position: relative;
-      z-index: 1000;
-    }
-
-    .input-container {
-      display: flex;
-      align-items: flex-end;
-      gap: 12px;
-      max-width: 100%;
-    }
-
-    .input-wrapper {
-      flex: 1;
-      position: relative;
-      min-width: 0;
-    }
-
-    textarea {
-      width: 100%;
-      min-height: 40px;
-      max-height: 120px;
-      padding: 12px 16px;
-      border: 1px solid #ddd;
-      border-radius: 20px;
-      font-size: 16px;
-      font-family: inherit;
-      line-height: 1.4;
-      resize: none;
-      outline: none;
-      background-color: #f8f9fa;
-      transition:
-        border-color 0.2s,
-        background-color 0.2s;
-      box-sizing: border-box;
-    }
-
-    textarea:focus {
-      border-color: #007bff;
-      background-color: #ffffff;
-    }
-
-    textarea:disabled {
-      background-color: #e9ecef;
-      color: #6c757d;
-      cursor: not-allowed;
-    }
-
-    textarea::placeholder {
-      color: #6c757d;
-    }
-
-    .send-button {
-      flex-shrink: 0;
-      width: 40px;
-      height: 40px;
-      border: none;
-      border-radius: 50%;
-      background-color: #007bff;
-      color: white;
-      cursor: pointer;
-      display: flex;
-      align-items: center;
-      justify-content: center;
-      font-size: 18px;
-      transition:
-        background-color 0.2s,
-        transform 0.1s;
-      outline: none;
-    }
-
-    .send-button:hover:not(:disabled) {
-      background-color: #0056b3;
-    }
-
-    .send-button:active:not(:disabled) {
-      transform: scale(0.95);
-    }
-
-    .send-button:disabled {
-      background-color: #6c757d;
-      cursor: not-allowed;
-      opacity: 0.6;
-    }
-
-    .send-icon {
-      width: 16px;
-      height: 16px;
-      fill: currentColor;
-    }
-
-    /* iOS specific adjustments */
-    @supports (-webkit-touch-callout: none) {
-      textarea {
-        font-size: 16px; /* Prevent zoom on iOS */
-      }
-    }
-
-    /* Upload progress indicator */
-    .upload-progress {
-      position: absolute;
-      top: -30px;
-      left: 50%;
-      transform: translateX(-50%);
-      background-color: #fff9c4;
-      color: #856404;
-      padding: 4px 8px;
-      border-radius: 4px;
-      font-size: 12px;
-      white-space: nowrap;
-      box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
-      z-index: 1000;
-    }
-  `;
-
   private handleInput = (e: Event) => {
     const target = e.target as HTMLTextAreaElement;
     this.inputValue = target.value;
@@ -294,42 +170,51 @@
       this.uploadsInProgress === 0;
 
     return html`
-      <div class="input-container">
-        <div class="input-wrapper">
-          <textarea
-            ${ref(this.textareaRef)}
-            .value=${this.inputValue}
-            @input=${this.handleInput}
-            @keydown=${this.handleKeyDown}
-            placeholder="Message Sketch..."
-            ?disabled=${this.disabled || this.uploadsInProgress > 0}
-            rows="1"
-          ></textarea>
+      <div
+        class="block bg-white border-t border-gray-200 p-3 relative z-[1000]"
+        style="padding-bottom: max(12px, env(safe-area-inset-bottom)); padding-left: max(16px, env(safe-area-inset-left)); padding-right: max(16px, env(safe-area-inset-right));"
+      >
+        <div class="flex items-end gap-3 max-w-full">
+          <div class="flex-1 relative min-w-0">
+            <textarea
+              ${ref(this.textareaRef)}
+              .value=${this.inputValue}
+              @input=${this.handleInput}
+              @keydown=${this.handleKeyDown}
+              placeholder="Message Sketch..."
+              ?disabled=${this.disabled || this.uploadsInProgress > 0}
+              rows="1"
+              class="w-full min-h-[40px] max-h-[120px] p-3 border border-gray-300 rounded-[20px] text-base font-inherit leading-relaxed resize-none outline-none bg-gray-50 transition-colors duration-200 box-border focus:border-blue-500 focus:bg-white disabled:bg-gray-200 disabled:text-gray-500 disabled:cursor-not-allowed placeholder:text-gray-500"
+              style="font-size: 16px;"
+            ></textarea>
 
-          ${this.showUploadProgress
-            ? html`
-                <div class="upload-progress">
-                  Uploading ${this.uploadsInProgress}
-                  file${this.uploadsInProgress > 1 ? "s" : ""}...
-                </div>
-              `
-            : ""}
+            ${this.showUploadProgress
+              ? html`
+                  <div
+                    class="absolute -top-8 left-1/2 transform -translate-x-1/2 bg-yellow-50 text-yellow-800 px-2 py-1 rounded text-xs whitespace-nowrap shadow-sm z-[1000]"
+                  >
+                    Uploading ${this.uploadsInProgress}
+                    file${this.uploadsInProgress > 1 ? "s" : ""}...
+                  </div>
+                `
+              : ""}
+          </div>
+
+          <button
+            class="flex-shrink-0 w-10 h-10 border-none rounded-full bg-blue-500 text-white cursor-pointer flex items-center justify-center text-lg transition-all duration-200 outline-none hover:bg-blue-600 active:scale-95 disabled:bg-gray-500 disabled:cursor-not-allowed disabled:opacity-60"
+            @click=${this.sendMessage}
+            ?disabled=${!canSend}
+            title=${this.uploadsInProgress > 0
+              ? "Please wait for upload to complete"
+              : "Send message"}
+          >
+            ${this.uploadsInProgress > 0
+              ? html`<span class="text-xs">⏳</span>`
+              : html`<svg class="w-4 h-4 fill-current" viewBox="0 0 24 24">
+                  <path d="M2,21L23,12L2,3V10L17,12L2,14V21Z" />
+                </svg>`}
+          </button>
         </div>
-
-        <button
-          class="send-button"
-          @click=${this.sendMessage}
-          ?disabled=${!canSend}
-          title=${this.uploadsInProgress > 0
-            ? "Please wait for upload to complete"
-            : "Send message"}
-        >
-          ${this.uploadsInProgress > 0
-            ? html`<span style="font-size: 12px;">⏳</span>`
-            : html`<svg class="send-icon" viewBox="0 0 24 24">
-                <path d="M2,21L23,12L2,3V10L17,12L2,14V21Z" />
-              </svg>`}
-        </button>
       </div>
     `;
   }