diff --git a/.env.linkedin.example b/.env.linkedin.example new file mode 100644 index 0000000..8564e7a --- /dev/null +++ b/.env.linkedin.example @@ -0,0 +1,23 @@ +# LinkedIn Auto-Posting Configuration +# Copy these lines to your .env file and fill in the values + +# ==================== LinkedIn API ==================== +# Create a LinkedIn app at: https://www.linkedin.com/developers/apps +# Required OAuth scopes: openid, profile, email, w_member_social + +LINKEDIN_CLIENT_ID=your_linkedin_app_client_id +LINKEDIN_CLIENT_SECRET=your_linkedin_app_client_secret +LINKEDIN_REDIRECT_URI=https://yourdomain.com/settings/linkedin/callback + +# ==================== Token Encryption ==================== +# Generate with: python scripts/generate_encryption_key.py +# ⚠️ IMPORTANT: Never change this key after storing tokens! + +ENCRYPTION_KEY=your_generated_fernet_key_here + +# ==================== Notes ==================== +# 1. Replace "yourdomain.com" with your actual domain +# 2. Ensure HTTPS is enabled (required for OAuth) +# 3. The redirect URI must match exactly in LinkedIn app settings +# 4. Keep these values secure and never commit to git +# 5. After setup, restart your application to load new variables diff --git a/Dockerfile b/Dockerfile index 3049370..dcf508d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -28,12 +28,12 @@ RUN useradd --create-home --shell /bin/bash appuser && \ chown -R appuser:appuser /app USER appuser -# Expose port -EXPOSE 8000 +# Expose port (default 8001, can be overridden by PORT env var) +EXPOSE 8001 -# Health check +# Health check (uses PORT env var with 8001 fallback) HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ - CMD python -c "import httpx; httpx.get('http://localhost:8000/login', timeout=5)" || exit 1 + CMD python -c "import os, httpx; port = os.getenv('PORT', '8001'); httpx.get(f'http://localhost:{port}/login', timeout=5)" || exit 1 -# Run the application -CMD ["python", "-m", "uvicorn", "src.web.app:app", "--host", "0.0.0.0", "--port", "8000"] +# Run the application (uses PORT env var with 8001 fallback) +CMD sh -c "python -m uvicorn src.web.app:app --host 0.0.0.0 --port ${PORT:-8001}" diff --git a/LINKEDIN_SETUP.md b/LINKEDIN_SETUP.md new file mode 100644 index 0000000..20e1ca0 --- /dev/null +++ b/LINKEDIN_SETUP.md @@ -0,0 +1,217 @@ +# LinkedIn Auto-Posting Setup Guide + +This guide walks you through setting up the LinkedIn auto-posting feature. + +## Overview + +Employees can link their LinkedIn accounts to automatically post scheduled content directly to LinkedIn, eliminating manual copy-paste work. + +## Prerequisites + +1. A LinkedIn Developer account +2. Access to your server's environment variables +3. HTTPS domain (required for OAuth) + +## Step 1: Create LinkedIn App + +1. Go to [LinkedIn Developers](https://www.linkedin.com/developers/apps) +2. Click "Create app" +3. Fill in app details: + - **App name**: Your app name (e.g., "LinkedInWorkflow Auto-Poster") + - **LinkedIn Page**: Select your company page + - **App logo**: Upload a logo (optional) +4. Check the agreement and create the app + +## Step 2: Configure OAuth Settings + +1. In your app dashboard, go to the **Auth** tab +2. Add OAuth 2.0 redirect URLs: + ``` + https://yourdomain.com/settings/linkedin/callback + ``` +3. Request the following **OAuth 2.0 scopes**: + - `openid` - For user identity + - `profile` - For user profile data + - `email` - For user email + - `w_member_social` - **CRITICAL** - For posting on user's behalf + + Note: `w_member_social` requires app review by LinkedIn. Submit for review if not already approved. + +4. Note your **Client ID** and **Client Secret** (found in Auth tab) + +## Step 3: Generate Encryption Key + +Run the helper script to generate a secure encryption key: + +```bash +python scripts/generate_encryption_key.py +``` + +Copy the generated key for the next step. + +## Step 4: Configure Environment Variables + +Add these variables to your `.env` file: + +```env +# LinkedIn API (Custom OAuth for auto-posting) +LINKEDIN_CLIENT_ID=your_client_id_here +LINKEDIN_CLIENT_SECRET=your_client_secret_here +LINKEDIN_REDIRECT_URI=https://yourdomain.com/settings/linkedin/callback + +# Token Encryption +ENCRYPTION_KEY=your_generated_fernet_key_here +``` + +⚠️ **Security**: Never commit these values to git! Keep them secure. + +## Step 5: Run Database Migration + +Apply the database migration to create the `linkedin_accounts` table: + +```bash +# Using psql +psql $DATABASE_URL -f config/migrate_add_linkedin_accounts.sql + +# Or via Supabase dashboard SQL editor +# Copy and paste the contents of config/migrate_add_linkedin_accounts.sql +``` + +## Step 6: Install Dependencies + +Install the new dependencies: + +```bash +pip install -r requirements.txt +``` + +## Step 7: Restart Application + +Restart your application to load the new environment variables: + +```bash +# If using systemd +sudo systemctl restart linkedinworkflow + +# If using docker +docker-compose restart + +# If running directly +# Stop the current process and restart +``` + +## Step 8: Test the Integration + +1. Log in as an employee user +2. Go to **Settings** (`/settings`) +3. Click **"Mit LinkedIn verbinden"** +4. Complete the LinkedIn OAuth flow +5. Verify the account shows as connected +6. Schedule a test post +7. Wait for the scheduled time (or manually trigger the scheduler) +8. Verify the post appears on LinkedIn + +## Troubleshooting + +### OAuth Errors + +**"Invalid redirect_uri"** +- Make sure the redirect URI in your .env matches exactly with the one configured in LinkedIn app settings +- Include the protocol (`https://`) and no trailing slash + +**"Insufficient permissions"** +- Ensure `w_member_social` scope is requested and approved +- Some scopes require LinkedIn app review + +### Token Errors + +**"Token expired"** +- LinkedIn tokens typically last 60 days +- The system will attempt to refresh automatically +- If refresh fails, user needs to reconnect + +**"Encryption error"** +- Verify `ENCRYPTION_KEY` is set in environment +- Never change the encryption key after storing tokens (or all tokens become unreadable) + +### Posting Errors + +**"Rate limit exceeded"** +- LinkedIn has rate limits on posting +- The system will fall back to email notification +- Wait before retrying + +**"Image upload failed"** +- Check that the image URL is publicly accessible +- Supabase storage URLs must be publicly readable +- System will post without image if upload fails + +## Rate Limits + +LinkedIn API has the following rate limits (as of 2024): + +- **Posts**: ~100 per day per user +- **API calls**: Varies by endpoint + +Plan your posting schedule accordingly. + +## Security Best Practices + +1. **Never log tokens**: Tokens are encrypted at rest and should never be logged in plaintext +2. **HTTPS only**: OAuth requires HTTPS in production +3. **Secure cookies**: Session cookies use httponly, secure, and samesite flags +4. **Token rotation**: Encourage users to reconnect periodically +5. **Audit logging**: Monitor for suspicious activity in `linkedin_accounts.last_error` + +## Architecture + +``` +┌─────────────────┐ +│ Employee │ +│ (Settings Page) │ +└────────┬────────┘ + │ 1. OAuth Flow + ▼ +┌─────────────────────────┐ +│ linkedin_accounts table │ +│ (encrypted tokens) │ +└────────┬────────────────┘ + │ 2. Scheduler checks + ▼ +┌─────────────────────────┐ +│ LinkedIn API Service │ +│ (UGC Posts API) │ +└─────────────────────────┘ +``` + +## Monitoring + +Key metrics to monitor: + +- **Token expiry**: Check `linkedin_accounts.token_expires_at` +- **API errors**: Check `linkedin_accounts.last_error` +- **Success rate**: Compare scheduled posts vs. published posts +- **Fallback rate**: How often email fallback is used + +Query to check accounts needing attention: + +```sql +SELECT + user_id, + linkedin_name, + last_error, + last_error_at, + token_expires_at +FROM linkedin_accounts +WHERE + (last_error IS NOT NULL OR token_expires_at < NOW() + INTERVAL '7 days') + AND is_active = true; +``` + +## Support + +For issues or questions: +1. Check logs for detailed error messages +2. Review LinkedIn API documentation +3. Verify environment configuration +4. Test with a single user account first diff --git a/config/fresh_migration.sql b/config/fresh_migration.sql new file mode 100644 index 0000000..a25c46e --- /dev/null +++ b/config/fresh_migration.sql @@ -0,0 +1,536 @@ +-- ==================== FRESH DATABASE MIGRATION ==================== +-- LinkedIn Workflow - Complete Schema for new Supabase Database +-- Run this in the Supabase SQL Editor + +-- Enable UUID extension +CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; + +-- ==================== CORE TABLES ==================== + +-- Customers/Clients Table +CREATE TABLE IF NOT EXISTS customers ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + + -- Basic Info + name TEXT NOT NULL, + email TEXT, + company_name TEXT, + + -- LinkedIn Profile + linkedin_url TEXT NOT NULL, + + -- Metadata + metadata JSONB DEFAULT '{}'::JSONB, + + -- Email workflow fields + creator_email TEXT, + customer_email TEXT, + + -- Relations + user_id UUID, -- FK added after auth setup + company_id UUID, -- FK added after companies table + writing_style_notes TEXT, + is_active BOOLEAN DEFAULT TRUE, + profile_picture TEXT +); + +-- Post Types Table (for categorizing posts by type) +CREATE TABLE IF NOT EXISTS post_types ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + customer_id UUID NOT NULL REFERENCES customers(id) ON DELETE CASCADE, + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + + -- Type Definition + name TEXT NOT NULL, + description TEXT, + identifying_hashtags TEXT[] DEFAULT '{}', + identifying_keywords TEXT[] DEFAULT '{}', + semantic_properties JSONB DEFAULT '{}'::JSONB, + + -- Analysis Results + analysis JSONB, + analysis_generated_at TIMESTAMP WITH TIME ZONE, + analyzed_post_count INTEGER DEFAULT 0, + + -- Status + is_active BOOLEAN DEFAULT TRUE, + + UNIQUE(customer_id, name) +); + +-- LinkedIn Profiles Table (scraped data) +CREATE TABLE IF NOT EXISTS linkedin_profiles ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + customer_id UUID NOT NULL REFERENCES customers(id) ON DELETE CASCADE, + scraped_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + + -- Profile Data + profile_data JSONB NOT NULL, + + -- Extracted Information + name TEXT, + headline TEXT, + summary TEXT, + location TEXT, + industry TEXT, + + UNIQUE(customer_id) +); + +-- LinkedIn Posts Table (scraped posts) +CREATE TABLE IF NOT EXISTS linkedin_posts ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + customer_id UUID NOT NULL REFERENCES customers(id) ON DELETE CASCADE, + scraped_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + + -- Post Data + post_url TEXT, + post_text TEXT NOT NULL, + post_date TIMESTAMP WITH TIME ZONE, + likes INTEGER DEFAULT 0, + comments INTEGER DEFAULT 0, + shares INTEGER DEFAULT 0, + + -- Raw Data + raw_data JSONB, + + -- Post type classification + post_type_id UUID REFERENCES post_types(id) ON DELETE SET NULL, + classification_method TEXT, + classification_confidence FLOAT, + + UNIQUE(customer_id, post_url) +); + +-- Topics Table (extracted from posts) +CREATE TABLE IF NOT EXISTS topics ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + customer_id UUID NOT NULL REFERENCES customers(id) ON DELETE CASCADE, + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + + -- Topic Info + title TEXT NOT NULL, + description TEXT, + category TEXT, + + -- AI Extraction + extracted_from_post_id UUID REFERENCES linkedin_posts(id), + extraction_confidence FLOAT, + + -- Status + is_used BOOLEAN DEFAULT FALSE, + used_at TIMESTAMP WITH TIME ZONE, + + -- Target post type + target_post_type_id UUID REFERENCES post_types(id) ON DELETE SET NULL +); + +-- Profile Analysis Table (AI-generated insights) +CREATE TABLE IF NOT EXISTS profile_analyses ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + customer_id UUID NOT NULL REFERENCES customers(id) ON DELETE CASCADE, + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + + -- Analysis Results + writing_style JSONB NOT NULL, + tone_analysis JSONB NOT NULL, + topic_patterns JSONB NOT NULL, + audience_insights JSONB NOT NULL, + + -- Full Analysis + full_analysis JSONB NOT NULL, + + UNIQUE(customer_id) +); + +-- Research Results Table +CREATE TABLE IF NOT EXISTS research_results ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + customer_id UUID NOT NULL REFERENCES customers(id) ON DELETE CASCADE, + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + + -- Research Data + query TEXT NOT NULL, + results JSONB NOT NULL, + + -- Topic Suggestions + suggested_topics JSONB NOT NULL, + + -- Metadata + source TEXT DEFAULT 'perplexity', + + -- Target post type + target_post_type_id UUID REFERENCES post_types(id) ON DELETE SET NULL +); + +-- Generated Posts Table +CREATE TABLE IF NOT EXISTS generated_posts ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + customer_id UUID NOT NULL REFERENCES customers(id) ON DELETE CASCADE, + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + + -- Topic + topic_id UUID REFERENCES topics(id), + topic_title TEXT NOT NULL, + + -- Post Content + post_content TEXT NOT NULL, + + -- Generation Metadata + iterations INTEGER DEFAULT 0, + writer_versions JSONB DEFAULT '[]'::JSONB, + critic_feedback JSONB DEFAULT '[]'::JSONB, + + -- Status: draft -> approved -> ready -> scheduled -> published + -- draft: AI generated, waiting for review + -- approved: User edited, email sent to customer + -- ready: Customer approved via email, can be scheduled + -- scheduled: Scheduled in calendar + -- published: Actually published to LinkedIn + -- rejected: Customer rejected, back to draft + status TEXT DEFAULT 'draft' CHECK (status IN ('draft', 'approved', 'ready', 'scheduled', 'published', 'rejected')), + approved_at TIMESTAMP WITH TIME ZONE, + published_at TIMESTAMP WITH TIME ZONE, + + -- Post type + post_type_id UUID REFERENCES post_types(id) ON DELETE SET NULL, + + -- Scheduling + scheduled_at TIMESTAMP WITH TIME ZONE, + scheduled_by_user_id UUID -- FK added after auth setup +); + +-- ==================== COMPANIES & PROFILES (Supabase Auth) ==================== + +-- Companies Table +CREATE TABLE IF NOT EXISTS companies ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + + -- Company Data + name TEXT NOT NULL, + description TEXT, + website TEXT, + industry TEXT, + + -- Strategy + company_strategy JSONB DEFAULT '{}'::JSONB, + + owner_user_id UUID NOT NULL, -- FK to auth.users + onboarding_completed BOOLEAN DEFAULT FALSE +); + +-- Profiles Table (extends auth.users) +CREATE TABLE IF NOT EXISTS profiles ( + id UUID PRIMARY KEY, -- Same as auth.users.id + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + + -- Account Type + account_type TEXT NOT NULL DEFAULT 'ghostwriter', + + -- Display name + display_name TEXT, + + -- Onboarding + onboarding_status TEXT DEFAULT 'pending', + onboarding_data JSONB DEFAULT '{}'::JSONB, + + -- Links + customer_id UUID REFERENCES customers(id) ON DELETE SET NULL, + company_id UUID REFERENCES companies(id) ON DELETE SET NULL +); + +-- Invitations Table +CREATE TABLE IF NOT EXISTS invitations ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + + email TEXT NOT NULL, + token TEXT NOT NULL UNIQUE, + expires_at TIMESTAMP WITH TIME ZONE NOT NULL, + + company_id UUID NOT NULL REFERENCES companies(id) ON DELETE CASCADE, + invited_by_user_id UUID NOT NULL, -- FK to auth.users + + status TEXT DEFAULT 'pending', + accepted_at TIMESTAMP WITH TIME ZONE, + accepted_by_user_id UUID -- FK to auth.users +); + +-- Example Posts Table +CREATE TABLE IF NOT EXISTS example_posts ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + customer_id UUID NOT NULL REFERENCES customers(id) ON DELETE CASCADE, + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + + post_text TEXT NOT NULL, + source TEXT DEFAULT 'manual', + source_linkedin_url TEXT, + post_type_id UUID REFERENCES post_types(id) ON DELETE SET NULL +); + +-- Reference Profiles Table +CREATE TABLE IF NOT EXISTS reference_profiles ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + customer_id UUID NOT NULL REFERENCES customers(id) ON DELETE CASCADE, + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + + linkedin_url TEXT NOT NULL, + name TEXT, + posts_scraped INTEGER DEFAULT 0, + + UNIQUE(customer_id, linkedin_url) +); + +-- ==================== ADD FOREIGN KEYS (after all tables exist) ==================== + +-- Add FK from profiles to auth.users +ALTER TABLE profiles + ADD CONSTRAINT fk_profiles_auth_users + FOREIGN KEY (id) REFERENCES auth.users(id) ON DELETE CASCADE; + +-- Add FK from companies to auth.users +ALTER TABLE companies + ADD CONSTRAINT fk_companies_owner + FOREIGN KEY (owner_user_id) REFERENCES auth.users(id) ON DELETE CASCADE; + +-- Add FK from customers to auth.users and companies +ALTER TABLE customers + ADD CONSTRAINT fk_customers_user + FOREIGN KEY (user_id) REFERENCES auth.users(id) ON DELETE SET NULL; + +ALTER TABLE customers + ADD CONSTRAINT fk_customers_company + FOREIGN KEY (company_id) REFERENCES companies(id) ON DELETE SET NULL; + +-- Add FK from generated_posts to auth.users +ALTER TABLE generated_posts + ADD CONSTRAINT fk_generated_posts_scheduled_by + FOREIGN KEY (scheduled_by_user_id) REFERENCES auth.users(id) ON DELETE SET NULL; + +-- Add FK from invitations to auth.users +ALTER TABLE invitations + ADD CONSTRAINT fk_invitations_invited_by + FOREIGN KEY (invited_by_user_id) REFERENCES auth.users(id) ON DELETE CASCADE; + +ALTER TABLE invitations + ADD CONSTRAINT fk_invitations_accepted_by + FOREIGN KEY (accepted_by_user_id) REFERENCES auth.users(id) ON DELETE SET NULL; + +-- ==================== INDEXES ==================== + +-- Customers +CREATE INDEX idx_customers_linkedin_url ON customers(linkedin_url); +CREATE INDEX idx_customers_user_id ON customers(user_id); +CREATE INDEX idx_customers_company_id ON customers(company_id); + +-- LinkedIn data +CREATE INDEX idx_linkedin_profiles_customer_id ON linkedin_profiles(customer_id); +CREATE INDEX idx_linkedin_posts_customer_id ON linkedin_posts(customer_id); +CREATE INDEX idx_linkedin_posts_post_type_id ON linkedin_posts(post_type_id); + +-- Topics +CREATE INDEX idx_topics_customer_id ON topics(customer_id); +CREATE INDEX idx_topics_is_used ON topics(is_used); +CREATE INDEX idx_topics_target_post_type_id ON topics(target_post_type_id); + +-- Profile analyses & research +CREATE INDEX idx_profile_analyses_customer_id ON profile_analyses(customer_id); +CREATE INDEX idx_research_results_customer_id ON research_results(customer_id); +CREATE INDEX idx_research_results_target_post_type_id ON research_results(target_post_type_id); + +-- Generated posts +CREATE INDEX idx_generated_posts_customer_id ON generated_posts(customer_id); +CREATE INDEX idx_generated_posts_status ON generated_posts(status); +CREATE INDEX idx_generated_posts_post_type_id ON generated_posts(post_type_id); +CREATE INDEX idx_generated_posts_scheduled_at ON generated_posts(scheduled_at) + WHERE scheduled_at IS NOT NULL AND status = 'scheduled'; + +-- Post types +CREATE INDEX idx_post_types_customer_id ON post_types(customer_id); +CREATE INDEX idx_post_types_is_active ON post_types(is_active); + +-- Profiles +CREATE INDEX idx_profiles_account_type ON profiles(account_type); +CREATE INDEX idx_profiles_onboarding_status ON profiles(onboarding_status); +CREATE INDEX idx_profiles_customer_id ON profiles(customer_id); +CREATE INDEX idx_profiles_company_id ON profiles(company_id); + +-- Companies +CREATE INDEX idx_companies_owner_user_id ON companies(owner_user_id); + +-- Invitations +CREATE INDEX idx_invitations_token ON invitations(token); +CREATE INDEX idx_invitations_company_id ON invitations(company_id); +CREATE INDEX idx_invitations_email ON invitations(email); +CREATE INDEX idx_invitations_status ON invitations(status); + +-- Example posts & reference profiles +CREATE INDEX idx_example_posts_customer_id ON example_posts(customer_id); +CREATE INDEX idx_reference_profiles_customer_id ON reference_profiles(customer_id); + +-- ==================== TRIGGERS ==================== + +-- Updated_at trigger function +CREATE OR REPLACE FUNCTION update_updated_at_column() +RETURNS TRIGGER AS $$ +BEGIN + NEW.updated_at = NOW(); + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +-- Add triggers +CREATE TRIGGER update_customers_updated_at + BEFORE UPDATE ON customers + FOR EACH ROW + EXECUTE FUNCTION update_updated_at_column(); + +CREATE TRIGGER update_post_types_updated_at + BEFORE UPDATE ON post_types + FOR EACH ROW + EXECUTE FUNCTION update_updated_at_column(); + +CREATE TRIGGER update_profiles_updated_at + BEFORE UPDATE ON profiles + FOR EACH ROW + EXECUTE FUNCTION update_updated_at_column(); + +CREATE TRIGGER update_companies_updated_at + BEFORE UPDATE ON companies + FOR EACH ROW + EXECUTE FUNCTION update_updated_at_column(); + +-- ==================== AUTO-CREATE PROFILE ON SIGNUP ==================== + +CREATE OR REPLACE FUNCTION public.handle_new_user() +RETURNS TRIGGER AS $$ +BEGIN + INSERT INTO public.profiles (id, account_type, onboarding_status) + VALUES ( + NEW.id, + COALESCE(NEW.raw_user_meta_data->>'account_type', 'ghostwriter'), + 'pending' + ); + RETURN NEW; +END; +$$ LANGUAGE plpgsql SECURITY DEFINER; + +DROP TRIGGER IF EXISTS on_auth_user_created ON auth.users; +CREATE TRIGGER on_auth_user_created + AFTER INSERT ON auth.users + FOR EACH ROW EXECUTE FUNCTION public.handle_new_user(); + +-- ==================== ROW LEVEL SECURITY ==================== + +-- Enable RLS +ALTER TABLE profiles ENABLE ROW LEVEL SECURITY; +ALTER TABLE companies ENABLE ROW LEVEL SECURITY; +ALTER TABLE customers ENABLE ROW LEVEL SECURITY; +ALTER TABLE generated_posts ENABLE ROW LEVEL SECURITY; +ALTER TABLE invitations ENABLE ROW LEVEL SECURITY; + +-- Profiles policies +CREATE POLICY "Users can view own profile" + ON profiles FOR SELECT + USING (auth.uid() = id); + +CREATE POLICY "Users can update own profile" + ON profiles FOR UPDATE + USING (auth.uid() = id); + +CREATE POLICY "Service role has full access to profiles" + ON profiles FOR ALL + USING (auth.jwt() ->> 'role' = 'service_role'); + +-- Companies policies +CREATE POLICY "Company owners can manage their company" + ON companies FOR ALL + USING (auth.uid() = owner_user_id); + +CREATE POLICY "Employees can view their company" + ON companies FOR SELECT + USING ( + EXISTS ( + SELECT 1 FROM profiles + WHERE profiles.id = auth.uid() + AND profiles.company_id = companies.id + ) + ); + +CREATE POLICY "Service role has full access to companies" + ON companies FOR ALL + USING (auth.jwt() ->> 'role' = 'service_role'); + +-- Customers policies +CREATE POLICY "Users can manage own customers" + ON customers FOR ALL + USING (auth.uid() = user_id); + +CREATE POLICY "Company members can view company customers" + ON customers FOR SELECT + USING ( + EXISTS ( + SELECT 1 FROM profiles + WHERE profiles.id = auth.uid() + AND profiles.company_id = customers.company_id + ) + ); + +CREATE POLICY "Service role has full access to customers" + ON customers FOR ALL + USING (auth.jwt() ->> 'role' = 'service_role'); + +-- Generated posts policies +CREATE POLICY "Users can manage posts of their customers" + ON generated_posts FOR ALL + USING ( + EXISTS ( + SELECT 1 FROM customers + WHERE customers.id = generated_posts.customer_id + AND customers.user_id = auth.uid() + ) + ); + +CREATE POLICY "Company members can view company posts" + ON generated_posts FOR SELECT + USING ( + EXISTS ( + SELECT 1 FROM customers + JOIN profiles ON profiles.company_id = customers.company_id + WHERE customers.id = generated_posts.customer_id + AND profiles.id = auth.uid() + ) + ); + +CREATE POLICY "Service role has full access to generated_posts" + ON generated_posts FOR ALL + USING (auth.jwt() ->> 'role' = 'service_role'); + +-- Invitations policies +CREATE POLICY "Company owners can manage invitations" + ON invitations FOR ALL + USING ( + EXISTS ( + SELECT 1 FROM companies + WHERE companies.id = invitations.company_id + AND companies.owner_user_id = auth.uid() + ) + ); + +CREATE POLICY "Anyone can view invitation by token" + ON invitations FOR SELECT + USING (true); + +CREATE POLICY "Service role has full access to invitations" + ON invitations FOR ALL + USING (auth.jwt() ->> 'role' = 'service_role'); + +-- ==================== DONE ==================== +-- Run this script in the Supabase SQL Editor +-- Make sure to configure your .env with the new Supabase URL and keys diff --git a/config/migrate_add_license_system.sql b/config/migrate_add_license_system.sql new file mode 100644 index 0000000..a6a2c6a --- /dev/null +++ b/config/migrate_add_license_system.sql @@ -0,0 +1,94 @@ +-- Migration: Add License Key System with Company Limits +-- This migration adds: +-- 1. license_keys table for managing license keys +-- 2. company_daily_quotas table for tracking daily usage +-- 3. license_key_id reference in companies table + +-- ==================== LICENSE KEYS TABLE ==================== + +CREATE TABLE IF NOT EXISTS license_keys ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + key TEXT UNIQUE NOT NULL, + description TEXT, + + -- Limits (stored here, NOT in companies table) + max_employees INT NOT NULL DEFAULT 5, + max_posts_per_day INT NOT NULL DEFAULT 10, + max_researches_per_day INT NOT NULL DEFAULT 5, + + -- Usage tracking + used BOOLEAN DEFAULT FALSE, + company_id UUID REFERENCES companies(id) ON DELETE SET NULL, + used_at TIMESTAMP WITH TIME ZONE, + + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() +); + +-- Index for fast lookups +CREATE INDEX IF NOT EXISTS idx_license_keys_key ON license_keys(key); +CREATE INDEX IF NOT EXISTS idx_license_keys_used ON license_keys(used); +CREATE INDEX IF NOT EXISTS idx_license_keys_company_id ON license_keys(company_id); + +-- RLS Policies (Admin only) +ALTER TABLE license_keys ENABLE ROW LEVEL SECURITY; + +CREATE POLICY "Service role has full access to license_keys" + ON license_keys FOR ALL + USING (auth.jwt()->>'role' = 'service_role'); + +-- ==================== EXTEND COMPANIES TABLE ==================== + +-- Add license key reference to companies table +-- (limits are stored in license_keys table, not duplicated here) +ALTER TABLE companies ADD COLUMN IF NOT EXISTS license_key_id UUID REFERENCES license_keys(id); + +-- Index +CREATE INDEX IF NOT EXISTS idx_companies_license_key_id ON companies(license_key_id); + +-- ==================== COMPANY DAILY QUOTAS TABLE ==================== + +-- Track daily usage per company +CREATE TABLE IF NOT EXISTS company_daily_quotas ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + company_id UUID NOT NULL REFERENCES companies(id) ON DELETE CASCADE, + date DATE NOT NULL, + posts_created INT DEFAULT 0, + researches_created INT DEFAULT 0, + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + + UNIQUE(company_id, date) +); + +-- Index for fast daily lookups +CREATE INDEX IF NOT EXISTS idx_company_daily_quotas_company_date ON company_daily_quotas(company_id, date); + +-- RLS Policies +ALTER TABLE company_daily_quotas ENABLE ROW LEVEL SECURITY; + +CREATE POLICY "Users can view own company quotas" + ON company_daily_quotas FOR SELECT + USING ( + company_id IN ( + SELECT company_id FROM profiles WHERE id = auth.uid() + ) + ); + +CREATE POLICY "Service role has full access to company_daily_quotas" + ON company_daily_quotas FOR ALL + USING (auth.jwt()->>'role' = 'service_role'); + +-- ==================== TRIGGERS ==================== + +DROP TRIGGER IF EXISTS update_license_keys_updated_at ON license_keys; +CREATE TRIGGER update_license_keys_updated_at + BEFORE UPDATE ON license_keys + FOR EACH ROW + EXECUTE FUNCTION update_updated_at_column(); + +DROP TRIGGER IF EXISTS update_company_daily_quotas_updated_at ON company_daily_quotas; +CREATE TRIGGER update_company_daily_quotas_updated_at + BEFORE UPDATE ON company_daily_quotas + FOR EACH ROW + EXECUTE FUNCTION update_updated_at_column(); diff --git a/config/migrate_add_linkedin_accounts.sql b/config/migrate_add_linkedin_accounts.sql new file mode 100644 index 0000000..d2a9de0 --- /dev/null +++ b/config/migrate_add_linkedin_accounts.sql @@ -0,0 +1,68 @@ +-- Migration: Add LinkedIn accounts for auto-posting +-- Description: Allows employees to link their LinkedIn accounts for automatic post publishing + +CREATE TABLE IF NOT EXISTS linkedin_accounts ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE, + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + + -- LinkedIn Identity + linkedin_user_id TEXT NOT NULL, + linkedin_vanity_name TEXT, + linkedin_name TEXT, + linkedin_picture TEXT, + + -- OAuth Tokens (encrypted) + access_token TEXT NOT NULL, + refresh_token TEXT, + token_expires_at TIMESTAMP WITH TIME ZONE NOT NULL, + granted_scopes TEXT[] DEFAULT '{}', + + -- Status + is_active BOOLEAN DEFAULT TRUE, + last_used_at TIMESTAMP WITH TIME ZONE, + last_error TEXT, + last_error_at TIMESTAMP WITH TIME ZONE, + + UNIQUE(user_id) -- Only one account per user +); + +CREATE INDEX idx_linkedin_accounts_user_id ON linkedin_accounts(user_id); + +-- Enable RLS +ALTER TABLE linkedin_accounts ENABLE ROW LEVEL SECURITY; + +-- Policy: Users can only see their own LinkedIn account +CREATE POLICY "Users can view own linkedin account" + ON linkedin_accounts FOR SELECT + USING (auth.uid() = user_id); + +-- Policy: Users can insert their own LinkedIn account +CREATE POLICY "Users can insert own linkedin account" + ON linkedin_accounts FOR INSERT + WITH CHECK (auth.uid() = user_id); + +-- Policy: Users can update their own LinkedIn account +CREATE POLICY "Users can update own linkedin account" + ON linkedin_accounts FOR UPDATE + USING (auth.uid() = user_id); + +-- Policy: Users can delete their own LinkedIn account +CREATE POLICY "Users can delete own linkedin account" + ON linkedin_accounts FOR DELETE + USING (auth.uid() = user_id); + +-- Function to update updated_at timestamp +CREATE OR REPLACE FUNCTION update_linkedin_accounts_updated_at() +RETURNS TRIGGER AS $$ +BEGIN + NEW.updated_at = NOW(); + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +CREATE TRIGGER update_linkedin_accounts_updated_at + BEFORE UPDATE ON linkedin_accounts + FOR EACH ROW + EXECUTE FUNCTION update_linkedin_accounts_updated_at(); diff --git a/config/migrate_add_metadata_to_posts.sql b/config/migrate_add_metadata_to_posts.sql new file mode 100644 index 0000000..801d14b --- /dev/null +++ b/config/migrate_add_metadata_to_posts.sql @@ -0,0 +1,8 @@ +-- Add metadata field to generated_posts table +-- This field stores additional information like LinkedIn post URLs, auto-posting status, etc. + +ALTER TABLE generated_posts +ADD COLUMN IF NOT EXISTS metadata JSONB DEFAULT '{}'::JSONB; + +-- Add comment +COMMENT ON COLUMN generated_posts.metadata IS 'Additional metadata (LinkedIn post URL, auto-posting status, etc.)'; diff --git a/config/migrate_remove_customers.sql b/config/migrate_remove_customers.sql new file mode 100644 index 0000000..6a0d1d7 --- /dev/null +++ b/config/migrate_remove_customers.sql @@ -0,0 +1,314 @@ +-- Migration: Remove customers table, move everything to user_id on profiles +-- BACKUP YOUR DATABASE BEFORE RUNNING THIS! +-- Run with: psql -d your_db -f config/migrate_remove_customers.sql + +BEGIN; + +-- ============================================================ +-- 1. Add new columns to profiles (from customers) +-- ============================================================ + +ALTER TABLE profiles + ADD COLUMN IF NOT EXISTS linkedin_url TEXT, + ADD COLUMN IF NOT EXISTS writing_style_notes TEXT, + ADD COLUMN IF NOT EXISTS metadata JSONB DEFAULT '{}'::JSONB, + ADD COLUMN IF NOT EXISTS profile_picture TEXT, + ADD COLUMN IF NOT EXISTS creator_email TEXT, + ADD COLUMN IF NOT EXISTS customer_email TEXT, + ADD COLUMN IF NOT EXISTS is_active BOOLEAN DEFAULT TRUE; + +-- ============================================================ +-- 2. Migrate data from customers to profiles +-- ============================================================ + +UPDATE profiles +SET + linkedin_url = c.linkedin_url, + writing_style_notes = c.writing_style_notes, + metadata = c.metadata, + profile_picture = c.profile_picture, + creator_email = c.creator_email, + customer_email = c.customer_email, + is_active = c.is_active +FROM customers c +WHERE profiles.customer_id = c.id; + +-- Also populate display_name from customer.name where profiles.display_name is null +UPDATE profiles +SET display_name = c.name +FROM customers c +WHERE profiles.customer_id = c.id + AND profiles.display_name IS NULL; + +-- ============================================================ +-- 3. Add user_id column to all content tables +-- ============================================================ + +ALTER TABLE linkedin_profiles + ADD COLUMN IF NOT EXISTS user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE; + +ALTER TABLE linkedin_posts + ADD COLUMN IF NOT EXISTS user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE; + +ALTER TABLE topics + ADD COLUMN IF NOT EXISTS user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE; + +ALTER TABLE post_types + ADD COLUMN IF NOT EXISTS user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE; + +ALTER TABLE profile_analyses + ADD COLUMN IF NOT EXISTS user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE; + +ALTER TABLE research_results + ADD COLUMN IF NOT EXISTS user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE; + +ALTER TABLE generated_posts + ADD COLUMN IF NOT EXISTS user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE; + +ALTER TABLE example_posts + ADD COLUMN IF NOT EXISTS user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE; + +ALTER TABLE reference_profiles + ADD COLUMN IF NOT EXISTS user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE; + +-- ============================================================ +-- 4. Populate user_id via profiles.customer_id mapping +-- ============================================================ + +UPDATE linkedin_profiles lp +SET user_id = p.id +FROM profiles p +WHERE lp.customer_id = p.customer_id + AND p.customer_id IS NOT NULL; + +UPDATE linkedin_posts lp +SET user_id = p.id +FROM profiles p +WHERE lp.customer_id = p.customer_id + AND p.customer_id IS NOT NULL; + +UPDATE topics t +SET user_id = p.id +FROM profiles p +WHERE t.customer_id = p.customer_id + AND p.customer_id IS NOT NULL; + +UPDATE post_types pt +SET user_id = p.id +FROM profiles p +WHERE pt.customer_id = p.customer_id + AND p.customer_id IS NOT NULL; + +UPDATE profile_analyses pa +SET user_id = p.id +FROM profiles p +WHERE pa.customer_id = p.customer_id + AND p.customer_id IS NOT NULL; + +UPDATE research_results rr +SET user_id = p.id +FROM profiles p +WHERE rr.customer_id = p.customer_id + AND p.customer_id IS NOT NULL; + +UPDATE generated_posts gp +SET user_id = p.id +FROM profiles p +WHERE gp.customer_id = p.customer_id + AND p.customer_id IS NOT NULL; + +UPDATE example_posts ep +SET user_id = p.id +FROM profiles p +WHERE ep.customer_id = p.customer_id + AND p.customer_id IS NOT NULL; + +UPDATE reference_profiles rp +SET user_id = p.id +FROM profiles p +WHERE rp.customer_id = p.customer_id + AND p.customer_id IS NOT NULL; + +-- ============================================================ +-- 5. Drop old unique constraints (customer_id based) +-- ============================================================ + +ALTER TABLE linkedin_profiles DROP CONSTRAINT IF EXISTS linkedin_profiles_customer_id_key; +ALTER TABLE profile_analyses DROP CONSTRAINT IF EXISTS profile_analyses_customer_id_key; +ALTER TABLE post_types DROP CONSTRAINT IF EXISTS post_types_customer_id_name_key; +ALTER TABLE linkedin_posts DROP CONSTRAINT IF EXISTS linkedin_posts_customer_id_post_url_key; +ALTER TABLE reference_profiles DROP CONSTRAINT IF EXISTS reference_profiles_customer_id_linkedin_url_key; + +-- ============================================================ +-- 6. Set user_id NOT NULL (after data migration) +-- ============================================================ + +-- Delete orphan rows that have no user_id (data without a profile link) +DELETE FROM linkedin_profiles WHERE user_id IS NULL; +DELETE FROM linkedin_posts WHERE user_id IS NULL; +DELETE FROM topics WHERE user_id IS NULL; +DELETE FROM post_types WHERE user_id IS NULL; +DELETE FROM profile_analyses WHERE user_id IS NULL; +DELETE FROM research_results WHERE user_id IS NULL; +DELETE FROM generated_posts WHERE user_id IS NULL; +DELETE FROM example_posts WHERE user_id IS NULL; +DELETE FROM reference_profiles WHERE user_id IS NULL; + +ALTER TABLE linkedin_profiles ALTER COLUMN user_id SET NOT NULL; +ALTER TABLE linkedin_posts ALTER COLUMN user_id SET NOT NULL; +ALTER TABLE topics ALTER COLUMN user_id SET NOT NULL; +ALTER TABLE post_types ALTER COLUMN user_id SET NOT NULL; +ALTER TABLE profile_analyses ALTER COLUMN user_id SET NOT NULL; +ALTER TABLE research_results ALTER COLUMN user_id SET NOT NULL; +ALTER TABLE generated_posts ALTER COLUMN user_id SET NOT NULL; +ALTER TABLE example_posts ALTER COLUMN user_id SET NOT NULL; +ALTER TABLE reference_profiles ALTER COLUMN user_id SET NOT NULL; + +-- ============================================================ +-- 7. Add new unique constraints (user_id based) +-- ============================================================ + +ALTER TABLE linkedin_profiles ADD CONSTRAINT linkedin_profiles_user_id_key UNIQUE (user_id); +ALTER TABLE profile_analyses ADD CONSTRAINT profile_analyses_user_id_key UNIQUE (user_id); +ALTER TABLE post_types ADD CONSTRAINT post_types_user_id_name_key UNIQUE (user_id, name); +ALTER TABLE linkedin_posts ADD CONSTRAINT linkedin_posts_user_id_post_url_key UNIQUE (user_id, post_url); +ALTER TABLE reference_profiles ADD CONSTRAINT reference_profiles_user_id_linkedin_url_key UNIQUE (user_id, linkedin_url); + +-- ============================================================ +-- 8. Drop RLS policies that reference customer_id +-- ============================================================ + +-- Drop ALL policies on generated_posts (we'll recreate them with user_id) +DROP POLICY IF EXISTS "Users can manage posts of their customers" ON generated_posts; +DROP POLICY IF EXISTS "Company members can view company posts" ON generated_posts; +DROP POLICY IF EXISTS "Service role has full access to generated_posts" ON generated_posts; + +-- Drop policies on customers table (will be dropped anyway) +DROP POLICY IF EXISTS "Users can manage own customers" ON customers; +DROP POLICY IF EXISTS "Company members can view company customers" ON customers; +DROP POLICY IF EXISTS "Service role has full access to customers" ON customers; + +-- ============================================================ +-- 8b. Drop users view (depends on profiles.customer_id) +-- ============================================================ + +DROP VIEW IF EXISTS users; + +-- ============================================================ +-- 9. Drop old customer_id columns from content tables +-- ============================================================ + +ALTER TABLE linkedin_profiles DROP COLUMN IF EXISTS customer_id; +ALTER TABLE linkedin_posts DROP COLUMN IF EXISTS customer_id; +ALTER TABLE topics DROP COLUMN IF EXISTS customer_id; +ALTER TABLE post_types DROP COLUMN IF EXISTS customer_id; +ALTER TABLE profile_analyses DROP COLUMN IF EXISTS customer_id; +ALTER TABLE research_results DROP COLUMN IF EXISTS customer_id; +ALTER TABLE generated_posts DROP COLUMN IF EXISTS customer_id; +ALTER TABLE example_posts DROP COLUMN IF EXISTS customer_id; +ALTER TABLE reference_profiles DROP COLUMN IF EXISTS customer_id; + +-- Drop customer_id from profiles +ALTER TABLE profiles DROP COLUMN IF EXISTS customer_id; + +-- Drop customer_id from api_usage_logs +ALTER TABLE api_usage_logs DROP COLUMN IF EXISTS customer_id; + +-- ============================================================ +-- 10. Create new RLS policies using user_id +-- ============================================================ + +-- Generated posts policies (using user_id instead of customer_id) +CREATE POLICY "Users can manage own posts" + ON generated_posts FOR ALL + USING (user_id = auth.uid()); + +CREATE POLICY "Company members can view company posts" + ON generated_posts FOR SELECT + USING ( + EXISTS ( + SELECT 1 FROM profiles p1 + JOIN profiles p2 ON p1.company_id = p2.company_id + WHERE p1.id = auth.uid() + AND p2.id = generated_posts.user_id + AND p1.company_id IS NOT NULL + ) + ); + +CREATE POLICY "Service role has full access to generated_posts" + ON generated_posts FOR ALL + USING (auth.jwt() ->> 'role' = 'service_role'); + +-- ============================================================ +-- 11. Drop old indexes and create new ones +-- ============================================================ + +DROP INDEX IF EXISTS idx_linkedin_profiles_customer_id; +DROP INDEX IF EXISTS idx_linkedin_posts_customer_id; +DROP INDEX IF EXISTS idx_topics_customer_id; +DROP INDEX IF EXISTS idx_post_types_customer_id; +DROP INDEX IF EXISTS idx_profile_analyses_customer_id; +DROP INDEX IF EXISTS idx_research_results_customer_id; +DROP INDEX IF EXISTS idx_generated_posts_customer_id; +DROP INDEX IF EXISTS idx_example_posts_customer_id; +DROP INDEX IF EXISTS idx_reference_profiles_customer_id; +DROP INDEX IF EXISTS idx_profiles_customer_id; +DROP INDEX IF EXISTS idx_customers_linkedin_url; +DROP INDEX IF EXISTS idx_customers_user_id; +DROP INDEX IF EXISTS idx_customers_company_id; +DROP INDEX IF EXISTS idx_api_usage_logs_customer_id; + +CREATE INDEX IF NOT EXISTS idx_linkedin_profiles_user_id ON linkedin_profiles(user_id); +CREATE INDEX IF NOT EXISTS idx_linkedin_posts_user_id ON linkedin_posts(user_id); +CREATE INDEX IF NOT EXISTS idx_topics_user_id ON topics(user_id); +CREATE INDEX IF NOT EXISTS idx_post_types_user_id ON post_types(user_id); +CREATE INDEX IF NOT EXISTS idx_profile_analyses_user_id ON profile_analyses(user_id); +CREATE INDEX IF NOT EXISTS idx_research_results_user_id ON research_results(user_id); +CREATE INDEX IF NOT EXISTS idx_generated_posts_user_id ON generated_posts(user_id); +CREATE INDEX IF NOT EXISTS idx_example_posts_user_id ON example_posts(user_id); +CREATE INDEX IF NOT EXISTS idx_reference_profiles_user_id ON reference_profiles(user_id); + +-- ============================================================ +-- 12. Drop customers table +-- ============================================================ + +-- Remove trigger first +DROP TRIGGER IF EXISTS update_customers_updated_at ON customers; + +DROP TABLE IF EXISTS customers CASCADE; + +-- ============================================================ +-- 13. Recreate users view (without customer_id) +-- ============================================================ + +CREATE OR REPLACE VIEW users AS +SELECT + au.id, + au.created_at, + au.updated_at, + au.email, + au.raw_user_meta_data->>'sub' AS linkedin_sub, + au.raw_user_meta_data->>'vanityName' AS linkedin_vanity_name, + au.raw_user_meta_data->>'name' AS linkedin_name, + au.raw_user_meta_data->>'picture' AS linkedin_picture, + CASE + WHEN au.raw_user_meta_data->>'iss' LIKE '%linkedin%' THEN 'linkedin_oauth' + ELSE 'email_password' + END AS auth_method, + COALESCE(p.account_type, 'ghostwriter') AS account_type, + p.display_name, + COALESCE(p.onboarding_status, 'pending') AS onboarding_status, + COALESCE(p.onboarding_data, '{}'::jsonb) AS onboarding_data, + p.company_id, + p.linkedin_url, + p.writing_style_notes, + p.metadata, + p.profile_picture, + p.creator_email, + p.customer_email, + p.is_active, + au.email_confirmed_at IS NOT NULL AS email_verified +FROM auth.users au +LEFT JOIN profiles p ON au.id = p.id; + +COMMIT; diff --git a/config/migrate_remove_linkedin_unique.sql b/config/migrate_remove_linkedin_unique.sql new file mode 100644 index 0000000..2187dce --- /dev/null +++ b/config/migrate_remove_linkedin_unique.sql @@ -0,0 +1,7 @@ +-- Migration: Remove UNIQUE constraint from customers.linkedin_url +-- This allows multiple customers (different ghostwriters/companies) to share the same LinkedIn URL. +-- Each user context gets its own independent Customer record. + +ALTER TABLE customers DROP CONSTRAINT IF EXISTS customers_linkedin_url_key; + +-- The existing index idx_customers_linkedin_url remains for fast lookups (non-unique). diff --git a/config/migrate_to_supabase_auth.sql b/config/migrate_to_supabase_auth.sql new file mode 100644 index 0000000..66fb31f --- /dev/null +++ b/config/migrate_to_supabase_auth.sql @@ -0,0 +1,210 @@ +-- Migration Script: From custom users table to Supabase Auth +-- ============================================================ +-- IMPORTANT: Run these steps IN ORDER, one at a time! +-- ============================================================ + +-- ============================================================ +-- STEP 1: Backup the old users table (run this first!) +-- ============================================================ + +-- Create backup of existing users table +CREATE TABLE IF NOT EXISTS users_backup AS SELECT * FROM users; + +-- Verify backup was created +SELECT COUNT(*) as backup_count FROM users_backup; + +-- ============================================================ +-- STEP 2: Create profiles table +-- ============================================================ + +CREATE TABLE IF NOT EXISTS profiles ( + id UUID PRIMARY KEY REFERENCES auth.users(id) ON DELETE CASCADE, + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + account_type TEXT NOT NULL DEFAULT 'ghostwriter', + display_name TEXT, + onboarding_status TEXT DEFAULT 'pending', + onboarding_data JSONB DEFAULT '{}'::JSONB, + customer_id UUID, + company_id UUID +); + +-- ============================================================ +-- STEP 3: Drop constraints that reference the old users table +-- ============================================================ + +-- Drop FK constraints from companies table +ALTER TABLE companies DROP CONSTRAINT IF EXISTS companies_owner_user_id_fkey; + +-- Drop FK constraints from invitations table +ALTER TABLE invitations DROP CONSTRAINT IF EXISTS invitations_invited_by_user_id_fkey; +ALTER TABLE invitations DROP CONSTRAINT IF EXISTS invitations_accepted_by_user_id_fkey; + +-- Drop FK constraints from customers table +ALTER TABLE customers DROP CONSTRAINT IF EXISTS customers_user_id_fkey; + +-- ============================================================ +-- STEP 4: Rename old users table +-- ============================================================ + +ALTER TABLE IF EXISTS users RENAME TO users_old; + +-- ============================================================ +-- STEP 5: Create the users VIEW +-- ============================================================ + +CREATE OR REPLACE VIEW users AS +SELECT + p.id, + p.created_at, + p.updated_at, + au.email, + NULL::TEXT as password_hash, + CASE + WHEN au.raw_app_meta_data->>'provider' = 'linkedin_oidc' THEN 'linkedin_oauth' + ELSE 'email_password' + END as auth_method, + au.raw_user_meta_data->>'sub' as linkedin_sub, + au.raw_user_meta_data->>'vanityName' as linkedin_vanity_name, + COALESCE(au.raw_user_meta_data->>'name', au.raw_user_meta_data->>'full_name') as linkedin_name, + au.raw_user_meta_data->>'picture' as linkedin_picture, + p.account_type, + p.display_name, + p.onboarding_status, + p.onboarding_data, + p.customer_id, + p.company_id, + au.email_confirmed_at IS NOT NULL as email_verified, + NULL::TEXT as email_verification_token, + NULL::TIMESTAMP WITH TIME ZONE as email_verification_expires_at +FROM profiles p +JOIN auth.users au ON p.id = au.id; + +-- ============================================================ +-- STEP 6: Create the trigger for new users +-- ============================================================ + +CREATE OR REPLACE FUNCTION public.handle_new_user() +RETURNS TRIGGER AS $$ +BEGIN + INSERT INTO public.profiles (id, account_type, onboarding_status, display_name) + VALUES ( + NEW.id, + COALESCE(NEW.raw_user_meta_data->>'account_type', 'ghostwriter'), + 'pending', + NEW.raw_user_meta_data->>'display_name' + ) + ON CONFLICT (id) DO NOTHING; + RETURN NEW; +END; +$$ LANGUAGE plpgsql SECURITY DEFINER; + +DROP TRIGGER IF EXISTS on_auth_user_created ON auth.users; +CREATE TRIGGER on_auth_user_created + AFTER INSERT ON auth.users + FOR EACH ROW EXECUTE FUNCTION public.handle_new_user(); + +-- ============================================================ +-- STEP 7: Re-add FK constraints pointing to auth.users +-- ============================================================ + +-- Add FK to companies +ALTER TABLE companies + ADD CONSTRAINT companies_owner_user_id_fkey + FOREIGN KEY (owner_user_id) REFERENCES auth.users(id) ON DELETE CASCADE; + +-- Add FK to invitations +ALTER TABLE invitations + ADD CONSTRAINT invitations_invited_by_user_id_fkey + FOREIGN KEY (invited_by_user_id) REFERENCES auth.users(id) ON DELETE CASCADE; + +ALTER TABLE invitations + ADD CONSTRAINT invitations_accepted_by_user_id_fkey + FOREIGN KEY (accepted_by_user_id) REFERENCES auth.users(id) ON DELETE SET NULL; + +-- Add FK to customers +ALTER TABLE customers + ADD CONSTRAINT customers_user_id_fkey + FOREIGN KEY (user_id) REFERENCES auth.users(id) ON DELETE SET NULL; + +-- Add FK from profiles to companies +ALTER TABLE profiles + ADD CONSTRAINT fk_profiles_company + FOREIGN KEY (company_id) REFERENCES companies(id) ON DELETE SET NULL; + +-- Add FK from profiles to customers +ALTER TABLE profiles + ADD CONSTRAINT fk_profiles_customer + FOREIGN KEY (customer_id) REFERENCES customers(id) ON DELETE SET NULL; + +-- ============================================================ +-- STEP 8: Create indexes on profiles +-- ============================================================ + +CREATE INDEX IF NOT EXISTS idx_profiles_account_type ON profiles(account_type); +CREATE INDEX IF NOT EXISTS idx_profiles_onboarding_status ON profiles(onboarding_status); +CREATE INDEX IF NOT EXISTS idx_profiles_customer_id ON profiles(customer_id); +CREATE INDEX IF NOT EXISTS idx_profiles_company_id ON profiles(company_id); + +-- ============================================================ +-- STEP 9: Create updated_at trigger for profiles +-- ============================================================ + +-- Make sure the function exists +CREATE OR REPLACE FUNCTION update_updated_at_column() +RETURNS TRIGGER AS $$ +BEGIN + NEW.updated_at = NOW(); + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +DROP TRIGGER IF EXISTS update_profiles_updated_at ON profiles; +CREATE TRIGGER update_profiles_updated_at + BEFORE UPDATE ON profiles + FOR EACH ROW + EXECUTE FUNCTION update_updated_at_column(); + +-- ============================================================ +-- STEP 10: Enable RLS on profiles +-- ============================================================ + +ALTER TABLE profiles ENABLE ROW LEVEL SECURITY; + +-- Drop existing policies if they exist +DROP POLICY IF EXISTS "Users can view own profile" ON profiles; +DROP POLICY IF EXISTS "Users can update own profile" ON profiles; +DROP POLICY IF EXISTS "Service role has full access to profiles" ON profiles; + +-- Profiles: Users can read/update their own profile +CREATE POLICY "Users can view own profile" + ON profiles FOR SELECT + USING (auth.uid() = id); + +CREATE POLICY "Users can update own profile" + ON profiles FOR UPDATE + USING (auth.uid() = id); + +-- Service role can do everything (for admin operations) +CREATE POLICY "Service role has full access to profiles" + ON profiles FOR ALL + USING (true); + +-- ============================================================ +-- DONE! +-- +-- The old users table has been renamed to 'users_old'. +-- A backup was created as 'users_backup'. +-- +-- New users will be created via Supabase Auth and will +-- automatically get a profile via the trigger. +-- +-- To clean up later (after verifying everything works): +-- DROP TABLE IF EXISTS users_old; +-- DROP TABLE IF EXISTS users_backup; +-- ============================================================ + +-- Verify the migration +SELECT 'Migration complete!' as status; +SELECT COUNT(*) as profiles_count FROM profiles; +SELECT COUNT(*) as auth_users_count FROM auth.users; diff --git a/config/migrate_to_supabase_auth_v2.sql b/config/migrate_to_supabase_auth_v2.sql new file mode 100644 index 0000000..ad931ab --- /dev/null +++ b/config/migrate_to_supabase_auth_v2.sql @@ -0,0 +1,257 @@ +-- Migration Script v2: From custom users table to Supabase Auth +-- ============================================================ +-- This version handles orphaned references properly +-- ============================================================ + +-- ============================================================ +-- STEP 1: Backup tables +-- ============================================================ + +CREATE TABLE IF NOT EXISTS users_backup AS SELECT * FROM users; +CREATE TABLE IF NOT EXISTS customers_backup AS SELECT * FROM customers; +CREATE TABLE IF NOT EXISTS companies_backup AS SELECT * FROM companies; +CREATE TABLE IF NOT EXISTS invitations_backup AS SELECT * FROM invitations; + +SELECT 'Backups created' as status; + +-- ============================================================ +-- STEP 2: Create profiles table +-- ============================================================ + +CREATE TABLE IF NOT EXISTS profiles ( + id UUID PRIMARY KEY REFERENCES auth.users(id) ON DELETE CASCADE, + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + account_type TEXT NOT NULL DEFAULT 'ghostwriter', + display_name TEXT, + onboarding_status TEXT DEFAULT 'pending', + onboarding_data JSONB DEFAULT '{}'::JSONB, + customer_id UUID, + company_id UUID +); + +SELECT 'Profiles table created' as status; + +-- ============================================================ +-- STEP 3: Drop ALL constraints that reference the old users table +-- ============================================================ + +ALTER TABLE companies DROP CONSTRAINT IF EXISTS companies_owner_user_id_fkey; +ALTER TABLE invitations DROP CONSTRAINT IF EXISTS invitations_invited_by_user_id_fkey; +ALTER TABLE invitations DROP CONSTRAINT IF EXISTS invitations_accepted_by_user_id_fkey; +ALTER TABLE customers DROP CONSTRAINT IF EXISTS customers_user_id_fkey; + +-- Also drop the FK from users to companies if it exists +ALTER TABLE users DROP CONSTRAINT IF EXISTS fk_users_company; +ALTER TABLE users DROP CONSTRAINT IF EXISTS users_company_id_fkey; + +SELECT 'Old constraints dropped' as status; + +-- ============================================================ +-- STEP 4: Clear orphaned references BEFORE adding new constraints +-- Set user_id to NULL where user doesn't exist in auth.users +-- ============================================================ + +-- Clear customers.user_id where user doesn't exist in auth.users +UPDATE customers +SET user_id = NULL +WHERE user_id IS NOT NULL +AND user_id NOT IN (SELECT id FROM auth.users); + +-- Clear companies.owner_user_id - but we can't set to NULL (NOT NULL constraint) +-- So we need to delete companies with orphaned owners, or skip FK for now +-- Let's check if there are any orphaned companies first +SELECT 'Orphaned companies:' as info, COUNT(*) as count +FROM companies +WHERE owner_user_id NOT IN (SELECT id FROM auth.users); + +-- Delete companies with orphaned owner_user_id (they can't be used anyway) +DELETE FROM companies +WHERE owner_user_id NOT IN (SELECT id FROM auth.users); + +-- Clear invitations with orphaned user references +UPDATE invitations +SET accepted_by_user_id = NULL +WHERE accepted_by_user_id IS NOT NULL +AND accepted_by_user_id NOT IN (SELECT id FROM auth.users); + +DELETE FROM invitations +WHERE invited_by_user_id NOT IN (SELECT id FROM auth.users); + +SELECT 'Orphaned references cleaned' as status; + +-- ============================================================ +-- STEP 5: Rename old users table +-- ============================================================ + +ALTER TABLE IF EXISTS users RENAME TO users_old; + +SELECT 'Old users table renamed to users_old' as status; + +-- ============================================================ +-- STEP 6: Create the users VIEW +-- ============================================================ + +CREATE OR REPLACE VIEW users AS +SELECT + p.id, + p.created_at, + p.updated_at, + au.email, + NULL::TEXT as password_hash, + CASE + WHEN au.raw_app_meta_data->>'provider' = 'linkedin_oidc' THEN 'linkedin_oauth' + ELSE 'email_password' + END as auth_method, + au.raw_user_meta_data->>'sub' as linkedin_sub, + au.raw_user_meta_data->>'vanityName' as linkedin_vanity_name, + COALESCE(au.raw_user_meta_data->>'name', au.raw_user_meta_data->>'full_name') as linkedin_name, + au.raw_user_meta_data->>'picture' as linkedin_picture, + p.account_type, + p.display_name, + p.onboarding_status, + p.onboarding_data, + p.customer_id, + p.company_id, + au.email_confirmed_at IS NOT NULL as email_verified, + NULL::TEXT as email_verification_token, + NULL::TIMESTAMP WITH TIME ZONE as email_verification_expires_at +FROM profiles p +JOIN auth.users au ON p.id = au.id; + +SELECT 'Users VIEW created' as status; + +-- ============================================================ +-- STEP 7: Create the trigger for new users +-- ============================================================ + +CREATE OR REPLACE FUNCTION public.handle_new_user() +RETURNS TRIGGER AS $$ +BEGIN + INSERT INTO public.profiles (id, account_type, onboarding_status, display_name) + VALUES ( + NEW.id, + COALESCE(NEW.raw_user_meta_data->>'account_type', 'ghostwriter'), + 'pending', + NEW.raw_user_meta_data->>'display_name' + ) + ON CONFLICT (id) DO NOTHING; + RETURN NEW; +END; +$$ LANGUAGE plpgsql SECURITY DEFINER; + +DROP TRIGGER IF EXISTS on_auth_user_created ON auth.users; +CREATE TRIGGER on_auth_user_created + AFTER INSERT ON auth.users + FOR EACH ROW EXECUTE FUNCTION public.handle_new_user(); + +SELECT 'Trigger created' as status; + +-- ============================================================ +-- STEP 8: Add NEW FK constraints pointing to auth.users +-- ============================================================ + +-- Add FK to customers (user_id can be NULL) +ALTER TABLE customers + ADD CONSTRAINT customers_user_id_fkey + FOREIGN KEY (user_id) REFERENCES auth.users(id) ON DELETE SET NULL; + +-- Add FK to companies (only if there are no orphaned references) +ALTER TABLE companies + ADD CONSTRAINT companies_owner_user_id_fkey + FOREIGN KEY (owner_user_id) REFERENCES auth.users(id) ON DELETE CASCADE; + +-- Add FK to invitations +ALTER TABLE invitations + ADD CONSTRAINT invitations_invited_by_user_id_fkey + FOREIGN KEY (invited_by_user_id) REFERENCES auth.users(id) ON DELETE CASCADE; + +ALTER TABLE invitations + ADD CONSTRAINT invitations_accepted_by_user_id_fkey + FOREIGN KEY (accepted_by_user_id) REFERENCES auth.users(id) ON DELETE SET NULL; + +-- Add FK from profiles to companies +ALTER TABLE profiles + ADD CONSTRAINT fk_profiles_company + FOREIGN KEY (company_id) REFERENCES companies(id) ON DELETE SET NULL; + +-- Add FK from profiles to customers +ALTER TABLE profiles + ADD CONSTRAINT fk_profiles_customer + FOREIGN KEY (customer_id) REFERENCES customers(id) ON DELETE SET NULL; + +SELECT 'New FK constraints added' as status; + +-- ============================================================ +-- STEP 9: Create indexes on profiles +-- ============================================================ + +CREATE INDEX IF NOT EXISTS idx_profiles_account_type ON profiles(account_type); +CREATE INDEX IF NOT EXISTS idx_profiles_onboarding_status ON profiles(onboarding_status); +CREATE INDEX IF NOT EXISTS idx_profiles_customer_id ON profiles(customer_id); +CREATE INDEX IF NOT EXISTS idx_profiles_company_id ON profiles(company_id); + +SELECT 'Indexes created' as status; + +-- ============================================================ +-- STEP 10: Create updated_at trigger for profiles +-- ============================================================ + +CREATE OR REPLACE FUNCTION update_updated_at_column() +RETURNS TRIGGER AS $$ +BEGIN + NEW.updated_at = NOW(); + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +DROP TRIGGER IF EXISTS update_profiles_updated_at ON profiles; +CREATE TRIGGER update_profiles_updated_at + BEFORE UPDATE ON profiles + FOR EACH ROW + EXECUTE FUNCTION update_updated_at_column(); + +SELECT 'Updated_at trigger created' as status; + +-- ============================================================ +-- STEP 11: Enable RLS on profiles +-- ============================================================ + +ALTER TABLE profiles ENABLE ROW LEVEL SECURITY; + +DROP POLICY IF EXISTS "Users can view own profile" ON profiles; +DROP POLICY IF EXISTS "Users can update own profile" ON profiles; +DROP POLICY IF EXISTS "Service role has full access to profiles" ON profiles; + +CREATE POLICY "Users can view own profile" + ON profiles FOR SELECT + USING (auth.uid() = id); + +CREATE POLICY "Users can update own profile" + ON profiles FOR UPDATE + USING (auth.uid() = id); + +CREATE POLICY "Service role has full access to profiles" + ON profiles FOR ALL + USING (true); + +SELECT 'RLS enabled' as status; + +-- ============================================================ +-- VERIFICATION +-- ============================================================ + +SELECT 'Migration complete!' as status; +SELECT 'Profiles count:' as info, COUNT(*) as count FROM profiles; +SELECT 'Auth users count:' as info, COUNT(*) as count FROM auth.users; +SELECT 'Customers with user_id:' as info, COUNT(*) as count FROM customers WHERE user_id IS NOT NULL; +SELECT 'Companies count:' as info, COUNT(*) as count FROM companies; + +-- ============================================================ +-- CLEANUP (run later after verifying everything works) +-- ============================================================ +DROP TABLE IF EXISTS users_old; +DROP TABLE IF EXISTS users_backup; +DROP TABLE IF EXISTS customers_backup; +DROP TABLE IF EXISTS companies_backup; +DROP TABLE IF EXISTS invitations_backup; diff --git a/config/supabase_schema.sql b/config/supabase_schema.sql index 407ead3..9cd6e71 100644 --- a/config/supabase_schema.sql +++ b/config/supabase_schema.sql @@ -1,30 +1,92 @@ -- LinkedIn Workflow Database Schema for Supabase +-- Post-migration schema: No customers table, all content uses user_id -- Enable UUID extension CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; --- Customers/Clients Table -CREATE TABLE IF NOT EXISTS customers ( - id UUID PRIMARY wKEY DEFAULT uuid_generate_v4(), +-- ==================== GHOSTWRITER & COMPANY ACCOUNTS ==================== +-- Uses Supabase Auth (auth.users) for authentication +-- The profiles table stores application-specific user data + +-- Profiles Table (extends auth.users with app-specific data) +CREATE TABLE IF NOT EXISTS profiles ( + id UUID PRIMARY KEY REFERENCES auth.users(id) ON DELETE CASCADE, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), - -- Basic Info - name TEXT NOT NULL, - email TEXT, - company_name TEXT, + -- Account Type + account_type TEXT NOT NULL DEFAULT 'ghostwriter', -- 'ghostwriter' | 'company' | 'employee' - -- LinkedIn Profile - linkedin_url TEXT NOT NULL UNIQUE, + -- Display name + display_name TEXT, - -- Metadata - metadata JSONB DEFAULT '{}'::JSONB + -- Onboarding + onboarding_status TEXT DEFAULT 'pending', -- 'pending' | 'profile_setup' | 'posts_scraped' | 'categorizing' | 'completed' + onboarding_data JSONB DEFAULT '{}'::JSONB, + + -- Company link + company_id UUID, -- FK added after companies table + + -- Fields migrated from customers + linkedin_url TEXT, + writing_style_notes TEXT, + metadata JSONB DEFAULT '{}'::JSONB, + profile_picture TEXT, + creator_email TEXT, + customer_email TEXT, + is_active BOOLEAN DEFAULT TRUE ); +-- Function to automatically create profile on user signup +CREATE OR REPLACE FUNCTION public.handle_new_user() +RETURNS TRIGGER AS $$ +BEGIN + INSERT INTO public.profiles (id, account_type, onboarding_status) + VALUES ( + NEW.id, + COALESCE(NEW.raw_user_meta_data->>'account_type', 'ghostwriter'), + 'pending' + ); + RETURN NEW; +END; +$$ LANGUAGE plpgsql SECURITY DEFINER; + +-- Trigger to create profile on auth.users insert +DROP TRIGGER IF EXISTS on_auth_user_created ON auth.users; +CREATE TRIGGER on_auth_user_created + AFTER INSERT ON auth.users + FOR EACH ROW EXECUTE FUNCTION public.handle_new_user(); + +-- Companies Table (company accounts) +CREATE TABLE IF NOT EXISTS companies ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + + -- Company Data + name TEXT NOT NULL, + description TEXT, + website TEXT, + industry TEXT, + + -- Strategy (used during post creation) + company_strategy JSONB DEFAULT '{}'::JSONB, + + owner_user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE, + onboarding_completed BOOLEAN DEFAULT FALSE +); + +-- Add FK from profiles to companies (after companies table exists) +ALTER TABLE profiles + ADD CONSTRAINT fk_profiles_company + FOREIGN KEY (company_id) REFERENCES companies(id) ON DELETE SET NULL; + +-- ==================== CONTENT TABLES (all use user_id) ==================== + -- LinkedIn Profiles Table (scraped data) CREATE TABLE IF NOT EXISTS linkedin_profiles ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), - customer_id UUID NOT NULL REFERENCES customers(id) ON DELETE CASCADE, + user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE, scraped_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), -- Profile Data @@ -37,123 +99,13 @@ CREATE TABLE IF NOT EXISTS linkedin_profiles ( location TEXT, industry TEXT, - UNIQUE(customer_id) + UNIQUE(user_id) ); --- LinkedIn Posts Table (scraped posts) -CREATE TABLE IF NOT EXISTS linkedin_posts ( - id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), - customer_id UUID NOT NULL REFERENCES customers(id) ON DELETE CASCADE, - scraped_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), - - -- Post Data - post_url TEXT, - post_text TEXT NOT NULL, - post_date TIMESTAMP WITH TIME ZONE, - likes INTEGER DEFAULT 0, - comments INTEGER DEFAULT 0, - shares INTEGER DEFAULT 0, - - -- Raw Data - raw_data JSONB, - - UNIQUE(customer_id, post_url) -); - --- Topics Table (extracted from posts) -CREATE TABLE IF NOT EXISTS topics ( - id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), - customer_id UUID NOT NULL REFERENCES customers(id) ON DELETE CASCADE, - created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), - - -- Topic Info - title TEXT NOT NULL, - description TEXT, - category TEXT, - - -- AI Extraction - extracted_from_post_id UUID REFERENCES linkedin_posts(id), - extraction_confidence FLOAT, - - -- Status - is_used BOOLEAN DEFAULT FALSE, - used_at TIMESTAMP WITH TIME ZONE -); - --- Profile Analysis Table (AI-generated insights) -CREATE TABLE IF NOT EXISTS profile_analyses ( - id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), - customer_id UUID NOT NULL REFERENCES customers(id) ON DELETE CASCADE, - created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), - - -- Analysis Results - writing_style JSONB NOT NULL, - tone_analysis JSONB NOT NULL, - topic_patterns JSONB NOT NULL, - audience_insights JSONB NOT NULL, - - -- Full Analysis - full_analysis JSONB NOT NULL, - - UNIQUE(customer_id) -); - --- Research Results Table -CREATE TABLE IF NOT EXISTS research_results ( - id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), - customer_id UUID NOT NULL REFERENCES customers(id) ON DELETE CASCADE, - created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), - - -- Research Data - query TEXT NOT NULL, - results JSONB NOT NULL, - - -- Topic Suggestions - suggested_topics JSONB NOT NULL, - - -- Metadata - source TEXT DEFAULT 'perplexity' -); - --- Generated Posts Table -CREATE TABLE IF NOT EXISTS generated_posts ( - id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), - customer_id UUID NOT NULL REFERENCES customers(id) ON DELETE CASCADE, - created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), - - -- Topic - topic_id UUID REFERENCES topics(id), - topic_title TEXT NOT NULL, - - -- Post Content - post_content TEXT NOT NULL, - - -- Generation Metadata - iterations INTEGER DEFAULT 0, - writer_versions JSONB DEFAULT '[]'::JSONB, - critic_feedback JSONB DEFAULT '[]'::JSONB, - - -- Status - status TEXT DEFAULT 'draft' CHECK (status IN ('draft', 'approved', 'published', 'rejected')), - approved_at TIMESTAMP WITH TIME ZONE, - published_at TIMESTAMP WITH TIME ZONE -); - --- Create Indexes -CREATE INDEX idx_customers_linkedin_url ON customers(linkedin_url); -CREATE INDEX idx_linkedin_profiles_customer_id ON linkedin_profiles(customer_id); -CREATE INDEX idx_linkedin_posts_customer_id ON linkedin_posts(customer_id); -CREATE INDEX idx_topics_customer_id ON topics(customer_id); -CREATE INDEX idx_topics_is_used ON topics(is_used); -CREATE INDEX idx_profile_analyses_customer_id ON profile_analyses(customer_id); -CREATE INDEX idx_research_results_customer_id ON research_results(customer_id); -CREATE INDEX idx_generated_posts_customer_id ON generated_posts(customer_id); -CREATE INDEX idx_generated_posts_status ON generated_posts(status); - -- Post Types Table (for categorizing posts by type) CREATE TABLE IF NOT EXISTS post_types ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), - customer_id UUID NOT NULL REFERENCES customers(id) ON DELETE CASCADE, + user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), @@ -172,36 +124,228 @@ CREATE TABLE IF NOT EXISTS post_types ( -- Status is_active BOOLEAN DEFAULT TRUE, - UNIQUE(customer_id, name) + UNIQUE(user_id, name) ); --- Add post_type_id to linkedin_posts -ALTER TABLE linkedin_posts - ADD COLUMN IF NOT EXISTS post_type_id UUID REFERENCES post_types(id) ON DELETE SET NULL, - ADD COLUMN IF NOT EXISTS classification_method TEXT, - ADD COLUMN IF NOT EXISTS classification_confidence FLOAT; +-- LinkedIn Posts Table (scraped posts) +CREATE TABLE IF NOT EXISTS linkedin_posts ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE, + scraped_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), --- Add target_post_type_id to topics -ALTER TABLE topics - ADD COLUMN IF NOT EXISTS target_post_type_id UUID REFERENCES post_types(id) ON DELETE SET NULL; + -- Post Data + post_url TEXT, + post_text TEXT NOT NULL, + post_date TIMESTAMP WITH TIME ZONE, + likes INTEGER DEFAULT 0, + comments INTEGER DEFAULT 0, + shares INTEGER DEFAULT 0, --- Add target_post_type_id to research_results -ALTER TABLE research_results - ADD COLUMN IF NOT EXISTS target_post_type_id UUID REFERENCES post_types(id) ON DELETE SET NULL; + -- Raw Data + raw_data JSONB, --- Add post_type_id to generated_posts -ALTER TABLE generated_posts - ADD COLUMN IF NOT EXISTS post_type_id UUID REFERENCES post_types(id) ON DELETE SET NULL; + -- Classification + post_type_id UUID REFERENCES post_types(id) ON DELETE SET NULL, + classification_method TEXT, + classification_confidence FLOAT, --- Create indexes for post_types -CREATE INDEX IF NOT EXISTS idx_post_types_customer_id ON post_types(customer_id); -CREATE INDEX IF NOT EXISTS idx_post_types_is_active ON post_types(is_active); -CREATE INDEX IF NOT EXISTS idx_linkedin_posts_post_type_id ON linkedin_posts(post_type_id); + UNIQUE(user_id, post_url) +); + +-- Topics Table (extracted from posts) +CREATE TABLE IF NOT EXISTS topics ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE, + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + + -- Topic Info + title TEXT NOT NULL, + description TEXT, + category TEXT, + + -- AI Extraction + extracted_from_post_id UUID REFERENCES linkedin_posts(id), + extraction_confidence FLOAT, + + -- Status + is_used BOOLEAN DEFAULT FALSE, + used_at TIMESTAMP WITH TIME ZONE, + + -- Target post type + target_post_type_id UUID REFERENCES post_types(id) ON DELETE SET NULL +); + +-- Profile Analysis Table (AI-generated insights) +CREATE TABLE IF NOT EXISTS profile_analyses ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE, + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + + -- Analysis Results + writing_style JSONB NOT NULL, + tone_analysis JSONB NOT NULL, + topic_patterns JSONB NOT NULL, + audience_insights JSONB NOT NULL, + + -- Full Analysis + full_analysis JSONB NOT NULL, + + UNIQUE(user_id) +); + +-- Research Results Table +CREATE TABLE IF NOT EXISTS research_results ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE, + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + + -- Research Data + query TEXT NOT NULL, + results JSONB NOT NULL, + + -- Topic Suggestions + suggested_topics JSONB NOT NULL, + + -- Metadata + source TEXT DEFAULT 'perplexity', + + -- Target post type + target_post_type_id UUID REFERENCES post_types(id) ON DELETE SET NULL +); + +-- Generated Posts Table +CREATE TABLE IF NOT EXISTS generated_posts ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE, + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + + -- Topic + topic_id UUID REFERENCES topics(id), + topic_title TEXT NOT NULL, + + -- Post Content + post_content TEXT NOT NULL, + + -- Generation Metadata + iterations INTEGER DEFAULT 0, + writer_versions JSONB DEFAULT '[]'::JSONB, + critic_feedback JSONB DEFAULT '[]'::JSONB, + + -- Status + status TEXT DEFAULT 'draft' CHECK (status IN ('draft', 'approved', 'scheduled', 'published', 'rejected')), + approved_at TIMESTAMP WITH TIME ZONE, + published_at TIMESTAMP WITH TIME ZONE, + + -- Post type + post_type_id UUID REFERENCES post_types(id) ON DELETE SET NULL, + + -- Scheduling + scheduled_at TIMESTAMP WITH TIME ZONE, + scheduled_by_user_id UUID REFERENCES auth.users(id) ON DELETE SET NULL, + + -- Image + image_url TEXT +); + +-- Invitations Table (employee invitations) +CREATE TABLE IF NOT EXISTS invitations ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + + email TEXT NOT NULL, + token TEXT NOT NULL UNIQUE, + expires_at TIMESTAMP WITH TIME ZONE NOT NULL, + + company_id UUID NOT NULL REFERENCES companies(id) ON DELETE CASCADE, + invited_by_user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE, + + status TEXT DEFAULT 'pending', -- 'pending' | 'accepted' | 'expired' | 'cancelled' + accepted_at TIMESTAMP WITH TIME ZONE, + accepted_by_user_id UUID REFERENCES auth.users(id) +); + +-- Example Posts Table (manual example posts when <10 scraped) +CREATE TABLE IF NOT EXISTS example_posts ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE, + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + + post_text TEXT NOT NULL, + source TEXT DEFAULT 'manual', -- 'manual' | 'reference_profile' + source_linkedin_url TEXT, + post_type_id UUID REFERENCES post_types(id) ON DELETE SET NULL +); + +-- Reference Profiles Table (alternative LinkedIn profiles for scraping) +CREATE TABLE IF NOT EXISTS reference_profiles ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE, + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + + linkedin_url TEXT NOT NULL, + name TEXT, + posts_scraped INTEGER DEFAULT 0, + + UNIQUE(user_id, linkedin_url) +); + +-- ==================== API USAGE LOGS ==================== + +CREATE TABLE IF NOT EXISTS api_usage_logs ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + user_id UUID REFERENCES auth.users(id) ON DELETE SET NULL, + company_id UUID REFERENCES companies(id) ON DELETE SET NULL, + provider TEXT NOT NULL, + model TEXT NOT NULL, + operation TEXT NOT NULL, + prompt_tokens INT NOT NULL DEFAULT 0, + completion_tokens INT NOT NULL DEFAULT 0, + total_tokens INT NOT NULL DEFAULT 0, + estimated_cost_usd DECIMAL(10,6) NOT NULL DEFAULT 0, + created_at TIMESTAMPTZ DEFAULT now() +); + +-- ==================== INDEXES ==================== + +-- Profiles +CREATE INDEX IF NOT EXISTS idx_profiles_account_type ON profiles(account_type); +CREATE INDEX IF NOT EXISTS idx_profiles_onboarding_status ON profiles(onboarding_status); +CREATE INDEX IF NOT EXISTS idx_profiles_company_id ON profiles(company_id); + +-- Content tables +CREATE INDEX IF NOT EXISTS idx_linkedin_profiles_user_id ON linkedin_profiles(user_id); +CREATE INDEX IF NOT EXISTS idx_linkedin_posts_user_id ON linkedin_posts(user_id); +CREATE INDEX IF NOT EXISTS idx_topics_user_id ON topics(user_id); +CREATE INDEX IF NOT EXISTS idx_topics_is_used ON topics(is_used); CREATE INDEX IF NOT EXISTS idx_topics_target_post_type_id ON topics(target_post_type_id); +CREATE INDEX IF NOT EXISTS idx_post_types_user_id ON post_types(user_id); +CREATE INDEX IF NOT EXISTS idx_post_types_is_active ON post_types(is_active); +CREATE INDEX IF NOT EXISTS idx_profile_analyses_user_id ON profile_analyses(user_id); +CREATE INDEX IF NOT EXISTS idx_research_results_user_id ON research_results(user_id); CREATE INDEX IF NOT EXISTS idx_research_results_target_post_type_id ON research_results(target_post_type_id); +CREATE INDEX IF NOT EXISTS idx_generated_posts_user_id ON generated_posts(user_id); +CREATE INDEX IF NOT EXISTS idx_generated_posts_status ON generated_posts(status); CREATE INDEX IF NOT EXISTS idx_generated_posts_post_type_id ON generated_posts(post_type_id); +CREATE INDEX IF NOT EXISTS idx_generated_posts_scheduled_at ON generated_posts(scheduled_at) + WHERE scheduled_at IS NOT NULL AND status = 'scheduled'; +CREATE INDEX IF NOT EXISTS idx_linkedin_posts_post_type_id ON linkedin_posts(post_type_id); +CREATE INDEX IF NOT EXISTS idx_example_posts_user_id ON example_posts(user_id); +CREATE INDEX IF NOT EXISTS idx_reference_profiles_user_id ON reference_profiles(user_id); + +-- Companies & Invitations +CREATE INDEX IF NOT EXISTS idx_companies_owner_user_id ON companies(owner_user_id); +CREATE INDEX IF NOT EXISTS idx_invitations_token ON invitations(token); +CREATE INDEX IF NOT EXISTS idx_invitations_company_id ON invitations(company_id); +CREATE INDEX IF NOT EXISTS idx_invitations_email ON invitations(email); +CREATE INDEX IF NOT EXISTS idx_invitations_status ON invitations(status); + +-- API usage +CREATE INDEX IF NOT EXISTS idx_api_usage_logs_user_id ON api_usage_logs(user_id); +CREATE INDEX IF NOT EXISTS idx_api_usage_logs_company_id ON api_usage_logs(company_id); +CREATE INDEX IF NOT EXISTS idx_api_usage_logs_created_at ON api_usage_logs(created_at); + +-- ==================== TRIGGERS ==================== --- Create updated_at trigger function CREATE OR REPLACE FUNCTION update_updated_at_column() RETURNS TRIGGER AS $$ BEGIN @@ -210,15 +354,196 @@ BEGIN END; $$ LANGUAGE plpgsql; --- Add trigger to customers table -CREATE TRIGGER update_customers_updated_at - BEFORE UPDATE ON customers +DROP TRIGGER IF EXISTS update_profiles_updated_at ON profiles; +CREATE TRIGGER update_profiles_updated_at + BEFORE UPDATE ON profiles FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); --- Add trigger to post_types table DROP TRIGGER IF EXISTS update_post_types_updated_at ON post_types; CREATE TRIGGER update_post_types_updated_at BEFORE UPDATE ON post_types FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); + +DROP TRIGGER IF EXISTS update_companies_updated_at ON companies; +CREATE TRIGGER update_companies_updated_at + BEFORE UPDATE ON companies + FOR EACH ROW + EXECUTE FUNCTION update_updated_at_column(); + +-- ==================== ROW LEVEL SECURITY ==================== + +ALTER TABLE profiles ENABLE ROW LEVEL SECURITY; + +CREATE POLICY "Users can view own profile" + ON profiles FOR SELECT + USING (auth.uid() = id); + +CREATE POLICY "Users can update own profile" + ON profiles FOR UPDATE + USING (auth.uid() = id); + +CREATE POLICY "Service role has full access to profiles" + ON profiles + USING (auth.jwt() ->> 'role' = 'service_role'); + +ALTER TABLE companies ENABLE ROW LEVEL SECURITY; + +CREATE POLICY "Company owners can manage their company" + ON companies + USING (auth.uid() = owner_user_id); + +CREATE POLICY "Employees can view their company" + ON companies FOR SELECT + USING ( + EXISTS ( + SELECT 1 FROM profiles + WHERE profiles.id = auth.uid() + AND profiles.company_id = companies.id + ) + ); + +CREATE POLICY "Service role has full access to companies" + ON companies + USING (auth.jwt() ->> 'role' = 'service_role'); + +-- Generated posts RLS +ALTER TABLE generated_posts ENABLE ROW LEVEL SECURITY; + +CREATE POLICY "Users can manage own posts" + ON generated_posts FOR ALL + USING (user_id = auth.uid()); + +CREATE POLICY "Company members can view company posts" + ON generated_posts FOR SELECT + USING ( + EXISTS ( + SELECT 1 FROM profiles p1 + JOIN profiles p2 ON p1.company_id = p2.company_id + WHERE p1.id = auth.uid() + AND p2.id = generated_posts.user_id + AND p1.company_id IS NOT NULL + ) + ); + +CREATE POLICY "Service role has full access to generated_posts" + ON generated_posts FOR ALL + USING (auth.jwt() ->> 'role' = 'service_role'); + +-- ==================== USERS VIEW ==================== + +CREATE OR REPLACE VIEW users AS +SELECT + au.id, + au.created_at, + au.updated_at, + au.email, + au.raw_user_meta_data->>'sub' AS linkedin_sub, + au.raw_user_meta_data->>'vanityName' AS linkedin_vanity_name, + au.raw_user_meta_data->>'name' AS linkedin_name, + au.raw_user_meta_data->>'picture' AS linkedin_picture, + CASE + WHEN au.raw_user_meta_data->>'iss' LIKE '%linkedin%' THEN 'linkedin_oauth' + ELSE 'email_password' + END AS auth_method, + COALESCE(p.account_type, 'ghostwriter') AS account_type, + p.display_name, + COALESCE(p.onboarding_status, 'pending') AS onboarding_status, + COALESCE(p.onboarding_data, '{}'::jsonb) AS onboarding_data, + p.company_id, + p.linkedin_url, + p.writing_style_notes, + p.metadata, + p.profile_picture, + p.creator_email, + p.customer_email, + p.is_active, + au.email_confirmed_at IS NOT NULL AS email_verified +FROM auth.users au +LEFT JOIN profiles p ON au.id = p.id; + +-- ==================== LICENSE KEY SYSTEM ==================== + +-- License Keys Table +CREATE TABLE IF NOT EXISTS license_keys ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + key TEXT UNIQUE NOT NULL, + description TEXT, + + -- Limits (stored here, NOT in companies table) + max_employees INT NOT NULL DEFAULT 5, + max_posts_per_day INT NOT NULL DEFAULT 10, + max_researches_per_day INT NOT NULL DEFAULT 5, + + -- Usage tracking + used BOOLEAN DEFAULT FALSE, + company_id UUID REFERENCES companies(id) ON DELETE SET NULL, + used_at TIMESTAMP WITH TIME ZONE, + + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() +); + +-- Index for fast lookups +CREATE INDEX IF NOT EXISTS idx_license_keys_key ON license_keys(key); +CREATE INDEX IF NOT EXISTS idx_license_keys_used ON license_keys(used); +CREATE INDEX IF NOT EXISTS idx_license_keys_company_id ON license_keys(company_id); + +-- RLS Policies (Admin only) +ALTER TABLE license_keys ENABLE ROW LEVEL SECURITY; + +CREATE POLICY "Service role has full access to license_keys" + ON license_keys FOR ALL + USING (auth.jwt()->>'role' = 'service_role'); + +-- Add license key reference to companies table +-- (limits are stored in license_keys table, not duplicated here) +ALTER TABLE companies ADD COLUMN IF NOT EXISTS license_key_id UUID REFERENCES license_keys(id); + +-- Index +CREATE INDEX IF NOT EXISTS idx_companies_license_key_id ON companies(license_key_id); + +-- Company Daily Quotas Table +CREATE TABLE IF NOT EXISTS company_daily_quotas ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + company_id UUID NOT NULL REFERENCES companies(id) ON DELETE CASCADE, + date DATE NOT NULL, + posts_created INT DEFAULT 0, + researches_created INT DEFAULT 0, + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + + UNIQUE(company_id, date) +); + +-- Index for fast daily lookups +CREATE INDEX IF NOT EXISTS idx_company_daily_quotas_company_date ON company_daily_quotas(company_id, date); + +-- RLS Policies +ALTER TABLE company_daily_quotas ENABLE ROW LEVEL SECURITY; + +CREATE POLICY "Users can view own company quotas" + ON company_daily_quotas FOR SELECT + USING ( + company_id IN ( + SELECT company_id FROM profiles WHERE id = auth.uid() + ) + ); + +CREATE POLICY "Service role has full access to company_daily_quotas" + ON company_daily_quotas FOR ALL + USING (auth.jwt()->>'role' = 'service_role'); + +-- Triggers for updated_at +DROP TRIGGER IF EXISTS update_license_keys_updated_at ON license_keys; +CREATE TRIGGER update_license_keys_updated_at + BEFORE UPDATE ON license_keys + FOR EACH ROW + EXECUTE FUNCTION update_updated_at_column(); + +DROP TRIGGER IF EXISTS update_company_daily_quotas_updated_at ON company_daily_quotas; +CREATE TRIGGER update_company_daily_quotas_updated_at + BEFORE UPDATE ON company_daily_quotas + FOR EACH ROW + EXECUTE FUNCTION update_updated_at_column(); diff --git a/docker-compose.ssl.yml b/docker-compose.ssl.yml index ad2db09..011c032 100644 --- a/docker-compose.ssl.yml +++ b/docker-compose.ssl.yml @@ -6,18 +6,19 @@ services: container_name: linkedin-posts restart: unless-stopped expose: - - "8000" + - "8001" env_file: - .env environment: - PYTHONPATH=/app + - PORT=8001 - VIRTUAL_HOST=linkedin.onyva.dev - - VIRTUAL_PORT=8000 + - VIRTUAL_PORT=8001 - LETSENCRYPT_HOST=linkedin.onyva.dev volumes: - ./logs:/app/logs healthcheck: - test: ["CMD", "python", "-c", "import httpx; httpx.get('http://localhost:8000/login', timeout=5)"] + test: ["CMD", "python", "-c", "import httpx; httpx.get('http://localhost:8001/login', timeout=5)"] interval: 30s timeout: 10s retries: 3 diff --git a/docker-compose.yml b/docker-compose.yml index 1591494..7581e75 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -6,16 +6,17 @@ services: container_name: linkedin-posts restart: unless-stopped ports: - - "8000:8000" + - "8001:8001" env_file: - .env environment: - PYTHONPATH=/app + - PORT=8001 volumes: # Optional: Mount logs directory - ./logs:/app/logs healthcheck: - test: ["CMD", "python", "-c", "import httpx; httpx.get('http://localhost:8000/login', timeout=5)"] + test: ["CMD", "python", "-c", "import httpx; httpx.get('http://localhost:8001/login', timeout=5)"] interval: 30s timeout: 10s retries: 3 diff --git a/localhost.har b/localhost.har new file mode 100644 index 0000000..d01d774 --- /dev/null +++ b/localhost.har @@ -0,0 +1,694 @@ +{ + "log": { + "version": "1.2", + "creator": { + "name": "WebInspector", + "version": "537.36" + }, + "pages": [ + { + "startedDateTime": "2026-02-05T19:27:21.684Z", + "id": "page_1", + "title": "http://localhost:8000/company/calendar", + "pageTimings": { + "onContentLoad": 4722.670000002836, + "onLoad": 4724.067000002833 + } + } + ], + "entries": [ + { + "_connectionId": "383438", + "_initiator": { + "type": "other" + }, + "_priority": "VeryHigh", + "_resourceType": "document", + "cache": {}, + "connection": "8000", + "pageref": "page_1", + "request": { + "method": "GET", + "url": "http://localhost:8000/company/calendar", + "httpVersion": "HTTP/1.1", + "headers": [ + { + "name": "Accept", + "value": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7" + }, + { + "name": "Accept-Encoding", + "value": "gzip, deflate, br, zstd" + }, + { + "name": "Accept-Language", + "value": "de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7" + }, + { + "name": "Cache-Control", + "value": "no-cache" + }, + { + "name": "Connection", + "value": "keep-alive" + }, + { + "name": "Host", + "value": "localhost:8000" + }, + { + "name": "Pragma", + "value": "no-cache" + }, + { + "name": "Referer", + "value": "http://localhost:8000/" + }, + { + "name": "Sec-Fetch-Dest", + "value": "document" + }, + { + "name": "Sec-Fetch-Mode", + "value": "navigate" + }, + { + "name": "Sec-Fetch-Site", + "value": "same-origin" + }, + { + "name": "Sec-Fetch-User", + "value": "?1" + }, + { + "name": "Upgrade-Insecure-Requests", + "value": "1" + }, + { + "name": "User-Agent", + "value": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Mobile Safari/537.36" + }, + { + "name": "sec-ch-ua", + "value": "\"Not(A:Brand\";v=\"8\", \"Chromium\";v=\"144\", \"Google Chrome\";v=\"144\"" + }, + { + "name": "sec-ch-ua-mobile", + "value": "?1" + }, + { + "name": "sec-ch-ua-platform", + "value": "\"Android\"" + } + ], + "queryString": [], + "cookies": [], + "headersSize": 1373, + "bodySize": 0 + }, + "response": { + "status": 200, + "statusText": "OK", + "httpVersion": "HTTP/1.1", + "headers": [ + { + "name": "content-length", + "value": "42769" + }, + { + "name": "content-type", + "value": "text/html; charset=utf-8" + }, + { + "name": "date", + "value": "Thu, 05 Feb 2026 19:27:21 GMT" + }, + { + "name": "server", + "value": "uvicorn" + } + ], + "cookies": [], + "content": { + "size": 42769, + "mimeType": "text/html", + "compression": 0, + "text": "\n\n\n \n \n Posting-Kalender - Test Unternehmen\n \n \n \n \n\n\n\n\n \n \n\n \n
\n
\n \n
\n \n
\n
\n

Posting-Kalender

\n

Plane und verwalte Posts aller Mitarbeiter

\n
\n
\n \n
\n \n \n \n \n \n Februar 2026\n \n \n \n \n \n
\n \n Heute\n \n
\n
\n\n \n \n
\n

Mitarbeiter

\n
\n \n
\n
\n Ruben Fischer\n
\n \n
\n
\n \n\n \n
\n \n
\n
Mo
\n
Di
\n
Mi
\n
Do
\n
Fr
\n
Sa
\n
So
\n
\n\n \n
\n \n \n
\n
\n \n
\n \n \n \n \n
\n \n
\n
\n \n
\n \n \n \n \n
\n \n
\n
\n \n
\n \n \n \n \n
\n \n
\n
\n \n
\n \n \n \n \n
\n \n
\n
\n \n
\n \n \n \n \n
\n \n
\n
\n \n
\n \n \n \n \n
\n \n
\n
\n 1\n
\n \n \n \n \n \n \n
\n \n \n \n
\n
\n 2\n
\n \n \n \n \n \n \n
\n \n
\n
\n 3\n
\n \n \n \n \n \n \n
\n \n
\n
\n 4\n
\n \n \n \n \n \n \n
\n \n
\n
\n 5\n
\n \n \n \n \n \n \n
\n \n
\n
\n 6\n
\n \n \n \n \n \n \n
\n \n
\n
\n 7\n
\n \n \n \n \n \n \n
\n \n
\n
\n 8\n
\n \n \n \n \n \n \n
\n \n \n \n
\n
\n 9\n
\n \n \n \n \n \n \n
\n \n
\n
\n 10\n
\n \n \n \n \n \n \n
\n \n
\n
\n 11\n
\n \n \n \n \n \n \n
\n \n
\n
\n 12\n
\n \n \n \n \n \n \n
\n \n
\n
\n 13\n
\n \n \n \n \n \n \n
\n \n
\n
\n 14\n
\n \n \n \n \n \n \n
\n \n
\n
\n 15\n
\n \n \n \n \n \n \n
\n \n \n \n
\n
\n 16\n
\n \n \n \n \n \n \n
\n \n
\n
\n 17\n
\n \n \n \n \n \n \n
\n \n
\n
\n 18\n
\n \n \n \n \n \n \n
\n \n
\n
\n 19\n
\n \n \n \n \n \n \n
\n \n
\n
\n 20\n
\n \n \n \n \n \n \n
\n \n
\n
\n 21\n
\n \n \n \n \n \n \n
\n \n
\n
\n 22\n
\n \n \n \n \n \n \n
\n \n \n \n
\n
\n 23\n
\n \n \n \n \n \n \n
\n \n
\n
\n 24\n
\n \n \n \n \n \n \n
\n \n
\n
\n 25\n
\n \n \n \n \n \n \n
\n \n
\n
\n 26\n
\n \n \n \n \n \n \n
\n \n
\n
\n 27\n
\n \n \n \n \n \n \n
\n \n
\n
\n 28\n
\n \n \n \n \n \n \n
\n \n
\n
\n \n
\n \n \n \n \n
\n \n \n
\n
\n\n \n \n
\n\n\n\n\n\n\n\n
\n
\n\n \n\n\n\n" + }, + "redirectURL": "", + "headersSize": 136, + "bodySize": 42769, + "_transferSize": 42905, + "_error": null, + "_fetchedViaServiceWorker": false + }, + "serverIPAddress": "127.0.0.1", + "startedDateTime": "2026-02-05T19:27:21.683Z", + "time": 4420.359000000665, + "timings": { + "blocked": 2.383000000653788, + "dns": 0.008000000000000007, + "ssl": -1, + "connect": 0.2610000000000001, + "send": 0.242, + "wait": 4416.512999997867, + "receive": 0.9520000021439046, + "_blocked_queueing": 1.2270000006537884, + "_workerStart": -1, + "_workerReady": -1, + "_workerFetchStart": -1, + "_workerRespondWithSettled": -1 + } + }, + { + "_connectionId": "382392", + "_initiator": { + "type": "parser", + "url": "http://localhost:8000/company/calendar", + "lineNumber": 6 + }, + "_priority": "High", + "_resourceType": "script", + "cache": {}, + "connection": "443", + "pageref": "page_1", + "request": { + "method": "GET", + "url": "https://cdn.tailwindcss.com/", + "httpVersion": "http/2.0", + "headers": [ + { + "name": ":authority", + "value": "cdn.tailwindcss.com" + }, + { + "name": ":method", + "value": "GET" + }, + { + "name": ":path", + "value": "/" + }, + { + "name": ":scheme", + "value": "https" + }, + { + "name": "accept", + "value": "*/*" + }, + { + "name": "accept-encoding", + "value": "gzip, deflate, br, zstd" + }, + { + "name": "accept-language", + "value": "de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7" + }, + { + "name": "cache-control", + "value": "no-cache" + }, + { + "name": "pragma", + "value": "no-cache" + }, + { + "name": "priority", + "value": "u=1" + }, + { + "name": "referer", + "value": "http://localhost:8000/" + }, + { + "name": "sec-ch-ua", + "value": "\"Not(A:Brand\";v=\"8\", \"Chromium\";v=\"144\", \"Google Chrome\";v=\"144\"" + }, + { + "name": "sec-ch-ua-mobile", + "value": "?1" + }, + { + "name": "sec-ch-ua-platform", + "value": "\"Android\"" + }, + { + "name": "sec-fetch-dest", + "value": "script" + }, + { + "name": "sec-fetch-mode", + "value": "no-cors" + }, + { + "name": "sec-fetch-site", + "value": "cross-site" + }, + { + "name": "sec-fetch-storage-access", + "value": "active" + }, + { + "name": "user-agent", + "value": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Mobile Safari/537.36" + } + ], + "queryString": [], + "cookies": [], + "headersSize": -1, + "bodySize": 0 + }, + "response": { + "status": 302, + "statusText": "", + "httpVersion": "http/2.0", + "headers": [ + { + "name": "age", + "value": "97" + }, + { + "name": "cache-control", + "value": "max-age=14400" + }, + { + "name": "cf-cache-status", + "value": "HIT" + }, + { + "name": "cf-ray", + "value": "9c94ce1c59f63d4d-FRA" + }, + { + "name": "date", + "value": "Thu, 05 Feb 2026 19:27:26 GMT" + }, + { + "name": "location", + "value": "/3.4.17" + }, + { + "name": "nel", + "value": "{\"report_to\":\"cf-nel\",\"success_fraction\":0.0,\"max_age\":604800}" + }, + { + "name": "report-to", + "value": "{\"group\":\"cf-nel\",\"max_age\":604800,\"endpoints\":[{\"url\":\"https://a.nel.cloudflare.com/report/v4?s=PwPqk4FenZacl1zZExHIYcLuFOOoPRMeZHI8S49DCaLUF9sBOII%2Fp1AoTA7ay4dgt%2B13hT8IOZdmOJsEED295QjtkyFYj8mpEhlvrsMtoK9PV2wVemzHDt8t%2FAThpA%3D%3D\"}]}" + }, + { + "name": "server", + "value": "cloudflare" + }, + { + "name": "strict-transport-security", + "value": "max-age=63072000" + }, + { + "name": "x-vercel-cache", + "value": "MISS" + }, + { + "name": "x-vercel-id", + "value": "fra1::iad1::9h6kb-1770319548263-4c2cdf7dc511" + } + ], + "cookies": [], + "content": { + "size": 0, + "mimeType": "x-unknown" + }, + "redirectURL": "/3.4.17", + "headersSize": -1, + "bodySize": -1, + "_transferSize": 264, + "_error": null, + "_fetchedViaServiceWorker": false + }, + "serverIPAddress": "[2606:4700:20::ac43:440b]", + "startedDateTime": "2026-02-05T19:27:26.116Z", + "time": 80.35699999891222, + "timings": { + "blocked": 2.2920000003818424, + "dns": -1, + "ssl": -1, + "connect": -1, + "send": 0.361, + "wait": 23.706000002758113, + "receive": 53.99799999577226, + "_blocked_queueing": 1.7850000003818423, + "_workerStart": -1, + "_workerReady": -1, + "_workerFetchStart": -1, + "_workerRespondWithSettled": -1 + } + }, + { + "_connectionId": "383438", + "_initiator": { + "type": "parser", + "url": "http://localhost:8000/company/calendar", + "lineNumber": 104 + }, + "_priority": "Medium", + "_resourceType": "image", + "cache": {}, + "connection": "8000", + "pageref": "page_1", + "request": { + "method": "GET", + "url": "http://localhost:8000/static/logo.png", + "httpVersion": "HTTP/1.1", + "headers": [ + { + "name": "Accept", + "value": "image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8" + }, + { + "name": "Accept-Encoding", + "value": "gzip, deflate, br, zstd" + }, + { + "name": "Accept-Language", + "value": "de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7" + }, + { + "name": "Cache-Control", + "value": "no-cache" + }, + { + "name": "Connection", + "value": "keep-alive" + }, + { + "name": "Host", + "value": "localhost:8000" + }, + { + "name": "Pragma", + "value": "no-cache" + }, + { + "name": "Referer", + "value": "http://localhost:8000/company/calendar" + }, + { + "name": "Sec-Fetch-Dest", + "value": "image" + }, + { + "name": "Sec-Fetch-Mode", + "value": "no-cors" + }, + { + "name": "Sec-Fetch-Site", + "value": "same-origin" + }, + { + "name": "User-Agent", + "value": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Mobile Safari/537.36" + }, + { + "name": "sec-ch-ua", + "value": "\"Not(A:Brand\";v=\"8\", \"Chromium\";v=\"144\", \"Google Chrome\";v=\"144\"" + }, + { + "name": "sec-ch-ua-mobile", + "value": "?1" + }, + { + "name": "sec-ch-ua-platform", + "value": "\"Android\"" + } + ], + "queryString": [], + "cookies": [], + "headersSize": 1263, + "bodySize": 0 + }, + "response": { + "status": 200, + "statusText": "OK", + "httpVersion": "HTTP/1.1", + "headers": [ + { + "name": "accept-ranges", + "value": "bytes" + }, + { + "name": "content-length", + "value": "53598" + }, + { + "name": "content-type", + "value": "image/png" + }, + { + "name": "date", + "value": "Thu, 05 Feb 2026 19:27:25 GMT" + }, + { + "name": "etag", + "value": "\"b312006b35ca688d31f5e310cfeba837\"" + }, + { + "name": "last-modified", + "value": "Wed, 21 Jan 2026 22:01:11 GMT" + }, + { + "name": "server", + "value": "uvicorn" + } + ], + "cookies": [], + "content": { + "size": 53598, + "mimeType": "image/png", + "compression": 0, + "text": "iVBORw0KGgoAAAANSUhEUgAAA+gAAAF4CAYAAADHWtOOAACAAElEQVR4Xuy9Xaws2Vmmefhx2xa2McKGwgbzYwYbI6AFTSNgukXPSMwgpm+sKS4AzSUtzchStxhpxhcjWa2h7/rCAg19M5oRg6YlwDI/ssDmuN3l+sEeCvyD7Sq7imOXy1Xl4/Ipu46xDQXUmXhWrjf2m99ekZn77L/c+7yv9GlFRkZmRkasiFjP+r71rStXoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoiiKoig6Dd269TVub7l162vPyu7+7d/+ustu9T+P7IqO/50gq2c6Rr/84IMvOI75sd56LLfU9/r+aJs7xa7UY3FZVc5zvYaPci3L9J3NorPXKV27V87qmqi/U+wtb3nLoX2rtvaZ6Gzl52pwbjh/9f6ydq/h/Hbbi/NY/kvd39u9T5631X0/bau/P7Ir9XqPoujO1K1bV2ZYquDzplu3Xriv9j9+7nMvOSmr331W5oB5R9yIp/+oeqZjUM/F7Zof043H0/ZhBPmj9zZZPacX1er/qsdkuk98bT2Ul0KlPnAs7n7ggRe7/fT/9Z4XVavHanTcuK/eunJlXA+j09XCdb7NvM6P7gd+bufG9HFUG+OT8UyWrYHa3Xd/HTbaryXTvtafjU5XHHOdo9G5qveTkR2qcw7rZ6lbBx3q7E99dizdK+t/Pi+r+7ur1efAUax+V7W6j9Xm6/asz3UURecv9eC2mwE3lY985CWYgOcXHnvsm1Qu2f908+Y3byrrurOwN37qU99W1+2b+THkGOuGzrm47CDUHny9nnEsOF8nYXyXwzq/VXeBfdADe1Tn9dqtdgT49eHnsF4b52G1ni3VuSXz/7d2HHqjQw0H7LKAJ42g9r/6ufZj+TP33/8tmNb93L0f3mprx86OGffby3LM9lp2jS9dr6PzeajOb7J+XrFjN6I7/AiAHL4FOvyeSr9v/fTb3/PyQ/tWTZ8Z3Q+jExedKqNnjM6Xm+qe7jOyup3Ov99LjlXnjiLqp7UVdV1x/Wh/da+s19S8/7VOnpDVe+82q9f/ktXzcTvm3zO637jV/9WsX7d+3mdoj6LoAqn3quohr8bD6EHBzVU3K24eApz/9qFPfpfsZz99/bXV/uVTN36A8l9/4QvfpeWR6f2l8rTs5558+kfrci3r8lnZz3z8k6+X1eOlYy7I1M18bjB2eBfAr/WsntVD+rjqjWb+C/+N/0p94HhwfDgnxzEdYwd1jtfaLlxZNZw4ptqHWu83WT1v1fwcj6xuj7HfdXnTum1l/T0dF1/etD+y+t+9I6Q2OLxBoXvORaqj7K8anNQd/i/H5yfueeAHdcxYHp3PpeNYj52OmerliYBddKAC5bq+63W77bzVc1dN10A9l8c6j/bc5hryzgQ9n3/iD9/96n/8px/4LjftE/v8g+98z/x/6j6zrfbZr9NW/6ITEcdSzzeBI+eunjMZ50vGvWXTa7bn/PN9ut/qHnusererOqD785t6xfNbz3D2WcZr33/9B5V1udrSdlrmtzd93rfZZvWe7qb39Z9qyfs6V7XctOxW91vG+XYT6K912gwgXvXizDtxoihalt9A1TOrh7tuWGqYCBr/xZ9/uMHNv3ji+k/J/tlffea/xn7iyRv/zVHtv3r6mX+5tI5yF/vn16+/0cu2/NSNn/f35236+lHZthus9++o6+vySdjoWLjpeOv4V+D0hmRtHHK+6a2vdWHvdGvlOfeHO//rn3zg4Z/ScWnn7BjGd3AcOXY0GjhOHKO1BxShodNDjfc4nqr/Ogcy9kvnpL6nc1bP476Y6lgtdzH/f24cB46JTHWU4+cdAAIYdTKtNST3WOyj10uvEz/2wYfeKPvh9//lf0fJMWLZj1197cdO17U6UTg+3J9PBO6ig86/qb6pg4VjrWdbrecj4/zVej+6Bih1HnWP4TweF3b1/PZ6qPuTX3u6N73hXf9f2y+vd/oP/l/YTvsusNAzpHZgRrepXv9odwnKOdacP467ztX3/8n9P8e5WbIfu3rfL7LNj77tHb/Aa5YxPv9j777nJ6l3rW5Mv6Hnf6t3Z3D/oHO7PcOna4znK/9Nda3eI2Xsu+ogr/04aJlyZNq+LmP6vEo/Vm7+WS99//yzo+XRutH7o/eWXsv8P7hxrlU2+4M/+jG3CvwO9YJ4zlOLuDmrTpwoihY0gcfPvulNLWxYDwj19KlRwc1QD/IGRBOg/uQj1/574Oa//MxT/8OPPPTYL//zJ5/+V7J/+ugTb/rHn/3Cvz5t+2c3vvC/qdw3+7HPPvO/qrwd43+pXDIdb87Bj3302i814LROCJ0zNfRr47A18PdZ3TukBjT7rYc79Y96xjH6J9ee/FWM4+3lj3/2c//OSxmv2UYl38Fx5JhxnGhENEg0OOQaaY2o3jnAPnDMqfsY+6Llanw35+usrouTtqV6yP/ZZPW4eD3l/Hkd5XgKRAXrAtF9hnQaMuxr67Sc6g4NTf7rT//ltf8Z+4mPP/ZmN9Zx7PSejqUvc8z82sZUN/kdh6Tjwt0dLXX+dThXBwv1sd1L+3W7ZDqXtd6P6j/nkO/ku9URqGid49ZvOlrpwFQ9bPcmoGe61vwe9f33feTfaJ/cvufBj/wvo/+hz/EdXK+6RnmGcG2m8X58qf41b2dvcwFlADfHnXP2+vf82b+irMZ5+76r73vzj/yn+39Vyyq1DZ/l3PGdfDd1XPeOswSwWw9eeQHtS+o99YjrQfdIjP9A6ft+u7Z0vLa9d7umfaY8qv3A2//439Z1bm9427t+pb7mtyj5L6OSThqWKUdWQV8gL2CnLmpoxHHvTVEUHUXckHvDpI1RG/TcqneTG7se8DyweYg7RAp43nD95r93+46nnv2No5g+45/d9Xt+8LNf/H/r8lFKt5985pn/R6XMX/s29Tvq9x6lfO3jz/wmZV1esh967Ol2fGQCUO8QUCNy7jjpjUN55NRAPPOQt6PI6mkL05oaF4JzHub85x/+6F/9FseR46ZzUW3Te5zPf/r5z/8fHDuOE8eIRi7HhkaFdoV94MHF9SEIA6rYhx959PFfw374o9feyvlQWa+JXev0RTDqIMYy/91L/j/v8VrHwztIVEcFpQJRBxgHdXka97GxwH55x5HqhY7B937wkf+Tknqq48U6P4512a9vHTOOE8eI31BH2+zJ3Lfr9iJInvOpEcq13qJipntj6+Cc6iP1s16vo3uznzOdN9V7lTqH3LP8HJ5kRyn/g+vFOy/5vR/6y0/876v75LW3ytgvv361TL3EfNtWj6fvaBA11W2+G9Djt1L3jiF1Dk3nTXDeYGl6tgBggPb33POxX6/G+Xntez/8f6vcZGyvc0c7jrrBfYp6d+YROHff/XXcr/h9Pb+5T7KPdb+17/tq2r9t5et/7+p/ZHlTqe3qcl33ut95539geVNJJ4dKwN9NcO8GxGN0BgncBesjUE/YexSdouZQ9j7OiQuQXjNumgrhEZDTINTDvTZSZIKcuz7zpatnYd/39M17VV4Uq/vt+z9a9vIoNgJSNSgF8TOwT41Q94Z46PteNbrUiO7RHYJjHu40HPl/3//wY/f+F488/uD3fPqZPz+q/cDTX/wz7Aef+Pw7gfTmSZ8gkYY6x2RuOE+Ni5fcffcreWgJwrg2aNS+/sPXfv+HPvHkOzgHlCOr52oXu506cB726o8+9q66DtP/1vvUR5YpeU/3EAdSOkkAi+Zpn86z6qiH1moM7L7UUUV2sJ/sM/u/ApsJyKf/Sf3EOAYqj2ocM46TOpHwpvN7Ary97Fzbc72FcdtTXeI5OMP5dO1zj6Qucsx/9OnPv/3117/w7tE9elejDrQ6P9Vx7sE6fycJ6C2EmHtk78CkDnJ/WnUa/tVv6R7F9Sdj31TWeunb8VlM4N4616Y2gte/4+7/nSh5znmuAectlNnAnOPNcX/NPQ+/k/NQy+98/0P3bzO21XkTpB86b2d032hJLu+98k3tPjnthzoxqZ/sJ/v7Pe98//t831Wet7FfRy2X1sm++3ffdZ9Kva9lL2V6PdruDe94zx8sWQX+Cv8O9PK+z7CukPge/r4W+h5F0clKYboCc3rLGpSbR1CeHxrP3vh2sPnep24+JHvV0889qnJf7JufuPmUyu9+5kuPbSp9u+946ubnKV/99Jee8dcqt5m+b2l5VG6yb3v8i5+Wsb2Xfg6WzBuWNDY5pwIh96rLYykI2pcGF41oATpwjOeGhzuNTx7sgPmrH/rsJzlWr370czfr8dtmnNfv+8znPvv6L9z4OJCuBjSNXCIMeBjR+OW6AdBpSNHbvOogWAGY9kFWrw1s1+uDuuIl+1i3kfl7m7Ybmbb3csmoa77s9i2PXn+8LlNq+TWPf/YhllVyHFiW6X5CHeX4C0TlZeeeRB0VqHt+gH2po+5BB7yapxEPJXD+4Wsfeu3HH3+YevFdDz9+Y3RM/fXoevdrmePD9euQ16Jg8G4k1P1I4pgB53iCqVstWmyqc9wjBeXcF3Rvr9fJ0jmT6bXOHfWBTkCesbWD5STqMp0NALq8k6M6KNM1umS+LcY12u5zU3uA48P9l05e/w91f6LNUlQY7TCcI3gyOa6vu+d9v9fA9L6PfPTb7vnEp6u99sFHnvi2+z/xDOeFktda5yXGuee7eFZR7wTo1HmeqWfdsUfbk3uW6ihtTf3Xtu+D/7v2v608K/v2d9z72Oj1pvI87bvfdvVjm2zUUSDIl6deHvYRqCf0PYpOUG1qnj7OnIsL0OGCaxdeD6daAc+1t9I4FpTzQKYRrYYIMAO4Yi/61FdvUb78M3/zD82e/LtbX/fE399imbIuv+DTz83lrqbPe+m/o+X2elr++s/+Qytfcv25591Yv6mUveDpf2jfQan3WZbpNeWSzdtf/+rBZ6bl1Xul7Otl/+iJ1WtK/99+TDjurPdS54JSx+2bP/flv8F0vjh/cyO/g1AFdUEQD+99gXSgg4YM+8MDgn2kvrZOJDxCNCAnMP+GR796y41jsbROx4y68qonbjz/2me//OXXPvPFp/CkewMarxq/rWsHQOe6oSHF73OdcI0AXq++9rnnvvXJL365/qb/9lyve12d69iorvSy1Uvet/W+rPdVUndkL3r82WGJtbozvab0dbWs62T8D/6vtsG0TnVT/1vLOj6UMu4p6iQR0Mvr+COPPfmbQAbnQ3VUY7CpoycFNscVdQNI0dhfdd7glQSMGpg/cuM5rw9+3JZM17KMa5g6SkebIJ3jsXYszrCxfdFFx4qicuikxOvM8w84B8y5J3zzM1/+mxfd+OtbGNcXr192/eYt7q2Uuoba+ZnWcW1jLN/1udV5pn5z7/UonepBP25jVx50OrCAn1VH+6oOqnPoNR/45N+++qPXn3/5Q1+8hVEPX/LJZ9t/eOW1z7byZY9cf/5ljz7z/FxPp+1e/KGVtfvc9F3c87g2iTSonUR1v6Kx2rAttcUIKb563y86nAN6gOiLH3j81qv+/JEvff37nr71ggefvuXli/7s0bZM+bK/eLyVMs41JkhvncnT9wPoig4768gbr6MH98lrbyVK4HX/+f2PqXPhFe/91PMj41jwnyix+j626fOH7D0fu4W97E8+OJdr9kcPNmNbX7e4rX1mbXlgL/nD9x8qd7W7fv+9X1oq6/Iutgb2E6w7qLtXXaDeclR1b/o+PH+j6OJqgguNM9cYc41zUo+twowE5vSWO5irQSkYdqiUCXAb7D793PrrAsO+vMkcYrZ+rv/m6LdfcWMd2v09lnm/reOzsht/P35t5T+68bcH5bSOkgbc15d1+g7AXOWh5bYvh4Hr0P/spo4INQg5NlrHPrBf7X9Pv0FDERgC1OWRWwT1HvpeE1GdV+NLgI6nS4BOCCBhe2p8Uj9pVDoEb7IGnP24cWyw7/7CF2++4ZkvfIDx6BwLGp54o27dd+WlANgM6FNjiuuG328hoL2DgOujgpcAyzuQ5vqnuiRTXan1rtWR1fYHdeagrq7qj9XdDfXISzdfB1jU9xuQl/e1zqHdjf/tkH/IOP5edpAB1Kmf3IMc1IlsADqULE1DM/Yh5J3fVvZsIjzw+qvhSeP4dY8+/mkB+tr/L6b6ok4cbcdxVIcbHll50lVPT9oTe+nVh820mUp6SDj1ijpG5wfPPofzVz7z7POU9RoC0GXf+vmvNuOeK9O9RYDOd9cIHcCW++utW1eOHf3A92gMOr/hnZgNric4d0AHzjGgXJA+ryuQTv3FuNfxfdx76TyjU4PODY5jvOi7S+POG5xPbTGeaYxJFpwD5YA2IAp8C0pZBsq9lL3wQ59qJRArSOd7OFc8rwBiOm98DHqrd2d43+R/U+8VsaLOTP43kL4JsHUMtkH6Tgacj8rBOgG8m0N9LReh/Yi2BO/+XrW67Sa764/fuzID9Vf/7rseF6grDF6grnHq8IOHvWuKtrOsR1F08dU9fx7OruyOAnMakfIEKgxVIXzyMgr8MIdyga68xoIHN4DDy7q8yWYY0Xd0qPHv2WQNUMvrb/jC83MDSst1+2/6wlfnkm38NSXr/DX2smdX6/Ue9sIv/M28PfBF+YJn+v+ZSkCeZQG9YJ7XgjEHMwFYM1+u1relwUjDkgYk55AGvryWCr0ESvEYyStHaLHCZ+lpp+GlRmStXmchB3T2heEYPNQd0GlMNk9QB3SP2nAox9TB5HCOB70CevNC3fPADzqcy4OOB5/GlH6fRiueeAdQ7YN3aOlaUb328626rVLr2Gaprqv+rL327+mf93XeYVStQXi7tg46hhqQ8L6u8/5aoLJUFx081UGxaHbciETgeHpod+tImkyg3vIodG+6xqbTm98S2JyD2lhSwov7WFJ5hxRi3MKLAZvpvx3qpOgmKHdInzs7pvXy2nL9Em4NpOON5VgoqaEa3XX/IpMSc/W8AT5khtBtOobakKTpWAvMm/fc78Gq/32Z54DsW774d7Pp3sK9lnss54t64aHhJ5lJW1myPZKDOkgnkUM6wM09UyYodxtFIAnWqcscJ4XsK5KD50SGWWwWXmR1DnGvaJm0GXd+z8d+nTBjQr3vuvcDNwXoMsG5POjbQH2G8+nct/wD3XtOfdcMJefRmUcdxVmkqdYOhout2qItrH+C9BGoq+NBcH5bkL4BxGcA9+UdbQTqc/nuvzhRcN/FKoy7vfiPHmhg7pD+jb9/zwzqr/mdqzcd1KtHXWPUPex9Hi4RRdEOsrBcLiIeBIC5EpDIYw6YC8pbeOrjzx540cxjtwbONw7DtoOCvwZEHVp3taXPCEoEwydpguxa0tjSa1l9PbK6jb/++i8erGNZ+8DxEsxXAFuyufPCjO9Qw1JGIx94kjdOIfA0SAEhGpHyqBOGiVfJM5qfR/K46kF3QKf+trpr3kkHY8GxQ/IBhK4iJ+T5ckCnMQOgA380JhzQ27Qkb3vXr4w86C3c9cmVx9M7srxDy68bP7/eWePnlnrQznHv2Kl11s2vNZX1e+rymlF3bhzsgzz6ql9zZ8EAVNa8+9cPOvIE6+oUqfA+gnSHdTzqGF5odSQpTFjZsDlPLeRuguRaf85CTHFFPRWke5ZicnmoI6cNEeqQvhZtUABdprqEyWOraxbo85wJXKeKKDiPhvdFUe1MwXtJJweh7RxPOqk5vvKMr99DDzqOdT3QCct9/MV//Q/Nvvmv/7YZzwzuKdxbWxLK6b6qBJTKpXASoe0u6qDyIWicL/cyRXJQB4EcedAVzi6PucO5bATo3G81HGWug9P/ov6dddj0hZIlPFVoe2uTkairh7YT5v2NDzx8CxOcO4jrtawCusN5C2/v3nOiejTFmu6VbejjGUvzod964Mp8r1TS15atvEN6DXfneAjIOQb++qiQ/vJ7VxBOyWcpfZ2WD8G6L2+wCukO6lreZG27CvUV7JfWLwC6AzmALkgHzN089B1Qr7DuY9SrNz3J46JoFxHW3hPBaaw5DwIyNbapHeyBDeDM4896o4TGCA0TeXXl7VUDfeXhXZWyCh0CBkHDca1+T33d1j27AuBNJdvVZYdnL2lk1XW+fmTa1qFenxt9l5YF6TT49J6OY31dwWotUsAajzI1MuVN1zJgitcHLw+NUoUWA0F4K+u0Yx5GC5TUancaGnnQa4g7DUZ5HZeGYFRApp5y3DhP3/nsF/9u5EHn94ByB3Ql81nzoE/QhQe9Hdse9r3ttx3IR3DO8qgO7Gr6Hn+9rVQnQN0Xma5xSnniq4degO6gPpfT+3o98rTLe+zGceX4ArdAOvVT3nSHdOqnGp7nBQh48Lk+FOrunll1hPI/1Jkj8J6HDBiYeyRIO569I4Q6prBpgV+7Tm3mAY0NjDfzsJS1vU0nOh0vDUfAGwxw0lE9X8sdzPV8c9N1w/0bMH/hV1YlcE6pvBbcU2rOAO4rp9WRgodSkD4abkEdBOIE6A7fDubeUYRVWBekEx1CHSTUnf+mjofT+G8XXYrc0JRqeI/1LAPO8ZwDpe45F5Avgbm/dkAHcOlAnseeTxDcci10b+c+nB/PAaEkxdRVIgk85J3OBoBcUQUjON8I6R2uBeT6PKWvq8C+q73yng/Nxm+pnK2D+QjOR+t2Ngf1gRddoO6l4NytgXmB9Vf8zrufd2CvY9Tdm66x6UogF1CPog1SWDsXTevletu7fkUhVEpcVL3mIzhXo11eXUG6lkfvaZ0AA3OYFhgftcSA2PbdUynjfQfwEZTXcrTOAdvBXMteqmG2ZHq/fte20m2Tx3QEV9Uc0KvRCQCYc54pCeVkvKU8c9Wb7tM6nYbnZ5NGHnQ1auRBV+OShiQAOPScF0DW8RWg06BWY5owf4W4awy6A3r1oANLnoBtE6T7vlTopXQwrud9V/PP+ncdpZS1/RF49+UK56pXAvW2bf/M2nHvkN7e79DOsnvYqwFLmIZm4E3XtHgKedeYXnUiAWHnFe6uRifeRAckAJDOBe65tUPU64yGZuj/t2PEe/0Yq5ONDiUAiVB3OgCUeAz4awm7kmX3kOhUXJsNYoIC70DheFLPlACOZ+H8rCt1Wc80wTn2LV/96uxBr6HtnB/v6FRI6Gl4MflO7tHUQQ911/2KUPdNgF7hXIBOxxLb4WWnpOOMY0abguuQzjJ1EmWoxWEBxcraPkc0Wmi7vOdLcF6hfORBr6HtfD/3IJ5bdBwCUOeZU8alpHHUFw0NOhh+uRqX7t50RRY4YO8M6R3QBeL+Pf66blNhvIK5loFkrdMyYK7XI8+6w3ZdX98/BOTVFrzogDfL7jkf2exBH3jVBerbvOkV0uv5jqLo1q2v0VhZLhp6ufD28XBWD7rGQ9JIVENRjRE1uKtXT54+Wdvmi+tQ3hrnUykvsHuDsQrdu5aCcf8uTGAr6K4QXl9reVT6943gWq/1PjaCd5V1X0frqm2CqaOYzpca9Q77L3j2uRnQX/jsV9qyxlkC6Qqh9RBGjXX1sZMtnPjuu0/9IS9Ap2EDfIySxMmDXsd8V2uQ2I8Nx4RzsuRBxyPJ71UPurK4r4W492tJsDWCc98HtxlorTwJSFc90LJyLtT1Wva67XXPre2fRWjonrEWbWP3kWZ4yrXM93Qvej0O9TgBpxxPh3SBuhLJqRPJIV2Ael4eIoVQ4yFlX4AyktsRQk19dS/tfO81L7qHvKsecXwc0AXpXKdkG+c4EErvWd2V3LHu350sARKQTEcfXt82Rns6L+25OF3HdJ7M52QH7zkwLkB/wZe/Moe34z1XMr+W12MQ4XAacC7x/VwHylA/zyxQveiWDM47OmUe2dE6jnpki8LiaUu0pHqPP7OKuJr+J8c3CePWxblWot6WpLdnbReca9z1EpxzviqQC9KBTF5TKrQduFViOOo69YC6f5I5D44rjglJ6vx+KUhnvxXtqeMjTzqwrVJQztj8CuTVgGYB+RKY7wrnMof0EbCrdA+6w/cQxHexCuS+PDCNPa8GfNfXS6B+aIy6edMd0umAoq6fVyd5FO2duBh8GjUeAt939X1v9sygTJUCzGhqFTVC1BARmLcG9o3tcI4BePJsu6dcQC2Pt9ZX+Fa5tOwAre+pZQXupeVaLsG5wwqlg3r1iMu037IRkANJbMsx9feVjM7ByEvfZhdr5wLo6efPG5SyBuod0Cuo00CVx1LeSjzpFdKpa6cNQrt60DeFuM8Q2Ouv6qrO9SYPeh2DLg+6plkbedBlo30ZwekMv3a+/Fo6qgnGaz1SWd9f+uy8L+ybPN8G6DLqjr92m7/HPu/lRuvgKk8zUEt4N6UyY+P5FAT5mPTTCiHeRTSA5UnnWmnJmab9q55ahVKPIF2J9agvrVPjxuq+q+uUz9CZ5hnC+Q1FumjqtX3wlJ23OAbUBT0buYfgVQSQuIaJbAA01WGtc0Ed3Abo8qDLe/5tN//6eTr75D1XBJJyBHAfO6tzAvhwn1Yd9HtWSxhXpqesXnPVxbXoIPOo046gU4N7MPWaTiKeEVyDrSPijP7nXgsQ7jPpKKpRoe20zQB0h3OHcg9rryHubnjNAVCfVo3v5TeIEiFahHq/z4m8AHWuDR8eJEivnnQBtADdPegjLzrby+NebQTqel1hfMmAY0p5yxXu7u8JrJeAfGn9bCPvua8roL5LaHsFcy3zGV7LAz+CdkBd3vQRpM/zpaszaA86hKLo/DQ9BJQQThAxw/l0U1MyLR6q7SHbGyFq/HljRKWDg9bTSBTgzGHmBhPVa67GzHGAQyZw2QQwI1jeZuoMqLBewXz0ntbXEquZ4lneVsrq66OYAH3UoDwE6Abp8q5TJzQ1m4cUuyedhygPUx76tSqepLaNQadea5jGkudax8HhnHrrHnQBunvQlcVd19aiB7037h2uaqI635d5jHa/rrB23lQu1O2zNl1rvp/eSSdg3NUc3PVdfLdKr7MH1mGpgzpQqgRyPr804e4O6UqYBiif11hsrg2FGbckTdP+aawz1xX1VoAuSJcH3euy112HdIam8Dm+C8jES+9zU8+hxnd4A8nhXONdFUare4iGHTQINUCvputB91GFtOM9F5wryz4dRzofc6fmGYZ+0jimk0Yh/dw3lRzWQ903edBrx2MF9Abp3YversNHH/81rkElFr3TcyFwDhQBptD2mrWd9hmgCIQrS3sFc3nQR550PgOc8z3AOW0+OmNGieHq/u2LlN2e/Zwh3TK8k0SvHat7PvFpJY8DfAXmlPKiC9SrsX0tNy1vMkC4lg7lHuouiK4e9K1QLqsgvmndwATdDuK19OXRttUYo+6Qjjfdx6UfCne/g58/UXSFi0Cecx9zTq8jnnM9iNUz3hp+C42Q1hAZeLzUiKaRKDgfhZ5Xmxv5HTy2lf65WrrV3zlpq8BeYd3XVav7vstyLbdZjTjQa52nGQjt+4aA3uHcAb0l8PrM+tzUPp5S2YhP21OyyYPOPNk0NJV5+BDUVG+1dyr16Aka1VgFdDzoNDIPzYPeAZ3ra82D3sdojgBdkF6vMe3T6No4L1NdUUcGrwXlbrxHXVkdy4O6s83WIL3/Rjsv9fvnY6R71OrczonlpmOtKI95KjbzpCth1Xl60j2kGigkpNpD3QlRd0iX97Z2NOlYtGPTo5YE6XwOKNQxwGOrualp6HLd3MmQRGSZJ+aqETjtPEyAqaiyGdD78fYS82ePQzr3kNbRZ4nhfFz2eWbYB9KBtLWOiR7qTttAYe4O3xXQ52PT35fJi84xJLcNHVA8H/gtfvM8p+jcB6lzSI6TmrUd4BRUOpz7cvWoO6R7aDvfRZ2mbrfOGAtt35fEcJuEF519VKeSOtPo0KDe6piN5kuvMI75+hF8C/JHpm2BaJUCagdz1o285SplO8O4W4XxUbnBHLZrWcFc2d4d0mVHgXTGpTukK9z9LIZDRtF+qow7J1u7eme5kTucjxp+br5enr7mAfZxpDdWjcQKrpjDq7Y9KfMGUrW1TOb9NfvgY293sZEnvHrSR6Au037U/Rnt57ZtZfU3RuYdJXzGz6V/RwV0LzG+h+0FQSMQUgZtPOk0wnigntZ4I8BCmZerB10h7uyfwHgJ0Gf463DOf5X3a+hBn/6be9AroCtcVHNcq5PAvU8jD/q8T4BpL9v56qDq57S+PitzcPZ66HDuHXQHnR4rSK+dP3UIBSbInD87gPRquid5ErmRJ11100O9a706E033ZTU22Rc6feRFx+OtUHf3ogsQD9WXXof92DukA4bApoOhOtAE6XeSFwOPnIZ9aUo1AMlD2+dkqY/caDkk/Ngfum/YdYAphwdg/oJnn52958oJoGgGOkrWPJjndA74fSUt1NRr+v9qHyhT+wzgPeS/ArqDPEb0gaZEVKi754SgY6Luz50gH3fOlGq0zTTs8Hve+f73yRssiATKZQ7kbsD5yHuu0PY5a3uf81yh7Rfl+m/j0i15HJDOM3/UsbELpFcTmDcDupdKWX1tBiTXUp5z2W2BObYJxpde2/oRlAvAq2fc4dzBfFdIX5uObcGTvs/RG1F0Ourjm5SZlvAp4FzhQHj3vId8G8S4zWG43ZvqjROFWQJ6NFQogUyA1cuR8X27lCNT6LX2qb5Xtz+qjfbbgd2Xl7aX+f9hOrN5fd/vxVIdIW6lcSiQ2rhO582+g/croLsHXZDfPm+eyhGk+3jD0/RUyoMuL8QoxN29X0v1ucIN/3ebB53/pd7fCujyoAvQub4U8jmC9Dkjt11b87XWz00tHZi9rMv1M1qmPm0ra906ZLUuWn3U77lXd7YK6AXSfdttgL52Hj9bsr33uqkoD6+bni/hvLx4XBPcn+ko0LRehAED0kA6+700Fr0CerMC6NyLOZ4cA0Kr2zR0mnO7dFLcSV504NxD2+W9lAdZ0975sC89H+t9Yw3Q+zXAtcOzDxuFtnMP8U6S884HoOEW6qSoQ3RqqPsM6gXS/d4m4/g1SJ++h6kQ6YBSqLuSFp7Gs2GfRf3zcedtOqoe2j7DefeeYw7nAnQHdax6zmvWdpwydBzzOzwn6Ry5qBE0NcO7IF1TBXsHR4X0Wrq3XMDtXvFDED7aZgOUz2Wfu1zzmPvrQwBeoXoJuo9g8ohjytquceOjpG/VNkF63RYwdxOke7i7j0lPqHt056l7zttD4N33/KTmZ6aHkR5VbuDeOz7y6q0BjDWCa6OcBgmNQTVK3ASvMsEoBpzejgHifL6Nhe6vfZ3KJQOC6rr6Wd/PJRNc19K/w/cPq6+3mfYV4KjvyXRc6v5VG0JVb1x6SGbLPlzgCWuAx2eKp3IEQoTTejjxaXgqBegaw7eLB111uDayNTRD0QIemlqzuGvKquZBv7KaurBda1Nj4RCgf/R6m6ubjjBBukLeueb8OLZOE7P5nN3YHF2xrfTPHdVUtw7Vm01mvyk7NCa9ADqexgrngkzZNjgfAbp3IJEvQfNOK9QbMJnnZj7rBsL0e4CZ5t6mXpHVnU4EPK14vdnvUVb3eo8WoDukC9A1TaIgUfkiNB79PDPbn4eUTZ9nYwuVLXNOC0x1zXK91udiq3863qrnvf5zvWh6SoW24z1XBIMnhqPzck6WdE7SMCHqIPvFsfC50eep13pn/nxcOqDL5Dl3QOf6A9Axvqd50SdIVySHsrpfNEi8XXnCXo0717BDH0vtYFkB3UHdTaAuQFfWdjqrlbVdc55rxoB23M+x7t2ubj14pXVu+jRsXMeC9HkMvx1Lh3HZDOALIL6rVSh3EF+0q386NIBapU+Fxmf89aZSBkCzrpYO5tsA/aW/e/9aWQGd9SqrOaA7pBPuTr2n/jcv+gWK5IiiY0tjHOmhkvdccA404B1Qb3j16NXGSIV0ZXf3sNQK5hXQHbBptGjZ3wM2adgIPH1ZIFrh9CwMD6r20Zf1X9jmJPdNME7jRuuUpZr35FXjtUqZ75tb86T1ZYcpBycBOtmH1yB9em+Grf65NRAySPfM0UqoJk/RSUOAAH00Bl0e9NYBZR70CjUO6PzPecz0s6sQ1epBB6AE6LceuPLiNh1STxSnLLx4Kmgg0MD1eYW9wS94bFaOJedRHTMeDaLz29Y/vYoYwfQe29b3/XO3a16/djF+2yHf61u9P2Bc59TPGdKtU+iogH4I0q1uEjYOpHMefQywvJgnXT93Fb/bQjanfeF64bohHF9edPa9etKX6rIPQfBQd46xwqyV1NFBcYaky95AolNkgnN16uE19k49OtUa5PTrtXZej451A3Sr41wznCfOGXDuHSOak/607om3I847+yFI96zuPh69TVn50CpnTYV0tSEqoGssukLd56zuE6RzLwWsFMWxD8fitOWRGzhOBJXAuTy/MyB2OB9BukO5wzmlvOcKbaezRaHtwLmmWrzox7t50vsQNyU75HhqqECdpm7kLT8qiLutwbjKBXPAFojXdZSblm/XBOHbXlfPt6yC+hK8VzCvViEdTzrn61Bm9yi67AJcPDNog5bpZt3mOZ8AvXrPK6TXBqAaxGosC/jU+JMBrHgOaAwCNnhuMBoqMiBOyzRefD2v9f7o9WmXo3X+H+qyb8Ny/Wwtj2o00LXMHMmj9TL2oZbVyHSNqRHpnSyAkcB8BvQOSu6FpV5Qzh0JVsqTXhukpzH/7SZA39WDrrm5K6AvedD1f9yD3qbK6R4RzWGrLNA+bVG1do56qc6NpfN2HnYa+0Ldo+SYstzK6X6B+X2E+qgOJJ/ucRugUwcrpKvjgP+jccA6lwCqvMjnFeqOuDaU1Z2OA8KAa1b3kRd9CdDlRV9Fhaw6Pjiu3IeUqExRLnNCxwuQLOq4EiBxrdKgV2i7e4zlPXfwrM/EJUDnvspxBs7VIaLIjZoYjvvWPnWIqKOIOkiEiYe6cw+b50cvnY3uPZdVQOczgnRC3YF0hbqv5YPYo+NxGlJSQoW2j8adA40Kw14C9OpBl9dcHvTmPW8RXKvEcLQBNe6fe93sPb/g2gTpSoY8j0vvdte9H7gpaMfaa3t/Nm2jz/T1r7p6b1s3KklSd1blt7/j3rbs5ZKRsM1LQHlb6VaBfQnMR7YE6UzBlvHo0R0pbsA+JpYbVQvdm+Dcx5GN4NwhvTYAHdBHcN7A/JkVeNM4oTGI0UjBALfTsKXv1vpN5WkY3q/R8midlilvx+r3VdM5wLPNeWlz8U4NSM6ZvHKKghiFtq950LeAOt5KQEih7vLUaf7beWqrE2iIaSzfKMR9kwd9vfNpBX4NBCeQaf+3//8RoFcPOvuhhD/qECOUkGuuNfqn/aChJOM8AV0cHy+1PDLmTa7rztp8/5aWN5n+Q5sDerovUDbPYu9oUp2s0SHztFYte/ZukF5BXYCusdjsxwiYzhMQNC81nQV0GhCCjhddoe5LkC4wnI9HAXR1OClCQfdn7guKcvFQ98vScB9JWds9C/R8v+hwDuDMnuH+fBzmZ7FjrWeiDwmhc1qdIfU+yLHWNH91H89bsxd9Ojbcx3QvBfjwytJ+ALYB9XnITj9WOl5a9qRy2lbj0anTdEBR//DsqpPotBKK7oPa82qQtX0tHBtPrAF6TRA3gnMPa5f3fNXWW8E5zyzyphzK2n5O97qTFhneFRWjyATaAnRKrIW8d1jnWNMhwjIlNq+7et/DWq4l7/nr0zQAduk13ue6PJd9O17Py7Zu7bWV2n5kJHgbgbpMEO5AXsshpJfM7tx3Wl6fKLrs4kErYHHvgELbK5yPQL02SORl9NBUhaG3RDg9GQ6NEgEyjUAS42AADo3O1z7+zG/SYKHkNUZm21GpbShHxveqZDv91tL6uo1e1/VLpv/j/8uNRofeHy1TbnpdjfX+P/1Y+Gd8f0bLlDT2aZjT2K9A5JDOsAOH8+ZZfuZgPm4aoirdHIR8jvQ27vL69Te6p/IkIKA1uHvveQV0eX7Yl2Eje/ZArsBPnvTVeOjn5vB2ATr/pXrQNW8xgI43fe7BnxqcNIoIE22Zc6fzNDKODdPBtWnAuum1r+e8eTnabqmsy2dtdX/4DzJ1kAlAPZzb66ZD+iHPpUH6fK/qYN7C6/sQAdVN92rqfCphGnWz1rGzEtcEHT40MtkXQt3J6s61i7dRWd1H49Frx4VDumYlWEWHPDuHugOPHH8Hx/Oc7uu05dE2oynVfNy5wra5Z+i+MXoeOpzruejPQ0VrcIw1+4PmoN/XkG550amDmnpN7Qe86A7p2AjS1bZwQNcx1Xh0ZXXXzAq6p15mD9qcGK7nBAJMWl6gCfwqnMuWvOgO5/Kgs8y54Rx5aLvmPPd8Gyfx/N0XaVYG96RrZgZ50wXqGMMJ3LSulvV9DK+vXvty/bzeA0BVLhnbenne5vvRwL3Du0C9AvcSqI+A3SHdM7szHp3zxfVxmTvpoqipPQimB6wnvmljyJScqpcC8iUPuhokmDdCZAJzGiN4pgTlNH5pmGAAWuspZw5UM7xEXgI1Xtbt3Or36bWv93VaptFblymraT37LtPrWhKq59v5e7tY/b66L9UAv7pu2++1xFjTuaDBzzlqnShTI4lwYzX8aVhyTpsn3eCchn7NCu6QLhiaPendiy5IpxEGBPj803efwFgjPZQ3edA1jrQCuiDd4Vwe9FZu8KBrCjl50JGmbJJnjkYRHiidrxay/NjTv6FStu0158zLkW16b5Nt+tzoPerQUqn91jpfrv9BnXKYOqfkKSb8VV511ct2r+ow2s5TB1BBuu5Pnh9BoF4hnbB6D/Pm2lDdbGHeJ1Avb1d4gmhAc40AzOxXzeoO3NTx6D5MYwnQR5CuxGXKFaGs2vvo2T2OBOca+1unFAMUgU8Pa3cwn6PJ2rFedeo5mGPq3PQOkFEdU8JMznXdz30Qx0rJt3Sc1IZgTDPH6bs++ql/8DB3D3Ufec/X7PHV9vKic5/mvsC9UvWPe/p5XocnLZ5TPrTCE8Ph1VU4dYVztxGcY+QGwATnbTjV1NbjuLbnznT+eC7SMcTzSZ3Kl1HqKOc40/5VpALPYzpE8KhTz4D1szA6YLxUR8FoG72n1/U9lreV1KlReTsGNMuWQH0E60uQXgFdkM730SHA7+BF55xRRxukX5IIjyhaEw83Knq7OX/0r35L0z0B6CNP+cjWQEbexd7AoyECuHiGWhrYNEYcyNt0RlPDhMYfRpgVRm85N01KXya0s77vn6EEjjBf1nePyrq8qxH6WsuzMLyvo9eUMvbHX2vbur++3+084CmbzguQBKhzzlqnSvdcAi80+jX2V3C+auDT0F8HdIG5e9MF6HwX36twYkHASSZH2hTirjHoanQPPWEG6Fgbh6+OiQnQVcfdg87/qN4eJYqjYdCmzJogHcCi3nF+2vRZZg28eslYT0zLOk/+erSN1lXjmvNlGiT+Pq/9++v3jfbjKOafrd/n+9Xmgf74Y29WxxIgKmAHmLhnuUe9wWi/D62NrzZPOvVWdXGGc0tYp4R3ivSRd1MZzfEwnXcDluzEePJpULNPHC+uVxrcdF4whtdD3XVcvKPp0DGyxIfewapQd9VreXhbwrhL5MXgXqOxv+pAmz3D6sjr3uB6r5BpiMyqjvWOon7P8OFehLZX7zl134cRnMS97zRF+0HT/3FP5Z6xllNDgK5j1kPYBd9zWTzqvp286EA6kTXD8eiXRIJGhV/7vN2eabxCucB8CdAF54A5pULbfc5zhbZrzvPL5DkfanoOa3ph2gXUJYZ60tlEXW5JyTDaTlo+IWtjqXvJb+m1llXSQVO3oRx9XmVd1nfUctPy7ZggX7A+AvUK3tVrvgTpPh69ee6n3+JcqSPpMnXSRVEDFoECN2V6DAUqCm33RscI0h3MD7znq4YIcC6vosBFWYEVqtYa5xOI0SChRxzjodvGP01Qs6u1Ht/Ba8ol43e8xPhdX+evffmkjAfBSZTsW/3uTeu3GeeBDhDOSwP+6Vw1UCfseDqHDkN4HDnfLaTd4NwB3aHcPeiCdA915ze8oYoX6SS8dJtC3NWY9OEc81zG1w9KQboa2dRxedI9r4J70Gn0NECfGgENzhHX3fRQoQFOo4D3/dgD7LVcWnfSxnmv67bZaB/r61FZtx0Z+6O6qE4MQFTATuOSOqP5qKlHgtFVJ+FzM3zyWlAqE5TLo65lAbo6jwSnzcM53bPavYDEXecs4IQ6RN3h+LROtU88+Q686B7q7kNTZH4cKqTreCn3BN8jkPQoF+WKaA2kS+DFUCeeQtvlFRac03kNMFY49468uUNvcM8QnLv3vHZ8cC7Ne77/x3Q670Q6tQ6N6fqsoe41xH0XQBekz170a597ju+iM64N55p+Q8eqjUe/BDDZnlE7JIarYD7ynI/gXIAu73lLBDy1+eQ9V2g797XWoXwJrudNaknjpvtW65Sb/i9tYY1N55lMfa5Gp4nKunzImKmllpPxmZa7gWRnVo7W1XJko23q9i0p7bSO0s3Xadm/7yjWkkx3yBeszx71DukjQN9kvi1J6xTqznfzW+wvs+Kcd2d5FJ2oWs93n9+V0B4eeA1UpgYIoIJnswL5Vji3xogAXVnaG9g9ffNeGnct9Hdq5NLw5gFLY5cGCQ1NHraACzdKHvo8LGTcQP21G9tj/tk2RtPeG5m21+9S1te+jd7Xa1+PcTz9tYybvpcj0/8f2dLntJ7P6reX9mEX4zxgPKg5L0ASDSHNuQwMuSedRmpLPNUa9gdwLkCvYA70aJ0DOhBBqLKDEJ0D7AONllp/jyr3oPPwqR50AF1hmKPOKG9sV0jH0zgag06D2wFd+6KGAdcf9VT1jHPox5/XlLuaPjcq3difWo7Wjcpttm0736/Rvvtr/211otGIpE5qaAD1knMoKKVeKrqD86PIDgGnIN1hvcF596oL0gXoml2iJvACTqlLbYzmOYbY4UVXqDv71CIvHn3817iXa7y+gFKQLk+6Q7oni/PjJaDk+uQ4CNJbR0X3onMcLnRWd84dU6pN16RnbRdsako1ZsYANAXn1Xu+Fm2zAOgcS+4ZiihTaLvXK87lviaGWxL3Mu5XXJt0oAF9iuSoXvQlUFfY+xqgd0hXVnfqM99LOwJv/ej+elGloU/AzmJiuIH3nOnSAHJNo1bNAZ2EfQ3SLTEcz0HupzwX27V8yYYN7CJFtrU50/tzWW07tbG8fbVk297HWhtkodxmvv2mz9y6/8q8DMDWZX2W1yyrrOu0bd3OjfulSsG6vOryqAvSN4W7j4z3yADvnnS+R2HurTPhD1dzo9dzGkUXVg4r3Jx5oNLAxXuuh+QIxGVrDZI+rpOGnoOLe84F5/ISyEtOYwRAwWhocqG1B8RkNFBo9I2M/Vep7VRqXQt96d8j02t+y1/rN0evsbZv9v68rzJtMy2rpLHn6+pn9JqHgb9PpwLLKvU9WsdrX7e2H0ewum9uDo2cK4XQapyrQmjpyGmh7njKB97z6kFXyPsI0gEA4N/DiWmwsj+1/h5V8qCrAe6AzphJZXEXoDuk08imMV4B3TO5j8agC9CBmEMZRycgUJ3VMace7GKcG5W7mBob6mhSqfUq67Jv5+ux2vHl34Xp/dF6/y+yus9ufF4dSIo4oE62MYMTRK3Caq+9tUI6nYPuRZ+HJIw86Qp97/XT53QXmCpRWstVMYEI9y7OWwPTcwJ01aN2nKfj0iIOPv7Ym7nXCtLl9XVAb/W3hLr7eH11YjhY+njpOvUc5+YiA7p3WFO3GhxN9weFa3N/UEcHVu8PM5j3jmp19rA8mmLUQ9t9OAzHkjpPvdrXsecjcX/lOqU9wbWhUHc6NjR1pCD9UMI4M4H5GqA7pPfx6Ap155mkSI6LWv+Aw9Zu6c8m7mvAB2OKGXMr73mbh7sngKsh7SM4dzB377nml/c5z+mQUmj7ZYmGuR2p81wdn6M2lq+r71Xb9j65aShpq7Lc8l8Mlum88ZL9G61vQySmz1Bq3dJ3qOS+59v5ft2678pL2U7L1FFeA+p6jbHsEK9OJnnTlyC9ArmbpmarMI8XvU67xm/WcxlFF1ZccNyQuYgUxufJ4TbBebWWZOnGQeZ2JReigawMwDQYCUnFc04DiAeqbmBACiFqCpV0a2BlpZbZHqvbbzMehJvWjd739d5JUN8fbXMU29QhcZqmY+8l6wWPNLxmb/qTq2RUNJBoLOFZA4ZojHpiOEH5CM7ds+6QjrdSXnSNRQcAgI9af48sxpo9ME4SJw96nbVg6EUXnD/dk+P1sboCdP7DEqDPIe5dqr9+LtSRpOVdSzfvoNLykqnDacn8uyhpGPj7eu1l3Val1i/9z3oMMO8w80YD5xFPewsx7N50Qbo8x6qXDuYjA94roAvOFebOOHSvl55lm3071wbt9NscW/aFiCTNDMA910P/qye9Qroyu3vHhXe4ahiHZ3Vv+QGma/QiezE3dd5p2Bf3OofImkhyhvOnD+Ac07HWUAHBuerTHNrOc7F7z8+90+d2NO0r1yedC0A69z1dj4L0NsXXAqQ3IO9lhXPfTuPR+U6+m2cR9V2RHNwrLtRxQ32oIXDTPJBX7/tF4KOGto8AXd5zgLyGtTuky3OuxHAcO4W26/rlOXvh6t0paI5w++0VqHNMVAKnKvXc0jota9v6nr5PnnrKdrx76d9dlx26taz3eV0/7+ta22fQpl0yfWap9GNR96mB/lSXAWaBeoV0H49eoRx7yR++fw3Q3YMuQNe0a57RvZ7HKLqwag+E7j2nMUJDhIatwtuV6GabyXtOw04N3lH23x/vIcv01NKQ5EKu+3QRVaHrRNQfkPruWmpZpnWnJW7G8tAphJHed3nR21j0HiaM902ArnBhQboA/mXPHmR6d0hXOLGH0bYka1PD67iNBjXCK6DTCPIx6HVaQRrhs/fcw9ynOt46pGwedMwB3TukGqBv8IjV81hfn4ZqvfL6VOuav38W++aq+0RJQ0KNWg1ZUJI0ZXmnLsmLrs5DAXk11i950DUOXeHIZJxXEkMBFZ0t63t99gKQ6bTgGm332qn+cZ0qiV7zYnqY+/WDziaH8upZpwNK9/R6X2/Hwsajc684z5D/2xH7TF1SYi6AD3gZhbbr2XjIe97hXMfPAZ2yjjuvxw9I4twBuJvuE/ssdfDyH3jO+9zognQPd9eQInV8tPts9ZwXUG8JSh+50ULd+U6FulPn1yDzgqhBUY/c4D7GuFrGna+Fthc4rwaIVw/6yHsuOKfTjugGjhvtP3eYXKTr9qy0y/OubrPr6/pMHT13R6rvjT5/1vL/0qC9gzrtLSC9TZH2jvf8QYV0B3EHdFn1oCvMXdOu8d0N0En6F0WXQYSaKpSPhpw8BTwIHc4F4DOIO5xb42QG9AlWvDFCo1ZJhXiI0gg5FO4b7b2aZ3Q6bzSC8I7gueBBLy86IOMh7W1ceq8bAnCA/MV//Q8N0AXpzKPuXnRPnNTGZU4gROOh7s9RBTTw0BCg0xCqHnS8DLt40NUQ1zzo3/zXfzsDuuBFnjF5d1qdzwPkRNU8EvddeSlgxb2MeknIJo1PPLyAFYDu8FnB3M1nHXAvuiI7NPzCoVTjhfcB0AGTNuTAOtI0Ht0z3bsXvdZpB0y95r4uSB+Nn6YjbW08+gQc+3A8dhLh7R9ZJeby3BQttP3D1z40e8/7c7EOf5nvC955N5nAnI4eTBFl3gHpEQhKdtY8wBdZeNKJcJnqYBvX/PHH3qxhAq/680e+BKDjSede64AuD3r1ovsUbHSOsD2f45zQQdzGozOrw0OP/TJ1XlOv1d3aV/kzSePOgQ7gfJ7zvMP5KCFcDWtfgnNKD22nA0qh7S3J5NQevEgdG9EFUE+GC6RTtzd50SucVy96BfQG6eZF5/pJ+yq6NOKhwM2ZxggPuLs+86WrNGgVvkd27hm+S7nWILl+0JjT9Dw1FFKeUC6iW/de+SYu2ro/0f4LyAVGqDeAEGFyNJKAF4UTywTm7j0HygF0zMeqA+kYICQYkhcaTyCdOse9+bLv1DsaIjWMlTHoI0BfbIg/fTD3u+q8IN096A1c3IN+jr3bl1E6p/J+0kBv2YivPfmrbb70CYRUL28H0N2LTp181dPPPerTYWn8NR7Dum/nIaAYOFGYcQWkFvFiWciXAF3XsOZJn4cDlM5Xz0Duoe78/kVp7NPJ4/cEDZVQhzUw2SDSOu08suYQoHdTB4gDukLbeSbOHRs9EkPThbE/dR8vmjj3dDao04w66Fnd5UX3ZHEV1B3SR6BO5BZ1mZwTfDfXvCL0OJ91n/ZRLaqrDzUkckNZ25lSrYa2VzAfAXoFc8F5s+lYtSl0p3qtsfu6Xjle3Dda3YsHPTpBEQ3E81mh7nNmd8vqXkPbqwe9ArogXaHumnKtheBH0WUQgE6DVt4CelYBdI0/XzXcDnvQK6CrMVcBncaIps1qUyJND06Fn13UEL47XQ0wJ1AGSlYN2VW9YTonQIhw2blx3ztzAB6FswPkAnSZ3pMn3cPcmU6HhkQb38oYZMKnbnPaIYW4qzHepgS57yP/ZtMY9CGgb/Ggs//uQdeUXPGgn4I0FOTBK20IhqCU0M3veOrZ3yDMnXoJIM33qEG28iVAr0MvBOic2zpf9V6AFd7L6d6qMGPAzzssfDy6vJUO6WuRBn08uo6XIN3Ho+se36Ze63NTK0pq78ej4znv4cUe2s7zUMMC2pCAR595XmBYAV2QzvFT6UMI3IMu7/naMIkehdG8vtPxupDjpxeEFx1I9yEDdBI1QH+4T702HdsK6nIQVEB34x7NZ7lf0+lEvSZKj05joJPjudcdRNM59oguwYumVMN7Dpzfde8HblbvuY8/r3A+gvS1adWm488zW9N/emh74Dw6DSncXW2u6kV3QHerHnSNQz+U0b1PuYaHXm2sOEKiCy9uzHgMNGcpPdF4EAUiCmFX+LqXM6Bbo47GGx5SQiE9lI+GIQ0RIKtlnrxNwIr2R61B2xtd8qLT6G9h7uZB3wTonvGdeiNAV5i7xmi2TO5k/L/fprS6DZ2GB3017/tz7f8A6d/y1VU49MiDftGyMl8oTQ1LedHpCNRUY21Wij7lmgAd2FyC9CVAVyZ3eT/lMXZA37eQbh+PrmmvlEAP8NT86M1bWTzognMB+pIX3YcxeZ4ROgZ0vdb92hcJzrkfLE27CAAK0HU/8HvC/Iy0+wJ1TYn1HM49a7tmAlACSY5VSxp1iaQcEcByazz3rO540demXiuQLu85kF6hXPdmzbYhL/oc6j5BuubzXgPPPZNH/nhoO1nb8Z77uPMa1l696Lt4zzV/vLK2c4z4Tdpk7MO+3buiyyWA2RPGAdSjMPcK525LoK4p14B/hinFERJdCrXe7T69mhLE4UGvY88dzudGW2nQKXwZWNHc53hW+E49NGksBs4vvmhcqO60udH71FYAjECoWa83FdBlDuk+Hh3ABR6aB71HXygB1XEaXGoU+Rj06kEXoGO1MT43yHvdF6zIgz4Kca8e9PTsnp44txqL3sZWXnvyV6mX3nG0BOYV0EeQvg3Qb7denpYAPuo6x0MJ9JTVXckdfeo173D1e71MnbA+Fr2GuhPx0jqlrl9/o7zo++qZ86ERAB2ApHuBEpoBj0sddp6HRfcETHDOcVJntWdt5xh51naNmb6Mz0bOPUPaWmRLn1NekD570UcJ4+x4V0AXnM+A3udGp63Bs2juFO3Hdi/H9JNVu+c9UNZ2wfk8pdp7PrY21/kIziukO6g7nPNs01h9JdTjnMwzUETRKQrHhJLF0e7yMPcRoDukjzzp7kUH8vmutTnRU6ejiy55QTX+nJs4D8LqOXdPijfW1GADUJT0Sx50GrM8NHkokFW5TfuTaRAujTiXPtctjX7G6LaGfvHECXpGkK4x6Ko78qL78AjNtczNHW/X7XqajuJBH3nLZg+6wtvlQf/yavz5yIMeQD87eSZkOo8A9JZXo3vQ8WTKG1zB3AF95EXnnDpkOaDjLQbQ9xEEOCY0wql/eGsV6q5p6A4ljDNAn49Vud9zndIRVb3oPh6diCn3Yu6bJ11jfz0xnCLJHB419lzAeAjO1YFtEQYCc2zOq9ET6nE/U2i7hgJcpPH6tyONR1dki+ognURrgO5edPOcVzhXeHuFdA91b7NnXL/+Rq7NE5mi84RVO4rpHFJo+ybv+ZKNPOgady445zlNHVdouxLD7VvHYnT51Npebz+YRpAw9wro2yAdKHdYlxddgN7Gob/7np/kmmpTsaatFV1kCdABFI0jBqzdc+5wroatGmoyAbp7EfkeGoGrB8O1t9JgblNlRRdeeHqUYFAJlQAhAB1PpQO66o3D+QjSPcxdIe7AEA1aGlttqrWPf/L1/O7tglBrlNsYdBrl1YOuMehLHnT3llUPOnC+yYO+92NyL7hqBnMB+lE86IJ06qFKz43gHnTOrQC9gegeNnSbB/OBK63jAo+ipqHjuhpBukdG6drFHNABTwG6z42ua1bTIwqQ9tFLp4464JxQX93HeF4pa7sgkJwsfi9YgnPqiwM69UbHRaHt1Bt1OM4Z72lMXkLvuUtZ3ZXE0b3oa9OuTSYPuQM6x37kQWeIBlEgDulyChB5ta/j0RW5Qd1z77ngnLHn3/jAw4c86EsGoFOOvOccZ5/zXB1DdFxcppwH0f5K49A9zL2NQ++AvuRFr3Aucy86n2XqNrzyfC9tuxbmHkUXWTwgPNEX4e3tYQigb/A0+VhETGOKBeiE9MljwIOBhnLLFhpAvzRS6Kym6KNRJA86psY89cUb+iM417RrlNWDTqOW72+ZgHtI3nE96Br359OsHcmDfr0DTAvzXY1Blxd9kwc9gH66EnQBAnS+AELqOKIhLw96vZ+NzCM6VCcV4i4vMd+/7x50BJzouCijNl5GZXXXWHS/dh3Q1+75BqAO6B7uruSOHB9NvbYvWbVpKKqjzkPb5T2fE8M9sprzfCm0fW34Vz9Gih6TqbPO64yP0Wd6vjlz9iUXdRAg1LWpoVE+NzrZ8jFBOibvuXvSBedzOPxkgnSFutMBBaTTvlm7PvcARtWRCKhwLJQYjmeQEsPtCuYygbk86cD5az7wyb+toe3ynreOoTuk7kX7IYf0GdB/9133CdArmAvORyZAxzxRHJ1dCnOvvx9FF0oCdDVMlMFdDY4KUg7oNMgE6MDJC79y4EGncQKgM/6ThhqNH3ptA+iXRzS421jfDz70Rs4vjQAaR0qQpMgK1RkH9CVIF6jjdVInj7xxaxlnb9MbV8egb/KgbwT0DR70IaB3SAmgn64EokqMRgi6Oo7m5IVEBJU6OYL2CugKVQa2WqhyH0e87x50iU4thRnTYdQSRU2QDswASA7p7kVv4e5232/g/vRBIrQhpPcoA0E6nbP8dt2n8xDnyL3nun9xD5D3fA5t78nKdO2//Mm/G3rPuZ9VQFfEhRKlkkDP5zynjs5Tqu0BNJ62NLMA49G5j9NJATRyzGl7MD+6AL1Beh/773C+Zh3MHdAbpD/6uZucQ75TzgFFX513JxqJ2LhHufeczqHvuedjv+7ec4H5i/7s0QbfKuUld2+5lw7qwLkSwym03ec8v5PqXrQfckDHOSIPujK5jyDdofxlf/TgIUi/64/fe+uu33/vl8jbwPdxPRHmHkCPLry4UQvQG5wA6DRCBnC+su4t7HDePIhfXgG6j8NtSXF6I41GIL3lNJj3cSxYdHuigavkNpoPnfpDw51G+iZAr/VKyeIE6IIhn75JmdCbB/2BK8cCdHnQR2PQN4W3axqlOQRYHnSr/5sAnUZR3afo5CRA13zo3HfaGPSpXgKfDdJtuM4SoAvOK6S7N3RO9NXDuC/COGJldSfUXYCEpxEvOtfaWsI4g3RFwgjQtY2SoSlhHB5jypqtXB7jebzreUEBmf5LYjjvoJP3XHPEY+41HyVPrXVGgO6J4TQkwiMujhMJdJHF+ee/6/psoe5A+oOPPAFUAud4fz1Z5y5wTodKq7+PXG9TrwHpRAXKi87vEbFAG+S8MpZzfxglhnPveZ1OzZO/1URwFdJHcM5zmetcx0DDxPb9XnXSakNImNpuOvfUQRnH4U61di88ZdNv4VThtRLFbfOgA+AO6MC5TOuAcyD9VVfvfYbv0Th0Or4C6NHF1nSzkgf9tY8/0zwpNGRJqCRAFzjhHT+A89U852qUjSC9jbsrgE6jMIB+eVQ9UDRwRx70JUh3UPc50ecIjJ5YiTokD/pxx7OOxqATXrjJg+6A7h50B3Suh40e9IS4n4lao2OCLyAU+MJbSQOVeklkkMK3K5hvg/R2bhc86GvjrPfYg47o2FKoO15cQl491J1IA0F6TYYoOMeIRBDEC0gV+t6uhT7F5vc+dbOFGmuIiofW1n07CzXvuRIVMTd3v3cpgkzec4EfHXKb4Nw950udOT79nDKLA4p3cmcddZD7Icd/nnptqoN40Ql1B9ABdSC9gjr35AroGIDepmvrc6MrOZqyutNJpKzu3CfqPp2FABTqns8gUhPD1dD2CuajZHA1vL3B+YOPPMHYc815zrFWBzd177w6Kc5TPP8Fpm12iW7cE6iTGG1UjA5XjONFyX2L5aOU+2AtCeYpGxwxWifjNXUeo2NKc6F7kji84HjRK5hjL/6jB2ZzSNc6edAF6JpurZ7/KLpQGnnQWyPEoGkF5w7pBuYF0JUgrnrQ2zzWAfRLJfeg44UGcKk/eM8boFtuAkH6DEQDD3qFdAF69aBz4z2OBx044MG1NAbdG4IO5nU6JQ3zcA/6IqDHg34mEqDLO8q5pYFOY13h20oUVyG92jbokkfUAX3fG70KseU+TOeasrr71GubQt0V3j7P+X19NWygQfuNg3HqrKOjl/sBw5zoANac31zD5/UcoGHeGq1lznP+e0sM16edG8F5HXO+BOc8/+igVmi7ogjI2k6nCHCuUGvCvus+3gniv3MeBOkAZOskmaDSIX0E6BXMBecO6IJ0edH5bup5g/Sp/gFlPAvqfp2mWvTWIDHcDOfde+7jyj2kfROYy5QYro0773DeEvRaaHuLYjmnDrLzVPOY48WdzoHAlXu2gyTHp0UY0IE53SO0TJ2hrqr0dVpubZPpMyq13tcdxfg8bZTRMqVeL5n/dt2/aq2zcrK6Xu/VUkad8tdLRn3H8JpjQLS855oHfeQ1r3DugC5jXfOgT6APoDMOnXYd5zNZ3KMLLTxNhzzoE1wLnNbh/PkViCxBegf1JQ96Qtwvl5onarrhK8T9kAe95yYYedCbDQBdeQyUXKl60HlAHMeDzkOaMVAjQHcP+gjQZ+95TxC38qAfDm/fBOiZ+uN0xflViDuNBzKWc16ZnaI14AWdBdBHkO7QRd0cAfpF86AjQj01Hv0go/a1twIxPh4d8BlBugN6G9Mvu7GaOx4jGR/DCRrsP/3co6spGJ89v6zat259TWugT9cf173G/uq+xXWv0HbqCdc+gH4UOOeeJUBXPVHWdr8HCM73vTPnNEXHBJ2s6iyhDioHAJ1EgnSgU550JYejXs5e9A7sAnSBvMavazw69U+eZOp8m3deU/+dwXAL7vnyniuDtRLDzXOeT3A+AvRdPOcO6IS309FBvea6bhEKJOWbjjPtL/bjUtY9zmMPYZ+95AXIeS4IbB02eVZwT2hDXqY2AXWkJRn94ENv9OVN5abloxr7Qsm++GvKFnnR19eyGm0zSuqbXmudltvc4dO+6vVJGd/bxoP35RZ6PhkADZyTdX3kPd8E55jC3VVq3Xdeve9hATq/x7muVSSKLpQU4u4e9NYYGXjQZzhv5WEwx5r33ABdYzXxnnCjo3FS9yG6mKpj0OVBd0AfedBlSyHumAO6J4nTtETH9aDXMeg0lGgccg3QuBOgY2vhrYfC24/mQaeBFEA/PbWxblPDuwI6jXS8wq0xP/Cgy9xTjCd4kwd9DnF/6sbPawz6RQB0qTVce6g7x0kJu4B0wGYE6bONAL3a9eeaF56wec+qjSdZ49HPBBQE59N1TyO9Ncw/eu2XFNoOvNEpITivnXOC8zqziaIFPGs70UO6bym0vdWR6T8rtP08k5TtmzgW6iiaQ90/fO1DGo8OpAPna/Oju/f8UwfTsQngHdLlRdd0rxqL7YnSThvQ57BqmzlEieHwngPnmvPc4dwhvYJ4NXnO56zt3XvOMaWuA6Hq2G6dI5fwGcR/0vA17v/yYAu6G5R2EOX4yxhm4EZ7gOOmkiiHpdcj4/3RMkanjJf8Xl2nctv7lNX4PZV1+SwM8F4qZQ3Mf/dd98l7DpzXKdQAb8aXVzgXjPt27kH3qdbiQY8uvATodQz6ykt+ENbeyh285xhgBqD7NGsAVqZZu1ziXFZAb9MTHcODrvB2AL1Os9ZCIQnxuv/250G/cvfdLYOzPDfVg05jbsmDXkPcNb6+wvkmQI8H/XQ1AnTuP5xXja0Gvg/VRaDrxt8fAnQ6kZYAfeRBP1Ov8DHlHkwfj65Qd01NN0O6hbXrGhCIy7hGtIwHms/xHXxXm3ptgnSeBSRKUxbzUz9m0zXveSe4j3DNC84157lAr1778qC3hHA257nDOWBO1MDcqVhC2/m/6qA7k06JCyRlddf0f4J0AbqmXROgz5BucC5Ar2Xzol/73HNKmLYW6v7E9Z86i2EpRGwptB1QBgobrNzzvt9z7zkwPoL0Td5zOi/cc645z/mfLSqGKVCn+yCwSv0/9WvtHEUnnO5nbQjBdJ23jhAB8j0f+/VqdMy7cU5oC1COjA6VUVmXT9sA0bruKKbPbytH61QC2LXkPX9fME5JXW9g3rO2O5wD2QC5jy1/2Z988BCcV0DXtpTf/o57H2u/PV1bmgu91pEoulAajUFXiHvzfHb4cBAZQnr3qgMnwBXmY/DoueYBzAOx7kN0MSVAlzcAwPV50KkPDYQE6NbAXa1fBnRgSBmQadQDQq2DZ2po0KADsuv+7KJNHnQHdIW4ro1Bvb4KeQVMRuPPBeny/leIiwf99KUkYD4GXefVk59VQK9wLiNcG0gfAfrIg37ajf2TFPtKR5eSPfI/gBc6NBg3zjGTF/0QpBdA57pg9o9qAnQ6fbme6QTmmpBH+Sy8mECJ4Jw6wf1qbdx5n/N8F0D3zhsBOmCO+bhzzXlO/VDW9njPx5IXnWNEG0GRDYS5C9IroM+gbkDupnVsQx32UHcNuVNE1qkm7iR6o0duKDlWDW1XYrglOK9QjtFpoVLec8E5kQLUbzrciEyg84Nn9aXsHO7RMTW3hMCcTgrAuQ3fmUryzFRj/P8+GiHbKs/LgN5NpZZ9fX1d1+PtxoDz6iV3rzj2iqt/uhXQMb6L726dBQH06LLIPeg8vNY96OYhPFSOPehtmp1eygPKGGJCGwPol0sN0LtHgAeh5puWB93r0AzoDkYLgK5xnJqqCUDX2EHqKwB2256A7k3bNAad0EgadrWR7h50/t+qk+EgcsS96IcA3bK4X7pG0r7Ixhm7B53G2TYPuuCrjrfGBOia1/qyeNAlPOkt9HsCZoW6A+kKdccDKUifpxm0EPdWTpAOkHOtCM5Zr846PMyAuoasvOH6zX9/Jl5MrvdeH3SvquPOgT8Na6mAPg9v6Z2LAnMHdP4b4+31vAPOlbVd08spa/tFrB+nLe6Ht+678lKOEdfsKifCyouusejtHMn6/dm95DLOm0oBOqb50RXqTnukjkfn3nHS92bPd9ISZb3tXb+i0HbgS6Htytg+siVIx3suQJ+ztk//bc7a/tFrv6ScLcd6Zu6hOE+KlmpZwycwbwnLrt73i4SF0x7Bi8u9XxDeojI45nSKFOPYzeV0TjBfXrK77v3ATZXbrEHpoBwZ+1JLtn/df35/A1yVF9HavOXdKoQLzuU9p6yQDpDXRHLyoAfQo0ul1nCZbuTyKJBMaQXogNMSpI/hHDARnAuwaJDhjaHXmsZKAP3yiAdjS1rS5xFeJYK6+fmDuZEPQtvVwJ1NIe8DSFcGdwd0ZgHgAdwyuE+Nudsd61uzuC+NQWdKrtpId+/hJg/6ENDjQT8TaaznRg/60+sedNVJB3SHdMBc3nMB+to86BfUg+6iXo7Go3tmc4UWV0jX9VG953qfewEQi+maBmD5DZ49XBenBQ98bx2KA8Bo3Ll7Yh3OPf+EvOdc89SbGt6O95z/hfdc484JbeeexfHU7A13emK4TeI8cYyAdJ/6DqjiPHmoO1bBXLBeAb2VPYkcdZj2DZBOB5TmR+cZxnAL7hsnmdm9JiWk/rXxuBM4ChSVGG4J0AHxJUBX8jyOjYe2c+z0v7im+V90wl2mulcjpTi2CmPn+pa3nOMMaAu2v/GBh29pGfB9+b0fu0XJejqDOBeUvM97WqZ85T0fusUyZQVqGdvVdbJXvGf1W3V5yUa/A6yOlv21l3XZzUH5pIxw9dHyyOr+VDB3eFe5iwedRHF0xDZAv/vu22onRtFeqIVedUCnQcYDjIYG4y5nKDcIX4L05jl3UJ+WNQ69JQiaGrMAOr3VdR+iiynqjkL2eCjSEdNCYvv4czVo1ahtnkuD8wro1Dl50FuSQWvMAw14AwhVxStx2w2p6Ya9LYu7vGmjEHcBeoM5vy4MzjcBejzop6ttHnS8uEuA7nBeAR0v+siDTvJLhTBfVA86ogHP/lNHgcp52quPP/4wjX+N0ca4LgTps4e5G+uBc72vaBrgnGuF4VN6JnBdE1KPF53ruu7TcdTuD+Sb6I14XefAEQ136sM837mBueDOO+dafemAzvNP9zbgnHuVwvd93Lmm3/Os7W3HTjGU/6ILgNR4dACTDhzVQULdAVJBegX0JQ96M0sqRz0myoHvBNJ9CjLqye3ODlKlpITqHPKs7dS/XeDcIb0aYN485x3Oa9Z2z9fCPYmZGy5L3XM4V1QMYO7eckG5wPtFf/boXLppHduprMsytvXXnDtKQF4lBlhrGQPGfX0ttbyrAasqz8sE2BW6t5l/FnP4BspHgO7bLgH67EHvU60RqRIPenThRaOsTQcx3di5wbdEcVODo3kJaIiYzWA+AHRtg/dTXnQfk9c8JtNDlx7d9rCILrzaA5KES338uepOqw+C8+I5V3jxCNAxjT93D7rGrFJ3jpuFdpMHXZ7WBuhTY45GnqZaEowI0CucV0hn30eADojc7r5H21U9K5s86IL0TR50QrjlQW9wOdVLpg3TGGOfB/0ie9A1VpbngbKcy4sOzAA25GZwL3q9NgToMsG5e9Ax7hFAEh16TL3Gb7Vru4cZ1127HXkkhRrx3gkH6LXORHlZi+fcAb39L407t+ehe8/pdFBou6IqlLW9jbGPdhLnjWOmrO5zHZxgS6HudW70ka150IsnnbqMI0JTr+FxVqbzNlb7mHVQcE4HtjqBW3bwEtoOmMsqlC95zQXnM6DTeWZZ29t/IVP59F84jpetQ5hjq3wSyobPf1ZUgsAcmB4B+TajI0jLAnUtc54qtFc4H70eQblgu67bBOtA6qZlL+vySZnDtZYdslnnpS/Xz8scyBuklzD3+htKJOfWvPW//94vOaDTMVbrTxRdKNEgA1I0Lo9GE1ANYK/B+QzmBx51b3yxjcafK9RdXlAaYzRcWsjfPQ/84O0m+Ir2S4QFquGr6ItVgsFxaPsuHnRMIe7KhgwIzdEXxw1Zsizu1YOuEHdlcReArGVw74B+MMY+HvR9UgX06kFvHS8AePGebwN0mYe4jzzox23cn6eYGx1PugAJLzpDk3RdtPHaGv/bIb160JW93b3n/pzQeHQgnfvFXZ/50lW86AoFJ9S+7tftaE7MxTzHPbRdiVCBGs8zMbLRzA0tMqhDOnDOvYr6wPPye5+6+ZBnbRec838ualTFuejWra/Bi06HjU+91iI58Bb3UHdlL8cqnHtExOw97+sYusSQDeoyMxX4mG1y5PC7xz1nPF8EkYSZy8M7w3n3nlcwB8prWPsSqDugA+eKBsAJwnHjty9T1naemcqGr0RwikjQcQXMCeeWp7vCN/VGpYzXL/uLxw9t66bv8nIE6UvAPgLwXd4bwTqQWkvZ2ut3/8XhUja91thuXldP9ZJVD3iF8VHJdnWdwF3vyXMOeMu0zcgqnHuYu6ZaC6BHl0KACjd0GrJ4M0joRqI4NULkHR95z1eQclC6B13ZrD0TN41ZGs3HDlOO9kI8KOWhpAEFAAnQ2wwABYBmm+EcwD0Yf07p2bI1TV/zoD914+eBrroPR5Vncfdp1kYe9E0h7vGg76d2HYPewpUHgC5I13L1oANjS4B+oT3oXUQ3UUepqy30d4JnJRDVDAeALaDjXvRD14hBrcC8GseSDmG+n85bPH90+h07zPjuu+fpljSlmnJkNDi30PYK5hXQsfmaN0jX+HN12PDc1LVOZ4PgnNB2In7qLkYLmgCdY6bx6ICYEpBqLDpg6p5koLyCujznioTSaw3T8KnXBOm0gWgL6dzdTmcbzxfdf3zceU0KJygH+DSN2jYodzhvgN47GVZw3uc8t9B2OtsuC6Drvq5OdeBcw1VImCY4H4G5m4N5BXWVFcxH6yqkVwOW6/IIzCuM+3ZezoA+AbXKEXgfWr56AOOjEhhuy710cHbPt5eykZd89Hq0XL979qB37/gmSK9g7oDO1G1tWrcAenQZRGPsIHPqtbfi0aDBQaOKBsjsPS9w7pDOtoJ0B3Reaxw6DRiy2qqXmgdIPOkXVzwsaYDgmVLjiZDRVadOB+8CQC20eADoSg6nMegKb1fDt3mlJsAFgOp+HFk7ZnGXB12APjfUe2N9NAbdAX3Jg94S9gTQT00AcgX0o3jQqzHNmgDdPegCMgE65/aie9AlGsNEq+AF1nMBCJAXvYa6104sAbh7z+d1j6+ma5PhRZfnGUivWbWPMm62ZXXu17a8bH5tL407r2DugK5oAMz/D/c5DeFSYjgy0+M9V2dNwPz2pczuDLcAnJXVnTpIGHKF1iUvup9r2QjS5YHWzAJ0FNE22vV6bpE7U3vGh1UocZmPO1fotbznDufbwFxw3qZV6/vOMSHKhecw+65Eqrdz/eytpv8gOFcmfB1TT6g2AuoK5iMbwXk1/26WR+HuFcwrnFNuAvEK5drmEJiPzIDcwXwJxkevHZjb93Tb5AnfBN91fV3Gamj7NjjHKpg7oCtR3A+8/Y//bQP040ZcRtF5SnPgtnFLU2OTBxVjLDVecD28fQzpshnop21e+JW/b6CiMHcaMfKUaD7rY3tKorNXT7zU6kxPLsjYc3knVx01K/BeQfkqC7LMvegt4dJXDkrP4C7vucZ0UmdOIvy1eTh6Q6oC+pw8qnjQNcb2KB70JUDPeNTT1TYPOkniqKOtg2UpwkMdStP7ADodR/Kgq+NIHnTuZ+5B37VBv8+ik0NZ3RVdRcZrOuLw2BFhBSAo+zne9NqBJZBVJ65DOUnkCIVnajKFhxNhBWjo+aDpoY7iAeR50uCIjO3mOVd+DDoX5pBn86xWc++5z+eu+xvPRc+R4R0M7Dtwl+v8+OJaorNGSQsbpE91UFOvAaruRa/j0iuYV0DX1GuALtEVdOQRMaIwceo/95JdrmmFX2+D8xGU+7oK5A7mM5z3ffaOBXn/2WeGCBzlutl38bxWokdNU8cc8njO5TXfBue+vGR1202m31OiuE1QXsG8wnhdrusc0kdA3iB8wVu+tg4g13bFcz4qGzgPPOsqZRW6R+sYIz56T98lQJdtAvUK5oLzoQf9MnRQRXeuPOOx5kOnwURjtAH6PJ3UdpvHqxdoaZA+NWqrpwRPR92faL9VQ8MFP4CPGrCefGsGcx+rOgP68w3Mq8lL6fUFrwZ1te7PUUVjq2ZxJ4GPQtxpyDugD8N3NU65QPquY9DrPkUnp9EYdAf06kFfz+Juy6zvkR4C9Dl5oXnQBejK1r1LY37v1cOM6bwVINF5y0wchKQD6ZofXZCucHd50N3bLEDX9QOcyxiLDqTTKcz1DqS3xHskWeuzNtTdG0nXtMOR8kq00Pwnbj41e8+3wLkvC9DVoYAtDXVQssCjdixEy6KjiPt0HY/eQNVC3R1it4L64ytAd0inTgPpdSw39Z86uHE4HlFZfTo1r3+a6suTljHe2cebq2T/lyB9Dc4153kPzSeJHvc4OqTUqXWZHB8aMgBsyXPucO4QXCHarQK4gznnRLbpM/6+kvvxuzWbe4X0JfCur2vpcC5Ab6XB+VwurRuYoFvLALOD+WwFmjdZg+T+PQ7ODue878sO3Uu/5dvos4L3+lsO6PKgb7xuo2jf5SGBhJ8TpkcjDO+Ah66vwcgAzmUKcW9jir+8GpPuXnQaYfwG4VgK+W3jgqeGbQtRnEo8OG6tFyx2JHtLOYZHtSv2HXNd6Y0QQa3Pfb4G6B1w1jznBdDb+33KPoE5YCsPunvPVVdOwiu1yYPuIe4aY6vw3RnOzUNYAX2jB30CjpP6D9GyKqBXD3oDxT7UosK5R3kI0KmjSl4IoMtrugTolwnMNB6dhj/XIDDANamp1wh1F6Q7oFc4d0B3OGd8v3vS1SlHJzGeTJ5HytvQ8kZM1y3H123OJ/GH7351yz7f4UjeVoW1t4ztvdPNveRLgF7ndGf/BehKfKp7FEO3FBo9Z23nPhwdWyQuVMI1JbMlrHvkRd/kSXdQF5wL0Fs9VtK4knCN58PPfPyTr1fYuNooXv+0f4Jzn07N4Rz4BvKqB93BvAI6+y1Il/dccK45z4lko2OIzoQ2hGo6ZvU4XjhN14+GDch7zlj+CucC4QrkI8CuHnOO9QjQ3Ytev0MmEBecU1bAriBeoVzmr+t7h8B8ZFuAXOBNqXB1Sl/m/aVSn9drGa9HsOyf12sHcxmv+e41QC+h7g76bg7mmLK4C9C5BhPiHl14cRNUaCANDBpGQBcNGoWDVghfsoOM7yvwql50D1v2Rg0NK8ICecDQyG0JWhhDVRplbV8B+AmyZG2sWrf630Zq295q1joFlqw2BHex+h2+n0cx/0+bTNvX/W0AumAc0zXjOPdjreO9tu30XuvAmRoo7jn30FEPb1fo8GwF0mcv5ZzBvYe5986cBui9nigjssJG57mEjyGOVwV096DPY9DlZevh7dWDPgL0eNDPX1wLIw86QOkedA9lH0H6AaCv6uja9H8DD/plCnF30UEHdHINci0S6k44+iEvOscVOO+lPOkca/eeN2/0BOZcT4zpF/R6uDvHFm891408mZxLrh9gSc8K7kc8O3gPeGNbgEVw3mZkeOLmUz5eXp1uDugV1CukK7xdcK77Uw1t5zitzXkeHV8d1Hx+9DoefQTp1Q550Q3Uud9XSAd+5znFJwBWHfT6R6k6SP1TtnYNlyIpHGDOPjoEVkivHvOR97zt3/Q/5enX/un6YF+UlPBSdA5x3qdnZZuJYQItji3P6BGcL3nQl0DbrXrOOVej76pgLiiXVag+ignANy5fPZy1fRdbAvJtVkG9LmOCb5V1fYPmUsq07TYPugO6h8lXUK+AnjHo0eVQ97gCY0rIQgMMDwGAPvSiL5jGoNcwd82J7o0bfoPGTZvKZ3rI0MjlQUNDl4cNDWzAkN7pNg5sA0gKTNvFOFmDXObKHtgM0xVQNxi/v83WPmMAXAF5CMkbtq3/sYK4/kPdHxnHD2tJ+abXlG4NvLuN3tOYznm6GAsd1fzI8p57iHuzEfiswc9zKwCa6ggeSuqJxp/T+JXneU66dAKegQro/C9FAywliROkbwP0XTzonKu6T9HJqV0Xb1/2oLcQ5x7lsQTn1YMuL/qSB11ThJ1UHd0rTc8GGv78P/5nm5uaqdcmiGlzMA886LWUyXMuOAd+VbbQ8R7uzvHVnOJ0CNBpzO/SoYtXHWs5U7AJnjjHgDkefryfc8b5Cc7dc+5WIb0CuuCcbbmvcY/D5D33rO3qfAucn564roFiedEF6QCwQr8rnFMvh3BuIe4V0JtNzwCN7waEW72afrOFkjNf+lTv2jSEDP2wOggws72m/PK5uCucU1YYdyhXJABGBIh7z9kv9olrgnYTnQR0YNAWuDSJCaf7jvIjKWs7QwbaFHUdXPE2b4PzJTB3QNf50XcJ0jfZGmC/57DXe1ebveN4wW3Z11Xo3sUcmE/aHLBHED7atq53myF9AOcqR2DugP7S370/gB5dbvEABJZpgNFTK0hvHpABkMto8Lb5YfscsYcyv8um14J0GmFAHo0wHjaM4aMBRmOHh588623KEOu1ltEAZ7wVpZaXDNj01/49Mr5/yfj9kek9bQfwjfZzZPX3/T39r1oKpHkteNZ38dsY+8Mx22Qc1yXz7bjByTsFnLt3igYwDQY1gOUt87nOh+N8p+Vv+ILmPj+AH03rB9hSN6gX1AmfT/gkvJMV0Plv1YPe5kHvjfq5EW+gsQTo8aCfv3byoNPhMgJzi/BwQNcMAz5tZPWgX6ox6CaijYBO/hv/kf8KMCthnLzoPu0akFuBGNjl+DqUc6zxprsXvXXQqZNuug8A20og9+Of/dy/a3Omf/yxN2Or+9G1tzqYE9EjMOfZJcD2TgOH8xGkO6Cr4xHzDmYNv/E5zwPnpycixngGaBiD5rUH0uVFxwTdDugtgsLg3D3ovp2HkXO/oH7PY72n+sezT/WP+wq2Sl537a3q4GV/Wk6WgefcQV2AyG+6p3xk7t2njrMvmrddOQ+A83rMLrQmsOIe7vPIE96O9xwgVqi4Q3MF9F1M50VQTgkcC8KXoLwB9LQfzSaIdq9+e+/q+nzl1fz9XZeX4FvrRu+dpo1A3ZdVvuJ33v28Svegq9wG5+5Fr5Au73kAPbr0akl2PvjQGzXvKI0dGjqax7Z60huYA+SCc4P0Ci8YXnT3pNMAY7y7GmA0tFrG7v4ApLMAaG+91pPhhaTkwQTwUNJglFcFo1e7lr7M9voM38F3tmUr/bdquWmZz6uUaT8x/13fZrSu/i83ztHa6/771Th+Kne1+rmWwXlqDNEAYWyow3kbAtHhXIAum0G9w47sAM4PplfDfE5hhbdr7HnzTD5w5cU+Hv52VQHdx9PPHvSeTEqgIe+5GvlrCeJKHd8G6BmDfrpyDzqNu5EH/aCulsiOoQd91ZHkgF496A7ol86DfmV1TIFPQAAg4L6gjjpBOoA+DwsZmIBXw0WAdIE6mfIB9W/9/FdnE6TTScxziIia+TnRjX0AzNkPniNsx2fwxPPM4nvmc90714Bvfn8Jzn0/2Z7Pt++anoN6Zin6S8NvFNpej1t0sgJC6Yym443nE+0UQsl9PDrXuEP5yIOu96oXvSWMo7MJUJ9KQbHAWOO+1VbBqIOto2B6Tx59IM8BUECuEiAXnPOswbTsYN7e6959dRZQ3+kk4Nnoc7ZftvsOHZ1tXH+fR/4N73jPH8zzyHdAH4H5UeAc41xhnDcgm+8n+kFzq1OOjH3x8rTtO6/e9/CorMujbSiZfmyp9OXvftvVj+n1aFllXR6Z3m/QXJYpMaBa0O4wXiG9wrkDejzo0R0hzYlO6JSm1ZEX3cOYBSmCMM0ZPHvSR1OzaXkyGjw0dAlnVAOMkEYaYTyAZACh23c89exvjIykc9vKuu6iGPvLuaCkQeAl62X1czpefuwoWxZcK+vxZb1/DrvrM1+6itEIZt5iPFQ0gh3O1+qGILbXkVZPetj7CM6xNr63N4CpB3jLAB+goA1fQCcxtq7Pg14BvXrQPWw3HvSLo1096HMdbeUKxkaAjlE/uaf5GHTuVxp/fJk96Kjn7fha6i4ROi28t097BaxyT5AXXVDkYFS90gLgliRuOtZKJEcnHZ0gGHDdohUUUj4ZxxzjPoSxzLOjjVuf3idZJfclOoH5Lqxdp+o4vL7qbFuCcwE6Yfhsz3PKn1e1Y8aHNsR7frpSHeTaBtLpfFNWdw23ANIFvIBthXR5ykfA7tbgXGbedEEysC5gp2xDvabnhjquAT86C7x0MJ/30cDcX1dYV9i9h7ZrSjWcKpfOe35ldR9v0yVOgK7M7YS3M1Z5k/ec47srpFNfMOAcGAe2gVE6fVoOgXsefqesDV2Yytfd877fc6PjoI2NL+tPy/i9Xct9tNf/3tX/qGVAGljXFGkO3NW2wXkAPbr0UgNMc8jSU0wDSIAuSNc4Qm/QOqjTqBlCugE6pnmF5SnBaOxh/O42k9dEpRpsasipMeelW/2Ml8CVfseXd7G6X1r2xmXdr1Hp37PN2I6GQt2X4xqNX6ZA8vGcNIIFOIJylRWAfPiDQtornGM1+ZZCR0/a47zkQV9LEvfIKlSS/yI4X4P0Y3jQA+inq5096DqPBuere1pfN0gSt+RBv9Rj0E0AksKMidxp+UOmZ4RyUfj4X1n1TCu0He85gK77A9cRQK2SY80zoiUXna4lOnMBcDeeG6zHuDdpalBdkxpuNUN6AXQ6FNr+lH3UfvE9c/6UHtmjBKfKKdHO+XRcTiK6J9qsOblr96TTTiG6q0VRVEjvgC4gr/VyDdTLmHTuEyNQF6xj3E+03HIxdDCnc1cQTglgq1yCcu3vITDvYfstcV0Ps+f/cu1xDSqy7NKMOzfxjCYrPqAFzAHOr7p671bvueBcGdr9XIzgHO+54FwA3joECKmfrGWO7+UmY4y8lxfFOL4qMfZ/l1I2+h7/vk0GrAPTQDXh70B2BfMlD/qRAP0kHDtRdN6icduyZjJdSA8hq150NWrmMMAC6dihMPdiChuUMfZQ309DS+MHtSxTY8xNjTRKNdp8HWCpdfU9f+3fVZe3Gb9by6XfqeU2YztvlPJ/akO1vk8DQ69Z1vFrIXzTd/LA0nuUOraaK1if1T4A5DR+D7yMBx4pt0OQ3rdbH/qwDuhkcBegt+SBPXszDWA6i2bv+UlpBw+6AJ1Guzzoc3i7dTwswXkA/fyE54VIoE0e9HYuh97zVVlD3Alvp94qiaE6ku4UD7qEF9ND3blGCbcFkLhueE4AKBWEBOozAPcp1nQd1aFTmHJSYHpGcOx5LmhZpYZgOZzLNASrdRw/vQL0tc4CwXl/rvE92h8NyeIerLwYDc575yHH4TLC0b7Lx6OvhltceyvXeJv+z7zoXv8A3iGcG6TLHNSXTODO8iHYfmQ1P7uXDt/zdhOUNyufFZhzLWnOczy4XGv8X9U9IJZjUY/PZRD/bTS1mnvPK5hjDuZunCeHc4E5HTt4ywHzls/iP60gm3Hv1fDmq7ys9qNve8cvqNxm9bP+HbsYEN086W+7+rHX/M7VmwD2CNIrnFdAF6QvAnoUXQbRuAQgNKdsGwfePSQ0vpSxVwmBRoBOY0jlymNKg2tV0vBRAwjz5EBAoHvpVQKGLAsS51LL26xu69/RS/3O4rpN5t/lv3c733UUK/9Tnm239rtl3bbP+HrAhd9qDVugZTrP3tAVsApe3XveYH3ajoY4n/EM/3gjgXJNqwbQtlDWCXoI3Wd8nZLfnLRn6qge9FGI+yi8fRugC+IC6KernT3oM6AfgHpNEqfhGPKgcy3cyR50xPElqkWQrvnRCXX3+dEdiKqHWuHtGhZ1CNK/vLpHsCxIJ+TdnxduvDeCfJm+X9O7aT8As7YMnBug830e4aVhNz7uXPkkAujnIzqK8KIT6u3OBHnR69Rrqo9DUC9wTikvegVzN54TS4C+Dcrre5TAo+C8eXg7nPu87G14CYlzCW2/xJ2B/D+ezXhZ8Z4D54S3a97xFz9wMDVaDWmvoM7xHXnNBeZ4yGkDAJf8JseYZ0fLdYCzyqwlKVQ5AeCRyv45mdaPth1tt3PJvk7LzXx5i+k7lEhYiYLrspez6Xd0rAbfL2u5BaYSSH/d77zzP8iLDqRXQMeDXuHcAV3e8yVA/9k3vSlDj6JLoh4KQiOThx8Zrum1VeOLMC49uDQ+d27cmgd9BXM1U3JvDHdooyHUxiBi3aOiZTW42EbmcE/DSa/5vlYKJnujW2BJyfe2ctoXSjoPKGkg7lr6Z7TMPmibuZFpXiH3HC+W1fSZHc33QV4pQbQawSq1fgaTagah+nw9r80KnK/VBfsspmRQamhjAnPBubySeKhoANPwB7CA2ZMGdBo13LTrNGujMejyoK9Buo7VEQEdDzoRAQH009UuHnSvp7p/HZzb3ulYxqD7NIB1KMad4kFH83j0Bx54MZBOY5ZjoEzqek5UEBoBut8r2z28XlMG2DwT8GZ7IjmZvqPdfzcBer93zR50gXkBdIXWL3XGMFOHxp0H0M9e1EE6im7dd+Wlfp23vCq9k0igqwzpnin9kBcdOLdQd/eiY0ogV+G8vddLLVfonsvpO/S7AnQZ8Eg5g3mHc7y7dDqQwLSNt5/aY4BSy9p+GZ8jCkW+++6vw0kEwNW5z5dC2wXm1XPutnZsSXA5wTke8+b5naCS9kCddlYz5tQpaN3qNLa8vnX/lUPbYUvr23v3XmnfM3q9y3J7veH7t9nS9+6yvGQvufvuV6qsRmcIIO2APhqLvgTnFdJHgB4PenQpxQOAXrKW3bs3vpSx173oauyO4Hxu/HbvlObBlbFeZVsG6m6sMvq28sbBuPYZNm+svPP+3gyevYHdGn9TA5ttlDkco5GtchW2ur5e4ayjddq+Lmub+v2+rcK5fcqm0WvffvUdB+9r2Uv9jkBC/3PJPJu6H58K4Frvmdc1bdrc4XJ9PSMypRreDucO5jIlgtL4Uo3vlKeZziHq32l4I3fxoAvQG8zh/ev/dQ3sKkxsAfR40M9Gu3jQ23nt9wsfd97qsO5hxYPOtVbHoCvE/U7yoEtAEpAOrALpLdpqek7Q8OUaamN2P3oQZqxwcgG6Om2HgG4GLGsb7h0k6hOY81rrBeK1dGsdjiXE3QGdSC1+j1LnmXwgdZiKzyoRQD8nTTDXIv6mOsi1LmeCOolaW6VA+pIHXff60Xh0ebx93cgc2AXeAnEHc+3DNjhv4dfTtdRmKrBx54LzS9kRCKAzBG36fzybCScnBJqx58D5KLS9Qjg2CnOfj+1k1I2Wjd/gnDpEhw/3b67pVo6M633aRy/bPZ9le5/zQ1vDP6ftRuspW6cTr21bvqd1iPZS2+m358/Y9m3b8t2H/kd9v+/3yPSdWvb19b95WX/HnS04SQDoeSx6z+wu2F6C8wrpGz3o03lt+xpFl01cUDwM6J1WGKPGo9PQVTj6gRddYdAdyg3gaqNI62ZvlZlgXA04Bz6Be2tMA5LzdvLI2Gc6aGIVVB2sHaYPYHiVZdzhuppvv/Q5//xoXX3tnx91Bsj8c/4f/v/23i7W0qu88zy4qqBRO1FbNBDiJEMwSQwRIQI8GUgHTToomRbOjTWVi2mrb5ASiVGk7pmL8VxYssQk0lz0BZrWRFxEGnmiGamkUTxpxa0w0Mgx/iAYQTu4bWJsUhQuV7lcZVcR28QBn9n/Z63/ep+99rtPnX3O/qo6v5+09O7v/b7r633+63nWWr4mC+p8zfm5RXcW4VmYNIHi96o4HwT6UMZTAy8p33txblE+Jc7T3E6LHXnPNb9wVR3rfgS6QxEt0MciBJq3bgGBHh7063DF3W3icB70oV9qbSHmoZdBMZWp0phX9ah40E0I9Mm16pq9srvyWt6+LNKzGJrt/0v0kwW61yTphXkW6Hluul8fE+UW/P593xf6e5E9557bbnHuVePdfj3txvudLzuyBw5GiIQHd8r+6E88e6dEuvdHz550C+JcH5VCnI8Ic4vzPY8W5el5CHOHsifveR4g6NNMWHsS5/Kcx3VN+jIPXIdAuw4XvvK9Wd5Vibf3/z+f/x/l5c4CXaLc4e0S3mMiXa/lveanBHqNTFA/FfPJJyJOHnLZA/r//px6IoKoS2Pv5e9c7yx6vfH5iX3nMs4CfSzMPQv1/vU9BToedLhekQGim4FEevOknz57b4xMP/vC6+FtqAavw9CbR6qK835rnV6k98LcXq1mQOdk8W3PexXk5ThtZPu97A3OQj2LWotiH/eT9vrOmEjvH+ekMO/+NaU8kJCPOeXryEcvbmVRPibIx5LyyMKk/5699PHZVG7Ka5WnQ9mV9J2riXN7qMYWXlrllkU2AiTiJNDleen3QZcHPQR6NdxmFok7nwR6Eula8G4vgS5vIx701ZI96DJqew/6mEjPHl3X62kv+ushDPflQZcn4ojgUGPNxfZ84Lb1lUV63X4tLy7a+v+a/2pLFud5WpNTFuJOeTpQL+SzSPcx7k/ny/2jF+iey96L87xgpfomiXPPOz9K5bzteFV3DaBIzGp3gfCkT4RuFui9OM8e9D7NiPF0tCfcj6dSFecW6nlgIItye/VHxflEQNpznsW57llLXzR1i9Bgi/puL1Ssxdq0sroEujznFuj9HPMszHXUe3n/ec8793ZqmnuuvNXgvP5L3lylRYUmHBzldUwx1CDMyDx0Je2NLlHufdLz816cK+m7CHQ4UsgQ0Y1Bxpfno0uke9G4vLCbjSAZPs3rWMMadcyiPQv06RD1QQQ1Iy4/z16vZlCX74V4r0afw+F9nPEiV3GeRfqqkkSzjgrPzMcstvvnY+K8H1zI382DBk5NVOu5vYH7FOtNnKTvxe/VPHUEg1IW5/n8Pa9ewkaitV8VWXUpFl7qDOC+Di6TeR50CXTduKcEeudBV2riTnUuCfQszvcU6HjQV4oGFnsPujxqM6u4p34lD+61fqm2AXvQVZ89Bz170POiYUfJg56RsaVBNcsdNPAAAFEZSURBVHnSvf2a90j3gG4IlCTS3bc78kYC2feQ/p5ioT2zXsflMk9dIj173XuB7/uS/8/3KItyH9Uv6b6mdiuBp4HD7DlX37TKwUM4GBGC+81v3qg2b0+6F41T/QtPulZdr1MuLJizILdQz+K7F+ZT7z9dfmtMnHsuehboY3Phs0CXgPR2auqvdP5ZnGse7/UszoXKUd5z35cl0LWCu1dv7xeHGxPpShbnSjm03YMf8sprIFHz+RHmm2OvheKyKM8pe9f3I9CP4v0YjhjqxGx8aeTxljOX7v25p8885jnpNqhsVMkIzl6K7Em3SM+hpBJ82Rsy14veDOoh9eLeIfL+Xc9ZV7JIzwI3C9teWOfk7cAWTVl859e9knn/OT/O3xt7nFOelz6bigfQR4v2Pul9DwA0UT8l8l9v+djWBagDH/aaK/XX2HvOZQTLO9V7zmUAS+CsOnR0UQ96L9BdB6e8elcJcc9bcV3vRtamOYgHfYgIKY/H5qCrLvcLhx11D3qPpqYoH7Q/tfdIt0jSto0SNLpHeP0SJd0TJJqzF7sX102gq//pRXryoOtzFvqao67y9HdV1v1vKknUq0z1vzmsve+bwnOu+ZWwlahsJNIlZtXu1adbpE8tHFeFekR2TASyF70dSzPe8ZTsIe8f9+I8i/Q+WZzLhgpxXj3necV22V2x6Jj2O7/OhaQ96BboElkW6HlxOIn0eeLcnnQL9KnohEk90CC88lfh7fqf/hxgfSj/s0D3dmveE733mmeRPpbGBPp+pi0AXNucPBnzOj3X0AvHZW+6jd8s1B2O7jmHfcqi2oZWeEMsuPcQ5r0o75OFpIV5eHQnj4soHxZY649ORfDuLdr3m/K2Yvm41+O9Uh4skDD0sax8/PrU4+JlGo7t/STqfexT/m42iHNy3nrgQOemlEVrFudTK5t3nvMQ5yueW6f/yB70vVZxV+oFuqd0qN7tV6DjQV8fMvJUl7xw1J5z0FOf4jUYpueeD9NVPNjUC/Teg77qAaZtReJBdVsi3QvHyUv1S6cv/FH2pOveoHZlod6voi6BnY8W8Bp0tRe99XO1H7cH3uHrfuwIH0f55LB3h9R70MV9kwYOFSkmz7n6Jl2LPedHtWyvBbxglewUT7fwHultgdsq1powfqKIatsuvUCfEelJbGcBnpN/qxfqFuRTwryKc8+LznudZ3F+vc4578nzzyXctIibBHUW6BLn2Yvei/PsRXdZ57nnberAN568Q9Pp+nOANTHRFPsV6H0a854j0OHoUldMVQeqUCuNUOsmko0vh5H14YleOKwX6tk4tthzuGLzol8l2XNiYd+8LJMUCwolITkm0HuxaaGZk8V7L5Kd/F7/mTEx3qf8nkW3jnrdIjeL3f61/J6Svp/FeX7vqq+n1Izg/LzO+/RCTZFnVcDna+hDvS1qsgGcVzX3okt9lVsVfYi7Pegy5BbxoDfPXApz7wV6XkjMAn2d13oUkTHbh7jP9aAncV4ez3rP3aau5kF3BMhR9qALCXW1rzwn3QuMWqR7IFcCaJ5I74W6ji3S6nLp69xHxz2mm1vu37BA93ZsFuZ5tfY85aYPa9cCZLTZa4MYJOoWiWzh7gptlp2SQp6V3CdYoOfF4noP+ZggHxPnWaBPifE+Jc+5BhHUTrI4j62stAr2EVl4TH23rnkvgW6RfvzRC3uGuSvFAMzTRaB7+oDyWH2SBuYl0I9Cvm4rBxHovSh30nfGBPpRGNgCaMgA1cIa3v82wrFSKKO86e88+/IrMn5kBGUjKadsIGeRbaGdRXoW4mOpF9pjqfcOh2d4j89kkW5jMB/3em1MlPeC3UeL2jFRbnGQX+uP/eOpZDHu69Jnsweq+1zvbc+ec29lpDK1SJf49vv6vq4hC/NY9boKcxm/ebsiGcAb2Ut40mFboEtE7MeDnkW6xZ0FukW680PX7u3jxjzo+u/+lGB59HPQFeLuOegy1lR+syHu0x707D2PNni5hLjjQd8/jrhSvbdIVx+gMFOvX+JkcdSL9BDb6bnuG1540n2zHluMj4l6b8lmQZ6Feb+Vms6xD2tnSsq1RYjZ6kl3H5AXLvQWbFmk9+La9bH3nPch7PPEeS/Se6+9PboW57KfZEdpMNHi3Fv59dd3PTPqQf/Cl5+a50HfS5xboLuMldcaBPH0AdULDQb05wBr4hAe9DFxPs+DjkCHI8VETL3JC7JIZMkA8x64MnI0N10GmOf15ZV5e7Hee8TlIcnC/GopC/ReqPdifF7qRXr+7l4ifa/UC3U99m/a4O+FuwRufi8Lab9uQZ2Fe3vs9/dz1HVXEZ4f98Jcj/OWRt4CKc8r7587nN3G763nX/piH9JuMeN5nesU6BJQY3tl68at0XoZcFOruCcvel5gbJ5AdxrzoK87WuAoorJ1/5QFujzoNqTHPOh9iHv2oLs9jnnQLdDxoE+jclC4uwbB1ObDKFb4rgZzqzezF+kS41Oh72eH7c+cvBil+yU9lgDXMX8mH51UfhbnXgzOC1VqznyecqM9kaOtYuBdc8iLrrLrw90V2uwt2HqR3nvTQ1yPifN67MX4zHeTOJ9JNdzaHl3VPS8I1zznE3F+1Ba4Up8RK7jv4UHPc9D3CnF3cp7HjhJPnP68V8bXKvHRX+NB3xjzBLqF99WEeS/Ss0DXAoMh0AGOEiHQtWqq5nxNjJjYhm1i1CgsUCJMgkQeiSzSHfIugWePhlMLg0+i3ULdx3nJIn2ewL5a6r+XUy+6D5Mkwseey+jPorw/v5aSsB59LQnwvQT7zADA5SLGfS4+P3uo8lZpFuhKvTD3XM5+lXbvbz4jYibGhz3naxc0yYNuESfj7T0P/Od/J+NNAl0r7cq4ipDHKtCzSM8C3YMWeTs5e9B7gU6I++rJAl0rOluga4udGYE+T6TXhRHdXiK6pRPoGnhy3c6DTnjQB2T8aoFRb9XpLbCySA/juRPqShbqcUwh6075HtK/l5O+b2GupPuSF4NzWLHKMHsuVX+Omji6nnBIeO9J9w4Dqn95kdssnrO49kBtn3pB3if3M/69/PvqhyzO2z7n33jyDrUPnedRXowwFiOuIe63feHL/3KeB30/3nPPQfdRK7jrPmCBrvzWAGJ/DrA++lXcD+NB7wV686ADHGUkOCS85E2XB+K2c5fukuEqcSYjVoZQeJwk2C99/7RW8735wvcvve2FV37gZGPKIr3NNXxpWOgtLwDXi/heXPcivX/PKQvQPvUieyyNfUdbqPWfy6J3LEkA6Dz12IIgn39OM0J+RIzb6+ff8WOJjDxYIDHp/8/PlXQdSr62PHfzbZde+YFDRD2H06JcXil7zb0Ssr3HOWRPQmbt4lxMBHoWcfKga0VXe1eaB/3pso2ORbpDoyXS+1Wgc7JIV76o7nuesvOAkNnV4pWA8+CLylUelCzQY6G/kXnoeZE47/jg6AiV79U86P35QFlhWyJd9wiJJIe8SyBbqFssZbFuUW0v+thR5Zi97Pk7Svodi3L9R+xv/tyLfyGR5v7J4lzzzWtED161a5ws0iV6PUiURXquf/JqR9KAkULRJ/VG9wEdJcrz4FFO6kv619TP6PPZY5695t5GLYvz3Yd23qF7UgxaH1Gv7n486BboOnqf8z4pz7P3PAv0iFaYiH/lOR70zdJ70Ptt1rJIz6/1yeJcSSL//X/+pT/T/uoIdIAJEl4yUOVFkjdJN8EwfC6+dHcsfDK5GWbB/t7nrzz5kxde/7bEuuarZ4+HQxglDrVCuAWvFpqTUNR7Etd57+38Haf+vf59C8/+M3otf25eGvtu/xmn/r3+eZ+yQM4pi/wxr/xBk8SHH+t//Nz/q+c6WqBkMW5Bbm+5ylyCVIM1EqXr3EJtX9QFD7XitIwBLyQ0JtBlmHkuukW6vXpZFDgyZCqK4PIrr3hua/agI9BXi8u296B7DrqN6ibS05SZeW05R4mMCfStq+NbiKOtJIQliNXmJFBa2PtEuNiraaO6F+lXSypbH5tAqtsrRTjx5H8cUqyILw2seDE4edPUNjHYry8keBUx1aL9Jn2C6l+Uv4R6tU/6+ekW6TmyY55Izyl/R7+j33P98xZqIcwnIlH3Hu3FLa8xURtFoGsOuvJEwk1RbRLob3/gP4U4V/TBP/3Lv33D4rwX6Mr/Xpg7eQ66ylz2qWxVPOibQ/1s7KIzEdJZoNsbrtSL9HkedKef+vMHT0ug33rfF/5v/a7qUf+/AEeL6pGUJ10j1TJSdSOUGPEcdRmx8qy///yVf2ux/p7vXvqahLpEupIEjQV6L0zHUi+OD5ocJqnf9FxGpf7/+uTPzvt+/x9+/2qf9X/v9zwsni2oe/Gd09XeH0v2CMeCb5OkKAh7oT5w7uX/y57yfo656oDnmW9V2F63I0GE3n7jyTvKPPRn/kQiQZ68tmLvRKTbk57FgOqqxJqPzh8lh/mPeVkR6KtFAlkCPe82oXKNvZCfKPNMVX45CsJh0jls2u3LwtwDMH2Iu7fjwoO+N177QULEYcdqe9pFoSziVRcbzR7NSXn1IrwX5H3qjXJvX6VFID3Xt3nM7bWU13ySGFy5PvHq7nIk2EZRe/XUPAv1KY/6HJG+V4rPeSG4ugic7ieeSqF6LnGofkn/H3PNv7zzY7oXhTg/4usdeBV3CSvtgy4PuvdBt0hX+pmvf+fvJdR1VOrF+FhS/28PuneQUcTMUc/zTdJ70HOIu4R4Fur98Sf+37/8vkS5jvqOxbl+J6Yrss89wHwUJqgRSntMtHK3boYS6vaaSOBJ6EnISPQ1j+zEAJaH3cc+zXv9IKn/ras9l0Cd915+rT9qMCI/HvtM//hqyeei46JJ/9M/zkcnl4eOWZhrsKV5y2sYu1dmd+i6Dd+NhLLPY3JD1lZrXsRKnXi/iFCI9CfPfUcLi/koY0vHd515+bsqP4fNjiXll73nHrhQ3mjQAoG+Yiblq8FCGXrqe2QQews9e8hUhm977srzKisdVec1SKi2pIGV3E48fSOXqxcWU1+mfs3bBcZA1DbV9S0jQo5rCG+kiTjx/cHtT4MpXshL7VBl9jNnzj3pMPirJX0+PJZVmP/CA4/eJ4NNOzU4tFWRMxLm/fnB9U+rgxOx7mkXasMeJIp5yhNB7bqn/r+vY31yv+K6p+/KYx7CfGLnyN7xIK3vA0dthfb9IFshL96qNqv2q3no73roby4pTF1HP1aSV13HiJSpRw+ORHlMXsvb2MVaSZP+xv11fw6wHrTegKIlJNA1X1xeb4t0edLnpRDhXdIgjr6r0HaJfXnPNcCjetT/LwBU1OFKBOXwd3WOEnPqKHXTksCToSshI0+sksTfXknG8dhxG1O+nnmPl5F++vnLf5SPOeX8mfd4r6SyUVI5qbyi7CZGjQwOGx1eJOtaWAStLXT48MNhpGnE3osIaUeCIhSe/WyEP8qzcvrCH40l5d9Ycp45tB0P6xrp1hjIUxhclmojt5y5dO+85PdVlnrsctV31W5DmKdBKRt710Ld3wYs0LXVjgxy5Z/KSd50lZUMc3m6VGYSN0pqj8p7HceSRLiOUc7/8aE/CO+bpjd86au/pwgZGfxq57uP7vyU5/r25wVHC89Nz1PzNFAkse76l+8BGjzy0UnP9b7roeurvq/f0b3SXnNHloU4x3M7gwbOHdmmgXP1B2rH7/3G038soe7kgbcyl7+Ug577sT4/HEu5xLQCLQ6XFvRc+xavMEVeb0CiWkJdAlti3UnP+6Q6oc/mpO+rr9dvSZzrd3VvYcoSwBzU+akTVJJQV3IIvIxnhcErqcOUoeukTjQniUAfnST0r3Y8SPIgwthxLOnGm49Ouo7+Nb/ef9evjV37Imksn/rXxh73v6PksnDZ6Kik8tLWNUoqSycLlGvphieR7u14dLPQtVmsq8xlICh/dMxJBr+SPqOjPuOj657y0J5V1fcm4DDMVk4Op/a805jvWctJBnNstTNJfqyjBLePTrlsp/qByW960MV93LVU97cBGU8R3ivB/ODOTfm+IANdbVH5HNNQvvjAx5QktMO7NjHC8lHJn1HSd/Ub+j217TbP9+GHY/sqFoKDHPau+qH6p/ua6o37jHwvcL/v/iL6jvqa3tfn4p5e66yiQ/R7ebqX+omjPtd8Lro3njx5TN5V9Qdqw3nQTiJbSY9zikG4yWd8VKRMpFou6htULiqPfC8mmm2zSEDLi672ZqE+L+V+fqq/14r/NeX+Xr+tetT/JwB0TBmuvUBJz9VhhsE2MaLUiVrU5yQxpRTh8wpjTUmv7/eopEbsJOOtP+6ZZOj5OJbqe7GlV035/Xmvz6T+f1Pa8zz7708ee50ACwo/boZD/YzKIQTOPubFSQxdD8IkizrXCdcrdfi6sVtA6PlY8uc1t1X1y3spe34romCNpMUA3VeojGQwy1CTCNTRRrQNc5VvX8Yq91y2MTI/KVv9tqdwXA9tYFtQO2n5WQ12h0ReLfmzNs56D0r/HKDWtzLtok7BcL1TW3c/oP7BA0ceAOpFQRMGk++3qRwp9f8N46jvjj530j97kKQXaGODcrls8uCcBH8sFqj+mrLYClTG3vLWbSwL7l50u4+3sJet1vr62t76/wCAFaPQJzVAJT32c4vIGAmt718tqRG337iK+NwX/o3+eBD67/bP57Hfz8H+qfXLNwDfDEKUPbwTgx86xmj/5NgeazG8Wk/7n4Q1o3YxSRociQGoSRnGokwS2DLY0gBdpOrN1TGM83pUmeayxcBbP1k09cll4s9RNnBYsqh2PVPfEan2HbEtn57X/n+sb6AuHozIx0mexpo2j+78lIRaLKxXB1XHBlNjTQuL8TqI6vvyWNnAhkkRE3G/rU4Rl2e+N/t+3Gx9fT210f6np8A+BlgduSHmZKGt1L+3n9T/D4DJdSwiCUYGe5Tye/a+UL+2iCrQw5teB/McqdM8KmlwxUeLcZdrX77938ByiTyu+Z/FePaU9x7zMMQBlky0+9o39NFvFn/RP9A3LBVHQLWdYLLwVgRTHXD1Y5eHy0iPLegoly1lcn92+4r7cXJ+5AFy21q0MYBrEBotrBLXr3wcu1mMvQbbSy7HsTKlLFdMnYrg6AV7UOwdc3I4secsjh0dBqnfcCikp/fY+9Km7eBVgQXo+4SxlD8Py2Esf/t8b++nQdT8G7D99GU5lvrvAAAAAMAKiGiGPy1bK2nuqOaaavEtLfiUkxZ98mOv+uvX8lFJ72txKC0s5IW+vECU1iNoC0Qh0gEAtgaEOAAAAMCGsTiPhZ7qdnixfdXfnP1zbaOUt1TKWy31Wy7ldMtfPv5/5G3X8hZLeeV9TX3ozwcAAAAAAADgyCEvdqzcm8T5zU+c/vwvfPvMd2/+9gtXlN79xN/+6N1Pnbn4k197+vtKtzz29HM65tf0WEnvOb3v8Wf/k5L3Sta+1B8/f/6OvBc1Ah0AAAAAAABgp3jPFdZucS4x/TNnzj35zrMvv/KTz1184+3Pntt1+vGnz78RYr1Leq9/TUnCXUL/lm+deUre+F8598Iffvz5i78jgY4HHQAAAAAAAGCnzDPU6rzynmuuuD3nEuc//fyVF3/8/JXdt1+6/IaSHlus5+NeSZ+5+dkXXv/5771wTiL9A8+9+Bch0M+fv0Mh7rHys1YGZg46AAAAAAAAHFW0baFWVNdK67/+tcc//L4vf/Pf3HLm0r3v+Pb5MxLVEuQnLl+eSf/o4t/t/sQLr7X0zhdLeteVv3sjp//i8sv/IJH+sy+9fOWWSy8/f+tLF79lgf7Rsxd/S+HteM4BAAAAAADgyGNxftuf/Yfb5D3/yLNn/+Dnnj7zmMS5hLWE+IlXXo30lld/uPvWv/vRRKC/sfuPX3pj98aLP9z9pxf/IdI7Xi7pbX/397vveO21lizSJdAlzn/xwstf/dilS//nbecu3aVF4mI/5V1WCgYAAAAAAIAjjvYlj3nnX/jyv3zPY9/8nzzv/MbvXN4N7/mF1yaC/PUmzI+//MPdE5cmjy/8aPf4uR/t/pOz/xCPe5FuoS5xriTv+fsvvfR1ifNfu/jS3Z5/rsXh+nMCAAAAAAAAOHLIe/7+z//Vb1ica6X1tz135Xl5zyXQ33zx70OQW5gfv/jDJs6PPffDSBLpN154vYn0H7/8w/CcKxQ+e88/fOHFP/XicPKeK7xdi8P15wQAAAAAAABwNNBibCdPHjv58MNvlff81i999ffe+42n/1ji3HPP/9GZy+E9D0Euj3n1mmdxfuK7rw8C/fzrb2gO+k0vlbD2LM61OJxC2//LF1/83z9+9sLvef9zhbf3pwYAAAAAAABwZNB+59pS7QN/8aVb5T3XnuRatf3mJ899x+Jc3vOYf25xfvmNKZFuoe504vxrIei92rvnnnthOHvPtTCcxbnOoz83AAAAAAAAgCODPOcW5x968vTvynseC8N9+4Urscd5FedveekHJbTd4rwe2zz0GvKuMHh93t+TQM+h7T9/4cqDmnsu7/knz174MJ5zAAAAAAAAgJ0y7/y2Lz7wMYW2e89zhbZ73rm3VpsS6Hum10OYW5xboHthuDb33Pue7+6wrRoAAAAAAAAcbbStmrznt33jyTskzrUw3C3fOvPUTz9/5cUc2j7jQe+SVnV30qJw3h+99543cV7nnv/3V668rT8nAAAAAAAAgCPDPffcc8O/+P3ff8snH3z8pl//2uMf9qrtEudatd3iPLZVmySFrStJoPci3cJc+6I7aVu1Nu/88iuvyHs+tjCctnXrzw0AAAAAAADgyCBx7oXhPviVv779I8+e/QPPO3doexbmftzmnHce9CzOlbTnuZLFub3n2vOcheEAAAAAAAAAxO7um/Kq7fKe33Lm0r0/c+bckxLneUs1L/rmx2Me9HkCXR70vDCcvee//fzFX9z98s6P7T62c2J3d+dN/ekBAAAAAAAAHAkU3v7Rf//Fmz/y9ad+VQvDffCJZz/7vqdOP6jQdnvPW0i79j2/8KPdGy+8vnuj90AfmYPei3N7z3/20vdPe9V2ec//+YVLvy3vOZ5zAAAAAAAAOPJ4W7Xbnnj2zvc88J//XV4YzqHtzWNekwW6veeac54Fep6Dnr3nXrVd4vyffe/5f6Vt1VgYDgAAAAAAAI40J0+dOqZV2+U9d2j7B5945k8091zbqr3z7MuveFG4GXFuD3rymPde9BDtr7wa4tze81vPv/RFh7b/2jPf+w3PPe/PDQAAAAAAAODIIM+5xPltf/YfbpP33KHtEudvf/bc7tteeOUH2k6tLQaX5qC3xeIuv955z18fUhXnec9zh7Z71XZ5zzX3vD83AAAAAAAAgKPByZPHfvOhh97x0Qce/oD2PPe2ajc/ee47Wrk9FoY7X1Zqj3nnmms+SV7B3au4ay/0QaS/HiHv5bXLsbWaxbkWhmve8/Pn75A4//3d3bf0pwUAAAAAAABwpPC88/f9fw998n1f/ua/ee83nv5jec8tzjX3PM8/zwJdXnUniXGLc6W3XH61CfS3X7oc3nOv2i7v+S+fe+lfy3uO5xwAAAAAAADg5Mljn3zw8Zt+/WuPf/hDT57+XYW23/zE6c9rWzWFtlugy4OuFdv70HYJcIvzlibCvAn0y0NouxeGc2i7vOfaVg3vOQAAAAAAABxtJuJcC8P98iNff/cHv/LXt//SX//N/yJxrlXbta2aFoZr4rwKdKcs0O0pV2qvWaiPLAz3K+de+MOPP3/xd7Rqu8Lb8Z4DAAAAAADAkUWrtueF4Yr3vKza/u6nzly0OHdoexbnJZV559lTHuHsk9fDo96J87wwnFZt98JwWrX9nt3dG/rzAwAAAAAAADgSeEu1WLX9G0/eYe/5u868/F2J85987uIbgzgftlfrPej9fPPsPffCcA5tt/f8n1+49NsW5/Ke7+7uINABAAAAAADgaBLe87Rqu7zn7/nupa/99PNXXrTn3ALdq7f3KV5/WXueT3vRPe/8Ha+9tivv+dSq7dV7HgvDTYQ54hwAAAAAAACONNpW7f2f/6vfkDj3qu32nueV2bP3PKfYC72Kcyet4C6vuVPe81ziXKu223uuheEQ5wAAAAAAAHA02d190z333HPDyW9+80ZtqybveV61XQvDve3SKz8IYX75cgj0tr953VpN6R+/9EaI8ybQXxlWbY/HkxTivHrPP3zhxT/Nq7bbe96fHgAAAAAAAMCRIBaGm4hzzT3/yNef+lV5z285c+lehbZ71fYszi3A41g95oMwf6N5zWPv8yrMxxaGi9D25y/+jsU53nMAAAAAAAA40njVdu157oXhfuJ73/+CQ9s159zCfAhflxCfTT9++YchzN/y6g8jWZxr3nleGM7e8zzvvD8vAAAAAAAAgCODQts/+eDjN+WF4eQ9V2j7zRe+f8niPMLZ27zyXpiX1yTO9xLoEudTC8M9f/F3Yr/zuigcIh0AAAAAAACOLNpW7Zcf+fq7f+2Z78XCcB/69pn/zd7zt70wzDsfwtdLivD1diwh7b1Az+LcAt17nueF4fpzAgAAAAAAADg67O6+6Xcfe+yEvOcR2v7Es3dKnHthOHnPtTCcxLm95xbpWayHEK8edIv0PPfc4lzh7XlhOIlzzz3vTw0AAAAAAADgyCBx/l//6Zf+ib3nH/3W6f/5l/7m7J+3Vdur91zifHoBuGGlds9HtxddyV50baeWxbkWh7P3XKHtnzx74cPeVq0/NwAAAAAAAIAjg1ZtlzjXqu0fevL078p7/vMXrjz489974ZxC0bP3PIv0Pg3z0l9vn73ppdeaQM/bqmnuubznHz178bfY8xwAAAAAAABgwm8+9NA7bvviAx9TaLtWbbf33KHt8p57v3Ol4xd/OJPGxLm+o+3UlCTQJc5/9tL3T7dt1c5e+D2FtuM5BwAAAAAAgCOPwtvlPf/gV/76doW2f/CJZ/5E3m0JaYnzt1+6/Ib3PM8C/cSF1+KxxXhLl19t4lxJHnQJ9PCe1z3PFdruueexrdpjOyfwngMAAAAAAMCRRduqae65FoZzaLsXhlNou8W5jhboN154vaUs0MNzPhHnOkqUO0mYS6B7WzUtDPcr5174Q889//QLL9zYnxcAAAAAAADAkeHkqVPHTj788Fs/+u+/eHNeGO6Wb515SnPPJawlzJ0k0N/54mtNnOtxFuKaZz61KNzL/9A85/aeyzP//vNX/m1eGE7e83t2d/GeAwAAAAAAwBFkd/dN2vP8vzt9+qYP/MWXbr3tG0/e8cEnnv3s+546/eAvfPvMd2/+9gtXLNDtQZcg75PD150szHX0a5p3nkPbNe/81587/6t3/O3fvkvec8Q5AAAAAAAAHF0mAt3ec4W3f+zpZ/9bhbdboMuDrgXiFJaupMfzkj/jFILcSV7zly5+6xcvvPzVFtp+/vwd4Tl/eOet8p73pwYAAAAAAABwdJgI9N0v7/zYf/Pkd2JrNa3eLg+6Qtx/7ukzj2kOuoS1j+99/sqTSnrux07yjOsz+ZiTwtrlOQ9xXr3nWhiuPyUAAAAAAACAo8dEoH/ywcdvygJd26t96PTZeyXSP/Dci3+hJHH9E9/7/hf8PD/2+0ryjuejHytpOzWt2P7Pvvf8v9Jcd3vP+1MCAAAAAAAAOHpUD7rEskLcNQdd3u2PPHv2DxTq/kunL/yRk5/rqAXedJQ3XI99nJckzH/53Ev/WovCyXOuPc/lPdfWbv0pAQAAAAAAABw96hz033zooXf8i++ev0Ui/aNnL/6W5qLL0x1brk2SH+sokS1Pu+aQx3HyPNLkeaTJY+1r3h/1uxLmGgyIPc/xngMAAAAAAAAMyIt98pvfvHH3wZ2btFicvelK2gItHz/6wMMf+M1vfefWnCTs8+Oc9Fs+SpRrtXYl/SertgMAAAAAAAAkJJS9F7rEs4S6POpKuw/txFFJW7FpvrrEfEuT7zhpuzal39/dfUuI/slv9iK8fw4AAAAAAAAAZnf3TRbpEtYS2BLcCkHXUc+9FZo+s3Py5LF77rnnBh2n0uR3+p8GAAAAAAAAgEWQuK5CvXnUU5IgV9rd3VF6k1L/EwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAsGF2d3fe1L8GAAAAV4d7KAAAwDWKbuKL3MgX/fxBWdf/iHX+z7r+CwCuPxbtPxb9PCzOInm8rnvAuv4HAAAAAAAAAAAAAGC57O7u3LD74M5Nu1/dec/uw2/+QKRHd34qXvvyzo/tPrZzQp/pv7cq4nwe3nnr7kM774jz0Hk56fnknPrvHIT2P7pG/9fDO7fG9eu/dP1rvO5Mu36fj47l+VuXWR7xW/rdKHv91yQpH/Sa8kX/t0XejygznbPLrJ33JI8eOfGh3UdP/FejSe85H/UdX9/kt/r/WDdxPX37y+dYymAp5Z1peanfH/t/vVbr2ibrgD1w7VxzO3V91bkObWMj5zp1jjofn6fq3iLJZeA6vUjaQH7E//R9iPvqeh79dzZJ6/ddRq73zsO+73D/4bLxtbk/XkHbPAxRD/vry/fPJd7XZup760Pq/WoL+lcAAIBrkrjBhig/dufuozfctfvIDf/D7leOnSzGSr2hr/FGGzd9/a/+/yvHbo/z+sqxT9V0u4yA/jtiUUN0ShjJCIv/uuHTcf36z2JobMS4jPP6q+Mfj/NRmeja9VznukRhGb8V5Ty53lLuJY+VHxZpSzLmlkErsxA+xz/RzvvRG+6ePP9fIz167HNDOv7Zmj4T+aj8VN12Xm6ofE0Y07kMdB2lvE9OlcGSyjsTean/dltz+49U638S6f3310UV6EVUxbmq3KNdKK8+Hc91rh7Q2NC5zuSn2lHUTdW9SR1s9XPyPD+eSXFdNfXvdd8dHpc6Xtrw7fH/S+4r5lEGNyVkp/rpk61cJnWo/84maQMKOjfllc439yGRl10f0sqq9sXuI1XWK87fRYn24utTGagscrno9ZFzXvj+6b7L9b38V8lLHZU/W1b2AAAA1wxxkw2DIwyTBybp/jBGbOhVT0H/vVXRBgzCyJvc7KdEl8TD5MY/YmAsShsIKOL8ZDGIj90b16//1PWv8bozcV5h5Bw7Vcvk3iJKqmgrQmQhg2qMEGBx7VH299U8vjteiwGC7TFAZ8R5ET86b+XRpM4ee2Ty/KmSjj0zpBseL+/FZ+6tZVsGPHR9GypjE+3L7e+RY19o9U+CZ4UDCYPgDTHpOnB/nEMRfp+wF2xTolc0cW4hWAbQ3C5OVdF0exPpG6qvkU9Z+JXzVH/ySKmDNzxW6+FeSdeUU//+SNLvtjqufmKt+VH6UA3uRP3R9UrUSsyWwZPJ+/13NkUnztXnazBK9xb1fQ+UvOz7j9aHqDxqH6nr0+BgvT+uOI8XIdqL+ow24Nzun/e2+/oShHP8j659ajAq/qf0s8qfyfv99wAAAGAfDCItjI/nqjFSbrAbEDFhRIVHrxkXNsZlgMoAuHMZwjH+R4aMR/7Lbz9SDbRTkSdLCqdflOLVD+NR16wykeH42SlhecjrF1Uc6trvi/8IgShDuxrXOo8l/M9BCS+NhfkQ6VAGboZBhWpYq95G2U3yKwzretRrVcAUAXyqXaMHoZSfG/K+1vanSBGJKwk5nauEZxko0fmtoB6WwY4pMXlfyqvPxeuqHxvIk8ysqIqy0zm6r9Kg0sbr64xALwN+KkedY6qH81IThqq3NXnAqf/s2Pfif9SOVW/WJ9AjrF6Dh7WfLm3MYvDkvIindRF9SKtDKfKmlI8F5Zg4dz8y3Yc0ARrX96n4vTwFadPtpQxo3VrbigYS7q1lonM/FW19CdFh8T8e4I7BCg3qxf3jqTLIpz5lewZnAAAArinCUI8bbNzEX62GiQzLT6/SgzePEjanm34YfZ9rN/1yXg9Uw0je7VsPE4I9JdCbt6v9j4ywO1chjPZDNXrvqkahyuRSNbCKGFH+LKFMilcy8lnXKyNfXr7PtVDZTQqeKcO6ibNicNrTa09jeSyxLpHQJ71+X/p8EurpWpXnVaQvIzphvwwRLFG2Fgn2hirs9hPL8Hj1FO9Xq/sSKsqbKvQmIlhtfwl17LBE/hTBIdF7Vy1PtdE3dku7UJ9QBq/mhO+ug6kID+VdDHy1QSS1L6VSF2eTRaLau66ptvmoCyqXq323vp8G19w/rjg/iuhVpJPaVBOxp0KwbVigR1t2GHYZ3JOg9qBvzfOWnMfj/YfSIHT9ed0zhjyvg9kHvSctgybQLZrj3KNchgEth7ofYgCnCfTpup4GlCdtdYNlDwAAcE2TvKgyOiwGi0C3V2qNhnoT6INwqDf9ODd5NGRw1DDOHS1KdKBzC0PKhv+0QL9UDbFNC/S7q0ElIaL06pSBtYTwwWK4hkBX2afoiZq/mxTo9kg6BLsYm/U87V2MulGFyfEyB7d4xsucy7g2iboQ9tVb1ryO5TfKAJDm8xYDew3zdjNxnTEgpXOwyGlCZ2WhoiUiQZ7Eli8lCqGcw13K9/47myBE5tRAQgwiqY+qbWLSJ7gMNzioMCUGBy+66t/dpf4p5fnlU3PMPe/Z9VvXVgck5QFV3d/jN+L9eF6iQvT/a6rLta+6K9VdlU9tk5P6tUGRFn18FudD/al9SLQz5fGpcr66jtSHlPIrfUjpf9QePZCi36jedYl3fW/yPxv2pBeBrvKv0w7KvUz3TbWV54p3O66lhOgfcPBvVqDnthn1AIEOAABwUDqBbq+URPA2CHQZRBIqySAPI6CIyDpPtv+N/TAi0OUtkdFVBLryZOMCPYw/C/RSNmFgyWNVjKsQeAf0+IaBVa69CoP4v0Gg6zxWbODPo3hOp+aKyoslQ7PW0TCSixAo1yCP0SfKFIBJ/dGxGI/yTt9Z8tMRGfFdeyr1m2VAKi3E15/PqghD11M6mgCoobYua53Tkg3+YsRHvtZBizwoMDmXSdn339kEUUcd2l6EeBlEK2VXBxRrvdXnDig4Dkv1XA4r4ntubhkEmU46zzKtqCysFmUv8R11PAv0+6sor4Kxfrf/veF3hwXiijd/5UKxCfTShzwz1S51ThusRyVCqA/BbvcS5W+p72WAoywgGQN1+k5Neq58LV5ilVFto9Fv1HoY11ymX3nKzBrvmZkm0O3VLtfsOvVGG1DQtdQIh4PUkSbQI7+i75JAVz7U/Jj0twh0AACAg1HnwMr4k7FigS6jfXMCPURWEyxZoGdxJoO8hLUe4PymBHoxMCXQ7Wl4IH57jUItMxi9MwL9jcEYrIJSRtIBDCyRPOg1j+P/JFY3KtA14FCF2e3VKNY5aWBG5aNUwv1LeHoxqmUY63xVX8M7vDNs+1M8aNXIjrLOkRk21MvvVU96f06rIq5V51kGIjSIIG9eFaHy8Es8LGdKg4lBnag74TVUfbKw0n8Xw32NebAXpSyjjkqsPlVSePtdH9Q3lD5L5VsEx4EGrA5DlKPy1SLdnvQQbBZ7J6a3RfNAUvO2R3+XBXoZLNH7Hnga+w2/rv/L4vwQg3f7JQ30ZIGustqoQK9lUUK9y7nUgajah5TBHq9FURbFzH1I6z8UpTW5xlJOZUX0oU/S76nu6TfrNascT2xsBfM6UKT72hyBrvtb9C/Kj7sOeq4IdAAAgBWy5R70NDc2hHMyMrSombwiB5v7NyXQBw96Mfi3xoMeBk8W6MqD5PmZnPecsN79GOazHvT4P3vQi3dlEwI9yqYa/kOIpoz/Ej7r+a0O5VUdlihR3Slzlsv+9kp6Xe+HN62KJntkSz7m326LKPXntCpC2M2E8se5uKw1EHX7Qer4POqgnLy7FoRq84+U/5agORGhuv33NkEpYwmiqJupjjo03+IjRPvdkY9DZMmh82oRQrRYpFuo5/rnx/l1tbFS9hqMUp1UfVRb17FMZ9H7+lz+bv7d/Lr/u17/fvqBw1D7qirQY/Bk4wI92pTyIkR31OnUzuOxxHVdTG+nLPDm/NtPH1LKS/dM9ZWD6PdAX7ShzQxwFYGePOhtYGtqkFt9i863RJ0oD3SdC/T1UwK95IUFeh1AR6ADAAAcmDA6tlmgT4e1ZrEqA0OrrQ8rmy8gqKshe60JdHt8LSglVA5sDEaeFe/HmEBf+xz0Jm6aZ6YJFotVGZvFw6s8OqD4qB7ramA3471GEBzXvsdaZGstdT7EhK95qPcqD12z0n1xruEtLSK9/439EP+j/FX7Gq5fAlf/MUTNbCDMfx5xrkM9UPnrXDUHWG2jeu1a/XgmPmfRVUV6/5vbRIic6O9qfS9zzR01U67HA5D63JoHHPbDHIG+sRD3qTpeQv/Vr6t+v1rzdujfDtpv6j+iPTryyoNHHjyd1E+J5A2UVxHoyYM+RA64/fhe4jZfpoq5zezznJtAH6LdEOgAAADLongFwpDZToFejAiHs9rbV8RU8a4OockLeP32FOglL7ZgkbhmrNu41HlZUCoviliRkaRyLB7WfYvWJNBruHdnwOo81ihypoRqEZA2rt8oRl8IsENvPRZ1LLxrUe72gnkQQPOAY9Cj5ufqvZDFqC7XXuYY+7prXayDEtU73H9/P4RAt3ApYal313K3QC/etCrO11nuPZEXxRtcQ5Sn8qOUTwhatXvPK466K7FUQsLX3G8dlLhWtd3m7U0CXc89+Lil11L7qq0Q6CN13BE4Huwq/eUS+rbaVtMUnDxQpHtSuR+tO5Kj9CVTHnS1l0dqvUr3zzhP3U+mB2T3ea5NoONBBwAAWD5b6UEfxJPDWOWVUAirDABvk2WxquPCoclhGM8T6FvhQZfh14x1GVZl66JBUNrIKqLkAGV1FQ/62kPcoy6GZ6ou6lTOywZlmWOsvCnexAOL5ih7Xdvg/VEd03/YiL8rR2Xs12g9LHFeISyaZ1hlUiMlqqfroF4/iRcb1DMeLw12TfK7zMHdd/1ZFUWch8jQ3Oy70zzaIn5cNiVMv15HXMv9RZTUecAbar+LEGVuT+SYQLcHfQvKZYytE+hTXt2oC+4rVX+mpov031+E6Bd8/ygiPQ92DVEvdeC0//6qmCPQ1afV+2f0J96aUuespHaz0C4IeNABAABWyFZ60KcFejUwbEhIrITh4fDk6vmt4aD79DCGYbz1Aj3Op3iPy7xrGV11O65mdCpvZByV8ORFoggGgV7zcUqgrz/EvYmu8O56IGIQZirfJYjl5rGOAYEmiPU/s556fWYJ/7lf6tx7XX8VPClSROV/QKO3XnNeNEsiyl61B6INHPC3l00aSJDwU9nUqJFq+Os6LETKZ9xHKA1rFCzQFjbBVDh2RAR0At192hZENcxj6wS663gZ4HMdl2h8LPrOBQZx96L1IdGHxv1TdXDEUz+po/u8Jy2DEYF+qg1ol6Puc2pTdfpY9K11oLdGDu2jv8ODDgAAsEK20oMeKxM34zwb36eKcJWojPcl2CUwamhy9Zztw0NyjQn0YlzqXJsxFFEFxSOSBdwCoZtJoCtv+xD39XvQB2M3rz2gPFCI5mckmPvvHIYwMiXCB2H0assDT5tYIOxzGcT/lTKuA1BV9ER+yIt+sDwIMTi0qxr22jxqZUeELRG0w0BC1PP7yzmG8a/Hn/Z5DvUlhEiKtDlW1qZ49GDRButib4GeBh0R6PsiDdqoT1P7qeekaIRJ+4l+frZOSNj3r+2XuI+UQT79n+5Dr5Y6GIN8ZX2UNbarEYF+X4o4u7/UMd3bPb2n3vNKXn0m17f+tzN40AEAAFbINeBBr4Z3nJ8MijvrHGWFFn62GmDT3t8aFtr/dmbrBXrxACWBXj0yxcNcwzcjvF2CRN7FYhTq2oe5j3sanlvnQdf/lfOp19UE5H2LTmHYL/U/JTCUByXc217YKjDWmgctiiAGaFy+Q5SI2sYBzifqQ4sWyHUmxFQZ/Fmjp28vkmfc4lznWcLXk2d8WKnbfYXzKoT6XesUhwdhb4GOB31RimiMQShFoKjuqP/Q+aht13vH3veFRQmvffxnuxdVgRr9aOmzl/yfezEq0Mu9U3lQpoKpzRRh7X7PUUplALwuwtn/dgYPOgAAwArZOg+6hPN8gV7CnGWYD+JShrvEi4xDGej7mo++9QK9D3GPubgSWFpAL4wi75utfFGZLTz3cdaDPiXQ1+9Bt7HfvOc3VM/xEB3Qf+ewTNejEEVDSHm0gUlerjMPYsBMBn8L01XdV9isylee4QMJ6RB4wwJ0JeokvIoRDv6pg+6HvAo6wVNC8KPtV3FRz3OqDZfIGrV/5VURt/qdq7SBTdIEuoUOHvRDEaKxzeVv0ScehCqDOysQy2XdjOiv01QMr4cwyYM1CtU5Al3nVQS6w9h1Dy39wb21zuXzLp70PdoOHnQAAIAVsvUe9BLaKyNLBld4EONzLbRQRmx8RsaBvYISdLfX+cOjXuStF+jTHvTHB6NpYnzF+zIKo9wsuFRuM3tZz7t+MetBD0Ntcx708JzOGLrDnOIVGHxpgEreauWz8qAasxIek3xcZx7YozoYvho4UNm+2uqlyr9uI7Zn+db34nNDdILauUTsMC1kA+18HnUg4fZSD1u9ljd0mNNb22V4L5UPZcur/trKgmDbLG7xoC+VEI1xT5BYbKJT94PBm72CcPOWB3lg0RFfKxpYnMdVBHrZqcHtp7QbDfKqH6hRNZFvZYrIHv0/HnQAAIAVsnUe9PAoTc1Btwe9CHSdk4wDJZ1beAFC1Olzg2dQBoJETjVuZ/5n2wX6jAe9eTVujTJTChEXn8vGsfKhepzfvOdevNvnQZ+cb5kjqfPw+ZyKa6xiq//OYUkDVBKEj9T/VH0ronDdAl2iTXWz1O0sVCU81QZKXlik7y3Qyz7bTfCrroQhPgjALRCxIbR1zV69PfqeiGKw2C6eP71Xp29Mfbf0YXl7ujL4kNcR2FBb3ou9BToe9EVJAl1tRn1nne6Qoi9WJdDLPaQK3SbQNcj36bXnwahAr9FlKhMLdLWbyK8ovzpAGQL7sdY3DP3M1H2kCXQ86AAAAMvnmvOgF6MhzieJdIW4OiT21Xrcc6GvrRfo8zzotTzCuC/zlTVAIUNMglIGUvUaVeN+j7Kb9aBPCfT1e9CnF2yrhl46n1WEpw7iToNBD1Tjsi68d3yhrYeWSZTvTH5cvV5nWh3RNZRBOIezvtqM6BKRsfbry0Q7ntparQ0kSKCXgQRdg/qGkQGJuM4yAOE+QO1GgvGzUb9XJMwOy94CHQ/6onQCvQzWWqC7HqxgGkfNA/WjuofUQb4aWr+P6VbLZL5Ad1TQ9Dz8YaC3zUcfBrhK1EHMR+/7iCbQld940AEAAJbL1nnQJT6zQJ/yoNfQ9SSci0FSPRjF6ybjoqzaa8NsZE/krRfo8zzoSTSH0V6iDT5djbHqCa8GlsOh5xj3xYMS116/VwXgJj3o5TpKORSDr3i/wvBesUBvg0E3lLnvEkyqi2vMg0wxgGfK5/7Io/1ESEQdV57KKI/6pLZUPIuu41sgXNtAQohUt/ts7E/O/SpCr3re657p8V31F8P0iBXUncPSBHq7djzohyHuBSXqRH25+kDdy4aBmhWtsxDlUwS6BgaGQb5yH71rqwR6vD7dFtIgve+BHtgoUwNUB7t+Ag86AADACrmGPOg6vxmBHt8pIqt60cOT7DDD6j05Fgv1ZAM3jGN737dRoPcedD3vykPexFJ+sap99ZA2cVu86Gk++sz/7B3ivhkP+iDQq5d3XQJdYigJdOWJ83yNeZBJbTOF30dbUB2/O8r1agJ98I6pfmcRNTUfdZNEPZ4WFTUaxAMSqqN7l31ty8WTVwbnLEpKX7ZGobhf8KAvl06g6z42CPSoGysU6EP/a4GufFDd3X6BPt1POCTefU1bdDXbAdFmEegAAACr4drwoDvEfY5Al/FajBKvbC4jIwnVKmZ0rf3CWdsq0PfyoPfXPyyUVQ3l+N50FMGIkZg86MrbXqCv34OOQJ8iDTypLpyq7eCZIV+Ox3oM/fdMtKUiWNSOqtFt0Vrr04bqeKYY+xEJorZbI2Y8zSDafNtabR7NC13aQB3QiLzSQNVnxur/ptlboONBPwgbEeglEqsI9BbB0QbStl+gz94LdQ+oYjvqY7nvqo3lQW71T8P0GQQ6AADAsrg2POjNwBgX6DIwdB1NpMswCWNhIvJC7BUvugRgXfBmxCjZLoHee9AtqPTe2KI9TWiGoSRRIgNfxtUj1UCdzbcZD/pUiPumPegOcR/mXK9UoOfBoBu2I8TdAy+qizFYEG3BobuawrCnBzy+X9p2HbDqQuS3ZBuyGEiI8rWRH+epOrlXiO3UXPQWSTKE8+v7yifl1+eiHLfgWjN7C3Q86IuSPOiegz4I9M2EuN9v73P/nVVxIIGuAbJcD8tAmdqP7p+DPRDe8iLS438s0PGgAwAALJet86DLUJhZxX3vEPf4nowGLzRVBLc8cWk+ehUlEjwSBJ5Dt60Cvfeg25s7T6Drmtp89Mi3wZNuQ1HXnq6piJq5Ie6b8aBPLYoW5T4MGKxeoA/GdakzZZG4NeZBJsp0MJpVTqqjqtMynB8oxn9ZwGlGsKo9DOJJxrm9YcrPoW1v6NoyZaCoDSSUNlsGEtQG7rSwmop+0TVbKLjt63oGwaD2by+q8q1Mc9kiodsEur3/eNAPRdSFMtBjgV7nUleBrrqxCoFe8sDtM4eHb98icSMCPb7nuhjX0qbVqB16PvqwEn5ti3jQAQAAVsS14UHfO8TdNKMhRLc9ji1k2d+PcNkwSLZZoM/zoKs8xgS6PRrNi9gMpler0V+2XksiPX5nGJzpBfr6PegxuKLrjvPw+ZwKY6+eT/+dw5Lqfw2LTsa1p0assf5nZgTcEF1QBFwetNHnap0ogzWeH5q/c8P0WgYj9WhdTAnrYVBNbVSDDxIFKvfSB+l9XY+uUUnf6ZPKcSqKpg1mlPasOj09wDWzGvy6aeXrQRg86IeiE+iz4lLvdZEYy6D011F/1Wfk+00ZDFt3HhxAoJvS37QB8nQ9rU+M62ltd2pQGIEOAACwFLbOgz46B70ZGHsKdFG8a/2CUzbWwtiIOYHN47atAr33oOu5RfOIsIrBCRln9oKUaxrCfB2yXT2J8Z0ZgT4V4r5+D3oIMYniMAafqQZfWYk7nfcyqYM0ygMJQuWz8kD5oed3hQG6xjwYo9RpCyEPukTdGLzhug6VvwdqYrpAG3iQUFHbfqR53aug35RQHcT5Tt1JYUqc6ny1iJ1C+z8RZRCRL7FQ1a3lOJJC6J7QlAAJdF/3q7U+lbUYHKa7oevO7C3Q8aAvShLon6v5qDZShKkH+VYi0KPfuqtG3ZQ8KNNRSh1edx4cRqDr+6VP1FQx3YNqv+j7w/Gy5WEMbOZF4vRfCHQAAIClcD150E3zJDcjJb6v65LBVrzo+o8SnqeF5eQBuLcaddsh0Kc86GEY7elBz7QogiJKHqtGo/Lg/vjd6kkqocVx7VviQW/GvsKbs6FbvP8rMHRbHpT/kdfyqfaf4S2aGLtrzIN5tIG0cm4SnqobpTyrEGoCvQw8ecFEDVAVT3Ix1u9chUhZlBbBorpYBkjyQIIGICT41HbvKn2B6ulVkn6ntPnP1HzSNVdvdM2rOvDUn88m2Fug40FflKj7ce+INvHIVL+n89nHYoMHoa6dITGre9QwsOj/XGN9O6xAF1HX2kBXa0tqm0rFNhjun7cP/+X7LAIdAADgUFwbHvS48ev89ifQ9RvNixgC9NSuvejFy1EM/3Ldd9bP2OuyHQJ9QQ96Jq6/XXsYaVXwHqvz8SU8qydy8LT3An39HnT931Be0+Hm1fM7851DekLrf95V/8eDODKuld8nI5/WmAfzqNEO1TPsQZfkHZyUd3wuvIiTsh/aTxZO+2o/6yDqaBYS04K69kNxzrpG1c+xNP1eRIm0Ab38W/Ki67fallH9+WyCJtA9UIEH/VC0ut8GphyeHfVEA0Cjg1OH7kMizDuuV32H6+0w0HkVQbxMliLQp6Nb6vocUS+HKQOlz1RffWf8ZhsUQaADAAAcmuvRg24GsT/lUbFX9lR53R64LRPoUx70KtD360G3d3JY1drhhzU0uhr+EQ4aAwHK2zoFYIMe9Dhnn28Y2DrnGqaq6y8idFk0Y3YQRq/W47ByvPJgj7xeF8WrpfIK8VGN7jaAUbZcK6HwdWs1i97WdjRI85ltCNkXs9cTZZ1FdRLXC6f+Nyz4NbARedCfzybAg75cWnsu/bkEeT2n4yUqJu4pZSpI/92DUgZDVY/7KRXHh50SRgYFVsUyBLopv6V7UdyL1X+UvCy/VcrY0S0IdAAAgOWxdR708Cj1q7g3A2MhgS7i/Mv1WexL7MqQlHCxSJdIsJd9OwT6ITzoIgxHi/QQm81DKSOyeBKblzWM6l6gr9+Dbk9iKfvqKW7h3EWELqFM5DFr+dOmAji8WgM4x0sotLxxah9Xyet1UITAiWHLtRwVUYzw8A7Wa1J9r6I38lBtqE4TmBjvh/QYLoPa76RtAeM6lFzeEqhVELTr3GfK34vvVuEU72te8EJ9yKrYW6DHuSPQFyAEZenvR+ZP18EZtaEl5GP8l8okrj8GuNXGygBRuc+UQU69/+DyV46fxzIFuhimS7VBU+Wjy1l9kAZ/794d2jECHQAA4LBczx500VaatbApQlWGukMRZcBUb0udq1s+c+ci/7NMZj3oYazv24NuZkVoDfks16drrqHknl9YBbry3sblEozZ/RLXNTv3UfXRK3GX0PxDlksII4uimOMc/2Pva9m+zOJcebgNgtZREeWcq4cwxFz12Ok6Judcwm0/U+u2y3RYxXoB43yVlMEj9TGR9xblRUSUwRiJVV3H3UPy83nHPun1EBbVsxflK5FRp3hE2W52oby5Aj0GGhDoCxADb2qzZW50FahR/1XuqmManCkDj4e8pxVxHkK4Dga0/1E9Ux2+M96vfUj//VWxdIE+TEXxmhYqW9XRS/WogQndS3RUGx76IwQ6AADAwdg6D3oLS29zaOuofZzf4gJ9dj6djIlqSISAkUH5SDU2bGDJ2NisQD+kB91Efg5z8VNEQly3fr8MSpSjjLjNhbhnr3+pkzJ0a9jzpA4Uz3YZODhE2ZSBAAmiqGO6XuVFGQgoRmzbii8+vyEBl5kRc4MoGMRcqd8abEvhtlG3S92RoV3qzkavJ9VJCWnlvc5TdXEYHFId0LVGfUjJr+XjvNeKsKiDUylCIg92KU/32ZaWTStTD7zgQT8UIdDdRsrq4vW8Os/2Eu5rw8Bn9NMSwb6nDAMrG2hrSxfofb9TIs7SYETUV99P6iAYAh0AAOBQXO8edFPEn72LzaNmYSrjqgrBLRHoh5iD3jMIgDbo4WgBGVm65nrd1YOewzPXKAqmBEsLq/TASRwVslrC7wexGR7QeZ7QeF2/68/p96cHLKqw8GDN8bKN0Brr/H5o1xGeuzYv1OLDbbZ4jacjAu6Pa03evLF8WgczgrQIiEE4R4TLcvucKiDTQECUdQn3DxE3OZc11vHMuPjBg34YptpIua9pkK8OQnbRJHWgb68+pL1e+pDyWP3OMADgKCQPdj4Q9W1DU0ni/JYo0E1cfwyARb+p+2Me2PX9s/Y5Gkyd5DECHQAA4GBsnQc9DPjlzUHPxLV43muIGIfq9UI1jJrNCvQledBFiDJ7lYqw83z7fN3V87FBD3oxgAcRXc71/lJOYfzr/PS8LMCk69E5DsJzJk+mRGEZ+Egh4vF7EkLeWq3uWxzG9cxvbQtpUE31tBrJcS0q15pfUbYO6439n9dZlmNM1cNSBhZPOlcJmxZ63n/3oJS8yqH0kTcSkHWV/jdvbBu9vQU6HvSDMi2iox+t5xZ5W4RqGQC9M/oZ96lz6l2UU0yV0sDSCU3Bub1es8O9n6nX7b7p5EFE8DJYlUAXbT56WwMjrrkf6NXjRxDoAAAAh+CoeNBFm+Mchpm8dW3+q40LX78GBTYr0JcwBz0TRqbKungucxRBMqzC2NyYQM/MhpA2Y3Dw9DscOso0BPiUdziMVQugtqJ9zKN0WLuMSeWBwsA1X7Xs73tAA3ZdxHU2D3TLF3uy7M0q+aR6pGvaUDlmisiZWQ/C516mFiy5zVVRkbani/wqYiVE1uR81ti/ZfYW6HjQD0PkbfOiKx+nBmSrp7sO9IU3fXy+eBPnHtxz3Z2ac32sbN+ZRf+D61sYLrNSga5+R/lQ5qOrzD3Qm++fSgh0AACAw7B1HnQZAUucg56J37YXpAxKeGu1XqBfVx70THy/lLcEiozpJNK3SKDbi+7BlCGscjCIo/xqyHuEpU8M0xDjkzJWirr7Zm2jVgzrkqcOSR1+q+RFEedVDPXns02EET5EmdiL1w22RN25P9rRhkRST/PAtXJwBEPUw7JK/wHr9TxKPbL4tVhpIv1Q/clhmYrumBHo16wHfYhy2aBAF1HfWrRGnI8HJd0+7os8z+sexPXU/sN9yOA19+/o/qjrrL8VZaZ71Z2lXZbBwv581sF8gV4jbA4j0D3I67UdSptSnmYPOgIdAADgsAwCPW7gNu6LYb8JgW4PejH6JmIxQl8fr+dXVuE9oEEd4dMS6RE1EALn7lEvenltgwI9RKWMH4uuZ+K5jCIZkPsU6Nmb3F6LKAKJgRgAubcboFA+yPi8O/JZ/7UhUdCMweJxPTmI9Bis0DnLMJRol+Cuq36rPFVvdW0yplWH9Fp8Vwa0jFUN9Nhg1W+U691yIdRThET1DpZrygK9RgVYNOwsde/ng1IGTyK03RERKTT4hliFvv/OYSmDcl500J7UEGd1kGdyPjqvkbayamY86KUs3RcpHP8aEOhv1r7jikqpg17H63SR6p3epEDPUyoGwZo9/Wo36kPUDyqv1Yfc1fqQ0o9M+hANKMV3dT/S53VPUjSGB441qPnpLM431d6aQC/tzAPcun/qesvA+wEFuhgiCjwIMOZFj/9jmzUAAICD0uZolhv4IFAt0CUE1i3Qw1h1KLIMv2ZQHUqgmzAyiidZW+SMCZxHwvg45P8clGJgxXn5nIqxvqBAH6MIlokROcwDlmhN/7MlAt2DKQ4vDY9Ni6qQUVgHbsLTmJIHc2yU+vU61zzCvut88xKVUEJcLYJk4G5ArC3K4HX1QMvUIJMM5jIFQJ+ZtPH+++smyjMiIiR2oi2XUOMVi7km0kqfovquulPre9SNsje26pn6hQO2q4MwK9CjbrscdY7Fw7/1Ar2VqadZONppJWW6X4pYrQOyxQvurcK63SxG+5Daj/TvRV/iwUFF8Oj3pjzn66xDPXHNeRHMFtof5y2xfiiBLqItT0+XUl5kka6+9jMIdAAAgAOydSHu9qBbjC1xDnqmhJaGF92eBov0suf2RgX6SIh7hGLW8jiMQC8CtCyipOsvXqMqmMKImwiYzYe4Z0LItEGF8BrnEGmJgjy4Mi/pMxYPaa/38bmn20wYyIMXyyHjah+6TkXAyEs4LIJW6stGBx2acCjRDCqHV2u9GwYSVuDp7zx+yisNfNV2NanvOp+67sC6xdWMQB/yxiJn8KDXAYT+NzZN6atm5qDfX/N5oyHumShbnWsbqInzq97lNt2l7zNy0vuXSl9cB5X0OzG1pg6abkH5lEGJ1YS4Z6LuOopnmIKU2vUNbLMGAABwUAaPtYzB8CxqTmuZ7yyDes3ipRhSYWCcrIa75mV+tpyfRPtyFnUKQ8aGcfGiysiQd63OS4zogUP/z0Go3n0Nmtzb5lrHOcozMhES1dPbf29R4re8qn0x5Dz//GTzCK3Z6AzxOSImo14oX6YXe0th6/aa996uMMAfqfl4qtWjHI2w5ms8LJFHjoRo6ylYcGh6ynFNhziZF8/rf2PdNOFgL7brdT9QMlL2h6H87072opZ2lUOTUz+3jHa1X+aeW4vw2Ny57ZcuEkn5qnU95FEvA7xLEIOLMq8ORb9pkV4ErAa2+rD1+X2Iw+HLoOYwgL2k/ngZtPvaMGf+M7Vsirdfry8poibq5fSgqfJS/XGZWrOk/wEAADhyNOFTQizvDGPLXgGLwTUKmBiZLwbprdXDK2NKC9J8YtmicfBenZBnT/9zsgibangt6X8WpQyahMF+ey2TYbBkCMUdNUIXoRisMd/d1357W2xtiQMByyAMz4h6kODU+cW84tvreX8q0jBv9O4YaPB80iIKS912PbI4L8Ln0Hm5bpr3NdpKLcN8jbkMN1SPM1F+U6HGtV6rLNTWVjQQGIMZxduXhbDqjepMGcSwAF5Su9ovc8+tb/O1DLelLWa6PkT5WvoQnXsVr/13NkXkYUQO1YEtn3PJ708V0e1556kP0etl8GRoXx782pL2ZaJO6Rp9X4uy8HXWtrakdhb11v1xmec/1F29tkVlDwAAcE0xZSTK2CheBhurJRx63Uar/tPn1KclGtGj/zN9/Uv5n0Vp55XLQ2nJ5TH3f/TalgmCqXrR11clGaUSM8UA1boJtzbhp/fy9Q3lu9T8XCctP3Id7tvvcI1bcX1xLn07my6LlZznVF7N///RyI1Vc9Vz2/I6GufW96Fdufbf2RQtr3Mf4vx2/yHBOdaH9H1kur5tK5t9lMlSznduXm5h2QMAAMABqMbTvg2HRT9vFv3eop8/DOv6n0XZ1vOaRzMWr7Hzvl5ZtBwW/Tysn0XLaNHPbxIJS/ch/XvbyqL3qUU/bxb93qKfBwAAgC1i0Rv5op83i37noP8DAHC9smifuOjnYXEWyePD3NcW+d5h/gcAAAAArhMwCgHgMNCHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAbJT/H3UnhqARECPXAAAAAElFTkSuQmCC", + "encoding": "base64" + }, + "redirectURL": "", + "headersSize": 231, + "bodySize": 53598, + "_transferSize": 53829, + "_error": null, + "_fetchedViaServiceWorker": false + }, + "serverIPAddress": "127.0.0.1", + "startedDateTime": "2026-02-05T19:27:26.116Z", + "time": 5.577999996603467, + "timings": { + "blocked": 2.3009999931007625, + "dns": -1, + "ssl": -1, + "connect": -1, + "send": 0.06999999999999995, + "wait": 2.009999998453073, + "receive": 1.197000005049631, + "_blocked_queueing": 1.7419999931007624, + "_workerStart": -1, + "_workerReady": -1, + "_workerFetchStart": -1, + "_workerRespondWithSettled": -1 + } + }, + { + "_connectionId": "382392", + "_initiator": { + "type": "parser", + "url": "http://localhost:8000/company/calendar", + "lineNumber": 6 + }, + "_priority": "High", + "_resourceType": "script", + "cache": {}, + "connection": "443", + "pageref": "page_1", + "request": { + "method": "GET", + "url": "https://cdn.tailwindcss.com/3.4.17", + "httpVersion": "http/2.0", + "headers": [ + { + "name": ":authority", + "value": "cdn.tailwindcss.com" + }, + { + "name": ":method", + "value": "GET" + }, + { + "name": ":path", + "value": "/3.4.17" + }, + { + "name": ":scheme", + "value": "https" + }, + { + "name": "accept", + "value": "*/*" + }, + { + "name": "accept-encoding", + "value": "gzip, deflate, br, zstd" + }, + { + "name": "accept-language", + "value": "de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7" + }, + { + "name": "cache-control", + "value": "no-cache" + }, + { + "name": "pragma", + "value": "no-cache" + }, + { + "name": "priority", + "value": "u=1" + }, + { + "name": "referer", + "value": "http://localhost:8000/" + }, + { + "name": "sec-ch-ua", + "value": "\"Not(A:Brand\";v=\"8\", \"Chromium\";v=\"144\", \"Google Chrome\";v=\"144\"" + }, + { + "name": "sec-ch-ua-mobile", + "value": "?1" + }, + { + "name": "sec-ch-ua-platform", + "value": "\"Android\"" + }, + { + "name": "sec-fetch-dest", + "value": "script" + }, + { + "name": "sec-fetch-mode", + "value": "no-cors" + }, + { + "name": "sec-fetch-site", + "value": "cross-site" + }, + { + "name": "sec-fetch-storage-access", + "value": "active" + }, + { + "name": "user-agent", + "value": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Mobile Safari/537.36" + } + ], + "queryString": [], + "cookies": [], + "headersSize": -1, + "bodySize": 0 + }, + "response": { + "status": 200, + "statusText": "", + "httpVersion": "http/2.0", + "headers": [ + { + "name": "age", + "value": "2824082" + }, + { + "name": "cache-control", + "value": "max-age=31536000" + }, + { + "name": "cf-cache-status", + "value": "HIT" + }, + { + "name": "cf-ray", + "value": "9c94ce1cdbcd3d4d-FRA" + }, + { + "name": "content-encoding", + "value": "br" + }, + { + "name": "content-type", + "value": "text/javascript" + }, + { + "name": "date", + "value": "Thu, 05 Feb 2026 19:27:26 GMT" + }, + { + "name": "last-modified", + "value": "Sun, 04 Jan 2026 02:59:23 GMT" + }, + { + "name": "nel", + "value": "{\"report_to\":\"cf-nel\",\"success_fraction\":0.0,\"max_age\":604800}" + }, + { + "name": "report-to", + "value": "{\"group\":\"cf-nel\",\"max_age\":604800,\"endpoints\":[{\"url\":\"https://a.nel.cloudflare.com/report/v4?s=MT%2FNDFf%2FWfmJJB%2F6Z%2FuncA4lE68pDVWA3lsKyn90%2FRuYiSEElGDaCtvhMjmVV8ijNlwlPgKHnIAJe4zyEZjLP1FFSNRJ%2B7p2n0HW3meW48ppKn6%2FNVvJONxy6ScMhw%3D%3D\"}]}" + }, + { + "name": "server", + "value": "cloudflare" + }, + { + "name": "strict-transport-security", + "value": "max-age=63072000" + }, + { + "name": "vary", + "value": "accept-encoding" + }, + { + "name": "x-vercel-cache", + "value": "MISS" + }, + { + "name": "x-vercel-id", + "value": "fra1::iad1::gv5lr-1767495562922-401ec0c56245" + } + ], + "cookies": [], + "content": { + "size": 407279, + "mimeType": "text/javascript", + "text": "(()=>{var qv=Object.create;var Hi=Object.defineProperty;var $v=Object.getOwnPropertyDescriptor;var Lv=Object.getOwnPropertyNames;var Mv=Object.getPrototypeOf,Nv=Object.prototype.hasOwnProperty;var df=r=>Hi(r,\"__esModule\",{value:!0});var hf=r=>{if(typeof require!=\"undefined\")return require(r);throw new Error('Dynamic require of \"'+r+'\" is not supported')};var P=(r,e)=>()=>(r&&(e=r(r=0)),e);var x=(r,e)=>()=>(e||r((e={exports:{}}).exports,e),e.exports),Ge=(r,e)=>{df(r);for(var t in e)Hi(r,t,{get:e[t],enumerable:!0})},Bv=(r,e,t)=>{if(e&&typeof e==\"object\"||typeof e==\"function\")for(let i of Lv(e))!Nv.call(r,i)&&i!==\"default\"&&Hi(r,i,{get:()=>e[i],enumerable:!(t=$v(e,i))||t.enumerable});return r},pe=r=>Bv(df(Hi(r!=null?qv(Mv(r)):{},\"default\",r&&r.__esModule&&\"default\"in r?{get:()=>r.default,enumerable:!0}:{value:r,enumerable:!0})),r);var m,u=P(()=>{m={platform:\"\",env:{},versions:{node:\"14.17.6\"}}});var Fv,be,ft=P(()=>{u();Fv=0,be={readFileSync:r=>self[r]||\"\",statSync:()=>({mtimeMs:Fv++}),promises:{readFile:r=>Promise.resolve(self[r]||\"\")}}});var Fs=x((oP,gf)=>{u();\"use strict\";var mf=class{constructor(e={}){if(!(e.maxSize&&e.maxSize>0))throw new TypeError(\"`maxSize` must be a number greater than 0\");if(typeof e.maxAge==\"number\"&&e.maxAge===0)throw new TypeError(\"`maxAge` must be a number greater than 0\");this.maxSize=e.maxSize,this.maxAge=e.maxAge||1/0,this.onEviction=e.onEviction,this.cache=new Map,this.oldCache=new Map,this._size=0}_emitEvictions(e){if(typeof this.onEviction==\"function\")for(let[t,i]of e)this.onEviction(t,i.value)}_deleteIfExpired(e,t){return typeof t.expiry==\"number\"&&t.expiry<=Date.now()?(typeof this.onEviction==\"function\"&&this.onEviction(e,t.value),this.delete(e)):!1}_getOrDeleteIfExpired(e,t){if(this._deleteIfExpired(e,t)===!1)return t.value}_getItemValue(e,t){return t.expiry?this._getOrDeleteIfExpired(e,t):t.value}_peek(e,t){let i=t.get(e);return this._getItemValue(e,i)}_set(e,t){this.cache.set(e,t),this._size++,this._size>=this.maxSize&&(this._size=0,this._emitEvictions(this.oldCache),this.oldCache=this.cache,this.cache=new Map)}_moveToRecent(e,t){this.oldCache.delete(e),this._set(e,t)}*_entriesAscending(){for(let e of this.oldCache){let[t,i]=e;this.cache.has(t)||this._deleteIfExpired(t,i)===!1&&(yield e)}for(let e of this.cache){let[t,i]=e;this._deleteIfExpired(t,i)===!1&&(yield e)}}get(e){if(this.cache.has(e)){let t=this.cache.get(e);return this._getItemValue(e,t)}if(this.oldCache.has(e)){let t=this.oldCache.get(e);if(this._deleteIfExpired(e,t)===!1)return this._moveToRecent(e,t),t.value}}set(e,t,{maxAge:i=this.maxAge===1/0?void 0:Date.now()+this.maxAge}={}){this.cache.has(e)?this.cache.set(e,{value:t,maxAge:i}):this._set(e,{value:t,expiry:i})}has(e){return this.cache.has(e)?!this._deleteIfExpired(e,this.cache.get(e)):this.oldCache.has(e)?!this._deleteIfExpired(e,this.oldCache.get(e)):!1}peek(e){if(this.cache.has(e))return this._peek(e,this.cache);if(this.oldCache.has(e))return this._peek(e,this.oldCache)}delete(e){let t=this.cache.delete(e);return t&&this._size--,this.oldCache.delete(e)||t}clear(){this.cache.clear(),this.oldCache.clear(),this._size=0}resize(e){if(!(e&&e>0))throw new TypeError(\"`maxSize` must be a number greater than 0\");let t=[...this._entriesAscending()],i=t.length-e;i<0?(this.cache=new Map(t),this.oldCache=new Map,this._size=t.length):(i>0&&this._emitEvictions(t.slice(0,i)),this.oldCache=new Map(t.slice(i)),this.cache=new Map,this._size=0),this.maxSize=e}*keys(){for(let[e]of this)yield e}*values(){for(let[,e]of this)yield e}*[Symbol.iterator](){for(let e of this.cache){let[t,i]=e;this._deleteIfExpired(t,i)===!1&&(yield[t,i.value])}for(let e of this.oldCache){let[t,i]=e;this.cache.has(t)||this._deleteIfExpired(t,i)===!1&&(yield[t,i.value])}}*entriesDescending(){let e=[...this.cache];for(let t=e.length-1;t>=0;--t){let i=e[t],[n,s]=i;this._deleteIfExpired(n,s)===!1&&(yield[n,s.value])}e=[...this.oldCache];for(let t=e.length-1;t>=0;--t){let i=e[t],[n,s]=i;this.cache.has(n)||this._deleteIfExpired(n,s)===!1&&(yield[n,s.value])}}*entriesAscending(){for(let[e,t]of this._entriesAscending())yield[e,t.value]}get size(){if(!this._size)return this.oldCache.size;let e=0;for(let t of this.oldCache.keys())this.cache.has(t)||e++;return Math.min(this._size+e,this.maxSize)}};gf.exports=mf});var yf,bf=P(()=>{u();yf=r=>r&&r._hash});function Wi(r){return yf(r,{ignoreUnknown:!0})}var wf=P(()=>{u();bf()});function xt(r){if(r=`${r}`,r===\"0\")return\"0\";if(/^[+-]?(\\d+|\\d*\\.\\d+)(e[+-]?\\d+)?(%|\\w+)?$/.test(r))return r.replace(/^[+-]?/,t=>t===\"-\"?\"\":\"-\");let e=[\"var\",\"calc\",\"min\",\"max\",\"clamp\"];for(let t of e)if(r.includes(`${t}(`))return`calc(${r} * -1)`}var Gi=P(()=>{u()});var vf,xf=P(()=>{u();vf=[\"preflight\",\"container\",\"accessibility\",\"pointerEvents\",\"visibility\",\"position\",\"inset\",\"isolation\",\"zIndex\",\"order\",\"gridColumn\",\"gridColumnStart\",\"gridColumnEnd\",\"gridRow\",\"gridRowStart\",\"gridRowEnd\",\"float\",\"clear\",\"margin\",\"boxSizing\",\"lineClamp\",\"display\",\"aspectRatio\",\"size\",\"height\",\"maxHeight\",\"minHeight\",\"width\",\"minWidth\",\"maxWidth\",\"flex\",\"flexShrink\",\"flexGrow\",\"flexBasis\",\"tableLayout\",\"captionSide\",\"borderCollapse\",\"borderSpacing\",\"transformOrigin\",\"translate\",\"rotate\",\"skew\",\"scale\",\"transform\",\"animation\",\"cursor\",\"touchAction\",\"userSelect\",\"resize\",\"scrollSnapType\",\"scrollSnapAlign\",\"scrollSnapStop\",\"scrollMargin\",\"scrollPadding\",\"listStylePosition\",\"listStyleType\",\"listStyleImage\",\"appearance\",\"columns\",\"breakBefore\",\"breakInside\",\"breakAfter\",\"gridAutoColumns\",\"gridAutoFlow\",\"gridAutoRows\",\"gridTemplateColumns\",\"gridTemplateRows\",\"flexDirection\",\"flexWrap\",\"placeContent\",\"placeItems\",\"alignContent\",\"alignItems\",\"justifyContent\",\"justifyItems\",\"gap\",\"space\",\"divideWidth\",\"divideStyle\",\"divideColor\",\"divideOpacity\",\"placeSelf\",\"alignSelf\",\"justifySelf\",\"overflow\",\"overscrollBehavior\",\"scrollBehavior\",\"textOverflow\",\"hyphens\",\"whitespace\",\"textWrap\",\"wordBreak\",\"borderRadius\",\"borderWidth\",\"borderStyle\",\"borderColor\",\"borderOpacity\",\"backgroundColor\",\"backgroundOpacity\",\"backgroundImage\",\"gradientColorStops\",\"boxDecorationBreak\",\"backgroundSize\",\"backgroundAttachment\",\"backgroundClip\",\"backgroundPosition\",\"backgroundRepeat\",\"backgroundOrigin\",\"fill\",\"stroke\",\"strokeWidth\",\"objectFit\",\"objectPosition\",\"padding\",\"textAlign\",\"textIndent\",\"verticalAlign\",\"fontFamily\",\"fontSize\",\"fontWeight\",\"textTransform\",\"fontStyle\",\"fontVariantNumeric\",\"lineHeight\",\"letterSpacing\",\"textColor\",\"textOpacity\",\"textDecoration\",\"textDecorationColor\",\"textDecorationStyle\",\"textDecorationThickness\",\"textUnderlineOffset\",\"fontSmoothing\",\"placeholderColor\",\"placeholderOpacity\",\"caretColor\",\"accentColor\",\"opacity\",\"backgroundBlendMode\",\"mixBlendMode\",\"boxShadow\",\"boxShadowColor\",\"outlineStyle\",\"outlineWidth\",\"outlineOffset\",\"outlineColor\",\"ringWidth\",\"ringColor\",\"ringOpacity\",\"ringOffsetWidth\",\"ringOffsetColor\",\"blur\",\"brightness\",\"contrast\",\"dropShadow\",\"grayscale\",\"hueRotate\",\"invert\",\"saturate\",\"sepia\",\"filter\",\"backdropBlur\",\"backdropBrightness\",\"backdropContrast\",\"backdropGrayscale\",\"backdropHueRotate\",\"backdropInvert\",\"backdropOpacity\",\"backdropSaturate\",\"backdropSepia\",\"backdropFilter\",\"transitionProperty\",\"transitionDelay\",\"transitionDuration\",\"transitionTimingFunction\",\"willChange\",\"contain\",\"content\",\"forcedColorAdjust\"]});function kf(r,e){return r===void 0?e:Array.isArray(r)?r:[...new Set(e.filter(i=>r!==!1&&r[i]!==!1).concat(Object.keys(r).filter(i=>r[i]!==!1)))]}var Sf=P(()=>{u()});var Af={};Ge(Af,{default:()=>Qe});var Qe,Qi=P(()=>{u();Qe=new Proxy({},{get:()=>String})});function js(r,e,t){typeof m!=\"undefined\"&&m.env.JEST_WORKER_ID||t&&Cf.has(t)||(t&&Cf.add(t),console.warn(\"\"),e.forEach(i=>console.warn(r,\"-\",i)))}function zs(r){return Qe.dim(r)}var Cf,G,Be=P(()=>{u();Qi();Cf=new Set;G={info(r,e){js(Qe.bold(Qe.cyan(\"info\")),...Array.isArray(r)?[r]:[e,r])},warn(r,e){[\"content-problems\"].includes(r)||js(Qe.bold(Qe.yellow(\"warn\")),...Array.isArray(r)?[r]:[e,r])},risk(r,e){js(Qe.bold(Qe.magenta(\"risk\")),...Array.isArray(r)?[r]:[e,r])}}});var _f={};Ge(_f,{default:()=>Us});function qr({version:r,from:e,to:t}){G.warn(`${e}-color-renamed`,[`As of Tailwind CSS ${r}, \\`${e}\\` has been renamed to \\`${t}\\`.`,\"Update your configuration file to silence this warning.\"])}var Us,Vs=P(()=>{u();Be();Us={inherit:\"inherit\",current:\"currentColor\",transparent:\"transparent\",black:\"#000\",white:\"#fff\",slate:{50:\"#f8fafc\",100:\"#f1f5f9\",200:\"#e2e8f0\",300:\"#cbd5e1\",400:\"#94a3b8\",500:\"#64748b\",600:\"#475569\",700:\"#334155\",800:\"#1e293b\",900:\"#0f172a\",950:\"#020617\"},gray:{50:\"#f9fafb\",100:\"#f3f4f6\",200:\"#e5e7eb\",300:\"#d1d5db\",400:\"#9ca3af\",500:\"#6b7280\",600:\"#4b5563\",700:\"#374151\",800:\"#1f2937\",900:\"#111827\",950:\"#030712\"},zinc:{50:\"#fafafa\",100:\"#f4f4f5\",200:\"#e4e4e7\",300:\"#d4d4d8\",400:\"#a1a1aa\",500:\"#71717a\",600:\"#52525b\",700:\"#3f3f46\",800:\"#27272a\",900:\"#18181b\",950:\"#09090b\"},neutral:{50:\"#fafafa\",100:\"#f5f5f5\",200:\"#e5e5e5\",300:\"#d4d4d4\",400:\"#a3a3a3\",500:\"#737373\",600:\"#525252\",700:\"#404040\",800:\"#262626\",900:\"#171717\",950:\"#0a0a0a\"},stone:{50:\"#fafaf9\",100:\"#f5f5f4\",200:\"#e7e5e4\",300:\"#d6d3d1\",400:\"#a8a29e\",500:\"#78716c\",600:\"#57534e\",700:\"#44403c\",800:\"#292524\",900:\"#1c1917\",950:\"#0c0a09\"},red:{50:\"#fef2f2\",100:\"#fee2e2\",200:\"#fecaca\",300:\"#fca5a5\",400:\"#f87171\",500:\"#ef4444\",600:\"#dc2626\",700:\"#b91c1c\",800:\"#991b1b\",900:\"#7f1d1d\",950:\"#450a0a\"},orange:{50:\"#fff7ed\",100:\"#ffedd5\",200:\"#fed7aa\",300:\"#fdba74\",400:\"#fb923c\",500:\"#f97316\",600:\"#ea580c\",700:\"#c2410c\",800:\"#9a3412\",900:\"#7c2d12\",950:\"#431407\"},amber:{50:\"#fffbeb\",100:\"#fef3c7\",200:\"#fde68a\",300:\"#fcd34d\",400:\"#fbbf24\",500:\"#f59e0b\",600:\"#d97706\",700:\"#b45309\",800:\"#92400e\",900:\"#78350f\",950:\"#451a03\"},yellow:{50:\"#fefce8\",100:\"#fef9c3\",200:\"#fef08a\",300:\"#fde047\",400:\"#facc15\",500:\"#eab308\",600:\"#ca8a04\",700:\"#a16207\",800:\"#854d0e\",900:\"#713f12\",950:\"#422006\"},lime:{50:\"#f7fee7\",100:\"#ecfccb\",200:\"#d9f99d\",300:\"#bef264\",400:\"#a3e635\",500:\"#84cc16\",600:\"#65a30d\",700:\"#4d7c0f\",800:\"#3f6212\",900:\"#365314\",950:\"#1a2e05\"},green:{50:\"#f0fdf4\",100:\"#dcfce7\",200:\"#bbf7d0\",300:\"#86efac\",400:\"#4ade80\",500:\"#22c55e\",600:\"#16a34a\",700:\"#15803d\",800:\"#166534\",900:\"#14532d\",950:\"#052e16\"},emerald:{50:\"#ecfdf5\",100:\"#d1fae5\",200:\"#a7f3d0\",300:\"#6ee7b7\",400:\"#34d399\",500:\"#10b981\",600:\"#059669\",700:\"#047857\",800:\"#065f46\",900:\"#064e3b\",950:\"#022c22\"},teal:{50:\"#f0fdfa\",100:\"#ccfbf1\",200:\"#99f6e4\",300:\"#5eead4\",400:\"#2dd4bf\",500:\"#14b8a6\",600:\"#0d9488\",700:\"#0f766e\",800:\"#115e59\",900:\"#134e4a\",950:\"#042f2e\"},cyan:{50:\"#ecfeff\",100:\"#cffafe\",200:\"#a5f3fc\",300:\"#67e8f9\",400:\"#22d3ee\",500:\"#06b6d4\",600:\"#0891b2\",700:\"#0e7490\",800:\"#155e75\",900:\"#164e63\",950:\"#083344\"},sky:{50:\"#f0f9ff\",100:\"#e0f2fe\",200:\"#bae6fd\",300:\"#7dd3fc\",400:\"#38bdf8\",500:\"#0ea5e9\",600:\"#0284c7\",700:\"#0369a1\",800:\"#075985\",900:\"#0c4a6e\",950:\"#082f49\"},blue:{50:\"#eff6ff\",100:\"#dbeafe\",200:\"#bfdbfe\",300:\"#93c5fd\",400:\"#60a5fa\",500:\"#3b82f6\",600:\"#2563eb\",700:\"#1d4ed8\",800:\"#1e40af\",900:\"#1e3a8a\",950:\"#172554\"},indigo:{50:\"#eef2ff\",100:\"#e0e7ff\",200:\"#c7d2fe\",300:\"#a5b4fc\",400:\"#818cf8\",500:\"#6366f1\",600:\"#4f46e5\",700:\"#4338ca\",800:\"#3730a3\",900:\"#312e81\",950:\"#1e1b4b\"},violet:{50:\"#f5f3ff\",100:\"#ede9fe\",200:\"#ddd6fe\",300:\"#c4b5fd\",400:\"#a78bfa\",500:\"#8b5cf6\",600:\"#7c3aed\",700:\"#6d28d9\",800:\"#5b21b6\",900:\"#4c1d95\",950:\"#2e1065\"},purple:{50:\"#faf5ff\",100:\"#f3e8ff\",200:\"#e9d5ff\",300:\"#d8b4fe\",400:\"#c084fc\",500:\"#a855f7\",600:\"#9333ea\",700:\"#7e22ce\",800:\"#6b21a8\",900:\"#581c87\",950:\"#3b0764\"},fuchsia:{50:\"#fdf4ff\",100:\"#fae8ff\",200:\"#f5d0fe\",300:\"#f0abfc\",400:\"#e879f9\",500:\"#d946ef\",600:\"#c026d3\",700:\"#a21caf\",800:\"#86198f\",900:\"#701a75\",950:\"#4a044e\"},pink:{50:\"#fdf2f8\",100:\"#fce7f3\",200:\"#fbcfe8\",300:\"#f9a8d4\",400:\"#f472b6\",500:\"#ec4899\",600:\"#db2777\",700:\"#be185d\",800:\"#9d174d\",900:\"#831843\",950:\"#500724\"},rose:{50:\"#fff1f2\",100:\"#ffe4e6\",200:\"#fecdd3\",300:\"#fda4af\",400:\"#fb7185\",500:\"#f43f5e\",600:\"#e11d48\",700:\"#be123c\",800:\"#9f1239\",900:\"#881337\",950:\"#4c0519\"},get lightBlue(){return qr({version:\"v2.2\",from:\"lightBlue\",to:\"sky\"}),this.sky},get warmGray(){return qr({version:\"v3.0\",from:\"warmGray\",to:\"stone\"}),this.stone},get trueGray(){return qr({version:\"v3.0\",from:\"trueGray\",to:\"neutral\"}),this.neutral},get coolGray(){return qr({version:\"v3.0\",from:\"coolGray\",to:\"gray\"}),this.gray},get blueGray(){return qr({version:\"v3.0\",from:\"blueGray\",to:\"slate\"}),this.slate}}});function Hs(r,...e){for(let t of e){for(let i in t)r?.hasOwnProperty?.(i)||(r[i]=t[i]);for(let i of Object.getOwnPropertySymbols(t))r?.hasOwnProperty?.(i)||(r[i]=t[i])}return r}var Ef=P(()=>{u()});function kt(r){if(Array.isArray(r))return r;let e=r.split(\"[\").length-1,t=r.split(\"]\").length-1;if(e!==t)throw new Error(`Path is invalid. Has unbalanced brackets: ${r}`);return r.split(/\\.(?![^\\[]*\\])|[\\[\\]]/g).filter(Boolean)}var Yi=P(()=>{u()});function we(r,e){return Ki.future.includes(e)?r.future===\"all\"||(r?.future?.[e]??Of[e]??!1):Ki.experimental.includes(e)?r.experimental===\"all\"||(r?.experimental?.[e]??Of[e]??!1):!1}function Tf(r){return r.experimental===\"all\"?Ki.experimental:Object.keys(r?.experimental??{}).filter(e=>Ki.experimental.includes(e)&&r.experimental[e])}function Rf(r){if(m.env.JEST_WORKER_ID===void 0&&Tf(r).length>0){let e=Tf(r).map(t=>Qe.yellow(t)).join(\", \");G.warn(\"experimental-flags-enabled\",[`You have enabled experimental features: ${e}`,\"Experimental features in Tailwind CSS are not covered by semver, may introduce breaking changes, and can change at any time.\"])}}var Of,Ki,ct=P(()=>{u();Qi();Be();Of={optimizeUniversalDefaults:!1,generalizedModifiers:!0,disableColorOpacityUtilitiesByDefault:!1,relativeContentPathsByDefault:!1},Ki={future:[\"hoverOnlyWhenSupported\",\"respectDefaultRingColorOpacity\",\"disableColorOpacityUtilitiesByDefault\",\"relativeContentPathsByDefault\"],experimental:[\"optimizeUniversalDefaults\",\"generalizedModifiers\"]}});function Pf(r){(()=>{if(r.purge||!r.content||!Array.isArray(r.content)&&!(typeof r.content==\"object\"&&r.content!==null))return!1;if(Array.isArray(r.content))return r.content.every(t=>typeof t==\"string\"?!0:!(typeof t?.raw!=\"string\"||t?.extension&&typeof t?.extension!=\"string\"));if(typeof r.content==\"object\"&&r.content!==null){if(Object.keys(r.content).some(t=>![\"files\",\"relative\",\"extract\",\"transform\"].includes(t)))return!1;if(Array.isArray(r.content.files)){if(!r.content.files.every(t=>typeof t==\"string\"?!0:!(typeof t?.raw!=\"string\"||t?.extension&&typeof t?.extension!=\"string\")))return!1;if(typeof r.content.extract==\"object\"){for(let t of Object.values(r.content.extract))if(typeof t!=\"function\")return!1}else if(!(r.content.extract===void 0||typeof r.content.extract==\"function\"))return!1;if(typeof r.content.transform==\"object\"){for(let t of Object.values(r.content.transform))if(typeof t!=\"function\")return!1}else if(!(r.content.transform===void 0||typeof r.content.transform==\"function\"))return!1;if(typeof r.content.relative!=\"boolean\"&&typeof r.content.relative!=\"undefined\")return!1}return!0}return!1})()||G.warn(\"purge-deprecation\",[\"The `purge`/`content` options have changed in Tailwind CSS v3.0.\",\"Update your configuration file to eliminate this warning.\",\"https://tailwindcss.com/docs/upgrade-guide#configure-content-sources\"]),r.safelist=(()=>{let{content:t,purge:i,safelist:n}=r;return Array.isArray(n)?n:Array.isArray(t?.safelist)?t.safelist:Array.isArray(i?.safelist)?i.safelist:Array.isArray(i?.options?.safelist)?i.options.safelist:[]})(),r.blocklist=(()=>{let{blocklist:t}=r;if(Array.isArray(t)){if(t.every(i=>typeof i==\"string\"))return t;G.warn(\"blocklist-invalid\",[\"The `blocklist` option must be an array of strings.\",\"https://tailwindcss.com/docs/content-configuration#discarding-classes\"])}return[]})(),typeof r.prefix==\"function\"?(G.warn(\"prefix-function\",[\"As of Tailwind CSS v3.0, `prefix` cannot be a function.\",\"Update `prefix` in your configuration to be a string to eliminate this warning.\",\"https://tailwindcss.com/docs/upgrade-guide#prefix-cannot-be-a-function\"]),r.prefix=\"\"):r.prefix=r.prefix??\"\",r.content={relative:(()=>{let{content:t}=r;return t?.relative?t.relative:we(r,\"relativeContentPathsByDefault\")})(),files:(()=>{let{content:t,purge:i}=r;return Array.isArray(i)?i:Array.isArray(i?.content)?i.content:Array.isArray(t)?t:Array.isArray(t?.content)?t.content:Array.isArray(t?.files)?t.files:[]})(),extract:(()=>{let t=(()=>r.purge?.extract?r.purge.extract:r.content?.extract?r.content.extract:r.purge?.extract?.DEFAULT?r.purge.extract.DEFAULT:r.content?.extract?.DEFAULT?r.content.extract.DEFAULT:r.purge?.options?.extractors?r.purge.options.extractors:r.content?.options?.extractors?r.content.options.extractors:{})(),i={},n=(()=>{if(r.purge?.options?.defaultExtractor)return r.purge.options.defaultExtractor;if(r.content?.options?.defaultExtractor)return r.content.options.defaultExtractor})();if(n!==void 0&&(i.DEFAULT=n),typeof t==\"function\")i.DEFAULT=t;else if(Array.isArray(t))for(let{extensions:s,extractor:a}of t??[])for(let o of s)i[o]=a;else typeof t==\"object\"&&t!==null&&Object.assign(i,t);return i})(),transform:(()=>{let t=(()=>r.purge?.transform?r.purge.transform:r.content?.transform?r.content.transform:r.purge?.transform?.DEFAULT?r.purge.transform.DEFAULT:r.content?.transform?.DEFAULT?r.content.transform.DEFAULT:{})(),i={};return typeof t==\"function\"?i.DEFAULT=t:typeof t==\"object\"&&t!==null&&Object.assign(i,t),i})()};for(let t of r.content.files)if(typeof t==\"string\"&&/{([^,]*?)}/g.test(t)){G.warn(\"invalid-glob-braces\",[`The glob pattern ${zs(t)} in your Tailwind CSS configuration is invalid.`,`Update it to ${zs(t.replace(/{([^,]*?)}/g,\"$1\"))} to silence this warning.`]);break}return r}var If=P(()=>{u();ct();Be()});function ke(r){if(Object.prototype.toString.call(r)!==\"[object Object]\")return!1;let e=Object.getPrototypeOf(r);return e===null||Object.getPrototypeOf(e)===null}var Kt=P(()=>{u()});function St(r){return Array.isArray(r)?r.map(e=>St(e)):typeof r==\"object\"&&r!==null?Object.fromEntries(Object.entries(r).map(([e,t])=>[e,St(t)])):r}var Xi=P(()=>{u()});function jt(r){return r.replace(/\\\\,/g,\"\\\\2c \")}var Zi=P(()=>{u()});var Ws,Df=P(()=>{u();Ws={aliceblue:[240,248,255],antiquewhite:[250,235,215],aqua:[0,255,255],aquamarine:[127,255,212],azure:[240,255,255],beige:[245,245,220],bisque:[255,228,196],black:[0,0,0],blanchedalmond:[255,235,205],blue:[0,0,255],blueviolet:[138,43,226],brown:[165,42,42],burlywood:[222,184,135],cadetblue:[95,158,160],chartreuse:[127,255,0],chocolate:[210,105,30],coral:[255,127,80],cornflowerblue:[100,149,237],cornsilk:[255,248,220],crimson:[220,20,60],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgoldenrod:[184,134,11],darkgray:[169,169,169],darkgreen:[0,100,0],darkgrey:[169,169,169],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkseagreen:[143,188,143],darkslateblue:[72,61,139],darkslategray:[47,79,79],darkslategrey:[47,79,79],darkturquoise:[0,206,209],darkviolet:[148,0,211],deeppink:[255,20,147],deepskyblue:[0,191,255],dimgray:[105,105,105],dimgrey:[105,105,105],dodgerblue:[30,144,255],firebrick:[178,34,34],floralwhite:[255,250,240],forestgreen:[34,139,34],fuchsia:[255,0,255],gainsboro:[220,220,220],ghostwhite:[248,248,255],gold:[255,215,0],goldenrod:[218,165,32],gray:[128,128,128],green:[0,128,0],greenyellow:[173,255,47],grey:[128,128,128],honeydew:[240,255,240],hotpink:[255,105,180],indianred:[205,92,92],indigo:[75,0,130],ivory:[255,255,240],khaki:[240,230,140],lavender:[230,230,250],lavenderblush:[255,240,245],lawngreen:[124,252,0],lemonchiffon:[255,250,205],lightblue:[173,216,230],lightcoral:[240,128,128],lightcyan:[224,255,255],lightgoldenrodyellow:[250,250,210],lightgray:[211,211,211],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightsalmon:[255,160,122],lightseagreen:[32,178,170],lightskyblue:[135,206,250],lightslategray:[119,136,153],lightslategrey:[119,136,153],lightsteelblue:[176,196,222],lightyellow:[255,255,224],lime:[0,255,0],limegreen:[50,205,50],linen:[250,240,230],magenta:[255,0,255],maroon:[128,0,0],mediumaquamarine:[102,205,170],mediumblue:[0,0,205],mediumorchid:[186,85,211],mediumpurple:[147,112,219],mediumseagreen:[60,179,113],mediumslateblue:[123,104,238],mediumspringgreen:[0,250,154],mediumturquoise:[72,209,204],mediumvioletred:[199,21,133],midnightblue:[25,25,112],mintcream:[245,255,250],mistyrose:[255,228,225],moccasin:[255,228,181],navajowhite:[255,222,173],navy:[0,0,128],oldlace:[253,245,230],olive:[128,128,0],olivedrab:[107,142,35],orange:[255,165,0],orangered:[255,69,0],orchid:[218,112,214],palegoldenrod:[238,232,170],palegreen:[152,251,152],paleturquoise:[175,238,238],palevioletred:[219,112,147],papayawhip:[255,239,213],peachpuff:[255,218,185],peru:[205,133,63],pink:[255,192,203],plum:[221,160,221],powderblue:[176,224,230],purple:[128,0,128],rebeccapurple:[102,51,153],red:[255,0,0],rosybrown:[188,143,143],royalblue:[65,105,225],saddlebrown:[139,69,19],salmon:[250,128,114],sandybrown:[244,164,96],seagreen:[46,139,87],seashell:[255,245,238],sienna:[160,82,45],silver:[192,192,192],skyblue:[135,206,235],slateblue:[106,90,205],slategray:[112,128,144],slategrey:[112,128,144],snow:[255,250,250],springgreen:[0,255,127],steelblue:[70,130,180],tan:[210,180,140],teal:[0,128,128],thistle:[216,191,216],tomato:[255,99,71],turquoise:[64,224,208],violet:[238,130,238],wheat:[245,222,179],white:[255,255,255],whitesmoke:[245,245,245],yellow:[255,255,0],yellowgreen:[154,205,50]}});function $r(r,{loose:e=!1}={}){if(typeof r!=\"string\")return null;if(r=r.trim(),r===\"transparent\")return{mode:\"rgb\",color:[\"0\",\"0\",\"0\"],alpha:\"0\"};if(r in Ws)return{mode:\"rgb\",color:Ws[r].map(s=>s.toString())};let t=r.replace(zv,(s,a,o,l,c)=>[\"#\",a,a,o,o,l,l,c?c+c:\"\"].join(\"\")).match(jv);if(t!==null)return{mode:\"rgb\",color:[parseInt(t[1],16),parseInt(t[2],16),parseInt(t[3],16)].map(s=>s.toString()),alpha:t[4]?(parseInt(t[4],16)/255).toString():void 0};let i=r.match(Uv)??r.match(Vv);if(i===null)return null;let n=[i[2],i[3],i[4]].filter(Boolean).map(s=>s.toString());return n.length===2&&n[0].startsWith(\"var(\")?{mode:i[1],color:[n[0]],alpha:n[1]}:!e&&n.length!==3||n.length<3&&!n.some(s=>/^var\\(.*?\\)$/.test(s))?null:{mode:i[1],color:n,alpha:i[5]?.toString?.()}}function Gs({mode:r,color:e,alpha:t}){let i=t!==void 0;return r===\"rgba\"||r===\"hsla\"?`${r}(${e.join(\", \")}${i?`, ${t}`:\"\"})`:`${r}(${e.join(\" \")}${i?` / ${t}`:\"\"})`}var jv,zv,At,Ji,qf,Ct,Uv,Vv,Qs=P(()=>{u();Df();jv=/^#([a-f\\d]{2})([a-f\\d]{2})([a-f\\d]{2})([a-f\\d]{2})?$/i,zv=/^#([a-f\\d])([a-f\\d])([a-f\\d])([a-f\\d])?$/i,At=/(?:\\d+|\\d*\\.\\d+)%?/,Ji=/(?:\\s*,\\s*|\\s+)/,qf=/\\s*[,/]\\s*/,Ct=/var\\(--(?:[^ )]*?)(?:,(?:[^ )]*?|var\\(--[^ )]*?\\)))?\\)/,Uv=new RegExp(`^(rgba?)\\\\(\\\\s*(${At.source}|${Ct.source})(?:${Ji.source}(${At.source}|${Ct.source}))?(?:${Ji.source}(${At.source}|${Ct.source}))?(?:${qf.source}(${At.source}|${Ct.source}))?\\\\s*\\\\)$`),Vv=new RegExp(`^(hsla?)\\\\(\\\\s*((?:${At.source})(?:deg|rad|grad|turn)?|${Ct.source})(?:${Ji.source}(${At.source}|${Ct.source}))?(?:${Ji.source}(${At.source}|${Ct.source}))?(?:${qf.source}(${At.source}|${Ct.source}))?\\\\s*\\\\)$`)});function Je(r,e,t){if(typeof r==\"function\")return r({opacityValue:e});let i=$r(r,{loose:!0});return i===null?t:Gs({...i,alpha:e})}function Ae({color:r,property:e,variable:t}){let i=[].concat(e);if(typeof r==\"function\")return{[t]:\"1\",...Object.fromEntries(i.map(s=>[s,r({opacityVariable:t,opacityValue:`var(${t}, 1)`})]))};let n=$r(r);return n===null?Object.fromEntries(i.map(s=>[s,r])):n.alpha!==void 0?Object.fromEntries(i.map(s=>[s,r])):{[t]:\"1\",...Object.fromEntries(i.map(s=>[s,Gs({...n,alpha:`var(${t}, 1)`})]))}}var Lr=P(()=>{u();Qs()});function ve(r,e){let t=[],i=[],n=0,s=!1;for(let a=0;a{u()});function en(r){return ve(r,\",\").map(t=>{let i=t.trim(),n={raw:i},s=i.split(Wv),a=new Set;for(let o of s)$f.lastIndex=0,!a.has(\"KEYWORD\")&&Hv.has(o)?(n.keyword=o,a.add(\"KEYWORD\")):$f.test(o)?a.has(\"X\")?a.has(\"Y\")?a.has(\"BLUR\")?a.has(\"SPREAD\")||(n.spread=o,a.add(\"SPREAD\")):(n.blur=o,a.add(\"BLUR\")):(n.y=o,a.add(\"Y\")):(n.x=o,a.add(\"X\")):n.color?(n.unknown||(n.unknown=[]),n.unknown.push(o)):n.color=o;return n.valid=n.x!==void 0&&n.y!==void 0,n})}function Lf(r){return r.map(e=>e.valid?[e.keyword,e.x,e.y,e.blur,e.spread,e.color].filter(Boolean).join(\" \"):e.raw).join(\", \")}var Hv,Wv,$f,Ys=P(()=>{u();zt();Hv=new Set([\"inset\",\"inherit\",\"initial\",\"revert\",\"unset\"]),Wv=/\\ +(?![^(]*\\))/g,$f=/^-?(\\d+|\\.\\d+)(.*?)$/g});function Ks(r){return Gv.some(e=>new RegExp(`^${e}\\\\(.*\\\\)`).test(r))}function K(r,e=null,t=!0){let i=e&&Qv.has(e.property);return r.startsWith(\"--\")&&!i?`var(${r})`:r.includes(\"url(\")?r.split(/(url\\(.*?\\))/g).filter(Boolean).map(n=>/^url\\(.*?\\)$/.test(n)?n:K(n,e,!1)).join(\"\"):(r=r.replace(/([^\\\\])_+/g,(n,s)=>s+\" \".repeat(n.length-1)).replace(/^_/g,\" \").replace(/\\\\_/g,\"_\"),t&&(r=r.trim()),r=Yv(r),r)}function Ye(r){return r.includes(\"=\")&&(r=r.replace(/(=.*)/g,(e,t)=>{if(t[1]===\"'\"||t[1]==='\"')return t;if(t.length>2){let i=t[t.length-1];if(t[t.length-2]===\" \"&&(i===\"i\"||i===\"I\"||i===\"s\"||i===\"S\"))return`=\"${t.slice(1,-2)}\" ${t[t.length-1]}`}return`=\"${t.slice(1)}\"`})),r}function Yv(r){let e=[\"theme\"],t=[\"min-content\",\"max-content\",\"fit-content\",\"safe-area-inset-top\",\"safe-area-inset-right\",\"safe-area-inset-bottom\",\"safe-area-inset-left\",\"titlebar-area-x\",\"titlebar-area-y\",\"titlebar-area-width\",\"titlebar-area-height\",\"keyboard-inset-top\",\"keyboard-inset-right\",\"keyboard-inset-bottom\",\"keyboard-inset-left\",\"keyboard-inset-width\",\"keyboard-inset-height\",\"radial-gradient\",\"linear-gradient\",\"conic-gradient\",\"repeating-radial-gradient\",\"repeating-linear-gradient\",\"repeating-conic-gradient\",\"anchor-size\"];return r.replace(/(calc|min|max|clamp)\\(.+\\)/g,i=>{let n=\"\";function s(){let a=n.trimEnd();return a[a.length-1]}for(let a=0;ai[a+p]===d)},l=function(f){let d=1/0;for(let h of f){let b=i.indexOf(h,a);b!==-1&&bo(f))){let f=t.find(d=>o(d));n+=f,a+=f.length-1}else e.some(f=>o(f))?n+=l([\")\"]):o(\"[\")?n+=l([\"]\"]):[\"+\",\"-\",\"*\",\"/\"].includes(c)&&![\"(\",\"+\",\"-\",\"*\",\"/\",\",\"].includes(s())?n+=` ${c} `:n+=c}return n.replace(/\\s+/g,\" \")})}function Xs(r){return r.startsWith(\"url(\")}function Zs(r){return!isNaN(Number(r))||Ks(r)}function Mr(r){return r.endsWith(\"%\")&&Zs(r.slice(0,-1))||Ks(r)}function Nr(r){return r===\"0\"||new RegExp(`^[+-]?[0-9]*.?[0-9]+(?:[eE][+-]?[0-9]+)?${Xv}$`).test(r)||Ks(r)}function Mf(r){return Zv.has(r)}function Nf(r){let e=en(K(r));for(let t of e)if(!t.valid)return!1;return!0}function Bf(r){let e=0;return ve(r,\"_\").every(i=>(i=K(i),i.startsWith(\"var(\")?!0:$r(i,{loose:!0})!==null?(e++,!0):!1))?e>0:!1}function Ff(r){let e=0;return ve(r,\",\").every(i=>(i=K(i),i.startsWith(\"var(\")?!0:Xs(i)||ex(i)||[\"element(\",\"image(\",\"cross-fade(\",\"image-set(\"].some(n=>i.startsWith(n))?(e++,!0):!1))?e>0:!1}function ex(r){r=K(r);for(let e of Jv)if(r.startsWith(`${e}(`))return!0;return!1}function jf(r){let e=0;return ve(r,\"_\").every(i=>(i=K(i),i.startsWith(\"var(\")?!0:tx.has(i)||Nr(i)||Mr(i)?(e++,!0):!1))?e>0:!1}function zf(r){let e=0;return ve(r,\",\").every(i=>(i=K(i),i.startsWith(\"var(\")?!0:i.includes(\" \")&&!/(['\"])([^\"']+)\\1/g.test(i)||/^\\d/g.test(i)?!1:(e++,!0)))?e>0:!1}function Uf(r){return rx.has(r)}function Vf(r){return ix.has(r)}function Hf(r){return nx.has(r)}var Gv,Qv,Kv,Xv,Zv,Jv,tx,rx,ix,nx,Br=P(()=>{u();Qs();Ys();zt();Gv=[\"min\",\"max\",\"clamp\",\"calc\"];Qv=new Set([\"scroll-timeline-name\",\"timeline-scope\",\"view-timeline-name\",\"font-palette\",\"anchor-name\",\"anchor-scope\",\"position-anchor\",\"position-try-options\",\"scroll-timeline\",\"animation-timeline\",\"view-timeline\",\"position-try\"]);Kv=[\"cm\",\"mm\",\"Q\",\"in\",\"pc\",\"pt\",\"px\",\"em\",\"ex\",\"ch\",\"rem\",\"lh\",\"rlh\",\"vw\",\"vh\",\"vmin\",\"vmax\",\"vb\",\"vi\",\"svw\",\"svh\",\"lvw\",\"lvh\",\"dvw\",\"dvh\",\"cqw\",\"cqh\",\"cqi\",\"cqb\",\"cqmin\",\"cqmax\"],Xv=`(?:${Kv.join(\"|\")})`;Zv=new Set([\"thin\",\"medium\",\"thick\"]);Jv=new Set([\"conic-gradient\",\"linear-gradient\",\"radial-gradient\",\"repeating-conic-gradient\",\"repeating-linear-gradient\",\"repeating-radial-gradient\"]);tx=new Set([\"center\",\"top\",\"right\",\"bottom\",\"left\"]);rx=new Set([\"serif\",\"sans-serif\",\"monospace\",\"cursive\",\"fantasy\",\"system-ui\",\"ui-serif\",\"ui-sans-serif\",\"ui-monospace\",\"ui-rounded\",\"math\",\"emoji\",\"fangsong\"]);ix=new Set([\"xx-small\",\"x-small\",\"small\",\"medium\",\"large\",\"x-large\",\"xx-large\",\"xxx-large\"]);nx=new Set([\"larger\",\"smaller\"])});function Wf(r){let e=[\"cover\",\"contain\"];return ve(r,\",\").every(t=>{let i=ve(t,\"_\").filter(Boolean);return i.length===1&&e.includes(i[0])?!0:i.length!==1&&i.length!==2?!1:i.every(n=>Nr(n)||Mr(n)||n===\"auto\")})}var Gf=P(()=>{u();Br();zt()});function Qf(r,e){r.walkClasses(t=>{t.value=e(t.value),t.raws&&t.raws.value&&(t.raws.value=jt(t.raws.value))})}function Yf(r,e){if(!_t(r))return;let t=r.slice(1,-1);if(!!e(t))return K(t)}function sx(r,e={},t){let i=e[r];if(i!==void 0)return xt(i);if(_t(r)){let n=Yf(r,t);return n===void 0?void 0:xt(n)}}function tn(r,e={},{validate:t=()=>!0}={}){let i=e.values?.[r];return i!==void 0?i:e.supportsNegativeValues&&r.startsWith(\"-\")?sx(r.slice(1),e.values,t):Yf(r,t)}function _t(r){return r.startsWith(\"[\")&&r.endsWith(\"]\")}function Kf(r){let e=r.lastIndexOf(\"/\"),t=r.lastIndexOf(\"[\",e),i=r.indexOf(\"]\",e);return r[e-1]===\"]\"||r[e+1]===\"[\"||t!==-1&&i!==-1&&t\")){let e=r;return({opacityValue:t=1})=>e.replace(//g,t)}return r}function Xf(r){return K(r.slice(1,-1))}function ax(r,e={},{tailwindConfig:t={}}={}){if(e.values?.[r]!==void 0)return Xt(e.values?.[r]);let[i,n]=Kf(r);if(n!==void 0){let s=e.values?.[i]??(_t(i)?i.slice(1,-1):void 0);return s===void 0?void 0:(s=Xt(s),_t(n)?Je(s,Xf(n)):t.theme?.opacity?.[n]===void 0?void 0:Je(s,t.theme.opacity[n]))}return tn(r,e,{validate:Bf})}function ox(r,e={}){return e.values?.[r]}function qe(r){return(e,t)=>tn(e,t,{validate:r})}function lx(r,e){let t=r.indexOf(e);return t===-1?[void 0,r]:[r.slice(0,t),r.slice(t+1)]}function ea(r,e,t,i){if(t.values&&e in t.values)for(let{type:s}of r??[]){let a=Js[s](e,t,{tailwindConfig:i});if(a!==void 0)return[a,s,null]}if(_t(e)){let s=e.slice(1,-1),[a,o]=lx(s,\":\");if(!/^[\\w-_]+$/g.test(a))o=s;else if(a!==void 0&&!Zf.includes(a))return[];if(o.length>0&&Zf.includes(a))return[tn(`[${o}]`,t),a,null]}let n=ta(r,e,t,i);for(let s of n)return s;return[]}function*ta(r,e,t,i){let n=we(i,\"generalizedModifiers\"),[s,a]=Kf(e);if(n&&t.modifiers!=null&&(t.modifiers===\"any\"||typeof t.modifiers==\"object\"&&(a&&_t(a)||a in t.modifiers))||(s=e,a=void 0),a!==void 0&&s===\"\"&&(s=\"DEFAULT\"),a!==void 0&&typeof t.modifiers==\"object\"){let l=t.modifiers?.[a]??null;l!==null?a=l:_t(a)&&(a=Xf(a))}for(let{type:l}of r??[]){let c=Js[l](s,t,{tailwindConfig:i});c!==void 0&&(yield[c,l,a??null])}}var Js,Zf,Fr=P(()=>{u();Zi();Lr();Br();Gi();Gf();ct();Js={any:tn,color:ax,url:qe(Xs),image:qe(Ff),length:qe(Nr),percentage:qe(Mr),position:qe(jf),lookup:ox,\"generic-name\":qe(Uf),\"family-name\":qe(zf),number:qe(Zs),\"line-width\":qe(Mf),\"absolute-size\":qe(Vf),\"relative-size\":qe(Hf),shadow:qe(Nf),size:qe(Wf)},Zf=Object.keys(Js)});function X(r){return typeof r==\"function\"?r({}):r}var ra=P(()=>{u()});function Zt(r){return typeof r==\"function\"}function jr(r,...e){let t=e.pop();for(let i of e)for(let n in i){let s=t(r[n],i[n]);s===void 0?ke(r[n])&&ke(i[n])?r[n]=jr({},r[n],i[n],t):r[n]=i[n]:r[n]=s}return r}function ux(r,...e){return Zt(r)?r(...e):r}function fx(r){return r.reduce((e,{extend:t})=>jr(e,t,(i,n)=>i===void 0?[n]:Array.isArray(i)?[n,...i]:[n,i]),{})}function cx(r){return{...r.reduce((e,t)=>Hs(e,t),{}),extend:fx(r)}}function Jf(r,e){if(Array.isArray(r)&&ke(r[0]))return r.concat(e);if(Array.isArray(e)&&ke(e[0])&&ke(r))return[r,...e];if(Array.isArray(e))return e}function px({extend:r,...e}){return jr(e,r,(t,i)=>!Zt(t)&&!i.some(Zt)?jr({},t,...i,Jf):(n,s)=>jr({},...[t,...i].map(a=>ux(a,n,s)),Jf))}function*dx(r){let e=kt(r);if(e.length===0||(yield e,Array.isArray(r)))return;let t=/^(.*?)\\s*\\/\\s*([^/]+)$/,i=r.match(t);if(i!==null){let[,n,s]=i,a=kt(n);a.alpha=s,yield a}}function hx(r){let e=(t,i)=>{for(let n of dx(t)){let s=0,a=r;for(;a!=null&&s(t[i]=Zt(r[i])?r[i](e,ia):r[i],t),{})}function ec(r){let e=[];return r.forEach(t=>{e=[...e,t];let i=t?.plugins??[];i.length!==0&&i.forEach(n=>{n.__isOptionsFunction&&(n=n()),e=[...e,...ec([n?.config??{}])]})}),e}function mx(r){return[...r].reduceRight((t,i)=>Zt(i)?i({corePlugins:t}):kf(i,t),vf)}function gx(r){return[...r].reduceRight((t,i)=>[...t,...i],[])}function na(r){let e=[...ec(r),{prefix:\"\",important:!1,separator:\":\"}];return Pf(Hs({theme:hx(px(cx(e.map(t=>t?.theme??{})))),corePlugins:mx(e.map(t=>t.corePlugins)),plugins:gx(r.map(t=>t?.plugins??[]))},...e))}var ia,tc=P(()=>{u();Gi();xf();Sf();Vs();Ef();Yi();If();Kt();Xi();Fr();Lr();ra();ia={colors:Us,negative(r){return Object.keys(r).filter(e=>r[e]!==\"0\").reduce((e,t)=>{let i=xt(r[t]);return i!==void 0&&(e[`-${t}`]=i),e},{})},breakpoints(r){return Object.keys(r).filter(e=>typeof r[e]==\"string\").reduce((e,t)=>({...e,[`screen-${t}`]:r[t]}),{})}}});var rn=x((f3,rc)=>{u();rc.exports={content:[],presets:[],darkMode:\"media\",theme:{accentColor:({theme:r})=>({...r(\"colors\"),auto:\"auto\"}),animation:{none:\"none\",spin:\"spin 1s linear infinite\",ping:\"ping 1s cubic-bezier(0, 0, 0.2, 1) infinite\",pulse:\"pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite\",bounce:\"bounce 1s infinite\"},aria:{busy:'busy=\"true\"',checked:'checked=\"true\"',disabled:'disabled=\"true\"',expanded:'expanded=\"true\"',hidden:'hidden=\"true\"',pressed:'pressed=\"true\"',readonly:'readonly=\"true\"',required:'required=\"true\"',selected:'selected=\"true\"'},aspectRatio:{auto:\"auto\",square:\"1 / 1\",video:\"16 / 9\"},backdropBlur:({theme:r})=>r(\"blur\"),backdropBrightness:({theme:r})=>r(\"brightness\"),backdropContrast:({theme:r})=>r(\"contrast\"),backdropGrayscale:({theme:r})=>r(\"grayscale\"),backdropHueRotate:({theme:r})=>r(\"hueRotate\"),backdropInvert:({theme:r})=>r(\"invert\"),backdropOpacity:({theme:r})=>r(\"opacity\"),backdropSaturate:({theme:r})=>r(\"saturate\"),backdropSepia:({theme:r})=>r(\"sepia\"),backgroundColor:({theme:r})=>r(\"colors\"),backgroundImage:{none:\"none\",\"gradient-to-t\":\"linear-gradient(to top, var(--tw-gradient-stops))\",\"gradient-to-tr\":\"linear-gradient(to top right, var(--tw-gradient-stops))\",\"gradient-to-r\":\"linear-gradient(to right, var(--tw-gradient-stops))\",\"gradient-to-br\":\"linear-gradient(to bottom right, var(--tw-gradient-stops))\",\"gradient-to-b\":\"linear-gradient(to bottom, var(--tw-gradient-stops))\",\"gradient-to-bl\":\"linear-gradient(to bottom left, var(--tw-gradient-stops))\",\"gradient-to-l\":\"linear-gradient(to left, var(--tw-gradient-stops))\",\"gradient-to-tl\":\"linear-gradient(to top left, var(--tw-gradient-stops))\"},backgroundOpacity:({theme:r})=>r(\"opacity\"),backgroundPosition:{bottom:\"bottom\",center:\"center\",left:\"left\",\"left-bottom\":\"left bottom\",\"left-top\":\"left top\",right:\"right\",\"right-bottom\":\"right bottom\",\"right-top\":\"right top\",top:\"top\"},backgroundSize:{auto:\"auto\",cover:\"cover\",contain:\"contain\"},blur:{0:\"0\",none:\"\",sm:\"4px\",DEFAULT:\"8px\",md:\"12px\",lg:\"16px\",xl:\"24px\",\"2xl\":\"40px\",\"3xl\":\"64px\"},borderColor:({theme:r})=>({...r(\"colors\"),DEFAULT:r(\"colors.gray.200\",\"currentColor\")}),borderOpacity:({theme:r})=>r(\"opacity\"),borderRadius:{none:\"0px\",sm:\"0.125rem\",DEFAULT:\"0.25rem\",md:\"0.375rem\",lg:\"0.5rem\",xl:\"0.75rem\",\"2xl\":\"1rem\",\"3xl\":\"1.5rem\",full:\"9999px\"},borderSpacing:({theme:r})=>({...r(\"spacing\")}),borderWidth:{DEFAULT:\"1px\",0:\"0px\",2:\"2px\",4:\"4px\",8:\"8px\"},boxShadow:{sm:\"0 1px 2px 0 rgb(0 0 0 / 0.05)\",DEFAULT:\"0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)\",md:\"0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)\",lg:\"0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1)\",xl:\"0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1)\",\"2xl\":\"0 25px 50px -12px rgb(0 0 0 / 0.25)\",inner:\"inset 0 2px 4px 0 rgb(0 0 0 / 0.05)\",none:\"none\"},boxShadowColor:({theme:r})=>r(\"colors\"),brightness:{0:\"0\",50:\".5\",75:\".75\",90:\".9\",95:\".95\",100:\"1\",105:\"1.05\",110:\"1.1\",125:\"1.25\",150:\"1.5\",200:\"2\"},caretColor:({theme:r})=>r(\"colors\"),colors:({colors:r})=>({inherit:r.inherit,current:r.current,transparent:r.transparent,black:r.black,white:r.white,slate:r.slate,gray:r.gray,zinc:r.zinc,neutral:r.neutral,stone:r.stone,red:r.red,orange:r.orange,amber:r.amber,yellow:r.yellow,lime:r.lime,green:r.green,emerald:r.emerald,teal:r.teal,cyan:r.cyan,sky:r.sky,blue:r.blue,indigo:r.indigo,violet:r.violet,purple:r.purple,fuchsia:r.fuchsia,pink:r.pink,rose:r.rose}),columns:{auto:\"auto\",1:\"1\",2:\"2\",3:\"3\",4:\"4\",5:\"5\",6:\"6\",7:\"7\",8:\"8\",9:\"9\",10:\"10\",11:\"11\",12:\"12\",\"3xs\":\"16rem\",\"2xs\":\"18rem\",xs:\"20rem\",sm:\"24rem\",md:\"28rem\",lg:\"32rem\",xl:\"36rem\",\"2xl\":\"42rem\",\"3xl\":\"48rem\",\"4xl\":\"56rem\",\"5xl\":\"64rem\",\"6xl\":\"72rem\",\"7xl\":\"80rem\"},container:{},content:{none:\"none\"},contrast:{0:\"0\",50:\".5\",75:\".75\",100:\"1\",125:\"1.25\",150:\"1.5\",200:\"2\"},cursor:{auto:\"auto\",default:\"default\",pointer:\"pointer\",wait:\"wait\",text:\"text\",move:\"move\",help:\"help\",\"not-allowed\":\"not-allowed\",none:\"none\",\"context-menu\":\"context-menu\",progress:\"progress\",cell:\"cell\",crosshair:\"crosshair\",\"vertical-text\":\"vertical-text\",alias:\"alias\",copy:\"copy\",\"no-drop\":\"no-drop\",grab:\"grab\",grabbing:\"grabbing\",\"all-scroll\":\"all-scroll\",\"col-resize\":\"col-resize\",\"row-resize\":\"row-resize\",\"n-resize\":\"n-resize\",\"e-resize\":\"e-resize\",\"s-resize\":\"s-resize\",\"w-resize\":\"w-resize\",\"ne-resize\":\"ne-resize\",\"nw-resize\":\"nw-resize\",\"se-resize\":\"se-resize\",\"sw-resize\":\"sw-resize\",\"ew-resize\":\"ew-resize\",\"ns-resize\":\"ns-resize\",\"nesw-resize\":\"nesw-resize\",\"nwse-resize\":\"nwse-resize\",\"zoom-in\":\"zoom-in\",\"zoom-out\":\"zoom-out\"},divideColor:({theme:r})=>r(\"borderColor\"),divideOpacity:({theme:r})=>r(\"borderOpacity\"),divideWidth:({theme:r})=>r(\"borderWidth\"),dropShadow:{sm:\"0 1px 1px rgb(0 0 0 / 0.05)\",DEFAULT:[\"0 1px 2px rgb(0 0 0 / 0.1)\",\"0 1px 1px rgb(0 0 0 / 0.06)\"],md:[\"0 4px 3px rgb(0 0 0 / 0.07)\",\"0 2px 2px rgb(0 0 0 / 0.06)\"],lg:[\"0 10px 8px rgb(0 0 0 / 0.04)\",\"0 4px 3px rgb(0 0 0 / 0.1)\"],xl:[\"0 20px 13px rgb(0 0 0 / 0.03)\",\"0 8px 5px rgb(0 0 0 / 0.08)\"],\"2xl\":\"0 25px 25px rgb(0 0 0 / 0.15)\",none:\"0 0 #0000\"},fill:({theme:r})=>({none:\"none\",...r(\"colors\")}),flex:{1:\"1 1 0%\",auto:\"1 1 auto\",initial:\"0 1 auto\",none:\"none\"},flexBasis:({theme:r})=>({auto:\"auto\",...r(\"spacing\"),\"1/2\":\"50%\",\"1/3\":\"33.333333%\",\"2/3\":\"66.666667%\",\"1/4\":\"25%\",\"2/4\":\"50%\",\"3/4\":\"75%\",\"1/5\":\"20%\",\"2/5\":\"40%\",\"3/5\":\"60%\",\"4/5\":\"80%\",\"1/6\":\"16.666667%\",\"2/6\":\"33.333333%\",\"3/6\":\"50%\",\"4/6\":\"66.666667%\",\"5/6\":\"83.333333%\",\"1/12\":\"8.333333%\",\"2/12\":\"16.666667%\",\"3/12\":\"25%\",\"4/12\":\"33.333333%\",\"5/12\":\"41.666667%\",\"6/12\":\"50%\",\"7/12\":\"58.333333%\",\"8/12\":\"66.666667%\",\"9/12\":\"75%\",\"10/12\":\"83.333333%\",\"11/12\":\"91.666667%\",full:\"100%\"}),flexGrow:{0:\"0\",DEFAULT:\"1\"},flexShrink:{0:\"0\",DEFAULT:\"1\"},fontFamily:{sans:[\"ui-sans-serif\",\"system-ui\",\"sans-serif\",'\"Apple Color Emoji\"','\"Segoe UI Emoji\"','\"Segoe UI Symbol\"','\"Noto Color Emoji\"'],serif:[\"ui-serif\",\"Georgia\",\"Cambria\",'\"Times New Roman\"',\"Times\",\"serif\"],mono:[\"ui-monospace\",\"SFMono-Regular\",\"Menlo\",\"Monaco\",\"Consolas\",'\"Liberation Mono\"','\"Courier New\"',\"monospace\"]},fontSize:{xs:[\"0.75rem\",{lineHeight:\"1rem\"}],sm:[\"0.875rem\",{lineHeight:\"1.25rem\"}],base:[\"1rem\",{lineHeight:\"1.5rem\"}],lg:[\"1.125rem\",{lineHeight:\"1.75rem\"}],xl:[\"1.25rem\",{lineHeight:\"1.75rem\"}],\"2xl\":[\"1.5rem\",{lineHeight:\"2rem\"}],\"3xl\":[\"1.875rem\",{lineHeight:\"2.25rem\"}],\"4xl\":[\"2.25rem\",{lineHeight:\"2.5rem\"}],\"5xl\":[\"3rem\",{lineHeight:\"1\"}],\"6xl\":[\"3.75rem\",{lineHeight:\"1\"}],\"7xl\":[\"4.5rem\",{lineHeight:\"1\"}],\"8xl\":[\"6rem\",{lineHeight:\"1\"}],\"9xl\":[\"8rem\",{lineHeight:\"1\"}]},fontWeight:{thin:\"100\",extralight:\"200\",light:\"300\",normal:\"400\",medium:\"500\",semibold:\"600\",bold:\"700\",extrabold:\"800\",black:\"900\"},gap:({theme:r})=>r(\"spacing\"),gradientColorStops:({theme:r})=>r(\"colors\"),gradientColorStopPositions:{\"0%\":\"0%\",\"5%\":\"5%\",\"10%\":\"10%\",\"15%\":\"15%\",\"20%\":\"20%\",\"25%\":\"25%\",\"30%\":\"30%\",\"35%\":\"35%\",\"40%\":\"40%\",\"45%\":\"45%\",\"50%\":\"50%\",\"55%\":\"55%\",\"60%\":\"60%\",\"65%\":\"65%\",\"70%\":\"70%\",\"75%\":\"75%\",\"80%\":\"80%\",\"85%\":\"85%\",\"90%\":\"90%\",\"95%\":\"95%\",\"100%\":\"100%\"},grayscale:{0:\"0\",DEFAULT:\"100%\"},gridAutoColumns:{auto:\"auto\",min:\"min-content\",max:\"max-content\",fr:\"minmax(0, 1fr)\"},gridAutoRows:{auto:\"auto\",min:\"min-content\",max:\"max-content\",fr:\"minmax(0, 1fr)\"},gridColumn:{auto:\"auto\",\"span-1\":\"span 1 / span 1\",\"span-2\":\"span 2 / span 2\",\"span-3\":\"span 3 / span 3\",\"span-4\":\"span 4 / span 4\",\"span-5\":\"span 5 / span 5\",\"span-6\":\"span 6 / span 6\",\"span-7\":\"span 7 / span 7\",\"span-8\":\"span 8 / span 8\",\"span-9\":\"span 9 / span 9\",\"span-10\":\"span 10 / span 10\",\"span-11\":\"span 11 / span 11\",\"span-12\":\"span 12 / span 12\",\"span-full\":\"1 / -1\"},gridColumnEnd:{auto:\"auto\",1:\"1\",2:\"2\",3:\"3\",4:\"4\",5:\"5\",6:\"6\",7:\"7\",8:\"8\",9:\"9\",10:\"10\",11:\"11\",12:\"12\",13:\"13\"},gridColumnStart:{auto:\"auto\",1:\"1\",2:\"2\",3:\"3\",4:\"4\",5:\"5\",6:\"6\",7:\"7\",8:\"8\",9:\"9\",10:\"10\",11:\"11\",12:\"12\",13:\"13\"},gridRow:{auto:\"auto\",\"span-1\":\"span 1 / span 1\",\"span-2\":\"span 2 / span 2\",\"span-3\":\"span 3 / span 3\",\"span-4\":\"span 4 / span 4\",\"span-5\":\"span 5 / span 5\",\"span-6\":\"span 6 / span 6\",\"span-7\":\"span 7 / span 7\",\"span-8\":\"span 8 / span 8\",\"span-9\":\"span 9 / span 9\",\"span-10\":\"span 10 / span 10\",\"span-11\":\"span 11 / span 11\",\"span-12\":\"span 12 / span 12\",\"span-full\":\"1 / -1\"},gridRowEnd:{auto:\"auto\",1:\"1\",2:\"2\",3:\"3\",4:\"4\",5:\"5\",6:\"6\",7:\"7\",8:\"8\",9:\"9\",10:\"10\",11:\"11\",12:\"12\",13:\"13\"},gridRowStart:{auto:\"auto\",1:\"1\",2:\"2\",3:\"3\",4:\"4\",5:\"5\",6:\"6\",7:\"7\",8:\"8\",9:\"9\",10:\"10\",11:\"11\",12:\"12\",13:\"13\"},gridTemplateColumns:{none:\"none\",subgrid:\"subgrid\",1:\"repeat(1, minmax(0, 1fr))\",2:\"repeat(2, minmax(0, 1fr))\",3:\"repeat(3, minmax(0, 1fr))\",4:\"repeat(4, minmax(0, 1fr))\",5:\"repeat(5, minmax(0, 1fr))\",6:\"repeat(6, minmax(0, 1fr))\",7:\"repeat(7, minmax(0, 1fr))\",8:\"repeat(8, minmax(0, 1fr))\",9:\"repeat(9, minmax(0, 1fr))\",10:\"repeat(10, minmax(0, 1fr))\",11:\"repeat(11, minmax(0, 1fr))\",12:\"repeat(12, minmax(0, 1fr))\"},gridTemplateRows:{none:\"none\",subgrid:\"subgrid\",1:\"repeat(1, minmax(0, 1fr))\",2:\"repeat(2, minmax(0, 1fr))\",3:\"repeat(3, minmax(0, 1fr))\",4:\"repeat(4, minmax(0, 1fr))\",5:\"repeat(5, minmax(0, 1fr))\",6:\"repeat(6, minmax(0, 1fr))\",7:\"repeat(7, minmax(0, 1fr))\",8:\"repeat(8, minmax(0, 1fr))\",9:\"repeat(9, minmax(0, 1fr))\",10:\"repeat(10, minmax(0, 1fr))\",11:\"repeat(11, minmax(0, 1fr))\",12:\"repeat(12, minmax(0, 1fr))\"},height:({theme:r})=>({auto:\"auto\",...r(\"spacing\"),\"1/2\":\"50%\",\"1/3\":\"33.333333%\",\"2/3\":\"66.666667%\",\"1/4\":\"25%\",\"2/4\":\"50%\",\"3/4\":\"75%\",\"1/5\":\"20%\",\"2/5\":\"40%\",\"3/5\":\"60%\",\"4/5\":\"80%\",\"1/6\":\"16.666667%\",\"2/6\":\"33.333333%\",\"3/6\":\"50%\",\"4/6\":\"66.666667%\",\"5/6\":\"83.333333%\",full:\"100%\",screen:\"100vh\",svh:\"100svh\",lvh:\"100lvh\",dvh:\"100dvh\",min:\"min-content\",max:\"max-content\",fit:\"fit-content\"}),hueRotate:{0:\"0deg\",15:\"15deg\",30:\"30deg\",60:\"60deg\",90:\"90deg\",180:\"180deg\"},inset:({theme:r})=>({auto:\"auto\",...r(\"spacing\"),\"1/2\":\"50%\",\"1/3\":\"33.333333%\",\"2/3\":\"66.666667%\",\"1/4\":\"25%\",\"2/4\":\"50%\",\"3/4\":\"75%\",full:\"100%\"}),invert:{0:\"0\",DEFAULT:\"100%\"},keyframes:{spin:{to:{transform:\"rotate(360deg)\"}},ping:{\"75%, 100%\":{transform:\"scale(2)\",opacity:\"0\"}},pulse:{\"50%\":{opacity:\".5\"}},bounce:{\"0%, 100%\":{transform:\"translateY(-25%)\",animationTimingFunction:\"cubic-bezier(0.8,0,1,1)\"},\"50%\":{transform:\"none\",animationTimingFunction:\"cubic-bezier(0,0,0.2,1)\"}}},letterSpacing:{tighter:\"-0.05em\",tight:\"-0.025em\",normal:\"0em\",wide:\"0.025em\",wider:\"0.05em\",widest:\"0.1em\"},lineHeight:{none:\"1\",tight:\"1.25\",snug:\"1.375\",normal:\"1.5\",relaxed:\"1.625\",loose:\"2\",3:\".75rem\",4:\"1rem\",5:\"1.25rem\",6:\"1.5rem\",7:\"1.75rem\",8:\"2rem\",9:\"2.25rem\",10:\"2.5rem\"},listStyleType:{none:\"none\",disc:\"disc\",decimal:\"decimal\"},listStyleImage:{none:\"none\"},margin:({theme:r})=>({auto:\"auto\",...r(\"spacing\")}),lineClamp:{1:\"1\",2:\"2\",3:\"3\",4:\"4\",5:\"5\",6:\"6\"},maxHeight:({theme:r})=>({...r(\"spacing\"),none:\"none\",full:\"100%\",screen:\"100vh\",svh:\"100svh\",lvh:\"100lvh\",dvh:\"100dvh\",min:\"min-content\",max:\"max-content\",fit:\"fit-content\"}),maxWidth:({theme:r,breakpoints:e})=>({...r(\"spacing\"),none:\"none\",xs:\"20rem\",sm:\"24rem\",md:\"28rem\",lg:\"32rem\",xl:\"36rem\",\"2xl\":\"42rem\",\"3xl\":\"48rem\",\"4xl\":\"56rem\",\"5xl\":\"64rem\",\"6xl\":\"72rem\",\"7xl\":\"80rem\",full:\"100%\",min:\"min-content\",max:\"max-content\",fit:\"fit-content\",prose:\"65ch\",...e(r(\"screens\"))}),minHeight:({theme:r})=>({...r(\"spacing\"),full:\"100%\",screen:\"100vh\",svh:\"100svh\",lvh:\"100lvh\",dvh:\"100dvh\",min:\"min-content\",max:\"max-content\",fit:\"fit-content\"}),minWidth:({theme:r})=>({...r(\"spacing\"),full:\"100%\",min:\"min-content\",max:\"max-content\",fit:\"fit-content\"}),objectPosition:{bottom:\"bottom\",center:\"center\",left:\"left\",\"left-bottom\":\"left bottom\",\"left-top\":\"left top\",right:\"right\",\"right-bottom\":\"right bottom\",\"right-top\":\"right top\",top:\"top\"},opacity:{0:\"0\",5:\"0.05\",10:\"0.1\",15:\"0.15\",20:\"0.2\",25:\"0.25\",30:\"0.3\",35:\"0.35\",40:\"0.4\",45:\"0.45\",50:\"0.5\",55:\"0.55\",60:\"0.6\",65:\"0.65\",70:\"0.7\",75:\"0.75\",80:\"0.8\",85:\"0.85\",90:\"0.9\",95:\"0.95\",100:\"1\"},order:{first:\"-9999\",last:\"9999\",none:\"0\",1:\"1\",2:\"2\",3:\"3\",4:\"4\",5:\"5\",6:\"6\",7:\"7\",8:\"8\",9:\"9\",10:\"10\",11:\"11\",12:\"12\"},outlineColor:({theme:r})=>r(\"colors\"),outlineOffset:{0:\"0px\",1:\"1px\",2:\"2px\",4:\"4px\",8:\"8px\"},outlineWidth:{0:\"0px\",1:\"1px\",2:\"2px\",4:\"4px\",8:\"8px\"},padding:({theme:r})=>r(\"spacing\"),placeholderColor:({theme:r})=>r(\"colors\"),placeholderOpacity:({theme:r})=>r(\"opacity\"),ringColor:({theme:r})=>({DEFAULT:r(\"colors.blue.500\",\"#3b82f6\"),...r(\"colors\")}),ringOffsetColor:({theme:r})=>r(\"colors\"),ringOffsetWidth:{0:\"0px\",1:\"1px\",2:\"2px\",4:\"4px\",8:\"8px\"},ringOpacity:({theme:r})=>({DEFAULT:\"0.5\",...r(\"opacity\")}),ringWidth:{DEFAULT:\"3px\",0:\"0px\",1:\"1px\",2:\"2px\",4:\"4px\",8:\"8px\"},rotate:{0:\"0deg\",1:\"1deg\",2:\"2deg\",3:\"3deg\",6:\"6deg\",12:\"12deg\",45:\"45deg\",90:\"90deg\",180:\"180deg\"},saturate:{0:\"0\",50:\".5\",100:\"1\",150:\"1.5\",200:\"2\"},scale:{0:\"0\",50:\".5\",75:\".75\",90:\".9\",95:\".95\",100:\"1\",105:\"1.05\",110:\"1.1\",125:\"1.25\",150:\"1.5\"},screens:{sm:\"640px\",md:\"768px\",lg:\"1024px\",xl:\"1280px\",\"2xl\":\"1536px\"},scrollMargin:({theme:r})=>({...r(\"spacing\")}),scrollPadding:({theme:r})=>r(\"spacing\"),sepia:{0:\"0\",DEFAULT:\"100%\"},skew:{0:\"0deg\",1:\"1deg\",2:\"2deg\",3:\"3deg\",6:\"6deg\",12:\"12deg\"},space:({theme:r})=>({...r(\"spacing\")}),spacing:{px:\"1px\",0:\"0px\",.5:\"0.125rem\",1:\"0.25rem\",1.5:\"0.375rem\",2:\"0.5rem\",2.5:\"0.625rem\",3:\"0.75rem\",3.5:\"0.875rem\",4:\"1rem\",5:\"1.25rem\",6:\"1.5rem\",7:\"1.75rem\",8:\"2rem\",9:\"2.25rem\",10:\"2.5rem\",11:\"2.75rem\",12:\"3rem\",14:\"3.5rem\",16:\"4rem\",20:\"5rem\",24:\"6rem\",28:\"7rem\",32:\"8rem\",36:\"9rem\",40:\"10rem\",44:\"11rem\",48:\"12rem\",52:\"13rem\",56:\"14rem\",60:\"15rem\",64:\"16rem\",72:\"18rem\",80:\"20rem\",96:\"24rem\"},stroke:({theme:r})=>({none:\"none\",...r(\"colors\")}),strokeWidth:{0:\"0\",1:\"1\",2:\"2\"},supports:{},data:{},textColor:({theme:r})=>r(\"colors\"),textDecorationColor:({theme:r})=>r(\"colors\"),textDecorationThickness:{auto:\"auto\",\"from-font\":\"from-font\",0:\"0px\",1:\"1px\",2:\"2px\",4:\"4px\",8:\"8px\"},textIndent:({theme:r})=>({...r(\"spacing\")}),textOpacity:({theme:r})=>r(\"opacity\"),textUnderlineOffset:{auto:\"auto\",0:\"0px\",1:\"1px\",2:\"2px\",4:\"4px\",8:\"8px\"},transformOrigin:{center:\"center\",top:\"top\",\"top-right\":\"top right\",right:\"right\",\"bottom-right\":\"bottom right\",bottom:\"bottom\",\"bottom-left\":\"bottom left\",left:\"left\",\"top-left\":\"top left\"},transitionDelay:{0:\"0s\",75:\"75ms\",100:\"100ms\",150:\"150ms\",200:\"200ms\",300:\"300ms\",500:\"500ms\",700:\"700ms\",1e3:\"1000ms\"},transitionDuration:{DEFAULT:\"150ms\",0:\"0s\",75:\"75ms\",100:\"100ms\",150:\"150ms\",200:\"200ms\",300:\"300ms\",500:\"500ms\",700:\"700ms\",1e3:\"1000ms\"},transitionProperty:{none:\"none\",all:\"all\",DEFAULT:\"color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter\",colors:\"color, background-color, border-color, text-decoration-color, fill, stroke\",opacity:\"opacity\",shadow:\"box-shadow\",transform:\"transform\"},transitionTimingFunction:{DEFAULT:\"cubic-bezier(0.4, 0, 0.2, 1)\",linear:\"linear\",in:\"cubic-bezier(0.4, 0, 1, 1)\",out:\"cubic-bezier(0, 0, 0.2, 1)\",\"in-out\":\"cubic-bezier(0.4, 0, 0.2, 1)\"},translate:({theme:r})=>({...r(\"spacing\"),\"1/2\":\"50%\",\"1/3\":\"33.333333%\",\"2/3\":\"66.666667%\",\"1/4\":\"25%\",\"2/4\":\"50%\",\"3/4\":\"75%\",full:\"100%\"}),size:({theme:r})=>({auto:\"auto\",...r(\"spacing\"),\"1/2\":\"50%\",\"1/3\":\"33.333333%\",\"2/3\":\"66.666667%\",\"1/4\":\"25%\",\"2/4\":\"50%\",\"3/4\":\"75%\",\"1/5\":\"20%\",\"2/5\":\"40%\",\"3/5\":\"60%\",\"4/5\":\"80%\",\"1/6\":\"16.666667%\",\"2/6\":\"33.333333%\",\"3/6\":\"50%\",\"4/6\":\"66.666667%\",\"5/6\":\"83.333333%\",\"1/12\":\"8.333333%\",\"2/12\":\"16.666667%\",\"3/12\":\"25%\",\"4/12\":\"33.333333%\",\"5/12\":\"41.666667%\",\"6/12\":\"50%\",\"7/12\":\"58.333333%\",\"8/12\":\"66.666667%\",\"9/12\":\"75%\",\"10/12\":\"83.333333%\",\"11/12\":\"91.666667%\",full:\"100%\",min:\"min-content\",max:\"max-content\",fit:\"fit-content\"}),width:({theme:r})=>({auto:\"auto\",...r(\"spacing\"),\"1/2\":\"50%\",\"1/3\":\"33.333333%\",\"2/3\":\"66.666667%\",\"1/4\":\"25%\",\"2/4\":\"50%\",\"3/4\":\"75%\",\"1/5\":\"20%\",\"2/5\":\"40%\",\"3/5\":\"60%\",\"4/5\":\"80%\",\"1/6\":\"16.666667%\",\"2/6\":\"33.333333%\",\"3/6\":\"50%\",\"4/6\":\"66.666667%\",\"5/6\":\"83.333333%\",\"1/12\":\"8.333333%\",\"2/12\":\"16.666667%\",\"3/12\":\"25%\",\"4/12\":\"33.333333%\",\"5/12\":\"41.666667%\",\"6/12\":\"50%\",\"7/12\":\"58.333333%\",\"8/12\":\"66.666667%\",\"9/12\":\"75%\",\"10/12\":\"83.333333%\",\"11/12\":\"91.666667%\",full:\"100%\",screen:\"100vw\",svw:\"100svw\",lvw:\"100lvw\",dvw:\"100dvw\",min:\"min-content\",max:\"max-content\",fit:\"fit-content\"}),willChange:{auto:\"auto\",scroll:\"scroll-position\",contents:\"contents\",transform:\"transform\"},zIndex:{auto:\"auto\",0:\"0\",10:\"10\",20:\"20\",30:\"30\",40:\"40\",50:\"50\"}},plugins:[]}});function nn(r){let e=(r?.presets??[ic.default]).slice().reverse().flatMap(n=>nn(n instanceof Function?n():n)),t={respectDefaultRingColorOpacity:{theme:{ringColor:({theme:n})=>({DEFAULT:\"#3b82f67f\",...n(\"colors\")})}},disableColorOpacityUtilitiesByDefault:{corePlugins:{backgroundOpacity:!1,borderOpacity:!1,divideOpacity:!1,placeholderOpacity:!1,ringOpacity:!1,textOpacity:!1}}},i=Object.keys(t).filter(n=>we(r,n)).map(n=>t[n]);return[r,...i,...e]}var ic,nc=P(()=>{u();ic=pe(rn());ct()});var sc={};Ge(sc,{default:()=>zr});function zr(...r){let[,...e]=nn(r[0]);return na([...r,...e])}var sa=P(()=>{u();tc();nc()});var Ur={};Ge(Ur,{default:()=>me});var me,et=P(()=>{u();me={resolve:r=>r,extname:r=>\".\"+r.split(\".\").pop()}});function sn(r){return typeof r==\"object\"&&r!==null}function bx(r){return Object.keys(r).length===0}function ac(r){return typeof r==\"string\"||r instanceof String}function aa(r){return sn(r)&&r.config===void 0&&!bx(r)?null:sn(r)&&r.config!==void 0&&ac(r.config)?me.resolve(r.config):sn(r)&&r.config!==void 0&&sn(r.config)?null:ac(r)?me.resolve(r):wx()}function wx(){for(let r of yx)try{let e=me.resolve(r);return be.accessSync(e),e}catch(e){}return null}var yx,oc=P(()=>{u();ft();et();yx=[\"./tailwind.config.js\",\"./tailwind.config.cjs\",\"./tailwind.config.mjs\",\"./tailwind.config.ts\",\"./tailwind.config.cts\",\"./tailwind.config.mts\"]});var lc={};Ge(lc,{default:()=>oa});var oa,la=P(()=>{u();oa={parse:r=>({href:r})}});var ua=x(()=>{u()});var an=x((v3,cc)=>{u();\"use strict\";var uc=(Qi(),Af),fc=ua(),Jt=class extends Error{constructor(e,t,i,n,s,a){super(e);this.name=\"CssSyntaxError\",this.reason=e,s&&(this.file=s),n&&(this.source=n),a&&(this.plugin=a),typeof t!=\"undefined\"&&typeof i!=\"undefined\"&&(typeof t==\"number\"?(this.line=t,this.column=i):(this.line=t.line,this.column=t.column,this.endLine=i.line,this.endColumn=i.column)),this.setMessage(),Error.captureStackTrace&&Error.captureStackTrace(this,Jt)}setMessage(){this.message=this.plugin?this.plugin+\": \":\"\",this.message+=this.file?this.file:\"\",typeof this.line!=\"undefined\"&&(this.message+=\":\"+this.line+\":\"+this.column),this.message+=\": \"+this.reason}showSourceCode(e){if(!this.source)return\"\";let t=this.source;e==null&&(e=uc.isColorSupported);let i=f=>f,n=f=>f,s=f=>f;if(e){let{bold:f,gray:d,red:p}=uc.createColors(!0);n=h=>f(p(h)),i=h=>d(h),fc&&(s=h=>fc(h))}let a=t.split(/\\r?\\n/),o=Math.max(this.line-3,0),l=Math.min(this.line+2,a.length),c=String(l).length;return a.slice(o,l).map((f,d)=>{let p=o+1+d,h=\" \"+(\" \"+p).slice(-c)+\" | \";if(p===this.line){if(f.length>160){let v=20,y=Math.max(0,this.column-v),w=Math.max(this.column+v,this.endColumn+v),k=f.slice(y,w),S=i(h.replace(/\\d/g,\" \"))+f.slice(0,Math.min(this.column-1,v-1)).replace(/[^\\t]/g,\" \");return n(\">\")+i(h)+s(k)+`\n `+S+n(\"^\")}let b=i(h.replace(/\\d/g,\" \"))+f.slice(0,this.column-1).replace(/[^\\t]/g,\" \");return n(\">\")+i(h)+s(f)+`\n `+b+n(\"^\")}return\" \"+i(h)+s(f)}).join(`\n`)}toString(){let e=this.showSourceCode();return e&&(e=`\n\n`+e+`\n`),this.name+\": \"+this.message+e}};cc.exports=Jt;Jt.default=Jt});var fa=x((x3,dc)=>{u();\"use strict\";var pc={after:`\n`,beforeClose:`\n`,beforeComment:`\n`,beforeDecl:`\n`,beforeOpen:\" \",beforeRule:`\n`,colon:\": \",commentLeft:\" \",commentRight:\" \",emptyBody:\"\",indent:\" \",semicolon:!1};function vx(r){return r[0].toUpperCase()+r.slice(1)}var on=class{constructor(e){this.builder=e}atrule(e,t){let i=\"@\"+e.name,n=e.params?this.rawValue(e,\"params\"):\"\";if(typeof e.raws.afterName!=\"undefined\"?i+=e.raws.afterName:n&&(i+=\" \"),e.nodes)this.block(e,i+n);else{let s=(e.raws.between||\"\")+(t?\";\":\"\");this.builder(i+n+s,e)}}beforeAfter(e,t){let i;e.type===\"decl\"?i=this.raw(e,null,\"beforeDecl\"):e.type===\"comment\"?i=this.raw(e,null,\"beforeComment\"):t===\"before\"?i=this.raw(e,null,\"beforeRule\"):i=this.raw(e,null,\"beforeClose\");let n=e.parent,s=0;for(;n&&n.type!==\"root\";)s+=1,n=n.parent;if(i.includes(`\n`)){let a=this.raw(e,null,\"indent\");if(a.length)for(let o=0;o0&&e.nodes[t].type===\"comment\";)t-=1;let i=this.raw(e,\"semicolon\");for(let n=0;n{if(n=l.raws[t],typeof n!=\"undefined\")return!1})}return typeof n==\"undefined\"&&(n=pc[i]),a.rawCache[i]=n,n}rawBeforeClose(e){let t;return e.walk(i=>{if(i.nodes&&i.nodes.length>0&&typeof i.raws.after!=\"undefined\")return t=i.raws.after,t.includes(`\n`)&&(t=t.replace(/[^\\n]+$/,\"\")),!1}),t&&(t=t.replace(/\\S/g,\"\")),t}rawBeforeComment(e,t){let i;return e.walkComments(n=>{if(typeof n.raws.before!=\"undefined\")return i=n.raws.before,i.includes(`\n`)&&(i=i.replace(/[^\\n]+$/,\"\")),!1}),typeof i==\"undefined\"?i=this.raw(t,null,\"beforeDecl\"):i&&(i=i.replace(/\\S/g,\"\")),i}rawBeforeDecl(e,t){let i;return e.walkDecls(n=>{if(typeof n.raws.before!=\"undefined\")return i=n.raws.before,i.includes(`\n`)&&(i=i.replace(/[^\\n]+$/,\"\")),!1}),typeof i==\"undefined\"?i=this.raw(t,null,\"beforeRule\"):i&&(i=i.replace(/\\S/g,\"\")),i}rawBeforeOpen(e){let t;return e.walk(i=>{if(i.type!==\"decl\"&&(t=i.raws.between,typeof t!=\"undefined\"))return!1}),t}rawBeforeRule(e){let t;return e.walk(i=>{if(i.nodes&&(i.parent!==e||e.first!==i)&&typeof i.raws.before!=\"undefined\")return t=i.raws.before,t.includes(`\n`)&&(t=t.replace(/[^\\n]+$/,\"\")),!1}),t&&(t=t.replace(/\\S/g,\"\")),t}rawColon(e){let t;return e.walkDecls(i=>{if(typeof i.raws.between!=\"undefined\")return t=i.raws.between.replace(/[^\\s:]/g,\"\"),!1}),t}rawEmptyBody(e){let t;return e.walk(i=>{if(i.nodes&&i.nodes.length===0&&(t=i.raws.after,typeof t!=\"undefined\"))return!1}),t}rawIndent(e){if(e.raws.indent)return e.raws.indent;let t;return e.walk(i=>{let n=i.parent;if(n&&n!==e&&n.parent&&n.parent===e&&typeof i.raws.before!=\"undefined\"){let s=i.raws.before.split(`\n`);return t=s[s.length-1],t=t.replace(/\\S/g,\"\"),!1}}),t}rawSemicolon(e){let t;return e.walk(i=>{if(i.nodes&&i.nodes.length&&i.last.type===\"decl\"&&(t=i.raws.semicolon,typeof t!=\"undefined\"))return!1}),t}rawValue(e,t){let i=e[t],n=e.raws[t];return n&&n.value===i?n.raw:i}root(e){this.body(e),e.raws.after&&this.builder(e.raws.after)}rule(e){this.block(e,this.rawValue(e,\"selector\")),e.raws.ownSemicolon&&this.builder(e.raws.ownSemicolon,e,\"end\")}stringify(e,t){if(!this[e.type])throw new Error(\"Unknown AST node type \"+e.type+\". Maybe you need to change PostCSS stringifier.\");this[e.type](e,t)}};dc.exports=on;on.default=on});var Vr=x((k3,hc)=>{u();\"use strict\";var xx=fa();function ca(r,e){new xx(e).stringify(r)}hc.exports=ca;ca.default=ca});var ln=x((S3,pa)=>{u();\"use strict\";pa.exports.isClean=Symbol(\"isClean\");pa.exports.my=Symbol(\"my\")});var Gr=x((A3,mc)=>{u();\"use strict\";var kx=an(),Sx=fa(),Ax=Vr(),{isClean:Hr,my:Cx}=ln();function da(r,e){let t=new r.constructor;for(let i in r){if(!Object.prototype.hasOwnProperty.call(r,i)||i===\"proxyCache\")continue;let n=r[i],s=typeof n;i===\"parent\"&&s===\"object\"?e&&(t[i]=e):i===\"source\"?t[i]=n:Array.isArray(n)?t[i]=n.map(a=>da(a,t)):(s===\"object\"&&n!==null&&(n=da(n)),t[i]=n)}return t}function Wr(r,e){if(e&&typeof e.offset!=\"undefined\")return e.offset;let t=1,i=1,n=0;for(let s=0;se.root().toProxy():e[t]},set(e,t,i){return e[t]===i||(e[t]=i,(t===\"prop\"||t===\"value\"||t===\"name\"||t===\"params\"||t===\"important\"||t===\"text\")&&e.markDirty()),!0}}}markClean(){this[Hr]=!0}markDirty(){if(this[Hr]){this[Hr]=!1;let e=this;for(;e=e.parent;)e[Hr]=!1}}next(){if(!this.parent)return;let e=this.parent.index(this);return this.parent.nodes[e+1]}positionBy(e){let t=this.source.start;if(e.index)t=this.positionInside(e.index);else if(e.word){let n=this.source.input.css.slice(Wr(this.source.input.css,this.source.start),Wr(this.source.input.css,this.source.end)).indexOf(e.word);n!==-1&&(t=this.positionInside(n))}return t}positionInside(e){let t=this.source.start.column,i=this.source.start.line,n=Wr(this.source.input.css,this.source.start),s=n+e;for(let a=n;atypeof l==\"object\"&&l.toJSON?l.toJSON(null,t):l);else if(typeof o==\"object\"&&o.toJSON)i[a]=o.toJSON(null,t);else if(a===\"source\"){let l=t.get(o.input);l==null&&(l=s,t.set(o.input,s),s++),i[a]={end:o.end,inputId:l,start:o.start}}else i[a]=o}return n&&(i.inputs=[...t.keys()].map(a=>a.toJSON())),i}toProxy(){return this.proxyCache||(this.proxyCache=new Proxy(this,this.getProxyProcessor())),this.proxyCache}toString(e=Ax){e.stringify&&(e=e.stringify);let t=\"\";return e(this,i=>{t+=i}),t}warn(e,t,i){let n={node:this};for(let s in i)n[s]=i[s];return e.warn(t,n)}get proxyOf(){return this}};mc.exports=un;un.default=un});var Qr=x((C3,gc)=>{u();\"use strict\";var _x=Gr(),fn=class extends _x{constructor(e){super(e);this.type=\"comment\"}};gc.exports=fn;fn.default=fn});var Yr=x((_3,yc)=>{u();\"use strict\";var Ex=Gr(),cn=class extends Ex{constructor(e){e&&typeof e.value!=\"undefined\"&&typeof e.value!=\"string\"&&(e={...e,value:String(e.value)});super(e);this.type=\"decl\"}get variable(){return this.prop.startsWith(\"--\")||this.prop[0]===\"$\"}};yc.exports=cn;cn.default=cn});var Et=x((E3,_c)=>{u();\"use strict\";var bc=Qr(),wc=Yr(),Ox=Gr(),{isClean:vc,my:xc}=ln(),ha,kc,Sc,ma;function Ac(r){return r.map(e=>(e.nodes&&(e.nodes=Ac(e.nodes)),delete e.source,e))}function Cc(r){if(r[vc]=!1,r.proxyOf.nodes)for(let e of r.proxyOf.nodes)Cc(e)}var Fe=class extends Ox{append(...e){for(let t of e){let i=this.normalize(t,this.last);for(let n of i)this.proxyOf.nodes.push(n)}return this.markDirty(),this}cleanRaws(e){if(super.cleanRaws(e),this.nodes)for(let t of this.nodes)t.cleanRaws(e)}each(e){if(!this.proxyOf.nodes)return;let t=this.getIterator(),i,n;for(;this.indexes[t]e[t](...i.map(n=>typeof n==\"function\"?(s,a)=>n(s.toProxy(),a):n)):t===\"every\"||t===\"some\"?i=>e[t]((n,...s)=>i(n.toProxy(),...s)):t===\"root\"?()=>e.root().toProxy():t===\"nodes\"?e.nodes.map(i=>i.toProxy()):t===\"first\"||t===\"last\"?e[t].toProxy():e[t]:e[t]},set(e,t,i){return e[t]===i||(e[t]=i,(t===\"name\"||t===\"params\"||t===\"selector\")&&e.markDirty()),!0}}}index(e){return typeof e==\"number\"?e:(e.proxyOf&&(e=e.proxyOf),this.proxyOf.nodes.indexOf(e))}insertAfter(e,t){let i=this.index(e),n=this.normalize(t,this.proxyOf.nodes[i]).reverse();i=this.index(e);for(let a of n)this.proxyOf.nodes.splice(i+1,0,a);let s;for(let a in this.indexes)s=this.indexes[a],i(n[xc]||Fe.rebuild(n),n=n.proxyOf,n.parent&&n.parent.removeChild(n),n[vc]&&Cc(n),n.raws||(n.raws={}),typeof n.raws.before==\"undefined\"&&t&&typeof t.raws.before!=\"undefined\"&&(n.raws.before=t.raws.before.replace(/\\S/g,\"\")),n.parent=this.proxyOf,n))}prepend(...e){e=e.reverse();for(let t of e){let i=this.normalize(t,this.first,\"prepend\").reverse();for(let n of i)this.proxyOf.nodes.unshift(n);for(let n in this.indexes)this.indexes[n]=this.indexes[n]+i.length}return this.markDirty(),this}push(e){return e.parent=this,this.proxyOf.nodes.push(e),this}removeAll(){for(let e of this.proxyOf.nodes)e.parent=void 0;return this.proxyOf.nodes=[],this.markDirty(),this}removeChild(e){e=this.index(e),this.proxyOf.nodes[e].parent=void 0,this.proxyOf.nodes.splice(e,1);let t;for(let i in this.indexes)t=this.indexes[i],t>=e&&(this.indexes[i]=t-1);return this.markDirty(),this}replaceValues(e,t,i){return i||(i=t,t={}),this.walkDecls(n=>{t.props&&!t.props.includes(n.prop)||t.fast&&!n.value.includes(t.fast)||(n.value=n.value.replace(e,i))}),this.markDirty(),this}some(e){return this.nodes.some(e)}walk(e){return this.each((t,i)=>{let n;try{n=e(t,i)}catch(s){throw t.addToError(s)}return n!==!1&&t.walk&&(n=t.walk(e)),n})}walkAtRules(e,t){return t?e instanceof RegExp?this.walk((i,n)=>{if(i.type===\"atrule\"&&e.test(i.name))return t(i,n)}):this.walk((i,n)=>{if(i.type===\"atrule\"&&i.name===e)return t(i,n)}):(t=e,this.walk((i,n)=>{if(i.type===\"atrule\")return t(i,n)}))}walkComments(e){return this.walk((t,i)=>{if(t.type===\"comment\")return e(t,i)})}walkDecls(e,t){return t?e instanceof RegExp?this.walk((i,n)=>{if(i.type===\"decl\"&&e.test(i.prop))return t(i,n)}):this.walk((i,n)=>{if(i.type===\"decl\"&&i.prop===e)return t(i,n)}):(t=e,this.walk((i,n)=>{if(i.type===\"decl\")return t(i,n)}))}walkRules(e,t){return t?e instanceof RegExp?this.walk((i,n)=>{if(i.type===\"rule\"&&e.test(i.selector))return t(i,n)}):this.walk((i,n)=>{if(i.type===\"rule\"&&i.selector===e)return t(i,n)}):(t=e,this.walk((i,n)=>{if(i.type===\"rule\")return t(i,n)}))}get first(){if(!!this.proxyOf.nodes)return this.proxyOf.nodes[0]}get last(){if(!!this.proxyOf.nodes)return this.proxyOf.nodes[this.proxyOf.nodes.length-1]}};Fe.registerParse=r=>{kc=r};Fe.registerRule=r=>{ma=r};Fe.registerAtRule=r=>{ha=r};Fe.registerRoot=r=>{Sc=r};_c.exports=Fe;Fe.default=Fe;Fe.rebuild=r=>{r.type===\"atrule\"?Object.setPrototypeOf(r,ha.prototype):r.type===\"rule\"?Object.setPrototypeOf(r,ma.prototype):r.type===\"decl\"?Object.setPrototypeOf(r,wc.prototype):r.type===\"comment\"?Object.setPrototypeOf(r,bc.prototype):r.type===\"root\"&&Object.setPrototypeOf(r,Sc.prototype),r[xc]=!0,r.nodes&&r.nodes.forEach(e=>{Fe.rebuild(e)})}});var pn=x((O3,Oc)=>{u();\"use strict\";var Ec=Et(),Kr=class extends Ec{constructor(e){super(e);this.type=\"atrule\"}append(...e){return this.proxyOf.nodes||(this.nodes=[]),super.append(...e)}prepend(...e){return this.proxyOf.nodes||(this.nodes=[]),super.prepend(...e)}};Oc.exports=Kr;Kr.default=Kr;Ec.registerAtRule(Kr)});var dn=x((T3,Pc)=>{u();\"use strict\";var Tx=Et(),Tc,Rc,er=class extends Tx{constructor(e){super({type:\"document\",...e});this.nodes||(this.nodes=[])}toResult(e={}){return new Tc(new Rc,this,e).stringify()}};er.registerLazyResult=r=>{Tc=r};er.registerProcessor=r=>{Rc=r};Pc.exports=er;er.default=er});var Dc=x((R3,Ic)=>{u();var Rx=\"useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict\",Px=(r,e=21)=>(t=e)=>{let i=\"\",n=t;for(;n--;)i+=r[Math.random()*r.length|0];return i},Ix=(r=21)=>{let e=\"\",t=r;for(;t--;)e+=Rx[Math.random()*64|0];return e};Ic.exports={nanoid:Ix,customAlphabet:Px}});var qc=x(()=>{u()});var ga=x((D3,$c)=>{u();$c.exports={}});var mn=x((q3,Bc)=>{u();\"use strict\";var{nanoid:Dx}=Dc(),{isAbsolute:ya,resolve:ba}=(et(),Ur),{SourceMapConsumer:qx,SourceMapGenerator:$x}=qc(),{fileURLToPath:Lc,pathToFileURL:hn}=(la(),lc),Mc=an(),Lx=ga(),wa=ua(),va=Symbol(\"fromOffsetCache\"),Mx=Boolean(qx&&$x),Nc=Boolean(ba&&ya),Xr=class{constructor(e,t={}){if(e===null||typeof e==\"undefined\"||typeof e==\"object\"&&!e.toString)throw new Error(`PostCSS received ${e} instead of CSS string`);if(this.css=e.toString(),this.css[0]===\"\\uFEFF\"||this.css[0]===\"\\uFFFE\"?(this.hasBOM=!0,this.css=this.css.slice(1)):this.hasBOM=!1,t.from&&(!Nc||/^\\w+:\\/\\//.test(t.from)||ya(t.from)?this.file=t.from:this.file=ba(t.from)),Nc&&Mx){let i=new Lx(this.css,t);if(i.text){this.map=i;let n=i.consumer().file;!this.file&&n&&(this.file=this.mapResolve(n))}}this.file||(this.id=\"\"),this.map&&(this.map.file=this.from)}error(e,t,i,n={}){let s,a,o;if(t&&typeof t==\"object\"){let c=t,f=i;if(typeof c.offset==\"number\"){let d=this.fromOffset(c.offset);t=d.line,i=d.col}else t=c.line,i=c.column;if(typeof f.offset==\"number\"){let d=this.fromOffset(f.offset);a=d.line,s=d.col}else a=f.line,s=f.column}else if(!i){let c=this.fromOffset(t);t=c.line,i=c.col}let l=this.origin(t,i,a,s);return l?o=new Mc(e,l.endLine===void 0?l.line:{column:l.column,line:l.line},l.endLine===void 0?l.column:{column:l.endColumn,line:l.endLine},l.source,l.file,n.plugin):o=new Mc(e,a===void 0?t:{column:i,line:t},a===void 0?i:{column:s,line:a},this.css,this.file,n.plugin),o.input={column:i,endColumn:s,endLine:a,line:t,source:this.css},this.file&&(hn&&(o.input.url=hn(this.file).toString()),o.input.file=this.file),o}fromOffset(e){let t,i;if(this[va])i=this[va];else{let s=this.css.split(`\n`);i=new Array(s.length);let a=0;for(let o=0,l=s.length;o=t)n=i.length-1;else{let s=i.length-2,a;for(;n>1),e=i[a+1])n=a+1;else{n=a;break}}return{col:e-i[n]+1,line:n+1}}mapResolve(e){return/^\\w+:\\/\\//.test(e)?e:ba(this.map.consumer().sourceRoot||this.map.root||\".\",e)}origin(e,t,i,n){if(!this.map)return!1;let s=this.map.consumer(),a=s.originalPositionFor({column:t,line:e});if(!a.source)return!1;let o;typeof i==\"number\"&&(o=s.originalPositionFor({column:n,line:i}));let l;ya(a.source)?l=hn(a.source):l=new URL(a.source,this.map.consumer().sourceRoot||hn(this.map.mapFile));let c={column:a.column,endColumn:o&&o.column,endLine:o&&o.line,line:a.line,url:l.toString()};if(l.protocol===\"file:\")if(Lc)c.file=Lc(l);else throw new Error(\"file: protocol is not available in this PostCSS build\");let f=s.sourceContentFor(a.source);return f&&(c.source=f),c}toJSON(){let e={};for(let t of[\"hasBOM\",\"css\",\"file\",\"id\"])this[t]!=null&&(e[t]=this[t]);return this.map&&(e.map={...this.map},e.map.consumerCache&&(e.map.consumerCache=void 0)),e}get from(){return this.file||this.id}};Bc.exports=Xr;Xr.default=Xr;wa&&wa.registerInput&&wa.registerInput(Xr)});var tr=x(($3,Uc)=>{u();\"use strict\";var Fc=Et(),jc,zc,Ut=class extends Fc{constructor(e){super(e);this.type=\"root\",this.nodes||(this.nodes=[])}normalize(e,t,i){let n=super.normalize(e);if(t){if(i===\"prepend\")this.nodes.length>1?t.raws.before=this.nodes[1].raws.before:delete t.raws.before;else if(this.first!==t)for(let s of n)s.raws.before=t.raws.before}return n}removeChild(e,t){let i=this.index(e);return!t&&i===0&&this.nodes.length>1&&(this.nodes[1].raws.before=this.nodes[i].raws.before),super.removeChild(e)}toResult(e={}){return new jc(new zc,this,e).stringify()}};Ut.registerLazyResult=r=>{jc=r};Ut.registerProcessor=r=>{zc=r};Uc.exports=Ut;Ut.default=Ut;Fc.registerRoot(Ut)});var xa=x((L3,Vc)=>{u();\"use strict\";var Zr={comma(r){return Zr.split(r,[\",\"],!0)},space(r){let e=[\" \",`\n`,\"\t\"];return Zr.split(r,e)},split(r,e,t){let i=[],n=\"\",s=!1,a=0,o=!1,l=\"\",c=!1;for(let f of r)c?c=!1:f===\"\\\\\"?c=!0:o?f===l&&(o=!1):f==='\"'||f===\"'\"?(o=!0,l=f):f===\"(\"?a+=1:f===\")\"?a>0&&(a-=1):a===0&&e.includes(f)&&(s=!0),s?(n!==\"\"&&i.push(n.trim()),n=\"\",s=!1):n+=f;return(t||n!==\"\")&&i.push(n.trim()),i}};Vc.exports=Zr;Zr.default=Zr});var gn=x((M3,Wc)=>{u();\"use strict\";var Hc=Et(),Nx=xa(),Jr=class extends Hc{constructor(e){super(e);this.type=\"rule\",this.nodes||(this.nodes=[])}get selectors(){return Nx.comma(this.selector)}set selectors(e){let t=this.selector?this.selector.match(/,\\s*/):null,i=t?t[0]:\",\"+this.raw(\"between\",\"beforeOpen\");this.selector=e.join(i)}};Wc.exports=Jr;Jr.default=Jr;Hc.registerRule(Jr)});var Qc=x((N3,Gc)=>{u();\"use strict\";var Bx=pn(),Fx=Qr(),jx=Yr(),zx=mn(),Ux=ga(),Vx=tr(),Hx=gn();function ei(r,e){if(Array.isArray(r))return r.map(n=>ei(n));let{inputs:t,...i}=r;if(t){e=[];for(let n of t){let s={...n,__proto__:zx.prototype};s.map&&(s.map={...s.map,__proto__:Ux.prototype}),e.push(s)}}if(i.nodes&&(i.nodes=r.nodes.map(n=>ei(n,e))),i.source){let{inputId:n,...s}=i.source;i.source=s,n!=null&&(i.source.input=e[n])}if(i.type===\"root\")return new Vx(i);if(i.type===\"decl\")return new jx(i);if(i.type===\"rule\")return new Hx(i);if(i.type===\"comment\")return new Fx(i);if(i.type===\"atrule\")return new Bx(i);throw new Error(\"Unknown node type: \"+r.type)}Gc.exports=ei;ei.default=ei});var ka=x((B3,Yc)=>{u();Yc.exports=function(r,e){return{generate:()=>{let t=\"\";return r(e,i=>{t+=i}),[t]}}}});var ep=x((F3,Jc)=>{u();\"use strict\";var Sa=\"'\".charCodeAt(0),Kc='\"'.charCodeAt(0),yn=\"\\\\\".charCodeAt(0),Xc=\"/\".charCodeAt(0),bn=`\n`.charCodeAt(0),ti=\" \".charCodeAt(0),wn=\"\\f\".charCodeAt(0),vn=\"\t\".charCodeAt(0),xn=\"\\r\".charCodeAt(0),Wx=\"[\".charCodeAt(0),Gx=\"]\".charCodeAt(0),Qx=\"(\".charCodeAt(0),Yx=\")\".charCodeAt(0),Kx=\"{\".charCodeAt(0),Xx=\"}\".charCodeAt(0),Zx=\";\".charCodeAt(0),Jx=\"*\".charCodeAt(0),e1=\":\".charCodeAt(0),t1=\"@\".charCodeAt(0),kn=/[\\t\\n\\f\\r \"#'()/;[\\\\\\]{}]/g,Sn=/[\\t\\n\\f\\r !\"#'():;@[\\\\\\]{}]|\\/(?=\\*)/g,r1=/.[\\r\\n\"'(/\\\\]/,Zc=/[\\da-f]/i;Jc.exports=function(e,t={}){let i=e.css.valueOf(),n=t.ignoreErrors,s,a,o,l,c,f,d,p,h,b,v=i.length,y=0,w=[],k=[];function S(){return y}function E(R){throw e.error(\"Unclosed \"+R,y)}function T(){return k.length===0&&y>=v}function B(R){if(k.length)return k.pop();if(y>=v)return;let F=R?R.ignoreUnclosed:!1;switch(s=i.charCodeAt(y),s){case bn:case ti:case vn:case xn:case wn:{l=y;do l+=1,s=i.charCodeAt(l);while(s===ti||s===bn||s===vn||s===xn||s===wn);f=[\"space\",i.slice(y,l)],y=l-1;break}case Wx:case Gx:case Kx:case Xx:case e1:case Zx:case Yx:{let Y=String.fromCharCode(s);f=[Y,Y,y];break}case Qx:{if(b=w.length?w.pop()[1]:\"\",h=i.charCodeAt(y+1),b===\"url\"&&h!==Sa&&h!==Kc&&h!==ti&&h!==bn&&h!==vn&&h!==wn&&h!==xn){l=y;do{if(d=!1,l=i.indexOf(\")\",l+1),l===-1)if(n||F){l=y;break}else E(\"bracket\");for(p=l;i.charCodeAt(p-1)===yn;)p-=1,d=!d}while(d);f=[\"brackets\",i.slice(y,l+1),y,l],y=l}else l=i.indexOf(\")\",y+1),a=i.slice(y,l+1),l===-1||r1.test(a)?f=[\"(\",\"(\",y]:(f=[\"brackets\",a,y,l],y=l);break}case Sa:case Kc:{c=s===Sa?\"'\":'\"',l=y;do{if(d=!1,l=i.indexOf(c,l+1),l===-1)if(n||F){l=y+1;break}else E(\"string\");for(p=l;i.charCodeAt(p-1)===yn;)p-=1,d=!d}while(d);f=[\"string\",i.slice(y,l+1),y,l],y=l;break}case t1:{kn.lastIndex=y+1,kn.test(i),kn.lastIndex===0?l=i.length-1:l=kn.lastIndex-2,f=[\"at-word\",i.slice(y,l+1),y,l],y=l;break}case yn:{for(l=y,o=!0;i.charCodeAt(l+1)===yn;)l+=1,o=!o;if(s=i.charCodeAt(l+1),o&&s!==Xc&&s!==ti&&s!==bn&&s!==vn&&s!==xn&&s!==wn&&(l+=1,Zc.test(i.charAt(l)))){for(;Zc.test(i.charAt(l+1));)l+=1;i.charCodeAt(l+1)===ti&&(l+=1)}f=[\"word\",i.slice(y,l+1),y,l],y=l;break}default:{s===Xc&&i.charCodeAt(y+1)===Jx?(l=i.indexOf(\"*/\",y+2)+1,l===0&&(n||F?l=i.length:E(\"comment\")),f=[\"comment\",i.slice(y,l+1),y,l],y=l):(Sn.lastIndex=y+1,Sn.test(i),Sn.lastIndex===0?l=i.length-1:l=Sn.lastIndex-2,f=[\"word\",i.slice(y,l+1),y,l],w.push(f),y=l);break}}return y++,f}function N(R){k.push(R)}return{back:N,endOfFile:T,nextToken:B,position:S}}});var sp=x((j3,np)=>{u();\"use strict\";var i1=pn(),n1=Qr(),s1=Yr(),a1=tr(),tp=gn(),o1=ep(),rp={empty:!0,space:!0};function l1(r){for(let e=r.length-1;e>=0;e--){let t=r[e],i=t[3]||t[2];if(i)return i}}var ip=class{constructor(e){this.input=e,this.root=new a1,this.current=this.root,this.spaces=\"\",this.semicolon=!1,this.createTokenizer(),this.root.source={input:e,start:{column:1,line:1,offset:0}}}atrule(e){let t=new i1;t.name=e[1].slice(1),t.name===\"\"&&this.unnamedAtrule(t,e),this.init(t,e[2]);let i,n,s,a=!1,o=!1,l=[],c=[];for(;!this.tokenizer.endOfFile();){if(e=this.tokenizer.nextToken(),i=e[0],i===\"(\"||i===\"[\"?c.push(i===\"(\"?\")\":\"]\"):i===\"{\"&&c.length>0?c.push(\"}\"):i===c[c.length-1]&&c.pop(),c.length===0)if(i===\";\"){t.source.end=this.getPosition(e[2]),t.source.end.offset++,this.semicolon=!0;break}else if(i===\"{\"){o=!0;break}else if(i===\"}\"){if(l.length>0){for(s=l.length-1,n=l[s];n&&n[0]===\"space\";)n=l[--s];n&&(t.source.end=this.getPosition(n[3]||n[2]),t.source.end.offset++)}this.end(e);break}else l.push(e);else l.push(e);if(this.tokenizer.endOfFile()){a=!0;break}}t.raws.between=this.spacesAndCommentsFromEnd(l),l.length?(t.raws.afterName=this.spacesAndCommentsFromStart(l),this.raw(t,\"params\",l),a&&(e=l[l.length-1],t.source.end=this.getPosition(e[3]||e[2]),t.source.end.offset++,this.spaces=t.raws.between,t.raws.between=\"\")):(t.raws.afterName=\"\",t.params=\"\"),o&&(t.nodes=[],this.current=t)}checkMissedSemicolon(e){let t=this.colon(e);if(t===!1)return;let i=0,n;for(let s=t-1;s>=0&&(n=e[s],!(n[0]!==\"space\"&&(i+=1,i===2)));s--);throw this.input.error(\"Missed semicolon\",n[0]===\"word\"?n[3]+1:n[2])}colon(e){let t=0,i,n,s;for(let[a,o]of e.entries()){if(n=o,s=n[0],s===\"(\"&&(t+=1),s===\")\"&&(t-=1),t===0&&s===\":\")if(!i)this.doubleColon(n);else{if(i[0]===\"word\"&&i[1]===\"progid\")continue;return a}i=n}return!1}comment(e){let t=new n1;this.init(t,e[2]),t.source.end=this.getPosition(e[3]||e[2]),t.source.end.offset++;let i=e[1].slice(2,-2);if(/^\\s*$/.test(i))t.text=\"\",t.raws.left=i,t.raws.right=\"\";else{let n=i.match(/^(\\s*)([^]*\\S)(\\s*)$/);t.text=n[2],t.raws.left=n[1],t.raws.right=n[3]}}createTokenizer(){this.tokenizer=o1(this.input)}decl(e,t){let i=new s1;this.init(i,e[0][2]);let n=e[e.length-1];for(n[0]===\";\"&&(this.semicolon=!0,e.pop()),i.source.end=this.getPosition(n[3]||n[2]||l1(e)),i.source.end.offset++;e[0][0]!==\"word\";)e.length===1&&this.unknownWord(e),i.raws.before+=e.shift()[1];for(i.source.start=this.getPosition(e[0][2]),i.prop=\"\";e.length;){let c=e[0][0];if(c===\":\"||c===\"space\"||c===\"comment\")break;i.prop+=e.shift()[1]}i.raws.between=\"\";let s;for(;e.length;)if(s=e.shift(),s[0]===\":\"){i.raws.between+=s[1];break}else s[0]===\"word\"&&/\\w/.test(s[1])&&this.unknownWord([s]),i.raws.between+=s[1];(i.prop[0]===\"_\"||i.prop[0]===\"*\")&&(i.raws.before+=i.prop[0],i.prop=i.prop.slice(1));let a=[],o;for(;e.length&&(o=e[0][0],!(o!==\"space\"&&o!==\"comment\"));)a.push(e.shift());this.precheckMissedSemicolon(e);for(let c=e.length-1;c>=0;c--){if(s=e[c],s[1].toLowerCase()===\"!important\"){i.important=!0;let f=this.stringFrom(e,c);f=this.spacesFromEnd(e)+f,f!==\" !important\"&&(i.raws.important=f);break}else if(s[1].toLowerCase()===\"important\"){let f=e.slice(0),d=\"\";for(let p=c;p>0;p--){let h=f[p][0];if(d.trim().startsWith(\"!\")&&h!==\"space\")break;d=f.pop()[1]+d}d.trim().startsWith(\"!\")&&(i.important=!0,i.raws.important=d,e=f)}if(s[0]!==\"space\"&&s[0]!==\"comment\")break}e.some(c=>c[0]!==\"space\"&&c[0]!==\"comment\")&&(i.raws.between+=a.map(c=>c[1]).join(\"\"),a=[]),this.raw(i,\"value\",a.concat(e),t),i.value.includes(\":\")&&!t&&this.checkMissedSemicolon(e)}doubleColon(e){throw this.input.error(\"Double colon\",{offset:e[2]},{offset:e[2]+e[1].length})}emptyRule(e){let t=new tp;this.init(t,e[2]),t.selector=\"\",t.raws.between=\"\",this.current=t}end(e){this.current.nodes&&this.current.nodes.length&&(this.current.raws.semicolon=this.semicolon),this.semicolon=!1,this.current.raws.after=(this.current.raws.after||\"\")+this.spaces,this.spaces=\"\",this.current.parent?(this.current.source.end=this.getPosition(e[2]),this.current.source.end.offset++,this.current=this.current.parent):this.unexpectedClose(e)}endFile(){this.current.parent&&this.unclosedBlock(),this.current.nodes&&this.current.nodes.length&&(this.current.raws.semicolon=this.semicolon),this.current.raws.after=(this.current.raws.after||\"\")+this.spaces,this.root.source.end=this.getPosition(this.tokenizer.position())}freeSemicolon(e){if(this.spaces+=e[1],this.current.nodes){let t=this.current.nodes[this.current.nodes.length-1];t&&t.type===\"rule\"&&!t.raws.ownSemicolon&&(t.raws.ownSemicolon=this.spaces,this.spaces=\"\")}}getPosition(e){let t=this.input.fromOffset(e);return{column:t.col,line:t.line,offset:e}}init(e,t){this.current.push(e),e.source={input:this.input,start:this.getPosition(t)},e.raws.before=this.spaces,this.spaces=\"\",e.type!==\"comment\"&&(this.semicolon=!1)}other(e){let t=!1,i=null,n=!1,s=null,a=[],o=e[1].startsWith(\"--\"),l=[],c=e;for(;c;){if(i=c[0],l.push(c),i===\"(\"||i===\"[\")s||(s=c),a.push(i===\"(\"?\")\":\"]\");else if(o&&n&&i===\"{\")s||(s=c),a.push(\"}\");else if(a.length===0)if(i===\";\")if(n){this.decl(l,o);return}else break;else if(i===\"{\"){this.rule(l);return}else if(i===\"}\"){this.tokenizer.back(l.pop()),t=!0;break}else i===\":\"&&(n=!0);else i===a[a.length-1]&&(a.pop(),a.length===0&&(s=null));c=this.tokenizer.nextToken()}if(this.tokenizer.endOfFile()&&(t=!0),a.length>0&&this.unclosedBracket(s),t&&n){if(!o)for(;l.length&&(c=l[l.length-1][0],!(c!==\"space\"&&c!==\"comment\"));)this.tokenizer.back(l.pop());this.decl(l,o)}else this.unknownWord(l)}parse(){let e;for(;!this.tokenizer.endOfFile();)switch(e=this.tokenizer.nextToken(),e[0]){case\"space\":this.spaces+=e[1];break;case\";\":this.freeSemicolon(e);break;case\"}\":this.end(e);break;case\"comment\":this.comment(e);break;case\"at-word\":this.atrule(e);break;case\"{\":this.emptyRule(e);break;default:this.other(e);break}this.endFile()}precheckMissedSemicolon(){}raw(e,t,i,n){let s,a,o=i.length,l=\"\",c=!0,f,d;for(let p=0;ph+b[1],\"\");e.raws[t]={raw:p,value:l}}e[t]=l}rule(e){e.pop();let t=new tp;this.init(t,e[0][2]),t.raws.between=this.spacesAndCommentsFromEnd(e),this.raw(t,\"selector\",e),this.current=t}spacesAndCommentsFromEnd(e){let t,i=\"\";for(;e.length&&(t=e[e.length-1][0],!(t!==\"space\"&&t!==\"comment\"));)i=e.pop()[1]+i;return i}spacesAndCommentsFromStart(e){let t,i=\"\";for(;e.length&&(t=e[0][0],!(t!==\"space\"&&t!==\"comment\"));)i+=e.shift()[1];return i}spacesFromEnd(e){let t,i=\"\";for(;e.length&&(t=e[e.length-1][0],t===\"space\");)i=e.pop()[1]+i;return i}stringFrom(e,t){let i=\"\";for(let n=t;n{u();\"use strict\";var u1=Et(),f1=mn(),c1=sp();function An(r,e){let t=new f1(r,e),i=new c1(t);try{i.parse()}catch(n){throw n}return i.root}ap.exports=An;An.default=An;u1.registerParse(An)});var Aa=x((U3,op)=>{u();\"use strict\";var _n=class{constructor(e,t={}){if(this.type=\"warning\",this.text=e,t.node&&t.node.source){let i=t.node.rangeBy(t);this.line=i.start.line,this.column=i.start.column,this.endLine=i.end.line,this.endColumn=i.end.column}for(let i in t)this[i]=t[i]}toString(){return this.node?this.node.error(this.text,{index:this.index,plugin:this.plugin,word:this.word}).message:this.plugin?this.plugin+\": \"+this.text:this.text}};op.exports=_n;_n.default=_n});var On=x((V3,lp)=>{u();\"use strict\";var p1=Aa(),En=class{constructor(e,t,i){this.processor=e,this.messages=[],this.root=t,this.opts=i,this.css=void 0,this.map=void 0}toString(){return this.css}warn(e,t={}){t.plugin||this.lastPlugin&&this.lastPlugin.postcssPlugin&&(t.plugin=this.lastPlugin.postcssPlugin);let i=new p1(e,t);return this.messages.push(i),i}warnings(){return this.messages.filter(e=>e.type===\"warning\")}get content(){return this.css}};lp.exports=En;En.default=En});var Ca=x((H3,fp)=>{u();\"use strict\";var up={};fp.exports=function(e){up[e]||(up[e]=!0,typeof console!=\"undefined\"&&console.warn&&console.warn(e))}});var Oa=x((G3,hp)=>{u();\"use strict\";var d1=Et(),h1=dn(),m1=ka(),g1=Cn(),cp=On(),y1=tr(),b1=Vr(),{isClean:tt,my:w1}=ln(),W3=Ca(),v1={atrule:\"AtRule\",comment:\"Comment\",decl:\"Declaration\",document:\"Document\",root:\"Root\",rule:\"Rule\"},x1={AtRule:!0,AtRuleExit:!0,Comment:!0,CommentExit:!0,Declaration:!0,DeclarationExit:!0,Document:!0,DocumentExit:!0,Once:!0,OnceExit:!0,postcssPlugin:!0,prepare:!0,Root:!0,RootExit:!0,Rule:!0,RuleExit:!0},k1={Once:!0,postcssPlugin:!0,prepare:!0},rr=0;function ri(r){return typeof r==\"object\"&&typeof r.then==\"function\"}function pp(r){let e=!1,t=v1[r.type];return r.type===\"decl\"?e=r.prop.toLowerCase():r.type===\"atrule\"&&(e=r.name.toLowerCase()),e&&r.append?[t,t+\"-\"+e,rr,t+\"Exit\",t+\"Exit-\"+e]:e?[t,t+\"-\"+e,t+\"Exit\",t+\"Exit-\"+e]:r.append?[t,rr,t+\"Exit\"]:[t,t+\"Exit\"]}function dp(r){let e;return r.type===\"document\"?e=[\"Document\",rr,\"DocumentExit\"]:r.type===\"root\"?e=[\"Root\",rr,\"RootExit\"]:e=pp(r),{eventIndex:0,events:e,iterator:0,node:r,visitorIndex:0,visitors:[]}}function _a(r){return r[tt]=!1,r.nodes&&r.nodes.forEach(e=>_a(e)),r}var Ea={},pt=class{constructor(e,t,i){this.stringified=!1,this.processed=!1;let n;if(typeof t==\"object\"&&t!==null&&(t.type===\"root\"||t.type===\"document\"))n=_a(t);else if(t instanceof pt||t instanceof cp)n=_a(t.root),t.map&&(typeof i.map==\"undefined\"&&(i.map={}),i.map.inline||(i.map.inline=!1),i.map.prev=t.map);else{let s=g1;i.syntax&&(s=i.syntax.parse),i.parser&&(s=i.parser),s.parse&&(s=s.parse);try{n=s(t,i)}catch(a){this.processed=!0,this.error=a}n&&!n[w1]&&d1.rebuild(n)}this.result=new cp(e,n,i),this.helpers={...Ea,postcss:Ea,result:this.result},this.plugins=this.processor.plugins.map(s=>typeof s==\"object\"&&s.prepare?{...s,...s.prepare(this.result)}:s)}async(){return this.error?Promise.reject(this.error):this.processed?Promise.resolve(this.result):(this.processing||(this.processing=this.runAsync()),this.processing)}catch(e){return this.async().catch(e)}finally(e){return this.async().then(e,e)}getAsyncError(){throw new Error(\"Use process(css).then(cb) to work with async plugins\")}handleError(e,t){let i=this.result.lastPlugin;try{t&&t.addToError(e),this.error=e,e.name===\"CssSyntaxError\"&&!e.plugin?(e.plugin=i.postcssPlugin,e.setMessage()):i.postcssVersion}catch(n){console&&console.error&&console.error(n)}return e}prepareVisitors(){this.listeners={};let e=(t,i,n)=>{this.listeners[i]||(this.listeners[i]=[]),this.listeners[i].push([t,n])};for(let t of this.plugins)if(typeof t==\"object\")for(let i in t){if(!x1[i]&&/^[A-Z]/.test(i))throw new Error(`Unknown event ${i} in ${t.postcssPlugin}. Try to update PostCSS (${this.processor.version} now).`);if(!k1[i])if(typeof t[i]==\"object\")for(let n in t[i])n===\"*\"?e(t,i,t[i][n]):e(t,i+\"-\"+n.toLowerCase(),t[i][n]);else typeof t[i]==\"function\"&&e(t,i,t[i])}this.hasListener=Object.keys(this.listeners).length>0}async runAsync(){this.plugin=0;for(let e=0;e0;){let i=this.visitTick(t);if(ri(i))try{await i}catch(n){let s=t[t.length-1].node;throw this.handleError(n,s)}}}if(this.listeners.OnceExit)for(let[t,i]of this.listeners.OnceExit){this.result.lastPlugin=t;try{if(e.type===\"document\"){let n=e.nodes.map(s=>i(s,this.helpers));await Promise.all(n)}else await i(e,this.helpers)}catch(n){throw this.handleError(n)}}}return this.processed=!0,this.stringify()}runOnRoot(e){this.result.lastPlugin=e;try{if(typeof e==\"object\"&&e.Once){if(this.result.root.type===\"document\"){let t=this.result.root.nodes.map(i=>e.Once(i,this.helpers));return ri(t[0])?Promise.all(t):t}return e.Once(this.result.root,this.helpers)}else if(typeof e==\"function\")return e(this.result.root,this.result)}catch(t){throw this.handleError(t)}}stringify(){if(this.error)throw this.error;if(this.stringified)return this.result;this.stringified=!0,this.sync();let e=this.result.opts,t=b1;e.syntax&&(t=e.syntax.stringify),e.stringifier&&(t=e.stringifier),t.stringify&&(t=t.stringify);let n=new m1(t,this.result.root,this.result.opts).generate();return this.result.css=n[0],this.result.map=n[1],this.result}sync(){if(this.error)throw this.error;if(this.processed)return this.result;if(this.processed=!0,this.processing)throw this.getAsyncError();for(let e of this.plugins){let t=this.runOnRoot(e);if(ri(t))throw this.getAsyncError()}if(this.prepareVisitors(),this.hasListener){let e=this.result.root;for(;!e[tt];)e[tt]=!0,this.walkSync(e);if(this.listeners.OnceExit)if(e.type===\"document\")for(let t of e.nodes)this.visitSync(this.listeners.OnceExit,t);else this.visitSync(this.listeners.OnceExit,e)}return this.result}then(e,t){return this.async().then(e,t)}toString(){return this.css}visitSync(e,t){for(let[i,n]of e){this.result.lastPlugin=i;let s;try{s=n(t,this.helpers)}catch(a){throw this.handleError(a,t.proxyOf)}if(t.type!==\"root\"&&t.type!==\"document\"&&!t.parent)return!0;if(ri(s))throw this.getAsyncError()}}visitTick(e){let t=e[e.length-1],{node:i,visitors:n}=t;if(i.type!==\"root\"&&i.type!==\"document\"&&!i.parent){e.pop();return}if(n.length>0&&t.visitorIndex{n[tt]||this.walkSync(n)});else{let n=this.listeners[i];if(n&&this.visitSync(n,e.toProxy()))return}}warnings(){return this.sync().warnings()}get content(){return this.stringify().content}get css(){return this.stringify().css}get map(){return this.stringify().map}get messages(){return this.sync().messages}get opts(){return this.result.opts}get processor(){return this.result.processor}get root(){return this.sync().root}get[Symbol.toStringTag](){return\"LazyResult\"}};pt.registerPostcss=r=>{Ea=r};hp.exports=pt;pt.default=pt;y1.registerLazyResult(pt);h1.registerLazyResult(pt)});var gp=x((Y3,mp)=>{u();\"use strict\";var S1=ka(),A1=Cn(),C1=On(),_1=Vr(),Q3=Ca(),Tn=class{constructor(e,t,i){t=t.toString(),this.stringified=!1,this._processor=e,this._css=t,this._opts=i,this._map=void 0;let n,s=_1;this.result=new C1(this._processor,n,this._opts),this.result.css=t;let a=this;Object.defineProperty(this.result,\"root\",{get(){return a.root}});let o=new S1(s,n,this._opts,t);if(o.isMap()){let[l,c]=o.generate();l&&(this.result.css=l),c&&(this.result.map=c)}else o.clearAnnotation(),this.result.css=o.css}async(){return this.error?Promise.reject(this.error):Promise.resolve(this.result)}catch(e){return this.async().catch(e)}finally(e){return this.async().then(e,e)}sync(){if(this.error)throw this.error;return this.result}then(e,t){return this.async().then(e,t)}toString(){return this._css}warnings(){return[]}get content(){return this.result.css}get css(){return this.result.css}get map(){return this.result.map}get messages(){return[]}get opts(){return this.result.opts}get processor(){return this.result.processor}get root(){if(this._root)return this._root;let e,t=A1;try{e=t(this._css,this._opts)}catch(i){this.error=i}if(this.error)throw this.error;return this._root=e,e}get[Symbol.toStringTag](){return\"NoWorkResult\"}};mp.exports=Tn;Tn.default=Tn});var bp=x((K3,yp)=>{u();\"use strict\";var E1=dn(),O1=Oa(),T1=gp(),R1=tr(),ir=class{constructor(e=[]){this.version=\"8.4.49\",this.plugins=this.normalize(e)}normalize(e){let t=[];for(let i of e)if(i.postcss===!0?i=i():i.postcss&&(i=i.postcss),typeof i==\"object\"&&Array.isArray(i.plugins))t=t.concat(i.plugins);else if(typeof i==\"object\"&&i.postcssPlugin)t.push(i);else if(typeof i==\"function\")t.push(i);else if(!(typeof i==\"object\"&&(i.parse||i.stringify)))throw new Error(i+\" is not a PostCSS plugin\");return t}process(e,t={}){return!this.plugins.length&&!t.parser&&!t.stringifier&&!t.syntax?new T1(this,e,t):new O1(this,e,t)}use(e){return this.plugins=this.plugins.concat(this.normalize([e])),this}};yp.exports=ir;ir.default=ir;R1.registerProcessor(ir);E1.registerProcessor(ir)});var $e=x((X3,Cp)=>{u();\"use strict\";var wp=pn(),vp=Qr(),P1=Et(),I1=an(),xp=Yr(),kp=dn(),D1=Qc(),q1=mn(),$1=Oa(),L1=xa(),M1=Gr(),N1=Cn(),Ta=bp(),B1=On(),Sp=tr(),Ap=gn(),F1=Vr(),j1=Aa();function J(...r){return r.length===1&&Array.isArray(r[0])&&(r=r[0]),new Ta(r)}J.plugin=function(e,t){let i=!1;function n(...a){console&&console.warn&&!i&&(i=!0,console.warn(e+`: postcss.plugin was deprecated. Migration guide:\nhttps://evilmartians.com/chronicles/postcss-8-plugin-migration`),m.env.LANG&&m.env.LANG.startsWith(\"cn\")&&console.warn(e+`: \\u91CC\\u9762 postcss.plugin \\u88AB\\u5F03\\u7528. \\u8FC1\\u79FB\\u6307\\u5357:\nhttps://www.w3ctech.com/topic/2226`));let o=t(...a);return o.postcssPlugin=e,o.postcssVersion=new Ta().version,o}let s;return Object.defineProperty(n,\"postcss\",{get(){return s||(s=n()),s}}),n.process=function(a,o,l){return J([n(l)]).process(a,o)},n};J.stringify=F1;J.parse=N1;J.fromJSON=D1;J.list=L1;J.comment=r=>new vp(r);J.atRule=r=>new wp(r);J.decl=r=>new xp(r);J.rule=r=>new Ap(r);J.root=r=>new Sp(r);J.document=r=>new kp(r);J.CssSyntaxError=I1;J.Declaration=xp;J.Container=P1;J.Processor=Ta;J.Document=kp;J.Comment=vp;J.Warning=j1;J.AtRule=wp;J.Result=B1;J.Input=q1;J.Rule=Ap;J.Root=Sp;J.Node=M1;$1.registerPostcss(J);Cp.exports=J;J.default=J});var re,ee,Z3,J3,eI,tI,rI,iI,nI,sI,aI,oI,lI,uI,fI,cI,pI,dI,hI,mI,gI,yI,bI,wI,vI,xI,Ot=P(()=>{u();re=pe($e()),ee=re.default,Z3=re.default.stringify,J3=re.default.fromJSON,eI=re.default.plugin,tI=re.default.parse,rI=re.default.list,iI=re.default.document,nI=re.default.comment,sI=re.default.atRule,aI=re.default.rule,oI=re.default.decl,lI=re.default.root,uI=re.default.CssSyntaxError,fI=re.default.Declaration,cI=re.default.Container,pI=re.default.Processor,dI=re.default.Document,hI=re.default.Comment,mI=re.default.Warning,gI=re.default.AtRule,yI=re.default.Result,bI=re.default.Input,wI=re.default.Rule,vI=re.default.Root,xI=re.default.Node});var Ra=x((SI,_p)=>{u();_p.exports=function(r,e,t,i,n){for(e=e.split?e.split(\".\"):e,i=0;i{u();\"use strict\";Rn.__esModule=!0;Rn.default=V1;function z1(r){for(var e=r.toLowerCase(),t=\"\",i=!1,n=0;n<6&&e[n]!==void 0;n++){var s=e.charCodeAt(n),a=s>=97&&s<=102||s>=48&&s<=57;if(i=s===32,!a)break;t+=e[n]}if(t.length!==0){var o=parseInt(t,16),l=o>=55296&&o<=57343;return l||o===0||o>1114111?[\"\\uFFFD\",t.length+(i?1:0)]:[String.fromCodePoint(o),t.length+(i?1:0)]}}var U1=/\\\\/;function V1(r){var e=U1.test(r);if(!e)return r;for(var t=\"\",i=0;i{u();\"use strict\";In.__esModule=!0;In.default=H1;function H1(r){for(var e=arguments.length,t=new Array(e>1?e-1:0),i=1;i0;){var n=t.shift();if(!r[n])return;r=r[n]}return r}Op.exports=In.default});var Pp=x((Dn,Rp)=>{u();\"use strict\";Dn.__esModule=!0;Dn.default=W1;function W1(r){for(var e=arguments.length,t=new Array(e>1?e-1:0),i=1;i0;){var n=t.shift();r[n]||(r[n]={}),r=r[n]}}Rp.exports=Dn.default});var Dp=x((qn,Ip)=>{u();\"use strict\";qn.__esModule=!0;qn.default=G1;function G1(r){for(var e=\"\",t=r.indexOf(\"/*\"),i=0;t>=0;){e=e+r.slice(i,t);var n=r.indexOf(\"*/\",t+2);if(n<0)return e;i=n+2,t=r.indexOf(\"/*\",i)}return e=e+r.slice(i),e}Ip.exports=qn.default});var ii=x(rt=>{u();\"use strict\";rt.__esModule=!0;rt.unesc=rt.stripComments=rt.getProp=rt.ensureObject=void 0;var Q1=$n(Pn());rt.unesc=Q1.default;var Y1=$n(Tp());rt.getProp=Y1.default;var K1=$n(Pp());rt.ensureObject=K1.default;var X1=$n(Dp());rt.stripComments=X1.default;function $n(r){return r&&r.__esModule?r:{default:r}}});var dt=x((ni,Lp)=>{u();\"use strict\";ni.__esModule=!0;ni.default=void 0;var qp=ii();function $p(r,e){for(var t=0;ti||this.source.end.linen||this.source.end.line===i&&this.source.end.column{u();\"use strict\";ie.__esModule=!0;ie.UNIVERSAL=ie.TAG=ie.STRING=ie.SELECTOR=ie.ROOT=ie.PSEUDO=ie.NESTING=ie.ID=ie.COMMENT=ie.COMBINATOR=ie.CLASS=ie.ATTRIBUTE=void 0;var tk=\"tag\";ie.TAG=tk;var rk=\"string\";ie.STRING=rk;var ik=\"selector\";ie.SELECTOR=ik;var nk=\"root\";ie.ROOT=nk;var sk=\"pseudo\";ie.PSEUDO=sk;var ak=\"nesting\";ie.NESTING=ak;var ok=\"id\";ie.ID=ok;var lk=\"comment\";ie.COMMENT=lk;var uk=\"combinator\";ie.COMBINATOR=uk;var fk=\"class\";ie.CLASS=fk;var ck=\"attribute\";ie.ATTRIBUTE=ck;var pk=\"universal\";ie.UNIVERSAL=pk});var Ln=x((si,Fp)=>{u();\"use strict\";si.__esModule=!0;si.default=void 0;var dk=mk(dt()),ht=hk(Se());function Mp(r){if(typeof WeakMap!=\"function\")return null;var e=new WeakMap,t=new WeakMap;return(Mp=function(n){return n?t:e})(r)}function hk(r,e){if(!e&&r&&r.__esModule)return r;if(r===null||typeof r!=\"object\"&&typeof r!=\"function\")return{default:r};var t=Mp(e);if(t&&t.has(r))return t.get(r);var i={},n=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var s in r)if(s!==\"default\"&&Object.prototype.hasOwnProperty.call(r,s)){var a=n?Object.getOwnPropertyDescriptor(r,s):null;a&&(a.get||a.set)?Object.defineProperty(i,s,a):i[s]=r[s]}return i.default=r,t&&t.set(r,i),i}function mk(r){return r&&r.__esModule?r:{default:r}}function gk(r,e){var t=typeof Symbol!=\"undefined\"&&r[Symbol.iterator]||r[\"@@iterator\"];if(t)return(t=t.call(r)).next.bind(t);if(Array.isArray(r)||(t=yk(r))||e&&r&&typeof r.length==\"number\"){t&&(r=t);var i=0;return function(){return i>=r.length?{done:!0}:{done:!1,value:r[i++]}}}throw new TypeError(`Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.`)}function yk(r,e){if(!!r){if(typeof r==\"string\")return Np(r,e);var t=Object.prototype.toString.call(r).slice(8,-1);if(t===\"Object\"&&r.constructor&&(t=r.constructor.name),t===\"Map\"||t===\"Set\")return Array.from(r);if(t===\"Arguments\"||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t))return Np(r,e)}}function Np(r,e){(e==null||e>r.length)&&(e=r.length);for(var t=0,i=new Array(e);t=n&&(this.indexes[a]=s-1);return this},t.removeAll=function(){for(var n=gk(this.nodes),s;!(s=n()).done;){var a=s.value;a.parent=void 0}return this.nodes=[],this},t.empty=function(){return this.removeAll()},t.insertAfter=function(n,s){s.parent=this;var a=this.index(n);this.nodes.splice(a+1,0,s),s.parent=this;var o;for(var l in this.indexes)o=this.indexes[l],a<=o&&(this.indexes[l]=o+1);return this},t.insertBefore=function(n,s){s.parent=this;var a=this.index(n);this.nodes.splice(a,0,s),s.parent=this;var o;for(var l in this.indexes)o=this.indexes[l],o<=a&&(this.indexes[l]=o+1);return this},t._findChildAtPosition=function(n,s){var a=void 0;return this.each(function(o){if(o.atPosition){var l=o.atPosition(n,s);if(l)return a=l,!1}else if(o.isAtPosition(n,s))return a=o,!1}),a},t.atPosition=function(n,s){if(this.isAtPosition(n,s))return this._findChildAtPosition(n,s)||this},t._inferEndPosition=function(){this.last&&this.last.source&&this.last.source.end&&(this.source=this.source||{},this.source.end=this.source.end||{},Object.assign(this.source.end,this.last.source.end))},t.each=function(n){this.lastEach||(this.lastEach=0),this.indexes||(this.indexes={}),this.lastEach++;var s=this.lastEach;if(this.indexes[s]=0,!!this.length){for(var a,o;this.indexes[s]{u();\"use strict\";ai.__esModule=!0;ai.default=void 0;var xk=Sk(Ln()),kk=Se();function Sk(r){return r&&r.__esModule?r:{default:r}}function jp(r,e){for(var t=0;t{u();\"use strict\";oi.__esModule=!0;oi.default=void 0;var Ek=Tk(Ln()),Ok=Se();function Tk(r){return r&&r.__esModule?r:{default:r}}function Rk(r,e){r.prototype=Object.create(e.prototype),r.prototype.constructor=r,qa(r,e)}function qa(r,e){return qa=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(i,n){return i.__proto__=n,i},qa(r,e)}var Pk=function(r){Rk(e,r);function e(t){var i;return i=r.call(this,t)||this,i.type=Ok.SELECTOR,i}return e}(Ek.default);oi.default=Pk;Up.exports=oi.default});var Mn=x((_I,Vp)=>{u();\"use strict\";var Ik={},Dk=Ik.hasOwnProperty,qk=function(e,t){if(!e)return t;var i={};for(var n in t)i[n]=Dk.call(e,n)?e[n]:t[n];return i},$k=/[ -,\\.\\/:-@\\[-\\^`\\{-~]/,Lk=/[ -,\\.\\/:-@\\[\\]\\^`\\{-~]/,Mk=/(^|\\\\+)?(\\\\[A-F0-9]{1,6})\\x20(?![a-fA-F0-9\\x20])/g,La=function r(e,t){t=qk(t,r.options),t.quotes!=\"single\"&&t.quotes!=\"double\"&&(t.quotes=\"single\");for(var i=t.quotes==\"double\"?'\"':\"'\",n=t.isIdentifier,s=e.charAt(0),a=\"\",o=0,l=e.length;o126){if(f>=55296&&f<=56319&&o{u();\"use strict\";li.__esModule=!0;li.default=void 0;var Nk=Hp(Mn()),Bk=ii(),Fk=Hp(dt()),jk=Se();function Hp(r){return r&&r.__esModule?r:{default:r}}function Wp(r,e){for(var t=0;t{u();\"use strict\";ui.__esModule=!0;ui.default=void 0;var Hk=Gk(dt()),Wk=Se();function Gk(r){return r&&r.__esModule?r:{default:r}}function Qk(r,e){r.prototype=Object.create(e.prototype),r.prototype.constructor=r,Ba(r,e)}function Ba(r,e){return Ba=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(i,n){return i.__proto__=n,i},Ba(r,e)}var Yk=function(r){Qk(e,r);function e(t){var i;return i=r.call(this,t)||this,i.type=Wk.COMMENT,i}return e}(Hk.default);ui.default=Yk;Qp.exports=ui.default});var za=x((fi,Yp)=>{u();\"use strict\";fi.__esModule=!0;fi.default=void 0;var Kk=Zk(dt()),Xk=Se();function Zk(r){return r&&r.__esModule?r:{default:r}}function Jk(r,e){r.prototype=Object.create(e.prototype),r.prototype.constructor=r,ja(r,e)}function ja(r,e){return ja=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(i,n){return i.__proto__=n,i},ja(r,e)}var eS=function(r){Jk(e,r);function e(i){var n;return n=r.call(this,i)||this,n.type=Xk.ID,n}var t=e.prototype;return t.valueToString=function(){return\"#\"+r.prototype.valueToString.call(this)},e}(Kk.default);fi.default=eS;Yp.exports=fi.default});var Nn=x((ci,Zp)=>{u();\"use strict\";ci.__esModule=!0;ci.default=void 0;var tS=Kp(Mn()),rS=ii(),iS=Kp(dt());function Kp(r){return r&&r.__esModule?r:{default:r}}function Xp(r,e){for(var t=0;t{u();\"use strict\";pi.__esModule=!0;pi.default=void 0;var oS=uS(Nn()),lS=Se();function uS(r){return r&&r.__esModule?r:{default:r}}function fS(r,e){r.prototype=Object.create(e.prototype),r.prototype.constructor=r,Va(r,e)}function Va(r,e){return Va=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(i,n){return i.__proto__=n,i},Va(r,e)}var cS=function(r){fS(e,r);function e(t){var i;return i=r.call(this,t)||this,i.type=lS.TAG,i}return e}(oS.default);pi.default=cS;Jp.exports=pi.default});var Ga=x((di,ed)=>{u();\"use strict\";di.__esModule=!0;di.default=void 0;var pS=hS(dt()),dS=Se();function hS(r){return r&&r.__esModule?r:{default:r}}function mS(r,e){r.prototype=Object.create(e.prototype),r.prototype.constructor=r,Wa(r,e)}function Wa(r,e){return Wa=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(i,n){return i.__proto__=n,i},Wa(r,e)}var gS=function(r){mS(e,r);function e(t){var i;return i=r.call(this,t)||this,i.type=dS.STRING,i}return e}(pS.default);di.default=gS;ed.exports=di.default});var Ya=x((hi,td)=>{u();\"use strict\";hi.__esModule=!0;hi.default=void 0;var yS=wS(Ln()),bS=Se();function wS(r){return r&&r.__esModule?r:{default:r}}function vS(r,e){r.prototype=Object.create(e.prototype),r.prototype.constructor=r,Qa(r,e)}function Qa(r,e){return Qa=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(i,n){return i.__proto__=n,i},Qa(r,e)}var xS=function(r){vS(e,r);function e(i){var n;return n=r.call(this,i)||this,n.type=bS.PSEUDO,n}var t=e.prototype;return t.toString=function(){var n=this.length?\"(\"+this.map(String).join(\",\")+\")\":\"\";return[this.rawSpaceBefore,this.stringifyProperty(\"value\"),n,this.rawSpaceAfter].join(\"\")},e}(yS.default);hi.default=xS;td.exports=hi.default});var Bn={};Ge(Bn,{deprecate:()=>kS});function kS(r){return r}var Fn=P(()=>{u()});var id=x((EI,rd)=>{u();rd.exports=(Fn(),Bn).deprecate});var to=x(yi=>{u();\"use strict\";yi.__esModule=!0;yi.default=void 0;yi.unescapeValue=Ja;var mi=Xa(Mn()),SS=Xa(Pn()),AS=Xa(Nn()),CS=Se(),Ka;function Xa(r){return r&&r.__esModule?r:{default:r}}function nd(r,e){for(var t=0;t0&&!n.quoted&&o.before.length===0&&!(n.spaces.value&&n.spaces.value.after)&&(o.before=\" \"),sd(a,o)}))),s.push(\"]\"),s.push(this.rawSpaceAfter),s.join(\"\")},_S(e,[{key:\"quoted\",get:function(){var n=this.quoteMark;return n===\"'\"||n==='\"'},set:function(n){RS()}},{key:\"quoteMark\",get:function(){return this._quoteMark},set:function(n){if(!this._constructed){this._quoteMark=n;return}this._quoteMark!==n&&(this._quoteMark=n,this._syncRawValue())}},{key:\"qualifiedAttribute\",get:function(){return this.qualifiedName(this.raws.attribute||this.attribute)}},{key:\"insensitiveFlag\",get:function(){return this.insensitive?\"i\":\"\"}},{key:\"value\",get:function(){return this._value},set:function(n){if(this._constructed){var s=Ja(n),a=s.deprecatedUsage,o=s.unescaped,l=s.quoteMark;if(a&&TS(),o===this._value&&l===this._quoteMark)return;this._value=o,this._quoteMark=l,this._syncRawValue()}else this._value=n}},{key:\"insensitive\",get:function(){return this._insensitive},set:function(n){n||(this._insensitive=!1,this.raws&&(this.raws.insensitiveFlag===\"I\"||this.raws.insensitiveFlag===\"i\")&&(this.raws.insensitiveFlag=void 0)),this._insensitive=n}},{key:\"attribute\",get:function(){return this._attribute},set:function(n){this._handleEscapes(\"attribute\",n),this._attribute=n}}]),e}(AS.default);yi.default=jn;jn.NO_QUOTE=null;jn.SINGLE_QUOTE=\"'\";jn.DOUBLE_QUOTE='\"';var eo=(Ka={\"'\":{quotes:\"single\",wrap:!0},'\"':{quotes:\"double\",wrap:!0}},Ka[null]={isIdentifier:!0},Ka);function sd(r,e){return\"\"+e.before+r+e.after}});var io=x((bi,ad)=>{u();\"use strict\";bi.__esModule=!0;bi.default=void 0;var DS=$S(Nn()),qS=Se();function $S(r){return r&&r.__esModule?r:{default:r}}function LS(r,e){r.prototype=Object.create(e.prototype),r.prototype.constructor=r,ro(r,e)}function ro(r,e){return ro=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(i,n){return i.__proto__=n,i},ro(r,e)}var MS=function(r){LS(e,r);function e(t){var i;return i=r.call(this,t)||this,i.type=qS.UNIVERSAL,i.value=\"*\",i}return e}(DS.default);bi.default=MS;ad.exports=bi.default});var so=x((wi,od)=>{u();\"use strict\";wi.__esModule=!0;wi.default=void 0;var NS=FS(dt()),BS=Se();function FS(r){return r&&r.__esModule?r:{default:r}}function jS(r,e){r.prototype=Object.create(e.prototype),r.prototype.constructor=r,no(r,e)}function no(r,e){return no=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(i,n){return i.__proto__=n,i},no(r,e)}var zS=function(r){jS(e,r);function e(t){var i;return i=r.call(this,t)||this,i.type=BS.COMBINATOR,i}return e}(NS.default);wi.default=zS;od.exports=wi.default});var oo=x((vi,ld)=>{u();\"use strict\";vi.__esModule=!0;vi.default=void 0;var US=HS(dt()),VS=Se();function HS(r){return r&&r.__esModule?r:{default:r}}function WS(r,e){r.prototype=Object.create(e.prototype),r.prototype.constructor=r,ao(r,e)}function ao(r,e){return ao=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(i,n){return i.__proto__=n,i},ao(r,e)}var GS=function(r){WS(e,r);function e(t){var i;return i=r.call(this,t)||this,i.type=VS.NESTING,i.value=\"&\",i}return e}(US.default);vi.default=GS;ld.exports=vi.default});var fd=x((zn,ud)=>{u();\"use strict\";zn.__esModule=!0;zn.default=QS;function QS(r){return r.sort(function(e,t){return e-t})}ud.exports=zn.default});var lo=x(M=>{u();\"use strict\";M.__esModule=!0;M.word=M.tilde=M.tab=M.str=M.space=M.slash=M.singleQuote=M.semicolon=M.plus=M.pipe=M.openSquare=M.openParenthesis=M.newline=M.greaterThan=M.feed=M.equals=M.doubleQuote=M.dollar=M.cr=M.comment=M.comma=M.combinator=M.colon=M.closeSquare=M.closeParenthesis=M.caret=M.bang=M.backslash=M.at=M.asterisk=M.ampersand=void 0;var YS=38;M.ampersand=YS;var KS=42;M.asterisk=KS;var XS=64;M.at=XS;var ZS=44;M.comma=ZS;var JS=58;M.colon=JS;var eA=59;M.semicolon=eA;var tA=40;M.openParenthesis=tA;var rA=41;M.closeParenthesis=rA;var iA=91;M.openSquare=iA;var nA=93;M.closeSquare=nA;var sA=36;M.dollar=sA;var aA=126;M.tilde=aA;var oA=94;M.caret=oA;var lA=43;M.plus=lA;var uA=61;M.equals=uA;var fA=124;M.pipe=fA;var cA=62;M.greaterThan=cA;var pA=32;M.space=pA;var cd=39;M.singleQuote=cd;var dA=34;M.doubleQuote=dA;var hA=47;M.slash=hA;var mA=33;M.bang=mA;var gA=92;M.backslash=gA;var yA=13;M.cr=yA;var bA=12;M.feed=bA;var wA=10;M.newline=wA;var vA=9;M.tab=vA;var xA=cd;M.str=xA;var kA=-1;M.comment=kA;var SA=-2;M.word=SA;var AA=-3;M.combinator=AA});var hd=x(xi=>{u();\"use strict\";xi.__esModule=!0;xi.FIELDS=void 0;xi.default=PA;var D=CA(lo()),nr,te;function pd(r){if(typeof WeakMap!=\"function\")return null;var e=new WeakMap,t=new WeakMap;return(pd=function(n){return n?t:e})(r)}function CA(r,e){if(!e&&r&&r.__esModule)return r;if(r===null||typeof r!=\"object\"&&typeof r!=\"function\")return{default:r};var t=pd(e);if(t&&t.has(r))return t.get(r);var i={},n=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var s in r)if(s!==\"default\"&&Object.prototype.hasOwnProperty.call(r,s)){var a=n?Object.getOwnPropertyDescriptor(r,s):null;a&&(a.get||a.set)?Object.defineProperty(i,s,a):i[s]=r[s]}return i.default=r,t&&t.set(r,i),i}var _A=(nr={},nr[D.tab]=!0,nr[D.newline]=!0,nr[D.cr]=!0,nr[D.feed]=!0,nr),EA=(te={},te[D.space]=!0,te[D.tab]=!0,te[D.newline]=!0,te[D.cr]=!0,te[D.feed]=!0,te[D.ampersand]=!0,te[D.asterisk]=!0,te[D.bang]=!0,te[D.comma]=!0,te[D.colon]=!0,te[D.semicolon]=!0,te[D.openParenthesis]=!0,te[D.closeParenthesis]=!0,te[D.openSquare]=!0,te[D.closeSquare]=!0,te[D.singleQuote]=!0,te[D.doubleQuote]=!0,te[D.plus]=!0,te[D.pipe]=!0,te[D.tilde]=!0,te[D.greaterThan]=!0,te[D.equals]=!0,te[D.dollar]=!0,te[D.caret]=!0,te[D.slash]=!0,te),uo={},dd=\"0123456789abcdefABCDEF\";for(Un=0;Un0?(k=a+v,S=w-y[v].length):(k=a,S=s),T=D.comment,a=k,p=k,d=w-S):c===D.slash?(w=o,T=c,p=a,d=o-s,l=w+1):(w=OA(t,o),T=D.word,p=a,d=w-s),l=w+1;break}e.push([T,a,o-s,p,d,o,l]),S&&(s=S,S=null),o=l}return e}});var kd=x((ki,xd)=>{u();\"use strict\";ki.__esModule=!0;ki.default=void 0;var IA=je(Da()),fo=je($a()),DA=je(Na()),md=je(Fa()),qA=je(za()),$A=je(Ha()),co=je(Ga()),LA=je(Ya()),gd=Vn(to()),MA=je(io()),po=je(so()),NA=je(oo()),BA=je(fd()),O=Vn(hd()),q=Vn(lo()),FA=Vn(Se()),ue=ii(),Vt,ho;function yd(r){if(typeof WeakMap!=\"function\")return null;var e=new WeakMap,t=new WeakMap;return(yd=function(n){return n?t:e})(r)}function Vn(r,e){if(!e&&r&&r.__esModule)return r;if(r===null||typeof r!=\"object\"&&typeof r!=\"function\")return{default:r};var t=yd(e);if(t&&t.has(r))return t.get(r);var i={},n=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var s in r)if(s!==\"default\"&&Object.prototype.hasOwnProperty.call(r,s)){var a=n?Object.getOwnPropertyDescriptor(r,s):null;a&&(a.get||a.set)?Object.defineProperty(i,s,a):i[s]=r[s]}return i.default=r,t&&t.set(r,i),i}function je(r){return r&&r.__esModule?r:{default:r}}function bd(r,e){for(var t=0;t0){var a=this.current.last;if(a){var o=this.convertWhitespaceNodesToSpace(s),l=o.space,c=o.rawSpace;c!==void 0&&(a.rawSpaceAfter+=c),a.spaces.after+=l}else s.forEach(function(T){return i.newNode(T)})}return}var f=this.currToken,d=void 0;n>this.position&&(d=this.parseWhitespaceEquivalentTokens(n));var p;if(this.isNamedCombinator()?p=this.namedCombinator():this.currToken[O.FIELDS.TYPE]===q.combinator?(p=new po.default({value:this.content(),source:sr(this.currToken),sourceIndex:this.currToken[O.FIELDS.START_POS]}),this.position++):mo[this.currToken[O.FIELDS.TYPE]]||d||this.unexpected(),p){if(d){var h=this.convertWhitespaceNodesToSpace(d),b=h.space,v=h.rawSpace;p.spaces.before=b,p.rawSpaceBefore=v}}else{var y=this.convertWhitespaceNodesToSpace(d,!0),w=y.space,k=y.rawSpace;k||(k=w);var S={},E={spaces:{}};w.endsWith(\" \")&&k.endsWith(\" \")?(S.before=w.slice(0,w.length-1),E.spaces.before=k.slice(0,k.length-1)):w.startsWith(\" \")&&k.startsWith(\" \")?(S.after=w.slice(1),E.spaces.after=k.slice(1)):E.value=k,p=new po.default({value:\" \",source:go(f,this.tokens[this.position-1]),sourceIndex:f[O.FIELDS.START_POS],spaces:S,raws:E})}return this.currToken&&this.currToken[O.FIELDS.TYPE]===q.space&&(p.spaces.after=this.optionalSpace(this.content()),this.position++),this.newNode(p)},e.comma=function(){if(this.position===this.tokens.length-1){this.root.trailingComma=!0,this.position++;return}this.current._inferEndPosition();var i=new fo.default({source:{start:wd(this.tokens[this.position+1])},sourceIndex:this.tokens[this.position+1][O.FIELDS.START_POS]});this.current.parent.append(i),this.current=i,this.position++},e.comment=function(){var i=this.currToken;this.newNode(new md.default({value:this.content(),source:sr(i),sourceIndex:i[O.FIELDS.START_POS]})),this.position++},e.error=function(i,n){throw this.root.error(i,n)},e.missingBackslash=function(){return this.error(\"Expected a backslash preceding the semicolon.\",{index:this.currToken[O.FIELDS.START_POS]})},e.missingParenthesis=function(){return this.expected(\"opening parenthesis\",this.currToken[O.FIELDS.START_POS])},e.missingSquareBracket=function(){return this.expected(\"opening square bracket\",this.currToken[O.FIELDS.START_POS])},e.unexpected=function(){return this.error(\"Unexpected '\"+this.content()+\"'. Escaping special characters with \\\\ may help.\",this.currToken[O.FIELDS.START_POS])},e.unexpectedPipe=function(){return this.error(\"Unexpected '|'.\",this.currToken[O.FIELDS.START_POS])},e.namespace=function(){var i=this.prevToken&&this.content(this.prevToken)||!0;if(this.nextToken[O.FIELDS.TYPE]===q.word)return this.position++,this.word(i);if(this.nextToken[O.FIELDS.TYPE]===q.asterisk)return this.position++,this.universal(i);this.unexpectedPipe()},e.nesting=function(){if(this.nextToken){var i=this.content(this.nextToken);if(i===\"|\"){this.position++;return}}var n=this.currToken;this.newNode(new NA.default({value:this.content(),source:sr(n),sourceIndex:n[O.FIELDS.START_POS]})),this.position++},e.parentheses=function(){var i=this.current.last,n=1;if(this.position++,i&&i.type===FA.PSEUDO){var s=new fo.default({source:{start:wd(this.tokens[this.position])},sourceIndex:this.tokens[this.position][O.FIELDS.START_POS]}),a=this.current;for(i.append(s),this.current=s;this.position1&&i.nextToken&&i.nextToken[O.FIELDS.TYPE]===q.openParenthesis&&i.error(\"Misplaced parenthesis.\",{index:i.nextToken[O.FIELDS.START_POS]})});else return this.expected([\"pseudo-class\",\"pseudo-element\"],this.currToken[O.FIELDS.START_POS])},e.space=function(){var i=this.content();this.position===0||this.prevToken[O.FIELDS.TYPE]===q.comma||this.prevToken[O.FIELDS.TYPE]===q.openParenthesis||this.current.nodes.every(function(n){return n.type===\"comment\"})?(this.spaces=this.optionalSpace(i),this.position++):this.position===this.tokens.length-1||this.nextToken[O.FIELDS.TYPE]===q.comma||this.nextToken[O.FIELDS.TYPE]===q.closeParenthesis?(this.current.last.spaces.after=this.optionalSpace(i),this.position++):this.combinator()},e.string=function(){var i=this.currToken;this.newNode(new co.default({value:this.content(),source:sr(i),sourceIndex:i[O.FIELDS.START_POS]})),this.position++},e.universal=function(i){var n=this.nextToken;if(n&&this.content(n)===\"|\")return this.position++,this.namespace();var s=this.currToken;this.newNode(new MA.default({value:this.content(),source:sr(s),sourceIndex:s[O.FIELDS.START_POS]}),i),this.position++},e.splitWord=function(i,n){for(var s=this,a=this.nextToken,o=this.content();a&&~[q.dollar,q.caret,q.equals,q.word].indexOf(a[O.FIELDS.TYPE]);){this.position++;var l=this.content();if(o+=l,l.lastIndexOf(\"\\\\\")===l.length-1){var c=this.nextToken;c&&c[O.FIELDS.TYPE]===q.space&&(o+=this.requiredSpace(this.content(c)),this.position++)}a=this.nextToken}var f=yo(o,\".\").filter(function(b){var v=o[b-1]===\"\\\\\",y=/^\\d+\\.\\d+%$/.test(o);return!v&&!y}),d=yo(o,\"#\").filter(function(b){return o[b-1]!==\"\\\\\"}),p=yo(o,\"#{\");p.length&&(d=d.filter(function(b){return!~p.indexOf(b)}));var h=(0,BA.default)(UA([0].concat(f,d)));h.forEach(function(b,v){var y=h[v+1]||o.length,w=o.slice(b,y);if(v===0&&n)return n.call(s,w,h.length);var k,S=s.currToken,E=S[O.FIELDS.START_POS]+h[v],T=Ht(S[1],S[2]+b,S[3],S[2]+(y-1));if(~f.indexOf(b)){var B={value:w.slice(1),source:T,sourceIndex:E};k=new DA.default(ar(B,\"value\"))}else if(~d.indexOf(b)){var N={value:w.slice(1),source:T,sourceIndex:E};k=new qA.default(ar(N,\"value\"))}else{var R={value:w,source:T,sourceIndex:E};ar(R,\"value\"),k=new $A.default(R)}s.newNode(k,i),i=null}),this.position++},e.word=function(i){var n=this.nextToken;return n&&this.content(n)===\"|\"?(this.position++,this.namespace()):this.splitWord(i)},e.loop=function(){for(;this.position{u();\"use strict\";Si.__esModule=!0;Si.default=void 0;var HA=WA(kd());function WA(r){return r&&r.__esModule?r:{default:r}}var GA=function(){function r(t,i){this.func=t||function(){},this.funcRes=null,this.options=i}var e=r.prototype;return e._shouldUpdateSelector=function(i,n){n===void 0&&(n={});var s=Object.assign({},this.options,n);return s.updateSelector===!1?!1:typeof i!=\"string\"},e._isLossy=function(i){i===void 0&&(i={});var n=Object.assign({},this.options,i);return n.lossless===!1},e._root=function(i,n){n===void 0&&(n={});var s=new HA.default(i,this._parseOptions(n));return s.root},e._parseOptions=function(i){return{lossy:this._isLossy(i)}},e._run=function(i,n){var s=this;return n===void 0&&(n={}),new Promise(function(a,o){try{var l=s._root(i,n);Promise.resolve(s.func(l)).then(function(c){var f=void 0;return s._shouldUpdateSelector(i,n)&&(f=l.toString(),i.selector=f),{transform:c,root:l,string:f}}).then(a,o)}catch(c){o(c);return}})},e._runSync=function(i,n){n===void 0&&(n={});var s=this._root(i,n),a=this.func(s);if(a&&typeof a.then==\"function\")throw new Error(\"Selector processor returned a promise to a synchronous call.\");var o=void 0;return n.updateSelector&&typeof i!=\"string\"&&(o=s.toString(),i.selector=o),{transform:a,root:s,string:o}},e.ast=function(i,n){return this._run(i,n).then(function(s){return s.root})},e.astSync=function(i,n){return this._runSync(i,n).root},e.transform=function(i,n){return this._run(i,n).then(function(s){return s.transform})},e.transformSync=function(i,n){return this._runSync(i,n).transform},e.process=function(i,n){return this._run(i,n).then(function(s){return s.string||s.root.toString()})},e.processSync=function(i,n){var s=this._runSync(i,n);return s.string||s.root.toString()},r}();Si.default=GA;Sd.exports=Si.default});var Cd=x(ne=>{u();\"use strict\";ne.__esModule=!0;ne.universal=ne.tag=ne.string=ne.selector=ne.root=ne.pseudo=ne.nesting=ne.id=ne.comment=ne.combinator=ne.className=ne.attribute=void 0;var QA=ze(to()),YA=ze(Na()),KA=ze(so()),XA=ze(Fa()),ZA=ze(za()),JA=ze(oo()),eC=ze(Ya()),tC=ze(Da()),rC=ze($a()),iC=ze(Ga()),nC=ze(Ha()),sC=ze(io());function ze(r){return r&&r.__esModule?r:{default:r}}var aC=function(e){return new QA.default(e)};ne.attribute=aC;var oC=function(e){return new YA.default(e)};ne.className=oC;var lC=function(e){return new KA.default(e)};ne.combinator=lC;var uC=function(e){return new XA.default(e)};ne.comment=uC;var fC=function(e){return new ZA.default(e)};ne.id=fC;var cC=function(e){return new JA.default(e)};ne.nesting=cC;var pC=function(e){return new eC.default(e)};ne.pseudo=pC;var dC=function(e){return new tC.default(e)};ne.root=dC;var hC=function(e){return new rC.default(e)};ne.selector=hC;var mC=function(e){return new iC.default(e)};ne.string=mC;var gC=function(e){return new nC.default(e)};ne.tag=gC;var yC=function(e){return new sC.default(e)};ne.universal=yC});var Td=x(Z=>{u();\"use strict\";Z.__esModule=!0;Z.isComment=Z.isCombinator=Z.isClassName=Z.isAttribute=void 0;Z.isContainer=TC;Z.isIdentifier=void 0;Z.isNamespace=RC;Z.isNesting=void 0;Z.isNode=bo;Z.isPseudo=void 0;Z.isPseudoClass=OC;Z.isPseudoElement=Od;Z.isUniversal=Z.isTag=Z.isString=Z.isSelector=Z.isRoot=void 0;var fe=Se(),Oe,bC=(Oe={},Oe[fe.ATTRIBUTE]=!0,Oe[fe.CLASS]=!0,Oe[fe.COMBINATOR]=!0,Oe[fe.COMMENT]=!0,Oe[fe.ID]=!0,Oe[fe.NESTING]=!0,Oe[fe.PSEUDO]=!0,Oe[fe.ROOT]=!0,Oe[fe.SELECTOR]=!0,Oe[fe.STRING]=!0,Oe[fe.TAG]=!0,Oe[fe.UNIVERSAL]=!0,Oe);function bo(r){return typeof r==\"object\"&&bC[r.type]}function Ue(r,e){return bo(e)&&e.type===r}var _d=Ue.bind(null,fe.ATTRIBUTE);Z.isAttribute=_d;var wC=Ue.bind(null,fe.CLASS);Z.isClassName=wC;var vC=Ue.bind(null,fe.COMBINATOR);Z.isCombinator=vC;var xC=Ue.bind(null,fe.COMMENT);Z.isComment=xC;var kC=Ue.bind(null,fe.ID);Z.isIdentifier=kC;var SC=Ue.bind(null,fe.NESTING);Z.isNesting=SC;var wo=Ue.bind(null,fe.PSEUDO);Z.isPseudo=wo;var AC=Ue.bind(null,fe.ROOT);Z.isRoot=AC;var CC=Ue.bind(null,fe.SELECTOR);Z.isSelector=CC;var _C=Ue.bind(null,fe.STRING);Z.isString=_C;var Ed=Ue.bind(null,fe.TAG);Z.isTag=Ed;var EC=Ue.bind(null,fe.UNIVERSAL);Z.isUniversal=EC;function Od(r){return wo(r)&&r.value&&(r.value.startsWith(\"::\")||r.value.toLowerCase()===\":before\"||r.value.toLowerCase()===\":after\"||r.value.toLowerCase()===\":first-letter\"||r.value.toLowerCase()===\":first-line\")}function OC(r){return wo(r)&&!Od(r)}function TC(r){return!!(bo(r)&&r.walk)}function RC(r){return _d(r)||Ed(r)}});var Rd=x(Ke=>{u();\"use strict\";Ke.__esModule=!0;var vo=Se();Object.keys(vo).forEach(function(r){r===\"default\"||r===\"__esModule\"||r in Ke&&Ke[r]===vo[r]||(Ke[r]=vo[r])});var xo=Cd();Object.keys(xo).forEach(function(r){r===\"default\"||r===\"__esModule\"||r in Ke&&Ke[r]===xo[r]||(Ke[r]=xo[r])});var ko=Td();Object.keys(ko).forEach(function(r){r===\"default\"||r===\"__esModule\"||r in Ke&&Ke[r]===ko[r]||(Ke[r]=ko[r])})});var it=x((Ai,Id)=>{u();\"use strict\";Ai.__esModule=!0;Ai.default=void 0;var PC=qC(Ad()),IC=DC(Rd());function Pd(r){if(typeof WeakMap!=\"function\")return null;var e=new WeakMap,t=new WeakMap;return(Pd=function(n){return n?t:e})(r)}function DC(r,e){if(!e&&r&&r.__esModule)return r;if(r===null||typeof r!=\"object\"&&typeof r!=\"function\")return{default:r};var t=Pd(e);if(t&&t.has(r))return t.get(r);var i={},n=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var s in r)if(s!==\"default\"&&Object.prototype.hasOwnProperty.call(r,s)){var a=n?Object.getOwnPropertyDescriptor(r,s):null;a&&(a.get||a.set)?Object.defineProperty(i,s,a):i[s]=r[s]}return i.default=r,t&&t.set(r,i),i}function qC(r){return r&&r.__esModule?r:{default:r}}var So=function(e){return new PC.default(e)};Object.assign(So,IC);delete So.__esModule;var $C=So;Ai.default=$C;Id.exports=Ai.default});function mt(r){return[\"fontSize\",\"outline\"].includes(r)?e=>(typeof e==\"function\"&&(e=e({})),Array.isArray(e)&&(e=e[0]),e):r===\"fontFamily\"?e=>{typeof e==\"function\"&&(e=e({}));let t=Array.isArray(e)&&ke(e[1])?e[0]:e;return Array.isArray(t)?t.join(\", \"):t}:[\"boxShadow\",\"transitionProperty\",\"transitionDuration\",\"transitionDelay\",\"transitionTimingFunction\",\"backgroundImage\",\"backgroundSize\",\"backgroundColor\",\"cursor\",\"animation\"].includes(r)?e=>(typeof e==\"function\"&&(e=e({})),Array.isArray(e)&&(e=e.join(\", \")),e):[\"gridTemplateColumns\",\"gridTemplateRows\",\"objectPosition\"].includes(r)?e=>(typeof e==\"function\"&&(e=e({})),typeof e==\"string\"&&(e=ee.list.comma(e).join(\" \")),e):(e,t={})=>(typeof e==\"function\"&&(e=e(t)),e)}var Ci=P(()=>{u();Ot();Kt()});var Bd=x((MI,Oo)=>{u();var{AtRule:LC,Rule:Dd}=$e(),qd=it();function Ao(r,e){let t;try{qd(i=>{t=i}).processSync(r)}catch(i){throw r.includes(\":\")?e?e.error(\"Missed semicolon\"):i:e?e.error(i.message):i}return t.at(0)}function $d(r,e){let t=!1;return r.each(i=>{if(i.type===\"nesting\"){let n=e.clone({});i.value!==\"&\"?i.replaceWith(Ao(i.value.replace(\"&\",n.toString()))):i.replaceWith(n),t=!0}else\"nodes\"in i&&i.nodes&&$d(i,e)&&(t=!0)}),t}function Ld(r,e){let t=[];return r.selectors.forEach(i=>{let n=Ao(i,r);e.selectors.forEach(s=>{if(!s)return;let a=Ao(s,e);$d(a,n)||(a.prepend(qd.combinator({value:\" \"})),a.prepend(n.clone({}))),t.push(a.toString())})}),t}function Hn(r,e){let t=r.prev();for(e.after(r);t&&t.type===\"comment\";){let i=t.prev();e.after(t),t=i}return r}function MC(r){return function e(t,i,n,s=n){let a=[];if(i.each(o=>{o.type===\"rule\"&&n?s&&(o.selectors=Ld(t,o)):o.type===\"atrule\"&&o.nodes?r[o.name]?e(t,o,s):i[_o]!==!1&&a.push(o):a.push(o)}),n&&a.length){let o=t.clone({nodes:[]});for(let l of a)o.append(l);i.prepend(o)}}}function Co(r,e,t){let i=new Dd({nodes:[],selector:r});return i.append(e),t.after(i),i}function Md(r,e){let t={};for(let i of r)t[i]=!0;if(e)for(let i of e)t[i.replace(/^@/,\"\")]=!0;return t}function NC(r){r=r.trim();let e=r.match(/^\\((.*)\\)$/);if(!e)return{selector:r,type:\"basic\"};let t=e[1].match(/^(with(?:out)?):(.+)$/);if(t){let i=t[1]===\"with\",n=Object.fromEntries(t[2].trim().split(/\\s+/).map(a=>[a,!0]));if(i&&n.all)return{type:\"noop\"};let s=a=>!!n[a];return n.all?s=()=>!0:i&&(s=a=>a===\"all\"?!1:!n[a]),{escapes:s,type:\"withrules\"}}return{type:\"unknown\"}}function BC(r){let e=[],t=r.parent;for(;t&&t instanceof LC;)e.push(t),t=t.parent;return e}function FC(r){let e=r[Nd];if(!e)r.after(r.nodes);else{let t=r.nodes,i,n=-1,s,a,o,l=BC(r);if(l.forEach((c,f)=>{if(e(c.name))i=c,n=f,a=o;else{let d=o;o=c.clone({nodes:[]}),d&&o.append(d),s=s||o}}),i?a?(s.append(t),i.after(a)):i.after(t):r.after(t),r.next()&&i){let c;l.slice(0,n+1).forEach((f,d,p)=>{let h=c;c=f.clone({nodes:[]}),h&&c.append(h);let b=[],y=(p[d-1]||r).next();for(;y;)b.push(y),y=y.next();c.append(b)}),c&&(a||t[t.length-1]).after(c)}}r.remove()}var _o=Symbol(\"rootRuleMergeSel\"),Nd=Symbol(\"rootRuleEscapes\");function jC(r){let{params:e}=r,{escapes:t,selector:i,type:n}=NC(e);if(n===\"unknown\")throw r.error(`Unknown @${r.name} parameter ${JSON.stringify(e)}`);if(n===\"basic\"&&i){let s=new Dd({nodes:r.nodes,selector:i});r.removeAll(),r.append(s)}r[Nd]=t,r[_o]=t?!t(\"all\"):n===\"noop\"}var Eo=Symbol(\"hasRootRule\");Oo.exports=(r={})=>{let e=Md([\"media\",\"supports\",\"layer\",\"container\",\"starting-style\"],r.bubble),t=MC(e),i=Md([\"document\",\"font-face\",\"keyframes\",\"-webkit-keyframes\",\"-moz-keyframes\"],r.unwrap),n=(r.rootRuleName||\"at-root\").replace(/^@/,\"\"),s=r.preserveEmpty;return{Once(a){a.walkAtRules(n,o=>{jC(o),a[Eo]=!0})},postcssPlugin:\"postcss-nested\",RootExit(a){a[Eo]&&(a.walkAtRules(n,FC),a[Eo]=!1)},Rule(a){let o=!1,l=a,c=!1,f=[];a.each(d=>{d.type===\"rule\"?(f.length&&(l=Co(a.selector,f,l),f=[]),c=!0,o=!0,d.selectors=Ld(a,d),l=Hn(d,l)):d.type===\"atrule\"?(f.length&&(l=Co(a.selector,f,l),f=[]),d.name===n?(o=!0,t(a,d,!0,d[_o]),l=Hn(d,l)):e[d.name]?(c=!0,o=!0,t(a,d,!0),l=Hn(d,l)):i[d.name]?(c=!0,o=!0,t(a,d,!1),l=Hn(d,l)):c&&f.push(d)):d.type===\"decl\"&&c&&f.push(d)}),f.length&&(l=Co(a.selector,f,l)),o&&s!==!0&&(a.raws.semicolon=!0,a.nodes.length===0&&a.remove())}}};Oo.exports.postcss=!0});var Ud=x((NI,zd)=>{u();\"use strict\";var Fd=/-(\\w|$)/g,jd=(r,e)=>e.toUpperCase(),zC=r=>(r=r.toLowerCase(),r===\"float\"?\"cssFloat\":r.startsWith(\"-ms-\")?r.substr(1).replace(Fd,jd):r.replace(Fd,jd));zd.exports=zC});var Po=x((BI,Vd)=>{u();var UC=Ud(),VC={boxFlex:!0,boxFlexGroup:!0,columnCount:!0,flex:!0,flexGrow:!0,flexPositive:!0,flexShrink:!0,flexNegative:!0,fontWeight:!0,lineClamp:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,tabSize:!0,widows:!0,zIndex:!0,zoom:!0,fillOpacity:!0,strokeDashoffset:!0,strokeOpacity:!0,strokeWidth:!0};function To(r){return typeof r.nodes==\"undefined\"?!0:Ro(r)}function Ro(r){let e,t={};return r.each(i=>{if(i.type===\"atrule\")e=\"@\"+i.name,i.params&&(e+=\" \"+i.params),typeof t[e]==\"undefined\"?t[e]=To(i):Array.isArray(t[e])?t[e].push(To(i)):t[e]=[t[e],To(i)];else if(i.type===\"rule\"){let n=Ro(i);if(t[i.selector])for(let s in n)t[i.selector][s]=n[s];else t[i.selector]=n}else if(i.type===\"decl\"){i.prop[0]===\"-\"&&i.prop[1]===\"-\"||i.parent&&i.parent.selector===\":export\"?e=i.prop:e=UC(i.prop);let n=i.value;!isNaN(i.value)&&VC[e]&&(n=parseFloat(i.value)),i.important&&(n+=\" !important\"),typeof t[e]==\"undefined\"?t[e]=n:Array.isArray(t[e])?t[e].push(n):t[e]=[t[e],n]}}),t}Vd.exports=Ro});var Wn=x((FI,Qd)=>{u();var _i=$e(),Hd=/\\s*!important\\s*$/i,HC={\"box-flex\":!0,\"box-flex-group\":!0,\"column-count\":!0,flex:!0,\"flex-grow\":!0,\"flex-positive\":!0,\"flex-shrink\":!0,\"flex-negative\":!0,\"font-weight\":!0,\"line-clamp\":!0,\"line-height\":!0,opacity:!0,order:!0,orphans:!0,\"tab-size\":!0,widows:!0,\"z-index\":!0,zoom:!0,\"fill-opacity\":!0,\"stroke-dashoffset\":!0,\"stroke-opacity\":!0,\"stroke-width\":!0};function WC(r){return r.replace(/([A-Z])/g,\"-$1\").replace(/^ms-/,\"-ms-\").toLowerCase()}function Wd(r,e,t){t===!1||t===null||(e.startsWith(\"--\")||(e=WC(e)),typeof t==\"number\"&&(t===0||HC[e]?t=t.toString():t+=\"px\"),e===\"css-float\"&&(e=\"float\"),Hd.test(t)?(t=t.replace(Hd,\"\"),r.push(_i.decl({prop:e,value:t,important:!0}))):r.push(_i.decl({prop:e,value:t})))}function Gd(r,e,t){let i=_i.atRule({name:e[1],params:e[3]||\"\"});typeof t==\"object\"&&(i.nodes=[],Io(t,i)),r.push(i)}function Io(r,e){let t,i,n;for(t in r)if(i=r[t],!(i===null||typeof i==\"undefined\"))if(t[0]===\"@\"){let s=t.match(/@(\\S+)(\\s+([\\W\\w]*)\\s*)?/);if(Array.isArray(i))for(let a of i)Gd(e,s,a);else Gd(e,s,i)}else if(Array.isArray(i))for(let s of i)Wd(e,t,s);else typeof i==\"object\"?(n=_i.rule({selector:t}),Io(i,n),e.push(n)):Wd(e,t,i)}Qd.exports=function(r){let e=_i.root();return Io(r,e),e}});var Do=x((jI,Yd)=>{u();var GC=Po();Yd.exports=function(e){return console&&console.warn&&e.warnings().forEach(t=>{let i=t.plugin||\"PostCSS\";console.warn(i+\": \"+t.text)}),GC(e.root)}});var Xd=x((zI,Kd)=>{u();var QC=$e(),YC=Do(),KC=Wn();Kd.exports=function(e){let t=QC(e);return async i=>{let n=await t.process(i,{parser:KC,from:void 0});return YC(n)}}});var Jd=x((UI,Zd)=>{u();var XC=$e(),ZC=Do(),JC=Wn();Zd.exports=function(r){let e=XC(r);return t=>{let i=e.process(t,{parser:JC,from:void 0});return ZC(i)}}});var th=x((VI,eh)=>{u();var e_=Po(),t_=Wn(),r_=Xd(),i_=Jd();eh.exports={objectify:e_,parse:t_,async:r_,sync:i_}});var or,rh,HI,WI,GI,QI,ih=P(()=>{u();or=pe(th()),rh=or.default,HI=or.default.objectify,WI=or.default.parse,GI=or.default.async,QI=or.default.sync});function lr(r){return Array.isArray(r)?r.flatMap(e=>ee([(0,nh.default)({bubble:[\"screen\"]})]).process(e,{parser:rh}).root.nodes):lr([r])}var nh,qo=P(()=>{u();Ot();nh=pe(Bd());ih()});function ur(r,e,t=!1){if(r===\"\")return e;let i=typeof e==\"string\"?(0,sh.default)().astSync(e):e;return i.walkClasses(n=>{let s=n.value,a=t&&s.startsWith(\"-\");n.value=a?`-${r}${s.slice(1)}`:`${r}${s}`}),typeof e==\"string\"?i.toString():i}var sh,Gn=P(()=>{u();sh=pe(it())});function Te(r){let e=ah.default.className();return e.value=r,jt(e?.raws?.value??e.value)}var ah,fr=P(()=>{u();ah=pe(it());Zi()});function $o(r){return jt(`.${Te(r)}`)}function Qn(r,e){return $o(Ei(r,e))}function Ei(r,e){return e===\"DEFAULT\"?r:e===\"-\"||e===\"-DEFAULT\"?`-${r}`:e.startsWith(\"-\")?`-${r}${e}`:e.startsWith(\"/\")?`${r}${e}`:`${r}-${e}`}var Lo=P(()=>{u();fr();Zi()});function L(r,e=[[r,[r]]],{filterDefault:t=!1,...i}={}){let n=mt(r);return function({matchUtilities:s,theme:a}){for(let o of e){let l=Array.isArray(o[0])?o:[o];s(l.reduce((c,[f,d])=>Object.assign(c,{[f]:p=>d.reduce((h,b)=>Array.isArray(b)?Object.assign(h,{[b[0]]:b[1]}):Object.assign(h,{[b]:n(p)}),{})}),{}),{...i,values:t?Object.fromEntries(Object.entries(a(r)??{}).filter(([c])=>c!==\"DEFAULT\")):a(r)})}}}var oh=P(()=>{u();Ci()});function Tt(r){return r=Array.isArray(r)?r:[r],r.map(e=>{let t=e.values.map(i=>i.raw!==void 0?i.raw:[i.min&&`(min-width: ${i.min})`,i.max&&`(max-width: ${i.max})`].filter(Boolean).join(\" and \"));return e.not?`not all and ${t}`:t}).join(\", \")}var Yn=P(()=>{u()});function Mo(r){return r.split(f_).map(t=>{let i=t.trim(),n={value:i},s=i.split(c_),a=new Set;for(let o of s)!a.has(\"DIRECTIONS\")&&n_.has(o)?(n.direction=o,a.add(\"DIRECTIONS\")):!a.has(\"PLAY_STATES\")&&s_.has(o)?(n.playState=o,a.add(\"PLAY_STATES\")):!a.has(\"FILL_MODES\")&&a_.has(o)?(n.fillMode=o,a.add(\"FILL_MODES\")):!a.has(\"ITERATION_COUNTS\")&&(o_.has(o)||p_.test(o))?(n.iterationCount=o,a.add(\"ITERATION_COUNTS\")):!a.has(\"TIMING_FUNCTION\")&&l_.has(o)||!a.has(\"TIMING_FUNCTION\")&&u_.some(l=>o.startsWith(`${l}(`))?(n.timingFunction=o,a.add(\"TIMING_FUNCTION\")):!a.has(\"DURATION\")&&lh.test(o)?(n.duration=o,a.add(\"DURATION\")):!a.has(\"DELAY\")&&lh.test(o)?(n.delay=o,a.add(\"DELAY\")):a.has(\"NAME\")?(n.unknown||(n.unknown=[]),n.unknown.push(o)):(n.name=o,a.add(\"NAME\"));return n})}var n_,s_,a_,o_,l_,u_,f_,c_,lh,p_,uh=P(()=>{u();n_=new Set([\"normal\",\"reverse\",\"alternate\",\"alternate-reverse\"]),s_=new Set([\"running\",\"paused\"]),a_=new Set([\"none\",\"forwards\",\"backwards\",\"both\"]),o_=new Set([\"infinite\"]),l_=new Set([\"linear\",\"ease\",\"ease-in\",\"ease-out\",\"ease-in-out\",\"step-start\",\"step-end\"]),u_=[\"cubic-bezier\",\"steps\"],f_=/\\,(?![^(]*\\))/g,c_=/\\ +(?![^(]*\\))/g,lh=/^(-?[\\d.]+m?s)$/,p_=/^(\\d+)$/});var fh,xe,ch=P(()=>{u();fh=r=>Object.assign({},...Object.entries(r??{}).flatMap(([e,t])=>typeof t==\"object\"?Object.entries(fh(t)).map(([i,n])=>({[e+(i===\"DEFAULT\"?\"\":`-${i}`)]:n})):[{[`${e}`]:t}])),xe=fh});var dh,ph=P(()=>{dh=\"3.4.17\"});function Rt(r,e=!0){return Array.isArray(r)?r.map(t=>{if(e&&Array.isArray(t))throw new Error(\"The tuple syntax is not supported for `screens`.\");if(typeof t==\"string\")return{name:t.toString(),not:!1,values:[{min:t,max:void 0}]};let[i,n]=t;return i=i.toString(),typeof n==\"string\"?{name:i,not:!1,values:[{min:n,max:void 0}]}:Array.isArray(n)?{name:i,not:!1,values:n.map(s=>mh(s))}:{name:i,not:!1,values:[mh(n)]}}):Rt(Object.entries(r??{}),!1)}function Kn(r){return r.values.length!==1?{result:!1,reason:\"multiple-values\"}:r.values[0].raw!==void 0?{result:!1,reason:\"raw-values\"}:r.values[0].min!==void 0&&r.values[0].max!==void 0?{result:!1,reason:\"min-and-max\"}:{result:!0,reason:null}}function hh(r,e,t){let i=Xn(e,r),n=Xn(t,r),s=Kn(i),a=Kn(n);if(s.reason===\"multiple-values\"||a.reason===\"multiple-values\")throw new Error(\"Attempted to sort a screen with multiple values. This should never happen. Please open a bug report.\");if(s.reason===\"raw-values\"||a.reason===\"raw-values\")throw new Error(\"Attempted to sort a screen with raw values. This should never happen. Please open a bug report.\");if(s.reason===\"min-and-max\"||a.reason===\"min-and-max\")throw new Error(\"Attempted to sort a screen with both min and max values. This should never happen. Please open a bug report.\");let{min:o,max:l}=i.values[0],{min:c,max:f}=n.values[0];e.not&&([o,l]=[l,o]),t.not&&([c,f]=[f,c]),o=o===void 0?o:parseFloat(o),l=l===void 0?l:parseFloat(l),c=c===void 0?c:parseFloat(c),f=f===void 0?f:parseFloat(f);let[d,p]=r===\"min\"?[o,c]:[f,l];return d-p}function Xn(r,e){return typeof r==\"object\"?r:{name:\"arbitrary-screen\",values:[{[e]:r}]}}function mh({\"min-width\":r,min:e=r,max:t,raw:i}={}){return{min:e,max:t,raw:i}}var Zn=P(()=>{u()});function Jn(r,e){r.walkDecls(t=>{if(e.includes(t.prop)){t.remove();return}for(let i of e)t.value.includes(`/ var(${i})`)?t.value=t.value.replace(`/ var(${i})`,\"\"):t.value.includes(`/ var(${i}, 1)`)&&(t.value=t.value.replace(`/ var(${i}, 1)`,\"\"))})}var gh=P(()=>{u()});var se,Xe,nt,ge,yh,bh=P(()=>{u();ft();et();Ot();oh();Yn();fr();uh();ch();Lr();ra();Kt();Ci();ph();Be();Zn();Ys();gh();ct();Br();Oi();se={childVariant:({addVariant:r})=>{r(\"*\",\"& > *\")},pseudoElementVariants:({addVariant:r})=>{r(\"first-letter\",\"&::first-letter\"),r(\"first-line\",\"&::first-line\"),r(\"marker\",[({container:e})=>(Jn(e,[\"--tw-text-opacity\"]),\"& *::marker\"),({container:e})=>(Jn(e,[\"--tw-text-opacity\"]),\"&::marker\")]),r(\"selection\",[\"& *::selection\",\"&::selection\"]),r(\"file\",\"&::file-selector-button\"),r(\"placeholder\",\"&::placeholder\"),r(\"backdrop\",\"&::backdrop\"),r(\"before\",({container:e})=>(e.walkRules(t=>{let i=!1;t.walkDecls(\"content\",()=>{i=!0}),i||t.prepend(ee.decl({prop:\"content\",value:\"var(--tw-content)\"}))}),\"&::before\")),r(\"after\",({container:e})=>(e.walkRules(t=>{let i=!1;t.walkDecls(\"content\",()=>{i=!0}),i||t.prepend(ee.decl({prop:\"content\",value:\"var(--tw-content)\"}))}),\"&::after\"))},pseudoClassVariants:({addVariant:r,matchVariant:e,config:t,prefix:i})=>{let n=[[\"first\",\"&:first-child\"],[\"last\",\"&:last-child\"],[\"only\",\"&:only-child\"],[\"odd\",\"&:nth-child(odd)\"],[\"even\",\"&:nth-child(even)\"],\"first-of-type\",\"last-of-type\",\"only-of-type\",[\"visited\",({container:a})=>(Jn(a,[\"--tw-text-opacity\",\"--tw-border-opacity\",\"--tw-bg-opacity\"]),\"&:visited\")],\"target\",[\"open\",\"&[open]\"],\"default\",\"checked\",\"indeterminate\",\"placeholder-shown\",\"autofill\",\"optional\",\"required\",\"valid\",\"invalid\",\"in-range\",\"out-of-range\",\"read-only\",\"empty\",\"focus-within\",[\"hover\",we(t(),\"hoverOnlyWhenSupported\")?\"@media (hover: hover) and (pointer: fine) { &:hover }\":\"&:hover\"],\"focus\",\"focus-visible\",\"active\",\"enabled\",\"disabled\"].map(a=>Array.isArray(a)?a:[a,`&:${a}`]);for(let[a,o]of n)r(a,l=>typeof o==\"function\"?o(l):o);let s={group:(a,{modifier:o})=>o?[`:merge(${i(\".group\")}\\\\/${Te(o)})`,\" &\"]:[`:merge(${i(\".group\")})`,\" &\"],peer:(a,{modifier:o})=>o?[`:merge(${i(\".peer\")}\\\\/${Te(o)})`,\" ~ &\"]:[`:merge(${i(\".peer\")})`,\" ~ &\"]};for(let[a,o]of Object.entries(s))e(a,(l=\"\",c)=>{let f=K(typeof l==\"function\"?l(c):l);f.includes(\"&\")||(f=\"&\"+f);let[d,p]=o(\"\",c),h=null,b=null,v=0;for(let y=0;y{r(\"ltr\",'&:where([dir=\"ltr\"], [dir=\"ltr\"] *)'),r(\"rtl\",'&:where([dir=\"rtl\"], [dir=\"rtl\"] *)')},reducedMotionVariants:({addVariant:r})=>{r(\"motion-safe\",\"@media (prefers-reduced-motion: no-preference)\"),r(\"motion-reduce\",\"@media (prefers-reduced-motion: reduce)\")},darkVariants:({config:r,addVariant:e})=>{let[t,i=\".dark\"]=[].concat(r(\"darkMode\",\"media\"));if(t===!1&&(t=\"media\",G.warn(\"darkmode-false\",[\"The `darkMode` option in your Tailwind CSS configuration is set to `false`, which now behaves the same as `media`.\",\"Change `darkMode` to `media` or remove it entirely.\",\"https://tailwindcss.com/docs/upgrade-guide#remove-dark-mode-configuration\"])),t===\"variant\"){let n;if(Array.isArray(i)||typeof i==\"function\"?n=i:typeof i==\"string\"&&(n=[i]),Array.isArray(n))for(let s of n)s===\".dark\"?(t=!1,G.warn(\"darkmode-variant-without-selector\",[\"When using `variant` for `darkMode`, you must provide a selector.\",'Example: `darkMode: [\"variant\", \".your-selector &\"]`'])):s.includes(\"&\")||(t=!1,G.warn(\"darkmode-variant-without-ampersand\",[\"When using `variant` for `darkMode`, your selector must contain `&`.\",'Example `darkMode: [\"variant\", \".your-selector &\"]`']));i=n}t===\"selector\"?e(\"dark\",`&:where(${i}, ${i} *)`):t===\"media\"?e(\"dark\",\"@media (prefers-color-scheme: dark)\"):t===\"variant\"?e(\"dark\",i):t===\"class\"&&e(\"dark\",`&:is(${i} *)`)},printVariant:({addVariant:r})=>{r(\"print\",\"@media print\")},screenVariants:({theme:r,addVariant:e,matchVariant:t})=>{let i=r(\"screens\")??{},n=Object.values(i).every(w=>typeof w==\"string\"),s=Rt(r(\"screens\")),a=new Set([]);function o(w){return w.match(/(\\D+)$/)?.[1]??\"(none)\"}function l(w){w!==void 0&&a.add(o(w))}function c(w){return l(w),a.size===1}for(let w of s)for(let k of w.values)l(k.min),l(k.max);let f=a.size<=1;function d(w){return Object.fromEntries(s.filter(k=>Kn(k).result).map(k=>{let{min:S,max:E}=k.values[0];if(w===\"min\"&&S!==void 0)return k;if(w===\"min\"&&E!==void 0)return{...k,not:!k.not};if(w===\"max\"&&E!==void 0)return k;if(w===\"max\"&&S!==void 0)return{...k,not:!k.not}}).map(k=>[k.name,k]))}function p(w){return(k,S)=>hh(w,k.value,S.value)}let h=p(\"max\"),b=p(\"min\");function v(w){return k=>{if(n)if(f){if(typeof k==\"string\"&&!c(k))return G.warn(\"minmax-have-mixed-units\",[\"The `min-*` and `max-*` variants are not supported with a `screens` configuration containing mixed units.\"]),[]}else return G.warn(\"mixed-screen-units\",[\"The `min-*` and `max-*` variants are not supported with a `screens` configuration containing mixed units.\"]),[];else return G.warn(\"complex-screen-config\",[\"The `min-*` and `max-*` variants are not supported with a `screens` configuration containing objects.\"]),[];return[`@media ${Tt(Xn(k,w))}`]}}t(\"max\",v(\"max\"),{sort:h,values:n?d(\"max\"):{}});let y=\"min-screens\";for(let w of s)e(w.name,`@media ${Tt(w)}`,{id:y,sort:n&&f?b:void 0,value:w});t(\"min\",v(\"min\"),{id:y,sort:b})},supportsVariants:({matchVariant:r,theme:e})=>{r(\"supports\",(t=\"\")=>{let i=K(t),n=/^\\w*\\s*\\(/.test(i);return i=n?i.replace(/\\b(and|or|not)\\b/g,\" $1 \"):i,n?`@supports ${i}`:(i.includes(\":\")||(i=`${i}: var(--tw)`),i.startsWith(\"(\")&&i.endsWith(\")\")||(i=`(${i})`),`@supports ${i}`)},{values:e(\"supports\")??{}})},hasVariants:({matchVariant:r,prefix:e})=>{r(\"has\",t=>`&:has(${K(t)})`,{values:{},[Pt]:{respectPrefix:!1}}),r(\"group-has\",(t,{modifier:i})=>i?`:merge(${e(\".group\")}\\\\/${i}):has(${K(t)}) &`:`:merge(${e(\".group\")}):has(${K(t)}) &`,{values:{},[Pt]:{respectPrefix:!1}}),r(\"peer-has\",(t,{modifier:i})=>i?`:merge(${e(\".peer\")}\\\\/${i}):has(${K(t)}) ~ &`:`:merge(${e(\".peer\")}):has(${K(t)}) ~ &`,{values:{},[Pt]:{respectPrefix:!1}})},ariaVariants:({matchVariant:r,theme:e})=>{r(\"aria\",t=>`&[aria-${Ye(K(t))}]`,{values:e(\"aria\")??{}}),r(\"group-aria\",(t,{modifier:i})=>i?`:merge(.group\\\\/${i})[aria-${Ye(K(t))}] &`:`:merge(.group)[aria-${Ye(K(t))}] &`,{values:e(\"aria\")??{}}),r(\"peer-aria\",(t,{modifier:i})=>i?`:merge(.peer\\\\/${i})[aria-${Ye(K(t))}] ~ &`:`:merge(.peer)[aria-${Ye(K(t))}] ~ &`,{values:e(\"aria\")??{}})},dataVariants:({matchVariant:r,theme:e})=>{r(\"data\",t=>`&[data-${Ye(K(t))}]`,{values:e(\"data\")??{}}),r(\"group-data\",(t,{modifier:i})=>i?`:merge(.group\\\\/${i})[data-${Ye(K(t))}] &`:`:merge(.group)[data-${Ye(K(t))}] &`,{values:e(\"data\")??{}}),r(\"peer-data\",(t,{modifier:i})=>i?`:merge(.peer\\\\/${i})[data-${Ye(K(t))}] ~ &`:`:merge(.peer)[data-${Ye(K(t))}] ~ &`,{values:e(\"data\")??{}})},orientationVariants:({addVariant:r})=>{r(\"portrait\",\"@media (orientation: portrait)\"),r(\"landscape\",\"@media (orientation: landscape)\")},prefersContrastVariants:({addVariant:r})=>{r(\"contrast-more\",\"@media (prefers-contrast: more)\"),r(\"contrast-less\",\"@media (prefers-contrast: less)\")},forcedColorsVariants:({addVariant:r})=>{r(\"forced-colors\",\"@media (forced-colors: active)\")}},Xe=[\"translate(var(--tw-translate-x), var(--tw-translate-y))\",\"rotate(var(--tw-rotate))\",\"skewX(var(--tw-skew-x))\",\"skewY(var(--tw-skew-y))\",\"scaleX(var(--tw-scale-x))\",\"scaleY(var(--tw-scale-y))\"].join(\" \"),nt=[\"var(--tw-blur)\",\"var(--tw-brightness)\",\"var(--tw-contrast)\",\"var(--tw-grayscale)\",\"var(--tw-hue-rotate)\",\"var(--tw-invert)\",\"var(--tw-saturate)\",\"var(--tw-sepia)\",\"var(--tw-drop-shadow)\"].join(\" \"),ge=[\"var(--tw-backdrop-blur)\",\"var(--tw-backdrop-brightness)\",\"var(--tw-backdrop-contrast)\",\"var(--tw-backdrop-grayscale)\",\"var(--tw-backdrop-hue-rotate)\",\"var(--tw-backdrop-invert)\",\"var(--tw-backdrop-opacity)\",\"var(--tw-backdrop-saturate)\",\"var(--tw-backdrop-sepia)\"].join(\" \"),yh={preflight:({addBase:r})=>{let e=ee.parse(`*,::after,::before{box-sizing:border-box;border-width:0;border-style:solid;border-color:theme('borderColor.DEFAULT', currentColor)}::after,::before{--tw-content:''}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;tab-size:4;font-family:theme('fontFamily.sans', ui-sans-serif, system-ui, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\");font-feature-settings:theme('fontFamily.sans[1].fontFeatureSettings', normal);font-variation-settings:theme('fontFamily.sans[1].fontVariationSettings', normal);-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:theme('fontFamily.mono', ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace);font-feature-settings:theme('fontFamily.mono[1].fontFeatureSettings', normal);font-variation-settings:theme('fontFamily.mono[1].fontVariationSettings', normal);font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::placeholder,textarea::placeholder{opacity:1;color:theme('colors.gray.4', #9ca3af)}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}`);r([ee.comment({text:`! tailwindcss v${dh} | MIT License | https://tailwindcss.com`}),...e.nodes])},container:(()=>{function r(t=[]){return t.flatMap(i=>i.values.map(n=>n.min)).filter(i=>i!==void 0)}function e(t,i,n){if(typeof n==\"undefined\")return[];if(!(typeof n==\"object\"&&n!==null))return[{screen:\"DEFAULT\",minWidth:0,padding:n}];let s=[];n.DEFAULT&&s.push({screen:\"DEFAULT\",minWidth:0,padding:n.DEFAULT});for(let a of t)for(let o of i)for(let{min:l}of o.values)l===a&&s.push({minWidth:a,padding:n[o.name]});return s}return function({addComponents:t,theme:i}){let n=Rt(i(\"container.screens\",i(\"screens\"))),s=r(n),a=e(s,n,i(\"container.padding\")),o=c=>{let f=a.find(d=>d.minWidth===c);return f?{paddingRight:f.padding,paddingLeft:f.padding}:{}},l=Array.from(new Set(s.slice().sort((c,f)=>parseInt(c)-parseInt(f)))).map(c=>({[`@media (min-width: ${c})`]:{\".container\":{\"max-width\":c,...o(c)}}}));t([{\".container\":Object.assign({width:\"100%\"},i(\"container.center\",!1)?{marginRight:\"auto\",marginLeft:\"auto\"}:{},o(0))},...l])}})(),accessibility:({addUtilities:r})=>{r({\".sr-only\":{position:\"absolute\",width:\"1px\",height:\"1px\",padding:\"0\",margin:\"-1px\",overflow:\"hidden\",clip:\"rect(0, 0, 0, 0)\",whiteSpace:\"nowrap\",borderWidth:\"0\"},\".not-sr-only\":{position:\"static\",width:\"auto\",height:\"auto\",padding:\"0\",margin:\"0\",overflow:\"visible\",clip:\"auto\",whiteSpace:\"normal\"}})},pointerEvents:({addUtilities:r})=>{r({\".pointer-events-none\":{\"pointer-events\":\"none\"},\".pointer-events-auto\":{\"pointer-events\":\"auto\"}})},visibility:({addUtilities:r})=>{r({\".visible\":{visibility:\"visible\"},\".invisible\":{visibility:\"hidden\"},\".collapse\":{visibility:\"collapse\"}})},position:({addUtilities:r})=>{r({\".static\":{position:\"static\"},\".fixed\":{position:\"fixed\"},\".absolute\":{position:\"absolute\"},\".relative\":{position:\"relative\"},\".sticky\":{position:\"sticky\"}})},inset:L(\"inset\",[[\"inset\",[\"inset\"]],[[\"inset-x\",[\"left\",\"right\"]],[\"inset-y\",[\"top\",\"bottom\"]]],[[\"start\",[\"inset-inline-start\"]],[\"end\",[\"inset-inline-end\"]],[\"top\",[\"top\"]],[\"right\",[\"right\"]],[\"bottom\",[\"bottom\"]],[\"left\",[\"left\"]]]],{supportsNegativeValues:!0}),isolation:({addUtilities:r})=>{r({\".isolate\":{isolation:\"isolate\"},\".isolation-auto\":{isolation:\"auto\"}})},zIndex:L(\"zIndex\",[[\"z\",[\"zIndex\"]]],{supportsNegativeValues:!0}),order:L(\"order\",void 0,{supportsNegativeValues:!0}),gridColumn:L(\"gridColumn\",[[\"col\",[\"gridColumn\"]]]),gridColumnStart:L(\"gridColumnStart\",[[\"col-start\",[\"gridColumnStart\"]]],{supportsNegativeValues:!0}),gridColumnEnd:L(\"gridColumnEnd\",[[\"col-end\",[\"gridColumnEnd\"]]],{supportsNegativeValues:!0}),gridRow:L(\"gridRow\",[[\"row\",[\"gridRow\"]]]),gridRowStart:L(\"gridRowStart\",[[\"row-start\",[\"gridRowStart\"]]],{supportsNegativeValues:!0}),gridRowEnd:L(\"gridRowEnd\",[[\"row-end\",[\"gridRowEnd\"]]],{supportsNegativeValues:!0}),float:({addUtilities:r})=>{r({\".float-start\":{float:\"inline-start\"},\".float-end\":{float:\"inline-end\"},\".float-right\":{float:\"right\"},\".float-left\":{float:\"left\"},\".float-none\":{float:\"none\"}})},clear:({addUtilities:r})=>{r({\".clear-start\":{clear:\"inline-start\"},\".clear-end\":{clear:\"inline-end\"},\".clear-left\":{clear:\"left\"},\".clear-right\":{clear:\"right\"},\".clear-both\":{clear:\"both\"},\".clear-none\":{clear:\"none\"}})},margin:L(\"margin\",[[\"m\",[\"margin\"]],[[\"mx\",[\"margin-left\",\"margin-right\"]],[\"my\",[\"margin-top\",\"margin-bottom\"]]],[[\"ms\",[\"margin-inline-start\"]],[\"me\",[\"margin-inline-end\"]],[\"mt\",[\"margin-top\"]],[\"mr\",[\"margin-right\"]],[\"mb\",[\"margin-bottom\"]],[\"ml\",[\"margin-left\"]]]],{supportsNegativeValues:!0}),boxSizing:({addUtilities:r})=>{r({\".box-border\":{\"box-sizing\":\"border-box\"},\".box-content\":{\"box-sizing\":\"content-box\"}})},lineClamp:({matchUtilities:r,addUtilities:e,theme:t})=>{r({\"line-clamp\":i=>({overflow:\"hidden\",display:\"-webkit-box\",\"-webkit-box-orient\":\"vertical\",\"-webkit-line-clamp\":`${i}`})},{values:t(\"lineClamp\")}),e({\".line-clamp-none\":{overflow:\"visible\",display:\"block\",\"-webkit-box-orient\":\"horizontal\",\"-webkit-line-clamp\":\"none\"}})},display:({addUtilities:r})=>{r({\".block\":{display:\"block\"},\".inline-block\":{display:\"inline-block\"},\".inline\":{display:\"inline\"},\".flex\":{display:\"flex\"},\".inline-flex\":{display:\"inline-flex\"},\".table\":{display:\"table\"},\".inline-table\":{display:\"inline-table\"},\".table-caption\":{display:\"table-caption\"},\".table-cell\":{display:\"table-cell\"},\".table-column\":{display:\"table-column\"},\".table-column-group\":{display:\"table-column-group\"},\".table-footer-group\":{display:\"table-footer-group\"},\".table-header-group\":{display:\"table-header-group\"},\".table-row-group\":{display:\"table-row-group\"},\".table-row\":{display:\"table-row\"},\".flow-root\":{display:\"flow-root\"},\".grid\":{display:\"grid\"},\".inline-grid\":{display:\"inline-grid\"},\".contents\":{display:\"contents\"},\".list-item\":{display:\"list-item\"},\".hidden\":{display:\"none\"}})},aspectRatio:L(\"aspectRatio\",[[\"aspect\",[\"aspect-ratio\"]]]),size:L(\"size\",[[\"size\",[\"width\",\"height\"]]]),height:L(\"height\",[[\"h\",[\"height\"]]]),maxHeight:L(\"maxHeight\",[[\"max-h\",[\"maxHeight\"]]]),minHeight:L(\"minHeight\",[[\"min-h\",[\"minHeight\"]]]),width:L(\"width\",[[\"w\",[\"width\"]]]),minWidth:L(\"minWidth\",[[\"min-w\",[\"minWidth\"]]]),maxWidth:L(\"maxWidth\",[[\"max-w\",[\"maxWidth\"]]]),flex:L(\"flex\"),flexShrink:L(\"flexShrink\",[[\"flex-shrink\",[\"flex-shrink\"]],[\"shrink\",[\"flex-shrink\"]]]),flexGrow:L(\"flexGrow\",[[\"flex-grow\",[\"flex-grow\"]],[\"grow\",[\"flex-grow\"]]]),flexBasis:L(\"flexBasis\",[[\"basis\",[\"flex-basis\"]]]),tableLayout:({addUtilities:r})=>{r({\".table-auto\":{\"table-layout\":\"auto\"},\".table-fixed\":{\"table-layout\":\"fixed\"}})},captionSide:({addUtilities:r})=>{r({\".caption-top\":{\"caption-side\":\"top\"},\".caption-bottom\":{\"caption-side\":\"bottom\"}})},borderCollapse:({addUtilities:r})=>{r({\".border-collapse\":{\"border-collapse\":\"collapse\"},\".border-separate\":{\"border-collapse\":\"separate\"}})},borderSpacing:({addDefaults:r,matchUtilities:e,theme:t})=>{r(\"border-spacing\",{\"--tw-border-spacing-x\":0,\"--tw-border-spacing-y\":0}),e({\"border-spacing\":i=>({\"--tw-border-spacing-x\":i,\"--tw-border-spacing-y\":i,\"@defaults border-spacing\":{},\"border-spacing\":\"var(--tw-border-spacing-x) var(--tw-border-spacing-y)\"}),\"border-spacing-x\":i=>({\"--tw-border-spacing-x\":i,\"@defaults border-spacing\":{},\"border-spacing\":\"var(--tw-border-spacing-x) var(--tw-border-spacing-y)\"}),\"border-spacing-y\":i=>({\"--tw-border-spacing-y\":i,\"@defaults border-spacing\":{},\"border-spacing\":\"var(--tw-border-spacing-x) var(--tw-border-spacing-y)\"})},{values:t(\"borderSpacing\")})},transformOrigin:L(\"transformOrigin\",[[\"origin\",[\"transformOrigin\"]]]),translate:L(\"translate\",[[[\"translate-x\",[[\"@defaults transform\",{}],\"--tw-translate-x\",[\"transform\",Xe]]],[\"translate-y\",[[\"@defaults transform\",{}],\"--tw-translate-y\",[\"transform\",Xe]]]]],{supportsNegativeValues:!0}),rotate:L(\"rotate\",[[\"rotate\",[[\"@defaults transform\",{}],\"--tw-rotate\",[\"transform\",Xe]]]],{supportsNegativeValues:!0}),skew:L(\"skew\",[[[\"skew-x\",[[\"@defaults transform\",{}],\"--tw-skew-x\",[\"transform\",Xe]]],[\"skew-y\",[[\"@defaults transform\",{}],\"--tw-skew-y\",[\"transform\",Xe]]]]],{supportsNegativeValues:!0}),scale:L(\"scale\",[[\"scale\",[[\"@defaults transform\",{}],\"--tw-scale-x\",\"--tw-scale-y\",[\"transform\",Xe]]],[[\"scale-x\",[[\"@defaults transform\",{}],\"--tw-scale-x\",[\"transform\",Xe]]],[\"scale-y\",[[\"@defaults transform\",{}],\"--tw-scale-y\",[\"transform\",Xe]]]]],{supportsNegativeValues:!0}),transform:({addDefaults:r,addUtilities:e})=>{r(\"transform\",{\"--tw-translate-x\":\"0\",\"--tw-translate-y\":\"0\",\"--tw-rotate\":\"0\",\"--tw-skew-x\":\"0\",\"--tw-skew-y\":\"0\",\"--tw-scale-x\":\"1\",\"--tw-scale-y\":\"1\"}),e({\".transform\":{\"@defaults transform\":{},transform:Xe},\".transform-cpu\":{transform:Xe},\".transform-gpu\":{transform:Xe.replace(\"translate(var(--tw-translate-x), var(--tw-translate-y))\",\"translate3d(var(--tw-translate-x), var(--tw-translate-y), 0)\")},\".transform-none\":{transform:\"none\"}})},animation:({matchUtilities:r,theme:e,config:t})=>{let i=s=>Te(t(\"prefix\")+s),n=Object.fromEntries(Object.entries(e(\"keyframes\")??{}).map(([s,a])=>[s,{[`@keyframes ${i(s)}`]:a}]));r({animate:s=>{let a=Mo(s);return[...a.flatMap(o=>n[o.name]),{animation:a.map(({name:o,value:l})=>o===void 0||n[o]===void 0?l:l.replace(o,i(o))).join(\", \")}]}},{values:e(\"animation\")})},cursor:L(\"cursor\"),touchAction:({addDefaults:r,addUtilities:e})=>{r(\"touch-action\",{\"--tw-pan-x\":\" \",\"--tw-pan-y\":\" \",\"--tw-pinch-zoom\":\" \"});let t=\"var(--tw-pan-x) var(--tw-pan-y) var(--tw-pinch-zoom)\";e({\".touch-auto\":{\"touch-action\":\"auto\"},\".touch-none\":{\"touch-action\":\"none\"},\".touch-pan-x\":{\"@defaults touch-action\":{},\"--tw-pan-x\":\"pan-x\",\"touch-action\":t},\".touch-pan-left\":{\"@defaults touch-action\":{},\"--tw-pan-x\":\"pan-left\",\"touch-action\":t},\".touch-pan-right\":{\"@defaults touch-action\":{},\"--tw-pan-x\":\"pan-right\",\"touch-action\":t},\".touch-pan-y\":{\"@defaults touch-action\":{},\"--tw-pan-y\":\"pan-y\",\"touch-action\":t},\".touch-pan-up\":{\"@defaults touch-action\":{},\"--tw-pan-y\":\"pan-up\",\"touch-action\":t},\".touch-pan-down\":{\"@defaults touch-action\":{},\"--tw-pan-y\":\"pan-down\",\"touch-action\":t},\".touch-pinch-zoom\":{\"@defaults touch-action\":{},\"--tw-pinch-zoom\":\"pinch-zoom\",\"touch-action\":t},\".touch-manipulation\":{\"touch-action\":\"manipulation\"}})},userSelect:({addUtilities:r})=>{r({\".select-none\":{\"user-select\":\"none\"},\".select-text\":{\"user-select\":\"text\"},\".select-all\":{\"user-select\":\"all\"},\".select-auto\":{\"user-select\":\"auto\"}})},resize:({addUtilities:r})=>{r({\".resize-none\":{resize:\"none\"},\".resize-y\":{resize:\"vertical\"},\".resize-x\":{resize:\"horizontal\"},\".resize\":{resize:\"both\"}})},scrollSnapType:({addDefaults:r,addUtilities:e})=>{r(\"scroll-snap-type\",{\"--tw-scroll-snap-strictness\":\"proximity\"}),e({\".snap-none\":{\"scroll-snap-type\":\"none\"},\".snap-x\":{\"@defaults scroll-snap-type\":{},\"scroll-snap-type\":\"x var(--tw-scroll-snap-strictness)\"},\".snap-y\":{\"@defaults scroll-snap-type\":{},\"scroll-snap-type\":\"y var(--tw-scroll-snap-strictness)\"},\".snap-both\":{\"@defaults scroll-snap-type\":{},\"scroll-snap-type\":\"both var(--tw-scroll-snap-strictness)\"},\".snap-mandatory\":{\"--tw-scroll-snap-strictness\":\"mandatory\"},\".snap-proximity\":{\"--tw-scroll-snap-strictness\":\"proximity\"}})},scrollSnapAlign:({addUtilities:r})=>{r({\".snap-start\":{\"scroll-snap-align\":\"start\"},\".snap-end\":{\"scroll-snap-align\":\"end\"},\".snap-center\":{\"scroll-snap-align\":\"center\"},\".snap-align-none\":{\"scroll-snap-align\":\"none\"}})},scrollSnapStop:({addUtilities:r})=>{r({\".snap-normal\":{\"scroll-snap-stop\":\"normal\"},\".snap-always\":{\"scroll-snap-stop\":\"always\"}})},scrollMargin:L(\"scrollMargin\",[[\"scroll-m\",[\"scroll-margin\"]],[[\"scroll-mx\",[\"scroll-margin-left\",\"scroll-margin-right\"]],[\"scroll-my\",[\"scroll-margin-top\",\"scroll-margin-bottom\"]]],[[\"scroll-ms\",[\"scroll-margin-inline-start\"]],[\"scroll-me\",[\"scroll-margin-inline-end\"]],[\"scroll-mt\",[\"scroll-margin-top\"]],[\"scroll-mr\",[\"scroll-margin-right\"]],[\"scroll-mb\",[\"scroll-margin-bottom\"]],[\"scroll-ml\",[\"scroll-margin-left\"]]]],{supportsNegativeValues:!0}),scrollPadding:L(\"scrollPadding\",[[\"scroll-p\",[\"scroll-padding\"]],[[\"scroll-px\",[\"scroll-padding-left\",\"scroll-padding-right\"]],[\"scroll-py\",[\"scroll-padding-top\",\"scroll-padding-bottom\"]]],[[\"scroll-ps\",[\"scroll-padding-inline-start\"]],[\"scroll-pe\",[\"scroll-padding-inline-end\"]],[\"scroll-pt\",[\"scroll-padding-top\"]],[\"scroll-pr\",[\"scroll-padding-right\"]],[\"scroll-pb\",[\"scroll-padding-bottom\"]],[\"scroll-pl\",[\"scroll-padding-left\"]]]]),listStylePosition:({addUtilities:r})=>{r({\".list-inside\":{\"list-style-position\":\"inside\"},\".list-outside\":{\"list-style-position\":\"outside\"}})},listStyleType:L(\"listStyleType\",[[\"list\",[\"listStyleType\"]]]),listStyleImage:L(\"listStyleImage\",[[\"list-image\",[\"listStyleImage\"]]]),appearance:({addUtilities:r})=>{r({\".appearance-none\":{appearance:\"none\"},\".appearance-auto\":{appearance:\"auto\"}})},columns:L(\"columns\",[[\"columns\",[\"columns\"]]]),breakBefore:({addUtilities:r})=>{r({\".break-before-auto\":{\"break-before\":\"auto\"},\".break-before-avoid\":{\"break-before\":\"avoid\"},\".break-before-all\":{\"break-before\":\"all\"},\".break-before-avoid-page\":{\"break-before\":\"avoid-page\"},\".break-before-page\":{\"break-before\":\"page\"},\".break-before-left\":{\"break-before\":\"left\"},\".break-before-right\":{\"break-before\":\"right\"},\".break-before-column\":{\"break-before\":\"column\"}})},breakInside:({addUtilities:r})=>{r({\".break-inside-auto\":{\"break-inside\":\"auto\"},\".break-inside-avoid\":{\"break-inside\":\"avoid\"},\".break-inside-avoid-page\":{\"break-inside\":\"avoid-page\"},\".break-inside-avoid-column\":{\"break-inside\":\"avoid-column\"}})},breakAfter:({addUtilities:r})=>{r({\".break-after-auto\":{\"break-after\":\"auto\"},\".break-after-avoid\":{\"break-after\":\"avoid\"},\".break-after-all\":{\"break-after\":\"all\"},\".break-after-avoid-page\":{\"break-after\":\"avoid-page\"},\".break-after-page\":{\"break-after\":\"page\"},\".break-after-left\":{\"break-after\":\"left\"},\".break-after-right\":{\"break-after\":\"right\"},\".break-after-column\":{\"break-after\":\"column\"}})},gridAutoColumns:L(\"gridAutoColumns\",[[\"auto-cols\",[\"gridAutoColumns\"]]]),gridAutoFlow:({addUtilities:r})=>{r({\".grid-flow-row\":{gridAutoFlow:\"row\"},\".grid-flow-col\":{gridAutoFlow:\"column\"},\".grid-flow-dense\":{gridAutoFlow:\"dense\"},\".grid-flow-row-dense\":{gridAutoFlow:\"row dense\"},\".grid-flow-col-dense\":{gridAutoFlow:\"column dense\"}})},gridAutoRows:L(\"gridAutoRows\",[[\"auto-rows\",[\"gridAutoRows\"]]]),gridTemplateColumns:L(\"gridTemplateColumns\",[[\"grid-cols\",[\"gridTemplateColumns\"]]]),gridTemplateRows:L(\"gridTemplateRows\",[[\"grid-rows\",[\"gridTemplateRows\"]]]),flexDirection:({addUtilities:r})=>{r({\".flex-row\":{\"flex-direction\":\"row\"},\".flex-row-reverse\":{\"flex-direction\":\"row-reverse\"},\".flex-col\":{\"flex-direction\":\"column\"},\".flex-col-reverse\":{\"flex-direction\":\"column-reverse\"}})},flexWrap:({addUtilities:r})=>{r({\".flex-wrap\":{\"flex-wrap\":\"wrap\"},\".flex-wrap-reverse\":{\"flex-wrap\":\"wrap-reverse\"},\".flex-nowrap\":{\"flex-wrap\":\"nowrap\"}})},placeContent:({addUtilities:r})=>{r({\".place-content-center\":{\"place-content\":\"center\"},\".place-content-start\":{\"place-content\":\"start\"},\".place-content-end\":{\"place-content\":\"end\"},\".place-content-between\":{\"place-content\":\"space-between\"},\".place-content-around\":{\"place-content\":\"space-around\"},\".place-content-evenly\":{\"place-content\":\"space-evenly\"},\".place-content-baseline\":{\"place-content\":\"baseline\"},\".place-content-stretch\":{\"place-content\":\"stretch\"}})},placeItems:({addUtilities:r})=>{r({\".place-items-start\":{\"place-items\":\"start\"},\".place-items-end\":{\"place-items\":\"end\"},\".place-items-center\":{\"place-items\":\"center\"},\".place-items-baseline\":{\"place-items\":\"baseline\"},\".place-items-stretch\":{\"place-items\":\"stretch\"}})},alignContent:({addUtilities:r})=>{r({\".content-normal\":{\"align-content\":\"normal\"},\".content-center\":{\"align-content\":\"center\"},\".content-start\":{\"align-content\":\"flex-start\"},\".content-end\":{\"align-content\":\"flex-end\"},\".content-between\":{\"align-content\":\"space-between\"},\".content-around\":{\"align-content\":\"space-around\"},\".content-evenly\":{\"align-content\":\"space-evenly\"},\".content-baseline\":{\"align-content\":\"baseline\"},\".content-stretch\":{\"align-content\":\"stretch\"}})},alignItems:({addUtilities:r})=>{r({\".items-start\":{\"align-items\":\"flex-start\"},\".items-end\":{\"align-items\":\"flex-end\"},\".items-center\":{\"align-items\":\"center\"},\".items-baseline\":{\"align-items\":\"baseline\"},\".items-stretch\":{\"align-items\":\"stretch\"}})},justifyContent:({addUtilities:r})=>{r({\".justify-normal\":{\"justify-content\":\"normal\"},\".justify-start\":{\"justify-content\":\"flex-start\"},\".justify-end\":{\"justify-content\":\"flex-end\"},\".justify-center\":{\"justify-content\":\"center\"},\".justify-between\":{\"justify-content\":\"space-between\"},\".justify-around\":{\"justify-content\":\"space-around\"},\".justify-evenly\":{\"justify-content\":\"space-evenly\"},\".justify-stretch\":{\"justify-content\":\"stretch\"}})},justifyItems:({addUtilities:r})=>{r({\".justify-items-start\":{\"justify-items\":\"start\"},\".justify-items-end\":{\"justify-items\":\"end\"},\".justify-items-center\":{\"justify-items\":\"center\"},\".justify-items-stretch\":{\"justify-items\":\"stretch\"}})},gap:L(\"gap\",[[\"gap\",[\"gap\"]],[[\"gap-x\",[\"columnGap\"]],[\"gap-y\",[\"rowGap\"]]]]),space:({matchUtilities:r,addUtilities:e,theme:t})=>{r({\"space-x\":i=>(i=i===\"0\"?\"0px\":i,{\"& > :not([hidden]) ~ :not([hidden])\":{\"--tw-space-x-reverse\":\"0\",\"margin-right\":`calc(${i} * var(--tw-space-x-reverse))`,\"margin-left\":`calc(${i} * calc(1 - var(--tw-space-x-reverse)))`}}),\"space-y\":i=>(i=i===\"0\"?\"0px\":i,{\"& > :not([hidden]) ~ :not([hidden])\":{\"--tw-space-y-reverse\":\"0\",\"margin-top\":`calc(${i} * calc(1 - var(--tw-space-y-reverse)))`,\"margin-bottom\":`calc(${i} * var(--tw-space-y-reverse))`}})},{values:t(\"space\"),supportsNegativeValues:!0}),e({\".space-y-reverse > :not([hidden]) ~ :not([hidden])\":{\"--tw-space-y-reverse\":\"1\"},\".space-x-reverse > :not([hidden]) ~ :not([hidden])\":{\"--tw-space-x-reverse\":\"1\"}})},divideWidth:({matchUtilities:r,addUtilities:e,theme:t})=>{r({\"divide-x\":i=>(i=i===\"0\"?\"0px\":i,{\"& > :not([hidden]) ~ :not([hidden])\":{\"@defaults border-width\":{},\"--tw-divide-x-reverse\":\"0\",\"border-right-width\":`calc(${i} * var(--tw-divide-x-reverse))`,\"border-left-width\":`calc(${i} * calc(1 - var(--tw-divide-x-reverse)))`}}),\"divide-y\":i=>(i=i===\"0\"?\"0px\":i,{\"& > :not([hidden]) ~ :not([hidden])\":{\"@defaults border-width\":{},\"--tw-divide-y-reverse\":\"0\",\"border-top-width\":`calc(${i} * calc(1 - var(--tw-divide-y-reverse)))`,\"border-bottom-width\":`calc(${i} * var(--tw-divide-y-reverse))`}})},{values:t(\"divideWidth\"),type:[\"line-width\",\"length\",\"any\"]}),e({\".divide-y-reverse > :not([hidden]) ~ :not([hidden])\":{\"@defaults border-width\":{},\"--tw-divide-y-reverse\":\"1\"},\".divide-x-reverse > :not([hidden]) ~ :not([hidden])\":{\"@defaults border-width\":{},\"--tw-divide-x-reverse\":\"1\"}})},divideStyle:({addUtilities:r})=>{r({\".divide-solid > :not([hidden]) ~ :not([hidden])\":{\"border-style\":\"solid\"},\".divide-dashed > :not([hidden]) ~ :not([hidden])\":{\"border-style\":\"dashed\"},\".divide-dotted > :not([hidden]) ~ :not([hidden])\":{\"border-style\":\"dotted\"},\".divide-double > :not([hidden]) ~ :not([hidden])\":{\"border-style\":\"double\"},\".divide-none > :not([hidden]) ~ :not([hidden])\":{\"border-style\":\"none\"}})},divideColor:({matchUtilities:r,theme:e,corePlugins:t})=>{r({divide:i=>t(\"divideOpacity\")?{[\"& > :not([hidden]) ~ :not([hidden])\"]:Ae({color:i,property:\"border-color\",variable:\"--tw-divide-opacity\"})}:{[\"& > :not([hidden]) ~ :not([hidden])\"]:{\"border-color\":X(i)}}},{values:(({DEFAULT:i,...n})=>n)(xe(e(\"divideColor\"))),type:[\"color\",\"any\"]})},divideOpacity:({matchUtilities:r,theme:e})=>{r({\"divide-opacity\":t=>({[\"& > :not([hidden]) ~ :not([hidden])\"]:{\"--tw-divide-opacity\":t}})},{values:e(\"divideOpacity\")})},placeSelf:({addUtilities:r})=>{r({\".place-self-auto\":{\"place-self\":\"auto\"},\".place-self-start\":{\"place-self\":\"start\"},\".place-self-end\":{\"place-self\":\"end\"},\".place-self-center\":{\"place-self\":\"center\"},\".place-self-stretch\":{\"place-self\":\"stretch\"}})},alignSelf:({addUtilities:r})=>{r({\".self-auto\":{\"align-self\":\"auto\"},\".self-start\":{\"align-self\":\"flex-start\"},\".self-end\":{\"align-self\":\"flex-end\"},\".self-center\":{\"align-self\":\"center\"},\".self-stretch\":{\"align-self\":\"stretch\"},\".self-baseline\":{\"align-self\":\"baseline\"}})},justifySelf:({addUtilities:r})=>{r({\".justify-self-auto\":{\"justify-self\":\"auto\"},\".justify-self-start\":{\"justify-self\":\"start\"},\".justify-self-end\":{\"justify-self\":\"end\"},\".justify-self-center\":{\"justify-self\":\"center\"},\".justify-self-stretch\":{\"justify-self\":\"stretch\"}})},overflow:({addUtilities:r})=>{r({\".overflow-auto\":{overflow:\"auto\"},\".overflow-hidden\":{overflow:\"hidden\"},\".overflow-clip\":{overflow:\"clip\"},\".overflow-visible\":{overflow:\"visible\"},\".overflow-scroll\":{overflow:\"scroll\"},\".overflow-x-auto\":{\"overflow-x\":\"auto\"},\".overflow-y-auto\":{\"overflow-y\":\"auto\"},\".overflow-x-hidden\":{\"overflow-x\":\"hidden\"},\".overflow-y-hidden\":{\"overflow-y\":\"hidden\"},\".overflow-x-clip\":{\"overflow-x\":\"clip\"},\".overflow-y-clip\":{\"overflow-y\":\"clip\"},\".overflow-x-visible\":{\"overflow-x\":\"visible\"},\".overflow-y-visible\":{\"overflow-y\":\"visible\"},\".overflow-x-scroll\":{\"overflow-x\":\"scroll\"},\".overflow-y-scroll\":{\"overflow-y\":\"scroll\"}})},overscrollBehavior:({addUtilities:r})=>{r({\".overscroll-auto\":{\"overscroll-behavior\":\"auto\"},\".overscroll-contain\":{\"overscroll-behavior\":\"contain\"},\".overscroll-none\":{\"overscroll-behavior\":\"none\"},\".overscroll-y-auto\":{\"overscroll-behavior-y\":\"auto\"},\".overscroll-y-contain\":{\"overscroll-behavior-y\":\"contain\"},\".overscroll-y-none\":{\"overscroll-behavior-y\":\"none\"},\".overscroll-x-auto\":{\"overscroll-behavior-x\":\"auto\"},\".overscroll-x-contain\":{\"overscroll-behavior-x\":\"contain\"},\".overscroll-x-none\":{\"overscroll-behavior-x\":\"none\"}})},scrollBehavior:({addUtilities:r})=>{r({\".scroll-auto\":{\"scroll-behavior\":\"auto\"},\".scroll-smooth\":{\"scroll-behavior\":\"smooth\"}})},textOverflow:({addUtilities:r})=>{r({\".truncate\":{overflow:\"hidden\",\"text-overflow\":\"ellipsis\",\"white-space\":\"nowrap\"},\".overflow-ellipsis\":{\"text-overflow\":\"ellipsis\"},\".text-ellipsis\":{\"text-overflow\":\"ellipsis\"},\".text-clip\":{\"text-overflow\":\"clip\"}})},hyphens:({addUtilities:r})=>{r({\".hyphens-none\":{hyphens:\"none\"},\".hyphens-manual\":{hyphens:\"manual\"},\".hyphens-auto\":{hyphens:\"auto\"}})},whitespace:({addUtilities:r})=>{r({\".whitespace-normal\":{\"white-space\":\"normal\"},\".whitespace-nowrap\":{\"white-space\":\"nowrap\"},\".whitespace-pre\":{\"white-space\":\"pre\"},\".whitespace-pre-line\":{\"white-space\":\"pre-line\"},\".whitespace-pre-wrap\":{\"white-space\":\"pre-wrap\"},\".whitespace-break-spaces\":{\"white-space\":\"break-spaces\"}})},textWrap:({addUtilities:r})=>{r({\".text-wrap\":{\"text-wrap\":\"wrap\"},\".text-nowrap\":{\"text-wrap\":\"nowrap\"},\".text-balance\":{\"text-wrap\":\"balance\"},\".text-pretty\":{\"text-wrap\":\"pretty\"}})},wordBreak:({addUtilities:r})=>{r({\".break-normal\":{\"overflow-wrap\":\"normal\",\"word-break\":\"normal\"},\".break-words\":{\"overflow-wrap\":\"break-word\"},\".break-all\":{\"word-break\":\"break-all\"},\".break-keep\":{\"word-break\":\"keep-all\"}})},borderRadius:L(\"borderRadius\",[[\"rounded\",[\"border-radius\"]],[[\"rounded-s\",[\"border-start-start-radius\",\"border-end-start-radius\"]],[\"rounded-e\",[\"border-start-end-radius\",\"border-end-end-radius\"]],[\"rounded-t\",[\"border-top-left-radius\",\"border-top-right-radius\"]],[\"rounded-r\",[\"border-top-right-radius\",\"border-bottom-right-radius\"]],[\"rounded-b\",[\"border-bottom-right-radius\",\"border-bottom-left-radius\"]],[\"rounded-l\",[\"border-top-left-radius\",\"border-bottom-left-radius\"]]],[[\"rounded-ss\",[\"border-start-start-radius\"]],[\"rounded-se\",[\"border-start-end-radius\"]],[\"rounded-ee\",[\"border-end-end-radius\"]],[\"rounded-es\",[\"border-end-start-radius\"]],[\"rounded-tl\",[\"border-top-left-radius\"]],[\"rounded-tr\",[\"border-top-right-radius\"]],[\"rounded-br\",[\"border-bottom-right-radius\"]],[\"rounded-bl\",[\"border-bottom-left-radius\"]]]]),borderWidth:L(\"borderWidth\",[[\"border\",[[\"@defaults border-width\",{}],\"border-width\"]],[[\"border-x\",[[\"@defaults border-width\",{}],\"border-left-width\",\"border-right-width\"]],[\"border-y\",[[\"@defaults border-width\",{}],\"border-top-width\",\"border-bottom-width\"]]],[[\"border-s\",[[\"@defaults border-width\",{}],\"border-inline-start-width\"]],[\"border-e\",[[\"@defaults border-width\",{}],\"border-inline-end-width\"]],[\"border-t\",[[\"@defaults border-width\",{}],\"border-top-width\"]],[\"border-r\",[[\"@defaults border-width\",{}],\"border-right-width\"]],[\"border-b\",[[\"@defaults border-width\",{}],\"border-bottom-width\"]],[\"border-l\",[[\"@defaults border-width\",{}],\"border-left-width\"]]]],{type:[\"line-width\",\"length\"]}),borderStyle:({addUtilities:r})=>{r({\".border-solid\":{\"border-style\":\"solid\"},\".border-dashed\":{\"border-style\":\"dashed\"},\".border-dotted\":{\"border-style\":\"dotted\"},\".border-double\":{\"border-style\":\"double\"},\".border-hidden\":{\"border-style\":\"hidden\"},\".border-none\":{\"border-style\":\"none\"}})},borderColor:({matchUtilities:r,theme:e,corePlugins:t})=>{r({border:i=>t(\"borderOpacity\")?Ae({color:i,property:\"border-color\",variable:\"--tw-border-opacity\"}):{\"border-color\":X(i)}},{values:(({DEFAULT:i,...n})=>n)(xe(e(\"borderColor\"))),type:[\"color\",\"any\"]}),r({\"border-x\":i=>t(\"borderOpacity\")?Ae({color:i,property:[\"border-left-color\",\"border-right-color\"],variable:\"--tw-border-opacity\"}):{\"border-left-color\":X(i),\"border-right-color\":X(i)},\"border-y\":i=>t(\"borderOpacity\")?Ae({color:i,property:[\"border-top-color\",\"border-bottom-color\"],variable:\"--tw-border-opacity\"}):{\"border-top-color\":X(i),\"border-bottom-color\":X(i)}},{values:(({DEFAULT:i,...n})=>n)(xe(e(\"borderColor\"))),type:[\"color\",\"any\"]}),r({\"border-s\":i=>t(\"borderOpacity\")?Ae({color:i,property:\"border-inline-start-color\",variable:\"--tw-border-opacity\"}):{\"border-inline-start-color\":X(i)},\"border-e\":i=>t(\"borderOpacity\")?Ae({color:i,property:\"border-inline-end-color\",variable:\"--tw-border-opacity\"}):{\"border-inline-end-color\":X(i)},\"border-t\":i=>t(\"borderOpacity\")?Ae({color:i,property:\"border-top-color\",variable:\"--tw-border-opacity\"}):{\"border-top-color\":X(i)},\"border-r\":i=>t(\"borderOpacity\")?Ae({color:i,property:\"border-right-color\",variable:\"--tw-border-opacity\"}):{\"border-right-color\":X(i)},\"border-b\":i=>t(\"borderOpacity\")?Ae({color:i,property:\"border-bottom-color\",variable:\"--tw-border-opacity\"}):{\"border-bottom-color\":X(i)},\"border-l\":i=>t(\"borderOpacity\")?Ae({color:i,property:\"border-left-color\",variable:\"--tw-border-opacity\"}):{\"border-left-color\":X(i)}},{values:(({DEFAULT:i,...n})=>n)(xe(e(\"borderColor\"))),type:[\"color\",\"any\"]})},borderOpacity:L(\"borderOpacity\",[[\"border-opacity\",[\"--tw-border-opacity\"]]]),backgroundColor:({matchUtilities:r,theme:e,corePlugins:t})=>{r({bg:i=>t(\"backgroundOpacity\")?Ae({color:i,property:\"background-color\",variable:\"--tw-bg-opacity\"}):{\"background-color\":X(i)}},{values:xe(e(\"backgroundColor\")),type:[\"color\",\"any\"]})},backgroundOpacity:L(\"backgroundOpacity\",[[\"bg-opacity\",[\"--tw-bg-opacity\"]]]),backgroundImage:L(\"backgroundImage\",[[\"bg\",[\"background-image\"]]],{type:[\"lookup\",\"image\",\"url\"]}),gradientColorStops:(()=>{function r(e){return Je(e,0,\"rgb(255 255 255 / 0)\")}return function({matchUtilities:e,theme:t,addDefaults:i}){i(\"gradient-color-stops\",{\"--tw-gradient-from-position\":\" \",\"--tw-gradient-via-position\":\" \",\"--tw-gradient-to-position\":\" \"});let n={values:xe(t(\"gradientColorStops\")),type:[\"color\",\"any\"]},s={values:t(\"gradientColorStopPositions\"),type:[\"length\",\"percentage\"]};e({from:a=>{let o=r(a);return{\"@defaults gradient-color-stops\":{},\"--tw-gradient-from\":`${X(a)} var(--tw-gradient-from-position)`,\"--tw-gradient-to\":`${o} var(--tw-gradient-to-position)`,\"--tw-gradient-stops\":\"var(--tw-gradient-from), var(--tw-gradient-to)\"}}},n),e({from:a=>({\"--tw-gradient-from-position\":a})},s),e({via:a=>{let o=r(a);return{\"@defaults gradient-color-stops\":{},\"--tw-gradient-to\":`${o} var(--tw-gradient-to-position)`,\"--tw-gradient-stops\":`var(--tw-gradient-from), ${X(a)} var(--tw-gradient-via-position), var(--tw-gradient-to)`}}},n),e({via:a=>({\"--tw-gradient-via-position\":a})},s),e({to:a=>({\"@defaults gradient-color-stops\":{},\"--tw-gradient-to\":`${X(a)} var(--tw-gradient-to-position)`})},n),e({to:a=>({\"--tw-gradient-to-position\":a})},s)}})(),boxDecorationBreak:({addUtilities:r})=>{r({\".decoration-slice\":{\"box-decoration-break\":\"slice\"},\".decoration-clone\":{\"box-decoration-break\":\"clone\"},\".box-decoration-slice\":{\"box-decoration-break\":\"slice\"},\".box-decoration-clone\":{\"box-decoration-break\":\"clone\"}})},backgroundSize:L(\"backgroundSize\",[[\"bg\",[\"background-size\"]]],{type:[\"lookup\",\"length\",\"percentage\",\"size\"]}),backgroundAttachment:({addUtilities:r})=>{r({\".bg-fixed\":{\"background-attachment\":\"fixed\"},\".bg-local\":{\"background-attachment\":\"local\"},\".bg-scroll\":{\"background-attachment\":\"scroll\"}})},backgroundClip:({addUtilities:r})=>{r({\".bg-clip-border\":{\"background-clip\":\"border-box\"},\".bg-clip-padding\":{\"background-clip\":\"padding-box\"},\".bg-clip-content\":{\"background-clip\":\"content-box\"},\".bg-clip-text\":{\"background-clip\":\"text\"}})},backgroundPosition:L(\"backgroundPosition\",[[\"bg\",[\"background-position\"]]],{type:[\"lookup\",[\"position\",{preferOnConflict:!0}]]}),backgroundRepeat:({addUtilities:r})=>{r({\".bg-repeat\":{\"background-repeat\":\"repeat\"},\".bg-no-repeat\":{\"background-repeat\":\"no-repeat\"},\".bg-repeat-x\":{\"background-repeat\":\"repeat-x\"},\".bg-repeat-y\":{\"background-repeat\":\"repeat-y\"},\".bg-repeat-round\":{\"background-repeat\":\"round\"},\".bg-repeat-space\":{\"background-repeat\":\"space\"}})},backgroundOrigin:({addUtilities:r})=>{r({\".bg-origin-border\":{\"background-origin\":\"border-box\"},\".bg-origin-padding\":{\"background-origin\":\"padding-box\"},\".bg-origin-content\":{\"background-origin\":\"content-box\"}})},fill:({matchUtilities:r,theme:e})=>{r({fill:t=>({fill:X(t)})},{values:xe(e(\"fill\")),type:[\"color\",\"any\"]})},stroke:({matchUtilities:r,theme:e})=>{r({stroke:t=>({stroke:X(t)})},{values:xe(e(\"stroke\")),type:[\"color\",\"url\",\"any\"]})},strokeWidth:L(\"strokeWidth\",[[\"stroke\",[\"stroke-width\"]]],{type:[\"length\",\"number\",\"percentage\"]}),objectFit:({addUtilities:r})=>{r({\".object-contain\":{\"object-fit\":\"contain\"},\".object-cover\":{\"object-fit\":\"cover\"},\".object-fill\":{\"object-fit\":\"fill\"},\".object-none\":{\"object-fit\":\"none\"},\".object-scale-down\":{\"object-fit\":\"scale-down\"}})},objectPosition:L(\"objectPosition\",[[\"object\",[\"object-position\"]]]),padding:L(\"padding\",[[\"p\",[\"padding\"]],[[\"px\",[\"padding-left\",\"padding-right\"]],[\"py\",[\"padding-top\",\"padding-bottom\"]]],[[\"ps\",[\"padding-inline-start\"]],[\"pe\",[\"padding-inline-end\"]],[\"pt\",[\"padding-top\"]],[\"pr\",[\"padding-right\"]],[\"pb\",[\"padding-bottom\"]],[\"pl\",[\"padding-left\"]]]]),textAlign:({addUtilities:r})=>{r({\".text-left\":{\"text-align\":\"left\"},\".text-center\":{\"text-align\":\"center\"},\".text-right\":{\"text-align\":\"right\"},\".text-justify\":{\"text-align\":\"justify\"},\".text-start\":{\"text-align\":\"start\"},\".text-end\":{\"text-align\":\"end\"}})},textIndent:L(\"textIndent\",[[\"indent\",[\"text-indent\"]]],{supportsNegativeValues:!0}),verticalAlign:({addUtilities:r,matchUtilities:e})=>{r({\".align-baseline\":{\"vertical-align\":\"baseline\"},\".align-top\":{\"vertical-align\":\"top\"},\".align-middle\":{\"vertical-align\":\"middle\"},\".align-bottom\":{\"vertical-align\":\"bottom\"},\".align-text-top\":{\"vertical-align\":\"text-top\"},\".align-text-bottom\":{\"vertical-align\":\"text-bottom\"},\".align-sub\":{\"vertical-align\":\"sub\"},\".align-super\":{\"vertical-align\":\"super\"}}),e({align:t=>({\"vertical-align\":t})})},fontFamily:({matchUtilities:r,theme:e})=>{r({font:t=>{let[i,n={}]=Array.isArray(t)&&ke(t[1])?t:[t],{fontFeatureSettings:s,fontVariationSettings:a}=n;return{\"font-family\":Array.isArray(i)?i.join(\", \"):i,...s===void 0?{}:{\"font-feature-settings\":s},...a===void 0?{}:{\"font-variation-settings\":a}}}},{values:e(\"fontFamily\"),type:[\"lookup\",\"generic-name\",\"family-name\"]})},fontSize:({matchUtilities:r,theme:e})=>{r({text:(t,{modifier:i})=>{let[n,s]=Array.isArray(t)?t:[t];if(i)return{\"font-size\":n,\"line-height\":i};let{lineHeight:a,letterSpacing:o,fontWeight:l}=ke(s)?s:{lineHeight:s};return{\"font-size\":n,...a===void 0?{}:{\"line-height\":a},...o===void 0?{}:{\"letter-spacing\":o},...l===void 0?{}:{\"font-weight\":l}}}},{values:e(\"fontSize\"),modifiers:e(\"lineHeight\"),type:[\"absolute-size\",\"relative-size\",\"length\",\"percentage\"]})},fontWeight:L(\"fontWeight\",[[\"font\",[\"fontWeight\"]]],{type:[\"lookup\",\"number\",\"any\"]}),textTransform:({addUtilities:r})=>{r({\".uppercase\":{\"text-transform\":\"uppercase\"},\".lowercase\":{\"text-transform\":\"lowercase\"},\".capitalize\":{\"text-transform\":\"capitalize\"},\".normal-case\":{\"text-transform\":\"none\"}})},fontStyle:({addUtilities:r})=>{r({\".italic\":{\"font-style\":\"italic\"},\".not-italic\":{\"font-style\":\"normal\"}})},fontVariantNumeric:({addDefaults:r,addUtilities:e})=>{let t=\"var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure) var(--tw-numeric-spacing) var(--tw-numeric-fraction)\";r(\"font-variant-numeric\",{\"--tw-ordinal\":\" \",\"--tw-slashed-zero\":\" \",\"--tw-numeric-figure\":\" \",\"--tw-numeric-spacing\":\" \",\"--tw-numeric-fraction\":\" \"}),e({\".normal-nums\":{\"font-variant-numeric\":\"normal\"},\".ordinal\":{\"@defaults font-variant-numeric\":{},\"--tw-ordinal\":\"ordinal\",\"font-variant-numeric\":t},\".slashed-zero\":{\"@defaults font-variant-numeric\":{},\"--tw-slashed-zero\":\"slashed-zero\",\"font-variant-numeric\":t},\".lining-nums\":{\"@defaults font-variant-numeric\":{},\"--tw-numeric-figure\":\"lining-nums\",\"font-variant-numeric\":t},\".oldstyle-nums\":{\"@defaults font-variant-numeric\":{},\"--tw-numeric-figure\":\"oldstyle-nums\",\"font-variant-numeric\":t},\".proportional-nums\":{\"@defaults font-variant-numeric\":{},\"--tw-numeric-spacing\":\"proportional-nums\",\"font-variant-numeric\":t},\".tabular-nums\":{\"@defaults font-variant-numeric\":{},\"--tw-numeric-spacing\":\"tabular-nums\",\"font-variant-numeric\":t},\".diagonal-fractions\":{\"@defaults font-variant-numeric\":{},\"--tw-numeric-fraction\":\"diagonal-fractions\",\"font-variant-numeric\":t},\".stacked-fractions\":{\"@defaults font-variant-numeric\":{},\"--tw-numeric-fraction\":\"stacked-fractions\",\"font-variant-numeric\":t}})},lineHeight:L(\"lineHeight\",[[\"leading\",[\"lineHeight\"]]]),letterSpacing:L(\"letterSpacing\",[[\"tracking\",[\"letterSpacing\"]]],{supportsNegativeValues:!0}),textColor:({matchUtilities:r,theme:e,corePlugins:t})=>{r({text:i=>t(\"textOpacity\")?Ae({color:i,property:\"color\",variable:\"--tw-text-opacity\"}):{color:X(i)}},{values:xe(e(\"textColor\")),type:[\"color\",\"any\"]})},textOpacity:L(\"textOpacity\",[[\"text-opacity\",[\"--tw-text-opacity\"]]]),textDecoration:({addUtilities:r})=>{r({\".underline\":{\"text-decoration-line\":\"underline\"},\".overline\":{\"text-decoration-line\":\"overline\"},\".line-through\":{\"text-decoration-line\":\"line-through\"},\".no-underline\":{\"text-decoration-line\":\"none\"}})},textDecorationColor:({matchUtilities:r,theme:e})=>{r({decoration:t=>({\"text-decoration-color\":X(t)})},{values:xe(e(\"textDecorationColor\")),type:[\"color\",\"any\"]})},textDecorationStyle:({addUtilities:r})=>{r({\".decoration-solid\":{\"text-decoration-style\":\"solid\"},\".decoration-double\":{\"text-decoration-style\":\"double\"},\".decoration-dotted\":{\"text-decoration-style\":\"dotted\"},\".decoration-dashed\":{\"text-decoration-style\":\"dashed\"},\".decoration-wavy\":{\"text-decoration-style\":\"wavy\"}})},textDecorationThickness:L(\"textDecorationThickness\",[[\"decoration\",[\"text-decoration-thickness\"]]],{type:[\"length\",\"percentage\"]}),textUnderlineOffset:L(\"textUnderlineOffset\",[[\"underline-offset\",[\"text-underline-offset\"]]],{type:[\"length\",\"percentage\",\"any\"]}),fontSmoothing:({addUtilities:r})=>{r({\".antialiased\":{\"-webkit-font-smoothing\":\"antialiased\",\"-moz-osx-font-smoothing\":\"grayscale\"},\".subpixel-antialiased\":{\"-webkit-font-smoothing\":\"auto\",\"-moz-osx-font-smoothing\":\"auto\"}})},placeholderColor:({matchUtilities:r,theme:e,corePlugins:t})=>{r({placeholder:i=>t(\"placeholderOpacity\")?{\"&::placeholder\":Ae({color:i,property:\"color\",variable:\"--tw-placeholder-opacity\"})}:{\"&::placeholder\":{color:X(i)}}},{values:xe(e(\"placeholderColor\")),type:[\"color\",\"any\"]})},placeholderOpacity:({matchUtilities:r,theme:e})=>{r({\"placeholder-opacity\":t=>({[\"&::placeholder\"]:{\"--tw-placeholder-opacity\":t}})},{values:e(\"placeholderOpacity\")})},caretColor:({matchUtilities:r,theme:e})=>{r({caret:t=>({\"caret-color\":X(t)})},{values:xe(e(\"caretColor\")),type:[\"color\",\"any\"]})},accentColor:({matchUtilities:r,theme:e})=>{r({accent:t=>({\"accent-color\":X(t)})},{values:xe(e(\"accentColor\")),type:[\"color\",\"any\"]})},opacity:L(\"opacity\",[[\"opacity\",[\"opacity\"]]]),backgroundBlendMode:({addUtilities:r})=>{r({\".bg-blend-normal\":{\"background-blend-mode\":\"normal\"},\".bg-blend-multiply\":{\"background-blend-mode\":\"multiply\"},\".bg-blend-screen\":{\"background-blend-mode\":\"screen\"},\".bg-blend-overlay\":{\"background-blend-mode\":\"overlay\"},\".bg-blend-darken\":{\"background-blend-mode\":\"darken\"},\".bg-blend-lighten\":{\"background-blend-mode\":\"lighten\"},\".bg-blend-color-dodge\":{\"background-blend-mode\":\"color-dodge\"},\".bg-blend-color-burn\":{\"background-blend-mode\":\"color-burn\"},\".bg-blend-hard-light\":{\"background-blend-mode\":\"hard-light\"},\".bg-blend-soft-light\":{\"background-blend-mode\":\"soft-light\"},\".bg-blend-difference\":{\"background-blend-mode\":\"difference\"},\".bg-blend-exclusion\":{\"background-blend-mode\":\"exclusion\"},\".bg-blend-hue\":{\"background-blend-mode\":\"hue\"},\".bg-blend-saturation\":{\"background-blend-mode\":\"saturation\"},\".bg-blend-color\":{\"background-blend-mode\":\"color\"},\".bg-blend-luminosity\":{\"background-blend-mode\":\"luminosity\"}})},mixBlendMode:({addUtilities:r})=>{r({\".mix-blend-normal\":{\"mix-blend-mode\":\"normal\"},\".mix-blend-multiply\":{\"mix-blend-mode\":\"multiply\"},\".mix-blend-screen\":{\"mix-blend-mode\":\"screen\"},\".mix-blend-overlay\":{\"mix-blend-mode\":\"overlay\"},\".mix-blend-darken\":{\"mix-blend-mode\":\"darken\"},\".mix-blend-lighten\":{\"mix-blend-mode\":\"lighten\"},\".mix-blend-color-dodge\":{\"mix-blend-mode\":\"color-dodge\"},\".mix-blend-color-burn\":{\"mix-blend-mode\":\"color-burn\"},\".mix-blend-hard-light\":{\"mix-blend-mode\":\"hard-light\"},\".mix-blend-soft-light\":{\"mix-blend-mode\":\"soft-light\"},\".mix-blend-difference\":{\"mix-blend-mode\":\"difference\"},\".mix-blend-exclusion\":{\"mix-blend-mode\":\"exclusion\"},\".mix-blend-hue\":{\"mix-blend-mode\":\"hue\"},\".mix-blend-saturation\":{\"mix-blend-mode\":\"saturation\"},\".mix-blend-color\":{\"mix-blend-mode\":\"color\"},\".mix-blend-luminosity\":{\"mix-blend-mode\":\"luminosity\"},\".mix-blend-plus-darker\":{\"mix-blend-mode\":\"plus-darker\"},\".mix-blend-plus-lighter\":{\"mix-blend-mode\":\"plus-lighter\"}})},boxShadow:(()=>{let r=mt(\"boxShadow\"),e=[\"var(--tw-ring-offset-shadow, 0 0 #0000)\",\"var(--tw-ring-shadow, 0 0 #0000)\",\"var(--tw-shadow)\"].join(\", \");return function({matchUtilities:t,addDefaults:i,theme:n}){i(\"box-shadow\",{\"--tw-ring-offset-shadow\":\"0 0 #0000\",\"--tw-ring-shadow\":\"0 0 #0000\",\"--tw-shadow\":\"0 0 #0000\",\"--tw-shadow-colored\":\"0 0 #0000\"}),t({shadow:s=>{s=r(s);let a=en(s);for(let o of a)!o.valid||(o.color=\"var(--tw-shadow-color)\");return{\"@defaults box-shadow\":{},\"--tw-shadow\":s===\"none\"?\"0 0 #0000\":s,\"--tw-shadow-colored\":s===\"none\"?\"0 0 #0000\":Lf(a),\"box-shadow\":e}}},{values:n(\"boxShadow\"),type:[\"shadow\"]})}})(),boxShadowColor:({matchUtilities:r,theme:e})=>{r({shadow:t=>({\"--tw-shadow-color\":X(t),\"--tw-shadow\":\"var(--tw-shadow-colored)\"})},{values:xe(e(\"boxShadowColor\")),type:[\"color\",\"any\"]})},outlineStyle:({addUtilities:r})=>{r({\".outline-none\":{outline:\"2px solid transparent\",\"outline-offset\":\"2px\"},\".outline\":{\"outline-style\":\"solid\"},\".outline-dashed\":{\"outline-style\":\"dashed\"},\".outline-dotted\":{\"outline-style\":\"dotted\"},\".outline-double\":{\"outline-style\":\"double\"}})},outlineWidth:L(\"outlineWidth\",[[\"outline\",[\"outline-width\"]]],{type:[\"length\",\"number\",\"percentage\"]}),outlineOffset:L(\"outlineOffset\",[[\"outline-offset\",[\"outline-offset\"]]],{type:[\"length\",\"number\",\"percentage\",\"any\"],supportsNegativeValues:!0}),outlineColor:({matchUtilities:r,theme:e})=>{r({outline:t=>({\"outline-color\":X(t)})},{values:xe(e(\"outlineColor\")),type:[\"color\",\"any\"]})},ringWidth:({matchUtilities:r,addDefaults:e,addUtilities:t,theme:i,config:n})=>{let s=(()=>{if(we(n(),\"respectDefaultRingColorOpacity\"))return i(\"ringColor.DEFAULT\");let a=i(\"ringOpacity.DEFAULT\",\"0.5\");return i(\"ringColor\")?.DEFAULT?Je(i(\"ringColor\")?.DEFAULT,a,`rgb(147 197 253 / ${a})`):`rgb(147 197 253 / ${a})`})();e(\"ring-width\",{\"--tw-ring-inset\":\" \",\"--tw-ring-offset-width\":i(\"ringOffsetWidth.DEFAULT\",\"0px\"),\"--tw-ring-offset-color\":i(\"ringOffsetColor.DEFAULT\",\"#fff\"),\"--tw-ring-color\":s,\"--tw-ring-offset-shadow\":\"0 0 #0000\",\"--tw-ring-shadow\":\"0 0 #0000\",\"--tw-shadow\":\"0 0 #0000\",\"--tw-shadow-colored\":\"0 0 #0000\"}),r({ring:a=>({\"@defaults ring-width\":{},\"--tw-ring-offset-shadow\":\"var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)\",\"--tw-ring-shadow\":`var(--tw-ring-inset) 0 0 0 calc(${a} + var(--tw-ring-offset-width)) var(--tw-ring-color)`,\"box-shadow\":[\"var(--tw-ring-offset-shadow)\",\"var(--tw-ring-shadow)\",\"var(--tw-shadow, 0 0 #0000)\"].join(\", \")})},{values:i(\"ringWidth\"),type:\"length\"}),t({\".ring-inset\":{\"@defaults ring-width\":{},\"--tw-ring-inset\":\"inset\"}})},ringColor:({matchUtilities:r,theme:e,corePlugins:t})=>{r({ring:i=>t(\"ringOpacity\")?Ae({color:i,property:\"--tw-ring-color\",variable:\"--tw-ring-opacity\"}):{\"--tw-ring-color\":X(i)}},{values:Object.fromEntries(Object.entries(xe(e(\"ringColor\"))).filter(([i])=>i!==\"DEFAULT\")),type:[\"color\",\"any\"]})},ringOpacity:r=>{let{config:e}=r;return L(\"ringOpacity\",[[\"ring-opacity\",[\"--tw-ring-opacity\"]]],{filterDefault:!we(e(),\"respectDefaultRingColorOpacity\")})(r)},ringOffsetWidth:L(\"ringOffsetWidth\",[[\"ring-offset\",[\"--tw-ring-offset-width\"]]],{type:\"length\"}),ringOffsetColor:({matchUtilities:r,theme:e})=>{r({\"ring-offset\":t=>({\"--tw-ring-offset-color\":X(t)})},{values:xe(e(\"ringOffsetColor\")),type:[\"color\",\"any\"]})},blur:({matchUtilities:r,theme:e})=>{r({blur:t=>({\"--tw-blur\":t.trim()===\"\"?\" \":`blur(${t})`,\"@defaults filter\":{},filter:nt})},{values:e(\"blur\")})},brightness:({matchUtilities:r,theme:e})=>{r({brightness:t=>({\"--tw-brightness\":`brightness(${t})`,\"@defaults filter\":{},filter:nt})},{values:e(\"brightness\")})},contrast:({matchUtilities:r,theme:e})=>{r({contrast:t=>({\"--tw-contrast\":`contrast(${t})`,\"@defaults filter\":{},filter:nt})},{values:e(\"contrast\")})},dropShadow:({matchUtilities:r,theme:e})=>{r({\"drop-shadow\":t=>({\"--tw-drop-shadow\":Array.isArray(t)?t.map(i=>`drop-shadow(${i})`).join(\" \"):`drop-shadow(${t})`,\"@defaults filter\":{},filter:nt})},{values:e(\"dropShadow\")})},grayscale:({matchUtilities:r,theme:e})=>{r({grayscale:t=>({\"--tw-grayscale\":`grayscale(${t})`,\"@defaults filter\":{},filter:nt})},{values:e(\"grayscale\")})},hueRotate:({matchUtilities:r,theme:e})=>{r({\"hue-rotate\":t=>({\"--tw-hue-rotate\":`hue-rotate(${t})`,\"@defaults filter\":{},filter:nt})},{values:e(\"hueRotate\"),supportsNegativeValues:!0})},invert:({matchUtilities:r,theme:e})=>{r({invert:t=>({\"--tw-invert\":`invert(${t})`,\"@defaults filter\":{},filter:nt})},{values:e(\"invert\")})},saturate:({matchUtilities:r,theme:e})=>{r({saturate:t=>({\"--tw-saturate\":`saturate(${t})`,\"@defaults filter\":{},filter:nt})},{values:e(\"saturate\")})},sepia:({matchUtilities:r,theme:e})=>{r({sepia:t=>({\"--tw-sepia\":`sepia(${t})`,\"@defaults filter\":{},filter:nt})},{values:e(\"sepia\")})},filter:({addDefaults:r,addUtilities:e})=>{r(\"filter\",{\"--tw-blur\":\" \",\"--tw-brightness\":\" \",\"--tw-contrast\":\" \",\"--tw-grayscale\":\" \",\"--tw-hue-rotate\":\" \",\"--tw-invert\":\" \",\"--tw-saturate\":\" \",\"--tw-sepia\":\" \",\"--tw-drop-shadow\":\" \"}),e({\".filter\":{\"@defaults filter\":{},filter:nt},\".filter-none\":{filter:\"none\"}})},backdropBlur:({matchUtilities:r,theme:e})=>{r({\"backdrop-blur\":t=>({\"--tw-backdrop-blur\":t.trim()===\"\"?\" \":`blur(${t})`,\"@defaults backdrop-filter\":{},\"-webkit-backdrop-filter\":ge,\"backdrop-filter\":ge})},{values:e(\"backdropBlur\")})},backdropBrightness:({matchUtilities:r,theme:e})=>{r({\"backdrop-brightness\":t=>({\"--tw-backdrop-brightness\":`brightness(${t})`,\"@defaults backdrop-filter\":{},\"-webkit-backdrop-filter\":ge,\"backdrop-filter\":ge})},{values:e(\"backdropBrightness\")})},backdropContrast:({matchUtilities:r,theme:e})=>{r({\"backdrop-contrast\":t=>({\"--tw-backdrop-contrast\":`contrast(${t})`,\"@defaults backdrop-filter\":{},\"-webkit-backdrop-filter\":ge,\"backdrop-filter\":ge})},{values:e(\"backdropContrast\")})},backdropGrayscale:({matchUtilities:r,theme:e})=>{r({\"backdrop-grayscale\":t=>({\"--tw-backdrop-grayscale\":`grayscale(${t})`,\"@defaults backdrop-filter\":{},\"-webkit-backdrop-filter\":ge,\"backdrop-filter\":ge})},{values:e(\"backdropGrayscale\")})},backdropHueRotate:({matchUtilities:r,theme:e})=>{r({\"backdrop-hue-rotate\":t=>({\"--tw-backdrop-hue-rotate\":`hue-rotate(${t})`,\"@defaults backdrop-filter\":{},\"-webkit-backdrop-filter\":ge,\"backdrop-filter\":ge})},{values:e(\"backdropHueRotate\"),supportsNegativeValues:!0})},backdropInvert:({matchUtilities:r,theme:e})=>{r({\"backdrop-invert\":t=>({\"--tw-backdrop-invert\":`invert(${t})`,\"@defaults backdrop-filter\":{},\"-webkit-backdrop-filter\":ge,\"backdrop-filter\":ge})},{values:e(\"backdropInvert\")})},backdropOpacity:({matchUtilities:r,theme:e})=>{r({\"backdrop-opacity\":t=>({\"--tw-backdrop-opacity\":`opacity(${t})`,\"@defaults backdrop-filter\":{},\"-webkit-backdrop-filter\":ge,\"backdrop-filter\":ge})},{values:e(\"backdropOpacity\")})},backdropSaturate:({matchUtilities:r,theme:e})=>{r({\"backdrop-saturate\":t=>({\"--tw-backdrop-saturate\":`saturate(${t})`,\"@defaults backdrop-filter\":{},\"-webkit-backdrop-filter\":ge,\"backdrop-filter\":ge})},{values:e(\"backdropSaturate\")})},backdropSepia:({matchUtilities:r,theme:e})=>{r({\"backdrop-sepia\":t=>({\"--tw-backdrop-sepia\":`sepia(${t})`,\"@defaults backdrop-filter\":{},\"-webkit-backdrop-filter\":ge,\"backdrop-filter\":ge})},{values:e(\"backdropSepia\")})},backdropFilter:({addDefaults:r,addUtilities:e})=>{r(\"backdrop-filter\",{\"--tw-backdrop-blur\":\" \",\"--tw-backdrop-brightness\":\" \",\"--tw-backdrop-contrast\":\" \",\"--tw-backdrop-grayscale\":\" \",\"--tw-backdrop-hue-rotate\":\" \",\"--tw-backdrop-invert\":\" \",\"--tw-backdrop-opacity\":\" \",\"--tw-backdrop-saturate\":\" \",\"--tw-backdrop-sepia\":\" \"}),e({\".backdrop-filter\":{\"@defaults backdrop-filter\":{},\"-webkit-backdrop-filter\":ge,\"backdrop-filter\":ge},\".backdrop-filter-none\":{\"-webkit-backdrop-filter\":\"none\",\"backdrop-filter\":\"none\"}})},transitionProperty:({matchUtilities:r,theme:e})=>{let t=e(\"transitionTimingFunction.DEFAULT\"),i=e(\"transitionDuration.DEFAULT\");r({transition:n=>({\"transition-property\":n,...n===\"none\"?{}:{\"transition-timing-function\":t,\"transition-duration\":i}})},{values:e(\"transitionProperty\")})},transitionDelay:L(\"transitionDelay\",[[\"delay\",[\"transitionDelay\"]]]),transitionDuration:L(\"transitionDuration\",[[\"duration\",[\"transitionDuration\"]]],{filterDefault:!0}),transitionTimingFunction:L(\"transitionTimingFunction\",[[\"ease\",[\"transitionTimingFunction\"]]],{filterDefault:!0}),willChange:L(\"willChange\",[[\"will-change\",[\"will-change\"]]]),contain:({addDefaults:r,addUtilities:e})=>{let t=\"var(--tw-contain-size) var(--tw-contain-layout) var(--tw-contain-paint) var(--tw-contain-style)\";r(\"contain\",{\"--tw-contain-size\":\" \",\"--tw-contain-layout\":\" \",\"--tw-contain-paint\":\" \",\"--tw-contain-style\":\" \"}),e({\".contain-none\":{contain:\"none\"},\".contain-content\":{contain:\"content\"},\".contain-strict\":{contain:\"strict\"},\".contain-size\":{\"@defaults contain\":{},\"--tw-contain-size\":\"size\",contain:t},\".contain-inline-size\":{\"@defaults contain\":{},\"--tw-contain-size\":\"inline-size\",contain:t},\".contain-layout\":{\"@defaults contain\":{},\"--tw-contain-layout\":\"layout\",contain:t},\".contain-paint\":{\"@defaults contain\":{},\"--tw-contain-paint\":\"paint\",contain:t},\".contain-style\":{\"@defaults contain\":{},\"--tw-contain-style\":\"style\",contain:t}})},content:L(\"content\",[[\"content\",[\"--tw-content\",[\"content\",\"var(--tw-content)\"]]]]),forcedColorAdjust:({addUtilities:r})=>{r({\".forced-color-adjust-auto\":{\"forced-color-adjust\":\"auto\"},\".forced-color-adjust-none\":{\"forced-color-adjust\":\"none\"}})}}});function h_(r){if(r===void 0)return!1;if(r===\"true\"||r===\"1\")return!0;if(r===\"false\"||r===\"0\")return!1;if(r===\"*\")return!0;let e=r.split(\",\").map(t=>t.split(\":\")[0]);return e.includes(\"-tailwindcss\")?!1:!!e.includes(\"tailwindcss\")}var Ze,wh,vh,es,No,gt,Ti,It=P(()=>{u();Ze=typeof m!=\"undefined\"?{NODE_ENV:\"production\",DEBUG:h_(m.env.DEBUG)}:{NODE_ENV:\"production\",DEBUG:!1},wh=new Map,vh=new Map,es=new Map,No=new Map,gt=new String(\"*\"),Ti=Symbol(\"__NONE__\")});function cr(r){let e=[],t=!1;for(let i=0;i0)}var xh,kh,m_,Bo=P(()=>{u();xh=new Map([[\"{\",\"}\"],[\"[\",\"]\"],[\"(\",\")\"]]),kh=new Map(Array.from(xh.entries()).map(([r,e])=>[e,r])),m_=new Set(['\"',\"'\",\"`\"])});function pr(r){let[e]=Sh(r);return e.forEach(([t,i])=>t.removeChild(i)),r.nodes.push(...e.map(([,t])=>t)),r}function Sh(r){let e=[],t=null;for(let i of r.nodes)if(i.type===\"combinator\")e=e.filter(([,n])=>jo(n).includes(\"jumpable\")),t=null;else if(i.type===\"pseudo\"){g_(i)?(t=i,e.push([r,i,null])):t&&y_(i,t)?e.push([r,i,t]):t=null;for(let n of i.nodes??[]){let[s,a]=Sh(n);t=a||t,e.push(...s)}}return[e,t]}function Ah(r){return r.value.startsWith(\"::\")||Fo[r.value]!==void 0}function g_(r){return Ah(r)&&jo(r).includes(\"terminal\")}function y_(r,e){return r.type!==\"pseudo\"||Ah(r)?!1:jo(e).includes(\"actionable\")}function jo(r){return Fo[r.value]??Fo.__default__}var Fo,ts=P(()=>{u();Fo={\"::after\":[\"terminal\",\"jumpable\"],\"::backdrop\":[\"terminal\",\"jumpable\"],\"::before\":[\"terminal\",\"jumpable\"],\"::cue\":[\"terminal\"],\"::cue-region\":[\"terminal\"],\"::first-letter\":[\"terminal\",\"jumpable\"],\"::first-line\":[\"terminal\",\"jumpable\"],\"::grammar-error\":[\"terminal\"],\"::marker\":[\"terminal\",\"jumpable\"],\"::part\":[\"terminal\",\"actionable\"],\"::placeholder\":[\"terminal\",\"jumpable\"],\"::selection\":[\"terminal\",\"jumpable\"],\"::slotted\":[\"terminal\"],\"::spelling-error\":[\"terminal\"],\"::target-text\":[\"terminal\"],\"::file-selector-button\":[\"terminal\",\"actionable\"],\"::deep\":[\"actionable\"],\"::v-deep\":[\"actionable\"],\"::ng-deep\":[\"actionable\"],\":after\":[\"terminal\",\"jumpable\"],\":before\":[\"terminal\",\"jumpable\"],\":first-letter\":[\"terminal\",\"jumpable\"],\":first-line\":[\"terminal\",\"jumpable\"],\":where\":[],\":is\":[],\":has\":[],__default__:[\"terminal\",\"actionable\"]}});function dr(r,{context:e,candidate:t}){let i=e?.tailwindConfig.prefix??\"\",n=r.map(a=>{let o=(0,st.default)().astSync(a.format);return{...a,ast:a.respectPrefix?ur(i,o):o}}),s=st.default.root({nodes:[st.default.selector({nodes:[st.default.className({value:Te(t)})]})]});for(let{ast:a}of n)[s,a]=w_(s,a),a.walkNesting(o=>o.replaceWith(...s.nodes[0].nodes)),s=a;return s}function _h(r){let e=[];for(;r.prev()&&r.prev().type!==\"combinator\";)r=r.prev();for(;r&&r.type!==\"combinator\";)e.push(r),r=r.next();return e}function b_(r){return r.sort((e,t)=>e.type===\"tag\"&&t.type===\"class\"?-1:e.type===\"class\"&&t.type===\"tag\"?1:e.type===\"class\"&&t.type===\"pseudo\"&&t.value.startsWith(\"::\")?-1:e.type===\"pseudo\"&&e.value.startsWith(\"::\")&&t.type===\"class\"?1:r.index(e)-r.index(t)),r}function Uo(r,e){let t=!1;r.walk(i=>{if(i.type===\"class\"&&i.value===e)return t=!0,!1}),t||r.remove()}function rs(r,e,{context:t,candidate:i,base:n}){let s=t?.tailwindConfig?.separator??\":\";n=n??ve(i,s).pop();let a=(0,st.default)().astSync(r);if(a.walkClasses(f=>{f.raws&&f.value.includes(n)&&(f.raws.value=Te((0,Ch.default)(f.raws.value)))}),a.each(f=>Uo(f,n)),a.length===0)return null;let o=Array.isArray(e)?dr(e,{context:t,candidate:i}):e;if(o===null)return a.toString();let l=st.default.comment({value:\"/*__simple__*/\"}),c=st.default.comment({value:\"/*__simple__*/\"});return a.walkClasses(f=>{if(f.value!==n)return;let d=f.parent,p=o.nodes[0].nodes;if(d.nodes.length===1){f.replaceWith(...p);return}let h=_h(f);d.insertBefore(h[0],l),d.insertAfter(h[h.length-1],c);for(let v of p)d.insertBefore(h[0],v.clone());f.remove(),h=_h(l);let b=d.index(l);d.nodes.splice(b,h.length,...b_(st.default.selector({nodes:h})).nodes),l.remove(),c.remove()}),a.walkPseudos(f=>{f.value===zo&&f.replaceWith(f.nodes)}),a.each(f=>pr(f)),a.toString()}function w_(r,e){let t=[];return r.walkPseudos(i=>{i.value===zo&&t.push({pseudo:i,value:i.nodes[0].toString()})}),e.walkPseudos(i=>{if(i.value!==zo)return;let n=i.nodes[0].toString(),s=t.find(c=>c.value===n);if(!s)return;let a=[],o=i.next();for(;o&&o.type!==\"combinator\";)a.push(o),o=o.next();let l=o;s.pseudo.parent.insertAfter(s.pseudo,st.default.selector({nodes:a.map(c=>c.clone())})),i.remove(),a.forEach(c=>c.remove()),l&&l.type===\"combinator\"&&l.remove()}),[r,e]}var st,Ch,zo,Vo=P(()=>{u();st=pe(it()),Ch=pe(Pn());fr();Gn();ts();zt();zo=\":merge\"});function is(r,e){let t=(0,Ho.default)().astSync(r);return t.each(i=>{i.nodes.some(s=>s.type===\"combinator\")&&(i.nodes=[Ho.default.pseudo({value:\":is\",nodes:[i.clone()]})]),pr(i)}),`${e} ${t.toString()}`}var Ho,Wo=P(()=>{u();Ho=pe(it());ts()});function Go(r){return v_.transformSync(r)}function*x_(r){let e=1/0;for(;e>=0;){let t,i=!1;if(e===1/0&&r.endsWith(\"]\")){let a=r.indexOf(\"[\");r[a-1]===\"-\"?t=a-1:r[a-1]===\"/\"?(t=a-1,i=!0):t=-1}else e===1/0&&r.includes(\"/\")?(t=r.lastIndexOf(\"/\"),i=!0):t=r.lastIndexOf(\"-\",e);if(t<0)break;let n=r.slice(0,t),s=r.slice(i?t:t+1);e=t-1,!(n===\"\"||s===\"/\")&&(yield[n,s])}}function k_(r,e){if(r.length===0||e.tailwindConfig.prefix===\"\")return r;for(let t of r){let[i]=t;if(i.options.respectPrefix){let n=ee.root({nodes:[t[1].clone()]}),s=t[1].raws.tailwind.classCandidate;n.walkRules(a=>{let o=s.startsWith(\"-\");a.selector=ur(e.tailwindConfig.prefix,a.selector,o)}),t[1]=n.nodes[0]}}return r}function S_(r,e){if(r.length===0)return r;let t=[];function i(n){return n.parent&&n.parent.type===\"atrule\"&&n.parent.name===\"keyframes\"}for(let[n,s]of r){let a=ee.root({nodes:[s.clone()]});a.walkRules(o=>{if(i(o))return;let l=(0,ns.default)().astSync(o.selector);l.each(c=>Uo(c,e)),Qf(l,c=>c===e?`!${c}`:c),o.selector=l.toString(),o.walkDecls(c=>c.important=!0)}),t.push([{...n,important:!0},a.nodes[0]])}return t}function A_(r,e,t){if(e.length===0)return e;let i={modifier:null,value:Ti};{let[n,...s]=ve(r,\"/\");if(s.length>1&&(n=n+\"/\"+s.slice(0,-1).join(\"/\"),s=s.slice(-1)),s.length&&!t.variantMap.has(r)&&(r=n,i.modifier=s[0],!we(t.tailwindConfig,\"generalizedModifiers\")))return[]}if(r.endsWith(\"]\")&&!r.startsWith(\"[\")){let n=/(.)(-?)\\[(.*)\\]/g.exec(r);if(n){let[,s,a,o]=n;if(s===\"@\"&&a===\"-\")return[];if(s!==\"@\"&&a===\"\")return[];r=r.replace(`${a}[${o}]`,\"\"),i.value=o}}if(Ko(r)&&!t.variantMap.has(r)){let n=t.offsets.recordVariant(r),s=K(r.slice(1,-1)),a=ve(s,\",\");if(a.length>1)return[];if(!a.every(ls))return[];let o=a.map((l,c)=>[t.offsets.applyParallelOffset(n,c),Ri(l.trim())]);t.variantMap.set(r,o)}if(t.variantMap.has(r)){let n=Ko(r),s=t.variantOptions.get(r)?.[Pt]??{},a=t.variantMap.get(r).slice(),o=[],l=(()=>!(n||s.respectPrefix===!1))();for(let[c,f]of e){if(c.layer===\"user\")continue;let d=ee.root({nodes:[f.clone()]});for(let[p,h,b]of a){let w=function(){v.raws.neededBackup||(v.raws.neededBackup=!0,v.walkRules(T=>T.raws.originalSelector=T.selector))},k=function(T){return w(),v.each(B=>{B.type===\"rule\"&&(B.selectors=B.selectors.map(N=>T({get className(){return Go(N)},selector:N})))}),v},v=(b??d).clone(),y=[],S=h({get container(){return w(),v},separator:t.tailwindConfig.separator,modifySelectors:k,wrap(T){let B=v.nodes;v.removeAll(),T.append(B),v.append(T)},format(T){y.push({format:T,respectPrefix:l})},args:i});if(Array.isArray(S)){for(let[T,B]of S.entries())a.push([t.offsets.applyParallelOffset(p,T),B,v.clone()]);continue}if(typeof S==\"string\"&&y.push({format:S,respectPrefix:l}),S===null)continue;v.raws.neededBackup&&(delete v.raws.neededBackup,v.walkRules(T=>{let B=T.raws.originalSelector;if(!B||(delete T.raws.originalSelector,B===T.selector))return;let N=T.selector,R=(0,ns.default)(F=>{F.walkClasses(Y=>{Y.value=`${r}${t.tailwindConfig.separator}${Y.value}`})}).processSync(B);y.push({format:N.replace(R,\"&\"),respectPrefix:l}),T.selector=B})),v.nodes[0].raws.tailwind={...v.nodes[0].raws.tailwind,parentLayer:c.layer};let E=[{...c,sort:t.offsets.applyVariantOffset(c.sort,p,Object.assign(i,t.variantOptions.get(r))),collectedFormats:(c.collectedFormats??[]).concat(y)},v.nodes[0]];o.push(E)}}return o}return[]}function Qo(r,e,t={}){return!ke(r)&&!Array.isArray(r)?[[r],t]:Array.isArray(r)?Qo(r[0],e,r[1]):(e.has(r)||e.set(r,lr(r)),[e.get(r),t])}function __(r){return C_.test(r)}function E_(r){if(!r.includes(\"://\"))return!1;try{let e=new URL(r);return e.scheme!==\"\"&&e.host!==\"\"}catch(e){return!1}}function Eh(r){let e=!0;return r.walkDecls(t=>{if(!Oh(t.prop,t.value))return e=!1,!1}),e}function Oh(r,e){if(E_(`${r}:${e}`))return!1;try{return ee.parse(`a{${r}:${e}}`).toResult(),!0}catch(t){return!1}}function O_(r,e){let[,t,i]=r.match(/^\\[([a-zA-Z0-9-_]+):(\\S+)\\]$/)??[];if(i===void 0||!__(t)||!cr(i))return null;let n=K(i,{property:t});return Oh(t,n)?[[{sort:e.offsets.arbitraryProperty(r),layer:\"utilities\",options:{respectImportant:!0}},()=>({[$o(r)]:{[t]:n}})]]:null}function*T_(r,e){e.candidateRuleMap.has(r)&&(yield[e.candidateRuleMap.get(r),\"DEFAULT\"]),yield*function*(o){o!==null&&(yield[o,\"DEFAULT\"])}(O_(r,e));let t=r,i=!1,n=e.tailwindConfig.prefix,s=n.length,a=t.startsWith(n)||t.startsWith(`-${n}`);t[s]===\"-\"&&a&&(i=!0,t=n+t.slice(s+1)),i&&e.candidateRuleMap.has(t)&&(yield[e.candidateRuleMap.get(t),\"-DEFAULT\"]);for(let[o,l]of x_(t))e.candidateRuleMap.has(o)&&(yield[e.candidateRuleMap.get(o),i?`-${l}`:l])}function R_(r,e){return r===gt?[gt]:ve(r,e)}function*P_(r,e){for(let t of r)t[1].raws.tailwind={...t[1].raws.tailwind,classCandidate:e,preserveSource:t[0].options?.preserveSource??!1},yield t}function*Yo(r,e){let t=e.tailwindConfig.separator,[i,...n]=R_(r,t).reverse(),s=!1;i.startsWith(\"!\")&&(s=!0,i=i.slice(1));for(let a of T_(i,e)){let o=[],l=new Map,[c,f]=a,d=c.length===1;for(let[p,h]of c){let b=[];if(typeof h==\"function\")for(let v of[].concat(h(f,{isOnlyPlugin:d}))){let[y,w]=Qo(v,e.postCssNodeCache);for(let k of y)b.push([{...p,options:{...p.options,...w}},k])}else if(f===\"DEFAULT\"||f===\"-DEFAULT\"){let v=h,[y,w]=Qo(v,e.postCssNodeCache);for(let k of y)b.push([{...p,options:{...p.options,...w}},k])}if(b.length>0){let v=Array.from(ta(p.options?.types??[],f,p.options??{},e.tailwindConfig)).map(([y,w])=>w);v.length>0&&l.set(b,v),o.push(b)}}if(Ko(f)){if(o.length>1){let b=function(y){return y.length===1?y[0]:y.find(w=>{let k=l.get(w);return w.some(([{options:S},E])=>Eh(E)?S.types.some(({type:T,preferOnConflict:B})=>k.includes(T)&&B):!1)})},[p,h]=o.reduce((y,w)=>(w.some(([{options:S}])=>S.types.some(({type:E})=>E===\"any\"))?y[0].push(w):y[1].push(w),y),[[],[]]),v=b(h)??b(p);if(v)o=[v];else{let y=o.map(k=>new Set([...l.get(k)??[]]));for(let k of y)for(let S of k){let E=!1;for(let T of y)k!==T&&T.has(S)&&(T.delete(S),E=!0);E&&k.delete(S)}let w=[];for(let[k,S]of y.entries())for(let E of S){let T=o[k].map(([,B])=>B).flat().map(B=>B.toString().split(`\n`).slice(1,-1).map(N=>N.trim()).map(N=>` ${N}`).join(`\n`)).join(`\n\n`);w.push(` Use \\`${r.replace(\"[\",`[${E}:`)}\\` for \\`${T.trim()}\\``);break}G.warn([`The class \\`${r}\\` is ambiguous and matches multiple utilities.`,...w,`If this is content and not a class, replace it with \\`${r.replace(\"[\",\"[\").replace(\"]\",\"]\")}\\` to silence this warning.`]);continue}}o=o.map(p=>p.filter(h=>Eh(h[1])))}o=o.flat(),o=Array.from(P_(o,i)),o=k_(o,e),s&&(o=S_(o,i));for(let p of n)o=A_(p,o,e);for(let p of o)p[1].raws.tailwind={...p[1].raws.tailwind,candidate:r},p=I_(p,{context:e,candidate:r}),p!==null&&(yield p)}}function I_(r,{context:e,candidate:t}){if(!r[0].collectedFormats)return r;let i=!0,n;try{n=dr(r[0].collectedFormats,{context:e,candidate:t})}catch{return null}let s=ee.root({nodes:[r[1].clone()]});return s.walkRules(a=>{if(!ss(a))try{let o=rs(a.selector,n,{candidate:t,context:e});if(o===null){a.remove();return}a.selector=o}catch{return i=!1,!1}}),!i||s.nodes.length===0?null:(r[1]=s.nodes[0],r)}function ss(r){return r.parent&&r.parent.type===\"atrule\"&&r.parent.name===\"keyframes\"}function D_(r){if(r===!0)return e=>{ss(e)||e.walkDecls(t=>{t.parent.type===\"rule\"&&!ss(t.parent)&&(t.important=!0)})};if(typeof r==\"string\")return e=>{ss(e)||(e.selectors=e.selectors.map(t=>is(t,r)))}}function as(r,e,t=!1){let i=[],n=D_(e.tailwindConfig.important);for(let s of r){if(e.notClassCache.has(s))continue;if(e.candidateRuleCache.has(s)){i=i.concat(Array.from(e.candidateRuleCache.get(s)));continue}let a=Array.from(Yo(s,e));if(a.length===0){e.notClassCache.add(s);continue}e.classCache.set(s,a);let o=e.candidateRuleCache.get(s)??new Set;e.candidateRuleCache.set(s,o);for(let l of a){let[{sort:c,options:f},d]=l;if(f.respectImportant&&n){let h=ee.root({nodes:[d.clone()]});h.walkRules(n),d=h.nodes[0]}let p=[c,t?d.clone():d];o.add(p),e.ruleCache.add(p),i.push(p)}}return i}function Ko(r){return r.startsWith(\"[\")&&r.endsWith(\"]\")}var ns,v_,C_,os=P(()=>{u();Ot();ns=pe(it());qo();Kt();Gn();Fr();Be();It();Vo();Lo();Br();Oi();Bo();zt();ct();Wo();v_=(0,ns.default)(r=>r.first.filter(({type:e})=>e===\"class\").pop().value);C_=/^[a-z_-]/});var Th,Rh=P(()=>{u();Th={}});function q_(r){try{return Th.createHash(\"md5\").update(r,\"utf-8\").digest(\"binary\")}catch(e){return\"\"}}function Ph(r,e){let t=e.toString();if(!t.includes(\"@tailwind\"))return!1;let i=No.get(r),n=q_(t),s=i!==n;return No.set(r,n),s}var Ih=P(()=>{u();Rh();It()});function us(r){return(r>0n)-(r<0n)}var Dh=P(()=>{u()});function qh(r,e){let t=0n,i=0n;for(let[n,s]of e)r&n&&(t=t|n,i=i|s);return r&~t|i}var $h=P(()=>{u()});function Lh(r){let e=null;for(let t of r)e=e??t,e=e>t?e:t;return e}function $_(r,e){let t=r.length,i=e.length,n=t{u();Dh();$h();Xo=class{constructor(){this.offsets={defaults:0n,base:0n,components:0n,utilities:0n,variants:0n,user:0n},this.layerPositions={defaults:0n,base:1n,components:2n,utilities:3n,user:4n,variants:5n},this.reservedVariantBits=0n,this.variantOffsets=new Map}create(e){return{layer:e,parentLayer:e,arbitrary:0n,variants:0n,parallelIndex:0n,index:this.offsets[e]++,propertyOffset:0n,property:\"\",options:[]}}arbitraryProperty(e){return{...this.create(\"utilities\"),arbitrary:1n,property:e}}forVariant(e,t=0){let i=this.variantOffsets.get(e);if(i===void 0)throw new Error(`Cannot find offset for unknown variant ${e}`);return{...this.create(\"variants\"),variants:i<n.startsWith(\"[\")).sort(([n],[s])=>$_(n,s)),t=e.map(([,n])=>n).sort((n,s)=>us(n-s));return e.map(([,n],s)=>[n,t[s]]).filter(([n,s])=>n!==s)}remapArbitraryVariantOffsets(e){let t=this.recalculateVariantOffsets();return t.length===0?e:e.map(i=>{let[n,s]=i;return n={...n,variants:qh(n.variants,t)},[n,s]})}sortArbitraryProperties(e){let t=new Set;for(let[a]of e)a.arbitrary===1n&&t.add(a.property);if(t.size===0)return e;let i=Array.from(t).sort(),n=new Map,s=1n;for(let a of i)n.set(a,s++);return e.map(a=>{let[o,l]=a;return o={...o,propertyOffset:n.get(o.property)??0n},[o,l]})}sort(e){return e=this.remapArbitraryVariantOffsets(e),e=this.sortArbitraryProperties(e),e.sort(([t],[i])=>us(this.compare(t,i)))}}});function tl(r,e){let t=r.tailwindConfig.prefix;return typeof t==\"function\"?t(e):t+e}function Bh({type:r=\"any\",...e}){let t=[].concat(r);return{...e,types:t.map(i=>Array.isArray(i)?{type:i[0],...i[1]}:{type:i,preferOnConflict:!1})}}function L_(r){let e=[],t=\"\",i=0;for(let n=0;n0&&e.push(t.trim()),e=e.filter(n=>n!==\"\"),e}function M_(r,e,{before:t=[]}={}){if(t=[].concat(t),t.length<=0){r.push(e);return}let i=r.length-1;for(let n of t){let s=r.indexOf(n);s!==-1&&(i=Math.min(i,s))}r.splice(i,0,e)}function Fh(r){return Array.isArray(r)?r.flatMap(e=>!Array.isArray(e)&&!ke(e)?e:lr(e)):Fh([r])}function N_(r,e){return(0,Zo.default)(i=>{let n=[];return e&&e(i),i.walkClasses(s=>{n.push(s.value)}),n}).transformSync(r)}function B_(r){r.walkPseudos(e=>{e.value===\":not\"&&e.remove()})}function F_(r,e={containsNonOnDemandable:!1},t=0){let i=[],n=[];r.type===\"rule\"?n.push(...r.selectors):r.type===\"atrule\"&&r.walkRules(s=>n.push(...s.selectors));for(let s of n){let a=N_(s,B_);a.length===0&&(e.containsNonOnDemandable=!0);for(let o of a)i.push(o)}return t===0?[e.containsNonOnDemandable||i.length===0,i]:i}function fs(r){return Fh(r).flatMap(e=>{let t=new Map,[i,n]=F_(e);return i&&n.unshift(gt),n.map(s=>(t.has(e)||t.set(e,e),[s,t.get(e)]))})}function ls(r){return r.startsWith(\"@\")||r.includes(\"&\")}function Ri(r){r=r.replace(/\\n+/g,\"\").replace(/\\s{1,}/g,\" \").trim();let e=L_(r).map(t=>{if(!t.startsWith(\"@\"))return({format:s})=>s(t);let[,i,n]=/@(\\S*)( .+|[({].*)?/g.exec(t);return({wrap:s})=>s(ee.atRule({name:i,params:n?.trim()??\"\"}))}).reverse();return t=>{for(let i of e)i(t)}}function j_(r,e,{variantList:t,variantMap:i,offsets:n,classList:s}){function a(p,h){return p?(0,Nh.default)(r,p,h):r}function o(p){return ur(r.prefix,p)}function l(p,h){return p===gt?gt:h.respectPrefix?e.tailwindConfig.prefix+p:p}function c(p,h,b={}){let v=kt(p),y=a([\"theme\",...v],h);return mt(v[0])(y,b)}let f=0,d={postcss:ee,prefix:o,e:Te,config:a,theme:c,corePlugins:p=>Array.isArray(r.corePlugins)?r.corePlugins.includes(p):a([\"corePlugins\",p],!0),variants:()=>[],addBase(p){for(let[h,b]of fs(p)){let v=l(h,{}),y=n.create(\"base\");e.candidateRuleMap.has(v)||e.candidateRuleMap.set(v,[]),e.candidateRuleMap.get(v).push([{sort:y,layer:\"base\"},b])}},addDefaults(p,h){let b={[`@defaults ${p}`]:h};for(let[v,y]of fs(b)){let w=l(v,{});e.candidateRuleMap.has(w)||e.candidateRuleMap.set(w,[]),e.candidateRuleMap.get(w).push([{sort:n.create(\"defaults\"),layer:\"defaults\"},y])}},addComponents(p,h){h=Object.assign({},{preserveSource:!1,respectPrefix:!0,respectImportant:!1},Array.isArray(h)?{}:h);for(let[v,y]of fs(p)){let w=l(v,h);s.add(w),e.candidateRuleMap.has(w)||e.candidateRuleMap.set(w,[]),e.candidateRuleMap.get(w).push([{sort:n.create(\"components\"),layer:\"components\",options:h},y])}},addUtilities(p,h){h=Object.assign({},{preserveSource:!1,respectPrefix:!0,respectImportant:!0},Array.isArray(h)?{}:h);for(let[v,y]of fs(p)){let w=l(v,h);s.add(w),e.candidateRuleMap.has(w)||e.candidateRuleMap.set(w,[]),e.candidateRuleMap.get(w).push([{sort:n.create(\"utilities\"),layer:\"utilities\",options:h},y])}},matchUtilities:function(p,h){h=Bh({...{respectPrefix:!0,respectImportant:!0,modifiers:!1},...h});let v=n.create(\"utilities\");for(let y in p){let S=function(T,{isOnlyPlugin:B}){let[N,R,F]=ea(h.types,T,h,r);if(N===void 0)return[];if(!h.types.some(({type:U})=>U===R))if(B)G.warn([`Unnecessary typehint \\`${R}\\` in \\`${y}-${T}\\`.`,`You can safely update it to \\`${y}-${T.replace(R+\":\",\"\")}\\`.`]);else return[];if(!cr(N))return[];let Y={get modifier(){return h.modifiers||G.warn(`modifier-used-without-options-for-${y}`,[\"Your plugin must set `modifiers: true` in its options to support modifiers.\"]),F}},_=we(r,\"generalizedModifiers\");return[].concat(_?k(N,Y):k(N)).filter(Boolean).map(U=>({[Qn(y,T)]:U}))},w=l(y,h),k=p[y];s.add([w,h]);let E=[{sort:v,layer:\"utilities\",options:h},S];e.candidateRuleMap.has(w)||e.candidateRuleMap.set(w,[]),e.candidateRuleMap.get(w).push(E)}},matchComponents:function(p,h){h=Bh({...{respectPrefix:!0,respectImportant:!1,modifiers:!1},...h});let v=n.create(\"components\");for(let y in p){let S=function(T,{isOnlyPlugin:B}){let[N,R,F]=ea(h.types,T,h,r);if(N===void 0)return[];if(!h.types.some(({type:U})=>U===R))if(B)G.warn([`Unnecessary typehint \\`${R}\\` in \\`${y}-${T}\\`.`,`You can safely update it to \\`${y}-${T.replace(R+\":\",\"\")}\\`.`]);else return[];if(!cr(N))return[];let Y={get modifier(){return h.modifiers||G.warn(`modifier-used-without-options-for-${y}`,[\"Your plugin must set `modifiers: true` in its options to support modifiers.\"]),F}},_=we(r,\"generalizedModifiers\");return[].concat(_?k(N,Y):k(N)).filter(Boolean).map(U=>({[Qn(y,T)]:U}))},w=l(y,h),k=p[y];s.add([w,h]);let E=[{sort:v,layer:\"components\",options:h},S];e.candidateRuleMap.has(w)||e.candidateRuleMap.set(w,[]),e.candidateRuleMap.get(w).push(E)}},addVariant(p,h,b={}){h=[].concat(h).map(v=>{if(typeof v!=\"string\")return(y={})=>{let{args:w,modifySelectors:k,container:S,separator:E,wrap:T,format:B}=y,N=v(Object.assign({modifySelectors:k,container:S,separator:E},b.type===Jo.MatchVariant&&{args:w,wrap:T,format:B}));if(typeof N==\"string\"&&!ls(N))throw new Error(`Your custom variant \\`${p}\\` has an invalid format string. Make sure it's an at-rule or contains a \\`&\\` placeholder.`);return Array.isArray(N)?N.filter(R=>typeof R==\"string\").map(R=>Ri(R)):N&&typeof N==\"string\"&&Ri(N)(y)};if(!ls(v))throw new Error(`Your custom variant \\`${p}\\` has an invalid format string. Make sure it's an at-rule or contains a \\`&\\` placeholder.`);return Ri(v)}),M_(t,p,b),i.set(p,h),e.variantOptions.set(p,b)},matchVariant(p,h,b){let v=b?.id??++f,y=p===\"@\",w=we(r,\"generalizedModifiers\");for(let[S,E]of Object.entries(b?.values??{}))S!==\"DEFAULT\"&&d.addVariant(y?`${p}${S}`:`${p}-${S}`,({args:T,container:B})=>h(E,w?{modifier:T?.modifier,container:B}:{container:B}),{...b,value:E,id:v,type:Jo.MatchVariant,variantInfo:el.Base});let k=\"DEFAULT\"in(b?.values??{});d.addVariant(p,({args:S,container:E})=>S?.value===Ti&&!k?null:h(S?.value===Ti?b.values.DEFAULT:S?.value??(typeof S==\"string\"?S:\"\"),w?{modifier:S?.modifier,container:E}:{container:E}),{...b,id:v,type:Jo.MatchVariant,variantInfo:el.Dynamic})}};return d}function cs(r){return rl.has(r)||rl.set(r,new Map),rl.get(r)}function jh(r,e){let t=!1,i=new Map;for(let n of r){if(!n)continue;let s=oa.parse(n),a=s.hash?s.href.replace(s.hash,\"\"):s.href;a=s.search?a.replace(s.search,\"\"):a;let o=be.statSync(decodeURIComponent(a),{throwIfNoEntry:!1})?.mtimeMs;!o||((!e.has(n)||o>e.get(n))&&(t=!0),i.set(n,o))}return[t,i]}function zh(r){r.walkAtRules(e=>{[\"responsive\",\"variants\"].includes(e.name)&&(zh(e),e.before(e.nodes),e.remove())})}function z_(r){let e=[];return r.each(t=>{t.type===\"atrule\"&&[\"responsive\",\"variants\"].includes(t.name)&&(t.name=\"layer\",t.params=\"utilities\")}),r.walkAtRules(\"layer\",t=>{if(zh(t),t.params===\"base\"){for(let i of t.nodes)e.push(function({addBase:n}){n(i,{respectPrefix:!1})});t.remove()}else if(t.params===\"components\"){for(let i of t.nodes)e.push(function({addComponents:n}){n(i,{respectPrefix:!1,preserveSource:!0})});t.remove()}else if(t.params===\"utilities\"){for(let i of t.nodes)e.push(function({addUtilities:n}){n(i,{respectPrefix:!1,preserveSource:!0})});t.remove()}}),e}function U_(r,e){let t=Object.entries({...se,...yh}).map(([l,c])=>r.tailwindConfig.corePlugins.includes(l)?c:null).filter(Boolean),i=r.tailwindConfig.plugins.map(l=>(l.__isOptionsFunction&&(l=l()),typeof l==\"function\"?l:l.handler)),n=z_(e),s=[se.childVariant,se.pseudoElementVariants,se.pseudoClassVariants,se.hasVariants,se.ariaVariants,se.dataVariants],a=[se.supportsVariants,se.reducedMotionVariants,se.prefersContrastVariants,se.screenVariants,se.orientationVariants,se.directionVariants,se.darkVariants,se.forcedColorsVariants,se.printVariant];return(r.tailwindConfig.darkMode===\"class\"||Array.isArray(r.tailwindConfig.darkMode)&&r.tailwindConfig.darkMode[0]===\"class\")&&(a=[se.supportsVariants,se.reducedMotionVariants,se.prefersContrastVariants,se.darkVariants,se.screenVariants,se.orientationVariants,se.directionVariants,se.forcedColorsVariants,se.printVariant]),[...t,...s,...i,...a,...n]}function V_(r,e){let t=[],i=new Map;e.variantMap=i;let n=new Xo;e.offsets=n;let s=new Set,a=j_(e.tailwindConfig,e,{variantList:t,variantMap:i,offsets:n,classList:s});for(let f of r)if(Array.isArray(f))for(let d of f)d(a);else f?.(a);n.recordVariants(t,f=>i.get(f).length);for(let[f,d]of i.entries())e.variantMap.set(f,d.map((p,h)=>[n.forVariant(f,h),p]));let o=(e.tailwindConfig.safelist??[]).filter(Boolean);if(o.length>0){let f=[];for(let d of o){if(typeof d==\"string\"){e.changedContent.push({content:d,extension:\"html\"});continue}if(d instanceof RegExp){G.warn(\"root-regex\",[\"Regular expressions in `safelist` work differently in Tailwind CSS v3.0.\",\"Update your `safelist` configuration to eliminate this warning.\",\"https://tailwindcss.com/docs/content-configuration#safelisting-classes\"]);continue}f.push(d)}if(f.length>0){let d=new Map,p=e.tailwindConfig.prefix.length,h=f.some(b=>b.pattern.source.includes(\"!\"));for(let b of s){let v=Array.isArray(b)?(()=>{let[y,w]=b,S=Object.keys(w?.values??{}).map(E=>Ei(y,E));return w?.supportsNegativeValues&&(S=[...S,...S.map(E=>\"-\"+E)],S=[...S,...S.map(E=>E.slice(0,p)+\"-\"+E.slice(p))]),w.types.some(({type:E})=>E===\"color\")&&(S=[...S,...S.flatMap(E=>Object.keys(e.tailwindConfig.theme.opacity).map(T=>`${E}/${T}`))]),h&&w?.respectImportant&&(S=[...S,...S.map(E=>\"!\"+E)]),S})():[b];for(let y of v)for(let{pattern:w,variants:k=[]}of f)if(w.lastIndex=0,d.has(w)||d.set(w,0),!!w.test(y)){d.set(w,d.get(w)+1),e.changedContent.push({content:y,extension:\"html\"});for(let S of k)e.changedContent.push({content:S+e.tailwindConfig.separator+y,extension:\"html\"})}}for(let[b,v]of d.entries())v===0&&G.warn([`The safelist pattern \\`${b}\\` doesn't match any Tailwind CSS classes.`,\"Fix this pattern or remove it from your `safelist` configuration.\",\"https://tailwindcss.com/docs/content-configuration#safelisting-classes\"])}}let l=[].concat(e.tailwindConfig.darkMode??\"media\")[1]??\"dark\",c=[tl(e,l),tl(e,\"group\"),tl(e,\"peer\")];e.getClassOrder=function(d){let p=[...d].sort((y,w)=>y===w?0:y[y,null])),b=as(new Set(p),e,!0);b=e.offsets.sort(b);let v=BigInt(c.length);for(let[,y]of b){let w=y.raws.tailwind.candidate;h.set(w,h.get(w)??v++)}return d.map(y=>{let w=h.get(y)??null,k=c.indexOf(y);return w===null&&k!==-1&&(w=BigInt(k)),[y,w]})},e.getClassList=function(d={}){let p=[];for(let h of s)if(Array.isArray(h)){let[b,v]=h,y=[],w=Object.keys(v?.modifiers??{});v?.types?.some(({type:E})=>E===\"color\")&&w.push(...Object.keys(e.tailwindConfig.theme.opacity??{}));let k={modifiers:w},S=d.includeMetadata&&w.length>0;for(let[E,T]of Object.entries(v?.values??{})){if(T==null)continue;let B=Ei(b,E);if(p.push(S?[B,k]:B),v?.supportsNegativeValues&&xt(T)){let N=Ei(b,`-${E}`);y.push(S?[N,k]:N)}}p.push(...y)}else p.push(h);return p},e.getVariants=function(){let d=Math.random().toString(36).substring(7).toUpperCase(),p=[];for(let[h,b]of e.variantOptions.entries())b.variantInfo!==el.Base&&p.push({name:h,isArbitrary:b.type===Symbol.for(\"MATCH_VARIANT\"),values:Object.keys(b.values??{}),hasDash:h!==\"@\",selectors({modifier:v,value:y}={}){let w=`TAILWINDPLACEHOLDER${d}`,k=ee.rule({selector:`.${w}`}),S=ee.root({nodes:[k.clone()]}),E=S.toString(),T=(e.variantMap.get(h)??[]).flatMap(([le,A])=>A),B=[];for(let le of T){let A=[],C={args:{modifier:v,value:b.values?.[y]??y},separator:e.tailwindConfig.separator,modifySelectors(V){return S.each(Ee=>{Ee.type===\"rule\"&&(Ee.selectors=Ee.selectors.map(Ie=>V({get className(){return Go(Ie)},selector:Ie})))}),S},format(V){A.push(V)},wrap(V){A.push(`@${V.name} ${V.params} { & }`)},container:S},he=le(C);if(A.length>0&&B.push(A),Array.isArray(he))for(let V of he)A=[],V(C),B.push(A)}let N=[],R=S.toString();E!==R&&(S.walkRules(le=>{let A=le.selector,C=(0,Zo.default)(he=>{he.walkClasses(V=>{V.value=`${h}${e.tailwindConfig.separator}${V.value}`})}).processSync(A);N.push(A.replace(C,\"&\").replace(w,\"&\"))}),S.walkAtRules(le=>{N.push(`@${le.name} (${le.params}) { & }`)}));let F=!(y in(b.values??{})),Y=b[Pt]??{},_=(()=>!(F||Y.respectPrefix===!1))();B=B.map(le=>le.map(A=>({format:A,respectPrefix:_}))),N=N.map(le=>({format:le,respectPrefix:_}));let Q={candidate:w,context:e},U=B.map(le=>rs(`.${w}`,dr(le,Q),Q).replace(`.${w}`,\"&\").replace(\"{ & }\",\"\").trim());return N.length>0&&U.push(dr(N,Q).toString().replace(`.${w}`,\"&\")),U}});return p}}function Uh(r,e){!r.classCache.has(e)||(r.notClassCache.add(e),r.classCache.delete(e),r.applyClassCache.delete(e),r.candidateRuleMap.delete(e),r.candidateRuleCache.delete(e),r.stylesheetCache=null)}function H_(r,e){let t=e.raws.tailwind.candidate;if(!!t){for(let i of r.ruleCache)i[1].raws.tailwind.candidate===t&&r.ruleCache.delete(i);Uh(r,t)}}function il(r,e=[],t=ee.root()){let i={disposables:[],ruleCache:new Set,candidateRuleCache:new Map,classCache:new Map,applyClassCache:new Map,notClassCache:new Set(r.blocklist??[]),postCssNodeCache:new Map,candidateRuleMap:new Map,tailwindConfig:r,changedContent:e,variantMap:new Map,stylesheetCache:null,variantOptions:new Map,markInvalidUtilityCandidate:s=>Uh(i,s),markInvalidUtilityNode:s=>H_(i,s)},n=U_(i,t);return V_(n,i),i}function Vh(r,e,t,i,n,s){let a=e.opts.from,o=i!==null;Ze.DEBUG&&console.log(\"Source path:\",a);let l;if(o&&hr.has(a))l=hr.get(a);else if(Pi.has(n)){let p=Pi.get(n);Dt.get(p).add(a),hr.set(a,p),l=p}let c=Ph(a,r);if(l){let[p,h]=jh([...s],cs(l));if(!p&&!c)return[l,!1,h]}if(hr.has(a)){let p=hr.get(a);if(Dt.has(p)&&(Dt.get(p).delete(a),Dt.get(p).size===0)){Dt.delete(p);for(let[h,b]of Pi)b===p&&Pi.delete(h);for(let h of p.disposables.splice(0))h(p)}}Ze.DEBUG&&console.log(\"Setting up new context...\");let f=il(t,[],r);Object.assign(f,{userConfigPath:i});let[,d]=jh([...s],cs(f));return Pi.set(n,f),hr.set(a,f),Dt.has(f)||Dt.set(f,new Set),Dt.get(f).add(a),[f,!0,d]}var Nh,Zo,Pt,Jo,el,rl,hr,Pi,Dt,Oi=P(()=>{u();ft();la();Ot();Nh=pe(Ra()),Zo=pe(it());Ci();qo();Gn();Kt();fr();Lo();Fr();bh();It();It();Yi();Be();Gi();Bo();os();Ih();Mh();ct();Vo();Pt=Symbol(),Jo={AddVariant:Symbol.for(\"ADD_VARIANT\"),MatchVariant:Symbol.for(\"MATCH_VARIANT\")},el={Base:1<<0,Dynamic:1<<1};rl=new WeakMap;hr=wh,Pi=vh,Dt=es});function nl(r){return r.ignore?[]:r.glob?m.env.ROLLUP_WATCH===\"true\"?[{type:\"dependency\",file:r.base}]:[{type:\"dir-dependency\",dir:r.base,glob:r.glob}]:[{type:\"dependency\",file:r.base}]}var Hh=P(()=>{u()});function Wh(r,e){return{handler:r,config:e}}var Gh,Qh=P(()=>{u();Wh.withOptions=function(r,e=()=>({})){let t=function(i){return{__options:i,handler:r(i),config:e(i)}};return t.__isOptionsFunction=!0,t.__pluginFunction=r,t.__configFunction=e,t};Gh=Wh});var sl={};Ge(sl,{default:()=>W_});var W_,al=P(()=>{u();Qh();W_=Gh});var Kh=x((z4,Yh)=>{u();var G_=(al(),sl).default,Q_={overflow:\"hidden\",display:\"-webkit-box\",\"-webkit-box-orient\":\"vertical\"},Y_=G_(function({matchUtilities:r,addUtilities:e,theme:t,variants:i}){let n=t(\"lineClamp\");r({\"line-clamp\":s=>({...Q_,\"-webkit-line-clamp\":`${s}`})},{values:n}),e([{\".line-clamp-none\":{\"-webkit-line-clamp\":\"unset\"}}],i(\"lineClamp\"))},{theme:{lineClamp:{1:\"1\",2:\"2\",3:\"3\",4:\"4\",5:\"5\",6:\"6\"}},variants:{lineClamp:[\"responsive\"]}});Yh.exports=Y_});function ol(r){r.content.files.length===0&&G.warn(\"content-problems\",[\"The `content` option in your Tailwind CSS configuration is missing or empty.\",\"Configure your content sources or your generated CSS will be missing styles.\",\"https://tailwindcss.com/docs/content-configuration\"]);try{let e=Kh();r.plugins.includes(e)&&(G.warn(\"line-clamp-in-core\",[\"As of Tailwind CSS v3.3, the `@tailwindcss/line-clamp` plugin is now included by default.\",\"Remove it from the `plugins` array in your configuration to eliminate this warning.\"]),r.plugins=r.plugins.filter(t=>t!==e))}catch{}return r}var Xh=P(()=>{u();Be()});var Zh,Jh=P(()=>{u();Zh=()=>!1});var ps,em=P(()=>{u();ps={sync:r=>[].concat(r),generateTasks:r=>[{dynamic:!1,base:\".\",negative:[],positive:[].concat(r),patterns:[].concat(r)}],escapePath:r=>r}});var ll,tm=P(()=>{u();ll=r=>r});var rm,im=P(()=>{u();rm=()=>\"\"});function nm(r){let e=r,t=rm(r);return t!==\".\"&&(e=r.substr(t.length),e.charAt(0)===\"/\"&&(e=e.substr(1))),e.substr(0,2)===\"./\"?e=e.substr(2):e.charAt(0)===\"/\"&&(e=e.substr(1)),{base:t,glob:e}}var sm=P(()=>{u();im()});var ds=x(Ve=>{u();\"use strict\";Ve.isInteger=r=>typeof r==\"number\"?Number.isInteger(r):typeof r==\"string\"&&r.trim()!==\"\"?Number.isInteger(Number(r)):!1;Ve.find=(r,e)=>r.nodes.find(t=>t.type===e);Ve.exceedsLimit=(r,e,t=1,i)=>i===!1||!Ve.isInteger(r)||!Ve.isInteger(e)?!1:(Number(e)-Number(r))/Number(t)>=i;Ve.escapeNode=(r,e=0,t)=>{let i=r.nodes[e];!i||(t&&i.type===t||i.type===\"open\"||i.type===\"close\")&&i.escaped!==!0&&(i.value=\"\\\\\"+i.value,i.escaped=!0)};Ve.encloseBrace=r=>r.type!==\"brace\"?!1:r.commas>>0+r.ranges>>0==0?(r.invalid=!0,!0):!1;Ve.isInvalidBrace=r=>r.type!==\"brace\"?!1:r.invalid===!0||r.dollar?!0:r.commas>>0+r.ranges>>0==0||r.open!==!0||r.close!==!0?(r.invalid=!0,!0):!1;Ve.isOpenOrClose=r=>r.type===\"open\"||r.type===\"close\"?!0:r.open===!0||r.close===!0;Ve.reduce=r=>r.reduce((e,t)=>(t.type===\"text\"&&e.push(t.value),t.type===\"range\"&&(t.type=\"text\"),e),[]);Ve.flatten=(...r)=>{let e=[],t=i=>{for(let n=0;n{u();\"use strict\";var am=ds();om.exports=(r,e={})=>{let t=(i,n={})=>{let s=e.escapeInvalid&&am.isInvalidBrace(n),a=i.invalid===!0&&e.escapeInvalid===!0,o=\"\";if(i.value)return(s||a)&&am.isOpenOrClose(i)?\"\\\\\"+i.value:i.value;if(i.value)return i.value;if(i.nodes)for(let l of i.nodes)o+=t(l);return o};return t(r)}});var um=x((J4,lm)=>{u();\"use strict\";lm.exports=function(r){return typeof r==\"number\"?r-r==0:typeof r==\"string\"&&r.trim()!==\"\"?Number.isFinite?Number.isFinite(+r):isFinite(+r):!1}});var bm=x((e6,ym)=>{u();\"use strict\";var fm=um(),Wt=(r,e,t)=>{if(fm(r)===!1)throw new TypeError(\"toRegexRange: expected the first argument to be a number\");if(e===void 0||r===e)return String(r);if(fm(e)===!1)throw new TypeError(\"toRegexRange: expected the second argument to be a number.\");let i={relaxZeros:!0,...t};typeof i.strictZeros==\"boolean\"&&(i.relaxZeros=i.strictZeros===!1);let n=String(i.relaxZeros),s=String(i.shorthand),a=String(i.capture),o=String(i.wrap),l=r+\":\"+e+\"=\"+n+s+a+o;if(Wt.cache.hasOwnProperty(l))return Wt.cache[l].result;let c=Math.min(r,e),f=Math.max(r,e);if(Math.abs(c-f)===1){let v=r+\"|\"+e;return i.capture?`(${v})`:i.wrap===!1?v:`(?:${v})`}let d=gm(r)||gm(e),p={min:r,max:e,a:c,b:f},h=[],b=[];if(d&&(p.isPadded=d,p.maxLen=String(p.max).length),c<0){let v=f<0?Math.abs(f):1;b=cm(v,Math.abs(c),p,i),c=p.a=0}return f>=0&&(h=cm(c,f,p,i)),p.negatives=b,p.positives=h,p.result=K_(b,h,i),i.capture===!0?p.result=`(${p.result})`:i.wrap!==!1&&h.length+b.length>1&&(p.result=`(?:${p.result})`),Wt.cache[l]=p,p.result};function K_(r,e,t){let i=ul(r,e,\"-\",!1,t)||[],n=ul(e,r,\"\",!1,t)||[],s=ul(r,e,\"-?\",!0,t)||[];return i.concat(s).concat(n).join(\"|\")}function X_(r,e){let t=1,i=1,n=dm(r,t),s=new Set([e]);for(;r<=n&&n<=e;)s.add(n),t+=1,n=dm(r,t);for(n=hm(e+1,i)-1;r1&&o.count.pop(),o.count.push(f.count[0]),o.string=o.pattern+mm(o.count),a=c+1;continue}t.isPadded&&(d=rE(c,t,i)),f.string=d+f.pattern+mm(f.count),s.push(f),a=c+1,o=f}return s}function ul(r,e,t,i,n){let s=[];for(let a of r){let{string:o}=a;!i&&!pm(e,\"string\",o)&&s.push(t+o),i&&pm(e,\"string\",o)&&s.push(t+o)}return s}function J_(r,e){let t=[];for(let i=0;ie?1:e>r?-1:0}function pm(r,e,t){return r.some(i=>i[e]===t)}function dm(r,e){return Number(String(r).slice(0,-e)+\"9\".repeat(e))}function hm(r,e){return r-r%Math.pow(10,e)}function mm(r){let[e=0,t=\"\"]=r;return t||e>1?`{${e+(t?\",\"+t:\"\")}}`:\"\"}function tE(r,e,t){return`[${r}${e-r==1?\"\":\"-\"}${e}]`}function gm(r){return/^-?(0+)\\d/.test(r)}function rE(r,e,t){if(!e.isPadded)return r;let i=Math.abs(e.maxLen-String(r).length),n=t.relaxZeros!==!1;switch(i){case 0:return\"\";case 1:return n?\"0?\":\"0\";case 2:return n?\"0{0,2}\":\"00\";default:return n?`0{0,${i}}`:`0{${i}}`}}Wt.cache={};Wt.clearCache=()=>Wt.cache={};ym.exports=Wt});var pl=x((t6,Cm)=>{u();\"use strict\";var iE=(Fn(),Bn),wm=bm(),vm=r=>r!==null&&typeof r==\"object\"&&!Array.isArray(r),nE=r=>e=>r===!0?Number(e):String(e),fl=r=>typeof r==\"number\"||typeof r==\"string\"&&r!==\"\",Ii=r=>Number.isInteger(+r),cl=r=>{let e=`${r}`,t=-1;if(e[0]===\"-\"&&(e=e.slice(1)),e===\"0\")return!1;for(;e[++t]===\"0\";);return t>0},sE=(r,e,t)=>typeof r==\"string\"||typeof e==\"string\"?!0:t.stringify===!0,aE=(r,e,t)=>{if(e>0){let i=r[0]===\"-\"?\"-\":\"\";i&&(r=r.slice(1)),r=i+r.padStart(i?e-1:e,\"0\")}return t===!1?String(r):r},ms=(r,e)=>{let t=r[0]===\"-\"?\"-\":\"\";for(t&&(r=r.slice(1),e--);r.length{r.negatives.sort((o,l)=>ol?1:0),r.positives.sort((o,l)=>ol?1:0);let i=e.capture?\"\":\"?:\",n=\"\",s=\"\",a;return r.positives.length&&(n=r.positives.map(o=>ms(String(o),t)).join(\"|\")),r.negatives.length&&(s=`-(${i}${r.negatives.map(o=>ms(String(o),t)).join(\"|\")})`),n&&s?a=`${n}|${s}`:a=n||s,e.wrap?`(${i}${a})`:a},xm=(r,e,t,i)=>{if(t)return wm(r,e,{wrap:!1,...i});let n=String.fromCharCode(r);if(r===e)return n;let s=String.fromCharCode(e);return`[${n}-${s}]`},km=(r,e,t)=>{if(Array.isArray(r)){let i=t.wrap===!0,n=t.capture?\"\":\"?:\";return i?`(${n}${r.join(\"|\")})`:r.join(\"|\")}return wm(r,e,t)},Sm=(...r)=>new RangeError(\"Invalid range arguments: \"+iE.inspect(...r)),Am=(r,e,t)=>{if(t.strictRanges===!0)throw Sm([r,e]);return[]},lE=(r,e)=>{if(e.strictRanges===!0)throw new TypeError(`Expected step \"${r}\" to be a number`);return[]},uE=(r,e,t=1,i={})=>{let n=Number(r),s=Number(e);if(!Number.isInteger(n)||!Number.isInteger(s)){if(i.strictRanges===!0)throw Sm([r,e]);return[]}n===0&&(n=0),s===0&&(s=0);let a=n>s,o=String(r),l=String(e),c=String(t);t=Math.max(Math.abs(t),1);let f=cl(o)||cl(l)||cl(c),d=f?Math.max(o.length,l.length,c.length):0,p=f===!1&&sE(r,e,i)===!1,h=i.transform||nE(p);if(i.toRegex&&t===1)return xm(ms(r,d),ms(e,d),!0,i);let b={negatives:[],positives:[]},v=k=>b[k<0?\"negatives\":\"positives\"].push(Math.abs(k)),y=[],w=0;for(;a?n>=s:n<=s;)i.toRegex===!0&&t>1?v(n):y.push(aE(h(n,w),d,p)),n=a?n-t:n+t,w++;return i.toRegex===!0?t>1?oE(b,i,d):km(y,null,{wrap:!1,...i}):y},fE=(r,e,t=1,i={})=>{if(!Ii(r)&&r.length>1||!Ii(e)&&e.length>1)return Am(r,e,i);let n=i.transform||(p=>String.fromCharCode(p)),s=`${r}`.charCodeAt(0),a=`${e}`.charCodeAt(0),o=s>a,l=Math.min(s,a),c=Math.max(s,a);if(i.toRegex&&t===1)return xm(l,c,!1,i);let f=[],d=0;for(;o?s>=a:s<=a;)f.push(n(s,d)),s=o?s-t:s+t,d++;return i.toRegex===!0?km(f,null,{wrap:!1,options:i}):f},gs=(r,e,t,i={})=>{if(e==null&&fl(r))return[r];if(!fl(r)||!fl(e))return Am(r,e,i);if(typeof t==\"function\")return gs(r,e,1,{transform:t});if(vm(t))return gs(r,e,0,t);let n={...i};return n.capture===!0&&(n.wrap=!0),t=t||n.step||1,Ii(t)?Ii(r)&&Ii(e)?uE(r,e,t,n):fE(r,e,Math.max(Math.abs(t),1),n):t!=null&&!vm(t)?lE(t,n):gs(r,e,1,t)};Cm.exports=gs});var Om=x((r6,Em)=>{u();\"use strict\";var cE=pl(),_m=ds(),pE=(r,e={})=>{let t=(i,n={})=>{let s=_m.isInvalidBrace(n),a=i.invalid===!0&&e.escapeInvalid===!0,o=s===!0||a===!0,l=e.escapeInvalid===!0?\"\\\\\":\"\",c=\"\";if(i.isOpen===!0)return l+i.value;if(i.isClose===!0)return console.log(\"node.isClose\",l,i.value),l+i.value;if(i.type===\"open\")return o?l+i.value:\"(\";if(i.type===\"close\")return o?l+i.value:\")\";if(i.type===\"comma\")return i.prev.type===\"comma\"?\"\":o?i.value:\"|\";if(i.value)return i.value;if(i.nodes&&i.ranges>0){let f=_m.reduce(i.nodes),d=cE(...f,{...e,wrap:!1,toRegex:!0,strictZeros:!0});if(d.length!==0)return f.length>1&&d.length>1?`(${d})`:d}if(i.nodes)for(let f of i.nodes)c+=t(f,i);return c};return t(r)};Em.exports=pE});var Pm=x((i6,Rm)=>{u();\"use strict\";var dE=pl(),Tm=hs(),mr=ds(),Gt=(r=\"\",e=\"\",t=!1)=>{let i=[];if(r=[].concat(r),e=[].concat(e),!e.length)return r;if(!r.length)return t?mr.flatten(e).map(n=>`{${n}}`):e;for(let n of r)if(Array.isArray(n))for(let s of n)i.push(Gt(s,e,t));else for(let s of e)t===!0&&typeof s==\"string\"&&(s=`{${s}}`),i.push(Array.isArray(s)?Gt(n,s,t):n+s);return mr.flatten(i)},hE=(r,e={})=>{let t=e.rangeLimit===void 0?1e3:e.rangeLimit,i=(n,s={})=>{n.queue=[];let a=s,o=s.queue;for(;a.type!==\"brace\"&&a.type!==\"root\"&&a.parent;)a=a.parent,o=a.queue;if(n.invalid||n.dollar){o.push(Gt(o.pop(),Tm(n,e)));return}if(n.type===\"brace\"&&n.invalid!==!0&&n.nodes.length===2){o.push(Gt(o.pop(),[\"{}\"]));return}if(n.nodes&&n.ranges>0){let d=mr.reduce(n.nodes);if(mr.exceedsLimit(...d,e.step,t))throw new RangeError(\"expanded array length exceeds range limit. Use options.rangeLimit to increase or disable the limit.\");let p=dE(...d,e);p.length===0&&(p=Tm(n,e)),o.push(Gt(o.pop(),p)),n.nodes=[];return}let l=mr.encloseBrace(n),c=n.queue,f=n;for(;f.type!==\"brace\"&&f.type!==\"root\"&&f.parent;)f=f.parent,c=f.queue;for(let d=0;d{u();\"use strict\";Im.exports={MAX_LENGTH:1e4,CHAR_0:\"0\",CHAR_9:\"9\",CHAR_UPPERCASE_A:\"A\",CHAR_LOWERCASE_A:\"a\",CHAR_UPPERCASE_Z:\"Z\",CHAR_LOWERCASE_Z:\"z\",CHAR_LEFT_PARENTHESES:\"(\",CHAR_RIGHT_PARENTHESES:\")\",CHAR_ASTERISK:\"*\",CHAR_AMPERSAND:\"&\",CHAR_AT:\"@\",CHAR_BACKSLASH:\"\\\\\",CHAR_BACKTICK:\"`\",CHAR_CARRIAGE_RETURN:\"\\r\",CHAR_CIRCUMFLEX_ACCENT:\"^\",CHAR_COLON:\":\",CHAR_COMMA:\",\",CHAR_DOLLAR:\"$\",CHAR_DOT:\".\",CHAR_DOUBLE_QUOTE:'\"',CHAR_EQUAL:\"=\",CHAR_EXCLAMATION_MARK:\"!\",CHAR_FORM_FEED:\"\\f\",CHAR_FORWARD_SLASH:\"/\",CHAR_HASH:\"#\",CHAR_HYPHEN_MINUS:\"-\",CHAR_LEFT_ANGLE_BRACKET:\"<\",CHAR_LEFT_CURLY_BRACE:\"{\",CHAR_LEFT_SQUARE_BRACKET:\"[\",CHAR_LINE_FEED:`\n`,CHAR_NO_BREAK_SPACE:\"\\xA0\",CHAR_PERCENT:\"%\",CHAR_PLUS:\"+\",CHAR_QUESTION_MARK:\"?\",CHAR_RIGHT_ANGLE_BRACKET:\">\",CHAR_RIGHT_CURLY_BRACE:\"}\",CHAR_RIGHT_SQUARE_BRACKET:\"]\",CHAR_SEMICOLON:\";\",CHAR_SINGLE_QUOTE:\"'\",CHAR_SPACE:\" \",CHAR_TAB:\"\t\",CHAR_UNDERSCORE:\"_\",CHAR_VERTICAL_LINE:\"|\",CHAR_ZERO_WIDTH_NOBREAK_SPACE:\"\\uFEFF\"}});var Nm=x((s6,Mm)=>{u();\"use strict\";var mE=hs(),{MAX_LENGTH:qm,CHAR_BACKSLASH:dl,CHAR_BACKTICK:gE,CHAR_COMMA:yE,CHAR_DOT:bE,CHAR_LEFT_PARENTHESES:wE,CHAR_RIGHT_PARENTHESES:vE,CHAR_LEFT_CURLY_BRACE:xE,CHAR_RIGHT_CURLY_BRACE:kE,CHAR_LEFT_SQUARE_BRACKET:$m,CHAR_RIGHT_SQUARE_BRACKET:Lm,CHAR_DOUBLE_QUOTE:SE,CHAR_SINGLE_QUOTE:AE,CHAR_NO_BREAK_SPACE:CE,CHAR_ZERO_WIDTH_NOBREAK_SPACE:_E}=Dm(),EE=(r,e={})=>{if(typeof r!=\"string\")throw new TypeError(\"Expected a string\");let t=e||{},i=typeof t.maxLength==\"number\"?Math.min(qm,t.maxLength):qm;if(r.length>i)throw new SyntaxError(`Input length (${r.length}), exceeds max characters (${i})`);let n={type:\"root\",input:r,nodes:[]},s=[n],a=n,o=n,l=0,c=r.length,f=0,d=0,p,h=()=>r[f++],b=v=>{if(v.type===\"text\"&&o.type===\"dot\"&&(o.type=\"text\"),o&&o.type===\"text\"&&v.type===\"text\"){o.value+=v.value;return}return a.nodes.push(v),v.parent=a,v.prev=o,o=v,v};for(b({type:\"bos\"});f0){if(a.ranges>0){a.ranges=0;let v=a.nodes.shift();a.nodes=[v,{type:\"text\",value:mE(a)}]}b({type:\"comma\",value:p}),a.commas++;continue}if(p===bE&&d>0&&a.commas===0){let v=a.nodes;if(d===0||v.length===0){b({type:\"text\",value:p});continue}if(o.type===\"dot\"){if(a.range=[],o.value+=p,o.type=\"range\",a.nodes.length!==3&&a.nodes.length!==5){a.invalid=!0,a.ranges=0,o.type=\"text\";continue}a.ranges++,a.args=[];continue}if(o.type===\"range\"){v.pop();let y=v[v.length-1];y.value+=o.value+p,o=y,a.ranges--;continue}b({type:\"dot\",value:p});continue}b({type:\"text\",value:p})}do if(a=s.pop(),a.type!==\"root\"){a.nodes.forEach(w=>{w.nodes||(w.type===\"open\"&&(w.isOpen=!0),w.type===\"close\"&&(w.isClose=!0),w.nodes||(w.type=\"text\"),w.invalid=!0)});let v=s[s.length-1],y=v.nodes.indexOf(a);v.nodes.splice(y,1,...a.nodes)}while(s.length>0);return b({type:\"eos\"}),n};Mm.exports=EE});var jm=x((a6,Fm)=>{u();\"use strict\";var Bm=hs(),OE=Om(),TE=Pm(),RE=Nm(),Le=(r,e={})=>{let t=[];if(Array.isArray(r))for(let i of r){let n=Le.create(i,e);Array.isArray(n)?t.push(...n):t.push(n)}else t=[].concat(Le.create(r,e));return e&&e.expand===!0&&e.nodupes===!0&&(t=[...new Set(t)]),t};Le.parse=(r,e={})=>RE(r,e);Le.stringify=(r,e={})=>typeof r==\"string\"?Bm(Le.parse(r,e),e):Bm(r,e);Le.compile=(r,e={})=>(typeof r==\"string\"&&(r=Le.parse(r,e)),OE(r,e));Le.expand=(r,e={})=>{typeof r==\"string\"&&(r=Le.parse(r,e));let t=TE(r,e);return e.noempty===!0&&(t=t.filter(Boolean)),e.nodupes===!0&&(t=[...new Set(t)]),t};Le.create=(r,e={})=>r===\"\"||r.length<3?[r]:e.expand!==!0?Le.compile(r,e):Le.expand(r,e);Fm.exports=Le});var Di=x((o6,Wm)=>{u();\"use strict\";var PE=(et(),Ur),at=\"\\\\\\\\/\",zm=`[^${at}]`,yt=\"\\\\.\",IE=\"\\\\+\",DE=\"\\\\?\",ys=\"\\\\/\",qE=\"(?=.)\",Um=\"[^/]\",hl=`(?:${ys}|$)`,Vm=`(?:^|${ys})`,ml=`${yt}{1,2}${hl}`,$E=`(?!${yt})`,LE=`(?!${Vm}${ml})`,ME=`(?!${yt}{0,1}${hl})`,NE=`(?!${ml})`,BE=`[^.${ys}]`,FE=`${Um}*?`,Hm={DOT_LITERAL:yt,PLUS_LITERAL:IE,QMARK_LITERAL:DE,SLASH_LITERAL:ys,ONE_CHAR:qE,QMARK:Um,END_ANCHOR:hl,DOTS_SLASH:ml,NO_DOT:$E,NO_DOTS:LE,NO_DOT_SLASH:ME,NO_DOTS_SLASH:NE,QMARK_NO_DOT:BE,STAR:FE,START_ANCHOR:Vm},jE={...Hm,SLASH_LITERAL:`[${at}]`,QMARK:zm,STAR:`${zm}*?`,DOTS_SLASH:`${yt}{1,2}(?:[${at}]|$)`,NO_DOT:`(?!${yt})`,NO_DOTS:`(?!(?:^|[${at}])${yt}{1,2}(?:[${at}]|$))`,NO_DOT_SLASH:`(?!${yt}{0,1}(?:[${at}]|$))`,NO_DOTS_SLASH:`(?!${yt}{1,2}(?:[${at}]|$))`,QMARK_NO_DOT:`[^.${at}]`,START_ANCHOR:`(?:^|[${at}])`,END_ANCHOR:`(?:[${at}]|$)`},zE={alnum:\"a-zA-Z0-9\",alpha:\"a-zA-Z\",ascii:\"\\\\x00-\\\\x7F\",blank:\" \\\\t\",cntrl:\"\\\\x00-\\\\x1F\\\\x7F\",digit:\"0-9\",graph:\"\\\\x21-\\\\x7E\",lower:\"a-z\",print:\"\\\\x20-\\\\x7E \",punct:\"\\\\-!\\\"#$%&'()\\\\*+,./:;<=>?@[\\\\]^_`{|}~\",space:\" \\\\t\\\\r\\\\n\\\\v\\\\f\",upper:\"A-Z\",word:\"A-Za-z0-9_\",xdigit:\"A-Fa-f0-9\"};Wm.exports={MAX_LENGTH:1024*64,POSIX_REGEX_SOURCE:zE,REGEX_BACKSLASH:/\\\\(?![*+?^${}(|)[\\]])/g,REGEX_NON_SPECIAL_CHARS:/^[^@![\\].,$*+?^{}()|\\\\/]+/,REGEX_SPECIAL_CHARS:/[-*+?.^${}(|)[\\]]/,REGEX_SPECIAL_CHARS_BACKREF:/(\\\\?)((\\W)(\\3*))/g,REGEX_SPECIAL_CHARS_GLOBAL:/([-*+?.^${}(|)[\\]])/g,REGEX_REMOVE_BACKSLASH:/(?:\\[.*?[^\\\\]\\]|\\\\(?=.))/g,REPLACEMENTS:{\"***\":\"*\",\"**/**\":\"**\",\"**/**/**\":\"**\"},CHAR_0:48,CHAR_9:57,CHAR_UPPERCASE_A:65,CHAR_LOWERCASE_A:97,CHAR_UPPERCASE_Z:90,CHAR_LOWERCASE_Z:122,CHAR_LEFT_PARENTHESES:40,CHAR_RIGHT_PARENTHESES:41,CHAR_ASTERISK:42,CHAR_AMPERSAND:38,CHAR_AT:64,CHAR_BACKWARD_SLASH:92,CHAR_CARRIAGE_RETURN:13,CHAR_CIRCUMFLEX_ACCENT:94,CHAR_COLON:58,CHAR_COMMA:44,CHAR_DOT:46,CHAR_DOUBLE_QUOTE:34,CHAR_EQUAL:61,CHAR_EXCLAMATION_MARK:33,CHAR_FORM_FEED:12,CHAR_FORWARD_SLASH:47,CHAR_GRAVE_ACCENT:96,CHAR_HASH:35,CHAR_HYPHEN_MINUS:45,CHAR_LEFT_ANGLE_BRACKET:60,CHAR_LEFT_CURLY_BRACE:123,CHAR_LEFT_SQUARE_BRACKET:91,CHAR_LINE_FEED:10,CHAR_NO_BREAK_SPACE:160,CHAR_PERCENT:37,CHAR_PLUS:43,CHAR_QUESTION_MARK:63,CHAR_RIGHT_ANGLE_BRACKET:62,CHAR_RIGHT_CURLY_BRACE:125,CHAR_RIGHT_SQUARE_BRACKET:93,CHAR_SEMICOLON:59,CHAR_SINGLE_QUOTE:39,CHAR_SPACE:32,CHAR_TAB:9,CHAR_UNDERSCORE:95,CHAR_VERTICAL_LINE:124,CHAR_ZERO_WIDTH_NOBREAK_SPACE:65279,SEP:PE.sep,extglobChars(r){return{\"!\":{type:\"negate\",open:\"(?:(?!(?:\",close:`))${r.STAR})`},\"?\":{type:\"qmark\",open:\"(?:\",close:\")?\"},\"+\":{type:\"plus\",open:\"(?:\",close:\")+\"},\"*\":{type:\"star\",open:\"(?:\",close:\")*\"},\"@\":{type:\"at\",open:\"(?:\",close:\")\"}}},globChars(r){return r===!0?jE:Hm}}});var qi=x(Re=>{u();\"use strict\";var UE=(et(),Ur),VE=m.platform===\"win32\",{REGEX_BACKSLASH:HE,REGEX_REMOVE_BACKSLASH:WE,REGEX_SPECIAL_CHARS:GE,REGEX_SPECIAL_CHARS_GLOBAL:QE}=Di();Re.isObject=r=>r!==null&&typeof r==\"object\"&&!Array.isArray(r);Re.hasRegexChars=r=>GE.test(r);Re.isRegexChar=r=>r.length===1&&Re.hasRegexChars(r);Re.escapeRegex=r=>r.replace(QE,\"\\\\$1\");Re.toPosixSlashes=r=>r.replace(HE,\"/\");Re.removeBackslashes=r=>r.replace(WE,e=>e===\"\\\\\"?\"\":e);Re.supportsLookbehinds=()=>{let r=m.version.slice(1).split(\".\").map(Number);return r.length===3&&r[0]>=9||r[0]===8&&r[1]>=10};Re.isWindows=r=>r&&typeof r.windows==\"boolean\"?r.windows:VE===!0||UE.sep===\"\\\\\";Re.escapeLast=(r,e,t)=>{let i=r.lastIndexOf(e,t);return i===-1?r:r[i-1]===\"\\\\\"?Re.escapeLast(r,e,i-1):`${r.slice(0,i)}\\\\${r.slice(i)}`};Re.removePrefix=(r,e={})=>{let t=r;return t.startsWith(\"./\")&&(t=t.slice(2),e.prefix=\"./\"),t};Re.wrapOutput=(r,e={},t={})=>{let i=t.contains?\"\":\"^\",n=t.contains?\"\":\"$\",s=`${i}(?:${r})${n}`;return e.negated===!0&&(s=`(?:^(?!${s}).*$)`),s}});var eg=x((u6,Jm)=>{u();\"use strict\";var Gm=qi(),{CHAR_ASTERISK:gl,CHAR_AT:YE,CHAR_BACKWARD_SLASH:$i,CHAR_COMMA:KE,CHAR_DOT:yl,CHAR_EXCLAMATION_MARK:bl,CHAR_FORWARD_SLASH:Qm,CHAR_LEFT_CURLY_BRACE:wl,CHAR_LEFT_PARENTHESES:vl,CHAR_LEFT_SQUARE_BRACKET:XE,CHAR_PLUS:ZE,CHAR_QUESTION_MARK:Ym,CHAR_RIGHT_CURLY_BRACE:JE,CHAR_RIGHT_PARENTHESES:Km,CHAR_RIGHT_SQUARE_BRACKET:e2}=Di(),Xm=r=>r===Qm||r===$i,Zm=r=>{r.isPrefix!==!0&&(r.depth=r.isGlobstar?1/0:1)},t2=(r,e)=>{let t=e||{},i=r.length-1,n=t.parts===!0||t.scanToEnd===!0,s=[],a=[],o=[],l=r,c=-1,f=0,d=0,p=!1,h=!1,b=!1,v=!1,y=!1,w=!1,k=!1,S=!1,E=!1,T=!1,B=0,N,R,F={value:\"\",depth:0,isGlob:!1},Y=()=>c>=i,_=()=>l.charCodeAt(c+1),Q=()=>(N=R,l.charCodeAt(++c));for(;c0&&(le=l.slice(0,f),l=l.slice(f),d-=f),U&&b===!0&&d>0?(U=l.slice(0,d),A=l.slice(d)):b===!0?(U=\"\",A=l):U=l,U&&U!==\"\"&&U!==\"/\"&&U!==l&&Xm(U.charCodeAt(U.length-1))&&(U=U.slice(0,-1)),t.unescape===!0&&(A&&(A=Gm.removeBackslashes(A)),U&&k===!0&&(U=Gm.removeBackslashes(U)));let C={prefix:le,input:r,start:f,base:U,glob:A,isBrace:p,isBracket:h,isGlob:b,isExtglob:v,isGlobstar:y,negated:S,negatedExtglob:E};if(t.tokens===!0&&(C.maxDepth=0,Xm(R)||a.push(F),C.tokens=a),t.parts===!0||t.tokens===!0){let he;for(let V=0;V{u();\"use strict\";var bs=Di(),Me=qi(),{MAX_LENGTH:ws,POSIX_REGEX_SOURCE:r2,REGEX_NON_SPECIAL_CHARS:i2,REGEX_SPECIAL_CHARS_BACKREF:n2,REPLACEMENTS:tg}=bs,s2=(r,e)=>{if(typeof e.expandRange==\"function\")return e.expandRange(...r,e);r.sort();let t=`[${r.join(\"-\")}]`;try{new RegExp(t)}catch(i){return r.map(n=>Me.escapeRegex(n)).join(\"..\")}return t},gr=(r,e)=>`Missing ${r}: \"${e}\" - use \"\\\\\\\\${e}\" to match literal characters`,xl=(r,e)=>{if(typeof r!=\"string\")throw new TypeError(\"Expected a string\");r=tg[r]||r;let t={...e},i=typeof t.maxLength==\"number\"?Math.min(ws,t.maxLength):ws,n=r.length;if(n>i)throw new SyntaxError(`Input length: ${n}, exceeds maximum allowed length: ${i}`);let s={type:\"bos\",value:\"\",output:t.prepend||\"\"},a=[s],o=t.capture?\"\":\"?:\",l=Me.isWindows(e),c=bs.globChars(l),f=bs.extglobChars(c),{DOT_LITERAL:d,PLUS_LITERAL:p,SLASH_LITERAL:h,ONE_CHAR:b,DOTS_SLASH:v,NO_DOT:y,NO_DOT_SLASH:w,NO_DOTS_SLASH:k,QMARK:S,QMARK_NO_DOT:E,STAR:T,START_ANCHOR:B}=c,N=$=>`(${o}(?:(?!${B}${$.dot?v:d}).)*?)`,R=t.dot?\"\":y,F=t.dot?S:E,Y=t.bash===!0?N(t):T;t.capture&&(Y=`(${Y})`),typeof t.noext==\"boolean\"&&(t.noextglob=t.noext);let _={input:r,index:-1,start:0,dot:t.dot===!0,consumed:\"\",output:\"\",prefix:\"\",backtrack:!1,negated:!1,brackets:0,braces:0,parens:0,quotes:0,globstar:!1,tokens:a};r=Me.removePrefix(r,_),n=r.length;let Q=[],U=[],le=[],A=s,C,he=()=>_.index===n-1,V=_.peek=($=1)=>r[_.index+$],Ee=_.advance=()=>r[++_.index]||\"\",Ie=()=>r.slice(_.index+1),De=($=\"\",ae=0)=>{_.consumed+=$,_.index+=ae},ji=$=>{_.output+=$.output!=null?$.output:$.value,De($.value)},Iv=()=>{let $=1;for(;V()===\"!\"&&(V(2)!==\"(\"||V(3)===\"?\");)Ee(),_.start++,$++;return $%2==0?!1:(_.negated=!0,_.start++,!0)},zi=$=>{_[$]++,le.push($)},Ft=$=>{_[$]--,le.pop()},W=$=>{if(A.type===\"globstar\"){let ae=_.braces>0&&($.type===\"comma\"||$.type===\"brace\"),I=$.extglob===!0||Q.length&&($.type===\"pipe\"||$.type===\"paren\");$.type!==\"slash\"&&$.type!==\"paren\"&&!ae&&!I&&(_.output=_.output.slice(0,-A.output.length),A.type=\"star\",A.value=\"*\",A.output=Y,_.output+=A.output)}if(Q.length&&$.type!==\"paren\"&&(Q[Q.length-1].inner+=$.value),($.value||$.output)&&ji($),A&&A.type===\"text\"&&$.type===\"text\"){A.value+=$.value,A.output=(A.output||\"\")+$.value;return}$.prev=A,a.push($),A=$},Ui=($,ae)=>{let I={...f[ae],conditions:1,inner:\"\"};I.prev=A,I.parens=_.parens,I.output=_.output;let H=(t.capture?\"(\":\"\")+I.open;zi(\"parens\"),W({type:$,value:ae,output:_.output?\"\":b}),W({type:\"paren\",extglob:!0,value:Ee(),output:H}),Q.push(I)},Dv=$=>{let ae=$.close+(t.capture?\")\":\"\"),I;if($.type===\"negate\"){let H=Y;if($.inner&&$.inner.length>1&&$.inner.includes(\"/\")&&(H=N(t)),(H!==Y||he()||/^\\)+$/.test(Ie()))&&(ae=$.close=`)$))${H}`),$.inner.includes(\"*\")&&(I=Ie())&&/^\\.[^\\\\/.]+$/.test(I)){let ce=xl(I,{...e,fastpaths:!1}).output;ae=$.close=`)${ce})${H})`}$.prev.type===\"bos\"&&(_.negatedExtglob=!0)}W({type:\"paren\",extglob:!0,value:C,output:ae}),Ft(\"parens\")};if(t.fastpaths!==!1&&!/(^[*!]|[/()[\\]{}\"])/.test(r)){let $=!1,ae=r.replace(n2,(I,H,ce,Ce,ye,Bs)=>Ce===\"\\\\\"?($=!0,I):Ce===\"?\"?H?H+Ce+(ye?S.repeat(ye.length):\"\"):Bs===0?F+(ye?S.repeat(ye.length):\"\"):S.repeat(ce.length):Ce===\".\"?d.repeat(ce.length):Ce===\"*\"?H?H+Ce+(ye?Y:\"\"):Y:H?I:`\\\\${I}`);return $===!0&&(t.unescape===!0?ae=ae.replace(/\\\\/g,\"\"):ae=ae.replace(/\\\\+/g,I=>I.length%2==0?\"\\\\\\\\\":I?\"\\\\\":\"\")),ae===r&&t.contains===!0?(_.output=r,_):(_.output=Me.wrapOutput(ae,_,e),_)}for(;!he();){if(C=Ee(),C===\"\\0\")continue;if(C===\"\\\\\"){let I=V();if(I===\"/\"&&t.bash!==!0||I===\".\"||I===\";\")continue;if(!I){C+=\"\\\\\",W({type:\"text\",value:C});continue}let H=/^\\\\+/.exec(Ie()),ce=0;if(H&&H[0].length>2&&(ce=H[0].length,_.index+=ce,ce%2!=0&&(C+=\"\\\\\")),t.unescape===!0?C=Ee():C+=Ee(),_.brackets===0){W({type:\"text\",value:C});continue}}if(_.brackets>0&&(C!==\"]\"||A.value===\"[\"||A.value===\"[^\")){if(t.posix!==!1&&C===\":\"){let I=A.value.slice(1);if(I.includes(\"[\")&&(A.posix=!0,I.includes(\":\"))){let H=A.value.lastIndexOf(\"[\"),ce=A.value.slice(0,H),Ce=A.value.slice(H+2),ye=r2[Ce];if(ye){A.value=ce+ye,_.backtrack=!0,Ee(),!s.output&&a.indexOf(A)===1&&(s.output=b);continue}}}(C===\"[\"&&V()!==\":\"||C===\"-\"&&V()===\"]\")&&(C=`\\\\${C}`),C===\"]\"&&(A.value===\"[\"||A.value===\"[^\")&&(C=`\\\\${C}`),t.posix===!0&&C===\"!\"&&A.value===\"[\"&&(C=\"^\"),A.value+=C,ji({value:C});continue}if(_.quotes===1&&C!=='\"'){C=Me.escapeRegex(C),A.value+=C,ji({value:C});continue}if(C==='\"'){_.quotes=_.quotes===1?0:1,t.keepQuotes===!0&&W({type:\"text\",value:C});continue}if(C===\"(\"){zi(\"parens\"),W({type:\"paren\",value:C});continue}if(C===\")\"){if(_.parens===0&&t.strictBrackets===!0)throw new SyntaxError(gr(\"opening\",\"(\"));let I=Q[Q.length-1];if(I&&_.parens===I.parens+1){Dv(Q.pop());continue}W({type:\"paren\",value:C,output:_.parens?\")\":\"\\\\)\"}),Ft(\"parens\");continue}if(C===\"[\"){if(t.nobracket===!0||!Ie().includes(\"]\")){if(t.nobracket!==!0&&t.strictBrackets===!0)throw new SyntaxError(gr(\"closing\",\"]\"));C=`\\\\${C}`}else zi(\"brackets\");W({type:\"bracket\",value:C});continue}if(C===\"]\"){if(t.nobracket===!0||A&&A.type===\"bracket\"&&A.value.length===1){W({type:\"text\",value:C,output:`\\\\${C}`});continue}if(_.brackets===0){if(t.strictBrackets===!0)throw new SyntaxError(gr(\"opening\",\"[\"));W({type:\"text\",value:C,output:`\\\\${C}`});continue}Ft(\"brackets\");let I=A.value.slice(1);if(A.posix!==!0&&I[0]===\"^\"&&!I.includes(\"/\")&&(C=`/${C}`),A.value+=C,ji({value:C}),t.literalBrackets===!1||Me.hasRegexChars(I))continue;let H=Me.escapeRegex(A.value);if(_.output=_.output.slice(0,-A.value.length),t.literalBrackets===!0){_.output+=H,A.value=H;continue}A.value=`(${o}${H}|${A.value})`,_.output+=A.value;continue}if(C===\"{\"&&t.nobrace!==!0){zi(\"braces\");let I={type:\"brace\",value:C,output:\"(\",outputIndex:_.output.length,tokensIndex:_.tokens.length};U.push(I),W(I);continue}if(C===\"}\"){let I=U[U.length-1];if(t.nobrace===!0||!I){W({type:\"text\",value:C,output:C});continue}let H=\")\";if(I.dots===!0){let ce=a.slice(),Ce=[];for(let ye=ce.length-1;ye>=0&&(a.pop(),ce[ye].type!==\"brace\");ye--)ce[ye].type!==\"dots\"&&Ce.unshift(ce[ye].value);H=s2(Ce,t),_.backtrack=!0}if(I.comma!==!0&&I.dots!==!0){let ce=_.output.slice(0,I.outputIndex),Ce=_.tokens.slice(I.tokensIndex);I.value=I.output=\"\\\\{\",C=H=\"\\\\}\",_.output=ce;for(let ye of Ce)_.output+=ye.output||ye.value}W({type:\"brace\",value:C,output:H}),Ft(\"braces\"),U.pop();continue}if(C===\"|\"){Q.length>0&&Q[Q.length-1].conditions++,W({type:\"text\",value:C});continue}if(C===\",\"){let I=C,H=U[U.length-1];H&&le[le.length-1]===\"braces\"&&(H.comma=!0,I=\"|\"),W({type:\"comma\",value:C,output:I});continue}if(C===\"/\"){if(A.type===\"dot\"&&_.index===_.start+1){_.start=_.index+1,_.consumed=\"\",_.output=\"\",a.pop(),A=s;continue}W({type:\"slash\",value:C,output:h});continue}if(C===\".\"){if(_.braces>0&&A.type===\"dot\"){A.value===\".\"&&(A.output=d);let I=U[U.length-1];A.type=\"dots\",A.output+=C,A.value+=C,I.dots=!0;continue}if(_.braces+_.parens===0&&A.type!==\"bos\"&&A.type!==\"slash\"){W({type:\"text\",value:C,output:d});continue}W({type:\"dot\",value:C,output:d});continue}if(C===\"?\"){if(!(A&&A.value===\"(\")&&t.noextglob!==!0&&V()===\"(\"&&V(2)!==\"?\"){Ui(\"qmark\",C);continue}if(A&&A.type===\"paren\"){let H=V(),ce=C;if(H===\"<\"&&!Me.supportsLookbehinds())throw new Error(\"Node.js v10 or higher is required for regex lookbehinds\");(A.value===\"(\"&&!/[!=<:]/.test(H)||H===\"<\"&&!/<([!=]|\\w+>)/.test(Ie()))&&(ce=`\\\\${C}`),W({type:\"text\",value:C,output:ce});continue}if(t.dot!==!0&&(A.type===\"slash\"||A.type===\"bos\")){W({type:\"qmark\",value:C,output:E});continue}W({type:\"qmark\",value:C,output:S});continue}if(C===\"!\"){if(t.noextglob!==!0&&V()===\"(\"&&(V(2)!==\"?\"||!/[!=<:]/.test(V(3)))){Ui(\"negate\",C);continue}if(t.nonegate!==!0&&_.index===0){Iv();continue}}if(C===\"+\"){if(t.noextglob!==!0&&V()===\"(\"&&V(2)!==\"?\"){Ui(\"plus\",C);continue}if(A&&A.value===\"(\"||t.regex===!1){W({type:\"plus\",value:C,output:p});continue}if(A&&(A.type===\"bracket\"||A.type===\"paren\"||A.type===\"brace\")||_.parens>0){W({type:\"plus\",value:C});continue}W({type:\"plus\",value:p});continue}if(C===\"@\"){if(t.noextglob!==!0&&V()===\"(\"&&V(2)!==\"?\"){W({type:\"at\",extglob:!0,value:C,output:\"\"});continue}W({type:\"text\",value:C});continue}if(C!==\"*\"){(C===\"$\"||C===\"^\")&&(C=`\\\\${C}`);let I=i2.exec(Ie());I&&(C+=I[0],_.index+=I[0].length),W({type:\"text\",value:C});continue}if(A&&(A.type===\"globstar\"||A.star===!0)){A.type=\"star\",A.star=!0,A.value+=C,A.output=Y,_.backtrack=!0,_.globstar=!0,De(C);continue}let $=Ie();if(t.noextglob!==!0&&/^\\([^?]/.test($)){Ui(\"star\",C);continue}if(A.type===\"star\"){if(t.noglobstar===!0){De(C);continue}let I=A.prev,H=I.prev,ce=I.type===\"slash\"||I.type===\"bos\",Ce=H&&(H.type===\"star\"||H.type===\"globstar\");if(t.bash===!0&&(!ce||$[0]&&$[0]!==\"/\")){W({type:\"star\",value:C,output:\"\"});continue}let ye=_.braces>0&&(I.type===\"comma\"||I.type===\"brace\"),Bs=Q.length&&(I.type===\"pipe\"||I.type===\"paren\");if(!ce&&I.type!==\"paren\"&&!ye&&!Bs){W({type:\"star\",value:C,output:\"\"});continue}for(;$.slice(0,3)===\"/**\";){let Vi=r[_.index+4];if(Vi&&Vi!==\"/\")break;$=$.slice(3),De(\"/**\",3)}if(I.type===\"bos\"&&he()){A.type=\"globstar\",A.value+=C,A.output=N(t),_.output=A.output,_.globstar=!0,De(C);continue}if(I.type===\"slash\"&&I.prev.type!==\"bos\"&&!Ce&&he()){_.output=_.output.slice(0,-(I.output+A.output).length),I.output=`(?:${I.output}`,A.type=\"globstar\",A.output=N(t)+(t.strictSlashes?\")\":\"|$)\"),A.value+=C,_.globstar=!0,_.output+=I.output+A.output,De(C);continue}if(I.type===\"slash\"&&I.prev.type!==\"bos\"&&$[0]===\"/\"){let Vi=$[1]!==void 0?\"|$\":\"\";_.output=_.output.slice(0,-(I.output+A.output).length),I.output=`(?:${I.output}`,A.type=\"globstar\",A.output=`${N(t)}${h}|${h}${Vi})`,A.value+=C,_.output+=I.output+A.output,_.globstar=!0,De(C+Ee()),W({type:\"slash\",value:\"/\",output:\"\"});continue}if(I.type===\"bos\"&&$[0]===\"/\"){A.type=\"globstar\",A.value+=C,A.output=`(?:^|${h}|${N(t)}${h})`,_.output=A.output,_.globstar=!0,De(C+Ee()),W({type:\"slash\",value:\"/\",output:\"\"});continue}_.output=_.output.slice(0,-A.output.length),A.type=\"globstar\",A.output=N(t),A.value+=C,_.output+=A.output,_.globstar=!0,De(C);continue}let ae={type:\"star\",value:C,output:Y};if(t.bash===!0){ae.output=\".*?\",(A.type===\"bos\"||A.type===\"slash\")&&(ae.output=R+ae.output),W(ae);continue}if(A&&(A.type===\"bracket\"||A.type===\"paren\")&&t.regex===!0){ae.output=C,W(ae);continue}(_.index===_.start||A.type===\"slash\"||A.type===\"dot\")&&(A.type===\"dot\"?(_.output+=w,A.output+=w):t.dot===!0?(_.output+=k,A.output+=k):(_.output+=R,A.output+=R),V()!==\"*\"&&(_.output+=b,A.output+=b)),W(ae)}for(;_.brackets>0;){if(t.strictBrackets===!0)throw new SyntaxError(gr(\"closing\",\"]\"));_.output=Me.escapeLast(_.output,\"[\"),Ft(\"brackets\")}for(;_.parens>0;){if(t.strictBrackets===!0)throw new SyntaxError(gr(\"closing\",\")\"));_.output=Me.escapeLast(_.output,\"(\"),Ft(\"parens\")}for(;_.braces>0;){if(t.strictBrackets===!0)throw new SyntaxError(gr(\"closing\",\"}\"));_.output=Me.escapeLast(_.output,\"{\"),Ft(\"braces\")}if(t.strictSlashes!==!0&&(A.type===\"star\"||A.type===\"bracket\")&&W({type:\"maybe_slash\",value:\"\",output:`${h}?`}),_.backtrack===!0){_.output=\"\";for(let $ of _.tokens)_.output+=$.output!=null?$.output:$.value,$.suffix&&(_.output+=$.suffix)}return _};xl.fastpaths=(r,e)=>{let t={...e},i=typeof t.maxLength==\"number\"?Math.min(ws,t.maxLength):ws,n=r.length;if(n>i)throw new SyntaxError(`Input length: ${n}, exceeds maximum allowed length: ${i}`);r=tg[r]||r;let s=Me.isWindows(e),{DOT_LITERAL:a,SLASH_LITERAL:o,ONE_CHAR:l,DOTS_SLASH:c,NO_DOT:f,NO_DOTS:d,NO_DOTS_SLASH:p,STAR:h,START_ANCHOR:b}=bs.globChars(s),v=t.dot?d:f,y=t.dot?p:f,w=t.capture?\"\":\"?:\",k={negated:!1,prefix:\"\"},S=t.bash===!0?\".*?\":h;t.capture&&(S=`(${S})`);let E=R=>R.noglobstar===!0?S:`(${w}(?:(?!${b}${R.dot?c:a}).)*?)`,T=R=>{switch(R){case\"*\":return`${v}${l}${S}`;case\".*\":return`${a}${l}${S}`;case\"*.*\":return`${v}${S}${a}${l}${S}`;case\"*/*\":return`${v}${S}${o}${l}${y}${S}`;case\"**\":return v+E(t);case\"**/*\":return`(?:${v}${E(t)}${o})?${y}${l}${S}`;case\"**/*.*\":return`(?:${v}${E(t)}${o})?${y}${S}${a}${l}${S}`;case\"**/.*\":return`(?:${v}${E(t)}${o})?${a}${l}${S}`;default:{let F=/^(.*?)\\.(\\w+)$/.exec(R);if(!F)return;let Y=T(F[1]);return Y?Y+a+F[2]:void 0}}},B=Me.removePrefix(r,k),N=T(B);return N&&t.strictSlashes!==!0&&(N+=`${o}?`),N};rg.exports=xl});var sg=x((c6,ng)=>{u();\"use strict\";var a2=(et(),Ur),o2=eg(),kl=ig(),Sl=qi(),l2=Di(),u2=r=>r&&typeof r==\"object\"&&!Array.isArray(r),de=(r,e,t=!1)=>{if(Array.isArray(r)){let f=r.map(p=>de(p,e,t));return p=>{for(let h of f){let b=h(p);if(b)return b}return!1}}let i=u2(r)&&r.tokens&&r.input;if(r===\"\"||typeof r!=\"string\"&&!i)throw new TypeError(\"Expected pattern to be a non-empty string\");let n=e||{},s=Sl.isWindows(e),a=i?de.compileRe(r,e):de.makeRe(r,e,!1,!0),o=a.state;delete a.state;let l=()=>!1;if(n.ignore){let f={...e,ignore:null,onMatch:null,onResult:null};l=de(n.ignore,f,t)}let c=(f,d=!1)=>{let{isMatch:p,match:h,output:b}=de.test(f,a,e,{glob:r,posix:s}),v={glob:r,state:o,regex:a,posix:s,input:f,output:b,match:h,isMatch:p};return typeof n.onResult==\"function\"&&n.onResult(v),p===!1?(v.isMatch=!1,d?v:!1):l(f)?(typeof n.onIgnore==\"function\"&&n.onIgnore(v),v.isMatch=!1,d?v:!1):(typeof n.onMatch==\"function\"&&n.onMatch(v),d?v:!0)};return t&&(c.state=o),c};de.test=(r,e,t,{glob:i,posix:n}={})=>{if(typeof r!=\"string\")throw new TypeError(\"Expected input to be a string\");if(r===\"\")return{isMatch:!1,output:\"\"};let s=t||{},a=s.format||(n?Sl.toPosixSlashes:null),o=r===i,l=o&&a?a(r):r;return o===!1&&(l=a?a(r):r,o=l===i),(o===!1||s.capture===!0)&&(s.matchBase===!0||s.basename===!0?o=de.matchBase(r,e,t,n):o=e.exec(l)),{isMatch:Boolean(o),match:o,output:l}};de.matchBase=(r,e,t,i=Sl.isWindows(t))=>(e instanceof RegExp?e:de.makeRe(e,t)).test(a2.basename(r));de.isMatch=(r,e,t)=>de(e,t)(r);de.parse=(r,e)=>Array.isArray(r)?r.map(t=>de.parse(t,e)):kl(r,{...e,fastpaths:!1});de.scan=(r,e)=>o2(r,e);de.compileRe=(r,e,t=!1,i=!1)=>{if(t===!0)return r.output;let n=e||{},s=n.contains?\"\":\"^\",a=n.contains?\"\":\"$\",o=`${s}(?:${r.output})${a}`;r&&r.negated===!0&&(o=`^(?!${o}).*$`);let l=de.toRegex(o,e);return i===!0&&(l.state=r),l};de.makeRe=(r,e={},t=!1,i=!1)=>{if(!r||typeof r!=\"string\")throw new TypeError(\"Expected a non-empty string\");let n={negated:!1,fastpaths:!0};return e.fastpaths!==!1&&(r[0]===\".\"||r[0]===\"*\")&&(n.output=kl.fastpaths(r,e)),n.output||(n=kl(r,e)),de.compileRe(n,e,t,i)};de.toRegex=(r,e)=>{try{let t=e||{};return new RegExp(r,t.flags||(t.nocase?\"i\":\"\"))}catch(t){if(e&&e.debug===!0)throw t;return/$^/}};de.constants=l2;ng.exports=de});var og=x((p6,ag)=>{u();\"use strict\";ag.exports=sg()});var dg=x((d6,pg)=>{u();\"use strict\";var lg=(Fn(),Bn),ug=jm(),ot=og(),Al=qi(),fg=r=>r===\"\"||r===\"./\",cg=r=>{let e=r.indexOf(\"{\");return e>-1&&r.indexOf(\"}\",e)>-1},oe=(r,e,t)=>{e=[].concat(e),r=[].concat(r);let i=new Set,n=new Set,s=new Set,a=0,o=f=>{s.add(f.output),t&&t.onResult&&t.onResult(f)};for(let f=0;f!i.has(f));if(t&&c.length===0){if(t.failglob===!0)throw new Error(`No matches found for \"${e.join(\", \")}\"`);if(t.nonull===!0||t.nullglob===!0)return t.unescape?e.map(f=>f.replace(/\\\\/g,\"\")):e}return c};oe.match=oe;oe.matcher=(r,e)=>ot(r,e);oe.isMatch=(r,e,t)=>ot(e,t)(r);oe.any=oe.isMatch;oe.not=(r,e,t={})=>{e=[].concat(e).map(String);let i=new Set,n=[],s=o=>{t.onResult&&t.onResult(o),n.push(o.output)},a=new Set(oe(r,e,{...t,onResult:s}));for(let o of n)a.has(o)||i.add(o);return[...i]};oe.contains=(r,e,t)=>{if(typeof r!=\"string\")throw new TypeError(`Expected a string: \"${lg.inspect(r)}\"`);if(Array.isArray(e))return e.some(i=>oe.contains(r,i,t));if(typeof e==\"string\"){if(fg(r)||fg(e))return!1;if(r.includes(e)||r.startsWith(\"./\")&&r.slice(2).includes(e))return!0}return oe.isMatch(r,e,{...t,contains:!0})};oe.matchKeys=(r,e,t)=>{if(!Al.isObject(r))throw new TypeError(\"Expected the first argument to be an object\");let i=oe(Object.keys(r),e,t),n={};for(let s of i)n[s]=r[s];return n};oe.some=(r,e,t)=>{let i=[].concat(r);for(let n of[].concat(e)){let s=ot(String(n),t);if(i.some(a=>s(a)))return!0}return!1};oe.every=(r,e,t)=>{let i=[].concat(r);for(let n of[].concat(e)){let s=ot(String(n),t);if(!i.every(a=>s(a)))return!1}return!0};oe.all=(r,e,t)=>{if(typeof r!=\"string\")throw new TypeError(`Expected a string: \"${lg.inspect(r)}\"`);return[].concat(e).every(i=>ot(i,t)(r))};oe.capture=(r,e,t)=>{let i=Al.isWindows(t),s=ot.makeRe(String(r),{...t,capture:!0}).exec(i?Al.toPosixSlashes(e):e);if(s)return s.slice(1).map(a=>a===void 0?\"\":a)};oe.makeRe=(...r)=>ot.makeRe(...r);oe.scan=(...r)=>ot.scan(...r);oe.parse=(r,e)=>{let t=[];for(let i of[].concat(r||[]))for(let n of ug(String(i),e))t.push(ot.parse(n,e));return t};oe.braces=(r,e)=>{if(typeof r!=\"string\")throw new TypeError(\"Expected a string\");return e&&e.nobrace===!0||!cg(r)?[r]:ug(r,e)};oe.braceExpand=(r,e)=>{if(typeof r!=\"string\")throw new TypeError(\"Expected a string\");return oe.braces(r,{...e,expand:!0})};oe.hasBraces=cg;pg.exports=oe});function mg(r,e){let t=e.content.files;t=t.filter(o=>typeof o==\"string\"),t=t.map(ll);let i=ps.generateTasks(t),n=[],s=[];for(let o of i)n.push(...o.positive.map(l=>gg(l,!1))),s.push(...o.negative.map(l=>gg(l,!0)));let a=[...n,...s];return a=c2(r,a),a=a.flatMap(p2),a=a.map(f2),a}function gg(r,e){let t={original:r,base:r,ignore:e,pattern:r,glob:null};return Zh(r)&&Object.assign(t,nm(r)),t}function f2(r){let e=ll(r.base);return e=ps.escapePath(e),r.pattern=r.glob?`${e}/${r.glob}`:e,r.pattern=r.ignore?`!${r.pattern}`:r.pattern,r}function c2(r,e){let t=[];return r.userConfigPath&&r.tailwindConfig.content.relative&&(t=[me.dirname(r.userConfigPath)]),e.map(i=>(i.base=me.resolve(...t,i.base),i))}function p2(r){let e=[r];try{let t=be.realpathSync(r.base);t!==r.base&&e.push({...r,base:t})}catch{}return e}function yg(r,e,t){let i=r.tailwindConfig.content.files.filter(a=>typeof a.raw==\"string\").map(({raw:a,extension:o=\"html\"})=>({content:a,extension:o})),[n,s]=h2(e,t);for(let a of n){let o=me.extname(a).slice(1);i.push({file:a,extension:o})}return[i,s]}function d2(r){if(!r.some(s=>s.includes(\"**\")&&!wg.test(s)))return()=>{};let t=[],i=[];for(let s of r){let a=hg.default.matcher(s);wg.test(s)&&i.push(a),t.push(a)}let n=!1;return s=>{if(n||i.some(f=>f(s)))return;let a=t.findIndex(f=>f(s));if(a===-1)return;let o=r[a],l=me.relative(m.cwd(),o);l[0]!==\".\"&&(l=`./${l}`);let c=bg.find(f=>s.includes(f));c&&(n=!0,G.warn(\"broad-content-glob-pattern\",[`Your \\`content\\` configuration includes a pattern which looks like it's accidentally matching all of \\`${c}\\` and can cause serious performance issues.`,`Pattern: \\`${l}\\``,\"See our documentation for recommendations:\",\"https://tailwindcss.com/docs/content-configuration#pattern-recommendations\"]))}}function h2(r,e){let t=r.map(o=>o.pattern),i=new Map,n=d2(t),s=new Set;Ze.DEBUG&&console.time(\"Finding changed files\");let a=ps.sync(t,{absolute:!0});for(let o of a){n(o);let l=e.get(o)||-1/0,c=be.statSync(o).mtimeMs;c>l&&(s.add(o),i.set(o,c))}return Ze.DEBUG&&console.timeEnd(\"Finding changed files\"),[s,i]}var hg,bg,wg,vg=P(()=>{u();ft();et();Jh();em();tm();sm();It();Be();hg=pe(dg());bg=[\"node_modules\"],wg=new RegExp(`(${bg.map(r=>String.raw`\\b${r}\\b`).join(\"|\")})`)});function xg(){}var kg=P(()=>{u()});function b2(r,e){for(let t of e){let i=`${r}${t}`;if(be.existsSync(i)&&be.statSync(i).isFile())return i}for(let t of e){let i=`${r}/index${t}`;if(be.existsSync(i))return i}return null}function*Sg(r,e,t,i=me.extname(r)){let n=b2(me.resolve(e,r),m2.includes(i)?g2:y2);if(n===null||t.has(n))return;t.add(n),yield n,e=me.dirname(n),i=me.extname(n);let s=be.readFileSync(n,\"utf-8\");for(let a of[...s.matchAll(/import[\\s\\S]*?['\"](.{3,}?)['\"]/gi),...s.matchAll(/import[\\s\\S]*from[\\s\\S]*?['\"](.{3,}?)['\"]/gi),...s.matchAll(/require\\(['\"`](.+)['\"`]\\)/gi)])!a[1].startsWith(\".\")||(yield*Sg(a[1],e,t,i))}function Cl(r){return r===null?new Set:new Set(Sg(r,me.dirname(r),new Set))}var m2,g2,y2,Ag=P(()=>{u();ft();et();m2=[\".js\",\".cjs\",\".mjs\"],g2=[\"\",\".js\",\".cjs\",\".mjs\",\".ts\",\".cts\",\".mts\",\".jsx\",\".tsx\"],y2=[\"\",\".ts\",\".cts\",\".mts\",\".tsx\",\".js\",\".cjs\",\".mjs\",\".jsx\"]});function w2(r,e){if(_l.has(r))return _l.get(r);let t=mg(r,e);return _l.set(r,t).get(r)}function v2(r){let e=aa(r);if(e!==null){let[i,n,s,a]=_g.get(e)||[],o=Cl(e),l=!1,c=new Map;for(let p of o){let h=be.statSync(p).mtimeMs;c.set(p,h),(!a||!a.has(p)||h>a.get(p))&&(l=!0)}if(!l)return[i,e,n,s];for(let p of o)delete hf.cache[p];let f=ol(zr(xg(e))),d=Wi(f);return _g.set(e,[f,d,o,c]),[f,e,d,o]}let t=zr(r?.config??r??{});return t=ol(t),[t,null,Wi(t),[]]}function El(r){return({tailwindDirectives:e,registerDependency:t})=>(i,n)=>{let[s,a,o,l]=v2(r),c=new Set(l);if(e.size>0){c.add(n.opts.from);for(let b of n.messages)b.type===\"dependency\"&&c.add(b.file)}let[f,,d]=Vh(i,n,s,a,o,c),p=cs(f),h=w2(f,s);if(e.size>0){for(let y of h)for(let w of nl(y))t(w);let[b,v]=yg(f,h,p);for(let y of b)f.changedContent.push(y);for(let[y,w]of v.entries())d.set(y,w)}for(let b of l)t({type:\"dependency\",file:b});for(let[b,v]of d.entries())p.set(b,v);return f}}var Cg,_g,_l,Eg=P(()=>{u();ft();Cg=pe(Fs());wf();sa();oc();Oi();Hh();Xh();vg();kg();Ag();_g=new Cg.default({maxSize:100}),_l=new WeakMap});function Ol(r){let e=new Set,t=new Set,i=new Set;if(r.walkAtRules(n=>{n.name===\"apply\"&&i.add(n),n.name===\"import\"&&(n.params==='\"tailwindcss/base\"'||n.params===\"'tailwindcss/base'\"?(n.name=\"tailwind\",n.params=\"base\"):n.params==='\"tailwindcss/components\"'||n.params===\"'tailwindcss/components'\"?(n.name=\"tailwind\",n.params=\"components\"):n.params==='\"tailwindcss/utilities\"'||n.params===\"'tailwindcss/utilities'\"?(n.name=\"tailwind\",n.params=\"utilities\"):(n.params==='\"tailwindcss/screens\"'||n.params===\"'tailwindcss/screens'\"||n.params==='\"tailwindcss/variants\"'||n.params===\"'tailwindcss/variants'\")&&(n.name=\"tailwind\",n.params=\"variants\")),n.name===\"tailwind\"&&(n.params===\"screens\"&&(n.params=\"variants\"),e.add(n.params)),[\"layer\",\"responsive\",\"variants\"].includes(n.name)&&([\"responsive\",\"variants\"].includes(n.name)&&G.warn(`${n.name}-at-rule-deprecated`,[`The \\`@${n.name}\\` directive has been deprecated in Tailwind CSS v3.0.`,\"Use `@layer utilities` or `@layer components` instead.\",\"https://tailwindcss.com/docs/upgrade-guide#replace-variants-with-layer\"]),t.add(n))}),!e.has(\"base\")||!e.has(\"components\")||!e.has(\"utilities\")){for(let n of t)if(n.name===\"layer\"&&[\"base\",\"components\",\"utilities\"].includes(n.params)){if(!e.has(n.params))throw n.error(`\\`@layer ${n.params}\\` is used but no matching \\`@tailwind ${n.params}\\` directive is present.`)}else if(n.name===\"responsive\"){if(!e.has(\"utilities\"))throw n.error(\"`@responsive` is used but `@tailwind utilities` is missing.\")}else if(n.name===\"variants\"&&!e.has(\"utilities\"))throw n.error(\"`@variants` is used but `@tailwind utilities` is missing.\")}return{tailwindDirectives:e,applyDirectives:i}}var Og=P(()=>{u();Be()});function Qt(r,e=void 0,t=void 0){return r.map(i=>{let n=i.clone();return t!==void 0&&(n.raws.tailwind={...n.raws.tailwind,...t}),e!==void 0&&Tg(n,s=>{if(s.raws.tailwind?.preserveSource===!0&&s.source)return!1;s.source=e}),n})}function Tg(r,e){e(r)!==!1&&r.each?.(t=>Tg(t,e))}var Rg=P(()=>{u()});function Tl(r){return r=Array.isArray(r)?r:[r],r=r.map(e=>e instanceof RegExp?e.source:e),r.join(\"\")}function Ne(r){return new RegExp(Tl(r),\"g\")}function qt(r){return`(?:${r.map(Tl).join(\"|\")})`}function Rl(r){return`(?:${Tl(r)})?`}function Ig(r){return r&&x2.test(r)?r.replace(Pg,\"\\\\$&\"):r||\"\"}var Pg,x2,Dg=P(()=>{u();Pg=/[\\\\^$.*+?()[\\]{}|]/g,x2=RegExp(Pg.source)});function qg(r){let e=Array.from(k2(r));return t=>{let i=[];for(let n of e)for(let s of t.match(n)??[])i.push(C2(s));for(let n of i.slice()){let s=ve(n,\".\");for(let a=0;a=s.length-1){i.push(o);continue}let l=Number(s[a+1]);isNaN(l)?i.push(o):a++}}return i}}function*k2(r){let e=r.tailwindConfig.separator,t=r.tailwindConfig.prefix!==\"\"?Rl(Ne([/-?/,Ig(r.tailwindConfig.prefix)])):\"\",i=qt([/\\[[^\\s:'\"`]+:[^\\s\\[\\]]+\\]/,/\\[[^\\s:'\"`\\]]+:[^\\s]+?\\[[^\\s]+\\][^\\s]+?\\]/,Ne([qt([/-?(?:\\w+)/,/@(?:\\w+)/]),Rl(qt([Ne([qt([/-(?:\\w+-)*\\['[^\\s]+'\\]/,/-(?:\\w+-)*\\[\"[^\\s]+\"\\]/,/-(?:\\w+-)*\\[`[^\\s]+`\\]/,/-(?:\\w+-)*\\[(?:[^\\s\\[\\]]+\\[[^\\s\\[\\]]+\\])*[^\\s:\\[\\]]+\\]/]),/(?![{([]])/,/(?:\\/[^\\s'\"`\\\\><$]*)?/]),Ne([qt([/-(?:\\w+-)*\\['[^\\s]+'\\]/,/-(?:\\w+-)*\\[\"[^\\s]+\"\\]/,/-(?:\\w+-)*\\[`[^\\s]+`\\]/,/-(?:\\w+-)*\\[(?:[^\\s\\[\\]]+\\[[^\\s\\[\\]]+\\])*[^\\s\\[\\]]+\\]/]),/(?![{([]])/,/(?:\\/[^\\s'\"`\\\\$]*)?/]),/[-\\/][^\\s'\"`\\\\$={><]*/]))])]),n=[qt([Ne([/@\\[[^\\s\"'`]+\\](\\/[^\\s\"'`]+)?/,e]),Ne([/([^\\s\"'`\\[\\\\]+-)?\\[[^\\s\"'`]+\\]\\/[\\w_-]+/,e]),Ne([/([^\\s\"'`\\[\\\\]+-)?\\[[^\\s\"'`]+\\]/,e]),Ne([/[^\\s\"'`\\[\\\\]+/,e])]),qt([Ne([/([^\\s\"'`\\[\\\\]+-)?\\[[^\\s`]+\\]\\/[\\w_-]+/,e]),Ne([/([^\\s\"'`\\[\\\\]+-)?\\[[^\\s`]+\\]/,e]),Ne([/[^\\s`\\[\\\\]+/,e])])];for(let s of n)yield Ne([\"((?=((\",s,\")+))\\\\2)?\",/!?/,t,i]);yield/[^<>\"'`\\s.(){}[\\]#=%$][^<>\"'`\\s(){}[\\]#=%$]*[^<>\"'`\\s.(){}[\\]#=%:$]/g}function C2(r){if(!r.includes(\"-[\"))return r;let e=0,t=[],i=r.matchAll(S2);i=Array.from(i).flatMap(n=>{let[,...s]=n;return s.map((a,o)=>Object.assign([],n,{index:n.index+o,0:a}))});for(let n of i){let s=n[0],a=t[t.length-1];if(s===a?t.pop():(s===\"'\"||s==='\"'||s===\"`\")&&t.push(s),!a){if(s===\"[\"){e++;continue}else if(s===\"]\"){e--;continue}if(e<0)return r.substring(0,n.index-1);if(e===0&&!A2.test(s))return r.substring(0,n.index)}}return r}var S2,A2,$g=P(()=>{u();Dg();zt();S2=/([\\[\\]'\"`])([^\\[\\]'\"`])?/g,A2=/[^\"'`\\s<>\\]]+/});function _2(r,e){let t=r.tailwindConfig.content.extract;return t[e]||t.DEFAULT||Mg[e]||Mg.DEFAULT(r)}function E2(r,e){let t=r.content.transform;return t[e]||t.DEFAULT||Ng[e]||Ng.DEFAULT}function O2(r,e,t,i){Li.has(e)||Li.set(e,new Lg.default({maxSize:25e3}));for(let n of r.split(`\n`))if(n=n.trim(),!i.has(n))if(i.add(n),Li.get(e).has(n))for(let s of Li.get(e).get(n))t.add(s);else{let s=e(n).filter(o=>o!==\"!*\"),a=new Set(s);for(let o of a)t.add(o);Li.get(e).set(n,a)}}function T2(r,e){let t=e.offsets.sort(r),i={base:new Set,defaults:new Set,components:new Set,utilities:new Set,variants:new Set};for(let[n,s]of t)i[n.layer].add(s);return i}function Pl(r){return async e=>{let t={base:null,components:null,utilities:null,variants:null};if(e.walkAtRules(y=>{y.name===\"tailwind\"&&Object.keys(t).includes(y.params)&&(t[y.params]=y)}),Object.values(t).every(y=>y===null))return e;let i=new Set([...r.candidates??[],gt]),n=new Set;bt.DEBUG&&console.time(\"Reading changed files\");let s=[];for(let y of r.changedContent){let w=E2(r.tailwindConfig,y.extension),k=_2(r,y.extension);s.push([y,{transformer:w,extractor:k}])}let a=500;for(let y=0;y{S=k?await be.promises.readFile(k,\"utf8\"):S,O2(E(S),T,i,n)}))}bt.DEBUG&&console.timeEnd(\"Reading changed files\");let o=r.classCache.size;bt.DEBUG&&console.time(\"Generate rules\"),bt.DEBUG&&console.time(\"Sorting candidates\");let l=new Set([...i].sort((y,w)=>y===w?0:y{let w=y.raws.tailwind?.parentLayer;return w===\"components\"?t.components!==null:w===\"utilities\"?t.utilities!==null:!0});t.variants?(t.variants.before(Qt(b,t.variants.source,{layer:\"variants\"})),t.variants.remove()):b.length>0&&e.append(Qt(b,e.source,{layer:\"variants\"})),e.source.end=e.source.end??e.source.start;let v=b.some(y=>y.raws.tailwind?.parentLayer===\"utilities\");t.utilities&&p.size===0&&!v&&G.warn(\"content-problems\",[\"No utility classes were detected in your source files. If this is unexpected, double-check the `content` option in your Tailwind CSS configuration.\",\"https://tailwindcss.com/docs/content-configuration\"]),bt.DEBUG&&(console.log(\"Potential classes: \",i.size),console.log(\"Active contexts: \",es.size)),r.changedContent=[],e.walkAtRules(\"layer\",y=>{Object.keys(t).includes(y.params)&&y.remove()})}}var Lg,bt,Mg,Ng,Li,Bg=P(()=>{u();ft();Lg=pe(Fs());It();os();Be();Rg();$g();bt=Ze,Mg={DEFAULT:qg},Ng={DEFAULT:r=>r,svelte:r=>r.replace(/(?:^|\\s)class:/g,\" \")};Li=new WeakMap});function xs(r){let e=new Map;ee.root({nodes:[r.clone()]}).walkRules(s=>{(0,vs.default)(a=>{a.walkClasses(o=>{let l=o.parent.toString(),c=e.get(l);c||e.set(l,c=new Set),c.add(o.value)})}).processSync(s.selector)});let i=Array.from(e.values(),s=>Array.from(s)),n=i.flat();return Object.assign(n,{groups:i})}function Il(r){return R2.astSync(r)}function Fg(r,e){let t=new Set;for(let i of r)t.add(i.split(e).pop());return Array.from(t)}function jg(r,e){let t=r.tailwindConfig.prefix;return typeof t==\"function\"?t(e):t+e}function*zg(r){for(yield r;r.parent;)yield r.parent,r=r.parent}function P2(r,e={}){let t=r.nodes;r.nodes=[];let i=r.clone(e);return r.nodes=t,i}function I2(r){for(let e of zg(r))if(r!==e){if(e.type===\"root\")break;r=P2(e,{nodes:[r]})}return r}function D2(r,e){let t=new Map;return r.walkRules(i=>{for(let a of zg(i))if(a.raws.tailwind?.layer!==void 0)return;let n=I2(i),s=e.offsets.create(\"user\");for(let a of xs(i)){let o=t.get(a)||[];t.set(a,o),o.push([{layer:\"user\",sort:s,important:!1},n])}}),t}function q2(r,e){for(let t of r){if(e.notClassCache.has(t)||e.applyClassCache.has(t))continue;if(e.classCache.has(t)){e.applyClassCache.set(t,e.classCache.get(t).map(([n,s])=>[n,s.clone()]));continue}let i=Array.from(Yo(t,e));if(i.length===0){e.notClassCache.add(t);continue}e.applyClassCache.set(t,i)}return e.applyClassCache}function $2(r){let e=null;return{get:t=>(e=e||r(),e.get(t)),has:t=>(e=e||r(),e.has(t))}}function L2(r){return{get:e=>r.flatMap(t=>t.get(e)||[]),has:e=>r.some(t=>t.has(e))}}function Ug(r){let e=r.split(/[\\s\\t\\n]+/g);return e[e.length-1]===\"!important\"?[e.slice(0,-1),!0]:[e,!1]}function Vg(r,e,t){let i=new Set,n=[];if(r.walkAtRules(\"apply\",l=>{let[c]=Ug(l.params);for(let f of c)i.add(f);n.push(l)}),n.length===0)return;let s=L2([t,q2(i,e)]);function a(l,c,f){let d=Il(l),p=Il(c),b=Il(`.${Te(f)}`).nodes[0].nodes[0];return d.each(v=>{let y=new Set;p.each(w=>{let k=!1;w=w.clone(),w.walkClasses(S=>{S.value===b.value&&(k||(S.replaceWith(...v.nodes.map(E=>E.clone())),y.add(w),k=!0))})});for(let w of y){let k=[[]];for(let S of w.nodes)S.type===\"combinator\"?(k.push(S),k.push([])):k[k.length-1].push(S);w.nodes=[];for(let S of k)Array.isArray(S)&&S.sort((E,T)=>E.type===\"tag\"&&T.type===\"class\"?-1:E.type===\"class\"&&T.type===\"tag\"?1:E.type===\"class\"&&T.type===\"pseudo\"&&T.value.startsWith(\"::\")?-1:E.type===\"pseudo\"&&E.value.startsWith(\"::\")&&T.type===\"class\"?1:0),w.nodes=w.nodes.concat(S)}v.replaceWith(...y)}),d.toString()}let o=new Map;for(let l of n){let[c]=o.get(l.parent)||[[],l.source];o.set(l.parent,[c,l.source]);let[f,d]=Ug(l.params);if(l.parent.type===\"atrule\"){if(l.parent.name===\"screen\"){let p=l.parent.params;throw l.error(`@apply is not supported within nested at-rules like @screen. We suggest you write this as @apply ${f.map(h=>`${p}:${h}`).join(\" \")} instead.`)}throw l.error(`@apply is not supported within nested at-rules like @${l.parent.name}. You can fix this by un-nesting @${l.parent.name}.`)}for(let p of f){if([jg(e,\"group\"),jg(e,\"peer\")].includes(p))throw l.error(`@apply should not be used with the '${p}' utility`);if(!s.has(p))throw l.error(`The \\`${p}\\` class does not exist. If \\`${p}\\` is a custom class, make sure it is defined within a \\`@layer\\` directive.`);let h=s.get(p);for(let[,b]of h)b.type!==\"atrule\"&&b.walkRules(()=>{throw l.error([`The \\`${p}\\` class cannot be used with \\`@apply\\` because \\`@apply\\` does not currently support nested CSS.`,\"Rewrite the selector without nesting or configure the `tailwindcss/nesting` plugin:\",\"https://tailwindcss.com/docs/using-with-preprocessors#nesting\"].join(`\n`))});c.push([p,d,h])}}for(let[l,[c,f]]of o){let d=[];for(let[h,b,v]of c){let y=[h,...Fg([h],e.tailwindConfig.separator)];for(let[w,k]of v){let S=xs(l),E=xs(k);if(E=E.groups.filter(R=>R.some(F=>y.includes(F))).flat(),E=E.concat(Fg(E,e.tailwindConfig.separator)),S.some(R=>E.includes(R)))throw k.error(`You cannot \\`@apply\\` the \\`${h}\\` utility here because it creates a circular dependency.`);let B=ee.root({nodes:[k.clone()]});B.walk(R=>{R.source=f}),(k.type!==\"atrule\"||k.type===\"atrule\"&&k.name!==\"keyframes\")&&B.walkRules(R=>{if(!xs(R).some(U=>U===h)){R.remove();return}let F=typeof e.tailwindConfig.important==\"string\"?e.tailwindConfig.important:null,_=l.raws.tailwind!==void 0&&F&&l.selector.indexOf(F)===0?l.selector.slice(F.length):l.selector;_===\"\"&&(_=l.selector),R.selector=a(_,R.selector,h),F&&_!==l.selector&&(R.selector=is(R.selector,F)),R.walkDecls(U=>{U.important=w.important||b});let Q=(0,vs.default)().astSync(R.selector);Q.each(U=>pr(U)),R.selector=Q.toString()}),!!B.nodes[0]&&d.push([w.sort,B.nodes[0]])}}let p=e.offsets.sort(d).map(h=>h[1]);l.after(p)}for(let l of n)l.parent.nodes.length>1?l.remove():l.parent.remove();Vg(r,e,t)}function Dl(r){return e=>{let t=$2(()=>D2(e,r));Vg(e,r,t)}}var vs,R2,Hg=P(()=>{u();Ot();vs=pe(it());os();fr();Wo();ts();R2=(0,vs.default)()});var Wg=x((nq,ks)=>{u();(function(){\"use strict\";function r(i,n,s){if(!i)return null;r.caseSensitive||(i=i.toLowerCase());var a=r.threshold===null?null:r.threshold*i.length,o=r.thresholdAbsolute,l;a!==null&&o!==null?l=Math.min(a,o):a!==null?l=a:o!==null?l=o:l=null;var c,f,d,p,h,b=n.length;for(h=0;hs)return s+1;var l=[],c,f,d,p,h;for(c=0;c<=o;c++)l[c]=[c];for(f=0;f<=a;f++)l[0][f]=f;for(c=1;c<=o;c++){for(d=e,p=1,c>s&&(p=c-s),h=o+1,h>s+c&&(h=s+c),f=1;f<=a;f++)fh?l[c][f]=s+1:n.charAt(c-1)===i.charAt(f-1)?l[c][f]=l[c-1][f-1]:l[c][f]=Math.min(l[c-1][f-1]+1,Math.min(l[c][f-1]+1,l[c-1][f]+1)),l[c][f]s)return s+1}return l[o][a]}})()});var Qg=x((sq,Gg)=>{u();var ql=\"(\".charCodeAt(0),$l=\")\".charCodeAt(0),Ss=\"'\".charCodeAt(0),Ll='\"'.charCodeAt(0),Ml=\"\\\\\".charCodeAt(0),yr=\"/\".charCodeAt(0),Nl=\",\".charCodeAt(0),Bl=\":\".charCodeAt(0),As=\"*\".charCodeAt(0),M2=\"u\".charCodeAt(0),N2=\"U\".charCodeAt(0),B2=\"+\".charCodeAt(0),F2=/^[a-f0-9?-]+$/i;Gg.exports=function(r){for(var e=[],t=r,i,n,s,a,o,l,c,f,d=0,p=t.charCodeAt(d),h=t.length,b=[{nodes:e}],v=0,y,w=\"\",k=\"\",S=\"\";d{u();Yg.exports=function r(e,t,i){var n,s,a,o;for(n=0,s=e.length;n{u();function Xg(r,e){var t=r.type,i=r.value,n,s;return e&&(s=e(r))!==void 0?s:t===\"word\"||t===\"space\"?i:t===\"string\"?(n=r.quote||\"\",n+i+(r.unclosed?\"\":n)):t===\"comment\"?\"/*\"+i+(r.unclosed?\"\":\"*/\"):t===\"div\"?(r.before||\"\")+i+(r.after||\"\"):Array.isArray(r.nodes)?(n=Zg(r.nodes,e),t!==\"function\"?n:i+\"(\"+(r.before||\"\")+n+(r.after||\"\")+(r.unclosed?\"\":\")\")):i}function Zg(r,e){var t,i;if(Array.isArray(r)){for(t=\"\",i=r.length-1;~i;i-=1)t=Xg(r[i],e)+t;return t}return Xg(r,e)}Jg.exports=Zg});var ry=x((lq,ty)=>{u();var Cs=\"-\".charCodeAt(0),_s=\"+\".charCodeAt(0),Fl=\".\".charCodeAt(0),j2=\"e\".charCodeAt(0),z2=\"E\".charCodeAt(0);function U2(r){var e=r.charCodeAt(0),t;if(e===_s||e===Cs){if(t=r.charCodeAt(1),t>=48&&t<=57)return!0;var i=r.charCodeAt(2);return t===Fl&&i>=48&&i<=57}return e===Fl?(t=r.charCodeAt(1),t>=48&&t<=57):e>=48&&e<=57}ty.exports=function(r){var e=0,t=r.length,i,n,s;if(t===0||!U2(r))return!1;for(i=r.charCodeAt(e),(i===_s||i===Cs)&&e++;e57));)e+=1;if(i=r.charCodeAt(e),n=r.charCodeAt(e+1),i===Fl&&n>=48&&n<=57)for(e+=2;e57));)e+=1;if(i=r.charCodeAt(e),n=r.charCodeAt(e+1),s=r.charCodeAt(e+2),(i===j2||i===z2)&&(n>=48&&n<=57||(n===_s||n===Cs)&&s>=48&&s<=57))for(e+=n===_s||n===Cs?3:2;e57));)e+=1;return{number:r.slice(0,e),unit:r.slice(e)}}});var ay=x((uq,sy)=>{u();var V2=Qg(),iy=Kg(),ny=ey();function $t(r){return this instanceof $t?(this.nodes=V2(r),this):new $t(r)}$t.prototype.toString=function(){return Array.isArray(this.nodes)?ny(this.nodes):\"\"};$t.prototype.walk=function(r,e){return iy(this.nodes,r,e),this};$t.unit=ry();$t.walk=iy;$t.stringify=ny;sy.exports=$t});function zl(r){return typeof r==\"object\"&&r!==null}function H2(r,e){let t=kt(e);do if(t.pop(),(0,Mi.default)(r,t)!==void 0)break;while(t.length);return t.length?t:void 0}function br(r){return typeof r==\"string\"?r:r.reduce((e,t,i)=>t.includes(\".\")?`${e}[${t}]`:i===0?t:`${e}.${t}`,\"\")}function ly(r){return r.map(e=>`'${e}'`).join(\", \")}function uy(r){return ly(Object.keys(r))}function Ul(r,e,t,i={}){let n=Array.isArray(e)?br(e):e.replace(/^['\"]+|['\"]+$/g,\"\"),s=Array.isArray(e)?e:kt(n),a=(0,Mi.default)(r.theme,s,t);if(a===void 0){let l=`'${n}' does not exist in your theme config.`,c=s.slice(0,-1),f=(0,Mi.default)(r.theme,c);if(zl(f)){let d=Object.keys(f).filter(h=>Ul(r,[...c,h]).isValid),p=(0,oy.default)(s[s.length-1],d);p?l+=` Did you mean '${br([...c,p])}'?`:d.length>0&&(l+=` '${br(c)}' has the following valid keys: ${ly(d)}`)}else{let d=H2(r.theme,n);if(d){let p=(0,Mi.default)(r.theme,d);zl(p)?l+=` '${br(d)}' has the following keys: ${uy(p)}`:l+=` '${br(d)}' is not an object.`}else l+=` Your theme has the following top-level keys: ${uy(r.theme)}`}return{isValid:!1,error:l}}if(!(typeof a==\"string\"||typeof a==\"number\"||typeof a==\"function\"||a instanceof String||a instanceof Number||Array.isArray(a))){let l=`'${n}' was found but does not resolve to a string.`;if(zl(a)){let c=Object.keys(a).filter(f=>Ul(r,[...s,f]).isValid);c.length&&(l+=` Did you mean something like '${br([...s,c[0]])}'?`)}return{isValid:!1,error:l}}let[o]=s;return{isValid:!0,value:mt(o)(a,i)}}function W2(r,e,t){e=e.map(n=>fy(r,n,t));let i=[\"\"];for(let n of e)n.type===\"div\"&&n.value===\",\"?i.push(\"\"):i[i.length-1]+=jl.default.stringify(n);return i}function fy(r,e,t){if(e.type===\"function\"&&t[e.value]!==void 0){let i=W2(r,e.nodes,t);e.type=\"word\",e.value=t[e.value](r,...i)}return e}function G2(r,e,t){return Object.keys(t).some(n=>e.includes(`${n}(`))?(0,jl.default)(e).walk(n=>{fy(r,n,t)}).toString():e}function*Y2(r){r=r.replace(/^['\"]+|['\"]+$/g,\"\");let e=r.match(/^([^\\s]+)(?![^\\[]*\\])(?:\\s*\\/\\s*([^\\/\\s]+))$/),t;yield[r,void 0],e&&(r=e[1],t=e[2],yield[r,t])}function K2(r,e,t){let i=Array.from(Y2(e)).map(([n,s])=>Object.assign(Ul(r,n,t,{opacityValue:s}),{resolvedPath:n,alpha:s}));return i.find(n=>n.isValid)??i[0]}function cy(r){let e=r.tailwindConfig,t={theme:(i,n,...s)=>{let{isValid:a,value:o,error:l,alpha:c}=K2(e,n,s.length?s:void 0);if(!a){let p=i.parent,h=p?.raws.tailwind?.candidate;if(p&&h!==void 0){r.markInvalidUtilityNode(p),p.remove(),G.warn(\"invalid-theme-key-in-class\",[`The utility \\`${h}\\` contains an invalid theme value and was not generated.`]);return}throw i.error(l)}let f=Xt(o),d=f!==void 0&&typeof f==\"function\";return(c!==void 0||d)&&(c===void 0&&(c=1),o=Je(f,c,f)),o},screen:(i,n)=>{n=n.replace(/^['\"]+/g,\"\").replace(/['\"]+$/g,\"\");let a=Rt(e.theme.screens).find(({name:o})=>o===n);if(!a)throw i.error(`The '${n}' screen does not exist in your theme.`);return Tt(a)}};return i=>{i.walk(n=>{let s=Q2[n.type];s!==void 0&&(n[s]=G2(n,n[s],t))})}}var Mi,oy,jl,Q2,py=P(()=>{u();Mi=pe(Ra()),oy=pe(Wg());Ci();jl=pe(ay());Zn();Yn();Yi();Lr();Fr();Be();Q2={atrule:\"params\",decl:\"value\"}});function dy({tailwindConfig:{theme:r}}){return function(e){e.walkAtRules(\"screen\",t=>{let i=t.params,s=Rt(r.screens).find(({name:a})=>a===i);if(!s)throw t.error(`No \\`${i}\\` screen found.`);t.name=\"media\",t.params=Tt(s)})}}var hy=P(()=>{u();Zn();Yn()});function X2(r){let e=r.filter(o=>o.type!==\"pseudo\"||o.nodes.length>0?!0:o.value.startsWith(\"::\")||[\":before\",\":after\",\":first-line\",\":first-letter\"].includes(o.value)).reverse(),t=new Set([\"tag\",\"class\",\"id\",\"attribute\"]),i=e.findIndex(o=>t.has(o.type));if(i===-1)return e.reverse().join(\"\").trim();let n=e[i],s=my[n.type]?my[n.type](n):n;e=e.slice(0,i);let a=e.findIndex(o=>o.type===\"combinator\"&&o.value===\">\");return a!==-1&&(e.splice(0,a),e.unshift(Es.default.universal())),[s,...e.reverse()].join(\"\").trim()}function J2(r){return Vl.has(r)||Vl.set(r,Z2.transformSync(r)),Vl.get(r)}function Hl({tailwindConfig:r}){return e=>{let t=new Map,i=new Set;if(e.walkAtRules(\"defaults\",n=>{if(n.nodes&&n.nodes.length>0){i.add(n);return}let s=n.params;t.has(s)||t.set(s,new Set),t.get(s).add(n.parent),n.remove()}),we(r,\"optimizeUniversalDefaults\"))for(let n of i){let s=new Map,a=t.get(n.params)??[];for(let o of a)for(let l of J2(o.selector)){let c=l.includes(\":-\")||l.includes(\"::-\")||l.includes(\":has\")?l:\"__DEFAULT__\",f=s.get(c)??new Set;s.set(c,f),f.add(l)}if(s.size===0){n.remove();continue}for(let[,o]of s){let l=ee.rule({source:n.source});l.selectors=[...o],l.append(n.nodes.map(c=>c.clone())),n.before(l)}n.remove()}else if(i.size){let n=ee.rule({selectors:[\"*\",\"::before\",\"::after\"]});for(let a of i)n.append(a.nodes),n.parent||a.before(n),n.source||(n.source=a.source),a.remove();let s=n.clone({selectors:[\"::backdrop\"]});n.after(s)}}}var Es,my,Z2,Vl,gy=P(()=>{u();Ot();Es=pe(it());ct();my={id(r){return Es.default.attribute({attribute:\"id\",operator:\"=\",value:r.value,quoteMark:'\"'})}};Z2=(0,Es.default)(r=>r.map(e=>{let t=e.split(i=>i.type===\"combinator\"&&i.value===\" \").pop();return X2(t)})),Vl=new Map});function Wl(){function r(e){let t=null;e.each(i=>{if(!eO.has(i.type)){t=null;return}if(t===null){t=i;return}let n=yy[i.type];i.type===\"atrule\"&&i.name===\"font-face\"?t=i:n.every(s=>(i[s]??\"\").replace(/\\s+/g,\" \")===(t[s]??\"\").replace(/\\s+/g,\" \"))?(i.nodes&&t.append(i.nodes),i.remove()):t=i}),e.each(i=>{i.type===\"atrule\"&&r(i)})}return e=>{r(e)}}var yy,eO,by=P(()=>{u();yy={atrule:[\"name\",\"params\"],rule:[\"selector\"]},eO=new Set(Object.keys(yy))});function Gl(){return r=>{r.walkRules(e=>{let t=new Map,i=new Set([]),n=new Map;e.walkDecls(s=>{if(s.parent===e){if(t.has(s.prop)){if(t.get(s.prop).value===s.value){i.add(t.get(s.prop)),t.set(s.prop,s);return}n.has(s.prop)||n.set(s.prop,new Set),n.get(s.prop).add(t.get(s.prop)),n.get(s.prop).add(s)}t.set(s.prop,s)}});for(let s of i)s.remove();for(let s of n.values()){let a=new Map;for(let o of s){let l=rO(o.value);l!==null&&(a.has(l)||a.set(l,new Set),a.get(l).add(o))}for(let o of a.values()){let l=Array.from(o).slice(0,-1);for(let c of l)c.remove()}}})}}function rO(r){let e=/^-?\\d*.?\\d+([\\w%]+)?$/g.exec(r);return e?e[1]??tO:null}var tO,wy=P(()=>{u();tO=Symbol(\"unitless-number\")});function iO(r){if(!r.walkAtRules)return;let e=new Set;if(r.walkAtRules(\"apply\",t=>{e.add(t.parent)}),e.size!==0)for(let t of e){let i=[],n=[];for(let s of t.nodes)s.type===\"atrule\"&&s.name===\"apply\"?(n.length>0&&(i.push(n),n=[]),i.push([s])):n.push(s);if(n.length>0&&i.push(n),i.length!==1){for(let s of[...i].reverse()){let a=t.clone({nodes:[]});a.append(s),t.after(a)}t.remove()}}}function Os(){return r=>{iO(r)}}var vy=P(()=>{u()});function Ts(r){return async function(e,t){let{tailwindDirectives:i,applyDirectives:n}=Ol(e);Os()(e,t);let s=r({tailwindDirectives:i,applyDirectives:n,registerDependency(a){t.messages.push({plugin:\"tailwindcss\",parent:t.opts.from,...a})},createContext(a,o){return il(a,o,e)}})(e,t);if(s.tailwindConfig.separator===\"-\")throw new Error(\"The '-' character cannot be used as a custom separator in JIT mode due to parsing ambiguity. Please use another character like '_' instead.\");Rf(s.tailwindConfig),await Pl(s)(e,t),Os()(e,t),Dl(s)(e,t),cy(s)(e,t),dy(s)(e,t),Hl(s)(e,t),Wl(s)(e,t),Gl(s)(e,t)}}var xy=P(()=>{u();Og();Bg();Hg();py();hy();gy();by();wy();vy();Oi();ct()});function ky(r,e){let t=null,i=null;return r.walkAtRules(\"config\",n=>{if(i=n.source?.input.file??e.opts.from??null,i===null)throw n.error(\"The `@config` directive cannot be used without setting `from` in your PostCSS config.\");if(t)throw n.error(\"Only one `@config` directive is allowed per file.\");let s=n.params.match(/(['\"])(.*?)\\1/);if(!s)throw n.error(\"A path is required when using the `@config` directive.\");let a=s[2];if(me.isAbsolute(a))throw n.error(\"The `@config` directive cannot be used with an absolute path.\");if(t=me.resolve(me.dirname(i),a),!be.existsSync(t))throw n.error(`The config file at \"${a}\" does not exist. Make sure the path is correct and the file exists.`);n.remove()}),t||null}var Sy=P(()=>{u();ft();et()});var Ay=x((Wq,Ql)=>{u();Eg();xy();It();Sy();Ql.exports=function(e){return{postcssPlugin:\"tailwindcss\",plugins:[Ze.DEBUG&&function(t){return console.log(`\n`),console.time(\"JIT TOTAL\"),t},async function(t,i){e=ky(t,i)??e;let n=El(e);if(t.type===\"document\"){let s=t.nodes.filter(a=>a.type===\"root\");for(let a of s)a.type===\"root\"&&await Ts(n)(a,i);return}await Ts(n)(t,i)},Ze.DEBUG&&function(t){return console.timeEnd(\"JIT TOTAL\"),console.log(`\n`),t}].filter(Boolean)}};Ql.exports.postcss=!0});var _y=x((Gq,Cy)=>{u();Cy.exports=Ay()});var Yl=x((Qq,Ey)=>{u();Ey.exports=()=>[\"and_chr 114\",\"and_uc 15.5\",\"chrome 114\",\"chrome 113\",\"chrome 109\",\"edge 114\",\"firefox 114\",\"ios_saf 16.5\",\"ios_saf 16.4\",\"ios_saf 16.3\",\"ios_saf 16.1\",\"opera 99\",\"safari 16.5\",\"samsung 21\"]});var Rs={};Ge(Rs,{agents:()=>nO,feature:()=>sO});function sO(){return{status:\"cr\",title:\"CSS Feature Queries\",stats:{ie:{\"6\":\"n\",\"7\":\"n\",\"8\":\"n\",\"9\":\"n\",\"10\":\"n\",\"11\":\"n\",\"5.5\":\"n\"},edge:{\"12\":\"y\",\"13\":\"y\",\"14\":\"y\",\"15\":\"y\",\"16\":\"y\",\"17\":\"y\",\"18\":\"y\",\"79\":\"y\",\"80\":\"y\",\"81\":\"y\",\"83\":\"y\",\"84\":\"y\",\"85\":\"y\",\"86\":\"y\",\"87\":\"y\",\"88\":\"y\",\"89\":\"y\",\"90\":\"y\",\"91\":\"y\",\"92\":\"y\",\"93\":\"y\",\"94\":\"y\",\"95\":\"y\",\"96\":\"y\",\"97\":\"y\",\"98\":\"y\",\"99\":\"y\",\"100\":\"y\",\"101\":\"y\",\"102\":\"y\",\"103\":\"y\",\"104\":\"y\",\"105\":\"y\",\"106\":\"y\",\"107\":\"y\",\"108\":\"y\",\"109\":\"y\",\"110\":\"y\",\"111\":\"y\",\"112\":\"y\",\"113\":\"y\",\"114\":\"y\"},firefox:{\"2\":\"n\",\"3\":\"n\",\"4\":\"n\",\"5\":\"n\",\"6\":\"n\",\"7\":\"n\",\"8\":\"n\",\"9\":\"n\",\"10\":\"n\",\"11\":\"n\",\"12\":\"n\",\"13\":\"n\",\"14\":\"n\",\"15\":\"n\",\"16\":\"n\",\"17\":\"n\",\"18\":\"n\",\"19\":\"n\",\"20\":\"n\",\"21\":\"n\",\"22\":\"y\",\"23\":\"y\",\"24\":\"y\",\"25\":\"y\",\"26\":\"y\",\"27\":\"y\",\"28\":\"y\",\"29\":\"y\",\"30\":\"y\",\"31\":\"y\",\"32\":\"y\",\"33\":\"y\",\"34\":\"y\",\"35\":\"y\",\"36\":\"y\",\"37\":\"y\",\"38\":\"y\",\"39\":\"y\",\"40\":\"y\",\"41\":\"y\",\"42\":\"y\",\"43\":\"y\",\"44\":\"y\",\"45\":\"y\",\"46\":\"y\",\"47\":\"y\",\"48\":\"y\",\"49\":\"y\",\"50\":\"y\",\"51\":\"y\",\"52\":\"y\",\"53\":\"y\",\"54\":\"y\",\"55\":\"y\",\"56\":\"y\",\"57\":\"y\",\"58\":\"y\",\"59\":\"y\",\"60\":\"y\",\"61\":\"y\",\"62\":\"y\",\"63\":\"y\",\"64\":\"y\",\"65\":\"y\",\"66\":\"y\",\"67\":\"y\",\"68\":\"y\",\"69\":\"y\",\"70\":\"y\",\"71\":\"y\",\"72\":\"y\",\"73\":\"y\",\"74\":\"y\",\"75\":\"y\",\"76\":\"y\",\"77\":\"y\",\"78\":\"y\",\"79\":\"y\",\"80\":\"y\",\"81\":\"y\",\"82\":\"y\",\"83\":\"y\",\"84\":\"y\",\"85\":\"y\",\"86\":\"y\",\"87\":\"y\",\"88\":\"y\",\"89\":\"y\",\"90\":\"y\",\"91\":\"y\",\"92\":\"y\",\"93\":\"y\",\"94\":\"y\",\"95\":\"y\",\"96\":\"y\",\"97\":\"y\",\"98\":\"y\",\"99\":\"y\",\"100\":\"y\",\"101\":\"y\",\"102\":\"y\",\"103\":\"y\",\"104\":\"y\",\"105\":\"y\",\"106\":\"y\",\"107\":\"y\",\"108\":\"y\",\"109\":\"y\",\"110\":\"y\",\"111\":\"y\",\"112\":\"y\",\"113\":\"y\",\"114\":\"y\",\"115\":\"y\",\"116\":\"y\",\"117\":\"y\",\"3.5\":\"n\",\"3.6\":\"n\"},chrome:{\"4\":\"n\",\"5\":\"n\",\"6\":\"n\",\"7\":\"n\",\"8\":\"n\",\"9\":\"n\",\"10\":\"n\",\"11\":\"n\",\"12\":\"n\",\"13\":\"n\",\"14\":\"n\",\"15\":\"n\",\"16\":\"n\",\"17\":\"n\",\"18\":\"n\",\"19\":\"n\",\"20\":\"n\",\"21\":\"n\",\"22\":\"n\",\"23\":\"n\",\"24\":\"n\",\"25\":\"n\",\"26\":\"n\",\"27\":\"n\",\"28\":\"y\",\"29\":\"y\",\"30\":\"y\",\"31\":\"y\",\"32\":\"y\",\"33\":\"y\",\"34\":\"y\",\"35\":\"y\",\"36\":\"y\",\"37\":\"y\",\"38\":\"y\",\"39\":\"y\",\"40\":\"y\",\"41\":\"y\",\"42\":\"y\",\"43\":\"y\",\"44\":\"y\",\"45\":\"y\",\"46\":\"y\",\"47\":\"y\",\"48\":\"y\",\"49\":\"y\",\"50\":\"y\",\"51\":\"y\",\"52\":\"y\",\"53\":\"y\",\"54\":\"y\",\"55\":\"y\",\"56\":\"y\",\"57\":\"y\",\"58\":\"y\",\"59\":\"y\",\"60\":\"y\",\"61\":\"y\",\"62\":\"y\",\"63\":\"y\",\"64\":\"y\",\"65\":\"y\",\"66\":\"y\",\"67\":\"y\",\"68\":\"y\",\"69\":\"y\",\"70\":\"y\",\"71\":\"y\",\"72\":\"y\",\"73\":\"y\",\"74\":\"y\",\"75\":\"y\",\"76\":\"y\",\"77\":\"y\",\"78\":\"y\",\"79\":\"y\",\"80\":\"y\",\"81\":\"y\",\"83\":\"y\",\"84\":\"y\",\"85\":\"y\",\"86\":\"y\",\"87\":\"y\",\"88\":\"y\",\"89\":\"y\",\"90\":\"y\",\"91\":\"y\",\"92\":\"y\",\"93\":\"y\",\"94\":\"y\",\"95\":\"y\",\"96\":\"y\",\"97\":\"y\",\"98\":\"y\",\"99\":\"y\",\"100\":\"y\",\"101\":\"y\",\"102\":\"y\",\"103\":\"y\",\"104\":\"y\",\"105\":\"y\",\"106\":\"y\",\"107\":\"y\",\"108\":\"y\",\"109\":\"y\",\"110\":\"y\",\"111\":\"y\",\"112\":\"y\",\"113\":\"y\",\"114\":\"y\",\"115\":\"y\",\"116\":\"y\",\"117\":\"y\"},safari:{\"4\":\"n\",\"5\":\"n\",\"6\":\"n\",\"7\":\"n\",\"8\":\"n\",\"9\":\"y\",\"10\":\"y\",\"11\":\"y\",\"12\":\"y\",\"13\":\"y\",\"14\":\"y\",\"15\":\"y\",\"17\":\"y\",\"9.1\":\"y\",\"10.1\":\"y\",\"11.1\":\"y\",\"12.1\":\"y\",\"13.1\":\"y\",\"14.1\":\"y\",\"15.1\":\"y\",\"15.2-15.3\":\"y\",\"15.4\":\"y\",\"15.5\":\"y\",\"15.6\":\"y\",\"16.0\":\"y\",\"16.1\":\"y\",\"16.2\":\"y\",\"16.3\":\"y\",\"16.4\":\"y\",\"16.5\":\"y\",\"16.6\":\"y\",TP:\"y\",\"3.1\":\"n\",\"3.2\":\"n\",\"5.1\":\"n\",\"6.1\":\"n\",\"7.1\":\"n\"},opera:{\"9\":\"n\",\"11\":\"n\",\"12\":\"n\",\"15\":\"y\",\"16\":\"y\",\"17\":\"y\",\"18\":\"y\",\"19\":\"y\",\"20\":\"y\",\"21\":\"y\",\"22\":\"y\",\"23\":\"y\",\"24\":\"y\",\"25\":\"y\",\"26\":\"y\",\"27\":\"y\",\"28\":\"y\",\"29\":\"y\",\"30\":\"y\",\"31\":\"y\",\"32\":\"y\",\"33\":\"y\",\"34\":\"y\",\"35\":\"y\",\"36\":\"y\",\"37\":\"y\",\"38\":\"y\",\"39\":\"y\",\"40\":\"y\",\"41\":\"y\",\"42\":\"y\",\"43\":\"y\",\"44\":\"y\",\"45\":\"y\",\"46\":\"y\",\"47\":\"y\",\"48\":\"y\",\"49\":\"y\",\"50\":\"y\",\"51\":\"y\",\"52\":\"y\",\"53\":\"y\",\"54\":\"y\",\"55\":\"y\",\"56\":\"y\",\"57\":\"y\",\"58\":\"y\",\"60\":\"y\",\"62\":\"y\",\"63\":\"y\",\"64\":\"y\",\"65\":\"y\",\"66\":\"y\",\"67\":\"y\",\"68\":\"y\",\"69\":\"y\",\"70\":\"y\",\"71\":\"y\",\"72\":\"y\",\"73\":\"y\",\"74\":\"y\",\"75\":\"y\",\"76\":\"y\",\"77\":\"y\",\"78\":\"y\",\"79\":\"y\",\"80\":\"y\",\"81\":\"y\",\"82\":\"y\",\"83\":\"y\",\"84\":\"y\",\"85\":\"y\",\"86\":\"y\",\"87\":\"y\",\"88\":\"y\",\"89\":\"y\",\"90\":\"y\",\"91\":\"y\",\"92\":\"y\",\"93\":\"y\",\"94\":\"y\",\"95\":\"y\",\"96\":\"y\",\"97\":\"y\",\"98\":\"y\",\"99\":\"y\",\"100\":\"y\",\"12.1\":\"y\",\"9.5-9.6\":\"n\",\"10.0-10.1\":\"n\",\"10.5\":\"n\",\"10.6\":\"n\",\"11.1\":\"n\",\"11.5\":\"n\",\"11.6\":\"n\"},ios_saf:{\"8\":\"n\",\"17\":\"y\",\"9.0-9.2\":\"y\",\"9.3\":\"y\",\"10.0-10.2\":\"y\",\"10.3\":\"y\",\"11.0-11.2\":\"y\",\"11.3-11.4\":\"y\",\"12.0-12.1\":\"y\",\"12.2-12.5\":\"y\",\"13.0-13.1\":\"y\",\"13.2\":\"y\",\"13.3\":\"y\",\"13.4-13.7\":\"y\",\"14.0-14.4\":\"y\",\"14.5-14.8\":\"y\",\"15.0-15.1\":\"y\",\"15.2-15.3\":\"y\",\"15.4\":\"y\",\"15.5\":\"y\",\"15.6\":\"y\",\"16.0\":\"y\",\"16.1\":\"y\",\"16.2\":\"y\",\"16.3\":\"y\",\"16.4\":\"y\",\"16.5\":\"y\",\"16.6\":\"y\",\"3.2\":\"n\",\"4.0-4.1\":\"n\",\"4.2-4.3\":\"n\",\"5.0-5.1\":\"n\",\"6.0-6.1\":\"n\",\"7.0-7.1\":\"n\",\"8.1-8.4\":\"n\"},op_mini:{all:\"y\"},android:{\"3\":\"n\",\"4\":\"n\",\"114\":\"y\",\"4.4\":\"y\",\"4.4.3-4.4.4\":\"y\",\"2.1\":\"n\",\"2.2\":\"n\",\"2.3\":\"n\",\"4.1\":\"n\",\"4.2-4.3\":\"n\"},bb:{\"7\":\"n\",\"10\":\"n\"},op_mob:{\"10\":\"n\",\"11\":\"n\",\"12\":\"n\",\"73\":\"y\",\"11.1\":\"n\",\"11.5\":\"n\",\"12.1\":\"n\"},and_chr:{\"114\":\"y\"},and_ff:{\"115\":\"y\"},ie_mob:{\"10\":\"n\",\"11\":\"n\"},and_uc:{\"15.5\":\"y\"},samsung:{\"4\":\"y\",\"20\":\"y\",\"21\":\"y\",\"5.0-5.4\":\"y\",\"6.2-6.4\":\"y\",\"7.2-7.4\":\"y\",\"8.2\":\"y\",\"9.2\":\"y\",\"10.1\":\"y\",\"11.1-11.2\":\"y\",\"12.0\":\"y\",\"13.0\":\"y\",\"14.0\":\"y\",\"15.0\":\"y\",\"16.0\":\"y\",\"17.0\":\"y\",\"18.0\":\"y\",\"19.0\":\"y\"},and_qq:{\"13.1\":\"y\"},baidu:{\"13.18\":\"y\"},kaios:{\"2.5\":\"y\",\"3.0-3.1\":\"y\"}}}}var nO,Ps=P(()=>{u();nO={ie:{prefix:\"ms\"},edge:{prefix:\"webkit\",prefix_exceptions:{\"12\":\"ms\",\"13\":\"ms\",\"14\":\"ms\",\"15\":\"ms\",\"16\":\"ms\",\"17\":\"ms\",\"18\":\"ms\"}},firefox:{prefix:\"moz\"},chrome:{prefix:\"webkit\"},safari:{prefix:\"webkit\"},opera:{prefix:\"webkit\",prefix_exceptions:{\"9\":\"o\",\"11\":\"o\",\"12\":\"o\",\"9.5-9.6\":\"o\",\"10.0-10.1\":\"o\",\"10.5\":\"o\",\"10.6\":\"o\",\"11.1\":\"o\",\"11.5\":\"o\",\"11.6\":\"o\",\"12.1\":\"o\"}},ios_saf:{prefix:\"webkit\"},op_mini:{prefix:\"o\"},android:{prefix:\"webkit\"},bb:{prefix:\"webkit\"},op_mob:{prefix:\"o\",prefix_exceptions:{\"73\":\"webkit\"}},and_chr:{prefix:\"webkit\"},and_ff:{prefix:\"moz\"},ie_mob:{prefix:\"ms\"},and_uc:{prefix:\"webkit\",prefix_exceptions:{\"15.5\":\"webkit\"}},samsung:{prefix:\"webkit\"},and_qq:{prefix:\"webkit\"},baidu:{prefix:\"webkit\"},kaios:{prefix:\"moz\"}}});var Oy=x(()=>{u()});var _e=x((Xq,Lt)=>{u();var{list:Kl}=$e();Lt.exports.error=function(r){let e=new Error(r);throw e.autoprefixer=!0,e};Lt.exports.uniq=function(r){return[...new Set(r)]};Lt.exports.removeNote=function(r){return r.includes(\" \")?r.split(\" \")[0]:r};Lt.exports.escapeRegexp=function(r){return r.replace(/[$()*+-.?[\\\\\\]^{|}]/g,\"\\\\$&\")};Lt.exports.regexp=function(r,e=!0){return e&&(r=this.escapeRegexp(r)),new RegExp(`(^|[\\\\s,(])(${r}($|[\\\\s(,]))`,\"gi\")};Lt.exports.editList=function(r,e){let t=Kl.comma(r),i=e(t,[]);if(t===i)return r;let n=r.match(/,\\s*/);return n=n?n[0]:\", \",i.join(n)};Lt.exports.splitSelector=function(r){return Kl.comma(r).map(e=>Kl.space(e).map(t=>t.split(/(?=\\.|#)/g)))}});var Mt=x((Zq,Py)=>{u();var aO=Yl(),Ty=(Ps(),Rs).agents,oO=_e(),Ry=class{static prefixes(){if(this.prefixesCache)return this.prefixesCache;this.prefixesCache=[];for(let e in Ty)this.prefixesCache.push(`-${Ty[e].prefix}-`);return this.prefixesCache=oO.uniq(this.prefixesCache).sort((e,t)=>t.length-e.length),this.prefixesCache}static withPrefix(e){return this.prefixesRegexp||(this.prefixesRegexp=new RegExp(this.prefixes().join(\"|\"))),this.prefixesRegexp.test(e)}constructor(e,t,i,n){this.data=e,this.options=i||{},this.browserslistOpts=n||{},this.selected=this.parse(t)}parse(e){let t={};for(let i in this.browserslistOpts)t[i]=this.browserslistOpts[i];return t.path=this.options.from,aO(e,t)}prefix(e){let[t,i]=e.split(\" \"),n=this.data[t],s=n.prefix_exceptions&&n.prefix_exceptions[i];return s||(s=n.prefix),`-${s}-`}isSelected(e){return this.selected.includes(e)}};Py.exports=Ry});var Ni=x((Jq,Iy)=>{u();Iy.exports={prefix(r){let e=r.match(/^(-\\w+-)/);return e?e[0]:\"\"},unprefixed(r){return r.replace(/^-\\w+-/,\"\")}}});var wr=x((e$,qy)=>{u();var lO=Mt(),Dy=Ni(),uO=_e();function Xl(r,e){let t=new r.constructor;for(let i of Object.keys(r||{})){let n=r[i];i===\"parent\"&&typeof n==\"object\"?e&&(t[i]=e):i===\"source\"||i===null?t[i]=n:Array.isArray(n)?t[i]=n.map(s=>Xl(s,t)):i!==\"_autoprefixerPrefix\"&&i!==\"_autoprefixerValues\"&&i!==\"proxyCache\"&&(typeof n==\"object\"&&n!==null&&(n=Xl(n,t)),t[i]=n)}return t}var Is=class{static hack(e){return this.hacks||(this.hacks={}),e.names.map(t=>(this.hacks[t]=e,this.hacks[t]))}static load(e,t,i){let n=this.hacks&&this.hacks[e];return n?new n(e,t,i):new this(e,t,i)}static clone(e,t){let i=Xl(e);for(let n in t)i[n]=t[n];return i}constructor(e,t,i){this.prefixes=t,this.name=e,this.all=i}parentPrefix(e){let t;return typeof e._autoprefixerPrefix!=\"undefined\"?t=e._autoprefixerPrefix:e.type===\"decl\"&&e.prop[0]===\"-\"?t=Dy.prefix(e.prop):e.type===\"root\"?t=!1:e.type===\"rule\"&&e.selector.includes(\":-\")&&/:(-\\w+-)/.test(e.selector)?t=e.selector.match(/:(-\\w+-)/)[1]:e.type===\"atrule\"&&e.name[0]===\"-\"?t=Dy.prefix(e.name):t=this.parentPrefix(e.parent),lO.prefixes().includes(t)||(t=!1),e._autoprefixerPrefix=t,e._autoprefixerPrefix}process(e,t){if(!this.check(e))return;let i=this.parentPrefix(e),n=this.prefixes.filter(a=>!i||i===uO.removeNote(a)),s=[];for(let a of n)this.add(e,a,s.concat([a]),t)&&s.push(a);return s}clone(e,t){return Is.clone(e,t)}};qy.exports=Is});var j=x((t$,My)=>{u();var fO=wr(),cO=Mt(),$y=_e(),Ly=class extends fO{check(){return!0}prefixed(e,t){return t+e}normalize(e){return e}otherPrefixes(e,t){for(let i of cO.prefixes())if(i!==t&&e.includes(i))return!0;return!1}set(e,t){return e.prop=this.prefixed(e.prop,t),e}needCascade(e){return e._autoprefixerCascade||(e._autoprefixerCascade=this.all.options.cascade!==!1&&e.raw(\"before\").includes(`\n`)),e._autoprefixerCascade}maxPrefixed(e,t){if(t._autoprefixerMax)return t._autoprefixerMax;let i=0;for(let n of e)n=$y.removeNote(n),n.length>i&&(i=n.length);return t._autoprefixerMax=i,t._autoprefixerMax}calcBefore(e,t,i=\"\"){let s=this.maxPrefixed(e,t)-$y.removeNote(i).length,a=t.raw(\"before\");return s>0&&(a+=Array(s).fill(\" \").join(\"\")),a}restoreBefore(e){let t=e.raw(\"before\").split(`\n`),i=t[t.length-1];this.all.group(e).up(n=>{let s=n.raw(\"before\").split(`\n`),a=s[s.length-1];a.lengtha.prop===n.prop&&a.value===n.value)))return this.needCascade(e)&&(n.raws.before=this.calcBefore(i,e,t)),e.parent.insertBefore(e,n)}isAlready(e,t){let i=this.all.group(e).up(n=>n.prop===t);return i||(i=this.all.group(e).down(n=>n.prop===t)),i}add(e,t,i,n){let s=this.prefixed(e.prop,t);if(!(this.isAlready(e,s)||this.otherPrefixes(e.value,t)))return this.insert(e,t,i,n)}process(e,t){if(!this.needCascade(e)){super.process(e,t);return}let i=super.process(e,t);!i||!i.length||(this.restoreBefore(e),e.raws.before=this.calcBefore(i,e))}old(e,t){return[this.prefixed(e,t)]}};My.exports=Ly});var By=x((r$,Ny)=>{u();Ny.exports=function r(e){return{mul:t=>new r(e*t),div:t=>new r(e/t),simplify:()=>new r(e),toString:()=>e.toString()}}});var zy=x((i$,jy)=>{u();var pO=By(),dO=wr(),Zl=_e(),hO=/(min|max)-resolution\\s*:\\s*\\d*\\.?\\d+(dppx|dpcm|dpi|x)/gi,mO=/(min|max)-resolution(\\s*:\\s*)(\\d*\\.?\\d+)(dppx|dpcm|dpi|x)/i,Fy=class extends dO{prefixName(e,t){return e===\"-moz-\"?t+\"--moz-device-pixel-ratio\":e+t+\"-device-pixel-ratio\"}prefixQuery(e,t,i,n,s){return n=new pO(n),s===\"dpi\"?n=n.div(96):s===\"dpcm\"&&(n=n.mul(2.54).div(96)),n=n.simplify(),e===\"-o-\"&&(n=n.n+\"/\"+n.d),this.prefixName(e,t)+i+n}clean(e){if(!this.bad){this.bad=[];for(let t of this.prefixes)this.bad.push(this.prefixName(t,\"min\")),this.bad.push(this.prefixName(t,\"max\"))}e.params=Zl.editList(e.params,t=>t.filter(i=>this.bad.every(n=>!i.includes(n))))}process(e){let t=this.parentPrefix(e),i=t?[t]:this.prefixes;e.params=Zl.editList(e.params,(n,s)=>{for(let a of n){if(!a.includes(\"min-resolution\")&&!a.includes(\"max-resolution\")){s.push(a);continue}for(let o of i){let l=a.replace(hO,c=>{let f=c.match(mO);return this.prefixQuery(o,f[1],f[2],f[3],f[4])});s.push(l)}s.push(a)}return Zl.uniq(s)})}};jy.exports=Fy});var Vy=x((n$,Uy)=>{u();var Jl=\"(\".charCodeAt(0),eu=\")\".charCodeAt(0),Ds=\"'\".charCodeAt(0),tu='\"'.charCodeAt(0),ru=\"\\\\\".charCodeAt(0),vr=\"/\".charCodeAt(0),iu=\",\".charCodeAt(0),nu=\":\".charCodeAt(0),qs=\"*\".charCodeAt(0),gO=\"u\".charCodeAt(0),yO=\"U\".charCodeAt(0),bO=\"+\".charCodeAt(0),wO=/^[a-f0-9?-]+$/i;Uy.exports=function(r){for(var e=[],t=r,i,n,s,a,o,l,c,f,d=0,p=t.charCodeAt(d),h=t.length,b=[{nodes:e}],v=0,y,w=\"\",k=\"\",S=\"\";d{u();Hy.exports=function r(e,t,i){var n,s,a,o;for(n=0,s=e.length;n{u();function Gy(r,e){var t=r.type,i=r.value,n,s;return e&&(s=e(r))!==void 0?s:t===\"word\"||t===\"space\"?i:t===\"string\"?(n=r.quote||\"\",n+i+(r.unclosed?\"\":n)):t===\"comment\"?\"/*\"+i+(r.unclosed?\"\":\"*/\"):t===\"div\"?(r.before||\"\")+i+(r.after||\"\"):Array.isArray(r.nodes)?(n=Qy(r.nodes,e),t!==\"function\"?n:i+\"(\"+(r.before||\"\")+n+(r.after||\"\")+(r.unclosed?\"\":\")\")):i}function Qy(r,e){var t,i;if(Array.isArray(r)){for(t=\"\",i=r.length-1;~i;i-=1)t=Gy(r[i],e)+t;return t}return Gy(r,e)}Yy.exports=Qy});var Zy=x((o$,Xy)=>{u();var $s=\"-\".charCodeAt(0),Ls=\"+\".charCodeAt(0),su=\".\".charCodeAt(0),vO=\"e\".charCodeAt(0),xO=\"E\".charCodeAt(0);function kO(r){var e=r.charCodeAt(0),t;if(e===Ls||e===$s){if(t=r.charCodeAt(1),t>=48&&t<=57)return!0;var i=r.charCodeAt(2);return t===su&&i>=48&&i<=57}return e===su?(t=r.charCodeAt(1),t>=48&&t<=57):e>=48&&e<=57}Xy.exports=function(r){var e=0,t=r.length,i,n,s;if(t===0||!kO(r))return!1;for(i=r.charCodeAt(e),(i===Ls||i===$s)&&e++;e57));)e+=1;if(i=r.charCodeAt(e),n=r.charCodeAt(e+1),i===su&&n>=48&&n<=57)for(e+=2;e57));)e+=1;if(i=r.charCodeAt(e),n=r.charCodeAt(e+1),s=r.charCodeAt(e+2),(i===vO||i===xO)&&(n>=48&&n<=57||(n===Ls||n===$s)&&s>=48&&s<=57))for(e+=n===Ls||n===$s?3:2;e57));)e+=1;return{number:r.slice(0,e),unit:r.slice(e)}}});var Ms=x((l$,tb)=>{u();var SO=Vy(),Jy=Wy(),eb=Ky();function Nt(r){return this instanceof Nt?(this.nodes=SO(r),this):new Nt(r)}Nt.prototype.toString=function(){return Array.isArray(this.nodes)?eb(this.nodes):\"\"};Nt.prototype.walk=function(r,e){return Jy(this.nodes,r,e),this};Nt.unit=Zy();Nt.walk=Jy;Nt.stringify=eb;tb.exports=Nt});var ab=x((u$,sb)=>{u();var{list:AO}=$e(),rb=Ms(),CO=Mt(),ib=Ni(),nb=class{constructor(e){this.props=[\"transition\",\"transition-property\"],this.prefixes=e}add(e,t){let i,n,s=this.prefixes.add[e.prop],a=this.ruleVendorPrefixes(e),o=a||s&&s.prefixes||[],l=this.parse(e.value),c=l.map(h=>this.findProp(h)),f=[];if(c.some(h=>h[0]===\"-\"))return;for(let h of l){if(n=this.findProp(h),n[0]===\"-\")continue;let b=this.prefixes.add[n];if(!(!b||!b.prefixes))for(i of b.prefixes){if(a&&!a.some(y=>i.includes(y)))continue;let v=this.prefixes.prefixed(n,i);v!==\"-ms-transform\"&&!c.includes(v)&&(this.disabled(n,i)||f.push(this.clone(n,v,h)))}}l=l.concat(f);let d=this.stringify(l),p=this.stringify(this.cleanFromUnprefixed(l,\"-webkit-\"));if(o.includes(\"-webkit-\")&&this.cloneBefore(e,`-webkit-${e.prop}`,p),this.cloneBefore(e,e.prop,p),o.includes(\"-o-\")){let h=this.stringify(this.cleanFromUnprefixed(l,\"-o-\"));this.cloneBefore(e,`-o-${e.prop}`,h)}for(i of o)if(i!==\"-webkit-\"&&i!==\"-o-\"){let h=this.stringify(this.cleanOtherPrefixes(l,i));this.cloneBefore(e,i+e.prop,h)}d!==e.value&&!this.already(e,e.prop,d)&&(this.checkForWarning(t,e),e.cloneBefore(),e.value=d)}findProp(e){let t=e[0].value;if(/^\\d/.test(t)){for(let[i,n]of e.entries())if(i!==0&&n.type===\"word\")return n.value}return t}already(e,t,i){return e.parent.some(n=>n.prop===t&&n.value===i)}cloneBefore(e,t,i){this.already(e,t,i)||e.cloneBefore({prop:t,value:i})}checkForWarning(e,t){if(t.prop!==\"transition-property\")return;let i=!1,n=!1;t.parent.each(s=>{if(s.type!==\"decl\"||s.prop.indexOf(\"transition-\")!==0)return;let a=AO.comma(s.value);if(s.prop===\"transition-property\"){a.forEach(o=>{let l=this.prefixes.add[o];l&&l.prefixes&&l.prefixes.length>0&&(i=!0)});return}return n=n||a.length>1,!1}),i&&n&&t.warn(e,\"Replace transition-property to transition, because Autoprefixer could not support any cases of transition-property and other transition-*\")}remove(e){let t=this.parse(e.value);t=t.filter(a=>{let o=this.prefixes.remove[this.findProp(a)];return!o||!o.remove});let i=this.stringify(t);if(e.value===i)return;if(t.length===0){e.remove();return}let n=e.parent.some(a=>a.prop===e.prop&&a.value===i),s=e.parent.some(a=>a!==e&&a.prop===e.prop&&a.value.length>i.length);if(n||s){e.remove();return}e.value=i}parse(e){let t=rb(e),i=[],n=[];for(let s of t.nodes)n.push(s),s.type===\"div\"&&s.value===\",\"&&(i.push(n),n=[]);return i.push(n),i.filter(s=>s.length>0)}stringify(e){if(e.length===0)return\"\";let t=[];for(let i of e)i[i.length-1].type!==\"div\"&&i.push(this.div(e)),t=t.concat(i);return t[0].type===\"div\"&&(t=t.slice(1)),t[t.length-1].type===\"div\"&&(t=t.slice(0,-2+1||void 0)),rb.stringify({nodes:t})}clone(e,t,i){let n=[],s=!1;for(let a of i)!s&&a.type===\"word\"&&a.value===e?(n.push({type:\"word\",value:t}),s=!0):n.push(a);return n}div(e){for(let t of e)for(let i of t)if(i.type===\"div\"&&i.value===\",\")return i;return{type:\"div\",value:\",\",after:\" \"}}cleanOtherPrefixes(e,t){return e.filter(i=>{let n=ib.prefix(this.findProp(i));return n===\"\"||n===t})}cleanFromUnprefixed(e,t){let i=e.map(s=>this.findProp(s)).filter(s=>s.slice(0,t.length)===t).map(s=>this.prefixes.unprefixed(s)),n=[];for(let s of e){let a=this.findProp(s),o=ib.prefix(a);!i.includes(a)&&(o===t||o===\"\")&&n.push(s)}return n}disabled(e,t){let i=[\"order\",\"justify-content\",\"align-self\",\"align-content\"];if(e.includes(\"flex\")||i.includes(e)){if(this.prefixes.options.flexbox===!1)return!0;if(this.prefixes.options.flexbox===\"no-2009\")return t.includes(\"2009\")}}ruleVendorPrefixes(e){let{parent:t}=e;if(t.type!==\"rule\")return!1;if(!t.selector.includes(\":-\"))return!1;let i=CO.prefixes().filter(n=>t.selector.includes(\":\"+n));return i.length>0?i:!1}};sb.exports=nb});var xr=x((f$,lb)=>{u();var _O=_e(),ob=class{constructor(e,t,i,n){this.unprefixed=e,this.prefixed=t,this.string=i||t,this.regexp=n||_O.regexp(t)}check(e){return e.includes(this.string)?!!e.match(this.regexp):!1}};lb.exports=ob});var He=x((c$,fb)=>{u();var EO=wr(),OO=xr(),TO=Ni(),RO=_e(),ub=class extends EO{static save(e,t){let i=t.prop,n=[];for(let s in t._autoprefixerValues){let a=t._autoprefixerValues[s];if(a===t.value)continue;let o,l=TO.prefix(i);if(l===\"-pie-\")continue;if(l===s){o=t.value=a,n.push(o);continue}let c=e.prefixed(i,s),f=t.parent;if(!f.every(b=>b.prop!==c)){n.push(o);continue}let d=a.replace(/\\s+/,\" \");if(f.some(b=>b.prop===t.prop&&b.value.replace(/\\s+/,\" \")===d)){n.push(o);continue}let h=this.clone(t,{value:a});o=t.parent.insertBefore(t,h),n.push(o)}return n}check(e){let t=e.value;return t.includes(this.name)?!!t.match(this.regexp()):!1}regexp(){return this.regexpCache||(this.regexpCache=RO.regexp(this.name))}replace(e,t){return e.replace(this.regexp(),`$1${t}$2`)}value(e){return e.raws.value&&e.raws.value.value===e.value?e.raws.value.raw:e.value}add(e,t){e._autoprefixerValues||(e._autoprefixerValues={});let i=e._autoprefixerValues[t]||this.value(e),n;do if(n=i,i=this.replace(i,t),i===!1)return;while(i!==n);e._autoprefixerValues[t]=i}old(e){return new OO(this.name,e+this.name)}};fb.exports=ub});var Bt=x((p$,cb)=>{u();cb.exports={}});var ou=x((d$,hb)=>{u();var pb=Ms(),PO=He(),IO=Bt().insertAreas,DO=/(^|[^-])linear-gradient\\(\\s*(top|left|right|bottom)/i,qO=/(^|[^-])radial-gradient\\(\\s*\\d+(\\w*|%)\\s+\\d+(\\w*|%)\\s*,/i,$O=/(!\\s*)?autoprefixer:\\s*ignore\\s+next/i,LO=/(!\\s*)?autoprefixer\\s*grid:\\s*(on|off|(no-)?autoplace)/i,MO=[\"width\",\"height\",\"min-width\",\"max-width\",\"min-height\",\"max-height\",\"inline-size\",\"min-inline-size\",\"max-inline-size\",\"block-size\",\"min-block-size\",\"max-block-size\"];function au(r){return r.parent.some(e=>e.prop===\"grid-template\"||e.prop===\"grid-template-areas\")}function NO(r){let e=r.parent.some(i=>i.prop===\"grid-template-rows\"),t=r.parent.some(i=>i.prop===\"grid-template-columns\");return e&&t}var db=class{constructor(e){this.prefixes=e}add(e,t){let i=this.prefixes.add[\"@resolution\"],n=this.prefixes.add[\"@keyframes\"],s=this.prefixes.add[\"@viewport\"],a=this.prefixes.add[\"@supports\"];e.walkAtRules(f=>{if(f.name===\"keyframes\"){if(!this.disabled(f,t))return n&&n.process(f)}else if(f.name===\"viewport\"){if(!this.disabled(f,t))return s&&s.process(f)}else if(f.name===\"supports\"){if(this.prefixes.options.supports!==!1&&!this.disabled(f,t))return a.process(f)}else if(f.name===\"media\"&&f.params.includes(\"-resolution\")&&!this.disabled(f,t))return i&&i.process(f)}),e.walkRules(f=>{if(!this.disabled(f,t))return this.prefixes.add.selectors.map(d=>d.process(f,t))});function o(f){return f.parent.nodes.some(d=>{if(d.type!==\"decl\")return!1;let p=d.prop===\"display\"&&/(inline-)?grid/.test(d.value),h=d.prop.startsWith(\"grid-template\"),b=/^grid-([A-z]+-)?gap/.test(d.prop);return p||h||b})}function l(f){return f.parent.some(d=>d.prop===\"display\"&&/(inline-)?flex/.test(d.value))}let c=this.gridStatus(e,t)&&this.prefixes.add[\"grid-area\"]&&this.prefixes.add[\"grid-area\"].prefixes;return e.walkDecls(f=>{if(this.disabledDecl(f,t))return;let d=f.parent,p=f.prop,h=f.value;if(p===\"grid-row-span\"){t.warn(\"grid-row-span is not part of final Grid Layout. Use grid-row.\",{node:f});return}else if(p===\"grid-column-span\"){t.warn(\"grid-column-span is not part of final Grid Layout. Use grid-column.\",{node:f});return}else if(p===\"display\"&&h===\"box\"){t.warn(\"You should write display: flex by final spec instead of display: box\",{node:f});return}else if(p===\"text-emphasis-position\")(h===\"under\"||h===\"over\")&&t.warn(\"You should use 2 values for text-emphasis-position For example, `under left` instead of just `under`.\",{node:f});else if(/^(align|justify|place)-(items|content)$/.test(p)&&l(f))(h===\"start\"||h===\"end\")&&t.warn(`${h} value has mixed support, consider using flex-${h} instead`,{node:f});else if(p===\"text-decoration-skip\"&&h===\"ink\")t.warn(\"Replace text-decoration-skip: ink to text-decoration-skip-ink: auto, because spec had been changed\",{node:f});else{if(c&&this.gridStatus(f,t))if(f.value===\"subgrid\"&&t.warn(\"IE does not support subgrid\",{node:f}),/^(align|justify|place)-items$/.test(p)&&o(f)){let v=p.replace(\"-items\",\"-self\");t.warn(`IE does not support ${p} on grid containers. Try using ${v} on child elements instead: ${f.parent.selector} > * { ${v}: ${f.value} }`,{node:f})}else if(/^(align|justify|place)-content$/.test(p)&&o(f))t.warn(`IE does not support ${f.prop} on grid containers`,{node:f});else if(p===\"display\"&&f.value===\"contents\"){t.warn(\"Please do not use display: contents; if you have grid setting enabled\",{node:f});return}else if(f.prop===\"grid-gap\"){let v=this.gridStatus(f,t);v===\"autoplace\"&&!NO(f)&&!au(f)?t.warn(\"grid-gap only works if grid-template(-areas) is being used or both rows and columns have been declared and cells have not been manually placed inside the explicit grid\",{node:f}):(v===!0||v===\"no-autoplace\")&&!au(f)&&t.warn(\"grid-gap only works if grid-template(-areas) is being used\",{node:f})}else if(p===\"grid-auto-columns\"){t.warn(\"grid-auto-columns is not supported by IE\",{node:f});return}else if(p===\"grid-auto-rows\"){t.warn(\"grid-auto-rows is not supported by IE\",{node:f});return}else if(p===\"grid-auto-flow\"){let v=d.some(w=>w.prop===\"grid-template-rows\"),y=d.some(w=>w.prop===\"grid-template-columns\");au(f)?t.warn(\"grid-auto-flow is not supported by IE\",{node:f}):h.includes(\"dense\")?t.warn(\"grid-auto-flow: dense is not supported by IE\",{node:f}):!v&&!y&&t.warn(\"grid-auto-flow works only if grid-template-rows and grid-template-columns are present in the same rule\",{node:f});return}else if(h.includes(\"auto-fit\")){t.warn(\"auto-fit value is not supported by IE\",{node:f,word:\"auto-fit\"});return}else if(h.includes(\"auto-fill\")){t.warn(\"auto-fill value is not supported by IE\",{node:f,word:\"auto-fill\"});return}else p.startsWith(\"grid-template\")&&h.includes(\"[\")&&t.warn(\"Autoprefixer currently does not support line names. Try using grid-template-areas instead.\",{node:f,word:\"[\"});if(h.includes(\"radial-gradient\"))if(qO.test(f.value))t.warn(\"Gradient has outdated direction syntax. New syntax is like `closest-side at 0 0` instead of `0 0, closest-side`.\",{node:f});else{let v=pb(h);for(let y of v.nodes)if(y.type===\"function\"&&y.value===\"radial-gradient\")for(let w of y.nodes)w.type===\"word\"&&(w.value===\"cover\"?t.warn(\"Gradient has outdated direction syntax. Replace `cover` to `farthest-corner`.\",{node:f}):w.value===\"contain\"&&t.warn(\"Gradient has outdated direction syntax. Replace `contain` to `closest-side`.\",{node:f}))}h.includes(\"linear-gradient\")&&DO.test(h)&&t.warn(\"Gradient has outdated direction syntax. New syntax is like `to left` instead of `right`.\",{node:f})}MO.includes(f.prop)&&(f.value.includes(\"-fill-available\")||(f.value.includes(\"fill-available\")?t.warn(\"Replace fill-available to stretch, because spec had been changed\",{node:f}):f.value.includes(\"fill\")&&pb(h).nodes.some(y=>y.type===\"word\"&&y.value===\"fill\")&&t.warn(\"Replace fill to stretch, because spec had been changed\",{node:f})));let b;if(f.prop===\"transition\"||f.prop===\"transition-property\")return this.prefixes.transition.add(f,t);if(f.prop===\"align-self\"){if(this.displayType(f)!==\"grid\"&&this.prefixes.options.flexbox!==!1&&(b=this.prefixes.add[\"align-self\"],b&&b.prefixes&&b.process(f)),this.gridStatus(f,t)!==!1&&(b=this.prefixes.add[\"grid-row-align\"],b&&b.prefixes))return b.process(f,t)}else if(f.prop===\"justify-self\"){if(this.gridStatus(f,t)!==!1&&(b=this.prefixes.add[\"grid-column-align\"],b&&b.prefixes))return b.process(f,t)}else if(f.prop===\"place-self\"){if(b=this.prefixes.add[\"place-self\"],b&&b.prefixes&&this.gridStatus(f,t)!==!1)return b.process(f,t)}else if(b=this.prefixes.add[f.prop],b&&b.prefixes)return b.process(f,t)}),this.gridStatus(e,t)&&IO(e,this.disabled),e.walkDecls(f=>{if(this.disabledValue(f,t))return;let d=this.prefixes.unprefixed(f.prop),p=this.prefixes.values(\"add\",d);if(Array.isArray(p))for(let h of p)h.process&&h.process(f,t);PO.save(this.prefixes,f)})}remove(e,t){let i=this.prefixes.remove[\"@resolution\"];e.walkAtRules((n,s)=>{this.prefixes.remove[`@${n.name}`]?this.disabled(n,t)||n.parent.removeChild(s):n.name===\"media\"&&n.params.includes(\"-resolution\")&&i&&i.clean(n)});for(let n of this.prefixes.remove.selectors)e.walkRules((s,a)=>{n.check(s)&&(this.disabled(s,t)||s.parent.removeChild(a))});return e.walkDecls((n,s)=>{if(this.disabled(n,t))return;let a=n.parent,o=this.prefixes.unprefixed(n.prop);if((n.prop===\"transition\"||n.prop===\"transition-property\")&&this.prefixes.transition.remove(n),this.prefixes.remove[n.prop]&&this.prefixes.remove[n.prop].remove){let l=this.prefixes.group(n).down(c=>this.prefixes.normalize(c.prop)===o);if(o===\"flex-flow\"&&(l=!0),n.prop===\"-webkit-box-orient\"){let c={\"flex-direction\":!0,\"flex-flow\":!0};if(!n.parent.some(f=>c[f.prop]))return}if(l&&!this.withHackValue(n)){n.raw(\"before\").includes(`\n`)&&this.reduceSpaces(n),a.removeChild(s);return}}for(let l of this.prefixes.values(\"remove\",o)){if(!l.check||!l.check(n.value))continue;if(o=l.unprefixed,this.prefixes.group(n).down(f=>f.value.includes(o))){a.removeChild(s);return}}})}withHackValue(e){return e.prop===\"-webkit-background-clip\"&&e.value===\"text\"}disabledValue(e,t){return this.gridStatus(e,t)===!1&&e.type===\"decl\"&&e.prop===\"display\"&&e.value.includes(\"grid\")||this.prefixes.options.flexbox===!1&&e.type===\"decl\"&&e.prop===\"display\"&&e.value.includes(\"flex\")||e.type===\"decl\"&&e.prop===\"content\"?!0:this.disabled(e,t)}disabledDecl(e,t){if(this.gridStatus(e,t)===!1&&e.type===\"decl\"&&(e.prop.includes(\"grid\")||e.prop===\"justify-items\"))return!0;if(this.prefixes.options.flexbox===!1&&e.type===\"decl\"){let i=[\"order\",\"justify-content\",\"align-items\",\"align-content\"];if(e.prop.includes(\"flex\")||i.includes(e.prop))return!0}return this.disabled(e,t)}disabled(e,t){if(!e)return!1;if(e._autoprefixerDisabled!==void 0)return e._autoprefixerDisabled;if(e.parent){let n=e.prev();if(n&&n.type===\"comment\"&&$O.test(n.text))return e._autoprefixerDisabled=!0,e._autoprefixerSelfDisabled=!0,!0}let i=null;if(e.nodes){let n;e.each(s=>{s.type===\"comment\"&&/(!\\s*)?autoprefixer:\\s*(off|on)/i.test(s.text)&&(typeof n!=\"undefined\"?t.warn(\"Second Autoprefixer control comment was ignored. Autoprefixer applies control comment to whole block, not to next rules.\",{node:s}):n=/on/i.test(s.text))}),n!==void 0&&(i=!n)}if(!e.nodes||i===null)if(e.parent){let n=this.disabled(e.parent,t);e.parent._autoprefixerSelfDisabled===!0?i=!1:i=n}else i=!1;return e._autoprefixerDisabled=i,i}reduceSpaces(e){let t=!1;if(this.prefixes.group(e).up(()=>(t=!0,!0)),t)return;let i=e.raw(\"before\").split(`\n`),n=i[i.length-1].length,s=!1;this.prefixes.group(e).down(a=>{i=a.raw(\"before\").split(`\n`);let o=i.length-1;i[o].length>n&&(s===!1&&(s=i[o].length-n),i[o]=i[o].slice(0,-s),a.raws.before=i.join(`\n`))})}displayType(e){for(let t of e.parent.nodes)if(t.prop===\"display\"){if(t.value.includes(\"flex\"))return\"flex\";if(t.value.includes(\"grid\"))return\"grid\"}return!1}gridStatus(e,t){if(!e)return!1;if(e._autoprefixerGridStatus!==void 0)return e._autoprefixerGridStatus;let i=null;if(e.nodes){let n;e.each(s=>{if(s.type===\"comment\"&&LO.test(s.text)){let a=/:\\s*autoplace/i.test(s.text),o=/no-autoplace/i.test(s.text);typeof n!=\"undefined\"?t.warn(\"Second Autoprefixer grid control comment was ignored. Autoprefixer applies control comments to the whole block, not to the next rules.\",{node:s}):a?n=\"autoplace\":o?n=!0:n=/on/i.test(s.text)}}),n!==void 0&&(i=n)}if(e.type===\"atrule\"&&e.name===\"supports\"){let n=e.params;n.includes(\"grid\")&&n.includes(\"auto\")&&(i=!1)}if(!e.nodes||i===null)if(e.parent){let n=this.gridStatus(e.parent,t);e.parent._autoprefixerSelfDisabled===!0?i=!1:i=n}else typeof this.prefixes.options.grid!=\"undefined\"?i=this.prefixes.options.grid:typeof m.env.AUTOPREFIXER_GRID!=\"undefined\"?m.env.AUTOPREFIXER_GRID===\"autoplace\"?i=\"autoplace\":i=!0:i=!1;return e._autoprefixerGridStatus=i,i}};hb.exports=db});var gb=x((h$,mb)=>{u();mb.exports={A:{A:{\"2\":\"K E F G A B JC\"},B:{\"1\":\"C L M H N D O P Q R S T U V W X Y Z a b c d e f g h i j n o p q r s t u v w x y z I\"},C:{\"1\":\"2 3 4 5 6 7 8 9 AB BB CB DB EB FB GB HB IB JB KB LB MB NB OB PB QB RB SB TB UB VB WB XB YB ZB aB bB cB 0B dB 1B eB fB gB hB iB jB kB lB mB nB oB m pB qB rB sB tB P Q R 2B S T U V W X Y Z a b c d e f g h i j n o p q r s t u v w x y z I uB 3B 4B\",\"2\":\"0 1 KC zB J K E F G A B C L M H N D O k l LC MC\"},D:{\"1\":\"8 9 AB BB CB DB EB FB GB HB IB JB KB LB MB NB OB PB QB RB SB TB UB VB WB XB YB ZB aB bB cB 0B dB 1B eB fB gB hB iB jB kB lB mB nB oB m pB qB rB sB tB P Q R S T U V W X Y Z a b c d e f g h i j n o p q r s t u v w x y z I uB 3B 4B\",\"2\":\"0 1 2 3 4 5 6 7 J K E F G A B C L M H N D O k l\"},E:{\"1\":\"G A B C L M H D RC 6B vB wB 7B SC TC 8B 9B xB AC yB BC CC DC EC FC GC UC\",\"2\":\"0 J K E F NC 5B OC PC QC\"},F:{\"1\":\"1 2 3 4 5 6 7 8 9 H N D O k l AB BB CB DB EB FB GB HB IB JB KB LB MB NB OB PB QB RB SB TB UB VB WB XB YB ZB aB bB cB dB eB fB gB hB iB jB kB lB mB nB oB m pB qB rB sB tB P Q R 2B S T U V W X Y Z a b c d e f g h i j wB\",\"2\":\"G B C VC WC XC YC vB HC ZC\"},G:{\"1\":\"D fC gC hC iC jC kC lC mC nC oC pC qC rC sC tC 8B 9B xB AC yB BC CC DC EC FC GC\",\"2\":\"F 5B aC IC bC cC dC eC\"},H:{\"1\":\"uC\"},I:{\"1\":\"I zC 0C\",\"2\":\"zB J vC wC xC yC IC\"},J:{\"2\":\"E A\"},K:{\"1\":\"m\",\"2\":\"A B C vB HC wB\"},L:{\"1\":\"I\"},M:{\"1\":\"uB\"},N:{\"2\":\"A B\"},O:{\"1\":\"xB\"},P:{\"1\":\"J k l 1C 2C 3C 4C 5C 6B 6C 7C 8C 9C AD yB BD CD DD\"},Q:{\"1\":\"7B\"},R:{\"1\":\"ED\"},S:{\"1\":\"FD GD\"}},B:4,C:\"CSS Feature Queries\"}});var vb=x((m$,wb)=>{u();function yb(r){return r[r.length-1]}var bb={parse(r){let e=[\"\"],t=[e];for(let i of r){if(i===\"(\"){e=[\"\"],yb(t).push(e),t.push(e);continue}if(i===\")\"){t.pop(),e=yb(t),e.push(\"\");continue}e[e.length-1]+=i}return t[0]},stringify(r){let e=\"\";for(let t of r){if(typeof t==\"object\"){e+=`(${bb.stringify(t)})`;continue}e+=t}return e}};wb.exports=bb});var Cb=x((g$,Ab)=>{u();var BO=gb(),{feature:FO}=(Ps(),Rs),{parse:jO}=$e(),zO=Mt(),lu=vb(),UO=He(),VO=_e(),xb=FO(BO),kb=[];for(let r in xb.stats){let e=xb.stats[r];for(let t in e){let i=e[t];/y/.test(i)&&kb.push(r+\" \"+t)}}var Sb=class{constructor(e,t){this.Prefixes=e,this.all=t}prefixer(){if(this.prefixerCache)return this.prefixerCache;let e=this.all.browsers.selected.filter(i=>kb.includes(i)),t=new zO(this.all.browsers.data,e,this.all.options);return this.prefixerCache=new this.Prefixes(this.all.data,t,this.all.options),this.prefixerCache}parse(e){let t=e.split(\":\"),i=t[0],n=t[1];return n||(n=\"\"),[i.trim(),n.trim()]}virtual(e){let[t,i]=this.parse(e),n=jO(\"a{}\").first;return n.append({prop:t,value:i,raws:{before:\"\"}}),n}prefixed(e){let t=this.virtual(e);if(this.disabled(t.first))return t.nodes;let i={warn:()=>null},n=this.prefixer().add[t.first.prop];n&&n.process&&n.process(t.first,i);for(let s of t.nodes){for(let a of this.prefixer().values(\"add\",t.first.prop))a.process(s);UO.save(this.all,s)}return t.nodes}isNot(e){return typeof e==\"string\"&&/not\\s*/i.test(e)}isOr(e){return typeof e==\"string\"&&/\\s*or\\s*/i.test(e)}isProp(e){return typeof e==\"object\"&&e.length===1&&typeof e[0]==\"string\"}isHack(e,t){return!new RegExp(`(\\\\(|\\\\s)${VO.escapeRegexp(t)}:`).test(e)}toRemove(e,t){let[i,n]=this.parse(e),s=this.all.unprefixed(i),a=this.all.cleaner();if(a.remove[i]&&a.remove[i].remove&&!this.isHack(t,s))return!0;for(let o of a.values(\"remove\",s))if(o.check(n))return!0;return!1}remove(e,t){let i=0;for(;itypeof t!=\"object\"?t:t.length===1&&typeof t[0]==\"object\"?this.cleanBrackets(t[0]):this.cleanBrackets(t))}convert(e){let t=[\"\"];for(let i of e)t.push([`${i.prop}: ${i.value}`]),t.push(\" or \");return t[t.length-1]=\"\",t}normalize(e){if(typeof e!=\"object\")return e;if(e=e.filter(t=>t!==\"\"),typeof e[0]==\"string\"){let t=e[0].trim();if(t.includes(\":\")||t===\"selector\"||t===\"not selector\")return[lu.stringify(e)]}return e.map(t=>this.normalize(t))}add(e,t){return e.map(i=>{if(this.isProp(i)){let n=this.prefixed(i[0]);return n.length>1?this.convert(n):i}return typeof i==\"object\"?this.add(i,t):i})}process(e){let t=lu.parse(e.params);t=this.normalize(t),t=this.remove(t,e.params),t=this.add(t,e.params),t=this.cleanBrackets(t),e.params=lu.stringify(t)}disabled(e){if(!this.all.options.grid&&(e.prop===\"display\"&&e.value.includes(\"grid\")||e.prop.includes(\"grid\")||e.prop===\"justify-items\"))return!0;if(this.all.options.flexbox===!1){if(e.prop===\"display\"&&e.value.includes(\"flex\"))return!0;let t=[\"order\",\"justify-content\",\"align-items\",\"align-content\"];if(e.prop.includes(\"flex\")||t.includes(e.prop))return!0}return!1}};Ab.exports=Sb});var Ob=x((y$,Eb)=>{u();var _b=class{constructor(e,t){this.prefix=t,this.prefixed=e.prefixed(this.prefix),this.regexp=e.regexp(this.prefix),this.prefixeds=e.possible().map(i=>[e.prefixed(i),e.regexp(i)]),this.unprefixed=e.name,this.nameRegexp=e.regexp()}isHack(e){let t=e.parent.index(e)+1,i=e.parent.nodes;for(;t{u();var{list:HO}=$e(),WO=Ob(),GO=wr(),QO=Mt(),YO=_e(),Tb=class extends GO{constructor(e,t,i){super(e,t,i);this.regexpCache=new Map}check(e){return e.selector.includes(this.name)?!!e.selector.match(this.regexp()):!1}prefixed(e){return this.name.replace(/^(\\W*)/,`$1${e}`)}regexp(e){if(!this.regexpCache.has(e)){let t=e?this.prefixed(e):this.name;this.regexpCache.set(e,new RegExp(`(^|[^:\"'=])${YO.escapeRegexp(t)}`,\"gi\"))}return this.regexpCache.get(e)}possible(){return QO.prefixes()}prefixeds(e){if(e._autoprefixerPrefixeds){if(e._autoprefixerPrefixeds[this.name])return e._autoprefixerPrefixeds}else e._autoprefixerPrefixeds={};let t={};if(e.selector.includes(\",\")){let n=HO.comma(e.selector).filter(s=>s.includes(this.name));for(let s of this.possible())t[s]=n.map(a=>this.replace(a,s)).join(\", \")}else for(let i of this.possible())t[i]=this.replace(e.selector,i);return e._autoprefixerPrefixeds[this.name]=t,e._autoprefixerPrefixeds}already(e,t,i){let n=e.parent.index(e)-1;for(;n>=0;){let s=e.parent.nodes[n];if(s.type!==\"rule\")return!1;let a=!1;for(let o in t[this.name]){let l=t[this.name][o];if(s.selector===l){if(i===o)return!0;a=!0;break}}if(!a)return!1;n-=1}return!1}replace(e,t){return e.replace(this.regexp(),`$1${this.prefixed(t)}`)}add(e,t){let i=this.prefixeds(e);if(this.already(e,i,t))return;let n=this.clone(e,{selector:i[this.name][t]});e.parent.insertBefore(e,n)}old(e){return new WO(this,e)}};Rb.exports=Tb});var Db=x((w$,Ib)=>{u();var KO=wr(),Pb=class extends KO{add(e,t){let i=t+e.name;if(e.parent.some(a=>a.name===i&&a.params===e.params))return;let s=this.clone(e,{name:i});return e.parent.insertBefore(e,s)}process(e){let t=this.parentPrefix(e);for(let i of this.prefixes)(!t||t===i)&&this.add(e,i)}};Ib.exports=Pb});var $b=x((v$,qb)=>{u();var XO=kr(),uu=class extends XO{prefixed(e){return e===\"-webkit-\"?\":-webkit-full-screen\":e===\"-moz-\"?\":-moz-full-screen\":`:${e}fullscreen`}};uu.names=[\":fullscreen\"];qb.exports=uu});var Mb=x((x$,Lb)=>{u();var ZO=kr(),fu=class extends ZO{possible(){return super.possible().concat([\"-moz- old\",\"-ms- old\"])}prefixed(e){return e===\"-webkit-\"?\"::-webkit-input-placeholder\":e===\"-ms-\"?\"::-ms-input-placeholder\":e===\"-ms- old\"?\":-ms-input-placeholder\":e===\"-moz- old\"?\":-moz-placeholder\":`::${e}placeholder`}};fu.names=[\"::placeholder\"];Lb.exports=fu});var Bb=x((k$,Nb)=>{u();var JO=kr(),cu=class extends JO{prefixed(e){return e===\"-ms-\"?\":-ms-input-placeholder\":`:${e}placeholder-shown`}};cu.names=[\":placeholder-shown\"];Nb.exports=cu});var jb=x((S$,Fb)=>{u();var eT=kr(),tT=_e(),pu=class extends eT{constructor(e,t,i){super(e,t,i);this.prefixes&&(this.prefixes=tT.uniq(this.prefixes.map(n=>\"-webkit-\")))}prefixed(e){return e===\"-webkit-\"?\"::-webkit-file-upload-button\":`::${e}file-selector-button`}};pu.names=[\"::file-selector-button\"];Fb.exports=pu});var Pe=x((A$,zb)=>{u();zb.exports=function(r){let e;return r===\"-webkit- 2009\"||r===\"-moz-\"?e=2009:r===\"-ms-\"?e=2012:r===\"-webkit-\"&&(e=\"final\"),r===\"-webkit- 2009\"&&(r=\"-webkit-\"),[e,r]}});var Wb=x((C$,Hb)=>{u();var Ub=$e().list,Vb=Pe(),rT=j(),Sr=class extends rT{prefixed(e,t){let i;return[i,t]=Vb(t),i===2009?t+\"box-flex\":super.prefixed(e,t)}normalize(){return\"flex\"}set(e,t){let i=Vb(t)[0];if(i===2009)return e.value=Ub.space(e.value)[0],e.value=Sr.oldValues[e.value]||e.value,super.set(e,t);if(i===2012){let n=Ub.space(e.value);n.length===3&&n[2]===\"0\"&&(e.value=n.slice(0,2).concat(\"0px\").join(\" \"))}return super.set(e,t)}};Sr.names=[\"flex\",\"box-flex\"];Sr.oldValues={auto:\"1\",none:\"0\"};Hb.exports=Sr});var Yb=x((_$,Qb)=>{u();var Gb=Pe(),iT=j(),du=class extends iT{prefixed(e,t){let i;return[i,t]=Gb(t),i===2009?t+\"box-ordinal-group\":i===2012?t+\"flex-order\":super.prefixed(e,t)}normalize(){return\"order\"}set(e,t){return Gb(t)[0]===2009&&/\\d/.test(e.value)?(e.value=(parseInt(e.value)+1).toString(),super.set(e,t)):super.set(e,t)}};du.names=[\"order\",\"flex-order\",\"box-ordinal-group\"];Qb.exports=du});var Xb=x((E$,Kb)=>{u();var nT=j(),hu=class extends nT{check(e){let t=e.value;return!t.toLowerCase().includes(\"alpha(\")&&!t.includes(\"DXImageTransform.Microsoft\")&&!t.includes(\"data:image/svg+xml\")}};hu.names=[\"filter\"];Kb.exports=hu});var Jb=x((O$,Zb)=>{u();var sT=j(),mu=class extends sT{insert(e,t,i,n){if(t!==\"-ms-\")return super.insert(e,t,i);let s=this.clone(e),a=e.prop.replace(/end$/,\"start\"),o=t+e.prop.replace(/end$/,\"span\");if(!e.parent.some(l=>l.prop===o)){if(s.prop=o,e.value.includes(\"span\"))s.value=e.value.replace(/span\\s/i,\"\");else{let l;if(e.parent.walkDecls(a,c=>{l=c}),l){let c=Number(e.value)-Number(l.value)+\"\";s.value=c}else e.warn(n,`Can not prefix ${e.prop} (${a} is not found)`)}e.cloneBefore(s)}}};mu.names=[\"grid-row-end\",\"grid-column-end\"];Zb.exports=mu});var tw=x((T$,ew)=>{u();var aT=j(),gu=class extends aT{check(e){return!e.value.split(/\\s+/).some(t=>{let i=t.toLowerCase();return i===\"reverse\"||i===\"alternate-reverse\"})}};gu.names=[\"animation\",\"animation-direction\"];ew.exports=gu});var iw=x((R$,rw)=>{u();var oT=Pe(),lT=j(),yu=class extends lT{insert(e,t,i){let n;if([n,t]=oT(t),n!==2009)return super.insert(e,t,i);let s=e.value.split(/\\s+/).filter(d=>d!==\"wrap\"&&d!==\"nowrap\"&&\"wrap-reverse\");if(s.length===0||e.parent.some(d=>d.prop===t+\"box-orient\"||d.prop===t+\"box-direction\"))return;let o=s[0],l=o.includes(\"row\")?\"horizontal\":\"vertical\",c=o.includes(\"reverse\")?\"reverse\":\"normal\",f=this.clone(e);return f.prop=t+\"box-orient\",f.value=l,this.needCascade(e)&&(f.raws.before=this.calcBefore(i,e,t)),e.parent.insertBefore(e,f),f=this.clone(e),f.prop=t+\"box-direction\",f.value=c,this.needCascade(e)&&(f.raws.before=this.calcBefore(i,e,t)),e.parent.insertBefore(e,f)}};yu.names=[\"flex-flow\",\"box-direction\",\"box-orient\"];rw.exports=yu});var sw=x((P$,nw)=>{u();var uT=Pe(),fT=j(),bu=class extends fT{normalize(){return\"flex\"}prefixed(e,t){let i;return[i,t]=uT(t),i===2009?t+\"box-flex\":i===2012?t+\"flex-positive\":super.prefixed(e,t)}};bu.names=[\"flex-grow\",\"flex-positive\"];nw.exports=bu});var ow=x((I$,aw)=>{u();var cT=Pe(),pT=j(),wu=class extends pT{set(e,t){if(cT(t)[0]!==2009)return super.set(e,t)}};wu.names=[\"flex-wrap\"];aw.exports=wu});var uw=x((D$,lw)=>{u();var dT=j(),Ar=Bt(),vu=class extends dT{insert(e,t,i,n){if(t!==\"-ms-\")return super.insert(e,t,i);let s=Ar.parse(e),[a,o]=Ar.translate(s,0,2),[l,c]=Ar.translate(s,1,3);[[\"grid-row\",a],[\"grid-row-span\",o],[\"grid-column\",l],[\"grid-column-span\",c]].forEach(([f,d])=>{Ar.insertDecl(e,f,d)}),Ar.warnTemplateSelectorNotFound(e,n),Ar.warnIfGridRowColumnExists(e,n)}};vu.names=[\"grid-area\"];lw.exports=vu});var cw=x((q$,fw)=>{u();var hT=j(),Bi=Bt(),xu=class extends hT{insert(e,t,i){if(t!==\"-ms-\")return super.insert(e,t,i);if(e.parent.some(a=>a.prop===\"-ms-grid-row-align\"))return;let[[n,s]]=Bi.parse(e);s?(Bi.insertDecl(e,\"grid-row-align\",n),Bi.insertDecl(e,\"grid-column-align\",s)):(Bi.insertDecl(e,\"grid-row-align\",n),Bi.insertDecl(e,\"grid-column-align\",n))}};xu.names=[\"place-self\"];fw.exports=xu});var dw=x(($$,pw)=>{u();var mT=j(),ku=class extends mT{check(e){let t=e.value;return!t.includes(\"/\")||t.includes(\"span\")}normalize(e){return e.replace(\"-start\",\"\")}prefixed(e,t){let i=super.prefixed(e,t);return t===\"-ms-\"&&(i=i.replace(\"-start\",\"\")),i}};ku.names=[\"grid-row-start\",\"grid-column-start\"];pw.exports=ku});var gw=x((L$,mw)=>{u();var hw=Pe(),gT=j(),Cr=class extends gT{check(e){return e.parent&&!e.parent.some(t=>t.prop&&t.prop.startsWith(\"grid-\"))}prefixed(e,t){let i;return[i,t]=hw(t),i===2012?t+\"flex-item-align\":super.prefixed(e,t)}normalize(){return\"align-self\"}set(e,t){let i=hw(t)[0];if(i===2012)return e.value=Cr.oldValues[e.value]||e.value,super.set(e,t);if(i===\"final\")return super.set(e,t)}};Cr.names=[\"align-self\",\"flex-item-align\"];Cr.oldValues={\"flex-end\":\"end\",\"flex-start\":\"start\"};mw.exports=Cr});var bw=x((M$,yw)=>{u();var yT=j(),bT=_e(),Su=class extends yT{constructor(e,t,i){super(e,t,i);this.prefixes&&(this.prefixes=bT.uniq(this.prefixes.map(n=>n===\"-ms-\"?\"-webkit-\":n)))}};Su.names=[\"appearance\"];yw.exports=Su});var xw=x((N$,vw)=>{u();var ww=Pe(),wT=j(),Au=class extends wT{normalize(){return\"flex-basis\"}prefixed(e,t){let i;return[i,t]=ww(t),i===2012?t+\"flex-preferred-size\":super.prefixed(e,t)}set(e,t){let i;if([i,t]=ww(t),i===2012||i===\"final\")return super.set(e,t)}};Au.names=[\"flex-basis\",\"flex-preferred-size\"];vw.exports=Au});var Sw=x((B$,kw)=>{u();var vT=j(),Cu=class extends vT{normalize(){return this.name.replace(\"box-image\",\"border\")}prefixed(e,t){let i=super.prefixed(e,t);return t===\"-webkit-\"&&(i=i.replace(\"border\",\"box-image\")),i}};Cu.names=[\"mask-border\",\"mask-border-source\",\"mask-border-slice\",\"mask-border-width\",\"mask-border-outset\",\"mask-border-repeat\",\"mask-box-image\",\"mask-box-image-source\",\"mask-box-image-slice\",\"mask-box-image-width\",\"mask-box-image-outset\",\"mask-box-image-repeat\"];kw.exports=Cu});var Cw=x((F$,Aw)=>{u();var xT=j(),lt=class extends xT{insert(e,t,i){let n=e.prop===\"mask-composite\",s;n?s=e.value.split(\",\"):s=e.value.match(lt.regexp)||[],s=s.map(c=>c.trim()).filter(c=>c);let a=s.length,o;if(a&&(o=this.clone(e),o.value=s.map(c=>lt.oldValues[c]||c).join(\", \"),s.includes(\"intersect\")&&(o.value+=\", xor\"),o.prop=t+\"mask-composite\"),n)return a?(this.needCascade(e)&&(o.raws.before=this.calcBefore(i,e,t)),e.parent.insertBefore(e,o)):void 0;let l=this.clone(e);return l.prop=t+l.prop,a&&(l.value=l.value.replace(lt.regexp,\"\")),this.needCascade(e)&&(l.raws.before=this.calcBefore(i,e,t)),e.parent.insertBefore(e,l),a?(this.needCascade(e)&&(o.raws.before=this.calcBefore(i,e,t)),e.parent.insertBefore(e,o)):e}};lt.names=[\"mask\",\"mask-composite\"];lt.oldValues={add:\"source-over\",subtract:\"source-out\",intersect:\"source-in\",exclude:\"xor\"};lt.regexp=new RegExp(`\\\\s+(${Object.keys(lt.oldValues).join(\"|\")})\\\\b(?!\\\\))\\\\s*(?=[,])`,\"ig\");Aw.exports=lt});var Ow=x((j$,Ew)=>{u();var _w=Pe(),kT=j(),_r=class extends kT{prefixed(e,t){let i;return[i,t]=_w(t),i===2009?t+\"box-align\":i===2012?t+\"flex-align\":super.prefixed(e,t)}normalize(){return\"align-items\"}set(e,t){let i=_w(t)[0];return(i===2009||i===2012)&&(e.value=_r.oldValues[e.value]||e.value),super.set(e,t)}};_r.names=[\"align-items\",\"flex-align\",\"box-align\"];_r.oldValues={\"flex-end\":\"end\",\"flex-start\":\"start\"};Ew.exports=_r});var Rw=x((z$,Tw)=>{u();var ST=j(),_u=class extends ST{set(e,t){return t===\"-ms-\"&&e.value===\"contain\"&&(e.value=\"element\"),super.set(e,t)}insert(e,t,i){if(!(e.value===\"all\"&&t===\"-ms-\"))return super.insert(e,t,i)}};_u.names=[\"user-select\"];Tw.exports=_u});var Dw=x((U$,Iw)=>{u();var Pw=Pe(),AT=j(),Eu=class extends AT{normalize(){return\"flex-shrink\"}prefixed(e,t){let i;return[i,t]=Pw(t),i===2012?t+\"flex-negative\":super.prefixed(e,t)}set(e,t){let i;if([i,t]=Pw(t),i===2012||i===\"final\")return super.set(e,t)}};Eu.names=[\"flex-shrink\",\"flex-negative\"];Iw.exports=Eu});var $w=x((V$,qw)=>{u();var CT=j(),Ou=class extends CT{prefixed(e,t){return`${t}column-${e}`}normalize(e){return e.includes(\"inside\")?\"break-inside\":e.includes(\"before\")?\"break-before\":\"break-after\"}set(e,t){return(e.prop===\"break-inside\"&&e.value===\"avoid-column\"||e.value===\"avoid-page\")&&(e.value=\"avoid\"),super.set(e,t)}insert(e,t,i){if(e.prop!==\"break-inside\")return super.insert(e,t,i);if(!(/region/i.test(e.value)||/page/i.test(e.value)))return super.insert(e,t,i)}};Ou.names=[\"break-inside\",\"page-break-inside\",\"column-break-inside\",\"break-before\",\"page-break-before\",\"column-break-before\",\"break-after\",\"page-break-after\",\"column-break-after\"];qw.exports=Ou});var Mw=x((H$,Lw)=>{u();var _T=j(),Tu=class extends _T{prefixed(e,t){return t+\"print-color-adjust\"}normalize(){return\"color-adjust\"}};Tu.names=[\"color-adjust\",\"print-color-adjust\"];Lw.exports=Tu});var Bw=x((W$,Nw)=>{u();var ET=j(),Er=class extends ET{insert(e,t,i){if(t===\"-ms-\"){let n=this.set(this.clone(e),t);this.needCascade(e)&&(n.raws.before=this.calcBefore(i,e,t));let s=\"ltr\";return e.parent.nodes.forEach(a=>{a.prop===\"direction\"&&(a.value===\"rtl\"||a.value===\"ltr\")&&(s=a.value)}),n.value=Er.msValues[s][e.value]||e.value,e.parent.insertBefore(e,n)}return super.insert(e,t,i)}};Er.names=[\"writing-mode\"];Er.msValues={ltr:{\"horizontal-tb\":\"lr-tb\",\"vertical-rl\":\"tb-rl\",\"vertical-lr\":\"tb-lr\"},rtl:{\"horizontal-tb\":\"rl-tb\",\"vertical-rl\":\"bt-rl\",\"vertical-lr\":\"bt-lr\"}};Nw.exports=Er});var jw=x((G$,Fw)=>{u();var OT=j(),Ru=class extends OT{set(e,t){return e.value=e.value.replace(/\\s+fill(\\s)/,\"$1\"),super.set(e,t)}};Ru.names=[\"border-image\"];Fw.exports=Ru});var Vw=x((Q$,Uw)=>{u();var zw=Pe(),TT=j(),Or=class extends TT{prefixed(e,t){let i;return[i,t]=zw(t),i===2012?t+\"flex-line-pack\":super.prefixed(e,t)}normalize(){return\"align-content\"}set(e,t){let i=zw(t)[0];if(i===2012)return e.value=Or.oldValues[e.value]||e.value,super.set(e,t);if(i===\"final\")return super.set(e,t)}};Or.names=[\"align-content\",\"flex-line-pack\"];Or.oldValues={\"flex-end\":\"end\",\"flex-start\":\"start\",\"space-between\":\"justify\",\"space-around\":\"distribute\"};Uw.exports=Or});var Ww=x((Y$,Hw)=>{u();var RT=j(),We=class extends RT{prefixed(e,t){return t===\"-moz-\"?t+(We.toMozilla[e]||e):super.prefixed(e,t)}normalize(e){return We.toNormal[e]||e}};We.names=[\"border-radius\"];We.toMozilla={};We.toNormal={};for(let r of[\"top\",\"bottom\"])for(let e of[\"left\",\"right\"]){let t=`border-${r}-${e}-radius`,i=`border-radius-${r}${e}`;We.names.push(t),We.names.push(i),We.toMozilla[t]=i,We.toNormal[i]=t}Hw.exports=We});var Qw=x((K$,Gw)=>{u();var PT=j(),Pu=class extends PT{prefixed(e,t){return e.includes(\"-start\")?t+e.replace(\"-block-start\",\"-before\"):t+e.replace(\"-block-end\",\"-after\")}normalize(e){return e.includes(\"-before\")?e.replace(\"-before\",\"-block-start\"):e.replace(\"-after\",\"-block-end\")}};Pu.names=[\"border-block-start\",\"border-block-end\",\"margin-block-start\",\"margin-block-end\",\"padding-block-start\",\"padding-block-end\",\"border-before\",\"border-after\",\"margin-before\",\"margin-after\",\"padding-before\",\"padding-after\"];Gw.exports=Pu});var Kw=x((X$,Yw)=>{u();var IT=j(),{parseTemplate:DT,warnMissedAreas:qT,getGridGap:$T,warnGridGap:LT,inheritGridGap:MT}=Bt(),Iu=class extends IT{insert(e,t,i,n){if(t!==\"-ms-\")return super.insert(e,t,i);if(e.parent.some(h=>h.prop===\"-ms-grid-rows\"))return;let s=$T(e),a=MT(e,s),{rows:o,columns:l,areas:c}=DT({decl:e,gap:a||s}),f=Object.keys(c).length>0,d=Boolean(o),p=Boolean(l);return LT({gap:s,hasColumns:p,decl:e,result:n}),qT(c,e,n),(d&&p||f)&&e.cloneBefore({prop:\"-ms-grid-rows\",value:o,raws:{}}),p&&e.cloneBefore({prop:\"-ms-grid-columns\",value:l,raws:{}}),e}};Iu.names=[\"grid-template\"];Yw.exports=Iu});var Zw=x((Z$,Xw)=>{u();var NT=j(),Du=class extends NT{prefixed(e,t){return t+e.replace(\"-inline\",\"\")}normalize(e){return e.replace(/(margin|padding|border)-(start|end)/,\"$1-inline-$2\")}};Du.names=[\"border-inline-start\",\"border-inline-end\",\"margin-inline-start\",\"margin-inline-end\",\"padding-inline-start\",\"padding-inline-end\",\"border-start\",\"border-end\",\"margin-start\",\"margin-end\",\"padding-start\",\"padding-end\"];Xw.exports=Du});var e0=x((J$,Jw)=>{u();var BT=j(),qu=class extends BT{check(e){return!e.value.includes(\"flex-\")&&e.value!==\"baseline\"}prefixed(e,t){return t+\"grid-row-align\"}normalize(){return\"align-self\"}};qu.names=[\"grid-row-align\"];Jw.exports=qu});var r0=x((eL,t0)=>{u();var FT=j(),Tr=class extends FT{keyframeParents(e){let{parent:t}=e;for(;t;){if(t.type===\"atrule\"&&t.name===\"keyframes\")return!0;({parent:t}=t)}return!1}contain3d(e){if(e.prop===\"transform-origin\")return!1;for(let t of Tr.functions3d)if(e.value.includes(`${t}(`))return!0;return!1}set(e,t){return e=super.set(e,t),t===\"-ms-\"&&(e.value=e.value.replace(/rotatez/gi,\"rotate\")),e}insert(e,t,i){if(t===\"-ms-\"){if(!this.contain3d(e)&&!this.keyframeParents(e))return super.insert(e,t,i)}else if(t===\"-o-\"){if(!this.contain3d(e))return super.insert(e,t,i)}else return super.insert(e,t,i)}};Tr.names=[\"transform\",\"transform-origin\"];Tr.functions3d=[\"matrix3d\",\"translate3d\",\"translateZ\",\"scale3d\",\"scaleZ\",\"rotate3d\",\"rotateX\",\"rotateY\",\"perspective\"];t0.exports=Tr});var s0=x((tL,n0)=>{u();var i0=Pe(),jT=j(),$u=class extends jT{normalize(){return\"flex-direction\"}insert(e,t,i){let n;if([n,t]=i0(t),n!==2009)return super.insert(e,t,i);if(e.parent.some(f=>f.prop===t+\"box-orient\"||f.prop===t+\"box-direction\"))return;let a=e.value,o,l;a===\"inherit\"||a===\"initial\"||a===\"unset\"?(o=a,l=a):(o=a.includes(\"row\")?\"horizontal\":\"vertical\",l=a.includes(\"reverse\")?\"reverse\":\"normal\");let c=this.clone(e);return c.prop=t+\"box-orient\",c.value=o,this.needCascade(e)&&(c.raws.before=this.calcBefore(i,e,t)),e.parent.insertBefore(e,c),c=this.clone(e),c.prop=t+\"box-direction\",c.value=l,this.needCascade(e)&&(c.raws.before=this.calcBefore(i,e,t)),e.parent.insertBefore(e,c)}old(e,t){let i;return[i,t]=i0(t),i===2009?[t+\"box-orient\",t+\"box-direction\"]:super.old(e,t)}};$u.names=[\"flex-direction\",\"box-direction\",\"box-orient\"];n0.exports=$u});var o0=x((rL,a0)=>{u();var zT=j(),Lu=class extends zT{check(e){return e.value===\"pixelated\"}prefixed(e,t){return t===\"-ms-\"?\"-ms-interpolation-mode\":super.prefixed(e,t)}set(e,t){return t!==\"-ms-\"?super.set(e,t):(e.prop=\"-ms-interpolation-mode\",e.value=\"nearest-neighbor\",e)}normalize(){return\"image-rendering\"}process(e,t){return super.process(e,t)}};Lu.names=[\"image-rendering\",\"interpolation-mode\"];a0.exports=Lu});var u0=x((iL,l0)=>{u();var UT=j(),VT=_e(),Mu=class extends UT{constructor(e,t,i){super(e,t,i);this.prefixes&&(this.prefixes=VT.uniq(this.prefixes.map(n=>n===\"-ms-\"?\"-webkit-\":n)))}};Mu.names=[\"backdrop-filter\"];l0.exports=Mu});var c0=x((nL,f0)=>{u();var HT=j(),WT=_e(),Nu=class extends HT{constructor(e,t,i){super(e,t,i);this.prefixes&&(this.prefixes=WT.uniq(this.prefixes.map(n=>n===\"-ms-\"?\"-webkit-\":n)))}check(e){return e.value.toLowerCase()===\"text\"}};Nu.names=[\"background-clip\"];f0.exports=Nu});var d0=x((sL,p0)=>{u();var GT=j(),QT=[\"none\",\"underline\",\"overline\",\"line-through\",\"blink\",\"inherit\",\"initial\",\"unset\"],Bu=class extends GT{check(e){return e.value.split(/\\s+/).some(t=>!QT.includes(t))}};Bu.names=[\"text-decoration\"];p0.exports=Bu});var g0=x((aL,m0)=>{u();var h0=Pe(),YT=j(),Rr=class extends YT{prefixed(e,t){let i;return[i,t]=h0(t),i===2009?t+\"box-pack\":i===2012?t+\"flex-pack\":super.prefixed(e,t)}normalize(){return\"justify-content\"}set(e,t){let i=h0(t)[0];if(i===2009||i===2012){let n=Rr.oldValues[e.value]||e.value;if(e.value=n,i!==2009||n!==\"distribute\")return super.set(e,t)}else if(i===\"final\")return super.set(e,t)}};Rr.names=[\"justify-content\",\"flex-pack\",\"box-pack\"];Rr.oldValues={\"flex-end\":\"end\",\"flex-start\":\"start\",\"space-between\":\"justify\",\"space-around\":\"distribute\"};m0.exports=Rr});var b0=x((oL,y0)=>{u();var KT=j(),Fu=class extends KT{set(e,t){let i=e.value.toLowerCase();return t===\"-webkit-\"&&!i.includes(\" \")&&i!==\"contain\"&&i!==\"cover\"&&(e.value=e.value+\" \"+e.value),super.set(e,t)}};Fu.names=[\"background-size\"];y0.exports=Fu});var v0=x((lL,w0)=>{u();var XT=j(),ju=Bt(),zu=class extends XT{insert(e,t,i){if(t!==\"-ms-\")return super.insert(e,t,i);let n=ju.parse(e),[s,a]=ju.translate(n,0,1);n[0]&&n[0].includes(\"span\")&&(a=n[0].join(\"\").replace(/\\D/g,\"\")),[[e.prop,s],[`${e.prop}-span`,a]].forEach(([l,c])=>{ju.insertDecl(e,l,c)})}};zu.names=[\"grid-row\",\"grid-column\"];w0.exports=zu});var S0=x((uL,k0)=>{u();var ZT=j(),{prefixTrackProp:x0,prefixTrackValue:JT,autoplaceGridItems:eR,getGridGap:tR,inheritGridGap:rR}=Bt(),iR=ou(),Uu=class extends ZT{prefixed(e,t){return t===\"-ms-\"?x0({prop:e,prefix:t}):super.prefixed(e,t)}normalize(e){return e.replace(/^grid-(rows|columns)/,\"grid-template-$1\")}insert(e,t,i,n){if(t!==\"-ms-\")return super.insert(e,t,i);let{parent:s,prop:a,value:o}=e,l=a.includes(\"rows\"),c=a.includes(\"columns\"),f=s.some(k=>k.prop===\"grid-template\"||k.prop===\"grid-template-areas\");if(f&&l)return!1;let d=new iR({options:{}}),p=d.gridStatus(s,n),h=tR(e);h=rR(e,h)||h;let b=l?h.row:h.column;(p===\"no-autoplace\"||p===!0)&&!f&&(b=null);let v=JT({value:o,gap:b});e.cloneBefore({prop:x0({prop:a,prefix:t}),value:v});let y=s.nodes.find(k=>k.prop===\"grid-auto-flow\"),w=\"row\";if(y&&!d.disabled(y,n)&&(w=y.value.trim()),p===\"autoplace\"){let k=s.nodes.find(E=>E.prop===\"grid-template-rows\");if(!k&&f)return;if(!k&&!f){e.warn(n,\"Autoplacement does not work without grid-template-rows property\");return}!s.nodes.find(E=>E.prop===\"grid-template-columns\")&&!f&&e.warn(n,\"Autoplacement does not work without grid-template-columns property\"),c&&!f&&eR(e,n,h,w)}}};Uu.names=[\"grid-template-rows\",\"grid-template-columns\",\"grid-rows\",\"grid-columns\"];k0.exports=Uu});var C0=x((fL,A0)=>{u();var nR=j(),Vu=class extends nR{check(e){return!e.value.includes(\"flex-\")&&e.value!==\"baseline\"}prefixed(e,t){return t+\"grid-column-align\"}normalize(){return\"justify-self\"}};Vu.names=[\"grid-column-align\"];A0.exports=Vu});var E0=x((cL,_0)=>{u();var sR=j(),Hu=class extends sR{prefixed(e,t){return t+\"scroll-chaining\"}normalize(){return\"overscroll-behavior\"}set(e,t){return e.value===\"auto\"?e.value=\"chained\":(e.value===\"none\"||e.value===\"contain\")&&(e.value=\"none\"),super.set(e,t)}};Hu.names=[\"overscroll-behavior\",\"scroll-chaining\"];_0.exports=Hu});var R0=x((pL,T0)=>{u();var aR=j(),{parseGridAreas:oR,warnMissedAreas:lR,prefixTrackProp:uR,prefixTrackValue:O0,getGridGap:fR,warnGridGap:cR,inheritGridGap:pR}=Bt();function dR(r){return r.trim().slice(1,-1).split(/[\"']\\s*[\"']?/g)}var Wu=class extends aR{insert(e,t,i,n){if(t!==\"-ms-\")return super.insert(e,t,i);let s=!1,a=!1,o=e.parent,l=fR(e);l=pR(e,l)||l,o.walkDecls(/-ms-grid-rows/,d=>d.remove()),o.walkDecls(/grid-template-(rows|columns)/,d=>{if(d.prop===\"grid-template-rows\"){a=!0;let{prop:p,value:h}=d;d.cloneBefore({prop:uR({prop:p,prefix:t}),value:O0({value:h,gap:l.row})})}else s=!0});let c=dR(e.value);s&&!a&&l.row&&c.length>1&&e.cloneBefore({prop:\"-ms-grid-rows\",value:O0({value:`repeat(${c.length}, auto)`,gap:l.row}),raws:{}}),cR({gap:l,hasColumns:s,decl:e,result:n});let f=oR({rows:c,gap:l});return lR(f,e,n),e}};Wu.names=[\"grid-template-areas\"];T0.exports=Wu});var I0=x((dL,P0)=>{u();var hR=j(),Gu=class extends hR{set(e,t){return t===\"-webkit-\"&&(e.value=e.value.replace(/\\s*(right|left)\\s*/i,\"\")),super.set(e,t)}};Gu.names=[\"text-emphasis-position\"];P0.exports=Gu});var q0=x((hL,D0)=>{u();var mR=j(),Qu=class extends mR{set(e,t){return e.prop===\"text-decoration-skip-ink\"&&e.value===\"auto\"?(e.prop=t+\"text-decoration-skip\",e.value=\"ink\",e):super.set(e,t)}};Qu.names=[\"text-decoration-skip-ink\",\"text-decoration-skip\"];D0.exports=Qu});var F0=x((mL,B0)=>{u();\"use strict\";B0.exports={wrap:$0,limit:L0,validate:M0,test:Yu,curry:gR,name:N0};function $0(r,e,t){var i=e-r;return((t-r)%i+i)%i+r}function L0(r,e,t){return Math.max(r,Math.min(e,t))}function M0(r,e,t,i,n){if(!Yu(r,e,t,i,n))throw new Error(t+\" is outside of range [\"+r+\",\"+e+\")\");return t}function Yu(r,e,t,i,n){return!(te||n&&t===e||i&&t===r)}function N0(r,e,t,i){return(t?\"(\":\"[\")+r+\",\"+e+(i?\")\":\"]\")}function gR(r,e,t,i){var n=N0.bind(null,r,e,t,i);return{wrap:$0.bind(null,r,e),limit:L0.bind(null,r,e),validate:function(s){return M0(r,e,s,t,i)},test:function(s){return Yu(r,e,s,t,i)},toString:n,name:n}}});var U0=x((gL,z0)=>{u();var Ku=Ms(),yR=F0(),bR=xr(),wR=He(),vR=_e(),j0=/top|left|right|bottom/gi,wt=class extends wR{replace(e,t){let i=Ku(e);for(let n of i.nodes)if(n.type===\"function\"&&n.value===this.name)if(n.nodes=this.newDirection(n.nodes),n.nodes=this.normalize(n.nodes),t===\"-webkit- old\"){if(!this.oldWebkit(n))return!1}else n.nodes=this.convertDirection(n.nodes),n.value=t+n.value;return i.toString()}replaceFirst(e,...t){return t.map(n=>n===\" \"?{type:\"space\",value:n}:{type:\"word\",value:n}).concat(e.slice(1))}normalizeUnit(e,t){return`${parseFloat(e)/t*360}deg`}normalize(e){if(!e[0])return e;if(/-?\\d+(.\\d+)?grad/.test(e[0].value))e[0].value=this.normalizeUnit(e[0].value,400);else if(/-?\\d+(.\\d+)?rad/.test(e[0].value))e[0].value=this.normalizeUnit(e[0].value,2*Math.PI);else if(/-?\\d+(.\\d+)?turn/.test(e[0].value))e[0].value=this.normalizeUnit(e[0].value,1);else if(e[0].value.includes(\"deg\")){let t=parseFloat(e[0].value);t=yR.wrap(0,360,t),e[0].value=`${t}deg`}return e[0].value===\"0deg\"?e=this.replaceFirst(e,\"to\",\" \",\"top\"):e[0].value===\"90deg\"?e=this.replaceFirst(e,\"to\",\" \",\"right\"):e[0].value===\"180deg\"?e=this.replaceFirst(e,\"to\",\" \",\"bottom\"):e[0].value===\"270deg\"&&(e=this.replaceFirst(e,\"to\",\" \",\"left\")),e}newDirection(e){if(e[0].value===\"to\"||(j0.lastIndex=0,!j0.test(e[0].value)))return e;e.unshift({type:\"word\",value:\"to\"},{type:\"space\",value:\" \"});for(let t=2;t0&&(e[0].value===\"to\"?this.fixDirection(e):e[0].value.includes(\"deg\")?this.fixAngle(e):this.isRadial(e)&&this.fixRadial(e)),e}fixDirection(e){e.splice(0,2);for(let t of e){if(t.type===\"div\")break;t.type===\"word\"&&(t.value=this.revertDirection(t.value))}}fixAngle(e){let t=e[0].value;t=parseFloat(t),t=Math.abs(450-t)%360,t=this.roundFloat(t,3),e[0].value=`${t}deg`}fixRadial(e){let t=[],i=[],n,s,a,o,l;for(o=0;o{u();var xR=xr(),kR=He();function V0(r){return new RegExp(`(^|[\\\\s,(])(${r}($|[\\\\s),]))`,\"gi\")}var Xu=class extends kR{regexp(){return this.regexpCache||(this.regexpCache=V0(this.name)),this.regexpCache}isStretch(){return this.name===\"stretch\"||this.name===\"fill\"||this.name===\"fill-available\"}replace(e,t){return t===\"-moz-\"&&this.isStretch()?e.replace(this.regexp(),\"$1-moz-available$3\"):t===\"-webkit-\"&&this.isStretch()?e.replace(this.regexp(),\"$1-webkit-fill-available$3\"):super.replace(e,t)}old(e){let t=e+this.name;return this.isStretch()&&(e===\"-moz-\"?t=\"-moz-available\":e===\"-webkit-\"&&(t=\"-webkit-fill-available\")),new xR(this.name,t,t,V0(t))}add(e,t){if(!(e.prop.includes(\"grid\")&&t!==\"-webkit-\"))return super.add(e,t)}};Xu.names=[\"max-content\",\"min-content\",\"fit-content\",\"fill\",\"fill-available\",\"stretch\"];H0.exports=Xu});var Y0=x((bL,Q0)=>{u();var G0=xr(),SR=He(),Zu=class extends SR{replace(e,t){return t===\"-webkit-\"?e.replace(this.regexp(),\"$1-webkit-optimize-contrast\"):t===\"-moz-\"?e.replace(this.regexp(),\"$1-moz-crisp-edges\"):super.replace(e,t)}old(e){return e===\"-webkit-\"?new G0(this.name,\"-webkit-optimize-contrast\"):e===\"-moz-\"?new G0(this.name,\"-moz-crisp-edges\"):super.old(e)}};Zu.names=[\"pixelated\"];Q0.exports=Zu});var X0=x((wL,K0)=>{u();var AR=He(),Ju=class extends AR{replace(e,t){let i=super.replace(e,t);return t===\"-webkit-\"&&(i=i.replace(/(\"[^\"]+\"|'[^']+')(\\s+\\d+\\w)/gi,\"url($1)$2\")),i}};Ju.names=[\"image-set\"];K0.exports=Ju});var J0=x((vL,Z0)=>{u();var CR=$e().list,_R=He(),ef=class extends _R{replace(e,t){return CR.space(e).map(i=>{if(i.slice(0,+this.name.length+1)!==this.name+\"(\")return i;let n=i.lastIndexOf(\")\"),s=i.slice(n+1),a=i.slice(this.name.length+1,n);if(t===\"-webkit-\"){let o=a.match(/\\d*.?\\d+%?/);o?(a=a.slice(o[0].length).trim(),a+=`, ${o[0]}`):a+=\", 0.5\"}return t+this.name+\"(\"+a+\")\"+s}).join(\" \")}};ef.names=[\"cross-fade\"];Z0.exports=ef});var tv=x((xL,ev)=>{u();var ER=Pe(),OR=xr(),TR=He(),tf=class extends TR{constructor(e,t){super(e,t);e===\"display-flex\"&&(this.name=\"flex\")}check(e){return e.prop===\"display\"&&e.value===this.name}prefixed(e){let t,i;return[t,e]=ER(e),t===2009?this.name===\"flex\"?i=\"box\":i=\"inline-box\":t===2012?this.name===\"flex\"?i=\"flexbox\":i=\"inline-flexbox\":t===\"final\"&&(i=this.name),e+i}replace(e,t){return this.prefixed(t)}old(e){let t=this.prefixed(e);if(!!t)return new OR(this.name,t)}};tf.names=[\"display-flex\",\"inline-flex\"];ev.exports=tf});var iv=x((kL,rv)=>{u();var RR=He(),rf=class extends RR{constructor(e,t){super(e,t);e===\"display-grid\"&&(this.name=\"grid\")}check(e){return e.prop===\"display\"&&e.value===this.name}};rf.names=[\"display-grid\",\"inline-grid\"];rv.exports=rf});var sv=x((SL,nv)=>{u();var PR=He(),nf=class extends PR{constructor(e,t){super(e,t);e===\"filter-function\"&&(this.name=\"filter\")}};nf.names=[\"filter\",\"filter-function\"];nv.exports=nf});var uv=x((AL,lv)=>{u();var av=Ni(),z=j(),ov=zy(),IR=ab(),DR=ou(),qR=Cb(),sf=Mt(),Pr=kr(),$R=Db(),ut=He(),Ir=_e(),LR=$b(),MR=Mb(),NR=Bb(),BR=jb(),FR=Wb(),jR=Yb(),zR=Xb(),UR=Jb(),VR=tw(),HR=iw(),WR=sw(),GR=ow(),QR=uw(),YR=cw(),KR=dw(),XR=gw(),ZR=bw(),JR=xw(),e5=Sw(),t5=Cw(),r5=Ow(),i5=Rw(),n5=Dw(),s5=$w(),a5=Mw(),o5=Bw(),l5=jw(),u5=Vw(),f5=Ww(),c5=Qw(),p5=Kw(),d5=Zw(),h5=e0(),m5=r0(),g5=s0(),y5=o0(),b5=u0(),w5=c0(),v5=d0(),x5=g0(),k5=b0(),S5=v0(),A5=S0(),C5=C0(),_5=E0(),E5=R0(),O5=I0(),T5=q0(),R5=U0(),P5=W0(),I5=Y0(),D5=X0(),q5=J0(),$5=tv(),L5=iv(),M5=sv();Pr.hack(LR);Pr.hack(MR);Pr.hack(NR);Pr.hack(BR);z.hack(FR);z.hack(jR);z.hack(zR);z.hack(UR);z.hack(VR);z.hack(HR);z.hack(WR);z.hack(GR);z.hack(QR);z.hack(YR);z.hack(KR);z.hack(XR);z.hack(ZR);z.hack(JR);z.hack(e5);z.hack(t5);z.hack(r5);z.hack(i5);z.hack(n5);z.hack(s5);z.hack(a5);z.hack(o5);z.hack(l5);z.hack(u5);z.hack(f5);z.hack(c5);z.hack(p5);z.hack(d5);z.hack(h5);z.hack(m5);z.hack(g5);z.hack(y5);z.hack(b5);z.hack(w5);z.hack(v5);z.hack(x5);z.hack(k5);z.hack(S5);z.hack(A5);z.hack(C5);z.hack(_5);z.hack(E5);z.hack(O5);z.hack(T5);ut.hack(R5);ut.hack(P5);ut.hack(I5);ut.hack(D5);ut.hack(q5);ut.hack($5);ut.hack(L5);ut.hack(M5);var af=new Map,Fi=class{constructor(e,t,i={}){this.data=e,this.browsers=t,this.options=i,[this.add,this.remove]=this.preprocess(this.select(this.data)),this.transition=new IR(this),this.processor=new DR(this)}cleaner(){if(this.cleanerCache)return this.cleanerCache;if(this.browsers.selected.length){let e=new sf(this.browsers.data,[]);this.cleanerCache=new Fi(this.data,e,this.options)}else return this;return this.cleanerCache}select(e){let t={add:{},remove:{}};for(let i in e){let n=e[i],s=n.browsers.map(l=>{let c=l.split(\" \");return{browser:`${c[0]} ${c[1]}`,note:c[2]}}),a=s.filter(l=>l.note).map(l=>`${this.browsers.prefix(l.browser)} ${l.note}`);a=Ir.uniq(a),s=s.filter(l=>this.browsers.isSelected(l.browser)).map(l=>{let c=this.browsers.prefix(l.browser);return l.note?`${c} ${l.note}`:c}),s=this.sort(Ir.uniq(s)),this.options.flexbox===\"no-2009\"&&(s=s.filter(l=>!l.includes(\"2009\")));let o=n.browsers.map(l=>this.browsers.prefix(l));n.mistakes&&(o=o.concat(n.mistakes)),o=o.concat(a),o=Ir.uniq(o),s.length?(t.add[i]=s,s.length!s.includes(l)))):t.remove[i]=o}return t}sort(e){return e.sort((t,i)=>{let n=Ir.removeNote(t).length,s=Ir.removeNote(i).length;return n===s?i.length-t.length:s-n})}preprocess(e){let t={selectors:[],\"@supports\":new qR(Fi,this)};for(let n in e.add){let s=e.add[n];if(n===\"@keyframes\"||n===\"@viewport\")t[n]=new $R(n,s,this);else if(n===\"@resolution\")t[n]=new ov(n,s,this);else if(this.data[n].selector)t.selectors.push(Pr.load(n,s,this));else{let a=this.data[n].props;if(a){let o=ut.load(n,s,this);for(let l of a)t[l]||(t[l]={values:[]}),t[l].values.push(o)}else{let o=t[n]&&t[n].values||[];t[n]=z.load(n,s,this),t[n].values=o}}}let i={selectors:[]};for(let n in e.remove){let s=e.remove[n];if(this.data[n].selector){let a=Pr.load(n,s);for(let o of s)i.selectors.push(a.old(o))}else if(n===\"@keyframes\"||n===\"@viewport\")for(let a of s){let o=`@${a}${n.slice(1)}`;i[o]={remove:!0}}else if(n===\"@resolution\")i[n]=new ov(n,s,this);else{let a=this.data[n].props;if(a){let o=ut.load(n,[],this);for(let l of s){let c=o.old(l);if(c)for(let f of a)i[f]||(i[f]={}),i[f].values||(i[f].values=[]),i[f].values.push(c)}}else for(let o of s){let l=this.decl(n).old(n,o);if(n===\"align-self\"){let c=t[n]&&t[n].prefixes;if(c){if(o===\"-webkit- 2009\"&&c.includes(\"-webkit-\"))continue;if(o===\"-webkit-\"&&c.includes(\"-webkit- 2009\"))continue}}for(let c of l)i[c]||(i[c]={}),i[c].remove=!0}}}return[t,i]}decl(e){return af.has(e)||af.set(e,z.load(e)),af.get(e)}unprefixed(e){let t=this.normalize(av.unprefixed(e));return t===\"flex-direction\"&&(t=\"flex-flow\"),t}normalize(e){return this.decl(e).normalize(e)}prefixed(e,t){return e=av.unprefixed(e),this.decl(e).prefixed(e,t)}values(e,t){let i=this[e],n=i[\"*\"]&&i[\"*\"].values,s=i[t]&&i[t].values;return n&&s?Ir.uniq(n.concat(s)):n||s||[]}group(e){let t=e.parent,i=t.index(e),{length:n}=t.nodes,s=this.unprefixed(e.prop),a=(o,l)=>{for(i+=o;i>=0&&i{u();fv.exports={\"backdrop-filter\":{feature:\"css-backdrop-filter\",browsers:[\"ios_saf 16.1\",\"ios_saf 16.3\",\"ios_saf 16.4\",\"ios_saf 16.5\",\"safari 16.5\"]},element:{props:[\"background\",\"background-image\",\"border-image\",\"mask\",\"list-style\",\"list-style-image\",\"content\",\"mask-image\"],feature:\"css-element-function\",browsers:[\"firefox 114\"]},\"user-select\":{mistakes:[\"-khtml-\"],feature:\"user-select-none\",browsers:[\"ios_saf 16.1\",\"ios_saf 16.3\",\"ios_saf 16.4\",\"ios_saf 16.5\",\"safari 16.5\"]},\"background-clip\":{feature:\"background-clip-text\",browsers:[\"and_chr 114\",\"and_uc 15.5\",\"chrome 109\",\"chrome 113\",\"chrome 114\",\"edge 114\",\"opera 99\",\"samsung 21\"]},hyphens:{feature:\"css-hyphens\",browsers:[\"ios_saf 16.1\",\"ios_saf 16.3\",\"ios_saf 16.4\",\"ios_saf 16.5\",\"safari 16.5\"]},fill:{props:[\"width\",\"min-width\",\"max-width\",\"height\",\"min-height\",\"max-height\",\"inline-size\",\"min-inline-size\",\"max-inline-size\",\"block-size\",\"min-block-size\",\"max-block-size\",\"grid\",\"grid-template\",\"grid-template-rows\",\"grid-template-columns\",\"grid-auto-columns\",\"grid-auto-rows\"],feature:\"intrinsic-width\",browsers:[\"and_chr 114\",\"and_uc 15.5\",\"chrome 109\",\"chrome 113\",\"chrome 114\",\"edge 114\",\"opera 99\",\"samsung 21\"]},\"fill-available\":{props:[\"width\",\"min-width\",\"max-width\",\"height\",\"min-height\",\"max-height\",\"inline-size\",\"min-inline-size\",\"max-inline-size\",\"block-size\",\"min-block-size\",\"max-block-size\",\"grid\",\"grid-template\",\"grid-template-rows\",\"grid-template-columns\",\"grid-auto-columns\",\"grid-auto-rows\"],feature:\"intrinsic-width\",browsers:[\"and_chr 114\",\"and_uc 15.5\",\"chrome 109\",\"chrome 113\",\"chrome 114\",\"edge 114\",\"opera 99\",\"samsung 21\"]},stretch:{props:[\"width\",\"min-width\",\"max-width\",\"height\",\"min-height\",\"max-height\",\"inline-size\",\"min-inline-size\",\"max-inline-size\",\"block-size\",\"min-block-size\",\"max-block-size\",\"grid\",\"grid-template\",\"grid-template-rows\",\"grid-template-columns\",\"grid-auto-columns\",\"grid-auto-rows\"],feature:\"intrinsic-width\",browsers:[\"firefox 114\"]},\"fit-content\":{props:[\"width\",\"min-width\",\"max-width\",\"height\",\"min-height\",\"max-height\",\"inline-size\",\"min-inline-size\",\"max-inline-size\",\"block-size\",\"min-block-size\",\"max-block-size\",\"grid\",\"grid-template\",\"grid-template-rows\",\"grid-template-columns\",\"grid-auto-columns\",\"grid-auto-rows\"],feature:\"intrinsic-width\",browsers:[\"firefox 114\"]},\"text-decoration-style\":{feature:\"text-decoration\",browsers:[\"ios_saf 16.1\",\"ios_saf 16.3\",\"ios_saf 16.4\",\"ios_saf 16.5\"]},\"text-decoration-color\":{feature:\"text-decoration\",browsers:[\"ios_saf 16.1\",\"ios_saf 16.3\",\"ios_saf 16.4\",\"ios_saf 16.5\"]},\"text-decoration-line\":{feature:\"text-decoration\",browsers:[\"ios_saf 16.1\",\"ios_saf 16.3\",\"ios_saf 16.4\",\"ios_saf 16.5\"]},\"text-decoration\":{feature:\"text-decoration\",browsers:[\"ios_saf 16.1\",\"ios_saf 16.3\",\"ios_saf 16.4\",\"ios_saf 16.5\"]},\"text-decoration-skip\":{feature:\"text-decoration\",browsers:[\"ios_saf 16.1\",\"ios_saf 16.3\",\"ios_saf 16.4\",\"ios_saf 16.5\"]},\"text-decoration-skip-ink\":{feature:\"text-decoration\",browsers:[\"ios_saf 16.1\",\"ios_saf 16.3\",\"ios_saf 16.4\",\"ios_saf 16.5\"]},\"text-size-adjust\":{feature:\"text-size-adjust\",browsers:[\"ios_saf 16.1\",\"ios_saf 16.3\",\"ios_saf 16.4\",\"ios_saf 16.5\"]},\"mask-clip\":{feature:\"css-masks\",browsers:[\"and_chr 114\",\"and_uc 15.5\",\"chrome 109\",\"chrome 113\",\"chrome 114\",\"edge 114\",\"opera 99\",\"samsung 21\"]},\"mask-composite\":{feature:\"css-masks\",browsers:[\"and_chr 114\",\"and_uc 15.5\",\"chrome 109\",\"chrome 113\",\"chrome 114\",\"edge 114\",\"opera 99\",\"samsung 21\"]},\"mask-image\":{feature:\"css-masks\",browsers:[\"and_chr 114\",\"and_uc 15.5\",\"chrome 109\",\"chrome 113\",\"chrome 114\",\"edge 114\",\"opera 99\",\"samsung 21\"]},\"mask-origin\":{feature:\"css-masks\",browsers:[\"and_chr 114\",\"and_uc 15.5\",\"chrome 109\",\"chrome 113\",\"chrome 114\",\"edge 114\",\"opera 99\",\"samsung 21\"]},\"mask-repeat\":{feature:\"css-masks\",browsers:[\"and_chr 114\",\"and_uc 15.5\",\"chrome 109\",\"chrome 113\",\"chrome 114\",\"edge 114\",\"opera 99\",\"samsung 21\"]},\"mask-border-repeat\":{feature:\"css-masks\",browsers:[\"and_chr 114\",\"and_uc 15.5\",\"chrome 109\",\"chrome 113\",\"chrome 114\",\"edge 114\",\"opera 99\",\"samsung 21\"]},\"mask-border-source\":{feature:\"css-masks\",browsers:[\"and_chr 114\",\"and_uc 15.5\",\"chrome 109\",\"chrome 113\",\"chrome 114\",\"edge 114\",\"opera 99\",\"samsung 21\"]},mask:{feature:\"css-masks\",browsers:[\"and_chr 114\",\"and_uc 15.5\",\"chrome 109\",\"chrome 113\",\"chrome 114\",\"edge 114\",\"opera 99\",\"samsung 21\"]},\"mask-position\":{feature:\"css-masks\",browsers:[\"and_chr 114\",\"and_uc 15.5\",\"chrome 109\",\"chrome 113\",\"chrome 114\",\"edge 114\",\"opera 99\",\"samsung 21\"]},\"mask-size\":{feature:\"css-masks\",browsers:[\"and_chr 114\",\"and_uc 15.5\",\"chrome 109\",\"chrome 113\",\"chrome 114\",\"edge 114\",\"opera 99\",\"samsung 21\"]},\"mask-border\":{feature:\"css-masks\",browsers:[\"and_chr 114\",\"and_uc 15.5\",\"chrome 109\",\"chrome 113\",\"chrome 114\",\"edge 114\",\"opera 99\",\"samsung 21\"]},\"mask-border-outset\":{feature:\"css-masks\",browsers:[\"and_chr 114\",\"and_uc 15.5\",\"chrome 109\",\"chrome 113\",\"chrome 114\",\"edge 114\",\"opera 99\",\"samsung 21\"]},\"mask-border-width\":{feature:\"css-masks\",browsers:[\"and_chr 114\",\"and_uc 15.5\",\"chrome 109\",\"chrome 113\",\"chrome 114\",\"edge 114\",\"opera 99\",\"samsung 21\"]},\"mask-border-slice\":{feature:\"css-masks\",browsers:[\"and_chr 114\",\"and_uc 15.5\",\"chrome 109\",\"chrome 113\",\"chrome 114\",\"edge 114\",\"opera 99\",\"samsung 21\"]},\"clip-path\":{feature:\"css-clip-path\",browsers:[\"samsung 21\"]},\"box-decoration-break\":{feature:\"css-boxdecorationbreak\",browsers:[\"and_chr 114\",\"and_uc 15.5\",\"chrome 109\",\"chrome 113\",\"chrome 114\",\"edge 114\",\"ios_saf 16.1\",\"ios_saf 16.3\",\"ios_saf 16.4\",\"ios_saf 16.5\",\"opera 99\",\"safari 16.5\",\"samsung 21\"]},appearance:{feature:\"css-appearance\",browsers:[\"samsung 21\"]},\"image-set\":{props:[\"background\",\"background-image\",\"border-image\",\"cursor\",\"mask\",\"mask-image\",\"list-style\",\"list-style-image\",\"content\"],feature:\"css-image-set\",browsers:[\"and_uc 15.5\",\"chrome 109\",\"samsung 21\"]},\"cross-fade\":{props:[\"background\",\"background-image\",\"border-image\",\"mask\",\"list-style\",\"list-style-image\",\"content\",\"mask-image\"],feature:\"css-cross-fade\",browsers:[\"and_chr 114\",\"and_uc 15.5\",\"chrome 109\",\"chrome 113\",\"chrome 114\",\"edge 114\",\"opera 99\",\"samsung 21\"]},isolate:{props:[\"unicode-bidi\"],feature:\"css-unicode-bidi\",browsers:[\"ios_saf 16.1\",\"ios_saf 16.3\",\"ios_saf 16.4\",\"ios_saf 16.5\",\"safari 16.5\"]},\"color-adjust\":{feature:\"css-color-adjust\",browsers:[\"chrome 109\",\"chrome 113\",\"chrome 114\",\"edge 114\",\"opera 99\"]}}});var dv=x((_L,pv)=>{u();pv.exports={}});var yv=x((EL,gv)=>{u();var N5=Yl(),{agents:B5}=(Ps(),Rs),of=Oy(),F5=Mt(),j5=uv(),z5=cv(),U5=dv(),hv={browsers:B5,prefixes:z5},mv=`\n Replace Autoprefixer \\`browsers\\` option to Browserslist config.\n Use \\`browserslist\\` key in \\`package.json\\` or \\`.browserslistrc\\` file.\n\n Using \\`browsers\\` option can cause errors. Browserslist config can\n be used for Babel, Autoprefixer, postcss-normalize and other tools.\n\n If you really need to use option, rename it to \\`overrideBrowserslist\\`.\n\n Learn more at:\n https://github.com/browserslist/browserslist#readme\n https://twitter.com/browserslist\n\n`;function V5(r){return Object.prototype.toString.apply(r)===\"[object Object]\"}var lf=new Map;function H5(r,e){e.browsers.selected.length!==0&&(e.add.selectors.length>0||Object.keys(e.add).length>2||r.warn(`Autoprefixer target browsers do not need any prefixes.You do not need Autoprefixer anymore.\nCheck your Browserslist config to be sure that your targets are set up correctly.\n\n Learn more at:\n https://github.com/postcss/autoprefixer#readme\n https://github.com/browserslist/browserslist#readme\n\n`))}gv.exports=Dr;function Dr(...r){let e;if(r.length===1&&V5(r[0])?(e=r[0],r=void 0):r.length===0||r.length===1&&!r[0]?r=void 0:r.length<=2&&(Array.isArray(r[0])||!r[0])?(e=r[1],r=r[0]):typeof r[r.length-1]==\"object\"&&(e=r.pop()),e||(e={}),e.browser)throw new Error(\"Change `browser` option to `overrideBrowserslist` in Autoprefixer\");if(e.browserslist)throw new Error(\"Change `browserslist` option to `overrideBrowserslist` in Autoprefixer\");e.overrideBrowserslist?r=e.overrideBrowserslist:e.browsers&&(typeof console!=\"undefined\"&&console.warn&&(of.red?console.warn(of.red(mv.replace(/`[^`]+`/g,n=>of.yellow(n.slice(1,-1))))):console.warn(mv)),r=e.browsers);let t={ignoreUnknownVersions:e.ignoreUnknownVersions,stats:e.stats,env:e.env};function i(n){let s=hv,a=new F5(s.browsers,r,n,t),o=a.selected.join(\", \")+JSON.stringify(e);return lf.has(o)||lf.set(o,new j5(s.prefixes,a,e)),lf.get(o)}return{postcssPlugin:\"autoprefixer\",prepare(n){let s=i({from:n.opts.from,env:e.env});return{OnceExit(a){H5(n,s),e.remove!==!1&&s.processor.remove(a,n),e.add!==!1&&s.processor.add(a,n)}}},info(n){return n=n||{},n.from=n.from||m.cwd(),U5(i(n))},options:e,browsers:r}}Dr.postcss=!0;Dr.data=hv;Dr.defaults=N5.defaults;Dr.info=()=>Dr().info()});var bv={};Ge(bv,{default:()=>W5});var W5,wv=P(()=>{u();W5=[]});var xv={};Ge(xv,{default:()=>G5});var vv,G5,kv=P(()=>{u();Xi();vv=pe(rn()),G5=St(vv.default.theme)});var Av={};Ge(Av,{default:()=>Q5});var Sv,Q5,Cv=P(()=>{u();Xi();Sv=pe(rn()),Q5=St(Sv.default)});u();\"use strict\";var Y5=vt(_y()),K5=vt($e()),X5=vt(yv()),Z5=vt((wv(),bv)),J5=vt((kv(),xv)),eP=vt((Cv(),Av)),tP=vt((Vs(),_f)),rP=vt((al(),sl)),iP=vt((sa(),sc));function vt(r){return r&&r.__esModule?r:{default:r}}console.warn(\"cdn.tailwindcss.com should not be used in production. To use Tailwind CSS in production, install it as a PostCSS plugin or use the Tailwind CLI: https://tailwindcss.com/docs/installation\");var Ns=\"tailwind\",uf=\"text/tailwindcss\",_v=\"/template.html\",Yt,Ev=!0,Ov=0,ff=new Set,cf,Tv=\"\",Rv=(r=!1)=>({get(e,t){return(!r||t===\"config\")&&typeof e[t]==\"object\"&&e[t]!==null?new Proxy(e[t],Rv()):e[t]},set(e,t,i){return e[t]=i,(!r||t===\"config\")&&pf(!0),!0}});window[Ns]=new Proxy({config:{},defaultTheme:J5.default,defaultConfig:eP.default,colors:tP.default,plugin:rP.default,resolveConfig:iP.default},Rv(!0));function Pv(r){cf.observe(r,{attributes:!0,attributeFilter:[\"type\"],characterData:!0,subtree:!0,childList:!0})}new MutationObserver(async r=>{let e=!1;if(!cf){cf=new MutationObserver(async()=>await pf(!0));for(let t of document.querySelectorAll(`style[type=\"${uf}\"]`))Pv(t)}for(let t of r)for(let i of t.addedNodes)i.nodeType===1&&i.tagName===\"STYLE\"&&i.getAttribute(\"type\")===uf&&(Pv(i),e=!0);await pf(e)}).observe(document.documentElement,{attributes:!0,attributeFilter:[\"class\"],childList:!0,subtree:!0});async function pf(r=!1){r&&(Ov++,ff.clear());let e=\"\";for(let i of document.querySelectorAll(`style[type=\"${uf}\"]`))e+=i.textContent;let t=new Set;for(let i of document.querySelectorAll(\"[class]\"))for(let n of i.classList)ff.has(n)||t.add(n);if(document.body&&(Ev||t.size>0||e!==Tv||!Yt||!Yt.isConnected)){for(let n of t)ff.add(n);Ev=!1,Tv=e,self[_v]=Array.from(t).join(\" \");let{css:i}=await(0,K5.default)([(0,Y5.default)({...window[Ns].config,_hash:Ov,content:{files:[_v],extract:{html:n=>n.split(\" \")}},plugins:[...Z5.default,...Array.isArray(window[Ns].config.plugins)?window[Ns].config.plugins:[]]}),(0,X5.default)({remove:!1})]).process(`@tailwind base;@tailwind components;@tailwind utilities;${e}`);(!Yt||!Yt.isConnected)&&(Yt=document.createElement(\"style\"),document.head.append(Yt)),Yt.textContent=i}}})();\n/*!\n * fill-range \n *\n * Copyright (c) 2014-present, Jon Schlinkert.\n * Licensed under the MIT License.\n */\n/*!\n * is-number \n *\n * Copyright (c) 2014-present, Jon Schlinkert.\n * Released under the MIT License.\n */\n/*!\n * to-regex-range \n *\n * Copyright (c) 2015-present, Jon Schlinkert.\n * Released under the MIT License.\n */\n/*! https://mths.be/cssesc v3.0.0 by @mathias */\n" + }, + "redirectURL": "", + "headersSize": -1, + "bodySize": -1, + "_transferSize": 126757, + "_error": null, + "_fetchedViaServiceWorker": false + }, + "serverIPAddress": "[2606:4700:20::ac43:440b]", + "startedDateTime": "2026-02-05T19:27:26.197Z", + "time": 65.57400000019697, + "timings": { + "blocked": 0.9709999983636662, + "dns": -1, + "ssl": -1, + "connect": -1, + "send": 0.131, + "wait": 26.581000001888256, + "receive": 37.89099999994505, + "_blocked_queueing": 0.46799999836366624, + "_workerStart": -1, + "_workerReady": -1, + "_workerFetchStart": -1, + "_workerRespondWithSettled": -1 + } + } + ] + } +} \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index c881140..0293217 100644 --- a/requirements.txt +++ b/requirements.txt @@ -18,6 +18,7 @@ rich==13.7.0 tenacity==8.2.3 loguru==0.7.2 httpx==0.27.0 +cryptography==41.0.7 # Web Frontend fastapi==0.115.0 diff --git a/scripts/backfill_usage_logs.py b/scripts/backfill_usage_logs.py new file mode 100644 index 0000000..e1bc3a4 --- /dev/null +++ b/scripts/backfill_usage_logs.py @@ -0,0 +1,145 @@ +"""Backfill api_usage_logs for all generated posts not yet tracked. + +Assumes per post: + - 1x gpt-4o call: ~20,000 tokens (14,000 prompt + 6,000 completion) + - 1x gpt-4o-mini call: ~17,000 tokens (13,000 prompt + 4,000 completion) + +Only processes posts older than 20 minutes whose created_at is not already +covered by an existing api_usage_log entry for the same customer. +""" +import asyncio +import sys +import os +from datetime import datetime, timedelta, timezone +from uuid import UUID + +# Add project root to path +sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + +from src.config import estimate_cost +from src.database.client import db + + +# ── Estimated token splits per post ────────────────────────────── +GPT4O_PROMPT = 14_000 +GPT4O_COMP = 6_000 +GPT4O_TOTAL = GPT4O_PROMPT + GPT4O_COMP # 20 000 + +MINI_PROMPT = 13_000 +MINI_COMP = 4_000 +MINI_TOTAL = MINI_PROMPT + MINI_COMP # 17 000 + + +async def main(): + cutoff = datetime.now(timezone.utc) - timedelta(minutes=20) + print(f"Cutoff: posts created before {cutoff.isoformat()}") + + # ── 1. Load all generated posts ────────────────────────────── + customers = await db.list_customers() + print(f"Found {len(customers)} customers") + + all_posts = [] + for cust in customers: + posts = await db.get_generated_posts(cust.id) + all_posts.extend(posts) + print(f"Found {len(all_posts)} total generated posts") + + # Filter to posts older than 20 min + eligible = [ + p for p in all_posts + if p.created_at and p.created_at.replace(tzinfo=timezone.utc) < cutoff + ] + print(f"{len(eligible)} posts older than 20 min") + + # ── 2. Load existing logs to avoid duplicates ──────────────── + try: + existing_logs = await asyncio.to_thread( + lambda: db.client.table("api_usage_logs") + .select("customer_id, created_at") + .eq("operation", "post_creation_backfill") + .execute() + ) + already_logged = set() + for log in existing_logs.data: + key = (log.get("customer_id"), log.get("created_at", "")[:19]) + already_logged.add(key) + print(f"{len(already_logged)} existing backfill entries found") + except Exception as e: + print(f"Could not read existing logs (table may be new): {e}") + already_logged = set() + + # ── 3. Build customer → user_id / company_id map ───────────── + cust_map = {} + for cust in customers: + cust_map[str(cust.id)] = { + "user_id": str(cust.user_id) if cust.user_id else None, + "company_id": str(cust.company_id) if cust.company_id else None, + } + + # ── 4. Insert two log rows per post ────────────────────────── + inserted = 0 + skipped = 0 + + for post in eligible: + cid = str(post.customer_id) + ts = post.created_at.isoformat()[:19] if post.created_at else "" + key = (cid, ts) + if key in already_logged: + skipped += 1 + continue + + ids = cust_map.get(cid, {}) + user_id = ids.get("user_id") + company_id = ids.get("company_id") + + base = { + "customer_id": cid, + "operation": "post_creation_backfill", + "created_at": post.created_at.isoformat() if post.created_at else None, + } + if user_id: + base["user_id"] = user_id + if company_id: + base["company_id"] = company_id + + # Row 1: gpt-4o + gpt4o_cost = estimate_cost("gpt-4o", GPT4O_PROMPT, GPT4O_COMP) + row_4o = { + **base, + "provider": "openai", + "model": "gpt-4o", + "prompt_tokens": GPT4O_PROMPT, + "completion_tokens": GPT4O_COMP, + "total_tokens": GPT4O_TOTAL, + "estimated_cost_usd": round(gpt4o_cost, 6), + } + + # Row 2: gpt-4o-mini + mini_cost = estimate_cost("gpt-4o-mini", MINI_PROMPT, MINI_COMP) + row_mini = { + **base, + "provider": "openai", + "model": "gpt-4o-mini", + "prompt_tokens": MINI_PROMPT, + "completion_tokens": MINI_COMP, + "total_tokens": MINI_TOTAL, + "estimated_cost_usd": round(mini_cost, 6), + } + + try: + await asyncio.to_thread( + lambda r1=row_4o, r2=row_mini: db.client.table("api_usage_logs") + .insert([r1, r2]).execute() + ) + inserted += 2 + name = post.topic_title[:40] if post.topic_title else "?" + print(f" + {name} (gpt-4o ${gpt4o_cost:.4f} + mini ${mini_cost:.4f})") + except Exception as e: + print(f" ! Error for post {post.id}: {e}") + + print(f"\nDone: {inserted} log rows inserted, {skipped} posts skipped (already backfilled)") + print(f"Estimated totals per post: gpt-4o ${estimate_cost('gpt-4o', GPT4O_PROMPT, GPT4O_COMP):.4f} + mini ${estimate_cost('gpt-4o-mini', MINI_PROMPT, MINI_COMP):.4f}") + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/scripts/generate_encryption_key.py b/scripts/generate_encryption_key.py new file mode 100644 index 0000000..a592377 --- /dev/null +++ b/scripts/generate_encryption_key.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 +""" +Generate a Fernet encryption key for token encryption. + +Usage: + python scripts/generate_encryption_key.py + +Add the output to your .env file as ENCRYPTION_KEY. +""" +from cryptography.fernet import Fernet + +if __name__ == "__main__": + key = Fernet.generate_key().decode() + print("=" * 70) + print("Generated Encryption Key:") + print("=" * 70) + print(key) + print("=" * 70) + print("\nAdd this to your .env file:") + print(f"ENCRYPTION_KEY={key}") + print("\n⚠️ Keep this key secure and never commit it to git!") diff --git a/scripts/setup_linkedin_auth.py b/scripts/setup_linkedin_auth.py new file mode 100755 index 0000000..fd5fd5d --- /dev/null +++ b/scripts/setup_linkedin_auth.py @@ -0,0 +1,171 @@ +#!/usr/bin/env python3 +""" +Script to configure LinkedIn (OIDC) authentication for self-hosted Supabase. +This script updates your .env file or docker-compose.yml with the necessary GoTrue environment variables. +""" + +import os +import sys +from pathlib import Path + +# ==================== CONFIGURATION ==================== +# TODO: Fill in your LinkedIn credentials below + +LINKEDIN_CLIENT_ID = "your-linkedin-client-id" +LINKEDIN_CLIENT_SECRET = "your-linkedin-client-secret" + +# Your Supabase instance URL (where your self-hosted instance is accessible) +SUPABASE_URL = "https://your-supabase-domain.com" # e.g., https://supabase.example.com + +# Path to your Supabase docker-compose directory (where .env or docker-compose.yml is located) +DOCKER_COMPOSE_DIR = "/path/to/your/supabase" # e.g., /home/user/supabase + +# ======================================================= + + +def validate_config(): + """Validate that all required configuration is set.""" + errors = [] + + if LINKEDIN_CLIENT_ID.startswith("your-"): + errors.append("LINKEDIN_CLIENT_ID") + if LINKEDIN_CLIENT_SECRET.startswith("your-"): + errors.append("LINKEDIN_CLIENT_SECRET") + if SUPABASE_URL.startswith("https://your-"): + errors.append("SUPABASE_URL") + if DOCKER_COMPOSE_DIR.startswith("/path/to/"): + errors.append("DOCKER_COMPOSE_DIR") + + if errors: + print("❌ Error: Please update the following configuration variables in this script:") + for var in errors: + print(f" - {var}") + sys.exit(1) + + +def find_env_file(): + """Find the .env file in the docker-compose directory.""" + compose_dir = Path(DOCKER_COMPOSE_DIR) + + if not compose_dir.exists(): + print(f"❌ Error: Directory not found: {DOCKER_COMPOSE_DIR}") + sys.exit(1) + + # Look for common .env file locations + env_files = [ + compose_dir / ".env", + compose_dir / "docker" / ".env", + compose_dir / ".env.local", + ] + + for env_file in env_files: + if env_file.exists(): + return env_file + + return None + + +def update_env_file(env_file): + """Update the .env file with LinkedIn OAuth configuration.""" + + # LinkedIn OAuth configuration for GoTrue + linkedin_config = f""" +# LinkedIn OAuth Configuration (added by setup script) +GOTRUE_EXTERNAL_LINKEDIN_ENABLED=true +GOTRUE_EXTERNAL_LINKEDIN_CLIENT_ID={LINKEDIN_CLIENT_ID} +GOTRUE_EXTERNAL_LINKEDIN_SECRET={LINKEDIN_CLIENT_SECRET} +GOTRUE_EXTERNAL_LINKEDIN_REDIRECT_URI={SUPABASE_URL}/auth/v1/callback +""" + + # Read existing content + if env_file: + with open(env_file, 'r') as f: + content = f.read() + + # Check if LinkedIn config already exists + if "GOTRUE_EXTERNAL_LINKEDIN_ENABLED" in content: + print(f"⚠️ LinkedIn OAuth configuration already exists in {env_file}") + print(" Please update it manually or remove the existing lines first.") + return False + + # Backup original file + backup_file = env_file.with_suffix('.env.backup') + with open(backup_file, 'w') as f: + f.write(content) + print(f"📋 Backup created: {backup_file}") + + # Append LinkedIn config + with open(env_file, 'a') as f: + f.write(linkedin_config) + + print(f"✅ LinkedIn OAuth configuration added to {env_file}") + return True + + return False + + +def create_env_snippet(): + """Create a snippet file if no .env file is found.""" + snippet_file = Path(DOCKER_COMPOSE_DIR) / "linkedin_oauth_snippet.env" + + linkedin_config = f"""# LinkedIn OAuth Configuration for GoTrue +# Add these lines to your .env file or docker-compose.yml environment section + +GOTRUE_EXTERNAL_LINKEDIN_ENABLED=true +GOTRUE_EXTERNAL_LINKEDIN_CLIENT_ID={LINKEDIN_CLIENT_ID} +GOTRUE_EXTERNAL_LINKEDIN_SECRET={LINKEDIN_CLIENT_SECRET} +GOTRUE_EXTERNAL_LINKEDIN_REDIRECT_URI={SUPABASE_URL}/auth/v1/callback +""" + + with open(snippet_file, 'w') as f: + f.write(linkedin_config) + + print(f"📄 Created configuration snippet: {snippet_file}") + print(" Copy these variables to your .env file or docker-compose.yml") + return True + + +def print_next_steps(): + """Print instructions for completing the setup.""" + print("\n" + "="*60) + print("✅ Configuration complete!") + print("="*60) + print("\nNext steps:\n") + print("1. Restart your Supabase services:") + print(f" cd {DOCKER_COMPOSE_DIR}") + print(" docker-compose down") + print(" docker-compose up -d") + print() + print("2. Add redirect URL in LinkedIn Developer Portal:") + print(f" {SUPABASE_URL}/auth/v1/callback") + print() + print("3. Test the authentication in your application") + print() + print("4. Check GoTrue logs for any errors:") + print(" docker-compose logs -f auth") + print() + + +def main(): + """Main function.""" + print("🔧 Configuring LinkedIn OAuth for self-hosted Supabase") + print("="*60) + + validate_config() + + env_file = find_env_file() + + if env_file: + print(f"📁 Found .env file: {env_file}") + if update_env_file(env_file): + print_next_steps() + else: + print("\n⚠️ Please update your configuration manually.") + else: + print("⚠️ No .env file found in the docker-compose directory") + create_env_snippet() + print_next_steps() + + +if __name__ == "__main__": + main() diff --git a/scripts/test_linkedin_setup.py b/scripts/test_linkedin_setup.py new file mode 100644 index 0000000..e004c59 --- /dev/null +++ b/scripts/test_linkedin_setup.py @@ -0,0 +1,131 @@ +#!/usr/bin/env python3 +""" +Test script to verify LinkedIn auto-posting setup. + +Usage: + python scripts/test_linkedin_setup.py +""" +import asyncio +import sys +from pathlib import Path + +# Add project root to path +sys.path.insert(0, str(Path(__file__).parent.parent)) + +from src.config import settings +from src.database.client import db + + +async def main(): + """Run setup checks.""" + print("=" * 70) + print("LinkedIn Auto-Posting Setup Verification") + print("=" * 70) + print() + + checks_passed = 0 + checks_total = 0 + + # Check 1: Environment variables + print("📋 Checking environment variables...") + checks_total += 4 + + if settings.linkedin_client_id: + print(" ✅ LINKEDIN_CLIENT_ID is set") + checks_passed += 1 + else: + print(" ❌ LINKEDIN_CLIENT_ID is missing") + + if settings.linkedin_client_secret: + print(" ✅ LINKEDIN_CLIENT_SECRET is set") + checks_passed += 1 + else: + print(" ❌ LINKEDIN_CLIENT_SECRET is missing") + + if settings.linkedin_redirect_uri: + print(" ✅ LINKEDIN_REDIRECT_URI is set") + checks_passed += 1 + print(f" URI: {settings.linkedin_redirect_uri}") + else: + print(" ❌ LINKEDIN_REDIRECT_URI is missing") + + if settings.encryption_key: + print(" ✅ ENCRYPTION_KEY is set") + checks_passed += 1 + else: + print(" ❌ ENCRYPTION_KEY is missing") + + print() + + # Check 2: Encryption + print("🔐 Testing encryption...") + checks_total += 1 + + try: + from src.utils.encryption import encrypt_token, decrypt_token + test_token = "test_access_token_12345" + encrypted = encrypt_token(test_token) + decrypted = decrypt_token(encrypted) + + if decrypted == test_token: + print(" ✅ Encryption/decryption working") + checks_passed += 1 + else: + print(" ❌ Encryption/decryption mismatch") + except Exception as e: + print(f" ❌ Encryption error: {e}") + + print() + + # Check 3: Database table + print("💾 Checking database schema...") + checks_total += 1 + + try: + # Try to query the table (will fail if it doesn't exist) + result = await asyncio.to_thread( + lambda: db.client.table("linkedin_accounts").select("id").limit(0).execute() + ) + print(" ✅ linkedin_accounts table exists") + checks_passed += 1 + except Exception as e: + print(f" ❌ linkedin_accounts table not found: {e}") + print(" Run: psql $DATABASE_URL -f config/migrate_add_linkedin_accounts.sql") + + print() + + # Check 4: LinkedIn service + print("🔧 Checking LinkedIn service...") + checks_total += 1 + + try: + from src.services.linkedin_service import linkedin_service + print(" ✅ LinkedIn service initialized") + checks_passed += 1 + except Exception as e: + print(f" ❌ LinkedIn service error: {e}") + + print() + + # Summary + print("=" * 70) + print(f"Summary: {checks_passed}/{checks_total} checks passed") + print("=" * 70) + + if checks_passed == checks_total: + print("✅ All checks passed! LinkedIn auto-posting is ready.") + print("\nNext steps:") + print("1. Restart your application") + print("2. Log in as an employee") + print("3. Go to Settings and connect your LinkedIn account") + print("4. Schedule a test post") + return 0 + else: + print("❌ Some checks failed. Please fix the issues above.") + print("\nSetup guide: See LINKEDIN_SETUP.md for detailed instructions") + return 1 + + +if __name__ == "__main__": + exit_code = asyncio.run(main()) + sys.exit(exit_code) diff --git a/src/agents/base.py b/src/agents/base.py index f9a8e0c..c00c8cb 100644 --- a/src/agents/base.py +++ b/src/agents/base.py @@ -1,12 +1,13 @@ """Base agent class.""" import asyncio from abc import ABC, abstractmethod -from typing import Any, Dict, Optional +from typing import Any, Dict, List, Optional, Tuple from openai import OpenAI import httpx from loguru import logger -from src.config import settings +from src.config import settings, estimate_cost +from src.database.client import db class BaseAgent(ABC): @@ -21,13 +22,59 @@ class BaseAgent(ABC): """ self.name = name self.openai_client = OpenAI(api_key=settings.openai_api_key) + self._usage_logs: List[Dict[str, Any]] = [] + self._user_id: Optional[str] = None + self._company_id: Optional[str] = None + self._operation: str = "unknown" logger.info(f"Initialized {name} agent") + def set_tracking_context( + self, + operation: str, + user_id: Optional[str] = None, + company_id: Optional[str] = None + ): + """Set context for usage tracking.""" + self._operation = operation + self._user_id = user_id + self._company_id = company_id + self._usage_logs = [] + @abstractmethod async def process(self, *args, **kwargs) -> Any: """Process the agent's task.""" pass + async def _log_usage(self, provider: str, model: str, prompt_tokens: int, completion_tokens: int, total_tokens: int): + """Log API usage to database.""" + cost = estimate_cost(model, prompt_tokens, completion_tokens) + usage = { + "provider": provider, + "model": model, + "operation": self._operation, + "prompt_tokens": prompt_tokens, + "completion_tokens": completion_tokens, + "total_tokens": total_tokens, + "estimated_cost_usd": cost + } + self._usage_logs.append(usage) + + try: + from uuid import UUID + await db.log_api_usage( + provider=provider, + model=model, + operation=self._operation, + prompt_tokens=prompt_tokens, + completion_tokens=completion_tokens, + total_tokens=total_tokens, + estimated_cost_usd=cost, + user_id=UUID(self._user_id) if self._user_id else None, + company_id=UUID(self._company_id) if self._company_id else None + ) + except Exception as e: + logger.warning(f"Failed to log usage to DB: {e}") + async def call_openai( self, system_prompt: str, @@ -74,6 +121,16 @@ class BaseAgent(ABC): result = response.choices[0].message.content logger.debug(f"[{self.name}] Received response (length: {len(result)})") + # Track usage + if response.usage: + await self._log_usage( + provider="openai", + model=model, + prompt_tokens=response.usage.prompt_tokens, + completion_tokens=response.usage.completion_tokens, + total_tokens=response.usage.total_tokens + ) + return result async def call_perplexity( @@ -117,4 +174,15 @@ class BaseAgent(ABC): content = result["choices"][0]["message"]["content"] logger.debug(f"[{self.name}] Received Perplexity response (length: {len(content)})") + # Track usage + usage = result.get("usage", {}) + if usage: + await self._log_usage( + provider="perplexity", + model=model, + prompt_tokens=usage.get("prompt_tokens", 0), + completion_tokens=usage.get("completion_tokens", 0), + total_tokens=usage.get("prompt_tokens", 0) + usage.get("completion_tokens", 0) + ) + return content diff --git a/src/agents/researcher.py b/src/agents/researcher.py index 746c4e6..2c6dd51 100644 --- a/src/agents/researcher.py +++ b/src/agents/researcher.py @@ -1,7 +1,7 @@ """Research agent using Perplexity.""" import json import random -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone from typing import Dict, Any, List from loguru import logger @@ -258,7 +258,7 @@ Gib deine Antwort im folgenden JSON-Format zurück: pain_points_text = ", ".join(pain_points) if pain_points else "Allgemeine Business-Probleme" # Current date for time-specific searches - today = datetime.now() + today = datetime.now(timezone.utc) date_str = today.strftime("%d. %B %Y") week_ago = (today - timedelta(days=7)).strftime("%d. %B %Y") diff --git a/src/agents/writer.py b/src/agents/writer.py index 2735f9e..1dcdc6c 100644 --- a/src/agents/writer.py +++ b/src/agents/writer.py @@ -27,7 +27,10 @@ class WriterAgent(BaseAgent): critic_result: Optional[Dict[str, Any]] = None, learned_lessons: Optional[Dict[str, Any]] = None, post_type: Any = None, - post_type_analysis: Optional[Dict[str, Any]] = None + post_type_analysis: Optional[Dict[str, Any]] = None, + user_thoughts: str = "", + selected_hook: str = "", + company_strategy: Optional[Dict[str, Any]] = None ) -> str: """ Write a LinkedIn post. @@ -42,6 +45,7 @@ class WriterAgent(BaseAgent): learned_lessons: Optional lessons learned from past critic feedback post_type: Optional PostType object for type-specific writing post_type_analysis: Optional analysis of the post type + company_strategy: Optional company strategy to consider during writing Returns: Written LinkedIn post @@ -58,12 +62,19 @@ class WriterAgent(BaseAgent): critic_result=critic_result, learned_lessons=learned_lessons, post_type=post_type, - post_type_analysis=post_type_analysis + post_type_analysis=post_type_analysis, + user_thoughts=user_thoughts, + selected_hook=selected_hook, + company_strategy=company_strategy ) else: logger.info(f"Writing initial post for topic: {topic.get('title', 'Unknown')}") if post_type: logger.info(f"Using post type: {post_type.name}") + if user_thoughts: + logger.info(f"Including user thoughts ({len(user_thoughts)} chars)") + if selected_hook: + logger.info(f"Using selected hook: {selected_hook[:50]}...") # Select example posts - use semantic matching if enabled selected_examples = self._select_example_posts(topic, example_posts, profile_analysis) @@ -76,7 +87,10 @@ class WriterAgent(BaseAgent): example_posts=selected_examples, learned_lessons=learned_lessons, post_type=post_type, - post_type_analysis=post_type_analysis + post_type_analysis=post_type_analysis, + user_thoughts=user_thoughts, + selected_hook=selected_hook, + company_strategy=company_strategy ) else: return await self._write_single_draft( @@ -85,7 +99,10 @@ class WriterAgent(BaseAgent): example_posts=selected_examples, learned_lessons=learned_lessons, post_type=post_type, - post_type_analysis=post_type_analysis + post_type_analysis=post_type_analysis, + user_thoughts=user_thoughts, + selected_hook=selected_hook, + company_strategy=company_strategy ) def _select_example_posts( @@ -164,10 +181,14 @@ class WriterAgent(BaseAgent): # If we still don't have enough, fill with top scored while len(selected) < 3 and len(selected) < len(example_posts): + found = False for item in scored_posts: if item["post"] not in selected: selected.append(item["post"]) + found = True break + if not found: + break # no more unique posts available logger.info(f"Selected {len(selected)} example posts via semantic matching") return selected @@ -212,7 +233,10 @@ class WriterAgent(BaseAgent): example_posts: List[str], learned_lessons: Optional[Dict[str, Any]] = None, post_type: Any = None, - post_type_analysis: Optional[Dict[str, Any]] = None + post_type_analysis: Optional[Dict[str, Any]] = None, + user_thoughts: str = "", + selected_hook: str = "", + company_strategy: Optional[Dict[str, Any]] = None ) -> str: """ Generate multiple drafts and select the best one. @@ -224,6 +248,7 @@ class WriterAgent(BaseAgent): learned_lessons: Lessons learned from past feedback post_type: Optional PostType object post_type_analysis: Optional post type analysis + company_strategy: Optional company strategy to consider Returns: Best selected draft @@ -231,7 +256,7 @@ class WriterAgent(BaseAgent): num_drafts = min(max(settings.writer_multi_draft_count, 2), 5) # Clamp between 2-5 logger.info(f"Generating {num_drafts} drafts for selection") - system_prompt = self._get_system_prompt(profile_analysis, example_posts, learned_lessons, post_type, post_type_analysis) + system_prompt = self._get_system_prompt(profile_analysis, example_posts, learned_lessons, post_type, post_type_analysis, company_strategy) # Generate drafts in parallel with different temperatures/approaches draft_configs = [ @@ -244,7 +269,7 @@ class WriterAgent(BaseAgent): # Create draft tasks async def generate_draft(config: Dict, draft_num: int) -> Dict[str, Any]: - user_prompt = self._get_user_prompt_for_draft(topic, draft_num, config["approach"]) + user_prompt = self._get_user_prompt_for_draft(topic, draft_num, config["approach"], user_thoughts, selected_hook) try: draft = await self.call_openai( system_prompt=system_prompt, @@ -286,7 +311,9 @@ class WriterAgent(BaseAgent): self, topic: Dict[str, Any], draft_num: int, - approach: str + approach: str, + user_thoughts: str = "", + selected_hook: str = "" ) -> str: """Get user prompt with slight variations for different drafts.""" # Different emphasis for each draft @@ -305,8 +332,11 @@ class WriterAgent(BaseAgent): if topic.get('angle'): angle_section = f"\n**ANGLE/PERSPEKTIVE:**\n{topic.get('angle')}\n" + # User-selected hook takes priority hook_section = "" - if topic.get('hook_idea'): + if selected_hook: + hook_section = f"\n**HOOK (VERWENDE DIESEN ALS EINSTIEG!):**\n\"{selected_hook}\"\n" + elif topic.get('hook_idea'): hook_section = f"\n**HOOK-IDEE (als Inspiration):**\n\"{topic.get('hook_idea')}\"\n" facts_section = "" @@ -318,6 +348,34 @@ class WriterAgent(BaseAgent): if topic.get('why_this_person'): why_section = f"\n**WARUM DU DARÜBER SCHREIBEN SOLLTEST:**\n{topic.get('why_this_person')}\n" + # User thoughts section + thoughts_section = "" + if user_thoughts: + thoughts_section = f""" +**PERSÖNLICHE GEDANKEN (UNBEDINGT einbauen!):** +{user_thoughts} + +Diese Gedanken MÜSSEN im Post verarbeitet werden! +""" + + # Adjust task based on hook + if selected_hook: + task_text = """**AUFGABE:** +Schreibe einen authentischen LinkedIn-Post, der: +1. Mit dem VORGEGEBENEN HOOK beginnt (exakt oder leicht angepasst) +2. Den Fakt/das Thema aufgreift und Mehrwert bietet +3. Die persönlichen Gedanken organisch einbaut +4. Die Key Facts verwendet wo es passt +5. Mit einem passenden CTA endet""" + else: + task_text = """**AUFGABE:** +Schreibe einen authentischen LinkedIn-Post, der: +1. Mit einem STARKEN, unerwarteten Hook beginnt (nutze die Hook-Idee als Inspiration, NICHT wörtlich!) +2. Den Fakt/das Thema aufgreift und Mehrwert bietet +3. Die persönlichen Gedanken organisch einbaut (falls vorhanden) +4. Die Key Facts einbaut wo es passt +5. Mit einem passenden CTA endet""" + return f"""Schreibe einen LinkedIn-Post zu folgendem Thema: **THEMA:** {topic.get('title', 'Unbekanntes Thema')} @@ -326,26 +384,19 @@ class WriterAgent(BaseAgent): {angle_section}{hook_section} **KERN-FAKT / INHALT:** {topic.get('fact', topic.get('description', ''))} -{facts_section} +{facts_section}{thoughts_section} **WARUM RELEVANT:** {topic.get('relevance', 'Aktuelles Thema für die Zielgruppe')} {why_section} **DEIN ANSATZ FÜR DIESEN ENTWURF ({approach}):** {emphasis} -**AUFGABE:** -Schreibe einen authentischen LinkedIn-Post, der: -1. Mit einem STARKEN, unerwarteten Hook beginnt (nutze die Hook-Idee als Inspiration, NICHT wörtlich!) -2. Den Fakt/das Thema aufgreift und Mehrwert bietet -3. Die Key Facts einbaut wo es passt -4. Eine persönliche Note oder Meinung enthält -5. Mit einem passenden CTA endet +{task_text} WICHTIG: - Vermeide KI-typische Formulierungen ("In der heutigen Zeit", "Tauchen Sie ein", etc.) - Schreibe natürlich und menschlich - Der Post soll SOFORT 85+ Punkte im Review erreichen -- Die Hook-Idee ist nur INSPIRATION - mach etwas Eigenes daraus! Gib NUR den fertigen Post zurück.""" @@ -446,7 +497,10 @@ Analysiere jeden Entwurf kurz und wähle den besten. Antworte im JSON-Format: critic_result: Optional[Dict[str, Any]] = None, learned_lessons: Optional[Dict[str, Any]] = None, post_type: Any = None, - post_type_analysis: Optional[Dict[str, Any]] = None + post_type_analysis: Optional[Dict[str, Any]] = None, + user_thoughts: str = "", + selected_hook: str = "", + company_strategy: Optional[Dict[str, Any]] = None ) -> str: """Write a single draft (original behavior).""" # Select examples if not already selected @@ -461,8 +515,8 @@ Analysiere jeden Entwurf kurz und wähle den besten. Antworte im JSON-Format: elif len(selected_examples) > 3: selected_examples = random.sample(selected_examples, 3) - system_prompt = self._get_system_prompt(profile_analysis, selected_examples, learned_lessons, post_type, post_type_analysis) - user_prompt = self._get_user_prompt(topic, feedback, previous_version, critic_result) + system_prompt = self._get_system_prompt(profile_analysis, selected_examples, learned_lessons, post_type, post_type_analysis, company_strategy) + user_prompt = self._get_user_prompt(topic, feedback, previous_version, critic_result, user_thoughts, selected_hook) # Lower temperature for more consistent style matching post = await self.call_openai( @@ -481,7 +535,8 @@ Analysiere jeden Entwurf kurz und wähle den besten. Antworte im JSON-Format: example_posts: List[str] = None, learned_lessons: Optional[Dict[str, Any]] = None, post_type: Any = None, - post_type_analysis: Optional[Dict[str, Any]] = None + post_type_analysis: Optional[Dict[str, Any]] = None, + company_strategy: Optional[Dict[str, Any]] = None ) -> str: """Get system prompt for writer - orientiert an bewährten n8n-Prompts.""" # Extract key profile information @@ -670,16 +725,65 @@ Vermeide IMMER diese KI-typischen Muster: - Zu perfekte, glatte Formulierungen - echte Menschen schreiben mit Ecken und Kanten {lessons_section} {post_type_section} +{self._get_company_strategy_section(company_strategy)} DEIN AUFTRAG: Schreibe den Post so, dass er für die Zielgruppe ({audience.get('target_audience', 'Professionals')}) einen klaren Mehrwert bietet und ihre Pain Points ({pain_points_str}) adressiert. Mach die Persönlichkeit des linguistischen Fingerabdrucks spürbar. Beginne DIREKT mit dem Hook. Keine einleitenden Sätze, kein "Hier ist der Post".""" + def _get_company_strategy_section(self, company_strategy: Optional[Dict[str, Any]] = None) -> str: + """Build the company strategy section for the system prompt.""" + if not company_strategy: + return "" + + # Extract strategy elements + mission = company_strategy.get("mission", "") + vision = company_strategy.get("vision", "") + brand_voice = company_strategy.get("brand_voice", "") + tone_guidelines = company_strategy.get("tone_guidelines", "") + content_pillars = company_strategy.get("content_pillars", []) + target_audience = company_strategy.get("target_audience", "") + dos = company_strategy.get("dos", []) + donts = company_strategy.get("donts", []) + + # Build section only if there's meaningful content + if not any([mission, vision, brand_voice, content_pillars, dos, donts]): + return "" + + section = """ + +8. UNTERNEHMENSSTRATEGIE (WICHTIG - IMMER BEACHTEN!): + +Der Post muss zur Unternehmensstrategie passen, aber authentisch im Stil des Autors bleiben! +""" + if mission: + section += f"\nMISSION: {mission}" + if vision: + section += f"\nVISION: {vision}" + if brand_voice: + section += f"\nBRAND VOICE: {brand_voice}" + if tone_guidelines: + section += f"\nTONE GUIDELINES: {tone_guidelines}" + if content_pillars: + section += f"\nCONTENT PILLARS: {', '.join(content_pillars)}" + if target_audience: + section += f"\nZIELGRUPPE: {target_audience}" + if dos: + section += f"\n\nDO's (empfohlen):\n" + "\n".join([f" + {d}" for d in dos]) + if donts: + section += f"\n\nDON'Ts (vermeiden):\n" + "\n".join([f" - {d}" for d in donts]) + + section += "\n\nWICHTIG: Integriere die Unternehmenswerte subtil - der Post soll nicht wie eine Werbung klingen!" + + return section + def _get_user_prompt( self, topic: Dict[str, Any], feedback: Optional[str] = None, previous_version: Optional[str] = None, - critic_result: Optional[Dict[str, Any]] = None + critic_result: Optional[Dict[str, Any]] = None, + user_thoughts: str = "", + selected_hook: str = "" ) -> str: """Get user prompt for writer.""" if feedback and previous_version: @@ -727,8 +831,11 @@ Gib NUR den überarbeiteten Post zurück - keine Kommentare.""" if topic.get('angle'): angle_section = f"\n**ANGLE/PERSPEKTIVE:**\n{topic.get('angle')}\n" + # User-selected hook takes priority over topic hook_idea hook_section = "" - if topic.get('hook_idea'): + if selected_hook: + hook_section = f"\n**HOOK (VERWENDE DIESEN ALS EINSTIEG!):**\n\"{selected_hook}\"\n" + elif topic.get('hook_idea'): hook_section = f"\n**HOOK-IDEE (als Inspiration):**\n\"{topic.get('hook_idea')}\"\n" facts_section = "" @@ -736,6 +843,34 @@ Gib NUR den überarbeiteten Post zurück - keine Kommentare.""" if key_facts and isinstance(key_facts, list) and len(key_facts) > 0: facts_section = "\n**KEY FACTS (nutze diese!):**\n" + "\n".join([f"- {f}" for f in key_facts]) + "\n" + # User thoughts section + thoughts_section = "" + if user_thoughts: + thoughts_section = f""" +**PERSÖNLICHE GEDANKEN (UNBEDINGT einbauen!):** +{user_thoughts} + +Diese Gedanken MÜSSEN im Post verarbeitet werden - sie spiegeln die echte Meinung der Person wider! +""" + + # Adjust task based on whether hook is provided + if selected_hook: + task_section = """**AUFGABE:** +Schreibe einen authentischen LinkedIn-Post, der: +1. Mit dem VORGEGEBENEN HOOK beginnt (exakt oder leicht angepasst) +2. Den Fakt/das Thema aufgreift und Mehrwert bietet +3. Die persönlichen Gedanken organisch einbaut +4. Die Key Facts verwendet wo es passt +5. Mit einem passenden CTA endet""" + else: + task_section = """**AUFGABE:** +Schreibe einen authentischen LinkedIn-Post, der: +1. Mit einem STARKEN, unerwarteten Hook beginnt (nutze Hook-Idee als Inspiration!) +2. Den Fakt/das Thema aufgreift und Mehrwert bietet +3. Die persönlichen Gedanken organisch einbaut (falls vorhanden) +4. Die Key Facts verwendet wo es passt +5. Mit einem passenden CTA endet""" + return f"""Schreibe einen LinkedIn-Post zu folgendem Thema: **THEMA:** {topic.get('title', 'Unbekanntes Thema')} @@ -744,17 +879,11 @@ Gib NUR den überarbeiteten Post zurück - keine Kommentare.""" {angle_section}{hook_section} **KERN-FAKT / INHALT:** {topic.get('fact', topic.get('description', ''))} -{facts_section} +{facts_section}{thoughts_section} **WARUM RELEVANT:** {topic.get('relevance', 'Aktuelles Thema für die Zielgruppe')} -**AUFGABE:** -Schreibe einen authentischen LinkedIn-Post, der: -1. Mit einem STARKEN, unerwarteten Hook beginnt (nutze Hook-Idee als Inspiration!) -2. Den Fakt/das Thema aufgreift und Mehrwert bietet -3. Die Key Facts einbaut wo es passt -4. Eine persönliche Note oder Meinung enthält -5. Mit einem passenden CTA endet +{task_section} WICHTIG: - Vermeide KI-typische Formulierungen ("In der heutigen Zeit", "Tauchen Sie ein", etc.) @@ -762,3 +891,263 @@ WICHTIG: - Der Post soll SOFORT 85+ Punkte im Review erreichen Gib NUR den fertigen Post zurück.""" + + async def generate_hooks( + self, + topic: Dict[str, Any], + profile_analysis: Dict[str, Any], + user_thoughts: str = "", + post_type: Any = None + ) -> List[Dict[str, str]]: + """ + Generate 4 different hook options for a topic. + + Args: + topic: Topic dictionary with title, fact, etc. + profile_analysis: Profile analysis for style matching + user_thoughts: User's personal thoughts about the topic + post_type: Optional PostType object + + Returns: + List of {"hook": "...", "style": "..."} dictionaries + """ + logger.info(f"Generating hooks for topic: {topic.get('title', 'Unknown')}") + + # Extract style info from profile + tone = profile_analysis.get('tone_analysis', {}).get('primary_tone', 'Professionell') + energy = profile_analysis.get('linguistic_fingerprint', {}).get('energy_level', 7) + address = profile_analysis.get('writing_style', {}).get('form_of_address', 'Du') + hook_phrases = profile_analysis.get('phrase_library', {}).get('hook_phrases', []) + + # Build example hooks section + example_hooks = "" + if hook_phrases: + example_hooks = "\n\nBeispiel-Hooks dieser Person (zur Inspiration):\n" + "\n".join([f"- {h}" for h in hook_phrases[:5]]) + + system_prompt = f"""Du bist ein Hook-Spezialist für LinkedIn Posts. Deine Aufgabe ist es, 4 verschiedene, aufmerksamkeitsstarke Hooks zu generieren. + +STIL DER PERSON: +- Tonalität: {tone} +- Energie-Level: {energy}/10 (höher = emotionaler, niedriger = sachlicher) +- Ansprache: {address} +{example_hooks} + +GENERIERE 4 VERSCHIEDENE HOOKS: +1. **Provokant** - Eine kontroverse These oder überraschende Aussage +2. **Storytelling** - Beginn einer persönlichen Geschichte oder Anekdote +3. **Fakten-basiert** - Eine überraschende Statistik oder Fakt +4. **Neugier-weckend** - Eine Frage oder ein Cliffhanger + +REGELN: +- Jeder Hook sollte 1-2 Sätze lang sein +- Hooks müssen zum Energie-Level und Ton der Person passen +- Keine KI-typischen Phrasen ("In der heutigen Zeit", "Stellen Sie sich vor") +- Die Hooks sollen SOFORT Aufmerksamkeit erregen +- Bei Energie 8+ darf es emotional/leidenschaftlich sein +- Bei Energie 5-7 eher sachlich-professionell + +Antworte im JSON-Format: +{{"hooks": [ + {{"hook": "Der Hook-Text hier", "style": "Provokant"}}, + {{"hook": "Der Hook-Text hier", "style": "Storytelling"}}, + {{"hook": "Der Hook-Text hier", "style": "Fakten-basiert"}}, + {{"hook": "Der Hook-Text hier", "style": "Neugier-weckend"}} +]}}""" + + # Build user prompt + thoughts_section = "" + if user_thoughts: + thoughts_section = f"\n\nPERSÖNLICHE GEDANKEN DER PERSON ZUM THEMA:\n{user_thoughts}\n\nIntegriere diese Perspektive in die Hooks!" + + post_type_section = "" + if post_type: + post_type_section = f"\n\nPOST-TYP: {post_type.name}" + if post_type.description: + post_type_section += f"\n{post_type.description}" + + user_prompt = f"""Generiere 4 Hooks für dieses Thema: + +THEMA: {topic.get('title', 'Unbekanntes Thema')} + +KATEGORIE: {topic.get('category', 'Allgemein')} + +KERN-FAKT/INHALT: +{topic.get('fact', topic.get('description', 'Keine Details verfügbar'))} +{thoughts_section}{post_type_section} + +Generiere jetzt die 4 verschiedenen Hooks im JSON-Format.""" + + response = await self.call_openai( + system_prompt=system_prompt, + user_prompt=user_prompt, + model="gpt-4o-mini", + temperature=0.8, + response_format={"type": "json_object"} + ) + + try: + result = json.loads(response) + hooks = result.get("hooks", []) + logger.info(f"Generated {len(hooks)} hooks successfully") + return hooks + except (json.JSONDecodeError, KeyError) as e: + logger.error(f"Failed to parse hooks response: {e}") + # Return fallback hooks + return [ + {"hook": f"Was wäre, wenn {topic.get('title', 'dieses Thema')} alles verändert?", "style": "Neugier-weckend"}, + {"hook": f"Letzte Woche habe ich etwas über {topic.get('title', 'dieses Thema')} gelernt, das mich nicht mehr loslässt.", "style": "Storytelling"}, + {"hook": f"Die meisten unterschätzen {topic.get('title', 'dieses Thema')}. Ein Fehler.", "style": "Provokant"}, + {"hook": f"Eine Zahl hat mich diese Woche überrascht: {topic.get('fact', 'Ein überraschender Fakt')}.", "style": "Fakten-basiert"} + ] + + async def generate_improvement_suggestions( + self, + post_content: str, + profile_analysis: Dict[str, Any], + critic_feedback: Optional[Dict[str, Any]] = None + ) -> List[Dict[str, str]]: + """ + Generate improvement suggestions for an existing post based on critic feedback. + + Args: + post_content: The current post content + profile_analysis: Profile analysis for style matching + critic_feedback: Optional feedback from the critic with improvements list + + Returns: + List of {"label": "...", "action": "..."} dictionaries + """ + logger.info("Generating improvement suggestions for post") + + # Extract style info from profile + tone = profile_analysis.get('tone_analysis', {}).get('primary_tone', 'Professionell') + energy = profile_analysis.get('linguistic_fingerprint', {}).get('energy_level', 7) + address = profile_analysis.get('writing_style', {}).get('form_of_address', 'Du') + + # Build feedback context + feedback_context = "" + if critic_feedback: + feedback_text = critic_feedback.get('feedback', '') + improvements = critic_feedback.get('improvements', []) + strengths = critic_feedback.get('strengths', []) + score = critic_feedback.get('overall_score', 0) + + feedback_context = f""" +LETZTES KRITIKER-FEEDBACK (Score: {score}/100): +{feedback_text} + +IDENTIFIZIERTE VERBESSERUNGSPUNKTE: +{chr(10).join(['- ' + imp for imp in improvements]) if improvements else '- Keine spezifischen Verbesserungen genannt'} + +STÄRKEN (diese beibehalten!): +{chr(10).join(['- ' + s for s in strengths]) if strengths else '- Keine spezifischen Stärken genannt'} +""" + + system_prompt = f"""Du generierst kurze, klickbare Verbesserungsvorschläge für LinkedIn-Posts. + +PROFIL DER PERSON: +- Tonalität: {tone} +- Energie: {energy}/10 +- Ansprache: {address} +{feedback_context} + +DEINE AUFGABE: +Generiere 4 KURZE Verbesserungsvorschläge (max 5-6 Worte pro Label). +Jeder Vorschlag muss: +1. Auf dem Kritiker-Feedback basieren (falls vorhanden) +2. Zum Stil der Person passen +3. Eine konkrete, umsetzbare Änderung beschreiben + +FORMAT - Kurze Labels wie: +- "Hook emotionaler machen" +- "Persönliche Anekdote einbauen" +- "Absätze kürzen" +- "Call-to-Action verstärken" + +Antworte im JSON-Format: +{{"suggestions": [ + {{"label": "Kurzes Label (max 6 Worte)", "action": "Detaillierte Anweisung für die KI, was genau geändert werden soll"}}, + ... +]}}""" + + user_prompt = f"""POST: +{post_content[:1500]} + +Generiere 4 kurze, spezifische Verbesserungsvorschläge basierend auf dem Feedback und Profil.""" + + response = await self.call_openai( + system_prompt=system_prompt, + user_prompt=user_prompt, + model="gpt-4o-mini", + temperature=0.7, + response_format={"type": "json_object"} + ) + + try: + result = json.loads(response) + suggestions = result.get("suggestions", []) + logger.info(f"Generated {len(suggestions)} improvement suggestions") + return suggestions + except (json.JSONDecodeError, KeyError) as e: + logger.error(f"Failed to parse suggestions response: {e}") + return [ + {"label": "Hook verstärken", "action": "Mache den ersten Satz emotionaler und aufmerksamkeitsstärker"}, + {"label": "Storytelling einbauen", "action": "Füge eine kurze persönliche Anekdote oder Erfahrung hinzu"}, + {"label": "Call-to-Action ergänzen", "action": "Füge am Ende eine Frage oder Handlungsaufforderung hinzu"}, + {"label": "Struktur verbessern", "action": "Kürze lange Absätze und verbessere die Lesbarkeit"} + ] + + async def apply_suggestion( + self, + post_content: str, + suggestion: str, + profile_analysis: Dict[str, Any] + ) -> str: + """ + Apply a specific improvement suggestion to a post. + + Args: + post_content: The current post content + suggestion: The suggestion to apply + profile_analysis: Profile analysis for style matching + + Returns: + The improved post content + """ + logger.info(f"Applying suggestion to post: {suggestion[:50]}...") + + # Extract style info from profile + tone = profile_analysis.get('tone_analysis', {}).get('primary_tone', 'Professionell') + energy = profile_analysis.get('linguistic_fingerprint', {}).get('energy_level', 7) + address = profile_analysis.get('writing_style', {}).get('form_of_address', 'Du') + + system_prompt = f"""Du bist ein LinkedIn-Experte. Deine Aufgabe ist es, einen Post basierend auf einem konkreten Verbesserungsvorschlag zu überarbeiten. + +STIL DER PERSON: +- Tonalität: {tone} +- Energie-Level: {energy}/10 +- Ansprache: {address} + +WICHTIG: +- Behalte den Kern und die Hauptaussage des Posts bei +- Wende NUR die angegebene Verbesserung an +- Behalte die Länge ungefähr bei +- Antworte NUR mit dem überarbeiteten Post, keine Erklärungen""" + + user_prompt = f"""ORIGINAL-POST: +{post_content} + +ANZUWENDENDE VERBESSERUNG: +{suggestion} + +Schreibe jetzt den überarbeiteten Post:""" + + response = await self.call_openai( + system_prompt=system_prompt, + user_prompt=user_prompt, + model="gpt-4o-mini", + temperature=0.5 + ) + + logger.info("Successfully applied suggestion to post") + return response.strip() diff --git a/src/config.py b/src/config.py index 40b112f..e50ddee 100644 --- a/src/config.py +++ b/src/config.py @@ -6,7 +6,6 @@ from pathlib import Path class Settings(BaseSettings): """Application settings loaded from environment variables.""" - # API Keys openai_api_key: str perplexity_api_key: str @@ -15,6 +14,7 @@ class Settings(BaseSettings): # Supabase supabase_url: str supabase_key: str + supabase_service_role_key: str = "" # Required for admin operations like deleting users # Apify apify_actor_id: str = "apimaestro~linkedin-profile-posts" @@ -46,6 +46,14 @@ class Settings(BaseSettings): user_frontend_enabled: bool = True # Enable user frontend with LinkedIn OAuth supabase_redirect_url: str = "" # OAuth Callback URL (e.g., https://linkedin.onyva.dev/auth/callback) + # LinkedIn API (Custom OAuth for auto-posting) + linkedin_client_id: str = "" + linkedin_client_secret: str = "" + linkedin_redirect_uri: str = "" # e.g., https://yourdomain.com/settings/linkedin/callback + + # Token Encryption + encryption_key: str = "" # Generate with: python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())" + model_config = SettingsConfigDict( env_file=".env", env_file_encoding="utf-8", @@ -55,3 +63,18 @@ class Settings(BaseSettings): # Global settings instance settings = Settings() + +# API pricing per 1M tokens (input, output) +API_PRICING = { + "gpt-4o": {"input": 2.50, "output": 10.00}, + "gpt-4o-mini": {"input": 0.15, "output": 0.60}, + "sonar": {"input": 1.00, "output": 1.00}, +} + + +def estimate_cost(model: str, prompt_tokens: int, completion_tokens: int) -> float: + """Estimate cost in USD for an API call.""" + pricing = API_PRICING.get(model, {"input": 1.00, "output": 1.00}) + input_cost = (prompt_tokens / 1_000_000) * pricing["input"] + output_cost = (completion_tokens / 1_000_000) * pricing["output"] + return input_cost + output_cost diff --git a/src/database/__init__.py b/src/database/__init__.py index c66f4bf..07ecb04 100644 --- a/src/database/__init__.py +++ b/src/database/__init__.py @@ -1,7 +1,6 @@ """Database module.""" from src.database.client import DatabaseClient, db from src.database.models import ( - Customer, LinkedInProfile, LinkedInPost, Topic, @@ -9,12 +8,24 @@ from src.database.models import ( ResearchResult, GeneratedPost, PostType, + User, + Profile, + Company, + Invitation, + ExamplePost, + ReferenceProfile, + AccountType, + OnboardingStatus, + AuthMethod, + InvitationStatus, + ApiUsageLog, + LicenseKey, + CompanyDailyQuota, ) __all__ = [ "DatabaseClient", "db", - "Customer", "LinkedInProfile", "LinkedInPost", "Topic", @@ -22,4 +33,17 @@ __all__ = [ "ResearchResult", "GeneratedPost", "PostType", + "User", + "Profile", + "Company", + "Invitation", + "ExamplePost", + "ReferenceProfile", + "AccountType", + "OnboardingStatus", + "AuthMethod", + "InvitationStatus", + "ApiUsageLog", + "LicenseKey", + "CompanyDailyQuota", ] diff --git a/src/database/client.py b/src/database/client.py index e8477c2..31e7dee 100644 --- a/src/database/client.py +++ b/src/database/client.py @@ -1,5 +1,6 @@ """Supabase database client.""" import asyncio +from datetime import datetime, date, timezone from typing import Optional, List, Dict, Any from uuid import UUID from supabase import create_client, Client @@ -7,8 +8,10 @@ from loguru import logger from src.config import settings from src.database.models import ( - Customer, LinkedInProfile, LinkedInPost, Topic, - ProfileAnalysis, ResearchResult, GeneratedPost, PostType + LinkedInProfile, LinkedInPost, Topic, + ProfileAnalysis, ResearchResult, GeneratedPost, PostType, + User, Profile, Company, Invitation, ExamplePost, ReferenceProfile, + ApiUsageLog, LicenseKey, CompanyDailyQuota ) @@ -17,85 +20,54 @@ class DatabaseClient: def __init__(self): """Initialize Supabase client.""" - self.client: Client = create_client( - settings.supabase_url, - settings.supabase_key - ) - logger.info("Supabase client initialized") + if settings.supabase_service_role_key: + self.client: Client = create_client( + settings.supabase_url, + settings.supabase_service_role_key + ) + logger.info("Supabase client initialized with service role key (fast mode)") + else: + self.client: Client = create_client( + settings.supabase_url, + settings.supabase_key + ) + logger.warning("Supabase client initialized with anon key (slow mode - RLS enabled)") - # ==================== CUSTOMERS ==================== - - async def create_customer(self, customer: Customer) -> Customer: - """Create a new customer.""" - data = customer.model_dump(exclude={"id", "created_at", "updated_at"}, exclude_none=True) - result = await asyncio.to_thread( - lambda: self.client.table("customers").insert(data).execute() - ) - logger.info(f"Created customer: {result.data[0]['id']}") - return Customer(**result.data[0]) - - async def get_customer(self, customer_id: UUID) -> Optional[Customer]: - """Get customer by ID.""" - result = await asyncio.to_thread( - lambda: self.client.table("customers").select("*").eq("id", str(customer_id)).execute() - ) - if result.data: - return Customer(**result.data[0]) - return None - - async def get_customer_by_linkedin(self, linkedin_url: str) -> Optional[Customer]: - """Get customer by LinkedIn URL.""" - result = await asyncio.to_thread( - lambda: self.client.table("customers").select("*").eq("linkedin_url", linkedin_url).execute() - ) - if result.data: - return Customer(**result.data[0]) - return None - - async def list_customers(self) -> List[Customer]: - """List all customers.""" - result = await asyncio.to_thread( - lambda: self.client.table("customers").select("*").execute() - ) - return [Customer(**item) for item in result.data] + self.admin_client: Optional[Client] = self.client if settings.supabase_service_role_key else None # ==================== LINKEDIN PROFILES ==================== async def save_linkedin_profile(self, profile: LinkedInProfile) -> LinkedInProfile: """Save or update LinkedIn profile.""" data = profile.model_dump(exclude={"id", "scraped_at"}, exclude_none=True) - # Convert UUID to string for Supabase - if "customer_id" in data: - data["customer_id"] = str(data["customer_id"]) + if "user_id" in data: + data["user_id"] = str(data["user_id"]) - # Check if profile exists existing = await asyncio.to_thread( lambda: self.client.table("linkedin_profiles").select("*").eq( - "customer_id", str(profile.customer_id) + "user_id", str(profile.user_id) ).execute() ) if existing.data: - # Update existing result = await asyncio.to_thread( lambda: self.client.table("linkedin_profiles").update(data).eq( - "customer_id", str(profile.customer_id) + "user_id", str(profile.user_id) ).execute() ) else: - # Insert new result = await asyncio.to_thread( lambda: self.client.table("linkedin_profiles").insert(data).execute() ) - logger.info(f"Saved LinkedIn profile for customer: {profile.customer_id}") + logger.info(f"Saved LinkedIn profile for user: {profile.user_id}") return LinkedInProfile(**result.data[0]) - async def get_linkedin_profile(self, customer_id: UUID) -> Optional[LinkedInProfile]: - """Get LinkedIn profile for customer.""" + async def get_linkedin_profile(self, user_id: UUID) -> Optional[LinkedInProfile]: + """Get LinkedIn profile for user.""" result = await asyncio.to_thread( lambda: self.client.table("linkedin_profiles").select("*").eq( - "customer_id", str(customer_id) + "user_id", str(user_id) ).execute() ) if result.data: @@ -108,11 +80,10 @@ class DatabaseClient: """Save LinkedIn posts (bulk).""" from datetime import datetime - # Deduplicate posts based on (customer_id, post_url) before saving seen = set() unique_posts = [] for p in posts: - key = (str(p.customer_id), p.post_url) + key = (str(p.user_id), p.post_url) if key not in seen: seen.add(key) unique_posts.append(p) @@ -123,54 +94,79 @@ class DatabaseClient: data = [] for p in unique_posts: post_dict = p.model_dump(exclude={"id", "scraped_at"}, exclude_none=True) - # Convert UUID to string for Supabase - if "customer_id" in post_dict: - post_dict["customer_id"] = str(post_dict["customer_id"]) - - # Convert datetime to ISO string for Supabase + if "user_id" in post_dict: + post_dict["user_id"] = str(post_dict["user_id"]) if "post_date" in post_dict and isinstance(post_dict["post_date"], datetime): post_dict["post_date"] = post_dict["post_date"].isoformat() - data.append(post_dict) if not data: logger.warning("No posts to save") return [] - # Use upsert with on_conflict to handle duplicates based on (customer_id, post_url) - # This will update existing posts instead of throwing an error result = await asyncio.to_thread( lambda: self.client.table("linkedin_posts").upsert( data, - on_conflict="customer_id,post_url" + on_conflict="user_id,post_url" ).execute() ) logger.info(f"Saved {len(result.data)} LinkedIn posts") return [LinkedInPost(**item) for item in result.data] - async def get_linkedin_posts(self, customer_id: UUID) -> List[LinkedInPost]: - """Get all LinkedIn posts for customer.""" + async def get_linkedin_posts(self, user_id: UUID) -> List[LinkedInPost]: + """Get all LinkedIn posts for user.""" result = await asyncio.to_thread( lambda: self.client.table("linkedin_posts").select("*").eq( - "customer_id", str(customer_id) + "user_id", str(user_id) ).order("post_date", desc=True).execute() ) return [LinkedInPost(**item) for item in result.data] - async def get_unclassified_posts(self, customer_id: UUID) -> List[LinkedInPost]: + async def copy_posts_to_user(self, source_user_id: UUID, target_user_id: UUID) -> List[LinkedInPost]: + """Copy all LinkedIn posts from one user to another.""" + source_posts = await self.get_linkedin_posts(source_user_id) + if not source_posts: + return [] + + new_posts = [] + for p in source_posts: + new_post = LinkedInPost( + user_id=target_user_id, + post_url=p.post_url, + post_text=p.post_text, + post_date=p.post_date, + likes=p.likes, + comments=p.comments, + shares=p.shares, + raw_data=p.raw_data, + ) + new_posts.append(new_post) + + saved = await self.save_linkedin_posts(new_posts) + logger.info(f"Copied {len(saved)} posts from user {source_user_id} to {target_user_id}") + return saved + + async def delete_linkedin_post(self, post_id: UUID) -> None: + """Delete a LinkedIn post.""" + await asyncio.to_thread( + lambda: self.client.table("linkedin_posts").delete().eq("id", str(post_id)).execute() + ) + logger.info(f"Deleted LinkedIn post: {post_id}") + + async def get_unclassified_posts(self, user_id: UUID) -> List[LinkedInPost]: """Get all LinkedIn posts without a post_type_id.""" result = await asyncio.to_thread( lambda: self.client.table("linkedin_posts").select("*").eq( - "customer_id", str(customer_id) + "user_id", str(user_id) ).is_("post_type_id", "null").execute() ) return [LinkedInPost(**item) for item in result.data] - async def get_posts_by_type(self, customer_id: UUID, post_type_id: UUID) -> List[LinkedInPost]: + async def get_posts_by_type(self, user_id: UUID, post_type_id: UUID) -> List[LinkedInPost]: """Get all LinkedIn posts for a specific post type.""" result = await asyncio.to_thread( lambda: self.client.table("linkedin_posts").select("*").eq( - "customer_id", str(customer_id) + "user_id", str(user_id) ).eq("post_type_id", str(post_type_id)).order("post_date", desc=True).execute() ) return [LinkedInPost(**item) for item in result.data] @@ -192,19 +188,23 @@ class DatabaseClient: ) logger.debug(f"Updated classification for post {post_id}") + async def update_linkedin_post(self, post_id: UUID, updates: Dict[str, Any]) -> None: + """Update a LinkedIn post with arbitrary fields.""" + if "post_type_id" in updates and updates["post_type_id"]: + updates["post_type_id"] = str(updates["post_type_id"]) + if "user_id" in updates and updates["user_id"]: + updates["user_id"] = str(updates["user_id"]) + + await asyncio.to_thread( + lambda: self.client.table("linkedin_posts").update(updates).eq("id", str(post_id)).execute() + ) + logger.debug(f"Updated LinkedIn post {post_id}") + async def update_posts_classification_bulk( self, classifications: List[Dict[str, Any]] ) -> int: - """ - Bulk update post classifications. - - Args: - classifications: List of dicts with post_id, post_type_id, classification_method, classification_confidence - - Returns: - Number of posts updated - """ + """Bulk update post classifications.""" count = 0 for classification in classifications: try: @@ -226,9 +226,8 @@ class DatabaseClient: async def create_post_type(self, post_type: PostType) -> PostType: """Create a new post type.""" data = post_type.model_dump(exclude={"id", "created_at", "updated_at"}, exclude_none=True) - # Convert UUID to string - if "customer_id" in data: - data["customer_id"] = str(data["customer_id"]) + if "user_id" in data: + data["user_id"] = str(data["user_id"]) result = await asyncio.to_thread( lambda: self.client.table("post_types").insert(data).execute() @@ -244,8 +243,8 @@ class DatabaseClient: data = [] for pt in post_types: pt_dict = pt.model_dump(exclude={"id", "created_at", "updated_at"}, exclude_none=True) - if "customer_id" in pt_dict: - pt_dict["customer_id"] = str(pt_dict["customer_id"]) + if "user_id" in pt_dict: + pt_dict["user_id"] = str(pt_dict["user_id"]) data.append(pt_dict) result = await asyncio.to_thread( @@ -254,10 +253,10 @@ class DatabaseClient: logger.info(f"Created {len(result.data)} post types") return [PostType(**item) for item in result.data] - async def get_post_types(self, customer_id: UUID, active_only: bool = True) -> List[PostType]: - """Get all post types for a customer.""" + async def get_post_types(self, user_id: UUID, active_only: bool = True) -> List[PostType]: + """Get all post types for a user.""" def _query(): - query = self.client.table("post_types").select("*").eq("customer_id", str(customer_id)) + query = self.client.table("post_types").select("*").eq("user_id", str(user_id)) if active_only: query = query.eq("is_active", True) return query.order("name").execute() @@ -265,6 +264,11 @@ class DatabaseClient: result = await asyncio.to_thread(_query) return [PostType(**item) for item in result.data] + # Alias for get_post_types + async def get_customer_post_types(self, user_id: UUID, active_only: bool = True) -> List[PostType]: + """Alias for get_post_types.""" + return await self.get_post_types(user_id, active_only) + async def get_post_type(self, post_type_id: UUID) -> Optional[PostType]: """Get a single post type by ID.""" result = await asyncio.to_thread( @@ -297,7 +301,7 @@ class DatabaseClient: result = await asyncio.to_thread( lambda: self.client.table("post_types").update({ "analysis": analysis, - "analysis_generated_at": datetime.now().isoformat(), + "analysis_generated_at": datetime.now(timezone.utc).isoformat(), "analyzed_post_count": analyzed_post_count }).eq("id", str(post_type_id)).execute() ) @@ -332,9 +336,8 @@ class DatabaseClient: data = [] for t in topics: topic_dict = t.model_dump(exclude={"id", "created_at"}, exclude_none=True) - # Convert UUID to string for Supabase - if "customer_id" in topic_dict: - topic_dict["customer_id"] = str(topic_dict["customer_id"]) + if "user_id" in topic_dict: + topic_dict["user_id"] = str(topic_dict["user_id"]) if "extracted_from_post_id" in topic_dict and topic_dict["extracted_from_post_id"]: topic_dict["extracted_from_post_id"] = str(topic_dict["extracted_from_post_id"]) if "target_post_type_id" in topic_dict and topic_dict["target_post_type_id"]: @@ -342,7 +345,6 @@ class DatabaseClient: data.append(topic_dict) try: - # Use insert and handle duplicates manually result = await asyncio.to_thread( lambda: self.client.table("topics").insert(data).execute() ) @@ -350,7 +352,6 @@ class DatabaseClient: return [Topic(**item) for item in result.data] except Exception as e: logger.error(f"Error saving topics: {e}", exc_info=True) - # Try one by one if batch fails saved = [] for topic_data in data: try: @@ -365,13 +366,13 @@ class DatabaseClient: async def get_topics( self, - customer_id: UUID, + user_id: UUID, unused_only: bool = False, post_type_id: Optional[UUID] = None ) -> List[Topic]: - """Get topics for customer, optionally filtered by post type.""" + """Get topics for user, optionally filtered by post type.""" def _query(): - query = self.client.table("topics").select("*").eq("customer_id", str(customer_id)) + query = self.client.table("topics").select("*").eq("user_id", str(user_id)) if unused_only: query = query.eq("is_used", False) if post_type_id: @@ -396,38 +397,34 @@ class DatabaseClient: async def save_profile_analysis(self, analysis: ProfileAnalysis) -> ProfileAnalysis: """Save profile analysis.""" data = analysis.model_dump(exclude={"id", "created_at"}, exclude_none=True) - # Convert UUID to string for Supabase - if "customer_id" in data: - data["customer_id"] = str(data["customer_id"]) + if "user_id" in data: + data["user_id"] = str(data["user_id"]) - # Check if analysis exists existing = await asyncio.to_thread( lambda: self.client.table("profile_analyses").select("*").eq( - "customer_id", str(analysis.customer_id) + "user_id", str(analysis.user_id) ).execute() ) if existing.data: - # Update existing result = await asyncio.to_thread( lambda: self.client.table("profile_analyses").update(data).eq( - "customer_id", str(analysis.customer_id) + "user_id", str(analysis.user_id) ).execute() ) else: - # Insert new result = await asyncio.to_thread( lambda: self.client.table("profile_analyses").insert(data).execute() ) - logger.info(f"Saved profile analysis for customer: {analysis.customer_id}") + logger.info(f"Saved profile analysis for user: {analysis.user_id}") return ProfileAnalysis(**result.data[0]) - async def get_profile_analysis(self, customer_id: UUID) -> Optional[ProfileAnalysis]: - """Get profile analysis for customer.""" + async def get_profile_analysis(self, user_id: UUID) -> Optional[ProfileAnalysis]: + """Get profile analysis for user.""" result = await asyncio.to_thread( lambda: self.client.table("profile_analyses").select("*").eq( - "customer_id", str(customer_id) + "user_id", str(user_id) ).execute() ) if result.data: @@ -439,23 +436,22 @@ class DatabaseClient: async def save_research_result(self, research: ResearchResult) -> ResearchResult: """Save research result.""" data = research.model_dump(exclude={"id", "created_at"}, exclude_none=True) - # Convert UUIDs to string for Supabase - if "customer_id" in data: - data["customer_id"] = str(data["customer_id"]) + if "user_id" in data: + data["user_id"] = str(data["user_id"]) if "target_post_type_id" in data and data["target_post_type_id"]: data["target_post_type_id"] = str(data["target_post_type_id"]) result = await asyncio.to_thread( lambda: self.client.table("research_results").insert(data).execute() ) - logger.info(f"Saved research result for customer: {research.customer_id}") + logger.info(f"Saved research result for user: {research.user_id}") return ResearchResult(**result.data[0]) - async def get_latest_research(self, customer_id: UUID) -> Optional[ResearchResult]: - """Get latest research result for customer.""" + async def get_latest_research(self, user_id: UUID) -> Optional[ResearchResult]: + """Get latest research result for user.""" result = await asyncio.to_thread( lambda: self.client.table("research_results").select("*").eq( - "customer_id", str(customer_id) + "user_id", str(user_id) ).order("created_at", desc=True).limit(1).execute() ) if result.data: @@ -464,13 +460,13 @@ class DatabaseClient: async def get_all_research( self, - customer_id: UUID, + user_id: UUID, post_type_id: Optional[UUID] = None ) -> List[ResearchResult]: - """Get all research results for customer, optionally filtered by post type.""" + """Get all research results for user, optionally filtered by post type.""" def _query(): query = self.client.table("research_results").select("*").eq( - "customer_id", str(customer_id) + "user_id", str(user_id) ) if post_type_id: query = query.eq("target_post_type_id", str(post_type_id)) @@ -484,9 +480,8 @@ class DatabaseClient: async def save_generated_post(self, post: GeneratedPost) -> GeneratedPost: """Save generated post.""" data = post.model_dump(exclude={"id", "created_at"}, exclude_none=True) - # Convert UUIDs to string for Supabase - if "customer_id" in data: - data["customer_id"] = str(data["customer_id"]) + if "user_id" in data: + data["user_id"] = str(data["user_id"]) if "topic_id" in data and data["topic_id"]: data["topic_id"] = str(data["topic_id"]) if "post_type_id" in data and data["post_type_id"]: @@ -500,6 +495,22 @@ class DatabaseClient: async def update_generated_post(self, post_id: UUID, updates: Dict[str, Any]) -> GeneratedPost: """Update generated post.""" + # Convert datetime fields to isoformat strings + datetime_fields = ['published_at', 'approved_at', 'rejected_at', 'scheduled_at'] + for field in datetime_fields: + if field in updates and isinstance(updates[field], datetime): + updates[field] = updates[field].isoformat() + + # Handle metadata dict - ensure all nested datetime values are serialized + if 'metadata' in updates and isinstance(updates['metadata'], dict): + serialized_metadata = {} + for key, value in updates['metadata'].items(): + if isinstance(value, datetime): + serialized_metadata[key] = value.isoformat() + else: + serialized_metadata[key] = value + updates['metadata'] = serialized_metadata + result = await asyncio.to_thread( lambda: self.client.table("generated_posts").update(updates).eq( "id", str(post_id) @@ -508,11 +519,11 @@ class DatabaseClient: logger.info(f"Updated generated post: {post_id}") return GeneratedPost(**result.data[0]) - async def get_generated_posts(self, customer_id: UUID) -> List[GeneratedPost]: - """Get all generated posts for customer.""" + async def get_generated_posts(self, user_id: UUID) -> List[GeneratedPost]: + """Get all generated posts for user.""" result = await asyncio.to_thread( lambda: self.client.table("generated_posts").select("*").eq( - "customer_id", str(customer_id) + "user_id", str(user_id) ).order("created_at", desc=True).execute() ) return [GeneratedPost(**item) for item in result.data] @@ -528,6 +539,858 @@ class DatabaseClient: return GeneratedPost(**result.data[0]) return None + async def get_scheduled_posts_due(self) -> List[GeneratedPost]: + """Get all posts that are scheduled and due for publishing.""" + from datetime import datetime + now = datetime.now(timezone.utc).isoformat() + result = await asyncio.to_thread( + lambda: self.client.table("generated_posts").select("*").eq( + "status", "scheduled" + ).lte("scheduled_at", now).execute() + ) + return [GeneratedPost(**item) for item in result.data] + + async def get_scheduled_posts_for_company(self, company_id: UUID) -> List[Dict[str, Any]]: + """Get all scheduled posts for a company with employee info.""" + # Get all profiles (employees) belonging to this company + profiles_result = await asyncio.to_thread( + lambda: self.client.table("profiles").select("id, display_name").eq( + "company_id", str(company_id) + ).execute() + ) + + if not profiles_result.data: + return [] + + user_ids = [p["id"] for p in profiles_result.data] + profile_map = {p["id"]: p for p in profiles_result.data} + + posts_result = await asyncio.to_thread( + lambda: self.client.table("generated_posts").select( + "id, user_id, topic_title, post_content, status, scheduled_at, created_at" + ).in_( + "user_id", user_ids + ).in_( + "status", ["ready", "scheduled", "published"] + ).order("scheduled_at", desc=False, nullsfirst=False).execute() + ) + + enriched_posts = [] + for post in posts_result.data: + profile = profile_map.get(post["user_id"]) + enriched_posts.append({ + **post, + "employee_name": profile["display_name"] if profile else "Unknown", + "employee_user_id": post["user_id"] + }) + + return enriched_posts + + async def schedule_post(self, post_id: UUID, scheduled_at: datetime, scheduled_by_user_id: UUID) -> GeneratedPost: + """Schedule a post for publishing.""" + from datetime import datetime as dt + updates = { + "status": "scheduled", + "scheduled_at": scheduled_at.isoformat() if isinstance(scheduled_at, dt) else scheduled_at, + "scheduled_by_user_id": str(scheduled_by_user_id) + } + return await self.update_generated_post(post_id, updates) + + async def unschedule_post(self, post_id: UUID) -> GeneratedPost: + """Remove scheduling from a post (back to approved status).""" + updates = { + "status": "approved", + "scheduled_at": None, + "scheduled_by_user_id": None + } + return await self.update_generated_post(post_id, updates) + + + # ==================== PROFILES / USERS ==================== + + async def create_profile(self, user_id: UUID, profile: Profile) -> Profile: + """Create a profile for a user.""" + data = profile.model_dump(exclude={"created_at", "updated_at"}, exclude_none=True) + data["id"] = str(user_id) + if "company_id" in data and data["company_id"]: + data["company_id"] = str(data["company_id"]) + if "account_type" in data: + data["account_type"] = data["account_type"].value if hasattr(data["account_type"], "value") else data["account_type"] + if "onboarding_status" in data: + data["onboarding_status"] = data["onboarding_status"].value if hasattr(data["onboarding_status"], "value") else data["onboarding_status"] + + result = await asyncio.to_thread( + lambda: self.client.table("profiles").upsert(data).execute() + ) + logger.info(f"Created/updated profile: {result.data[0]['id']}") + return Profile(**result.data[0]) + + async def get_profile(self, user_id: UUID) -> Optional[Profile]: + """Get profile by user ID.""" + result = await asyncio.to_thread( + lambda: self.client.table("profiles").select("*").eq("id", str(user_id)).execute() + ) + if result.data: + return Profile(**result.data[0]) + return None + + async def get_profiles_by_linkedin_url(self, linkedin_url: str) -> List[Profile]: + """Get all profiles with this LinkedIn URL.""" + result = await asyncio.to_thread( + lambda: self.client.table("profiles").select("*").eq("linkedin_url", linkedin_url).execute() + ) + return [Profile(**item) for item in result.data] + + async def update_profile(self, user_id: UUID, updates: Dict[str, Any]) -> Profile: + """Update profile fields.""" + if "company_id" in updates and updates["company_id"]: + updates["company_id"] = str(updates["company_id"]) + for key in ["account_type", "onboarding_status"]: + if key in updates and hasattr(updates[key], "value"): + updates[key] = updates[key].value + + result = await asyncio.to_thread( + lambda: self.client.table("profiles").update(updates).eq( + "id", str(user_id) + ).execute() + ) + logger.info(f"Updated profile: {user_id}") + return Profile(**result.data[0]) + + # ==================== LINKEDIN ACCOUNTS ==================== + + async def get_linkedin_account(self, user_id: UUID) -> Optional['LinkedInAccount']: + """Get LinkedIn account for user.""" + from src.database.models import LinkedInAccount + result = await asyncio.to_thread( + lambda: self.client.table("linkedin_accounts").select("*") + .eq("user_id", str(user_id)).eq("is_active", True).execute() + ) + if result.data: + return LinkedInAccount(**result.data[0]) + return None + + async def get_linkedin_account_by_id(self, account_id: UUID) -> Optional['LinkedInAccount']: + """Get LinkedIn account by ID.""" + from src.database.models import LinkedInAccount + result = await asyncio.to_thread( + lambda: self.client.table("linkedin_accounts").select("*") + .eq("id", str(account_id)).execute() + ) + if result.data: + return LinkedInAccount(**result.data[0]) + return None + + async def create_linkedin_account(self, account: 'LinkedInAccount') -> 'LinkedInAccount': + """Create LinkedIn account connection.""" + from src.database.models import LinkedInAccount + data = account.model_dump(exclude={'id', 'created_at', 'updated_at'}) + data['user_id'] = str(data['user_id']) + data['token_expires_at'] = data['token_expires_at'].isoformat() + + result = await asyncio.to_thread( + lambda: self.client.table("linkedin_accounts").insert(data).execute() + ) + logger.info(f"Created LinkedIn account for user: {account.user_id}") + return LinkedInAccount(**result.data[0]) + + async def update_linkedin_account(self, account_id: UUID, updates: Dict) -> 'LinkedInAccount': + """Update LinkedIn account.""" + from src.database.models import LinkedInAccount + # Convert all datetime fields to isoformat strings + datetime_fields = ['token_expires_at', 'last_used_at', 'last_error_at'] + for field in datetime_fields: + if field in updates and isinstance(updates[field], datetime): + updates[field] = updates[field].isoformat() + + result = await asyncio.to_thread( + lambda: self.client.table("linkedin_accounts").update(updates) + .eq("id", str(account_id)).execute() + ) + logger.info(f"Updated LinkedIn account: {account_id}") + return LinkedInAccount(**result.data[0]) + + async def delete_linkedin_account(self, account_id: UUID) -> None: + """Delete LinkedIn account connection.""" + await asyncio.to_thread( + lambda: self.client.table("linkedin_accounts").delete() + .eq("id", str(account_id)).execute() + ) + logger.info(f"Deleted LinkedIn account: {account_id}") + + # ==================== USERS ==================== + + async def get_user(self, user_id: UUID) -> Optional[User]: + """Get user by ID (from users view).""" + result = await asyncio.to_thread( + lambda: self.client.table("users").select("*").eq("id", str(user_id)).execute() + ) + if result.data: + return User(**result.data[0]) + return None + + async def get_user_by_email(self, email: str) -> Optional[User]: + """Get user by email (from users view).""" + result = await asyncio.to_thread( + lambda: self.client.table("users").select("*").eq("email", email.lower()).execute() + ) + if result.data: + return User(**result.data[0]) + return None + + async def get_user_by_linkedin_sub(self, linkedin_sub: str) -> Optional[User]: + """Get user by LinkedIn sub (OAuth identifier).""" + result = await asyncio.to_thread( + lambda: self.client.table("users").select("*").eq("linkedin_sub", linkedin_sub).execute() + ) + if result.data: + return User(**result.data[0]) + return None + + async def update_user(self, user_id: UUID, updates: Dict[str, Any]) -> User: + """Update user/profile fields.""" + profile_fields = ["account_type", "display_name", "onboarding_status", + "onboarding_data", "company_id", + "linkedin_url", "writing_style_notes", "metadata", + "profile_picture", "creator_email", "customer_email", "is_active"] + profile_updates = {k: v for k, v in updates.items() if k in profile_fields} + + if profile_updates: + await self.update_profile(user_id, profile_updates) + + return await self.get_user(user_id) + + async def list_users(self, account_type: Optional[str] = None, company_id: Optional[UUID] = None) -> List[User]: + """List users with optional filters (from users view).""" + def _query(): + query = self.client.table("users").select("*") + if account_type: + query = query.eq("account_type", account_type) + if company_id: + query = query.eq("company_id", str(company_id)) + return query.order("created_at", desc=True).execute() + + result = await asyncio.to_thread(_query) + return [User(**item) for item in result.data] + + async def delete_user_completely(self, user_id: UUID) -> bool: + """Delete a user and all related data completely.""" + try: + user_id_str = str(user_id) + + # Delete all content data directly via user_id + for table in ["generated_posts", "linkedin_posts", "example_posts", + "reference_profiles", "profile_analyses", "linkedin_profiles", + "post_types", "research_results", "topics"]: + await asyncio.to_thread( + lambda t=table: self.client.table(t).delete().eq( + "user_id", user_id_str + ).execute() + ) + + # Get user email for invitation deletion + user = await self.get_user(user_id) + if user and user.email: + await asyncio.to_thread( + lambda: self.client.table("invitations").delete().eq( + "email", user.email + ).execute() + ) + + # Delete profile + await asyncio.to_thread( + lambda: self.client.table("profiles").delete().eq( + "id", user_id_str + ).execute() + ) + + # Delete from auth.users + if self.admin_client: + await asyncio.to_thread( + lambda: self.admin_client.auth.admin.delete_user(user_id_str) + ) + logger.info(f"Deleted auth user {user_id}") + else: + logger.warning(f"Cannot delete auth user {user_id} - no service role key configured") + + logger.info(f"Completely deleted user {user_id} and all related data") + return True + + except Exception as e: + logger.error(f"Error deleting user {user_id}: {e}") + raise + + # ==================== COMPANIES ==================== + + async def create_company(self, company: Company) -> Company: + """Create a new company.""" + data = company.model_dump(exclude={"id", "created_at", "updated_at"}, exclude_none=True) + if "owner_user_id" in data: + data["owner_user_id"] = str(data["owner_user_id"]) + if "license_key_id" in data: + data["license_key_id"] = str(data["license_key_id"]) + + result = await asyncio.to_thread( + lambda: self.client.table("companies").insert(data).execute() + ) + logger.info(f"Created company: {result.data[0]['id']}") + return Company(**result.data[0]) + + async def get_company(self, company_id: UUID) -> Optional[Company]: + """Get company by ID.""" + result = await asyncio.to_thread( + lambda: self.client.table("companies").select("*").eq("id", str(company_id)).execute() + ) + if result.data: + return Company(**result.data[0]) + return None + + async def get_company_by_owner(self, owner_user_id: UUID) -> Optional[Company]: + """Get company by owner user ID.""" + result = await asyncio.to_thread( + lambda: self.client.table("companies").select("*").eq("owner_user_id", str(owner_user_id)).execute() + ) + if result.data: + return Company(**result.data[0]) + return None + + async def get_company_employees(self, company_id: UUID) -> List[User]: + """Get all employees of a company.""" + result = await asyncio.to_thread( + lambda: self.client.table("users").select("*").eq( + "company_id", str(company_id) + ).eq("account_type", "employee").order("created_at", desc=True).execute() + ) + return [User(**item) for item in result.data] + + async def update_company(self, company_id: UUID, updates: Dict[str, Any]) -> Company: + """Update company fields.""" + result = await asyncio.to_thread( + lambda: self.client.table("companies").update(updates).eq( + "id", str(company_id) + ).execute() + ) + logger.info(f"Updated company: {company_id}") + return Company(**result.data[0]) + + async def list_companies(self) -> List[Company]: + """List all companies.""" + result = await asyncio.to_thread( + lambda: self.client.table("companies").select("*").order("created_at", desc=True).execute() + ) + return [Company(**item) for item in result.data] + + # ==================== INVITATIONS ==================== + + async def create_invitation(self, invitation: Invitation) -> Invitation: + """Create a new invitation.""" + from datetime import datetime + data = invitation.model_dump(exclude={"id", "created_at"}, exclude_none=True) + for key in ["company_id", "invited_by_user_id", "accepted_by_user_id"]: + if key in data and data[key]: + data[key] = str(data[key]) + if "expires_at" in data and isinstance(data["expires_at"], datetime): + data["expires_at"] = data["expires_at"].isoformat() + if "accepted_at" in data and isinstance(data["accepted_at"], datetime): + data["accepted_at"] = data["accepted_at"].isoformat() + if "status" in data and hasattr(data["status"], "value"): + data["status"] = data["status"].value + + result = await asyncio.to_thread( + lambda: self.client.table("invitations").insert(data).execute() + ) + logger.info(f"Created invitation: {result.data[0]['id']}") + return Invitation(**result.data[0]) + + async def get_invitation_by_token(self, token: str) -> Optional[Invitation]: + """Get invitation by token.""" + result = await asyncio.to_thread( + lambda: self.client.table("invitations").select("*").eq("token", token).execute() + ) + if result.data: + return Invitation(**result.data[0]) + return None + + async def get_invitation(self, invitation_id: UUID) -> Optional[Invitation]: + """Get invitation by ID.""" + result = await asyncio.to_thread( + lambda: self.client.table("invitations").select("*").eq("id", str(invitation_id)).execute() + ) + if result.data: + return Invitation(**result.data[0]) + return None + + async def update_invitation(self, invitation_id: UUID, updates: Dict[str, Any]) -> Invitation: + """Update invitation fields.""" + from datetime import datetime + if "accepted_by_user_id" in updates and updates["accepted_by_user_id"]: + updates["accepted_by_user_id"] = str(updates["accepted_by_user_id"]) + if "accepted_at" in updates and isinstance(updates["accepted_at"], datetime): + updates["accepted_at"] = updates["accepted_at"].isoformat() + if "status" in updates and hasattr(updates["status"], "value"): + updates["status"] = updates["status"].value + + result = await asyncio.to_thread( + lambda: self.client.table("invitations").update(updates).eq( + "id", str(invitation_id) + ).execute() + ) + logger.info(f"Updated invitation: {invitation_id}") + return Invitation(**result.data[0]) + + async def get_pending_invitations(self, company_id: UUID) -> List[Invitation]: + """Get all pending invitations for a company.""" + result = await asyncio.to_thread( + lambda: self.client.table("invitations").select("*").eq( + "company_id", str(company_id) + ).eq("status", "pending").order("created_at", desc=True).execute() + ) + return [Invitation(**item) for item in result.data] + + async def get_invitations_by_email(self, email: str) -> List[Invitation]: + """Get all invitations for an email address.""" + result = await asyncio.to_thread( + lambda: self.client.table("invitations").select("*").eq( + "email", email.lower() + ).order("created_at", desc=True).execute() + ) + return [Invitation(**item) for item in result.data] + + # ==================== EXAMPLE POSTS ==================== + + async def save_example_posts(self, posts: List[ExamplePost]) -> List[ExamplePost]: + """Save example posts (bulk).""" + if not posts: + return [] + + data = [] + for p in posts: + post_dict = p.model_dump(exclude={"id", "created_at"}, exclude_none=True) + if "user_id" in post_dict: + post_dict["user_id"] = str(post_dict["user_id"]) + if "post_type_id" in post_dict and post_dict["post_type_id"]: + post_dict["post_type_id"] = str(post_dict["post_type_id"]) + data.append(post_dict) + + result = await asyncio.to_thread( + lambda: self.client.table("example_posts").insert(data).execute() + ) + logger.info(f"Saved {len(result.data)} example posts") + return [ExamplePost(**item) for item in result.data] + + async def save_example_post(self, post: ExamplePost) -> ExamplePost: + """Save a single example post.""" + posts = await self.save_example_posts([post]) + return posts[0] if posts else post + + async def get_example_posts(self, user_id: UUID) -> List[ExamplePost]: + """Get all example posts for a user.""" + result = await asyncio.to_thread( + lambda: self.client.table("example_posts").select("*").eq( + "user_id", str(user_id) + ).order("created_at", desc=True).execute() + ) + return [ExamplePost(**item) for item in result.data] + + async def delete_example_post(self, post_id: UUID) -> None: + """Delete an example post.""" + await asyncio.to_thread( + lambda: self.client.table("example_posts").delete().eq("id", str(post_id)).execute() + ) + logger.info(f"Deleted example post: {post_id}") + + # ==================== REFERENCE PROFILES ==================== + + async def create_reference_profile(self, profile: ReferenceProfile) -> ReferenceProfile: + """Create a new reference profile.""" + data = profile.model_dump(exclude={"id", "created_at"}, exclude_none=True) + if "user_id" in data: + data["user_id"] = str(data["user_id"]) + + result = await asyncio.to_thread( + lambda: self.client.table("reference_profiles").insert(data).execute() + ) + logger.info(f"Created reference profile: {result.data[0]['id']}") + return ReferenceProfile(**result.data[0]) + + async def get_reference_profiles(self, user_id: UUID) -> List[ReferenceProfile]: + """Get all reference profiles for a user.""" + result = await asyncio.to_thread( + lambda: self.client.table("reference_profiles").select("*").eq( + "user_id", str(user_id) + ).order("created_at", desc=True).execute() + ) + return [ReferenceProfile(**item) for item in result.data] + + async def update_reference_profile(self, profile_id: UUID, updates: Dict[str, Any]) -> ReferenceProfile: + """Update reference profile fields.""" + result = await asyncio.to_thread( + lambda: self.client.table("reference_profiles").update(updates).eq( + "id", str(profile_id) + ).execute() + ) + logger.info(f"Updated reference profile: {profile_id}") + return ReferenceProfile(**result.data[0]) + + async def delete_reference_profile(self, profile_id: UUID) -> None: + """Delete a reference profile.""" + await asyncio.to_thread( + lambda: self.client.table("reference_profiles").delete().eq("id", str(profile_id)).execute() + ) + logger.info(f"Deleted reference profile: {profile_id}") + + # ==================== API USAGE LOGS ==================== + + async def log_api_usage( + self, + provider: str, + model: str, + operation: str, + prompt_tokens: int, + completion_tokens: int, + total_tokens: int, + estimated_cost_usd: float, + user_id: Optional[UUID] = None, + company_id: Optional[UUID] = None + ) -> None: + """Log an API usage event.""" + data = { + "provider": provider, + "model": model, + "operation": operation, + "prompt_tokens": prompt_tokens, + "completion_tokens": completion_tokens, + "total_tokens": total_tokens, + "estimated_cost_usd": estimated_cost_usd + } + if user_id: + data["user_id"] = str(user_id) + if company_id: + data["company_id"] = str(company_id) + + try: + await asyncio.to_thread( + lambda: self.client.table("api_usage_logs").insert(data).execute() + ) + except Exception as e: + logger.warning(f"Failed to log API usage: {e}") + + async def get_usage_stats( + self, + start_date: Optional[str] = None, + end_date: Optional[str] = None, + user_id: Optional[UUID] = None, + company_id: Optional[UUID] = None + ) -> List[Dict[str, Any]]: + """Get aggregated usage stats.""" + def _query(): + query = self.client.table("api_usage_logs").select("*") + if user_id: + query = query.eq("user_id", str(user_id)) + if company_id: + query = query.eq("company_id", str(company_id)) + if start_date: + query = query.gte("created_at", start_date) + if end_date: + query = query.lte("created_at", end_date) + return query.order("created_at", desc=True).execute() + + result = await asyncio.to_thread(_query) + return result.data + + async def get_usage_by_day( + self, + start_date: Optional[str] = None, + end_date: Optional[str] = None, + user_id: Optional[UUID] = None, + company_id: Optional[UUID] = None + ) -> List[Dict[str, Any]]: + """Get daily usage breakdown.""" + logs = await self.get_usage_stats(start_date, end_date, user_id, company_id) + + from collections import defaultdict + daily = defaultdict(lambda: {"total_tokens": 0, "estimated_cost_usd": 0.0, "count": 0}) + + for log in logs: + day = log["created_at"][:10] + daily[day]["total_tokens"] += log.get("total_tokens", 0) + daily[day]["estimated_cost_usd"] += float(log.get("estimated_cost_usd", 0)) + daily[day]["count"] += 1 + + return [{"date": day, **data} for day, data in sorted(daily.items())] + + async def get_post_stats( + self, + start_date: Optional[str] = None, + user_id: Optional[UUID] = None, + company_id: Optional[UUID] = None + ) -> List[Dict[str, Any]]: + """Get generated posts with select fields for statistics.""" + # Resolve user_ids for filtering + target_user_ids = None + if user_id: + target_user_ids = [str(user_id)] + if company_id: + # Get all users belonging to this company + profiles_result = await asyncio.to_thread( + lambda: self.client.table("profiles").select("id").eq( + "company_id", str(company_id) + ).execute() + ) + company_user_ids = [p["id"] for p in profiles_result.data] + if target_user_ids is not None: + target_user_ids = [u for u in target_user_ids if u in company_user_ids] + else: + target_user_ids = company_user_ids + if not target_user_ids: + return [] + + def _query(): + q = self.client.table("generated_posts").select( + "id, created_at, status, approved_at, published_at, user_id" + ) + if target_user_ids is not None: + q = q.in_("user_id", target_user_ids) + if start_date: + q = q.gte("created_at", start_date) + return q.order("created_at", desc=True).execute() + + result = await asyncio.to_thread(_query) + return result.data + + # ==================== LICENSE KEYS ==================== + + async def list_license_keys(self) -> List[LicenseKey]: + """Get all license keys (admin only).""" + result = await asyncio.to_thread( + lambda: self.client.table("license_keys") + .select("*") + .order("created_at", desc=True) + .execute() + ) + return [LicenseKey(**row) for row in result.data] + + async def get_license_key(self, key: str) -> Optional[LicenseKey]: + """Get license key by key string.""" + result = await asyncio.to_thread( + lambda: self.client.table("license_keys") + .select("*") + .eq("key", key) + .execute() + ) + return LicenseKey(**result.data[0]) if result.data else None + + async def create_license_key( + self, + key: str, + max_employees: int, + max_posts_per_day: int, + max_researches_per_day: int, + description: Optional[str] = None + ) -> LicenseKey: + """Create new license key.""" + data = { + "key": key, + "max_employees": max_employees, + "max_posts_per_day": max_posts_per_day, + "max_researches_per_day": max_researches_per_day, + "description": description, + } + result = await asyncio.to_thread( + lambda: self.client.table("license_keys").insert(data).execute() + ) + logger.info(f"Created license key: {key}") + return LicenseKey(**result.data[0]) + + async def mark_license_key_used( + self, + key: str, + company_id: UUID + ) -> LicenseKey: + """Mark license key as used and link to company.""" + data = { + "used": True, + "company_id": str(company_id), + "used_at": datetime.now(timezone.utc).isoformat() + } + result = await asyncio.to_thread( + lambda: self.client.table("license_keys") + .update(data) + .eq("key", key) + .execute() + ) + logger.info(f"Marked license key as used: {key}") + return LicenseKey(**result.data[0]) + + async def update_license_key(self, key_id: UUID, updates: Dict[str, Any]) -> LicenseKey: + """Update license key limits (admin only).""" + result = await asyncio.to_thread( + lambda: self.client.table("license_keys") + .update(updates) + .eq("id", str(key_id)) + .execute() + ) + logger.info(f"Updated license key: {key_id}") + return LicenseKey(**result.data[0]) if result.data else None + + async def delete_license_key(self, key_id: UUID) -> None: + """Delete license key (admin only).""" + await asyncio.to_thread( + lambda: self.client.table("license_keys") + .delete() + .eq("id", str(key_id)) + .execute() + ) + logger.info(f"Deleted license key: {key_id}") + + # ==================== COMPANY QUOTAS ==================== + + async def get_company_daily_quota( + self, + company_id: UUID, + date_: Optional[date] = None + ) -> CompanyDailyQuota: + """Get or create daily quota for company.""" + if date_ is None: + date_ = date.today() + + # Try to get existing + result = await asyncio.to_thread( + lambda: self.client.table("company_daily_quotas") + .select("*") + .eq("company_id", str(company_id)) + .eq("date", date_.isoformat()) + .execute() + ) + + if result.data: + return CompanyDailyQuota(**result.data[0]) + + # Create new quota for today + data = { + "company_id": str(company_id), + "date": date_.isoformat(), + "posts_created": 0, + "researches_created": 0 + } + result = await asyncio.to_thread( + lambda: self.client.table("company_daily_quotas") + .insert(data) + .execute() + ) + return CompanyDailyQuota(**result.data[0]) + + async def increment_company_posts_quota(self, company_id: UUID) -> None: + """Increment daily posts count for company.""" + try: + quota = await self.get_company_daily_quota(company_id) + new_count = quota.posts_created + 1 + + result = await asyncio.to_thread( + lambda: self.client.table("company_daily_quotas") + .update({"posts_created": new_count}) + .eq("id", str(quota.id)) + .execute() + ) + + logger.info(f"Incremented posts quota for company {company_id}: {quota.posts_created} -> {new_count}") + + if not result.data: + logger.error(f"Failed to increment posts quota - no data returned") + except Exception as e: + logger.error(f"Error incrementing posts quota for company {company_id}: {e}") + raise + + async def increment_company_researches_quota(self, company_id: UUID) -> None: + """Increment daily researches count for company.""" + try: + quota = await self.get_company_daily_quota(company_id) + new_count = quota.researches_created + 1 + + result = await asyncio.to_thread( + lambda: self.client.table("company_daily_quotas") + .update({"researches_created": new_count}) + .eq("id", str(quota.id)) + .execute() + ) + + logger.info(f"Incremented researches quota for company {company_id}: {quota.researches_created} -> {new_count}") + + if not result.data: + logger.error(f"Failed to increment researches quota - no data returned") + except Exception as e: + logger.error(f"Error incrementing researches quota for company {company_id}: {e}") + raise + + async def get_company_limits(self, company_id: UUID) -> Optional[LicenseKey]: + """Get company limits from associated license key. + + Returns None if no license key is associated (uses defaults). + """ + company = await self.get_company(company_id) + if not company or not company.license_key_id: + return None + + result = await asyncio.to_thread( + lambda: self.client.table("license_keys") + .select("*") + .eq("id", str(company.license_key_id)) + .execute() + ) + + return LicenseKey(**result.data[0]) if result.data else None + + async def check_company_post_limit(self, company_id: UUID) -> tuple[bool, str]: + """Check if company can create more posts today. + + Returns (can_create: bool, error_message: str) + """ + license_key = await self.get_company_limits(company_id) + if not license_key: + # No license key, use defaults (unlimited) + return True, "" + + quota = await self.get_company_daily_quota(company_id) + + if quota.posts_created >= license_key.max_posts_per_day: + return False, f"Tageslimit erreicht ({license_key.max_posts_per_day} Posts/Tag). Versuche es morgen wieder." + + return True, "" + + async def check_company_research_limit(self, company_id: UUID) -> tuple[bool, str]: + """Check if company can create more researches today. + + Returns (can_create: bool, error_message: str) + """ + license_key = await self.get_company_limits(company_id) + if not license_key: + # No license key, use defaults (unlimited) + return True, "" + + quota = await self.get_company_daily_quota(company_id) + + if quota.researches_created >= license_key.max_researches_per_day: + return False, f"Tageslimit erreicht ({license_key.max_researches_per_day} Researches/Tag). Versuche es morgen wieder." + + return True, "" + + async def check_company_employee_limit(self, company_id: UUID) -> tuple[bool, str]: + """Check if company can add more employees. + + Returns (can_add: bool, error_message: str) + """ + license_key = await self.get_company_limits(company_id) + if not license_key: + # No license key, use defaults (unlimited) + return True, "" + + employees = await self.list_users(account_type="employee", company_id=company_id) + + if len(employees) >= license_key.max_employees: + return False, f"Maximale Mitarbeiteranzahl erreicht ({license_key.max_employees}). Bitte Lizenz upgraden." + + return True, "" + # Global database client instance db = DatabaseClient() diff --git a/src/database/models.py b/src/database/models.py index c0e32ae..a390e7c 100644 --- a/src/database/models.py +++ b/src/database/models.py @@ -1,5 +1,6 @@ """Pydantic models for database entities.""" -from datetime import datetime +from datetime import datetime, date +from enum import Enum from typing import Optional, Dict, Any, List from uuid import UUID from pydantic import BaseModel, Field, ConfigDict @@ -10,22 +11,210 @@ class DBModel(BaseModel): model_config = ConfigDict(extra='ignore') -class Customer(DBModel): - """Customer/Client model.""" +# ==================== ENUMS ==================== + +class AccountType(str, Enum): + """User account type.""" + GHOSTWRITER = "ghostwriter" + COMPANY = "company" + EMPLOYEE = "employee" + + +class OnboardingStatus(str, Enum): + """Onboarding status for users.""" + PENDING = "pending" + PROFILE_SETUP = "profile_setup" + POSTS_SCRAPED = "posts_scraped" + CATEGORIZING = "categorizing" + COMPLETED = "completed" + + +class AuthMethod(str, Enum): + """Authentication method for users.""" + LINKEDIN_OAUTH = "linkedin_oauth" + EMAIL_PASSWORD = "email_password" + + +class InvitationStatus(str, Enum): + """Status of an invitation.""" + PENDING = "pending" + ACCEPTED = "accepted" + EXPIRED = "expired" + CANCELLED = "cancelled" + + +# ==================== USER & COMPANY MODELS ==================== + +class Profile(DBModel): + """Profile model - extends auth.users with app-specific data. + + The id is the same as auth.users.id (Supabase handles authentication). + """ id: Optional[UUID] = None created_at: Optional[datetime] = None updated_at: Optional[datetime] = None - name: str - email: Optional[str] = None - company_name: Optional[str] = None - linkedin_url: str + + # Account Type + account_type: AccountType = AccountType.GHOSTWRITER + + # Display name + display_name: Optional[str] = None + + # Onboarding + onboarding_status: OnboardingStatus = OnboardingStatus.PENDING + onboarding_data: Dict[str, Any] = Field(default_factory=dict) + + # Links + company_id: Optional[UUID] = None + + # Fields migrated from customers table + linkedin_url: Optional[str] = None + writing_style_notes: Optional[str] = None metadata: Dict[str, Any] = Field(default_factory=dict) + profile_picture: Optional[str] = None + creator_email: Optional[str] = None + customer_email: Optional[str] = None + is_active: bool = True + + +class LinkedInAccount(DBModel): + """LinkedIn account connection for auto-posting.""" + id: Optional[UUID] = None + user_id: UUID + created_at: Optional[datetime] = None + updated_at: Optional[datetime] = None + + # LinkedIn Identity + linkedin_user_id: str + linkedin_vanity_name: Optional[str] = None + linkedin_name: Optional[str] = None + linkedin_picture: Optional[str] = None + + # OAuth Tokens (encrypted) + access_token: str + refresh_token: Optional[str] = None + token_expires_at: datetime + granted_scopes: List[str] = Field(default_factory=list) + + # Status + is_active: bool = True + last_used_at: Optional[datetime] = None + last_error: Optional[str] = None + last_error_at: Optional[datetime] = None + + +class User(DBModel): + """User model - combines auth.users data with profile data. + + This is a view model that combines data from Supabase auth.users + and our profiles table. Used for compatibility with existing code. + """ + id: Optional[UUID] = None + created_at: Optional[datetime] = None + updated_at: Optional[datetime] = None + + # From auth.users + email: str = "" + password_hash: Optional[str] = None # Not stored, Supabase handles it + auth_method: AuthMethod = AuthMethod.LINKEDIN_OAUTH + + # LinkedIn OAuth Data (from auth.users.raw_user_meta_data) + linkedin_sub: Optional[str] = None + linkedin_vanity_name: Optional[str] = None + linkedin_name: Optional[str] = None + linkedin_picture: Optional[str] = None + + # From profiles table + account_type: AccountType = AccountType.GHOSTWRITER + display_name: Optional[str] = None + onboarding_status: OnboardingStatus = OnboardingStatus.PENDING + onboarding_data: Dict[str, Any] = Field(default_factory=dict) + company_id: Optional[UUID] = None + + # Fields migrated from customers table + linkedin_url: Optional[str] = None + writing_style_notes: Optional[str] = None + metadata: Dict[str, Any] = Field(default_factory=dict) + profile_picture: Optional[str] = None + creator_email: Optional[str] = None + customer_email: Optional[str] = None + is_active: bool = True + + # Email verification (from auth.users) + email_verified: bool = False + email_verification_token: Optional[str] = None + email_verification_expires_at: Optional[datetime] = None + + +class Company(DBModel): + """Company model for company accounts.""" + id: Optional[UUID] = None + created_at: Optional[datetime] = None + updated_at: Optional[datetime] = None + + # Company Data + name: str + description: Optional[str] = None + website: Optional[str] = None + industry: Optional[str] = None + + # Strategy + company_strategy: Dict[str, Any] = Field(default_factory=dict) + + owner_user_id: UUID + onboarding_completed: bool = False + + # License key reference (limits are stored in license_keys table) + license_key_id: Optional[UUID] = None + + +class Invitation(DBModel): + """Invitation model for employee invitations.""" + id: Optional[UUID] = None + created_at: Optional[datetime] = None + + email: str + token: str + expires_at: datetime + + company_id: UUID + invited_by_user_id: UUID + + status: InvitationStatus = InvitationStatus.PENDING + accepted_at: Optional[datetime] = None + accepted_by_user_id: Optional[UUID] = None + + +class ExamplePost(DBModel): + """Example post model for manual posts during onboarding.""" + id: Optional[UUID] = None + user_id: UUID + created_at: Optional[datetime] = None + + post_text: str + source: str = "manual" # 'manual' | 'reference_profile' + source_linkedin_url: Optional[str] = None + post_type_id: Optional[UUID] = None + + +class ReferenceProfile(DBModel): + """Reference profile for scraping alternative LinkedIn profiles.""" + id: Optional[UUID] = None + user_id: UUID + created_at: Optional[datetime] = None + + linkedin_url: str + name: Optional[str] = None + posts_scraped: int = 0 + + +# ==================== CONTENT MODELS ==================== class PostType(DBModel): """Post type model for categorizing different types of posts.""" id: Optional[UUID] = None - customer_id: UUID + user_id: UUID created_at: Optional[datetime] = None updated_at: Optional[datetime] = None name: str @@ -42,7 +231,7 @@ class PostType(DBModel): class LinkedInProfile(DBModel): """LinkedIn profile model.""" id: Optional[UUID] = None - customer_id: UUID + user_id: UUID scraped_at: Optional[datetime] = None profile_data: Dict[str, Any] name: Optional[str] = None @@ -55,7 +244,7 @@ class LinkedInProfile(DBModel): class LinkedInPost(DBModel): """LinkedIn post model.""" id: Optional[UUID] = None - customer_id: UUID + user_id: UUID scraped_at: Optional[datetime] = None post_url: Optional[str] = None post_text: str @@ -73,7 +262,7 @@ class LinkedInPost(DBModel): class Topic(DBModel): """Topic model.""" id: Optional[UUID] = None - customer_id: UUID + user_id: UUID created_at: Optional[datetime] = None title: str description: Optional[str] = None @@ -82,13 +271,13 @@ class Topic(DBModel): extraction_confidence: Optional[float] = None is_used: bool = False used_at: Optional[datetime] = None - target_post_type_id: Optional[UUID] = None # Target post type for this topic + target_post_type_id: Optional[UUID] = None class ProfileAnalysis(DBModel): """Profile analysis model.""" id: Optional[UUID] = None - customer_id: UUID + user_id: UUID created_at: Optional[datetime] = None writing_style: Dict[str, Any] tone_analysis: Dict[str, Any] @@ -100,19 +289,33 @@ class ProfileAnalysis(DBModel): class ResearchResult(DBModel): """Research result model.""" id: Optional[UUID] = None - customer_id: UUID + user_id: UUID created_at: Optional[datetime] = None query: str results: Dict[str, Any] suggested_topics: List[Dict[str, Any]] source: str = "perplexity" - target_post_type_id: Optional[UUID] = None # Target post type for this research + target_post_type_id: Optional[UUID] = None + + +class ApiUsageLog(DBModel): + """API usage log for tracking token usage and costs.""" + id: Optional[UUID] = None + user_id: Optional[UUID] = None + provider: str # 'openai' | 'perplexity' + model: str # 'gpt-4o', 'gpt-4o-mini', 'sonar' + operation: str # 'post_creation', 'research', 'profile_analysis', etc. + prompt_tokens: int = 0 + completion_tokens: int = 0 + total_tokens: int = 0 + estimated_cost_usd: float = 0.0 + created_at: Optional[datetime] = None class GeneratedPost(DBModel): """Generated post model.""" id: Optional[UUID] = None - customer_id: UUID + user_id: UUID created_at: Optional[datetime] = None topic_id: Optional[UUID] = None topic_title: str @@ -120,7 +323,48 @@ class GeneratedPost(DBModel): iterations: int = 0 writer_versions: List[str] = Field(default_factory=list) critic_feedback: List[Dict[str, Any]] = Field(default_factory=list) - status: str = "draft" # draft, approved, published, rejected + status: str = "draft" # draft, approved, ready, scheduled, published, rejected approved_at: Optional[datetime] = None published_at: Optional[datetime] = None - post_type_id: Optional[UUID] = None # Post type used for this generated post + post_type_id: Optional[UUID] = None + # Image + image_url: Optional[str] = None + # Scheduling fields + scheduled_at: Optional[datetime] = None + scheduled_by_user_id: Optional[UUID] = None + # Metadata for additional info (e.g., LinkedIn post URL, auto-posting status) + metadata: Optional[Dict[str, Any]] = None + + +# ==================== LICENSE KEY MODELS ==================== + + +class LicenseKey(DBModel): + """License key for company registration.""" + id: Optional[UUID] = None + key: str + description: Optional[str] = None + + # Limits + max_employees: int = 5 + max_posts_per_day: int = 10 + max_researches_per_day: int = 5 + + # Usage + used: bool = False + company_id: Optional[UUID] = None + used_at: Optional[datetime] = None + + created_at: Optional[datetime] = None + updated_at: Optional[datetime] = None + + +class CompanyDailyQuota(DBModel): + """Daily usage quota for a company.""" + id: Optional[UUID] = None + company_id: UUID + date: date + posts_created: int = 0 + researches_created: int = 0 + created_at: Optional[datetime] = None + updated_at: Optional[datetime] = None diff --git a/src/orchestrator.py b/src/orchestrator.py index b0ba52d..bdc9dfc 100644 --- a/src/orchestrator.py +++ b/src/orchestrator.py @@ -5,7 +5,7 @@ from uuid import UUID from loguru import logger from src.config import settings -from src.database import db, Customer, LinkedInProfile, LinkedInPost, Topic +from src.database import db, LinkedInProfile, LinkedInPost, Topic from src.scraper import scraper from src.agents import ( ProfileAnalyzerAgent, @@ -31,65 +31,80 @@ class WorkflowOrchestrator: self.critic = CriticAgent() self.post_classifier = PostClassifierAgent() self.post_type_analyzer = PostTypeAnalyzerAgent() + self._all_agents = [ + self.profile_analyzer, self.topic_extractor, self.researcher, + self.writer, self.critic, self.post_classifier, self.post_type_analyzer + ] logger.info("WorkflowOrchestrator initialized") + def _set_tracking(self, operation: str, user_id: Optional[str] = None, + company_id: Optional[str] = None): + """Set tracking context on all agents.""" + uid = str(user_id) if user_id else None + comp_id = str(company_id) if company_id else None + for agent in self._all_agents: + agent.set_tracking_context(operation=operation, user_id=uid, company_id=comp_id) + + async def _resolve_tracking_ids(self, user_id: UUID) -> dict: + """Resolve company_id from a user_id for tracking.""" + try: + profile = await db.get_profile(user_id) + if profile: + return { + "user_id": str(user_id), + "company_id": str(profile.company_id) if profile.company_id else None + } + except Exception as e: + logger.debug(f"Could not resolve tracking IDs for user {user_id}: {e}") + return {"user_id": str(user_id), "company_id": None} + async def run_initial_setup( self, + user_id: UUID, linkedin_url: str, - customer_name: str, - customer_data: Dict[str, Any], + profile_data: Dict[str, Any], post_types_data: Optional[List[Dict[str, Any]]] = None - ) -> Customer: + ) -> None: """ - Run initial setup for a new customer. + Run initial setup for a user. This includes: - 1. Creating customer record + 1. Updating profile with linkedin_url and metadata 2. Creating post types (if provided) 3. Scraping LinkedIn posts (NO profile scraping) - 4. Creating profile from customer_data + 4. Creating profile from profile_data 5. Analyzing profile 6. Extracting topics from existing posts 7. Classifying posts by type (if post types exist) 8. Analyzing post types (if enough posts) Args: + user_id: User UUID linkedin_url: LinkedIn profile URL - customer_name: Customer name - customer_data: Complete customer data (company, persona, style_guide, etc.) + profile_data: Profile data (writing style notes, etc.) post_types_data: Optional list of post type definitions - - Returns: - Customer object """ - logger.info(f"=== INITIAL SETUP for {customer_name} ===") + logger.info(f"=== INITIAL SETUP for user {user_id} ===") + ids = await self._resolve_tracking_ids(user_id) + self._set_tracking("initial_setup", **ids) - # Step 1: Check if customer already exists - existing_customer = await db.get_customer_by_linkedin(linkedin_url) - if existing_customer: - logger.warning(f"Customer already exists: {existing_customer.id}") - return existing_customer - - # Step 2: Create customer + # Step 1: Update profile with linkedin_url total_steps = 7 if post_types_data else 5 - logger.info(f"Step 1/{total_steps}: Creating customer record") - customer = Customer( - name=customer_name, - linkedin_url=linkedin_url, - company_name=customer_data.get("company_name"), - email=customer_data.get("email"), - metadata=customer_data - ) - customer = await db.create_customer(customer) - logger.info(f"Customer created: {customer.id}") + logger.info(f"Step 1/{total_steps}: Updating profile") + await db.update_profile(user_id, { + "linkedin_url": linkedin_url, + "writing_style_notes": profile_data.get("writing_style_notes"), + "metadata": profile_data + }) + logger.info(f"Profile updated for user: {user_id}") - # Step 2.5: Create post types if provided + # Step 2: Create post types if provided created_post_types = [] if post_types_data: logger.info(f"Step 2/{total_steps}: Creating {len(post_types_data)} post types") for pt_data in post_types_data: post_type = PostType( - customer_id=customer.id, + user_id=user_id, name=pt_data.get("name", "Unnamed"), description=pt_data.get("description"), identifying_hashtags=pt_data.get("identifying_hashtags", []), @@ -102,19 +117,20 @@ class WorkflowOrchestrator: created_post_types = await db.create_post_types_bulk(created_post_types) logger.info(f"Created {len(created_post_types)} post types") - # Step 3: Create LinkedIn profile from customer data (NO scraping) + # Step 3: Create LinkedIn profile from profile data (NO scraping) step_num = 3 if post_types_data else 2 logger.info(f"Step {step_num}/{total_steps}: Creating LinkedIn profile from provided data") + profile = await db.get_profile(user_id) linkedin_profile = LinkedInProfile( - customer_id=customer.id, + user_id=user_id, profile_data={ - "persona": customer_data.get("persona"), - "form_of_address": customer_data.get("form_of_address"), - "style_guide": customer_data.get("style_guide"), + "persona": profile_data.get("persona"), + "form_of_address": profile_data.get("form_of_address"), + "style_guide": profile_data.get("style_guide"), "linkedin_url": linkedin_url }, - name=customer_name, - headline=customer_data.get("persona", "")[:100] if customer_data.get("persona") else None + name=profile.display_name or "", + headline=profile_data.get("persona", "")[:100] if profile_data.get("persona") else None ) await db.save_linkedin_profile(linkedin_profile) logger.info("LinkedIn profile saved") @@ -129,7 +145,7 @@ class WorkflowOrchestrator: linkedin_posts = [] for post_data in parsed_posts: post = LinkedInPost( - customer_id=customer.id, + user_id=user_id, **post_data ) linkedin_posts.append(post) @@ -151,13 +167,13 @@ class WorkflowOrchestrator: profile_analysis = await self.profile_analyzer.process( profile=linkedin_profile, posts=linkedin_posts, - customer_data=customer_data + customer_data=profile_data ) # Save profile analysis from src.database.models import ProfileAnalysis analysis_record = ProfileAnalysis( - customer_id=customer.id, + user_id=user_id, writing_style=profile_analysis.get("writing_style", {}), tone_analysis=profile_analysis.get("tone_analysis", {}), topic_patterns=profile_analysis.get("topic_patterns", {}), @@ -177,7 +193,7 @@ class WorkflowOrchestrator: try: topics = await self.topic_extractor.process( posts=linkedin_posts, - customer_id=customer.id # Pass UUID directly + user_id=user_id ) if topics: await db.save_topics(topics) @@ -192,40 +208,41 @@ class WorkflowOrchestrator: # Step 7: Classify posts logger.info(f"Step {total_steps - 1}/{total_steps}: Classifying posts by type") try: - await self.classify_posts(customer.id) + await self.classify_posts(user_id) except Exception as e: logger.error(f"Post classification failed: {e}", exc_info=True) # Step 8: Analyze post types logger.info(f"Step {total_steps}/{total_steps}: Analyzing post types") try: - await self.analyze_post_types(customer.id) + await self.analyze_post_types(user_id) except Exception as e: logger.error(f"Post type analysis failed: {e}", exc_info=True) logger.info(f"Step {total_steps}/{total_steps}: Initial setup complete!") - return customer - async def classify_posts(self, customer_id: UUID) -> int: + async def classify_posts(self, user_id: UUID) -> int: """ - Classify unclassified posts for a customer. + Classify unclassified posts for a user. Args: - customer_id: Customer UUID + user_id: User UUID Returns: Number of posts classified """ - logger.info(f"=== CLASSIFYING POSTS for customer {customer_id} ===") + logger.info(f"=== CLASSIFYING POSTS for user {user_id} ===") + ids = await self._resolve_tracking_ids(user_id) + self._set_tracking("classify_posts", **ids) # Get post types - post_types = await db.get_post_types(customer_id) + post_types = await db.get_post_types(user_id) if not post_types: logger.info("No post types defined, skipping classification") return 0 # Get unclassified posts - posts = await db.get_unclassified_posts(customer_id) + posts = await db.get_unclassified_posts(user_id) if not posts: logger.info("No unclassified posts found") return 0 @@ -243,20 +260,22 @@ class WorkflowOrchestrator: return 0 - async def analyze_post_types(self, customer_id: UUID) -> Dict[str, Any]: + async def analyze_post_types(self, user_id: UUID) -> Dict[str, Any]: """ - Analyze all post types for a customer. + Analyze all post types for a user. Args: - customer_id: Customer UUID + user_id: User UUID Returns: Dictionary with analysis results per post type """ - logger.info(f"=== ANALYZING POST TYPES for customer {customer_id} ===") + logger.info(f"=== ANALYZING POST TYPES for user {user_id} ===") + ids = await self._resolve_tracking_ids(user_id) + self._set_tracking("analyze_post_types", **ids) # Get post types - post_types = await db.get_post_types(customer_id) + post_types = await db.get_post_types(user_id) if not post_types: logger.info("No post types defined") return {} @@ -264,7 +283,7 @@ class WorkflowOrchestrator: results = {} for post_type in post_types: # Get posts for this type - posts = await db.get_posts_by_type(customer_id, post_type.id) + posts = await db.get_posts_by_type(user_id, post_type.id) if len(posts) < self.post_type_analyzer.MIN_POSTS_FOR_ANALYSIS: logger.info(f"Post type '{post_type.name}' has only {len(posts)} posts, skipping analysis") @@ -292,22 +311,24 @@ class WorkflowOrchestrator: async def research_new_topics( self, - customer_id: UUID, + user_id: UUID, progress_callback: Optional[Callable[[str, int, int], None]] = None, post_type_id: Optional[UUID] = None ) -> List[Dict[str, Any]]: """ - Research new content topics for a customer. + Research new content topics for a user. Args: - customer_id: Customer UUID + user_id: User UUID progress_callback: Optional callback(message, current_step, total_steps) post_type_id: Optional post type to target research for Returns: List of suggested topics """ - logger.info(f"=== RESEARCHING NEW TOPICS for customer {customer_id} ===") + logger.info(f"=== RESEARCHING NEW TOPICS for user {user_id} ===") + ids = await self._resolve_tracking_ids(user_id) + self._set_tracking("research", **ids) # Get post type context if specified post_type = None @@ -324,7 +345,7 @@ class WorkflowOrchestrator: # Step 1: Get profile analysis report_progress("Lade Profil-Analyse...", 1) - profile_analysis = await db.get_profile_analysis(customer_id) + profile_analysis = await db.get_profile_analysis(user_id) if not profile_analysis: raise ValueError("Profile analysis not found. Run initial setup first.") @@ -333,12 +354,12 @@ class WorkflowOrchestrator: existing_topics = set() # From topics table - existing_topics_records = await db.get_topics(customer_id) + existing_topics_records = await db.get_topics(user_id) for t in existing_topics_records: existing_topics.add(t.title) # From previous research results - all_research = await db.get_all_research(customer_id) + all_research = await db.get_all_research(user_id) for research in all_research: if research.suggested_topics: for topic in research.suggested_topics: @@ -346,7 +367,7 @@ class WorkflowOrchestrator: existing_topics.add(topic["title"]) # From generated posts - generated_posts = await db.get_generated_posts(customer_id) + generated_posts = await db.get_generated_posts(user_id) for post in generated_posts: if post.topic_title: existing_topics.add(post.topic_title) @@ -354,15 +375,15 @@ class WorkflowOrchestrator: existing_topics = list(existing_topics) logger.info(f"Found {len(existing_topics)} existing topics to avoid") - # Get customer data - customer = await db.get_customer(customer_id) + # Get profile data + profile = await db.get_profile(user_id) # Get example posts to understand the person's actual content style # If post_type_id is specified, only use posts of that type if post_type_id: - linkedin_posts = await db.get_posts_by_type(customer_id, post_type_id) + linkedin_posts = await db.get_posts_by_type(user_id, post_type_id) else: - linkedin_posts = await db.get_linkedin_posts(customer_id) + linkedin_posts = await db.get_linkedin_posts(user_id) example_post_texts = [ post.post_text for post in linkedin_posts @@ -376,7 +397,7 @@ class WorkflowOrchestrator: research_results = await self.researcher.process( profile_analysis=profile_analysis.full_analysis, existing_topics=existing_topics, - customer_data=customer.metadata, + customer_data=profile.metadata, example_posts=example_post_texts, post_type=post_type, post_type_analysis=post_type_analysis @@ -386,8 +407,8 @@ class WorkflowOrchestrator: report_progress("Speichere Ergebnisse...", 4) from src.database.models import ResearchResult research_record = ResearchResult( - customer_id=customer_id, - query=f"New topics for {customer.name}" + (f" ({post_type.name})" if post_type else ""), + user_id=user_id, + query=f"New topics for {profile.display_name}" + (f" ({post_type.name})" if post_type else ""), results={"raw_response": research_results["raw_response"]}, suggested_topics=research_results["suggested_topics"], target_post_type_id=post_type_id @@ -397,19 +418,135 @@ class WorkflowOrchestrator: return research_results["suggested_topics"] + async def generate_hooks( + self, + user_id: UUID, + topic: Dict[str, Any], + user_thoughts: str = "", + post_type_id: Optional[UUID] = None + ) -> List[Dict[str, str]]: + """ + Generate 4 hook options for a topic. + + Args: + user_id: User UUID + topic: Topic dictionary + user_thoughts: User's personal thoughts about the topic + post_type_id: Optional post type for context + + Returns: + List of {"hook": "...", "style": "..."} dictionaries + """ + logger.info(f"=== GENERATING HOOKS for topic: {topic.get('title')} ===") + ids = await self._resolve_tracking_ids(user_id) + self._set_tracking("generate_hooks", **ids) + + # Get profile analysis for style matching + profile_analysis = await db.get_profile_analysis(user_id) + if not profile_analysis: + raise ValueError("Profile analysis not found. Run initial setup first.") + + # Get post type context if specified + post_type = None + if post_type_id: + post_type = await db.get_post_type(post_type_id) + + # Generate hooks via writer agent + hooks = await self.writer.generate_hooks( + topic=topic, + profile_analysis=profile_analysis.full_analysis, + user_thoughts=user_thoughts, + post_type=post_type + ) + + logger.info(f"Generated {len(hooks)} hooks") + return hooks + + async def generate_improvement_suggestions( + self, + user_id: UUID, + post_content: str, + critic_feedback: Optional[Dict[str, Any]] = None + ) -> List[Dict[str, str]]: + """ + Generate improvement suggestions for an existing post. + + Args: + user_id: User UUID + post_content: The current post content + critic_feedback: Optional feedback from the critic + + Returns: + List of {"label": "...", "action": "..."} dictionaries + """ + logger.info("=== GENERATING IMPROVEMENT SUGGESTIONS ===") + ids = await self._resolve_tracking_ids(user_id) + self._set_tracking("improvement_suggestions", **ids) + + # Get profile analysis for style matching + profile_analysis = await db.get_profile_analysis(user_id) + if not profile_analysis: + raise ValueError("Profile analysis not found.") + + suggestions = await self.writer.generate_improvement_suggestions( + post_content=post_content, + profile_analysis=profile_analysis.full_analysis, + critic_feedback=critic_feedback + ) + + logger.info(f"Generated {len(suggestions)} improvement suggestions") + return suggestions + + async def apply_suggestion_to_post( + self, + user_id: UUID, + post_content: str, + suggestion: str + ) -> str: + """ + Apply a suggestion to a post and return the improved version. + + Args: + user_id: User UUID + post_content: The current post content + suggestion: The suggestion to apply + + Returns: + The improved post content + """ + logger.info(f"=== APPLYING SUGGESTION TO POST ===") + ids = await self._resolve_tracking_ids(user_id) + self._set_tracking("apply_suggestion", **ids) + + # Get profile analysis for style matching + profile_analysis = await db.get_profile_analysis(user_id) + if not profile_analysis: + raise ValueError("Profile analysis not found.") + + improved_post = await self.writer.apply_suggestion( + post_content=post_content, + suggestion=suggestion, + profile_analysis=profile_analysis.full_analysis + ) + + logger.info("Successfully applied suggestion to post") + return improved_post + async def create_post( self, - customer_id: UUID, + user_id: UUID, topic: Dict[str, Any], max_iterations: int = 3, progress_callback: Optional[Callable[[str, int, int, Optional[int], Optional[List], Optional[List]], None]] = None, - post_type_id: Optional[UUID] = None + post_type_id: Optional[UUID] = None, + user_thoughts: str = "", + selected_hook: str = "" ) -> Dict[str, Any]: """ Create a LinkedIn post through writer-critic iteration. Args: - customer_id: Customer UUID + user_id: User UUID topic: Topic dictionary max_iterations: Maximum number of writer-critic iterations progress_callback: Optional callback(message, iteration, max_iterations, score, versions, feedback_list) @@ -419,6 +556,8 @@ class WorkflowOrchestrator: Dictionary with final post and metadata """ logger.info(f"=== CREATING POST for topic: {topic.get('title')} ===") + ids = await self._resolve_tracking_ids(user_id) + self._set_tracking("post_creation", **ids) def report_progress(message: str, iteration: int, score: Optional[int] = None, versions: Optional[List] = None, feedback_list: Optional[List] = None): @@ -427,7 +566,7 @@ class WorkflowOrchestrator: # Get profile analysis report_progress("Lade Profil-Analyse...", 0, None, [], []) - profile_analysis = await db.get_profile_analysis(customer_id) + profile_analysis = await db.get_profile_analysis(user_id) if not profile_analysis: raise ValueError("Profile analysis not found. Run initial setup first.") @@ -440,16 +579,16 @@ class WorkflowOrchestrator: post_type_analysis = post_type.analysis logger.info(f"Using post type '{post_type.name}' for writing") - # Load customer's real posts as style examples + # Load user's real posts as style examples # If post_type_id is specified, only use posts of that type if post_type_id: - linkedin_posts = await db.get_posts_by_type(customer_id, post_type_id) + linkedin_posts = await db.get_posts_by_type(user_id, post_type_id) if len(linkedin_posts) < 3: # Fall back to all posts if not enough type-specific posts - linkedin_posts = await db.get_linkedin_posts(customer_id) + linkedin_posts = await db.get_linkedin_posts(user_id) logger.info("Not enough type-specific posts, using all posts") else: - linkedin_posts = await db.get_linkedin_posts(customer_id) + linkedin_posts = await db.get_linkedin_posts(user_id) example_post_texts = [ post.post_text for post in linkedin_posts @@ -458,7 +597,7 @@ class WorkflowOrchestrator: logger.info(f"Loaded {len(example_post_texts)} example posts for style reference") # Extract lessons from past feedback (if enabled) - feedback_lessons = await self._extract_recurring_feedback(customer_id) + feedback_lessons = await self._extract_recurring_feedback(user_id) # Initialize tracking writer_versions = [] @@ -467,6 +606,15 @@ class WorkflowOrchestrator: approved = False iteration = 0 + # Load company strategy if user belongs to a company + company_strategy = None + profile = await db.get_profile(user_id) + if profile and profile.company_id: + company = await db.get_company(profile.company_id) + if company and company.company_strategy: + company_strategy = company.company_strategy + logger.info(f"Loaded company strategy for post creation: {company.name}") + # Writer-Critic loop while iteration < max_iterations and not approved: iteration += 1 @@ -482,7 +630,10 @@ class WorkflowOrchestrator: example_posts=example_post_texts, learned_lessons=feedback_lessons, # Pass lessons from past feedback post_type=post_type, - post_type_analysis=post_type_analysis + post_type_analysis=post_type_analysis, + user_thoughts=user_thoughts, + selected_hook=selected_hook, + company_strategy=company_strategy # NEW: Pass company strategy ) else: # Revision based on feedback - pass full critic result for structured changes @@ -497,7 +648,10 @@ class WorkflowOrchestrator: critic_result=last_feedback, # Pass full critic result with specific_changes learned_lessons=feedback_lessons, # Also for revisions post_type=post_type, - post_type_analysis=post_type_analysis + post_type_analysis=post_type_analysis, + user_thoughts=user_thoughts, + selected_hook=selected_hook, + company_strategy=company_strategy # NEW: Pass company strategy ) writer_versions.append(current_post) @@ -538,19 +692,14 @@ class WorkflowOrchestrator: if iteration < max_iterations: logger.info("Post needs revision, continuing...") - # Determine final status based on score + # All new posts start as draft - user moves them via Kanban board final_score = critic_feedback_list[-1].get("overall_score", 0) if critic_feedback_list else 0 - if approved and final_score >= 85: - status = "approved" - elif approved and final_score >= 80: - status = "approved" # Auto-approved - else: - status = "draft" + status = "draft" # Save generated post from src.database.models import GeneratedPost generated_post = GeneratedPost( - customer_id=customer_id, + user_id=user_id, topic_title=topic.get("title", "Unknown"), post_content=current_post, iterations=iteration, @@ -573,12 +722,12 @@ class WorkflowOrchestrator: "critic_feedback": critic_feedback_list } - async def _extract_recurring_feedback(self, customer_id: UUID) -> Dict[str, Any]: + async def _extract_recurring_feedback(self, user_id: UUID) -> Dict[str, Any]: """ Extract recurring feedback patterns from past generated posts. Args: - customer_id: Customer UUID + user_id: User UUID Returns: Dictionary with recurring improvements and lessons learned @@ -587,7 +736,7 @@ class WorkflowOrchestrator: return {"lessons": [], "patterns": {}} # Get recent generated posts with their critic feedback - generated_posts = await db.get_generated_posts(customer_id) + generated_posts = await db.get_generated_posts(user_id) if not generated_posts: return {"lessons": [], "patterns": {}} @@ -683,26 +832,26 @@ class WorkflowOrchestrator: } } - async def get_customer_status(self, customer_id: UUID) -> Dict[str, Any]: + async def get_user_status(self, user_id: UUID) -> Dict[str, Any]: """ - Get status information for a customer. + Get status information for a user. Args: - customer_id: Customer UUID + user_id: User UUID Returns: Status dictionary """ - customer = await db.get_customer(customer_id) - if not customer: - raise ValueError("Customer not found") + profile = await db.get_profile(user_id) + if not profile: + raise ValueError("User not found") - profile = await db.get_linkedin_profile(customer_id) - posts = await db.get_linkedin_posts(customer_id) - analysis = await db.get_profile_analysis(customer_id) - generated_posts = await db.get_generated_posts(customer_id) - all_research = await db.get_all_research(customer_id) - post_types = await db.get_post_types(customer_id) + linkedin_profile = await db.get_linkedin_profile(user_id) + posts = await db.get_linkedin_posts(user_id) + analysis = await db.get_profile_analysis(user_id) + generated_posts = await db.get_generated_posts(user_id) + all_research = await db.get_all_research(user_id) + post_types = await db.get_post_types(user_id) # Count total research entries research_count = len(all_research) diff --git a/src/services/__init__.py b/src/services/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/services/background_jobs.py b/src/services/background_jobs.py new file mode 100644 index 0000000..fa825ff --- /dev/null +++ b/src/services/background_jobs.py @@ -0,0 +1,481 @@ +"""Background job system for running AI analyses without blocking the UI.""" +import asyncio +from datetime import datetime +from enum import Enum +from typing import Dict, Any, Optional, Callable, Awaitable +from uuid import UUID, uuid4 +from dataclasses import dataclass, field +from loguru import logger + + +class JobStatus(str, Enum): + PENDING = "pending" + RUNNING = "running" + COMPLETED = "completed" + FAILED = "failed" + + +class JobType(str, Enum): + POST_SCRAPING = "post_scraping" + PROFILE_ANALYSIS = "profile_analysis" + POST_CATEGORIZATION = "post_categorization" + POST_TYPE_ANALYSIS = "post_type_analysis" + + +@dataclass +class BackgroundJob: + id: str + job_type: JobType + user_id: str + status: JobStatus = JobStatus.PENDING + progress: int = 0 + message: str = "" + created_at: datetime = field(default_factory=datetime.utcnow) + started_at: Optional[datetime] = None + completed_at: Optional[datetime] = None + error: Optional[str] = None + result: Optional[Dict[str, Any]] = None + + +class BackgroundJobManager: + """Manages background jobs for AI analyses.""" + + def __init__(self): + self._jobs: Dict[str, BackgroundJob] = {} + self._user_jobs: Dict[str, list] = {} # user_id -> list of job_ids + self._listeners: Dict[str, list] = {} # user_id -> list of callbacks + + def create_job(self, job_type: JobType, user_id: str) -> BackgroundJob: + """Create a new background job.""" + job_id = str(uuid4()) + job = BackgroundJob( + id=job_id, + job_type=job_type, + user_id=user_id + ) + self._jobs[job_id] = job + + if user_id not in self._user_jobs: + self._user_jobs[user_id] = [] + self._user_jobs[user_id].append(job_id) + + logger.info(f"Created background job {job_id} of type {job_type} for user {user_id}") + return job + + def get_job(self, job_id: str) -> Optional[BackgroundJob]: + """Get a job by ID.""" + return self._jobs.get(job_id) + + def get_user_jobs(self, user_id: str) -> list[BackgroundJob]: + """Get all jobs for a user.""" + job_ids = self._user_jobs.get(user_id, []) + return [self._jobs[jid] for jid in job_ids if jid in self._jobs] + + def get_active_jobs(self, user_id: str) -> list[BackgroundJob]: + """Get running/pending jobs for a user.""" + return [ + j for j in self.get_user_jobs(user_id) + if j.status in (JobStatus.PENDING, JobStatus.RUNNING) + ] + + async def update_job( + self, + job_id: str, + status: Optional[JobStatus] = None, + progress: Optional[int] = None, + message: Optional[str] = None, + error: Optional[str] = None, + result: Optional[Dict[str, Any]] = None + ): + """Update a job's status and notify listeners.""" + job = self._jobs.get(job_id) + if not job: + return + + if status: + job.status = status + if status == JobStatus.RUNNING and not job.started_at: + job.started_at = datetime.utcnow() + elif status in (JobStatus.COMPLETED, JobStatus.FAILED): + job.completed_at = datetime.utcnow() + + if progress is not None: + job.progress = progress + if message: + job.message = message + if error: + job.error = error + if result: + job.result = result + + # Notify listeners + await self._notify_listeners(job.user_id, job) + + async def _notify_listeners(self, user_id: str, job: BackgroundJob): + """Notify all listeners for a user about job updates.""" + listeners = self._listeners.get(user_id, []) + for callback in listeners: + try: + await callback(job) + except Exception as e: + logger.error(f"Error notifying listener: {e}") + + def add_listener(self, user_id: str, callback: Callable[[BackgroundJob], Awaitable[None]]): + """Add a listener for job updates.""" + if user_id not in self._listeners: + self._listeners[user_id] = [] + self._listeners[user_id].append(callback) + + def remove_listener(self, user_id: str, callback: Callable[[BackgroundJob], Awaitable[None]]): + """Remove a listener.""" + if user_id in self._listeners: + try: + self._listeners[user_id].remove(callback) + except ValueError: + pass + + def cleanup_old_jobs(self, max_age_hours: int = 24): + """Remove completed jobs older than max_age_hours.""" + cutoff = datetime.utcnow() + to_remove = [] + + for job_id, job in self._jobs.items(): + if job.completed_at: + age = (cutoff - job.completed_at).total_seconds() / 3600 + if age > max_age_hours: + to_remove.append(job_id) + + for job_id in to_remove: + job = self._jobs.pop(job_id, None) + if job: + user_jobs = self._user_jobs.get(job.user_id, []) + if job_id in user_jobs: + user_jobs.remove(job_id) + + if to_remove: + logger.info(f"Cleaned up {len(to_remove)} old background jobs") + + +# Global instance +job_manager = BackgroundJobManager() + + +async def run_post_scraping(user_id: UUID, linkedin_url: str, job_id: str): + """Run LinkedIn post scraping in background.""" + from src.database.client import DatabaseClient + from src.scraper import scraper + from src.database.models import LinkedInPost + + db = DatabaseClient() + + try: + await job_manager.update_job( + job_id, + status=JobStatus.RUNNING, + progress=10, + message="Starte LinkedIn-Scraping..." + ) + + # Scrape posts + await job_manager.update_job( + job_id, + progress=30, + message="Lade Posts von LinkedIn..." + ) + + raw_posts = await scraper.scrape_posts(linkedin_url, limit=50) + parsed_posts = scraper.parse_posts_data(raw_posts) + + await job_manager.update_job( + job_id, + progress=60, + message=f"{len(parsed_posts)} Posts gefunden, speichere..." + ) + + linkedin_posts = [] + profile_picture = None + + for post_data in parsed_posts: + post = LinkedInPost(user_id=user_id, **post_data) + linkedin_posts.append(post) + + # Extract profile picture from first post with author data + if not profile_picture and post_data.get("raw_data"): + author = post_data["raw_data"].get("author", {}) + if author and isinstance(author, dict): + profile_picture = author.get("profile_picture") + + if linkedin_posts: + await db.save_linkedin_posts(linkedin_posts) + + # Save profile picture to profile + if profile_picture: + await db.update_profile(user_id, {"profile_picture": profile_picture}) + + await job_manager.update_job( + job_id, + status=JobStatus.COMPLETED, + progress=100, + message=f"{len(linkedin_posts)} Posts gespeichert!", + result={"posts_count": len(linkedin_posts), "profile_picture": profile_picture} + ) + + logger.info(f"Post scraping completed for user {user_id}: {len(linkedin_posts)} posts") + + except Exception as e: + logger.error(f"Post scraping failed: {e}") + await job_manager.update_job( + job_id, + status=JobStatus.FAILED, + error=str(e) + ) + + +async def run_profile_analysis(user_id: UUID, job_id: str): + """Run profile analysis in background.""" + from src.database.client import DatabaseClient + from src.agents.profile_analyzer import ProfileAnalyzerAgent + from src.database.models import ProfileAnalysis + + db = DatabaseClient() + + try: + await job_manager.update_job( + job_id, + status=JobStatus.RUNNING, + progress=10, + message="Lade LinkedIn-Posts..." + ) + + # Get posts and profile + posts = await db.get_linkedin_posts(user_id) + if not posts: + await job_manager.update_job( + job_id, + status=JobStatus.FAILED, + error="Keine Posts gefunden" + ) + return + + linkedin_profile = await db.get_linkedin_profile(user_id) + user_profile = await db.get_profile(user_id) + + await job_manager.update_job( + job_id, + progress=30, + message=f"Analysiere {len(posts)} Posts..." + ) + + # Run analysis + analyzer = ProfileAnalyzerAgent() + + # Prepare user data + user_data = { + "name": user_profile.display_name if user_profile else "", + "company": "", + "writing_style_notes": user_profile.writing_style_notes if user_profile else "" + } + + # Create a minimal linkedin profile if none exists + if not linkedin_profile: + from src.database.models import LinkedInProfile + linkedin_profile = LinkedInProfile( + user_id=user_id, + profile_data={}, + name=user_profile.display_name if user_profile else "" + ) + + analysis_result = await analyzer.process(linkedin_profile, posts, user_data) + + await job_manager.update_job( + job_id, + progress=80, + message="Speichere Analyse..." + ) + + # Save analysis + profile_analysis = ProfileAnalysis( + user_id=user_id, + writing_style=analysis_result.get("writing_style", {}), + tone_analysis=analysis_result.get("tone_analysis", {}), + topic_patterns=analysis_result.get("topic_patterns", {}), + audience_insights=analysis_result.get("audience_insights", {}), + full_analysis=analysis_result + ) + await db.save_profile_analysis(profile_analysis) + + await job_manager.update_job( + job_id, + status=JobStatus.COMPLETED, + progress=100, + message="Profil-Analyse abgeschlossen!" + ) + + logger.info(f"Profile analysis completed for user {user_id}") + + except Exception as e: + logger.error(f"Profile analysis failed: {e}") + await job_manager.update_job( + job_id, + status=JobStatus.FAILED, + error=str(e) + ) + + +async def run_post_categorization(user_id: UUID, job_id: str): + """Run automatic post categorization in background.""" + from src.database.client import DatabaseClient + from src.agents.post_classifier import PostClassifierAgent + + db = DatabaseClient() + + try: + await job_manager.update_job( + job_id, + status=JobStatus.RUNNING, + progress=10, + message="Lade Posts und Typen..." + ) + + # Get posts and types + posts = await db.get_unclassified_posts(user_id) + post_types = await db.get_post_types(user_id) + + if not posts: + await job_manager.update_job( + job_id, + status=JobStatus.COMPLETED, + progress=100, + message="Alle Posts sind bereits kategorisiert!" + ) + return + + if not post_types: + await job_manager.update_job( + job_id, + status=JobStatus.FAILED, + error="Keine Post-Typen definiert" + ) + return + + await job_manager.update_job( + job_id, + progress=30, + message=f"Kategorisiere {len(posts)} Posts..." + ) + + # Run classification + classifier = PostClassifierAgent() + classifications = await classifier.process(posts, post_types) + + await job_manager.update_job( + job_id, + progress=70, + message="Speichere Kategorisierungen..." + ) + + # Save classifications + if classifications: + await db.update_posts_classification_bulk(classifications) + + await job_manager.update_job( + job_id, + status=JobStatus.COMPLETED, + progress=100, + message=f"{len(classifications)} Posts kategorisiert!" + ) + + logger.info(f"Post categorization completed for user {user_id}: {len(classifications)} posts") + + except Exception as e: + logger.error(f"Post categorization failed: {e}") + await job_manager.update_job( + job_id, + status=JobStatus.FAILED, + error=str(e) + ) + + +async def run_post_type_analysis(user_id: UUID, job_id: str): + """Run post type analysis in background.""" + from src.database.client import DatabaseClient + from src.agents.post_type_analyzer import PostTypeAnalyzerAgent + + db = DatabaseClient() + + try: + await job_manager.update_job( + job_id, + status=JobStatus.RUNNING, + progress=10, + message="Lade Post-Typen..." + ) + + post_types = await db.get_post_types(user_id) + + if not post_types: + await job_manager.update_job( + job_id, + status=JobStatus.FAILED, + error="Keine Post-Typen definiert" + ) + return + + analyzer = PostTypeAnalyzerAgent() + total = len(post_types) + + for i, post_type in enumerate(post_types): + await job_manager.update_job( + job_id, + progress=int(10 + (80 * i / total)), + message=f"Analysiere '{post_type.name}'..." + ) + + # Get posts for this type + posts = await db.get_posts_by_type(user_id, post_type.id) + + if posts: + analysis = await analyzer.process(post_type, posts) + await db.update_post_type_analysis(post_type.id, analysis, len(posts)) + + await job_manager.update_job( + job_id, + status=JobStatus.COMPLETED, + progress=100, + message=f"{total} Post-Typen analysiert!" + ) + + logger.info(f"Post type analysis completed for user {user_id}") + + except Exception as e: + logger.error(f"Post type analysis failed: {e}") + await job_manager.update_job( + job_id, + status=JobStatus.FAILED, + error=str(e) + ) + + +async def run_full_analysis_pipeline(user_id: UUID): + """Run the full analysis pipeline in sequence: Profile -> Categorization -> Post Types.""" + logger.info(f"Starting full analysis pipeline for user {user_id}") + + # 1. Profile Analysis + job1 = job_manager.create_job(JobType.PROFILE_ANALYSIS, str(user_id)) + await run_profile_analysis(user_id, job1.id) + + if job1.status == JobStatus.FAILED: + logger.warning(f"Profile analysis failed, continuing with categorization") + + # 2. Post Categorization + job2 = job_manager.create_job(JobType.POST_CATEGORIZATION, str(user_id)) + await run_post_categorization(user_id, job2.id) + + if job2.status == JobStatus.FAILED: + logger.warning(f"Post categorization failed, continuing with post type analysis") + + # 3. Post Type Analysis + job3 = job_manager.create_job(JobType.POST_TYPE_ANALYSIS, str(user_id)) + await run_post_type_analysis(user_id, job3.id) + + logger.info(f"Full analysis pipeline completed for user {user_id}") diff --git a/src/services/email_service.py b/src/services/email_service.py new file mode 100644 index 0000000..b7dd928 --- /dev/null +++ b/src/services/email_service.py @@ -0,0 +1,467 @@ +"""Email service for sending post approval emails.""" +import smtplib +import secrets +from email.mime.text import MIMEText +from email.mime.multipart import MIMEMultipart +from typing import Optional, Dict, Any +from datetime import datetime, timedelta +from uuid import UUID +from loguru import logger + +from src.config import settings + +# In-memory token store (in production, use Redis or database) +_email_tokens: Dict[str, Dict[str, Any]] = {} + + +def generate_token(post_id: UUID, action: str, expires_hours: int = 72) -> str: + """Generate a unique token for email action.""" + token = secrets.token_urlsafe(32) + _email_tokens[token] = { + "post_id": str(post_id), + "action": action, + "expires_at": datetime.utcnow() + timedelta(hours=expires_hours), + "used": False + } + return token + + +def validate_token(token: str) -> Optional[Dict[str, Any]]: + """Validate and return token data if valid.""" + if token not in _email_tokens: + return None + + token_data = _email_tokens[token] + + if token_data["used"]: + return None + + if datetime.utcnow() > token_data["expires_at"]: + return None + + return token_data + + +def mark_token_used(token: str): + """Mark a token as used.""" + if token in _email_tokens: + _email_tokens[token]["used"] = True + + +def send_email(to_email: str, subject: str, html_content: str) -> bool: + """Send an email using SMTP.""" + if not settings.smtp_host or not settings.smtp_user: + logger.warning("SMTP not configured, skipping email send") + return False + + try: + msg = MIMEMultipart("alternative") + msg["Subject"] = subject + msg["From"] = f"{settings.smtp_from_name} <{settings.smtp_user}>" + msg["To"] = to_email + + html_part = MIMEText(html_content, "html") + msg.attach(html_part) + + with smtplib.SMTP(settings.smtp_host, settings.smtp_port) as server: + server.starttls() + server.login(settings.smtp_user, settings.smtp_password) + server.sendmail(settings.smtp_user, to_email, msg.as_string()) + + logger.info(f"Email sent to {to_email}: {subject}") + return True + except Exception as e: + logger.error(f"Failed to send email to {to_email}: {e}") + return False + + +def send_approval_request_email( + to_email: str, + post_id: UUID, + post_title: str, + post_content: str, + base_url: str, + image_url: Optional[str] = None +) -> bool: + """Send email to customer requesting approval of a post.""" + approve_token = generate_token(post_id, "approve") + reject_token = generate_token(post_id, "reject") + + approve_url = f"{base_url}/api/email-action/{approve_token}" + reject_url = f"{base_url}/api/email-action/{reject_token}" + + html_content = f""" + + + + + + + +
+
+

Neuer LinkedIn Post zur Freigabe

+
+
+

Hallo,

+

Ein neuer LinkedIn Post wurde erstellt und wartet auf deine Freigabe:

+ +

{post_title}

+ +
{post_content}
+ + {f'Post-Bild' if image_url else ''} + +

Bitte entscheide, ob der Post veröffentlicht werden soll:

+ + +
+ +
+ + + """ + + return send_email( + to_email=to_email, + subject=f"Post zur Freigabe: {post_title}", + html_content=html_content + ) + + +def send_invitation_email( + to_email: str, + company_name: str, + inviter_name: str, + token: str, + base_url: str +) -> bool: + """Send invitation email to a new employee.""" + invite_url = f"{base_url}/invite/{token}" + + html_content = f""" + + + + + + + +
+
+

Du wurdest eingeladen!

+
+
+

Hallo,

+

{inviter_name} hat dich eingeladen, dem Team beizutreten:

+ +

{company_name}

+ +

Als Teammitglied kannst du LinkedIn-Posts erstellen, die der Unternehmensstrategie entsprechen.

+ + Einladung annehmen + +

+ Oder kopiere diesen Link in deinen Browser:
+ {invite_url} +

+
+ +
+ + + """ + + return send_email( + to_email=to_email, + subject=f"Einladung von {company_name}", + html_content=html_content + ) + + +def send_email_verification( + to_email: str, + token: str, + base_url: str +) -> bool: + """Send email verification link.""" + verify_url = f"{base_url}/verify-email/{token}" + + html_content = f""" + + + + + + + +
+
+

E-Mail bestaetigen

+
+
+

Hallo,

+

Bitte bestaetigen deine E-Mail-Adresse, um dein Konto zu aktivieren:

+ + E-Mail bestaetigen + +

+ Oder kopiere diesen Link in deinen Browser:
+ {verify_url} +

+
+ +
+ + + """ + + return send_email( + to_email=to_email, + subject="E-Mail-Adresse bestaetigen", + html_content=html_content + ) + + +def send_welcome_email( + to_email: str, + user_name: str, + account_type: str, + base_url: str +) -> bool: + """Send welcome email after registration/onboarding completion.""" + login_url = f"{base_url}/login" + + if account_type == "company": + account_type_text = "Unternehmens-Konto" + next_steps = """ +
    +
  • Lade deine Teammitglieder ein
  • +
  • Verfeinere deine Unternehmensstrategie
  • +
  • Beginne mit der Post-Erstellung
  • +
+ """ + elif account_type == "employee": + account_type_text = "Mitarbeiter-Konto" + next_steps = """ +
    +
  • Recherchiere neue Topics
  • +
  • Erstelle deinen ersten Post
  • +
+ """ + else: + account_type_text = "Ghostwriter-Konto" + next_steps = """ +
    +
  • Recherchiere neue Topics
  • +
  • Erstelle deinen ersten Post
  • +
  • Passe deine Einstellungen an
  • +
+ """ + + html_content = f""" + + + + + + + +
+
+

Willkommen bei LinkedIn Posts!

+
+
+

Hallo {user_name},

+

Dein Konto wurde erfolgreich eingerichtet!

+ + + +

Naechste Schritte:

+ {next_steps} + + Zum Dashboard +
+ +
+ + + """ + + return send_email( + to_email=to_email, + subject="Willkommen bei LinkedIn Posts!", + html_content=html_content + ) + + +def send_employee_removal_email( + to_email: str, + employee_name: str, + company_name: str +) -> bool: + """Send email to notify an employee they have been removed from the company.""" + html_content = f""" + + + + + + + +
+
+

Konto entfernt

+
+
+

Hallo {employee_name},

+

dein Konto wurde aus dem Unternehmen entfernt:

+ +

{company_name}

+ +

Dein Zugang wurde deaktiviert und alle zugehoerigen Daten wurden geloescht.

+ +

Falls du Fragen hast, wende dich bitte an deinen ehemaligen Arbeitgeber.

+
+ +
+ + + """ + + return send_email( + to_email=to_email, + subject=f"Dein Konto bei {company_name} wurde entfernt", + html_content=html_content + ) + + +def send_decision_notification_email( + to_email: str, + post_title: str, + decision: str, # "approved" or "rejected" + base_url: str, + post_id: UUID, + image_url: Optional[str] = None +) -> bool: + """Send email to creator notifying them of the customer's decision.""" + post_url = f"{base_url}/posts/{post_id}" + + if decision == "approved": + decision_text = "freigegeben" + decision_color = "#22c55e" + action_text = "Der Post wurde in die Spalte 'Freigegeben' verschoben und kann nun im Kalender eingeplant werden." + else: + decision_text = "zur Überarbeitung zurückgeschickt" + decision_color = "#f59e0b" + action_text = "Der Post wurde zurück in die Spalte 'Vorschläge' verschoben. Bitte überarbeite ihn und sende ihn erneut zur Freigabe." + + html_content = f""" + + + + + + + +
+
+

Entscheidung zu deinem Post

+
+
+

Hallo,

+

Der Kunde hat eine Entscheidung zu deinem Post getroffen:

+ +

{post_title}

+ +

{decision_text.upper()}

+ + {f'Post-Bild' if image_url else ''} + +

{action_text}

+ + Post ansehen +
+ +
+ + + """ + + return send_email( + to_email=to_email, + subject=f"Post {decision_text}: {post_title}", + html_content=html_content + ) diff --git a/src/services/linkedin_service.py b/src/services/linkedin_service.py new file mode 100644 index 0000000..4247d76 --- /dev/null +++ b/src/services/linkedin_service.py @@ -0,0 +1,314 @@ +""" +LinkedIn API service for auto-posting. + +Handles: +- UGC Posts API (v2) for posting content +- OAuth token refresh +- Image upload for posts with media +""" + +import asyncio +import logging +from datetime import datetime, timedelta, timezone +from typing import Dict, Optional +from uuid import UUID + +import httpx + +from src.config import settings +from src.database.client import db +from src.utils.encryption import decrypt_token, encrypt_token + +logger = logging.getLogger(__name__) + + +class LinkedInService: + """Service for LinkedIn API interactions.""" + + BASE_URL = "https://api.linkedin.com/v2" + TIMEOUT = 30.0 + + def __init__(self): + self.db = db + + async def post_to_linkedin( + self, + linkedin_account_id: UUID, + text: str, + image_url: Optional[str] = None + ) -> Dict: + """ + Post content to LinkedIn using UGC Posts API. + + Args: + linkedin_account_id: ID of the linkedin_accounts record + text: Post text content + image_url: Optional image URL from Supabase storage + + Returns: + Dict with 'url' key containing the LinkedIn post URL + + Raises: + Exception: On API errors + """ + # Get account with decrypted token + account = await self._get_account_with_token(linkedin_account_id) + if not account: + raise ValueError(f"LinkedIn account {linkedin_account_id} not found") + + # Check token expiry + if account['token_expires_at'] < datetime.now(timezone.utc): + raise ValueError("Access token expired - refresh needed") + + access_token = account['access_token'] + linkedin_user_id = account['linkedin_user_id'] + + # Build post payload + payload = { + "author": f"urn:li:person:{linkedin_user_id}", + "lifecycleState": "PUBLISHED", + "specificContent": { + "com.linkedin.ugc.ShareContent": { + "shareCommentary": { + "text": text + }, + "shareMediaCategory": "NONE" + } + }, + "visibility": { + "com.linkedin.ugc.MemberNetworkVisibility": "PUBLIC" + } + } + + # Handle image upload if provided + if image_url: + try: + media_urn = await self._upload_image(access_token, linkedin_user_id, image_url) + if media_urn: + payload["specificContent"]["com.linkedin.ugc.ShareContent"]["shareMediaCategory"] = "IMAGE" + payload["specificContent"]["com.linkedin.ugc.ShareContent"]["media"] = [ + { + "status": "READY", + "media": media_urn + } + ] + except Exception as e: + logger.warning(f"Image upload failed for {linkedin_account_id}: {e}. Posting without image.") + + # Post to LinkedIn + async with httpx.AsyncClient(timeout=self.TIMEOUT) as client: + response = await client.post( + f"{self.BASE_URL}/ugcPosts", + headers={ + "Authorization": f"Bearer {access_token}", + "Content-Type": "application/json", + "X-Restli-Protocol-Version": "2.0.0" + }, + json=payload + ) + + if response.status_code == 201: + # Success + post_id = response.json().get("id") + await self._update_account_last_used(linkedin_account_id) + + # LinkedIn post URL format (best effort) + post_url = f"https://www.linkedin.com/feed/update/{post_id}/" if post_id else None + + logger.info(f"Posted to LinkedIn: {post_url}") + return {"url": post_url, "post_id": post_id} + + elif response.status_code == 401: + # Token expired or invalid + await self._mark_account_error( + linkedin_account_id, + f"Token expired or invalid (401)" + ) + raise Exception("LinkedIn authentication failed - token may be expired") + + elif response.status_code == 429: + # Rate limit + await self._mark_account_error( + linkedin_account_id, + "Rate limit exceeded (429)" + ) + raise Exception("LinkedIn rate limit exceeded") + + else: + # Other error + error_msg = f"LinkedIn API error {response.status_code}: {response.text}" + await self._mark_account_error(linkedin_account_id, error_msg) + raise Exception(error_msg) + + async def _upload_image( + self, + access_token: str, + linkedin_user_id: str, + image_url: str + ) -> Optional[str]: + """ + Upload image to LinkedIn for use in post. + + Returns media URN if successful, None otherwise. + """ + try: + # Step 1: Register upload + register_payload = { + "registerUploadRequest": { + "recipes": ["urn:li:digitalmediaRecipe:feedshare-image"], + "owner": f"urn:li:person:{linkedin_user_id}", + "serviceRelationships": [ + { + "relationshipType": "OWNER", + "identifier": "urn:li:userGeneratedContent" + } + ] + } + } + + async with httpx.AsyncClient(timeout=self.TIMEOUT) as client: + register_response = await client.post( + f"{self.BASE_URL}/assets?action=registerUpload", + headers={ + "Authorization": f"Bearer {access_token}", + "Content-Type": "application/json" + }, + json=register_payload + ) + + if register_response.status_code != 200: + logger.error(f"Image register failed: {register_response.status_code}") + return None + + register_data = register_response.json() + upload_url = register_data["value"]["uploadMechanism"]["com.linkedin.digitalmedia.uploading.MediaUploadHttpRequest"]["uploadUrl"] + asset_urn = register_data["value"]["asset"] + + # Step 2: Download image from Supabase + image_response = await client.get(image_url) + if image_response.status_code != 200: + logger.error(f"Failed to download image from {image_url}") + return None + + image_data = image_response.content + + # Step 3: Upload to LinkedIn + upload_response = await client.put( + upload_url, + headers={ + "Authorization": f"Bearer {access_token}", + "Content-Type": "application/octet-stream" + }, + content=image_data + ) + + if upload_response.status_code in [200, 201]: + logger.info(f"Image uploaded successfully: {asset_urn}") + return asset_urn + else: + logger.error(f"Image upload failed: {upload_response.status_code}") + return None + + except Exception as e: + logger.error(f"Image upload error: {e}") + return None + + async def refresh_access_token(self, linkedin_account_id: UUID) -> bool: + """ + Attempt to refresh the access token using refresh token. + + Note: LinkedIn's OAuth 2.0 may not support refresh tokens for all scopes. + Check LinkedIn documentation for current support. + + Returns: + True if refresh succeeded, False otherwise + """ + account = await self._get_account_with_token(linkedin_account_id) + if not account or not account.get('refresh_token'): + logger.warning(f"No refresh token for account {linkedin_account_id}") + return False + + refresh_token = account['refresh_token'] + + try: + async with httpx.AsyncClient(timeout=self.TIMEOUT) as client: + response = await client.post( + "https://www.linkedin.com/oauth/v2/accessToken", + data={ + "grant_type": "refresh_token", + "refresh_token": refresh_token, + "client_id": settings.linkedin_client_id, + "client_secret": settings.linkedin_client_secret + }, + headers={"Content-Type": "application/x-www-form-urlencoded"} + ) + + if response.status_code == 200: + token_data = response.json() + new_access_token = token_data["access_token"] + expires_in = token_data.get("expires_in", 5184000) # Default 60 days + + # Encrypt and update tokens + encrypted_access = encrypt_token(new_access_token) + new_refresh = token_data.get("refresh_token") + encrypted_refresh = encrypt_token(new_refresh) if new_refresh else account['refresh_token'] + + await self.db.update_linkedin_account( + linkedin_account_id, + { + "access_token": encrypted_access, + "refresh_token": encrypted_refresh, + "token_expires_at": datetime.now(timezone.utc) + timedelta(seconds=expires_in), + "last_error": None, + "last_error_at": None + } + ) + + logger.info(f"Token refreshed for account {linkedin_account_id}") + return True + else: + logger.error(f"Token refresh failed: {response.status_code} - {response.text}") + return False + + except Exception as e: + logger.error(f"Token refresh error: {e}") + return False + + async def _get_account_with_token(self, linkedin_account_id: UUID) -> Optional[Dict]: + """Get account and decrypt tokens.""" + account = await self.db.get_linkedin_account_by_id(linkedin_account_id) + if not account: + return None + + # Decrypt tokens + account_dict = account.model_dump() + account_dict['access_token'] = decrypt_token(account.access_token) + if account.refresh_token: + account_dict['refresh_token'] = decrypt_token(account.refresh_token) + + return account_dict + + async def _update_account_last_used(self, linkedin_account_id: UUID): + """Update last_used_at timestamp.""" + await self.db.update_linkedin_account( + linkedin_account_id, + { + "last_used_at": datetime.now(timezone.utc), + "last_error": None, + "last_error_at": None + } + ) + + async def _mark_account_error(self, linkedin_account_id: UUID, error_msg: str): + """Mark account with error.""" + await self.db.update_linkedin_account( + linkedin_account_id, + { + "last_error": error_msg, + "last_error_at": datetime.now(timezone.utc) + } + ) + + +# Global instance +linkedin_service = LinkedInService() diff --git a/src/services/scheduler_service.py b/src/services/scheduler_service.py new file mode 100644 index 0000000..f737e01 --- /dev/null +++ b/src/services/scheduler_service.py @@ -0,0 +1,288 @@ +"""Background scheduler for handling scheduled posts. + +This service runs in the background and: +1. Checks for posts that are due for publishing +2. Marks them as published +3. Sends notification emails to employees + +Future extension: Could integrate with LinkedIn API for automatic posting. +""" +import asyncio +from datetime import datetime, timezone +from typing import Optional +from loguru import logger + +from src.database.client import DatabaseClient +from src.services.email_service import send_email + + +class SchedulerService: + """Background scheduler for post publishing.""" + + def __init__(self, db: DatabaseClient, check_interval_seconds: int = 60): + """Initialize the scheduler. + + Args: + db: Database client instance + check_interval_seconds: How often to check for due posts (default: 60s) + """ + self.db = db + self.check_interval = check_interval_seconds + self._running = False + self._task: Optional[asyncio.Task] = None + + async def start(self): + """Start the background scheduler.""" + if self._running: + logger.warning("Scheduler already running") + return + + self._running = True + self._task = asyncio.create_task(self._run_loop()) + logger.info(f"Scheduler started (checking every {self.check_interval}s)") + + async def stop(self): + """Stop the background scheduler.""" + self._running = False + if self._task: + self._task.cancel() + try: + await self._task + except asyncio.CancelledError: + pass + logger.info("Scheduler stopped") + + async def _run_loop(self): + """Main scheduler loop.""" + while self._running: + try: + await self._process_due_posts() + except Exception as e: + logger.error(f"Scheduler error: {e}") + + await asyncio.sleep(self.check_interval) + + async def _process_due_posts(self): + """Process all posts that are due for publishing.""" + due_posts = await self.db.get_scheduled_posts_due() + + if not due_posts: + return + + logger.info(f"Processing {len(due_posts)} due posts") + + for post in due_posts: + try: + await self._publish_post(post) + except Exception as e: + logger.error(f"Failed to publish post {post.id}: {e}") + + async def _publish_post(self, post): + """Publish a single post - with LinkedIn API if account linked, otherwise email.""" + from uuid import UUID + + # Get LinkedIn account for user + linkedin_account = await self.db.get_linkedin_account(post.user_id) + + if linkedin_account: + # User has LinkedIn account linked -> Try auto-posting + try: + await self._auto_post_to_linkedin(post, linkedin_account) + return # Success - no need for email fallback + except Exception as e: + logger.error(f"LinkedIn auto-post failed for post {post.id}: {e}") + # Fall through to email notification + + # No LinkedIn account or auto-post failed -> Send email notification + await self._publish_post_via_email(post) + + async def _auto_post_to_linkedin(self, post, linkedin_account): + """Auto-post to LinkedIn using linked account.""" + from src.services.linkedin_service import linkedin_service + from src.utils.encryption import decrypt_token + + # Check token expiry + if linkedin_account.token_expires_at < datetime.now(timezone.utc): + logger.warning(f"LinkedIn token expired for account {linkedin_account.id}") + + # Try to refresh token + refreshed = await linkedin_service.refresh_access_token(linkedin_account.id) + if not refreshed: + # Token refresh failed -> Fall back to email + raise Exception("Token expired and refresh failed") + + # Post to LinkedIn + result = await linkedin_service.post_to_linkedin( + linkedin_account_id=linkedin_account.id, + text=post.post_content, + image_url=post.image_url + ) + + # Update post as published with LinkedIn metadata + await self.db.update_generated_post( + post.id, + { + "status": "published", + "published_at": datetime.now(timezone.utc), + "metadata": { + **(post.metadata or {}), + "linkedin_post_url": result.get("url"), + "auto_posted": True, + "posted_at": datetime.now(timezone.utc).isoformat() + } + } + ) + + logger.info(f"✅ Post {post.id} auto-posted to LinkedIn: {result.get('url')}") + + # Send success notification + profile = await self.db.get_profile(post.user_id) + if profile: + await self._send_auto_post_success_notification(post, profile, result) + + async def _publish_post_via_email(self, post): + """Fallback: Mark as published and send email notification.""" + # Update post status to published + await self.db.update_generated_post( + post.id, + { + "status": "published", + "published_at": datetime.now(timezone.utc) + } + ) + + logger.info(f"Post {post.id} marked as published (email notification)") + + # Get profile info for notification + profile = await self.db.get_profile(post.user_id) + if not profile: + logger.warning(f"No profile found for post {post.id}") + return + + # Send notification email + await self._send_publish_notification(post, profile) + + async def _send_publish_notification(self, post, profile): + """Send email notification that a post is ready to be published. + + Future: When LinkedIn API is integrated, this could instead + confirm that the post was automatically published. + """ + # Try to get employee email + recipient_email = profile.creator_email or profile.customer_email + if not recipient_email: + logger.warning(f"No email for profile {profile.id}, skipping notification") + return + + subject = "LinkedIn Post bereit zum Veröffentlichen" + + display_name = profile.display_name or "dort" + + html_content = f""" +
+

Dein LinkedIn Post ist bereit! 🚀

+ +

Hallo {display_name},

+ +

Dein geplanter LinkedIn Post ist jetzt zur Veröffentlichung bereit:

+ +
+

Thema: {post.topic_title}

+
+{post.post_content[:500]}{'...' if len(post.post_content) > 500 else ''} +
+ {f'Post-Bild' if post.image_url else ''} +
+ +

Nächste Schritte:

+
    +
  1. Öffne LinkedIn
  2. +
  3. Erstelle einen neuen Post
  4. +
  5. Kopiere den Text oben und füge ihn ein
  6. +
  7. Veröffentliche den Post
  8. +
+ +

+ Diese Nachricht wurde automatisch generiert. +

+
+ """ + + try: + send_email( + to_email=recipient_email, + subject=subject, + html_content=html_content + ) + logger.info(f"Publish notification sent to {recipient_email}") + except Exception as e: + logger.error(f"Failed to send publish notification: {e}") + + async def _send_auto_post_success_notification(self, post, profile, result): + """Send success email after auto-posting to LinkedIn.""" + recipient_email = profile.creator_email or profile.customer_email + if not recipient_email: + logger.warning(f"No email for profile {profile.id}, skipping success notification") + return + + subject = "✅ LinkedIn Post automatisch veröffentlicht" + display_name = profile.display_name or "dort" + linkedin_url = result.get('url', 'https://www.linkedin.com/feed/') + + html_content = f""" +
+

Post erfolgreich veröffentlicht! 🎉

+ +

Hallo {display_name},

+ +

Dein geplanter LinkedIn Post wurde automatisch veröffentlicht:

+ +
+

Thema: {post.topic_title}

+
+{post.post_content[:500]}{'...' if len(post.post_content) > 500 else ''} +
+ {f'Post-Bild' if post.image_url else ''} +
+ +

+ + Auf LinkedIn ansehen → + +

+ +

+ Dein Post wurde automatisch über dein verbundenes LinkedIn-Konto veröffentlicht. +

+ +

+ Diese Nachricht wurde automatisch generiert. +

+
+ """ + + try: + send_email( + to_email=recipient_email, + subject=subject, + html_content=html_content + ) + logger.info(f"Auto-post success notification sent to {recipient_email}") + except Exception as e: + logger.error(f"Failed to send success notification: {e}") + + +# Global scheduler instance +_scheduler: Optional[SchedulerService] = None + + +def get_scheduler() -> Optional[SchedulerService]: + """Get the global scheduler instance.""" + return _scheduler + + +def init_scheduler(db: DatabaseClient, check_interval: int = 60) -> SchedulerService: + """Initialize and return the global scheduler instance.""" + global _scheduler + _scheduler = SchedulerService(db, check_interval) + return _scheduler diff --git a/src/services/storage_service.py b/src/services/storage_service.py new file mode 100644 index 0000000..fa2dcaa --- /dev/null +++ b/src/services/storage_service.py @@ -0,0 +1,103 @@ +"""Supabase Storage service for post image uploads.""" +import asyncio +import uuid +from typing import Optional +from loguru import logger + +from src.config import settings + + +ALLOWED_CONTENT_TYPES = {"image/jpeg", "image/png", "image/gif", "image/webp"} +MAX_FILE_SIZE = 5 * 1024 * 1024 # 5MB +BUCKET_NAME = "post-images" + +CONTENT_TYPE_EXTENSIONS = { + "image/jpeg": "jpg", + "image/png": "png", + "image/gif": "gif", + "image/webp": "webp", +} + + +class StorageService: + """Handles image uploads to Supabase Storage.""" + + def __init__(self): + from supabase import create_client + key = settings.supabase_service_role_key or settings.supabase_key + self.client = create_client(settings.supabase_url, key) + self._bucket_ensured = False + + def _ensure_bucket(self): + """Create the post-images bucket if it doesn't exist.""" + if self._bucket_ensured: + return + try: + self.client.storage.get_bucket(BUCKET_NAME) + except Exception: + try: + self.client.storage.create_bucket( + BUCKET_NAME, + options={"public": True} + ) + logger.info(f"Created storage bucket: {BUCKET_NAME}") + except Exception as e: + if "already exists" in str(e).lower() or "Duplicate" in str(e): + pass + else: + logger.error(f"Failed to create bucket: {e}") + raise + self._bucket_ensured = True + + async def upload_image( + self, + file_content: bytes, + content_type: str, + user_id: str, + ) -> str: + """Upload an image to Supabase Storage. + + Returns the public URL of the uploaded image. + """ + if content_type not in ALLOWED_CONTENT_TYPES: + raise ValueError(f"Unzulässiger Dateityp: {content_type}. Erlaubt: JPEG, PNG, GIF, WebP") + + if len(file_content) > MAX_FILE_SIZE: + raise ValueError(f"Datei zu groß (max. {MAX_FILE_SIZE // 1024 // 1024} MB)") + + ext = CONTENT_TYPE_EXTENSIONS[content_type] + file_name = f"{user_id}/{uuid.uuid4()}.{ext}" + + def _upload(): + self._ensure_bucket() + self.client.storage.from_(BUCKET_NAME).upload( + path=file_name, + file=file_content, + file_options={"content-type": content_type}, + ) + + await asyncio.to_thread(_upload) + + public_url = f"{settings.supabase_url}/storage/v1/object/public/{BUCKET_NAME}/{file_name}" + logger.info(f"Uploaded image: {file_name}") + return public_url + + async def delete_image(self, image_url: str) -> None: + """Delete an image from Supabase Storage by its public URL.""" + prefix = f"{settings.supabase_url}/storage/v1/object/public/{BUCKET_NAME}/" + if not image_url.startswith(prefix): + logger.warning(f"Cannot delete image, URL doesn't match bucket: {image_url}") + return + + file_path = image_url[len(prefix):] + + def _delete(): + self._ensure_bucket() + self.client.storage.from_(BUCKET_NAME).remove([file_path]) + + await asyncio.to_thread(_delete) + logger.info(f"Deleted image: {file_path}") + + +# Global singleton +storage = StorageService() diff --git a/src/tui/app.py b/src/tui/app.py index 33c04c6..5b1ea6e 100644 --- a/src/tui/app.py +++ b/src/tui/app.py @@ -717,7 +717,7 @@ class StatusScreen(Screen): output = "" for customer in customers: - status = await orchestrator.get_customer_status(customer.id) + status = await orchestrator.get_user_status(customer.id) output += f"[bold cyan]╔═══ {customer.name} ═══╗[/]\n" output += f"[bold]Customer ID:[/] {customer.id}\n" diff --git a/src/utils/__init__.py b/src/utils/__init__.py new file mode 100644 index 0000000..183c974 --- /dev/null +++ b/src/utils/__init__.py @@ -0,0 +1 @@ +"""Utility modules.""" diff --git a/src/utils/encryption.py b/src/utils/encryption.py new file mode 100644 index 0000000..47dc2a6 --- /dev/null +++ b/src/utils/encryption.py @@ -0,0 +1,38 @@ +"""Token encryption utilities using Fernet symmetric encryption.""" + +from cryptography.fernet import Fernet +from src.config import settings + + +def encrypt_token(token: str) -> str: + """ + Encrypt a token string using Fernet symmetric encryption. + + Args: + token: The plaintext token to encrypt + + Returns: + Base64-encoded encrypted token string + """ + if not settings.encryption_key: + raise ValueError("ENCRYPTION_KEY not configured in environment") + + cipher = Fernet(settings.encryption_key.encode()) + return cipher.encrypt(token.encode()).decode() + + +def decrypt_token(encrypted: str) -> str: + """ + Decrypt a Fernet-encrypted token string. + + Args: + encrypted: The base64-encoded encrypted token + + Returns: + The decrypted plaintext token + """ + if not settings.encryption_key: + raise ValueError("ENCRYPTION_KEY not configured in environment") + + cipher = Fernet(settings.encryption_key.encode()) + return cipher.decrypt(encrypted.encode()).decode() diff --git a/src/web/admin/routes.py b/src/web/admin/routes.py index c2570f7..e27cf27 100644 --- a/src/web/admin/routes.py +++ b/src/web/admin/routes.py @@ -1,20 +1,18 @@ -"""Admin panel routes (password-protected).""" -import asyncio -import json +"""Admin panel routes (password-protected) - User Management & Statistics.""" +import re +import secrets +from datetime import datetime, timedelta from pathlib import Path from typing import Optional from uuid import UUID +from collections import defaultdict -from fastapi import APIRouter, Request, Form, BackgroundTasks, HTTPException +from fastapi import APIRouter, Request, Form, HTTPException from fastapi.templating import Jinja2Templates -from fastapi.responses import HTMLResponse, RedirectResponse -from pydantic import BaseModel +from fastapi.responses import HTMLResponse, RedirectResponse, JSONResponse from loguru import logger -from src.config import settings from src.database import db -from src.orchestrator import orchestrator -from src.email_service import email_service from src.web.admin.auth import ( WEB_PASSWORD, AUTH_COOKIE_NAME, hash_password, verify_auth ) @@ -26,22 +24,6 @@ admin_router = APIRouter(prefix="/admin", tags=["admin"]) # Templates templates = Jinja2Templates(directory=Path(__file__).parent.parent / "templates" / "admin") -# Store for progress updates -progress_store = {} - - -async def get_customer_profile_picture(customer_id: UUID) -> Optional[str]: - """Get profile picture URL from customer's LinkedIn posts.""" - linkedin_posts = await db.get_linkedin_posts(customer_id) - for lp in linkedin_posts: - if lp.raw_data and isinstance(lp.raw_data, dict): - author = lp.raw_data.get("author", {}) - if author and isinstance(author, dict): - profile_picture_url = author.get("profile_picture") - if profile_picture_url: - return profile_picture_url - return None - # ==================== AUTH ROUTES ==================== @@ -82,44 +64,89 @@ async def logout(): return response -@admin_router.get("/impersonate/{customer_id}") -async def impersonate_user(request: Request, customer_id: UUID): - """Login as a user without OAuth (for testing). +# ==================== IMPERSONATE (enhanced for all account types) ==================== - Creates a user session for the given customer and redirects to the user dashboard. +@admin_router.get("/impersonate/{user_id}") +async def impersonate_user(request: Request, user_id: UUID): + """Login as any user type (ghostwriter, company owner, employee). + + Creates a user session for the given user and redirects to their dashboard. Only accessible by authenticated admins. """ if not verify_auth(request): return RedirectResponse(url="/admin/login", status_code=302) try: - customer = await db.get_customer(customer_id) - if not customer: - raise HTTPException(status_code=404, detail="Customer not found") + # Get profile to determine account type + profile = await db.get_profile(user_id) + if not profile: + raise HTTPException(status_code=404, detail="User not found") - # Extract vanity name from LinkedIn URL if available - linkedin_vanity = "" - if customer.linkedin_url: - import re - match = re.search(r'linkedin\.com/in/([^/?]+)', customer.linkedin_url) - if match: - linkedin_vanity = match.group(1) + user = await db.get_user(user_id) + account_type = profile.account_type.value if hasattr(profile.account_type, 'value') else profile.account_type - # Get profile picture - profile_picture = await get_customer_profile_picture(customer_id) + # Build session based on account type + if account_type == "company": + # Company owner + company = await db.get_company_by_owner(user_id) if not profile.company_id else await db.get_company(profile.company_id) + session = UserSession( + user_id=str(user_id), + linkedin_vanity_name="", + linkedin_name=profile.display_name or (user.email if user else ""), + linkedin_picture=None, + email=user.email if user else None, + account_type="company", + company_id=str(company.id) if company else (str(profile.company_id) if profile.company_id else None), + onboarding_status=profile.onboarding_status.value if hasattr(profile.onboarding_status, 'value') else profile.onboarding_status, + company_name=company.name if company else None, + display_name=profile.display_name + ) + response = RedirectResponse(url="/company/manage", status_code=302) - # Create user session - session = UserSession( - customer_id=str(customer.id), - customer_name=customer.name, - linkedin_vanity_name=linkedin_vanity or customer.name.lower().replace(" ", "-"), - linkedin_name=customer.name, - linkedin_picture=profile_picture, - email=customer.email - ) + elif account_type == "employee": + # Employee + company = await db.get_company(profile.company_id) if profile.company_id else None + + linkedin_vanity = "" + if profile.linkedin_url: + match = re.search(r'linkedin\.com/in/([^/?]+)', profile.linkedin_url) + if match: + linkedin_vanity = match.group(1) + + session = UserSession( + user_id=str(user_id), + linkedin_vanity_name=linkedin_vanity, + linkedin_name=profile.display_name or "", + linkedin_picture=profile.profile_picture, + email=user.email if user else None, + account_type="employee", + company_id=str(profile.company_id) if profile.company_id else None, + onboarding_status=profile.onboarding_status.value if hasattr(profile.onboarding_status, 'value') else profile.onboarding_status, + company_name=company.name if company else None, + display_name=profile.display_name + ) + response = RedirectResponse(url="/", status_code=302) + + else: + # Ghostwriter (default) + linkedin_vanity = "" + if profile.linkedin_url: + match = re.search(r'linkedin\.com/in/([^/?]+)', profile.linkedin_url) + if match: + linkedin_vanity = match.group(1) + + session = UserSession( + user_id=str(user_id), + linkedin_vanity_name=linkedin_vanity or (profile.display_name or "").lower().replace(" ", "-"), + linkedin_name=profile.display_name or "", + linkedin_picture=profile.profile_picture, + email=user.email if user else None, + account_type="ghostwriter", + onboarding_status=profile.onboarding_status.value if hasattr(profile.onboarding_status, 'value') else profile.onboarding_status, + display_name=profile.display_name + ) + response = RedirectResponse(url="/", status_code=302) - # Redirect to user dashboard with session cookie - response = RedirectResponse(url="/", status_code=302) set_user_session(response, session) return response @@ -130,564 +157,464 @@ async def impersonate_user(request: Request, customer_id: UUID): raise HTTPException(status_code=500, detail=str(e)) -# ==================== PAGES ==================== +# ==================== USER MANAGEMENT (main admin page) ==================== @admin_router.get("", response_class=HTMLResponse) @admin_router.get("/", response_class=HTMLResponse) async def home(request: Request): - """Admin dashboard.""" + """Admin dashboard - User Management.""" if not verify_auth(request): return RedirectResponse(url="/admin/login", status_code=302) try: - customers = await db.list_customers() - total_posts = 0 - for customer in customers: - posts = await db.get_generated_posts(customer.id) - total_posts += len(posts) + # Get all users + all_users = await db.list_users() + + # Get all companies + companies = await db.list_companies() + company_map = {str(c.id): c for c in companies} + + # Build company data with owner + employees + company_data = [] + for company in companies: + owner = next((u for u in all_users if str(u.id) == str(company.owner_user_id)), None) + employee_users = await db.list_users(account_type="employee", company_id=company.id) + company_data.append({ + "company": company, + "owner": owner, + "employees": employee_users, + "employee_count": len(employee_users) + }) + + # Get ghostwriters + ghostwriters = await db.list_users(account_type="ghostwriter") + + # Summary stats + total_users = len(all_users) + total_companies = len(companies) + total_ghostwriters = len(ghostwriters) + total_employees = sum(cd["employee_count"] for cd in company_data) return templates.TemplateResponse("dashboard.html", { "request": request, - "page": "home", - "customers_count": len(customers), - "total_posts": total_posts + "page": "users", + "total_users": total_users, + "total_companies": total_companies, + "total_ghostwriters": total_ghostwriters, + "total_employees": total_employees, + "company_data": company_data, + "ghostwriters": ghostwriters, + "company_map": company_map, }) except Exception as e: - logger.error(f"Error loading dashboard: {e}") + logger.error(f"Error loading admin dashboard: {e}") return templates.TemplateResponse("dashboard.html", { "request": request, - "page": "home", - "error": str(e) + "page": "users", + "error": str(e), + "total_users": 0, + "total_companies": 0, + "total_ghostwriters": 0, + "total_employees": 0, + "company_data": [], + "ghostwriters": [], + "company_map": {}, }) -@admin_router.get("/customers/new", response_class=HTMLResponse) -async def new_customer_page(request: Request): - """New customer setup page.""" +# ==================== USER DELETE API ==================== + +@admin_router.delete("/api/users/{user_id}") +async def delete_user(request: Request, user_id: UUID): + """Delete a user and all related data.""" if not verify_auth(request): - return RedirectResponse(url="/admin/login", status_code=302) - return templates.TemplateResponse("new_customer.html", { - "request": request, - "page": "new_customer" - }) - - -@admin_router.get("/research", response_class=HTMLResponse) -async def research_page(request: Request): - """Research topics page.""" - if not verify_auth(request): - return RedirectResponse(url="/admin/login", status_code=302) - customers = await db.list_customers() - return templates.TemplateResponse("research.html", { - "request": request, - "page": "research", - "customers": customers - }) - - -@admin_router.get("/create", response_class=HTMLResponse) -async def create_post_page(request: Request): - """Create post page.""" - if not verify_auth(request): - return RedirectResponse(url="/admin/login", status_code=302) - customers = await db.list_customers() - return templates.TemplateResponse("create_post.html", { - "request": request, - "page": "create", - "customers": customers - }) - - -@admin_router.get("/posts", response_class=HTMLResponse) -async def posts_page(request: Request): - """View all posts page.""" - if not verify_auth(request): - return RedirectResponse(url="/admin/login", status_code=302) + raise HTTPException(status_code=401, detail="Not authenticated") try: - customers = await db.list_customers() - customers_with_posts = [] - - for customer in customers: - posts = await db.get_generated_posts(customer.id) - profile_picture = await get_customer_profile_picture(customer.id) - customers_with_posts.append({ - "customer": customer, - "posts": posts, - "post_count": len(posts), - "profile_picture": profile_picture - }) - - return templates.TemplateResponse("posts.html", { - "request": request, - "page": "posts", - "customers_with_posts": customers_with_posts, - "total_posts": sum(c["post_count"] for c in customers_with_posts) - }) + await db.delete_user_completely(user_id) + return {"success": True, "message": f"User {user_id} deleted"} except Exception as e: - logger.error(f"Error loading posts: {e}") - return templates.TemplateResponse("posts.html", { - "request": request, - "page": "posts", - "customers_with_posts": [], - "total_posts": 0, - "error": str(e) - }) - - -@admin_router.get("/posts/{post_id}", response_class=HTMLResponse) -async def post_detail_page(request: Request, post_id: str): - """Detailed view of a single post.""" - if not verify_auth(request): - return RedirectResponse(url="/admin/login", status_code=302) - try: - post = await db.get_generated_post(UUID(post_id)) - if not post: - return RedirectResponse(url="/admin/posts", status_code=302) - - customer = await db.get_customer(post.customer_id) - linkedin_posts = await db.get_linkedin_posts(post.customer_id) - reference_posts = [p.post_text for p in linkedin_posts if p.post_text and len(p.post_text) > 100][:10] - - profile_picture_url = None - for lp in linkedin_posts: - if lp.raw_data and isinstance(lp.raw_data, dict): - author = lp.raw_data.get("author", {}) - if author and isinstance(author, dict): - profile_picture_url = author.get("profile_picture") - if profile_picture_url: - break - - profile_analysis_record = await db.get_profile_analysis(post.customer_id) - profile_analysis = profile_analysis_record.full_analysis if profile_analysis_record else None - - post_type = None - post_type_analysis = None - if post.post_type_id: - post_type = await db.get_post_type(post.post_type_id) - if post_type and post_type.analysis: - post_type_analysis = post_type.analysis - - final_feedback = None - if post.critic_feedback and len(post.critic_feedback) > 0: - final_feedback = post.critic_feedback[-1] - - return templates.TemplateResponse("post_detail.html", { - "request": request, - "page": "posts", - "post": post, - "customer": customer, - "reference_posts": reference_posts, - "profile_analysis": profile_analysis, - "post_type": post_type, - "post_type_analysis": post_type_analysis, - "final_feedback": final_feedback, - "profile_picture_url": profile_picture_url - }) - except Exception as e: - logger.error(f"Error loading post detail: {e}") - return RedirectResponse(url="/admin/posts", status_code=302) - - -@admin_router.get("/status", response_class=HTMLResponse) -async def status_page(request: Request): - """Customer status page.""" - if not verify_auth(request): - return RedirectResponse(url="/admin/login", status_code=302) - try: - customers = await db.list_customers() - customer_statuses = [] - - for customer in customers: - status = await orchestrator.get_customer_status(customer.id) - profile_picture = await get_customer_profile_picture(customer.id) - customer_statuses.append({ - "customer": customer, - "status": status, - "profile_picture": profile_picture - }) - - return templates.TemplateResponse("status.html", { - "request": request, - "page": "status", - "customer_statuses": customer_statuses - }) - except Exception as e: - logger.error(f"Error loading status: {e}") - return templates.TemplateResponse("status.html", { - "request": request, - "page": "status", - "customer_statuses": [], - "error": str(e) - }) - - -@admin_router.get("/scraped-posts", response_class=HTMLResponse) -async def scraped_posts_page(request: Request): - """Manage scraped LinkedIn posts.""" - if not verify_auth(request): - return RedirectResponse(url="/admin/login", status_code=302) - customers = await db.list_customers() - return templates.TemplateResponse("scraped_posts.html", { - "request": request, - "page": "scraped_posts", - "customers": customers - }) - - -# ==================== API ENDPOINTS ==================== - -@admin_router.post("/api/customers") -async def create_customer( - background_tasks: BackgroundTasks, - name: str = Form(...), - linkedin_url: str = Form(...), - company_name: str = Form(None), - email: str = Form(None), - persona: str = Form(None), - form_of_address: str = Form(None), - style_guide: str = Form(None), - post_types_json: str = Form(None) -): - """Create a new customer and run initial setup.""" - task_id = f"setup_{name}_{asyncio.get_event_loop().time()}" - progress_store[task_id] = {"status": "starting", "message": "Starte Setup...", "progress": 0} - - customer_data = { - "company_name": company_name, - "email": email, - "persona": persona, - "form_of_address": form_of_address, - "style_guide": style_guide, - "topic_history": [], - "example_posts": [] - } - - post_types_data = None - if post_types_json: - try: - post_types_data = json.loads(post_types_json) - except json.JSONDecodeError: - logger.warning("Failed to parse post_types_json") - - async def run_setup(): - try: - progress_store[task_id] = {"status": "running", "message": "Erstelle Kunde...", "progress": 10} - await asyncio.sleep(0.1) - progress_store[task_id] = {"status": "running", "message": "Scrape LinkedIn Posts...", "progress": 30} - - customer = await orchestrator.run_initial_setup( - linkedin_url=linkedin_url, - customer_name=name, - customer_data=customer_data, - post_types_data=post_types_data - ) - - progress_store[task_id] = { - "status": "completed", - "message": "Setup abgeschlossen!", - "progress": 100, - "customer_id": str(customer.id) - } - except Exception as e: - logger.exception(f"Setup failed: {e}") - progress_store[task_id] = {"status": "error", "message": str(e), "progress": 0} - - background_tasks.add_task(run_setup) - return {"task_id": task_id} - - -@admin_router.get("/api/tasks/{task_id}") -async def get_task_status(task_id: str): - """Get task progress.""" - return progress_store.get(task_id, {"status": "unknown", "message": "Task not found"}) - - -@admin_router.get("/api/customers/{customer_id}/post-types") -async def get_customer_post_types(customer_id: str): - """Get post types for a customer.""" - try: - post_types = await db.get_post_types(UUID(customer_id)) - return { - "post_types": [ - { - "id": str(pt.id), - "name": pt.name, - "description": pt.description, - "identifying_hashtags": pt.identifying_hashtags, - "identifying_keywords": pt.identifying_keywords, - "semantic_properties": pt.semantic_properties, - "has_analysis": pt.analysis is not None, - "analyzed_post_count": pt.analyzed_post_count, - "is_active": pt.is_active - } - for pt in post_types - ] - } - except Exception as e: - logger.error(f"Error loading post types: {e}") - return {"post_types": [], "error": str(e)} - - -@admin_router.get("/api/customers/{customer_id}/linkedin-posts") -async def get_customer_linkedin_posts(customer_id: str): - """Get all scraped LinkedIn posts for a customer.""" - try: - posts = await db.get_linkedin_posts(UUID(customer_id)) - result_posts = [] - for post in posts: - try: - result_posts.append({ - "id": str(post.id), - "post_text": post.post_text, - "post_url": post.post_url, - "posted_at": post.post_date.isoformat() if post.post_date else None, - "engagement_score": (post.likes or 0) + (post.comments or 0) + (post.shares or 0), - "likes": post.likes, - "comments": post.comments, - "shares": post.shares, - "post_type_id": str(post.post_type_id) if post.post_type_id else None, - "classification_method": post.classification_method, - "classification_confidence": post.classification_confidence - }) - except Exception as post_error: - logger.error(f"Error processing post {post.id}: {post_error}") - return {"posts": result_posts, "total": len(result_posts)} - except Exception as e: - logger.exception(f"Error loading LinkedIn posts: {e}") - return {"posts": [], "total": 0, "error": str(e)} - - -class ClassifyPostRequest(BaseModel): - post_type_id: Optional[str] = None - - -@admin_router.patch("/api/linkedin-posts/{post_id}/classify") -async def classify_linkedin_post(post_id: str, request: ClassifyPostRequest): - """Manually classify a LinkedIn post.""" - try: - if request.post_type_id: - await db.update_post_classification( - post_id=UUID(post_id), - post_type_id=UUID(request.post_type_id), - classification_method="manual", - classification_confidence=1.0 - ) - else: - await asyncio.to_thread( - lambda: db.client.table("linkedin_posts").update({ - "post_type_id": None, - "classification_method": None, - "classification_confidence": None - }).eq("id", post_id).execute() - ) - return {"success": True, "post_id": post_id} - except Exception as e: - logger.error(f"Error classifying post: {e}") + logger.error(f"Error deleting user {user_id}: {e}") raise HTTPException(status_code=500, detail=str(e)) -@admin_router.post("/api/customers/{customer_id}/classify-posts") -async def classify_customer_posts(customer_id: str, background_tasks: BackgroundTasks): - """Trigger post classification for a customer.""" - task_id = f"classify_{customer_id}_{asyncio.get_event_loop().time()}" - progress_store[task_id] = {"status": "starting", "message": "Starte Klassifizierung...", "progress": 0} +# ==================== STATISTICS ==================== - async def run_classification(): - try: - progress_store[task_id] = {"status": "running", "message": "Klassifiziere Posts...", "progress": 50} - count = await orchestrator.classify_posts(UUID(customer_id)) - progress_store[task_id] = { - "status": "completed", - "message": f"{count} Posts klassifiziert", - "progress": 100, - "classified_count": count - } - except Exception as e: - logger.exception(f"Classification failed: {e}") - progress_store[task_id] = {"status": "error", "message": str(e), "progress": 0} +@admin_router.get("/statistics", response_class=HTMLResponse) +async def statistics_page(request: Request): + """Statistics page with usage charts.""" + if not verify_auth(request): + return RedirectResponse(url="/admin/login", status_code=302) - background_tasks.add_task(run_classification) - return {"task_id": task_id} - - -@admin_router.post("/api/customers/{customer_id}/analyze-post-types") -async def analyze_customer_post_types(customer_id: str, background_tasks: BackgroundTasks): - """Trigger post type analysis for a customer.""" - task_id = f"analyze_{customer_id}_{asyncio.get_event_loop().time()}" - progress_store[task_id] = {"status": "starting", "message": "Starte Analyse...", "progress": 0} - - async def run_analysis(): - try: - progress_store[task_id] = {"status": "running", "message": "Analysiere Post-Typen...", "progress": 50} - results = await orchestrator.analyze_post_types(UUID(customer_id)) - analyzed_count = sum(1 for r in results.values() if r.get("sufficient_data")) - progress_store[task_id] = { - "status": "completed", - "message": f"{analyzed_count} Post-Typen analysiert", - "progress": 100, - "results": results - } - except Exception as e: - logger.exception(f"Analysis failed: {e}") - progress_store[task_id] = {"status": "error", "message": str(e), "progress": 0} - - background_tasks.add_task(run_analysis) - return {"task_id": task_id} - - -@admin_router.get("/api/customers/{customer_id}/topics") -async def get_customer_topics(customer_id: str, include_used: bool = False, post_type_id: str = None): - """Get research topics for a customer.""" try: - if post_type_id: - all_research = await db.get_all_research(UUID(customer_id), UUID(post_type_id)) - else: - all_research = await db.get_all_research(UUID(customer_id)) + # Get all users for the user selector + all_users = await db.list_users() + companies = await db.list_companies() - used_topic_titles = set() - if not include_used: - generated_posts = await db.get_generated_posts(UUID(customer_id)) - for post in generated_posts: - if post.topic_title: - used_topic_titles.add(post.topic_title.lower().strip()) - - all_topics = [] - for research in all_research: - if research.suggested_topics: - for topic in research.suggested_topics: - topic_title = topic.get("title", "").lower().strip() - if topic_title in used_topic_titles: - continue - topic["research_id"] = str(research.id) - topic["target_post_type_id"] = str(research.target_post_type_id) if research.target_post_type_id else None - all_topics.append(topic) - - return {"topics": all_topics, "used_count": len(used_topic_titles), "available_count": len(all_topics)} - except Exception as e: - logger.error(f"Error loading topics: {e}") - return {"topics": [], "error": str(e)} - - -@admin_router.post("/api/research") -async def start_research(background_tasks: BackgroundTasks, customer_id: str = Form(...), post_type_id: str = Form(None)): - """Start research for a customer.""" - task_id = f"research_{customer_id}_{asyncio.get_event_loop().time()}" - progress_store[task_id] = {"status": "starting", "message": "Starte Recherche...", "progress": 0} - - async def run_research(): - try: - def progress_callback(message: str, step: int, total: int): - progress_store[task_id] = {"status": "running", "message": message, "progress": int((step / total) * 100)} - - topics = await orchestrator.research_new_topics( - UUID(customer_id), - progress_callback=progress_callback, - post_type_id=UUID(post_type_id) if post_type_id else None - ) - progress_store[task_id] = {"status": "completed", "message": f"{len(topics)} Topics gefunden!", "progress": 100, "topics": topics} - except Exception as e: - logger.exception(f"Research failed: {e}") - progress_store[task_id] = {"status": "error", "message": str(e), "progress": 0} - - background_tasks.add_task(run_research) - return {"task_id": task_id} - - -@admin_router.post("/api/posts") -async def create_post(background_tasks: BackgroundTasks, customer_id: str = Form(...), topic_json: str = Form(...), post_type_id: str = Form(None)): - """Create a new post.""" - task_id = f"post_{customer_id}_{asyncio.get_event_loop().time()}" - progress_store[task_id] = {"status": "starting", "message": "Starte Post-Erstellung...", "progress": 0} - topic = json.loads(topic_json) - - async def run_create_post(): - try: - def progress_callback(message: str, iteration: int, max_iterations: int, score: int = None, versions: list = None, feedback_list: list = None): - progress = int((iteration / max_iterations) * 100) if iteration > 0 else 5 - score_text = f" (Score: {score}/100)" if score else "" - progress_store[task_id] = { - "status": "running", "message": f"{message}{score_text}", "progress": progress, - "iteration": iteration, "max_iterations": max_iterations, - "versions": versions or [], "feedback_list": feedback_list or [] - } - - result = await orchestrator.create_post( - customer_id=UUID(customer_id), topic=topic, max_iterations=3, - progress_callback=progress_callback, - post_type_id=UUID(post_type_id) if post_type_id else None - ) - progress_store[task_id] = { - "status": "completed", "message": "Post erstellt!", "progress": 100, - "result": { - "post_id": str(result["post_id"]), "final_post": result["final_post"], - "iterations": result["iterations"], "final_score": result["final_score"], "approved": result["approved"] - } - } - except Exception as e: - logger.exception(f"Post creation failed: {e}") - progress_store[task_id] = {"status": "error", "message": str(e), "progress": 0} - - background_tasks.add_task(run_create_post) - return {"task_id": task_id} - - -@admin_router.get("/api/posts") -async def get_all_posts(): - """Get all posts as JSON.""" - customers = await db.list_customers() - all_posts = [] - for customer in customers: - posts = await db.get_generated_posts(customer.id) - for post in posts: - all_posts.append({ - "id": str(post.id), "customer_name": customer.name, "topic_title": post.topic_title, - "content": post.post_content, "iterations": post.iterations, "status": post.status, - "created_at": post.created_at.isoformat() if post.created_at else None + # Build company data with employees for the dropdown + company_data = [] + for company in companies: + owner = next((u for u in all_users if str(u.id) == str(company.owner_user_id)), None) + employees = await db.list_users(account_type="employee", company_id=company.id) + company_data.append({ + "company": company, + "owner": owner, + "employees": employees, }) - return {"posts": all_posts, "total": len(all_posts)} + + # Get ghostwriters + ghostwriters = await db.list_users(account_type="ghostwriter") + + return templates.TemplateResponse("statistics.html", { + "request": request, + "page": "statistics", + "users": all_users, + "companies": company_data, + "ghostwriters": ghostwriters, + }) + except Exception as e: + logger.error(f"Error loading statistics: {e}") + return templates.TemplateResponse("statistics.html", { + "request": request, + "page": "statistics", + "error": str(e), + "users": [], + "companies": [], + "ghostwriters": [], + }) -class EmailRequest(BaseModel): - recipient: str - post_id: str - - -@admin_router.get("/api/email/config") -async def get_email_config(request: Request): - """Check if email is configured.""" +@admin_router.get("/api/statistics") +async def get_statistics(request: Request, period: str = "month", user_id: str = None, company_id: str = None): + """Get statistics data as JSON for charts.""" if not verify_auth(request): raise HTTPException(status_code=401, detail="Not authenticated") - return {"configured": email_service.is_configured(), "default_recipient": settings.email_default_recipient or ""} - - -@admin_router.post("/api/email/send") -async def send_post_email(request: Request, email_request: EmailRequest): - """Send a post via email.""" - if not verify_auth(request): - raise HTTPException(status_code=401, detail="Not authenticated") - if not email_service.is_configured(): - raise HTTPException(status_code=400, detail="E-Mail ist nicht konfiguriert.") try: - post = await db.get_generated_post(UUID(email_request.post_id)) - if not post: - raise HTTPException(status_code=404, detail="Post nicht gefunden") + now = datetime.utcnow() - customer = await db.get_customer(post.customer_id) - score = None - if post.critic_feedback and len(post.critic_feedback) > 0: - score = post.critic_feedback[-1].get("overall_score") + if period == "today": + start_date = now.replace(hour=0, minute=0, second=0, microsecond=0).isoformat() + elif period == "week": + start_date = (now - timedelta(days=7)).isoformat() + elif period == "month": + start_date = (now - timedelta(days=30)).isoformat() + else: # "all" + start_date = None - success = email_service.send_post( - recipient=email_request.recipient, post_content=post.post_content, - topic_title=post.topic_title or "LinkedIn Post", - customer_name=customer.name if customer else "Unbekannt", score=score - ) - if success: - return {"success": True, "message": f"E-Mail wurde an {email_request.recipient} gesendet"} - else: - raise HTTPException(status_code=500, detail="E-Mail konnte nicht gesendet werden.") - except HTTPException: - raise + uid = UUID(user_id) if user_id else None + cid = UUID(company_id) if company_id else None + + # Get raw logs (table may not exist yet) + try: + logs = await db.get_usage_stats( + start_date=start_date, + user_id=uid, + company_id=cid + ) + except Exception: + # Table doesn't exist yet - return empty data + return { + "total_tokens": 0, "total_cost": 0, "total_calls": 0, + "daily": [], "by_model": [], "by_user": [], "by_operation": [], + "period": period + } + + # Aggregate totals + total_tokens = sum(l.get("total_tokens", 0) for l in logs) + total_cost = sum(float(l.get("estimated_cost_usd", 0)) for l in logs) + total_calls = len(logs) + + # Time breakdown: hourly for "today", daily otherwise + use_hourly = period == "today" + bucket = defaultdict(lambda: {"tokens": 0, "cost": 0.0, "calls": 0}) + bucket_model = defaultdict(lambda: defaultdict(lambda: {"tokens": 0, "cost": 0.0})) + all_models = set() + + for log in logs: + if use_hourly: + key = log["created_at"][11:13] + ":00" # "HH:00" + else: + key = log["created_at"][:10] + bucket[key]["tokens"] += log.get("total_tokens", 0) + bucket[key]["cost"] += float(log.get("estimated_cost_usd", 0)) + bucket[key]["calls"] += 1 + model = log.get("model", "unknown") + all_models.add(model) + bucket_model[key][model]["tokens"] += log.get("total_tokens", 0) + bucket_model[key][model]["cost"] += float(log.get("estimated_cost_usd", 0)) + + # For today: ensure all 24 hours are present + if use_hourly: + for h in range(24): + hk = f"{h:02d}:00" + if hk not in bucket: + bucket[hk] # triggers defaultdict + + sorted_keys = sorted(bucket.keys()) + daily_data = [{"date": key, **bucket[key]} for key in sorted_keys] + + # Build per-model series + all_models_sorted = sorted(all_models) + daily_by_model = {} + for model in all_models_sorted: + daily_by_model[model] = [ + {"date": key, "tokens": bucket_model[key][model]["tokens"], "cost": bucket_model[key][model]["cost"]} + for key in sorted_keys + ] + + # By model + by_model = defaultdict(lambda: {"tokens": 0, "cost": 0.0, "calls": 0}) + for log in logs: + model = log.get("model", "unknown") + by_model[model]["tokens"] += log.get("total_tokens", 0) + by_model[model]["cost"] += float(log.get("estimated_cost_usd", 0)) + by_model[model]["calls"] += 1 + + model_data = [{"model": model, **data} for model, data in by_model.items()] + + # By user + by_user = defaultdict(lambda: {"tokens": 0, "cost": 0.0, "calls": 0, "user_id": None}) + for log in logs: + uid_str = log.get("user_id") or "unknown" + by_user[uid_str]["tokens"] += log.get("total_tokens", 0) + by_user[uid_str]["cost"] += float(log.get("estimated_cost_usd", 0)) + by_user[uid_str]["calls"] += 1 + by_user[uid_str]["user_id"] = uid_str + + user_data = list(by_user.values()) + + # By operation + by_operation = defaultdict(lambda: {"tokens": 0, "cost": 0.0, "calls": 0}) + for log in logs: + op = log.get("operation", "unknown") + by_operation[op]["tokens"] += log.get("total_tokens", 0) + by_operation[op]["cost"] += float(log.get("estimated_cost_usd", 0)) + by_operation[op]["calls"] += 1 + + operation_data = [{"operation": op, **data} for op, data in by_operation.items()] + + # ── Post statistics ───────────────────────────────────── + try: + posts = await db.get_post_stats( + start_date=start_date, + user_id=uid, + company_id=cid + ) + except Exception: + posts = [] + + total_created = len(posts) + total_approved = sum(1 for p in posts if p.get("status") in ("approved", "scheduled", "published")) + total_published = sum(1 for p in posts if p.get("status") == "published") + + # Daily post counts + posts_daily = defaultdict(lambda: {"created": 0, "approved": 0, "published": 0}) + for p in posts: + if use_hourly: + created_key = p["created_at"][11:13] + ":00" + else: + created_key = p["created_at"][:10] + posts_daily[created_key]["created"] += 1 + + if p.get("status") in ("approved", "scheduled", "published"): + ts = p.get("approved_at") or p["created_at"] + if use_hourly: + ak = ts[11:13] + ":00" + else: + ak = ts[:10] + posts_daily[ak]["approved"] += 1 + + if p.get("status") == "published": + ts = p.get("published_at") or p["created_at"] + if use_hourly: + pk = ts[11:13] + ":00" + else: + pk = ts[:10] + posts_daily[pk]["published"] += 1 + + # For today: ensure all 24 hours present + if use_hourly: + for h in range(24): + hk = f"{h:02d}:00" + if hk not in posts_daily: + posts_daily[hk] + + sorted_post_keys = sorted(posts_daily.keys()) + posts_daily_data = [{"date": k, **posts_daily[k]} for k in sorted_post_keys] + + # ── Derived metrics ───────────────────────────────────── + avg_cost_per_post = round(total_cost / total_created, 4) if total_created > 0 else 0 + avg_tokens_per_post = round(total_tokens / total_created) if total_created > 0 else 0 + + # Monthly projection based on daily average + num_days = len(daily_data) if daily_data else 1 + daily_avg_cost = total_cost / num_days if num_days > 0 else 0 + monthly_projection = round(daily_avg_cost * 30, 2) + + # Conversion rates + approval_rate = round(total_approved / total_created * 100, 1) if total_created > 0 else 0 + publish_rate = round(total_published / total_created * 100, 1) if total_created > 0 else 0 + + # Average time to approval / publication (in hours) + approval_times = [] + publish_times = [] + for p in posts: + created = p.get("created_at") + if not created: + continue + try: + created_dt = datetime.fromisoformat(created.replace("Z", "+00:00")) + except Exception: + continue + + if p.get("status") in ("approved", "scheduled", "published") and p.get("approved_at"): + try: + approved_dt = datetime.fromisoformat(p["approved_at"].replace("Z", "+00:00")) + diff = (approved_dt - created_dt).total_seconds() / 3600 + if diff >= 0: + approval_times.append(diff) + except Exception: + pass + + if p.get("status") == "published" and p.get("published_at"): + try: + published_dt = datetime.fromisoformat(p["published_at"].replace("Z", "+00:00")) + diff = (published_dt - created_dt).total_seconds() / 3600 + if diff >= 0: + publish_times.append(diff) + except Exception: + pass + + avg_hours_to_approval = round(sum(approval_times) / len(approval_times), 1) if approval_times else None + avg_hours_to_publish = round(sum(publish_times) / len(publish_times), 1) if publish_times else None + + return { + "total_tokens": total_tokens, + "total_cost": round(total_cost, 4), + "total_calls": total_calls, + "daily": daily_data, + "daily_by_model": daily_by_model, + "by_model": model_data, + "by_user": user_data, + "by_operation": operation_data, + "total_created": total_created, + "total_approved": total_approved, + "total_published": total_published, + "posts_daily": posts_daily_data, + "avg_cost_per_post": avg_cost_per_post, + "avg_tokens_per_post": avg_tokens_per_post, + "monthly_projection": monthly_projection, + "approval_rate": approval_rate, + "publish_rate": publish_rate, + "avg_hours_to_approval": avg_hours_to_approval, + "avg_hours_to_publish": avg_hours_to_publish, + "period": period + } except Exception as e: - logger.error(f"Error sending email: {e}") - raise HTTPException(status_code=500, detail=f"Fehler beim Senden: {str(e)}") + logger.error(f"Error getting statistics: {e}") + raise HTTPException(status_code=500, detail=str(e)) + + +# ==================== LICENSE KEYS MANAGEMENT ==================== + +@admin_router.get("/license-keys", response_class=HTMLResponse) +async def license_keys_page(request: Request): + """License keys management page.""" + if not verify_auth(request): + return RedirectResponse(url="/admin/login", status_code=302) + + try: + keys = await db.list_license_keys() + return templates.TemplateResponse("license_keys.html", { + "request": request, + "page": "license_keys", + "keys": keys, + "total_keys": len(keys), + "used_keys": len([k for k in keys if k.used]), + "available_keys": len([k for k in keys if not k.used]) + }) + except Exception as e: + logger.error(f"Error loading license keys: {e}") + return templates.TemplateResponse("license_keys.html", { + "request": request, + "page": "license_keys", + "error": str(e), + "keys": [], + "total_keys": 0, + "used_keys": 0, + "available_keys": 0 + }) + + +@admin_router.post("/api/license-keys/generate") +async def generate_license_key( + request: Request, + max_employees: int = Form(...), + max_posts_per_day: int = Form(...), + max_researches_per_day: int = Form(...), + description: str = Form(None) +): + """Generate new license key.""" + if not verify_auth(request): + raise HTTPException(status_code=401) + + try: + # Generate random key (XXXX-XXXX-XXXX-XXXX format) + key = "-".join([ + "".join(secrets.choice("ABCDEFGHJKLMNPQRSTUVWXYZ23456789") for _ in range(4)) + for _ in range(4) + ]) + + license_key = await db.create_license_key( + key=key, + max_employees=max_employees, + max_posts_per_day=max_posts_per_day, + max_researches_per_day=max_researches_per_day, + description=description + ) + + return JSONResponse({"success": True, "key": license_key.model_dump(mode='json')}) + except Exception as e: + logger.error(f"Error generating license key: {e}") + raise HTTPException(status_code=500, detail=str(e)) + + +@admin_router.patch("/api/license-keys/{key_id}") +async def update_license_key_route( + request: Request, + key_id: str, + max_employees: int = Form(...), + max_posts_per_day: int = Form(...), + max_researches_per_day: int = Form(...), + description: str = Form(None) +): + """Update license key limits.""" + if not verify_auth(request): + raise HTTPException(status_code=401) + + try: + updates = { + "max_employees": max_employees, + "max_posts_per_day": max_posts_per_day, + "max_researches_per_day": max_researches_per_day, + "description": description + } + + await db.update_license_key(UUID(key_id), updates) + return JSONResponse({"success": True}) + except Exception as e: + logger.error(f"Error updating license key: {e}") + raise HTTPException(status_code=500, detail=str(e)) + + +@admin_router.delete("/api/license-keys/{key_id}") +async def delete_license_key_route(request: Request, key_id: str): + """Delete license key.""" + if not verify_auth(request): + raise HTTPException(status_code=401) + + try: + await db.delete_license_key(UUID(key_id)) + return JSONResponse({"success": True}) + except Exception as e: + logger.error(f"Error deleting license key: {e}") + raise HTTPException(status_code=500, detail=str(e)) diff --git a/src/web/app.py b/src/web/app.py index 160dba8..04c7d6d 100644 --- a/src/web/app.py +++ b/src/web/app.py @@ -1,15 +1,47 @@ """FastAPI web frontend for LinkedIn Post Creation System.""" +from contextlib import asynccontextmanager from pathlib import Path from fastapi import FastAPI from fastapi.staticfiles import StaticFiles from fastapi.responses import RedirectResponse +from loguru import logger from src.config import settings from src.web.admin import admin_router + +@asynccontextmanager +async def lifespan(app: FastAPI): + """Manage application lifecycle - startup and shutdown.""" + # Startup + logger.info("Starting LinkedIn Post Creation System...") + + # Initialize and start scheduler if enabled + scheduler = None + if settings.user_frontend_enabled: + try: + from src.database.client import DatabaseClient + from src.services.scheduler_service import init_scheduler + + db = DatabaseClient() + scheduler = init_scheduler(db, check_interval=60) # Check every 60 seconds + await scheduler.start() + logger.info("Scheduler service started") + except Exception as e: + logger.error(f"Failed to start scheduler: {e}") + + yield # Application runs here + + # Shutdown + logger.info("Shutting down LinkedIn Post Creation System...") + if scheduler: + await scheduler.stop() + logger.info("Scheduler service stopped") + + # Setup -app = FastAPI(title="LinkedIn Post Creation System") +app = FastAPI(title="LinkedIn Post Creation System", lifespan=lifespan) # Static files app.mount("/static", StaticFiles(directory=Path(__file__).parent / "static"), name="static") diff --git a/src/web/templates/admin/base.html b/src/web/templates/admin/base.html index 3b9e486..4b24a72 100644 --- a/src/web/templates/admin/base.html +++ b/src/web/templates/admin/base.html @@ -3,7 +3,7 @@ - {% block title %}Admin - LinkedIn Posts{% endblock %} + {% block title %}Admin Panel{% endblock %} -{% endblock %} diff --git a/src/web/templates/admin/dashboard.html b/src/web/templates/admin/dashboard.html index f106474..73640cc 100644 --- a/src/web/templates/admin/dashboard.html +++ b/src/web/templates/admin/dashboard.html @@ -1,10 +1,10 @@ {% extends "base.html" %} -{% block title %}Dashboard - LinkedIn Posts{% endblock %} +{% block title %}Nutzerverwaltung - Admin{% endblock %} {% block content %}
-

Dashboard

-

Willkommen zum LinkedIn Post Creation System

+

Nutzerverwaltung

+

Alle Benutzer und Firmen verwalten

{% if error %} @@ -13,85 +13,277 @@ {% endif %} - -
+ +
-

Kunden

-

{{ customers_count or 0 }}

+

Gesamt Nutzer

+

{{ total_users }}

+
+
+
+
+
+
+ +
+
+

Firmen

+

{{ total_companies }}

-
- +
-

Generierte Posts

-

{{ total_posts or 0 }}

+

Ghostwriter

+

{{ total_ghostwriters }}

-
-
- +
+
-

AI Agents

-

5

+

Mitarbeiter

+

{{ total_employees }}

- -
-

Schnellaktionen

-
- -
- + +
+ +
+ + +
+ + +
+ + +
+ {% for cd in company_data %} +
+ +
+ -
-

Neuer Kunde

-

Setup starten

+
+ + + + + +