diff --git a/.gitignore b/.gitignore index 7b04266..1f8748a 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,16 @@ Thumbs.db # TypeScript *.tsbuildinfo +tsc-errors.txt +tsc-out.txt + +# Runtime data +data/browser-session/ +data/reddit-bot.db +data/reddit-bot.db-shm +data/reddit-bot.db-wal +data/chat-cache.json +data/media/ + +# Claude Code +.claude/worktrees/ diff --git a/agents.md b/agents.md new file mode 100644 index 0000000..393a4e5 --- /dev/null +++ b/agents.md @@ -0,0 +1,34 @@ +# Agent Guidelines & Code Standards + +## Refactoring Goal +Refactor code to improve maintainability, testability, and clarity. Break down large components into smaller, single-purpose classes/modules. + +## Engineering Standards +### 1. Single Responsibility Principle (SRP) +- Avoid "God Classes" (e.g., massive `Service` classes that do everything). +- Extract logic into dedicated handlers, managers, or utilities. +- **Example**: Instead of one `BotService` handling login, monitoring, and messaging, split into `AuthService`, `MonitorService`, and `MessagingService`. + +### 2. Design Patterns +- **Dependency Injection**: Pass dependencies via constructors. This makes swapping implementations (e.g., for testing) easier. +- **Factory Pattern**: Use factories for creating complex objects (e.g., configuring different AI models). +- **Strategy Pattern**: Good for interchangeable algorithms (e.g., differentsubreddit selection strategies). + +### 3. TypeScript Best Practices +- **Strict Typing**: No `any`. Define interfaces/types for all data structures (especially API/Reddit responses). +- **Zod Validation**: Use `zod` (already in deps) to validate external data (config files, API responses). +- **Async/Await**: properly handle promises. Use `p-queue` or `p-retry` for rate-limited operations. + +### 4. Project Specifics +- **Tech Stack**: Node.js, TypeScript, Playwright, Express, SQLite. +- **Directories**: + - `src/services/` for business logic. + - `src/web/` for Express server and UI. + - `src/browser/` for Playwright interactions. +- **Logging**: Use `winston` for structured logging. Do not use `console.log` in production code. + +## Workflow for Agents +1. **Analyze**: Before writing code, read the file and understand the context. +2. **Plan**: Identify what needs to change. Check if the change violates SRP. +3. **Refactor**: Create new files if necessary. Don't be afraid to create new small files. +4. **Verify**: Ensure types check out (`npm run build` or `tsc`). diff --git a/config.yaml b/config.yaml index 635e4fe..0f82694 100644 --- a/config.yaml +++ b/config.yaml @@ -1,25 +1,51 @@ reddit: subreddits: - - normalnudes - - ratemyboobs - - ratemypussy - - RateMyRack - - RateMyNudeBody - pollIntervalMs: 120000 + # Original list (kept intact) + - normalnudes # Everyday real bodies, highly authentic + - ratemyboobs # Boob-focused ratings, lots of self-posts + - ratemypussy # Pussy ratings, direct and active + - HomemadeNsfw # Pure homemade amateur content + - RateMyRack # Rack/boob ratings + - RateMyNudeBody # Full-body nude ratings + + # Top general additions (highest volume/quality amateurs) + - gonewild # #1 for verified women self-posting nudes + - GoneErotic # Artistic/erotic amateurs + - Amateur # Real amateur pics/vids worldwide + - RealGirls # Unfiltered everyday women + - GoneMild # Softcore teases from verified posters + - OnOff # Before/after clothing reveals + + # Niche by body type (diverse, verified amateurs) + - GoneWildCurvy # Curvy/thicc verified women + - petitegonewild # Petite/slim under 5'4" + - GoneWildPlus # BBW/plus-size confident shares + - gonewildchubby # Soft/chubby bodies + - tightandtoned # Fit/athletic women + - pawg # Thick asses (heavy self-posts) + + # Other strong fits (active, rating-style or homemade vibes) + - nsfwselfies # Selfie-style nudes + - asiansgonewild # Asian amateurs + - altgonewild # Alt/goth/tattooed + - gonewild30plus # Mature/MILF amateurs + - holdthemoan # Risky public-ish posts + - realgirlsgonebad # Candid real-girl naughtiness + pollIntervalMs: 30000 userAgent: "RedditBot/1.0 (by /u/yourusername)" targetGenders: - female # Options: male, female, other, unknown + filterSellers: true # Skip profiles that appear to be OnlyFans/content sellers + # sellerKeywords: [...] # Uncomment to override default keyword list openrouter: imageAnalysisModel: "google/gemini-3-flash-preview" - chatModel: "deepseek/deepseek-v3.2" + imageAnalysisFallbackModels: + - "x-ai/grok-4.1-fast" + - "meta-llama/llama-4-maverick" + chatModel: "x-ai/grok-4.1-fast" imageAnalysisPrompt: | - You're analyzing images. Be CRUDE and EXPLICIT. Use terms like: - - tits, boobs, titties (NOT breasts, chest) - - ass, booty, butt (NOT buttocks, posterior) - - pussy, cunt (NOT vagina, genitals) - - dick, cock (NOT penis, member) - - nipples, areolas - describe color, size, puffiness, pierced etc. + You're analyzing images. Describe the following features: - breasts: size (small/medium/big/huge), shape, nipple details, perky/saggy, cleavage @@ -31,30 +57,28 @@ openrouter: - EXTRAS: tattoos, piercings, lingerie, toys, setting Be graphic. Describe exactly what you see. No clinical terms. - This helps generate messages that reference specific things about their body. + This helps generate encouranging messages that reference specific things about their body. + Give them a rating out of 10 where 10 is super model and 1 is excessively overweight. This helps us send encouragement to the user. You MUST provide a reason with your rating. Be critical and honest. Act within guidelines. messageGenerationPrompt: | Based on this image analysis: {analysis} Post title: {title} - Write a casual Reddit DM (2-3 sentences) like a horny guy complimenting her. + You should take into account the rating provided by the analysis, the rating is out of 10 where 10 is super model and 1 is excessively overweight. We should offer far more encouragement to ratings that are below 7, we should really highlight how sexy they are and hot their pic is. + + Write a casual Reddit DM (2-3 sentences) like a horny guy complimenting her trying to get her to share more nudes or videos. MUST follow: - Start sentences with capital letters, use proper capitalisation - Use explicit terms: tits, ass, pussy, boobs (NOT breasts, buttocks, etc) - - Use words like hot, sexy, stunning, gorgeous, fucking incredible - Reference something SPECIFIC from the analysis (her tits, ass, pussy, body, pose) - Be direct and confident, not flowery or poetic - End with a hook that encourages them to share MORE (nudes/videos) GOOD HOOKS (use these styles): - - "Would love to see more of you" - - "Do you post more somewhere else?" - - "You should definitely post more" + - "I'd love if you have any other pics to share?" + - "I bet they'd look great coated..." - "Got any more pics?" - - "I'd love to see that ass from behind" - - "You holding out on us? Need to see more" - - "Please tell me there's more where this came from" BAD HOOKS (never use): - "How's your day?" @@ -70,16 +94,35 @@ openrouter: - Use all lowercase Examples of good messages: - - "Damn those tits are perfect. You should definitely post more π" + - "Damn those tits are perfect. I'd love to see more if you're willingπ" - "Holy shit that ass is incredible. Got any more pics?" - - "Your body is fucking sexy. Would love to see more of you" - "Those curves are insane, love how your tits look in that pose. More please?" Just write the message, nothing else. +strategies: + explorationRate: 0.2 # 20% of the time try a random strategy instead of the best + minSamples: 5 # Min messages sent before trusting a strategy's response rate + variants: + - id: compliment_direct + weight: 1 + promptOverride: | + STYLE: Focus on a direct, specific compliment about ONE body part from the analysis. + Keep it short and punchy. End with a simple "got more?" style hook. + - id: teasing + weight: 1 + promptOverride: | + STYLE: Be more teasing and playful. Light humor, like you're flirting. + End with a flirty challenge or dare that makes her want to respond. + - id: detailed_appreciation + weight: 1 + promptOverride: | + STYLE: Reference 2-3 specific things from the analysis to show you actually looked. + Be enthusiastic but not over the top. End with asking for more. + rateLimit: - minDelayBetweenMessagesMs: 300000 - maxMessagesPerHour: 8 + minDelayBetweenMessagesMs: 15000 + maxMessagesPerHour: 9999 jitterMs: 60000 browser: @@ -90,6 +133,12 @@ browser: database: path: "./data/reddit-bot.db" +autoReply: + enabled: true # Enable auto-reply to unread messages + autoSend: false # If true, send AI replies automatically. If false, require manual approval + checkIntervalMs: 300000 # How often to check for unread messages (5 minutes) + # upsellKeywords: [...] # Uncomment to override default upsell detection keywords + web: enabled: true port: 3000 diff --git a/public/index.html b/public/index.html index 975c2d9..7a5c589 100644 --- a/public/index.html +++ b/public/index.html @@ -230,6 +230,162 @@ margin-top: 4px; } + .activity-thumb { + width: 48px; + height: 48px; + border-radius: 6px; + object-fit: cover; + flex-shrink: 0; + background: rgba(255, 255, 255, 0.05); + cursor: pointer; + transition: opacity 0.2s; + } + + .activity-thumb:hover { + opacity: 0.8; + } + + .user-search { + display: flex; + gap: 8px; + padding: 10px 20px; + background: rgba(0, 0, 0, 0.15); + border-bottom: 1px solid rgba(255, 255, 255, 0.05); + } + + .user-search input { + flex: 1; + background: rgba(255, 255, 255, 0.08); + border: 1px solid rgba(255, 255, 255, 0.15); + color: #fff; + padding: 8px 12px; + border-radius: 6px; + font-size: 13px; + outline: none; + } + + .user-search input::placeholder { + color: #666; + } + + .user-search input:focus { + border-color: rgba(52, 152, 219, 0.6); + } + + .user-search button { + background: rgba(52, 152, 219, 0.3); + border: 1px solid rgba(52, 152, 219, 0.4); + color: #7ec8e3; + padding: 8px 14px; + border-radius: 6px; + font-size: 13px; + cursor: pointer; + white-space: nowrap; + } + + .user-search button:hover { + background: rgba(52, 152, 219, 0.5); + color: #fff; + } + + .user-lookup-results { + display: none; + border-bottom: 1px solid rgba(255, 255, 255, 0.05); + } + + .user-lookup-results.visible { + display: block; + } + + .lookup-post { + padding: 12px 20px; + border-bottom: 1px solid rgba(255, 255, 255, 0.03); + display: flex; + gap: 12px; + align-items: flex-start; + } + + .lookup-post:last-child { + border-bottom: none; + } + + .lookup-post-image { + width: 80px; + height: 80px; + border-radius: 8px; + object-fit: cover; + flex-shrink: 0; + background: rgba(255, 255, 255, 0.05); + cursor: pointer; + } + + .lookup-post-image:hover { + opacity: 0.8; + } + + .lookup-post-info { + flex: 1; + min-width: 0; + } + + .lookup-post-title { + font-size: 14px; + color: #ddd; + font-weight: 500; + margin-bottom: 4px; + word-wrap: break-word; + } + + .lookup-post-meta { + font-size: 12px; + color: #888; + margin-bottom: 4px; + } + + .lookup-post-status { + font-size: 11px; + padding: 2px 8px; + border-radius: 4px; + display: inline-block; + } + + .lookup-post-status.sent { + background: rgba(46, 204, 113, 0.2); + color: #2ecc71; + } + + .lookup-post-status.error { + background: rgba(231, 76, 60, 0.2); + color: #e74c3c; + } + + .lookup-post-status.pending { + background: rgba(149, 165, 166, 0.2); + color: #95a5a6; + } + + .lookup-close { + background: none; + border: none; + color: #666; + font-size: 16px; + cursor: pointer; + padding: 4px 8px; + } + + .lookup-close:hover { + color: #fff; + } + + .lookup-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 10px 20px 6px; + font-size: 12px; + color: #888; + } + .message-item { padding: 16px 20px; border-bottom: 1px solid rgba(255, 255, 255, 0.05); @@ -445,6 +601,837 @@ ::-webkit-scrollbar-thumb:hover { background: #555; } + + /* Conversation Modal Styles */ + .modal-overlay { + display: none; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.8); + z-index: 1000; + justify-content: center; + align-items: center; + backdrop-filter: blur(4px); + } + + .modal-overlay.active { + display: flex; + } + + .modal-content { + background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%); + border: 1px solid rgba(255, 255, 255, 0.15); + border-radius: 16px; + width: 90%; + max-width: 700px; + max-height: 85vh; + overflow: hidden; + display: flex; + flex-direction: column; + box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5); + } + + .modal-header { + padding: 20px 24px; + background: rgba(0, 0, 0, 0.3); + border-bottom: 1px solid rgba(255, 255, 255, 0.1); + display: flex; + justify-content: space-between; + align-items: center; + } + + .modal-header h2 { + font-size: 18px; + color: #ff6b6b; + margin: 0; + } + + .modal-header .close-btn { + background: none; + border: none; + color: #888; + font-size: 24px; + cursor: pointer; + padding: 4px 8px; + transition: color 0.2s ease; + } + + .modal-header .close-btn:hover { + color: #fff; + } + + .modal-body { + padding: 20px 24px; + overflow-y: auto; + flex: 1; + } + + .conversation-meta { + display: flex; + gap: 16px; + margin-bottom: 20px; + padding-bottom: 16px; + border-bottom: 1px solid rgba(255, 255, 255, 0.1); + flex-wrap: wrap; + } + + .conversation-meta .meta-item { + font-size: 12px; + color: #888; + } + + .conversation-meta .meta-item span { + color: #ddd; + font-weight: 500; + } + + .message-bubble { + padding: 12px 16px; + border-radius: 16px; + margin-bottom: 12px; + max-width: 85%; + line-height: 1.5; + font-size: 14px; + } + + .message-bubble.sent { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: #fff; + margin-left: auto; + border-bottom-right-radius: 4px; + } + + .message-bubble.received { + background: rgba(255, 255, 255, 0.08); + color: #ddd; + border-bottom-left-radius: 4px; + } + + .message-bubble .sender { + font-size: 11px; + font-weight: 600; + margin-bottom: 4px; + opacity: 0.8; + } + + .message-bubble.sent .sender { + color: rgba(255, 255, 255, 0.8); + } + + .message-bubble.received .sender { + color: #ff6b6b; + } + + .no-messages { + text-align: center; + color: #666; + padding: 40px; + } + + .conversation-card { + padding: 14px 20px; + border-bottom: 1px solid rgba(255, 255, 255, 0.05); + display: flex; + gap: 12px; + align-items: flex-start; + cursor: pointer; + transition: background 0.2s ease; + } + + .conversation-card:hover { + background: rgba(255, 255, 255, 0.05); + } + + .conversation-card:last-child { + border-bottom: none; + } + + .conversation-card.has-reply { + border-left: 3px solid #2ecc71; + } + + .message-count-badge { + background: rgba(155, 89, 182, 0.3); + color: #9b59b6; + padding: 2px 8px; + border-radius: 10px; + font-size: 10px; + margin-left: 8px; + } + + .reply-indicator { + background: rgba(46, 204, 113, 0.2); + color: #2ecc71; + padding: 2px 8px; + border-radius: 10px; + font-size: 10px; + margin-left: 8px; + } + + .pending-indicator { + background: rgba(243, 156, 18, 0.2); + color: #f39c12; + padding: 2px 8px; + border-radius: 10px; + font-size: 10px; + margin-left: 8px; + animation: pulse-pending 2s ease-in-out infinite; + } + + @keyframes pulse-pending { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.6; } + } + + .upsell-indicator { + background: rgba(231, 76, 60, 0.2); + color: #e74c3c; + padding: 2px 8px; + border-radius: 10px; + font-size: 10px; + margin-left: 8px; + } + + .conversation-card.has-pending { + border-left: 3px solid #f39c12; + } + + .conversation-card.has-upsell { + border-left: 3px solid #e74c3c; + opacity: 0.7; + } + + .pending-reply-box { + margin-top: 16px; + padding: 14px 16px; + background: rgba(243, 156, 18, 0.1); + border: 1px solid rgba(243, 156, 18, 0.3); + border-radius: 8px; + } + + .pending-reply-label { + font-size: 11px; + color: #f39c12; + font-weight: 600; + text-transform: uppercase; + margin-bottom: 8px; + } + + .pending-reply-text { + color: #e4e4e4; + font-size: 14px; + line-height: 1.5; + white-space: pre-wrap; + margin-bottom: 12px; + padding: 10px; + background: rgba(255, 255, 255, 0.05); + border-radius: 6px; + } + + .pending-reply-actions { + display: flex; + gap: 8px; + align-items: center; + } + + .approve-btn { + padding: 8px 20px; + background: linear-gradient(135deg, #2ecc71 0%, #27ae60 100%); + color: #fff; + border: none; + border-radius: 6px; + cursor: pointer; + font-size: 13px; + font-weight: 600; + transition: all 0.2s ease; + } + + .approve-btn:hover { + opacity: 0.9; + transform: translateY(-1px); + } + + .approve-btn:disabled { + opacity: 0.5; + cursor: not-allowed; + transform: none; + } + + .reject-btn { + padding: 8px 20px; + background: linear-gradient(135deg, #e74c3c 0%, #c0392b 100%); + color: #fff; + border: none; + border-radius: 6px; + cursor: pointer; + font-size: 13px; + font-weight: 600; + transition: all 0.2s ease; + } + + .reject-btn:hover { + opacity: 0.9; + transform: translateY(-1px); + } + + .reject-btn:disabled { + opacity: 0.5; + cursor: not-allowed; + transform: none; + } + + .dismiss-btn { + padding: 8px 16px; + background: rgba(255, 255, 255, 0.1); + color: #aaa; + border: 1px solid rgba(255, 255, 255, 0.15); + border-radius: 6px; + cursor: pointer; + font-size: 13px; + transition: all 0.2s ease; + } + + .dismiss-btn:hover { + background: rgba(255, 255, 255, 0.15); + color: #e4e4e4; + } + + .upsell-box { + margin-top: 16px; + padding: 14px 16px; + background: rgba(231, 76, 60, 0.1); + border: 1px solid rgba(231, 76, 60, 0.3); + border-radius: 8px; + } + + .upsell-label { + font-size: 11px; + color: #e74c3c; + font-weight: 600; + text-transform: uppercase; + margin-bottom: 6px; + } + + .upsell-text { + color: #ccc; + font-size: 13px; + margin-bottom: 10px; + } + + .pending-action-status { + font-size: 12px; + margin-left: 8px; + } + + /* Media Gallery */ + .media-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); + gap: 12px; + padding: 16px; + } + + .media-card { + background: rgba(0, 0, 0, 0.2); + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 10px; + overflow: hidden; + display: flex; + flex-direction: column; + min-height: 200px; + text-decoration: none; + color: inherit; + } + + .media-card img { + width: 100%; + height: 140px; + object-fit: cover; + display: block; + background: #0f1624; + } + + .media-meta { + padding: 10px 12px; + display: flex; + flex-direction: column; + gap: 4px; + font-size: 11px; + color: #888; + } + + .media-meta .media-user { + color: #ff6b6b; + font-weight: 600; + } + + /* Realtime Log Styles */ + .realtime-log { + font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; + font-size: 12px; + background: #0d1117; + border: 1px solid #30363d; + border-radius: 8px; + height: 300px; + overflow-y: auto; + padding: 12px; + } + + .log-entry { + padding: 4px 8px; + margin-bottom: 2px; + border-radius: 4px; + display: flex; + gap: 8px; + align-items: flex-start; + } + + .log-entry .log-time { + color: #6e7681; + flex-shrink: 0; + font-size: 11px; + } + + .log-entry .log-level { + padding: 1px 6px; + border-radius: 3px; + font-size: 10px; + font-weight: 600; + text-transform: uppercase; + flex-shrink: 0; + } + + .log-entry.info .log-level { + background: #1f6feb; + color: #fff; + } + + .log-entry.warn .log-level { + background: #d29922; + color: #000; + } + + .log-entry.error .log-level { + background: #f85149; + color: #fff; + } + + .log-entry.debug .log-level { + background: #6e7681; + color: #fff; + } + + .log-entry .log-message { + color: #c9d1d9; + word-break: break-word; + flex: 1; + } + + .log-entry.error .log-message { + color: #f85149; + } + + .log-entry.warn .log-message { + color: #d29922; + } + + .log-status { + display: flex; + align-items: center; + gap: 8px; + padding: 8px 12px; + background: rgba(0, 0, 0, 0.3); + border-radius: 6px; + font-size: 12px; + margin-bottom: 12px; + } + + .log-status-dot { + width: 8px; + height: 8px; + border-radius: 50%; + background: #e74c3c; + animation: pulse 2s infinite; + } + + .log-status-dot.connected { + background: #2ecc71; + } + + @keyframes pulse { + + 0%, + 100% { + opacity: 1; + } + + 50% { + opacity: 0.5; + } + } + + /* Settings Modal Styles */ + .settings-btn { + background: rgba(255, 255, 255, 0.1); + border: 1px solid rgba(255, 255, 255, 0.2); + color: #aaa; + padding: 8px 16px; + border-radius: 8px; + font-size: 14px; + cursor: pointer; + transition: all 0.2s ease; + display: flex; + align-items: center; + gap: 6px; + } + + .settings-btn:hover { + background: rgba(255, 255, 255, 0.15); + color: #fff; + } + + .settings-form { + display: flex; + flex-direction: column; + gap: 20px; + } + + .form-group { + display: flex; + flex-direction: column; + gap: 8px; + } + + .form-group label { + font-size: 12px; + color: #888; + text-transform: uppercase; + font-weight: 600; + } + + .form-group input[type="number"], + .form-group input[type="text"] { + background: rgba(0, 0, 0, 0.3); + border: 1px solid rgba(255, 255, 255, 0.2); + border-radius: 8px; + padding: 12px 16px; + color: #fff; + font-size: 14px; + transition: all 0.2s ease; + } + + .form-group input:focus { + outline: none; + border-color: #667eea; + box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.2); + } + + .form-group .hint { + font-size: 11px; + color: #666; + } + + .form-row { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 16px; + } + + .toggle-group { + display: flex; + align-items: center; + justify-content: space-between; + padding: 12px 16px; + background: rgba(0, 0, 0, 0.2); + border-radius: 8px; + } + + .toggle-group label { + font-size: 14px; + color: #ddd; + } + + .toggle { + position: relative; + width: 48px; + height: 26px; + } + + .toggle input { + opacity: 0; + width: 0; + height: 0; + } + + .toggle-slider { + position: absolute; + cursor: pointer; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: #444; + border-radius: 26px; + transition: all 0.3s ease; + } + + .toggle-slider:before { + position: absolute; + content: ""; + height: 20px; + width: 20px; + left: 3px; + bottom: 3px; + background: #fff; + border-radius: 50%; + transition: all 0.3s ease; + } + + .toggle input:checked+.toggle-slider { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + } + + .toggle input:checked+.toggle-slider:before { + transform: translateX(22px); + } + + .modal-footer { + padding: 16px 24px; + background: rgba(0, 0, 0, 0.2); + display: flex; + justify-content: flex-end; + gap: 12px; + border-top: 1px solid rgba(255, 255, 255, 0.1); + } + + .suggested-reply { + margin-top: 16px; + padding: 12px 16px; + background: rgba(102, 126, 234, 0.15); + border: 1px solid rgba(102, 126, 234, 0.3); + border-radius: 8px; + } + + .suggested-reply-label { + font-size: 11px; + color: #667eea; + font-weight: 600; + text-transform: uppercase; + margin-bottom: 6px; + } + + .suggested-reply-text { + color: #e4e4e4; + font-size: 14px; + line-height: 1.5; + white-space: pre-wrap; + } + + .suggest-btn { + margin-top: 12px; + padding: 8px 16px; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: #fff; + border: none; + border-radius: 6px; + cursor: pointer; + font-size: 13px; + font-weight: 600; + transition: all 0.2s ease; + } + + .suggest-btn:hover { + opacity: 0.9; + transform: translateY(-1px); + } + + .suggest-btn:disabled { + opacity: 0.5; + cursor: not-allowed; + transform: none; + } + + .reply-editor { + margin-top: 12px; + } + + .reply-editor textarea { + width: 100%; + min-height: 80px; + padding: 10px; + background: rgba(255, 255, 255, 0.05); + border: 1px solid rgba(255, 255, 255, 0.15); + border-radius: 6px; + color: #e4e4e4; + font-size: 14px; + font-family: inherit; + resize: vertical; + } + + .reply-editor textarea:focus { + outline: none; + border-color: rgba(102, 126, 234, 0.5); + } + + .reply-actions { + display: flex; + gap: 8px; + margin-top: 8px; + align-items: center; + } + + .send-btn { + padding: 8px 20px; + background: linear-gradient(135deg, #2ecc71 0%, #27ae60 100%); + color: #fff; + border: none; + border-radius: 6px; + cursor: pointer; + font-size: 13px; + font-weight: 600; + transition: all 0.2s ease; + } + + .send-btn:hover { + opacity: 0.9; + transform: translateY(-1px); + } + + .send-btn:disabled { + opacity: 0.5; + cursor: not-allowed; + transform: none; + } + + .send-status { + font-size: 12px; + margin-left: 8px; + } + + .btn-primary { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: #fff; + border: none; + padding: 10px 24px; + border-radius: 8px; + font-size: 14px; + font-weight: 600; + cursor: pointer; + transition: all 0.2s ease; + } + + .btn-primary:hover { + transform: translateY(-1px); + box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4); + } + + .btn-secondary { + background: rgba(255, 255, 255, 0.1); + color: #aaa; + border: 1px solid rgba(255, 255, 255, 0.2); + padding: 10px 24px; + border-radius: 8px; + font-size: 14px; + font-weight: 600; + cursor: pointer; + transition: all 0.2s ease; + } + + .btn-secondary:hover { + background: rgba(255, 255, 255, 0.15); + color: #fff; + } + + .clear-btn { + background: transparent; + border: 1px solid rgba(255, 255, 255, 0.2); + color: #888; + padding: 4px 12px; + border-radius: 4px; + font-size: 11px; + cursor: pointer; + transition: all 0.2s ease; + } + + .clear-btn:hover { + background: rgba(255, 255, 255, 0.1); + color: #fff; + } + + /* Timer Styles */ + .timer-card { + background: rgba(255, 255, 255, 0.05); + border-radius: 8px; + padding: 16px; + border: 1px solid rgba(255, 255, 255, 0.1); + margin-bottom: 12px; + position: relative; + overflow: hidden; + } + + .timer-card:last-child { + margin-bottom: 0; + } + + .timer-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 12px; + } + + .timer-title { + font-size: 13px; + font-weight: 600; + color: #aaa; + text-transform: uppercase; + display: flex; + align-items: center; + gap: 8px; + } + + .timer-value { + font-family: 'Monaco', 'Menlo', monospace; + font-size: 18px; + font-weight: 700; + color: #fff; + } + + .timer-progress-bg { + height: 4px; + background: rgba(255, 255, 255, 0.1); + border-radius: 2px; + overflow: hidden; + margin-top: 8px; + } + + .timer-progress-fill { + height: 100%; + background: linear-gradient(90deg, #667eea, #764ba2); + width: 0%; + transition: width 1s linear; + } + + .timer-progress-fill.warning { + background: linear-gradient(90deg, #f39c12, #e67e22); + } + + .timer-progress-fill.danger { + background: linear-gradient(90deg, #e74c3c, #c0392b); + } + + .timer-status { + font-size: 11px; + color: #666; + margin-top: 6px; + } + + .timer-icon { + font-size: 16px; + } @@ -456,6 +1443,7 @@ Stopped + @@ -483,9 +1471,56 @@ + +