Files
SkinbaseNova/.env.example
T
test 9c537e08d0 Wire new routes and environment keys for the recent modules.
Register AdSense, artwork review, comment captcha, lazy collections, and upload review props, and document the matching env vars and CLI commands.
2026-09-20 14:51:04 +02:00

498 lines
16 KiB
Bash

APP_NAME=Laravel
APP_ENV=local
APP_KEY=
APP_DEBUG=true
APP_URL=http://localhost
# Maximum secondary artwork categories in addition to the primary category.
CATEGORIES_MAX_SECONDARY=5
# Artwork original downloads: PHP fallback unless nginx X-Accel is configured.
# Leave false until the internal location exists and has been verified.
DOWNLOAD_ACCEL_ENABLED=false
DOWNLOAD_ACCEL_PATH=/internal/originals
SECURITY_REPORT_ENABLED=true
SECURITY_REPORT_NOTIFY_EMAIL=
SECURITY_REPORT_SCAN_NPM=true
SECURITY_REPORT_SCAN_COMPOSER=true
SECURITY_REPORT_STORE_RAW=true
SECURITY_REPORT_MAX_RAW_KB=512
SECURITY_REPORT_FAIL_ON_HIGH=false
SECURITY_REPORT_FAIL_ON_CRITICAL=false
APP_LOCALE=en
APP_FALLBACK_LOCALE=en
APP_FAKER_LOCALE=en_US
APP_MAINTENANCE_DRIVER=file
# APP_MAINTENANCE_STORE=database
# PHP_CLI_SERVER_WORKERS=4
BCRYPT_ROUNDS=12
LOG_CHANNEL=stack
LOG_STACK=single
LOG_DEPRECATIONS_CHANNEL=null
LOG_LEVEL=debug
# M12 — slow Laravel HTTP log (threshold only; disable for zero overhead)
HTTP_SLOW_REQUEST_LOG_ENABLED=false
HTTP_SLOW_REQUEST_MS=750
HTTP_SLOW_REQUEST_LOG_DAYS=14
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=skinbase26
DB_USERNAME=root
DB_PASSWORD=
# Legacy database connection (projekti_old_skinbase)
LEGACY_DB_CONNECTION=mysql
LEGACY_DB_HOST=127.0.0.1
LEGACY_DB_PORT=3306
LEGACY_DB_DATABASE=projekti_old_skinbase
LEGACY_DB_USERNAME=root
LEGACY_DB_PASSWORD=
SESSION_DRIVER=database
SESSION_LIFETIME=120
SESSION_ENCRYPT=false
SESSION_PATH=/
SESSION_DOMAIN=null
# Skinbase conditional public sessions
SKINBASE_CONDITIONAL_SESSIONS_ENABLED=true
SKINBASE_SKIP_ANONYMOUS_PUBLIC_GET_SESSIONS=true
SKINBASE_SKIP_BOT_PUBLIC_GET_SESSIONS=true
# Debug only; do not enable permanently in production
SKINBASE_SESSION_DEBUG_HEADER=false
BROADCAST_CONNECTION=reverb
FILESYSTEM_DISK=local
QUEUE_CONNECTION=redis
# Comment spam protection: observe records scores without changing visibility.
COMMENT_SPAM_ENABLED=true
COMMENT_SPAM_MODE=enforce
COMMENT_SPAM_AI_ENABLED=true
COMMENT_SPAM_AI_PROVIDER=together
# Supported: Prism-ML/Ternary-Bonsai-27B or meta-llama/Llama-3.2-3B-Instruct-Turbo
COMMENT_SPAM_AI_MODEL=meta-llama/Llama-3.2-3B-Instruct-Turbo
COMMENT_SPAM_AI_TIMEOUT=5
TOGETHER_API_KEY=
TOGETHER_API_BASE_URL=https://api.together.xyz/v1
COMMENT_SPAM_LOCAL_SAFE_MAX=29
COMMENT_SPAM_LOCAL_SPAM_MIN=70
COMMENT_SPAM_AI_SPAM_MIN=70
COMMENT_SPAM_SIGNATURE_CACHE_MINUTES=1440
COMMENT_TURNSTILE_ENABLED=false
COMMENT_TURNSTILE_SITE_KEY=
COMMENT_TURNSTILE_SECRET_KEY=
COMMENT_TURNSTILE_RISK_THRESHOLD=40
COMMENT_TURNSTILE_FAIL_OPEN=false
# Must exceed Horizon supervisor-default timeout (960s). Production used 90s
# and nightly RecComputeSimilar* jobs failed with MaxAttemptsExceeded.
REDIS_QUEUE_RETRY_AFTER=1080
# Collection maintenance / forum AI jobs have no Horizon consumer.
# Leave false until a dedicated supervisor exists (M7).
COLLECTIONS_V5_DISPATCH_ENABLED=false
FORUM_QUEUE_DISPATCH_ENABLED=false
ONLINE_VISITOR_INDEX_READ_LIMIT=2000
ONLINE_VISITOR_INDEX_PRUNE_ENABLED=false
ONLINE_VISITOR_INDEX_PRUNE_SCAN_COUNT=500
ONLINE_VISITOR_INDEX_PRUNE_MAX_BATCHES=200
ONLINE_VISITOR_INDEX_PRUNE_SLEEP_MS=25
# RankBuildScopeListsJob unique lock (seconds). Must exceed queue wait + 300s timeout.
RANK_SCOPE_JOB_UNIQUE_FOR=21600
# Hourly artwork metric snapshots (heat / rising / monthly leaderboards).
# Production currently holds ~7 days because the scheduler passed --keep-days=7.
# Monthly leaderboards and Studio 30d views need 30 days of hourly history.
ARTWORK_METRIC_HOURLY_RETENTION_DAYS=30
ARTWORK_METRIC_HOURLY_PRUNE_CHUNK=5000
ARTWORK_METRIC_HOURLY_PRUNE_SLEEP_MS=50
MESSAGING_REALTIME=true
MESSAGING_BROADCAST_QUEUE=broadcasts
MESSAGING_TYPING_TTL=8
MESSAGING_TYPING_CACHE_STORE=redis
MESSAGING_PRESENCE_TTL=90
MESSAGING_CONVERSATION_PRESENCE_TTL=45
MESSAGING_PRESENCE_CACHE_STORE=redis
MESSAGING_RECOVERY_MAX_MESSAGES=100
MESSAGING_OFFLINE_FALLBACK_ONLY=true
HORIZON_NAME=skinbase-nova
HORIZON_PATH=horizon
HORIZON_PREFIX=skinbase_nova_horizon:
REVERB_APP_ID=skinbase-local
REVERB_APP_KEY=skinbase-local-key
REVERB_APP_SECRET=skinbase-local-secret
REVERB_HOST=127.0.0.1
REVERB_PORT=8080
REVERB_SCHEME=http
REVERB_SERVER_HOST=0.0.0.0
REVERB_SERVER_PORT=8080
REVERB_SERVER_PATH=
REVERB_SCALING_ENABLED=false
VITE_REVERB_APP_KEY="${REVERB_APP_KEY}"
VITE_REVERB_HOST="${REVERB_HOST}"
VITE_REVERB_PORT="${REVERB_PORT}"
VITE_REVERB_SCHEME="${REVERB_SCHEME}"
# Upload UI feature flag (legacy upload remains default unless explicitly enabled)
SKINBASE_UPLOADS_V2=false
# Upload transport tuning
UPLOAD_CHUNK_MAX_BYTES=5242880
UPLOAD_CHUNK_REQUEST_TIMEOUT_MS=45000
UPLOAD_RATE_CHUNK_USER=180
UPLOAD_RATE_CHUNK_IP=360
# New accounts without this many moderator/legacy published artworks go to review.
# Queued uploads and trusted_auto publishes do not count, so a new user's 6th upload stays hidden.
UPLOAD_MIN_APPROVED_UPLOADS=5
UPLOAD_MIN_ACCOUNT_AGE_DAYS=7
UPLOAD_MAX_UNTRUSTED_LEVEL=1
UPLOAD_BOT_RISK_REVIEW_THRESHOLD=40
# Draft abuse prevention controls
SKINBASE_MAX_DRAFTS=10
SKINBASE_MAX_DRAFT_STORAGE_MB=1024
SKINBASE_DUPLICATE_HASH_POLICY=block
# Vision / AI auto-tagging (local defaults)
VISION_ENABLED=true
VISION_QUEUE=default
VISION_IMAGE_VARIANT=md
VISION_GATEWAY_URL=
VISION_GATEWAY_TIMEOUT=10
VISION_GATEWAY_CONNECT_TIMEOUT=3
VISION_VECTOR_GATEWAY_ENABLED=true
VISION_VECTOR_GATEWAY_URL=
VISION_VECTOR_GATEWAY_API_KEY=
VISION_VECTOR_GATEWAY_COLLECTION=images
VISION_VECTOR_GATEWAY_TIMEOUT=20
VISION_VECTOR_GATEWAY_CONNECT_TIMEOUT=5
VISION_VECTOR_GATEWAY_RETRIES=1
VISION_VECTOR_GATEWAY_RETRY_DELAY_MS=250
VISION_VECTOR_GATEWAY_UPSERT_ENDPOINT=/vectors/upsert
VISION_VECTOR_GATEWAY_SEARCH_ENDPOINT=/vectors/search
VISION_VECTOR_GATEWAY_SEARCH_FILE_ENDPOINT=/vectors/search/file
VISION_VECTOR_GATEWAY_SEARCH_TIMEOUT=6
VISION_VECTOR_GATEWAY_SEARCH_CONNECT_TIMEOUT=2
VISION_VECTOR_GATEWAY_SEARCH_RETRIES=0
VISION_VECTOR_GATEWAY_CIRCUIT_SECONDS=30
VISION_VECTOR_GATEWAY_DELETE_ENDPOINT=/vectors/delete
VISION_VECTOR_GATEWAY_COLLECTIONS_ENDPOINT=/vectors/collections
# CLIP service (set base URL to enable CLIP calls)
CLIP_BASE_URL=
CLIP_ANALYZE_ENDPOINT=/analyze
CLIP_TIMEOUT_SECONDS=8
CLIP_CONNECT_TIMEOUT_SECONDS=2
CLIP_HTTP_RETRIES=1
CLIP_HTTP_RETRY_DELAY_MS=200
CLIP_EMBED_ENDPOINT=/embed
CLIP_EMBED_TIMEOUT_SECONDS=8
CLIP_EMBED_CONNECT_TIMEOUT_SECONDS=2
CLIP_EMBED_HTTP_RETRIES=1
CLIP_EMBED_HTTP_RETRY_DELAY_MS=200
# Similar artworks / embedding pipeline
RECOMMENDATIONS_QUEUE=${VISION_QUEUE}
RECOMMENDATIONS_EMBEDDING_ENABLED=true
RECOMMENDATIONS_EMBEDDING_MODEL=clip
RECOMMENDATIONS_EMBEDDING_MODEL_VERSION=v1
RECOMMENDATIONS_ALGO_VERSION=clip-cosine-v1
RECOMMENDATIONS_AB_ALGO_VERSIONS=clip-cosine-v1
RECOMMENDATIONS_MIN_DIM=64
RECOMMENDATIONS_MAX_DIM=4096
RECOMMENDATIONS_BACKFILL_BATCH=200
SIMILARITY_VECTOR_ENABLED=false
SIMILARITY_VECTOR_ADAPTER=pgvector
# Personalized discovery foundation (Phase 8)
DISCOVERY_QUEUE=${RECOMMENDATIONS_QUEUE}
DISCOVERY_PROFILE_VERSION=profile-v1
DISCOVERY_EVENT_VERSION=event-v1
DISCOVERY_ALGO_VERSION=${RECOMMENDATIONS_ALGO_VERSION}
DISCOVERY_CACHE_VERSION=cache-v1
DISCOVERY_DECAY_HALF_LIFE_HOURS=72
DISCOVERY_WEIGHT_VIEW=1
DISCOVERY_WEIGHT_CLICK=2
DISCOVERY_WEIGHT_FAVORITE=4
DISCOVERY_WEIGHT_DOWNLOAD=3
DISCOVERY_CACHE_TTL_MINUTES=60
DISCOVERY_V3_ENABLED=false
DISCOVERY_V3_CACHE_VERSION=cache-v3
DISCOVERY_V3_CACHE_TTL_MINUTES=5
DISCOVERY_V3_VECTOR_SIMILARITY_WEIGHT=0.8
DISCOVERY_V3_VECTOR_BASE_SCORE=0.75
DISCOVERY_V3_MAX_SEED_ARTWORKS=3
DISCOVERY_V3_VECTOR_CANDIDATE_POOL=60
DISCOVERY_V3_SECTION_SIMILAR_STYLE_LIMIT=3
DISCOVERY_V3_SECTION_YOU_MAY_ALSO_LIKE_LIMIT=6
DISCOVERY_V3_SECTION_VISUALLY_RELATED_LIMIT=6
DISCOVERY_RANKING_WEIGHTS_VERSION=rank-w-v1
DISCOVERY_RANKING_W1=0.65
DISCOVERY_RANKING_W2=0.20
DISCOVERY_RANKING_W3=0.10
DISCOVERY_RANKING_W4=0.05
DISCOVERY_RANKING_WEIGHTS_VERSION_CLIP_COSINE_V1=rank-w-v1
DISCOVERY_RANKING_W1_CLIP_COSINE_V1=0.65
DISCOVERY_RANKING_W2_CLIP_COSINE_V1=0.20
DISCOVERY_RANKING_W3_CLIP_COSINE_V1=0.10
DISCOVERY_RANKING_W4_CLIP_COSINE_V1=0.05
DISCOVERY_RANKING_WEIGHTS_VERSION_CLIP_COSINE_V2=rank-w-v2-prod-1
DISCOVERY_RANKING_W1_CLIP_COSINE_V2=0.52
DISCOVERY_RANKING_W2_CLIP_COSINE_V2=0.23
DISCOVERY_RANKING_W3_CLIP_COSINE_V2=0.15
DISCOVERY_RANKING_W4_CLIP_COSINE_V2=0.10
DISCOVERY_ROLLOUT_ENABLED=false
DISCOVERY_ROLLOUT_BASELINE_ALGO_VERSION=clip-cosine-v1
DISCOVERY_ROLLOUT_CANDIDATE_ALGO_VERSION=clip-cosine-v2
DISCOVERY_ROLLOUT_ACTIVE_GATE=g10
DISCOVERY_ROLLOUT_GATE_10_PERCENT=10
DISCOVERY_ROLLOUT_GATE_50_PERCENT=50
DISCOVERY_ROLLOUT_GATE_100_PERCENT=100
DISCOVERY_FORCE_ALGO_VERSION=
DISCOVERY_ROLLOUT_WARN_CTR_DROP_PCT=3
DISCOVERY_ROLLOUT_ROLLBACK_CTR_DROP_PCT=5
DISCOVERY_ROLLOUT_WARN_LONG_DWELL_DROP_PCT=4
DISCOVERY_ROLLOUT_ROLLBACK_LONG_DWELL_DROP_PCT=8
DISCOVERY_ROLLOUT_WARN_DIVERSITY_CONCENTRATION_RISE_PCT=10
DISCOVERY_ROLLOUT_ROLLBACK_DIVERSITY_CONCENTRATION_RISE_PCT=15
DISCOVERY_EVAL_WEIGHT_CTR=0.45
DISCOVERY_EVAL_WEIGHT_SAVE_RATE=0.35
DISCOVERY_EVAL_WEIGHT_LONG_DWELL=0.25
DISCOVERY_EVAL_WEIGHT_BOUNCE_PENALTY=0.15
DISCOVERY_EVAL_SAVE_RATE_INFORMATIONAL=true
# YOLO service (optional)
YOLO_ENABLED=true
YOLO_BASE_URL=
YOLO_ANALYZE_ENDPOINT=/analyze
YOLO_TIMEOUT_SECONDS=8
YOLO_CONNECT_TIMEOUT_SECONDS=2
YOLO_HTTP_RETRIES=1
YOLO_HTTP_RETRY_DELAY_MS=200
YOLO_PHOTOGRAPHY_ONLY=true
# Academy feature flags
SKINBASE_ACADEMY_ENABLED=true
SKINBASE_ACADEMY_PAYMENTS_ENABLED=false
ACADEMY_BILLING_ENABLED=false
ACADEMY_STRIPE_SUBSCRIPTION_NAME=academy
# Stripe / Cashier
STRIPE_KEY=pk_test_xxx
STRIPE_SECRET=sk_test_xxx
STRIPE_WEBHOOK_SECRET=whsec_xxx
CASHIER_CURRENCY=eur
CASHIER_CURRENCY_LOCALE=sl_SI
# Academy billing price IDs
ACADEMY_CREATOR_MONTHLY_PRICE_ID=price_xxx
ACADEMY_PRO_MONTHLY_PRICE_ID=price_xxx
# Stripe expects real price object IDs that start with price_, not product IDs like prod_...
# -----------------------------------------------------------------------------
# Production examples (uncomment and adjust)
# -----------------------------------------------------------------------------
# VISION_ENABLED=true
# VISION_QUEUE=vision
# VISION_IMAGE_VARIANT=md
# VISION_GATEWAY_URL=https://vision.internal
# VISION_GATEWAY_TIMEOUT=8
# VISION_GATEWAY_CONNECT_TIMEOUT=2
#
# CLIP_BASE_URL=https://clip.internal
# CLIP_ANALYZE_ENDPOINT=/analyze
# CLIP_TIMEOUT_SECONDS=5
# CLIP_CONNECT_TIMEOUT_SECONDS=1
# CLIP_HTTP_RETRIES=1
# CLIP_HTTP_RETRY_DELAY_MS=150
# CLIP_EMBED_ENDPOINT=/embed
# CLIP_EMBED_TIMEOUT_SECONDS=5
# CLIP_EMBED_CONNECT_TIMEOUT_SECONDS=1
# CLIP_EMBED_HTTP_RETRIES=1
# CLIP_EMBED_HTTP_RETRY_DELAY_MS=150
# RECOMMENDATIONS_QUEUE=vision
# RECOMMENDATIONS_EMBEDDING_ENABLED=true
# RECOMMENDATIONS_EMBEDDING_MODEL=clip
# RECOMMENDATIONS_EMBEDDING_MODEL_VERSION=v1
# RECOMMENDATIONS_ALGO_VERSION=clip-cosine-v1
# RECOMMENDATIONS_AB_ALGO_VERSIONS=clip-cosine-v1,clip-cosine-v2
# RECOMMENDATIONS_BACKFILL_BATCH=250
# DISCOVERY_QUEUE=vision
# DISCOVERY_PROFILE_VERSION=profile-v1
# DISCOVERY_EVENT_VERSION=event-v1
# DISCOVERY_ALGO_VERSION=clip-cosine-v1
# DISCOVERY_CACHE_VERSION=cache-v1
# DISCOVERY_DECAY_HALF_LIFE_HOURS=72
# DISCOVERY_WEIGHT_VIEW=1
# DISCOVERY_WEIGHT_CLICK=2
# DISCOVERY_WEIGHT_FAVORITE=4
# DISCOVERY_WEIGHT_DOWNLOAD=3
# DISCOVERY_V3_ENABLED=true
# DISCOVERY_V3_CACHE_VERSION=cache-v3
# DISCOVERY_V3_CACHE_TTL_MINUTES=5
# DISCOVERY_V3_VECTOR_SIMILARITY_WEIGHT=0.8
# DISCOVERY_V3_VECTOR_BASE_SCORE=0.75
# DISCOVERY_V3_MAX_SEED_ARTWORKS=3
# DISCOVERY_V3_VECTOR_CANDIDATE_POOL=60
# DISCOVERY_V3_SECTION_SIMILAR_STYLE_LIMIT=3
# DISCOVERY_V3_SECTION_YOU_MAY_ALSO_LIKE_LIMIT=6
# DISCOVERY_V3_SECTION_VISUALLY_RELATED_LIMIT=6
# DISCOVERY_RANKING_WEIGHTS_VERSION=rank-w-v1
# DISCOVERY_RANKING_W1=0.65
# DISCOVERY_RANKING_W2=0.20
# DISCOVERY_RANKING_W3=0.10
# DISCOVERY_RANKING_W4=0.05
#
# YOLO_ENABLED=true
# YOLO_BASE_URL=https://yolo.internal
# YOLO_ANALYZE_ENDPOINT=/analyze
# YOLO_TIMEOUT_SECONDS=5
# YOLO_CONNECT_TIMEOUT_SECONDS=1
# YOLO_HTTP_RETRIES=1
# YOLO_HTTP_RETRY_DELAY_MS=150
# YOLO_PHOTOGRAPHY_ONLY=true
CACHE_STORE=database
# CACHE_PREFIX=
MEMCACHED_HOST=127.0.0.1
REDIS_CLIENT=phpredis
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
MAIL_MAILER=log
MAIL_SCHEME=null
MAIL_HOST=127.0.0.1
MAIL_PORT=2525
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_FROM_ADDRESS="[email protected]"
MAIL_FROM_NAME="${APP_NAME}"
# Registration anti-spam
REGISTRATION_IP_PER_MINUTE_LIMIT=3
REGISTRATION_IP_PER_DAY_LIMIT=20
REGISTRATION_EMAIL_PER_MINUTE_LIMIT=6
REGISTRATION_EMAIL_COOLDOWN_MINUTES=30
REGISTRATION_VERIFY_TOKEN_TTL_HOURS=24
TURNSTILE_ENABLED=false
REGISTRATION_DISPOSABLE_DOMAINS_ENABLED=true
REGISTRATION_TURNSTILE_SUSPICIOUS_ATTEMPTS=2
REGISTRATION_TURNSTILE_ATTEMPT_WINDOW_MINUTES=30
REGISTRATION_EMAIL_GLOBAL_SEND_PER_MINUTE=30
REGISTRATION_MONTHLY_EMAIL_LIMIT=10000
TURNSTILE_SITE_KEY=
TURNSTILE_SECRET_KEY=
TURNSTILE_FAIL_OPEN=false
TURNSTILE_VERIFY_URL=https://challenges.cloudflare.com/turnstile/v0/siteverify
TURNSTILE_TIMEOUT=5
ENHANCE_DISK=public
ENHANCE_SOURCE_PREFIX=enhance/sources
ENHANCE_OUTPUT_PREFIX=enhance/outputs
ENHANCE_PREVIEW_PREFIX=enhance/previews
ENHANCE_ENGINE=stub
ENHANCE_MAX_UPLOAD_MB=20
ENHANCE_MAX_INPUT_WIDTH=4096
ENHANCE_MAX_INPUT_HEIGHT=4096
ENHANCE_MAX_OUTPUT_WIDTH=8192
ENHANCE_MAX_OUTPUT_HEIGHT=8192
ENHANCE_DAILY_LIMIT=10
ENHANCE_QUEUE=default
ENHANCE_COMPLETED_EXPIRES_AFTER_DAYS=30
ENHANCE_FAILED_EXPIRES_AFTER_DAYS=7
ENHANCE_DELETED_FILE_GRACE_DAYS=1
ENHANCE_CLEANUP_CHUNK_SIZE=100
ENHANCE_STUCK_PROCESSING_AFTER_MINUTES=30
ENHANCE_STUCK_QUEUED_AFTER_MINUTES=60
ENHANCE_STUB_SHOW_WARNING=true
ENHANCE_WORKER_URL=
ENHANCE_WORKER_TIMEOUT=300
ENHANCE_WORKER_TOKEN=
ENHANCE_WORKER_MAX_DOWNLOAD_MB=60
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=
AWS_USE_PATH_STYLE_ENDPOINT=false
VITE_APP_NAME="${APP_NAME}"
# ─── Early-Stage Growth System ───────────────────────────────────────────────
# Set NOVA_EARLY_GROWTH_ENABLED=false to instantly revert to normal behaviour.
# NOVA_EARLY_GROWTH_MODE: off | light | aggressive
NOVA_EARLY_GROWTH_ENABLED=false
NOVA_EARLY_GROWTH_MODE=off
# Module toggles (only active when NOVA_EARLY_GROWTH_ENABLED=true)
NOVA_EGS_ADAPTIVE_WINDOW=true
NOVA_EGS_GRID_FILLER=true
NOVA_EGS_SPOTLIGHT=true
NOVA_EGS_ACTIVITY_LAYER=false
# AdaptiveTimeWindow thresholds
NOVA_EGS_UPLOADS_PER_DAY_NARROW=10
NOVA_EGS_UPLOADS_PER_DAY_WIDE=3
NOVA_EGS_WINDOW_NARROW_DAYS=7
NOVA_EGS_WINDOW_MEDIUM_DAYS=30
NOVA_EGS_WINDOW_WIDE_DAYS=90
# GridFiller minimum items per page
NOVA_EGS_GRID_MIN_RESULTS=12
# Auto-disable when site reaches organic scale
NOVA_EGS_AUTO_DISABLE=false
NOVA_EGS_AUTO_DISABLE_UPLOADS=50
NOVA_EGS_AUTO_DISABLE_USERS=500
# Cache TTLs (seconds)
NOVA_EGS_SPOTLIGHT_TTL=3600
NOVA_EGS_BLEND_TTL=300
NOVA_EGS_WINDOW_TTL=600
NOVA_EGS_ACTIVITY_TTL=1800
# ─── OAuth / Social Login ─────────────────────────────────────────────────────
# Google — https://console.cloud.google.com/apis/credentials
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
GOOGLE_REDIRECT_URI=/auth/google/callback
# Google AdSense Management API (admin analytics, read-only)
# Redirect URI must match the Google Cloud OAuth client exactly.
ADSENSE_CLIENT_ID=
ADSENSE_CLIENT_SECRET=
ADSENSE_REDIRECT_URI=https://skinbase.org/admin/adsense/oauth/callback
ADSENSE_SYNC_ENABLED=true
# Optional light theme feature
# Set LIGHT_THEME_ENABLED=true to allow a light theme in the client-side toggle.
# Set LIGHT_THEME_SHOW_SWITCH=true to display the theme switch in the toolbar.
LIGHT_THEME_ENABLED=false
LIGHT_THEME_SHOW_SWITCH=false
# Discord — https://discord.com/developers/applications
DISCORD_CLIENT_ID=
DISCORD_CLIENT_SECRET=
DISCORD_REDIRECT_URI=/auth/discord/callback
# Apple — https://developer.apple.com/account/resources/identifiers/list/serviceId
# Apple sign in removed