# FurryPlace Plugins

This directory contains plugins that extend the FurryPlace UI using the FurryPlace SDK.

## Quick Start

1. **Create a plugin file** in this directory (e.g., `my-plugin.js`)
2. **Refresh the page** - Plugins are automatically discovered and loaded!
3. **Use the SDK** to register buttons or add other functionality

> **✨ Auto-Loading:** Plugins are automatically discovered from the `frontend-backup/plugins/` directory. Any `.js` file you add here will be automatically loaded when the page loads. No need to manually edit HTML files!

## Example Plugins

### [example-button.js](./example-button.js)
Complete example showing how to:
- Add custom buttons with icons
- Handle click events
- Use conditional rendering
- Apply custom styling
- Open external links

### [example-user-state.js](./example-user-state.js)
Demonstrates user state access:
- Display user stats and information
- Show droplet counter
- Monitor paint charge status
- Display level progress
- Conditional rendering based on login status
- Refresh user data from server

### [example-info-modal.js](./example-info-modal.js)
Demonstrates info modal customization:
- Add custom sections to the info modal
- Display announcements and updates
- Show server statistics
- Add custom links and resources
- Display user-specific information
- Create interactive collapsible sections
- Add tips, tricks, and changelogs

## Captcha Plugins

FurryPlace includes built-in support for captcha verification at different points:
- **Login**: When logging into an existing account
- **Registration**: When creating a new account
- **Paint**: When painting pixels (can be configured to require every N paints)
- **Google OAuth**: Before initiating Google OAuth flow

### [captcha-turnstile.js](./captcha-turnstile.js)
Cloudflare Turnstile captcha integration:
- Free and privacy-friendly
- Lightweight and fast
- Automatic theme detection
- Configure per verification point

**Configuration:**
```javascript
window.FURRYPLACE_CAPTCHA_CONFIG = {
  login: {
    enabled: true,
    siteKey: 'your-turnstile-site-key',
    theme: 'auto', // 'light', 'dark', or 'auto'
  },
  register: {
    enabled: true,
    siteKey: 'your-turnstile-site-key',
    theme: 'auto',
  },
  paint: {
    enabled: true,
    siteKey: 'your-turnstile-site-key',
    theme: 'dark',
  },
  googleOAuth: {
    enabled: true,
    siteKey: 'your-turnstile-site-key',
    theme: 'light',
  }
};
```

### [captcha-hcaptcha.js](./captcha-hcaptcha.js)
hCaptcha integration:
- Privacy-focused alternative
- Customizable themes and sizes
- Supports compact mode

**Configuration:**
```javascript
window.FURRYPLACE_CAPTCHA_CONFIG = {
  login: {
    enabled: true,
    siteKey: 'your-hcaptcha-site-key',
    theme: 'light', // 'light' or 'dark'
    size: 'normal', // 'normal', 'compact', or 'invisible'
  },
  register: {
    enabled: true,
    siteKey: 'your-hcaptcha-site-key',
    theme: 'dark',
    size: 'normal',
  },
  paint: {
    enabled: true,
    siteKey: 'your-hcaptcha-site-key',
    theme: 'dark',
    size: 'compact',
  }
};
```

### [captcha-recaptcha.js](./captcha-recaptcha.js)
Google reCAPTCHA v2 and v3 integration:
- Support for both v2 (checkbox) and v3 (invisible)
- Per-action configuration for v3
- Customizable themes for v2

**Configuration:**
```javascript
window.FURRYPLACE_CAPTCHA_CONFIG = {
  login: {
    enabled: true,
    siteKey: 'your-recaptcha-site-key',
    version: 'v2', // 'v2' or 'v3'
    theme: 'light', // v2 only
    size: 'normal', // v2 only
    action: 'login', // v3 only
  },
  paint: {
    enabled: true,
    siteKey: 'your-recaptcha-v3-site-key',
    version: 'v3',
    action: 'paint',
  }
};
```

**Important:** Only load ONE captcha plugin at a time. Choose the one that matches your backend configuration.

## FurryPlace SDK API

### Button Registration

#### `window.FurryPlaceSDK.registerButton(config)`

Register a custom button in the UI.

**Configuration Object:**

```javascript
{
  id: string,              // Required: Unique identifier
  title: string,           // Required: Tooltip text
  icon: string|HTMLElement, // Required: SVG string or DOM element
  onClick: Function,       // Required: (context, event) => {}
  position: string,        // Optional: 'top', 'bottom', 'before-leaderboard', 'after-leaderboard'
  className: string,       // Optional: Custom CSS classes (default: 'btn btn-square shadow-md')
  wrapperClass: string,    // Optional: Wrapper div CSS classes
  condition: Function,     // Optional: (context) => boolean
  disabled: boolean        // Optional: Whether button is disabled
}
```

**Positions:**
- `'top'` - First button in the container
- `'bottom'` - Last button in the container
- `'before-leaderboard'` - Before the leaderboard button
- `'after-leaderboard'` - After the leaderboard button (default)

**Context Object** (passed to `onClick` and `condition`):

