# 📖 Storyfyd SDK - Kompletna Dokumentacja

> Pełna dokumentacja techniczna JavaScript SDK do wyświetlania interaktywnych stories na stronach internetowych

## 📑 Spis Treści

1. [Wprowadzenie](#wprowadzenie)
2. [Instalacja i Integracja](#instalacja-i-integracja)
3. [Architektura SDK](#architektura-sdk)
4. [Konfiguracja](#konfiguracja)
5. [Komponenty i Interfejs](#komponenty-i-interfejs)
6. [Logika Działania](#logika-działania)
7. [Typy Elementów Story](#typy-elementów-story)
8. [System Trackingu](#system-trackingu)
9. [Obsługa Mediów](#obsługa-mediów)
10. [API Reference](#api-reference)
11. [Przykłady Użycia](#przykłady-użycia)
12. [Troubleshooting](#troubleshooting)

---

## 🎯 Wprowadzenie

### Czym jest Storyfyd SDK?

Storyfyd SDK to lekka, wydajna biblioteka JavaScript do wyświetlania interaktywnych stories na stronach internetowych. SDK jest zbudowane jako **Web Component** (Custom Element), co zapewnia:

- ✅ Pełną enkapsulację i izolację od reszty strony
- ✅ Łatwą integrację bez konfliktów z istniejącym kodem
- ✅ Native performance bez dodatkowych frameworków
- ✅ Kompatybilność ze wszystkimi nowoczesnymi przeglądarkami

### Kluczowe Funkcje

- 🎬 **Fullscreen Story Viewer** z płynną animacją
- 📊 **Auto-play z Progress Bars** dla każdego story
- 🎨 **6 Typów Interaktywnych Elementów** (text, button, image, emoji, rating, swipe-up)
- 📹 **Obsługa Video i Image Backgrounds**
- 📱 **Cross-Group Navigation** - nawigacja między grupami stories
- 📈 **Built-in Analytics Tracking** - tracking wyświetleń i interakcji
- 🔐 **Token-based Authentication** z headers SDK
- 🆔 **Device ID Tracking** - unikalna identyfikacja urządzeń
- 🎯 **Smart Auto-fitting** - automatyczne dopasowanie tekstów

---

## 🚀 Instalacja i Integracja

### Metoda 1: Bezpośrednie Dodanie do HTML

```html
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Moja Strona ze Stories</title>
</head>
<body>
    <!-- Umieść widget tam gdzie chcesz wyświetlić story circles -->
    <storyfyd-web id="my-stories"></storyfyd-web>

    <!-- Załaduj SDK -->
    <script src="/storyfyd-sdk.js"></script>
    
    <!-- Inicjalizuj widget -->
    <script>
        document.addEventListener('DOMContentLoaded', () => {
            const widget = document.querySelector('#my-stories');
            widget.init({
                token: 'YOUR_APPLICATION_TOKEN',
                apiBaseUrl: 'https://your-api.example.com',
                debug: false
            });
        });
    </script>
</body>
</html>
```

### Metoda 2: Dynamiczne Tworzenie

```javascript
// Stwórz element programatycznie
const storyfydWidget = document.createElement('storyfyd-web');
document.body.appendChild(storyfydWidget);

// Inicjalizuj z konfiguracją
storyfydWidget.init({
    token: 'YOUR_APPLICATION_TOKEN',
    apiBaseUrl: 'https://your-api.example.com',
    debug: true
});
```

### Wymagania

- **Przeglądarki**: Chrome 67+, Firefox 63+, Safari 10.1+, Edge 79+
- **JavaScript**: ES6+ (native support for Classes, async/await, fetch)
- **Custom Elements V1**: Natywne wsparcie Web Components

---

## 🏗️ Architektura SDK

### Struktura Głównej Klasy

```javascript
class StoryfydWebComponent extends HTMLElement {
    constructor() {
        // Inicjalizacja stanu
        this.config = { ...DEFAULT_CONFIG };
        this.storyGroups = [];      // Story groups z API
        this.stories = [];           // Aktualnie wyświetlane stories
        this.currentStoryIndex = 0;  // Indeks aktualnego story
        this.currentGroupIndex = 0;  // Indeks aktualnej grupy
        this.isFullscreen = false;   // Stan fullscreen
        this.isPaused = false;       // Stan pauzy
        this.deviceId = null;        // Unikalne ID urządzenia
        
        // Timery
        this.autoPlayTimer = null;   // Timer auto-play
        this.progressTimer = null;   // Timer progress bar
        
        // Tracking
        this.storyProgress = {};     // Progress każdego story
        this.pausedStories = new Set(); // Pauzowane stories
        this.trackedStories = new Set(); // Tracked stories (dla show events)
    }
}
```

### Cykl Życia Komponentu

```
1. Construction (new StoryfydWebComponent)
   ↓
2. init(config) - User Configuration
   ↓
3. loadStories() - Fetch Story Groups from API
   ↓
4. render() - Render Story Circles
   ↓
5. [User Click] → showFullscreen()
   ↓
6. updateStoryContent() - Render Story Elements
   ↓
7. startAutoPlay() + updateProgress()
   ↓
8. [Auto or Manual] → nextStory() / prevStory()
   ↓
9. [ESC or Close] → hideFullscreen()
```

### Device ID System

SDK automatycznie generuje i przechowuje unikalny ID urządzenia:

```javascript
getOrCreateDeviceId() {
    const DEVICE_ID_KEY = 'storyfyd_device_id';
    
    // Próba pobrania z localStorage
    let deviceId = localStorage.getItem(DEVICE_ID_KEY);
    
    // Jeśli nie istnieje - generuj nowy
    if (!deviceId) {
        deviceId = 'device_' + Date.now() + '_' + 
                   Math.random().toString(36).substring(2, 11);
        localStorage.setItem(DEVICE_ID_KEY, deviceId);
    }
    
    return deviceId;
}
```

**Format Device ID**: `device_1234567890_abc123xyz`

---

## ⚙️ Konfiguracja

### Domyślna Konfiguracja

```javascript
const DEFAULT_CONFIG = {
    apiBaseUrl: 'http://localhost:8081',
    templatePath: '/templates/template-xxx.json',
    token: null,
    debug: false
};
```

### Parametry Konfiguracji

#### `apiBaseUrl` (string, wymagany)
**Opis**: URL bazowy API do pobierania story groups  
**Domyślnie**: `'http://localhost:8081'`  
**Przykład**:
```javascript
widget.init({
    apiBaseUrl: 'https://api.storyfyd.com'
});
```

#### `token` (string, wymagany)
**Opis**: Token aplikacji do autoryzacji requestów API  
**Format**: JWT token  
**Nagłówek**: `X-Application-Token`  
**Przykład**:
```javascript
widget.init({
    token: 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9...'
});
```

#### `templatePath` (string, opcjonalny)
**Opis**: Ścieżka do pliku template JSON (legacy, obecnie nieużywane)  
**Domyślnie**: `'/templates/template-xxx.json'`

#### `debug` (boolean, opcjonalny)
**Opis**: Włącza szczegółowe logi w konsoli  
**Domyślnie**: `false`  
**Przykład**:
```javascript
widget.init({
    debug: true  // Włącza debugLog() w całym SDK
});
```

### Request Headers

SDK automatycznie dodaje następujące nagłówki do wszystkich requestów API:

```javascript
{
    'Content-Type': 'application/json',
    'X-Sdk-Type': 'js',                    // Typ SDK
    'X-Sdk-Version': '1.0.0',              // Wersja SDK
    'X-Device-Id': 'device_xxx',           // Unikalne ID urządzenia
    'X-Application-Token': 'your_token'    // Token aplikacji
}
```

---

## 🎨 Komponenty i Interfejs

### 1. Story Circles (Lista Grup)

Główny widok z kółkami story groups.

**HTML Struktura**:
```html
<div class="storyfyd-container">
    <div class="storyfyd-story-circle" data-group-index="0">
        <div class="storyfyd-story-ring">
            <div class="storyfyd-story-preview" style="background-image: url(...)">
                <div class="storyfyd-story-overlay">
                    <div class="storyfyd-play-icon">▶</div>
                </div>
            </div>
            <div class="storyfyd-group-title">Grupa 1</div>
        </div>
    </div>
    <!-- więcej kółek... -->
</div>
```

**Cechy**:
- Gradient ring: `linear-gradient(45deg, #ff6b6b, #4ecdc4, #45b7d1, #96ceb4)`
- Rozmiar: 80x80px
- Background preview: Pierwsze tło z grupy lub `imageUrl` grupy
- Hover effect: `scale(1.05)` + overlay z play icon
- Click: Otwiera fullscreen z pierwszym story grupy

### 2. Fullscreen Story Viewer

Główny komponent do wyświetlania stories.

**Struktura**:
```
┌─────────────────────────────────────────┐
│  Logo                    [X] Close      │ ← Fixed overlay
├─────────────────────────────────────────┤
│                                         │
│    ┌─────┐   ┌──────────────┐   ┌─────┐│
│    │Prev │   │  Main Story  │   │Next ││ ← Story previews + main
│    │     │   │              │   │     ││
│    └─────┘   │  ══════════  │   └─────┘│ ← Progress bars
│              │  👤 Title  ⏸ │          │ ← User info + controls
│              │              │          │
│              │   Content    │          │ ← Story elements
│              └──────────────┘          │
│         ‹              ›               │ ← Navigation arrows
└─────────────────────────────────────────┘
```

**Wymiary**:
- Main Story: `90vh` height, `9:16` aspect ratio
- Side Previews: `45vh` height (50% main)
- Border radius: `20px` (main), `15px` (previews)

**Elementy UI**:

#### Progress Bars
```html
<div class="storyfyd-progress-container">
    <div class="storyfyd-progress-bar">
        <div class="storyfyd-progress-fill" data-story-index="0"></div>
    </div>
    <!-- bar dla każdego story w grupie -->
</div>
```
- Liczba barów = liczba stories w grupie
- Wypełnienie: 0% → 100% podczas wyświetlania
- Poprzednie: 100%, Obecny: animowany, Następne: 0%

#### User Info Header
```html
<div class="storyfyd-user-info">
    <div class="storyfyd-user-avatar"></div>
    <div class="storyfyd-user-name">Story Title</div>
</div>
```

#### Play/Pause Button
```html
<div class="storyfyd-play-pause-btn">
    <svg class="pause-icon">...</svg>  <!-- Widoczny gdy playing -->
    <svg class="play-icon">...</svg>   <!-- Widoczny gdy paused -->
</div>
```

#### Navigation Arrows
```html
<div class="storyfyd-nav-left">
    <div class="storyfyd-arrow-left">‹</div>
</div>
<div class="storyfyd-nav-right">
    <div class="storyfyd-arrow-right">›</div>
</div>
```

### 3. Story Previews (Side Panels)

Podglądy sąsiednich stories po bokach głównego story.

**Logika wyświetlania**:
```javascript
// Left Preview
if (currentStoryIndex > 0) {
    // Poprzedni story z tej samej grupy
    showPreview(currentGroupIndex, currentStoryIndex - 1);
} else if (currentGroupIndex > 0) {
    // Ostatni story z poprzedniej grupy
    const prevGroup = storyGroups[currentGroupIndex - 1];
    showPreview(currentGroupIndex - 1, prevGroup.stories.length - 1);
}

// Right Preview
if (currentStoryIndex < stories.length - 1) {
    // Następny story z tej samej grupy
    showPreview(currentGroupIndex, currentStoryIndex + 1);
} else if (currentGroupIndex < storyGroups.length - 1) {
    // Pierwszy story z następnej grupy
    showPreview(currentGroupIndex + 1, 0);
}
```

**Cechy**:
- Opacity: `0.7` (hover: `0.9`)
- Elementy: Renderowane w skali 0.7x (funkcja `renderPreviewElements()`)
- Click: Nawiguje do tego story (cross-group navigation)

---

## 🔄 Logika Działania

### Auto-Play System

#### 1. Start Auto-Play

```javascript
startAutoPlay() {
    if (this.isPaused) return;
    
    this.stopAutoPlay(); // Clear existing timer
    
    const duration = this.getCurrentStoryDuration();
    
    this.autoPlayTimer = setTimeout(() => {
        this.nextStory();
    }, duration);
}
```

#### 2. Story Duration Logic

SDK określa czas wyświetlania story w następującej kolejności:

```javascript
getCurrentStoryDuration() {
    const story = this.stories[this.currentStoryIndex];
    
    // 1. Explicit duration z API (priorytet najwyższy)
    if (story.duration > 0) {
        return story.duration; // ms
    }
    
    // 2. Background duration z API
    if (story.backgroundDuration > 0) {
        return story.backgroundDuration * 1000; // seconds → ms
    }
    
    // 3. Video duration (jeśli background to video)
    if (story.backgroundType === 'video') {
        return this.defaultDuration * 2; // 14 seconds
    }
    
    // 4. Default duration
    return this.defaultDuration; // 7 seconds
}
```

#### 3. Progress Bar Update

```javascript
updateProgress() {
    const duration = this.getCurrentStoryDuration();
    const existingProgress = this.pausedStories.has(this.currentStoryIndex)
        ? (this.storyProgress[this.currentStoryIndex] || 0)
        : 0;
    
    const remainingTime = (100 - existingProgress) / 100 * duration;
    
    this.storyStartTime = Date.now();
    
    this.progressTimer = setInterval(() => {
        const elapsed = Date.now() - this.storyStartTime;
        const progressIncrement = (elapsed / remainingTime) * (100 - existingProgress);
        const newProgress = Math.min(existingProgress + progressIncrement, 100);
        
        // Update progress bar width
        progressFill.style.width = newProgress + '%';
        
        // Track story show after threshold
        if (elapsed >= trackingThreshold && !this.trackedStories.has(trackingKey)) {
            this.trackStoryShow(storyGroupId, storyId);
            this.trackedStories.add(trackingKey);
        }
        
        // Complete?
        if (newProgress >= 100) {
            clearInterval(this.progressTimer);
            this.nextStory();
        }
    }, 50); // Update co 50ms
}
```

### Navigation Logic

#### Next Story Flow

```javascript
nextStory() {
    // Clear progress for current story
    delete this.storyProgress[this.currentStoryIndex];
    this.pausedStories.delete(this.currentStoryIndex);
    
    if (this.currentStoryIndex < this.stories.length - 1) {
        // Next story in same group
        this.currentStoryIndex++;
        this.storyProgress[this.currentStoryIndex] = 0;
        this.isPaused = false;
        this.updateStoryContent();
        this.startAutoPlay();
    } else {
        // Try next group
        if (this.moveToNextGroup()) {
            this.isPaused = false;
            this.updateStoryContent();
            this.startAutoPlay();
        } else {
            // End of all stories
            this.hideFullscreen();
        }
    }
}
```

#### Cross-Group Navigation

```javascript
moveToNextGroup() {
    if (this.currentGroupIndex < this.storyGroups.length - 1) {
        this.currentGroupIndex++;
        const newGroup = this.storyGroups[this.currentGroupIndex];
        this.stories = newGroup.stories;
        this.currentStoryIndex = 0;
        
        // Update progress bars for new group
        this.updateProgressBars();
        
        return true;
    }
    return false;
}

moveToPrevGroup() {
    if (this.currentGroupIndex > 0) {
        this.currentGroupIndex--;
        const newGroup = this.storyGroups[this.currentGroupIndex];
        this.stories = newGroup.stories;
        this.currentStoryIndex = newGroup.stories.length - 1; // Last story
        
        this.updateProgressBars();
        
        return true;
    }
    return false;
}
```

### Progress Bars Update

Przy zmianie grupy, progress bars są regenerowane:

```javascript
updateProgressBars() {
    const progressContainer = document.querySelector('.storyfyd-progress-container');
    progressContainer.innerHTML = '';
    
    // Generate new bars for current group
    const progressBarsHtml = this.stories.map((_, index) => `
        <div class="storyfyd-progress-bar">
            <div class="storyfyd-progress-fill" data-story-index="${index}"></div>
        </div>
    `).join('');
    
    progressContainer.innerHTML = progressBarsHtml;
}
```

### Play/Pause System

```javascript
togglePlayPause() {
    if (this.isPaused) {
        // Resume
        this.isPaused = false;
        this.resumeVideoBackground();
        this.startAutoPlay();
        this.updateProgress(); // Resume from saved progress
        this.pausedStories.delete(this.currentStoryIndex);
    } else {
        // Pause
        this.isPaused = true;
        this.pausedStories.add(this.currentStoryIndex); // Mark as paused
        this.pauseVideoBackground();
        this.stopAutoPlay();
        clearInterval(this.progressTimer); // Keep progress saved
    }
    
    this.updatePlayPauseIcon();
}
```

**Ważne**: Progress jest zachowywany tylko dla **manualnie** zapauzowanych stories (w `pausedStories` Set). Stories, do których użytkownik nawiguje, zawsze startują od 0%.

### Keyboard Controls

```javascript
handleKeyPress(event) {
    switch (event.key) {
        case 'Escape':
            this.hideFullscreen();
            break;
        case 'ArrowLeft':
            this.prevStory();
            break;
        case 'ArrowRight':
            this.nextStory();
            break;
        case ' ':
        case 'Space':
            event.preventDefault();
            this.togglePlayPause();
            break;
    }
}
```

---

## 🎯 Typy Elementów Story

SDK obsługuje 6 typów interaktywnych elementów, które można umieszczać w stories.

### Format Elementu (Wspólne Właściwości)

```javascript
{
    id: "element-uuid",          // Unikalne ID
    type: "text|button|...",     // Typ elementu
    x: "10%",                    // Pozycja X (% lub px)
    y: "20%",                    // Pozycja Y (% lub px)
    width: "80%",                // Szerokość (% lub px)
    height: "auto",              // Wysokość (% lub px lub "auto")
    rotation: 0,                 // Rotacja w stopniach
    z: 1                         // Z-index
}
```

### 1. Text Element

Wyświetla tekst z **auto-fitting** do kontenera.

```javascript
{
    type: "text",
    content: "Witaj w Storyfyd!",
    fontFamily: "Arial, sans-serif",
    fontWeight: "bold",
    color: "#ffffff",
    textAlign: "center",
    width: "80%",
    height: "auto"
}
```

**Auto-Fitting Algorithm**:
```javascript
fitTextToContainer(text, containerWidth, containerHeight, options = {}) {
    const { min = 10, max = 42, fontFamily = "Arial" } = options;
    
    // Binary search for optimal font size
    let low = min, high = max, bestFit = min;
    
    while (low <= high) {
        const mid = Math.floor((low + high) / 2);
        span.style.fontSize = mid + 'px';
        
        if (span.offsetWidth <= containerWidth && 
            span.offsetHeight <= containerHeight) {
            bestFit = mid;
            low = mid + 1;
        } else {
            high = mid - 1;
        }
    }
    
    return bestFit;
}
```

### 2. Button Element

Klikalny przycisk z linkiem.

```javascript
{
    type: "button",
    content: "Kliknij mnie!",
    fillColor: "#007bff",
    borderColor: "#000000",
    border: 2,              // px
    radius: 8,              // px (max 20px dla fixed height)
    fontWeight: "bold",
    url: "https://example.com",
    width: "200px",
    height: "55px"          // FIXED HEIGHT dla buttons
}
```

**Cechy**:
- Automatyczny kolor tekstu (contrast calculation)
- Fixed height: `55px`
- Max radius: `20px` (połowa wysokości)
- Click → opens URL in new tab + auto-resume story

### 3. Image CTA Element

Klikalny obrazek.

```javascript
{
    type: "imageCTA",
    imageUrl: "https://example.com/image.jpg",
    url: "https://example.com/landing",
    radius: 8,              // Border radius w px
    width: "200px",
    height: "150px"
}
```

**Cechy**:
- `object-fit: contain` - zachowuje proporcje
- Cursor: `pointer` gdy ma URL
- Click → opens URL + auto-resume

### 4. Swipe Up Element

Przycisk "Swipe Up" z animowaną strzałką.

```javascript
{
    type: "swipeUp",
    content: "Przesuń w górę",
    fillColor: "#000000",
    fontWeight: "bold",
    url: "https://example.com"
}
```

**Pozycjonowanie**:
```javascript
div.style.position = 'absolute';
div.style.bottom = '16px';        // Fixed bottom
div.style.left = '50%';           // Centered
div.style.transform = 'translateX(-50%)';
```

**Animacja strzałki**:
```css
@keyframes bounce {
    0%, 20%, 50%, 80%, 100% { transform: translateY(0); }
    40% { transform: translateY(-10px); }
    60% { transform: translateY(-5px); }
}
```

### 5. Emoji Reactions Element

Interaktywne reakcje emoji z licznikami.

```javascript
{
    type: "emoji",
    emoji: ["1f60d", "1f44d", "1f525"],  // Emoji codes (hex)
    emojiStats: [
        { emoji: "1f60d", count: 5 },
        { emoji: "1f44d", count: 3 },
        { emoji: "1f525", count: 8 }
    ],
    selectedEmoji: null,         // Wybór użytkownika (1 raz)
    theme: "dark",               // "dark" | "light"
    orientation: "horizontal",   // "horizontal" | "vertical"
    displayType: "count"         // "count" | "percentage"
}
```

**Emoji Image URL Format**:
```
https://cdn.jsdelivr.net/npm/emoji-datasource-apple/img/apple/64/{code}.png
```

**Interakcja**:
1. User może wybrać **tylko 1 emoji** (one-time selection)
2. Po wyborze: count +1, border highlight, disabled further clicks
3. Tracking: `trackElementInteraction(groupId, storyId, elementId, emoji)`

**Liczniki**:
- Widoczne **tylko po interakcji** użytkownika
- Display: `count` (np. "5") lub `percentage` (np. "50%")
- Style: Font size `10px`, opacity `0.8`

### 6. Emoji Rating Element

Slider z emoji do oceny (0-100%).

```javascript
{
    type: "emojiRating",
    question: "Jak się czujesz?",
    emoji: "1f642",              // Emoji code na sliderze
    userRating: null,            // Ocena użytkownika (0-100)
    ratingStats: {
        averageRating: null,     // Średnia wszystkich ocen
        totalRatings: 0
    }
}
```

**Pozycjonowanie**:
```javascript
div.style.width = '75%';
div.style.height = '75px';
div.style.left = '8%';           // Fixed left
```

**Slider**:
- Track: `rgba(255, 255, 255, 0.2)`
- Progress fill: `linear-gradient(90deg, #b900b3, #f00010)`
- Emoji thumb: 24px emoji na sliderze
- Draggable: Mouse drag or click to set

**Bounce Animation (initial)**:
```javascript
startBounceAnimation(progressFill, emojiThumb) {
    // 0% → 25% → 0% (synchronized)
    // Shows user how to interact
}
```

**After Interaction**:
- Average indicator: Arrow `↓` + label "Average Answer"
- Disabled further interactions (one-time rating)
- Tracking: `trackElementInteraction(groupId, storyId, elementId, rating.toString())`

---

## 📊 System Trackingu

SDK automatycznie trackuje 2 typy eventów:

### 1. Story Show Event

Wysyłany po określonym czasie wyświetlania story.

**Endpoint**:
```
POST /story_groups/{groupId}/stories/{storyId}/show
```

**Threshold**:
- Image backgrounds: `1000ms` (1 sekunda)
- Video backgrounds: `2000ms` (2 sekundy)

**Kod**:
```javascript
this.progressTimer = setInterval(() => {
    const elapsed = Date.now() - this.storyStartTime;
    
    if (!this.trackedStories.has(trackingKey) && storyId && storyGroupId) {
        let trackingThreshold = backgroundType === 'video' ? 2000 : 1000;
        
        if (elapsed >= trackingThreshold) {
            this.trackStoryShow(storyGroupId, storyId);
            this.trackedStories.add(trackingKey); // Prevent duplicate
        }
    }
}, 50);
```

**Request**:
```javascript
async trackStoryShow(storyGroupId, storyId) {
    const url = `${this.config.apiBaseUrl}/story_groups/${storyGroupId}/stories/${storyId}/show`;
    
    const response = await fetch(url, {
        method: 'POST',
        headers: this.getRequestHeaders()
    });
}
```

**Deduplication**:
```javascript
// Tracking key: unique per story
const trackingKey = `${storyGroupId}-${storyId}`;
this.trackedStories.add(trackingKey);

// Reset on navigation
this.trackedStories.delete(trackingKey);
```

### 2. Element Interaction Event

Wysyłany po interakcji z elementem (click emoji, button, rating).

**Endpoint**:
```
POST /story_groups/{groupId}/stories/{storyId}/elements/{elementId}/interact
```

**Body**:
```json
{
    "interact": "value"  // emoji code, rating, button click, etc.
}
```

**Przykłady**:

**Emoji Reaction**:
```javascript
this.trackElementInteraction(
    storyGroupId,
    storyId,
    elementId,
    "1f60d"  // Emoji code
);
```

**Rating**:
```javascript
this.trackElementInteraction(
    storyGroupId,
    storyId,
    elementId,
    "75"  // Rating value (0-100)
);
```

**Kod**:
```javascript
async trackElementInteraction(storyGroupId, storyId, elementId, interactValue) {
    const url = `${this.config.apiBaseUrl}/story_groups/${storyGroupId}/stories/${storyId}/elements/${elementId}/interact`;
    
    const response = await fetch(url, {
        method: 'POST',
        headers: this.getRequestHeaders(),
        body: JSON.stringify({ interact: interactValue })
    });
}
```

---

## 🎬 Obsługa Mediów

### Image Backgrounds

**Ustawienie**:
```javascript
storyElement.style.backgroundImage = `url('${backgroundUrl}')`;
storyElement.style.backgroundSize = 'cover';
storyElement.style.backgroundPosition = 'center';
```

**API Format**:
```javascript
{
    backgroundType: "image",  // or null (defaults to image)
    backgroundMedia: "https://example.com/image.jpg",
    backgroundColor: "#ffffff"  // Fallback
}
```

### Video Backgrounds

**Ustawienie**:
```javascript
setVideoBackground(storyElement, videoUrl) {
    const video = document.createElement('video');
    video.className = 'storyfyd-background-video';
    video.src = videoUrl;
    video.autoplay = true;
    video.loop = true;
    video.muted = true;
    video.playsInline = true;
    video.style.position = 'absolute';
    video.style.top = '0';
    video.style.left = '0';
    video.style.width = '100%';
    video.style.height = '100%';
    video.style.objectFit = 'cover';
    video.style.zIndex = '-1';
    
    storyElement.appendChild(video);
}
```

**API Format**:
```javascript
{
    backgroundType: "video",
    backgroundMedia: "https://example.com/video.mp4",
    backgroundDuration: 10  // seconds (optional)
}
```

**Event Handlers**:
```javascript
const eventHandlers = {
    loadstart: () => console.log('Video load started'),
    loadedmetadata: () => console.log('Duration:', video.duration),
    canplay: () => video.play(),
    error: (e) => console.error('Video failed:', e),
    playing: () => console.log('Video playing')
};

// Attach and store for cleanup
Object.entries(eventHandlers).forEach(([event, handler]) => {
    video.addEventListener(event, handler);
});
video._storyfydEventHandlers = eventHandlers;
```

**Cleanup (Critical)**:
```javascript
// Remove ALL event listeners before removing video
if (video._storyfydEventHandlers) {
    Object.entries(video._storyfydEventHandlers).forEach(([event, handler]) => {
        video.removeEventListener(event, handler);
    });
    delete video._storyfydEventHandlers;
}

// Stop and cleanup
video.pause();
video.src = '';
video.srcObject = null;
video.remove();
```

**Pause/Resume**:
```javascript
pauseVideoBackground() {
    const video = document.querySelector('.storyfyd-background-video');
    if (video) video.pause();
}

resumeVideoBackground() {
    const video = document.querySelector('.storyfyd-background-video');
    if (video) video.play().catch(e => console.error(e));
}
```

### Color Backgrounds

Gdy brak `backgroundMedia`:
```javascript
storyElement.style.backgroundColor = story.backgroundColor || '#ffffff';
storyElement.style.backgroundImage = 'none';
```

### Header Colors (Smart Contrast)

SDK automatycznie dostosowuje kolory nagłówka do tła:

```javascript
updateHeaderColors(backgroundColor, backgroundUrl) {
    const hasBackgroundImage = backgroundUrl && backgroundUrl.trim() !== '';
    const isLightBackground = this.isLightColor(backgroundColor);
    
    // Dla obrazów: zawsze biały
    // Dla kolorów: kontrast based on luminance
    const headerColor = hasBackgroundImage 
        ? '#ffffff' 
        : (isLightBackground ? '#3d3d3d' : '#ffffff');
    
    const textShadow = hasBackgroundImage || !isLightBackground
        ? '0 1px 3px rgba(0, 0, 0, 0.5)'
        : 'none';
    
    // Apply to: user name, logo, close btn, play/pause, arrows
}

isLightColor(color) {
    const hex = color.replace('#', '');
    const r = parseInt(hex.substr(0, 2), 16);
    const g = parseInt(hex.substr(2, 2), 16);
    const b = parseInt(hex.substr(4, 2), 16);
    
    const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
    return luminance > 0.5;
}
```

---

## 📚 API Reference

### Metody Publiczne

#### `init(config)`

Inicjalizuje SDK z konfiguracją.

**Parametry**:
```typescript
config: {
    apiBaseUrl?: string;      // URL API
    token?: string;           // Application token
    templatePath?: string;    // Template path (legacy)
    debug?: boolean;          // Enable debug logs
}
```

**Przykład**:
```javascript
const widget = document.querySelector('storyfyd-web');
widget.init({
    apiBaseUrl: 'https://api.storyfyd.com',
    token: 'your-jwt-token',
    debug: true
});
```

### Metody Wewnętrzne (Private)

#### Story Loading

```typescript
loadStories(): Promise<void>
// Fetches story groups from API
// Endpoint: GET /story_groups
```

#### Rendering

```typescript
render(): void
// Renders story circles from storyGroups

renderStoryElements(story): void
// Renders all elements for a story

createElement(element): HTMLElement
// Creates single story element
```

#### Fullscreen

```typescript
showFullscreen(): void
// Opens fullscreen viewer

hideFullscreen(): void
// Closes fullscreen viewer

updateStoryContent(): void
// Updates content when navigating stories
```

#### Navigation

```typescript
nextStory(): void
// Navigate to next story (cross-group)

prevStory(): void
// Navigate to previous story (cross-group)

moveToNextGroup(): boolean
// Move to next story group

moveToPrevGroup(): boolean
// Move to previous story group

navigateToStory(groupIndex, storyIndex): void
// Navigate to specific story (from preview click)
```

#### Auto-Play & Progress

```typescript
startAutoPlay(): void
// Starts auto-play timer

stopAutoPlay(): void
// Stops auto-play timer

updateProgress(): void
// Updates progress bar animation

togglePlayPause(): void
// Toggles play/pause state

getCurrentStoryDuration(): number
// Returns duration in ms for current story
```

#### Media

```typescript
setVideoBackground(element, url): void
// Sets video background for story

pauseVideoBackground(): void
// Pauses video background

resumeVideoBackground(): void
// Resumes video background
```

#### Tracking

```typescript
trackStoryShow(groupId, storyId): Promise<void>
// Tracks story show event

trackElementInteraction(groupId, storyId, elementId, value): Promise<void>
// Tracks element interaction
```

#### Helpers

```typescript
getRequestHeaders(): object
// Returns headers for API requests

fitTextToContainer(text, width, height, options): number
// Returns optimal font size for text

isLightColor(color): boolean
// Checks if color is light (for contrast)

getContrastColor(color): string
// Returns black or white for optimal contrast
```

---

## 💡 Przykłady Użycia

### Podstawowa Integracja

```html
<!DOCTYPE html>
<html>
<head>
    <title>Moja Strona</title>
</head>
<body>
    <h1>Witaj!</h1>
    
    <!-- Story Widget -->
    <storyfyd-web id="stories"></storyfyd-web>
    
    <script src="/storyfyd-sdk.js"></script>
    <script>
        document.addEventListener('DOMContentLoaded', () => {
            const widget = document.querySelector('#stories');
            widget.init({
                token: 'YOUR_TOKEN',
                apiBaseUrl: 'https://api.example.com'
            });
        });
    </script>
</body>
</html>
```

### Wiele Widgetów na Stronie

```html
<!-- Widget 1: Promocje -->
<div class="promo-section">
    <h2>Promocje</h2>
    <storyfyd-web id="promo-stories"></storyfyd-web>
</div>

<!-- Widget 2: Nowości -->
<div class="news-section">
    <h2>Nowości</h2>
    <storyfyd-web id="news-stories"></storyfyd-web>
</div>

<script>
    // Różne tokeny dla różnych kategorii
    document.querySelector('#promo-stories').init({
        token: 'PROMO_TOKEN',
        apiBaseUrl: 'https://api.example.com'
    });
    
    document.querySelector('#news-stories').init({
        token: 'NEWS_TOKEN',
        apiBaseUrl: 'https://api.example.com'
    });
</script>
```

### Development Setup (Debug Mode)

```javascript
const widget = document.querySelector('storyfyd-web');
widget.init({
    apiBaseUrl: 'http://localhost:8081',  // Local API
    token: 'DEV_TOKEN',
    debug: true  // Włącza wszystkie debugLog()
});
```

Debug logs będą wyglądać tak:
```
🎬 Storyfyd SDK v1.0.0 initialized
📱 Using existing device ID: device_1234567890_abc123
🚀 Fetch options: {...}
✅ Story groups loaded: 3 groups
🎨 Stories rendered
🔍 Fullscreen story opened: 0
🎥 Setting video background
⏱️ Using background duration from API: 10000ms
📊 Progress started for story: 0
```

### React Integration

```jsx
import React, { useEffect, useRef } from 'react';

function StoryWidget({ token, apiUrl }) {
    const widgetRef = useRef(null);
    
    useEffect(() => {
        if (widgetRef.current) {
            widgetRef.current.init({
                token: token,
                apiBaseUrl: apiUrl,
                debug: process.env.NODE_ENV === 'development'
            });
        }
    }, [token, apiUrl]);
    
    return <storyfyd-web ref={widgetRef}></storyfyd-web>;
}

export default StoryWidget;
```

### Vue.js Integration

```vue
<template>
    <storyfyd-web ref="widget"></storyfyd-web>
</template>

<script>
export default {
    name: 'StoryWidget',
    props: ['token', 'apiUrl'],
    mounted() {
        this.$refs.widget.init({
            token: this.token,
            apiBaseUrl: this.apiUrl,
            debug: this.$env.DEV
        });
    }
}
</script>
```

### Angular Integration

```typescript
import { Component, ElementRef, ViewChild, AfterViewInit } from '@angular/core';

@Component({
    selector: 'app-story-widget',
    template: '<storyfyd-web #widget></storyfyd-web>'
})
export class StoryWidgetComponent implements AfterViewInit {
    @ViewChild('widget') widget!: ElementRef;
    
    ngAfterViewInit() {
        const element = this.widget.nativeElement;
        element.init({
            token: 'YOUR_TOKEN',
            apiBaseUrl: 'https://api.example.com',
            debug: false
        });
    }
}
```

---

## 🐛 Troubleshooting

### Problem: Stories nie ładują się

**Przyczyny**:
1. Brak tokenu lub niewłaściwy token
2. Błędny URL API
3. CORS issues

**Rozwiązanie**:
```javascript
// Włącz debug mode
widget.init({
    token: 'YOUR_TOKEN',
    apiBaseUrl: 'https://api.example.com',
    debug: true  // Zobacz szczegółowe logi
});

// Sprawdź konsole:
// ✅ lub ❌ Story groups loaded: X groups
```

### Problem: Video nie odtwarza się

**Przyczyny**:
1. Browser autoplay policy (wymaga muted)
2. Niewłaściwy format video
3. CORS na video URL

**Rozwiązanie**:
- SDK automatycznie ustawia `muted: true` dla autoplay
- Wspierane formaty: MP4, WebM
- Video musi mieć odpowiednie CORS headers

**Debug**:
```javascript
// W konsoli zobaczysz:
🎥 Video load started
🎥 Video metadata loaded, duration: 10s
🎥 Video ready to play
✅ Video autoplay started successfully
// LUB
⚠️ Video autoplay failed: NotAllowedError
```

### Problem: Progress bar nie resetuje się

**Przyczyna**: Story było manualnie zapauzowane

**Wyjaśnienie**:
- Stories w `pausedStories` Set zachowują progress
- Stories, do których nawigujemy normalnie, zawsze startują od 0%

**Rozwiązanie**:
```javascript
// SDK automatycznie czyści progress przy nawigacji:
nextStory() {
    delete this.storyProgress[oldStoryIndex];
    this.pausedStories.delete(oldStoryIndex);
    // ...
}
```

### Problem: Elementy nie wyświetlają się w preview

**Przyczyna**: Preview używa skali 0.7x i może mieć overflow hidden

**Rozwiązanie**:
```css
.storyfyd-story-preview {
    overflow: visible !important;
}
```

### Problem: Tracking duplikuje się

**Przyczyna**: Story show event trackowany wielokrotnie

**Rozwiązanie**:
SDK używa `trackedStories` Set do deduplikacji:
```javascript
const trackingKey = `${storyGroupId}-${storyId}`;
if (!this.trackedStories.has(trackingKey)) {
    this.trackStoryShow(storyGroupId, storyId);
    this.trackedStories.add(trackingKey);
}
```

Reset przy nawigacji:
```javascript
this.trackedStories.delete(trackingKey);
```

### Problem: Memory leak przy video

**Przyczyna**: Video event listeners nie są usuwane

**Rozwiązanie**:
SDK automatycznie usuwa listeners przed usunięciem video:
```javascript
if (video._storyfydEventHandlers) {
    Object.entries(video._storyfydEventHandlers).forEach(([event, handler]) => {
        video.removeEventListener(event, handler);
    });
}
video.pause();
video.src = '';
video.remove();
```

### Problem: Device ID się zmienia

**Przyczyna**: localStorage jest czyszczony lub niedostępny

**Rozwiązanie**:
- SDK automatycznie regeneruje Device ID gdy nie ma w localStorage
- W private browsing może generować nowy ID każdej sesji
- To normalne zachowanie dla bezpieczeństwa

---

## 📈 Performance Tips

### 1. Lazy Loading

```javascript
// Załaduj SDK tylko gdy potrzebny
const loadStoryWidget = () => {
    const script = document.createElement('script');
    script.src = '/storyfyd-sdk.js';
    script.onload = () => {
        const widget = document.querySelector('storyfyd-web');
        widget.init({ token: 'TOKEN', apiBaseUrl: 'URL' });
    };
    document.head.appendChild(script);
};

// Load on scroll or user action
window.addEventListener('scroll', loadStoryWidget, { once: true });
```

### 2. Preload API Data

```javascript
// Prefetch story groups przed inicjalizacją
fetch('https://api.example.com/story_groups', {
    headers: {
        'X-Application-Token': 'TOKEN',
        'X-Device-Id': localStorage.getItem('storyfyd_device_id')
    }
}).then(res => res.json())
  .then(data => {
      // Cache w localStorage
      localStorage.setItem('storyfyd_cache', JSON.stringify(data));
  });
```

### 3. Optimize Video

- Używaj **H.264** codec dla najlepszej kompatybilności
- Max resolution: **1080x1920** (Full HD vertical)
- Bitrate: **2-4 Mbps** dla video
- Format: **MP4** z FastStart (moov atom na początku)

### 4. Debounce Interactions

SDK już używa debounce dla progress (50ms intervals), ale można dodatkowo:

```javascript
// Throttle tracking calls
const throttledTrack = _.throttle((groupId, storyId, elementId, value) => {
    widget.trackElementInteraction(groupId, storyId, elementId, value);
}, 1000);
```

---

## 🔐 Security Best Practices

### 1. Token Management

```javascript
// ❌ NIE hardcode tokenu w frontend
widget.init({
    token: 'eyJ0eXAiOiJKV1...'  // Widoczny w źródle
});

// ✅ Pobierz token z backend
fetch('/api/storyfyd-token')
    .then(res => res.json())
    .then(data => {
        widget.init({
            token: data.token,
            apiBaseUrl: data.apiUrl
        });
    });
```

### 2. CORS Configuration

Backend powinien mieć właściwe CORS headers:

```
Access-Control-Allow-Origin: https://your-domain.com
Access-Control-Allow-Methods: GET, POST, OPTIONS
Access-Control-Allow-Headers: Content-Type, X-Application-Token, X-Device-Id, X-Sdk-Type, X-Sdk-Version
```

### 3. Rate Limiting

Implementuj rate limiting dla tracking endpoints:
- Max 100 tracking calls per minute per device
- Exponential backoff dla failed requests

---

## 📊 Analytics & Monitoring

### Dostępne Metryki

SDK automatycznie trackuje:

1. **Story Shows**: Ile razy story było wyświetlone > threshold
2. **Story Completion Rate**: % stories które dotarły do końca
3. **Element Interactions**: Clicks, emoji reactions, ratings
4. **Navigation Patterns**: Swipes, clicks na preview, keyboard navigation
5. **Device IDs**: Unique users tracking

### Custom Analytics Integration

```javascript
// Hook into SDK events
const originalTrackStoryShow = widget.trackStoryShow.bind(widget);
widget.trackStoryShow = async (groupId, storyId) => {
    // Custom analytics
    gtag('event', 'story_view', {
        story_group: groupId,
        story_id: storyId
    });
    
    // Call original
    return originalTrackStoryShow(groupId, storyId);
};
```

---

## 🚀 Roadmap & Future Features

Planowane funkcje w przyszłych wersjach:

- [ ] **Story Replies**: Możliwość odpowiedzi na story
- [ ] **Story Sharing**: Share story via social media
- [ ] **Story Bookmarks**: Save favorite stories
- [ ] **Offline Support**: Cache stories for offline viewing
- [ ] **Progressive Web App**: Install as PWA
- [ ] **Accessibility**: Full ARIA support, keyboard navigation
- [ ] **RTL Support**: Right-to-left languages
- [ ] **Custom Themes**: Theme customization API
- [ ] **Story Insights**: Built-in analytics dashboard
- [ ] **A/B Testing**: Built-in A/B testing for stories

---

## 📞 Support & Contact

- **Documentation**: https://docs.storyfyd.com
- **API Reference**: https://api.storyfyd.com/docs
- **GitHub Issues**: https://github.com/storyfyd/sdk/issues
- **Email**: support@storyfyd.com

---

## 📄 License

Storyfyd SDK is proprietary software. All rights reserved.

Copyright © 2025 Storyfyd. Unauthorized copying, modification, or distribution is prohibited.

---

**Wersja Dokumentacji**: 1.0.0  
**Data Aktualizacji**: 2025-01-21  
**SDK Version**: 1.0.0