```javascript
{
  sdk: {
    version: string,                    // SDK version
    refreshUserState: Function          // Function to refresh user state
  },
  user: {
    isLoggedIn: boolean,                // User login status
    id: number|null,                    // User ID
    name: string|null,                  // Display name
    discord: string|null,               // Discord username
    country: string|null,               // User's country
    banned: boolean,                    // Is user banned
    role: string|null,                  // User role (admin/moderator/user)
    level: number,                      // Current level
    pixelsPainted: number,              // Total pixels painted
    droplets: number,                   // Currency amount
    picture: string|null,               // Profile picture URL
    equippedFlag: string|null,          // Equipped country flag
    allianceId: number|null,            // Alliance ID
    allianceRole: string|null,          // Alliance role
    charges: {
      current: number,                  // Current paint charges
      max: number,                      // Maximum charges
      cooldownMs: number,               // Cooldown in milliseconds
      percentage: number                // Charge percentage (0-100)
    },
    hasChargesToPaint: boolean,         // Can paint (>= 1 charge)
    needsPhoneVerification: boolean,    // Needs phone verification
    isTimedOut: boolean,                // Currently timed out
    timeoutUntil: string|null           // Timeout expiry ISO string
  },
  map: {}                               // Map context (future use)
}
```

### User State Methods

#### `await FurryPlaceSDK.getUser()`
Get full user data (async). Returns user object or `null` if not logged in.

#### `await FurryPlaceSDK.refreshUser()`
Refresh user state from server and return updated data.

#### `FurryPlaceSDK.isLoggedIn()`
Returns `true` if user is logged in.

#### `FurryPlaceSDK.getDroplets()`
Get user's droplet count (currency).

#### `FurryPlaceSDK.getCharges()`
Get paint charge information:
```javascript
{
  current: number,    // Current charges
  max: number,        // Max capacity
  cooldownMs: number, // Recharge cooldown
  percentage: number  // Fill percentage (0-100)
}
```

#### `FurryPlaceSDK.hasChargesToPaint()`
Returns `true` if user has at least 1 charge.

#### `FurryPlaceSDK.getLevel()`
Get user's current level.

#### `FurryPlaceSDK.getPixelsPainted()`
Get total pixels painted by user.

#### `FurryPlaceSDK.getAllianceId()`
Get user's alliance ID or `null`.

#### `FurryPlaceSDK.isInAlliance()`
Returns `true` if user is in an alliance.

### Info Modal Customization

#### `FurryPlaceSDK.addInfoSection(config)`
Add a custom section to the info modal (the one with rules and YouTube video).

**Configuration Object:**
```javascript
{
  id: string,                    // Required: Unique identifier
  title: string,                 // Optional: Section title
  content: string|HTMLElement|Function, // Required: HTML string, DOM element, or function
  position: string,              // Optional: 'top', 'bottom', 'after-video'
  className: string              // Optional: Custom CSS classes for section
}
```

**Positions:**
- `'top'` - At the beginning of the modal
- `'bottom'` - At the end (before footer)
- `'after-video'` - Right after the YouTube video section

**Content Types:**
- **String**: HTML string that will be inserted
- **HTMLElement**: DOM element to append
- **Function**: Function that returns a string or HTMLElement

#### `FurryPlaceSDK.removeInfoSection(id)`
Remove a custom section from the info modal.

#### `FurryPlaceSDK.getInfoSections()`
Get list of all registered info sections.

### Captcha Methods

#### `FurryPlaceSDK.registerCaptchaCallback(verificationPoint, callback)`
Register a callback function that provides captcha tokens for a specific verification point.

**Parameters:**
- `verificationPoint`: One of `'login'`, `'register'`, `'googleOAuth'`, or `'paint'`
- `callback`: Async function that returns a captcha token string

**Example:**
```javascript
window.FurryPlaceSDK.registerCaptchaCallback('login', async () => {
  // Show captcha UI and wait for user to complete it
  const token = await showCaptchaWidget();
  return token;
});
```

#### `await FurryPlaceSDK.requestCaptcha(verificationPoint)`
Request a captcha token for a specific verification point. This calls the registered callback.

**Returns:** Promise that resolves to a captcha token string or `null` if no callback is registered.

#### `FurryPlaceSDK.hasCaptchaCallback(verificationPoint)`
Check if a captcha callback is registered for a verification point.

**Returns:** `true` if callback is registered, `false` otherwise.

#### `FurryPlaceSDK.unregisterCaptchaCallback(verificationPoint)`
Remove a captcha callback for a verification point.

#### `FurryPlaceSDK.getCaptchaCallbacks()`
Get list of all verification points with registered callbacks.

**Returns:** Array of strings (verification point names).

### Other SDK Methods

- `FurryPlaceSDK.unregisterButton(id)` - Remove a registered button
- `FurryPlaceSDK.getButtons()` - Get list of all registered buttons
- `FurryPlaceSDK.getContext()` - Get current context object
- `FurryPlaceSDK.init()` - Manually initialize SDK (usually automatic)

## Example Usage

### Basic Button

```javascript
(function() {
  'use strict';

  function waitForSDK(callback) {
    if (window.FurryPlaceSDK) {
      callback();
    } else {
      setTimeout(() => waitForSDK(callback), 100);
    }
  }

  waitForSDK(() => {
    window.FurryPlaceSDK.registerButton({
      id: 'my-custom-button',
      title: 'My Button',
      position: 'bottom',
      icon: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960" fill="currentColor" class="size-5">
        <path d="M480-80q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Z"/>
      </svg>`,
      onClick: (context) => {
        alert('Button clicked!');
      }
    });
  });
})();
```

### Accessing User State

```javascript
waitForSDK(() => {
  // Synchronous methods
  console.log('Logged in:', window.FurryPlaceSDK.isLoggedIn());
  console.log('Droplets:', window.FurryPlaceSDK.getDroplets());
  console.log('Level:', window.FurryPlaceSDK.getLevel());

  // Async method for full user data
  window.FurryPlaceSDK.getUser().then(user => {
    if (user) {
      console.log('User name:', user.name);
      console.log('Alliance:', user.allianceId);
    }
  });
});
```

### Button with User State Condition

```javascript
// Only show button if user is logged in and has charges
window.FurryPlaceSDK.registerButton({
  id: 'paint-helper',
  title: 'Quick Paint',
  position: 'top',
  icon: `...`,
  onClick: async (context) => {
    const charges = context.user.charges;
    alert(`You have ${charges.current}/${charges.max} charges`);
  },
  condition: (context) => {
    return context.user?.isLoggedIn && context.user?.hasChargesToPaint;
  }
});
```

### Charge Monitor Example

```javascript
window.FurryPlaceSDK.registerButton({
  id: 'charge-monitor',
  title: 'Charge Monitor',
  position: 'bottom',
  icon: `...`,
  onClick: () => {
    const charges = window.FurryPlaceSDK.getCharges();
    alert(`
      Current: ${charges.current}/${charges.max}
      Percentage: ${charges.percentage.toFixed(1)}%
      Can Paint: ${window.FurryPlaceSDK.hasChargesToPaint() ? 'Yes' : 'No'}
    `);
  },
  condition: (context) => context.user?.isLoggedIn
});
```

### Info Modal Section Examples

#### Simple HTML Section
```javascript
window.FurryPlaceSDK.addInfoSection({
  id: 'welcome-message',
  title: 'Welcome!',
  position: 'top',
  content: '<p class="text-sm">Welcome to our server!</p>'
});
```

#### Dynamic Content with Function
```javascript
window.FurryPlaceSDK.addInfoSection({
  id: 'user-stats',
  title: 'Your Stats',
  position: 'after-video',
  content: () => {
    const level = window.FurryPlaceSDK.getLevel();
    const droplets = window.FurryPlaceSDK.getDroplets();

    return `
      <div class="text-sm">
        <p>Level: ${level}</p>
        <p>Droplets: ${droplets}</p>
      </div>
    `;
  }
});
```

#### Interactive Section with DOM Elements
```javascript
window.FurryPlaceSDK.addInfoSection({
  id: 'interactive-section',
  title: 'Custom Links',
  position: 'bottom',
  content: () => {
    const div = document.createElement('div');
    div.className = 'flex gap-2';

    const button = document.createElement('button');
    button.className = 'btn btn-sm btn-primary';
    button.textContent = 'Click Me';
    button.onclick = () => alert('Button clicked!');

    div.appendChild(button);
    return div;
  }
});
```

## Finding SVG Icons

You can find Material Design icons at:
- https://fonts.google.com/icons

Copy the SVG code and use it as the `icon` parameter.

## Tips

1. **Wrap your plugin in an IIFE** to avoid polluting the global scope
2. **Wait for the SDK** to be available before registering buttons
3. **Use unique IDs** to avoid conflicts with other plugins
4. **Test button positions** to find the best placement for your use case
5. **Check the console** for SDK loading messages and errors

## How Auto-Loading Works

The SDK automatically:
1. Calls `/api/plugins` to get a list of all `.js` files in the `plugins/` directory
2. Dynamically loads each plugin by creating `<script>` tags
3. Reports loading progress in the browser console

You can check which plugins are loaded by looking at the console:
```
[FurryPlace SDK] Auto-discovering plugins...
[FurryPlace SDK] Found 2 plugin(s): ['example-button.js', 'my-plugin.js']
[FurryPlace SDK] Loaded plugin: example-button.js
[FurryPlace SDK] Loaded plugin: my-plugin.js
```

> **Note:** If you need to disable a plugin, either delete it or rename it to something other than `.js` (like `.js.disabled`)

## Troubleshooting

**Button not appearing?**
- Check the browser console for errors
- Verify the SDK is loaded: `console.log(window.FurryPlaceSDK)`
- Ensure your button ID is unique
- Try different positions

**Button appearing in wrong place?**
- Try different `position` values
- Check if the button container has changed in the UI

**Icon not showing?**
- Verify your SVG has `viewBox` and `fill="currentColor"`
- Add `class="size-5"` to the SVG element
- Check for syntax errors in the SVG string
