diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..acc3baa --- /dev/null +++ b/.dockerignore @@ -0,0 +1,16 @@ +.git +.venv +**/__pycache__/ +*.py[cod] +db.sqlite3 +.env +htmlcov/ +.pytest_cache/ +.mypy_cache/ +*.log +staticfiles/ +proposal/ +template/ +docs/ +*.pptx +.~lock.* diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..d122fad --- /dev/null +++ b/.env.example @@ -0,0 +1,81 @@ +# Local development defaults. Copy to `.env` (gitignored) — never commit secrets. +# docker compose auto-loads `.env` for ${VAR} substitution into the web container. + +DJANGO_ENV=dev +DJANGO_DEBUG=true +DJANGO_SECRET_KEY=dev-only-change-me +DJANGO_ALLOWED_HOSTS=localhost,127.0.0.1,0.0.0.0 + +# Leave empty for SQLite, or point at local compose Postgres: +# DATABASE_URL=postgres://monica_site:monica_site@127.0.0.1:5432/monica_site + +SITE_UNDER_CONSTRUCTION=false +SITE_NAME=Monica Dhillon +SITE_TAGLINE=MKDRealtor.com · EXIT Realty +PUBLIC_SITE_URL=http://127.0.0.1:8000 +CONTACT_PHONE=(630) 452-4443 +CONTACT_EMAIL=moni.dhill@gmail.com +CONTACT_SERVICE_AREA=Serving Chicagoland +CREDIT_NAME=AI ML Operations, LLC +CREDIT_URL=https://aimloperations.com + +# reCAPTCHA (optional locally — form skips captcha when empty) +RECAPTCHA_PUBLIC_KEY= +RECAPTCHA_PRIVATE_KEY= + +# SMTP2GO — fill in `.env` (not this file). Dev defaults to console backend +# until you set EMAIL_BACKEND to the SMTP backend below. +EMAIL_HOST=mail.smtp2go.com +EMAIL_HOST_USER= +EMAIL_HOST_PASSWORD= +EMAIL_PORT=2525 +EMAIL_USE_TLS=true +DEFAULT_FROM_EMAIL=noreply@mkdrealtor.com +# Console (default in DJANGO_ENV=dev) prints mail to container logs. +# For real SMTP2GO delivery locally, uncomment: +# EMAIL_BACKEND=django.core.mail.backends.smtp.EmailBackend +SMTP2GO_SMS_API_KEY= +# Shared secret for email + SMS webhooks. +# In SMTP2GO: Authorization header = Bearer, value = this secret. +SMTP2GO_WEBHOOK_SECRET= + +# Postcards — PCM Integrations (DirectMail API v3) +POSTCARD_PROVIDER=pcm +PCM_API_KEY= +# Inbound webhook auth (Bearer / ?token=) — set a long random string +PCM_WEBHOOK_SECRET= +# Return address on every postcard order (JSON). Example: +# PCM_RETURN_ADDRESS={"firstName":"Monica","lastName":"Dhillon","address":"123 Main St","city":"Naperville","state":"IL","zipCode":"60540"} +PCM_RETURN_ADDRESS= +# Or set fields individually if JSON is empty: +# PCM_RETURN_LINE1= +# PCM_RETURN_CITY= +# PCM_RETURN_STATE= +# PCM_RETURN_ZIP= + +# Social +META_APP_ID= +META_APP_SECRET= +LINKEDIN_CLIENT_ID= +LINKEDIN_CLIENT_SECRET= +# Generate: python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())" +SOCIAL_TOKEN_ENCRYPTION_KEY= + +# Ollama (LAN) — standard Ollama HTTP port +OLLAMA_BASE_URL=http://10.0.0.128:11434 +OLLAMA_MODEL=llama3.2 + +# Nominatim (LAN) — address autocomplete via Django /api/address-suggest/ +# Nominatim has no built-in API keys; optional NOMINATIM_API_KEY only if you +# put a gateway in front that checks X-API-Key. +NOMINATIM_BASE_URL=http://10.0.0.128:8089 +NOMINATIM_TIMEOUT_SECONDS=8 +NOMINATIM_COUNTRY_CODES=us +# NOMINATIM_API_KEY= + +# Tianji analytics (off in local/dev by default) +TIANJI_ENABLED=false +TIANJI_TRACKER_URL=https://tianji.aimloperations.com/tracker.js +TIANJI_WEBSITE_ID=cmshzhxdf6gee10qzkzrfn9iw + +GUNICORN_WORKERS=2 diff --git a/.env.prod.example b/.env.prod.example new file mode 100644 index 0000000..255af8f --- /dev/null +++ b/.env.prod.example @@ -0,0 +1,113 @@ +# Secret env files for server-infra deploy. +# Copy to the control node (never commit): +# ~/Documents/secrets/monica_site/monica_site_prod.env +# ~/Documents/secrets/monica_site/monica_site_beta.env +# +# server-infra pushes these to /opt/apps/env/monica_site_.env on each host. +# +# Docker Compose: if a secret contains $ (e.g. in DATABASE_URL password), escape each +# $ as $$ or compose will treat $word as a variable. + +# ============================================================================= +# PROD — mkdrealtor.com (holding page until launch) +# ============================================================================= +DJANGO_ENV=prod +DJANGO_DEBUG=false +DJANGO_SECRET_KEY=replace-with-a-long-random-secret +DJANGO_ALLOWED_HOSTS=mkdrealtor.com,www.mkdrealtor.com +# Optional override; when unset, https:// origins are derived from DJANGO_ALLOWED_HOSTS. +# DJANGO_CSRF_TRUSTED_ORIGINS=https://mkdrealtor.com,https://www.mkdrealtor.com + +# Holding page gate — keep true on prod until go-live; beta should be false. +SITE_UNDER_CONSTRUCTION=true + +SITE_NAME=Monica Dhillon +SITE_TAGLINE=MKDRealtor.com · EXIT Realty Redefined +PUBLIC_SITE_URL=https://mkdrealtor.com +DEFAULT_FROM_EMAIL=noreply@mkdrealtor.com +CONTACT_PHONE= +CONTACT_EMAIL= +CONTACT_SERVICE_AREA=Serving Greater Metro Area +CREDIT_NAME=AI ML Operations, LLC +CREDIT_URL=https://aimloperations.com + +# Shared external Postgres (10.0.0.230) +DATABASE_URL=postgres://westfarn:replace-db-password@10.0.0.230:5432/monica_site + +# Host port on adama/roslin/ai-server-4080 (must match server-infra host_apps) +WEB_PORT=8004 + +# reCAPTCHA v3 +RECAPTCHA_PUBLIC_KEY=replace-me +RECAPTCHA_PRIVATE_KEY=replace-me + +# SMTP2GO email +EMAIL_HOST=mail.smtp2go.com +EMAIL_HOST_USER=replace-me +EMAIL_HOST_PASSWORD=replace-me +EMAIL_PORT=2525 +EMAIL_USE_TLS=true + +# SMTP2GO SMS +SMTP2GO_SMS_API_KEY=replace-me +# Email + SMS event webhook auth. SMTP2GO Authorization header = Bearer + this value. +SMTP2GO_WEBHOOK_SECRET=replace-me + +# Postcards — PCM Integrations +POSTCARD_PROVIDER=pcm +PCM_API_KEY=replace-me +PCM_WEBHOOK_SECRET=replace-me +# JSON return address for postcard orders +PCM_RETURN_ADDRESS={"firstName":"Monica","lastName":"Dhillon","address":"replace-me","city":"replace-me","state":"IL","zipCode":"replace-me"} +# LOB_API_KEY= # only if POSTCARD_PROVIDER=lob +# CLICK2MAIL_API_KEY= +# POSTGRID_API_KEY= + +# Social (native) +META_APP_ID= +META_APP_SECRET= +LINKEDIN_CLIENT_ID= +LINKEDIN_CLIENT_SECRET= +SOCIAL_TOKEN_ENCRYPTION_KEY=replace-with-fernet-key + +# Ollama for social drafting (reachable from app hosts) +OLLAMA_BASE_URL=http://10.0.0.128:11434 +OLLAMA_MODEL=llama3.2 +OLLAMA_TIMEOUT_SECONDS=120 + +# Nominatim address suggest (server-side proxy only; not called from browser) +NOMINATIM_BASE_URL=http://10.0.0.128:8089 +NOMINATIM_TIMEOUT_SECONDS=8 +NOMINATIM_COUNTRY_CODES=us +# NOMINATIM_API_KEY= + +# Tianji analytics (pageviews + events) +TIANJI_ENABLED=true +TIANJI_TRACKER_URL=https://tianji.aimloperations.com/tracker.js +TIANJI_WEBSITE_ID=cmshzhxdf6gee10qzkzrfn9iw + +GUNICORN_WORKERS=2 +GUNICORN_BIND=0.0.0.0:8000 + +# ============================================================================= +# BETA overrides — monica-preview.aimloperations.com (full app) +# File: monica_site_beta.env +# ============================================================================= +# DJANGO_ENV=beta +# DJANGO_DEBUG=false +# DJANGO_SECRET_KEY=replace-with-a-different-beta-secret +# DJANGO_ALLOWED_HOSTS=monica-preview.aimloperations.com +# SITE_UNDER_CONSTRUCTION=false +# PUBLIC_SITE_URL=https://monica-preview.aimloperations.com +# DATABASE_URL=postgres://westfarn:replace-db-password@10.0.0.230:5432/monica_site_beta +# WEB_PORT=8014 +# RECAPTCHA_PUBLIC_KEY=replace-me +# RECAPTCHA_PRIVATE_KEY=replace-me +# EMAIL_HOST_USER=replace-me +# EMAIL_HOST_PASSWORD=replace-me +# SMTP2GO_SMS_API_KEY=replace-me +# LOB_API_KEY=replace-me +# SOCIAL_TOKEN_ENCRYPTION_KEY=replace-with-fernet-key +# OLLAMA_BASE_URL=http://10.0.0.128:11434 +# NOMINATIM_BASE_URL=http://10.0.0.128:8089 +# NOMINATIM_COUNTRY_CODES=us diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml new file mode 100644 index 0000000..2ff9c9c --- /dev/null +++ b/.gitea/workflows/ci.yml @@ -0,0 +1,30 @@ +name: CI + +on: + pull_request: + branches: [main] + +jobs: + test: + runs-on: self-hosted + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install uv + run: | + curl -LsSf https://astral.sh/uv/install.sh | sh + echo "$HOME/.local/bin" >> "$GITHUB_PATH" + + - name: Install dependencies + run: uv sync --frozen + + - name: Run unit tests + env: + DJANGO_ENV: dev + DJANGO_SECRET_KEY: test-secret-key + DATABASE_URL: "" + DB_HOST: "" + run: | + cd site + uv run python manage.py test diff --git a/.gitea/workflows/deploy-beta.yml b/.gitea/workflows/deploy-beta.yml new file mode 100644 index 0000000..0a70ec0 --- /dev/null +++ b/.gitea/workflows/deploy-beta.yml @@ -0,0 +1,74 @@ +name: Deploy Beta + +on: + push: + branches: + - main + +jobs: + unit-tests: + runs-on: self-hosted + steps: + - uses: actions/checkout@v4 + + - name: Install uv + run: | + curl -LsSf https://astral.sh/uv/install.sh | sh + echo "$HOME/.local/bin" >> "$GITHUB_PATH" + + - name: Install dependencies + run: uv sync --frozen + + - name: Run unit tests + env: + DJANGO_ENV: dev + DJANGO_SECRET_KEY: test-secret-key + DATABASE_URL: "" + DB_HOST: "" + run: | + cd site + uv run python manage.py test + + docker: + needs: unit-tests + runs-on: self-hosted + steps: + - uses: actions/checkout@v4 + + - name: Build Docker image + run: docker compose build + + - name: Run containerized tests + run: | + set -euo pipefail + unset DATABASE_URL DB_HOST DB_NAME DB_USER DB_PASSWORD DB_PORT \ + COMPOSE_DATABASE_URL DJANGO_ENV DJANGO_SECRET_KEY DJANGO_DEBUG \ + DJANGO_ALLOWED_HOSTS || true + + PROJECT="monica-ci-${{ gitea.sha }}" + cleanup() { docker compose -p "$PROJECT" down -v --remove-orphans || true; } + trap cleanup EXIT + + docker compose -p "$PROJECT" up -d --wait db + docker compose -p "$PROJECT" run --rm --no-deps --entrypoint "" \ + -e DJANGO_ENV=dev \ + -e DJANGO_SECRET_KEY=test-secret-key \ + -e DJANGO_DEBUG=true \ + -e DJANGO_ALLOWED_HOSTS=localhost,127.0.0.1,testserver \ + -e DATABASE_URL=postgres://monica_site:monica_site@db:5432/monica_site \ + -e SITE_UNDER_CONSTRUCTION=false \ + web uv run python manage.py test + + deploy-beta: + needs: docker + runs-on: self-hosted + env: + SERVER_INFRA_ROOT: /home/westfarn/Documents/repos/server-infra + ANSIBLE_PRIVATE_KEY_FILE: /home/westfarn/.ssh/ansible_deploy + steps: + - name: Deploy monica_site beta to all webservers + run: | + "$SERVER_INFRA_ROOT/scripts/deploy.sh" \ + --app monica_site \ + --env beta \ + --ref "${{ gitea.sha }}" diff --git a/.gitea/workflows/deploy-prod.yml b/.gitea/workflows/deploy-prod.yml new file mode 100644 index 0000000..2433325 --- /dev/null +++ b/.gitea/workflows/deploy-prod.yml @@ -0,0 +1,72 @@ +name: Deploy Prod + +on: + workflow_dispatch: {} + +jobs: + unit-tests: + runs-on: self-hosted + steps: + - uses: actions/checkout@v4 + + - name: Install uv + run: | + curl -LsSf https://astral.sh/uv/install.sh | sh + echo "$HOME/.local/bin" >> "$GITHUB_PATH" + + - name: Install dependencies + run: uv sync --frozen + + - name: Run unit tests + env: + DJANGO_ENV: dev + DJANGO_SECRET_KEY: test-secret-key + DATABASE_URL: "" + DB_HOST: "" + run: | + cd site + uv run python manage.py test + + docker: + needs: unit-tests + runs-on: self-hosted + steps: + - uses: actions/checkout@v4 + + - name: Build Docker image + run: docker compose build + + - name: Run containerized tests + run: | + set -euo pipefail + unset DATABASE_URL DB_HOST DB_NAME DB_USER DB_PASSWORD DB_PORT \ + COMPOSE_DATABASE_URL DJANGO_ENV DJANGO_SECRET_KEY DJANGO_DEBUG \ + DJANGO_ALLOWED_HOSTS || true + + PROJECT="monica-ci-${{ gitea.sha }}" + cleanup() { docker compose -p "$PROJECT" down -v --remove-orphans || true; } + trap cleanup EXIT + + docker compose -p "$PROJECT" up -d --wait db + docker compose -p "$PROJECT" run --rm --no-deps --entrypoint "" \ + -e DJANGO_ENV=dev \ + -e DJANGO_SECRET_KEY=test-secret-key \ + -e DJANGO_DEBUG=true \ + -e DJANGO_ALLOWED_HOSTS=localhost,127.0.0.1,testserver \ + -e DATABASE_URL=postgres://monica_site:monica_site@db:5432/monica_site \ + -e SITE_UNDER_CONSTRUCTION=false \ + web uv run python manage.py test + + deploy-prod: + needs: docker + runs-on: self-hosted + env: + SERVER_INFRA_ROOT: /home/westfarn/Documents/repos/server-infra + ANSIBLE_PRIVATE_KEY_FILE: /home/westfarn/.ssh/ansible_deploy + steps: + - name: Deploy monica_site prod to all webservers + run: | + "$SERVER_INFRA_ROOT/scripts/deploy.sh" \ + --app monica_site \ + --env prod \ + --ref "${{ gitea.sha }}" diff --git a/.gitignore b/.gitignore index 3e29de5..9912d95 100644 --- a/.gitignore +++ b/.gitignore @@ -137,8 +137,8 @@ venv.bak/ # Rope project settings .ropeproject -# mkdocs documentation -/site +# mkdocs documentation (do not use /site — that is the Django project tree) +/mkdocs_site/ # mypy .mypy_cache/ @@ -167,3 +167,4 @@ cython_debug/ # PyPI configuration file .pypirc +.ux-compare/ diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..e1e9a41 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,29 @@ +FROM python:3.12-slim + +ENV PYTHONDONTWRITEBYTECODE=1 \ + PYTHONUNBUFFERED=1 \ + UV_COMPILE_BYTECODE=1 \ + UV_LINK_MODE=copy \ + UV_PROJECT_ENVIRONMENT=/app/.venv + +WORKDIR /app + +RUN apt-get update \ + && apt-get install -y --no-install-recommends libpq5 \ + && rm -rf /var/lib/apt/lists/* + +COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv + +COPY pyproject.toml uv.lock ./ +RUN uv sync --frozen --no-dev + +COPY site ./site +COPY scripts/docker-entrypoint.sh /entrypoint.sh +COPY scripts/worker-entrypoint.sh /worker-entrypoint.sh +RUN chmod +x /entrypoint.sh /worker-entrypoint.sh + +WORKDIR /app/site + +EXPOSE 8000 + +ENTRYPOINT ["/entrypoint.sh"] diff --git a/README.md b/README.md index 4d55767..1fa75bd 100644 --- a/README.md +++ b/README.md @@ -1,54 +1,92 @@ # monica_site -Django 6.0 marketing platform for a single realtor client — a cheaper replacement for -PostcardMania with added social-media automation. - -- **Public site** with a reCAPTCHA-protected contact form. -- **Private portal** for the realtor: lead inbox + UTM/campaign analytics. -- **Mailing list** with per-channel consent and easy opt-out; outreach over - **email + SMS (SMTP2GO)** and **postcard** (pluggable provider, default Lob). -- **Social automation** — schedule/post to Facebook, Instagram, and LinkedIn via native APIs. - -See [`docs/monica-site-design.md`](docs/monica-site-design.md) for the full architecture, -data model, folder layout, deployment, and phased implementation plan. +Django 6 marketing platform for Monica Dhillon / MKDRealtor.com — public site, realtor +portal, multi-channel outreach (email + SMS via SMTP2GO, postcards via PCM Integrations), and social +scheduling (Facebook / Instagram / LinkedIn) with Ollama-assisted drafting. ## Stack -- Django 6.0, Python 3.12 -- Postgres (production), SQLite (local fallback) -- **Django Tasks (`django.tasks`)** for background work (sends, scheduled posts) — Postgres - queue store + a single worker process; no Redis/Celery -- `uv` package manager -- Docker; deployed via the `server-infra` Ansible pipeline onto active/active hosts behind - Nginx Proxy Manager (same pattern as `company_site`) +- Django 6 + `uv` +- Postgres (prod/beta); SQLite locally when `DATABASE_URL` unset +- **dj-queue** (Django Tasks backend, Postgres queue, no Redis/Celery) +- Docker; deploy via `server-infra` Ansible pipeline -## Status +## Environments -Design/planning phase. Implementation follows the phases in the design doc: +| Env | Domain | Port (host) | Gate | +|-----|--------|-------------|------| +| prod | `mkdrealtor.com` | 8004 | `SITE_UNDER_CONSTRUCTION=true` (holding page) | +| beta | `monica-preview.aimloperations.com` | 8014 | full app (`SITE_UNDER_CONSTRUCTION=false`) | -0. Scaffolding & deploy skeleton -1. Public site + contact form + leads + UTM -2. Mailing list + email/SMS outreach + opt-out -3. Postcards -4. Social automation -5. Hardening & polish - -## Local development (planned) - -Once scaffolded: +## Local development ```bash uv sync -docker compose up # web + postgres +cd site uv run python manage.py migrate uv run python manage.py runserver -# Dev uses ImmediateBackend — tasks run inline. For prod-like workers, run worker-entrypoint. +# optional: docker compose up # web + postgres (hot reload) +# optional worker: docker compose --profile worker up worker ``` -## Deployment +Docker compose mounts `./site` into the container and runs Django `runserver` when +`DJANGO_ENV=dev` (the default). Edit Python/templates → auto-reload; no image rebuild. +Rebuild only when Dockerfile / deps (`pyproject.toml`, `uv.lock`) change: -CI on merge to `main` runs tests, then calls -`server-infra/scripts/deploy.sh --app monica_site --env prod --ref `. -Requires a `monica_site` entry in the `server-infra` `app_catalog`, a reserved host port pair, -a shared-Postgres database, a singleton django.tasks worker on one host, and a control-node -secret env file. Details in the design doc. +```bash +docker compose up --build +``` + +### Local secrets (SMTP2GO, etc.) + +Copy `.env.example` → `.env` (gitignored). Compose reads `.env` for email vars. + +```bash +cp .env.example .env +# fill EMAIL_HOST_USER / EMAIL_HOST_PASSWORD +# for real delivery (not console logs): +# EMAIL_BACKEND=django.core.mail.backends.smtp.EmailBackend +docker compose up +``` + +Without `EMAIL_BACKEND=…smtp…`, mail prints to the web container logs (console backend). + +### SMTP2GO delivery webhooks + +See [`site/messaging/README.md`](site/messaging/README.md) for SMTP2GO email/SMS webhooks +and PCM Integrations postcard designer + webhook setup (Bearer auth, events). + +Prod compose (`docker-compose.prod.yml`) still uses gunicorn with baked-in image code. + +## Secret env files (control node) + +Create on the deploy host: + +```text +~/Documents/secrets/monica_site/monica_site_prod.env +~/Documents/secrets/monica_site/monica_site_beta.env +``` + +Templates: `.env.prod.example` (full var list). Validate with: + +```bash +./scripts/validate-env.sh ~/Documents/secrets/monica_site/monica_site_prod.env +``` + +## Deploy + +CI on merge to `main` → tests → `server-infra/scripts/deploy.sh --app monica_site --env prod --ref `. + +Start the **dj-queue worker on exactly one host**: + +```bash +docker compose -f docker-compose.prod.yml --profile worker up -d +``` + +## Apps + +`public` · `accounts` · `dashboard` · `leads` · `contacts` · `analytics` · `messaging` · `social` + +Social AI drafts: `POST /portal/social/api/generate/` → Ollama at `OLLAMA_BASE_URL` (default `http://10.0.0.128:11434`). + +See [`docs/monica-site-design.md`](docs/monica-site-design.md) for architecture. diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml new file mode 100644 index 0000000..d636ff3 --- /dev/null +++ b/docker-compose.prod.yml @@ -0,0 +1,22 @@ +# Production compose for server-infra deploy. No bundled Postgres — use shared +# external DB via DATABASE_URL in .env (see .env.prod.example). +# +# Web runs on every app host (active/active). Start the dj-queue worker on +# exactly ONE host via: +# docker compose -f docker-compose.prod.yml --profile worker up -d +services: + web: + build: . + restart: unless-stopped + ports: + - "${WEB_PORT:-8004}:8000" + env_file: + - .env + + worker: + build: . + restart: unless-stopped + profiles: ["worker"] + entrypoint: ["/worker-entrypoint.sh"] + env_file: + - .env diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..44b710b --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,78 @@ +services: + db: + image: postgres:16-alpine + environment: + POSTGRES_DB: monica_site + POSTGRES_USER: monica_site + POSTGRES_PASSWORD: monica_site + volumes: + - postgres_data:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U monica_site -d monica_site"] + interval: 5s + timeout: 5s + retries: 10 + start_period: 10s + + web: + build: . + ports: + - "8000:8000" + # Bind-mount source so edit → runserver auto-reload (no rebuild). + volumes: + - ./site:/app/site + # No required env_file — CI has no .env. Defaults below; for local secrets: + # docker compose --env-file .env up + # + # Do NOT interpolate ${DATABASE_URL} here. On the Act runner / control node that + # var often points at shared prod/beta Postgres; compose would bake it into + # containerized tests. Use COMPOSE_DATABASE_URL only if you need to override. + environment: + DJANGO_ENV: ${DJANGO_ENV:-dev} + DJANGO_SECRET_KEY: ${DJANGO_SECRET_KEY:-dev-only-change-me} + DJANGO_DEBUG: ${DJANGO_DEBUG:-true} + DJANGO_ALLOWED_HOSTS: ${DJANGO_ALLOWED_HOSTS:-localhost,127.0.0.1,0.0.0.0} + DATABASE_URL: ${COMPOSE_DATABASE_URL:-postgres://monica_site:monica_site@db:5432/monica_site} + SITE_UNDER_CONSTRUCTION: ${SITE_UNDER_CONSTRUCTION:-false} + PUBLIC_SITE_URL: ${PUBLIC_SITE_URL:-http://127.0.0.1:8000} + EMAIL_HOST: ${EMAIL_HOST:-mail.smtp2go.com} + EMAIL_HOST_USER: ${EMAIL_HOST_USER:-} + EMAIL_HOST_PASSWORD: ${EMAIL_HOST_PASSWORD:-} + EMAIL_PORT: ${EMAIL_PORT:-2525} + EMAIL_USE_TLS: ${EMAIL_USE_TLS:-true} + DEFAULT_FROM_EMAIL: ${DEFAULT_FROM_EMAIL:-noreply@mkdrealtor.com} + # Dev default = console. Set smtp backend in `.env` for real SMTP2GO sends. + EMAIL_BACKEND: ${EMAIL_BACKEND:-django.core.mail.backends.console.EmailBackend} + SMTP2GO_SMS_API_KEY: ${SMTP2GO_SMS_API_KEY:-} + SMTP2GO_WEBHOOK_SECRET: ${SMTP2GO_WEBHOOK_SECRET:-} + depends_on: + db: + condition: service_healthy + + worker: + build: . + profiles: ["worker"] + entrypoint: ["/worker-entrypoint.sh"] + volumes: + - ./site:/app/site + environment: + DJANGO_ENV: ${DJANGO_ENV:-dev} + DJANGO_SECRET_KEY: ${DJANGO_SECRET_KEY:-dev-only-change-me} + DJANGO_DEBUG: ${DJANGO_DEBUG:-true} + DJANGO_ALLOWED_HOSTS: ${DJANGO_ALLOWED_HOSTS:-localhost,127.0.0.1,0.0.0.0} + DATABASE_URL: ${COMPOSE_DATABASE_URL:-postgres://monica_site:monica_site@db:5432/monica_site} + EMAIL_HOST: ${EMAIL_HOST:-mail.smtp2go.com} + EMAIL_HOST_USER: ${EMAIL_HOST_USER:-} + EMAIL_HOST_PASSWORD: ${EMAIL_HOST_PASSWORD:-} + EMAIL_PORT: ${EMAIL_PORT:-2525} + EMAIL_USE_TLS: ${EMAIL_USE_TLS:-true} + DEFAULT_FROM_EMAIL: ${DEFAULT_FROM_EMAIL:-noreply@mkdrealtor.com} + EMAIL_BACKEND: ${EMAIL_BACKEND:-django.core.mail.backends.console.EmailBackend} + SMTP2GO_SMS_API_KEY: ${SMTP2GO_SMS_API_KEY:-} + SMTP2GO_WEBHOOK_SECRET: ${SMTP2GO_WEBHOOK_SECRET:-} + depends_on: + db: + condition: service_healthy + +volumes: + postgres_data: diff --git a/docs/monica-site-design.md b/docs/monica-site-design.md index fe49e9e..03fc087 100644 --- a/docs/monica-site-design.md +++ b/docs/monica-site-design.md @@ -23,7 +23,7 @@ multiple active/active instances behind Nginx Proxy Manager (NPM). composer, social scheduler. - One mailing list of contacts. Form submissions auto-create/append contacts. - Per-channel, per-contact consent with easy opt-out (email unsubscribe link, SMS `STOP`). -- Outreach over Email + SMS (SMTP2GO) and Postcard (pluggable provider, default Lob). +- Outreach over Email + SMS (SMTP2GO) and Postcard (PCM Integrations, pluggable). - Social posting/scheduling to Facebook, Instagram, LinkedIn via native APIs. - Cheaper than PostcardMania + adds social automation. @@ -74,7 +74,7 @@ flowchart TB subgraph external [External APIs] SMTP2GO["SMTP2GO (email + SMS)"] - Postcard["Postcard provider (Lob default)"] + Postcard["Postcard provider (PCM Integrations)"] Meta["Meta Graph API (FB + IG)"] LinkedIn["LinkedIn API"] Recaptcha["Google reCAPTCHA"] @@ -230,30 +230,34 @@ a contact opt out of SMS while keeping email. writes a `Suppression`. A2P 10DLC registration is a client onboarding prerequisite (call out in Phase 3). -### 6.2 Postcards — pluggable provider (default Lob) +### 6.2 Postcards — PCM Integrations (pluggable) -A small provider interface so the realtor isn't locked in and we can shop on price: +Default provider is **PCM Integrations** (DirectMail API v3). A small provider interface +keeps Lob / Click2Mail / PostGrid available behind `POSTCARD_PROVIDER` if needed later. ```python class PostcardProvider(Protocol): - def send_postcard(self, *, to: PostalAddress, from_: PostalAddress, - front: Asset, back: Asset, idempotency_key: str) -> ProviderResult: ... - def get_status(self, provider_id: str) -> DeliveryStatus: ... + def send_postcard(self, message) -> PostcardResult: ... + def get_status(self, provider_id: str) -> str: ... ``` Adapters live in `messaging/providers/postcard/`. Selected via `POSTCARD_PROVIDER` env var. -| Provider | Model | ~4x6 postcard | Best for | Notes | -|----------|-------|---------------|----------|-------| -| **Lob** (default) | API-first, tiered | Free dev tier ~$0.77; ~$0.51 on $260/mo Startup | Clean API, address verification, in-transit tracking | Best DX; monthly fee only worth it at volume | -| Click2Mail | Pay-per-piece, no subscription | ~$0.35–0.70 | Low/occasional volume, no monthly fee | API less polished; great when volume is small | -| PostGrid | API + dashboard, subscription | Contact sales (from ~$250/mo) | Compliance-heavy, templates | Overkill unless compliance-driven | -| Stannp | Marketer-friendly, no minimums | Transparent per-piece | Non-dev fallback, EU | Good dashboard | -| USPS EDDM | Postage-only saturation | Postage only | Whole-route blasts (no list) | No per-address API; manual/bulk option | +| Provider | Model | Notes | +|----------|-------|-------| +| **PCM Integrations** (default) | DirectMail API v3 + embedded designer | Iframe editor (`POST /design/custom`, embed URL); orders with `designID` + recipients; inbound status webhooks | +| Lob | API-first, tiered | Alternate behind same interface | +| Click2Mail | Pay-per-piece | Low-volume alternate | +| PostGrid | API + dashboard | Compliance-heavy alternate | -> Recommendation: start on **Lob Developer (free) tier** to validate, keep **Click2Mail** as the -> low-volume cost option behind the same interface. Revisit once monthly volume is known — the -> abstraction makes switching a config change. +**Designer:** portal embeds PCM’s editor (no homemade layout tool). Saved designs become +`MessageTemplate` rows with `postcard_front.design_id`. + +**Webhooks:** `POST /portal/messaging/webhooks/postcard/` with Bearer `PCM_WEBHOOK_SECRET`. +Correlate via `extRefNbr` (= `Message.pk`) or `orderID` (= `provider_message_id`). + +**Campaign notify:** when any channel campaign reaches `completed`, one summary email goes +to `created_by.email` or `CONTACT_EMAIL` (`Campaign.notify_sent_at` guard). ### 6.3 Social — native APIs (Facebook, Instagram, LinkedIn) Chosen over an aggregator (Ayrshare) to avoid per-profile monthly fees, since this is one @@ -324,10 +328,10 @@ monica_site/ # repo root │ ├── worker-entrypoint.sh # django.tasks backend worker (+ optional dispatch_due loop) │ └── validate-env.sh ├── .gitea/workflows/ -│ ├── ci.yml # PR: uv sync + manage.py test +│ ├── ci.yml # PR: uv sync + cd site && manage.py test │ ├── unittests.yml # master: tests gate deploy │ └── deploy.yml # calls server-infra/scripts/deploy.sh --app monica_site -└── monica_site/ # Django project dir (manage.py lives here) +└── site/ # Django project dir (manage.py lives here) ├── manage.py ├── monica_site/ # project package │ ├── __init__.py @@ -358,7 +362,7 @@ monica_site/ # repo root │ └── providers/ │ ├── email/smtp2go.py │ ├── sms/smtp2go.py - │ └── postcard/{base.py,lob.py,click2mail.py,postgrid.py} + │ └── postcard/{__init__.py,pcm.py,lob.py,click2mail.py,postgrid.py} └── social/ # accounts, posts, scheduling ├── models.py ├── tasks.py # @task publish_social_target, etc. @@ -409,7 +413,7 @@ monica_site: ### 8.3 CI/CD flow Same as `company_site`: -1. PR → `ci.yml` runs `uv sync` + `manage.py test`. +1. PR → `ci.yml` runs `uv sync` + `cd site && manage.py test`. 2. Merge to `main` → `unittests.yml` (containerized tests) → on green, `deploy.yml` calls `server-infra/scripts/deploy.sh --app monica_site --env prod --ref `. 3. `deploy-apps.yml` checks out the ref on each app host, injects `.env`, `docker compose build`, @@ -464,9 +468,11 @@ EMAIL_USE_TLS=true # SMTP2GO SMS SMTP2GO_SMS_API_KEY=... -# Postcards -POSTCARD_PROVIDER=lob # lob|click2mail|postgrid -LOB_API_KEY=... +# Postcards (PCM Integrations) +POSTCARD_PROVIDER=pcm # pcm|lob|click2mail|postgrid +PCM_API_KEY=... +PCM_WEBHOOK_SECRET=... +PCM_RETURN_ADDRESS={...} # Social (native) META_APP_ID=... @@ -512,11 +518,11 @@ TIANJI_WEBSITE_ID=... - **Exit:** realtor sends a real email + SMS campaign to consented contacts; opt-out works. ### Phase 3 — Postcards -- `messaging/providers/postcard/` interface + Lob adapter (+ Click2Mail adapter as the - low-volume option); `POSTCARD_PROVIDER` switch. -- Postcard campaign flow: pick template/artwork, select recipients (address required + Lob - address verification), enqueue batch as Django Tasks, poll delivery status. -- Client prerequisites called out: Lob account, A2P 10DLC for SMS. +- `messaging/providers/postcard/` + PCM Integrations adapter (Lob/Click2Mail kept as + alternates); `POSTCARD_PROVIDER=pcm`. +- PCM iframe designer; save `design_id` on `MessageTemplate`; postcard campaign audience; + inbound PCM webhooks → `ProviderEvent`; campaign completion notify email. +- Client prerequisites: PCM Integrations account + API key, A2P 10DLC for SMS. - **Exit:** realtor mails a postcard batch and sees delivery tracking. ### Phase 4 — Social automation diff --git a/proposal/powerpoint/.~lock.MonicaSite-CostProposal.pptx# b/proposal/powerpoint/.~lock.MonicaSite-CostProposal.pptx# deleted file mode 100644 index 041d464..0000000 --- a/proposal/powerpoint/.~lock.MonicaSite-CostProposal.pptx# +++ /dev/null @@ -1 +0,0 @@ -,westfarn,ryan-development-1,15.07.2026 14:50,file:///home/westfarn/.config/libreoffice/4; \ No newline at end of file diff --git a/proposal/powerpoint/MonicaSite-CostProposal_updated.pptx b/proposal/powerpoint/MonicaSite-CostProposal_updated.pptx index ca446cc..513d95d 100644 Binary files a/proposal/powerpoint/MonicaSite-CostProposal_updated.pptx and b/proposal/powerpoint/MonicaSite-CostProposal_updated.pptx differ diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..55b27ad --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,26 @@ +[project] +name = "monica-site" +version = "0.1.0" +description = "Django marketing platform for MKDRealtor (Monica Dhillon)" +readme = "README.md" +requires-python = ">=3.12" +dependencies = [ + "django>=6.0,<7", + "dj-queue>=0.13.0", + "django-phonenumber-field>=8.1.0", + "django-recaptcha>=4.1.0", + "cryptography>=44.0.0", + "gunicorn>=23.0.0", + "phonenumbers>=9.0.0", + "psycopg2-binary>=2.9.10", + "requests>=2.32.0", + "whitenoise>=6.9.0", +] + +[dependency-groups] +dev = [ + "pre-commit>=4.1.0", +] + +[tool.uv] +package = false diff --git a/scripts/docker-entrypoint.sh b/scripts/docker-entrypoint.sh new file mode 100755 index 0000000..623a65d --- /dev/null +++ b/scripts/docker-entrypoint.sh @@ -0,0 +1,56 @@ +#!/usr/bin/env bash +set -euo pipefail + +cd /app/site + +wait_for_database() { + if [[ -z "${DATABASE_URL:-}" && -z "${DB_HOST:-}" ]]; then + return 0 + fi + + echo "Waiting for database..." + for _ in $(seq 1 30); do + if uv run python - <<'PY' +import os +import sys + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "monica_site.settings") + +import django +from django.db import connections +from django.db.utils import OperationalError + +django.setup() + +try: + connections["default"].ensure_connection() +except OperationalError: + sys.exit(1) +PY + then + echo "Database is ready." + return 0 + fi + sleep 2 + done + + echo "Database did not become ready in time." >&2 + exit 1 +} + +wait_for_database + +uv run python manage.py migrate --noinput + +# Local compose defaults to DJANGO_ENV=dev: runserver + source bind-mount → hot reload. +# Prod/beta images keep gunicorn (no file watch). +if [[ "${DJANGO_ENV:-}" == "dev" || "${DJANGO_USE_RUNSERVER:-}" == "true" ]]; then + echo "Starting Django runserver (auto-reload on)." + exec uv run python manage.py runserver "${GUNICORN_BIND:-0.0.0.0:8000}" +fi + +uv run python manage.py collectstatic --noinput + +exec uv run gunicorn monica_site.wsgi:application \ + --bind "${GUNICORN_BIND:-0.0.0.0:8000}" \ + --workers "${GUNICORN_WORKERS:-2}" diff --git a/scripts/validate-env.sh b/scripts/validate-env.sh new file mode 100755 index 0000000..570ed23 --- /dev/null +++ b/scripts/validate-env.sh @@ -0,0 +1,60 @@ +#!/usr/bin/env bash +set -euo pipefail + +ENV_FILE="${1:?Usage: $0 }" + +if [[ ! -f "$ENV_FILE" ]]; then + echo "Environment file not found: $ENV_FILE" >&2 + exit 1 +fi + +set -a +# shellcheck disable=SC1090 +source "$ENV_FILE" +set +a + +DJANGO_ENV="${DJANGO_ENV:-prod}" + +required_vars=( + DJANGO_SECRET_KEY + DJANGO_ALLOWED_HOSTS + DATABASE_URL + WEB_PORT +) + +if [[ "$DJANGO_ENV" == "prod" || "$DJANGO_ENV" == "beta" ]]; then + required_vars+=( + RECAPTCHA_PUBLIC_KEY + RECAPTCHA_PRIVATE_KEY + EMAIL_HOST_USER + EMAIL_HOST_PASSWORD + ) +fi + +if [[ "$DJANGO_ENV" == "prod" ]]; then + required_vars+=( + SITE_UNDER_CONSTRUCTION + TIANJI_WEBSITE_ID + ) +fi + +missing=() +for var in "${required_vars[@]}"; do + if [[ -z "${!var:-}" ]]; then + missing+=("$var") + fi +done + +if ((${#missing[@]} > 0)); then + echo "Missing required environment variables in $ENV_FILE:" >&2 + printf ' - %s\n' "${missing[@]}" >&2 + echo "Copy .env.prod.example to ~/Documents/secrets/monica_site/monica_site_${DJANGO_ENV}.env and set values." >&2 + exit 1 +fi + +if [[ "$DJANGO_ENV" == "prod" && "$DJANGO_SECRET_KEY" == change-me* ]]; then + echo "DJANGO_SECRET_KEY must be changed from the example value for production." >&2 + exit 1 +fi + +echo "Environment validation passed (DJANGO_ENV=$DJANGO_ENV)." diff --git a/scripts/worker-entrypoint.sh b/scripts/worker-entrypoint.sh new file mode 100755 index 0000000..995bae1 --- /dev/null +++ b/scripts/worker-entrypoint.sh @@ -0,0 +1,46 @@ +#!/usr/bin/env bash +set -euo pipefail + +cd /app/site + +wait_for_database() { + if [[ -z "${DATABASE_URL:-}" && -z "${DB_HOST:-}" ]]; then + return 0 + fi + + echo "Waiting for database..." + for _ in $(seq 1 30); do + if uv run python - <<'PY' +import os +import sys + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "monica_site.settings") + +import django +from django.db import connections +from django.db.utils import OperationalError + +django.setup() + +try: + connections["default"].ensure_connection() +except OperationalError: + sys.exit(1) +PY + then + echo "Database is ready." + return 0 + fi + sleep 2 + done + + echo "Database did not become ready in time." >&2 + exit 1 +} + +wait_for_database + +uv run python manage.py migrate --noinput + +# dj-queue supervisor (workers + dispatcher + scheduler). Run on ONE host only. +exec uv run python manage.py dj_queue diff --git a/site/accounts/__init__.py b/site/accounts/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/site/accounts/admin.py b/site/accounts/admin.py new file mode 100644 index 0000000..139290f --- /dev/null +++ b/site/accounts/admin.py @@ -0,0 +1,9 @@ +from django.contrib import admin + +from accounts.models import RealtorProfile + + +@admin.register(RealtorProfile) +class RealtorProfileAdmin(admin.ModelAdmin): + list_display = ("user", "display_name", "phone") + search_fields = ("user__username", "display_name") diff --git a/site/accounts/apps.py b/site/accounts/apps.py new file mode 100644 index 0000000..0cb51e6 --- /dev/null +++ b/site/accounts/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class AccountsConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "accounts" diff --git a/site/accounts/migrations/0001_initial.py b/site/accounts/migrations/0001_initial.py new file mode 100644 index 0000000..683819b --- /dev/null +++ b/site/accounts/migrations/0001_initial.py @@ -0,0 +1,33 @@ +# Generated by Django 6.1 on 2026-08-06 18:01 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='RealtorProfile', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('display_name', models.CharField(blank=True, max_length=120)), + ('phone', models.CharField(blank=True, max_length=32)), + ('title', models.CharField(blank=True, max_length=120)), + ('bio', models.TextField(blank=True)), + ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='realtor_profile', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'abstract': False, + }, + ), + ] diff --git a/site/accounts/migrations/__init__.py b/site/accounts/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/site/accounts/models.py b/site/accounts/models.py new file mode 100644 index 0000000..5143b5f --- /dev/null +++ b/site/accounts/models.py @@ -0,0 +1,19 @@ +from django.conf import settings +from django.db import models + +from core.models import TimeStampedModel + + +class RealtorProfile(TimeStampedModel): + user = models.OneToOneField( + settings.AUTH_USER_MODEL, + on_delete=models.CASCADE, + related_name="realtor_profile", + ) + display_name = models.CharField(max_length=120, blank=True) + phone = models.CharField(max_length=32, blank=True) + title = models.CharField(max_length=120, blank=True) + bio = models.TextField(blank=True) + + def __str__(self) -> str: + return self.display_name or self.user.get_username() diff --git a/site/accounts/templates/accounts/login.html b/site/accounts/templates/accounts/login.html new file mode 100644 index 0000000..d6dd3f6 --- /dev/null +++ b/site/accounts/templates/accounts/login.html @@ -0,0 +1,42 @@ +{% load static %} + + + + + + Sign in · Portal · MKDRealtor.com + + + + + + + + diff --git a/site/accounts/urls.py b/site/accounts/urls.py new file mode 100644 index 0000000..bee2c7f --- /dev/null +++ b/site/accounts/urls.py @@ -0,0 +1,17 @@ +from django.contrib.auth import views as auth_views +from django.urls import path + +app_name = "accounts" + +urlpatterns = [ + path( + "login/", + auth_views.LoginView.as_view(template_name="accounts/login.html"), + name="login", + ), + path( + "logout/", + auth_views.LogoutView.as_view(), + name="logout", + ), +] diff --git a/site/analytics/__init__.py b/site/analytics/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/site/analytics/admin.py b/site/analytics/admin.py new file mode 100644 index 0000000..3e224fa --- /dev/null +++ b/site/analytics/admin.py @@ -0,0 +1,14 @@ +from django.contrib import admin + +from analytics.models import Attribution, UTMVisit + + +@admin.register(UTMVisit) +class UTMVisitAdmin(admin.ModelAdmin): + list_display = ("utm_source", "utm_campaign", "path", "created_at") + search_fields = ("utm_source", "utm_campaign", "correlation_id") + + +@admin.register(Attribution) +class AttributionAdmin(admin.ModelAdmin): + list_display = ("lead", "utm_source", "utm_campaign", "created_at") diff --git a/site/analytics/apps.py b/site/analytics/apps.py new file mode 100644 index 0000000..cc765df --- /dev/null +++ b/site/analytics/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class AnalyticsConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "analytics" diff --git a/site/analytics/middleware.py b/site/analytics/middleware.py new file mode 100644 index 0000000..257fb40 --- /dev/null +++ b/site/analytics/middleware.py @@ -0,0 +1,62 @@ +from django.utils.crypto import get_random_string + +from analytics.models import Attribution, UTMVisit + + +CORRELATION_COOKIE = "ms_cid" + + +class UTMTrackingMiddleware: + """Capture UTM params into UTMVisit and stash a correlation id cookie.""" + + def __init__(self, get_response): + self.get_response = get_response + + def __call__(self, request): + cid = request.COOKIES.get(CORRELATION_COOKIE) or get_random_string(32) + request.utm_correlation_id = cid + + params = request.GET + has_utm = any(params.get(k) for k in ( + "utm_source", "utm_medium", "utm_campaign", "utm_term", "utm_content" + )) + if has_utm or params.get("utm_source"): + UTMVisit.objects.create( + correlation_id=cid, + path=request.path[:512], + referrer=(request.META.get("HTTP_REFERER") or "")[:1024], + utm_source=params.get("utm_source", "")[:128], + utm_medium=params.get("utm_medium", "")[:128], + utm_campaign=params.get("utm_campaign", "")[:128], + utm_term=params.get("utm_term", "")[:128], + utm_content=params.get("utm_content", "")[:128], + user_agent=(request.META.get("HTTP_USER_AGENT") or "")[:512], + ) + + response = self.get_response(request) + if CORRELATION_COOKIE not in request.COOKIES: + response.set_cookie( + CORRELATION_COOKIE, + cid, + max_age=60 * 60 * 24 * 30, + samesite="Lax", + ) + return response + + +def attribute_lead_from_request(request, lead) -> Attribution | None: + cid = getattr(request, "utm_correlation_id", None) or request.COOKIES.get( + CORRELATION_COOKIE + ) + visit = None + if cid: + visit = ( + UTMVisit.objects.filter(correlation_id=cid).order_by("-created_at").first() + ) + return Attribution.objects.create( + lead=lead, + visit=visit, + utm_source=visit.utm_source if visit else "", + utm_medium=visit.utm_medium if visit else "", + utm_campaign=visit.utm_campaign if visit else "", + ) diff --git a/site/analytics/migrations/0001_initial.py b/site/analytics/migrations/0001_initial.py new file mode 100644 index 0000000..7fe65f3 --- /dev/null +++ b/site/analytics/migrations/0001_initial.py @@ -0,0 +1,53 @@ +# Generated by Django 6.1 on 2026-08-06 18:01 + +import django.db.models.deletion +import uuid +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('leads', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='UTMVisit', + fields=[ + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('correlation_id', models.CharField(db_index=True, max_length=64)), + ('path', models.CharField(blank=True, max_length=512)), + ('referrer', models.URLField(blank=True, max_length=1024)), + ('utm_source', models.CharField(blank=True, max_length=128)), + ('utm_medium', models.CharField(blank=True, max_length=128)), + ('utm_campaign', models.CharField(blank=True, max_length=128)), + ('utm_term', models.CharField(blank=True, max_length=128)), + ('utm_content', models.CharField(blank=True, max_length=128)), + ('user_agent', models.CharField(blank=True, max_length=512)), + ], + options={ + 'ordering': ['-created_at'], + }, + ), + migrations.CreateModel( + name='Attribution', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('utm_source', models.CharField(blank=True, max_length=128)), + ('utm_medium', models.CharField(blank=True, max_length=128)), + ('utm_campaign', models.CharField(blank=True, max_length=128)), + ('lead', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='attribution', to='leads.lead')), + ('visit', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='analytics.utmvisit')), + ], + options={ + 'abstract': False, + }, + ), + ] diff --git a/site/analytics/migrations/__init__.py b/site/analytics/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/site/analytics/models.py b/site/analytics/models.py new file mode 100644 index 0000000..18cc0dc --- /dev/null +++ b/site/analytics/models.py @@ -0,0 +1,37 @@ +from django.db import models + +from core.models import TimeStampedModel, UUIDPrimaryKeyModel +from leads.models import Lead + + +class UTMVisit(UUIDPrimaryKeyModel, TimeStampedModel): + correlation_id = models.CharField(max_length=64, db_index=True) + path = models.CharField(max_length=512, blank=True) + referrer = models.URLField(blank=True, max_length=1024) + utm_source = models.CharField(max_length=128, blank=True) + utm_medium = models.CharField(max_length=128, blank=True) + utm_campaign = models.CharField(max_length=128, blank=True) + utm_term = models.CharField(max_length=128, blank=True) + utm_content = models.CharField(max_length=128, blank=True) + user_agent = models.CharField(max_length=512, blank=True) + + class Meta: + ordering = ["-created_at"] + + def __str__(self) -> str: + return f"{self.utm_source or 'direct'} / {self.path}" + + +class Attribution(TimeStampedModel): + lead = models.OneToOneField( + Lead, on_delete=models.CASCADE, related_name="attribution" + ) + visit = models.ForeignKey( + UTMVisit, null=True, blank=True, on_delete=models.SET_NULL + ) + utm_source = models.CharField(max_length=128, blank=True) + utm_medium = models.CharField(max_length=128, blank=True) + utm_campaign = models.CharField(max_length=128, blank=True) + + def __str__(self) -> str: + return f"attr {self.lead_id} ← {self.utm_source or 'direct'}" diff --git a/site/analytics/services.py b/site/analytics/services.py new file mode 100644 index 0000000..725cb76 --- /dev/null +++ b/site/analytics/services.py @@ -0,0 +1,3 @@ +"""Analytics services.""" + +from analytics.middleware import attribute_lead_from_request # noqa: F401 diff --git a/site/analytics/templates/analytics/report.html b/site/analytics/templates/analytics/report.html new file mode 100644 index 0000000..cffda3a --- /dev/null +++ b/site/analytics/templates/analytics/report.html @@ -0,0 +1,124 @@ +{% extends "portal_base.html" %} +{% block title %}Analytics · Portal{% endblock %} +{% block topbar_title %}Analytics{% endblock %} +{% block portal_content %} +
+
+
Views (last 30 days)
+
{{ visits_last_30_days }}
+
+
+
UTM landings
+
{{ total_visits }}
+
+
+
Attributed leads
+
{{ total_attributed }}
+
+
+
Top source
+
{{ top_source|default:"—" }}
+
+
+
Top campaign
+
{{ top_campaign|default:"—" }}
+
+
+ +
+

Landing volume by source

+
+
+ {% for row in visits_by_source|slice:":8" %} +
+ {% empty %} +
+ {% endfor %} +
+
+
+ +
+

UTM landings

From ?utm_* hits
+
+ + + + + + + + + + + {% for row in visits_by_combo %} + + + + + + + {% empty %} + + {% endfor %} + +
SourceMediumCampaignLandings
{{ row.utm_source|default:"(direct)" }}{{ row.utm_medium|default:"—" }}{{ row.utm_campaign|default:"—" }}{{ row.count }}
No UTM landings yet. Open a public URL with ?utm_source=…
+
+
+ +
+

Recent landings

+
+ + + + + + + + + + + {% for visit in recent_visits %} + + + + + + + {% empty %} + + {% endfor %} + +
WhenPathSourceCampaign
{{ visit.created_at|date:"M j, g:i A" }}{{ visit.path }}{{ visit.utm_source|default:"—" }}{{ visit.utm_campaign|default:"—" }}
No visits recorded.
+
+
+ +
+

Lead attribution

After contact-form submit
+
+ + + + + + + + + + + {% for row in leads_by_combo %} + + + + + + + {% empty %} + + {% endfor %} + +
SourceMediumCampaignLeads
{{ row.utm_source|default:"(direct)" }}{{ row.utm_medium|default:"—" }}{{ row.utm_campaign|default:"—" }}{{ row.count }}
No attributed leads yet — submit the contact form after a UTM landing.
+
+
+{% endblock %} diff --git a/site/analytics/urls.py b/site/analytics/urls.py new file mode 100644 index 0000000..168cc2b --- /dev/null +++ b/site/analytics/urls.py @@ -0,0 +1,9 @@ +from django.urls import path + +from analytics import views + +app_name = "analytics" + +urlpatterns = [ + path("", views.report, name="report"), +] diff --git a/site/analytics/views.py b/site/analytics/views.py new file mode 100644 index 0000000..2971483 --- /dev/null +++ b/site/analytics/views.py @@ -0,0 +1,74 @@ +from datetime import timedelta + +from django.contrib.auth.decorators import login_required +from django.db.models import Count +from django.shortcuts import render +from django.utils import timezone + +from analytics.models import Attribution, UTMVisit + + +def _bar_pct(rows, key="count"): + max_count = max((row[key] for row in rows), default=1) or 1 + for row in rows: + row["bar_pct"] = max(12, int(100 * row[key] / max_count)) + return rows + + +@login_required +def report(request): + since_30d = timezone.now() - timedelta(days=30) + visits_last_30_days = UTMVisit.objects.filter(created_at__gte=since_30d).count() + + visits_by_source = _bar_pct( + list( + UTMVisit.objects.values("utm_source") + .annotate(count=Count("id")) + .order_by("-count")[:20] + ) + ) + visits_by_combo = list( + UTMVisit.objects.values("utm_source", "utm_medium", "utm_campaign") + .annotate(count=Count("id")) + .order_by("-count")[:50] + ) + top_visit = visits_by_source[0] if visits_by_source else None + top_visit_campaign = ( + UTMVisit.objects.exclude(utm_campaign="") + .values("utm_campaign") + .annotate(count=Count("id")) + .order_by("-count") + .first() + ) + + leads_by_source = _bar_pct( + list( + Attribution.objects.values("utm_source") + .annotate(count=Count("id")) + .order_by("-count")[:20] + ) + ) + leads_by_combo = list( + Attribution.objects.values("utm_source", "utm_medium", "utm_campaign") + .annotate(count=Count("id")) + .order_by("-count")[:50] + ) + + recent_visits = UTMVisit.objects.all()[:25] + + return render( + request, + "analytics/report.html", + { + "visits_last_30_days": visits_last_30_days, + "total_visits": UTMVisit.objects.count(), + "total_attributed": Attribution.objects.count(), + "top_source": (top_visit or {}).get("utm_source") or "(direct)", + "top_campaign": (top_visit_campaign or {}).get("utm_campaign") or "—", + "visits_by_source": visits_by_source, + "visits_by_combo": visits_by_combo, + "leads_by_source": leads_by_source, + "leads_by_combo": leads_by_combo, + "recent_visits": recent_visits, + }, + ) diff --git a/site/contacts/__init__.py b/site/contacts/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/site/contacts/admin.py b/site/contacts/admin.py new file mode 100644 index 0000000..5511f7a --- /dev/null +++ b/site/contacts/admin.py @@ -0,0 +1,33 @@ +from django.contrib import admin + +from contacts.models import ConsentRecord, Contact, Suppression + + +class ConsentInline(admin.TabularInline): + model = ConsentRecord + extra = 0 + + +class SuppressionInline(admin.TabularInline): + model = Suppression + extra = 0 + + +@admin.register(Contact) +class ContactAdmin(admin.ModelAdmin): + list_display = ("email", "first_name", "last_name", "phone", "source", "created_at") + search_fields = ("email", "first_name", "last_name", "phone") + list_filter = ("source",) + fields = ( + "email", + "first_name", + "last_name", + "phone", + "postal_address", + "source", + "notes", + "created_at", + "updated_at", + ) + readonly_fields = ("created_at", "updated_at") + inlines = [ConsentInline, SuppressionInline] diff --git a/site/contacts/apps.py b/site/contacts/apps.py new file mode 100644 index 0000000..218407f --- /dev/null +++ b/site/contacts/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class ContactsConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "contacts" diff --git a/site/contacts/migrations/0001_initial.py b/site/contacts/migrations/0001_initial.py new file mode 100644 index 0000000..6483f54 --- /dev/null +++ b/site/contacts/migrations/0001_initial.py @@ -0,0 +1,66 @@ +# Generated by Django 6.1 on 2026-08-06 18:01 + +import django.db.models.deletion +import uuid +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Contact', + fields=[ + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('email', models.EmailField(blank=True, max_length=254, null=True, unique=True)), + ('phone', models.CharField(blank=True, max_length=32)), + ('first_name', models.CharField(blank=True, max_length=100)), + ('last_name', models.CharField(blank=True, max_length=100)), + ('postal_address', models.JSONField(blank=True, default=dict)), + ('source', models.CharField(choices=[('contact_form', 'Contact form'), ('import', 'Import'), ('manual', 'Manual'), ('notify_me', 'Notify me'), ('other', 'Other')], default='other', max_length=32)), + ('notes', models.TextField(blank=True)), + ], + options={ + 'ordering': ['-created_at'], + }, + ), + migrations.CreateModel( + name='ConsentRecord', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('channel', models.CharField(choices=[('email', 'Email'), ('sms', 'SMS'), ('postcard', 'Postcard')], max_length=16)), + ('opted_in', models.BooleanField(default=False)), + ('changed_at', models.DateTimeField(auto_now=True)), + ('reason', models.CharField(blank=True, max_length=255)), + ('contact', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='consents', to='contacts.contact')), + ], + options={ + 'ordering': ['-changed_at'], + 'unique_together': {('contact', 'channel')}, + }, + ), + migrations.CreateModel( + name='Suppression', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('channel', models.CharField(choices=[('email', 'Email'), ('sms', 'SMS'), ('postcard', 'Postcard')], max_length=16)), + ('reason', models.CharField(blank=True, max_length=255)), + ('active', models.BooleanField(default=True)), + ('contact', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='suppressions', to='contacts.contact')), + ], + options={ + 'unique_together': {('contact', 'channel')}, + }, + ), + ] diff --git a/site/contacts/migrations/__init__.py b/site/contacts/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/site/contacts/models.py b/site/contacts/models.py new file mode 100644 index 0000000..fb949d5 --- /dev/null +++ b/site/contacts/models.py @@ -0,0 +1,101 @@ +from django.db import models + +from core.models import TimeStampedModel, UUIDPrimaryKeyModel + + +class Channel(models.TextChoices): + EMAIL = "email", "Email" + SMS = "sms", "SMS" + POSTCARD = "postcard", "Postcard" + + +class Contact(UUIDPrimaryKeyModel, TimeStampedModel): + class Source(models.TextChoices): + CONTACT_FORM = "contact_form", "Contact form" + IMPORT = "import", "Import" + MANUAL = "manual", "Manual" + NOTIFY_ME = "notify_me", "Notify me" + OTHER = "other", "Other" + + email = models.EmailField(unique=True, blank=True, null=True) + phone = models.CharField(max_length=32, blank=True) + first_name = models.CharField(max_length=100, blank=True) + last_name = models.CharField(max_length=100, blank=True) + postal_address = models.JSONField(default=dict, blank=True) + source = models.CharField( + max_length=32, choices=Source.choices, default=Source.OTHER + ) + notes = models.TextField(blank=True) + + class Meta: + ordering = ["-created_at"] + + def __str__(self) -> str: + name = f"{self.first_name} {self.last_name}".strip() + return name or self.email or self.phone or str(self.pk) + + @property + def full_name(self) -> str: + return f"{self.first_name} {self.last_name}".strip() + + @staticmethod + def make_postal_address( + *, + line1: str = "", + line2: str = "", + city: str = "", + state: str = "", + zip_code: str = "", + country: str = "US", + ) -> dict: + """Normalize Lob-shaped postal address dict.""" + return { + "line1": (line1 or "").strip(), + "line2": (line2 or "").strip(), + "city": (city or "").strip(), + "state": (state or "").strip(), + "zip": (zip_code or "").strip(), + "country": ((country or "").strip() or "US"), + } + + @staticmethod + def postal_address_has_content(addr: dict | None) -> bool: + if not addr: + return False + return any( + (addr.get(key) or "").strip() + for key in ("line1", "line2", "city", "state", "zip") + ) + + +class ConsentRecord(TimeStampedModel): + contact = models.ForeignKey( + Contact, on_delete=models.CASCADE, related_name="consents" + ) + channel = models.CharField(max_length=16, choices=Channel.choices) + opted_in = models.BooleanField(default=False) + changed_at = models.DateTimeField(auto_now=True) + reason = models.CharField(max_length=255, blank=True) + + class Meta: + unique_together = ("contact", "channel") + ordering = ["-changed_at"] + + def __str__(self) -> str: + state = "in" if self.opted_in else "out" + return f"{self.contact} {self.channel} opt-{state}" + + +class Suppression(TimeStampedModel): + contact = models.ForeignKey( + Contact, on_delete=models.CASCADE, related_name="suppressions" + ) + channel = models.CharField(max_length=16, choices=Channel.choices) + reason = models.CharField(max_length=255, blank=True) + active = models.BooleanField(default=True) + + class Meta: + unique_together = ("contact", "channel") + + def __str__(self) -> str: + return f"suppress {self.contact} {self.channel}" diff --git a/site/contacts/nominatim.py b/site/contacts/nominatim.py new file mode 100644 index 0000000..b614493 --- /dev/null +++ b/site/contacts/nominatim.py @@ -0,0 +1,214 @@ +"""Nominatim client — server-side only; browsers never call Nominatim directly.""" + +from __future__ import annotations + +import logging +import re +from typing import Any + +import requests +from django.conf import settings + +logger = logging.getLogger(__name__) + +# ISO3166-2-lvl4 "US-OH" → "OH"; fall back to common full-name map. +_US_STATE_ABBREV = { + "alabama": "AL", + "alaska": "AK", + "arizona": "AZ", + "arkansas": "AR", + "california": "CA", + "colorado": "CO", + "connecticut": "CT", + "delaware": "DE", + "district of columbia": "DC", + "florida": "FL", + "georgia": "GA", + "hawaii": "HI", + "idaho": "ID", + "illinois": "IL", + "indiana": "IN", + "iowa": "IA", + "kansas": "KS", + "kentucky": "KY", + "louisiana": "LA", + "maine": "ME", + "maryland": "MD", + "massachusetts": "MA", + "michigan": "MI", + "minnesota": "MN", + "mississippi": "MS", + "missouri": "MO", + "montana": "MT", + "nebraska": "NE", + "nevada": "NV", + "new hampshire": "NH", + "new jersey": "NJ", + "new mexico": "NM", + "new york": "NY", + "north carolina": "NC", + "north dakota": "ND", + "ohio": "OH", + "oklahoma": "OK", + "oregon": "OR", + "pennsylvania": "PA", + "rhode island": "RI", + "south carolina": "SC", + "south dakota": "SD", + "tennessee": "TN", + "texas": "TX", + "utah": "UT", + "vermont": "VT", + "virginia": "VA", + "washington": "WA", + "west virginia": "WV", + "wisconsin": "WI", + "wyoming": "WY", +} + + +class NominatimError(RuntimeError): + pass + + +# Leading house / unit number from user query (e.g. "1968", "12A", "100-102"). +_HOUSE_FROM_QUERY = re.compile(r"^(\d+[A-Za-z]?(?:-\d+[A-Za-z]?)?)\b") + + +def _house_from_query(query: str) -> str: + match = _HOUSE_FROM_QUERY.match((query or "").strip()) + return match.group(1) if match else "" + + +def _state_code(addr: dict[str, Any]) -> str: + iso = (addr.get("ISO3166-2-lvl4") or "").strip() + if iso.startswith("US-") and len(iso) == 5: + return iso[3:] + raw = (addr.get("state") or "").strip() + if len(raw) == 2: + return raw.upper() + return _US_STATE_ABBREV.get(raw.lower(), raw) + + +def _city(addr: dict[str, Any]) -> str: + for key in ("city", "town", "village", "hamlet", "municipality", "suburb"): + val = (addr.get(key) or "").strip() + if val: + return val + return "" + + +def _line1(addr: dict[str, Any], display_name: str, *, query: str = "") -> str: + house = (addr.get("house_number") or "").strip() + road = (addr.get("road") or addr.get("pedestrian") or "").strip() + # Nominatim often returns road-level hits with no house_number even when the + # user typed one — keep that number so mailing street isn't incomplete. + if not house: + house = _house_from_query(query) + if house and road: + return f"{house} {road}" + if road: + return road + # Place-level hits (city only) — leave street empty for the user to fill. + if house or road: + return " ".join(p for p in (house, road) if p) + first = (display_name or "").split(",")[0].strip() + # Avoid stuffing "Akron" into street when it's a city result. + if first and first.lower() != _city(addr).lower(): + return first + return "" + + +def normalize_hit(raw: dict[str, Any], *, query: str = "") -> dict[str, str]: + addr = raw.get("address") or {} + if not isinstance(addr, dict): + addr = {} + country_code = (addr.get("country_code") or "us").upper() + if country_code == "US": + country = "US" + else: + country = country_code[:2] or "US" + display = (raw.get("display_name") or "").strip() + line1 = _line1(addr, display, query=query) + label = display + # Surface recovered house number in the dropdown when OSM omitted it. + house = (addr.get("house_number") or "").strip() or _house_from_query(query) + if house and label and not re.match(rf"^{re.escape(house)}\b", label, re.I): + label = f"{house} {label}" + return { + "label": label, + "line1": line1, + "line2": "", + "city": _city(addr), + "state": _state_code(addr), + "zip": (addr.get("postcode") or "").strip().split(";")[0].strip(), + "country": country, + } + + +def suggest_addresses(query: str, *, limit: int = 5) -> list[dict[str, str]]: + """ + Proxy Nominatim /search. Returns normalized address dicts for the UI. + + Nominatim itself has no API-key auth — LAN firewall + this Django proxy + gate access. Optional NOMINATIM_API_KEY is sent as X-API-Key if you put + a gateway in front of Nominatim later. + """ + base = (settings.NOMINATIM_BASE_URL or "").rstrip("/") + if not base: + raise NominatimError("NOMINATIM_BASE_URL is not configured") + + q = (query or "").strip() + if len(q) < 3: + return [] + + limit = max(1, min(int(limit or 5), 8)) + params: dict[str, str | int] = { + "q": q, + "format": "json", + "addressdetails": 1, + "limit": limit, + } + countrycodes = (settings.NOMINATIM_COUNTRY_CODES or "").strip() + if countrycodes: + params["countrycodes"] = countrycodes + + headers = { + "User-Agent": settings.NOMINATIM_USER_AGENT, + "Accept": "application/json", + } + api_key = (settings.NOMINATIM_API_KEY or "").strip() + if api_key: + headers["X-API-Key"] = api_key + + url = f"{base}/search" + try: + response = requests.get( + url, + params=params, + headers=headers, + timeout=settings.NOMINATIM_TIMEOUT_SECONDS, + ) + response.raise_for_status() + payload = response.json() + except requests.RequestException as exc: + logger.exception("Nominatim request failed") + raise NominatimError(f"Nominatim unreachable at {url}: {exc}") from exc + except ValueError as exc: + raise NominatimError("Nominatim returned invalid JSON") from exc + + if not isinstance(payload, list): + return [] + + results: list[dict[str, str]] = [] + seen: set[str] = set() + for item in payload: + if not isinstance(item, dict): + continue + normalized = normalize_hit(item, query=q) + key = re.sub(r"\s+", " ", normalized["label"].lower()) + if not key or key in seen: + continue + seen.add(key) + results.append(normalized) + return results diff --git a/site/contacts/templates/contacts/detail.html b/site/contacts/templates/contacts/detail.html new file mode 100644 index 0000000..4ebb0e8 --- /dev/null +++ b/site/contacts/templates/contacts/detail.html @@ -0,0 +1,85 @@ +{% extends "portal_base.html" %} +{% load static %} +{% block title %}{{ contact }} · Contact{% endblock %} +{% block topbar_title %}Contact · {{ contact }}{% endblock %} +{% block extra_head %} + +{% endblock %} +{% block portal_content %} +
+ {% csrf_token %} +
+
+

Profile

+
+
+
+
+
+
+
+
+
+
+ +
+
+ + +
+
+ + +
+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+ +
+ +
+
+ + ← Mailing list +
+
+
+
+

Consent

+
+
+ +
+
+ +
+
+ +
+

Postcard campaigns need a street address and postcard consent. Opt-outs also write a suppression so campaigns skip this contact.

+
+
+
+
+{% endblock %} +{% block extra_js %} + +{% endblock %} diff --git a/site/contacts/templates/contacts/import.html b/site/contacts/templates/contacts/import.html new file mode 100644 index 0000000..15f9d08 --- /dev/null +++ b/site/contacts/templates/contacts/import.html @@ -0,0 +1,64 @@ +{% extends "portal_base.html" %} +{% block title %}Import contacts · Portal{% endblock %} +{% block topbar_title %}Import contacts{% endblock %} +{% block portal_content %} +
+
1 Upload
+
2 Map columns
+
3 Consent
+
4 Import
+
+ +
+
+
+

Upload

+
+
+

Drop CSV or Excel here

+

Import processing wires up next. Accepted: .csv, .xlsx

+

+
+
+
+
+

Column mapping

+
+ + + + + + + + + + + + +
Your columnMaps to
Emailemail
Firstfirst_name
Lastlast_name
Phonephone
Streetpostal_address.line1
Citypostal_address.city
Statepostal_address.state
ZIPpostal_address.zip
+
+
+
+
+
+

Consent defaults

+
+ + + +
+
+
+
+
+

Preview

+
+

Sample rows appear after upload.

+ +

← Back to mailing list

+
+
+
+
+{% endblock %} diff --git a/site/contacts/templates/contacts/list.html b/site/contacts/templates/contacts/list.html new file mode 100644 index 0000000..4088493 --- /dev/null +++ b/site/contacts/templates/contacts/list.html @@ -0,0 +1,67 @@ +{% extends "portal_base.html" %} +{% block title %}Mailing list · Portal{% endblock %} +{% block topbar_title %}Mailing list{% endblock %} +{% block portal_content %} +
+
+ + +
+ +
+ +
+
+ + + + + + + + + + + + {% for contact in contacts %} + + + + + + + + {% empty %} + + {% endfor %} + +
ContactAddressConsentSource
+ {{ contact }}
+ + {% if contact.email %}{{ contact.email }}{% endif %} + {% if contact.email and contact.phone %} · {% endif %} + {% if contact.phone %}{{ contact.phone }}{% endif %} + +
+ {% if contact.postal_address.line1 %} + {{ contact.postal_address.line1 }}{% if contact.postal_address.city %}, {{ contact.postal_address.city }}{% endif %} + {% else %} + — + {% endif %} + + {% with c=contact.consent_flags %} + E + S + P + {% endwith %} + {{ contact.get_source_display }}
No contacts yet.
+
+
+

+ E = email · S = SMS · P = postcard. + Import contacts for bulk CSV/Excel. +

+{% endblock %} diff --git a/site/contacts/tests_nominatim.py b/site/contacts/tests_nominatim.py new file mode 100644 index 0000000..baee710 --- /dev/null +++ b/site/contacts/tests_nominatim.py @@ -0,0 +1,42 @@ +from contacts.nominatim import normalize_hit + + +def test_line1_keeps_house_number_from_query_when_nominatim_omits_it(): + raw = { + "display_name": ( + "Greensboro Drive, Wheaton, DuPage County, Illinois, 60189, United States" + ), + "address": { + "road": "Greensboro Drive", + "town": "Wheaton", + "county": "DuPage County", + "state": "Illinois", + "postcode": "60189", + "country_code": "us", + "ISO3166-2-lvl4": "US-IL", + }, + } + hit = normalize_hit(raw, query="1968 Greensboro Drive, Wheaton") + assert hit["line1"] == "1968 Greensboro Drive" + assert hit["label"].startswith("1968 Greensboro Drive") + assert hit["city"] == "Wheaton" + assert hit["state"] == "IL" + assert hit["zip"] == "60189" + + +def test_line1_prefers_nominatim_house_number(): + raw = { + "display_name": "1968 Greensboro Drive, Wheaton, Illinois, 60189, United States", + "address": { + "house_number": "1968", + "road": "Greensboro Drive", + "town": "Wheaton", + "state": "Illinois", + "postcode": "60189", + "country_code": "us", + "ISO3166-2-lvl4": "US-IL", + }, + } + hit = normalize_hit(raw, query="1968 Greensboro Drive") + assert hit["line1"] == "1968 Greensboro Drive" + assert hit["label"] == raw["display_name"] diff --git a/site/contacts/urls.py b/site/contacts/urls.py new file mode 100644 index 0000000..cca07a7 --- /dev/null +++ b/site/contacts/urls.py @@ -0,0 +1,11 @@ +from django.urls import path + +from contacts import views + +app_name = "contacts" + +urlpatterns = [ + path("", views.contact_list, name="list"), + path("import/", views.contact_import, name="import"), + path("/", views.contact_detail, name="detail"), +] diff --git a/site/contacts/views.py b/site/contacts/views.py new file mode 100644 index 0000000..d36e70c --- /dev/null +++ b/site/contacts/views.py @@ -0,0 +1,101 @@ +from django.contrib import messages +from django.contrib.auth.decorators import login_required +from django.db.models import Prefetch, Q +from django.http import JsonResponse +from django.shortcuts import get_object_or_404, redirect, render +from django.views.decorators.http import require_GET, require_http_methods + +from contacts.models import Channel, ConsentRecord, Contact +from contacts.nominatim import NominatimError, suggest_addresses +from messaging.services import channel_preferences, set_channel_preferences + + +def _consent_flags(contact: Contact) -> dict[str, bool]: + return channel_preferences(contact) + + +def _postal_from_post(post) -> dict: + return Contact.make_postal_address( + line1=post.get("address_line1", ""), + line2=post.get("address_line2", ""), + city=post.get("address_city", ""), + state=post.get("address_state", ""), + zip_code=post.get("address_zip", ""), + country=post.get("address_country", "US"), + ) + + +@login_required +def contact_list(request): + contacts = Contact.objects.prefetch_related( + Prefetch("consents", queryset=ConsentRecord.objects.all()) + ).all() + q = (request.GET.get("q") or "").strip() + if q: + contacts = contacts.filter( + Q(first_name__icontains=q) + | Q(last_name__icontains=q) + | Q(email__icontains=q) + | Q(phone__icontains=q) + ) + rows = list(contacts[:200]) + for contact in rows: + contact.consent_flags = _consent_flags(contact) + return render( + request, + "contacts/list.html", + {"contacts": rows, "q": q}, + ) + + +@login_required +@require_http_methods(["GET", "POST"]) +def contact_detail(request, pk): + contact = get_object_or_404( + Contact.objects.prefetch_related("consents"), pk=pk + ) + if request.method == "POST": + contact.postal_address = _postal_from_post(request.POST) + contact.notes = (request.POST.get("notes") or "").strip() + contact.save(update_fields=["postal_address", "notes", "updated_at"]) + set_channel_preferences( + contact, + { + Channel.EMAIL: "consent_email" in request.POST, + Channel.SMS: "consent_sms" in request.POST, + Channel.POSTCARD: "consent_postcard" in request.POST, + }, + reason="portal_manual", + ) + messages.success(request, "Contact updated.") + return redirect("contacts:detail", pk=contact.pk) + prefs = _consent_flags(contact) + return render( + request, + "contacts/detail.html", + {"contact": contact, "prefs": prefs}, + ) + +@login_required +def contact_import(request): + return render(request, "contacts/import.html") + + +@require_GET +def address_suggest(request): + """ + Backend proxy for Nominatim search. Browser JS must call this URL only — + never Nominatim directly. + """ + q = (request.GET.get("q") or "").strip() + if len(q) < 3: + return JsonResponse({"results": []}) + try: + limit = int(request.GET.get("limit") or 5) + except (TypeError, ValueError): + limit = 5 + try: + results = suggest_addresses(q, limit=limit) + except NominatimError as exc: + return JsonResponse({"error": str(exc), "results": []}, status=502) + return JsonResponse({"results": results}) diff --git a/site/core/__init__.py b/site/core/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/site/core/apps.py b/site/core/apps.py new file mode 100644 index 0000000..c0ce093 --- /dev/null +++ b/site/core/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class CoreConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "core" diff --git a/site/core/management/__init__.py b/site/core/management/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/site/core/management/commands/__init__.py b/site/core/management/commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/site/core/management/commands/dispatch_due.py b/site/core/management/commands/dispatch_due.py new file mode 100644 index 0000000..61a3e6b --- /dev/null +++ b/site/core/management/commands/dispatch_due.py @@ -0,0 +1,38 @@ +from django.core.management.base import BaseCommand +from django.utils import timezone + +from messaging.models import Message +from messaging.tasks import send_campaign_message +from social.models import SocialPost +from social.tasks import publish_social_post + + +class Command(BaseCommand): + help = ( + "Enqueue due scheduled campaign messages and social posts. " + "Optional when the task backend supports run_after defer; useful as a safety net." + ) + + def handle(self, *args, **options): + now = timezone.now() + enqueued = 0 + + for message in Message.objects.filter( + status=Message.Status.SCHEDULED, + scheduled_for__lte=now, + ).iterator(): + message.status = Message.Status.QUEUED + message.save(update_fields=["status", "updated_at"]) + send_campaign_message.enqueue(message_id=str(message.pk)) + enqueued += 1 + + for post in SocialPost.objects.filter( + status=SocialPost.Status.SCHEDULED, + scheduled_for__lte=now, + ).iterator(): + post.status = SocialPost.Status.QUEUED + post.save(update_fields=["status", "updated_at"]) + publish_social_post.enqueue(post_id=str(post.pk)) + enqueued += 1 + + self.stdout.write(self.style.SUCCESS(f"Enqueued {enqueued} due item(s).")) diff --git a/site/core/migrations/__init__.py b/site/core/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/site/core/models.py b/site/core/models.py new file mode 100644 index 0000000..81880bc --- /dev/null +++ b/site/core/models.py @@ -0,0 +1,20 @@ +import uuid + +from django.db import models + + +class TimeStampedModel(models.Model): + """Abstract base with created/updated timestamps (mirrors company_site TimeInfoBase).""" + + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + + class Meta: + abstract = True + + +class UUIDPrimaryKeyModel(models.Model): + id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) + + class Meta: + abstract = True diff --git a/site/core/tests.py b/site/core/tests.py new file mode 100644 index 0000000..8d2967c --- /dev/null +++ b/site/core/tests.py @@ -0,0 +1,29 @@ +from django.test import Client, TestCase, override_settings +from django.urls import reverse + + +class HealthzTests(TestCase): + def test_healthz_ok(self): + response = Client().get("/healthz/") + self.assertEqual(response.status_code, 200) + self.assertEqual(response.json()["status"], "ok") + + +class UnderConstructionTests(TestCase): + @override_settings(SITE_UNDER_CONSTRUCTION=True) + def test_home_redirects_when_gated(self): + response = Client().get("/") + self.assertEqual(response.status_code, 302) + self.assertIn("/under-construction", response["Location"]) + + @override_settings(SITE_UNDER_CONSTRUCTION=False) + def test_home_ok_when_open(self): + response = Client().get("/") + self.assertEqual(response.status_code, 200) + + +class PublicSmokeTests(TestCase): + def test_about_and_contact_get(self): + client = Client() + self.assertEqual(client.get(reverse("public:about")).status_code, 200) + self.assertEqual(client.get(reverse("public:contact")).status_code, 200) diff --git a/site/core/views.py b/site/core/views.py new file mode 100644 index 0000000..9a183bf --- /dev/null +++ b/site/core/views.py @@ -0,0 +1,6 @@ +from django.http import JsonResponse + + +def healthz(_request): + """Liveness probe for deploy / NPM health checks.""" + return JsonResponse({"status": "ok"}) diff --git a/site/dashboard/__init__.py b/site/dashboard/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/site/dashboard/apps.py b/site/dashboard/apps.py new file mode 100644 index 0000000..db15b45 --- /dev/null +++ b/site/dashboard/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class DashboardConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "dashboard" diff --git a/site/dashboard/migrations/__init__.py b/site/dashboard/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/site/dashboard/models.py b/site/dashboard/models.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/site/dashboard/models.py @@ -0,0 +1 @@ + diff --git a/site/dashboard/templates/dashboard/home.html b/site/dashboard/templates/dashboard/home.html new file mode 100644 index 0000000..14d6ff1 --- /dev/null +++ b/site/dashboard/templates/dashboard/home.html @@ -0,0 +1,67 @@ +{% extends "portal_base.html" %} +{% block title %}Dashboard · Portal{% endblock %} +{% block topbar_title %}Dashboard{% endblock %} +{% block portal_content %} +
+
+
New leads
+
{{ lead_count }}
+
+
+
Open pipeline
+
{{ open_pipeline }}
+
+
+
Mailing list
+
{{ contact_count }}
+
+
+
Scheduled posts
+
{{ scheduled_posts }}
+
+
+ +
+
+
+

Recent leads

+ View all +
+
+ + + + + + {% for lead in recent_leads %} + + + + + + {% empty %} + + {% endfor %} + +
NameSourceStatus
{{ lead.contact }}{% if lead.attribution %}{{ lead.attribution.utm_source|default:"direct" }}{% if lead.attribution.utm_campaign %} / {{ lead.attribution.utm_campaign }}{% endif %}{% else %}—{% endif %}{{ lead.get_status_display }}
No leads yet.
+
+
+
+
+

Upcoming outreach

+ Compose +
+
+ {% for campaign in upcoming_campaigns %} +

+ {{ campaign.name }} + — {{ campaign.get_channel_display }} · {{ campaign.get_status_display }} + {% if campaign.scheduled_for %} · {{ campaign.scheduled_for }}{% endif %} +

+ {% empty %} +

No campaigns yet.

+ {% endfor %} +
+
+
+{% endblock %} diff --git a/site/dashboard/urls.py b/site/dashboard/urls.py new file mode 100644 index 0000000..4f13690 --- /dev/null +++ b/site/dashboard/urls.py @@ -0,0 +1,9 @@ +from django.urls import path + +from dashboard import views + +app_name = "dashboard" + +urlpatterns = [ + path("", views.home, name="home"), +] diff --git a/site/dashboard/views.py b/site/dashboard/views.py new file mode 100644 index 0000000..efc94ee --- /dev/null +++ b/site/dashboard/views.py @@ -0,0 +1,27 @@ +from django.contrib.auth.decorators import login_required +from django.shortcuts import render + +from contacts.models import Contact +from leads.models import Lead +from messaging.models import Campaign +from social.models import SocialPost + + +@login_required +def home(request): + upcoming = Campaign.objects.exclude( + status__in=[Campaign.Status.COMPLETED, Campaign.Status.CANCELLED] + ).order_by("scheduled_for", "-created_at")[:5] + context = { + "lead_count": Lead.objects.filter(status=Lead.Status.NEW).count(), + "open_pipeline": Lead.objects.filter( + status__in=[Lead.Status.NEW, Lead.Status.CONTACTED] + ).count(), + "contact_count": Contact.objects.count(), + "scheduled_posts": SocialPost.objects.filter( + status__in=[SocialPost.Status.SCHEDULED, SocialPost.Status.QUEUED] + ).count(), + "recent_leads": Lead.objects.select_related("contact", "attribution").all()[:8], + "upcoming_campaigns": upcoming, + } + return render(request, "dashboard/home.html", context) diff --git a/site/leads/__init__.py b/site/leads/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/site/leads/admin.py b/site/leads/admin.py new file mode 100644 index 0000000..e692591 --- /dev/null +++ b/site/leads/admin.py @@ -0,0 +1,16 @@ +from django.contrib import admin + +from leads.models import Lead, LeadNote + + +class LeadNoteInline(admin.TabularInline): + model = LeadNote + extra = 0 + + +@admin.register(Lead) +class LeadAdmin(admin.ModelAdmin): + list_display = ("contact", "status", "created_at") + list_filter = ("status",) + search_fields = ("contact__email", "contact__first_name", "contact__last_name") + inlines = [LeadNoteInline] diff --git a/site/leads/apps.py b/site/leads/apps.py new file mode 100644 index 0000000..67b4c24 --- /dev/null +++ b/site/leads/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class LeadsConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "leads" diff --git a/site/leads/migrations/0001_initial.py b/site/leads/migrations/0001_initial.py new file mode 100644 index 0000000..48eee17 --- /dev/null +++ b/site/leads/migrations/0001_initial.py @@ -0,0 +1,48 @@ +# Generated by Django 6.1 on 2026-08-06 18:01 + +import django.db.models.deletion +import uuid +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('contacts', '0001_initial'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='Lead', + fields=[ + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('message', models.TextField(blank=True)), + ('status', models.CharField(choices=[('new', 'New'), ('contacted', 'Contacted'), ('won', 'Won'), ('lost', 'Lost')], default='new', max_length=16)), + ('contact', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='leads', to='contacts.contact')), + ('owner', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='leads', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'ordering': ['-created_at'], + }, + ), + migrations.CreateModel( + name='LeadNote', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('body', models.TextField()), + ('author', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)), + ('lead', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='notes', to='leads.lead')), + ], + options={ + 'ordering': ['-created_at'], + }, + ), + ] diff --git a/site/leads/migrations/__init__.py b/site/leads/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/site/leads/models.py b/site/leads/models.py new file mode 100644 index 0000000..70e50a3 --- /dev/null +++ b/site/leads/models.py @@ -0,0 +1,46 @@ +from django.conf import settings +from django.db import models + +from contacts.models import Contact +from core.models import TimeStampedModel, UUIDPrimaryKeyModel + + +class Lead(UUIDPrimaryKeyModel, TimeStampedModel): + class Status(models.TextChoices): + NEW = "new", "New" + CONTACTED = "contacted", "Contacted" + WON = "won", "Won" + LOST = "lost", "Lost" + + contact = models.ForeignKey(Contact, on_delete=models.CASCADE, related_name="leads") + message = models.TextField(blank=True) + status = models.CharField( + max_length=16, choices=Status.choices, default=Status.NEW + ) + owner = models.ForeignKey( + settings.AUTH_USER_MODEL, + null=True, + blank=True, + on_delete=models.SET_NULL, + related_name="leads", + ) + + class Meta: + ordering = ["-created_at"] + + def __str__(self) -> str: + return f"Lead {self.contact} ({self.status})" + + +class LeadNote(TimeStampedModel): + lead = models.ForeignKey(Lead, on_delete=models.CASCADE, related_name="notes") + author = models.ForeignKey( + settings.AUTH_USER_MODEL, + null=True, + blank=True, + on_delete=models.SET_NULL, + ) + body = models.TextField() + + class Meta: + ordering = ["-created_at"] diff --git a/site/leads/templates/leads/detail.html b/site/leads/templates/leads/detail.html new file mode 100644 index 0000000..c369467 --- /dev/null +++ b/site/leads/templates/leads/detail.html @@ -0,0 +1,88 @@ +{% extends "portal_base.html" %} +{% block title %}{{ lead.contact }} · Lead{% endblock %} +{% block topbar_title %}Lead · {{ lead.contact }}{% endblock %} +{% block portal_content %} +
+
+
+

Contact

+
+
+
+ + +
+
+ + +
+
+
+ + +
+
+ + +
+

Status edits and note posting come next in functionality work.

+ ← Back to inbox +
+
+
+

Notes

+
+ {% for note in lead.notes.all %} +
+ {% if note.author %}{{ note.author }}{% else %}System{% endif %} + · {{ note.created_at|date:"M j, g:i A" }} — {{ note.body }} +
+ {% empty %} +

No notes yet.

+ {% endfor %} +
+
+
+
+
+

Attribution

+
+ {% if lead.attribution %} +

+ utm_source {{ lead.attribution.utm_source|default:"—" }}
+ utm_medium {{ lead.attribution.utm_medium|default:"—" }}
+ utm_campaign {{ lead.attribution.utm_campaign|default:"—" }}
+ {% if lead.attribution.visit %} + Landing {{ lead.attribution.visit.path|default:"—" }}
+ First touch {{ lead.attribution.visit.created_at|date:"M j, g:i A" }} + {% endif %} +
Converted {{ lead.created_at|date:"M j, g:i A" }} +

+ {% else %} +

No UTM attribution on this lead.

+ {% endif %} +
+
+
+

Linked contact · consent

+
+ +

+ Form submit defaulted email opt-in with notice. Postcard requires address + separate consent. +

+

+ Open contact record → +

+
+
+
+
+{% endblock %} diff --git a/site/leads/templates/leads/list.html b/site/leads/templates/leads/list.html new file mode 100644 index 0000000..c7d5960 --- /dev/null +++ b/site/leads/templates/leads/list.html @@ -0,0 +1,57 @@ +{% extends "portal_base.html" %} +{% block title %}Leads · Portal{% endblock %} +{% block topbar_title %}Lead inbox{% endblock %} +{% block portal_content %} +
+
+ + + +
+
+ +
+
+ + + + + + + + + + + + {% for lead in leads %} + + + + + + + + {% empty %} + + {% endfor %} + +
LeadInterestUTM / sourceStatusReceived
+ {{ lead.contact }}
+ {{ lead.contact.email }}{% if lead.contact.phone %} · {{ lead.contact.phone }}{% endif %} +
{{ lead.message|truncatechars:40|default:"—" }} + {% if lead.attribution %} + {{ lead.attribution.utm_source|default:"direct" }} + {% if lead.attribution.utm_medium %}/ {{ lead.attribution.utm_medium }}{% endif %} + {% if lead.attribution.utm_campaign %}/ {{ lead.attribution.utm_campaign }}{% endif %} + {% else %} + — + {% endif %} + {{ lead.get_status_display }}{{ lead.created_at|date:"M j, g:i A" }}
No leads match.
+
+
+{% endblock %} diff --git a/site/leads/urls.py b/site/leads/urls.py new file mode 100644 index 0000000..4d9e605 --- /dev/null +++ b/site/leads/urls.py @@ -0,0 +1,10 @@ +from django.urls import path + +from leads import views + +app_name = "leads" + +urlpatterns = [ + path("", views.lead_list, name="list"), + path("/", views.lead_detail, name="detail"), +] diff --git a/site/leads/views.py b/site/leads/views.py new file mode 100644 index 0000000..7e3c9b7 --- /dev/null +++ b/site/leads/views.py @@ -0,0 +1,51 @@ +from django.contrib.auth.decorators import login_required +from django.db.models import Q +from django.shortcuts import get_object_or_404, render + +from contacts.models import Channel +from leads.models import Lead + + +@login_required +def lead_list(request): + leads = Lead.objects.select_related("contact", "attribution").all() + q = (request.GET.get("q") or "").strip() + status = (request.GET.get("status") or "").strip() + if q: + leads = leads.filter( + Q(contact__first_name__icontains=q) + | Q(contact__last_name__icontains=q) + | Q(contact__email__icontains=q) + | Q(contact__phone__icontains=q) + | Q(message__icontains=q) + ) + if status: + leads = leads.filter(status=status) + return render( + request, + "leads/list.html", + { + "leads": leads[:200], + "status_choices": Lead.Status.choices, + "q": q, + "status_filter": status, + }, + ) + + +@login_required +def lead_detail(request, pk): + lead = get_object_or_404( + Lead.objects.select_related( + "contact", "attribution", "attribution__visit" + ).prefetch_related("notes", "contact__consents"), + pk=pk, + ) + consent_map = {c.value: False for c in Channel} + for record in lead.contact.consents.all(): + consent_map[record.channel] = record.opted_in + return render( + request, + "leads/detail.html", + {"lead": lead, "consent_map": consent_map}, + ) diff --git a/site/manage.py b/site/manage.py new file mode 100644 index 0000000..a596c6e --- /dev/null +++ b/site/manage.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python +"""Django's command-line utility for administrative tasks.""" + +import os +import sys + + +def main() -> None: + """Run administrative tasks.""" + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "monica_site.settings") + try: + from django.core.management import execute_from_command_line + except ImportError as exc: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) from exc + execute_from_command_line(sys.argv) + + +if __name__ == "__main__": + main() diff --git a/site/messaging/README.md b/site/messaging/README.md new file mode 100644 index 0000000..30a781e --- /dev/null +++ b/site/messaging/README.md @@ -0,0 +1,116 @@ +# Messaging + +Campaign compose/send, SMTP2GO email + SMS, PCM Integrations postcards, and delivery webhooks. + +## SMTP2GO webhook setup + +Campaign report page polls provider events every 10s. Create **two** webhooks in +SMTP2GO → **Settings → Webhooks** (email and SMS stay separate). + +### Auth (`SMTP2GO_WEBHOOK_SECRET`) + +1. Set `SMTP2GO_WEBHOOK_SECRET` in `.env` / prod env (long random string). +2. In SMTP2GO, set **Authorization header** to **Bearer** and paste that same secret + (do not leave it as “None”). +3. Fallback: `?token=` on the webhook URL also works. + +### Email webhook + +| Field | Value | +|-------|--------| +| URL | `https://mkdrealtor.com/portal/messaging/webhooks/email/` | +| Authorization header | **Bearer** + `SMTP2GO_WEBHOOK_SECRET` | +| Output type | JSON | +| Email events | processed, bounced, rejected, spam, delivered, unsub/resub, opened, clicked | +| Email headers | `X-Monica-Message-Id` | +| SMS events | leave unchecked | + +`X-Monica-Message-Id` is set on every campaign email send and is required so webhook +events match the correct recipient row. + +Beta / other hosts: swap the hostname, keep the path. + +### SMS webhook (separate) + +| Field | Value | +|-------|--------| +| URL | `https://mkdrealtor.com/portal/messaging/webhooks/sms/` | +| Authorization header | **Bearer** + same `SMTP2GO_WEBHOOK_SECRET` | +| Output type | JSON | +| Email events | leave unchecked | +| SMS events | Submitted, Sending, Delivered, Failed, Rejected, Opt-out | + +This endpoint also accepts inbound reply POSTs (`text=STOP`, `from=…`) and opts the +contact out of SMS. + +## PCM Integrations (postcards) + +Default postcard provider. Designer embeds PCM’s editor; orders use DirectMail API v3. + +### Env + +| Var | Purpose | +|-----|---------| +| `PCM_API_KEY` | Bearer token for `https://v3.pcmintegrations.com` | +| `PCM_WEBHOOK_SECRET` | Auth for inbound status webhooks | +| `PCM_RETURN_ADDRESS` | JSON return address on orders | +| `POSTCARD_PROVIDER` | `pcm` (default) | + +### Designer + +Portal → **Postcard design**: create/list designs via API, edit in iframe +(`POST /design/custom`, `GET /design/{id}/edit?mode=embed`). Save as a +`MessageTemplate` (stores `design_id`) then pick it when composing a postcard campaign. + +### Postcard webhook + +Create a webhook subscription in the PCM dashboard (Working with Webhooks): + +| Field | Value | +|-------|--------| +| URL | `https://mkdrealtor.com/portal/messaging/webhooks/postcard/` | +| Authorization | **Bearer** + `PCM_WEBHOOK_SECRET` | +| Events | Order / recipient status (Pending, Processing, Processed, Delivered, Undeliverable, Canceled) | +| Environments | Sandbox and/or Production as needed | + +Fallback: `?token=` on the URL. + +Correlation: we send `extRefNbr=` on each recipient; webhooks should +echo that (or `orderID`, matched to `Message.provider_message_id`). + +### Campaign completion email + +When a campaign reaches **completed** (email, SMS, or postcard), one summary email +goes to `campaign.created_by.email`, else `CONTACT_EMAIL`. Guarded by +`Campaign.notify_sent_at` so it only sends once. + +### Local development + +SMTP2GO / PCM cannot reach `localhost`. Use a tunnel (Cloudflare Tunnel / ngrok) to `:8000`, +or test webhooks against beta/prod. + +For real SMTP delivery locally (not console logs): + +```bash +# in .env +EMAIL_BACKEND=django.core.mail.backends.smtp.EmailBackend +EMAIL_HOST_USER=… +EMAIL_HOST_PASSWORD=… +SMTP2GO_WEBHOOK_SECRET=… +SMTP2GO_SMS_API_KEY=… # SMS sends only +PCM_API_KEY=… +PCM_WEBHOOK_SECRET=… +PCM_RETURN_ADDRESS={…} +``` + +### Endpoints (app) + +| Path | Purpose | +|------|---------| +| `POST /portal/messaging/webhooks/email/` | Email delivery / open / click / bounce / … | +| `POST /portal/messaging/webhooks/sms/` | SMS delivery events + inbound STOP | +| `POST /portal/messaging/webhooks/postcard/` | PCM order / mail tracking events | +| `GET /portal/messaging/campaigns//status.json` | Live stats for the campaign report UI | +| `GET /portal/messaging/postcard/` | PCM designer iframe | + +Code: `webhooks.py`, `providers/postcard/pcm.py`, `views.py`. diff --git a/site/messaging/__init__.py b/site/messaging/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/site/messaging/admin.py b/site/messaging/admin.py new file mode 100644 index 0000000..4bb0bd8 --- /dev/null +++ b/site/messaging/admin.py @@ -0,0 +1,40 @@ +from django.contrib import admin + +from messaging.models import Campaign, Message, MessageTemplate, ProviderEvent + + +@admin.register(MessageTemplate) +class MessageTemplateAdmin(admin.ModelAdmin): + list_display = ("name", "channel", "created_at") + list_filter = ("channel",) + + +class MessageInline(admin.TabularInline): + model = Message + extra = 0 + readonly_fields = ("status", "provider", "provider_message_id", "sent_at") + + +@admin.register(Campaign) +class CampaignAdmin(admin.ModelAdmin): + list_display = ( + "name", + "channel", + "audience", + "status", + "scheduled_for", + "created_at", + ) + list_filter = ("channel", "audience", "status") + inlines = [MessageInline] + + +@admin.register(Message) +class MessageAdmin(admin.ModelAdmin): + list_display = ("campaign", "contact", "channel", "status", "scheduled_for") + list_filter = ("channel", "status") + + +@admin.register(ProviderEvent) +class ProviderEventAdmin(admin.ModelAdmin): + list_display = ("provider", "event_type", "created_at") diff --git a/site/messaging/apps.py b/site/messaging/apps.py new file mode 100644 index 0000000..751513d --- /dev/null +++ b/site/messaging/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class MessagingConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "messaging" diff --git a/site/messaging/channels/__init__.py b/site/messaging/channels/__init__.py new file mode 100644 index 0000000..828a0b7 --- /dev/null +++ b/site/messaging/channels/__init__.py @@ -0,0 +1,29 @@ +"""Channel dispatch — email / SMS / postcard.""" + +from dataclasses import dataclass + +from contacts.models import Channel +from messaging.models import Message +from messaging.providers.email.smtp2go import send_email +from messaging.providers.postcard import get_postcard_provider +from messaging.providers.sms.smtp2go import send_sms + + +@dataclass +class ProviderResult: + provider: str + provider_id: str + + +def dispatch_message(message: Message) -> ProviderResult: + if message.channel == Channel.EMAIL: + result = send_email(message) + return ProviderResult(provider="smtp2go_email", provider_id=result) + if message.channel == Channel.SMS: + result = send_sms(message) + return ProviderResult(provider="smtp2go_sms", provider_id=result) + if message.channel == Channel.POSTCARD: + provider = get_postcard_provider() + result = provider.send_postcard(message) + return ProviderResult(provider=provider.name, provider_id=result.provider_id) + raise ValueError(f"Unsupported channel: {message.channel}") diff --git a/site/messaging/management/__init__.py b/site/messaging/management/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/site/messaging/management/commands/__init__.py b/site/messaging/management/commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/site/messaging/migrations/0001_initial.py b/site/messaging/migrations/0001_initial.py new file mode 100644 index 0000000..491e482 --- /dev/null +++ b/site/messaging/migrations/0001_initial.py @@ -0,0 +1,91 @@ +# Generated by Django 6.1 on 2026-08-06 18:01 + +import django.db.models.deletion +import uuid +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('contacts', '0001_initial'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='MessageTemplate', + fields=[ + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('name', models.CharField(max_length=120)), + ('channel', models.CharField(choices=[('email', 'Email'), ('sms', 'SMS'), ('postcard', 'Postcard')], max_length=16)), + ('subject', models.CharField(blank=True, max_length=255)), + ('body', models.TextField()), + ('postcard_front', models.JSONField(blank=True, default=dict)), + ('postcard_back', models.JSONField(blank=True, default=dict)), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='Campaign', + fields=[ + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('name', models.CharField(max_length=120)), + ('channel', models.CharField(choices=[('email', 'Email'), ('sms', 'SMS'), ('postcard', 'Postcard')], max_length=16)), + ('status', models.CharField(choices=[('draft', 'Draft'), ('scheduled', 'Scheduled'), ('sending', 'Sending'), ('completed', 'Completed'), ('cancelled', 'Cancelled')], default='draft', max_length=16)), + ('scheduled_for', models.DateTimeField(blank=True, null=True)), + ('subject_override', models.CharField(blank=True, max_length=255)), + ('body_override', models.TextField(blank=True)), + ('created_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)), + ('template', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='campaigns', to='messaging.messagetemplate')), + ], + options={ + 'ordering': ['-created_at'], + }, + ), + migrations.CreateModel( + name='Message', + fields=[ + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('channel', models.CharField(choices=[('email', 'Email'), ('sms', 'SMS'), ('postcard', 'Postcard')], max_length=16)), + ('status', models.CharField(choices=[('draft', 'Draft'), ('scheduled', 'Scheduled'), ('queued', 'Queued'), ('sent', 'Sent'), ('delivered', 'Delivered'), ('failed', 'Failed'), ('bounced', 'Bounced'), ('suppressed', 'Suppressed')], default='draft', max_length=16)), + ('provider', models.CharField(blank=True, max_length=64)), + ('provider_message_id', models.CharField(blank=True, max_length=255)), + ('scheduled_for', models.DateTimeField(blank=True, null=True)), + ('sent_at', models.DateTimeField(blank=True, null=True)), + ('error', models.TextField(blank=True)), + ('body_snapshot', models.TextField(blank=True)), + ('campaign', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='messages', to='messaging.campaign')), + ('contact', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='messages', to='contacts.contact')), + ], + options={ + 'ordering': ['-created_at'], + }, + ), + migrations.CreateModel( + name='ProviderEvent', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('provider', models.CharField(max_length=64)), + ('event_type', models.CharField(max_length=64)), + ('payload', models.JSONField(blank=True, default=dict)), + ('message', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='events', to='messaging.message')), + ], + options={ + 'abstract': False, + }, + ), + ] diff --git a/site/messaging/migrations/0002_campaign_audience.py b/site/messaging/migrations/0002_campaign_audience.py new file mode 100644 index 0000000..225b492 --- /dev/null +++ b/site/messaging/migrations/0002_campaign_audience.py @@ -0,0 +1,18 @@ +# Generated by Django 6.1 on 2026-08-08 10:48 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('messaging', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='campaign', + name='audience', + field=models.CharField(blank=True, choices=[('email_opt_in', 'Mailing list · email opt-in'), ('sms_opt_in', 'Mailing list · SMS opt-in'), ('postcard_opt_in', 'Mailing list · postcard opt-in')], default='', max_length=32), + ), + ] diff --git a/site/messaging/migrations/0003_campaign_notify_sent_at.py b/site/messaging/migrations/0003_campaign_notify_sent_at.py new file mode 100644 index 0000000..a5db9fc --- /dev/null +++ b/site/messaging/migrations/0003_campaign_notify_sent_at.py @@ -0,0 +1,18 @@ +# Generated manually for Campaign.notify_sent_at + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("messaging", "0002_campaign_audience"), + ] + + operations = [ + migrations.AddField( + model_name="campaign", + name="notify_sent_at", + field=models.DateTimeField(blank=True, null=True), + ), + ] diff --git a/site/messaging/migrations/__init__.py b/site/messaging/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/site/messaging/models.py b/site/messaging/models.py new file mode 100644 index 0000000..fb9cd08 --- /dev/null +++ b/site/messaging/models.py @@ -0,0 +1,115 @@ +from django.conf import settings +from django.db import models + +from contacts.models import Channel, Contact +from core.models import TimeStampedModel, UUIDPrimaryKeyModel + + +class MessageTemplate(UUIDPrimaryKeyModel, TimeStampedModel): + name = models.CharField(max_length=120) + channel = models.CharField(max_length=16, choices=Channel.choices) + subject = models.CharField(max_length=255, blank=True) + body = models.TextField() + postcard_front = models.JSONField(default=dict, blank=True) + postcard_back = models.JSONField(default=dict, blank=True) + + def __str__(self) -> str: + return f"{self.name} ({self.channel})" + + +class Campaign(UUIDPrimaryKeyModel, TimeStampedModel): + class Status(models.TextChoices): + DRAFT = "draft", "Draft" + SCHEDULED = "scheduled", "Scheduled" + SENDING = "sending", "Sending" + COMPLETED = "completed", "Completed" + CANCELLED = "cancelled", "Cancelled" + + class Audience(models.TextChoices): + EMAIL_OPT_IN = "email_opt_in", "Mailing list · email opt-in" + SMS_OPT_IN = "sms_opt_in", "Mailing list · SMS opt-in" + POSTCARD_OPT_IN = "postcard_opt_in", "Mailing list · postcard opt-in" + + name = models.CharField(max_length=120) + channel = models.CharField(max_length=16, choices=Channel.choices) + audience = models.CharField( + max_length=32, + choices=Audience.choices, + blank=True, + default="", + ) + template = models.ForeignKey( + MessageTemplate, + null=True, + blank=True, + on_delete=models.SET_NULL, + related_name="campaigns", + ) + status = models.CharField( + max_length=16, choices=Status.choices, default=Status.DRAFT + ) + scheduled_for = models.DateTimeField(null=True, blank=True) + created_by = models.ForeignKey( + settings.AUTH_USER_MODEL, + null=True, + blank=True, + on_delete=models.SET_NULL, + ) + subject_override = models.CharField(max_length=255, blank=True) + body_override = models.TextField(blank=True) + # Set when realtor summary email is sent (campaign COMPLETED). + notify_sent_at = models.DateTimeField(null=True, blank=True) + + class Meta: + ordering = ["-created_at"] + + def __str__(self) -> str: + return self.name + + +class Message(UUIDPrimaryKeyModel, TimeStampedModel): + class Status(models.TextChoices): + DRAFT = "draft", "Draft" + SCHEDULED = "scheduled", "Scheduled" + QUEUED = "queued", "Queued" + SENT = "sent", "Sent" + DELIVERED = "delivered", "Delivered" + FAILED = "failed", "Failed" + BOUNCED = "bounced", "Bounced" + SUPPRESSED = "suppressed", "Suppressed" + + campaign = models.ForeignKey( + Campaign, on_delete=models.CASCADE, related_name="messages" + ) + contact = models.ForeignKey( + Contact, on_delete=models.CASCADE, related_name="messages" + ) + channel = models.CharField(max_length=16, choices=Channel.choices) + status = models.CharField( + max_length=16, choices=Status.choices, default=Status.DRAFT + ) + provider = models.CharField(max_length=64, blank=True) + provider_message_id = models.CharField(max_length=255, blank=True) + scheduled_for = models.DateTimeField(null=True, blank=True) + sent_at = models.DateTimeField(null=True, blank=True) + error = models.TextField(blank=True) + body_snapshot = models.TextField(blank=True) + + class Meta: + ordering = ["-created_at"] + + def __str__(self) -> str: + return f"{self.channel} → {self.contact} ({self.status})" + + +class ProviderEvent(TimeStampedModel): + message = models.ForeignKey( + Message, + null=True, + blank=True, + on_delete=models.SET_NULL, + related_name="events", + ) + provider = models.CharField(max_length=64) + event_type = models.CharField(max_length=64) + payload = models.JSONField(default=dict, blank=True) diff --git a/site/messaging/providers/__init__.py b/site/messaging/providers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/site/messaging/providers/email/__init__.py b/site/messaging/providers/email/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/site/messaging/providers/email/smtp2go.py b/site/messaging/providers/email/smtp2go.py new file mode 100644 index 0000000..3edd3b9 --- /dev/null +++ b/site/messaging/providers/email/smtp2go.py @@ -0,0 +1,50 @@ +"""SMTP2GO email via Django's SMTP backend (mail.smtp2go.com).""" + +from django.conf import settings +from django.core.mail import EmailMultiAlternatives + +from contacts.models import Channel +from messaging.services import one_click_unsubscribe_url, preferences_url + +# Reported back on SMTP2GO webhooks when this header is selected in webhook settings. +MONICA_MESSAGE_HEADER = "X-Monica-Message-Id" + + +def send_email(message) -> str: + contact = message.contact + if not contact.email: + raise ValueError("Contact has no email address") + + campaign = message.campaign + subject = campaign.subject_override or ( + campaign.template.subject if campaign.template else "Message from Monica" + ) + body = message.body_snapshot or campaign.body_override or ( + campaign.template.body if campaign.template else "" + ) + + site = (settings.PUBLIC_SITE_URL or "").rstrip("/") + prefs_path = preferences_url(str(contact.pk), Channel.EMAIL) + one_click_path = one_click_unsubscribe_url(str(contact.pk), Channel.EMAIL) + prefs_url = f"{site}{prefs_path}" if site else prefs_path + one_click_url = f"{site}{one_click_path}" if site else one_click_path + body_with_unsub = ( + f"{body}\n\n---\n" + f"Manage preferences: {prefs_url}\n" + f"Unsubscribe from email: {one_click_url}" + ) + + email = EmailMultiAlternatives( + subject=subject, + body=body_with_unsub, + from_email=settings.DEFAULT_FROM_EMAIL, + to=[contact.email], + headers={ + "List-Unsubscribe": f"<{one_click_url}>", + "List-Unsubscribe-Post": "List-Unsubscribe=One-Click", + MONICA_MESSAGE_HEADER: str(message.pk), + }, + ) + email.send(fail_silently=False) + # Placeholder until SMTP2GO webhook supplies the real email_id. + return f"smtp-{message.pk}" diff --git a/site/messaging/providers/postcard/__init__.py b/site/messaging/providers/postcard/__init__.py new file mode 100644 index 0000000..7a918d8 --- /dev/null +++ b/site/messaging/providers/postcard/__init__.py @@ -0,0 +1,42 @@ +"""Pluggable postcard providers.""" + +from dataclasses import dataclass +from typing import Protocol + +from django.conf import settings + + +@dataclass +class PostcardResult: + provider_id: str + + +class PostcardProvider(Protocol): + name: str + + def send_postcard(self, message) -> PostcardResult: ... + + def get_status(self, provider_id: str) -> str: ... + + +def get_postcard_provider() -> PostcardProvider: + name = (settings.POSTCARD_PROVIDER or "pcm").lower() + if name == "pcm": + from messaging.providers.postcard.pcm import PcmProvider + + return PcmProvider() + if name == "click2mail": + from messaging.providers.postcard.click2mail import Click2MailProvider + + return Click2MailProvider() + if name == "postgrid": + from messaging.providers.postcard.postgrid import PostGridProvider + + return PostGridProvider() + if name == "lob": + from messaging.providers.postcard.lob import LobProvider + + return LobProvider() + from messaging.providers.postcard.pcm import PcmProvider + + return PcmProvider() diff --git a/site/messaging/providers/postcard/click2mail.py b/site/messaging/providers/postcard/click2mail.py new file mode 100644 index 0000000..d3d1bd0 --- /dev/null +++ b/site/messaging/providers/postcard/click2mail.py @@ -0,0 +1,23 @@ +"""Click2Mail postcard adapter (low-volume pay-per-piece option).""" + +from dataclasses import dataclass + +from django.conf import settings + +from messaging.providers.postcard import PostcardResult + + +@dataclass +class Click2MailProvider: + name: str = "click2mail" + + def send_postcard(self, message) -> PostcardResult: + if not settings.CLICK2MAIL_API_KEY: + raise RuntimeError("CLICK2MAIL_API_KEY is not configured") + # Placeholder: wire full Click2Mail job API when account credentials are ready. + raise NotImplementedError( + "Click2Mail adapter stub — configure account then implement job submit" + ) + + def get_status(self, provider_id: str) -> str: + return "unknown" diff --git a/site/messaging/providers/postcard/lob.py b/site/messaging/providers/postcard/lob.py new file mode 100644 index 0000000..f42eacb --- /dev/null +++ b/site/messaging/providers/postcard/lob.py @@ -0,0 +1,66 @@ +"""Lob postcard adapter (default).""" + +from dataclasses import dataclass + +import requests +from django.conf import settings + +from messaging.providers.postcard import PostcardResult + + +@dataclass +class LobProvider: + name: str = "lob" + + def send_postcard(self, message) -> PostcardResult: + api_key = settings.LOB_API_KEY + if not api_key: + raise RuntimeError("LOB_API_KEY is not configured") + + contact = message.contact + address = contact.postal_address or {} + if not address.get("line1"): + raise ValueError("Contact postal_address.line1 required for postcard") + + # Minimal Lob create-postcard payload; artwork URLs come from template JSON. + template = message.campaign.template + front = (template.postcard_front if template else {}) or {} + back = (template.postcard_back if template else {}) or {} + + payload = { + "description": f"campaign-{message.campaign_id}", + "to": { + "name": contact.full_name or contact.email or "Resident", + "address_line1": address.get("line1", ""), + "address_line2": address.get("line2", ""), + "address_city": address.get("city", ""), + "address_state": address.get("state", ""), + "address_zip": address.get("zip", ""), + "address_country": address.get("country", "US"), + }, + "front": front.get("html") or front.get("url") or "", + "back": back.get("html") or back.get("url") or "", + } + response = requests.post( + "https://api.lob.com/v1/postcards", + json=payload, + auth=(api_key, ""), + timeout=60, + headers={"Idempotency-Key": str(message.pk)}, + ) + response.raise_for_status() + data = response.json() + return PostcardResult(provider_id=str(data.get("id") or message.pk)) + + def get_status(self, provider_id: str) -> str: + api_key = settings.LOB_API_KEY + if not api_key: + return "unknown" + response = requests.get( + f"https://api.lob.com/v1/postcards/{provider_id}", + auth=(api_key, ""), + timeout=30, + ) + if not response.ok: + return "unknown" + return str(response.json().get("status") or "unknown") diff --git a/site/messaging/providers/postcard/pcm.py b/site/messaging/providers/postcard/pcm.py new file mode 100644 index 0000000..f23044b --- /dev/null +++ b/site/messaging/providers/postcard/pcm.py @@ -0,0 +1,260 @@ +"""PCM Integrations (DirectMail API v3) postcard adapter.""" + +from __future__ import annotations + +import json +import logging +from dataclasses import dataclass +from typing import Any + +import requests +from django.conf import settings + +from messaging.providers.postcard import PostcardResult + +logger = logging.getLogger(__name__) + +PCM_API_BASE = "https://v3.pcmintegrations.com" + +# PCM size codes for custom designer designs. +PCM_SIZE_CHOICES = ( + ("46", "4.25 × 6"), + ("68", "6 × 8.5"), + ("69", "6 × 9"), + ("611", "6 × 11"), + ("811", "8.5 × 11"), +) + + +class PcmApiError(RuntimeError): + """Raised when a PCM API call fails.""" + + +def _api_key() -> str: + return (settings.PCM_API_KEY or "").strip() + + +def _headers() -> dict[str, str]: + key = _api_key() + if not key: + raise PcmApiError("PCM_API_KEY is not configured") + return { + "Accept": "application/json", + "Authorization": f"Bearer {key}", + "Content-Type": "application/json", + } + + +def pcm_request( + method: str, + path: str, + *, + params: dict[str, Any] | None = None, + json_body: dict[str, Any] | None = None, + timeout: int = 60, +) -> Any: + """Call PCM v3 API. ``path`` is absolute under the API host (e.g. ``/design``).""" + url = f"{PCM_API_BASE}{path}" + response = requests.request( + method, + url, + headers=_headers(), + params=params, + json=json_body, + timeout=timeout, + ) + if response.status_code >= 400: + detail = (response.text or "")[:500] + raise PcmApiError( + f"PCM {method} {path} → {response.status_code}: {detail}" + ) + if not response.content: + return {} + try: + return response.json() + except ValueError: + return {"raw": response.text} + + +def return_address_from_settings() -> dict[str, str]: + """Build PCM returnAddress from PCM_RETURN_ADDRESS JSON or CONTACT_* vars.""" + raw = (settings.PCM_RETURN_ADDRESS or "").strip() + if raw: + data = json.loads(raw) + if not isinstance(data, dict): + raise PcmApiError("PCM_RETURN_ADDRESS must be a JSON object") + return { + "company": str(data.get("company") or ""), + "firstName": str(data.get("firstName") or data.get("first_name") or ""), + "lastName": str(data.get("lastName") or data.get("last_name") or ""), + "address": str(data.get("address") or data.get("line1") or ""), + "address2": str(data.get("address2") or data.get("line2") or ""), + "city": str(data.get("city") or ""), + "state": str(data.get("state") or ""), + "zipCode": str(data.get("zipCode") or data.get("zip") or ""), + } + + name = (settings.SITE_NAME or "").strip() + parts = name.split(None, 1) + first = parts[0] if parts else "Monica" + last = parts[1] if len(parts) > 1 else "" + return { + "company": "", + "firstName": first, + "lastName": last, + "address": str(getattr(settings, "PCM_RETURN_LINE1", "") or ""), + "address2": str(getattr(settings, "PCM_RETURN_LINE2", "") or ""), + "city": str(getattr(settings, "PCM_RETURN_CITY", "") or ""), + "state": str(getattr(settings, "PCM_RETURN_STATE", "") or ""), + "zipCode": str(getattr(settings, "PCM_RETURN_ZIP", "") or ""), + } + + +def contact_to_pcm_recipient(contact, *, ext_ref: str) -> dict[str, str]: + address = contact.postal_address or {} + line1 = (address.get("line1") or "").strip() + if not line1: + raise ValueError("Contact postal_address.line1 required for postcard") + + first = (contact.first_name or "").strip() + last = (contact.last_name or "").strip() + if not first and not last: + # PCM requires name or company. + first = (contact.full_name or contact.email or "Resident").strip() + + return { + "firstName": first, + "lastName": last, + "address": line1, + "address2": (address.get("line2") or "").strip() or " ", + "city": (address.get("city") or "").strip(), + "state": (address.get("state") or "").strip(), + "zipCode": (address.get("zip") or "").strip(), + "extRefNbr": ext_ref, + } + + +def list_designs(*, product_type: str = "postcard", page: int = 1, per_page: int = 50) -> list[dict]: + data = pcm_request( + "GET", + "/design", + params={ + "productType": product_type, + "page": page, + "perPage": per_page, + }, + ) + if isinstance(data, dict): + results = data.get("results") or data.get("designs") or [] + return results if isinstance(results, list) else [] + return [] + + +def create_custom_design(*, name: str, size: str) -> dict[str, Any]: + """POST /design/custom → designID + embed url.""" + return pcm_request( + "POST", + "/design/custom", + json_body={"name": name, "size": size}, + ) + + +def get_design_embed_url(design_id: int | str, *, duplicate: bool = False) -> str: + """GET /design/{id}/edit?mode=embed → iframe URL.""" + params: dict[str, Any] = {"mode": "embed"} + if duplicate: + params["duplicate"] = "true" + data = pcm_request("GET", f"/design/{design_id}/edit", params=params) + if not isinstance(data, dict): + raise PcmApiError("Unexpected embed response from PCM") + url = data.get("embed_url") or data.get("url") or "" + if not url: + raise PcmApiError("PCM did not return an embed URL") + return str(url) + + +def get_order(order_id: int | str) -> dict[str, Any]: + data = pcm_request("GET", f"/order/{order_id}") + return data if isinstance(data, dict) else {} + + +def place_postcard_order( + *, + design_id: int, + recipient: dict[str, str], + ext_ref: str, + mail_class: str = "FirstClass", +) -> str: + """Place a one-recipient postcard order; return PCM orderID as string.""" + payload = { + "designID": design_id, + "mailClass": mail_class, + "extRefNbr": ext_ref, + "returnAddress": return_address_from_settings(), + "recipients": [recipient], + } + data = pcm_request("POST", "/order", json_body=payload) + if not isinstance(data, dict): + raise PcmApiError("Unexpected order response from PCM") + + order_id = data.get("orderID") or data.get("orderId") or data.get("id") + if order_id is None and isinstance(data.get("results"), list) and data["results"]: + order_id = data["results"][0].get("orderID") + if order_id is None: + raise PcmApiError(f"PCM order response missing orderID: {data!r}"[:400]) + return str(order_id) + + +def design_id_from_template(template) -> int | None: + """Read design_id from MessageTemplate.postcard_front JSON.""" + if not template: + return None + front = template.postcard_front or {} + if not isinstance(front, dict): + return None + raw = front.get("design_id") or front.get("designID") + if raw is None: + return None + try: + return int(raw) + except (TypeError, ValueError): + return None + + +@dataclass +class PcmProvider: + name: str = "pcm" + + def send_postcard(self, message) -> PostcardResult: + template = message.campaign.template if message.campaign_id else None + design_id = design_id_from_template(template) + if not design_id: + raise ValueError( + "Postcard campaign template missing PCM design_id " + "(save a design from the postcard designer first)" + ) + + recipient = contact_to_pcm_recipient( + message.contact, ext_ref=str(message.pk) + ) + mail_class = "FirstClass" + if template and isinstance(template.postcard_front, dict): + mail_class = ( + template.postcard_front.get("mail_class") or mail_class + ) + + order_id = place_postcard_order( + design_id=design_id, + recipient=recipient, + ext_ref=str(message.pk), + mail_class=str(mail_class), + ) + return PostcardResult(provider_id=order_id) + + def get_status(self, provider_id: str) -> str: + try: + data = get_order(provider_id) + except PcmApiError: + logger.exception("PCM get_status failed for %s", provider_id) + return "unknown" + return str(data.get("status") or "unknown") diff --git a/site/messaging/providers/postcard/postgrid.py b/site/messaging/providers/postcard/postgrid.py new file mode 100644 index 0000000..7464a4a --- /dev/null +++ b/site/messaging/providers/postcard/postgrid.py @@ -0,0 +1,22 @@ +"""PostGrid postcard adapter.""" + +from dataclasses import dataclass + +from django.conf import settings + +from messaging.providers.postcard import PostcardResult + + +@dataclass +class PostGridProvider: + name: str = "postgrid" + + def send_postcard(self, message) -> PostcardResult: + if not settings.POSTGRID_API_KEY: + raise RuntimeError("POSTGRID_API_KEY is not configured") + raise NotImplementedError( + "PostGrid adapter stub — configure account then implement send" + ) + + def get_status(self, provider_id: str) -> str: + return "unknown" diff --git a/site/messaging/providers/sms/__init__.py b/site/messaging/providers/sms/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/site/messaging/providers/sms/smtp2go.py b/site/messaging/providers/sms/smtp2go.py new file mode 100644 index 0000000..937f866 --- /dev/null +++ b/site/messaging/providers/sms/smtp2go.py @@ -0,0 +1,42 @@ +"""SMTP2GO SMS REST API.""" + +import logging + +import requests +from django.conf import settings + +logger = logging.getLogger(__name__) + + +def send_sms(message) -> str: + contact = message.contact + if not contact.phone: + raise ValueError("Contact has no phone number") + + api_key = settings.SMTP2GO_SMS_API_KEY + if not api_key: + raise RuntimeError("SMTP2GO_SMS_API_KEY is not configured") + + campaign = message.campaign + body = message.body_snapshot or campaign.body_override or ( + campaign.template.body if campaign.template else "" + ) + + payload = { + "api_key": api_key, + "to": contact.phone, + "text": body[:1600], + } + response = requests.post( + settings.SMTP2GO_SMS_API_URL, + json=payload, + timeout=30, + ) + response.raise_for_status() + data = response.json() if response.content else {} + # SMTP2GO returns varying shapes; store a useful id when present. + return str( + data.get("data", {}).get("sms_id") + or data.get("request_id") + or f"sms-{message.pk}" + ) diff --git a/site/messaging/services.py b/site/messaging/services.py new file mode 100644 index 0000000..6b60428 --- /dev/null +++ b/site/messaging/services.py @@ -0,0 +1,404 @@ +"""Consent checks and unsubscribe helpers.""" + +from __future__ import annotations + +from datetime import datetime +from typing import TYPE_CHECKING + +from django.core import signing +from django.db.models import QuerySet +from django.urls import reverse +from django.utils import timezone + +from contacts.models import Channel, ConsentRecord, Contact, Suppression +from messaging.models import Campaign, Message, MessageTemplate + +if TYPE_CHECKING: + from django.contrib.auth.models import AbstractBaseUser + +AUDIENCE_CHANNEL = { + Campaign.Audience.EMAIL_OPT_IN: Channel.EMAIL, + Campaign.Audience.SMS_OPT_IN: Channel.SMS, + Campaign.Audience.POSTCARD_OPT_IN: Channel.POSTCARD, +} + +UNSUB_SALT = "monica-site-unsubscribe" +UNSUB_MAX_AGE = 60 * 60 * 24 * 365 # 1 year + + +def contact_may_receive(contact: Contact, channel: str) -> bool: + if Suppression.objects.filter( + contact=contact, channel=channel, active=True + ).exists(): + return False + consent = ConsentRecord.objects.filter(contact=contact, channel=channel).first() + return bool(consent and consent.opted_in) + + +def channel_preferences(contact: Contact) -> dict[str, bool]: + """Current opt-in flags for every channel (missing record = False).""" + flags = {c.value: False for c in Channel} + for record in contact.consents.all(): + flags[record.channel] = record.opted_in + return flags + + +def set_channel_consent( + contact: Contact, + channel: str, + *, + opted_in: bool, + reason: str = "", +) -> None: + """Write ConsentRecord + Suppression for one channel.""" + if channel not in Channel.values: + raise ValueError(f"Unknown channel: {channel}") + ConsentRecord.objects.update_or_create( + contact=contact, + channel=channel, + defaults={"opted_in": opted_in, "reason": reason}, + ) + Suppression.objects.update_or_create( + contact=contact, + channel=channel, + defaults={ + "active": not opted_in, + "reason": reason if not opted_in else "", + }, + ) + + +def set_channel_preferences( + contact: Contact, + preferences: dict[str, bool], + *, + reason: str = "", +) -> None: + """Update consent for each provided channel key.""" + for channel, opted_in in preferences.items(): + if channel not in Channel.values: + continue + set_channel_consent( + contact, channel, opted_in=bool(opted_in), reason=reason + ) + + +def unsubscribe_all(contact: Contact, *, reason: str = "unsubscribe_all") -> None: + for channel in Channel: + set_channel_consent( + contact, channel.value, opted_in=False, reason=reason + ) + + +def make_unsubscribe_token(contact_id: str, channel: str = Channel.EMAIL) -> str: + return signing.dumps({"c": str(contact_id), "ch": channel}, salt=UNSUB_SALT) + + +def parse_unsubscribe_token(token: str) -> tuple[Contact | None, str]: + """Return (contact, channel) or (None, '') on bad/expired token.""" + try: + data = signing.loads(token, salt=UNSUB_SALT, max_age=UNSUB_MAX_AGE) + except signing.BadSignature: + return None, "" + contact = ( + Contact.objects.filter(pk=data.get("c")) + .prefetch_related("consents") + .first() + ) + if not contact: + return None, "" + channel = data.get("ch") or Channel.EMAIL + if channel not in Channel.values: + channel = Channel.EMAIL + return contact, channel + + +def process_unsubscribe_token(token: str) -> bool: + """One-click opt-out for the channel encoded in the token.""" + contact, channel = parse_unsubscribe_token(token) + if not contact: + return False + set_channel_consent( + contact, channel, opted_in=False, reason="unsubscribe_link" + ) + return True + + +def preferences_url(contact_id: str, channel: str = Channel.EMAIL) -> str: + token = make_unsubscribe_token(contact_id, channel) + return reverse("public:unsubscribe", kwargs={"token": token}) + + +def one_click_unsubscribe_url(contact_id: str, channel: str = Channel.EMAIL) -> str: + token = make_unsubscribe_token(contact_id, channel) + return reverse("public:unsubscribe_one_click", kwargs={"token": token}) + + +def record_sms_stop(phone: str) -> bool: + digits = "".join(ch for ch in (phone or "") if ch.isdigit()) + if len(digits) < 7: + return False + tail = digits[-10:] + contact = None + for row in Contact.objects.exclude(phone="").iterator(): + stored = "".join(ch for ch in row.phone if ch.isdigit()) + if stored.endswith(tail) or tail.endswith(stored[-10:]): + contact = row + break + if not contact: + return False + set_channel_consent( + contact, Channel.SMS, opted_in=False, reason="sms_stop" + ) + return True + + +def channel_for_audience(audience: str) -> str: + try: + return AUDIENCE_CHANNEL[audience] + except KeyError as exc: + raise ValueError(f"Unknown audience: {audience}") from exc + + +def opted_in_contacts(channel: str) -> QuerySet[Contact]: + """Contacts opted in for channel and not actively suppressed.""" + suppressed = Suppression.objects.filter( + channel=channel, active=True + ).values_list("contact_id", flat=True) + qs = ( + Contact.objects.filter( + consents__channel=channel, + consents__opted_in=True, + ) + .exclude(pk__in=suppressed) + .distinct() + .order_by("first_name", "last_name", "email") + ) + if channel == Channel.POSTCARD: + qs = qs.filter(postal_address__has_key="line1").exclude( + postal_address__line1="" + ) + return qs + + +def parse_scheduled_for(raw: str | None): + """Parse optional ``datetime-local`` value into an aware datetime.""" + value = (raw or "").strip() + if not value: + return None + try: + parsed = datetime.fromisoformat(value) + except ValueError as exc: + raise ValueError("Invalid schedule datetime.") from exc + if timezone.is_naive(parsed): + return timezone.make_aware(parsed, timezone.get_current_timezone()) + return parsed + + +def create_campaign_draft( + *, + name: str, + audience: str, + subject: str = "", + body: str = "", + scheduled_for=None, + created_by: AbstractBaseUser | None = None, + template: MessageTemplate | None = None, +) -> Campaign: + """Persist a draft campaign and per-recipient Message stubs.""" + channel = channel_for_audience(audience) + campaign = Campaign.objects.create( + name=name, + channel=channel, + audience=audience, + status=Campaign.Status.DRAFT, + scheduled_for=scheduled_for, + subject_override=subject, + body_override=body, + created_by=created_by, + template=template, + ) + contacts = list(opted_in_contacts(channel)) + Message.objects.bulk_create( + [ + Message( + campaign=campaign, + contact=contact, + channel=channel, + status=Message.Status.DRAFT, + scheduled_for=scheduled_for, + body_snapshot=body, + ) + for contact in contacts + ] + ) + return campaign + + +def campaign_notify_recipient(campaign: Campaign) -> str: + """Email address for the realtor summary (created_by, else CONTACT_EMAIL).""" + from django.conf import settings + + user = campaign.created_by + if user is not None: + email = (getattr(user, "email", None) or "").strip() + if email: + return email + return (settings.CONTACT_EMAIL or "").strip() + + +def send_campaign_completion_notify(campaign: Campaign) -> bool: + """ + One-shot summary email when a campaign finishes sending. + + Returns True if mail was sent (or already sent earlier). + """ + from django.conf import settings + from django.core.mail import EmailMultiAlternatives + from django.db.models import Count, Q + from django.urls import reverse + + if campaign.notify_sent_at: + return True + if campaign.status != Campaign.Status.COMPLETED: + return False + + to_email = campaign_notify_recipient(campaign) + if not to_email: + return False + + counts = campaign.messages.aggregate( + sent=Count("id", filter=Q(status=Message.Status.SENT)), + delivered=Count("id", filter=Q(status=Message.Status.DELIVERED)), + failed=Count( + "id", + filter=Q( + status__in=[ + Message.Status.FAILED, + Message.Status.BOUNCED, + ] + ), + ), + suppressed=Count("id", filter=Q(status=Message.Status.SUPPRESSED)), + total=Count("id"), + ) + report_path = reverse("messaging:campaign_detail", kwargs={"pk": campaign.pk}) + public = (settings.PUBLIC_SITE_URL or "").rstrip("/") + report_url = f"{public}{report_path}" if public else report_path + + subject = f"Campaign sent: {campaign.name}" + body = ( + f"Your {campaign.get_channel_display()} campaign “{campaign.name}” " + f"has finished sending.\n\n" + f"Recipients: {counts['total']}\n" + f"Sent: {counts['sent']}\n" + f"Delivered: {counts['delivered']}\n" + f"Failed / bounced: {counts['failed']}\n" + f"Suppressed: {counts['suppressed']}\n\n" + f"Report: {report_url}\n" + ) + email = EmailMultiAlternatives( + subject=subject, + body=body, + from_email=settings.DEFAULT_FROM_EMAIL, + to=[to_email], + ) + try: + email.send(fail_silently=False) + except Exception: # noqa: BLE001 — don't block completion on mail errors + import logging + + logging.getLogger(__name__).exception( + "Campaign completion notify failed for %s", campaign.pk + ) + return False + + campaign.notify_sent_at = timezone.now() + campaign.save(update_fields=["notify_sent_at", "updated_at"]) + return True + + +def refresh_campaign_status(campaign: Campaign) -> Campaign: + """Set campaign to completed when no messages remain pending.""" + pending = campaign.messages.filter( + status__in=[ + Message.Status.DRAFT, + Message.Status.SCHEDULED, + Message.Status.QUEUED, + ] + ).exists() + if pending: + return campaign + if campaign.status == Campaign.Status.SENDING: + campaign.status = Campaign.Status.COMPLETED + campaign.save(update_fields=["status", "updated_at"]) + send_campaign_completion_notify(campaign) + return campaign + + +def enqueue_campaign_send(campaign: Campaign) -> int: + """ + Queue draft/scheduled/failed messages for send. + + Dev uses ImmediateBackend → each enqueue runs inline via SMTP/console. + """ + from messaging.tasks import send_campaign_message + + sendable = list( + campaign.messages.filter( + status__in=[ + Message.Status.DRAFT, + Message.Status.SCHEDULED, + Message.Status.FAILED, + ] + ) + ) + if not sendable: + return 0 + + campaign.status = Campaign.Status.SENDING + campaign.save(update_fields=["status", "updated_at"]) + + enqueued = 0 + for message in sendable: + message.status = Message.Status.QUEUED + message.save(update_fields=["status", "updated_at"]) + try: + send_campaign_message.enqueue(message_id=str(message.pk)) + except Exception: # noqa: BLE001 — task already persisted FAILED + pass + enqueued += 1 + + refresh_campaign_status(campaign) + return enqueued + + +def send_campaign_test_email(campaign: Campaign, to_email: str) -> None: + """Send one preview copy to ``to_email`` without touching recipient rows.""" + from django.conf import settings + from django.core.mail import EmailMultiAlternatives + + if campaign.channel != Channel.EMAIL: + raise ValueError("Test send is only available for email campaigns.") + subject = campaign.subject_override or ( + campaign.template.subject if campaign.template_id else "Message from Monica" + ) + body = campaign.body_override or ( + campaign.template.body if campaign.template_id else "" + ) + if not subject.strip(): + raise ValueError("Campaign has no subject.") + if not body.strip(): + raise ValueError("Campaign has no body.") + + email = EmailMultiAlternatives( + subject=f"[TEST] {subject}", + body=( + f"{body}\n\n---\n" + "This is a test send from the Monica portal. " + "Recipient list was not notified." + ), + from_email=settings.DEFAULT_FROM_EMAIL, + to=[to_email], + ) + email.send(fail_silently=False) \ No newline at end of file diff --git a/site/messaging/tasks.py b/site/messaging/tasks.py new file mode 100644 index 0000000..15206ba --- /dev/null +++ b/site/messaging/tasks.py @@ -0,0 +1,45 @@ +from django.tasks import task +from django.utils import timezone + +from messaging.channels import dispatch_message +from messaging.models import Message +from messaging.services import contact_may_receive + + +@task +def send_campaign_message(message_id: str) -> None: + try: + message = Message.objects.select_related("contact", "campaign").get( + pk=message_id + ) + except Message.DoesNotExist: + return + + if not contact_may_receive(message.contact, message.channel): + message.status = Message.Status.SUPPRESSED + message.error = "Contact opted out or suppressed" + message.save(update_fields=["status", "error", "updated_at"]) + return + + try: + result = dispatch_message(message) + message.status = Message.Status.SENT + message.provider = result.provider + message.provider_message_id = result.provider_id + message.sent_at = timezone.now() + message.error = "" + message.save( + update_fields=[ + "status", + "provider", + "provider_message_id", + "sent_at", + "error", + "updated_at", + ] + ) + except Exception as exc: # noqa: BLE001 — persist provider failures + message.status = Message.Status.FAILED + message.error = str(exc)[:2000] + message.save(update_fields=["status", "error", "updated_at"]) + raise diff --git a/site/messaging/templates/messaging/campaign_detail.html b/site/messaging/templates/messaging/campaign_detail.html new file mode 100644 index 0000000..9575e8c --- /dev/null +++ b/site/messaging/templates/messaging/campaign_detail.html @@ -0,0 +1,199 @@ +{% extends "portal_base.html" %} +{% block title %}{{ campaign.name }} · Campaign{% endblock %} +{% block topbar_title %}{{ campaign.name }}{% endblock %} +{% block portal_content %} +
+
+ {{ campaign.get_status_display }} + {{ campaign.get_channel_display }} + {% if campaign.scheduled_for %} + Scheduled {{ campaign.scheduled_for|date:"M j, g:i A" }} + {% endif %} + Live · updates every 10s +
+ ← Campaigns +
+ +{% if campaign.channel == "email" or can_send %} +
+

Send

+
+ {% if campaign.channel == "email" %} +
+ {% csrf_token %} +
+ + +
Sends one [TEST] copy. Does not notify the recipient list.
+
+
+ +
+
+ {% endif %} + {% if can_send %} +
+ {% csrf_token %} + +

+ Enqueues draft / scheduled / failed messages via SMTP2GO (dev ImmediateBackend runs inline). +

+
+ {% endif %} +
+
+{% endif %} + +
+
+
Messages
+
{{ stats.total }}
+
+
+
Sent
+
{{ stats.sent }}
+
+
+
Delivered
+
{{ stats.delivered }}
+
+
+
Opens
+
{{ stats.opens }}
+
+
+
Clicks
+
{{ stats.clicks }}
+
+
+
Bounced / failed
+
{{ stats.failed }}
+
+
+
Suppressed
+
{{ stats.suppressed }}
+
+
+ +
+
+

Engagement

+
+

+ Unique recipients: {{ stats.opens }} opened · + {{ stats.clicks }} clicked + ({{ stats.open_events }} open events / {{ stats.click_events }} click events from SMTP2GO). +

+
+
+
+

Recent SMTP2GO events

+
+ + + + {% for event in recent_events %} + + + + + + {% empty %} + + {% endfor %} + +
WhenEventContact
{{ event.created_at|date:"M j, g:i A" }}{{ event.event_type }}{% if event.message %}{{ event.message.contact }}{% else %}—{% endif %}
No provider events yet. Configure the SMTP2GO webhook after first send.
+
+
+
+ +
+

Recipients

+
+ + + + + + {% for message in messages %} + + + + + + + {% empty %} + + {% endfor %} + +
ContactStatusProvider idError
{{ message.contact }}{{ message.get_status_display }}{{ message.provider_message_id|default:"—" }}{{ message.error|truncatechars:60|default:"—" }}
No messages on this campaign.
+
+
+{% endblock %} +{% block extra_js %} + +{% endblock %} diff --git a/site/messaging/templates/messaging/campaign_list.html b/site/messaging/templates/messaging/campaign_list.html new file mode 100644 index 0000000..2a575ff --- /dev/null +++ b/site/messaging/templates/messaging/campaign_list.html @@ -0,0 +1,158 @@ +{% extends "portal_base.html" %} +{% block title %}Campaigns · Portal{% endblock %} +{% block topbar_title %}Campaign composer{% endblock %} +{% block portal_content %} +
+ Email + SMS + Postcard +
+ +
+
+

Compose

+
+ {% if form_errors %} +
    + {% for err in form_errors %} +
  • {{ err }}
  • + {% endfor %} +
+ {% endif %} +
+ {% csrf_token %} +
+ + +
+
+ + +
Email only — ignored for SMS / postcard
+
+
+ + +
Merge tags: first_name, last_name, unsubscribe_url · optional for postcard
+
+
+ + + +
+
+
+ + +
+
+ + +
Date & time · leave blank to keep as unscheduled draft
+
+
+

Saves a draft campaign and recipient stubs. Send from the campaign report when ready. You’ll get an email when the send finishes.

+ +
+
+
+
+

Preview

+
+
+
Preview updates as you type.
+ +
+

After send → open the campaign report for delivery & engagement.

+
+
+
+ +
+

Recent campaigns

+
+ + + + + + + + + + + + {% for campaign in campaigns %} + + + + + + + + {% empty %} + + {% endfor %} + +
NameChannelStatusScheduled
{{ campaign.name }}{{ campaign.get_channel_display }}{{ campaign.get_status_display }}{% if campaign.scheduled_for %}{{ campaign.scheduled_for|date:"M j, g:i A" }}{% else %}—{% endif %}Report
No campaigns yet.
+
+
+{% endblock %} +{% block extra_js %} + +{% endblock %} diff --git a/site/messaging/templates/messaging/postcard_designer.html b/site/messaging/templates/messaging/postcard_designer.html new file mode 100644 index 0000000..59eda7f --- /dev/null +++ b/site/messaging/templates/messaging/postcard_designer.html @@ -0,0 +1,134 @@ +{% extends "portal_base.html" %} +{% block title %}Postcard designer · Portal{% endblock %} +{% block topbar_title %}Postcard designer{% endblock %} +{% block portal_content %} +{% if api_error %} +
    +
  • {{ api_error }}
  • +
+{% endif %} + +
+
+
+

New design

+
+
+ {% csrf_token %} +
+ + +
+
+ + +
+ +
+

+ Opens PCM Integrations editor in the frame. Artwork stays on their side; + we store the design id for campaigns. +

+
+
+ +
+

Your designs

+
+ + + + + + + + + + {% for d in designs %} + + + + + + {% empty %} + + {% endfor %} + +
NameID
{{ d.name }}{{ d.design_id }} + Edit +
No designs yet — create one above.
+
+
+ + {% if active_design_id %} +
+

Save for campaigns

+
+
+ {% csrf_token %} + + +
+ + +
+ +
+

+ Saved templates appear when composing a postcard campaign. +

+
+
+ {% endif %} + + {% if saved_templates %} +
+

Saved templates

+
+ + + + + + {% for t in saved_templates %} + + + + + + {% endfor %} + +
NameDesign
{{ t.name }}{{ t.postcard_front.design_id }} + Open +
+
+
+ {% endif %} +
+ +
+
+

{% if active_design_id %}PCM editor · design {{ active_design_id }}{% else %}Editor{% endif %}

+
+
+ {% if embed_url %} + + {% else %} +
+ Create a design or pick one from the list to open the PCM editor here. +
+ {% endif %} +
+
+
+{% endblock %} diff --git a/site/messaging/tests.py b/site/messaging/tests.py new file mode 100644 index 0000000..439734a --- /dev/null +++ b/site/messaging/tests.py @@ -0,0 +1,629 @@ +from django.contrib.auth import get_user_model +from django.test import Client, TestCase +from django.urls import reverse + +from contacts.models import Channel, ConsentRecord, Contact, Suppression +from messaging.models import Campaign, Message, MessageTemplate, ProviderEvent +from messaging.services import ( + contact_may_receive, + create_campaign_draft, + make_unsubscribe_token, + set_channel_consent, +) + + +class PreferenceCenterTests(TestCase): + def setUp(self): + self.client = Client() + self.contact = Contact.objects.create( + email="jordan.lee@example.com", + phone="5550184420", + first_name="Jordan", + last_name="Lee", + ) + for channel in Channel: + set_channel_consent( + self.contact, channel.value, opted_in=True, reason="test_seed" + ) + self.token = make_unsubscribe_token(str(self.contact.pk), Channel.EMAIL) + + def test_preferences_get_shows_form(self): + url = reverse("public:unsubscribe", kwargs={"token": self.token}) + response = self.client.get(url) + self.assertEqual(response.status_code, 200) + self.assertContains(response, "Email marketing") + self.assertContains(response, "SMS updates") + self.assertContains(response, "Postcard mailings") + self.assertTrue(contact_may_receive(self.contact, Channel.EMAIL)) + + def test_invalid_token(self): + url = reverse("public:unsubscribe", kwargs={"token": "not-a-valid-token"}) + response = self.client.get(url) + self.assertEqual(response.status_code, 200) + self.assertContains(response, "invalid or has expired") + + def test_one_click_query_opts_out_email_only(self): + url = reverse("public:unsubscribe", kwargs={"token": self.token}) + response = self.client.get(f"{url}?one_click=1") + self.assertEqual(response.status_code, 302) + self.contact.refresh_from_db() + self.assertFalse(contact_may_receive(self.contact, Channel.EMAIL)) + self.assertTrue(contact_may_receive(self.contact, Channel.SMS)) + self.assertTrue(contact_may_receive(self.contact, Channel.POSTCARD)) + + def test_one_click_endpoint(self): + url = reverse( + "public:unsubscribe_one_click", kwargs={"token": self.token} + ) + response = self.client.get(url) + self.assertEqual(response.status_code, 302) + self.assertFalse(contact_may_receive(self.contact, Channel.EMAIL)) + + def test_one_click_post_rfc8058(self): + url = reverse( + "public:unsubscribe_one_click", kwargs={"token": self.token} + ) + response = self.client.post( + url, {"List-Unsubscribe": "One-Click"} + ) + self.assertEqual(response.status_code, 302) + self.assertFalse(contact_may_receive(self.contact, Channel.EMAIL)) + + def test_save_preferences_partial_opt_out(self): + url = reverse("public:unsubscribe", kwargs={"token": self.token}) + response = self.client.post( + url, + { + "action": "save", + "consent_email": "1", + # SMS unchecked + "consent_postcard": "1", + }, + ) + self.assertEqual(response.status_code, 302) + self.assertTrue(contact_may_receive(self.contact, Channel.EMAIL)) + self.assertFalse(contact_may_receive(self.contact, Channel.SMS)) + self.assertTrue(contact_may_receive(self.contact, Channel.POSTCARD)) + sms_sup = Suppression.objects.get( + contact=self.contact, channel=Channel.SMS + ) + self.assertTrue(sms_sup.active) + + def test_unsubscribe_all(self): + url = reverse("public:unsubscribe", kwargs={"token": self.token}) + response = self.client.post(url, {"action": "unsubscribe_all"}) + self.assertEqual(response.status_code, 302) + for channel in Channel: + self.assertFalse(contact_may_receive(self.contact, channel.value)) + + +class SmsStopWebhookTests(TestCase): + def setUp(self): + self.client = Client() + self.contact = Contact.objects.create( + email="avery@example.com", + phone="5550142291", + ) + set_channel_consent( + self.contact, Channel.SMS, opted_in=True, reason="test" + ) + + def test_stop_webhook(self): + url = reverse("messaging:sms_webhook") + response = self.client.post( + url, {"from": "5550142291", "text": "STOP"} + ) + self.assertEqual(response.status_code, 200) + self.assertFalse(contact_may_receive(self.contact, Channel.SMS)) + + +class PortalConsentToggleTests(TestCase): + def setUp(self): + User = get_user_model() + self.user = User.objects.create_user( + username="monica", password="test-pass-123" + ) + self.client = Client() + self.client.login(username="monica", password="test-pass-123") + self.contact = Contact.objects.create( + email="sam@example.com", + first_name="Sam", + ) + set_channel_consent( + self.contact, Channel.EMAIL, opted_in=True, reason="test" + ) + + def test_portal_can_toggle_consent(self): + url = reverse("contacts:detail", kwargs={"pk": self.contact.pk}) + response = self.client.post( + url, + { + "notes": "updated", + "consent_sms": "1", + "consent_postcard": "1", + # email unchecked → opt out + }, + ) + self.assertEqual(response.status_code, 302) + self.assertFalse(contact_may_receive(self.contact, Channel.EMAIL)) + self.assertTrue(contact_may_receive(self.contact, Channel.SMS)) + self.assertTrue(contact_may_receive(self.contact, Channel.POSTCARD)) + email_consent = ConsentRecord.objects.get( + contact=self.contact, channel=Channel.EMAIL + ) + self.assertEqual(email_consent.reason, "portal_manual") + + +class CampaignDraftSaveTests(TestCase): + def setUp(self): + User = get_user_model() + self.user = User.objects.create_user( + username="composer", password="test-pass-123" + ) + self.client = Client() + self.client.login(username="composer", password="test-pass-123") + self.contact = Contact.objects.create( + email="pat@example.com", + first_name="Pat", + ) + set_channel_consent( + self.contact, Channel.EMAIL, opted_in=True, reason="test" + ) + + def test_save_draft_creates_campaign_and_messages(self): + url = reverse("messaging:campaign_list") + response = self.client.post( + url, + { + "name": "Spring tips", + "subject": "Hello sellers", + "body": "Hi {first_name}", + "audience": Campaign.Audience.EMAIL_OPT_IN, + "scheduled_for": "2026-08-10T09:30", + }, + ) + campaign = Campaign.objects.get(name="Spring tips") + self.assertEqual(response.status_code, 302) + self.assertEqual( + response.url, + reverse("messaging:campaign_detail", kwargs={"pk": campaign.pk}), + ) + self.assertEqual(campaign.status, Campaign.Status.DRAFT) + self.assertEqual(campaign.channel, Channel.EMAIL) + self.assertEqual(campaign.audience, Campaign.Audience.EMAIL_OPT_IN) + self.assertIsNotNone(campaign.scheduled_for) + self.assertEqual(campaign.messages.count(), 1) + msg = campaign.messages.get() + self.assertEqual(msg.contact, self.contact) + self.assertEqual(msg.status, Message.Status.DRAFT) + + def test_save_draft_requires_subject_for_email(self): + url = reverse("messaging:campaign_list") + response = self.client.post( + url, + { + "name": "No subject", + "subject": "", + "body": "Body only", + "audience": Campaign.Audience.EMAIL_OPT_IN, + }, + ) + self.assertEqual(response.status_code, 200) + self.assertContains(response, "Subject is required") + self.assertFalse(Campaign.objects.filter(name="No subject").exists()) + + def test_create_campaign_draft_helper(self): + campaign = create_campaign_draft( + name="Helper draft", + audience=Campaign.Audience.EMAIL_OPT_IN, + subject="Subj", + body="Body", + created_by=self.user, + ) + self.assertEqual(campaign.messages.count(), 1) + self.assertEqual(campaign.created_by, self.user) + + +class CampaignSendTests(TestCase): + def setUp(self): + User = get_user_model() + self.user = User.objects.create_user( + username="sender", password="test-pass-123", email="sender@example.com" + ) + self.client = Client() + self.client.login(username="sender", password="test-pass-123") + self.contact = Contact.objects.create( + email="pat@example.com", + first_name="Pat", + ) + set_channel_consent( + self.contact, Channel.EMAIL, opted_in=True, reason="test" + ) + self.campaign = create_campaign_draft( + name="Send me", + audience=Campaign.Audience.EMAIL_OPT_IN, + subject="Hello", + body="Body text", + created_by=self.user, + ) + + def test_detail_shows_send_controls(self): + url = reverse( + "messaging:campaign_detail", kwargs={"pk": self.campaign.pk} + ) + response = self.client.get(url) + self.assertEqual(response.status_code, 200) + self.assertContains(response, "Send test email") + self.assertContains(response, "Send now to recipients") + + def test_test_send_uses_locmem(self): + from django.core import mail + + url = reverse( + "messaging:campaign_test_send", kwargs={"pk": self.campaign.pk} + ) + with self.settings( + EMAIL_BACKEND="django.core.mail.backends.locmem.EmailBackend", + DEFAULT_FROM_EMAIL="noreply@example.com", + ): + response = self.client.post(url, {"test_email": "me@example.com"}) + self.assertEqual(response.status_code, 302) + self.assertEqual(len(mail.outbox), 1) + self.assertEqual(mail.outbox[0].to, ["me@example.com"]) + self.assertTrue(mail.outbox[0].subject.startswith("[TEST]")) + # Recipients untouched + self.assertEqual( + self.campaign.messages.filter(status=Message.Status.DRAFT).count(), 1 + ) + + def test_send_now_marks_messages_sent(self): + from django.core import mail + + url = reverse( + "messaging:campaign_send", kwargs={"pk": self.campaign.pk} + ) + with self.settings( + EMAIL_BACKEND="django.core.mail.backends.locmem.EmailBackend", + DEFAULT_FROM_EMAIL="noreply@example.com", + PUBLIC_SITE_URL="http://testserver", + ): + response = self.client.post(url) + self.assertEqual(response.status_code, 302) + self.campaign.refresh_from_db() + self.assertEqual(self.campaign.status, Campaign.Status.COMPLETED) + self.assertEqual( + self.campaign.messages.filter(status=Message.Status.SENT).count(), 1 + ) + # Recipient campaign email + one realtor completion summary. + self.assertEqual(len(mail.outbox), 2) + self.assertIsNotNone(self.campaign.notify_sent_at) + summary = mail.outbox[1] + self.assertIn("Campaign sent:", summary.subject) + self.assertEqual(summary.to, [self.user.email]) + + +class Smtp2goEmailWebhookTests(TestCase): + def setUp(self): + self.client = Client() + self.contact = Contact.objects.create( + email="pat@example.com", + first_name="Pat", + ) + set_channel_consent( + self.contact, Channel.EMAIL, opted_in=True, reason="test" + ) + self.campaign = create_campaign_draft( + name="Webhook campaign", + audience=Campaign.Audience.EMAIL_OPT_IN, + subject="Hello", + body="Body", + ) + self.message = self.campaign.messages.get() + self.message.status = Message.Status.SENT + self.message.provider_message_id = f"smtp-{self.message.pk}" + self.message.save() + + def test_delivered_updates_status_via_monica_header(self): + url = reverse("messaging:email_webhook") + response = self.client.post( + url, + data={ + "event": "delivered", + "rcpt": "pat@example.com", + "email_id": "smtp2go-abc-123", + "X-Monica-Message-Id": str(self.message.pk), + }, + ) + self.assertEqual(response.status_code, 200) + self.message.refresh_from_db() + self.assertEqual(self.message.status, Message.Status.DELIVERED) + self.assertEqual(self.message.provider_message_id, "smtp2go-abc-123") + self.assertTrue( + ProviderEvent.objects.filter( + message=self.message, event_type="delivered" + ).exists() + ) + + def test_open_records_event_keeps_delivered(self): + self.message.status = Message.Status.DELIVERED + self.message.save(update_fields=["status"]) + url = reverse("messaging:email_webhook") + self.client.post( + url, + data={ + "event": "open", + "rcpt": "pat@example.com", + "X-Monica-Message-Id": str(self.message.pk), + }, + ) + self.message.refresh_from_db() + self.assertEqual(self.message.status, Message.Status.DELIVERED) + self.assertTrue( + ProviderEvent.objects.filter( + message=self.message, event_type="open" + ).exists() + ) + + def test_hard_bounce_suppresses_contact(self): + url = reverse("messaging:email_webhook") + self.client.post( + url, + data={ + "event": "bounce", + "bounce": "hard", + "rcpt": "pat@example.com", + "message": "550 user unknown", + "X-Monica-Message-Id": str(self.message.pk), + }, + ) + self.message.refresh_from_db() + self.assertEqual(self.message.status, Message.Status.BOUNCED) + self.assertFalse(contact_may_receive(self.contact, Channel.EMAIL)) + + def test_webhook_secret_required_when_configured(self): + url = reverse("messaging:email_webhook") + with self.settings(SMTP2GO_WEBHOOK_SECRET="s3cret"): + denied = self.client.post(url, data={"event": "delivered"}) + self.assertEqual(denied.status_code, 403) + ok = self.client.post( + f"{url}?token=s3cret", + data={ + "event": "delivered", + "X-Monica-Message-Id": str(self.message.pk), + }, + ) + self.assertEqual(ok.status_code, 200) + + def test_status_json_includes_opens(self): + User = get_user_model() + user = User.objects.create_user(username="viewer", password="test-pass-123") + self.client.login(username="viewer", password="test-pass-123") + ProviderEvent.objects.create( + message=self.message, + provider="smtp2go_email", + event_type="open", + payload={"event": "open"}, + ) + url = reverse( + "messaging:campaign_status_json", kwargs={"pk": self.campaign.pk} + ) + response = self.client.get(url) + self.assertEqual(response.status_code, 200) + data = response.json() + self.assertEqual(data["stats"]["opens"], 1) + + def test_bearer_authorization_header(self): + import json + + url = reverse("messaging:email_webhook") + with self.settings(SMTP2GO_WEBHOOK_SECRET="s3cret"): + denied = self.client.post( + url, + data=json.dumps( + { + "event": "delivered", + "X-Monica-Message-Id": str(self.message.pk), + } + ), + content_type="application/json", + HTTP_AUTHORIZATION="Bearer wrong", + ) + self.assertEqual(denied.status_code, 403) + ok = self.client.post( + url, + data=json.dumps( + { + "event": "delivered", + "X-Monica-Message-Id": str(self.message.pk), + "email_id": "e-1", + } + ), + content_type="application/json", + HTTP_AUTHORIZATION="Bearer s3cret", + ) + self.assertEqual(ok.status_code, 200) + + +class Smtp2goSmsWebhookTests(TestCase): + def setUp(self): + self.client = Client() + self.contact = Contact.objects.create( + email="pat@example.com", + phone="+15550142291", + first_name="Pat", + ) + set_channel_consent( + self.contact, Channel.SMS, opted_in=True, reason="test" + ) + self.campaign = create_campaign_draft( + name="SMS blast", + audience=Campaign.Audience.SMS_OPT_IN, + subject="", + body="Hi there", + ) + self.message = self.campaign.messages.get() + self.message.status = Message.Status.SENT + self.message.provider_message_id = "sms-provider-99" + self.message.save() + + def test_sms_delivered_by_message_id(self): + url = reverse("messaging:sms_webhook") + import json + + response = self.client.post( + url, + data=json.dumps( + { + "event": "sms_delivered", + "message_id": "sms-provider-99", + "destination_number": "5550142291", + } + ), + content_type="application/json", + ) + self.assertEqual(response.status_code, 200) + self.message.refresh_from_db() + self.assertEqual(self.message.status, Message.Status.DELIVERED) + self.assertTrue( + ProviderEvent.objects.filter( + message=self.message, event_type="sms_delivered" + ).exists() + ) + + def test_sms_failed_by_phone_fallback(self): + url = reverse("messaging:sms_webhook") + import json + + self.message.provider_message_id = "other-id" + self.message.save(update_fields=["provider_message_id"]) + response = self.client.post( + url, + data=json.dumps( + { + "event": "sms_failed", + "message_id": "unknown", + "destination_number": "+1 (555) 014-2291", + "status_code": "undeliverable", + } + ), + content_type="application/json", + ) + self.assertEqual(response.status_code, 200) + self.message.refresh_from_db() + self.assertEqual(self.message.status, Message.Status.FAILED) + + def test_inbound_stop_still_works(self): + url = reverse("messaging:sms_webhook") + response = self.client.post( + url, {"from": "5550142291", "text": "STOP"} + ) + self.assertEqual(response.status_code, 200) + self.assertFalse(contact_may_receive(self.contact, Channel.SMS)) + + def test_sms_webhook_requires_bearer_when_secret_set(self): + url = reverse("messaging:sms_webhook") + import json + + with self.settings(SMTP2GO_WEBHOOK_SECRET="s3cret"): + denied = self.client.post( + url, + data=json.dumps({"event": "sms_delivered", "message_id": "x"}), + content_type="application/json", + ) + self.assertEqual(denied.status_code, 403) + ok = self.client.post( + url, + data=json.dumps( + { + "event": "sms_delivered", + "message_id": "sms-provider-99", + } + ), + content_type="application/json", + HTTP_AUTHORIZATION="Bearer s3cret", + ) + self.assertEqual(ok.status_code, 200) + + +class PcmPostcardWebhookTests(TestCase): + def setUp(self): + self.client = Client() + self.contact = Contact.objects.create( + email="mail@example.com", + first_name="Pat", + last_name="Lee", + postal_address=Contact.make_postal_address( + line1="123 Main St", + city="Naperville", + state="IL", + zip_code="60540", + ), + ) + set_channel_consent( + self.contact, Channel.POSTCARD, opted_in=True, reason="test" + ) + template = MessageTemplate.objects.create( + name="PCM test design", + channel=Channel.POSTCARD, + body="Postcard", + postcard_front={"design_id": 99, "size": "46", "provider": "pcm"}, + ) + self.campaign = create_campaign_draft( + name="March mailer", + audience=Campaign.Audience.POSTCARD_OPT_IN, + body="Postcard mailing", + template=template, + ) + self.message = self.campaign.messages.get() + self.message.status = Message.Status.SENT + self.message.provider = "pcm" + self.message.provider_message_id = "order-555" + self.message.save() + + def test_delivered_by_ext_ref(self): + import json + + url = reverse("messaging:postcard_webhook") + response = self.client.post( + url, + data=json.dumps( + { + "status": "Delivered", + "extRefNbr": str(self.message.pk), + "orderID": 555, + } + ), + content_type="application/json", + ) + self.assertEqual(response.status_code, 200) + self.message.refresh_from_db() + self.assertEqual(self.message.status, Message.Status.DELIVERED) + self.assertTrue( + ProviderEvent.objects.filter( + message=self.message, provider="pcm", event_type="Delivered" + ).exists() + ) + + def test_requires_bearer_when_secret_set(self): + import json + + url = reverse("messaging:postcard_webhook") + with self.settings(PCM_WEBHOOK_SECRET="pcm-secret"): + denied = self.client.post( + url, + data=json.dumps({"status": "Delivered", "orderID": 555}), + content_type="application/json", + ) + self.assertEqual(denied.status_code, 403) + ok = self.client.post( + url, + data=json.dumps( + { + "status": "Delivered", + "orderID": "order-555", + } + ), + content_type="application/json", + HTTP_AUTHORIZATION="Bearer pcm-secret", + ) + self.assertEqual(ok.status_code, 200) diff --git a/site/messaging/urls.py b/site/messaging/urls.py new file mode 100644 index 0000000..4827c09 --- /dev/null +++ b/site/messaging/urls.py @@ -0,0 +1,39 @@ +from django.urls import path + +from messaging import views + +app_name = "messaging" + +urlpatterns = [ + path("campaigns/", views.campaign_list, name="campaign_list"), + path("campaigns//", views.campaign_detail, name="campaign_detail"), + path( + "campaigns//status.json", + views.campaign_status_json, + name="campaign_status_json", + ), + path("campaigns//send/", views.campaign_send, name="campaign_send"), + path( + "campaigns//test-send/", + views.campaign_test_send, + name="campaign_test_send", + ), + path("postcard/", views.postcard_designer, name="postcard_designer"), + path( + "postcard/create/", + views.postcard_design_create, + name="postcard_design_create", + ), + path( + "postcard/save/", + views.postcard_design_save, + name="postcard_design_save", + ), + path("webhooks/sms/", views.sms_webhook, name="sms_webhook"), + path("webhooks/email/", views.email_webhook, name="email_webhook"), + path( + "webhooks/postcard/", + views.postcard_webhook, + name="postcard_webhook", + ), +] diff --git a/site/messaging/views.py b/site/messaging/views.py new file mode 100644 index 0000000..a7847f8 --- /dev/null +++ b/site/messaging/views.py @@ -0,0 +1,549 @@ +from django.conf import settings +from django.contrib import messages +from django.contrib.auth.decorators import login_required +from django.core.exceptions import ValidationError +from django.core.validators import validate_email +from django.http import HttpResponseForbidden, JsonResponse +from django.shortcuts import get_object_or_404, redirect, render +from django.urls import reverse +from django.views.decorators.csrf import csrf_exempt +from django.views.decorators.http import require_GET, require_http_methods, require_POST + +from contacts.models import Channel +from messaging.models import Campaign, MessageTemplate, ProviderEvent +from messaging.providers.postcard.pcm import ( + PCM_SIZE_CHOICES, + PcmApiError, + create_custom_design, + get_design_embed_url, + list_designs, +) +from messaging.services import ( + create_campaign_draft, + enqueue_campaign_send, + opted_in_contacts, + parse_scheduled_for, + record_sms_stop, + send_campaign_test_email, +) +from messaging.webhooks import ( + campaign_engagement_stats, + is_inbound_sms_stop, + parse_webhook_payload, + process_pcm_postcard_webhook, + process_smtp2go_email_webhook, + process_smtp2go_sms_webhook, +) + + +def _audience_choices() -> list[tuple[str, str]]: + """Labeled audience options with live opted-in counts.""" + rows = [ + (Campaign.Audience.EMAIL_OPT_IN, Channel.EMAIL, "email"), + (Campaign.Audience.SMS_OPT_IN, Channel.SMS, "SMS"), + (Campaign.Audience.POSTCARD_OPT_IN, Channel.POSTCARD, "postcard"), + ] + choices = [] + for value, channel, label in rows: + count = opted_in_contacts(channel).count() + noun = "contact" if count == 1 else "contacts" + choices.append( + (value, f"Mailing list · {label} opt-in ({count} {noun})") + ) + return choices + + +def _postcard_templates(): + return MessageTemplate.objects.filter(channel=Channel.POSTCARD).order_by( + "-updated_at" + )[:50] + + +def _campaign_report(campaign: Campaign) -> dict: + messages_qs = list(campaign.messages.select_related("contact").all()[:200]) + stats = campaign_engagement_stats(campaign) + recent_events = ( + ProviderEvent.objects.filter(message__campaign=campaign) + .select_related("message", "message__contact") + .order_by("-created_at")[:25] + ) + return { + "messages": messages_qs, + "stats": stats, + "recent_events": recent_events, + } + + +def _webhook_authorized(request, *, secret: str) -> bool: + secret = (secret or "").strip() + if not secret: + return True + token = (request.GET.get("token") or "").strip() + auth = (request.headers.get("Authorization") or "").strip() + if token and token == secret: + return True + if auth.lower().startswith("bearer ") and auth[7:].strip() == secret: + return True + return False + + +@login_required +@require_http_methods(["GET", "POST"]) +def campaign_list(request): + form_errors: list[str] = [] + form_data = { + "name": "", + "subject": "", + "body": "", + "audience": Campaign.Audience.EMAIL_OPT_IN, + "scheduled_for": "", + "template_id": "", + } + + if request.method == "POST": + name = (request.POST.get("name") or "").strip() + subject = (request.POST.get("subject") or "").strip() + body = (request.POST.get("body") or "").strip() + audience = (request.POST.get("audience") or "").strip() + scheduled_raw = request.POST.get("scheduled_for") or "" + template_id = (request.POST.get("template_id") or "").strip() + + form_data.update( + { + "name": name, + "subject": subject, + "body": body, + "audience": audience, + "scheduled_for": scheduled_raw, + "template_id": template_id, + } + ) + + template = None + if template_id: + template = MessageTemplate.objects.filter(pk=template_id).first() + + if not name: + form_errors.append("Campaign name is required.") + if audience not in Campaign.Audience.values: + form_errors.append("Choose a recipient list.") + if audience == Campaign.Audience.POSTCARD_OPT_IN: + if not template or template.channel != Channel.POSTCARD: + form_errors.append( + "Choose a saved postcard template (design it under Postcard first)." + ) + if not body: + body = "Postcard mailing" + else: + if not body: + form_errors.append("Body is required.") + if ( + audience == Campaign.Audience.EMAIL_OPT_IN + and not subject + ): + form_errors.append("Subject is required for email campaigns.") + + scheduled_for = None + try: + scheduled_for = parse_scheduled_for(scheduled_raw) + except ValueError as exc: + form_errors.append(str(exc)) + + if not form_errors: + campaign = create_campaign_draft( + name=name, + audience=audience, + subject=subject, + body=body, + scheduled_for=scheduled_for, + created_by=request.user, + template=template, + ) + recipient_count = campaign.messages.count() + messages.success( + request, + f'Draft “{campaign.name}” saved ' + f"({recipient_count} recipient" + f"{'' if recipient_count == 1 else 's'}).", + ) + return redirect("messaging:campaign_detail", pk=campaign.pk) + + campaigns = Campaign.objects.all()[:100] + return render( + request, + "messaging/campaign_list.html", + { + "campaigns": campaigns, + "audience_choices": _audience_choices(), + "postcard_templates": _postcard_templates(), + "form_data": form_data, + "form_errors": form_errors, + }, + ) + + +@login_required +def campaign_detail(request, pk): + campaign = get_object_or_404(Campaign, pk=pk) + ctx = _campaign_report(campaign) + return render( + request, + "messaging/campaign_detail.html", + { + "campaign": campaign, + "messages": ctx["messages"], + "stats": ctx["stats"], + "recent_events": ctx["recent_events"], + "can_send": campaign.status + in { + Campaign.Status.DRAFT, + Campaign.Status.SCHEDULED, + Campaign.Status.SENDING, + } + and campaign.messages.exclude( + status__in={"sent", "delivered", "suppressed"} + ).exists(), + }, + ) + + +@login_required +@require_GET +def campaign_status_json(request, pk): + """JSON snapshot for live-updating the campaign report page.""" + campaign = get_object_or_404(Campaign, pk=pk) + ctx = _campaign_report(campaign) + return JsonResponse( + { + "status": campaign.status, + "status_display": campaign.get_status_display(), + "stats": ctx["stats"], + "messages": [ + { + "id": str(m.pk), + "contact": str(m.contact), + "status": m.status, + "status_display": m.get_status_display(), + "provider_message_id": m.provider_message_id or "", + "error": (m.error or "")[:120], + } + for m in ctx["messages"] + ], + "events": [ + { + "event_type": e.event_type, + "contact": str(e.message.contact) if e.message_id else "—", + "created_at": e.created_at.isoformat(), + } + for e in ctx["recent_events"] + ], + } + ) + + +@login_required +@require_POST +def campaign_send(request, pk): + campaign = get_object_or_404(Campaign, pk=pk) + if campaign.status == Campaign.Status.CANCELLED: + messages.error(request, "Cancelled campaigns cannot be sent.") + return redirect("messaging:campaign_detail", pk=campaign.pk) + + count = enqueue_campaign_send(campaign) + campaign.refresh_from_db() + if count == 0: + messages.warning(request, "No draft/scheduled/failed messages to send.") + else: + sent = campaign.messages.filter(status="sent").count() + failed = campaign.messages.filter(status="failed").count() + messages.success( + request, + f"Send finished for {count} message(s): {sent} sent, {failed} failed.", + ) + return redirect("messaging:campaign_detail", pk=campaign.pk) + + +@login_required +@require_POST +def campaign_test_send(request, pk): + campaign = get_object_or_404(Campaign, pk=pk) + to_email = (request.POST.get("test_email") or "").strip() + if not to_email: + messages.error(request, "Enter an email address for the test send.") + return redirect("messaging:campaign_detail", pk=campaign.pk) + try: + validate_email(to_email) + except ValidationError: + messages.error(request, "That test email address is not valid.") + return redirect("messaging:campaign_detail", pk=campaign.pk) + + try: + send_campaign_test_email(campaign, to_email) + except ValueError as exc: + messages.error(request, str(exc)) + except Exception as exc: # noqa: BLE001 — surface SMTP misconfig to portal + messages.error(request, f"Test send failed: {exc}") + else: + messages.success(request, f"Test email sent to {to_email}.") + return redirect("messaging:campaign_detail", pk=campaign.pk) + + +@login_required +def postcard_designer(request): + """PCM Integrations designer — list designs + embed iframe.""" + api_error = "" + designs: list[dict] = [] + embed_url = "" + active_design_id = (request.GET.get("design_id") or "").strip() + active_name = "" + active_size = "46" + + try: + remote = list_designs(product_type="postcard") + for item in remote: + if not isinstance(item, dict): + continue + did = item.get("designID") or item.get("design_id") or item.get("id") + if did is None: + continue + size_info = item.get("size") or {} + size_key = ( + size_info.get("key") + if isinstance(size_info, dict) + else size_info + ) or "" + designs.append( + { + "design_id": str(did), + "name": item.get("friendlyName") + or item.get("name") + or f"Design {did}", + "size": str(size_key), + } + ) + except PcmApiError as exc: + api_error = str(exc) + + # Merge saved local templates that may not appear in the remote page yet. + seen = {d["design_id"] for d in designs} + for tmpl in _postcard_templates(): + front = tmpl.postcard_front or {} + did = front.get("design_id") + if did is None: + continue + did_s = str(did) + if did_s in seen: + continue + designs.insert( + 0, + { + "design_id": did_s, + "name": tmpl.name, + "size": str(front.get("size") or ""), + }, + ) + seen.add(did_s) + + if active_design_id: + match = next( + (d for d in designs if d["design_id"] == active_design_id), None + ) + if match: + active_name = match["name"] + active_size = match.get("size") or "46" + else: + active_name = f"Design {active_design_id}" + try: + embed_url = get_design_embed_url(active_design_id) + except PcmApiError as exc: + api_error = api_error or str(exc) + + return render( + request, + "messaging/postcard_designer.html", + { + "api_error": api_error, + "designs": designs, + "embed_url": embed_url, + "active_design_id": active_design_id, + "active_name": active_name, + "active_size": active_size, + "size_choices": PCM_SIZE_CHOICES, + "new_name": "", + "new_size": "46", + "saved_templates": _postcard_templates(), + }, + ) + + +@login_required +@require_POST +def postcard_design_create(request): + name = (request.POST.get("name") or "").strip() or "Untitled postcard" + size = (request.POST.get("size") or "46").strip() + allowed = {code for code, _ in PCM_SIZE_CHOICES} + if size not in allowed: + messages.error(request, "Invalid postcard size.") + return redirect("messaging:postcard_designer") + try: + data = create_custom_design(name=name, size=size) + except PcmApiError as exc: + messages.error(request, f"PCM create failed: {exc}") + return redirect("messaging:postcard_designer") + + design_id = data.get("designID") or data.get("design_id") + if design_id is None: + messages.error(request, "PCM did not return a design ID.") + return redirect("messaging:postcard_designer") + messages.success(request, f"Design {design_id} created — edit below.") + return redirect( + f"{reverse('messaging:postcard_designer')}?design_id={design_id}" + ) + + +@login_required +@require_POST +def postcard_design_save(request): + design_id = (request.POST.get("design_id") or "").strip() + template_name = (request.POST.get("template_name") or "").strip() + size = (request.POST.get("size") or "").strip() + if not design_id: + messages.error(request, "Missing design id.") + return redirect("messaging:postcard_designer") + if not template_name: + messages.error(request, "Template name is required.") + return redirect( + f"{reverse('messaging:postcard_designer')}?design_id={design_id}" + ) + try: + design_id_int = int(design_id) + except ValueError: + messages.error(request, "Invalid design id.") + return redirect("messaging:postcard_designer") + + front = { + "design_id": design_id_int, + "size": size, + "name": template_name, + "provider": "pcm", + } + tmpl, created = MessageTemplate.objects.update_or_create( + channel=Channel.POSTCARD, + name=template_name, + defaults={ + "subject": "", + "body": f"PCM design {design_id_int}", + "postcard_front": front, + "postcard_back": {}, + }, + ) + verb = "Created" if created else "Updated" + messages.success( + request, + f"{verb} postcard template “{tmpl.name}” (design {design_id_int}).", + ) + return redirect( + f"{reverse('messaging:postcard_designer')}?design_id={design_id}" + ) + + +@csrf_exempt +@require_POST +def postcard_webhook(request): + """ + PCM Integrations order / mail-tracking webhook. + + Configure in PCM → Webhooks: + URL: https:///portal/messaging/webhooks/postcard/ + Authorization: Bearer + PCM_WEBHOOK_SECRET + Events: order / recipient status updates (Delivered, Undeliverable, …) + """ + if not _webhook_authorized( + request, secret=settings.PCM_WEBHOOK_SECRET or "" + ): + return HttpResponseForbidden("invalid webhook token") + payload = parse_webhook_payload(request) + if not payload: + payload = request.POST.dict() or {} + event = process_pcm_postcard_webhook(payload) + return JsonResponse( + { + "ok": True, + "matched": bool(event and event.message_id), + "event_id": event.pk if event else None, + } + ) + + +@csrf_exempt +@require_POST +def sms_webhook(request): + """ + SMTP2GO SMS webhook — delivery status events + inbound STOP replies. + + Configure a *separate* webhook in SMTP2GO → Settings → Webhooks: + URL: https:///portal/messaging/webhooks/sms/ + Authorization header: Bearer + value = SMTP2GO_WEBHOOK_SECRET + Output type: JSON + SMS events: Submitted, Sending, Delivered, Failed, Rejected, Opt-out + (leave Email events unchecked on this webhook) + + Inbound gateway POSTs without ``event`` (text=STOP, from=…) still opt out. + """ + if not _webhook_authorized( + request, secret=settings.SMTP2GO_WEBHOOK_SECRET or "" + ): + return HttpResponseForbidden("invalid webhook token") + + payload = parse_webhook_payload(request) + if not payload: + payload = request.POST.dict() or {} + + # Inbound reply (STOP) — different payload shape than delivery events. + if is_inbound_sms_stop(payload): + phone = ( + payload.get("from") + or payload.get("phone") + or payload.get("source_number") + or payload.get("destination_number") + or "" + ) + stopped = bool(phone) and record_sms_stop(str(phone)) + return JsonResponse({"ok": True, "opt_out": stopped}) + + event = process_smtp2go_sms_webhook(payload) + return JsonResponse( + { + "ok": True, + "matched": bool(event and event.message_id), + "event_id": event.pk if event else None, + } + ) + + +@csrf_exempt +@require_POST +def email_webhook(request): + """ + SMTP2GO email event webhook (delivered / open / click / bounce / …). + + Configure in SMTP2GO → Settings → Webhooks: + URL: https:///portal/messaging/webhooks/email/ + Authorization header: Bearer + value = SMTP2GO_WEBHOOK_SECRET + Output type: JSON + Email events: all delivery/engagement boxes + Email headers: X-Monica-Message-Id + """ + if not _webhook_authorized( + request, secret=settings.SMTP2GO_WEBHOOK_SECRET or "" + ): + return HttpResponseForbidden("invalid webhook token") + payload = parse_webhook_payload(request) + event = process_smtp2go_email_webhook(payload) + return JsonResponse( + { + "ok": True, + "matched": bool(event and event.message_id), + "event_id": event.pk if event else None, + } + ) diff --git a/site/messaging/webhooks.py b/site/messaging/webhooks.py new file mode 100644 index 0000000..627e5df --- /dev/null +++ b/site/messaging/webhooks.py @@ -0,0 +1,575 @@ +"""SMTP2GO email/SMS event webhooks → Message + ProviderEvent updates.""" + +from __future__ import annotations + +import json +import logging +from typing import Any + +from django.http import HttpRequest + +from contacts.models import Channel, Contact +from messaging.models import Message, ProviderEvent +from messaging.services import set_channel_consent + +logger = logging.getLogger(__name__) + +PROVIDER_EMAIL = "smtp2go_email" +PROVIDER_SMS = "smtp2go_sms" +PROVIDER_PCM = "pcm" +PROVIDER = PROVIDER_EMAIL # backward-compatible alias + +# Do not move a message backward to a weaker delivery state. +_STATUS_RANK = { + Message.Status.DRAFT: 0, + Message.Status.SCHEDULED: 1, + Message.Status.QUEUED: 2, + Message.Status.SENT: 3, + Message.Status.FAILED: 3, + Message.Status.DELIVERED: 4, + Message.Status.BOUNCED: 5, + Message.Status.SUPPRESSED: 5, +} + +_MONICA_HEADER_KEYS = ( + "X-Monica-Message-Id", + "x-monica-message-id", + "X_Monica_Message_Id", + "monica-message-id", +) + + +def parse_webhook_payload(request: HttpRequest) -> dict[str, Any]: + """Accept JSON or form-encoded SMTP2GO webhook bodies.""" + content_type = (request.content_type or "").lower() + if "application/json" in content_type: + try: + data = json.loads(request.body.decode() or "{}") + except json.JSONDecodeError: + return {} + return data if isinstance(data, dict) else {} + # Form-encoded (SMTP2GO default) + return {key: request.POST.get(key) for key in request.POST.keys()} + + +def extract_monica_message_id(payload: dict[str, Any]) -> str: + """Pull our correlation id from flat keys or a nested headers object.""" + for key in _MONICA_HEADER_KEYS: + value = payload.get(key) + if value: + return str(value).strip() + + headers = payload.get("headers") or payload.get("email_headers") or {} + if isinstance(headers, dict): + for key in _MONICA_HEADER_KEYS: + value = headers.get(key) + if value: + return str(value).strip() + # Case-insensitive scan + lower_map = {str(k).lower(): v for k, v in headers.items()} + for key in _MONICA_HEADER_KEYS: + value = lower_map.get(key.lower()) + if value: + return str(value).strip() + return "" + + +def find_message_for_email_event(payload: dict[str, Any]) -> Message | None: + monica_id = extract_monica_message_id(payload) + if monica_id: + message = ( + Message.objects.select_related("contact", "campaign") + .filter(pk=monica_id) + .first() + ) + if message: + return message + + email_id = (payload.get("email_id") or payload.get("email-id") or "").strip() + if email_id: + message = ( + Message.objects.select_related("contact", "campaign") + .filter(provider_message_id=email_id) + .first() + ) + if message: + return message + + rcpt = (payload.get("rcpt") or "").strip().lower() + if not rcpt: + recipients = payload.get("recipients") + if isinstance(recipients, str) and recipients.strip(): + rcpt = recipients.split(",")[0].strip().lower() + elif isinstance(recipients, list) and recipients: + rcpt = str(recipients[0]).strip().lower() + + if not rcpt: + return None + + contact = Contact.objects.filter(email__iexact=rcpt).first() + if not contact: + return None + + return ( + Message.objects.select_related("contact", "campaign") + .filter( + contact=contact, + channel=Channel.EMAIL, + status__in=[ + Message.Status.QUEUED, + Message.Status.SENT, + Message.Status.DELIVERED, + Message.Status.FAILED, + Message.Status.BOUNCED, + ], + ) + .order_by("-sent_at", "-updated_at") + .first() + ) + + +def _maybe_upgrade_status(message: Message, new_status: str, *, error: str = "") -> None: + current_rank = _STATUS_RANK.get(message.status, 0) + new_rank = _STATUS_RANK.get(new_status, 0) + # Always allow bounce/suppress to overwrite delivered; allow delivered over sent. + if new_rank < current_rank and new_status not in { + Message.Status.BOUNCED, + Message.Status.SUPPRESSED, + Message.Status.FAILED, + }: + return + if ( + message.status + in {Message.Status.BOUNCED, Message.Status.SUPPRESSED} + and new_status == Message.Status.DELIVERED + ): + return + + fields = ["status", "updated_at"] + message.status = new_status + if error: + message.error = error[:2000] + fields.append("error") + elif new_status == Message.Status.DELIVERED: + message.error = "" + fields.append("error") + message.save(update_fields=fields) + + +def _apply_email_event(message: Message, event: str, payload: dict[str, Any]) -> None: + event = (event or "").strip().lower() + bounce_kind = (payload.get("bounce") or "").strip().lower() + err = (payload.get("message") or payload.get("context") or "").strip() + + email_id = (payload.get("email_id") or payload.get("email-id") or "").strip() + if email_id and message.provider_message_id != email_id: + message.provider_message_id = email_id + message.provider = PROVIDER_EMAIL + message.save( + update_fields=["provider_message_id", "provider", "updated_at"] + ) + + if event == "processed": + if message.status in {Message.Status.QUEUED, Message.Status.DRAFT}: + _maybe_upgrade_status(message, Message.Status.SENT) + return + + if event == "delivered": + _maybe_upgrade_status(message, Message.Status.DELIVERED) + return + + if event == "bounce": + status = Message.Status.BOUNCED + _maybe_upgrade_status( + message, + status, + error=err or f"{bounce_kind or 'unknown'} bounce", + ) + if bounce_kind == "hard": + set_channel_consent( + message.contact, + Channel.EMAIL, + opted_in=False, + reason="smtp2go_hard_bounce", + ) + return + + if event == "reject": + _maybe_upgrade_status( + message, Message.Status.FAILED, error=err or "rejected by provider" + ) + return + + if event == "spam": + _maybe_upgrade_status( + message, Message.Status.SUPPRESSED, error=err or "spam complaint" + ) + set_channel_consent( + message.contact, + Channel.EMAIL, + opted_in=False, + reason="smtp2go_spam", + ) + return + + if event == "unsubscribe": + _maybe_upgrade_status( + message, Message.Status.SUPPRESSED, error="provider unsubscribe" + ) + set_channel_consent( + message.contact, + Channel.EMAIL, + opted_in=False, + reason="smtp2go_unsubscribe", + ) + return + + # open / click / resubscribe — event row only (status unchanged) + + +def process_smtp2go_email_webhook(payload: dict[str, Any]) -> ProviderEvent | None: + """ + Persist ProviderEvent and update Message delivery status when possible. + + Returns the stored event (even if message could not be matched). + """ + event = (payload.get("event") or "").strip().lower() + if not event: + logger.warning("SMTP2GO webhook missing event: %s", payload) + return None + + message = find_message_for_email_event(payload) + if message: + _apply_email_event(message, event, payload) + message.refresh_from_db() + else: + logger.info( + "SMTP2GO webhook unmatched event=%s rcpt=%s email_id=%s", + event, + payload.get("rcpt"), + payload.get("email_id"), + ) + + return ProviderEvent.objects.create( + message=message, + provider=PROVIDER_EMAIL, + event_type=event, + payload=payload, + ) + + +def normalize_phone(value: str) -> str: + return "".join(ch for ch in (value or "") if ch.isdigit()) + + +def find_message_for_sms_event(payload: dict[str, Any]) -> Message | None: + provider_id = ( + payload.get("message_id") + or payload.get("sms_id") + or payload.get("id") + or "" + ) + provider_id = str(provider_id).strip() + if provider_id: + message = ( + Message.objects.select_related("contact", "campaign") + .filter(channel=Channel.SMS, provider_message_id=provider_id) + .first() + ) + if message: + return message + + raw_phone = ( + payload.get("destination_number") + or payload.get("to") + or payload.get("phone") + or payload.get("from") + or "" + ) + digits = normalize_phone(str(raw_phone)) + if len(digits) < 7: + return None + + # Match last 10 digits so +1 / formatting differences still hit. + tail = digits[-10:] + contacts = Contact.objects.exclude(phone="").only("id", "phone") + contact = None + for row in contacts.iterator(): + if normalize_phone(row.phone).endswith(tail): + contact = row + break + if not contact: + return None + + return ( + Message.objects.select_related("contact", "campaign") + .filter( + contact=contact, + channel=Channel.SMS, + status__in=[ + Message.Status.QUEUED, + Message.Status.SENT, + Message.Status.DELIVERED, + Message.Status.FAILED, + ], + ) + .order_by("-sent_at", "-updated_at") + .first() + ) + + +def _apply_sms_event(message: Message, event: str, payload: dict[str, Any]) -> None: + event = (event or "").strip().lower().replace("-", "_") + err = ( + payload.get("message") + or payload.get("status_code") + or payload.get("context") + or "" + ) + err = str(err).strip() + + provider_id = ( + payload.get("message_id") or payload.get("sms_id") or "" + ) + provider_id = str(provider_id).strip() + if provider_id and message.provider_message_id != provider_id: + message.provider_message_id = provider_id + message.provider = PROVIDER_SMS + message.save( + update_fields=["provider_message_id", "provider", "updated_at"] + ) + + if event in {"sms_sending", "sending", "sms_submitted", "submitted"}: + if message.status in {Message.Status.QUEUED, Message.Status.DRAFT}: + _maybe_upgrade_status(message, Message.Status.SENT) + return + + if event in {"sms_delivered", "delivered"}: + _maybe_upgrade_status(message, Message.Status.DELIVERED) + return + + if event in {"sms_failed", "failed", "sms_rejected", "rejected"}: + _maybe_upgrade_status( + message, + Message.Status.FAILED, + error=err or event, + ) + return + + if event in {"sms_opt_out", "opt_out", "optout"}: + _maybe_upgrade_status( + message, Message.Status.SUPPRESSED, error="sms opt-out" + ) + set_channel_consent( + message.contact, + Channel.SMS, + opted_in=False, + reason="smtp2go_sms_opt_out", + ) + return + + +def process_smtp2go_sms_webhook(payload: dict[str, Any]) -> ProviderEvent | None: + """Persist SMS delivery/opt-out ProviderEvent and update Message when matched.""" + event = (payload.get("event") or "").strip().lower() + if not event: + logger.warning("SMTP2GO SMS webhook missing event: %s", payload) + return None + + message = find_message_for_sms_event(payload) + if message: + _apply_sms_event(message, event, payload) + message.refresh_from_db() + else: + # Opt-out with no matched campaign message still suppresses by phone. + if event.replace("-", "_") in {"sms_opt_out", "opt_out", "optout"}: + phone = ( + payload.get("destination_number") + or payload.get("from") + or payload.get("source_number") + or "" + ) + if phone: + from messaging.services import record_sms_stop + + record_sms_stop(str(phone)) + logger.info( + "SMTP2GO SMS webhook unmatched event=%s phone=%s message_id=%s", + event, + payload.get("destination_number"), + payload.get("message_id"), + ) + + return ProviderEvent.objects.create( + message=message, + provider=PROVIDER_SMS, + event_type=event, + payload=payload, + ) + + +def is_inbound_sms_stop(payload: dict[str, Any]) -> bool: + """True for gateway-style inbound reply payloads (STOP / UNSUBSCRIBE).""" + if payload.get("event"): + return False + text = ( + payload.get("text") + or payload.get("message") + or payload.get("message_content") + or "" + ) + text = str(text).strip().upper() + return text in {"STOP", "UNSUBSCRIBE", "CANCEL", "END", "QUIT"} + + +def _pcm_event_type(payload: dict[str, Any]) -> str: + for key in ("event", "eventType", "event_type", "type", "status"): + value = payload.get(key) + if value: + return str(value).strip() + return "unknown" + + +def find_message_for_pcm_event(payload: dict[str, Any]) -> Message | None: + """Correlate PCM webhook to Message via extRefNbr or orderID.""" + ext = ( + payload.get("extRefNbr") + or payload.get("ext_ref_nbr") + or payload.get("externalReference") + or "" + ) + if not ext and isinstance(payload.get("recipient"), dict): + ext = payload["recipient"].get("extRefNbr") or "" + ext = str(ext).strip() + if ext: + message = ( + Message.objects.select_related("contact", "campaign") + .filter(pk=ext) + .first() + ) + if message: + return message + + order_id = ( + payload.get("orderID") + or payload.get("orderId") + or payload.get("order_id") + or "" + ) + order_id = str(order_id).strip() + if order_id: + message = ( + Message.objects.select_related("contact", "campaign") + .filter(provider_message_id=order_id, channel=Channel.POSTCARD) + .first() + ) + if message: + return message + return None + + +def _apply_pcm_status(message: Message, status: str, payload: dict[str, Any]) -> None: + status_norm = (status or "").strip().lower() + err = ( + payload.get("message") + or payload.get("error") + or payload.get("reason") + or "" + ) + err = str(err).strip() + + order_id = ( + payload.get("orderID") + or payload.get("orderId") + or payload.get("order_id") + or "" + ) + if order_id and message.provider_message_id != str(order_id): + message.provider_message_id = str(order_id) + message.provider = PROVIDER_PCM + message.save( + update_fields=["provider_message_id", "provider", "updated_at"] + ) + + if status_norm in {"delivered"}: + _maybe_upgrade_status(message, Message.Status.DELIVERED) + return + if status_norm in {"undeliverable", "returned"}: + _maybe_upgrade_status( + message, + Message.Status.BOUNCED, + error=err or "undeliverable", + ) + return + if status_norm in {"canceled", "cancelled"}: + _maybe_upgrade_status( + message, Message.Status.FAILED, error=err or "canceled" + ) + return + if status_norm in {"pending", "processing", "processed", "mailed", "intransit", "in_transit"}: + if message.status in { + Message.Status.QUEUED, + Message.Status.DRAFT, + Message.Status.SCHEDULED, + }: + _maybe_upgrade_status(message, Message.Status.SENT) + return + + +def process_pcm_postcard_webhook(payload: dict[str, Any]) -> ProviderEvent | None: + """Record a PCM Integrations postcard event and advance Message status.""" + if not payload: + return None + + # Nested data wrappers some webhook UIs use. + if "data" in payload and isinstance(payload["data"], dict): + inner = dict(payload["data"]) + for key in ("event", "eventType", "type"): + if key in payload and key not in inner: + inner[key] = payload[key] + payload = inner + + event_type = _pcm_event_type(payload) + message = find_message_for_pcm_event(payload) + if message: + status_for_apply = ( + payload.get("status") + or payload.get("orderStatus") + or event_type + ) + _apply_pcm_status(message, str(status_for_apply), payload) + + return ProviderEvent.objects.create( + message=message, + provider=PROVIDER_PCM, + event_type=event_type[:64], + payload=payload, + ) + + +def campaign_engagement_stats(campaign) -> dict[str, int]: + """Aggregate delivery + open/click counts for the campaign report.""" + messages_qs = campaign.messages.all() + statuses = list(messages_qs.values_list("status", flat=True)) + message_ids = list(messages_qs.values_list("pk", flat=True)) + + events = ProviderEvent.objects.filter(message_id__in=message_ids) + open_message_ids = set( + events.filter(event_type__iexact="open").values_list("message_id", flat=True) + ) + click_message_ids = set( + events.filter(event_type__iexact="click").values_list("message_id", flat=True) + ) + + return { + "total": len(statuses), + "sent": sum(1 for s in statuses if s in {"sent", "delivered"}), + "delivered": sum(1 for s in statuses if s == "delivered"), + "failed": sum(1 for s in statuses if s in {"failed", "bounced"}), + "bounced": sum(1 for s in statuses if s == "bounced"), + "suppressed": sum(1 for s in statuses if s == "suppressed"), + "opens": len(open_message_ids), + "clicks": len(click_message_ids), + "open_events": events.filter(event_type__iexact="open").count(), + "click_events": events.filter(event_type__iexact="click").count(), + } diff --git a/site/monica_site/__init__.py b/site/monica_site/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/site/monica_site/__init__.py @@ -0,0 +1 @@ + diff --git a/site/monica_site/asgi.py b/site/monica_site/asgi.py new file mode 100644 index 0000000..701b5d9 --- /dev/null +++ b/site/monica_site/asgi.py @@ -0,0 +1,9 @@ +"""ASGI config for monica_site.""" + +import os + +from django.core.asgi import get_asgi_application + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "monica_site.settings") + +application = get_asgi_application() diff --git a/site/monica_site/settings/__init__.py b/site/monica_site/settings/__init__.py new file mode 100644 index 0000000..fc73b65 --- /dev/null +++ b/site/monica_site/settings/__init__.py @@ -0,0 +1,12 @@ +"""Load environment-specific Django settings based on DJANGO_ENV.""" + +import os + +_environment = os.environ.get("DJANGO_ENV", "dev").lower() + +if _environment == "prod": + from .prod import * # noqa: F403 +elif _environment == "beta": + from .beta import * # noqa: F403 +else: + from .dev import * # noqa: F403 diff --git a/site/monica_site/settings/base.py b/site/monica_site/settings/base.py new file mode 100644 index 0000000..8698ab5 --- /dev/null +++ b/site/monica_site/settings/base.py @@ -0,0 +1,299 @@ +"""Shared Django settings for all environments.""" + +import json +import os +from pathlib import Path +from urllib.parse import urlparse + +BASE_DIR = Path(__file__).resolve().parent.parent.parent + + +def env(key: str, default: str | None = None) -> str | None: + return os.environ.get(key, default) + + +def env_bool(key: str, default: bool = False) -> bool: + value = os.environ.get(key) + if value is None: + return default + return value.lower() in {"1", "true", "yes", "on"} + + +def env_list(key: str, default: str = "") -> list[str]: + value = os.environ.get(key, default) + if not value: + return [] + value = value.strip() + if value.startswith("["): + try: + parsed = json.loads(value) + except ValueError: + parsed = None + if isinstance(parsed, list): + return [str(item).strip() for item in parsed if str(item).strip()] + return [item.strip() for item in value.split(",") if item.strip()] + + +def database_config() -> dict: + database_url = env("DATABASE_URL") + if database_url: + parsed = urlparse(database_url) + return { + "default": { + "ENGINE": "django.db.backends.postgresql", + "NAME": parsed.path.lstrip("/"), + "USER": parsed.username or "", + "PASSWORD": parsed.password or "", + "HOST": parsed.hostname or "", + "PORT": str(parsed.port or 5432), + } + } + + if env("DB_HOST"): + return { + "default": { + "ENGINE": "django.db.backends.postgresql", + "NAME": env("DB_NAME", "monica_site"), + "USER": env("DB_USER", "monica_site"), + "PASSWORD": env("DB_PASSWORD", ""), + "HOST": env("DB_HOST"), + "PORT": env("DB_PORT", "5432"), + } + } + + return { + "default": { + "ENGINE": "django.db.backends.sqlite3", + "NAME": BASE_DIR / "db.sqlite3", + } + } + + +def build_csrf_trusted_origins( + allowed_hosts: list[str], explicit: list[str] | None = None +) -> list[str]: + """Build CSRF_TRUSTED_ORIGINS for Django 4+ Origin checks on HTTPS POSTs.""" + if explicit: + return explicit + + local_hosts = {"localhost", "127.0.0.1", "0.0.0.0"} + origins: list[str] = [] + for host in allowed_hosts: + if not host or host == "*" or host.startswith("."): + continue + hostname = host.split(":")[0] + scheme = "http" if hostname in local_hosts else "https" + origins.append(f"{scheme}://{host}") + return origins + + +SECRET_KEY = env( + "DJANGO_SECRET_KEY", + "django-insecure-dev-only-change-me-before-production", +) + +DEBUG = env_bool("DJANGO_DEBUG", False) + +allowed_hosts = env_list("DJANGO_ALLOWED_HOSTS", "*") +ALLOWED_HOSTS = allowed_hosts if allowed_hosts else ["*"] + +CSRF_TRUSTED_ORIGINS = build_csrf_trusted_origins( + ALLOWED_HOSTS, + env_list("DJANGO_CSRF_TRUSTED_ORIGINS"), +) + +INSTALLED_APPS = [ + "core.apps.CoreConfig", + "public.apps.PublicConfig", + "accounts.apps.AccountsConfig", + "dashboard.apps.DashboardConfig", + "leads.apps.LeadsConfig", + "contacts.apps.ContactsConfig", + "analytics.apps.AnalyticsConfig", + "messaging.apps.MessagingConfig", + "social.apps.SocialConfig", + "django.contrib.admin", + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "whitenoise.runserver_nostatic", + "django.contrib.staticfiles", + "phonenumber_field", + "django_recaptcha", + "dj_queue", +] + +MIDDLEWARE = [ + "django.middleware.security.SecurityMiddleware", + "whitenoise.middleware.WhiteNoiseMiddleware", + "django.contrib.sessions.middleware.SessionMiddleware", + "django.middleware.common.CommonMiddleware", + "django.middleware.csrf.CsrfViewMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", + "django.middleware.clickjacking.XFrameOptionsMiddleware", + "analytics.middleware.UTMTrackingMiddleware", + "public.middleware.UnderConstructionMiddleware", +] + +ROOT_URLCONF = "monica_site.urls" + +TEMPLATES = [ + { + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [BASE_DIR / "monica_site" / "templates"], + "APP_DIRS": True, + "OPTIONS": { + "context_processors": [ + "django.template.context_processors.debug", + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.contrib.messages.context_processors.messages", + "public.context_processors.site_branding", + "public.context_processors.tianji_tracking", + ], + }, + }, +] + +WSGI_APPLICATION = "monica_site.wsgi.application" + +DATABASES = database_config() + +AUTH_PASSWORD_VALIDATORS = [ + { + "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", + }, + { + "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", + }, + { + "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", + }, + { + "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", + }, +] + +LANGUAGE_CODE = "en-us" +TIME_ZONE = "America/Chicago" +USE_I18N = True +USE_TZ = True + +STATIC_URL = "static/" +STATIC_ROOT = BASE_DIR / "staticfiles" +STATICFILES_DIRS = [ + BASE_DIR / "monica_site" / "static", +] +STORAGES = { + "staticfiles": { + "BACKEND": "whitenoise.storage.CompressedManifestStaticFilesStorage", + }, +} + +DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" + +LOGIN_URL = "accounts:login" +LOGIN_REDIRECT_URL = "dashboard:home" +LOGOUT_REDIRECT_URL = "accounts:login" + +# --- Feature gates --- +# When true, public visitors only see the under-construction page. +# Prod launch holding page: SITE_UNDER_CONSTRUCTION=true +# Beta (full app): SITE_UNDER_CONSTRUCTION=false +SITE_UNDER_CONSTRUCTION = env_bool("SITE_UNDER_CONSTRUCTION", False) + +# --- Branding / public site --- +SITE_NAME = env("SITE_NAME", "Monica Dhillon") +SITE_TAGLINE = env("SITE_TAGLINE", "MKDRealtor.com · EXIT Realty Redefined") +PUBLIC_SITE_URL = env("PUBLIC_SITE_URL", "") +DEFAULT_FROM_EMAIL = env("DEFAULT_FROM_EMAIL", "noreply@mkdrealtor.com") +CONTACT_PHONE = env("CONTACT_PHONE", "(555) 123-4567") +CONTACT_EMAIL = env("CONTACT_EMAIL", "monica@example.com") +CONTACT_SERVICE_AREA = env("CONTACT_SERVICE_AREA", "Serving Greater Metro Area") +CREDIT_NAME = env("CREDIT_NAME", "AI ML Operations, LLC") +CREDIT_URL = env("CREDIT_URL", "https://aimloperations.com") + +# --- reCAPTCHA v3 --- +RECAPTCHA_PUBLIC_KEY = env("RECAPTCHA_PUBLIC_KEY", "") +RECAPTCHA_PRIVATE_KEY = env("RECAPTCHA_PRIVATE_KEY", "") + +# --- Email (SMTP2GO) --- +EMAIL_HOST = env("EMAIL_HOST", "mail.smtp2go.com") +EMAIL_HOST_USER = env("EMAIL_HOST_USER", "") +EMAIL_HOST_PASSWORD = env("EMAIL_HOST_PASSWORD", "") +EMAIL_PORT = int(env("EMAIL_PORT", "2525") or "2525") +EMAIL_USE_TLS = env_bool("EMAIL_USE_TLS", True) +EMAIL_BACKEND = env( + "EMAIL_BACKEND", + "django.core.mail.backends.smtp.EmailBackend", +) + +# --- SMTP2GO SMS --- +SMTP2GO_SMS_API_KEY = env("SMTP2GO_SMS_API_KEY", "") +SMTP2GO_SMS_API_URL = env( + "SMTP2GO_SMS_API_URL", + "https://api.smtp2go.com/v3/sms/send", +) +# Optional shared secret: ?token=… or Authorization: Bearer … +SMTP2GO_WEBHOOK_SECRET = env("SMTP2GO_WEBHOOK_SECRET", "") + +# --- Postcards (PCM Integrations default) --- +POSTCARD_PROVIDER = env("POSTCARD_PROVIDER", "pcm") +PCM_API_KEY = env("PCM_API_KEY", "") +# Shared secret for inbound PCM event webhooks (Bearer / ?token=). +PCM_WEBHOOK_SECRET = env("PCM_WEBHOOK_SECRET", "") +# JSON object: company, firstName, lastName, address, address2, city, state, zipCode +PCM_RETURN_ADDRESS = env("PCM_RETURN_ADDRESS", "") +PCM_RETURN_LINE1 = env("PCM_RETURN_LINE1", "") +PCM_RETURN_LINE2 = env("PCM_RETURN_LINE2", "") +PCM_RETURN_CITY = env("PCM_RETURN_CITY", "") +PCM_RETURN_STATE = env("PCM_RETURN_STATE", "") +PCM_RETURN_ZIP = env("PCM_RETURN_ZIP", "") +# Legacy / alternate providers (POSTCARD_PROVIDER=lob|click2mail|postgrid) +LOB_API_KEY = env("LOB_API_KEY", "") +CLICK2MAIL_API_KEY = env("CLICK2MAIL_API_KEY", "") +POSTGRID_API_KEY = env("POSTGRID_API_KEY", "") + +# --- Social (native APIs) --- +META_APP_ID = env("META_APP_ID", "") +META_APP_SECRET = env("META_APP_SECRET", "") +LINKEDIN_CLIENT_ID = env("LINKEDIN_CLIENT_ID", "") +LINKEDIN_CLIENT_SECRET = env("LINKEDIN_CLIENT_SECRET", "") +SOCIAL_TOKEN_ENCRYPTION_KEY = env("SOCIAL_TOKEN_ENCRYPTION_KEY", "") + +# --- Ollama (social post drafting) --- +# Host-network reachable from app hosts; default matches internal LAN Ollama. +OLLAMA_BASE_URL = env("OLLAMA_BASE_URL", "http://10.0.0.128:11434") +OLLAMA_MODEL = env("OLLAMA_MODEL", "llama3.2") +OLLAMA_TIMEOUT_SECONDS = int(env("OLLAMA_TIMEOUT_SECONDS", "120") or "120") + +# --- Nominatim (address suggest; server-side proxy only) --- +# Self-hosted on ai-server-4080. Nominatim has no native API keys — gate with +# LAN UFW + this Django proxy. Optional NOMINATIM_API_KEY is forwarded as +# X-API-Key if you later put auth in front of Nominatim. +NOMINATIM_BASE_URL = env("NOMINATIM_BASE_URL", "http://10.0.0.128:8089") +NOMINATIM_TIMEOUT_SECONDS = int(env("NOMINATIM_TIMEOUT_SECONDS", "8") or "8") +NOMINATIM_COUNTRY_CODES = env("NOMINATIM_COUNTRY_CODES", "us") +NOMINATIM_USER_AGENT = env( + "NOMINATIM_USER_AGENT", + "monica_site/1.0 (address-suggest; contact admin)", +) +NOMINATIM_API_KEY = env("NOMINATIM_API_KEY", "") + +# --- Tianji analytics (pageviews + events; notice banner, not opt-in) --- +TIANJI_ENABLED = env_bool("TIANJI_ENABLED", not DEBUG) +TIANJI_TRACKER_URL = env( + "TIANJI_TRACKER_URL", + "https://tianji.aimloperations.com/tracker.js", +) +TIANJI_WEBSITE_ID = env("TIANJI_WEBSITE_ID", "cmshzhxdf6gee10qzkzrfn9iw") + +# --- Django Tasks --- +# Dev: ImmediateBackend (settings/dev.py). Beta/prod: dj-queue (settings/beta.py|prod.py). +TASKS = { + "default": { + "BACKEND": "django.tasks.backends.immediate.ImmediateBackend", + }, +} diff --git a/site/monica_site/settings/beta.py b/site/monica_site/settings/beta.py new file mode 100644 index 0000000..6676c8b --- /dev/null +++ b/site/monica_site/settings/beta.py @@ -0,0 +1,31 @@ +"""Beta/staging settings — full functionality for preview.""" + +from .base import * # noqa: F403 +from .logging import build_logging_config, logging_level_for_env + +DEBUG = env_bool("DJANGO_DEBUG", False) # noqa: F405 +TIANJI_ENABLED = env_bool("TIANJI_ENABLED", True) # noqa: F405 + +if DEBUG: + import warnings + + warnings.warn("DEBUG is enabled in beta environment.", stacklevel=1) + +SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https") +USE_X_FORWARDED_HOST = True +SESSION_COOKIE_SECURE = not DEBUG +CSRF_COOKIE_SECURE = not DEBUG + +# Beta always runs the full app unless explicitly overridden. +SITE_UNDER_CONSTRUCTION = env_bool("SITE_UNDER_CONSTRUCTION", False) # noqa: F405 + +DATABASE_ROUTERS = ["dj_queue.routers.DjQueueRouter"] +TASKS = { + "default": { + "BACKEND": "dj_queue.backend.DjQueueBackend", + "QUEUES": [], + "OPTIONS": {}, + }, +} + +LOGGING = build_logging_config(logging_level_for_env("beta"), "beta") diff --git a/site/monica_site/settings/dev.py b/site/monica_site/settings/dev.py new file mode 100644 index 0000000..02e7e2f --- /dev/null +++ b/site/monica_site/settings/dev.py @@ -0,0 +1,35 @@ +"""Development settings.""" + +from .base import * # noqa: F403 +from .logging import build_logging_config, logging_level_for_env + +DEBUG = True + +SITE_UNDER_CONSTRUCTION = env_bool("SITE_UNDER_CONSTRUCTION", False) # noqa: F405 +TIANJI_ENABLED = env_bool("TIANJI_ENABLED", False) # noqa: F405 + +STORAGES = { + "staticfiles": { + "BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage", + }, +} + +EMAIL_BACKEND = env( # noqa: F405 + "EMAIL_BACKEND", + "django.core.mail.backends.console.EmailBackend", +) + +# ALLOWED_HOSTS default "*" → empty CSRF_TRUSTED_ORIGINS, which breaks browser +# Origin checks on POST. Always trust local runserver origins in dev. +_local_csrf = [ + "http://127.0.0.1:8000", + "http://localhost:8000", + "http://0.0.0.0:8000", + "http://127.0.0.1:8001", + "http://localhost:8001", +] +CSRF_TRUSTED_ORIGINS = list( + dict.fromkeys([*_local_csrf, *CSRF_TRUSTED_ORIGINS]) # noqa: F405 +) + +LOGGING = build_logging_config(logging_level_for_env("dev"), "dev") diff --git a/site/monica_site/settings/logging.py b/site/monica_site/settings/logging.py new file mode 100644 index 0000000..55f6df8 --- /dev/null +++ b/site/monica_site/settings/logging.py @@ -0,0 +1,58 @@ +"""Environment-specific logging configuration.""" + +import os + + +def build_logging_config(level: str, environment: str) -> dict: + """Return a Django LOGGING dict for the given level and environment name.""" + return { + "version": 1, + "disable_existing_loggers": False, + "formatters": { + "verbose": { + "format": ( + f"{{levelname}} {{asctime}} {{name}} {{process:d}} {{thread:d}} " + f"[env={environment}] {{message}}" + ), + "style": "{", + }, + "simple": { + "format": f"{{levelname}} [env={environment}] {{message}}", + "style": "{", + }, + }, + "handlers": { + "console": { + "class": "logging.StreamHandler", + "formatter": "verbose" if environment == "dev" else "simple", + }, + }, + "root": { + "handlers": ["console"], + "level": level, + }, + "loggers": { + "django": { + "handlers": ["console"], + "level": level, + "propagate": False, + }, + "django.request": { + "handlers": ["console"], + "level": "ERROR" if environment == "prod" else level, + "propagate": False, + }, + }, + } + + +def logging_level_for_env(environment: str) -> str: + override = os.environ.get("DJANGO_LOG_LEVEL") + if override: + return override.upper() + + if environment == "dev": + return "DEBUG" + if environment == "beta": + return "INFO" + return "WARNING" diff --git a/site/monica_site/settings/prod.py b/site/monica_site/settings/prod.py new file mode 100644 index 0000000..fd13870 --- /dev/null +++ b/site/monica_site/settings/prod.py @@ -0,0 +1,31 @@ +"""Production settings.""" + +from .base import * # noqa: F403 +from .logging import build_logging_config, logging_level_for_env + +DEBUG = False + +TIANJI_ENABLED = env_bool("TIANJI_ENABLED", True) # noqa: F405 + +if not env("DJANGO_SECRET_KEY"): # noqa: F405 + raise ValueError("DJANGO_SECRET_KEY must be set in production.") + +SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https") +USE_X_FORWARDED_HOST = True +SESSION_COOKIE_SECURE = True +CSRF_COOKIE_SECURE = True + +# Prod holds visitors on under-construction until launch (set false when ready). +if env("SITE_UNDER_CONSTRUCTION") is None: # noqa: F405 + SITE_UNDER_CONSTRUCTION = True # noqa: F405 + +DATABASE_ROUTERS = ["dj_queue.routers.DjQueueRouter"] +TASKS = { + "default": { + "BACKEND": "dj_queue.backend.DjQueueBackend", + "QUEUES": [], + "OPTIONS": {}, + }, +} + +LOGGING = build_logging_config(logging_level_for_env("prod"), "prod") diff --git a/site/monica_site/static/brand/apple-touch-icon.png b/site/monica_site/static/brand/apple-touch-icon.png new file mode 100644 index 0000000..27bb80c Binary files /dev/null and b/site/monica_site/static/brand/apple-touch-icon.png differ diff --git a/site/monica_site/static/brand/exit_logo.png b/site/monica_site/static/brand/exit_logo.png new file mode 100644 index 0000000..4fea4c5 Binary files /dev/null and b/site/monica_site/static/brand/exit_logo.png differ diff --git a/site/monica_site/static/brand/favicon-32.png b/site/monica_site/static/brand/favicon-32.png new file mode 100644 index 0000000..9fad4f1 Binary files /dev/null and b/site/monica_site/static/brand/favicon-32.png differ diff --git a/site/monica_site/static/css/address-autocomplete.css b/site/monica_site/static/css/address-autocomplete.css new file mode 100644 index 0000000..643a30d --- /dev/null +++ b/site/monica_site/static/css/address-autocomplete.css @@ -0,0 +1,35 @@ +/* Address autocomplete dropdown (public + portal). */ +.address-ac-wrap { + position: relative; +} + +.address-ac-list { + position: absolute; + z-index: 40; + left: 0; + right: 0; + top: calc(100% + 2px); + margin: 0; + padding: 4px 0; + list-style: none; + max-height: 240px; + overflow-y: auto; + background: #fff; + border: 1px solid #d1d5db; + border-radius: 6px; + box-shadow: 0 8px 24px rgba(15, 23, 42, 0.12); +} + +.address-ac-item { + padding: 8px 12px; + font-size: 13px; + line-height: 1.35; + color: #111827; + cursor: pointer; +} + +.address-ac-item:hover, +.address-ac-item:focus { + background: #f3f4f6; + outline: none; +} diff --git a/site/monica_site/static/css/bootstrap.css b/site/monica_site/static/css/bootstrap.css new file mode 100755 index 0000000..59e8743 --- /dev/null +++ b/site/monica_site/static/css/bootstrap.css @@ -0,0 +1,6 @@ +/*! + * Bootstrap v4.0.0 (https://getbootstrap.com) + * Copyright 2011-2018 The Bootstrap Authors + * Copyright 2011-2018 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */ :root { --blue: #007bff; --indigo: #6610f2; --purple: #6f42c1; --pink: #e83e8c; --red: #dc3545; --orange: #fd7e14; --yellow: #ffc107; --green: #28a745; --teal: #20c997; --cyan: #17a2b8; --white: #fff; --gray: #6c757d; --gray-dark: #343a40; --breakpoint-xs: 0; --breakpoint-sm: 576px; --breakpoint-md: 768px; --breakpoint-lg: 992px; --breakpoint-xl: 1200px; --breakpoint-xxl: 1600px; --font-family-sans-serif: "Work Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; --font-family-monospace: Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; } *, *::before, *::after { box-sizing: border-box; } html { font-family: sans-serif; line-height: 1.15; -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; -ms-overflow-style: scrollbar; -webkit-tap-highlight-color: rgba(0, 0, 0, 0); } @-ms-viewport { width: device-width; } article, aside, dialog, figcaption, figure, footer, header, hgroup, main, nav, section { display: block; } body { margin: 0; font-family: "Work Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; font-size: 16px; font-weight: 300; line-height: 1.5; color: #74757f; text-align: left; background-color: #fff; } [tabindex="-1"]:focus { outline: 0 !important; } hr { box-sizing: content-box; height: 0; overflow: visible; } h1, h2, h3, h4, h5, h6 { margin-top: 0; margin-bottom: 0.5rem; } p { margin-top: 0; margin-bottom: 1rem; } abbr[title], abbr[data-original-title] { text-decoration: underline; text-decoration: underline dotted; cursor: help; border-bottom: 0; } address { margin-bottom: 1rem; font-style: normal; line-height: inherit; } ol, ul, dl { margin-top: 0; margin-bottom: 1rem; } ol ol, ul ul, ol ul, ul ol { margin-bottom: 0; } dt { font-weight: inherit; } dd { margin-bottom: .5rem; margin-left: 0; } blockquote { margin: 0 0 1rem; } dfn { font-style: italic; } b, strong { font-weight: bolder; } small { font-size: 80%; } sub, sup { position: relative; font-size: 75%; line-height: 0; vertical-align: baseline; } sub { bottom: -.25em; } sup { top: -.5em; } a { color: #0045b6; text-decoration: none; background-color: transparent; -webkit-text-decoration-skip: objects; } a:hover { color: #00286a; text-decoration: underline; } a:not([href]):not([tabindex]) { text-decoration: none; } a:not([href]):not([tabindex]):hover, a:not([href]):not([tabindex]):focus { text-decoration: none; } a:not([href]):not([tabindex]):focus { outline: 0; } pre, code, kbd, samp { font-family: monospace, monospace; font-size: 1em; } pre { margin-top: 0; margin-bottom: 1rem; overflow: auto; -ms-overflow-style: scrollbar; } figure { margin: 0 0 1rem; } img { vertical-align: middle; border-style: none; } svg:not(:root) { overflow: hidden; } table { border-collapse: collapse; } caption { padding-top: 0.75rem; padding-bottom: 0.75rem; color: #6c757d; text-align: left; caption-side: bottom; } th { text-align: inherit; } label { display: inline-block; margin-bottom: .5rem; } button { border-radius: 0; } button:focus { outline: 1px dotted; outline: 5px auto -webkit-focus-ring-color; } input, button, select, optgroup, textarea { margin: 0; font-family: inherit; font-size: inherit; line-height: inherit; } button, input { overflow: visible; } button, select { text-transform: none; } button, html [type="button"], [type="reset"], [type="submit"] { -webkit-appearance: button; } button::-moz-focus-inner, [type="button"]::-moz-focus-inner, [type="reset"]::-moz-focus-inner, [type="submit"]::-moz-focus-inner { padding: 0; border-style: none; } input[type="radio"], input[type="checkbox"] { box-sizing: border-box; padding: 0; } input[type="date"], input[type="time"], input[type="datetime-local"], input[type="month"] { -webkit-appearance: listbox; } textarea { overflow: auto; resize: vertical; } fieldset { min-width: 0; padding: 0; margin: 0; border: 0; } legend { display: block; width: 100%; max-width: 100%; padding: 0; margin-bottom: .5rem; font-size: 1.5rem; line-height: inherit; color: inherit; white-space: normal; } progress { vertical-align: baseline; } [type="number"]::-webkit-inner-spin-button, [type="number"]::-webkit-outer-spin-button { height: auto; } [type="search"] { outline-offset: -2px; -webkit-appearance: none; } [type="search"]::-webkit-search-cancel-button, [type="search"]::-webkit-search-decoration { -webkit-appearance: none; } ::-webkit-file-upload-button { font: inherit; -webkit-appearance: button; } output { display: inline-block; } summary { display: list-item; cursor: pointer; } template { display: none; } [hidden] { display: none !important; } h1, h2, h3, h4, h5, h6, .h1, .h2, .h3, .h4, .h5, .h6 { margin-bottom: 0.5rem; font-family: "Poppins", sans-serif; font-weight: 400; line-height: 1.1; color: #151515; } h1, h2, h3, .h1, .h2, .h3 { font-weight: 700; } h1, .h1 { font-size: 90px; } h2, .h2 { font-size: 50px; } h3, .h3 { font-size: 38px; } h4, .h4 { font-size: 24px; } h5, .h5 { font-size: 22px; } h6, .h6 { font-size: 16px; } h4, .h4 { font-weight: 300; } .lead { font-size: 24px; font-weight: 300; } .display-1 { font-size: 6rem; font-weight: 300; line-height: 1.2; } .display-2 { font-size: 5.5rem; font-weight: 300; line-height: 1.2; } .display-3 { font-size: 4.5rem; font-weight: 300; line-height: 1.2; } .display-4 { font-size: 3.5rem; font-weight: 300; line-height: 1.2; } hr { margin-top: 1rem; margin-bottom: 1rem; border: 0; border-top: 1px solid #e8e9ee; } small, .small { font-size: 80%; font-weight: 400; } mark, .mark { padding: 5px 10px; background-color: #0045b6; } .list-unstyled { padding-left: 0; list-style: none; } .list-inline { padding-left: 0; list-style: none; } .list-inline-item { display: inline-block; } .list-inline-item:not(:last-child) { margin-right: 5px; } .initialism { font-size: 90%; text-transform: uppercase; } .blockquote { margin-bottom: 1rem; font-size: 1.25rem; } .blockquote-footer { display: block; font-size: 80%; color: #6c757d; } .blockquote-footer::before { content: "\2014 \00A0"; } .img-fluid { max-width: 100%; height: auto; } .img-thumbnail { padding: 0.25rem; background-color: #fff; border: 1px solid #dee2e6; border-radius: 0.25rem; max-width: 100%; height: auto; } .figure { display: inline-block; } .figure-img { margin-bottom: 0.5rem; line-height: 1; } .figure-caption { font-size: 90%; color: #6c757d; } code, kbd, pre, samp { font-family: Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; } code { font-size: 90%; color: #111111; word-break: break-word; } a > code { color: inherit; } kbd { padding: 0.2rem 0.4rem; font-size: 87.5%; color: #fff; background-color: #212529; border-radius: 0.2rem; } kbd kbd { padding: 0; font-size: 100%; font-weight: 700; } pre { display: block; font-size: 90%; color: #212529; } pre code { font-size: inherit; color: inherit; word-break: normal; } .pre-scrollable { max-height: 340px; overflow-y: scroll; } .container { width: 100%; padding-right: 15px; padding-left: 15px; margin-right: auto; margin-left: auto; } @media (min-width: 576px) { .container { max-width: 540px; } } @media (min-width: 768px) { .container { max-width: 720px; } } @media (min-width: 992px) { .container { max-width: 960px; } } @media (min-width: 1200px) { .container { max-width: 1200px; } } .container-fluid { width: 100%; padding-right: 15px; padding-left: 15px; margin-right: auto; margin-left: auto; } .row { display: flex; flex-wrap: wrap; margin-right: -15px; margin-left: -15px; } .no-gutters { margin-right: 0; margin-left: 0; } .no-gutters > .col, .no-gutters > [class*="col-"] { padding-right: 0; padding-left: 0; } .col-1, .col-2, .col-3, .col-4, .col-5, .col-6, .col-7, .col-8, .col-9, .col-10, .col-11, .col-12, .col, .col-auto, .col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12, .col-sm, .col-sm-auto, .col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12, .col-md, .col-md-auto, .col-lg-1, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-10, .col-lg-11, .col-lg-12, .col-lg, .col-lg-auto, .col-xl-1, .col-xl-2, .col-xl-3, .col-xl-4, .col-xl-5, .col-xl-6, .col-xl-7, .col-xl-8, .col-xl-9, .col-xl-10, .col-xl-11, .col-xl-12, .col-xl, .col-xl-auto, .col-xxl-1, .col-xxl-2, .col-xxl-3, .col-xxl-4, .col-xxl-5, .col-xxl-6, .col-xxl-7, .col-xxl-8, .col-xxl-9, .col-xxl-10, .col-xxl-11, .col-xxl-12, .col-xxl, .col-xxl-auto { position: relative; width: 100%; min-height: 1px; padding-right: 15px; padding-left: 15px; } .col { flex-basis: 0; flex-grow: 1; max-width: 100%; } .col-auto { flex: 0 0 auto; width: auto; max-width: none; } .col-1 { flex: 0 0 8.33333%; max-width: 8.33333%; } .col-2 { flex: 0 0 16.66667%; max-width: 16.66667%; } .col-3 { flex: 0 0 25%; max-width: 25%; } .col-4 { flex: 0 0 33.33333%; max-width: 33.33333%; } .col-5 { flex: 0 0 41.66667%; max-width: 41.66667%; } .col-6 { flex: 0 0 50%; max-width: 50%; } .col-7 { flex: 0 0 58.33333%; max-width: 58.33333%; } .col-8 { flex: 0 0 66.66667%; max-width: 66.66667%; } .col-9 { flex: 0 0 75%; max-width: 75%; } .col-10 { flex: 0 0 83.33333%; max-width: 83.33333%; } .col-11 { flex: 0 0 91.66667%; max-width: 91.66667%; } .col-12 { flex: 0 0 100%; max-width: 100%; } .order-first { order: -1; } .order-last { order: 13; } .order-0 { order: 0; } .order-1 { order: 1; } .order-2 { order: 2; } .order-3 { order: 3; } .order-4 { order: 4; } .order-5 { order: 5; } .order-6 { order: 6; } .order-7 { order: 7; } .order-8 { order: 8; } .order-9 { order: 9; } .order-10 { order: 10; } .order-11 { order: 11; } .order-12 { order: 12; } .offset-1 { margin-left: 8.33333%; } .offset-2 { margin-left: 16.66667%; } .offset-3 { margin-left: 25%; } .offset-4 { margin-left: 33.33333%; } .offset-5 { margin-left: 41.66667%; } .offset-6 { margin-left: 50%; } .offset-7 { margin-left: 58.33333%; } .offset-8 { margin-left: 66.66667%; } .offset-9 { margin-left: 75%; } .offset-10 { margin-left: 83.33333%; } .offset-11 { margin-left: 91.66667%; } @media (min-width: 576px) { .col-sm { flex-basis: 0; flex-grow: 1; max-width: 100%; } .col-sm-auto { flex: 0 0 auto; width: auto; max-width: none; } .col-sm-1 { flex: 0 0 8.33333%; max-width: 8.33333%; } .col-sm-2 { flex: 0 0 16.66667%; max-width: 16.66667%; } .col-sm-3 { flex: 0 0 25%; max-width: 25%; } .col-sm-4 { flex: 0 0 33.33333%; max-width: 33.33333%; } .col-sm-5 { flex: 0 0 41.66667%; max-width: 41.66667%; } .col-sm-6 { flex: 0 0 50%; max-width: 50%; } .col-sm-7 { flex: 0 0 58.33333%; max-width: 58.33333%; } .col-sm-8 { flex: 0 0 66.66667%; max-width: 66.66667%; } .col-sm-9 { flex: 0 0 75%; max-width: 75%; } .col-sm-10 { flex: 0 0 83.33333%; max-width: 83.33333%; } .col-sm-11 { flex: 0 0 91.66667%; max-width: 91.66667%; } .col-sm-12 { flex: 0 0 100%; max-width: 100%; } .order-sm-first { order: -1; } .order-sm-last { order: 13; } .order-sm-0 { order: 0; } .order-sm-1 { order: 1; } .order-sm-2 { order: 2; } .order-sm-3 { order: 3; } .order-sm-4 { order: 4; } .order-sm-5 { order: 5; } .order-sm-6 { order: 6; } .order-sm-7 { order: 7; } .order-sm-8 { order: 8; } .order-sm-9 { order: 9; } .order-sm-10 { order: 10; } .order-sm-11 { order: 11; } .order-sm-12 { order: 12; } .offset-sm-0 { margin-left: 0; } .offset-sm-1 { margin-left: 8.33333%; } .offset-sm-2 { margin-left: 16.66667%; } .offset-sm-3 { margin-left: 25%; } .offset-sm-4 { margin-left: 33.33333%; } .offset-sm-5 { margin-left: 41.66667%; } .offset-sm-6 { margin-left: 50%; } .offset-sm-7 { margin-left: 58.33333%; } .offset-sm-8 { margin-left: 66.66667%; } .offset-sm-9 { margin-left: 75%; } .offset-sm-10 { margin-left: 83.33333%; } .offset-sm-11 { margin-left: 91.66667%; } } @media (min-width: 768px) { .col-md { flex-basis: 0; flex-grow: 1; max-width: 100%; } .col-md-auto { flex: 0 0 auto; width: auto; max-width: none; } .col-md-1 { flex: 0 0 8.33333%; max-width: 8.33333%; } .col-md-2 { flex: 0 0 16.66667%; max-width: 16.66667%; } .col-md-3 { flex: 0 0 25%; max-width: 25%; } .col-md-4 { flex: 0 0 33.33333%; max-width: 33.33333%; } .col-md-5 { flex: 0 0 41.66667%; max-width: 41.66667%; } .col-md-6 { flex: 0 0 50%; max-width: 50%; } .col-md-7 { flex: 0 0 58.33333%; max-width: 58.33333%; } .col-md-8 { flex: 0 0 66.66667%; max-width: 66.66667%; } .col-md-9 { flex: 0 0 75%; max-width: 75%; } .col-md-10 { flex: 0 0 83.33333%; max-width: 83.33333%; } .col-md-11 { flex: 0 0 91.66667%; max-width: 91.66667%; } .col-md-12 { flex: 0 0 100%; max-width: 100%; } .order-md-first { order: -1; } .order-md-last { order: 13; } .order-md-0 { order: 0; } .order-md-1 { order: 1; } .order-md-2 { order: 2; } .order-md-3 { order: 3; } .order-md-4 { order: 4; } .order-md-5 { order: 5; } .order-md-6 { order: 6; } .order-md-7 { order: 7; } .order-md-8 { order: 8; } .order-md-9 { order: 9; } .order-md-10 { order: 10; } .order-md-11 { order: 11; } .order-md-12 { order: 12; } .offset-md-0 { margin-left: 0; } .offset-md-1 { margin-left: 8.33333%; } .offset-md-2 { margin-left: 16.66667%; } .offset-md-3 { margin-left: 25%; } .offset-md-4 { margin-left: 33.33333%; } .offset-md-5 { margin-left: 41.66667%; } .offset-md-6 { margin-left: 50%; } .offset-md-7 { margin-left: 58.33333%; } .offset-md-8 { margin-left: 66.66667%; } .offset-md-9 { margin-left: 75%; } .offset-md-10 { margin-left: 83.33333%; } .offset-md-11 { margin-left: 91.66667%; } } @media (min-width: 992px) { .col-lg { flex-basis: 0; flex-grow: 1; max-width: 100%; } .col-lg-auto { flex: 0 0 auto; width: auto; max-width: none; } .col-lg-1 { flex: 0 0 8.33333%; max-width: 8.33333%; } .col-lg-2 { flex: 0 0 16.66667%; max-width: 16.66667%; } .col-lg-3 { flex: 0 0 25%; max-width: 25%; } .col-lg-4 { flex: 0 0 33.33333%; max-width: 33.33333%; } .col-lg-5 { flex: 0 0 41.66667%; max-width: 41.66667%; } .col-lg-6 { flex: 0 0 50%; max-width: 50%; } .col-lg-7 { flex: 0 0 58.33333%; max-width: 58.33333%; } .col-lg-8 { flex: 0 0 66.66667%; max-width: 66.66667%; } .col-lg-9 { flex: 0 0 75%; max-width: 75%; } .col-lg-10 { flex: 0 0 83.33333%; max-width: 83.33333%; } .col-lg-11 { flex: 0 0 91.66667%; max-width: 91.66667%; } .col-lg-12 { flex: 0 0 100%; max-width: 100%; } .order-lg-first { order: -1; } .order-lg-last { order: 13; } .order-lg-0 { order: 0; } .order-lg-1 { order: 1; } .order-lg-2 { order: 2; } .order-lg-3 { order: 3; } .order-lg-4 { order: 4; } .order-lg-5 { order: 5; } .order-lg-6 { order: 6; } .order-lg-7 { order: 7; } .order-lg-8 { order: 8; } .order-lg-9 { order: 9; } .order-lg-10 { order: 10; } .order-lg-11 { order: 11; } .order-lg-12 { order: 12; } .offset-lg-0 { margin-left: 0; } .offset-lg-1 { margin-left: 8.33333%; } .offset-lg-2 { margin-left: 16.66667%; } .offset-lg-3 { margin-left: 25%; } .offset-lg-4 { margin-left: 33.33333%; } .offset-lg-5 { margin-left: 41.66667%; } .offset-lg-6 { margin-left: 50%; } .offset-lg-7 { margin-left: 58.33333%; } .offset-lg-8 { margin-left: 66.66667%; } .offset-lg-9 { margin-left: 75%; } .offset-lg-10 { margin-left: 83.33333%; } .offset-lg-11 { margin-left: 91.66667%; } } @media (min-width: 1200px) { .col-xl { flex-basis: 0; flex-grow: 1; max-width: 100%; } .col-xl-auto { flex: 0 0 auto; width: auto; max-width: none; } .col-xl-1 { flex: 0 0 8.33333%; max-width: 8.33333%; } .col-xl-2 { flex: 0 0 16.66667%; max-width: 16.66667%; } .col-xl-3 { flex: 0 0 25%; max-width: 25%; } .col-xl-4 { flex: 0 0 33.33333%; max-width: 33.33333%; } .col-xl-5 { flex: 0 0 41.66667%; max-width: 41.66667%; } .col-xl-6 { flex: 0 0 50%; max-width: 50%; } .col-xl-7 { flex: 0 0 58.33333%; max-width: 58.33333%; } .col-xl-8 { flex: 0 0 66.66667%; max-width: 66.66667%; } .col-xl-9 { flex: 0 0 75%; max-width: 75%; } .col-xl-10 { flex: 0 0 83.33333%; max-width: 83.33333%; } .col-xl-11 { flex: 0 0 91.66667%; max-width: 91.66667%; } .col-xl-12 { flex: 0 0 100%; max-width: 100%; } .order-xl-first { order: -1; } .order-xl-last { order: 13; } .order-xl-0 { order: 0; } .order-xl-1 { order: 1; } .order-xl-2 { order: 2; } .order-xl-3 { order: 3; } .order-xl-4 { order: 4; } .order-xl-5 { order: 5; } .order-xl-6 { order: 6; } .order-xl-7 { order: 7; } .order-xl-8 { order: 8; } .order-xl-9 { order: 9; } .order-xl-10 { order: 10; } .order-xl-11 { order: 11; } .order-xl-12 { order: 12; } .offset-xl-0 { margin-left: 0; } .offset-xl-1 { margin-left: 8.33333%; } .offset-xl-2 { margin-left: 16.66667%; } .offset-xl-3 { margin-left: 25%; } .offset-xl-4 { margin-left: 33.33333%; } .offset-xl-5 { margin-left: 41.66667%; } .offset-xl-6 { margin-left: 50%; } .offset-xl-7 { margin-left: 58.33333%; } .offset-xl-8 { margin-left: 66.66667%; } .offset-xl-9 { margin-left: 75%; } .offset-xl-10 { margin-left: 83.33333%; } .offset-xl-11 { margin-left: 91.66667%; } } @media (min-width: 1600px) { .col-xxl { flex-basis: 0; flex-grow: 1; max-width: 100%; } .col-xxl-auto { flex: 0 0 auto; width: auto; max-width: none; } .col-xxl-1 { flex: 0 0 8.33333%; max-width: 8.33333%; } .col-xxl-2 { flex: 0 0 16.66667%; max-width: 16.66667%; } .col-xxl-3 { flex: 0 0 25%; max-width: 25%; } .col-xxl-4 { flex: 0 0 33.33333%; max-width: 33.33333%; } .col-xxl-5 { flex: 0 0 41.66667%; max-width: 41.66667%; } .col-xxl-6 { flex: 0 0 50%; max-width: 50%; } .col-xxl-7 { flex: 0 0 58.33333%; max-width: 58.33333%; } .col-xxl-8 { flex: 0 0 66.66667%; max-width: 66.66667%; } .col-xxl-9 { flex: 0 0 75%; max-width: 75%; } .col-xxl-10 { flex: 0 0 83.33333%; max-width: 83.33333%; } .col-xxl-11 { flex: 0 0 91.66667%; max-width: 91.66667%; } .col-xxl-12 { flex: 0 0 100%; max-width: 100%; } .order-xxl-first { order: -1; } .order-xxl-last { order: 13; } .order-xxl-0 { order: 0; } .order-xxl-1 { order: 1; } .order-xxl-2 { order: 2; } .order-xxl-3 { order: 3; } .order-xxl-4 { order: 4; } .order-xxl-5 { order: 5; } .order-xxl-6 { order: 6; } .order-xxl-7 { order: 7; } .order-xxl-8 { order: 8; } .order-xxl-9 { order: 9; } .order-xxl-10 { order: 10; } .order-xxl-11 { order: 11; } .order-xxl-12 { order: 12; } .offset-xxl-0 { margin-left: 0; } .offset-xxl-1 { margin-left: 8.33333%; } .offset-xxl-2 { margin-left: 16.66667%; } .offset-xxl-3 { margin-left: 25%; } .offset-xxl-4 { margin-left: 33.33333%; } .offset-xxl-5 { margin-left: 41.66667%; } .offset-xxl-6 { margin-left: 50%; } .offset-xxl-7 { margin-left: 58.33333%; } .offset-xxl-8 { margin-left: 66.66667%; } .offset-xxl-9 { margin-left: 75%; } .offset-xxl-10 { margin-left: 83.33333%; } .offset-xxl-11 { margin-left: 91.66667%; } } .table { width: 100%; max-width: 100%; margin-bottom: 1rem; background-color: transparent; } .table th, .table td { padding: 0.75rem; vertical-align: top; border-top: 1px solid #dee2e6; } .table thead th { vertical-align: bottom; border-bottom: 2px solid #dee2e6; } .table tbody + tbody { border-top: 2px solid #dee2e6; } .table .table { background-color: #fff; } .table-sm th, .table-sm td { padding: 0.3rem; } .table-bordered { border: 1px solid #dee2e6; } .table-bordered th, .table-bordered td { border: 1px solid #dee2e6; } .table-bordered thead th, .table-bordered thead td { border-bottom-width: 2px; } .table-striped tbody tr:nth-of-type(odd) { background-color: rgba(0, 0, 0, 0.05); } .table-hover tbody tr:hover { background-color: rgba(0, 0, 0, 0.075); } .table-active, .table-active > th, .table-active > td { background-color: rgba(0, 0, 0, 0.075); } .table-hover .table-active:hover { background-color: rgba(0, 0, 0, 0.075); } .table-hover .table-active:hover > td, .table-hover .table-active:hover > th { background-color: rgba(0, 0, 0, 0.075); } .table .thead-dark th { color: #fff; background-color: #212529; border-color: #32383e; } .table .thead-light th { color: #495057; background-color: #e9ecef; border-color: #dee2e6; } .table-dark { color: #fff; background-color: #212529; } .table-dark th, .table-dark td, .table-dark thead th { border-color: #32383e; } .table-dark.table-bordered { border: 0; } .table-dark.table-striped tbody tr:nth-of-type(odd) { background-color: rgba(255, 255, 255, 0.05); } .table-dark.table-hover tbody tr:hover { background-color: rgba(255, 255, 255, 0.075); } @media (max-width: 575.98px) { .table-responsive-sm { display: block; width: 100%; overflow-x: auto; -webkit-overflow-scrolling: touch; -ms-overflow-style: -ms-autohiding-scrollbar; } .table-responsive-sm > .table-bordered { border: 0; } } @media (max-width: 767.98px) { .table-responsive-md { display: block; width: 100%; overflow-x: auto; -webkit-overflow-scrolling: touch; -ms-overflow-style: -ms-autohiding-scrollbar; } .table-responsive-md > .table-bordered { border: 0; } } @media (max-width: 991.98px) { .table-responsive-lg { display: block; width: 100%; overflow-x: auto; -webkit-overflow-scrolling: touch; -ms-overflow-style: -ms-autohiding-scrollbar; } .table-responsive-lg > .table-bordered { border: 0; } } @media (max-width: 1199.98px) { .table-responsive-xl { display: block; width: 100%; overflow-x: auto; -webkit-overflow-scrolling: touch; -ms-overflow-style: -ms-autohiding-scrollbar; } .table-responsive-xl > .table-bordered { border: 0; } } @media (max-width: 1599.98px) { .table-responsive-xxl { display: block; width: 100%; overflow-x: auto; -webkit-overflow-scrolling: touch; -ms-overflow-style: -ms-autohiding-scrollbar; } .table-responsive-xxl > .table-bordered { border: 0; } } .table-responsive { display: block; width: 100%; overflow-x: auto; -webkit-overflow-scrolling: touch; -ms-overflow-style: -ms-autohiding-scrollbar; } .table-responsive > .table-bordered { border: 0; } .form-control { display: block; width: 100%; padding: 0.375rem 0.75rem; font-size: 16px; line-height: 1.5; color: #495057; background-color: #fff; background-clip: padding-box; border: 1px solid #ced4da; border-radius: 0.25rem; transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; } .form-control::-ms-expand { background-color: transparent; border: 0; } .form-control:focus { color: #495057; background-color: #fff; border-color: #3783ff; outline: 0; box-shadow: 0 0 0 0.2rem rgba(0, 69, 182, 0.25); } .form-control::placeholder { color: #6c757d; opacity: 1; } .form-control:disabled, .form-control[readonly] { background-color: #e9ecef; opacity: 1; } select.form-control:not([size]):not([multiple]) { height: calc(2.25rem + 2px); } select.form-control:focus::-ms-value { color: #495057; background-color: #fff; } .form-control-file, .form-control-range { display: block; width: 100%; } .col-form-label { padding-top: calc(0.375rem + 1px); padding-bottom: calc(0.375rem + 1px); margin-bottom: 0; font-size: inherit; line-height: 1.5; } .col-form-label-lg { padding-top: calc(0.5rem + 1px); padding-bottom: calc(0.5rem + 1px); font-size: 20px; line-height: 1.5; } .col-form-label-sm { padding-top: calc(0.25rem + 1px); padding-bottom: calc(0.25rem + 1px); font-size: 12px; line-height: 1.5; } .form-control-plaintext { display: block; width: 100%; padding-top: 0.375rem; padding-bottom: 0.375rem; margin-bottom: 0; line-height: 1.5; background-color: transparent; border: solid transparent; border-width: 1px 0; } .form-control-plaintext.form-control-sm, .input-group-sm > .form-control-plaintext.form-control, .input-group-sm > .input-group-prepend > .form-control-plaintext.input-group-text, .input-group-sm > .input-group-append > .form-control-plaintext.input-group-text, .input-group-sm > .input-group-prepend > .form-control-plaintext.btn, .input-group-sm > .input-group-append > .form-control-plaintext.btn, .form-control-plaintext.form-control-lg, .input-group-lg > .form-control-plaintext.form-control, .input-group-lg > .input-group-prepend > .form-control-plaintext.input-group-text, .input-group-lg > .input-group-append > .form-control-plaintext.input-group-text, .input-group-lg > .input-group-prepend > .form-control-plaintext.btn, .input-group-lg > .input-group-append > .form-control-plaintext.btn { padding-right: 0; padding-left: 0; } .form-control-sm, .input-group-sm > .form-control, .input-group-sm > .input-group-prepend > .input-group-text, .input-group-sm > .input-group-append > .input-group-text, .input-group-sm > .input-group-prepend > .btn, .input-group-sm > .input-group-append > .btn { padding: 0.25rem 0.5rem; font-size: 12px; line-height: 1.5; border-radius: 0.2rem; } select.form-control-sm:not([size]):not([multiple]), .input-group-sm > select.form-control:not([size]):not([multiple]), .input-group-sm > .input-group-prepend > select.input-group-text:not([size]):not([multiple]), .input-group-sm > .input-group-append > select.input-group-text:not([size]):not([multiple]), .input-group-sm > .input-group-prepend > select.btn:not([size]):not([multiple]), .input-group-sm > .input-group-append > select.btn:not([size]):not([multiple]) { height: calc(1.8125rem + 2px); } .form-control-lg, .input-group-lg > .form-control, .input-group-lg > .input-group-prepend > .input-group-text, .input-group-lg > .input-group-append > .input-group-text, .input-group-lg > .input-group-prepend > .btn, .input-group-lg > .input-group-append > .btn { padding: 0.5rem 1rem; font-size: 20px; line-height: 1.5; border-radius: 0.3rem; } select.form-control-lg:not([size]):not([multiple]), .input-group-lg > select.form-control:not([size]):not([multiple]), .input-group-lg > .input-group-prepend > select.input-group-text:not([size]):not([multiple]), .input-group-lg > .input-group-append > select.input-group-text:not([size]):not([multiple]), .input-group-lg > .input-group-prepend > select.btn:not([size]):not([multiple]), .input-group-lg > .input-group-append > select.btn:not([size]):not([multiple]) { height: calc(2.875rem + 2px); } .form-group { margin-bottom: 1rem; } .form-text { display: block; margin-top: 0.25rem; } .form-row { display: flex; flex-wrap: wrap; margin-right: -5px; margin-left: -5px; } .form-row > .col, .form-row > [class*="col-"] { padding-right: 5px; padding-left: 5px; } .form-check { position: relative; display: block; padding-left: 1.25rem; } .form-check-input { position: absolute; margin-top: 0.3rem; margin-left: -1.25rem; } .form-check-input:disabled ~ .form-check-label { color: #6c757d; } .form-check-label { margin-bottom: 0; } .form-check-inline { display: inline-flex; align-items: center; padding-left: 0; margin-right: 0.75rem; } .form-check-inline .form-check-input { position: static; margin-top: 0; margin-right: 0.3125rem; margin-left: 0; } .valid-feedback { display: none; width: 100%; margin-top: 0.25rem; font-size: 80%; color: #98bf44; } .valid-tooltip { position: absolute; top: 100%; z-index: 5; display: none; max-width: 100%; padding: .5rem; margin-top: .1rem; font-size: .875rem; line-height: 1; color: #fff; background-color: rgba(152, 191, 68, 0.8); border-radius: .2rem; } .was-validated .form-control:valid, .form-control.is-valid, .was-validated .custom-select:valid, .custom-select.is-valid { border-color: #98bf44; } .was-validated .form-control:valid:focus, .form-control.is-valid:focus, .was-validated .custom-select:valid:focus, .custom-select.is-valid:focus { border-color: #98bf44; box-shadow: 0 0 0 0.2rem rgba(152, 191, 68, 0.25); } .was-validated .form-control:valid ~ .valid-feedback, .was-validated .form-control:valid ~ .valid-tooltip, .form-control.is-valid ~ .valid-feedback, .form-control.is-valid ~ .valid-tooltip, .was-validated .custom-select:valid ~ .valid-feedback, .was-validated .custom-select:valid ~ .valid-tooltip, .custom-select.is-valid ~ .valid-feedback, .custom-select.is-valid ~ .valid-tooltip { display: block; } .was-validated .form-check-input:valid ~ .form-check-label, .form-check-input.is-valid ~ .form-check-label { color: #98bf44; } .was-validated .form-check-input:valid ~ .valid-feedback, .was-validated .form-check-input:valid ~ .valid-tooltip, .form-check-input.is-valid ~ .valid-feedback, .form-check-input.is-valid ~ .valid-tooltip { display: block; } .was-validated .custom-control-input:valid ~ .custom-control-label, .custom-control-input.is-valid ~ .custom-control-label { color: #98bf44; } .was-validated .custom-control-input:valid ~ .custom-control-label::before, .custom-control-input.is-valid ~ .custom-control-label::before { background-color: #cce0a3; } .was-validated .custom-control-input:valid ~ .valid-feedback, .was-validated .custom-control-input:valid ~ .valid-tooltip, .custom-control-input.is-valid ~ .valid-feedback, .custom-control-input.is-valid ~ .valid-tooltip { display: block; } .was-validated .custom-control-input:valid:checked ~ .custom-control-label::before, .custom-control-input.is-valid:checked ~ .custom-control-label::before { background-color: #adcc6a; } .was-validated .custom-control-input:valid:focus ~ .custom-control-label::before, .custom-control-input.is-valid:focus ~ .custom-control-label::before { box-shadow: 0 0 0 1px #fff, 0 0 0 0.2rem rgba(152, 191, 68, 0.25); } .was-validated .custom-file-input:valid ~ .custom-file-label, .custom-file-input.is-valid ~ .custom-file-label { border-color: #98bf44; } .was-validated .custom-file-input:valid ~ .custom-file-label::before, .custom-file-input.is-valid ~ .custom-file-label::before { border-color: inherit; } .was-validated .custom-file-input:valid ~ .valid-feedback, .was-validated .custom-file-input:valid ~ .valid-tooltip, .custom-file-input.is-valid ~ .valid-feedback, .custom-file-input.is-valid ~ .valid-tooltip { display: block; } .was-validated .custom-file-input:valid:focus ~ .custom-file-label, .custom-file-input.is-valid:focus ~ .custom-file-label { box-shadow: 0 0 0 0.2rem rgba(152, 191, 68, 0.25); } .invalid-feedback { display: none; width: 100%; margin-top: 0.25rem; font-size: 80%; color: #dc0000; } .invalid-tooltip { position: absolute; top: 100%; z-index: 5; display: none; max-width: 100%; padding: .5rem; margin-top: .1rem; font-size: .875rem; line-height: 1; color: #fff; background-color: rgba(220, 0, 0, 0.8); border-radius: .2rem; } .was-validated .form-control:invalid, .form-control.is-invalid, .was-validated .custom-select:invalid, .custom-select.is-invalid { border-color: #dc0000; } .was-validated .form-control:invalid:focus, .form-control.is-invalid:focus, .was-validated .custom-select:invalid:focus, .custom-select.is-invalid:focus { border-color: #dc0000; box-shadow: 0 0 0 0.2rem rgba(220, 0, 0, 0.25); } .was-validated .form-control:invalid ~ .invalid-feedback, .was-validated .form-control:invalid ~ .invalid-tooltip, .form-control.is-invalid ~ .invalid-feedback, .form-control.is-invalid ~ .invalid-tooltip, .was-validated .custom-select:invalid ~ .invalid-feedback, .was-validated .custom-select:invalid ~ .invalid-tooltip, .custom-select.is-invalid ~ .invalid-feedback, .custom-select.is-invalid ~ .invalid-tooltip { display: block; } .was-validated .form-check-input:invalid ~ .form-check-label, .form-check-input.is-invalid ~ .form-check-label { color: #dc0000; } .was-validated .form-check-input:invalid ~ .invalid-feedback, .was-validated .form-check-input:invalid ~ .invalid-tooltip, .form-check-input.is-invalid ~ .invalid-feedback, .form-check-input.is-invalid ~ .invalid-tooltip { display: block; } .was-validated .custom-control-input:invalid ~ .custom-control-label, .custom-control-input.is-invalid ~ .custom-control-label { color: #dc0000; } .was-validated .custom-control-input:invalid ~ .custom-control-label::before, .custom-control-input.is-invalid ~ .custom-control-label::before { background-color: #ff5d5d; } .was-validated .custom-control-input:invalid ~ .invalid-feedback, .was-validated .custom-control-input:invalid ~ .invalid-tooltip, .custom-control-input.is-invalid ~ .invalid-feedback, .custom-control-input.is-invalid ~ .invalid-tooltip { display: block; } .was-validated .custom-control-input:invalid:checked ~ .custom-control-label::before, .custom-control-input.is-invalid:checked ~ .custom-control-label::before { background-color: #ff1010; } .was-validated .custom-control-input:invalid:focus ~ .custom-control-label::before, .custom-control-input.is-invalid:focus ~ .custom-control-label::before { box-shadow: 0 0 0 1px #fff, 0 0 0 0.2rem rgba(220, 0, 0, 0.25); } .was-validated .custom-file-input:invalid ~ .custom-file-label, .custom-file-input.is-invalid ~ .custom-file-label { border-color: #dc0000; } .was-validated .custom-file-input:invalid ~ .custom-file-label::before, .custom-file-input.is-invalid ~ .custom-file-label::before { border-color: inherit; } .was-validated .custom-file-input:invalid ~ .invalid-feedback, .was-validated .custom-file-input:invalid ~ .invalid-tooltip, .custom-file-input.is-invalid ~ .invalid-feedback, .custom-file-input.is-invalid ~ .invalid-tooltip { display: block; } .was-validated .custom-file-input:invalid:focus ~ .custom-file-label, .custom-file-input.is-invalid:focus ~ .custom-file-label { box-shadow: 0 0 0 0.2rem rgba(220, 0, 0, 0.25); } .form-inline { display: flex; flex-flow: row wrap; align-items: center; } .form-inline .form-check { width: 100%; } @media (min-width: 576px) { .form-inline label { display: flex; align-items: center; justify-content: center; margin-bottom: 0; } .form-inline .form-group { display: flex; flex: 0 0 auto; flex-flow: row wrap; align-items: center; margin-bottom: 0; } .form-inline .form-control { display: inline-block; width: auto; vertical-align: middle; } .form-inline .form-control-plaintext { display: inline-block; } .form-inline .input-group { width: auto; } .form-inline .form-check { display: flex; align-items: center; justify-content: center; width: auto; padding-left: 0; } .form-inline .form-check-input { position: relative; margin-top: 0; margin-right: 0.25rem; margin-left: 0; } .form-inline .custom-control { align-items: center; justify-content: center; } .form-inline .custom-control-label { margin-bottom: 0; } } .btn { display: inline-block; font-weight: 500; text-align: center; white-space: nowrap; vertical-align: middle; user-select: none; border: 1px solid transparent; padding: 0.375rem 0.75rem; font-size: 16px; line-height: 1.5; border-radius: 6px; transition: all 0.15s ease-in-out; } .btn:hover, .btn:focus { text-decoration: none; } .btn:focus, .btn.focus { outline: 0; box-shadow: 0; } .btn.disabled, .btn:disabled { opacity: 0.65; } .btn:not(:disabled):not(.disabled) { cursor: pointer; } .btn:not(:disabled):not(.disabled):active, .btn:not(:disabled):not(.disabled).active { background-image: none; } a.btn.disabled, fieldset:disabled a.btn { pointer-events: none; } .btn-link { font-weight: 400; color: #0045b6; background-color: transparent; } .btn-link:hover { color: #00286a; text-decoration: underline; background-color: transparent; border-color: transparent; } .btn-link:focus, .btn-link.focus { text-decoration: underline; border-color: transparent; box-shadow: none; } .btn-link:disabled, .btn-link.disabled { color: #868e96; } .btn-lg, .btn-group-lg > .btn { padding: 0.5rem 1rem; font-size: 20px; line-height: 1.5; border-radius: 6px; } .btn-sm, .btn-group-sm > .btn { padding: 0.25rem 0.5rem; font-size: 12px; line-height: 1.5; border-radius: 0.2rem; } .btn-block { display: block; width: 100%; } .btn-block + .btn-block { margin-top: 0.5rem; } input[type="submit"].btn-block, input[type="reset"].btn-block, input[type="button"].btn-block { width: 100%; } .fade { opacity: 0; transition: opacity 0.15s linear; } .fade.show { opacity: 1; } .collapse { display: none; } .collapse.show { display: block; } tr.collapse.show { display: table-row; } tbody.collapse.show { display: table-row-group; } .collapsing { position: relative; height: 0; overflow: hidden; transition: height 0.35s ease; } .dropup, .dropdown { position: relative; } .dropdown-toggle::after { display: inline-block; width: 0; height: 0; margin-left: 0.255em; vertical-align: 0.255em; content: ""; border-top: 0.3em solid; border-right: 0.3em solid transparent; border-bottom: 0; border-left: 0.3em solid transparent; } .dropdown-toggle:empty::after { margin-left: 0; } .dropdown-menu { position: absolute; top: 100%; left: 0; z-index: 1000; display: none; float: left; min-width: 10rem; padding: 0.5rem 0; margin: 0.125rem 0 0; font-size: 16px; color: #74757f; text-align: left; list-style: none; background-color: #fff; background-clip: padding-box; border: 1px solid rgba(0, 0, 0, 0.15); border-radius: 0.25rem; } .dropup .dropdown-menu { margin-top: 0; margin-bottom: 0.125rem; } .dropup .dropdown-toggle::after { display: inline-block; width: 0; height: 0; margin-left: 0.255em; vertical-align: 0.255em; content: ""; border-top: 0; border-right: 0.3em solid transparent; border-bottom: 0.3em solid; border-left: 0.3em solid transparent; } .dropup .dropdown-toggle:empty::after { margin-left: 0; } .dropright .dropdown-menu { margin-top: 0; margin-left: 0.125rem; } .dropright .dropdown-toggle::after { display: inline-block; width: 0; height: 0; margin-left: 0.255em; vertical-align: 0.255em; content: ""; border-top: 0.3em solid transparent; border-bottom: 0.3em solid transparent; border-left: 0.3em solid; } .dropright .dropdown-toggle:empty::after { margin-left: 0; } .dropright .dropdown-toggle::after { vertical-align: 0; } .dropleft .dropdown-menu { margin-top: 0; margin-right: 0.125rem; } .dropleft .dropdown-toggle::after { display: inline-block; width: 0; height: 0; margin-left: 0.255em; vertical-align: 0.255em; content: ""; } .dropleft .dropdown-toggle::after { display: none; } .dropleft .dropdown-toggle::before { display: inline-block; width: 0; height: 0; margin-right: 0.255em; vertical-align: 0.255em; content: ""; border-top: 0.3em solid transparent; border-right: 0.3em solid; border-bottom: 0.3em solid transparent; } .dropleft .dropdown-toggle:empty::after { margin-left: 0; } .dropleft .dropdown-toggle::before { vertical-align: 0; } .dropdown-divider { height: 0; margin: 0.5rem 0; overflow: hidden; border-top: 1px solid #e9ecef; } .dropdown-item { display: block; width: 100%; padding: 0.25rem 1.5rem; clear: both; font-weight: 400; color: #212529; text-align: inherit; white-space: nowrap; background-color: transparent; border: 0; } .dropdown-item:hover, .dropdown-item:focus { color: #16181b; text-decoration: none; background-color: #f8f9fa; } .dropdown-item.active, .dropdown-item:active { color: #fff; text-decoration: none; background-color: #0045b6; } .dropdown-item.disabled, .dropdown-item:disabled { color: #6c757d; background-color: transparent; } .dropdown-menu.show { display: block; } .dropdown-header { display: block; padding: 0.5rem 1.5rem; margin-bottom: 0; font-size: 12px; color: #6c757d; white-space: nowrap; } .btn-group, .btn-group-vertical { position: relative; display: inline-flex; vertical-align: middle; } .btn-group > .btn, .btn-group-vertical > .btn { position: relative; flex: 0 1 auto; } .btn-group > .btn:hover, .btn-group-vertical > .btn:hover { z-index: 1; } .btn-group > .btn:focus, .btn-group > .btn:active, .btn-group > .btn.active, .btn-group-vertical > .btn:focus, .btn-group-vertical > .btn:active, .btn-group-vertical > .btn.active { z-index: 1; } .btn-group .btn + .btn, .btn-group .btn + .btn-group, .btn-group .btn-group + .btn, .btn-group .btn-group + .btn-group, .btn-group-vertical .btn + .btn, .btn-group-vertical .btn + .btn-group, .btn-group-vertical .btn-group + .btn, .btn-group-vertical .btn-group + .btn-group { margin-left: -1px; } .btn-toolbar { display: flex; flex-wrap: wrap; justify-content: flex-start; } .btn-toolbar .input-group { width: auto; } .btn-group > .btn:first-child { margin-left: 0; } .btn-group > .btn:not(:last-child):not(.dropdown-toggle), .btn-group > .btn-group:not(:last-child) > .btn { border-top-right-radius: 0; border-bottom-right-radius: 0; } .btn-group > .btn:not(:first-child), .btn-group > .btn-group:not(:first-child) > .btn { border-top-left-radius: 0; border-bottom-left-radius: 0; } .dropdown-toggle-split { padding-right: 0.5625rem; padding-left: 0.5625rem; } .dropdown-toggle-split::after { margin-left: 0; } .btn-sm + .dropdown-toggle-split, .btn-group-sm > .btn + .dropdown-toggle-split { padding-right: 0.375rem; padding-left: 0.375rem; } .btn-lg + .dropdown-toggle-split, .btn-group-lg > .btn + .dropdown-toggle-split { padding-right: 0.75rem; padding-left: 0.75rem; } .btn-group-vertical { flex-direction: column; align-items: flex-start; justify-content: center; } .btn-group-vertical .btn, .btn-group-vertical .btn-group { width: 100%; } .btn-group-vertical > .btn + .btn, .btn-group-vertical > .btn + .btn-group, .btn-group-vertical > .btn-group + .btn, .btn-group-vertical > .btn-group + .btn-group { margin-top: -1px; margin-left: 0; } .btn-group-vertical > .btn:not(:last-child):not(.dropdown-toggle), .btn-group-vertical > .btn-group:not(:last-child) > .btn { border-bottom-right-radius: 0; border-bottom-left-radius: 0; } .btn-group-vertical > .btn:not(:first-child), .btn-group-vertical > .btn-group:not(:first-child) > .btn { border-top-left-radius: 0; border-top-right-radius: 0; } .btn-group-toggle > .btn, .btn-group-toggle > .btn-group > .btn { margin-bottom: 0; } .btn-group-toggle > .btn input[type="radio"], .btn-group-toggle > .btn input[type="checkbox"], .btn-group-toggle > .btn-group > .btn input[type="radio"], .btn-group-toggle > .btn-group > .btn input[type="checkbox"] { position: absolute; clip: rect(0, 0, 0, 0); pointer-events: none; } .input-group { position: relative; display: flex; flex-wrap: wrap; align-items: stretch; width: 100%; } .input-group > .form-control, .input-group > .custom-select, .input-group > .custom-file { position: relative; flex: 1 1 auto; width: 1%; margin-bottom: 0; } .input-group > .form-control:focus, .input-group > .custom-select:focus, .input-group > .custom-file:focus { z-index: 3; } .input-group > .form-control + .form-control, .input-group > .form-control + .custom-select, .input-group > .form-control + .custom-file, .input-group > .custom-select + .form-control, .input-group > .custom-select + .custom-select, .input-group > .custom-select + .custom-file, .input-group > .custom-file + .form-control, .input-group > .custom-file + .custom-select, .input-group > .custom-file + .custom-file { margin-left: -1px; } .input-group > .form-control:not(:last-child), .input-group > .custom-select:not(:last-child) { border-top-right-radius: 0; border-bottom-right-radius: 0; } .input-group > .form-control:not(:first-child), .input-group > .custom-select:not(:first-child) { border-top-left-radius: 0; border-bottom-left-radius: 0; } .input-group > .custom-file { display: flex; align-items: center; } .input-group > .custom-file:not(:last-child) .custom-file-label, .input-group > .custom-file:not(:last-child) .custom-file-label::before { border-top-right-radius: 0; border-bottom-right-radius: 0; } .input-group > .custom-file:not(:first-child) .custom-file-label, .input-group > .custom-file:not(:first-child) .custom-file-label::before { border-top-left-radius: 0; border-bottom-left-radius: 0; } .input-group-prepend, .input-group-append { display: flex; } .input-group-prepend .btn, .input-group-append .btn { position: relative; z-index: 2; } .input-group-prepend .btn + .btn, .input-group-prepend .btn + .input-group-text, .input-group-prepend .input-group-text + .input-group-text, .input-group-prepend .input-group-text + .btn, .input-group-append .btn + .btn, .input-group-append .btn + .input-group-text, .input-group-append .input-group-text + .input-group-text, .input-group-append .input-group-text + .btn { margin-left: -1px; } .input-group-prepend { margin-right: -1px; } .input-group-append { margin-left: -1px; } .input-group-text { display: flex; align-items: center; padding: 0.375rem 0.75rem; margin-bottom: 0; font-size: 16px; font-weight: 400; line-height: 1.5; color: #495057; text-align: center; white-space: nowrap; background-color: #e9ecef; border: 1px solid #ced4da; border-radius: 0.25rem; } .input-group-text input[type="radio"], .input-group-text input[type="checkbox"] { margin-top: 0; } .input-group > .input-group-prepend > .btn, .input-group > .input-group-prepend > .input-group-text, .input-group > .input-group-append:not(:last-child) > .btn, .input-group > .input-group-append:not(:last-child) > .input-group-text, .input-group > .input-group-append:last-child > .btn:not(:last-child):not(.dropdown-toggle), .input-group > .input-group-append:last-child > .input-group-text:not(:last-child) { border-top-right-radius: 0; border-bottom-right-radius: 0; } .input-group > .input-group-append > .btn, .input-group > .input-group-append > .input-group-text, .input-group > .input-group-prepend:not(:first-child) > .btn, .input-group > .input-group-prepend:not(:first-child) > .input-group-text, .input-group > .input-group-prepend:first-child > .btn:not(:first-child), .input-group > .input-group-prepend:first-child > .input-group-text:not(:first-child) { border-top-left-radius: 0; border-bottom-left-radius: 0; } .custom-control { position: relative; display: block; min-height: 1.5rem; padding-left: 1.5rem; } .custom-control-inline { display: inline-flex; margin-right: 1rem; } .custom-control-input { position: absolute; z-index: -1; opacity: 0; } .custom-control-input:checked ~ .custom-control-label::before { color: #fff; background-color: #0045b6; } .custom-control-input:focus ~ .custom-control-label::before { box-shadow: 0 0 0 1px #fff, 0 0 0 0.2rem rgba(0, 69, 182, 0.25); } .custom-control-input:active ~ .custom-control-label::before { color: #fff; background-color: #6aa2ff; } .custom-control-input:disabled ~ .custom-control-label { color: #6c757d; } .custom-control-input:disabled ~ .custom-control-label::before { background-color: #e9ecef; } .custom-control-label { margin-bottom: 0; } .custom-control-label::before { position: absolute; top: 0.25rem; left: 0; display: block; width: 1rem; height: 1rem; pointer-events: none; content: ""; user-select: none; background-color: #dee2e6; } .custom-control-label::after { position: absolute; top: 0.25rem; left: 0; display: block; width: 1rem; height: 1rem; content: ""; background-repeat: no-repeat; background-position: center center; background-size: 50% 50%; } .custom-checkbox .custom-control-label::before { border-radius: 0.25rem; } .custom-checkbox .custom-control-input:checked ~ .custom-control-label::before { background-color: #0045b6; } .custom-checkbox .custom-control-input:checked ~ .custom-control-label::after { background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3E%3Cpath fill='%23fff' d='M6.564.75l-3.59 3.612-1.538-1.55L0 4.26 2.974 7.25 8 2.193z'/%3E%3C/svg%3E"); } .custom-checkbox .custom-control-input:indeterminate ~ .custom-control-label::before { background-color: #0045b6; } .custom-checkbox .custom-control-input:indeterminate ~ .custom-control-label::after { background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 4'%3E%3Cpath stroke='%23fff' d='M0 2h4'/%3E%3C/svg%3E"); } .custom-checkbox .custom-control-input:disabled:checked ~ .custom-control-label::before { background-color: rgba(0, 69, 182, 0.5); } .custom-checkbox .custom-control-input:disabled:indeterminate ~ .custom-control-label::before { background-color: rgba(0, 69, 182, 0.5); } .custom-radio .custom-control-label::before { border-radius: 50%; } .custom-radio .custom-control-input:checked ~ .custom-control-label::before { background-color: #0045b6; } .custom-radio .custom-control-input:checked ~ .custom-control-label::after { background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3E%3Ccircle r='3' fill='%23fff'/%3E%3C/svg%3E"); } .custom-radio .custom-control-input:disabled:checked ~ .custom-control-label::before { background-color: rgba(0, 69, 182, 0.5); } .custom-select { display: inline-block; width: 100%; height: calc(2.25rem + 2px); padding: 0.375rem 1.75rem 0.375rem 0.75rem; line-height: 1.5; color: #495057; vertical-align: middle; background: #fff url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'%3E%3Cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3E%3C/svg%3E") no-repeat right 0.75rem center; background-size: 8px 10px; border: 1px solid #ced4da; border-radius: 0.25rem; appearance: none; } .custom-select:focus { border-color: #3783ff; outline: 0; box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.075), 0 0 5px rgba(55, 131, 255, 0.5); } .custom-select:focus::-ms-value { color: #495057; background-color: #fff; } .custom-select[multiple], .custom-select[size]:not([size="1"]) { height: auto; padding-right: 0.75rem; background-image: none; } .custom-select:disabled { color: #6c757d; background-color: #e9ecef; } .custom-select::-ms-expand { opacity: 0; } .custom-select-sm { height: calc(1.8125rem + 2px); padding-top: 0.375rem; padding-bottom: 0.375rem; font-size: 75%; } .custom-select-lg { height: calc(2.875rem + 2px); padding-top: 0.375rem; padding-bottom: 0.375rem; font-size: 125%; } .custom-file { position: relative; display: inline-block; width: 100%; height: calc(2.25rem + 2px); margin-bottom: 0; } .custom-file-input { position: relative; z-index: 2; width: 100%; height: calc(2.25rem + 2px); margin: 0; opacity: 0; } .custom-file-input:focus ~ .custom-file-control { border-color: #3783ff; box-shadow: 0 0 0 0.2rem rgba(0, 69, 182, 0.25); } .custom-file-input:focus ~ .custom-file-control::before { border-color: #3783ff; } .custom-file-input:lang(en) ~ .custom-file-label::after { content: "Browse"; } .custom-file-label { position: absolute; top: 0; right: 0; left: 0; z-index: 1; height: calc(2.25rem + 2px); padding: 0.375rem 0.75rem; line-height: 1.5; color: #495057; background-color: #fff; border: 1px solid #ced4da; border-radius: 0.25rem; } .custom-file-label::after { position: absolute; top: 0; right: 0; bottom: 0; z-index: 3; display: block; height: calc(calc(2.25rem + 2px) - 1px * 2); padding: 0.375rem 0.75rem; line-height: 1.5; color: #495057; content: "Browse"; background-color: #e9ecef; border-left: 1px solid #ced4da; border-radius: 0 0.25rem 0.25rem 0; } .nav { display: flex; flex-wrap: wrap; padding-left: 0; margin-bottom: 0; list-style: none; } .nav-link { display: block; padding: 0.5rem 1rem; } .nav-link:hover, .nav-link:focus { text-decoration: none; } .nav-link.disabled { color: #6c757d; } .nav-tabs { border-bottom: 1px solid #dee2e6; } .nav-tabs .nav-item { margin-bottom: -1px; } .nav-tabs .nav-link { border: 1px solid transparent; border-top-left-radius: 0.25rem; border-top-right-radius: 0.25rem; } .nav-tabs .nav-link:hover, .nav-tabs .nav-link:focus { border-color: #e9ecef #e9ecef #dee2e6; } .nav-tabs .nav-link.disabled { color: #6c757d; background-color: transparent; border-color: transparent; } .nav-tabs .nav-link.active, .nav-tabs .nav-item.show .nav-link { color: #495057; background-color: #fff; border-color: #dee2e6 #dee2e6 #fff; } .nav-tabs .dropdown-menu { margin-top: -1px; border-top-left-radius: 0; border-top-right-radius: 0; } .nav-pills .nav-link { border-radius: 0.25rem; } .nav-pills .nav-link.active, .nav-pills .show > .nav-link { color: #fff; background-color: #0045b6; } .nav-fill .nav-item { flex: 1 1 auto; text-align: center; } .nav-justified .nav-item { flex-basis: 0; flex-grow: 1; text-align: center; } .tab-content > .tab-pane { display: none; } .tab-content > .active { display: block; } .navbar { position: relative; display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; padding: 0.5rem 1rem; } .navbar > .container, .navbar > .container-fluid { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; } .navbar-brand { display: inline-block; padding-top: 0.3125rem; padding-bottom: 0.3125rem; margin-right: 1rem; font-size: 1.25rem; line-height: inherit; white-space: nowrap; } .navbar-brand:hover, .navbar-brand:focus { text-decoration: none; } .navbar-nav { display: flex; flex-direction: column; padding-left: 0; margin-bottom: 0; list-style: none; } .navbar-nav .nav-link { padding-right: 0; padding-left: 0; } .navbar-nav .dropdown-menu { position: static; float: none; } .navbar-text { display: inline-block; padding-top: 0.5rem; padding-bottom: 0.5rem; } .navbar-collapse { flex-basis: 100%; flex-grow: 1; align-items: center; } .navbar-toggler { padding: 0.25rem 0.75rem; font-size: 1.25rem; line-height: 1; background-color: transparent; border: 1px solid transparent; border-radius: 0.25rem; } .navbar-toggler:hover, .navbar-toggler:focus { text-decoration: none; } .navbar-toggler:not(:disabled):not(.disabled) { cursor: pointer; } .navbar-toggler-icon { display: inline-block; width: 1.5em; height: 1.5em; vertical-align: middle; content: ""; background: no-repeat center center; background-size: 100% 100%; } @media (max-width: 575.98px) { .navbar-expand-sm > .container, .navbar-expand-sm > .container-fluid { padding-right: 0; padding-left: 0; } } @media (min-width: 576px) { .navbar-expand-sm { flex-flow: row nowrap; justify-content: flex-start; } .navbar-expand-sm .navbar-nav { flex-direction: row; } .navbar-expand-sm .navbar-nav .dropdown-menu { position: absolute; } .navbar-expand-sm .navbar-nav .dropdown-menu-right { right: 0; left: auto; } .navbar-expand-sm .navbar-nav .nav-link { padding-right: 0.5rem; padding-left: 0.5rem; } .navbar-expand-sm > .container, .navbar-expand-sm > .container-fluid { flex-wrap: nowrap; } .navbar-expand-sm .navbar-collapse { display: flex !important; flex-basis: auto; } .navbar-expand-sm .navbar-toggler { display: none; } .navbar-expand-sm .dropup .dropdown-menu { top: auto; bottom: 100%; } } @media (max-width: 767.98px) { .navbar-expand-md > .container, .navbar-expand-md > .container-fluid { padding-right: 0; padding-left: 0; } } @media (min-width: 768px) { .navbar-expand-md { flex-flow: row nowrap; justify-content: flex-start; } .navbar-expand-md .navbar-nav { flex-direction: row; } .navbar-expand-md .navbar-nav .dropdown-menu { position: absolute; } .navbar-expand-md .navbar-nav .dropdown-menu-right { right: 0; left: auto; } .navbar-expand-md .navbar-nav .nav-link { padding-right: 0.5rem; padding-left: 0.5rem; } .navbar-expand-md > .container, .navbar-expand-md > .container-fluid { flex-wrap: nowrap; } .navbar-expand-md .navbar-collapse { display: flex !important; flex-basis: auto; } .navbar-expand-md .navbar-toggler { display: none; } .navbar-expand-md .dropup .dropdown-menu { top: auto; bottom: 100%; } } @media (max-width: 991.98px) { .navbar-expand-lg > .container, .navbar-expand-lg > .container-fluid { padding-right: 0; padding-left: 0; } } @media (min-width: 992px) { .navbar-expand-lg { flex-flow: row nowrap; justify-content: flex-start; } .navbar-expand-lg .navbar-nav { flex-direction: row; } .navbar-expand-lg .navbar-nav .dropdown-menu { position: absolute; } .navbar-expand-lg .navbar-nav .dropdown-menu-right { right: 0; left: auto; } .navbar-expand-lg .navbar-nav .nav-link { padding-right: 0.5rem; padding-left: 0.5rem; } .navbar-expand-lg > .container, .navbar-expand-lg > .container-fluid { flex-wrap: nowrap; } .navbar-expand-lg .navbar-collapse { display: flex !important; flex-basis: auto; } .navbar-expand-lg .navbar-toggler { display: none; } .navbar-expand-lg .dropup .dropdown-menu { top: auto; bottom: 100%; } } @media (max-width: 1199.98px) { .navbar-expand-xl > .container, .navbar-expand-xl > .container-fluid { padding-right: 0; padding-left: 0; } } @media (min-width: 1200px) { .navbar-expand-xl { flex-flow: row nowrap; justify-content: flex-start; } .navbar-expand-xl .navbar-nav { flex-direction: row; } .navbar-expand-xl .navbar-nav .dropdown-menu { position: absolute; } .navbar-expand-xl .navbar-nav .dropdown-menu-right { right: 0; left: auto; } .navbar-expand-xl .navbar-nav .nav-link { padding-right: 0.5rem; padding-left: 0.5rem; } .navbar-expand-xl > .container, .navbar-expand-xl > .container-fluid { flex-wrap: nowrap; } .navbar-expand-xl .navbar-collapse { display: flex !important; flex-basis: auto; } .navbar-expand-xl .navbar-toggler { display: none; } .navbar-expand-xl .dropup .dropdown-menu { top: auto; bottom: 100%; } } @media (max-width: 1599.98px) { .navbar-expand-xxl > .container, .navbar-expand-xxl > .container-fluid { padding-right: 0; padding-left: 0; } } @media (min-width: 1600px) { .navbar-expand-xxl { flex-flow: row nowrap; justify-content: flex-start; } .navbar-expand-xxl .navbar-nav { flex-direction: row; } .navbar-expand-xxl .navbar-nav .dropdown-menu { position: absolute; } .navbar-expand-xxl .navbar-nav .dropdown-menu-right { right: 0; left: auto; } .navbar-expand-xxl .navbar-nav .nav-link { padding-right: 0.5rem; padding-left: 0.5rem; } .navbar-expand-xxl > .container, .navbar-expand-xxl > .container-fluid { flex-wrap: nowrap; } .navbar-expand-xxl .navbar-collapse { display: flex !important; flex-basis: auto; } .navbar-expand-xxl .navbar-toggler { display: none; } .navbar-expand-xxl .dropup .dropdown-menu { top: auto; bottom: 100%; } } .navbar-expand { flex-flow: row nowrap; justify-content: flex-start; } .navbar-expand > .container, .navbar-expand > .container-fluid { padding-right: 0; padding-left: 0; } .navbar-expand .navbar-nav { flex-direction: row; } .navbar-expand .navbar-nav .dropdown-menu { position: absolute; } .navbar-expand .navbar-nav .dropdown-menu-right { right: 0; left: auto; } .navbar-expand .navbar-nav .nav-link { padding-right: 0.5rem; padding-left: 0.5rem; } .navbar-expand > .container, .navbar-expand > .container-fluid { flex-wrap: nowrap; } .navbar-expand .navbar-collapse { display: flex !important; flex-basis: auto; } .navbar-expand .navbar-toggler { display: none; } .navbar-expand .dropup .dropdown-menu { top: auto; bottom: 100%; } .navbar-light .navbar-brand { color: rgba(0, 0, 0, 0.9); } .navbar-light .navbar-brand:hover, .navbar-light .navbar-brand:focus { color: rgba(0, 0, 0, 0.9); } .navbar-light .navbar-nav .nav-link { color: rgba(0, 0, 0, 0.5); } .navbar-light .navbar-nav .nav-link:hover, .navbar-light .navbar-nav .nav-link:focus { color: rgba(0, 0, 0, 0.7); } .navbar-light .navbar-nav .nav-link.disabled { color: rgba(0, 0, 0, 0.3); } .navbar-light .navbar-nav .show > .nav-link, .navbar-light .navbar-nav .active > .nav-link, .navbar-light .navbar-nav .nav-link.show, .navbar-light .navbar-nav .nav-link.active { color: rgba(0, 0, 0, 0.9); } .navbar-light .navbar-toggler { color: rgba(0, 0, 0, 0.5); border-color: rgba(0, 0, 0, 0.1); } .navbar-light .navbar-toggler-icon { background-image: url("data:image/svg+xml;charset=utf8,%3Csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke='rgba(0, 0, 0, 0.5)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E"); } .navbar-light .navbar-text { color: rgba(0, 0, 0, 0.5); } .navbar-light .navbar-text a { color: rgba(0, 0, 0, 0.9); } .navbar-light .navbar-text a:hover, .navbar-light .navbar-text a:focus { color: rgba(0, 0, 0, 0.9); } .navbar-dark .navbar-brand { color: #fff; } .navbar-dark .navbar-brand:hover, .navbar-dark .navbar-brand:focus { color: #fff; } .navbar-dark .navbar-nav .nav-link { color: rgba(255, 255, 255, 0.5); } .navbar-dark .navbar-nav .nav-link:hover, .navbar-dark .navbar-nav .nav-link:focus { color: rgba(255, 255, 255, 0.75); } .navbar-dark .navbar-nav .nav-link.disabled { color: rgba(255, 255, 255, 0.25); } .navbar-dark .navbar-nav .show > .nav-link, .navbar-dark .navbar-nav .active > .nav-link, .navbar-dark .navbar-nav .nav-link.show, .navbar-dark .navbar-nav .nav-link.active { color: #fff; } .navbar-dark .navbar-toggler { color: rgba(255, 255, 255, 0.5); border-color: rgba(255, 255, 255, 0.1); } .navbar-dark .navbar-toggler-icon { background-image: url("data:image/svg+xml;charset=utf8,%3Csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke='rgba(255, 255, 255, 0.5)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E"); } .navbar-dark .navbar-text { color: rgba(255, 255, 255, 0.5); } .navbar-dark .navbar-text a { color: #fff; } .navbar-dark .navbar-text a:hover, .navbar-dark .navbar-text a:focus { color: #fff; } .card { position: relative; display: flex; flex-direction: column; min-width: 0; word-wrap: break-word; background-color: #fff; background-clip: border-box; border: 1px solid rgba(0, 0, 0, 0.125); border-radius: 0.25rem; } .card > hr { margin-right: 0; margin-left: 0; } .card > .list-group:first-child .list-group-item:first-child { border-top-left-radius: 0.25rem; border-top-right-radius: 0.25rem; } .card > .list-group:last-child .list-group-item:last-child { border-bottom-right-radius: 0.25rem; border-bottom-left-radius: 0.25rem; } .card-body { flex: 1 1 auto; padding: 1.25rem; } .card-title { margin-bottom: 0.75rem; } .card-subtitle { margin-top: -0.375rem; margin-bottom: 0; } .card-text:last-child { margin-bottom: 0; } .card-link:hover { text-decoration: none; } .card-link + .card-link { margin-left: 1.25rem; } .card-header { padding: 0.75rem 1.25rem; margin-bottom: 0; background-color: rgba(0, 0, 0, 0.03); border-bottom: 1px solid rgba(0, 0, 0, 0.125); } .card-header:first-child { border-radius: calc(0.25rem - 1px) calc(0.25rem - 1px) 0 0; } .card-header + .list-group .list-group-item:first-child { border-top: 0; } .card-footer { padding: 0.75rem 1.25rem; background-color: rgba(0, 0, 0, 0.03); border-top: 1px solid rgba(0, 0, 0, 0.125); } .card-footer:last-child { border-radius: 0 0 calc(0.25rem - 1px) calc(0.25rem - 1px); } .card-header-tabs { margin-right: -0.625rem; margin-bottom: -0.75rem; margin-left: -0.625rem; border-bottom: 0; } .card-header-pills { margin-right: -0.625rem; margin-left: -0.625rem; } .card-img-overlay { position: absolute; top: 0; right: 0; bottom: 0; left: 0; padding: 1.25rem; } .card-img { width: 100%; border-radius: calc(0.25rem - 1px); } .card-img-top { width: 100%; border-top-left-radius: calc(0.25rem - 1px); border-top-right-radius: calc(0.25rem - 1px); } .card-img-bottom { width: 100%; border-bottom-right-radius: calc(0.25rem - 1px); border-bottom-left-radius: calc(0.25rem - 1px); } .card-deck { display: flex; flex-direction: column; } .card-deck .card { margin-bottom: 15px; } @media (min-width: 576px) { .card-deck { flex-flow: row wrap; margin-right: -15px; margin-left: -15px; } .card-deck .card { display: flex; flex: 1 0 0%; flex-direction: column; margin-right: 15px; margin-bottom: 0; margin-left: 15px; } } .card-group { display: flex; flex-direction: column; } .card-group > .card { margin-bottom: 15px; } @media (min-width: 576px) { .card-group { flex-flow: row wrap; } .card-group > .card { flex: 1 0 0%; margin-bottom: 0; } .card-group > .card + .card { margin-left: 0; border-left: 0; } .card-group > .card:first-child { border-top-right-radius: 0; border-bottom-right-radius: 0; } .card-group > .card:first-child .card-img-top, .card-group > .card:first-child .card-header { border-top-right-radius: 0; } .card-group > .card:first-child .card-img-bottom, .card-group > .card:first-child .card-footer { border-bottom-right-radius: 0; } .card-group > .card:last-child { border-top-left-radius: 0; border-bottom-left-radius: 0; } .card-group > .card:last-child .card-img-top, .card-group > .card:last-child .card-header { border-top-left-radius: 0; } .card-group > .card:last-child .card-img-bottom, .card-group > .card:last-child .card-footer { border-bottom-left-radius: 0; } .card-group > .card:only-child { border-radius: 0.25rem; } .card-group > .card:only-child .card-img-top, .card-group > .card:only-child .card-header { border-top-left-radius: 0.25rem; border-top-right-radius: 0.25rem; } .card-group > .card:only-child .card-img-bottom, .card-group > .card:only-child .card-footer { border-bottom-right-radius: 0.25rem; border-bottom-left-radius: 0.25rem; } .card-group > .card:not(:first-child):not(:last-child):not(:only-child) { border-radius: 0; } .card-group > .card:not(:first-child):not(:last-child):not(:only-child) .card-img-top, .card-group > .card:not(:first-child):not(:last-child):not(:only-child) .card-img-bottom, .card-group > .card:not(:first-child):not(:last-child):not(:only-child) .card-header, .card-group > .card:not(:first-child):not(:last-child):not(:only-child) .card-footer { border-radius: 0; } } .card-columns .card { margin-bottom: 0.75rem; } @media (min-width: 576px) { .card-columns { column-count: 3; column-gap: 1.25rem; } .card-columns .card { display: inline-block; width: 100%; } } .breadcrumb { display: flex; flex-wrap: wrap; padding: 0.75rem 1rem; margin-bottom: 1rem; list-style: none; background-color: #e9ecef; border-radius: 5; } .breadcrumb-item + .breadcrumb-item::before { display: inline-block; padding-right: 0.5rem; padding-left: 0.5rem; color: #6c757d; content: "/"; } .breadcrumb-item + .breadcrumb-item:hover::before { text-decoration: underline; } .breadcrumb-item + .breadcrumb-item:hover::before { text-decoration: none; } .breadcrumb-item.active { color: #6c757d; } .pagination { display: flex; padding-left: 0; list-style: none; border-radius: 5; } .page-link { position: relative; display: block; padding: 10px 10px; margin-left: 0; line-height: 24px; color: #fff; background-color: #0045b6; border: 0 solid #e8e9ee; } .page-link:hover { color: #fff; text-decoration: none; background-color: #343db0; border-color: #0045b6; } .page-link:focus { z-index: 2; outline: 0; } .page-link:not(:disabled):not(.disabled) { cursor: pointer; } .page-item:first-child .page-link { margin-left: 0; border-top-left-radius: 5; border-bottom-left-radius: 5; } .page-item:last-child .page-link { border-top-right-radius: 5; border-bottom-right-radius: 5; } .page-item.active .page-link { z-index: 1; color: #fff; background-color: #343db0; border-color: #0045b6; } .page-item.disabled .page-link { color: #74757f; pointer-events: none; cursor: auto; background-color: #e8e9ee; border-color: #e8e9ee; } .pagination-lg .page-link { padding: 0.75rem 1.5rem; font-size: 20px; line-height: 1.6; } .pagination-lg .page-item:first-child .page-link { border-top-left-radius: 6px; border-bottom-left-radius: 6px; } .pagination-lg .page-item:last-child .page-link { border-top-right-radius: 6px; border-bottom-right-radius: 6px; } .pagination-sm .page-link { padding: 0.25rem 0.5rem; font-size: 12px; line-height: 1.5; } .pagination-sm .page-item:first-child .page-link { border-top-left-radius: 0.2rem; border-bottom-left-radius: 0.2rem; } .pagination-sm .page-item:last-child .page-link { border-top-right-radius: 0.2rem; border-bottom-right-radius: 0.2rem; } .badge { display: inline-block; padding: 0.25em 0.4em; font-size: 75%; font-weight: 700; line-height: 1; text-align: center; white-space: nowrap; vertical-align: baseline; border-radius: 0.25rem; } .badge:empty { display: none; } .btn .badge { position: relative; top: -1px; } .badge-pill { padding-right: 0.6em; padding-left: 0.6em; border-radius: 10rem; } .jumbotron { padding: 2rem 1rem; margin-bottom: 2rem; background-color: #e9ecef; border-radius: 6px; } @media (min-width: 576px) { .jumbotron { padding: 4rem 2rem; } } .jumbotron-fluid { padding-right: 0; padding-left: 0; border-radius: 0; } .alert { position: relative; padding: 0.75rem 1.25rem; margin-bottom: 1rem; border: 1px solid transparent; border-radius: 0.25rem; } .alert-heading { color: inherit; } .alert-link { font-weight: 700; } .alert-dismissible { padding-right: 4rem; } .alert-dismissible .close { position: absolute; top: 0; right: 0; padding: 0.75rem 1.25rem; color: inherit; } @keyframes progress-bar-stripes { from { background-position: 1rem 0; } to { background-position: 0 0; } } .progress { display: flex; height: 1rem; overflow: hidden; font-size: 0.75rem; background-color: #e9ecef; border-radius: 0.25rem; } .progress-bar { display: flex; flex-direction: column; justify-content: center; color: #fff; text-align: center; background-color: #0045b6; transition: width 0.6s ease; } .progress-bar-striped { background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); background-size: 1rem 1rem; } .progress-bar-animated { animation: progress-bar-stripes 1s linear infinite; } .media { display: flex; align-items: flex-start; } .media-body { flex: 1; } .list-group { display: flex; flex-direction: column; padding-left: 0; margin-bottom: 0; } .list-group-item-action { width: 100%; color: #495057; text-align: inherit; } .list-group-item-action:hover, .list-group-item-action:focus { color: #495057; text-decoration: none; background-color: #f8f9fa; } .list-group-item-action:active { color: #212529; background-color: #e9ecef; } .list-group-item { position: relative; display: block; padding: 0.75rem 1.25rem; margin-bottom: -1px; background-color: #fff; border: 1px solid rgba(0, 0, 0, 0.125); } .list-group-item:first-child { border-top-left-radius: 0.25rem; border-top-right-radius: 0.25rem; } .list-group-item:last-child { margin-bottom: 0; border-bottom-right-radius: 0.25rem; border-bottom-left-radius: 0.25rem; } .list-group-item:hover, .list-group-item:focus { z-index: 1; text-decoration: none; } .list-group-item.disabled, .list-group-item:disabled { color: #6c757d; background-color: #fff; } .list-group-item.active { z-index: 2; color: #fff; background-color: #0045b6; border-color: #0045b6; } .list-group-flush .list-group-item { border-right: 0; border-left: 0; border-radius: 0; } .list-group-flush:first-child .list-group-item:first-child { border-top: 0; } .list-group-flush:last-child .list-group-item:last-child { border-bottom: 0; } .close { float: right; font-size: 1.5rem; font-weight: 700; line-height: 1; color: #000; text-shadow: 0 1px 0 #fff; opacity: .5; } .close:hover, .close:focus { color: #000; text-decoration: none; opacity: .75; } .close:not(:disabled):not(.disabled) { cursor: pointer; } button.close { padding: 0; background-color: transparent; border: 0; -webkit-appearance: none; } .modal-open { overflow: hidden; } .modal { position: fixed; top: 0; right: 0; bottom: 0; left: 0; z-index: 1150; display: none; overflow: hidden; outline: 0; } .modal-open .modal { overflow-x: hidden; overflow-y: auto; } .modal-dialog { position: relative; width: auto; margin: 0.5rem; pointer-events: none; } .modal.fade .modal-dialog { transition: transform 0.3s ease-out; transform: translate(0, -25%); } .modal.show .modal-dialog { transform: translate(0, 0); } .modal-dialog-centered { display: flex; align-items: center; min-height: calc(100% - (0.5rem * 2)); } .modal-content { position: relative; display: flex; flex-direction: column; width: 100%; pointer-events: auto; background-color: #fff; background-clip: padding-box; border: 1px solid rgba(0, 0, 0, 0.2); border-radius: 6px; outline: 0; } .modal-backdrop { position: fixed; top: 0; right: 0; bottom: 0; left: 0; z-index: 1140; background-color: #000; } .modal-backdrop.fade { opacity: 0; } .modal-backdrop.show { opacity: 0.5; } .modal-header { display: flex; align-items: flex-start; justify-content: space-between; padding: 1rem; border-bottom: 1px solid #e9ecef; border-top-left-radius: 6px; border-top-right-radius: 6px; } .modal-header .close { padding: 1rem; margin: -1rem -1rem -1rem auto; } .modal-title { margin-bottom: 0; line-height: 1.5; } .modal-body { position: relative; flex: 1 1 auto; padding: 1rem; } .modal-footer { display: flex; align-items: center; justify-content: flex-end; padding: 1rem; border-top: 1px solid #e9ecef; } .modal-footer > :not(:first-child) { margin-left: .25rem; } .modal-footer > :not(:last-child) { margin-right: .25rem; } .modal-scrollbar-measure { position: absolute; top: -9999px; width: 50px; height: 50px; overflow: scroll; } @media (min-width: 576px) { .modal-dialog { max-width: 500px; margin: 1.75rem auto; } .modal-dialog-centered { min-height: calc(100% - (1.75rem * 2)); } .modal-sm { max-width: 300px; } } @media (min-width: 992px) { .modal-lg { max-width: 800px; } } .tooltip { position: absolute; z-index: 1070; display: block; margin: 0; font-family: "Work Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; font-style: normal; font-weight: 400; line-height: 1.5; text-align: left; text-align: start; text-decoration: none; text-shadow: none; text-transform: none; letter-spacing: normal; word-break: normal; word-spacing: normal; white-space: normal; line-break: auto; font-size: 0.875rem; word-wrap: break-word; opacity: 0; } .tooltip.show { opacity: 1; } .tooltip .arrow { position: absolute; display: block; width: 14px; height: 7px; } .tooltip .arrow::before { position: absolute; content: ""; border-color: transparent; border-style: solid; } .bs-tooltip-top, .bs-tooltip-auto[x-placement^="top"] { padding: 7px 0; } .bs-tooltip-top .arrow, .bs-tooltip-auto[x-placement^="top"] .arrow { bottom: 0; } .bs-tooltip-top .arrow::before, .bs-tooltip-auto[x-placement^="top"] .arrow::before { top: 0; border-width: 7px 7px 0; border-top-color: #0045b6; } .bs-tooltip-right, .bs-tooltip-auto[x-placement^="right"] { padding: 0 7px; } .bs-tooltip-right .arrow, .bs-tooltip-auto[x-placement^="right"] .arrow { left: 0; width: 7px; height: 14px; } .bs-tooltip-right .arrow::before, .bs-tooltip-auto[x-placement^="right"] .arrow::before { right: 0; border-width: 7px 7px 7px 0; border-right-color: #0045b6; } .bs-tooltip-bottom, .bs-tooltip-auto[x-placement^="bottom"] { padding: 7px 0; } .bs-tooltip-bottom .arrow, .bs-tooltip-auto[x-placement^="bottom"] .arrow { top: 0; } .bs-tooltip-bottom .arrow::before, .bs-tooltip-auto[x-placement^="bottom"] .arrow::before { bottom: 0; border-width: 0 7px 7px; border-bottom-color: #0045b6; } .bs-tooltip-left, .bs-tooltip-auto[x-placement^="left"] { padding: 0 7px; } .bs-tooltip-left .arrow, .bs-tooltip-auto[x-placement^="left"] .arrow { right: 0; width: 7px; height: 14px; } .bs-tooltip-left .arrow::before, .bs-tooltip-auto[x-placement^="left"] .arrow::before { left: 0; border-width: 7px 0 7px 7px; border-left-color: #0045b6; } .tooltip-inner { max-width: 200px; padding: 6px 10px; color: #fff; text-align: center; background-color: #0045b6; border-radius: 0.25rem; } .popover { position: absolute; top: 0; left: 0; z-index: 1060; display: block; max-width: 276px; font-family: "Work Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; font-style: normal; font-weight: 400; line-height: 1.5; text-align: left; text-align: start; text-decoration: none; text-shadow: none; text-transform: none; letter-spacing: normal; word-break: normal; word-spacing: normal; white-space: normal; line-break: auto; font-size: 0.875rem; word-wrap: break-word; background-color: #fff; background-clip: padding-box; border: 1px solid rgba(0, 0, 0, 0.2); border-radius: 0.3rem; } .popover .arrow { position: absolute; display: block; width: 1rem; height: 0.5rem; margin: 0 6px; } .popover .arrow::before, .popover .arrow::after { position: absolute; display: block; content: ""; border-color: transparent; border-style: solid; } .bs-popover-top, .bs-popover-auto[x-placement^="top"] { margin-bottom: 0.5rem; } .bs-popover-top .arrow, .bs-popover-auto[x-placement^="top"] .arrow { bottom: calc((0.5rem + 1px) * -1); } .bs-popover-top .arrow::before, .bs-popover-auto[x-placement^="top"] .arrow::before, .bs-popover-top .arrow::after, .bs-popover-auto[x-placement^="top"] .arrow::after { border-width: 0.5rem 0.5rem 0; } .bs-popover-top .arrow::before, .bs-popover-auto[x-placement^="top"] .arrow::before { bottom: 0; border-top-color: rgba(0, 0, 0, 0.25); } .bs-popover-top .arrow::after, .bs-popover-auto[x-placement^="top"] .arrow::after { bottom: 1px; border-top-color: #fff; } .bs-popover-right, .bs-popover-auto[x-placement^="right"] { margin-left: 0.5rem; } .bs-popover-right .arrow, .bs-popover-auto[x-placement^="right"] .arrow { left: calc((0.5rem + 1px) * -1); width: 0.5rem; height: 1rem; margin: 6px 0; } .bs-popover-right .arrow::before, .bs-popover-auto[x-placement^="right"] .arrow::before, .bs-popover-right .arrow::after, .bs-popover-auto[x-placement^="right"] .arrow::after { border-width: 0.5rem 0.5rem 0.5rem 0; } .bs-popover-right .arrow::before, .bs-popover-auto[x-placement^="right"] .arrow::before { left: 0; border-right-color: rgba(0, 0, 0, 0.25); } .bs-popover-right .arrow::after, .bs-popover-auto[x-placement^="right"] .arrow::after { left: 1px; border-right-color: #fff; } .bs-popover-bottom, .bs-popover-auto[x-placement^="bottom"] { margin-top: 0.5rem; } .bs-popover-bottom .arrow, .bs-popover-auto[x-placement^="bottom"] .arrow { top: calc((0.5rem + 1px) * -1); } .bs-popover-bottom .arrow::before, .bs-popover-auto[x-placement^="bottom"] .arrow::before, .bs-popover-bottom .arrow::after, .bs-popover-auto[x-placement^="bottom"] .arrow::after { border-width: 0 0.5rem 0.5rem 0.5rem; } .bs-popover-bottom .arrow::before, .bs-popover-auto[x-placement^="bottom"] .arrow::before { top: 0; border-bottom-color: rgba(0, 0, 0, 0.25); } .bs-popover-bottom .arrow::after, .bs-popover-auto[x-placement^="bottom"] .arrow::after { top: 1px; border-bottom-color: #fff; } .bs-popover-bottom .popover-header::before, .bs-popover-auto[x-placement^="bottom"] .popover-header::before { position: absolute; top: 0; left: 50%; display: block; width: 1rem; margin-left: -0.5rem; content: ""; border-bottom: 1px solid #f7f7f7; } .bs-popover-left, .bs-popover-auto[x-placement^="left"] { margin-right: 0.5rem; } .bs-popover-left .arrow, .bs-popover-auto[x-placement^="left"] .arrow { right: calc((0.5rem + 1px) * -1); width: 0.5rem; height: 1rem; margin: 6px 0; } .bs-popover-left .arrow::before, .bs-popover-auto[x-placement^="left"] .arrow::before, .bs-popover-left .arrow::after, .bs-popover-auto[x-placement^="left"] .arrow::after { border-width: 0.5rem 0 0.5rem 0.5rem; } .bs-popover-left .arrow::before, .bs-popover-auto[x-placement^="left"] .arrow::before { right: 0; border-left-color: rgba(0, 0, 0, 0.25); } .bs-popover-left .arrow::after, .bs-popover-auto[x-placement^="left"] .arrow::after { right: 1px; border-left-color: #fff; } .popover-header { padding: 0.5rem 0.75rem; margin-bottom: 0; font-size: 16px; color: #343a40; background-color: #f7f7f7; border-bottom: 1px solid #ebebeb; border-top-left-radius: calc(6px - 1px); border-top-right-radius: calc(6px - 1px); } .popover-header:empty { display: none; } .popover-body { padding: 0.5rem 0.75rem; color: #212529; } .carousel { position: relative; } .carousel-inner { position: relative; width: 100%; overflow: hidden; } .carousel-item { position: relative; display: none; align-items: center; width: 100%; transition: transform 0.6s ease; backface-visibility: hidden; perspective: 1000px; } .carousel-item.active, .carousel-item-next, .carousel-item-prev { display: block; } .carousel-item-next, .carousel-item-prev { position: absolute; top: 0; } .carousel-item-next.carousel-item-left, .carousel-item-prev.carousel-item-right { transform: translateX(0); } @supports (transform-style: preserve-3d) { .carousel-item-next.carousel-item-left, .carousel-item-prev.carousel-item-right { transform: translate3d(0, 0, 0); } } .carousel-item-next, .active.carousel-item-right { transform: translateX(100%); } @supports (transform-style: preserve-3d) { .carousel-item-next, .active.carousel-item-right { transform: translate3d(100%, 0, 0); } } .carousel-item-prev, .active.carousel-item-left { transform: translateX(-100%); } @supports (transform-style: preserve-3d) { .carousel-item-prev, .active.carousel-item-left { transform: translate3d(-100%, 0, 0); } } .carousel-control-prev, .carousel-control-next { position: absolute; top: 0; bottom: 0; display: flex; align-items: center; justify-content: center; width: 15%; color: #fff; text-align: center; opacity: 0.5; } .carousel-control-prev:hover, .carousel-control-prev:focus, .carousel-control-next:hover, .carousel-control-next:focus { color: #fff; text-decoration: none; outline: 0; opacity: .9; } .carousel-control-prev { left: 0; } .carousel-control-next { right: 0; } .carousel-control-prev-icon, .carousel-control-next-icon { display: inline-block; width: 20px; height: 20px; background: transparent no-repeat center center; background-size: 100% 100%; } .carousel-control-prev-icon { background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 8 8'%3E%3Cpath d='M5.25 0l-4 4 4 4 1.5-1.5-2.5-2.5 2.5-2.5-1.5-1.5z'/%3E%3C/svg%3E"); } .carousel-control-next-icon { background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 8 8'%3E%3Cpath d='M2.75 0l-1.5 1.5 2.5 2.5-2.5 2.5 1.5 1.5 4-4-4-4z'/%3E%3C/svg%3E"); } .carousel-indicators { position: absolute; right: 0; bottom: 10px; left: 0; z-index: 15; display: flex; justify-content: center; padding-left: 0; margin-right: 15%; margin-left: 15%; list-style: none; } .carousel-indicators li { position: relative; flex: 0 1 auto; width: 30px; height: 3px; margin-right: 3px; margin-left: 3px; text-indent: -999px; background-color: rgba(255, 255, 255, 0.5); } .carousel-indicators li::before { position: absolute; top: -10px; left: 0; display: inline-block; width: 100%; height: 10px; content: ""; } .carousel-indicators li::after { position: absolute; bottom: -10px; left: 0; display: inline-block; width: 100%; height: 10px; content: ""; } .carousel-indicators .active { background-color: #fff; } .carousel-caption { position: absolute; right: 15%; bottom: 20px; left: 15%; z-index: 10; padding-top: 20px; padding-bottom: 20px; color: #fff; text-align: center; } .align-baseline { vertical-align: baseline !important; } .align-top { vertical-align: top !important; } .align-middle { vertical-align: middle !important; } .align-bottom { vertical-align: bottom !important; } .align-text-bottom { vertical-align: text-bottom !important; } .align-text-top { vertical-align: text-top !important; } .bg-white { background-color: #fff !important; } .bg-transparent { background-color: transparent !important; } .border { border: 1px solid #dee2e6 !important; } .border-top { border-top: 1px solid #dee2e6 !important; } .border-right { border-right: 1px solid #dee2e6 !important; } .border-bottom { border-bottom: 1px solid #dee2e6 !important; } .border-left { border-left: 1px solid #dee2e6 !important; } .border-0 { border: 0 !important; } .border-top-0 { border-top: 0 !important; } .border-right-0 { border-right: 0 !important; } .border-bottom-0 { border-bottom: 0 !important; } .border-left-0 { border-left: 0 !important; } .border-white { border-color: #fff !important; } .rounded { border-radius: 5 !important; } .rounded-top { border-top-left-radius: 5 !important; border-top-right-radius: 5 !important; } .rounded-right { border-top-right-radius: 5 !important; border-bottom-right-radius: 5 !important; } .rounded-bottom { border-bottom-right-radius: 5 !important; border-bottom-left-radius: 5 !important; } .rounded-left { border-top-left-radius: 5 !important; border-bottom-left-radius: 5 !important; } .rounded-circle { border-radius: 50% !important; } .rounded-0 { border-radius: 0 !important; } .clearfix::after { display: block; clear: both; content: ""; } .d-none { display: none !important; } .d-inline { display: inline !important; } .d-inline-block { display: inline-block !important; } .d-block { display: block !important; } .d-table { display: table !important; } .d-table-row { display: table-row !important; } .d-table-cell { display: table-cell !important; } .d-flex { display: flex !important; } .d-inline-flex { display: inline-flex !important; } @media (min-width: 576px) { .d-sm-none { display: none !important; } .d-sm-inline { display: inline !important; } .d-sm-inline-block { display: inline-block !important; } .d-sm-block { display: block !important; } .d-sm-table { display: table !important; } .d-sm-table-row { display: table-row !important; } .d-sm-table-cell { display: table-cell !important; } .d-sm-flex { display: flex !important; } .d-sm-inline-flex { display: inline-flex !important; } } @media (min-width: 768px) { .d-md-none { display: none !important; } .d-md-inline { display: inline !important; } .d-md-inline-block { display: inline-block !important; } .d-md-block { display: block !important; } .d-md-table { display: table !important; } .d-md-table-row { display: table-row !important; } .d-md-table-cell { display: table-cell !important; } .d-md-flex { display: flex !important; } .d-md-inline-flex { display: inline-flex !important; } } @media (min-width: 992px) { .d-lg-none { display: none !important; } .d-lg-inline { display: inline !important; } .d-lg-inline-block { display: inline-block !important; } .d-lg-block { display: block !important; } .d-lg-table { display: table !important; } .d-lg-table-row { display: table-row !important; } .d-lg-table-cell { display: table-cell !important; } .d-lg-flex { display: flex !important; } .d-lg-inline-flex { display: inline-flex !important; } } @media (min-width: 1200px) { .d-xl-none { display: none !important; } .d-xl-inline { display: inline !important; } .d-xl-inline-block { display: inline-block !important; } .d-xl-block { display: block !important; } .d-xl-table { display: table !important; } .d-xl-table-row { display: table-row !important; } .d-xl-table-cell { display: table-cell !important; } .d-xl-flex { display: flex !important; } .d-xl-inline-flex { display: inline-flex !important; } } @media (min-width: 1600px) { .d-xxl-none { display: none !important; } .d-xxl-inline { display: inline !important; } .d-xxl-inline-block { display: inline-block !important; } .d-xxl-block { display: block !important; } .d-xxl-table { display: table !important; } .d-xxl-table-row { display: table-row !important; } .d-xxl-table-cell { display: table-cell !important; } .d-xxl-flex { display: flex !important; } .d-xxl-inline-flex { display: inline-flex !important; } } @media print { .d-print-none { display: none !important; } .d-print-inline { display: inline !important; } .d-print-inline-block { display: inline-block !important; } .d-print-block { display: block !important; } .d-print-table { display: table !important; } .d-print-table-row { display: table-row !important; } .d-print-table-cell { display: table-cell !important; } .d-print-flex { display: flex !important; } .d-print-inline-flex { display: inline-flex !important; } } .embed-responsive { position: relative; display: block; width: 100%; padding: 0; overflow: hidden; } .embed-responsive::before { display: block; content: ""; } .embed-responsive .embed-responsive-item, .embed-responsive iframe, .embed-responsive embed, .embed-responsive object, .embed-responsive video { position: absolute; top: 0; bottom: 0; left: 0; width: 100%; height: 100%; border: 0; } .embed-responsive-21by9::before { padding-top: 42.85714%; } .embed-responsive-16by9::before { padding-top: 56.25%; } .embed-responsive-4by3::before { padding-top: 75%; } .embed-responsive-1by1::before { padding-top: 100%; } .flex-row { flex-direction: row !important; } .flex-column { flex-direction: column !important; } .flex-row-reverse { flex-direction: row-reverse !important; } .flex-column-reverse { flex-direction: column-reverse !important; } .flex-wrap { flex-wrap: wrap !important; } .flex-nowrap { flex-wrap: nowrap !important; } .flex-wrap-reverse { flex-wrap: wrap-reverse !important; } .justify-content-start { justify-content: flex-start !important; } .justify-content-end { justify-content: flex-end !important; } .justify-content-center { justify-content: center !important; } .justify-content-between { justify-content: space-between !important; } .justify-content-around { justify-content: space-around !important; } .align-items-start { align-items: flex-start !important; } .align-items-end { align-items: flex-end !important; } .align-items-center { align-items: center !important; } .align-items-baseline { align-items: baseline !important; } .align-items-stretch { align-items: stretch !important; } .align-content-start { align-content: flex-start !important; } .align-content-end { align-content: flex-end !important; } .align-content-center { align-content: center !important; } .align-content-between { align-content: space-between !important; } .align-content-around { align-content: space-around !important; } .align-content-stretch { align-content: stretch !important; } .align-self-auto { align-self: auto !important; } .align-self-start { align-self: flex-start !important; } .align-self-end { align-self: flex-end !important; } .align-self-center { align-self: center !important; } .align-self-baseline { align-self: baseline !important; } .align-self-stretch { align-self: stretch !important; } @media (min-width: 576px) { .flex-sm-row { flex-direction: row !important; } .flex-sm-column { flex-direction: column !important; } .flex-sm-row-reverse { flex-direction: row-reverse !important; } .flex-sm-column-reverse { flex-direction: column-reverse !important; } .flex-sm-wrap { flex-wrap: wrap !important; } .flex-sm-nowrap { flex-wrap: nowrap !important; } .flex-sm-wrap-reverse { flex-wrap: wrap-reverse !important; } .justify-content-sm-start { justify-content: flex-start !important; } .justify-content-sm-end { justify-content: flex-end !important; } .justify-content-sm-center { justify-content: center !important; } .justify-content-sm-between { justify-content: space-between !important; } .justify-content-sm-around { justify-content: space-around !important; } .align-items-sm-start { align-items: flex-start !important; } .align-items-sm-end { align-items: flex-end !important; } .align-items-sm-center { align-items: center !important; } .align-items-sm-baseline { align-items: baseline !important; } .align-items-sm-stretch { align-items: stretch !important; } .align-content-sm-start { align-content: flex-start !important; } .align-content-sm-end { align-content: flex-end !important; } .align-content-sm-center { align-content: center !important; } .align-content-sm-between { align-content: space-between !important; } .align-content-sm-around { align-content: space-around !important; } .align-content-sm-stretch { align-content: stretch !important; } .align-self-sm-auto { align-self: auto !important; } .align-self-sm-start { align-self: flex-start !important; } .align-self-sm-end { align-self: flex-end !important; } .align-self-sm-center { align-self: center !important; } .align-self-sm-baseline { align-self: baseline !important; } .align-self-sm-stretch { align-self: stretch !important; } } @media (min-width: 768px) { .flex-md-row { flex-direction: row !important; } .flex-md-column { flex-direction: column !important; } .flex-md-row-reverse { flex-direction: row-reverse !important; } .flex-md-column-reverse { flex-direction: column-reverse !important; } .flex-md-wrap { flex-wrap: wrap !important; } .flex-md-nowrap { flex-wrap: nowrap !important; } .flex-md-wrap-reverse { flex-wrap: wrap-reverse !important; } .justify-content-md-start { justify-content: flex-start !important; } .justify-content-md-end { justify-content: flex-end !important; } .justify-content-md-center { justify-content: center !important; } .justify-content-md-between { justify-content: space-between !important; } .justify-content-md-around { justify-content: space-around !important; } .align-items-md-start { align-items: flex-start !important; } .align-items-md-end { align-items: flex-end !important; } .align-items-md-center { align-items: center !important; } .align-items-md-baseline { align-items: baseline !important; } .align-items-md-stretch { align-items: stretch !important; } .align-content-md-start { align-content: flex-start !important; } .align-content-md-end { align-content: flex-end !important; } .align-content-md-center { align-content: center !important; } .align-content-md-between { align-content: space-between !important; } .align-content-md-around { align-content: space-around !important; } .align-content-md-stretch { align-content: stretch !important; } .align-self-md-auto { align-self: auto !important; } .align-self-md-start { align-self: flex-start !important; } .align-self-md-end { align-self: flex-end !important; } .align-self-md-center { align-self: center !important; } .align-self-md-baseline { align-self: baseline !important; } .align-self-md-stretch { align-self: stretch !important; } } @media (min-width: 992px) { .flex-lg-row { flex-direction: row !important; } .flex-lg-column { flex-direction: column !important; } .flex-lg-row-reverse { flex-direction: row-reverse !important; } .flex-lg-column-reverse { flex-direction: column-reverse !important; } .flex-lg-wrap { flex-wrap: wrap !important; } .flex-lg-nowrap { flex-wrap: nowrap !important; } .flex-lg-wrap-reverse { flex-wrap: wrap-reverse !important; } .justify-content-lg-start { justify-content: flex-start !important; } .justify-content-lg-end { justify-content: flex-end !important; } .justify-content-lg-center { justify-content: center !important; } .justify-content-lg-between { justify-content: space-between !important; } .justify-content-lg-around { justify-content: space-around !important; } .align-items-lg-start { align-items: flex-start !important; } .align-items-lg-end { align-items: flex-end !important; } .align-items-lg-center { align-items: center !important; } .align-items-lg-baseline { align-items: baseline !important; } .align-items-lg-stretch { align-items: stretch !important; } .align-content-lg-start { align-content: flex-start !important; } .align-content-lg-end { align-content: flex-end !important; } .align-content-lg-center { align-content: center !important; } .align-content-lg-between { align-content: space-between !important; } .align-content-lg-around { align-content: space-around !important; } .align-content-lg-stretch { align-content: stretch !important; } .align-self-lg-auto { align-self: auto !important; } .align-self-lg-start { align-self: flex-start !important; } .align-self-lg-end { align-self: flex-end !important; } .align-self-lg-center { align-self: center !important; } .align-self-lg-baseline { align-self: baseline !important; } .align-self-lg-stretch { align-self: stretch !important; } } @media (min-width: 1200px) { .flex-xl-row { flex-direction: row !important; } .flex-xl-column { flex-direction: column !important; } .flex-xl-row-reverse { flex-direction: row-reverse !important; } .flex-xl-column-reverse { flex-direction: column-reverse !important; } .flex-xl-wrap { flex-wrap: wrap !important; } .flex-xl-nowrap { flex-wrap: nowrap !important; } .flex-xl-wrap-reverse { flex-wrap: wrap-reverse !important; } .justify-content-xl-start { justify-content: flex-start !important; } .justify-content-xl-end { justify-content: flex-end !important; } .justify-content-xl-center { justify-content: center !important; } .justify-content-xl-between { justify-content: space-between !important; } .justify-content-xl-around { justify-content: space-around !important; } .align-items-xl-start { align-items: flex-start !important; } .align-items-xl-end { align-items: flex-end !important; } .align-items-xl-center { align-items: center !important; } .align-items-xl-baseline { align-items: baseline !important; } .align-items-xl-stretch { align-items: stretch !important; } .align-content-xl-start { align-content: flex-start !important; } .align-content-xl-end { align-content: flex-end !important; } .align-content-xl-center { align-content: center !important; } .align-content-xl-between { align-content: space-between !important; } .align-content-xl-around { align-content: space-around !important; } .align-content-xl-stretch { align-content: stretch !important; } .align-self-xl-auto { align-self: auto !important; } .align-self-xl-start { align-self: flex-start !important; } .align-self-xl-end { align-self: flex-end !important; } .align-self-xl-center { align-self: center !important; } .align-self-xl-baseline { align-self: baseline !important; } .align-self-xl-stretch { align-self: stretch !important; } } @media (min-width: 1600px) { .flex-xxl-row { flex-direction: row !important; } .flex-xxl-column { flex-direction: column !important; } .flex-xxl-row-reverse { flex-direction: row-reverse !important; } .flex-xxl-column-reverse { flex-direction: column-reverse !important; } .flex-xxl-wrap { flex-wrap: wrap !important; } .flex-xxl-nowrap { flex-wrap: nowrap !important; } .flex-xxl-wrap-reverse { flex-wrap: wrap-reverse !important; } .justify-content-xxl-start { justify-content: flex-start !important; } .justify-content-xxl-end { justify-content: flex-end !important; } .justify-content-xxl-center { justify-content: center !important; } .justify-content-xxl-between { justify-content: space-between !important; } .justify-content-xxl-around { justify-content: space-around !important; } .align-items-xxl-start { align-items: flex-start !important; } .align-items-xxl-end { align-items: flex-end !important; } .align-items-xxl-center { align-items: center !important; } .align-items-xxl-baseline { align-items: baseline !important; } .align-items-xxl-stretch { align-items: stretch !important; } .align-content-xxl-start { align-content: flex-start !important; } .align-content-xxl-end { align-content: flex-end !important; } .align-content-xxl-center { align-content: center !important; } .align-content-xxl-between { align-content: space-between !important; } .align-content-xxl-around { align-content: space-around !important; } .align-content-xxl-stretch { align-content: stretch !important; } .align-self-xxl-auto { align-self: auto !important; } .align-self-xxl-start { align-self: flex-start !important; } .align-self-xxl-end { align-self: flex-end !important; } .align-self-xxl-center { align-self: center !important; } .align-self-xxl-baseline { align-self: baseline !important; } .align-self-xxl-stretch { align-self: stretch !important; } } .float-left { float: left !important; } .float-right { float: right !important; } .float-none { float: none !important; } @media (min-width: 576px) { .float-sm-left { float: left !important; } .float-sm-right { float: right !important; } .float-sm-none { float: none !important; } } @media (min-width: 768px) { .float-md-left { float: left !important; } .float-md-right { float: right !important; } .float-md-none { float: none !important; } } @media (min-width: 992px) { .float-lg-left { float: left !important; } .float-lg-right { float: right !important; } .float-lg-none { float: none !important; } } @media (min-width: 1200px) { .float-xl-left { float: left !important; } .float-xl-right { float: right !important; } .float-xl-none { float: none !important; } } @media (min-width: 1600px) { .float-xxl-left { float: left !important; } .float-xxl-right { float: right !important; } .float-xxl-none { float: none !important; } } .position-static { position: static !important; } .position-relative { position: relative !important; } .position-absolute { position: absolute !important; } .position-fixed { position: fixed !important; } .position-sticky { position: sticky !important; } .fixed-top { position: fixed; top: 0; right: 0; left: 0; z-index: 1030; } .fixed-bottom { position: fixed; right: 0; bottom: 0; left: 0; z-index: 1030; } @supports (position: sticky) { .sticky-top { position: sticky; top: 0; z-index: 1020; } } .sr-only { position: absolute; width: 1px; height: 1px; padding: 0; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; clip-path: inset(50%); border: 0; } .sr-only-focusable:active, .sr-only-focusable:focus { position: static; width: auto; height: auto; overflow: visible; clip: auto; white-space: normal; clip-path: none; } .w-25 { width: 25% !important; } .w-50 { width: 50% !important; } .w-75 { width: 75% !important; } .w-100 { width: 100% !important; } .h-25 { height: 25% !important; } .h-50 { height: 50% !important; } .h-75 { height: 75% !important; } .h-100 { height: 100% !important; } .mw-100 { max-width: 100% !important; } .mh-100 { max-height: 100% !important; } .m-0 { margin: 0 !important; } .mt-0, .my-0 { margin-top: 0 !important; } .mr-0, .mx-0 { margin-right: 0 !important; } .mb-0, .my-0 { margin-bottom: 0 !important; } .ml-0, .mx-0 { margin-left: 0 !important; } .m-1 { margin: 0.25rem !important; } .mt-1, .my-1 { margin-top: 0.25rem !important; } .mr-1, .mx-1 { margin-right: 0.25rem !important; } .mb-1, .my-1 { margin-bottom: 0.25rem !important; } .ml-1, .mx-1 { margin-left: 0.25rem !important; } .m-2 { margin: 0.5rem !important; } .mt-2, .my-2 { margin-top: 0.5rem !important; } .mr-2, .mx-2 { margin-right: 0.5rem !important; } .mb-2, .my-2 { margin-bottom: 0.5rem !important; } .ml-2, .mx-2 { margin-left: 0.5rem !important; } .m-3 { margin: 1rem !important; } .mt-3, .my-3 { margin-top: 1rem !important; } .mr-3, .mx-3 { margin-right: 1rem !important; } .mb-3, .my-3 { margin-bottom: 1rem !important; } .ml-3, .mx-3 { margin-left: 1rem !important; } .m-4 { margin: 1.5rem !important; } .mt-4, .my-4 { margin-top: 1.5rem !important; } .mr-4, .mx-4 { margin-right: 1.5rem !important; } .mb-4, .my-4 { margin-bottom: 1.5rem !important; } .ml-4, .mx-4 { margin-left: 1.5rem !important; } .m-5 { margin: 3rem !important; } .mt-5, .my-5 { margin-top: 3rem !important; } .mr-5, .mx-5 { margin-right: 3rem !important; } .mb-5, .my-5 { margin-bottom: 3rem !important; } .ml-5, .mx-5 { margin-left: 3rem !important; } .p-0 { padding: 0 !important; } .pt-0, .py-0 { padding-top: 0 !important; } .pr-0, .px-0 { padding-right: 0 !important; } .pb-0, .py-0 { padding-bottom: 0 !important; } .pl-0, .px-0 { padding-left: 0 !important; } .p-1 { padding: 0.25rem !important; } .pt-1, .py-1 { padding-top: 0.25rem !important; } .pr-1, .px-1 { padding-right: 0.25rem !important; } .pb-1, .py-1 { padding-bottom: 0.25rem !important; } .pl-1, .px-1 { padding-left: 0.25rem !important; } .p-2 { padding: 0.5rem !important; } .pt-2, .py-2 { padding-top: 0.5rem !important; } .pr-2, .px-2 { padding-right: 0.5rem !important; } .pb-2, .py-2 { padding-bottom: 0.5rem !important; } .pl-2, .px-2 { padding-left: 0.5rem !important; } .p-3 { padding: 1rem !important; } .pt-3, .py-3 { padding-top: 1rem !important; } .pr-3, .px-3 { padding-right: 1rem !important; } .pb-3, .py-3 { padding-bottom: 1rem !important; } .pl-3, .px-3 { padding-left: 1rem !important; } .p-4 { padding: 1.5rem !important; } .pt-4, .py-4 { padding-top: 1.5rem !important; } .pr-4, .px-4 { padding-right: 1.5rem !important; } .pb-4, .py-4 { padding-bottom: 1.5rem !important; } .pl-4, .px-4 { padding-left: 1.5rem !important; } .p-5 { padding: 3rem !important; } .pt-5, .py-5 { padding-top: 3rem !important; } .pr-5, .px-5 { padding-right: 3rem !important; } .pb-5, .py-5 { padding-bottom: 3rem !important; } .pl-5, .px-5 { padding-left: 3rem !important; } .m-auto { margin: auto !important; } .mt-auto, .my-auto { margin-top: auto !important; } .mr-auto, .mx-auto { margin-right: auto !important; } .mb-auto, .my-auto { margin-bottom: auto !important; } .ml-auto, .mx-auto { margin-left: auto !important; } @media (min-width: 576px) { .m-sm-0 { margin: 0 !important; } .mt-sm-0, .my-sm-0 { margin-top: 0 !important; } .mr-sm-0, .mx-sm-0 { margin-right: 0 !important; } .mb-sm-0, .my-sm-0 { margin-bottom: 0 !important; } .ml-sm-0, .mx-sm-0 { margin-left: 0 !important; } .m-sm-1 { margin: 0.25rem !important; } .mt-sm-1, .my-sm-1 { margin-top: 0.25rem !important; } .mr-sm-1, .mx-sm-1 { margin-right: 0.25rem !important; } .mb-sm-1, .my-sm-1 { margin-bottom: 0.25rem !important; } .ml-sm-1, .mx-sm-1 { margin-left: 0.25rem !important; } .m-sm-2 { margin: 0.5rem !important; } .mt-sm-2, .my-sm-2 { margin-top: 0.5rem !important; } .mr-sm-2, .mx-sm-2 { margin-right: 0.5rem !important; } .mb-sm-2, .my-sm-2 { margin-bottom: 0.5rem !important; } .ml-sm-2, .mx-sm-2 { margin-left: 0.5rem !important; } .m-sm-3 { margin: 1rem !important; } .mt-sm-3, .my-sm-3 { margin-top: 1rem !important; } .mr-sm-3, .mx-sm-3 { margin-right: 1rem !important; } .mb-sm-3, .my-sm-3 { margin-bottom: 1rem !important; } .ml-sm-3, .mx-sm-3 { margin-left: 1rem !important; } .m-sm-4 { margin: 1.5rem !important; } .mt-sm-4, .my-sm-4 { margin-top: 1.5rem !important; } .mr-sm-4, .mx-sm-4 { margin-right: 1.5rem !important; } .mb-sm-4, .my-sm-4 { margin-bottom: 1.5rem !important; } .ml-sm-4, .mx-sm-4 { margin-left: 1.5rem !important; } .m-sm-5 { margin: 3rem !important; } .mt-sm-5, .my-sm-5 { margin-top: 3rem !important; } .mr-sm-5, .mx-sm-5 { margin-right: 3rem !important; } .mb-sm-5, .my-sm-5 { margin-bottom: 3rem !important; } .ml-sm-5, .mx-sm-5 { margin-left: 3rem !important; } .p-sm-0 { padding: 0 !important; } .pt-sm-0, .py-sm-0 { padding-top: 0 !important; } .pr-sm-0, .px-sm-0 { padding-right: 0 !important; } .pb-sm-0, .py-sm-0 { padding-bottom: 0 !important; } .pl-sm-0, .px-sm-0 { padding-left: 0 !important; } .p-sm-1 { padding: 0.25rem !important; } .pt-sm-1, .py-sm-1 { padding-top: 0.25rem !important; } .pr-sm-1, .px-sm-1 { padding-right: 0.25rem !important; } .pb-sm-1, .py-sm-1 { padding-bottom: 0.25rem !important; } .pl-sm-1, .px-sm-1 { padding-left: 0.25rem !important; } .p-sm-2 { padding: 0.5rem !important; } .pt-sm-2, .py-sm-2 { padding-top: 0.5rem !important; } .pr-sm-2, .px-sm-2 { padding-right: 0.5rem !important; } .pb-sm-2, .py-sm-2 { padding-bottom: 0.5rem !important; } .pl-sm-2, .px-sm-2 { padding-left: 0.5rem !important; } .p-sm-3 { padding: 1rem !important; } .pt-sm-3, .py-sm-3 { padding-top: 1rem !important; } .pr-sm-3, .px-sm-3 { padding-right: 1rem !important; } .pb-sm-3, .py-sm-3 { padding-bottom: 1rem !important; } .pl-sm-3, .px-sm-3 { padding-left: 1rem !important; } .p-sm-4 { padding: 1.5rem !important; } .pt-sm-4, .py-sm-4 { padding-top: 1.5rem !important; } .pr-sm-4, .px-sm-4 { padding-right: 1.5rem !important; } .pb-sm-4, .py-sm-4 { padding-bottom: 1.5rem !important; } .pl-sm-4, .px-sm-4 { padding-left: 1.5rem !important; } .p-sm-5 { padding: 3rem !important; } .pt-sm-5, .py-sm-5 { padding-top: 3rem !important; } .pr-sm-5, .px-sm-5 { padding-right: 3rem !important; } .pb-sm-5, .py-sm-5 { padding-bottom: 3rem !important; } .pl-sm-5, .px-sm-5 { padding-left: 3rem !important; } .m-sm-auto { margin: auto !important; } .mt-sm-auto, .my-sm-auto { margin-top: auto !important; } .mr-sm-auto, .mx-sm-auto { margin-right: auto !important; } .mb-sm-auto, .my-sm-auto { margin-bottom: auto !important; } .ml-sm-auto, .mx-sm-auto { margin-left: auto !important; } } @media (min-width: 768px) { .m-md-0 { margin: 0 !important; } .mt-md-0, .my-md-0 { margin-top: 0 !important; } .mr-md-0, .mx-md-0 { margin-right: 0 !important; } .mb-md-0, .my-md-0 { margin-bottom: 0 !important; } .ml-md-0, .mx-md-0 { margin-left: 0 !important; } .m-md-1 { margin: 0.25rem !important; } .mt-md-1, .my-md-1 { margin-top: 0.25rem !important; } .mr-md-1, .mx-md-1 { margin-right: 0.25rem !important; } .mb-md-1, .my-md-1 { margin-bottom: 0.25rem !important; } .ml-md-1, .mx-md-1 { margin-left: 0.25rem !important; } .m-md-2 { margin: 0.5rem !important; } .mt-md-2, .my-md-2 { margin-top: 0.5rem !important; } .mr-md-2, .mx-md-2 { margin-right: 0.5rem !important; } .mb-md-2, .my-md-2 { margin-bottom: 0.5rem !important; } .ml-md-2, .mx-md-2 { margin-left: 0.5rem !important; } .m-md-3 { margin: 1rem !important; } .mt-md-3, .my-md-3 { margin-top: 1rem !important; } .mr-md-3, .mx-md-3 { margin-right: 1rem !important; } .mb-md-3, .my-md-3 { margin-bottom: 1rem !important; } .ml-md-3, .mx-md-3 { margin-left: 1rem !important; } .m-md-4 { margin: 1.5rem !important; } .mt-md-4, .my-md-4 { margin-top: 1.5rem !important; } .mr-md-4, .mx-md-4 { margin-right: 1.5rem !important; } .mb-md-4, .my-md-4 { margin-bottom: 1.5rem !important; } .ml-md-4, .mx-md-4 { margin-left: 1.5rem !important; } .m-md-5 { margin: 3rem !important; } .mt-md-5, .my-md-5 { margin-top: 3rem !important; } .mr-md-5, .mx-md-5 { margin-right: 3rem !important; } .mb-md-5, .my-md-5 { margin-bottom: 3rem !important; } .ml-md-5, .mx-md-5 { margin-left: 3rem !important; } .p-md-0 { padding: 0 !important; } .pt-md-0, .py-md-0 { padding-top: 0 !important; } .pr-md-0, .px-md-0 { padding-right: 0 !important; } .pb-md-0, .py-md-0 { padding-bottom: 0 !important; } .pl-md-0, .px-md-0 { padding-left: 0 !important; } .p-md-1 { padding: 0.25rem !important; } .pt-md-1, .py-md-1 { padding-top: 0.25rem !important; } .pr-md-1, .px-md-1 { padding-right: 0.25rem !important; } .pb-md-1, .py-md-1 { padding-bottom: 0.25rem !important; } .pl-md-1, .px-md-1 { padding-left: 0.25rem !important; } .p-md-2 { padding: 0.5rem !important; } .pt-md-2, .py-md-2 { padding-top: 0.5rem !important; } .pr-md-2, .px-md-2 { padding-right: 0.5rem !important; } .pb-md-2, .py-md-2 { padding-bottom: 0.5rem !important; } .pl-md-2, .px-md-2 { padding-left: 0.5rem !important; } .p-md-3 { padding: 1rem !important; } .pt-md-3, .py-md-3 { padding-top: 1rem !important; } .pr-md-3, .px-md-3 { padding-right: 1rem !important; } .pb-md-3, .py-md-3 { padding-bottom: 1rem !important; } .pl-md-3, .px-md-3 { padding-left: 1rem !important; } .p-md-4 { padding: 1.5rem !important; } .pt-md-4, .py-md-4 { padding-top: 1.5rem !important; } .pr-md-4, .px-md-4 { padding-right: 1.5rem !important; } .pb-md-4, .py-md-4 { padding-bottom: 1.5rem !important; } .pl-md-4, .px-md-4 { padding-left: 1.5rem !important; } .p-md-5 { padding: 3rem !important; } .pt-md-5, .py-md-5 { padding-top: 3rem !important; } .pr-md-5, .px-md-5 { padding-right: 3rem !important; } .pb-md-5, .py-md-5 { padding-bottom: 3rem !important; } .pl-md-5, .px-md-5 { padding-left: 3rem !important; } .m-md-auto { margin: auto !important; } .mt-md-auto, .my-md-auto { margin-top: auto !important; } .mr-md-auto, .mx-md-auto { margin-right: auto !important; } .mb-md-auto, .my-md-auto { margin-bottom: auto !important; } .ml-md-auto, .mx-md-auto { margin-left: auto !important; } } @media (min-width: 992px) { .m-lg-0 { margin: 0 !important; } .mt-lg-0, .my-lg-0 { margin-top: 0 !important; } .mr-lg-0, .mx-lg-0 { margin-right: 0 !important; } .mb-lg-0, .my-lg-0 { margin-bottom: 0 !important; } .ml-lg-0, .mx-lg-0 { margin-left: 0 !important; } .m-lg-1 { margin: 0.25rem !important; } .mt-lg-1, .my-lg-1 { margin-top: 0.25rem !important; } .mr-lg-1, .mx-lg-1 { margin-right: 0.25rem !important; } .mb-lg-1, .my-lg-1 { margin-bottom: 0.25rem !important; } .ml-lg-1, .mx-lg-1 { margin-left: 0.25rem !important; } .m-lg-2 { margin: 0.5rem !important; } .mt-lg-2, .my-lg-2 { margin-top: 0.5rem !important; } .mr-lg-2, .mx-lg-2 { margin-right: 0.5rem !important; } .mb-lg-2, .my-lg-2 { margin-bottom: 0.5rem !important; } .ml-lg-2, .mx-lg-2 { margin-left: 0.5rem !important; } .m-lg-3 { margin: 1rem !important; } .mt-lg-3, .my-lg-3 { margin-top: 1rem !important; } .mr-lg-3, .mx-lg-3 { margin-right: 1rem !important; } .mb-lg-3, .my-lg-3 { margin-bottom: 1rem !important; } .ml-lg-3, .mx-lg-3 { margin-left: 1rem !important; } .m-lg-4 { margin: 1.5rem !important; } .mt-lg-4, .my-lg-4 { margin-top: 1.5rem !important; } .mr-lg-4, .mx-lg-4 { margin-right: 1.5rem !important; } .mb-lg-4, .my-lg-4 { margin-bottom: 1.5rem !important; } .ml-lg-4, .mx-lg-4 { margin-left: 1.5rem !important; } .m-lg-5 { margin: 3rem !important; } .mt-lg-5, .my-lg-5 { margin-top: 3rem !important; } .mr-lg-5, .mx-lg-5 { margin-right: 3rem !important; } .mb-lg-5, .my-lg-5 { margin-bottom: 3rem !important; } .ml-lg-5, .mx-lg-5 { margin-left: 3rem !important; } .p-lg-0 { padding: 0 !important; } .pt-lg-0, .py-lg-0 { padding-top: 0 !important; } .pr-lg-0, .px-lg-0 { padding-right: 0 !important; } .pb-lg-0, .py-lg-0 { padding-bottom: 0 !important; } .pl-lg-0, .px-lg-0 { padding-left: 0 !important; } .p-lg-1 { padding: 0.25rem !important; } .pt-lg-1, .py-lg-1 { padding-top: 0.25rem !important; } .pr-lg-1, .px-lg-1 { padding-right: 0.25rem !important; } .pb-lg-1, .py-lg-1 { padding-bottom: 0.25rem !important; } .pl-lg-1, .px-lg-1 { padding-left: 0.25rem !important; } .p-lg-2 { padding: 0.5rem !important; } .pt-lg-2, .py-lg-2 { padding-top: 0.5rem !important; } .pr-lg-2, .px-lg-2 { padding-right: 0.5rem !important; } .pb-lg-2, .py-lg-2 { padding-bottom: 0.5rem !important; } .pl-lg-2, .px-lg-2 { padding-left: 0.5rem !important; } .p-lg-3 { padding: 1rem !important; } .pt-lg-3, .py-lg-3 { padding-top: 1rem !important; } .pr-lg-3, .px-lg-3 { padding-right: 1rem !important; } .pb-lg-3, .py-lg-3 { padding-bottom: 1rem !important; } .pl-lg-3, .px-lg-3 { padding-left: 1rem !important; } .p-lg-4 { padding: 1.5rem !important; } .pt-lg-4, .py-lg-4 { padding-top: 1.5rem !important; } .pr-lg-4, .px-lg-4 { padding-right: 1.5rem !important; } .pb-lg-4, .py-lg-4 { padding-bottom: 1.5rem !important; } .pl-lg-4, .px-lg-4 { padding-left: 1.5rem !important; } .p-lg-5 { padding: 3rem !important; } .pt-lg-5, .py-lg-5 { padding-top: 3rem !important; } .pr-lg-5, .px-lg-5 { padding-right: 3rem !important; } .pb-lg-5, .py-lg-5 { padding-bottom: 3rem !important; } .pl-lg-5, .px-lg-5 { padding-left: 3rem !important; } .m-lg-auto { margin: auto !important; } .mt-lg-auto, .my-lg-auto { margin-top: auto !important; } .mr-lg-auto, .mx-lg-auto { margin-right: auto !important; } .mb-lg-auto, .my-lg-auto { margin-bottom: auto !important; } .ml-lg-auto, .mx-lg-auto { margin-left: auto !important; } } @media (min-width: 1200px) { .m-xl-0 { margin: 0 !important; } .mt-xl-0, .my-xl-0 { margin-top: 0 !important; } .mr-xl-0, .mx-xl-0 { margin-right: 0 !important; } .mb-xl-0, .my-xl-0 { margin-bottom: 0 !important; } .ml-xl-0, .mx-xl-0 { margin-left: 0 !important; } .m-xl-1 { margin: 0.25rem !important; } .mt-xl-1, .my-xl-1 { margin-top: 0.25rem !important; } .mr-xl-1, .mx-xl-1 { margin-right: 0.25rem !important; } .mb-xl-1, .my-xl-1 { margin-bottom: 0.25rem !important; } .ml-xl-1, .mx-xl-1 { margin-left: 0.25rem !important; } .m-xl-2 { margin: 0.5rem !important; } .mt-xl-2, .my-xl-2 { margin-top: 0.5rem !important; } .mr-xl-2, .mx-xl-2 { margin-right: 0.5rem !important; } .mb-xl-2, .my-xl-2 { margin-bottom: 0.5rem !important; } .ml-xl-2, .mx-xl-2 { margin-left: 0.5rem !important; } .m-xl-3 { margin: 1rem !important; } .mt-xl-3, .my-xl-3 { margin-top: 1rem !important; } .mr-xl-3, .mx-xl-3 { margin-right: 1rem !important; } .mb-xl-3, .my-xl-3 { margin-bottom: 1rem !important; } .ml-xl-3, .mx-xl-3 { margin-left: 1rem !important; } .m-xl-4 { margin: 1.5rem !important; } .mt-xl-4, .my-xl-4 { margin-top: 1.5rem !important; } .mr-xl-4, .mx-xl-4 { margin-right: 1.5rem !important; } .mb-xl-4, .my-xl-4 { margin-bottom: 1.5rem !important; } .ml-xl-4, .mx-xl-4 { margin-left: 1.5rem !important; } .m-xl-5 { margin: 3rem !important; } .mt-xl-5, .my-xl-5 { margin-top: 3rem !important; } .mr-xl-5, .mx-xl-5 { margin-right: 3rem !important; } .mb-xl-5, .my-xl-5 { margin-bottom: 3rem !important; } .ml-xl-5, .mx-xl-5 { margin-left: 3rem !important; } .p-xl-0 { padding: 0 !important; } .pt-xl-0, .py-xl-0 { padding-top: 0 !important; } .pr-xl-0, .px-xl-0 { padding-right: 0 !important; } .pb-xl-0, .py-xl-0 { padding-bottom: 0 !important; } .pl-xl-0, .px-xl-0 { padding-left: 0 !important; } .p-xl-1 { padding: 0.25rem !important; } .pt-xl-1, .py-xl-1 { padding-top: 0.25rem !important; } .pr-xl-1, .px-xl-1 { padding-right: 0.25rem !important; } .pb-xl-1, .py-xl-1 { padding-bottom: 0.25rem !important; } .pl-xl-1, .px-xl-1 { padding-left: 0.25rem !important; } .p-xl-2 { padding: 0.5rem !important; } .pt-xl-2, .py-xl-2 { padding-top: 0.5rem !important; } .pr-xl-2, .px-xl-2 { padding-right: 0.5rem !important; } .pb-xl-2, .py-xl-2 { padding-bottom: 0.5rem !important; } .pl-xl-2, .px-xl-2 { padding-left: 0.5rem !important; } .p-xl-3 { padding: 1rem !important; } .pt-xl-3, .py-xl-3 { padding-top: 1rem !important; } .pr-xl-3, .px-xl-3 { padding-right: 1rem !important; } .pb-xl-3, .py-xl-3 { padding-bottom: 1rem !important; } .pl-xl-3, .px-xl-3 { padding-left: 1rem !important; } .p-xl-4 { padding: 1.5rem !important; } .pt-xl-4, .py-xl-4 { padding-top: 1.5rem !important; } .pr-xl-4, .px-xl-4 { padding-right: 1.5rem !important; } .pb-xl-4, .py-xl-4 { padding-bottom: 1.5rem !important; } .pl-xl-4, .px-xl-4 { padding-left: 1.5rem !important; } .p-xl-5 { padding: 3rem !important; } .pt-xl-5, .py-xl-5 { padding-top: 3rem !important; } .pr-xl-5, .px-xl-5 { padding-right: 3rem !important; } .pb-xl-5, .py-xl-5 { padding-bottom: 3rem !important; } .pl-xl-5, .px-xl-5 { padding-left: 3rem !important; } .m-xl-auto { margin: auto !important; } .mt-xl-auto, .my-xl-auto { margin-top: auto !important; } .mr-xl-auto, .mx-xl-auto { margin-right: auto !important; } .mb-xl-auto, .my-xl-auto { margin-bottom: auto !important; } .ml-xl-auto, .mx-xl-auto { margin-left: auto !important; } } @media (min-width: 1600px) { .m-xxl-0 { margin: 0 !important; } .mt-xxl-0, .my-xxl-0 { margin-top: 0 !important; } .mr-xxl-0, .mx-xxl-0 { margin-right: 0 !important; } .mb-xxl-0, .my-xxl-0 { margin-bottom: 0 !important; } .ml-xxl-0, .mx-xxl-0 { margin-left: 0 !important; } .m-xxl-1 { margin: 0.25rem !important; } .mt-xxl-1, .my-xxl-1 { margin-top: 0.25rem !important; } .mr-xxl-1, .mx-xxl-1 { margin-right: 0.25rem !important; } .mb-xxl-1, .my-xxl-1 { margin-bottom: 0.25rem !important; } .ml-xxl-1, .mx-xxl-1 { margin-left: 0.25rem !important; } .m-xxl-2 { margin: 0.5rem !important; } .mt-xxl-2, .my-xxl-2 { margin-top: 0.5rem !important; } .mr-xxl-2, .mx-xxl-2 { margin-right: 0.5rem !important; } .mb-xxl-2, .my-xxl-2 { margin-bottom: 0.5rem !important; } .ml-xxl-2, .mx-xxl-2 { margin-left: 0.5rem !important; } .m-xxl-3 { margin: 1rem !important; } .mt-xxl-3, .my-xxl-3 { margin-top: 1rem !important; } .mr-xxl-3, .mx-xxl-3 { margin-right: 1rem !important; } .mb-xxl-3, .my-xxl-3 { margin-bottom: 1rem !important; } .ml-xxl-3, .mx-xxl-3 { margin-left: 1rem !important; } .m-xxl-4 { margin: 1.5rem !important; } .mt-xxl-4, .my-xxl-4 { margin-top: 1.5rem !important; } .mr-xxl-4, .mx-xxl-4 { margin-right: 1.5rem !important; } .mb-xxl-4, .my-xxl-4 { margin-bottom: 1.5rem !important; } .ml-xxl-4, .mx-xxl-4 { margin-left: 1.5rem !important; } .m-xxl-5 { margin: 3rem !important; } .mt-xxl-5, .my-xxl-5 { margin-top: 3rem !important; } .mr-xxl-5, .mx-xxl-5 { margin-right: 3rem !important; } .mb-xxl-5, .my-xxl-5 { margin-bottom: 3rem !important; } .ml-xxl-5, .mx-xxl-5 { margin-left: 3rem !important; } .p-xxl-0 { padding: 0 !important; } .pt-xxl-0, .py-xxl-0 { padding-top: 0 !important; } .pr-xxl-0, .px-xxl-0 { padding-right: 0 !important; } .pb-xxl-0, .py-xxl-0 { padding-bottom: 0 !important; } .pl-xxl-0, .px-xxl-0 { padding-left: 0 !important; } .p-xxl-1 { padding: 0.25rem !important; } .pt-xxl-1, .py-xxl-1 { padding-top: 0.25rem !important; } .pr-xxl-1, .px-xxl-1 { padding-right: 0.25rem !important; } .pb-xxl-1, .py-xxl-1 { padding-bottom: 0.25rem !important; } .pl-xxl-1, .px-xxl-1 { padding-left: 0.25rem !important; } .p-xxl-2 { padding: 0.5rem !important; } .pt-xxl-2, .py-xxl-2 { padding-top: 0.5rem !important; } .pr-xxl-2, .px-xxl-2 { padding-right: 0.5rem !important; } .pb-xxl-2, .py-xxl-2 { padding-bottom: 0.5rem !important; } .pl-xxl-2, .px-xxl-2 { padding-left: 0.5rem !important; } .p-xxl-3 { padding: 1rem !important; } .pt-xxl-3, .py-xxl-3 { padding-top: 1rem !important; } .pr-xxl-3, .px-xxl-3 { padding-right: 1rem !important; } .pb-xxl-3, .py-xxl-3 { padding-bottom: 1rem !important; } .pl-xxl-3, .px-xxl-3 { padding-left: 1rem !important; } .p-xxl-4 { padding: 1.5rem !important; } .pt-xxl-4, .py-xxl-4 { padding-top: 1.5rem !important; } .pr-xxl-4, .px-xxl-4 { padding-right: 1.5rem !important; } .pb-xxl-4, .py-xxl-4 { padding-bottom: 1.5rem !important; } .pl-xxl-4, .px-xxl-4 { padding-left: 1.5rem !important; } .p-xxl-5 { padding: 3rem !important; } .pt-xxl-5, .py-xxl-5 { padding-top: 3rem !important; } .pr-xxl-5, .px-xxl-5 { padding-right: 3rem !important; } .pb-xxl-5, .py-xxl-5 { padding-bottom: 3rem !important; } .pl-xxl-5, .px-xxl-5 { padding-left: 3rem !important; } .m-xxl-auto { margin: auto !important; } .mt-xxl-auto, .my-xxl-auto { margin-top: auto !important; } .mr-xxl-auto, .mx-xxl-auto { margin-right: auto !important; } .mb-xxl-auto, .my-xxl-auto { margin-bottom: auto !important; } .ml-xxl-auto, .mx-xxl-auto { margin-left: auto !important; } } .text-justify { text-align: justify !important; } .text-nowrap { white-space: nowrap !important; } .text-truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .text-left { text-align: left !important; } .text-right { text-align: right !important; } .text-center { text-align: center !important; } @media (min-width: 576px) { .text-sm-left { text-align: left !important; } .text-sm-right { text-align: right !important; } .text-sm-center { text-align: center !important; } } @media (min-width: 768px) { .text-md-left { text-align: left !important; } .text-md-right { text-align: right !important; } .text-md-center { text-align: center !important; } } @media (min-width: 992px) { .text-lg-left { text-align: left !important; } .text-lg-right { text-align: right !important; } .text-lg-center { text-align: center !important; } } @media (min-width: 1200px) { .text-xl-left { text-align: left !important; } .text-xl-right { text-align: right !important; } .text-xl-center { text-align: center !important; } } @media (min-width: 1600px) { .text-xxl-left { text-align: left !important; } .text-xxl-right { text-align: right !important; } .text-xxl-center { text-align: center !important; } } .text-lowercase { text-transform: lowercase !important; } .text-uppercase { text-transform: uppercase !important; } .text-capitalize { text-transform: capitalize !important; } .font-weight-light { font-weight: 300 !important; } .font-weight-normal { font-weight: 400 !important; } .font-weight-bold { font-weight: 700 !important; } .font-italic { font-style: italic !important; } .text-white { color: #fff !important; } .text-black { color: #333 !important; } .text-muted { color: #6c757d !important; } .text-hide { font: 0/0 a; color: transparent; text-shadow: none; background-color: transparent; border: 0; } .visible { visibility: visible !important; } .invisible { visibility: hidden !important; } @media print { *, *::before, *::after { text-shadow: none !important; box-shadow: none !important; } a:not(.btn) { text-decoration: underline; } abbr[title]::after { content: " (" attr(title) ")"; } pre { white-space: pre-wrap !important; } pre, blockquote { border: 1px solid #999; page-break-inside: avoid; } thead { display: table-header-group; } tr, img { page-break-inside: avoid; } p, h2, h3 { orphans: 3; widows: 3; } h2, h3 { page-break-after: avoid; } @page { size: a3; } body { min-width: 992px !important; } .container { min-width: 992px !important; } .navbar { display: none; } .badge { border: 1px solid #000; } .table { border-collapse: collapse !important; } .table td, .table th { background-color: #fff !important; } .table-bordered th, .table-bordered td { border: 1px solid #ddd !important; } } \ No newline at end of file diff --git a/site/monica_site/static/css/fonts.css b/site/monica_site/static/css/fonts.css new file mode 100755 index 0000000..5bc19d3 --- /dev/null +++ b/site/monica_site/static/css/fonts.css @@ -0,0 +1,18 @@ +/* +* +* Fonts +*/ /* +* +* Font Awesome Icons 4.7.0 +* -------------------------------------------------- +*/ @font-face { font-family: 'FontAwesome'; src: url("../fonts/fontawesome-webfont.eot?v=4.7.0"); src: url("../fonts/fontawesome-webfont.eot?#iefix&v=4.7.0") format("embedded-opentype"), url("../fonts/fontawesome-webfont.woff2?v=4.7.0") format("woff2"), url("../fonts/fontawesome-webfont.woff?v=4.7.0") format("woff"), url("../fonts/fontawesome-webfont.ttf?v=4.7.0") format("truetype"), url("../fonts/fontawesome-webfont.svg?v=4.7.0#fontawesomeregular") format("svg"); font-weight: normal; font-style: normal; } .fa { display: inline-block; font-family: 'FontAwesome'; font-size: inherit; text-rendering: auto; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } [class*='fa-']:before { font-weight: 400; font-family: 'FontAwesome'; } /* makes the font 33% larger relative to the icon container */ .fa-lg { font-size: 1.33333333em; line-height: 0.75em; vertical-align: -15%; } .fa-2x { font-size: 2em; } .fa-3x { font-size: 3em; } .fa-4x { font-size: 4em; } .fa-5x { font-size: 5em; } .fa-fw { width: 1.28571429em; text-align: center; } .fa-ul { padding-left: 0; margin-left: 2.14285714em; list-style-type: none; } .fa-ul > li { position: relative; } .fa-li { position: absolute; left: -2.14285714em; width: 2.14285714em; top: 0.14285714em; text-align: center; } .fa-li.fa-lg { left: -1.85714286em; } .fa-border { padding: .2em .25em .15em; border: solid 0.08em #eeeeee; border-radius: .1em; } .fa-pull-left { float: left; } .fa-pull-right { float: right; } .fa.fa-pull-left { margin-right: .3em; } .fa.fa-pull-right { margin-left: .3em; } /* Deprecated as of 4.4.0 */ .pull-right { float: right; } .pull-left { float: left; } .fa.pull-left { margin-right: .3em; } .fa.pull-right { margin-left: .3em; } .fa-spin { -webkit-animation: fa-spin 2s infinite linear; animation: fa-spin 2s infinite linear; } .fa-pulse { -webkit-animation: fa-spin 1s infinite steps(8); animation: fa-spin 1s infinite steps(8); } @-webkit-keyframes fa-spin { 0% { -webkit-transform: rotate(0deg); transform: rotate(0deg); } 100% { -webkit-transform: rotate(359deg); transform: rotate(359deg); } } @keyframes fa-spin { 0% { -webkit-transform: rotate(0deg); transform: rotate(0deg); } 100% { -webkit-transform: rotate(359deg); transform: rotate(359deg); } } .fa-rotate-90 { -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=1)"; -webkit-transform: rotate(90deg); -ms-transform: rotate(90deg); transform: rotate(90deg); } .fa-rotate-180 { -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2)"; -webkit-transform: rotate(180deg); -ms-transform: rotate(180deg); transform: rotate(180deg); } .fa-rotate-270 { -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=3)"; -webkit-transform: rotate(270deg); -ms-transform: rotate(270deg); transform: rotate(270deg); } .fa-flip-horizontal { -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)"; -webkit-transform: scale(-1, 1); -ms-transform: scale(-1, 1); transform: scale(-1, 1); } .fa-flip-vertical { -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)"; -webkit-transform: scale(1, -1); -ms-transform: scale(1, -1); transform: scale(1, -1); } :root .fa-rotate-90, :root .fa-rotate-180, :root .fa-rotate-270, :root .fa-flip-horizontal, :root .fa-flip-vertical { filter: none; } .fa-stack { position: relative; display: inline-block; width: 2em; height: 2em; line-height: 2em; vertical-align: middle; } .fa-stack-1x, .fa-stack-2x { position: absolute; left: 0; width: 100%; text-align: center; } .fa-stack-1x { line-height: inherit; } .fa-stack-2x { font-size: 2em; } .fa-inverse { color: #ffffff; } /* Font Awesome uses the Unicode Private Use Area (PUA) to ensure screen + readers do not read off random characters that represent icons */ .fa-glass:before { content: "\f000"; } .fa-music:before { content: "\f001"; } .fa-search:before { content: "\f002"; } .fa-envelope-o:before { content: "\f003"; } .fa-heart:before { content: "\f004"; } .fa-star:before { content: "\f005"; } .fa-star-o:before { content: "\f006"; } .fa-user:before { content: "\f007"; } .fa-film:before { content: "\f008"; } .fa-th-large:before { content: "\f009"; } .fa-th:before { content: "\f00a"; } .fa-th-list:before { content: "\f00b"; } .fa-check:before { content: "\f00c"; } .fa-remove:before, .fa-close:before, .fa-times:before { content: "\f00d"; } .fa-search-plus:before { content: "\f00e"; } .fa-search-minus:before { content: "\f010"; } .fa-power-off:before { content: "\f011"; } .fa-signal:before { content: "\f012"; } .fa-gear:before, .fa-cog:before { content: "\f013"; } .fa-trash-o:before { content: "\f014"; } .fa-home:before { content: "\f015"; } .fa-file-o:before { content: "\f016"; } .fa-clock-o:before { content: "\f017"; } .fa-road:before { content: "\f018"; } .fa-download:before { content: "\f019"; } .fa-arrow-circle-o-down:before { content: "\f01a"; } .fa-arrow-circle-o-up:before { content: "\f01b"; } .fa-inbox:before { content: "\f01c"; } .fa-play-circle-o:before { content: "\f01d"; } .fa-rotate-right:before, .fa-repeat:before { content: "\f01e"; } .fa-refresh:before { content: "\f021"; } .fa-list-alt:before { content: "\f022"; } .fa-lock:before { content: "\f023"; } .fa-flag:before { content: "\f024"; } .fa-headphones:before { content: "\f025"; } .fa-volume-off:before { content: "\f026"; } .fa-volume-down:before { content: "\f027"; } .fa-volume-up:before { content: "\f028"; } .fa-qrcode:before { content: "\f029"; } .fa-barcode:before { content: "\f02a"; } .fa-tag:before { content: "\f02b"; } .fa-tags:before { content: "\f02c"; } .fa-book:before { content: "\f02d"; } .fa-bookmark:before { content: "\f02e"; } .fa-print:before { content: "\f02f"; } .fa-camera:before { content: "\f030"; } .fa-font:before { content: "\f031"; } .fa-bold:before { content: "\f032"; } .fa-italic:before { content: "\f033"; } .fa-text-height:before { content: "\f034"; } .fa-text-width:before { content: "\f035"; } .fa-align-left:before { content: "\f036"; } .fa-align-center:before { content: "\f037"; } .fa-align-right:before { content: "\f038"; } .fa-align-justify:before { content: "\f039"; } .fa-list:before { content: "\f03a"; } .fa-dedent:before, .fa-outdent:before { content: "\f03b"; } .fa-indent:before { content: "\f03c"; } .fa-video-camera:before { content: "\f03d"; } .fa-photo:before, .fa-image:before, .fa-picture-o:before { content: "\f03e"; } .fa-pencil:before { content: "\f040"; } .fa-map-marker:before { content: "\f041"; } .fa-adjust:before { content: "\f042"; } .fa-tint:before { content: "\f043"; } .fa-edit:before, .fa-pencil-square-o:before { content: "\f044"; } .fa-share-square-o:before { content: "\f045"; } .fa-check-square-o:before { content: "\f046"; } .fa-arrows:before { content: "\f047"; } .fa-step-backward:before { content: "\f048"; } .fa-fast-backward:before { content: "\f049"; } .fa-backward:before { content: "\f04a"; } .fa-play:before { content: "\f04b"; } .fa-pause:before { content: "\f04c"; } .fa-stop:before { content: "\f04d"; } .fa-forward:before { content: "\f04e"; } .fa-fast-forward:before { content: "\f050"; } .fa-step-forward:before { content: "\f051"; } .fa-eject:before { content: "\f052"; } .fa-chevron-left:before { content: "\f053"; } .fa-chevron-right:before { content: "\f054"; } .fa-plus-circle:before { content: "\f055"; } .fa-minus-circle:before { content: "\f056"; } .fa-times-circle:before { content: "\f057"; } .fa-check-circle:before { content: "\f058"; } .fa-question-circle:before { content: "\f059"; } .fa-info-circle:before { content: "\f05a"; } .fa-crosshairs:before { content: "\f05b"; } .fa-times-circle-o:before { content: "\f05c"; } .fa-check-circle-o:before { content: "\f05d"; } .fa-ban:before { content: "\f05e"; } .fa-arrow-left:before { content: "\f060"; } .fa-arrow-right:before { content: "\f061"; } .fa-arrow-up:before { content: "\f062"; } .fa-arrow-down:before { content: "\f063"; } .fa-mail-forward:before, .fa-share:before { content: "\f064"; } .fa-expand:before { content: "\f065"; } .fa-compress:before { content: "\f066"; } .fa-plus:before { content: "\f067"; } .fa-minus:before { content: "\f068"; } .fa-asterisk:before { content: "\f069"; } .fa-exclamation-circle:before { content: "\f06a"; } .fa-gift:before { content: "\f06b"; } .fa-leaf:before { content: "\f06c"; } .fa-fire:before { content: "\f06d"; } .fa-eye:before { content: "\f06e"; } .fa-eye-slash:before { content: "\f070"; } .fa-warning:before, .fa-exclamation-triangle:before { content: "\f071"; } .fa-plane:before { content: "\f072"; } .fa-calendar:before { content: "\f073"; } .fa-random:before { content: "\f074"; } .fa-comment:before { content: "\f075"; } .fa-magnet:before { content: "\f076"; } .fa-chevron-up:before { content: "\f077"; } .fa-chevron-down:before { content: "\f078"; } .fa-retweet:before { content: "\f079"; } .fa-shopping-cart:before { content: "\f07a"; } .fa-folder:before { content: "\f07b"; } .fa-folder-open:before { content: "\f07c"; } .fa-arrows-v:before { content: "\f07d"; } .fa-arrows-h:before { content: "\f07e"; } .fa-bar-chart-o:before, .fa-bar-chart:before { content: "\f080"; } .fa-twitter-square:before { content: "\f081"; } .fa-facebook-square:before { content: "\f082"; } .fa-camera-retro:before { content: "\f083"; } .fa-key:before { content: "\f084"; } .fa-gears:before, .fa-cogs:before { content: "\f085"; } .fa-comments:before { content: "\f086"; } .fa-thumbs-o-up:before { content: "\f087"; } .fa-thumbs-o-down:before { content: "\f088"; } .fa-star-half:before { content: "\f089"; } .fa-heart-o:before { content: "\f08a"; } .fa-sign-out:before { content: "\f08b"; } .fa-linkedin-square:before { content: "\f08c"; } .fa-thumb-tack:before { content: "\f08d"; } .fa-external-link:before { content: "\f08e"; } .fa-sign-in:before { content: "\f090"; } .fa-trophy:before { content: "\f091"; } .fa-github-square:before { content: "\f092"; } .fa-upload:before { content: "\f093"; } .fa-lemon-o:before { content: "\f094"; } .fa-phone:before { content: "\f095"; } .fa-square-o:before { content: "\f096"; } .fa-bookmark-o:before { content: "\f097"; } .fa-phone-square:before { content: "\f098"; } .fa-twitter:before { content: "\f099"; } .fa-facebook-f:before, .fa-facebook:before { content: "\f09a"; } .fa-github:before { content: "\f09b"; } .fa-unlock:before { content: "\f09c"; } .fa-credit-card:before { content: "\f09d"; } .fa-feed:before, .fa-rss:before { content: "\f09e"; } .fa-hdd-o:before { content: "\f0a0"; } .fa-bullhorn:before { content: "\f0a1"; } .fa-bell:before { content: "\f0f3"; } .fa-certificate:before { content: "\f0a3"; } .fa-hand-o-right:before { content: "\f0a4"; } .fa-hand-o-left:before { content: "\f0a5"; } .fa-hand-o-up:before { content: "\f0a6"; } .fa-hand-o-down:before { content: "\f0a7"; } .fa-arrow-circle-left:before { content: "\f0a8"; } .fa-arrow-circle-right:before { content: "\f0a9"; } .fa-arrow-circle-up:before { content: "\f0aa"; } .fa-arrow-circle-down:before { content: "\f0ab"; } .fa-globe:before { content: "\f0ac"; } .fa-wrench:before { content: "\f0ad"; } .fa-tasks:before { content: "\f0ae"; } .fa-filter:before { content: "\f0b0"; } .fa-briefcase:before { content: "\f0b1"; } .fa-arrows-alt:before { content: "\f0b2"; } .fa-group:before, .fa-users:before { content: "\f0c0"; } .fa-chain:before, .fa-link:before { content: "\f0c1"; } .fa-cloud:before { content: "\f0c2"; } .fa-flask:before { content: "\f0c3"; } .fa-cut:before, .fa-scissors:before { content: "\f0c4"; } .fa-copy:before, .fa-files-o:before { content: "\f0c5"; } .fa-paperclip:before { content: "\f0c6"; } .fa-save:before, .fa-floppy-o:before { content: "\f0c7"; } .fa-square:before { content: "\f0c8"; } .fa-navicon:before, .fa-reorder:before, .fa-bars:before { content: "\f0c9"; } .fa-list-ul:before { content: "\f0ca"; } .fa-list-ol:before { content: "\f0cb"; } .fa-strikethrough:before { content: "\f0cc"; } .fa-underline:before { content: "\f0cd"; } .fa-table:before { content: "\f0ce"; } .fa-magic:before { content: "\f0d0"; } .fa-truck:before { content: "\f0d1"; } .fa-pinterest:before { content: "\f0d2"; } .fa-pinterest-square:before { content: "\f0d3"; } .fa-google-plus-square:before { content: "\f0d4"; } .fa-google-plus:before { content: "\f0d5"; } .fa-money:before { content: "\f0d6"; } .fa-caret-down:before { content: "\f0d7"; } .fa-caret-up:before { content: "\f0d8"; } .fa-caret-left:before { content: "\f0d9"; } .fa-caret-right:before { content: "\f0da"; } .fa-columns:before { content: "\f0db"; } .fa-unsorted:before, .fa-sort:before { content: "\f0dc"; } .fa-sort-down:before, .fa-sort-desc:before { content: "\f0dd"; } .fa-sort-up:before, .fa-sort-asc:before { content: "\f0de"; } .fa-envelope:before { content: "\f0e0"; } .fa-linkedin:before { content: "\f0e1"; } .fa-rotate-left:before, .fa-undo:before { content: "\f0e2"; } .fa-legal:before, .fa-gavel:before { content: "\f0e3"; } .fa-dashboard:before, .fa-tachometer:before { content: "\f0e4"; } .fa-comment-o:before { content: "\f0e5"; } .fa-comments-o:before { content: "\f0e6"; } .fa-flash:before, .fa-bolt:before { content: "\f0e7"; } .fa-sitemap:before { content: "\f0e8"; } .fa-umbrella:before { content: "\f0e9"; } .fa-paste:before, .fa-clipboard:before { content: "\f0ea"; } .fa-lightbulb-o:before { content: "\f0eb"; } .fa-exchange:before { content: "\f0ec"; } .fa-cloud-download:before { content: "\f0ed"; } .fa-cloud-upload:before { content: "\f0ee"; } .fa-user-md:before { content: "\f0f0"; } .fa-stethoscope:before { content: "\f0f1"; } .fa-suitcase:before { content: "\f0f2"; } .fa-bell-o:before { content: "\f0a2"; } .fa-coffee:before { content: "\f0f4"; } .fa-cutlery:before { content: "\f0f5"; } .fa-file-text-o:before { content: "\f0f6"; } .fa-building-o:before { content: "\f0f7"; } .fa-hospital-o:before { content: "\f0f8"; } .fa-ambulance:before { content: "\f0f9"; } .fa-medkit:before { content: "\f0fa"; } .fa-fighter-jet:before { content: "\f0fb"; } .fa-beer:before { content: "\f0fc"; } .fa-h-square:before { content: "\f0fd"; } .fa-plus-square:before { content: "\f0fe"; } .fa-angle-double-left:before { content: "\f100"; } .fa-angle-double-right:before { content: "\f101"; } .fa-angle-double-up:before { content: "\f102"; } .fa-angle-double-down:before { content: "\f103"; } .fa-angle-left:before { content: "\f104"; } .fa-angle-right:before { content: "\f105"; } .fa-angle-up:before { content: "\f106"; } .fa-angle-down:before { content: "\f107"; } .fa-desktop:before { content: "\f108"; } .fa-laptop:before { content: "\f109"; } .fa-tablet:before { content: "\f10a"; } .fa-mobile-phone:before, .fa-mobile:before { content: "\f10b"; } .fa-circle-o:before { content: "\f10c"; } .fa-quote-left:before { content: "\f10d"; } .fa-quote-right:before { content: "\f10e"; } .fa-spinner:before { content: "\f110"; } .fa-circle:before { content: "\f111"; } .fa-mail-reply:before, .fa-reply:before { content: "\f112"; } .fa-github-alt:before { content: "\f113"; } .fa-folder-o:before { content: "\f114"; } .fa-folder-open-o:before { content: "\f115"; } .fa-smile-o:before { content: "\f118"; } .fa-frown-o:before { content: "\f119"; } .fa-meh-o:before { content: "\f11a"; } .fa-gamepad:before { content: "\f11b"; } .fa-keyboard-o:before { content: "\f11c"; } .fa-flag-o:before { content: "\f11d"; } .fa-flag-checkered:before { content: "\f11e"; } .fa-terminal:before { content: "\f120"; } .fa-code:before { content: "\f121"; } .fa-mail-reply-all:before, .fa-reply-all:before { content: "\f122"; } .fa-star-half-empty:before, .fa-star-half-full:before, .fa-star-half-o:before { content: "\f123"; } .fa-location-arrow:before { content: "\f124"; } .fa-crop:before { content: "\f125"; } .fa-code-fork:before { content: "\f126"; } .fa-unlink:before, .fa-chain-broken:before { content: "\f127"; } .fa-question:before { content: "\f128"; } .fa-info:before { content: "\f129"; } .fa-exclamation:before { content: "\f12a"; } .fa-superscript:before { content: "\f12b"; } .fa-subscript:before { content: "\f12c"; } .fa-eraser:before { content: "\f12d"; } .fa-puzzle-piece:before { content: "\f12e"; } .fa-microphone:before { content: "\f130"; } .fa-microphone-slash:before { content: "\f131"; } .fa-shield:before { content: "\f132"; } .fa-calendar-o:before { content: "\f133"; } .fa-fire-extinguisher:before { content: "\f134"; } .fa-rocket:before { content: "\f135"; } .fa-maxcdn:before { content: "\f136"; } .fa-chevron-circle-left:before { content: "\f137"; } .fa-chevron-circle-right:before { content: "\f138"; } .fa-chevron-circle-up:before { content: "\f139"; } .fa-chevron-circle-down:before { content: "\f13a"; } .fa-html5:before { content: "\f13b"; } .fa-css3:before { content: "\f13c"; } .fa-anchor:before { content: "\f13d"; } .fa-unlock-alt:before { content: "\f13e"; } .fa-bullseye:before { content: "\f140"; } .fa-ellipsis-h:before { content: "\f141"; } .fa-ellipsis-v:before { content: "\f142"; } .fa-rss-square:before { content: "\f143"; } .fa-play-circle:before { content: "\f144"; } .fa-ticket:before { content: "\f145"; } .fa-minus-square:before { content: "\f146"; } .fa-minus-square-o:before { content: "\f147"; } .fa-level-up:before { content: "\f148"; } .fa-level-down:before { content: "\f149"; } .fa-check-square:before { content: "\f14a"; } .fa-pencil-square:before { content: "\f14b"; } .fa-external-link-square:before { content: "\f14c"; } .fa-share-square:before { content: "\f14d"; } .fa-compass:before { content: "\f14e"; } .fa-toggle-down:before, .fa-caret-square-o-down:before { content: "\f150"; } .fa-toggle-up:before, .fa-caret-square-o-up:before { content: "\f151"; } .fa-toggle-right:before, .fa-caret-square-o-right:before { content: "\f152"; } .fa-euro:before, .fa-eur:before { content: "\f153"; } .fa-gbp:before { content: "\f154"; } .fa-dollar:before, .fa-usd:before { content: "\f155"; } .fa-rupee:before, .fa-inr:before { content: "\f156"; } .fa-cny:before, .fa-rmb:before, .fa-yen:before, .fa-jpy:before { content: "\f157"; } .fa-ruble:before, .fa-rouble:before, .fa-rub:before { content: "\f158"; } .fa-won:before, .fa-krw:before { content: "\f159"; } .fa-bitcoin:before, .fa-btc:before { content: "\f15a"; } .fa-file:before { content: "\f15b"; } .fa-file-text:before { content: "\f15c"; } .fa-sort-alpha-asc:before { content: "\f15d"; } .fa-sort-alpha-desc:before { content: "\f15e"; } .fa-sort-amount-asc:before { content: "\f160"; } .fa-sort-amount-desc:before { content: "\f161"; } .fa-sort-numeric-asc:before { content: "\f162"; } .fa-sort-numeric-desc:before { content: "\f163"; } .fa-thumbs-up:before { content: "\f164"; } .fa-thumbs-down:before { content: "\f165"; } .fa-youtube-square:before { content: "\f166"; } .fa-youtube:before { content: "\f167"; } .fa-xing:before { content: "\f168"; } .fa-xing-square:before { content: "\f169"; } .fa-youtube-play:before { content: "\f16a"; } .fa-dropbox:before { content: "\f16b"; } .fa-stack-overflow:before { content: "\f16c"; } .fa-instagram:before { content: "\f16d"; } .fa-flickr:before { content: "\f16e"; } .fa-adn:before { content: "\f170"; } .fa-bitbucket:before { content: "\f171"; } .fa-bitbucket-square:before { content: "\f172"; } .fa-tumblr:before { content: "\f173"; } .fa-tumblr-square:before { content: "\f174"; } .fa-long-arrow-down:before { content: "\f175"; } .fa-long-arrow-up:before { content: "\f176"; } .fa-long-arrow-left:before { content: "\f177"; } .fa-long-arrow-right:before { content: "\f178"; } .fa-apple:before { content: "\f179"; } .fa-windows:before { content: "\f17a"; } .fa-android:before { content: "\f17b"; } .fa-linux:before { content: "\f17c"; } .fa-dribbble:before { content: "\f17d"; } .fa-skype:before { content: "\f17e"; } .fa-foursquare:before { content: "\f180"; } .fa-trello:before { content: "\f181"; } .fa-female:before { content: "\f182"; } .fa-male:before { content: "\f183"; } .fa-gittip:before, .fa-gratipay:before { content: "\f184"; } .fa-sun-o:before { content: "\f185"; } .fa-moon-o:before { content: "\f186"; } .fa-archive:before { content: "\f187"; } .fa-bug:before { content: "\f188"; } .fa-vk:before { content: "\f189"; } .fa-weibo:before { content: "\f18a"; } .fa-renren:before { content: "\f18b"; } .fa-pagelines:before { content: "\f18c"; } .fa-stack-exchange:before { content: "\f18d"; } .fa-arrow-circle-o-right:before { content: "\f18e"; } .fa-arrow-circle-o-left:before { content: "\f190"; } .fa-toggle-left:before, .fa-caret-square-o-left:before { content: "\f191"; } .fa-dot-circle-o:before { content: "\f192"; } .fa-wheelchair:before { content: "\f193"; } .fa-vimeo-square:before { content: "\f194"; } .fa-turkish-lira:before, .fa-try:before { content: "\f195"; } .fa-plus-square-o:before { content: "\f196"; } .fa-space-shuttle:before { content: "\f197"; } .fa-slack:before { content: "\f198"; } .fa-envelope-square:before { content: "\f199"; } .fa-wordpress:before { content: "\f19a"; } .fa-openid:before { content: "\f19b"; } .fa-institution:before, .fa-bank:before, .fa-university:before { content: "\f19c"; } .fa-mortar-board:before, .fa-graduation-cap:before { content: "\f19d"; } .fa-yahoo:before { content: "\f19e"; } .fa-google:before { content: "\f1a0"; } .fa-reddit:before { content: "\f1a1"; } .fa-reddit-square:before { content: "\f1a2"; } .fa-stumbleupon-circle:before { content: "\f1a3"; } .fa-stumbleupon:before { content: "\f1a4"; } .fa-delicious:before { content: "\f1a5"; } .fa-digg:before { content: "\f1a6"; } .fa-pied-piper-pp:before { content: "\f1a7"; } .fa-pied-piper-alt:before { content: "\f1a8"; } .fa-drupal:before { content: "\f1a9"; } .fa-joomla:before { content: "\f1aa"; } .fa-language:before { content: "\f1ab"; } .fa-fax:before { content: "\f1ac"; } .fa-building:before { content: "\f1ad"; } .fa-child:before { content: "\f1ae"; } .fa-paw:before { content: "\f1b0"; } .fa-spoon:before { content: "\f1b1"; } .fa-cube:before { content: "\f1b2"; } .fa-cubes:before { content: "\f1b3"; } .fa-behance:before { content: "\f1b4"; } .fa-behance-square:before { content: "\f1b5"; } .fa-steam:before { content: "\f1b6"; } .fa-steam-square:before { content: "\f1b7"; } .fa-recycle:before { content: "\f1b8"; } .fa-automobile:before, .fa-car:before { content: "\f1b9"; } .fa-cab:before, .fa-taxi:before { content: "\f1ba"; } .fa-tree:before { content: "\f1bb"; } .fa-spotify:before { content: "\f1bc"; } .fa-deviantart:before { content: "\f1bd"; } .fa-soundcloud:before { content: "\f1be"; } .fa-database:before { content: "\f1c0"; } .fa-file-pdf-o:before { content: "\f1c1"; } .fa-file-word-o:before { content: "\f1c2"; } .fa-file-excel-o:before { content: "\f1c3"; } .fa-file-powerpoint-o:before { content: "\f1c4"; } .fa-file-photo-o:before, .fa-file-picture-o:before, .fa-file-image-o:before { content: "\f1c5"; } .fa-file-zip-o:before, .fa-file-archive-o:before { content: "\f1c6"; } .fa-file-sound-o:before, .fa-file-audio-o:before { content: "\f1c7"; } .fa-file-movie-o:before, .fa-file-video-o:before { content: "\f1c8"; } .fa-file-code-o:before { content: "\f1c9"; } .fa-vine:before { content: "\f1ca"; } .fa-codepen:before { content: "\f1cb"; } .fa-jsfiddle:before { content: "\f1cc"; } .fa-life-bouy:before, .fa-life-buoy:before, .fa-life-saver:before, .fa-support:before, .fa-life-ring:before { content: "\f1cd"; } .fa-circle-o-notch:before { content: "\f1ce"; } .fa-ra:before, .fa-resistance:before, .fa-rebel:before { content: "\f1d0"; } .fa-ge:before, .fa-empire:before { content: "\f1d1"; } .fa-git-square:before { content: "\f1d2"; } .fa-git:before { content: "\f1d3"; } .fa-y-combinator-square:before, .fa-yc-square:before, .fa-hacker-news:before { content: "\f1d4"; } .fa-tencent-weibo:before { content: "\f1d5"; } .fa-qq:before { content: "\f1d6"; } .fa-wechat:before, .fa-weixin:before { content: "\f1d7"; } .fa-send:before, .fa-paper-plane:before { content: "\f1d8"; } .fa-send-o:before, .fa-paper-plane-o:before { content: "\f1d9"; } .fa-history:before { content: "\f1da"; } .fa-circle-thin:before { content: "\f1db"; } .fa-header:before { content: "\f1dc"; } .fa-paragraph:before { content: "\f1dd"; } .fa-sliders:before { content: "\f1de"; } .fa-share-alt:before { content: "\f1e0"; } .fa-share-alt-square:before { content: "\f1e1"; } .fa-bomb:before { content: "\f1e2"; } .fa-soccer-ball-o:before, .fa-futbol-o:before { content: "\f1e3"; } .fa-tty:before { content: "\f1e4"; } .fa-binoculars:before { content: "\f1e5"; } .fa-plug:before { content: "\f1e6"; } .fa-slideshare:before { content: "\f1e7"; } .fa-twitch:before { content: "\f1e8"; } .fa-yelp:before { content: "\f1e9"; } .fa-newspaper-o:before { content: "\f1ea"; } .fa-wifi:before { content: "\f1eb"; } .fa-calculator:before { content: "\f1ec"; } .fa-paypal:before { content: "\f1ed"; } .fa-google-wallet:before { content: "\f1ee"; } .fa-cc-visa:before { content: "\f1f0"; } .fa-cc-mastercard:before { content: "\f1f1"; } .fa-cc-discover:before { content: "\f1f2"; } .fa-cc-amex:before { content: "\f1f3"; } .fa-cc-paypal:before { content: "\f1f4"; } .fa-cc-stripe:before { content: "\f1f5"; } .fa-bell-slash:before { content: "\f1f6"; } .fa-bell-slash-o:before { content: "\f1f7"; } .fa-trash:before { content: "\f1f8"; } .fa-copyright:before { content: "\f1f9"; } .fa-at:before { content: "\f1fa"; } .fa-eyedropper:before { content: "\f1fb"; } .fa-paint-brush:before { content: "\f1fc"; } .fa-birthday-cake:before { content: "\f1fd"; } .fa-area-chart:before { content: "\f1fe"; } .fa-pie-chart:before { content: "\f200"; } .fa-line-chart:before { content: "\f201"; } .fa-lastfm:before { content: "\f202"; } .fa-lastfm-square:before { content: "\f203"; } .fa-toggle-off:before { content: "\f204"; } .fa-toggle-on:before { content: "\f205"; } .fa-bicycle:before { content: "\f206"; } .fa-bus:before { content: "\f207"; } .fa-ioxhost:before { content: "\f208"; } .fa-angellist:before { content: "\f209"; } .fa-cc:before { content: "\f20a"; } .fa-shekel:before, .fa-sheqel:before, .fa-ils:before { content: "\f20b"; } .fa-meanpath:before { content: "\f20c"; } .fa-buysellads:before { content: "\f20d"; } .fa-connectdevelop:before { content: "\f20e"; } .fa-dashcube:before { content: "\f210"; } .fa-forumbee:before { content: "\f211"; } .fa-leanpub:before { content: "\f212"; } .fa-sellsy:before { content: "\f213"; } .fa-shirtsinbulk:before { content: "\f214"; } .fa-simplybuilt:before { content: "\f215"; } .fa-skyatlas:before { content: "\f216"; } .fa-cart-plus:before { content: "\f217"; } .fa-cart-arrow-down:before { content: "\f218"; } .fa-diamond:before { content: "\f219"; } .fa-ship:before { content: "\f21a"; } .fa-user-secret:before { content: "\f21b"; } .fa-motorcycle:before { content: "\f21c"; } .fa-street-view:before { content: "\f21d"; } .fa-heartbeat:before { content: "\f21e"; } .fa-venus:before { content: "\f221"; } .fa-mars:before { content: "\f222"; } .fa-mercury:before { content: "\f223"; } .fa-intersex:before, .fa-transgender:before { content: "\f224"; } .fa-transgender-alt:before { content: "\f225"; } .fa-venus-double:before { content: "\f226"; } .fa-mars-double:before { content: "\f227"; } .fa-venus-mars:before { content: "\f228"; } .fa-mars-stroke:before { content: "\f229"; } .fa-mars-stroke-v:before { content: "\f22a"; } .fa-mars-stroke-h:before { content: "\f22b"; } .fa-neuter:before { content: "\f22c"; } .fa-genderless:before { content: "\f22d"; } .fa-facebook-official:before { content: "\f230"; } .fa-pinterest-p:before { content: "\f231"; } .fa-whatsapp:before { content: "\f232"; } .fa-server:before { content: "\f233"; } .fa-user-plus:before { content: "\f234"; } .fa-user-times:before { content: "\f235"; } .fa-hotel:before, .fa-bed:before { content: "\f236"; } .fa-viacoin:before { content: "\f237"; } .fa-train:before { content: "\f238"; } .fa-subway:before { content: "\f239"; } .fa-medium:before { content: "\f23a"; } .fa-yc:before, .fa-y-combinator:before { content: "\f23b"; } .fa-optin-monster:before { content: "\f23c"; } .fa-opencart:before { content: "\f23d"; } .fa-expeditedssl:before { content: "\f23e"; } .fa-battery-4:before, .fa-battery:before, .fa-battery-full:before { content: "\f240"; } .fa-battery-3:before, .fa-battery-three-quarters:before { content: "\f241"; } .fa-battery-2:before, .fa-battery-half:before { content: "\f242"; } .fa-battery-1:before, .fa-battery-quarter:before { content: "\f243"; } .fa-battery-0:before, .fa-battery-empty:before { content: "\f244"; } .fa-mouse-pointer:before { content: "\f245"; } .fa-i-cursor:before { content: "\f246"; } .fa-object-group:before { content: "\f247"; } .fa-object-ungroup:before { content: "\f248"; } .fa-sticky-note:before { content: "\f249"; } .fa-sticky-note-o:before { content: "\f24a"; } .fa-cc-jcb:before { content: "\f24b"; } .fa-cc-diners-club:before { content: "\f24c"; } .fa-clone:before { content: "\f24d"; } .fa-balance-scale:before { content: "\f24e"; } .fa-hourglass-o:before { content: "\f250"; } .fa-hourglass-1:before, .fa-hourglass-start:before { content: "\f251"; } .fa-hourglass-2:before, .fa-hourglass-half:before { content: "\f252"; } .fa-hourglass-3:before, .fa-hourglass-end:before { content: "\f253"; } .fa-hourglass:before { content: "\f254"; } .fa-hand-grab-o:before, .fa-hand-rock-o:before { content: "\f255"; } .fa-hand-stop-o:before, .fa-hand-paper-o:before { content: "\f256"; } .fa-hand-scissors-o:before { content: "\f257"; } .fa-hand-lizard-o:before { content: "\f258"; } .fa-hand-spock-o:before { content: "\f259"; } .fa-hand-pointer-o:before { content: "\f25a"; } .fa-hand-peace-o:before { content: "\f25b"; } .fa-trademark:before { content: "\f25c"; } .fa-registered:before { content: "\f25d"; } .fa-creative-commons:before { content: "\f25e"; } .fa-gg:before { content: "\f260"; } .fa-gg-circle:before { content: "\f261"; } .fa-tripadvisor:before { content: "\f262"; } .fa-odnoklassniki:before { content: "\f263"; } .fa-odnoklassniki-square:before { content: "\f264"; } .fa-get-pocket:before { content: "\f265"; } .fa-wikipedia-w:before { content: "\f266"; } .fa-safari:before { content: "\f267"; } .fa-chrome:before { content: "\f268"; } .fa-firefox:before { content: "\f269"; } .fa-opera:before { content: "\f26a"; } .fa-internet-explorer:before { content: "\f26b"; } .fa-tv:before, .fa-television:before { content: "\f26c"; } .fa-contao:before { content: "\f26d"; } .fa-500px:before { content: "\f26e"; } .fa-amazon:before { content: "\f270"; } .fa-calendar-plus-o:before { content: "\f271"; } .fa-calendar-minus-o:before { content: "\f272"; } .fa-calendar-times-o:before { content: "\f273"; } .fa-calendar-check-o:before { content: "\f274"; } .fa-industry:before { content: "\f275"; } .fa-map-pin:before { content: "\f276"; } .fa-map-signs:before { content: "\f277"; } .fa-map-o:before { content: "\f278"; } .fa-map:before { content: "\f279"; } .fa-commenting:before { content: "\f27a"; } .fa-commenting-o:before { content: "\f27b"; } .fa-houzz:before { content: "\f27c"; } .fa-vimeo:before { content: "\f27d"; } .fa-black-tie:before { content: "\f27e"; } .fa-fonticons:before { content: "\f280"; } .fa-reddit-alien:before { content: "\f281"; } .fa-edge:before { content: "\f282"; } .fa-credit-card-alt:before { content: "\f283"; } .fa-codiepie:before { content: "\f284"; } .fa-modx:before { content: "\f285"; } .fa-fort-awesome:before { content: "\f286"; } .fa-usb:before { content: "\f287"; } .fa-product-hunt:before { content: "\f288"; } .fa-mixcloud:before { content: "\f289"; } .fa-scribd:before { content: "\f28a"; } .fa-pause-circle:before { content: "\f28b"; } .fa-pause-circle-o:before { content: "\f28c"; } .fa-stop-circle:before { content: "\f28d"; } .fa-stop-circle-o:before { content: "\f28e"; } .fa-shopping-bag:before { content: "\f290"; } .fa-shopping-basket:before { content: "\f291"; } .fa-hashtag:before { content: "\f292"; } .fa-bluetooth:before { content: "\f293"; } .fa-bluetooth-b:before { content: "\f294"; } .fa-percent:before { content: "\f295"; } .fa-gitlab:before { content: "\f296"; } .fa-wpbeginner:before { content: "\f297"; } .fa-wpforms:before { content: "\f298"; } .fa-envira:before { content: "\f299"; } .fa-universal-access:before { content: "\f29a"; } .fa-wheelchair-alt:before { content: "\f29b"; } .fa-question-circle-o:before { content: "\f29c"; } .fa-blind:before { content: "\f29d"; } .fa-audio-description:before { content: "\f29e"; } .fa-volume-control-phone:before { content: "\f2a0"; } .fa-braille:before { content: "\f2a1"; } .fa-assistive-listening-systems:before { content: "\f2a2"; } .fa-asl-interpreting:before, .fa-american-sign-language-interpreting:before { content: "\f2a3"; } .fa-deafness:before, .fa-hard-of-hearing:before, .fa-deaf:before { content: "\f2a4"; } .fa-glide:before { content: "\f2a5"; } .fa-glide-g:before { content: "\f2a6"; } .fa-signing:before, .fa-sign-language:before { content: "\f2a7"; } .fa-low-vision:before { content: "\f2a8"; } .fa-viadeo:before { content: "\f2a9"; } .fa-viadeo-square:before { content: "\f2aa"; } .fa-snapchat:before { content: "\f2ab"; } .fa-snapchat-ghost:before { content: "\f2ac"; } .fa-snapchat-square:before { content: "\f2ad"; } .fa-pied-piper:before { content: "\f2ae"; } .fa-first-order:before { content: "\f2b0"; } .fa-yoast:before { content: "\f2b1"; } .fa-themeisle:before { content: "\f2b2"; } .fa-google-plus-circle:before, .fa-google-plus-official:before { content: "\f2b3"; } .fa-fa:before, .fa-font-awesome:before { content: "\f2b4"; } .fa-handshake-o:before { content: "\f2b5"; } .fa-envelope-open:before { content: "\f2b6"; } .fa-envelope-open-o:before { content: "\f2b7"; } .fa-linode:before { content: "\f2b8"; } .fa-address-book:before { content: "\f2b9"; } .fa-address-book-o:before { content: "\f2ba"; } .fa-vcard:before, .fa-address-card:before { content: "\f2bb"; } .fa-vcard-o:before, .fa-address-card-o:before { content: "\f2bc"; } .fa-user-circle:before { content: "\f2bd"; } .fa-user-circle-o:before { content: "\f2be"; } .fa-user-o:before { content: "\f2c0"; } .fa-id-badge:before { content: "\f2c1"; } .fa-drivers-license:before, .fa-id-card:before { content: "\f2c2"; } .fa-drivers-license-o:before, .fa-id-card-o:before { content: "\f2c3"; } .fa-quora:before { content: "\f2c4"; } .fa-free-code-camp:before { content: "\f2c5"; } .fa-telegram:before { content: "\f2c6"; } .fa-thermometer-4:before, .fa-thermometer:before, .fa-thermometer-full:before { content: "\f2c7"; } .fa-thermometer-3:before, .fa-thermometer-three-quarters:before { content: "\f2c8"; } .fa-thermometer-2:before, .fa-thermometer-half:before { content: "\f2c9"; } .fa-thermometer-1:before, .fa-thermometer-quarter:before { content: "\f2ca"; } .fa-thermometer-0:before, .fa-thermometer-empty:before { content: "\f2cb"; } .fa-shower:before { content: "\f2cc"; } .fa-bathtub:before, .fa-s15:before, .fa-bath:before { content: "\f2cd"; } .fa-podcast:before { content: "\f2ce"; } .fa-window-maximize:before { content: "\f2d0"; } .fa-window-minimize:before { content: "\f2d1"; } .fa-window-restore:before { content: "\f2d2"; } .fa-times-rectangle:before, .fa-window-close:before { content: "\f2d3"; } .fa-times-rectangle-o:before, .fa-window-close-o:before { content: "\f2d4"; } .fa-bandcamp:before { content: "\f2d5"; } .fa-grav:before { content: "\f2d6"; } .fa-etsy:before { content: "\f2d7"; } .fa-imdb:before { content: "\f2d8"; } .fa-ravelry:before { content: "\f2d9"; } .fa-eercast:before { content: "\f2da"; } .fa-microchip:before { content: "\f2db"; } .fa-snowflake-o:before { content: "\f2dc"; } .fa-superpowers:before { content: "\f2dd"; } .fa-wpexplorer:before { content: "\f2de"; } .fa-meetup:before { content: "\f2e0"; } .sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); border: 0; } .sr-only-focusable:active, .sr-only-focusable:focus { position: static; width: auto; height: auto; margin: 0; overflow: visible; clip: auto; } /* +* +* Material Design Icons +* -------------------------------------------------- +*/ @font-face { font-family: "Material Design Icons"; src: url("../fonts/materialdesignicons-webfont.eot?v=1.4.57"); src: url("../fonts/materialdesignicons-webfont.eot?#iefix&v=1.4.57") format("embedded-opentype"), url("../fonts/materialdesignicons-webfont.woff2?v=1.4.57") format("woff2"), url("../fonts/materialdesignicons-webfont.woff?v=1.4.57") format("woff"), url("../fonts/materialdesignicons-webfont.ttf?v=1.4.57") format("truetype"), url("../fonts/materialdesignicons-webfont.svg?v=1.4.57#materialdesigniconsregular") format("svg"); font-weight: normal; font-style: normal; } .mdi { display: inline-block; font: normal normal normal 24px/1 "Material Design Icons"; font-size: inherit; text-rendering: auto; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; transform: translate(0, 0); } .mdi-access-point:before { content: "\f101"; } .mdi-access-point-network:before { content: "\f102"; } .mdi-account:before { content: "\f103"; } .mdi-account-alert:before { content: "\f104"; } .mdi-account-box:before { content: "\f105"; } .mdi-account-box-outline:before { content: "\f106"; } .mdi-account-check:before { content: "\f107"; } .mdi-account-circle:before { content: "\f108"; } .mdi-account-convert:before { content: "\f109"; } .mdi-account-key:before { content: "\f10a"; } .mdi-account-location:before { content: "\f10b"; } .mdi-account-minus:before { content: "\f10c"; } .mdi-account-multiple:before { content: "\f10d"; } .mdi-account-multiple-outline:before { content: "\f10e"; } .mdi-account-multiple-plus:before { content: "\f10f"; } .mdi-account-network:before { content: "\f110"; } .mdi-account-off:before { content: "\f111"; } .mdi-account-outline:before { content: "\f112"; } .mdi-account-plus:before { content: "\f113"; } .mdi-account-remove:before { content: "\f114"; } .mdi-account-search:before { content: "\f115"; } .mdi-account-star:before { content: "\f116"; } .mdi-account-star-variant:before { content: "\f117"; } .mdi-account-switch:before { content: "\f118"; } .mdi-adjust:before { content: "\f119"; } .mdi-air-conditioner:before { content: "\f11a"; } .mdi-airballoon:before { content: "\f11b"; } .mdi-airplane:before { content: "\f11c"; } .mdi-airplane-off:before { content: "\f11d"; } .mdi-airplay:before { content: "\f11e"; } .mdi-alarm:before { content: "\f11f"; } .mdi-alarm-check:before { content: "\f120"; } .mdi-alarm-multiple:before { content: "\f121"; } .mdi-alarm-off:before { content: "\f122"; } .mdi-alarm-plus:before { content: "\f123"; } .mdi-album:before { content: "\f124"; } .mdi-alert:before { content: "\f125"; } .mdi-alert-box:before { content: "\f126"; } .mdi-alert-circle:before { content: "\f127"; } .mdi-alert-octagon:before { content: "\f128"; } .mdi-alert-outline:before { content: "\f129"; } .mdi-alpha:before { content: "\f12a"; } .mdi-alphabetical:before { content: "\f12b"; } .mdi-amazon:before { content: "\f12c"; } .mdi-amazon-clouddrive:before { content: "\f12d"; } .mdi-ambulance:before { content: "\f12e"; } .mdi-anchor:before { content: "\f12f"; } .mdi-android:before { content: "\f130"; } .mdi-android-debug-bridge:before { content: "\f131"; } .mdi-android-studio:before { content: "\f132"; } .mdi-apple:before { content: "\f133"; } .mdi-apple-finder:before { content: "\f134"; } .mdi-apple-ios:before { content: "\f135"; } .mdi-apple-mobileme:before { content: "\f136"; } .mdi-apple-safari:before { content: "\f137"; } .mdi-appnet:before { content: "\f138"; } .mdi-apps:before { content: "\f139"; } .mdi-archive:before { content: "\f13a"; } .mdi-arrange-bring-forward:before { content: "\f13b"; } .mdi-arrange-bring-to-front:before { content: "\f13c"; } .mdi-arrange-send-backward:before { content: "\f13d"; } .mdi-arrange-send-to-back:before { content: "\f13e"; } .mdi-arrow-all:before { content: "\f13f"; } .mdi-arrow-bottom-drop-circle:before { content: "\f140"; } .mdi-arrow-bottom-left:before { content: "\f141"; } .mdi-arrow-bottom-right:before { content: "\f142"; } .mdi-arrow-collapse:before { content: "\f143"; } .mdi-arrow-down:before { content: "\f144"; } .mdi-arrow-down-bold:before { content: "\f145"; } .mdi-arrow-down-bold-circle:before { content: "\f146"; } .mdi-arrow-down-bold-circle-outline:before { content: "\f147"; } .mdi-arrow-down-bold-hexagon-outline:before { content: "\f148"; } .mdi-arrow-expand:before { content: "\f149"; } .mdi-arrow-left:before { content: "\f14a"; } .mdi-arrow-left-bold:before { content: "\f14b"; } .mdi-arrow-left-bold-circle:before { content: "\f14c"; } .mdi-arrow-left-bold-circle-outline:before { content: "\f14d"; } .mdi-arrow-left-bold-hexagon-outline:before { content: "\f14e"; } .mdi-arrow-right:before { content: "\f14f"; } .mdi-arrow-right-bold:before { content: "\f150"; } .mdi-arrow-right-bold-circle:before { content: "\f151"; } .mdi-arrow-right-bold-circle-outline:before { content: "\f152"; } .mdi-arrow-right-bold-hexagon-outline:before { content: "\f153"; } .mdi-arrow-top-left:before { content: "\f154"; } .mdi-arrow-top-right:before { content: "\f155"; } .mdi-arrow-up:before { content: "\f156"; } .mdi-arrow-up-bold:before { content: "\f157"; } .mdi-arrow-up-bold-circle:before { content: "\f158"; } .mdi-arrow-up-bold-circle-outline:before { content: "\f159"; } .mdi-arrow-up-bold-hexagon-outline:before { content: "\f15a"; } .mdi-assistant:before { content: "\f15b"; } .mdi-at:before { content: "\f15c"; } .mdi-attachment:before { content: "\f15d"; } .mdi-audiobook:before { content: "\f15e"; } .mdi-auto-fix:before { content: "\f15f"; } .mdi-auto-upload:before { content: "\f160"; } .mdi-autorenew:before { content: "\f161"; } .mdi-av-timer:before { content: "\f162"; } .mdi-baby:before { content: "\f163"; } .mdi-backburger:before { content: "\f164"; } .mdi-backspace:before { content: "\f165"; } .mdi-backup-restore:before { content: "\f166"; } .mdi-bank:before { content: "\f167"; } .mdi-barcode:before { content: "\f168"; } .mdi-barcode-scan:before { content: "\f169"; } .mdi-barley:before { content: "\f16a"; } .mdi-barrel:before { content: "\f16b"; } .mdi-basecamp:before { content: "\f16c"; } .mdi-basket:before { content: "\f16d"; } .mdi-basket-fill:before { content: "\f16e"; } .mdi-basket-unfill:before { content: "\f16f"; } .mdi-battery:before { content: "\f170"; } .mdi-battery-10:before { content: "\f171"; } .mdi-battery-20:before { content: "\f172"; } .mdi-battery-30:before { content: "\f173"; } .mdi-battery-40:before { content: "\f174"; } .mdi-battery-50:before { content: "\f175"; } .mdi-battery-60:before { content: "\f176"; } .mdi-battery-70:before { content: "\f177"; } .mdi-battery-80:before { content: "\f178"; } .mdi-battery-90:before { content: "\f179"; } .mdi-battery-alert:before { content: "\f17a"; } .mdi-battery-charging:before { content: "\f17b"; } .mdi-battery-charging-100:before { content: "\f17c"; } .mdi-battery-charging-20:before { content: "\f17d"; } .mdi-battery-charging-30:before { content: "\f17e"; } .mdi-battery-charging-40:before { content: "\f17f"; } .mdi-battery-charging-60:before { content: "\f180"; } .mdi-battery-charging-80:before { content: "\f181"; } .mdi-battery-charging-90:before { content: "\f182"; } .mdi-battery-minus:before { content: "\f183"; } .mdi-battery-negative:before { content: "\f184"; } .mdi-battery-outline:before { content: "\f185"; } .mdi-battery-plus:before { content: "\f186"; } .mdi-battery-positive:before { content: "\f187"; } .mdi-battery-unknown:before { content: "\f188"; } .mdi-beach:before { content: "\f189"; } .mdi-beaker:before { content: "\f18a"; } .mdi-beaker-empty:before { content: "\f18b"; } .mdi-beaker-empty-outline:before { content: "\f18c"; } .mdi-beaker-outline:before { content: "\f18d"; } .mdi-beats:before { content: "\f18e"; } .mdi-beer:before { content: "\f18f"; } .mdi-behance:before { content: "\f190"; } .mdi-bell:before { content: "\f191"; } .mdi-bell-off:before { content: "\f192"; } .mdi-bell-outline:before { content: "\f193"; } .mdi-bell-plus:before { content: "\f194"; } .mdi-bell-ring:before { content: "\f195"; } .mdi-bell-ring-outline:before { content: "\f196"; } .mdi-bell-sleep:before { content: "\f197"; } .mdi-beta:before { content: "\f198"; } .mdi-bike:before { content: "\f199"; } .mdi-bing:before { content: "\f19a"; } .mdi-binoculars:before { content: "\f19b"; } .mdi-bio:before { content: "\f19c"; } .mdi-biohazard:before { content: "\f19d"; } .mdi-bitbucket:before { content: "\f19e"; } .mdi-black-mesa:before { content: "\f19f"; } .mdi-blackberry:before { content: "\f1a0"; } .mdi-blender:before { content: "\f1a1"; } .mdi-blinds:before { content: "\f1a2"; } .mdi-block-helper:before { content: "\f1a3"; } .mdi-blogger:before { content: "\f1a4"; } .mdi-bluetooth:before { content: "\f1a5"; } .mdi-bluetooth-audio:before { content: "\f1a6"; } .mdi-bluetooth-connect:before { content: "\f1a7"; } .mdi-bluetooth-off:before { content: "\f1a8"; } .mdi-bluetooth-settings:before { content: "\f1a9"; } .mdi-bluetooth-transfer:before { content: "\f1aa"; } .mdi-blur:before { content: "\f1ab"; } .mdi-blur-linear:before { content: "\f1ac"; } .mdi-blur-off:before { content: "\f1ad"; } .mdi-blur-radial:before { content: "\f1ae"; } .mdi-bone:before { content: "\f1af"; } .mdi-book:before { content: "\f1b0"; } .mdi-book-multiple:before { content: "\f1b1"; } .mdi-book-multiple-variant:before { content: "\f1b2"; } .mdi-book-open:before { content: "\f1b3"; } .mdi-book-open-variant:before { content: "\f1b4"; } .mdi-book-variant:before { content: "\f1b5"; } .mdi-bookmark:before { content: "\f1b6"; } .mdi-bookmark-check:before { content: "\f1b7"; } .mdi-bookmark-music:before { content: "\f1b8"; } .mdi-bookmark-outline:before { content: "\f1b9"; } .mdi-bookmark-outline-plus:before { content: "\f1ba"; } .mdi-bookmark-plus:before { content: "\f1bb"; } .mdi-bookmark-remove:before { content: "\f1bc"; } .mdi-border-all:before { content: "\f1bd"; } .mdi-border-bottom:before { content: "\f1be"; } .mdi-border-color:before { content: "\f1bf"; } .mdi-border-horizontal:before { content: "\f1c0"; } .mdi-border-inside:before { content: "\f1c1"; } .mdi-border-left:before { content: "\f1c2"; } .mdi-border-none:before { content: "\f1c3"; } .mdi-border-outside:before { content: "\f1c4"; } .mdi-border-right:before { content: "\f1c5"; } .mdi-border-style:before { content: "\f1c6"; } .mdi-border-top:before { content: "\f1c7"; } .mdi-border-vertical:before { content: "\f1c8"; } .mdi-bowling:before { content: "\f1c9"; } .mdi-box:before { content: "\f1ca"; } .mdi-box-cutter:before { content: "\f1cb"; } .mdi-briefcase:before { content: "\f1cc"; } .mdi-briefcase-check:before { content: "\f1cd"; } .mdi-briefcase-download:before { content: "\f1ce"; } .mdi-briefcase-upload:before { content: "\f1cf"; } .mdi-brightness-1:before { content: "\f1d0"; } .mdi-brightness-2:before { content: "\f1d1"; } .mdi-brightness-3:before { content: "\f1d2"; } .mdi-brightness-4:before { content: "\f1d3"; } .mdi-brightness-5:before { content: "\f1d4"; } .mdi-brightness-6:before { content: "\f1d5"; } .mdi-brightness-7:before { content: "\f1d6"; } .mdi-brightness-auto:before { content: "\f1d7"; } .mdi-broom:before { content: "\f1d8"; } .mdi-brush:before { content: "\f1d9"; } .mdi-bug:before { content: "\f1da"; } .mdi-bulletin-board:before { content: "\f1db"; } .mdi-bullhorn:before { content: "\f1dc"; } .mdi-bus:before { content: "\f1dd"; } .mdi-cached:before { content: "\f1de"; } .mdi-cake:before { content: "\f1df"; } .mdi-cake-layered:before { content: "\f1e0"; } .mdi-cake-variant:before { content: "\f1e1"; } .mdi-calculator:before { content: "\f1e2"; } .mdi-calendar:before { content: "\f1e3"; } .mdi-calendar-blank:before { content: "\f1e4"; } .mdi-calendar-check:before { content: "\f1e5"; } .mdi-calendar-clock:before { content: "\f1e6"; } .mdi-calendar-multiple:before { content: "\f1e7"; } .mdi-calendar-multiple-check:before { content: "\f1e8"; } .mdi-calendar-plus:before { content: "\f1e9"; } .mdi-calendar-remove:before { content: "\f1ea"; } .mdi-calendar-text:before { content: "\f1eb"; } .mdi-calendar-today:before { content: "\f1ec"; } .mdi-call-made:before { content: "\f1ed"; } .mdi-call-merge:before { content: "\f1ee"; } .mdi-call-missed:before { content: "\f1ef"; } .mdi-call-received:before { content: "\f1f0"; } .mdi-call-split:before { content: "\f1f1"; } .mdi-camcorder:before { content: "\f1f2"; } .mdi-camcorder-box:before { content: "\f1f3"; } .mdi-camcorder-box-off:before { content: "\f1f4"; } .mdi-camcorder-off:before { content: "\f1f5"; } .mdi-camera:before { content: "\f1f6"; } .mdi-camera-enhance:before { content: "\f1f7"; } .mdi-camera-front:before { content: "\f1f8"; } .mdi-camera-front-variant:before { content: "\f1f9"; } .mdi-camera-iris:before { content: "\f1fa"; } .mdi-camera-party-mode:before { content: "\f1fb"; } .mdi-camera-rear:before { content: "\f1fc"; } .mdi-camera-rear-variant:before { content: "\f1fd"; } .mdi-camera-switch:before { content: "\f1fe"; } .mdi-camera-timer:before { content: "\f1ff"; } .mdi-candycane:before { content: "\f200"; } .mdi-car:before { content: "\f201"; } .mdi-car-battery:before { content: "\f202"; } .mdi-car-connected:before { content: "\f203"; } .mdi-car-wash:before { content: "\f204"; } .mdi-carrot:before { content: "\f205"; } .mdi-cart:before { content: "\f206"; } .mdi-cart-outline:before { content: "\f207"; } .mdi-cart-plus:before { content: "\f208"; } .mdi-case-sensitive-alt:before { content: "\f209"; } .mdi-cash:before { content: "\f20a"; } .mdi-cash-100:before { content: "\f20b"; } .mdi-cash-multiple:before { content: "\f20c"; } .mdi-cash-usd:before { content: "\f20d"; } .mdi-cast:before { content: "\f20e"; } .mdi-cast-connected:before { content: "\f20f"; } .mdi-castle:before { content: "\f210"; } .mdi-cat:before { content: "\f211"; } .mdi-cellphone:before { content: "\f212"; } .mdi-cellphone-android:before { content: "\f213"; } .mdi-cellphone-basic:before { content: "\f214"; } .mdi-cellphone-dock:before { content: "\f215"; } .mdi-cellphone-iphone:before { content: "\f216"; } .mdi-cellphone-link:before { content: "\f217"; } .mdi-cellphone-link-off:before { content: "\f218"; } .mdi-cellphone-settings:before { content: "\f219"; } .mdi-certificate:before { content: "\f21a"; } .mdi-chair-school:before { content: "\f21b"; } .mdi-chart-arc:before { content: "\f21c"; } .mdi-chart-areaspline:before { content: "\f21d"; } .mdi-chart-bar:before { content: "\f21e"; } .mdi-chart-histogram:before { content: "\f21f"; } .mdi-chart-line:before { content: "\f220"; } .mdi-chart-pie:before { content: "\f221"; } .mdi-check:before { content: "\f222"; } .mdi-check-all:before { content: "\f223"; } .mdi-checkbox-blank:before { content: "\f224"; } .mdi-checkbox-blank-circle:before { content: "\f225"; } .mdi-checkbox-blank-circle-outline:before { content: "\f226"; } .mdi-checkbox-blank-outline:before { content: "\f227"; } .mdi-checkbox-marked:before { content: "\f228"; } .mdi-checkbox-marked-circle:before { content: "\f229"; } .mdi-checkbox-marked-circle-outline:before { content: "\f22a"; } .mdi-checkbox-marked-outline:before { content: "\f22b"; } .mdi-checkbox-multiple-blank:before { content: "\f22c"; } .mdi-checkbox-multiple-blank-outline:before { content: "\f22d"; } .mdi-checkbox-multiple-marked:before { content: "\f22e"; } .mdi-checkbox-multiple-marked-outline:before { content: "\f22f"; } .mdi-checkerboard:before { content: "\f230"; } .mdi-chemical-weapon:before { content: "\f231"; } .mdi-chevron-double-down:before { content: "\f232"; } .mdi-chevron-double-left:before { content: "\f233"; } .mdi-chevron-double-right:before { content: "\f234"; } .mdi-chevron-double-up:before { content: "\f235"; } .mdi-chevron-down:before { content: "\f236"; } .mdi-chevron-left:before { content: "\f237"; } .mdi-chevron-right:before { content: "\f238"; } .mdi-chevron-up:before { content: "\f239"; } .mdi-church:before { content: "\f23a"; } .mdi-cisco-webex:before { content: "\f23b"; } .mdi-city:before { content: "\f23c"; } .mdi-clipboard:before { content: "\f23d"; } .mdi-clipboard-account:before { content: "\f23e"; } .mdi-clipboard-alert:before { content: "\f23f"; } .mdi-clipboard-arrow-down:before { content: "\f240"; } .mdi-clipboard-arrow-left:before { content: "\f241"; } .mdi-clipboard-check:before { content: "\f242"; } .mdi-clipboard-outline:before { content: "\f243"; } .mdi-clipboard-text:before { content: "\f244"; } .mdi-clippy:before { content: "\f245"; } .mdi-clock:before { content: "\f246"; } .mdi-clock-end:before { content: "\f247"; } .mdi-clock-fast:before { content: "\f248"; } .mdi-clock-in:before { content: "\f249"; } .mdi-clock-out:before { content: "\f24a"; } .mdi-clock-start:before { content: "\f24b"; } .mdi-close:before { content: "\f24c"; } .mdi-close-box:before { content: "\f24d"; } .mdi-close-box-outline:before { content: "\f24e"; } .mdi-close-circle:before { content: "\f24f"; } .mdi-close-circle-outline:before { content: "\f250"; } .mdi-close-network:before { content: "\f251"; } .mdi-close-octagon:before { content: "\f252"; } .mdi-close-octagon-outline:before { content: "\f253"; } .mdi-closed-caption:before { content: "\f254"; } .mdi-cloud:before { content: "\f255"; } .mdi-cloud-check:before { content: "\f256"; } .mdi-cloud-circle:before { content: "\f257"; } .mdi-cloud-download:before { content: "\f258"; } .mdi-cloud-outline:before { content: "\f259"; } .mdi-cloud-outline-off:before { content: "\f25a"; } .mdi-cloud-print:before { content: "\f25b"; } .mdi-cloud-print-outline:before { content: "\f25c"; } .mdi-cloud-upload:before { content: "\f25d"; } .mdi-code-array:before { content: "\f25e"; } .mdi-code-braces:before { content: "\f25f"; } .mdi-code-brackets:before { content: "\f260"; } .mdi-code-equal:before { content: "\f261"; } .mdi-code-greater-than:before { content: "\f262"; } .mdi-code-greater-than-or-equal:before { content: "\f263"; } .mdi-code-less-than:before { content: "\f264"; } .mdi-code-less-than-or-equal:before { content: "\f265"; } .mdi-code-not-equal:before { content: "\f266"; } .mdi-code-not-equal-variant:before { content: "\f267"; } .mdi-code-parentheses:before { content: "\f268"; } .mdi-code-string:before { content: "\f269"; } .mdi-code-tags:before { content: "\f26a"; } .mdi-codepen:before { content: "\f26b"; } .mdi-coffee:before { content: "\f26c"; } .mdi-coffee-to-go:before { content: "\f26d"; } .mdi-coin:before { content: "\f26e"; } .mdi-color-helper:before { content: "\f26f"; } .mdi-comment:before { content: "\f270"; } .mdi-comment-account:before { content: "\f271"; } .mdi-comment-account-outline:before { content: "\f272"; } .mdi-comment-alert:before { content: "\f273"; } .mdi-comment-alert-outline:before { content: "\f274"; } .mdi-comment-check:before { content: "\f275"; } .mdi-comment-check-outline:before { content: "\f276"; } .mdi-comment-multiple-outline:before { content: "\f277"; } .mdi-comment-outline:before { content: "\f278"; } .mdi-comment-plus-outline:before { content: "\f279"; } .mdi-comment-processing:before { content: "\f27a"; } .mdi-comment-processing-outline:before { content: "\f27b"; } .mdi-comment-question-outline:before { content: "\f27c"; } .mdi-comment-remove-outline:before { content: "\f27d"; } .mdi-comment-text:before { content: "\f27e"; } .mdi-comment-text-outline:before { content: "\f27f"; } .mdi-compare:before { content: "\f280"; } .mdi-compass:before { content: "\f281"; } .mdi-compass-outline:before { content: "\f282"; } .mdi-console:before { content: "\f283"; } .mdi-contact-mail:before { content: "\f284"; } .mdi-content-copy:before { content: "\f285"; } .mdi-content-cut:before { content: "\f286"; } .mdi-content-duplicate:before { content: "\f287"; } .mdi-content-paste:before { content: "\f288"; } .mdi-content-save:before { content: "\f289"; } .mdi-content-save-all:before { content: "\f28a"; } .mdi-contrast:before { content: "\f28b"; } .mdi-contrast-box:before { content: "\f28c"; } .mdi-contrast-circle:before { content: "\f28d"; } .mdi-cookie:before { content: "\f28e"; } .mdi-cow:before { content: "\f28f"; } .mdi-credit-card:before { content: "\f290"; } .mdi-credit-card-multiple:before { content: "\f291"; } .mdi-credit-card-scan:before { content: "\f292"; } .mdi-crop:before { content: "\f293"; } .mdi-crop-free:before { content: "\f294"; } .mdi-crop-landscape:before { content: "\f295"; } .mdi-crop-portrait:before { content: "\f296"; } .mdi-crop-square:before { content: "\f297"; } .mdi-crosshairs:before { content: "\f298"; } .mdi-crosshairs-gps:before { content: "\f299"; } .mdi-crown:before { content: "\f29a"; } .mdi-cube:before { content: "\f29b"; } .mdi-cube-outline:before { content: "\f29c"; } .mdi-cube-send:before { content: "\f29d"; } .mdi-cube-unfolded:before { content: "\f29e"; } .mdi-cup:before { content: "\f29f"; } .mdi-cup-water:before { content: "\f2a0"; } .mdi-currency-btc:before { content: "\f2a1"; } .mdi-currency-eur:before { content: "\f2a2"; } .mdi-currency-gbp:before { content: "\f2a3"; } .mdi-currency-inr:before { content: "\f2a4"; } .mdi-currency-ngn:before { content: "\f2a5"; } .mdi-currency-rub:before { content: "\f2a6"; } .mdi-currency-try:before { content: "\f2a7"; } .mdi-currency-usd:before { content: "\f2a8"; } .mdi-cursor-default:before { content: "\f2a9"; } .mdi-cursor-default-outline:before { content: "\f2aa"; } .mdi-cursor-move:before { content: "\f2ab"; } .mdi-cursor-pointer:before { content: "\f2ac"; } .mdi-database:before { content: "\f2ad"; } .mdi-database-minus:before { content: "\f2ae"; } .mdi-database-plus:before { content: "\f2af"; } .mdi-debug-step-into:before { content: "\f2b0"; } .mdi-debug-step-out:before { content: "\f2b1"; } .mdi-debug-step-over:before { content: "\f2b2"; } .mdi-decimal-decrease:before { content: "\f2b3"; } .mdi-decimal-increase:before { content: "\f2b4"; } .mdi-delete:before { content: "\f2b5"; } .mdi-delete-variant:before { content: "\f2b6"; } .mdi-delta:before { content: "\f2b7"; } .mdi-deskphone:before { content: "\f2b8"; } .mdi-desktop-mac:before { content: "\f2b9"; } .mdi-desktop-tower:before { content: "\f2ba"; } .mdi-details:before { content: "\f2bb"; } .mdi-deviantart:before { content: "\f2bc"; } .mdi-diamond:before { content: "\f2bd"; } .mdi-dice:before { content: "\f2be"; } .mdi-dice-1:before { content: "\f2bf"; } .mdi-dice-2:before { content: "\f2c0"; } .mdi-dice-3:before { content: "\f2c1"; } .mdi-dice-4:before { content: "\f2c2"; } .mdi-dice-5:before { content: "\f2c3"; } .mdi-dice-6:before { content: "\f2c4"; } .mdi-directions:before { content: "\f2c5"; } .mdi-disk-alert:before { content: "\f2c6"; } .mdi-disqus:before { content: "\f2c7"; } .mdi-disqus-outline:before { content: "\f2c8"; } .mdi-division:before { content: "\f2c9"; } .mdi-division-box:before { content: "\f2ca"; } .mdi-dns:before { content: "\f2cb"; } .mdi-domain:before { content: "\f2cc"; } .mdi-dots-horizontal:before { content: "\f2cd"; } .mdi-dots-vertical:before { content: "\f2ce"; } .mdi-download:before { content: "\f2cf"; } .mdi-drag:before { content: "\f2d0"; } .mdi-drag-horizontal:before { content: "\f2d1"; } .mdi-drag-vertical:before { content: "\f2d2"; } .mdi-drawing:before { content: "\f2d3"; } .mdi-drawing-box:before { content: "\f2d4"; } .mdi-dribbble:before { content: "\f2d5"; } .mdi-dribbble-box:before { content: "\f2d6"; } .mdi-drone:before { content: "\f2d7"; } .mdi-dropbox:before { content: "\f2d8"; } .mdi-drupal:before { content: "\f2d9"; } .mdi-duck:before { content: "\f2da"; } .mdi-dumbbell:before { content: "\f2db"; } .mdi-earth:before { content: "\f2dc"; } .mdi-earth-off:before { content: "\f2dd"; } .mdi-edge:before { content: "\f2de"; } .mdi-eject:before { content: "\f2df"; } .mdi-elevation-decline:before { content: "\f2e0"; } .mdi-elevation-rise:before { content: "\f2e1"; } .mdi-elevator:before { content: "\f2e2"; } .mdi-email:before { content: "\f2e3"; } .mdi-email-open:before { content: "\f2e4"; } .mdi-email-outline:before { content: "\f2e5"; } .mdi-email-secure:before { content: "\f2e6"; } .mdi-emoticon:before { content: "\f2e7"; } .mdi-emoticon-cool:before { content: "\f2e8"; } .mdi-emoticon-devil:before { content: "\f2e9"; } .mdi-emoticon-happy:before { content: "\f2ea"; } .mdi-emoticon-neutral:before { content: "\f2eb"; } .mdi-emoticon-poop:before { content: "\f2ec"; } .mdi-emoticon-sad:before { content: "\f2ed"; } .mdi-emoticon-tongue:before { content: "\f2ee"; } .mdi-engine:before { content: "\f2ef"; } .mdi-engine-outline:before { content: "\f2f0"; } .mdi-equal:before { content: "\f2f1"; } .mdi-equal-box:before { content: "\f2f2"; } .mdi-eraser:before { content: "\f2f3"; } .mdi-escalator:before { content: "\f2f4"; } .mdi-ethernet:before { content: "\f2f5"; } .mdi-ethernet-cable:before { content: "\f2f6"; } .mdi-ethernet-cable-off:before { content: "\f2f7"; } .mdi-etsy:before { content: "\f2f8"; } .mdi-evernote:before { content: "\f2f9"; } .mdi-exclamation:before { content: "\f2fa"; } .mdi-exit-to-app:before { content: "\f2fb"; } .mdi-export:before { content: "\f2fc"; } .mdi-eye:before { content: "\f2fd"; } .mdi-eye-off:before { content: "\f2fe"; } .mdi-eyedropper:before { content: "\f2ff"; } .mdi-eyedropper-variant:before { content: "\f300"; } .mdi-facebook:before { content: "\f301"; } .mdi-facebook-box:before { content: "\f302"; } .mdi-facebook-messenger:before { content: "\f303"; } .mdi-factory:before { content: "\f304"; } .mdi-fan:before { content: "\f305"; } .mdi-fast-forward:before { content: "\f306"; } .mdi-fax:before { content: "\f307"; } .mdi-ferry:before { content: "\f308"; } .mdi-file:before { content: "\f309"; } .mdi-file-chart:before { content: "\f30a"; } .mdi-file-check:before { content: "\f30b"; } .mdi-file-cloud:before { content: "\f30c"; } .mdi-file-delimited:before { content: "\f30d"; } .mdi-file-document:before { content: "\f30e"; } .mdi-file-document-box:before { content: "\f30f"; } .mdi-file-excel:before { content: "\f310"; } .mdi-file-excel-box:before { content: "\f311"; } .mdi-file-export:before { content: "\f312"; } .mdi-file-find:before { content: "\f313"; } .mdi-file-image:before { content: "\f314"; } .mdi-file-import:before { content: "\f315"; } .mdi-file-lock:before { content: "\f316"; } .mdi-file-multiple:before { content: "\f317"; } .mdi-file-music:before { content: "\f318"; } .mdi-file-outline:before { content: "\f319"; } .mdi-file-pdf:before { content: "\f31a"; } .mdi-file-pdf-box:before { content: "\f31b"; } .mdi-file-powerpoint:before { content: "\f31c"; } .mdi-file-powerpoint-box:before { content: "\f31d"; } .mdi-file-presentation-box:before { content: "\f31e"; } .mdi-file-send:before { content: "\f31f"; } .mdi-file-video:before { content: "\f320"; } .mdi-file-word:before { content: "\f321"; } .mdi-file-word-box:before { content: "\f322"; } .mdi-file-xml:before { content: "\f323"; } .mdi-film:before { content: "\f324"; } .mdi-filmstrip:before { content: "\f325"; } .mdi-filmstrip-off:before { content: "\f326"; } .mdi-filter:before { content: "\f327"; } .mdi-filter-outline:before { content: "\f328"; } .mdi-filter-remove:before { content: "\f329"; } .mdi-filter-remove-outline:before { content: "\f32a"; } .mdi-filter-variant:before { content: "\f32b"; } .mdi-fingerprint:before { content: "\f32c"; } .mdi-fire:before { content: "\f32d"; } .mdi-firefox:before { content: "\f32e"; } .mdi-fish:before { content: "\f32f"; } .mdi-flag:before { content: "\f330"; } .mdi-flag-checkered:before { content: "\f331"; } .mdi-flag-outline:before { content: "\f332"; } .mdi-flag-outline-variant:before { content: "\f333"; } .mdi-flag-triangle:before { content: "\f334"; } .mdi-flag-variant:before { content: "\f335"; } .mdi-flash:before { content: "\f336"; } .mdi-flash-auto:before { content: "\f337"; } .mdi-flash-off:before { content: "\f338"; } .mdi-flashlight:before { content: "\f339"; } .mdi-flashlight-off:before { content: "\f33a"; } .mdi-flattr:before { content: "\f33b"; } .mdi-flip-to-back:before { content: "\f33c"; } .mdi-flip-to-front:before { content: "\f33d"; } .mdi-floppy:before { content: "\f33e"; } .mdi-flower:before { content: "\f33f"; } .mdi-folder:before { content: "\f340"; } .mdi-folder-account:before { content: "\f341"; } .mdi-folder-download:before { content: "\f342"; } .mdi-folder-google-drive:before { content: "\f343"; } .mdi-folder-image:before { content: "\f344"; } .mdi-folder-lock:before { content: "\f345"; } .mdi-folder-lock-open:before { content: "\f346"; } .mdi-folder-move:before { content: "\f347"; } .mdi-folder-multiple:before { content: "\f348"; } .mdi-folder-multiple-image:before { content: "\f349"; } .mdi-folder-multiple-outline:before { content: "\f34a"; } .mdi-folder-outline:before { content: "\f34b"; } .mdi-folder-plus:before { content: "\f34c"; } .mdi-folder-remove:before { content: "\f34d"; } .mdi-folder-upload:before { content: "\f34e"; } .mdi-food:before { content: "\f34f"; } .mdi-food-apple:before { content: "\f350"; } .mdi-food-variant:before { content: "\f351"; } .mdi-football:before { content: "\f352"; } .mdi-football-australian:before { content: "\f353"; } .mdi-football-helmet:before { content: "\f354"; } .mdi-format-align-center:before { content: "\f355"; } .mdi-format-align-justify:before { content: "\f356"; } .mdi-format-align-left:before { content: "\f357"; } .mdi-format-align-right:before { content: "\f358"; } .mdi-format-bold:before { content: "\f359"; } .mdi-format-clear:before { content: "\f35a"; } .mdi-format-color-fill:before { content: "\f35b"; } .mdi-format-float-center:before { content: "\f35c"; } .mdi-format-float-left:before { content: "\f35d"; } .mdi-format-float-none:before { content: "\f35e"; } .mdi-format-float-right:before { content: "\f35f"; } .mdi-format-header-1:before { content: "\f360"; } .mdi-format-header-2:before { content: "\f361"; } .mdi-format-header-3:before { content: "\f362"; } .mdi-format-header-4:before { content: "\f363"; } .mdi-format-header-5:before { content: "\f364"; } .mdi-format-header-6:before { content: "\f365"; } .mdi-format-header-decrease:before { content: "\f366"; } .mdi-format-header-equal:before { content: "\f367"; } .mdi-format-header-increase:before { content: "\f368"; } .mdi-format-header-pound:before { content: "\f369"; } .mdi-format-indent-decrease:before { content: "\f36a"; } .mdi-format-indent-increase:before { content: "\f36b"; } .mdi-format-italic:before { content: "\f36c"; } .mdi-format-line-spacing:before { content: "\f36d"; } .mdi-format-list-bulleted:before { content: "\f36e"; } .mdi-format-list-bulleted-type:before { content: "\f36f"; } .mdi-format-list-numbers:before { content: "\f370"; } .mdi-format-paint:before { content: "\f371"; } .mdi-format-paragraph:before { content: "\f372"; } .mdi-format-quote:before { content: "\f373"; } .mdi-format-size:before { content: "\f374"; } .mdi-format-strikethrough:before { content: "\f375"; } .mdi-format-strikethrough-variant:before { content: "\f376"; } .mdi-format-subscript:before { content: "\f377"; } .mdi-format-superscript:before { content: "\f378"; } .mdi-format-text:before { content: "\f379"; } .mdi-format-textdirection-l-to-r:before { content: "\f37a"; } .mdi-format-textdirection-r-to-l:before { content: "\f37b"; } .mdi-format-underline:before { content: "\f37c"; } .mdi-format-wrap-inline:before { content: "\f37d"; } .mdi-format-wrap-square:before { content: "\f37e"; } .mdi-format-wrap-tight:before { content: "\f37f"; } .mdi-format-wrap-top-bottom:before { content: "\f380"; } .mdi-forum:before { content: "\f381"; } .mdi-forward:before { content: "\f382"; } .mdi-foursquare:before { content: "\f383"; } .mdi-fridge:before { content: "\f384"; } .mdi-fridge-filled:before { content: "\f385"; } .mdi-fridge-filled-bottom:before { content: "\f386"; } .mdi-fridge-filled-top:before { content: "\f387"; } .mdi-fullscreen:before { content: "\f388"; } .mdi-fullscreen-exit:before { content: "\f389"; } .mdi-function:before { content: "\f38a"; } .mdi-gamepad:before { content: "\f38b"; } .mdi-gamepad-variant:before { content: "\f38c"; } .mdi-gas-station:before { content: "\f38d"; } .mdi-gate:before { content: "\f38e"; } .mdi-gauge:before { content: "\f38f"; } .mdi-gavel:before { content: "\f390"; } .mdi-gender-female:before { content: "\f391"; } .mdi-gender-male:before { content: "\f392"; } .mdi-gender-male-female:before { content: "\f393"; } .mdi-gender-transgender:before { content: "\f394"; } .mdi-ghost:before { content: "\f395"; } .mdi-gift:before { content: "\f396"; } .mdi-git:before { content: "\f397"; } .mdi-github-box:before { content: "\f398"; } .mdi-github-circle:before { content: "\f399"; } .mdi-glass-flute:before { content: "\f39a"; } .mdi-glass-mug:before { content: "\f39b"; } .mdi-glass-stange:before { content: "\f39c"; } .mdi-glass-tulip:before { content: "\f39d"; } .mdi-glasses:before { content: "\f39e"; } .mdi-gmail:before { content: "\f39f"; } .mdi-google:before { content: "\f3a0"; } .mdi-google-cardboard:before { content: "\f3a1"; } .mdi-google-chrome:before { content: "\f3a2"; } .mdi-google-circles:before { content: "\f3a3"; } .mdi-google-circles-communities:before { content: "\f3a4"; } .mdi-google-circles-extended:before { content: "\f3a5"; } .mdi-google-circles-group:before { content: "\f3a6"; } .mdi-google-controller:before { content: "\f3a7"; } .mdi-google-controller-off:before { content: "\f3a8"; } .mdi-google-drive:before { content: "\f3a9"; } .mdi-google-earth:before { content: "\f3aa"; } .mdi-google-glass:before { content: "\f3ab"; } .mdi-google-nearby:before { content: "\f3ac"; } .mdi-google-pages:before { content: "\f3ad"; } .mdi-google-physical-web:before { content: "\f3ae"; } .mdi-google-play:before { content: "\f3af"; } .mdi-google-plus:before { content: "\f3b0"; } .mdi-google-plus-box:before { content: "\f3b1"; } .mdi-google-translate:before { content: "\f3b2"; } .mdi-google-wallet:before { content: "\f3b3"; } .mdi-grid:before { content: "\f3b4"; } .mdi-grid-off:before { content: "\f3b5"; } .mdi-group:before { content: "\f3b6"; } .mdi-guitar:before { content: "\f3b7"; } .mdi-guitar-pick:before { content: "\f3b8"; } .mdi-guitar-pick-outline:before { content: "\f3b9"; } .mdi-hand-pointing-right:before { content: "\f3ba"; } .mdi-hanger:before { content: "\f3bb"; } .mdi-hangouts:before { content: "\f3bc"; } .mdi-harddisk:before { content: "\f3bd"; } .mdi-headphones:before { content: "\f3be"; } .mdi-headphones-box:before { content: "\f3bf"; } .mdi-headphones-settings:before { content: "\f3c0"; } .mdi-headset:before { content: "\f3c1"; } .mdi-headset-dock:before { content: "\f3c2"; } .mdi-headset-off:before { content: "\f3c3"; } .mdi-heart:before { content: "\f3c4"; } .mdi-heart-box:before { content: "\f3c5"; } .mdi-heart-box-outline:before { content: "\f3c6"; } .mdi-heart-broken:before { content: "\f3c7"; } .mdi-heart-outline:before { content: "\f3c8"; } .mdi-help:before { content: "\f3c9"; } .mdi-help-circle:before { content: "\f3ca"; } .mdi-hexagon:before { content: "\f3cb"; } .mdi-hexagon-outline:before { content: "\f3cc"; } .mdi-history:before { content: "\f3cd"; } .mdi-hololens:before { content: "\f3ce"; } .mdi-home:before { content: "\f3cf"; } .mdi-home-modern:before { content: "\f3d0"; } .mdi-home-variant:before { content: "\f3d1"; } .mdi-hops:before { content: "\f3d2"; } .mdi-hospital:before { content: "\f3d3"; } .mdi-hospital-building:before { content: "\f3d4"; } .mdi-hospital-marker:before { content: "\f3d5"; } .mdi-hotel:before { content: "\f3d6"; } .mdi-houzz:before { content: "\f3d7"; } .mdi-houzz-box:before { content: "\f3d8"; } .mdi-human:before { content: "\f3d9"; } .mdi-human-child:before { content: "\f3da"; } .mdi-human-male-female:before { content: "\f3db"; } .mdi-image:before { content: "\f3dc"; } .mdi-image-album:before { content: "\f3dd"; } .mdi-image-area:before { content: "\f3de"; } .mdi-image-area-close:before { content: "\f3df"; } .mdi-image-broken:before { content: "\f3e0"; } .mdi-image-broken-variant:before { content: "\f3e1"; } .mdi-image-filter:before { content: "\f3e2"; } .mdi-image-filter-black-white:before { content: "\f3e3"; } .mdi-image-filter-center-focus:before { content: "\f3e4"; } .mdi-image-filter-center-focus-weak:before { content: "\f3e5"; } .mdi-image-filter-drama:before { content: "\f3e6"; } .mdi-image-filter-frames:before { content: "\f3e7"; } .mdi-image-filter-hdr:before { content: "\f3e8"; } .mdi-image-filter-none:before { content: "\f3e9"; } .mdi-image-filter-tilt-shift:before { content: "\f3ea"; } .mdi-image-filter-vintage:before { content: "\f3eb"; } .mdi-image-multiple:before { content: "\f3ec"; } .mdi-import:before { content: "\f3ed"; } .mdi-inbox:before { content: "\f3ee"; } .mdi-information:before { content: "\f3ef"; } .mdi-information-outline:before { content: "\f3f0"; } .mdi-instagram:before { content: "\f3f1"; } .mdi-instapaper:before { content: "\f3f2"; } .mdi-internet-explorer:before { content: "\f3f3"; } .mdi-invert-colors:before { content: "\f3f4"; } .mdi-jeepney:before { content: "\f3f5"; } .mdi-jira:before { content: "\f3f6"; } .mdi-jsfiddle:before { content: "\f3f7"; } .mdi-keg:before { content: "\f3f8"; } .mdi-key:before { content: "\f3f9"; } .mdi-key-change:before { content: "\f3fa"; } .mdi-key-minus:before { content: "\f3fb"; } .mdi-key-plus:before { content: "\f3fc"; } .mdi-key-remove:before { content: "\f3fd"; } .mdi-key-variant:before { content: "\f3fe"; } .mdi-keyboard:before { content: "\f3ff"; } .mdi-keyboard-backspace:before { content: "\f400"; } .mdi-keyboard-caps:before { content: "\f401"; } .mdi-keyboard-close:before { content: "\f402"; } .mdi-keyboard-off:before { content: "\f403"; } .mdi-keyboard-return:before { content: "\f404"; } .mdi-keyboard-tab:before { content: "\f405"; } .mdi-keyboard-variant:before { content: "\f406"; } .mdi-label:before { content: "\f407"; } .mdi-label-outline:before { content: "\f408"; } .mdi-lan:before { content: "\f409"; } .mdi-lan-connect:before { content: "\f40a"; } .mdi-lan-disconnect:before { content: "\f40b"; } .mdi-lan-pending:before { content: "\f40c"; } .mdi-language-csharp:before { content: "\f40d"; } .mdi-language-css3:before { content: "\f40e"; } .mdi-language-html5:before { content: "\f40f"; } .mdi-language-javascript:before { content: "\f410"; } .mdi-language-php:before { content: "\f411"; } .mdi-language-python:before { content: "\f412"; } .mdi-language-python-text:before { content: "\f413"; } .mdi-laptop:before { content: "\f414"; } .mdi-laptop-chromebook:before { content: "\f415"; } .mdi-laptop-mac:before { content: "\f416"; } .mdi-laptop-windows:before { content: "\f417"; } .mdi-lastfm:before { content: "\f418"; } .mdi-launch:before { content: "\f419"; } .mdi-layers:before { content: "\f41a"; } .mdi-layers-off:before { content: "\f41b"; } .mdi-leaf:before { content: "\f41c"; } .mdi-led-off:before { content: "\f41d"; } .mdi-led-on:before { content: "\f41e"; } .mdi-led-outline:before { content: "\f41f"; } .mdi-led-variant-off:before { content: "\f420"; } .mdi-led-variant-on:before { content: "\f421"; } .mdi-led-variant-outline:before { content: "\f422"; } .mdi-library:before { content: "\f423"; } .mdi-library-books:before { content: "\f424"; } .mdi-library-music:before { content: "\f425"; } .mdi-library-plus:before { content: "\f426"; } .mdi-lightbulb:before { content: "\f427"; } .mdi-lightbulb-outline:before { content: "\f428"; } .mdi-link:before { content: "\f429"; } .mdi-link-off:before { content: "\f42a"; } .mdi-link-variant:before { content: "\f42b"; } .mdi-link-variant-off:before { content: "\f42c"; } .mdi-linkedin:before { content: "\f42d"; } .mdi-linkedin-box:before { content: "\f42e"; } .mdi-linux:before { content: "\f42f"; } .mdi-lock:before { content: "\f430"; } .mdi-lock-open:before { content: "\f431"; } .mdi-lock-open-outline:before { content: "\f432"; } .mdi-lock-outline:before { content: "\f433"; } .mdi-login:before { content: "\f434"; } .mdi-logout:before { content: "\f435"; } .mdi-looks:before { content: "\f436"; } .mdi-loupe:before { content: "\f437"; } .mdi-lumx:before { content: "\f438"; } .mdi-magnet:before { content: "\f439"; } .mdi-magnet-on:before { content: "\f43a"; } .mdi-magnify:before { content: "\f43b"; } .mdi-magnify-minus:before { content: "\f43c"; } .mdi-magnify-plus:before { content: "\f43d"; } .mdi-mail-ru:before { content: "\f43e"; } .mdi-map:before { content: "\f43f"; } .mdi-map-marker:before { content: "\f440"; } .mdi-map-marker-circle:before { content: "\f441"; } .mdi-map-marker-multiple:before { content: "\f442"; } .mdi-map-marker-off:before { content: "\f443"; } .mdi-map-marker-radius:before { content: "\f444"; } .mdi-margin:before { content: "\f445"; } .mdi-markdown:before { content: "\f446"; } .mdi-marker-check:before { content: "\f447"; } .mdi-martini:before { content: "\f448"; } .mdi-material-ui:before { content: "\f449"; } .mdi-math-compass:before { content: "\f44a"; } .mdi-maxcdn:before { content: "\f44b"; } .mdi-medium:before { content: "\f44c"; } .mdi-memory:before { content: "\f44d"; } .mdi-menu:before { content: "\f44e"; } .mdi-menu-down:before { content: "\f44f"; } .mdi-menu-left:before { content: "\f450"; } .mdi-menu-right:before { content: "\f451"; } .mdi-menu-up:before { content: "\f452"; } .mdi-message:before { content: "\f453"; } .mdi-message-alert:before { content: "\f454"; } .mdi-message-draw:before { content: "\f455"; } .mdi-message-image:before { content: "\f456"; } .mdi-message-outline:before { content: "\f457"; } .mdi-message-processing:before { content: "\f458"; } .mdi-message-reply:before { content: "\f459"; } .mdi-message-reply-text:before { content: "\f45a"; } .mdi-message-text:before { content: "\f45b"; } .mdi-message-text-outline:before { content: "\f45c"; } .mdi-message-video:before { content: "\f45d"; } .mdi-microphone:before { content: "\f45e"; } .mdi-microphone-off:before { content: "\f45f"; } .mdi-microphone-outline:before { content: "\f460"; } .mdi-microphone-settings:before { content: "\f461"; } .mdi-microphone-variant:before { content: "\f462"; } .mdi-microphone-variant-off:before { content: "\f463"; } .mdi-microsoft:before { content: "\f464"; } .mdi-minus:before { content: "\f465"; } .mdi-minus-box:before { content: "\f466"; } .mdi-minus-circle:before { content: "\f467"; } .mdi-minus-circle-outline:before { content: "\f468"; } .mdi-minus-network:before { content: "\f469"; } .mdi-monitor:before { content: "\f46a"; } .mdi-monitor-multiple:before { content: "\f46b"; } .mdi-more:before { content: "\f46c"; } .mdi-motorbike:before { content: "\f46d"; } .mdi-mouse:before { content: "\f46e"; } .mdi-mouse-off:before { content: "\f46f"; } .mdi-mouse-variant:before { content: "\f470"; } .mdi-mouse-variant-off:before { content: "\f471"; } .mdi-movie:before { content: "\f472"; } .mdi-multiplication:before { content: "\f473"; } .mdi-multiplication-box:before { content: "\f474"; } .mdi-music-box:before { content: "\f475"; } .mdi-music-box-outline:before { content: "\f476"; } .mdi-music-circle:before { content: "\f477"; } .mdi-music-note:before { content: "\f478"; } .mdi-music-note-eighth:before { content: "\f479"; } .mdi-music-note-half:before { content: "\f47a"; } .mdi-music-note-off:before { content: "\f47b"; } .mdi-music-note-quarter:before { content: "\f47c"; } .mdi-music-note-sixteenth:before { content: "\f47d"; } .mdi-music-note-whole:before { content: "\f47e"; } .mdi-nature:before { content: "\f47f"; } .mdi-nature-people:before { content: "\f480"; } .mdi-navigation:before { content: "\f481"; } .mdi-needle:before { content: "\f482"; } .mdi-nest-protect:before { content: "\f483"; } .mdi-nest-thermostat:before { content: "\f484"; } .mdi-newspaper:before { content: "\f485"; } .mdi-nfc:before { content: "\f486"; } .mdi-nfc-tap:before { content: "\f487"; } .mdi-nfc-variant:before { content: "\f488"; } .mdi-nodejs:before { content: "\f489"; } .mdi-note:before { content: "\f48a"; } .mdi-note-outline:before { content: "\f48b"; } .mdi-note-plus:before { content: "\f48c"; } .mdi-note-plus-outline:before { content: "\f48d"; } .mdi-note-text:before { content: "\f48e"; } .mdi-notification-clear-all:before { content: "\f48f"; } .mdi-numeric:before { content: "\f490"; } .mdi-numeric-0-box:before { content: "\f491"; } .mdi-numeric-0-box-multiple-outline:before { content: "\f492"; } .mdi-numeric-0-box-outline:before { content: "\f493"; } .mdi-numeric-1-box:before { content: "\f494"; } .mdi-numeric-1-box-multiple-outline:before { content: "\f495"; } .mdi-numeric-1-box-outline:before { content: "\f496"; } .mdi-numeric-2-box:before { content: "\f497"; } .mdi-numeric-2-box-multiple-outline:before { content: "\f498"; } .mdi-numeric-2-box-outline:before { content: "\f499"; } .mdi-numeric-3-box:before { content: "\f49a"; } .mdi-numeric-3-box-multiple-outline:before { content: "\f49b"; } .mdi-numeric-3-box-outline:before { content: "\f49c"; } .mdi-numeric-4-box:before { content: "\f49d"; } .mdi-numeric-4-box-multiple-outline:before { content: "\f49e"; } .mdi-numeric-4-box-outline:before { content: "\f49f"; } .mdi-numeric-5-box:before { content: "\f4a0"; } .mdi-numeric-5-box-multiple-outline:before { content: "\f4a1"; } .mdi-numeric-5-box-outline:before { content: "\f4a2"; } .mdi-numeric-6-box:before { content: "\f4a3"; } .mdi-numeric-6-box-multiple-outline:before { content: "\f4a4"; } .mdi-numeric-6-box-outline:before { content: "\f4a5"; } .mdi-numeric-7-box:before { content: "\f4a6"; } .mdi-numeric-7-box-multiple-outline:before { content: "\f4a7"; } .mdi-numeric-7-box-outline:before { content: "\f4a8"; } .mdi-numeric-8-box:before { content: "\f4a9"; } .mdi-numeric-8-box-multiple-outline:before { content: "\f4aa"; } .mdi-numeric-8-box-outline:before { content: "\f4ab"; } .mdi-numeric-9-box:before { content: "\f4ac"; } .mdi-numeric-9-box-multiple-outline:before { content: "\f4ad"; } .mdi-numeric-9-box-outline:before { content: "\f4ae"; } .mdi-numeric-9-plus-box:before { content: "\f4af"; } .mdi-numeric-9-plus-box-multiple-outline:before { content: "\f4b0"; } .mdi-numeric-9-plus-box-outline:before { content: "\f4b1"; } .mdi-nutrition:before { content: "\f4b2"; } .mdi-octagon:before { content: "\f4b3"; } .mdi-octagon-outline:before { content: "\f4b4"; } .mdi-odnoklassniki:before { content: "\f4b5"; } .mdi-office:before { content: "\f4b6"; } .mdi-oil:before { content: "\f4b7"; } .mdi-oil-temperature:before { content: "\f4b8"; } .mdi-omega:before { content: "\f4b9"; } .mdi-onedrive:before { content: "\f4ba"; } .mdi-open-in-app:before { content: "\f4bb"; } .mdi-open-in-new:before { content: "\f4bc"; } .mdi-opera:before { content: "\f4bd"; } .mdi-ornament:before { content: "\f4be"; } .mdi-ornament-variant:before { content: "\f4bf"; } .mdi-outbox:before { content: "\f4c0"; } .mdi-owl:before { content: "\f4c1"; } .mdi-package:before { content: "\f4c2"; } .mdi-package-down:before { content: "\f4c3"; } .mdi-package-up:before { content: "\f4c4"; } .mdi-package-variant:before { content: "\f4c5"; } .mdi-package-variant-closed:before { content: "\f4c6"; } .mdi-palette:before { content: "\f4c7"; } .mdi-palette-advanced:before { content: "\f4c8"; } .mdi-panda:before { content: "\f4c9"; } .mdi-pandora:before { content: "\f4ca"; } .mdi-panorama:before { content: "\f4cb"; } .mdi-panorama-fisheye:before { content: "\f4cc"; } .mdi-panorama-horizontal:before { content: "\f4cd"; } .mdi-panorama-vertical:before { content: "\f4ce"; } .mdi-panorama-wide-angle:before { content: "\f4cf"; } .mdi-paper-cut-vertical:before { content: "\f4d0"; } .mdi-paperclip:before { content: "\f4d1"; } .mdi-parking:before { content: "\f4d2"; } .mdi-pause:before { content: "\f4d3"; } .mdi-pause-circle:before { content: "\f4d4"; } .mdi-pause-circle-outline:before { content: "\f4d5"; } .mdi-pause-octagon:before { content: "\f4d6"; } .mdi-pause-octagon-outline:before { content: "\f4d7"; } .mdi-paw:before { content: "\f4d8"; } .mdi-pen:before { content: "\f4d9"; } .mdi-pencil:before { content: "\f4da"; } .mdi-pencil-box:before { content: "\f4db"; } .mdi-pencil-box-outline:before { content: "\f4dc"; } .mdi-pencil-lock:before { content: "\f4dd"; } .mdi-pencil-off:before { content: "\f4de"; } .mdi-percent:before { content: "\f4df"; } .mdi-pharmacy:before { content: "\f4e0"; } .mdi-phone:before { content: "\f4e1"; } .mdi-phone-bluetooth:before { content: "\f4e2"; } .mdi-phone-forward:before { content: "\f4e3"; } .mdi-phone-hangup:before { content: "\f4e4"; } .mdi-phone-in-talk:before { content: "\f4e5"; } .mdi-phone-incoming:before { content: "\f4e6"; } .mdi-phone-locked:before { content: "\f4e7"; } .mdi-phone-log:before { content: "\f4e8"; } .mdi-phone-missed:before { content: "\f4e9"; } .mdi-phone-outgoing:before { content: "\f4ea"; } .mdi-phone-paused:before { content: "\f4eb"; } .mdi-phone-settings:before { content: "\f4ec"; } .mdi-phone-voip:before { content: "\f4ed"; } .mdi-pi:before { content: "\f4ee"; } .mdi-pi-box:before { content: "\f4ef"; } .mdi-pig:before { content: "\f4f0"; } .mdi-pill:before { content: "\f4f1"; } .mdi-pin:before { content: "\f4f2"; } .mdi-pin-off:before { content: "\f4f3"; } .mdi-pine-tree:before { content: "\f4f4"; } .mdi-pine-tree-box:before { content: "\f4f5"; } .mdi-pinterest:before { content: "\f4f6"; } .mdi-pinterest-box:before { content: "\f4f7"; } .mdi-pizza:before { content: "\f4f8"; } .mdi-play:before { content: "\f4f9"; } .mdi-play-box-outline:before { content: "\f4fa"; } .mdi-play-circle:before { content: "\f4fb"; } .mdi-play-circle-outline:before { content: "\f4fc"; } .mdi-play-pause:before { content: "\f4fd"; } .mdi-play-protected-content:before { content: "\f4fe"; } .mdi-playlist-minus:before { content: "\f4ff"; } .mdi-playlist-play:before { content: "\f500"; } .mdi-playlist-plus:before { content: "\f501"; } .mdi-playlist-remove:before { content: "\f502"; } .mdi-playstation:before { content: "\f503"; } .mdi-plus:before { content: "\f504"; } .mdi-plus-box:before { content: "\f505"; } .mdi-plus-circle:before { content: "\f506"; } .mdi-plus-circle-multiple-outline:before { content: "\f507"; } .mdi-plus-circle-outline:before { content: "\f508"; } .mdi-plus-network:before { content: "\f509"; } .mdi-plus-one:before { content: "\f50a"; } .mdi-pocket:before { content: "\f50b"; } .mdi-pokeball:before { content: "\f50c"; } .mdi-polaroid:before { content: "\f50d"; } .mdi-poll:before { content: "\f50e"; } .mdi-poll-box:before { content: "\f50f"; } .mdi-polymer:before { content: "\f510"; } .mdi-popcorn:before { content: "\f511"; } .mdi-pound:before { content: "\f512"; } .mdi-pound-box:before { content: "\f513"; } .mdi-power:before { content: "\f514"; } .mdi-power-settings:before { content: "\f515"; } .mdi-power-socket:before { content: "\f516"; } .mdi-presentation:before { content: "\f517"; } .mdi-presentation-play:before { content: "\f518"; } .mdi-printer:before { content: "\f519"; } .mdi-printer-3d:before { content: "\f51a"; } .mdi-printer-alert:before { content: "\f51b"; } .mdi-professional-hexagon:before { content: "\f51c"; } .mdi-projector:before { content: "\f51d"; } .mdi-projector-screen:before { content: "\f51e"; } .mdi-pulse:before { content: "\f51f"; } .mdi-puzzle:before { content: "\f520"; } .mdi-qrcode:before { content: "\f521"; } .mdi-qrcode-scan:before { content: "\f522"; } .mdi-quadcopter:before { content: "\f523"; } .mdi-quality-high:before { content: "\f524"; } .mdi-quicktime:before { content: "\f525"; } .mdi-radar:before { content: "\f526"; } .mdi-radiator:before { content: "\f527"; } .mdi-radio:before { content: "\f528"; } .mdi-radio-handheld:before { content: "\f529"; } .mdi-radio-tower:before { content: "\f52a"; } .mdi-radioactive:before { content: "\f52b"; } .mdi-radiobox-blank:before { content: "\f52c"; } .mdi-radiobox-marked:before { content: "\f52d"; } .mdi-raspberrypi:before { content: "\f52e"; } .mdi-ray-end:before { content: "\f52f"; } .mdi-ray-end-arrow:before { content: "\f530"; } .mdi-ray-start:before { content: "\f531"; } .mdi-ray-start-arrow:before { content: "\f532"; } .mdi-ray-start-end:before { content: "\f533"; } .mdi-ray-vertex:before { content: "\f534"; } .mdi-rdio:before { content: "\f535"; } .mdi-read:before { content: "\f536"; } .mdi-readability:before { content: "\f537"; } .mdi-receipt:before { content: "\f538"; } .mdi-record:before { content: "\f539"; } .mdi-record-rec:before { content: "\f53a"; } .mdi-recycle:before { content: "\f53b"; } .mdi-reddit:before { content: "\f53c"; } .mdi-redo:before { content: "\f53d"; } .mdi-redo-variant:before { content: "\f53e"; } .mdi-refresh:before { content: "\f53f"; } .mdi-regex:before { content: "\f540"; } .mdi-relative-scale:before { content: "\f541"; } .mdi-reload:before { content: "\f542"; } .mdi-remote:before { content: "\f543"; } .mdi-rename-box:before { content: "\f544"; } .mdi-repeat:before { content: "\f545"; } .mdi-repeat-off:before { content: "\f546"; } .mdi-repeat-once:before { content: "\f547"; } .mdi-replay:before { content: "\f548"; } .mdi-reply:before { content: "\f549"; } .mdi-reply-all:before { content: "\f54a"; } .mdi-reproduction:before { content: "\f54b"; } .mdi-resize-bottom-right:before { content: "\f54c"; } .mdi-responsive:before { content: "\f54d"; } .mdi-rewind:before { content: "\f54e"; } .mdi-ribbon:before { content: "\f54f"; } .mdi-road:before { content: "\f550"; } .mdi-road-variant:before { content: "\f551"; } .mdi-rocket:before { content: "\f552"; } .mdi-rotate-3d:before { content: "\f553"; } .mdi-rotate-left:before { content: "\f554"; } .mdi-rotate-left-variant:before { content: "\f555"; } .mdi-rotate-right:before { content: "\f556"; } .mdi-rotate-right-variant:before { content: "\f557"; } .mdi-router-wireless:before { content: "\f558"; } .mdi-routes:before { content: "\f559"; } .mdi-rss:before { content: "\f55a"; } .mdi-rss-box:before { content: "\f55b"; } .mdi-ruler:before { content: "\f55c"; } .mdi-run:before { content: "\f55d"; } .mdi-sale:before { content: "\f55e"; } .mdi-satellite:before { content: "\f55f"; } .mdi-satellite-variant:before { content: "\f560"; } .mdi-scale:before { content: "\f561"; } .mdi-scale-bathroom:before { content: "\f562"; } .mdi-school:before { content: "\f563"; } .mdi-screen-rotation:before { content: "\f564"; } .mdi-screen-rotation-lock:before { content: "\f565"; } .mdi-screwdriver:before { content: "\f566"; } .mdi-script:before { content: "\f567"; } .mdi-sd:before { content: "\f568"; } .mdi-seal:before { content: "\f569"; } .mdi-seat-flat:before { content: "\f56a"; } .mdi-seat-flat-angled:before { content: "\f56b"; } .mdi-seat-individual-suite:before { content: "\f56c"; } .mdi-seat-legroom-extra:before { content: "\f56d"; } .mdi-seat-legroom-normal:before { content: "\f56e"; } .mdi-seat-legroom-reduced:before { content: "\f56f"; } .mdi-seat-recline-extra:before { content: "\f570"; } .mdi-seat-recline-normal:before { content: "\f571"; } .mdi-security:before { content: "\f572"; } .mdi-security-network:before { content: "\f573"; } .mdi-select:before { content: "\f574"; } .mdi-select-all:before { content: "\f575"; } .mdi-select-inverse:before { content: "\f576"; } .mdi-select-off:before { content: "\f577"; } .mdi-selection:before { content: "\f578"; } .mdi-send:before { content: "\f579"; } .mdi-server:before { content: "\f57a"; } .mdi-server-minus:before { content: "\f57b"; } .mdi-server-network:before { content: "\f57c"; } .mdi-server-network-off:before { content: "\f57d"; } .mdi-server-off:before { content: "\f57e"; } .mdi-server-plus:before { content: "\f57f"; } .mdi-server-remove:before { content: "\f580"; } .mdi-server-security:before { content: "\f581"; } .mdi-settings:before { content: "\f582"; } .mdi-settings-box:before { content: "\f583"; } .mdi-shape-plus:before { content: "\f584"; } .mdi-share:before { content: "\f585"; } .mdi-share-variant:before { content: "\f586"; } .mdi-shield:before { content: "\f587"; } .mdi-shield-outline:before { content: "\f588"; } .mdi-shopping:before { content: "\f589"; } .mdi-shopping-music:before { content: "\f58a"; } .mdi-shredder:before { content: "\f58b"; } .mdi-shuffle:before { content: "\f58c"; } .mdi-shuffle-disabled:before { content: "\f58d"; } .mdi-shuffle-variant:before { content: "\f58e"; } .mdi-sigma:before { content: "\f58f"; } .mdi-sign-caution:before { content: "\f590"; } .mdi-signal:before { content: "\f591"; } .mdi-silverware:before { content: "\f592"; } .mdi-silverware-fork:before { content: "\f593"; } .mdi-silverware-spoon:before { content: "\f594"; } .mdi-silverware-variant:before { content: "\f595"; } .mdi-sim:before { content: "\f596"; } .mdi-sim-alert:before { content: "\f597"; } .mdi-sim-off:before { content: "\f598"; } .mdi-sitemap:before { content: "\f599"; } .mdi-skip-backward:before { content: "\f59a"; } .mdi-skip-forward:before { content: "\f59b"; } .mdi-skip-next:before { content: "\f59c"; } .mdi-skip-previous:before { content: "\f59d"; } .mdi-skype:before { content: "\f59e"; } .mdi-skype-business:before { content: "\f59f"; } .mdi-slack:before { content: "\f5a0"; } .mdi-sleep:before { content: "\f5a1"; } .mdi-sleep-off:before { content: "\f5a2"; } .mdi-smoking:before { content: "\f5a3"; } .mdi-smoking-off:before { content: "\f5a4"; } .mdi-snapchat:before { content: "\f5a5"; } .mdi-snowman:before { content: "\f5a6"; } .mdi-sofa:before { content: "\f5a7"; } .mdi-sort:before { content: "\f5a8"; } .mdi-sort-alphabetical:before { content: "\f5a9"; } .mdi-sort-ascending:before { content: "\f5aa"; } .mdi-sort-descending:before { content: "\f5ab"; } .mdi-sort-numeric:before { content: "\f5ac"; } .mdi-sort-variant:before { content: "\f5ad"; } .mdi-soundcloud:before { content: "\f5ae"; } .mdi-source-fork:before { content: "\f5af"; } .mdi-source-pull:before { content: "\f5b0"; } .mdi-speaker:before { content: "\f5b1"; } .mdi-speaker-off:before { content: "\f5b2"; } .mdi-speedometer:before { content: "\f5b3"; } .mdi-spellcheck:before { content: "\f5b4"; } .mdi-spotify:before { content: "\f5b5"; } .mdi-spotlight:before { content: "\f5b6"; } .mdi-spotlight-beam:before { content: "\f5b7"; } .mdi-square-inc:before { content: "\f5b8"; } .mdi-square-inc-cash:before { content: "\f5b9"; } .mdi-stackoverflow:before { content: "\f5ba"; } .mdi-stairs:before { content: "\f5bb"; } .mdi-star:before { content: "\f5bc"; } .mdi-star-circle:before { content: "\f5bd"; } .mdi-star-half:before { content: "\f5be"; } .mdi-star-off:before { content: "\f5bf"; } .mdi-star-outline:before { content: "\f5c0"; } .mdi-steam:before { content: "\f5c1"; } .mdi-steering:before { content: "\f5c2"; } .mdi-step-backward:before { content: "\f5c3"; } .mdi-step-backward-2:before { content: "\f5c4"; } .mdi-step-forward:before { content: "\f5c5"; } .mdi-step-forward-2:before { content: "\f5c6"; } .mdi-stethoscope:before { content: "\f5c7"; } .mdi-stocking:before { content: "\f5c8"; } .mdi-stop:before { content: "\f5c9"; } .mdi-store:before { content: "\f5ca"; } .mdi-store-24-hour:before { content: "\f5cb"; } .mdi-stove:before { content: "\f5cc"; } .mdi-subway:before { content: "\f5cd"; } .mdi-sunglasses:before { content: "\f5ce"; } .mdi-swap-horizontal:before { content: "\f5cf"; } .mdi-swap-vertical:before { content: "\f5d0"; } .mdi-swim:before { content: "\f5d1"; } .mdi-switch:before { content: "\f5d2"; } .mdi-sword:before { content: "\f5d3"; } .mdi-sync:before { content: "\f5d4"; } .mdi-sync-alert:before { content: "\f5d5"; } .mdi-sync-off:before { content: "\f5d6"; } .mdi-tab:before { content: "\f5d7"; } .mdi-tab-unselected:before { content: "\f5d8"; } .mdi-table:before { content: "\f5d9"; } .mdi-table-column-plus-after:before { content: "\f5da"; } .mdi-table-column-plus-before:before { content: "\f5db"; } .mdi-table-column-remove:before { content: "\f5dc"; } .mdi-table-column-width:before { content: "\f5dd"; } .mdi-table-edit:before { content: "\f5de"; } .mdi-table-large:before { content: "\f5df"; } .mdi-table-row-height:before { content: "\f5e0"; } .mdi-table-row-plus-after:before { content: "\f5e1"; } .mdi-table-row-plus-before:before { content: "\f5e2"; } .mdi-table-row-remove:before { content: "\f5e3"; } .mdi-tablet:before { content: "\f5e4"; } .mdi-tablet-android:before { content: "\f5e5"; } .mdi-tablet-ipad:before { content: "\f5e6"; } .mdi-tag:before { content: "\f5e7"; } .mdi-tag-faces:before { content: "\f5e8"; } .mdi-tag-multiple:before { content: "\f5e9"; } .mdi-tag-outline:before { content: "\f5ea"; } .mdi-tag-text-outline:before { content: "\f5eb"; } .mdi-target:before { content: "\f5ec"; } .mdi-taxi:before { content: "\f5ed"; } .mdi-teamviewer:before { content: "\f5ee"; } .mdi-telegram:before { content: "\f5ef"; } .mdi-television:before { content: "\f5f0"; } .mdi-television-guide:before { content: "\f5f1"; } .mdi-temperature-celsius:before { content: "\f5f2"; } .mdi-temperature-fahrenheit:before { content: "\f5f3"; } .mdi-temperature-kelvin:before { content: "\f5f4"; } .mdi-tennis:before { content: "\f5f5"; } .mdi-tent:before { content: "\f5f6"; } .mdi-terrain:before { content: "\f5f7"; } .mdi-text-to-speech:before { content: "\f5f8"; } .mdi-text-to-speech-off:before { content: "\f5f9"; } .mdi-texture:before { content: "\f5fa"; } .mdi-theater:before { content: "\f5fb"; } .mdi-theme-light-dark:before { content: "\f5fc"; } .mdi-thermometer:before { content: "\f5fd"; } .mdi-thermometer-lines:before { content: "\f5fe"; } .mdi-thumb-down:before { content: "\f5ff"; } .mdi-thumb-down-outline:before { content: "\f600"; } .mdi-thumb-up:before { content: "\f601"; } .mdi-thumb-up-outline:before { content: "\f602"; } .mdi-thumbs-up-down:before { content: "\f603"; } .mdi-ticket:before { content: "\f604"; } .mdi-ticket-account:before { content: "\f605"; } .mdi-ticket-confirmation:before { content: "\f606"; } .mdi-tie:before { content: "\f607"; } .mdi-timelapse:before { content: "\f608"; } .mdi-timer:before { content: "\f609"; } .mdi-timer-10:before { content: "\f60a"; } .mdi-timer-3:before { content: "\f60b"; } .mdi-timer-off:before { content: "\f60c"; } .mdi-timer-sand:before { content: "\f60d"; } .mdi-timetable:before { content: "\f60e"; } .mdi-toggle-switch:before { content: "\f60f"; } .mdi-toggle-switch-off:before { content: "\f610"; } .mdi-tooltip:before { content: "\f611"; } .mdi-tooltip-edit:before { content: "\f612"; } .mdi-tooltip-image:before { content: "\f613"; } .mdi-tooltip-outline:before { content: "\f614"; } .mdi-tooltip-outline-plus:before { content: "\f615"; } .mdi-tooltip-text:before { content: "\f616"; } .mdi-tor:before { content: "\f617"; } .mdi-traffic-light:before { content: "\f618"; } .mdi-train:before { content: "\f619"; } .mdi-tram:before { content: "\f61a"; } .mdi-transcribe:before { content: "\f61b"; } .mdi-transcribe-close:before { content: "\f61c"; } .mdi-transfer:before { content: "\f61d"; } .mdi-tree:before { content: "\f61e"; } .mdi-trello:before { content: "\f61f"; } .mdi-trending-down:before { content: "\f620"; } .mdi-trending-neutral:before { content: "\f621"; } .mdi-trending-up:before { content: "\f622"; } .mdi-triangle:before { content: "\f623"; } .mdi-triangle-outline:before { content: "\f624"; } .mdi-trophy:before { content: "\f625"; } .mdi-trophy-award:before { content: "\f626"; } .mdi-trophy-outline:before { content: "\f627"; } .mdi-trophy-variant:before { content: "\f628"; } .mdi-trophy-variant-outline:before { content: "\f629"; } .mdi-truck:before { content: "\f62a"; } .mdi-truck-delivery:before { content: "\f62b"; } .mdi-tshirt-crew:before { content: "\f62c"; } .mdi-tshirt-v:before { content: "\f62d"; } .mdi-tumblr:before { content: "\f62e"; } .mdi-tumblr-reblog:before { content: "\f62f"; } .mdi-twitch:before { content: "\f630"; } .mdi-twitter:before { content: "\f631"; } .mdi-twitter-box:before { content: "\f632"; } .mdi-twitter-circle:before { content: "\f633"; } .mdi-twitter-retweet:before { content: "\f634"; } .mdi-ubuntu:before { content: "\f635"; } .mdi-umbraco:before { content: "\f636"; } .mdi-umbrella:before { content: "\f637"; } .mdi-umbrella-outline:before { content: "\f638"; } .mdi-undo:before { content: "\f639"; } .mdi-undo-variant:before { content: "\f63a"; } .mdi-unfold-less:before { content: "\f63b"; } .mdi-unfold-more:before { content: "\f63c"; } .mdi-ungroup:before { content: "\f63d"; } .mdi-untappd:before { content: "\f63e"; } .mdi-upload:before { content: "\f63f"; } .mdi-usb:before { content: "\f640"; } .mdi-vector-arrange-above:before { content: "\f641"; } .mdi-vector-arrange-below:before { content: "\f642"; } .mdi-vector-circle:before { content: "\f643"; } .mdi-vector-circle-variant:before { content: "\f644"; } .mdi-vector-combine:before { content: "\f645"; } .mdi-vector-curve:before { content: "\f646"; } .mdi-vector-difference:before { content: "\f647"; } .mdi-vector-difference-ab:before { content: "\f648"; } .mdi-vector-difference-ba:before { content: "\f649"; } .mdi-vector-intersection:before { content: "\f64a"; } .mdi-vector-line:before { content: "\f64b"; } .mdi-vector-point:before { content: "\f64c"; } .mdi-vector-polygon:before { content: "\f64d"; } .mdi-vector-polyline:before { content: "\f64e"; } .mdi-vector-selection:before { content: "\f64f"; } .mdi-vector-square:before { content: "\f650"; } .mdi-vector-triangle:before { content: "\f651"; } .mdi-vector-union:before { content: "\f652"; } .mdi-verified:before { content: "\f653"; } .mdi-vibrate:before { content: "\f654"; } .mdi-video:before { content: "\f655"; } .mdi-video-off:before { content: "\f656"; } .mdi-video-switch:before { content: "\f657"; } .mdi-view-agenda:before { content: "\f658"; } .mdi-view-array:before { content: "\f659"; } .mdi-view-carousel:before { content: "\f65a"; } .mdi-view-column:before { content: "\f65b"; } .mdi-view-dashboard:before { content: "\f65c"; } .mdi-view-day:before { content: "\f65d"; } .mdi-view-grid:before { content: "\f65e"; } .mdi-view-headline:before { content: "\f65f"; } .mdi-view-list:before { content: "\f660"; } .mdi-view-module:before { content: "\f661"; } .mdi-view-quilt:before { content: "\f662"; } .mdi-view-stream:before { content: "\f663"; } .mdi-view-week:before { content: "\f664"; } .mdi-vimeo:before { content: "\f665"; } .mdi-vine:before { content: "\f666"; } .mdi-vk:before { content: "\f667"; } .mdi-vk-box:before { content: "\f668"; } .mdi-vk-circle:before { content: "\f669"; } .mdi-voicemail:before { content: "\f66a"; } .mdi-volume-high:before { content: "\f66b"; } .mdi-volume-low:before { content: "\f66c"; } .mdi-volume-medium:before { content: "\f66d"; } .mdi-volume-off:before { content: "\f66e"; } .mdi-vpn:before { content: "\f66f"; } .mdi-walk:before { content: "\f670"; } .mdi-wallet:before { content: "\f671"; } .mdi-wallet-giftcard:before { content: "\f672"; } .mdi-wallet-membership:before { content: "\f673"; } .mdi-wallet-travel:before { content: "\f674"; } .mdi-wan:before { content: "\f675"; } .mdi-watch:before { content: "\f676"; } .mdi-watch-export:before { content: "\f677"; } .mdi-watch-import:before { content: "\f678"; } .mdi-water:before { content: "\f679"; } .mdi-water-off:before { content: "\f67a"; } .mdi-water-percent:before { content: "\f67b"; } .mdi-water-pump:before { content: "\f67c"; } .mdi-weather-cloudy:before { content: "\f67d"; } .mdi-weather-fog:before { content: "\f67e"; } .mdi-weather-hail:before { content: "\f67f"; } .mdi-weather-lightning:before { content: "\f680"; } .mdi-weather-night:before { content: "\f681"; } .mdi-weather-partlycloudy:before { content: "\f682"; } .mdi-weather-pouring:before { content: "\f683"; } .mdi-weather-rainy:before { content: "\f684"; } .mdi-weather-snowy:before { content: "\f685"; } .mdi-weather-sunny:before { content: "\f686"; } .mdi-weather-sunset:before { content: "\f687"; } .mdi-weather-sunset-down:before { content: "\f688"; } .mdi-weather-sunset-up:before { content: "\f689"; } .mdi-weather-windy:before { content: "\f68a"; } .mdi-weather-windy-variant:before { content: "\f68b"; } .mdi-web:before { content: "\f68c"; } .mdi-webcam:before { content: "\f68d"; } .mdi-weight:before { content: "\f68e"; } .mdi-weight-kilogram:before { content: "\f68f"; } .mdi-whatsapp:before { content: "\f690"; } .mdi-wheelchair-accessibility:before { content: "\f691"; } .mdi-white-balance-auto:before { content: "\f692"; } .mdi-white-balance-incandescent:before { content: "\f693"; } .mdi-white-balance-irradescent:before { content: "\f694"; } .mdi-white-balance-sunny:before { content: "\f695"; } .mdi-wifi:before { content: "\f696"; } .mdi-wifi-off:before { content: "\f697"; } .mdi-wii:before { content: "\f698"; } .mdi-wikipedia:before { content: "\f699"; } .mdi-window-close:before { content: "\f69a"; } .mdi-window-closed:before { content: "\f69b"; } .mdi-window-maximize:before { content: "\f69c"; } .mdi-window-minimize:before { content: "\f69d"; } .mdi-window-open:before { content: "\f69e"; } .mdi-window-restore:before { content: "\f69f"; } .mdi-windows:before { content: "\f6a0"; } .mdi-wordpress:before { content: "\f6a1"; } .mdi-worker:before { content: "\f6a2"; } .mdi-wrap:before { content: "\f6a3"; } .mdi-wrench:before { content: "\f6a4"; } .mdi-wunderlist:before { content: "\f6a5"; } .mdi-xbox:before { content: "\f6a6"; } .mdi-xbox-controller:before { content: "\f6a7"; } .mdi-xbox-controller-off:before { content: "\f6a8"; } .mdi-xda:before { content: "\f6a9"; } .mdi-xing:before { content: "\f6aa"; } .mdi-xing-box:before { content: "\f6ab"; } .mdi-xing-circle:before { content: "\f6ac"; } .mdi-xml:before { content: "\f6ad"; } .mdi-yeast:before { content: "\f6ae"; } .mdi-yelp:before { content: "\f6af"; } .mdi-youtube-play:before { content: "\f6b0"; } .mdi-zip-box:before { content: "\f6b1"; } .mdi-18px { font-size: 18px; } .mdi-24px { font-size: 24px; } .mdi-36px { font-size: 36px; } .mdi-48px { font-size: 48px; } .mdi-dark { color: rgba(0, 0, 0, 0.54); } .mdi-dark.mdi-inactive { color: rgba(0, 0, 0, 0.26); } .mdi-light { color: white; } .mdi-light.mdi-inactive { color: rgba(255, 255, 255, 0.3); } /** + * Title: Bigmug Line Flaticon Pack + * Author: Catalin Fertu + * Source: http://www.flaticon.com/packs/bigmug-line + * License: CC BY 3.0 (http://creativecommons.org/licenses/by/3.0/) + */ @font-face { font-family: "fl-bigmug-line"; src: url("../fonts/fl-bigmug-line.eot"); src: url("../fonts/fl-bigmug-line.eot#iefix") format("embedded-opentype"), url("../fonts/fl-bigmug-line.woff") format("woff"), url("../fonts/fl-bigmug-line.ttf") format("truetype"), url("../fonts/fl-bigmug-line.svg") format("svg"); font-weight: normal; font-style: normal; } .fl-bigmug-line-ico, [class^="fl-bigmug-line-"]:before, [class*=" fl-bigmug-line-"]:before, [class^="fl-bigmug-line-"]:after, [class*=" fl-bigmug-line-"]:after { font-family: 'fl-bigmug-line'; font-size: inherit; font-weight: 400; font-style: normal; } .fl-bigmug-line-add137:before { content: "\e000"; } .fl-bigmug-line-add139:before { content: "\e001"; } .fl-bigmug-line-add149:before { content: "\e002"; } .fl-bigmug-line-airplane86:before { content: "\e003"; } .fl-bigmug-line-alarm31:before { content: "\e004"; } .fl-bigmug-line-arrow592:before { content: "\e005"; } .fl-bigmug-line-attach8:before { content: "\e006"; } .fl-bigmug-line-attachment15:before { content: "\e007"; } .fl-bigmug-line-audio46:before { content: "\e008"; } .fl-bigmug-line-back44:before { content: "\e009"; } .fl-bigmug-line-back46:before { content: "\e00a"; } .fl-bigmug-line-big104:before { content: "\e00b"; } .fl-bigmug-line-book188:before { content: "\e00c"; } .fl-bigmug-line-bookmark28:before { content: "\e00d"; } .fl-bigmug-line-bottle34:before { content: "\e00e"; } .fl-bigmug-line-button5:before { content: "\e00f"; } .fl-bigmug-line-buttons5:before { content: "\e010"; } .fl-bigmug-line-cellphone55:before { content: "\e011"; } .fl-bigmug-line-cellular9:before { content: "\e012"; } .fl-bigmug-line-center10:before { content: "\e013"; } .fl-bigmug-line-chat51:before { content: "\e014"; } .fl-bigmug-line-chat55:before { content: "\e015"; } .fl-bigmug-line-checkmark14:before { content: "\e016"; } .fl-bigmug-line-checkmark15:before { content: "\e017"; } .fl-bigmug-line-checkmark16:before { content: "\e018"; } .fl-bigmug-line-circular220:before { content: "\e019"; } .fl-bigmug-line-circular224:before { content: "\e01a"; } .fl-bigmug-line-circular228:before { content: "\e01b"; } .fl-bigmug-line-circular229:before { content: "\e01c"; } .fl-bigmug-line-clipboard68:before { content: "\e01d"; } .fl-bigmug-line-close42:before { content: "\e01e"; } .fl-bigmug-line-cloud255:before { content: "\e01f"; } .fl-bigmug-line-cloud260:before { content: "\e020"; } .fl-bigmug-line-cocktail26:before { content: "\e021"; } .fl-bigmug-line-code30:before { content: "\e022"; } .fl-bigmug-line-collapse5:before { content: "\e023"; } .fl-bigmug-line-comment45:before { content: "\e024"; } .fl-bigmug-line-compass80:before { content: "\e025"; } .fl-bigmug-line-contract5:before { content: "\e026"; } .fl-bigmug-line-copy23:before { content: "\e027"; } .fl-bigmug-line-crescent23:before { content: "\e028"; } .fl-bigmug-line-cropping1:before { content: "\e029"; } .fl-bigmug-line-cross81:before { content: "\e02a"; } .fl-bigmug-line-cross83:before { content: "\e02b"; } .fl-bigmug-line-cube29:before { content: "\e02c"; } .fl-bigmug-line-double97:before { content: "\e02d"; } .fl-bigmug-line-double98:before { content: "\e02e"; } .fl-bigmug-line-double99:before { content: "\e02f"; } .fl-bigmug-line-down55:before { content: "\e030"; } .fl-bigmug-line-down56:before { content: "\e031"; } .fl-bigmug-line-down58:before { content: "\e032"; } .fl-bigmug-line-down59:before { content: "\e033"; } .fl-bigmug-line-down64:before { content: "\e034"; } .fl-bigmug-line-download136:before { content: "\e035"; } .fl-bigmug-line-download142:before { content: "\e036"; } .fl-bigmug-line-download146:before { content: "\e037"; } .fl-bigmug-line-download147:before { content: "\e038"; } .fl-bigmug-line-download148:before { content: "\e039"; } .fl-bigmug-line-electrical17:before { content: "\e03a"; } .fl-bigmug-line-electronic57:before { content: "\e03b"; } .fl-bigmug-line-email64:before { content: "\e03c"; } .fl-bigmug-line-email67:before { content: "\e03d"; } .fl-bigmug-line-equalization3:before { content: "\e03e"; } .fl-bigmug-line-equalizer26:before { content: "\e03f"; } .fl-bigmug-line-event6:before { content: "\e040"; } .fl-bigmug-line-expand25:before { content: "\e041"; } .fl-bigmug-line-expanding2:before { content: "\e042"; } .fl-bigmug-line-fast33:before { content: "\e043"; } .fl-bigmug-line-favourites5:before { content: "\e044"; } .fl-bigmug-line-file68:before { content: "\e045"; } .fl-bigmug-line-file69:before { content: "\e046"; } .fl-bigmug-line-film57:before { content: "\e047"; } .fl-bigmug-line-flag53:before { content: "\e048"; } .fl-bigmug-line-fog10:before { content: "\e049"; } .fl-bigmug-line-foggy3:before { content: "\e04a"; } .fl-bigmug-line-folder173:before { content: "\e04b"; } .fl-bigmug-line-fork34:before { content: "\e04c"; } .fl-bigmug-line-four87:before { content: "\e04d"; } .fl-bigmug-line-full40:before { content: "\e04e"; } .fl-bigmug-line-games32:before { content: "\e04f"; } .fl-bigmug-line-gear30:before { content: "\e050"; } .fl-bigmug-line-giftbox54:before { content: "\e051"; } .fl-bigmug-line-graphical8:before { content: "\e052"; } .fl-bigmug-line-headphones32:before { content: "\e053"; } .fl-bigmug-line-hot67:before { content: "\e054"; } .fl-bigmug-line-images21:before { content: "\e055"; } .fl-bigmug-line-ink12:before { content: "\e056"; } .fl-bigmug-line-label25:before { content: "\e057"; } .fl-bigmug-line-left144:before { content: "\e058"; } .fl-bigmug-line-left145:before { content: "\e059"; } .fl-bigmug-line-left146:before { content: "\e05a"; } .fl-bigmug-line-left148:before { content: "\e05b"; } .fl-bigmug-line-left152:before { content: "\e05c"; } .fl-bigmug-line-left153:before { content: "\e05d"; } .fl-bigmug-line-left158:before { content: "\e05e"; } .fl-bigmug-line-left159:before { content: "\e05f"; } .fl-bigmug-line-like51:before { content: "\e060"; } .fl-bigmug-line-link52:before { content: "\e061"; } .fl-bigmug-line-list63:before { content: "\e062"; } .fl-bigmug-line-list65:before { content: "\e063"; } .fl-bigmug-line-lock64:before { content: "\e064"; } .fl-bigmug-line-login12:before { content: "\e065"; } .fl-bigmug-line-login9:before { content: "\e066"; } .fl-bigmug-line-map87:before { content: "\e067"; } .fl-bigmug-line-megaphone11:before { content: "\e068"; } .fl-bigmug-line-men25:before { content: "\e069"; } .fl-bigmug-line-menu40:before { content: "\e06a"; } .fl-bigmug-line-menu41:before { content: "\e06b"; } .fl-bigmug-line-microphone76:before { content: "\e06c"; } .fl-bigmug-line-microphone77:before { content: "\e06d"; } .fl-bigmug-line-minus79:before { content: "\e06e"; } .fl-bigmug-line-minus80:before { content: "\e06f"; } .fl-bigmug-line-minus83:before { content: "\e070"; } .fl-bigmug-line-minus86:before { content: "\e071"; } .fl-bigmug-line-monitor74:before { content: "\e072"; } .fl-bigmug-line-music218:before { content: "\e073"; } .fl-bigmug-line-music219:before { content: "\e074"; } .fl-bigmug-line-music221:before { content: "\e075"; } .fl-bigmug-line-musical100:before { content: "\e076"; } .fl-bigmug-line-musical98:before { content: "\e077"; } .fl-bigmug-line-mute34:before { content: "\e078"; } .fl-bigmug-line-new83:before { content: "\e079"; } .fl-bigmug-line-nine16:before { content: "\e07a"; } .fl-bigmug-line-note35:before { content: "\e07b"; } .fl-bigmug-line-notebook41:before { content: "\e07c"; } .fl-bigmug-line-notification4:before { content: "\e07d"; } .fl-bigmug-line-notification5:before { content: "\e07e"; } .fl-bigmug-line-opened25:before { content: "\e07f"; } .fl-bigmug-line-oval34:before { content: "\e080"; } .fl-bigmug-line-paintbrush9:before { content: "\e081"; } .fl-bigmug-line-paper122:before { content: "\e082"; } .fl-bigmug-line-pause37:before { content: "\e083"; } .fl-bigmug-line-pencil85:before { content: "\e084"; } .fl-bigmug-line-phone351:before { content: "\e085"; } .fl-bigmug-line-photo181:before { content: "\e086"; } .fl-bigmug-line-pin42:before { content: "\e087"; } .fl-bigmug-line-planetary2:before { content: "\e088"; } .fl-bigmug-line-play83:before { content: "\e089"; } .fl-bigmug-line-portfolio23:before { content: "\e08a"; } .fl-bigmug-line-print34:before { content: "\e08b"; } .fl-bigmug-line-radio46:before { content: "\e08c"; } .fl-bigmug-line-rain30:before { content: "\e08d"; } .fl-bigmug-line-rectangular78:before { content: "\e08e"; } .fl-bigmug-line-recycling10:before { content: "\e08f"; } .fl-bigmug-line-rewind37:before { content: "\e090"; } .fl-bigmug-line-right139:before { content: "\e091"; } .fl-bigmug-line-right141:before { content: "\e092"; } .fl-bigmug-line-right142:before { content: "\e093"; } .fl-bigmug-line-right144:before { content: "\e094"; } .fl-bigmug-line-right148:before { content: "\e095"; } .fl-bigmug-line-right153:before { content: "\e096"; } .fl-bigmug-line-right154:before { content: "\e097"; } .fl-bigmug-line-right156:before { content: "\e098"; } .fl-bigmug-line-rounded51:before { content: "\e099"; } .fl-bigmug-line-sand14:before { content: "\e09a"; } .fl-bigmug-line-save15:before { content: "\e09b"; } .fl-bigmug-line-search74:before { content: "\e09c"; } .fl-bigmug-line-search78:before { content: "\e09d"; } .fl-bigmug-line-share27:before { content: "\e09e"; } .fl-bigmug-line-shopping198:before { content: "\e09f"; } .fl-bigmug-line-shopping199:before { content: "\e0a0"; } .fl-bigmug-line-shopping202:before { content: "\e0a1"; } .fl-bigmug-line-shopping204:before { content: "\e0a2"; } .fl-bigmug-line-shuffle17:before { content: "\e0a3"; } .fl-bigmug-line-sort47:before { content: "\e0a4"; } .fl-bigmug-line-sort48:before { content: "\e0a5"; } .fl-bigmug-line-speaker75:before { content: "\e0a6"; } .fl-bigmug-line-speaker80:before { content: "\e0a7"; } .fl-bigmug-line-speaker81:before { content: "\e0a8"; } .fl-bigmug-line-speaker86:before { content: "\e0a9"; } .fl-bigmug-line-speaker87:before { content: "\e0aa"; } .fl-bigmug-line-speech96:before { content: "\e0ab"; } .fl-bigmug-line-square152:before { content: "\e0ac"; } .fl-bigmug-line-square156:before { content: "\e0ad"; } .fl-bigmug-line-square160:before { content: "\e0ae"; } .fl-bigmug-line-store10:before { content: "\e0af"; } .fl-bigmug-line-sun81:before { content: "\e0b0"; } .fl-bigmug-line-sunrise3:before { content: "\e0b1"; } .fl-bigmug-line-switch23:before { content: "\e0b2"; } .fl-bigmug-line-switch24:before { content: "\e0b3"; } .fl-bigmug-line-tag47:before { content: "\e0b4"; } .fl-bigmug-line-television20:before { content: "\e0b5"; } .fl-bigmug-line-text108:before { content: "\e0b6"; } .fl-bigmug-line-text109:before { content: "\e0b7"; } .fl-bigmug-line-three142:before { content: "\e0b8"; } .fl-bigmug-line-timer35:before { content: "\e0b9"; } .fl-bigmug-line-tool16:before { content: "\e0ba"; } .fl-bigmug-line-triangle33:before { content: "\e0bb"; } .fl-bigmug-line-trophy55:before { content: "\e0bc"; } .fl-bigmug-line-two311:before { content: "\e0bd"; } .fl-bigmug-line-two316:before { content: "\e0be"; } .fl-bigmug-line-two317:before { content: "\e0bf"; } .fl-bigmug-line-two319:before { content: "\e0c0"; } .fl-bigmug-line-two323:before { content: "\e0c1"; } .fl-bigmug-line-unlocked27:before { content: "\e0c2"; } .fl-bigmug-line-up100:before { content: "\e0c3"; } .fl-bigmug-line-up102:before { content: "\e0c4"; } .fl-bigmug-line-up103:before { content: "\e0c5"; } .fl-bigmug-line-up104:before { content: "\e0c6"; } .fl-bigmug-line-up107:before { content: "\e0c7"; } .fl-bigmug-line-up111:before { content: "\e0c8"; } .fl-bigmug-line-up112:before { content: "\e0c9"; } .fl-bigmug-line-up114:before { content: "\e0ca"; } .fl-bigmug-line-up98:before { content: "\e0cb"; } .fl-bigmug-line-up99:before { content: "\e0cc"; } .fl-bigmug-line-upload91:before { content: "\e0cd"; } .fl-bigmug-line-upload92:before { content: "\e0ce"; } .fl-bigmug-line-upper8:before { content: "\e0cf"; } .fl-bigmug-line-user143:before { content: "\e0d0"; } .fl-bigmug-line-user144:before { content: "\e0d1"; } .fl-bigmug-line-video163:before { content: "\e0d2"; } .fl-bigmug-line-wallet26:before { content: "\e0d3"; } .fl-bigmug-line-weather21:before { content: "\e0d4"; } .fl-bigmug-line-weekly14:before { content: "\e0d5"; } .fl-bigmug-line-weekly15:before { content: "\e0d6"; } .fl-bigmug-line-wind24:before { content: "\e0d7"; } .fl-bigmug-line-window50:before { content: "\e0d8"; } .fl-bigmug-line-winds4:before { content: "\e0d9"; } .fl-bigmug-line-wrench66:before { content: "\e0da"; } .fl-bigmug-line-zoom60:before { content: "\e0db"; } @font-face { font-family: 'Linearicons'; font-weight: normal; font-style: normal; src: url(../fonts/Linearicons.eot); /* For IE6-8 */ src: local("Linearicons"), local("Linearicons"), url(../fonts/Linearicons.woff) format("woff"), url(../fonts/Linearicons.ttf) format("truetype"); } .linearicons, [class^="linearicons-"], [class*=" linearicons-"] { /* use !important to prevent issues with browser extensions that change fonts */ font-family: 'Linearicons' !important; speak: none; font-style: normal; font-weight: normal; font-variant: normal; text-transform: none; line-height: 1; /* Better Font Rendering =========== */ -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } .linearicons-home:before { content: "\e900"; } .linearicons-home2:before { content: "\e901"; } .linearicons-home3:before { content: "\e902"; } .linearicons-home-icon3:before { content: "\e902"; } .linearicons-home4:before { content: "\e903"; } .linearicons-home5:before { content: "\e904"; } .linearicons-home6:before { content: "\e905"; } .linearicons-bathtub:before { content: "\e906"; } .linearicons-toothbrush:before { content: "\e907"; } .linearicons-bed:before { content: "\e908"; } .linearicons-couch:before { content: "\e909"; } .linearicons-chair:before { content: "\e90a"; } .linearicons-city:before { content: "\e90b"; } .linearicons-apartment:before { content: "\e90c"; } .linearicons-pencil:before { content: "\e90d"; } .linearicons-pencil2:before { content: "\e90e"; } .linearicons-pen:before { content: "\e90f"; } .linearicons-pencil3:before { content: "\e910"; } .linearicons-eraser:before { content: "\e911"; } .linearicons-pencil4:before { content: "\e912"; } .linearicons-pencil5:before { content: "\e913"; } .linearicons-feather:before { content: "\e914"; } .linearicons-feather2:before { content: "\e915"; } .linearicons-feather3:before { content: "\e916"; } .linearicons-pen2:before { content: "\e917"; } .linearicons-pen-add:before { content: "\e918"; } .linearicons-pen-remove:before { content: "\e919"; } .linearicons-vector:before { content: "\e91a"; } .linearicons-pen3:before { content: "\e91b"; } .linearicons-blog:before { content: "\e91c"; } .linearicons-brush:before { content: "\e91d"; } .linearicons-brush2:before { content: "\e91e"; } .linearicons-spray:before { content: "\e91f"; } .linearicons-paint-roller:before { content: "\e920"; } .linearicons-stamp:before { content: "\e921"; } .linearicons-tape:before { content: "\e922"; } .linearicons-desk-tape:before { content: "\e923"; } .linearicons-texture:before { content: "\e924"; } .linearicons-eye-dropper:before { content: "\e925"; } .linearicons-palette:before { content: "\e926"; } .linearicons-color-sampler:before { content: "\e927"; } .linearicons-bucket:before { content: "\e928"; } .linearicons-gradient:before { content: "\e929"; } .linearicons-gradient2:before { content: "\e92a"; } .linearicons-magic-wand:before { content: "\e92b"; } .linearicons-magnet:before { content: "\e92c"; } .linearicons-pencil-ruler:before { content: "\e92d"; } .linearicons-pencil-ruler2:before { content: "\e92e"; } .linearicons-compass:before { content: "\e92f"; } .linearicons-aim:before { content: "\e930"; } .linearicons-gun:before { content: "\e931"; } .linearicons-bottle:before { content: "\e932"; } .linearicons-drop:before { content: "\e933"; } .linearicons-drop-crossed:before { content: "\e934"; } .linearicons-drop2:before { content: "\e935"; } .linearicons-snow:before { content: "\e936"; } .linearicons-snow2:before { content: "\e937"; } .linearicons-fire:before { content: "\e938"; } .linearicons-lighter:before { content: "\e939"; } .linearicons-knife:before { content: "\e93a"; } .linearicons-dagger:before { content: "\e93b"; } .linearicons-tissue:before { content: "\e93c"; } .linearicons-toilet-paper:before { content: "\e93d"; } .linearicons-poop:before { content: "\e93e"; } .linearicons-umbrella:before { content: "\e93f"; } .linearicons-umbrella2:before { content: "\e940"; } .linearicons-rain:before { content: "\e941"; } .linearicons-tornado:before { content: "\e942"; } .linearicons-wind:before { content: "\e943"; } .linearicons-fan:before { content: "\e944"; } .linearicons-contrast:before { content: "\e945"; } .linearicons-sun-small:before { content: "\e946"; } .linearicons-sun:before { content: "\e947"; } .linearicons-sun2:before { content: "\e948"; } .linearicons-moon:before { content: "\e949"; } .linearicons-cloud:before { content: "\e94a"; } .linearicons-cloud-upload:before { content: "\e94b"; } .linearicons-cloud-download:before { content: "\e94c"; } .linearicons-cloud-rain:before { content: "\e94d"; } .linearicons-cloud-hailstones:before { content: "\e94e"; } .linearicons-cloud-snow:before { content: "\e94f"; } .linearicons-cloud-windy:before { content: "\e950"; } .linearicons-sun-wind:before { content: "\e951"; } .linearicons-cloud-fog:before { content: "\e952"; } .linearicons-cloud-sun:before { content: "\e953"; } .linearicons-cloud-lightning:before { content: "\e954"; } .linearicons-cloud-sync:before { content: "\e955"; } .linearicons-cloud-lock:before { content: "\e956"; } .linearicons-cloud-gear:before { content: "\e957"; } .linearicons-cloud-alert:before { content: "\e958"; } .linearicons-cloud-check:before { content: "\e959"; } .linearicons-cloud-cross:before { content: "\e95a"; } .linearicons-cloud-crossed:before { content: "\e95b"; } .linearicons-cloud-database:before { content: "\e95c"; } .linearicons-database:before { content: "\e95d"; } .linearicons-database-add:before { content: "\e95e"; } .linearicons-database-remove:before { content: "\e95f"; } .linearicons-database-lock:before { content: "\e960"; } .linearicons-database-refresh:before { content: "\e961"; } .linearicons-database-check:before { content: "\e962"; } .linearicons-database-history:before { content: "\e963"; } .linearicons-database-upload:before { content: "\e964"; } .linearicons-database-download:before { content: "\e965"; } .linearicons-server:before { content: "\e966"; } .linearicons-shield:before { content: "\e967"; } .linearicons-shield-check:before { content: "\e968"; } .linearicons-shield-alert:before { content: "\e969"; } .linearicons-shield-cross:before { content: "\e96a"; } .linearicons-lock:before { content: "\e96b"; } .linearicons-rotation-lock:before { content: "\e96c"; } .linearicons-unlock:before { content: "\e96d"; } .linearicons-key:before { content: "\e96e"; } .linearicons-key-hole:before { content: "\e96f"; } .linearicons-toggle-off:before { content: "\e970"; } .linearicons-toggle-on:before { content: "\e971"; } .linearicons-cog:before { content: "\e972"; } .linearicons-cog2:before { content: "\e973"; } .linearicons-wrench:before { content: "\e974"; } .linearicons-screwdriver:before { content: "\e975"; } .linearicons-hammer-wrench:before { content: "\e976"; } .linearicons-hammer:before { content: "\e977"; } .linearicons-saw:before { content: "\e978"; } .linearicons-axe:before { content: "\e979"; } .linearicons-axe2:before { content: "\e97a"; } .linearicons-shovel:before { content: "\e97b"; } .linearicons-pickaxe:before { content: "\e97c"; } .linearicons-factory:before { content: "\e97d"; } .linearicons-factory2:before { content: "\e97e"; } .linearicons-recycle:before { content: "\e97f"; } .linearicons-trash:before { content: "\e980"; } .linearicons-trash2:before { content: "\e981"; } .linearicons-trash3:before { content: "\e982"; } .linearicons-broom:before { content: "\e983"; } .linearicons-game:before { content: "\e984"; } .linearicons-gamepad:before { content: "\e985"; } .linearicons-joystick:before { content: "\e986"; } .linearicons-dice:before { content: "\e987"; } .linearicons-spades:before { content: "\e988"; } .linearicons-diamonds:before { content: "\e989"; } .linearicons-clubs:before { content: "\e98a"; } .linearicons-hearts:before { content: "\e98b"; } .linearicons-heart:before { content: "\e98c"; } .linearicons-star:before { content: "\e98d"; } .linearicons-star-half:before { content: "\e98e"; } .linearicons-star-empty:before { content: "\e98f"; } .linearicons-flag:before { content: "\e990"; } .linearicons-flag2:before { content: "\e991"; } .linearicons-flag3:before { content: "\e992"; } .linearicons-mailbox-full:before { content: "\e993"; } .linearicons-mailbox-empty:before { content: "\e994"; } .linearicons-at-sign:before { content: "\e995"; } .linearicons-envelope:before { content: "\e996"; } .linearicons-envelope-open:before { content: "\e997"; } .linearicons-paperclip:before { content: "\e998"; } .linearicons-paper-plane:before { content: "\e999"; } .linearicons-reply:before { content: "\e99a"; } .linearicons-reply-all:before { content: "\e99b"; } .linearicons-inbox:before { content: "\e99c"; } .linearicons-inbox2:before { content: "\e99d"; } .linearicons-outbox:before { content: "\e99e"; } .linearicons-box:before { content: "\e99f"; } .linearicons-archive:before { content: "\e9a0"; } .linearicons-archive2:before { content: "\e9a1"; } .linearicons-drawers:before { content: "\e9a2"; } .linearicons-drawers2:before { content: "\e9a3"; } .linearicons-drawers3:before { content: "\e9a4"; } .linearicons-eye:before { content: "\e9a5"; } .linearicons-eye-crossed:before { content: "\e9a6"; } .linearicons-eye-plus:before { content: "\e9a7"; } .linearicons-eye-minus:before { content: "\e9a8"; } .linearicons-binoculars:before { content: "\e9a9"; } .linearicons-binoculars2:before { content: "\e9aa"; } .linearicons-hdd:before { content: "\e9ab"; } .linearicons-hdd-down:before { content: "\e9ac"; } .linearicons-hdd-up:before { content: "\e9ad"; } .linearicons-floppy-disk:before { content: "\e9ae"; } .linearicons-disc:before { content: "\e9af"; } .linearicons-tape2:before { content: "\e9b0"; } .linearicons-printer:before { content: "\e9b1"; } .linearicons-shredder:before { content: "\e9b2"; } .linearicons-file-empty:before { content: "\e9b3"; } .linearicons-file-add:before { content: "\e9b4"; } .linearicons-file-check:before { content: "\e9b5"; } .linearicons-file-lock:before { content: "\e9b6"; } .linearicons-files:before { content: "\e9b7"; } .linearicons-copy:before { content: "\e9b8"; } .linearicons-compare:before { content: "\e9b9"; } .linearicons-folder:before { content: "\e9ba"; } .linearicons-folder-search:before { content: "\e9bb"; } .linearicons-folder-plus:before { content: "\e9bc"; } .linearicons-folder-minus:before { content: "\e9bd"; } .linearicons-folder-download:before { content: "\e9be"; } .linearicons-folder-upload:before { content: "\e9bf"; } .linearicons-folder-star:before { content: "\e9c0"; } .linearicons-folder-heart:before { content: "\e9c1"; } .linearicons-folder-user:before { content: "\e9c2"; } .linearicons-folder-shared:before { content: "\e9c3"; } .linearicons-folder-music:before { content: "\e9c4"; } .linearicons-folder-picture:before { content: "\e9c5"; } .linearicons-folder-film:before { content: "\e9c6"; } .linearicons-scissors:before { content: "\e9c7"; } .linearicons-paste:before { content: "\e9c8"; } .linearicons-clipboard-empty:before { content: "\e9c9"; } .linearicons-clipboard-pencil:before { content: "\e9ca"; } .linearicons-clipboard-text:before { content: "\e9cb"; } .linearicons-clipboard-check:before { content: "\e9cc"; } .linearicons-clipboard-down:before { content: "\e9cd"; } .linearicons-clipboard-left:before { content: "\e9ce"; } .linearicons-clipboard-alert:before { content: "\e9cf"; } .linearicons-clipboard-user:before { content: "\e9d0"; } .linearicons-register:before { content: "\e9d1"; } .linearicons-enter:before { content: "\e9d2"; } .linearicons-exit:before { content: "\e9d3"; } .linearicons-papers:before { content: "\e9d4"; } .linearicons-news:before { content: "\e9d5"; } .linearicons-reading:before { content: "\e9d6"; } .linearicons-typewriter:before { content: "\e9d7"; } .linearicons-document:before { content: "\e9d8"; } .linearicons-document2:before { content: "\e9d9"; } .linearicons-graduation-hat:before { content: "\e9da"; } .linearicons-license:before { content: "\e9db"; } .linearicons-license2:before { content: "\e9dc"; } .linearicons-medal-empty:before { content: "\e9dd"; } .linearicons-medal-first:before { content: "\e9de"; } .linearicons-medal-second:before { content: "\e9df"; } .linearicons-medal-third:before { content: "\e9e0"; } .linearicons-podium:before { content: "\e9e1"; } .linearicons-trophy:before { content: "\e9e2"; } .linearicons-trophy2:before { content: "\e9e3"; } .linearicons-music-note:before { content: "\e9e4"; } .linearicons-music-note2:before { content: "\e9e5"; } .linearicons-music-note3:before { content: "\e9e6"; } .linearicons-playlist:before { content: "\e9e7"; } .linearicons-playlist-add:before { content: "\e9e8"; } .linearicons-guitar:before { content: "\e9e9"; } .linearicons-trumpet:before { content: "\e9ea"; } .linearicons-album:before { content: "\e9eb"; } .linearicons-shuffle:before { content: "\e9ec"; } .linearicons-repeat-one:before { content: "\e9ed"; } .linearicons-repeat:before { content: "\e9ee"; } .linearicons-headphones:before { content: "\e9ef"; } .linearicons-headset:before { content: "\e9f0"; } .linearicons-loudspeaker:before { content: "\e9f1"; } .linearicons-equalizer:before { content: "\e9f2"; } .linearicons-theater:before { content: "\e9f3"; } .linearicons-3d-glasses:before { content: "\e9f4"; } .linearicons-ticket:before { content: "\e9f5"; } .linearicons-presentation:before { content: "\e9f6"; } .linearicons-play:before { content: "\e9f7"; } .linearicons-film-play:before { content: "\e9f8"; } .linearicons-clapboard-play:before { content: "\e9f9"; } .linearicons-media:before { content: "\e9fa"; } .linearicons-film:before { content: "\e9fb"; } .linearicons-film2:before { content: "\e9fc"; } .linearicons-surveillance:before { content: "\e9fd"; } .linearicons-surveillance2:before { content: "\e9fe"; } .linearicons-camera:before { content: "\e9ff"; } .linearicons-camera-crossed:before { content: "\ea00"; } .linearicons-camera-play:before { content: "\ea01"; } .linearicons-time-lapse:before { content: "\ea02"; } .linearicons-record:before { content: "\ea03"; } .linearicons-camera2:before { content: "\ea04"; } .linearicons-camera-flip:before { content: "\ea05"; } .linearicons-panorama:before { content: "\ea06"; } .linearicons-time-lapse2:before { content: "\ea07"; } .linearicons-shutter:before { content: "\ea08"; } .linearicons-shutter2:before { content: "\ea09"; } .linearicons-face-detection:before { content: "\ea0a"; } .linearicons-flare:before { content: "\ea0b"; } .linearicons-convex:before { content: "\ea0c"; } .linearicons-concave:before { content: "\ea0d"; } .linearicons-picture:before { content: "\ea0e"; } .linearicons-picture2:before { content: "\ea0f"; } .linearicons-picture3:before { content: "\ea10"; } .linearicons-pictures:before { content: "\ea11"; } .linearicons-book:before { content: "\ea12"; } .linearicons-audio-book:before { content: "\ea13"; } .linearicons-book2:before { content: "\ea14"; } .linearicons-bookmark:before { content: "\ea15"; } .linearicons-bookmark2:before { content: "\ea16"; } .linearicons-label:before { content: "\ea17"; } .linearicons-library:before { content: "\ea18"; } .linearicons-library2:before { content: "\ea19"; } .linearicons-contacts:before { content: "\ea1a"; } .linearicons-profile:before { content: "\ea1b"; } .linearicons-portrait:before { content: "\ea1c"; } .linearicons-portrait2:before { content: "\ea1d"; } .linearicons-user:before { content: "\ea1e"; } .linearicons-user-plus:before { content: "\ea1f"; } .linearicons-user-minus:before { content: "\ea20"; } .linearicons-user-lock:before { content: "\ea21"; } .linearicons-users:before { content: "\ea22"; } .linearicons-users2:before { content: "\ea23"; } .linearicons-users-plus:before { content: "\ea24"; } .linearicons-users-minus:before { content: "\ea25"; } .linearicons-group-work:before { content: "\ea26"; } .linearicons-woman:before { content: "\ea27"; } .linearicons-man:before { content: "\ea28"; } .linearicons-baby:before { content: "\ea29"; } .linearicons-baby2:before { content: "\ea2a"; } .linearicons-baby3:before { content: "\ea2b"; } .linearicons-baby-bottle:before { content: "\ea2c"; } .linearicons-walk:before { content: "\ea2d"; } .linearicons-hand-waving:before { content: "\ea2e"; } .linearicons-jump:before { content: "\ea2f"; } .linearicons-run:before { content: "\ea30"; } .linearicons-woman2:before { content: "\ea31"; } .linearicons-man2:before { content: "\ea32"; } .linearicons-man-woman:before { content: "\ea33"; } .linearicons-height:before { content: "\ea34"; } .linearicons-weight:before { content: "\ea35"; } .linearicons-scale:before { content: "\ea36"; } .linearicons-button:before { content: "\ea37"; } .linearicons-bow-tie:before { content: "\ea38"; } .linearicons-tie:before { content: "\ea39"; } .linearicons-socks:before { content: "\ea3a"; } .linearicons-shoe:before { content: "\ea3b"; } .linearicons-shoes:before { content: "\ea3c"; } .linearicons-hat:before { content: "\ea3d"; } .linearicons-pants:before { content: "\ea3e"; } .linearicons-shorts:before { content: "\ea3f"; } .linearicons-flip-flops:before { content: "\ea40"; } .linearicons-shirt:before { content: "\ea41"; } .linearicons-hanger:before { content: "\ea42"; } .linearicons-laundry:before { content: "\ea43"; } .linearicons-store:before { content: "\ea44"; } .linearicons-haircut:before { content: "\ea45"; } .linearicons-store-24:before { content: "\ea46"; } .linearicons-barcode:before { content: "\ea47"; } .linearicons-barcode2:before { content: "\ea48"; } .linearicons-barcode3:before { content: "\ea49"; } .linearicons-cashier:before { content: "\ea4a"; } .linearicons-bag:before { content: "\ea4b"; } .linearicons-bag2:before { content: "\ea4c"; } .linearicons-cart:before { content: "\ea4d"; } .linearicons-cart-empty:before { content: "\ea4e"; } .linearicons-cart-full:before { content: "\ea4f"; } .linearicons-cart-plus:before { content: "\ea50"; } .linearicons-cart-plus2:before { content: "\ea51"; } .linearicons-cart-add:before { content: "\ea52"; } .linearicons-cart-remove:before { content: "\ea53"; } .linearicons-cart-exchange:before { content: "\ea54"; } .linearicons-tag:before { content: "\ea55"; } .linearicons-tags:before { content: "\ea56"; } .linearicons-receipt:before { content: "\ea57"; } .linearicons-wallet:before { content: "\ea58"; } .linearicons-credit-card:before { content: "\ea59"; } .linearicons-cash-dollar:before { content: "\ea5a"; } .linearicons-cash-euro:before { content: "\ea5b"; } .linearicons-cash-pound:before { content: "\ea5c"; } .linearicons-cash-yen:before { content: "\ea5d"; } .linearicons-bag-dollar:before { content: "\ea5e"; } .linearicons-bag-euro:before { content: "\ea5f"; } .linearicons-bag-pound:before { content: "\ea60"; } .linearicons-bag-yen:before { content: "\ea61"; } .linearicons-coin-dollar:before { content: "\ea62"; } .linearicons-coin-euro:before { content: "\ea63"; } .linearicons-coin-pound:before { content: "\ea64"; } .linearicons-coin-yen:before { content: "\ea65"; } .linearicons-calculator:before { content: "\ea66"; } .linearicons-calculator2:before { content: "\ea67"; } .linearicons-abacus:before { content: "\ea68"; } .linearicons-vault:before { content: "\ea69"; } .linearicons-telephone:before { content: "\ea6a"; } .linearicons-phone-lock:before { content: "\ea6b"; } .linearicons-phone-wave:before { content: "\ea6c"; } .linearicons-phone-pause:before { content: "\ea6d"; } .linearicons-phone-outgoing:before { content: "\ea6e"; } .linearicons-phone-incoming:before { content: "\ea6f"; } .linearicons-phone-in-out:before { content: "\ea70"; } .linearicons-phone-error:before { content: "\ea71"; } .linearicons-phone-sip:before { content: "\ea72"; } .linearicons-phone-plus:before { content: "\ea73"; } .linearicons-phone-minus:before { content: "\ea74"; } .linearicons-voicemail:before { content: "\ea75"; } .linearicons-dial:before { content: "\ea76"; } .linearicons-telephone2:before { content: "\ea77"; } .linearicons-pushpin:before { content: "\ea78"; } .linearicons-pushpin2:before { content: "\ea79"; } .linearicons-map-marker:before { content: "\ea7a"; } .linearicons-map-marker-user:before { content: "\ea7b"; } .linearicons-map-marker-down:before { content: "\ea7c"; } .linearicons-map-marker-check:before { content: "\ea7d"; } .linearicons-map-marker-crossed:before { content: "\ea7e"; } .linearicons-radar:before { content: "\ea7f"; } .linearicons-compass2:before { content: "\ea80"; } .linearicons-map:before { content: "\ea81"; } .linearicons-map2:before { content: "\ea82"; } .linearicons-location:before { content: "\ea83"; } .linearicons-road-sign:before { content: "\ea84"; } .linearicons-calendar-empty:before { content: "\ea85"; } .linearicons-calendar-check:before { content: "\ea86"; } .linearicons-calendar-cross:before { content: "\ea87"; } .linearicons-calendar-31:before { content: "\ea88"; } .linearicons-calendar-full:before { content: "\ea89"; } .linearicons-calendar-insert:before { content: "\ea8a"; } .linearicons-calendar-text:before { content: "\ea8b"; } .linearicons-calendar-user:before { content: "\ea8c"; } .linearicons-mouse:before { content: "\ea8d"; } .linearicons-mouse-left:before { content: "\ea8e"; } .linearicons-mouse-right:before { content: "\ea8f"; } .linearicons-mouse-both:before { content: "\ea90"; } .linearicons-keyboard:before { content: "\ea91"; } .linearicons-keyboard-up:before { content: "\ea92"; } .linearicons-keyboard-down:before { content: "\ea93"; } .linearicons-delete:before { content: "\ea94"; } .linearicons-spell-check:before { content: "\ea95"; } .linearicons-escape:before { content: "\ea96"; } .linearicons-enter2:before { content: "\ea97"; } .linearicons-screen:before { content: "\ea98"; } .linearicons-aspect-ratio:before { content: "\ea99"; } .linearicons-signal:before { content: "\ea9a"; } .linearicons-signal-lock:before { content: "\ea9b"; } .linearicons-signal-80:before { content: "\ea9c"; } .linearicons-signal-60:before { content: "\ea9d"; } .linearicons-signal-40:before { content: "\ea9e"; } .linearicons-signal-20:before { content: "\ea9f"; } .linearicons-signal-0:before { content: "\eaa0"; } .linearicons-signal-blocked:before { content: "\eaa1"; } .linearicons-sim:before { content: "\eaa2"; } .linearicons-flash-memory:before { content: "\eaa3"; } .linearicons-usb-drive:before { content: "\eaa4"; } .linearicons-phone:before { content: "\eaa5"; } .linearicons-smartphone:before { content: "\eaa6"; } .linearicons-smartphone-notification:before { content: "\eaa7"; } .linearicons-smartphone-vibration:before { content: "\eaa8"; } .linearicons-smartphone-embed:before { content: "\eaa9"; } .linearicons-smartphone-waves:before { content: "\eaaa"; } .linearicons-tablet:before { content: "\eaab"; } .linearicons-tablet2:before { content: "\eaac"; } .linearicons-laptop:before { content: "\eaad"; } .linearicons-laptop-phone:before { content: "\eaae"; } .linearicons-desktop:before { content: "\eaaf"; } .linearicons-launch:before { content: "\eab0"; } .linearicons-new-tab:before { content: "\eab1"; } .linearicons-window:before { content: "\eab2"; } .linearicons-cable:before { content: "\eab3"; } .linearicons-cable2:before { content: "\eab4"; } .linearicons-tv:before { content: "\eab5"; } .linearicons-radio:before { content: "\eab6"; } .linearicons-remote-control:before { content: "\eab7"; } .linearicons-power-switch:before { content: "\eab8"; } .linearicons-power:before { content: "\eab9"; } .linearicons-power-crossed:before { content: "\eaba"; } .linearicons-flash-auto:before { content: "\eabb"; } .linearicons-lamp:before { content: "\eabc"; } .linearicons-flashlight:before { content: "\eabd"; } .linearicons-lampshade:before { content: "\eabe"; } .linearicons-cord:before { content: "\eabf"; } .linearicons-outlet:before { content: "\eac0"; } .linearicons-battery-power:before { content: "\eac1"; } .linearicons-battery-empty:before { content: "\eac2"; } .linearicons-battery-alert:before { content: "\eac3"; } .linearicons-battery-error:before { content: "\eac4"; } .linearicons-battery-low1:before { content: "\eac5"; } .linearicons-battery-low2:before { content: "\eac6"; } .linearicons-battery-low3:before { content: "\eac7"; } .linearicons-battery-mid1:before { content: "\eac8"; } .linearicons-battery-mid2:before { content: "\eac9"; } .linearicons-battery-mid3:before { content: "\eaca"; } .linearicons-battery-full:before { content: "\eacb"; } .linearicons-battery-charging:before { content: "\eacc"; } .linearicons-battery-charging2:before { content: "\eacd"; } .linearicons-battery-charging3:before { content: "\eace"; } .linearicons-battery-charging4:before { content: "\eacf"; } .linearicons-battery-charging5:before { content: "\ead0"; } .linearicons-battery-charging6:before { content: "\ead1"; } .linearicons-battery-charging7:before { content: "\ead2"; } .linearicons-chip:before { content: "\ead3"; } .linearicons-chip-x64:before { content: "\ead4"; } .linearicons-chip-x86:before { content: "\ead5"; } .linearicons-bubble:before { content: "\ead6"; } .linearicons-bubbles:before { content: "\ead7"; } .linearicons-bubble-dots:before { content: "\ead8"; } .linearicons-bubble-alert:before { content: "\ead9"; } .linearicons-bubble-question:before { content: "\eada"; } .linearicons-bubble-text:before { content: "\eadb"; } .linearicons-bubble-pencil:before { content: "\eadc"; } .linearicons-bubble-picture:before { content: "\eadd"; } .linearicons-bubble-video:before { content: "\eade"; } .linearicons-bubble-user:before { content: "\eadf"; } .linearicons-bubble-quote:before { content: "\eae0"; } .linearicons-bubble-heart:before { content: "\eae1"; } .linearicons-bubble-emoticon:before { content: "\eae2"; } .linearicons-bubble-attachment:before { content: "\eae3"; } .linearicons-phone-bubble:before { content: "\eae4"; } .linearicons-quote-open:before { content: "\eae5"; } .linearicons-quote-close:before { content: "\eae6"; } .linearicons-dna:before { content: "\eae7"; } .linearicons-heart-pulse:before { content: "\eae8"; } .linearicons-pulse:before { content: "\eae9"; } .linearicons-syringe:before { content: "\eaea"; } .linearicons-pills:before { content: "\eaeb"; } .linearicons-first-aid:before { content: "\eaec"; } .linearicons-lifebuoy:before { content: "\eaed"; } .linearicons-bandage:before { content: "\eaee"; } .linearicons-bandages:before { content: "\eaef"; } .linearicons-thermometer:before { content: "\eaf0"; } .linearicons-microscope:before { content: "\eaf1"; } .linearicons-brain:before { content: "\eaf2"; } .linearicons-beaker:before { content: "\eaf3"; } .linearicons-skull:before { content: "\eaf4"; } .linearicons-bone:before { content: "\eaf5"; } .linearicons-construction:before { content: "\eaf6"; } .linearicons-construction-cone:before { content: "\eaf7"; } .linearicons-pie-chart:before { content: "\eaf8"; } .linearicons-pie-chart2:before { content: "\eaf9"; } .linearicons-graph:before { content: "\eafa"; } .linearicons-chart-growth:before { content: "\eafb"; } .linearicons-chart-bars:before { content: "\eafc"; } .linearicons-chart-settings:before { content: "\eafd"; } .linearicons-cake:before { content: "\eafe"; } .linearicons-gift:before { content: "\eaff"; } .linearicons-balloon:before { content: "\eb00"; } .linearicons-rank:before { content: "\eb01"; } .linearicons-rank2:before { content: "\eb02"; } .linearicons-rank3:before { content: "\eb03"; } .linearicons-crown:before { content: "\eb04"; } .linearicons-lotus:before { content: "\eb05"; } .linearicons-diamond:before { content: "\eb06"; } .linearicons-diamond2:before { content: "\eb07"; } .linearicons-diamond3:before { content: "\eb08"; } .linearicons-diamond4:before { content: "\eb09"; } .linearicons-linearicons:before { content: "\eb0a"; } .linearicons-teacup:before { content: "\eb0b"; } .linearicons-teapot:before { content: "\eb0c"; } .linearicons-glass:before { content: "\eb0d"; } .linearicons-bottle2:before { content: "\eb0e"; } .linearicons-glass-cocktail:before { content: "\eb0f"; } .linearicons-glass2:before { content: "\eb10"; } .linearicons-dinner:before { content: "\eb11"; } .linearicons-dinner2:before { content: "\eb12"; } .linearicons-chef:before { content: "\eb13"; } .linearicons-scale2:before { content: "\eb14"; } .linearicons-egg:before { content: "\eb15"; } .linearicons-egg2:before { content: "\eb16"; } .linearicons-eggs:before { content: "\eb17"; } .linearicons-platter:before { content: "\eb18"; } .linearicons-steak:before { content: "\eb19"; } .linearicons-hamburger:before { content: "\eb1a"; } .linearicons-hotdog:before { content: "\eb1b"; } .linearicons-pizza:before { content: "\eb1c"; } .linearicons-sausage:before { content: "\eb1d"; } .linearicons-chicken:before { content: "\eb1e"; } .linearicons-fish:before { content: "\eb1f"; } .linearicons-carrot:before { content: "\eb20"; } .linearicons-cheese:before { content: "\eb21"; } .linearicons-bread:before { content: "\eb22"; } .linearicons-ice-cream:before { content: "\eb23"; } .linearicons-ice-cream2:before { content: "\eb24"; } .linearicons-candy:before { content: "\eb25"; } .linearicons-lollipop:before { content: "\eb26"; } .linearicons-coffee-bean:before { content: "\eb27"; } .linearicons-coffee-cup:before { content: "\eb28"; } .linearicons-cherry:before { content: "\eb29"; } .linearicons-grapes:before { content: "\eb2a"; } .linearicons-citrus:before { content: "\eb2b"; } .linearicons-apple:before { content: "\eb2c"; } .linearicons-leaf:before { content: "\eb2d"; } .linearicons-landscape:before { content: "\eb2e"; } .linearicons-pine-tree:before { content: "\eb2f"; } .linearicons-tree:before { content: "\eb30"; } .linearicons-cactus:before { content: "\eb31"; } .linearicons-paw:before { content: "\eb32"; } .linearicons-footprint:before { content: "\eb33"; } .linearicons-speed-slow:before { content: "\eb34"; } .linearicons-speed-medium:before { content: "\eb35"; } .linearicons-speed-fast:before { content: "\eb36"; } .linearicons-rocket:before { content: "\eb37"; } .linearicons-hammer2:before { content: "\eb38"; } .linearicons-balance:before { content: "\eb39"; } .linearicons-briefcase:before { content: "\eb3a"; } .linearicons-luggage-weight:before { content: "\eb3b"; } .linearicons-dolly:before { content: "\eb3c"; } .linearicons-plane:before { content: "\eb3d"; } .linearicons-plane-crossed:before { content: "\eb3e"; } .linearicons-helicopter:before { content: "\eb3f"; } .linearicons-traffic-lights:before { content: "\eb40"; } .linearicons-siren:before { content: "\eb41"; } .linearicons-road:before { content: "\eb42"; } .linearicons-engine:before { content: "\eb43"; } .linearicons-oil-pressure:before { content: "\eb44"; } .linearicons-coolant-temperature:before { content: "\eb45"; } .linearicons-car-battery:before { content: "\eb46"; } .linearicons-gas:before { content: "\eb47"; } .linearicons-gallon:before { content: "\eb48"; } .linearicons-transmission:before { content: "\eb49"; } .linearicons-car:before { content: "\eb4a"; } .linearicons-car-wash:before { content: "\eb4b"; } .linearicons-car-wash2:before { content: "\eb4c"; } .linearicons-bus:before { content: "\eb4d"; } .linearicons-bus2:before { content: "\eb4e"; } .linearicons-car2:before { content: "\eb4f"; } .linearicons-parking:before { content: "\eb50"; } .linearicons-car-lock:before { content: "\eb51"; } .linearicons-taxi:before { content: "\eb52"; } .linearicons-car-siren:before { content: "\eb53"; } .linearicons-car-wash3:before { content: "\eb54"; } .linearicons-car-wash4:before { content: "\eb55"; } .linearicons-ambulance:before { content: "\eb56"; } .linearicons-truck:before { content: "\eb57"; } .linearicons-trailer:before { content: "\eb58"; } .linearicons-scale-truck:before { content: "\eb59"; } .linearicons-train:before { content: "\eb5a"; } .linearicons-ship:before { content: "\eb5b"; } .linearicons-ship2:before { content: "\eb5c"; } .linearicons-anchor:before { content: "\eb5d"; } .linearicons-boat:before { content: "\eb5e"; } .linearicons-bicycle:before { content: "\eb5f"; } .linearicons-bicycle2:before { content: "\eb60"; } .linearicons-dumbbell:before { content: "\eb61"; } .linearicons-bench-press:before { content: "\eb62"; } .linearicons-swim:before { content: "\eb63"; } .linearicons-football:before { content: "\eb64"; } .linearicons-baseball-bat:before { content: "\eb65"; } .linearicons-baseball:before { content: "\eb66"; } .linearicons-tennis:before { content: "\eb67"; } .linearicons-tennis2:before { content: "\eb68"; } .linearicons-ping-pong:before { content: "\eb69"; } .linearicons-hockey:before { content: "\eb6a"; } .linearicons-8ball:before { content: "\eb6b"; } .linearicons-bowling:before { content: "\eb6c"; } .linearicons-bowling-pins:before { content: "\eb6d"; } .linearicons-golf:before { content: "\eb6e"; } .linearicons-golf2:before { content: "\eb6f"; } .linearicons-archery:before { content: "\eb70"; } .linearicons-slingshot:before { content: "\eb71"; } .linearicons-soccer:before { content: "\eb72"; } .linearicons-basketball:before { content: "\eb73"; } .linearicons-cube:before { content: "\eb74"; } .linearicons-3d-rotate:before { content: "\eb75"; } .linearicons-puzzle:before { content: "\eb76"; } .linearicons-glasses:before { content: "\eb77"; } .linearicons-glasses2:before { content: "\eb78"; } .linearicons-accessibility:before { content: "\eb79"; } .linearicons-wheelchair:before { content: "\eb7a"; } .linearicons-wall:before { content: "\eb7b"; } .linearicons-fence:before { content: "\eb7c"; } .linearicons-wall2:before { content: "\eb7d"; } .linearicons-icons:before { content: "\eb7e"; } .linearicons-resize-handle:before { content: "\eb7f"; } .linearicons-icons2:before { content: "\eb80"; } .linearicons-select:before { content: "\eb81"; } .linearicons-select2:before { content: "\eb82"; } .linearicons-site-map:before { content: "\eb83"; } .linearicons-earth:before { content: "\eb84"; } .linearicons-earth-lock:before { content: "\eb85"; } .linearicons-network:before { content: "\eb86"; } .linearicons-network-lock:before { content: "\eb87"; } .linearicons-planet:before { content: "\eb88"; } .linearicons-happy:before { content: "\eb89"; } .linearicons-smile:before { content: "\eb8a"; } .linearicons-grin:before { content: "\eb8b"; } .linearicons-tongue:before { content: "\eb8c"; } .linearicons-sad:before { content: "\eb8d"; } .linearicons-wink:before { content: "\eb8e"; } .linearicons-dream:before { content: "\eb8f"; } .linearicons-shocked:before { content: "\eb90"; } .linearicons-shocked2:before { content: "\eb91"; } .linearicons-tongue2:before { content: "\eb92"; } .linearicons-neutral:before { content: "\eb93"; } .linearicons-happy-grin:before { content: "\eb94"; } .linearicons-cool:before { content: "\eb95"; } .linearicons-mad:before { content: "\eb96"; } .linearicons-grin-evil:before { content: "\eb97"; } .linearicons-evil:before { content: "\eb98"; } .linearicons-wow:before { content: "\eb99"; } .linearicons-annoyed:before { content: "\eb9a"; } .linearicons-wondering:before { content: "\eb9b"; } .linearicons-confused:before { content: "\eb9c"; } .linearicons-zipped:before { content: "\eb9d"; } .linearicons-grumpy:before { content: "\eb9e"; } .linearicons-mustache:before { content: "\eb9f"; } .linearicons-tombstone-hipster:before { content: "\eba0"; } .linearicons-tombstone:before { content: "\eba1"; } .linearicons-ghost:before { content: "\eba2"; } .linearicons-ghost-hipster:before { content: "\eba3"; } .linearicons-halloween:before { content: "\eba4"; } .linearicons-christmas:before { content: "\eba5"; } .linearicons-easter-egg:before { content: "\eba6"; } .linearicons-mustache2:before { content: "\eba7"; } .linearicons-mustache-glasses:before { content: "\eba8"; } .linearicons-pipe:before { content: "\eba9"; } .linearicons-alarm:before { content: "\ebaa"; } .linearicons-alarm-add:before { content: "\ebab"; } .linearicons-alarm-snooze:before { content: "\ebac"; } .linearicons-alarm-ringing:before { content: "\ebad"; } .linearicons-bullhorn:before { content: "\ebae"; } .linearicons-hearing:before { content: "\ebaf"; } .linearicons-volume-high:before { content: "\ebb0"; } .linearicons-volume-medium:before { content: "\ebb1"; } .linearicons-volume-low:before { content: "\ebb2"; } .linearicons-volume:before { content: "\ebb3"; } .linearicons-mute:before { content: "\ebb4"; } .linearicons-lan:before { content: "\ebb5"; } .linearicons-lan2:before { content: "\ebb6"; } .linearicons-wifi:before { content: "\ebb7"; } .linearicons-wifi-lock:before { content: "\ebb8"; } .linearicons-wifi-blocked:before { content: "\ebb9"; } .linearicons-wifi-mid:before { content: "\ebba"; } .linearicons-wifi-low:before { content: "\ebbb"; } .linearicons-wifi-low2:before { content: "\ebbc"; } .linearicons-wifi-alert:before { content: "\ebbd"; } .linearicons-wifi-alert-mid:before { content: "\ebbe"; } .linearicons-wifi-alert-low:before { content: "\ebbf"; } .linearicons-wifi-alert-low2:before { content: "\ebc0"; } .linearicons-stream:before { content: "\ebc1"; } .linearicons-stream-check:before { content: "\ebc2"; } .linearicons-stream-error:before { content: "\ebc3"; } .linearicons-stream-alert:before { content: "\ebc4"; } .linearicons-communication:before { content: "\ebc5"; } .linearicons-communication-crossed:before { content: "\ebc6"; } .linearicons-broadcast:before { content: "\ebc7"; } .linearicons-antenna:before { content: "\ebc8"; } .linearicons-satellite:before { content: "\ebc9"; } .linearicons-satellite2:before { content: "\ebca"; } .linearicons-mic:before { content: "\ebcb"; } .linearicons-mic-mute:before { content: "\ebcc"; } .linearicons-mic2:before { content: "\ebcd"; } .linearicons-spotlights:before { content: "\ebce"; } .linearicons-hourglass:before { content: "\ebcf"; } .linearicons-loading:before { content: "\ebd0"; } .linearicons-loading2:before { content: "\ebd1"; } .linearicons-loading3:before { content: "\ebd2"; } .linearicons-refresh:before { content: "\ebd3"; } .linearicons-refresh2:before { content: "\ebd4"; } .linearicons-undo:before { content: "\ebd5"; } .linearicons-redo:before { content: "\ebd6"; } .linearicons-jump2:before { content: "\ebd7"; } .linearicons-undo2:before { content: "\ebd8"; } .linearicons-redo2:before { content: "\ebd9"; } .linearicons-sync:before { content: "\ebda"; } .linearicons-repeat-one2:before { content: "\ebdb"; } .linearicons-sync-crossed:before { content: "\ebdc"; } .linearicons-sync2:before { content: "\ebdd"; } .linearicons-repeat-one3:before { content: "\ebde"; } .linearicons-sync-crossed2:before { content: "\ebdf"; } .linearicons-return:before { content: "\ebe0"; } .linearicons-return2:before { content: "\ebe1"; } .linearicons-refund:before { content: "\ebe2"; } .linearicons-history:before { content: "\ebe3"; } .linearicons-history2:before { content: "\ebe4"; } .linearicons-self-timer:before { content: "\ebe5"; } .linearicons-clock:before { content: "\ebe6"; } .linearicons-clock2:before { content: "\ebe7"; } .linearicons-clock3:before { content: "\ebe8"; } .linearicons-watch:before { content: "\ebe9"; } .linearicons-alarm2:before { content: "\ebea"; } .linearicons-alarm-add2:before { content: "\ebeb"; } .linearicons-alarm-remove:before { content: "\ebec"; } .linearicons-alarm-check:before { content: "\ebed"; } .linearicons-alarm-error:before { content: "\ebee"; } .linearicons-timer:before { content: "\ebef"; } .linearicons-timer-crossed:before { content: "\ebf0"; } .linearicons-timer2:before { content: "\ebf1"; } .linearicons-timer-crossed2:before { content: "\ebf2"; } .linearicons-download:before { content: "\ebf3"; } .linearicons-upload:before { content: "\ebf4"; } .linearicons-download2:before { content: "\ebf5"; } .linearicons-upload2:before { content: "\ebf6"; } .linearicons-enter-up:before { content: "\ebf7"; } .linearicons-enter-down:before { content: "\ebf8"; } .linearicons-enter-left:before { content: "\ebf9"; } .linearicons-enter-right:before { content: "\ebfa"; } .linearicons-exit-up:before { content: "\ebfb"; } .linearicons-exit-down:before { content: "\ebfc"; } .linearicons-exit-left:before { content: "\ebfd"; } .linearicons-exit-right:before { content: "\ebfe"; } .linearicons-enter-up2:before { content: "\ebff"; } .linearicons-enter-down2:before { content: "\ec00"; } .linearicons-enter-vertical:before { content: "\ec01"; } .linearicons-enter-left2:before { content: "\ec02"; } .linearicons-enter-right2:before { content: "\ec03"; } .linearicons-enter-horizontal:before { content: "\ec04"; } .linearicons-exit-up2:before { content: "\ec05"; } .linearicons-exit-down2:before { content: "\ec06"; } .linearicons-exit-left2:before { content: "\ec07"; } .linearicons-exit-right2:before { content: "\ec08"; } .linearicons-cli:before { content: "\ec09"; } .linearicons-bug:before { content: "\ec0a"; } .linearicons-code:before { content: "\ec0b"; } .linearicons-file-code:before { content: "\ec0c"; } .linearicons-file-image:before { content: "\ec0d"; } .linearicons-file-zip:before { content: "\ec0e"; } .linearicons-file-audio:before { content: "\ec0f"; } .linearicons-file-video:before { content: "\ec10"; } .linearicons-file-preview:before { content: "\ec11"; } .linearicons-file-charts:before { content: "\ec12"; } .linearicons-file-stats:before { content: "\ec13"; } .linearicons-file-spreadsheet:before { content: "\ec14"; } .linearicons-link:before { content: "\ec15"; } .linearicons-unlink:before { content: "\ec16"; } .linearicons-link2:before { content: "\ec17"; } .linearicons-unlink2:before { content: "\ec18"; } .linearicons-thumbs-up:before { content: "\ec19"; } .linearicons-thumbs-down:before { content: "\ec1a"; } .linearicons-thumbs-up2:before { content: "\ec1b"; } .linearicons-thumbs-down2:before { content: "\ec1c"; } .linearicons-thumbs-up3:before { content: "\ec1d"; } .linearicons-thumbs-down3:before { content: "\ec1e"; } .linearicons-share:before { content: "\ec1f"; } .linearicons-share2:before { content: "\ec20"; } .linearicons-share3:before { content: "\ec21"; } .linearicons-magnifier:before { content: "\ec22"; } .linearicons-file-search:before { content: "\ec23"; } .linearicons-find-replace:before { content: "\ec24"; } .linearicons-zoom-in:before { content: "\ec25"; } .linearicons-zoom-out:before { content: "\ec26"; } .linearicons-loupe:before { content: "\ec27"; } .linearicons-loupe-zoom-in:before { content: "\ec28"; } .linearicons-loupe-zoom-out:before { content: "\ec29"; } .linearicons-cross:before { content: "\ec2a"; } .linearicons-menu:before { content: "\ec2b"; } .linearicons-list:before { content: "\ec2c"; } .linearicons-list2:before { content: "\ec2d"; } .linearicons-list3:before { content: "\ec2e"; } .linearicons-menu2:before { content: "\ec2f"; } .linearicons-list4:before { content: "\ec30"; } .linearicons-menu3:before { content: "\ec31"; } .linearicons-exclamation:before { content: "\ec32"; } .linearicons-question:before { content: "\ec33"; } .linearicons-check:before { content: "\ec34"; } .linearicons-cross2:before { content: "\ec35"; } .linearicons-plus:before { content: "\ec36"; } .linearicons-minus:before { content: "\ec37"; } .linearicons-percent:before { content: "\ec38"; } .linearicons-chevron-up:before { content: "\ec39"; } .linearicons-chevron-down:before { content: "\ec3a"; } .linearicons-chevron-left:before { content: "\ec3b"; } .linearicons-chevron-right:before { content: "\ec3c"; } .linearicons-chevrons-expand-vertical:before { content: "\ec3d"; } .linearicons-chevrons-expand-horizontal:before { content: "\ec3e"; } .linearicons-chevrons-contract-vertical:before { content: "\ec3f"; } .linearicons-chevrons-contract-horizontal:before { content: "\ec40"; } .linearicons-arrow-up:before { content: "\ec41"; } .linearicons-arrow-down:before { content: "\ec42"; } .linearicons-arrow-left:before { content: "\ec43"; } .linearicons-arrow-right:before { content: "\ec44"; } .linearicons-arrow-up-right:before { content: "\ec45"; } .linearicons-arrows-merge:before { content: "\ec46"; } .linearicons-arrows-split:before { content: "\ec47"; } .linearicons-arrow-divert:before { content: "\ec48"; } .linearicons-arrow-return:before { content: "\ec49"; } .linearicons-expand:before { content: "\ec4a"; } .linearicons-contract:before { content: "\ec4b"; } .linearicons-expand2:before { content: "\ec4c"; } .linearicons-contract2:before { content: "\ec4d"; } .linearicons-move:before { content: "\ec4e"; } .linearicons-tab:before { content: "\ec4f"; } .linearicons-arrow-wave:before { content: "\ec50"; } .linearicons-expand3:before { content: "\ec51"; } .linearicons-expand4:before { content: "\ec52"; } .linearicons-contract3:before { content: "\ec53"; } .linearicons-notification:before { content: "\ec54"; } .linearicons-warning:before { content: "\ec55"; } .linearicons-notification-circle:before { content: "\ec56"; } .linearicons-question-circle:before { content: "\ec57"; } .linearicons-menu-circle:before { content: "\ec58"; } .linearicons-checkmark-circle:before { content: "\ec59"; } .linearicons-cross-circle:before { content: "\ec5a"; } .linearicons-plus-circle:before { content: "\ec5b"; } .linearicons-circle-minus:before { content: "\ec5c"; } .linearicons-percent-circle:before { content: "\ec5d"; } .linearicons-arrow-up-circle:before { content: "\ec5e"; } .linearicons-arrow-down-circle:before { content: "\ec5f"; } .linearicons-arrow-left-circle:before { content: "\ec60"; } .linearicons-arrow-right-circle:before { content: "\ec61"; } .linearicons-chevron-up-circle:before { content: "\ec62"; } .linearicons-chevron-down-circle:before { content: "\ec63"; } .linearicons-chevron-left-circle:before { content: "\ec64"; } .linearicons-chevron-right-circle:before { content: "\ec65"; } .linearicons-backward-circle:before { content: "\ec66"; } .linearicons-first-circle:before { content: "\ec67"; } .linearicons-previous-circle:before { content: "\ec68"; } .linearicons-stop-circle:before { content: "\ec69"; } .linearicons-play-circle:before { content: "\ec6a"; } .linearicons-pause-circle:before { content: "\ec6b"; } .linearicons-next-circle:before { content: "\ec6c"; } .linearicons-last-circle:before { content: "\ec6d"; } .linearicons-forward-circle:before { content: "\ec6e"; } .linearicons-eject-circle:before { content: "\ec6f"; } .linearicons-crop:before { content: "\ec70"; } .linearicons-frame-expand:before { content: "\ec71"; } .linearicons-frame-contract:before { content: "\ec72"; } .linearicons-focus:before { content: "\ec73"; } .linearicons-transform:before { content: "\ec74"; } .linearicons-grid:before { content: "\ec75"; } .linearicons-grid-crossed:before { content: "\ec76"; } .linearicons-layers:before { content: "\ec77"; } .linearicons-layers-crossed:before { content: "\ec78"; } .linearicons-toggle:before { content: "\ec79"; } .linearicons-rulers:before { content: "\ec7a"; } .linearicons-ruler:before { content: "\ec7b"; } .linearicons-funnel:before { content: "\ec7c"; } .linearicons-flip-horizontal:before { content: "\ec7d"; } .linearicons-flip-vertical:before { content: "\ec7e"; } .linearicons-flip-horizontal2:before { content: "\ec7f"; } .linearicons-flip-vertical2:before { content: "\ec80"; } .linearicons-angle:before { content: "\ec81"; } .linearicons-angle2:before { content: "\ec82"; } .linearicons-subtract:before { content: "\ec83"; } .linearicons-combine:before { content: "\ec84"; } .linearicons-intersect:before { content: "\ec85"; } .linearicons-exclude:before { content: "\ec86"; } .linearicons-align-center-vertical:before { content: "\ec87"; } .linearicons-align-right:before { content: "\ec88"; } .linearicons-align-bottom:before { content: "\ec89"; } .linearicons-align-left:before { content: "\ec8a"; } .linearicons-align-center-horizontal:before { content: "\ec8b"; } .linearicons-align-top:before { content: "\ec8c"; } .linearicons-square:before { content: "\ec8d"; } .linearicons-plus-square:before { content: "\ec8e"; } .linearicons-minus-square:before { content: "\ec8f"; } .linearicons-percent-square:before { content: "\ec90"; } .linearicons-arrow-up-square:before { content: "\ec91"; } .linearicons-arrow-down-square:before { content: "\ec92"; } .linearicons-arrow-left-square:before { content: "\ec93"; } .linearicons-arrow-right-square:before { content: "\ec94"; } .linearicons-chevron-up-square:before { content: "\ec95"; } .linearicons-chevron-down-square:before { content: "\ec96"; } .linearicons-chevron-left-square:before { content: "\ec97"; } .linearicons-chevron-right-square:before { content: "\ec98"; } .linearicons-check-square:before { content: "\ec99"; } .linearicons-cross-square:before { content: "\ec9a"; } .linearicons-menu-square:before { content: "\ec9b"; } .linearicons-prohibited:before { content: "\ec9c"; } .linearicons-circle:before { content: "\ec9d"; } .linearicons-radio-button:before { content: "\ec9e"; } .linearicons-ligature:before { content: "\ec9f"; } .linearicons-text-format:before { content: "\eca0"; } .linearicons-text-format-remove:before { content: "\eca1"; } .linearicons-text-size:before { content: "\eca2"; } .linearicons-bold:before { content: "\eca3"; } .linearicons-italic:before { content: "\eca4"; } .linearicons-underline:before { content: "\eca5"; } .linearicons-strikethrough:before { content: "\eca6"; } .linearicons-highlight:before { content: "\eca7"; } .linearicons-text-align-left:before { content: "\eca8"; } .linearicons-text-align-center:before { content: "\eca9"; } .linearicons-text-align-right:before { content: "\ecaa"; } .linearicons-text-align-justify:before { content: "\ecab"; } .linearicons-line-spacing:before { content: "\ecac"; } .linearicons-indent-increase:before { content: "\ecad"; } .linearicons-indent-decrease:before { content: "\ecae"; } .linearicons-text-wrap:before { content: "\ecaf"; } .linearicons-pilcrow:before { content: "\ecb0"; } .linearicons-direction-ltr:before { content: "\ecb1"; } .linearicons-direction-rtl:before { content: "\ecb2"; } .linearicons-page-break:before { content: "\ecb3"; } .linearicons-page-break2:before { content: "\ecb4"; } .linearicons-sort-alpha-asc:before { content: "\ecb5"; } .linearicons-sort-alpha-desc:before { content: "\ecb6"; } .linearicons-sort-numeric-asc:before { content: "\ecb7"; } .linearicons-sort-numeric-desc:before { content: "\ecb8"; } .linearicons-sort-amount-asc:before { content: "\ecb9"; } .linearicons-sort-amount-desc:before { content: "\ecba"; } .linearicons-sort-time-asc:before { content: "\ecbb"; } .linearicons-sort-time-desc:before { content: "\ecbc"; } .linearicons-sigma:before { content: "\ecbd"; } .linearicons-pencil-line:before { content: "\ecbe"; } .linearicons-hand:before { content: "\ecbf"; } .linearicons-pointer-up:before { content: "\ecc0"; } .linearicons-pointer-right:before { content: "\ecc1"; } .linearicons-pointer-down:before { content: "\ecc2"; } .linearicons-pointer-left:before { content: "\ecc3"; } .linearicons-finger-tap:before { content: "\ecc4"; } .linearicons-fingers-tap:before { content: "\ecc5"; } .linearicons-reminder:before { content: "\ecc6"; } .linearicons-fingers-crossed:before { content: "\ecc7"; } .linearicons-fingers-victory:before { content: "\ecc8"; } .linearicons-gesture-zoom:before { content: "\ecc9"; } .linearicons-gesture-pinch:before { content: "\ecca"; } .linearicons-fingers-scroll-horizontal:before { content: "\eccb"; } .linearicons-fingers-scroll-vertical:before { content: "\eccc"; } .linearicons-fingers-scroll-left:before { content: "\eccd"; } .linearicons-fingers-scroll-right:before { content: "\ecce"; } .linearicons-hand2:before { content: "\eccf"; } .linearicons-pointer-up2:before { content: "\ecd0"; } .linearicons-pointer-right2:before { content: "\ecd1"; } .linearicons-pointer-down2:before { content: "\ecd2"; } .linearicons-pointer-left2:before { content: "\ecd3"; } .linearicons-finger-tap2:before { content: "\ecd4"; } .linearicons-fingers-tap2:before { content: "\ecd5"; } .linearicons-reminder2:before { content: "\ecd6"; } .linearicons-gesture-zoom2:before { content: "\ecd7"; } .linearicons-gesture-pinch2:before { content: "\ecd8"; } .linearicons-fingers-scroll-horizontal2:before { content: "\ecd9"; } .linearicons-fingers-scroll-vertical2:before { content: "\ecda"; } .linearicons-fingers-scroll-left2:before { content: "\ecdb"; } .linearicons-fingers-scroll-right2:before { content: "\ecdc"; } .linearicons-fingers-scroll-vertical3:before { content: "\ecdd"; } .linearicons-border-style:before { content: "\ecde"; } .linearicons-border-all:before { content: "\ecdf"; } .linearicons-border-outer:before { content: "\ece0"; } .linearicons-border-inner:before { content: "\ece1"; } .linearicons-border-top:before { content: "\ece2"; } .linearicons-border-horizontal:before { content: "\ece3"; } .linearicons-border-bottom:before { content: "\ece4"; } .linearicons-border-left:before { content: "\ece5"; } .linearicons-border-vertical:before { content: "\ece6"; } .linearicons-border-right:before { content: "\ece7"; } .linearicons-border-none:before { content: "\ece8"; } .linearicons-ellipsis:before { content: "\ece9"; } \ No newline at end of file diff --git a/site/monica_site/static/css/portal.css b/site/monica_site/static/css/portal.css new file mode 100644 index 0000000..0cbd17b --- /dev/null +++ b/site/monica_site/static/css/portal.css @@ -0,0 +1,593 @@ +:root { + --monica-primary: #00626c; + --monica-primary-light: #008898; + --monica-ink: #212121; + --monica-muted: #6b7280; + --monica-surface: #f4f7f7; + --monica-border: #d9e3e4; + --monica-ok: #1a7f4b; + --monica-warn: #b45309; + --monica-danger: #b91c1c; + --portal-sidebar: 240px; + --exit-dark: #080808; + --exit-teal: #00626c; + --exit-teal-bright: #008898; +} + +/* Portal shell */ +body.portal { + margin: 0; + background: var(--monica-surface); + font-family: "Work Sans", sans-serif; + color: var(--monica-ink); +} +.portal-shell { display: flex; min-height: 100vh; } +.portal-sidebar { + width: var(--portal-sidebar); + background: #080808; + color: #cbd5e1; + flex-shrink: 0; + display: flex; + flex-direction: column; +} +.portal-brand { + padding: 16px 18px; + border-bottom: 1px solid rgba(255,255,255,0.08); + font-family: Poppins, sans-serif; + font-weight: 700; + color: #fff; + font-size: 14px; + text-decoration: none; + display: flex; + align-items: center; + gap: 10px; +} +.portal-brand img { + height: 36px; + width: auto; + display: block; +} +.portal-brand span { color: #7dd3da; font-size: 12px; letter-spacing: 0.04em; } +.portal-nav { padding: 12px 0; flex: 1; } +.portal-nav a { + display: block; + padding: 10px 18px; + color: #94a3b8; + text-decoration: none; + font-size: 14px; + border-left: 3px solid transparent; +} +.portal-nav a:hover { color: #fff; background: rgba(255,255,255,0.04); } +.portal-nav a.active { color: #fff; background: rgba(0,98,108,0.28); border-left-color: #00a0ab; } +.portal-nav .nav-section { + font-size: 10px; + text-transform: uppercase; + letter-spacing: 0.1em; + color: #64748b; + padding: 16px 18px 6px; +} +.portal-main { flex: 1; min-width: 0; display: flex; flex-direction: column; } +.portal-topbar { + background: #fff; + border-bottom: 1px solid var(--monica-border); + padding: 14px 24px; + display: flex; + align-items: center; + justify-content: space-between; + gap: 16px; +} +.portal-topbar h1 { margin: 0; font-size: 20px; font-family: Poppins, sans-serif; font-weight: 600; } +.portal-content { padding: 24px; flex: 1; } +.portal-user { font-size: 13px; color: var(--monica-muted); } +.portal-user strong { color: var(--monica-ink); } + +/* Portal widgets */ +.stat-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 16px; margin-bottom: 24px; } +.stat-card { + background: #fff; + border: 1px solid var(--monica-border); + padding: 18px; +} +.stat-card .label { font-size: 12px; text-transform: uppercase; letter-spacing: 0.06em; color: var(--monica-muted); } +.stat-card .value { font-size: 28px; font-weight: 700; font-family: Poppins, sans-serif; margin-top: 4px; } +.stat-card .delta { font-size: 12px; margin-top: 4px; color: var(--monica-ok); } + +.panel { + background: #fff; + border: 1px solid var(--monica-border); + margin-bottom: 20px; +} +.panel-h { + padding: 14px 18px; + border-bottom: 1px solid var(--monica-border); + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; +} +.panel-h h2 { margin: 0; font-size: 15px; font-family: Poppins, sans-serif; font-weight: 600; } +.panel-b { padding: 18px; } + +.btn { + display: inline-block; + padding: 9px 18px; + font-size: 13px; + font-weight: 600; + border: none; + cursor: pointer; + text-decoration: none; + font-family: "Work Sans", sans-serif; +} +.btn-primary { background: var(--monica-primary); color: #fff; } +.btn-primary:hover { background: #004e56; color: #fff; text-decoration: none; } +.btn-ghost { background: transparent; border: 1px solid var(--monica-border); color: var(--monica-ink); } +.btn-sm { padding: 6px 12px; font-size: 12px; } + +.table { width: 100%; border-collapse: collapse; font-size: 14px; } +.table th { + text-align: left; + font-size: 11px; + text-transform: uppercase; + letter-spacing: 0.06em; + color: var(--monica-muted); + padding: 10px 12px; + border-bottom: 1px solid var(--monica-border); + font-weight: 600; +} +.table td { padding: 12px; border-bottom: 1px solid var(--monica-border); vertical-align: middle; } +.table tr:hover td { background: #f8fafc; } +.table a { color: var(--monica-primary); text-decoration: none; font-weight: 500; } + +.badge { + display: inline-block; + padding: 2px 8px; + font-size: 11px; + font-weight: 600; + border-radius: 2px; + text-transform: uppercase; + letter-spacing: 0.04em; +} +.badge-new { background: #d0eef0; color: #00626c; } +.badge-contacted { background: #fef3c7; color: #92400e; } +.badge-won { background: #d1fae5; color: #065f46; } +.badge-lost { background: #fee2e2; color: #991b1b; } +.badge-sent { background: #d0eef0; color: #00626c; } +.badge-scheduled { background: #ede9fe; color: #5b21b6; } +.badge-delivered { background: #d1fae5; color: #065f46; } +.badge-failed { background: #fee2e2; color: #991b1b; } +.badge-optin { background: #d1fae5; color: #065f46; } +.badge-optout { background: #f3f4f6; color: #4b5563; } +.badge-draft { background: #f3f4f6; color: #4b5563; } +.badge-completed { background: #d1fae5; color: #065f46; } +.badge-cancelled { background: #f3f4f6; color: #4b5563; } +.badge-sending { background: #d0eef0; color: #00626c; } +.badge-queued { background: #ede9fe; color: #5b21b6; } +.badge-bounced { background: #fee2e2; color: #991b1b; } +.badge-published { background: #d1fae5; color: #065f46; } +.badge-publishing { background: #d0eef0; color: #00626c; } + +.form-grid { display: grid; gap: 16px; } +.form-grid.cols-2 { grid-template-columns: 1fr 1fr; } +@media (max-width: 700px) { .form-grid.cols-2 { grid-template-columns: 1fr; } } +.field label { display: block; font-size: 12px; font-weight: 600; margin-bottom: 6px; color: #374151; } +.field input, .field select, .field textarea { + width: 100%; + padding: 10px 12px; + border: 1px solid var(--monica-border); + font: inherit; + background: #fff; + box-sizing: border-box; +} +.field textarea { min-height: 100px; resize: vertical; } +.field .hint { font-size: 12px; color: var(--monica-muted); margin-top: 4px; } + +.channel-tabs { display: flex; gap: 0; border-bottom: 1px solid var(--monica-border); margin-bottom: 18px; } +.channel-tabs a { + padding: 10px 16px; + font-size: 13px; + font-weight: 600; + color: var(--monica-muted); + text-decoration: none; + border-bottom: 2px solid transparent; + margin-bottom: -1px; +} +.channel-tabs a.active { color: var(--monica-primary); border-bottom-color: var(--monica-primary); } + +.chart-placeholder { + height: 220px; + background: linear-gradient(180deg, #e8f4f5 0%, #fff 100%); + border: 1px dashed #a8d8dc; + display: flex; + align-items: flex-end; + gap: 8px; + padding: 24px 16px 16px; +} +.chart-placeholder .bar { + flex: 1; + background: var(--monica-primary); + opacity: 0.75; + border-radius: 2px 2px 0 0; + min-height: 20px; +} + +.consent-pills { display: flex; gap: 6px; flex-wrap: wrap; } +.preview-pane { + background: #f8fafc; + border: 1px solid var(--monica-border); + padding: 16px; + font-size: 14px; + min-height: 160px; +} +.calendar-grid { + display: grid; + grid-template-columns: repeat(7, 1fr); + gap: 4px; + font-size: 12px; +} +.calendar-grid .dow { text-align: center; color: var(--monica-muted); font-weight: 600; padding: 6px; } +.calendar-grid .day { + aspect-ratio: 1; + border: 1px solid var(--monica-border); + background: #fff; + padding: 6px; + position: relative; +} +.calendar-grid .day.has-post { background: #e8f4f5; } +.calendar-grid .day .dot { + width: 6px; height: 6px; border-radius: 50%; + background: var(--monica-primary); + margin-top: 4px; +} + +.login-wrap { + min-height: 100vh; + display: flex; + align-items: center; + justify-content: center; + background: linear-gradient(160deg, #080808, #00626c 60%, #e8f4f5 60%); + padding: 24px; +} +.login-card { + background: #fff; + width: 100%; + max-width: 400px; + padding: 36px 32px; + border: 1px solid var(--monica-border); +} +.login-card h1 { font-family: Poppins, sans-serif; font-size: 22px; margin: 0 0 6px; } +.login-card .sub { color: var(--monica-muted); font-size: 14px; margin-bottom: 24px; } + +.split { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; } +@media (max-width: 900px) { + .portal-shell { flex-direction: column; } + .portal-sidebar { width: 100%; } + .portal-nav { display: flex; flex-wrap: wrap; padding: 8px; } + .portal-nav .nav-section { display: none; } + .portal-nav a { border-left: none; border-bottom: 2px solid transparent; padding: 8px 12px; } + .portal-nav a.active { border-bottom-color: #00a0ab; } + .split { grid-template-columns: 1fr; } +} + +/* Public brand — EXIT Realty logo from exitrealtywheaton.com */ +.page .rd-navbar-brand { + display: inline-flex; + align-items: center; + gap: 10px; +} +.page .rd-navbar-brand img { + display: block !important; + height: 44px; + width: auto; + max-width: 160px; +} +.page .rd-navbar-brand::before, +.page .rd-navbar-brand::after { content: none !important; display: none !important; } +.page .rd-navbar-brand .brand-site { + font-family: Poppins, sans-serif; + font-weight: 700; + font-size: 13px; + letter-spacing: 0.04em; + color: #00626c; + text-decoration: none; + line-height: 1.2; +} +.page .rd-navbar-brand .brand-site span { + display: block; + font-size: 10px; + font-weight: 600; + letter-spacing: 0.12em; + text-transform: uppercase; + color: #6b7280; +} + +/* Shared extras */ +.muted { color: var(--monica-muted); font-size: 12px; } +.text-warn { color: var(--monica-warn); font-size: 12px; } +.hint-block { font-size: 13px; color: var(--monica-muted); margin: 12px 0 0; } +.plain-list { margin: 0 0 12px; padding-left: 18px; } +.plain-list li { margin-bottom: 6px; } +.check-row { display: flex; align-items: center; gap: 8px; font-size: 14px; } +.row-warn td { background: #fffbeb; } +.auth-alert { + background: #fff7ed; + border: 1px solid #fdba74; + color: #9a3412; + padding: 12px 16px; + margin-bottom: 16px; + font-size: 14px; +} +.auth-alert a { color: #9a3412; font-weight: 600; } + +/* Social accounts */ +.connect-grid { display: grid; gap: 12px; } +.connect-card { + display: flex; gap: 14px; align-items: flex-start; text-align: left; + padding: 14px; border: 1px solid var(--monica-border); background: #fff; + cursor: pointer; font: inherit; width: 100%; +} +.connect-card:hover { border-color: var(--monica-primary); } +.connect-card p { margin: 4px 0 0; font-size: 13px; color: var(--monica-muted); } +.platform-icon { + width: 40px; height: 40px; border-radius: 8px; display: flex; align-items: center; + justify-content: center; color: #fff; font-weight: 700; flex-shrink: 0; font-size: 14px; +} +.platform-icon.meta { background: #1877f2; } +.platform-icon.ig { background: linear-gradient(45deg, #f58529, #dd2a7b, #8134af); font-size: 12px; } +.platform-icon.li { background: #0a66c2; } +.platform-pill { + display: inline-block; padding: 2px 8px; font-size: 11px; font-weight: 600; + color: #fff; border-radius: 2px; +} +.platform-pill.meta { background: #1877f2; } +.platform-pill.ig { background: #dd2a7b; } +.platform-pill.li { background: #0a66c2; } + +/* Import wizard */ +.steps { + display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 20px; +} +.step { + display: flex; align-items: center; gap: 8px; font-size: 13px; color: var(--monica-muted); + padding: 8px 12px; background: #fff; border: 1px solid var(--monica-border); +} +.step span { + width: 22px; height: 22px; border-radius: 50%; background: #e5e7eb; color: #374151; + display: inline-flex; align-items: center; justify-content: center; font-size: 12px; font-weight: 700; +} +.step.active { border-color: var(--monica-primary); color: var(--monica-ink); } +.step.active span { background: var(--monica-primary); color: #fff; } +.dropzone { + border: 2px dashed var(--monica-border); padding: 32px; text-align: center; background: #f8fafc; +} + +/* Designer shared layout */ +.designer-layout { + display: grid; grid-template-columns: minmax(280px, 380px) 1fr; gap: 20px; align-items: start; +} +.designer-layout.with-ai { + grid-template-columns: minmax(260px, 340px) minmax(240px, 320px) 1fr; +} +@media (max-width: 1200px) { + .designer-layout.with-ai { grid-template-columns: 1fr 1fr; } + .designer-layout.with-ai .designer-preview-col { grid-column: 1 / -1; } +} +@media (max-width: 960px) { + .designer-layout, + .designer-layout.with-ai { grid-template-columns: 1fr; } +} + +/* PCM Integrations embedded designer */ +.pcm-iframe-wrap { + padding: 0 !important; + min-height: 70vh; + background: #f8fafc; +} +.pcm-iframe-wrap iframe { + display: block; + width: 100%; + height: min(80vh, 900px); + border: 0; + background: #fff; +} +.pcm-designer .table tr.is-active td { + background: #e8f4f5; +} + +/* Size picker */ +.size-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; } +.size-option { + border: 1px solid var(--monica-border); background: #fff; padding: 10px 12px; + text-align: left; cursor: pointer; font: inherit; color: inherit; +} +.size-option:hover { border-color: var(--monica-primary); } +.size-option.active { + border-color: var(--monica-primary); background: #e8f4f5; + box-shadow: inset 3px 0 0 var(--monica-primary); +} +.size-option .sz-name { font-weight: 700; font-size: 13px; font-family: Poppins, sans-serif; } +.size-option .sz-meta { font-size: 11px; color: var(--monica-muted); margin-top: 2px; } +.size-option .sz-badge { + display: inline-block; margin-top: 6px; font-size: 10px; font-weight: 600; + text-transform: uppercase; letter-spacing: 0.04em; color: var(--monica-primary); +} + +/* Media library */ +.media-library { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; } +.media-tile { + position: relative; aspect-ratio: 4 / 3; border: 2px solid transparent; + background-size: cover; background-position: center; background-color: #e5e7eb; + cursor: pointer; padding: 0; overflow: hidden; +} +.media-tile.active { border-color: var(--monica-primary); } +.media-tile .media-label { + position: absolute; left: 0; right: 0; bottom: 0; padding: 4px 6px; + background: rgba(15, 23, 42, 0.72); color: #fff; font-size: 10px; + white-space: nowrap; overflow: hidden; text-overflow: ellipsis; +} +.media-tile.upload-tile { + border: 2px dashed var(--monica-border); background: #f8fafc; + display: flex; flex-direction: column; align-items: center; justify-content: center; + gap: 4px; color: var(--monica-muted); font-size: 11px; font-weight: 600; +} +.media-tile.upload-tile:hover { border-color: var(--monica-primary); color: var(--monica-primary); } +.media-tile.upload-tile span { font-size: 22px; line-height: 1; color: var(--monica-primary); } +.dropzone.compact { padding: 16px; font-size: 13px; cursor: pointer; } +.dropzone.compact:hover { border-color: var(--monica-primary); } +.library-hint { font-size: 12px; color: var(--monica-muted); margin: 8px 0 0; } + +/* AI chat assist */ +.ai-chat { + display: flex; flex-direction: column; height: min(640px, 75vh); + background: #fff; border: 1px solid var(--monica-border); +} +.ai-chat-h { + padding: 12px 14px; border-bottom: 1px solid var(--monica-border); + display: flex; align-items: center; justify-content: space-between; gap: 8px; +} +.ai-chat-h h2 { margin: 0; font-size: 15px; font-family: Poppins, sans-serif; font-weight: 600; } +.ai-pill { + font-size: 10px; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; + background: #e8f4f5; color: var(--monica-primary); padding: 3px 8px; +} +.ai-chat-messages { + flex: 1; overflow-y: auto; padding: 14px; display: flex; flex-direction: column; gap: 12px; + background: linear-gradient(180deg, #f8fafc 0%, #fff 40%); +} +.ai-msg { max-width: 92%; font-size: 13px; line-height: 1.45; } +.ai-msg.bot { align-self: flex-start; } +.ai-msg.user { align-self: flex-end; } +.ai-msg .bubble { + padding: 10px 12px; border: 1px solid var(--monica-border); background: #fff; +} +.ai-msg.user .bubble { + background: var(--monica-primary); color: #fff; border-color: var(--monica-primary); +} +.ai-msg .who { + font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; + color: var(--monica-muted); margin-bottom: 4px; +} +.ai-msg.user .who { text-align: right; color: var(--monica-primary); } +.ai-draft { + margin-top: 8px; padding: 10px; background: #f8fafc; border: 1px dashed #a8d8dc; + white-space: pre-wrap; font-size: 12px; color: var(--monica-ink); +} +.ai-msg-actions { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; } +.ai-chat-prompts { + padding: 8px 12px; border-top: 1px solid var(--monica-border); + display: flex; flex-wrap: wrap; gap: 6px; background: #fafbfc; +} +.chip { + border: 1px solid var(--monica-border); background: #fff; font: inherit; + font-size: 11px; font-weight: 600; padding: 5px 10px; cursor: pointer; color: var(--monica-ink); +} +.chip:hover { border-color: var(--monica-primary); color: var(--monica-primary); } +.ai-chat-input { + border-top: 1px solid var(--monica-border); padding: 10px 12px; + display: flex; gap: 8px; align-items: flex-end; +} +.ai-chat-input textarea { + flex: 1; min-height: 44px; max-height: 100px; resize: vertical; + border: 1px solid var(--monica-border); padding: 8px 10px; font: inherit; font-size: 13px; +} +.ai-chat-input .btn { flex-shrink: 0; } + +/* Postcard */ +.postcard { + width: 100%; max-width: 420px; aspect-ratio: 6 / 4; position: relative; overflow: hidden; + background: #111; box-shadow: 0 12px 40px rgba(15, 23, 42, 0.25); color: #fff; + transition: max-width 0.2s, aspect-ratio 0.2s; +} +.postcard.size-4x6 { max-width: 420px; aspect-ratio: 6 / 4; } +.postcard.size-6x9 { max-width: 480px; aspect-ratio: 9 / 6; } +.postcard.size-6x11 { max-width: 520px; aspect-ratio: 11 / 6; } +.postcard.size-square { max-width: 400px; aspect-ratio: 1 / 1; } +.postcard-media { + position: absolute; inset: 0; background-size: cover; background-position: center; +} +.postcard-overlay { position: absolute; inset: 0; pointer-events: none; } +.postcard-overlay.ov-dark { background: linear-gradient(transparent 35%, rgba(0,0,0,.75)); } +.postcard-overlay.ov-light { background: linear-gradient(transparent 50%, rgba(255,255,255,.92)); } +.postcard-overlay.ov-none { background: none; } +.postcard-overlay.ov-light ~ .postcard-copy, +.ov-light + .postcard-copy { color: #111; } +.postcard-copy { + position: absolute; left: 20px; right: 20px; bottom: 18px; z-index: 2; +} +.postcard-headline { + font-family: Poppins, sans-serif; font-weight: 700; font-size: 28px; letter-spacing: 0.04em; + border-left: 4px solid #00626c; padding-left: 10px; line-height: 1.1; +} +.postcard-sub { margin-top: 8px; font-size: 14px; opacity: 0.95; } +.postcard-back { background: #fafafa; color: #212121; } +.postcard-back-grid { display: grid; grid-template-columns: 1.2fr 1fr; height: 100%; } +.postcard-back-msg { padding: 16px; display: flex; flex-direction: column; gap: 10px; border-right: 1px dashed #cbd5e1; } +.postcard-back-body { font-size: 12px; white-space: pre-wrap; flex: 1; line-height: 1.45; } +.postcard-agent { font-size: 11px; font-weight: 600; color: #00626c; } +.postcard-qr { + width: 48px; height: 48px; background: #111; color: #fff; font-size: 10px; + display: flex; align-items: center; justify-content: center; letter-spacing: 0.05em; +} +.postcard-back-mail { padding: 12px; position: relative; } +.stamp { + width: 44px; height: 52px; border: 2px dashed #94a3b8; float: right; + font-size: 9px; color: #94a3b8; display: flex; align-items: center; justify-content: center; +} +.addr-block { + margin-top: 48px; font-size: 12px; line-height: 1.4; + border-bottom: 1px solid #e2e8f0; padding-bottom: 8px; +} + +/* Social live preview */ +.platform-toggles { display: flex; flex-wrap: wrap; gap: 8px; } +.toggle-pill { + border: 1px solid var(--monica-border); padding: 8px 12px; font-size: 13px; + background: #fff; display: inline-flex; align-items: center; gap: 6px; +} +.toggle-pill.warn { border-color: #fdba74; background: #fff7ed; } +.social-phone { + width: 100%; max-width: 360px; background: #fff; border: 1px solid #d1d5db; + box-shadow: 0 8px 30px rgba(15,23,42,.12); overflow: hidden; font-size: 14px; +} +.social-phone.ig { max-width: 340px; } +.soc-header { + display: flex; align-items: center; gap: 10px; padding: 12px 14px; position: relative; +} +.soc-avatar { + width: 40px; height: 40px; background: #00626c; color: #fff; display: flex; + align-items: center; justify-content: center; font-weight: 700; font-size: 13px; flex-shrink: 0; +} +.soc-avatar.round { border-radius: 50%; background: linear-gradient(45deg, #f58529, #dd2a7b); } +.soc-avatar.sq { border-radius: 4px; background: #0a66c2; } +.soc-name { font-weight: 700; font-size: 13px; } +.soc-meta { font-size: 11px; color: #6b7280; } +.soc-more { margin-left: auto; color: #6b7280; } +.soc-caption { padding: 0 14px 12px; white-space: pre-wrap; line-height: 1.4; font-size: 13px; } +.soc-image { + height: 200px; background-size: cover; background-position: center; background-color: #e5e7eb; +} +.soc-image.square { height: 340px; } +.soc-actions { + padding: 10px 14px; border-top: 1px solid #e5e7eb; font-size: 12px; color: #4b5563; font-weight: 600; +} +.ig-act { font-size: 18px; border-top: none; padding-top: 8px; } + + + +/* Portal logout button in topbar */ +.portal-user form { display: inline; margin: 0; } +.portal-user button.linkish { + background: none; border: 0; padding: 0; color: var(--monica-primary); + font: inherit; font-size: 13px; font-weight: 600; cursor: pointer; text-decoration: underline; +} +.portal-flash { list-style: none; margin: 0 0 16px; padding: 0; } +.portal-flash li { + padding: 10px 14px; background: #e8f4f5; border-left: 3px solid var(--monica-primary); + margin-bottom: 8px; font-size: 14px; +} +.portal-flash li.error { background: #fee2e2; border-left-color: #b91c1c; } +.toolbar { display: flex; flex-wrap: wrap; gap: 12px; align-items: center; justify-content: space-between; margin-bottom: 16px; } +.toolbar-filters { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; } +.toolbar-filters input, .toolbar-filters select { + padding: 8px 10px; border: 1px solid var(--monica-border); font: inherit; font-size: 13px; background: #fff; +} +.empty-state { padding: 24px; color: var(--monica-muted); font-size: 14px; } diff --git a/site/monica_site/static/css/site.css b/site/monica_site/static/css/site.css new file mode 100644 index 0000000..d30e184 --- /dev/null +++ b/site/monica_site/static/css/site.css @@ -0,0 +1,211 @@ +:root { + --bg: #1a2332; + --bg-2: #243044; + --ink: #f3efe6; + --muted: #b7c0ce; + --accent: #c4a574; + --accent-2: #7ea08a; + --danger: #c97b7b; + --radius: 2px; + --font-display: "Fraunces", Georgia, serif; + --font-body: "Source Sans 3", system-ui, sans-serif; +} + +* { box-sizing: border-box; } +html, body { margin: 0; padding: 0; } +body { + font-family: var(--font-body); + color: var(--ink); + background: + radial-gradient(1200px 600px at 10% -10%, #2c3d55 0%, transparent 55%), + radial-gradient(900px 500px at 100% 0%, #3a2f28 0%, transparent 45%), + var(--bg); + min-height: 100vh; + line-height: 1.5; +} +a { color: var(--accent); text-decoration: none; } +a:hover { text-decoration: underline; } +.site-header, .site-footer { + display: flex; + justify-content: space-between; + align-items: center; + padding: 1rem 1.5rem; + gap: 1rem; +} +.site-header nav { display: flex; gap: 1rem; flex-wrap: wrap; } +.brand { + font-family: var(--font-display); + font-size: 1.4rem; + color: var(--ink); + font-weight: 700; +} +main { padding: 1.5rem; max-width: 1100px; margin: 0 auto; } +.hero { + min-height: 70vh; + display: grid; + align-content: end; + gap: 0.75rem; + padding: 2rem 0 3rem; +} +.hero h1 { + font-family: var(--font-display); + font-size: clamp(2.4rem, 6vw, 4.2rem); + margin: 0; + max-width: 14ch; + line-height: 1.05; +} +.hero p { max-width: 42ch; color: var(--muted); font-size: 1.1rem; } +.btn { + display: inline-block; + background: var(--accent); + color: #1a2332; + padding: 0.7rem 1.2rem; + border: 0; + font-weight: 600; + cursor: pointer; +} +.btn.secondary { background: transparent; color: var(--ink); border: 1px solid var(--muted); } +.flash { list-style: none; padding: 0 1.5rem; } +.flash li { background: var(--bg-2); padding: 0.6rem 0.9rem; margin: 0.4rem 0; } +.form-grid { display: grid; gap: 0.8rem; max-width: 32rem; } +.form-grid label { display: grid; gap: 0.3rem; color: var(--muted); } +.form-grid input, .form-grid textarea, .form-grid select { + background: var(--bg-2); + border: 1px solid #3a4a63; + color: var(--ink); + padding: 0.6rem 0.7rem; + font: inherit; +} +.portal-shell { display: grid; grid-template-columns: 220px 1fr; gap: 1.5rem; min-height: 70vh; } +.portal-nav { + background: rgba(36, 48, 68, 0.85); + padding: 1rem; + display: grid; + gap: 0.55rem; + align-content: start; +} +.portal-nav a { color: var(--ink); } +.portal-logout { margin: 0; } +.portal-logout button { + background: none; + border: 0; + padding: 0; + color: var(--ink); + font: inherit; + cursor: pointer; + text-align: left; +} +.portal-logout button:hover { text-decoration: underline; } +.brand-mini { font-family: var(--font-display); margin: 0 0 0.5rem; color: var(--accent); } +.stat-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 0.8rem; } +.stat { + background: rgba(36, 48, 68, 0.9); + padding: 1rem; +} +.stat strong { display: block; font-size: 1.6rem; font-family: var(--font-display); } +.table { width: 100%; border-collapse: collapse; } +.table th, .table td { text-align: left; padding: 0.55rem 0.4rem; border-bottom: 1px solid #33455f; } +.holding { + min-height: 80vh; + display: grid; + align-content: center; + gap: 1rem; + max-width: 36rem; +} +.holding h1 { font-family: var(--font-display); font-size: clamp(2rem, 5vw, 3rem); margin: 0; } +.muted { color: var(--muted); } +.error { color: var(--danger); } + +.visually-hidden { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; +} + +.site-footer { + flex-wrap: wrap; +} +.footer-meta { display: grid; gap: 0.25rem; } +.footer-meta p { margin: 0; color: var(--muted); } +.footer-links { margin: 0; } +.footer-link-btn { + background: none; + border: none; + padding: 0; + color: var(--muted); + font: inherit; + font-size: 0.9rem; + cursor: pointer; +} +.footer-link-btn:hover { + color: var(--accent); + text-decoration: underline; +} + +.cookie-consent-banner { + position: fixed; + bottom: 0; + left: 0; + right: 0; + z-index: 1100; + padding: 1rem; + background: rgba(26, 35, 50, 0.97); + border-top: 1px solid rgba(196, 165, 116, 0.35); + backdrop-filter: blur(10px); +} +.cookie-consent-content { + max-width: 960px; + margin: 0 auto; + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: space-between; + gap: 1rem; +} +.cookie-consent-text { + flex: 1 1 420px; + color: var(--muted); + font-size: 0.95rem; + margin: 0; +} +.cookie-consent-text-short { display: none; } +.cookie-consent-actions { + display: flex; + gap: 0.75rem; + flex-shrink: 0; +} + +@media (max-width: 640px) { + .cookie-consent-banner { + padding: 0.625rem 0.75rem; + padding-bottom: max(0.625rem, env(safe-area-inset-bottom)); + } + .cookie-consent-content { + flex-direction: column; + align-items: stretch; + gap: 0.625rem; + } + .cookie-consent-text-short { display: inline; } + .cookie-consent-text-full { display: none; } + .cookie-consent-text { + flex: none; + font-size: 0.8125rem; + line-height: 1.35; + } + .cookie-consent-actions { width: 100%; } + .cookie-consent-actions .btn { + flex: 1; + padding: 0.5rem 0.75rem; + font-size: 0.85rem; + } +} + +@media (max-width: 800px) { + .portal-shell { grid-template-columns: 1fr; } +} diff --git a/site/monica_site/static/css/style.css b/site/monica_site/static/css/style.css new file mode 100755 index 0000000..d6089c0 --- /dev/null +++ b/site/monica_site/static/css/style.css @@ -0,0 +1,239 @@ +@charset "UTF-8"; /* +* Trunk version 2.0.0 +*/ a:focus, button:focus { outline: none !important; } button::-moz-focus-inner { border: 0; } *:focus { outline: none; } blockquote { padding: 0; margin: 0; } input, button, select, textarea { outline: none; } label { margin-bottom: 0; } p { margin: 0; } ul, ol { list-style: none; padding: 0; margin: 0; } ul li, ol li { display: block; } dl { margin: 0; } dt, dd { line-height: inherit; } dt { font-weight: inherit; } dd { margin-bottom: 0; } cite { font-style: normal; } form { margin-bottom: 0; } blockquote { padding-left: 0; border-left: 0; } address { margin-top: 0; margin-bottom: 0; } figure { margin-bottom: 0; } html p a:hover { text-decoration: none; } /* +* +* Typography +*/ body { font-family: "Work Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; font-size: 14px; line-height: 1.7; font-weight: 300; color: #74757f; background-color: #fff; -webkit-text-size-adjust: none; -webkit-font-smoothing: subpixel-antialiased; letter-spacing: .03em; } @media (min-width: 768px) { body { font-size: 16px; line-height: 1.5; } } h1, h2, h3, h4, h5, h6, [class^='heading-'] { margin-top: 0; margin-bottom: 0; color: #151515; } h1 a:hover, h2 a:hover, h3 a:hover, h4 a:hover, h5 a:hover, h6 a:hover, [class^='heading-'] a:hover { color: #0045b6; } h1 span, h2 span, h3 span, h4 span, h5 span, h6 span, [class^='heading-'] span { display: inline-block; } h1 span[data-toggle='modal'], h2 span[data-toggle='modal'], h3 span[data-toggle='modal'], h4 span[data-toggle='modal'], h5 span[data-toggle='modal'], h6 span[data-toggle='modal'], [class^='heading-'] span[data-toggle='modal'] { border-bottom: 2px dashed rgba(0, 69, 182, 0.3); cursor: pointer; } h1 > span.icon, h2 > span.icon, h3 > span.icon, h4 > span.icon, h5 > span.icon, h6 > span.icon, [class^='heading-'] > span.icon { display: inline-block; margin-right: 5px; } h1, .heading-1 { font-size: 30px; line-height: 1.15; letter-spacing: 0; } @media (min-width: 576px) { h1, .heading-1 { font-size: 32px; } } @media (min-width: 768px) { h1, .heading-1 { font-size: 46px; line-height: 1.25; } } @media (min-width: 992px) { h1, .heading-1 { font-size: 54px; } } @media (min-width: 1200px) { h1, .heading-1 { font-size: 58px; } } @media (min-width: 1600px) { h1, .heading-1 { font-size: 90px; line-height: 0.88889; } } h2, .heading-2 { font-family: "Poppins", sans-serif; font-size: 28px; line-height: 1.4; letter-spacing: 0; } @media (min-width: 576px) { h2, .heading-2 { font-size: 30px; } } @media (min-width: 768px) { h2, .heading-2 { font-size: 44px; } } @media (min-width: 1200px) { h2, .heading-2 { font-size: 50px; line-height: 1.4; } } h3, .heading-3 { font-size: 24px; line-height: 1.3; letter-spacing: 0; } @media (min-width: 768px) { h3, .heading-3 { font-size: 30px; line-height: 1.4; } } @media (min-width: 1200px) { h3, .heading-3 { font-size: 38px; line-height: 1.21053; } } h3 .big, .heading-3 .big { font-size: 2em; line-height: 1; } h4, .heading-4 { font-size: 18px; line-height: 1.5; letter-spacing: 0; } @media (min-width: 1200px) { h4, .heading-4 { font-size: 24px; line-height: 1.33333; } } h4.h4-smaller, .heading-4.h4-smaller { font-size: 19px; } @media (min-width: 1200px) { h4.h4-smaller, .heading-4.h4-smaller { font-size: 22px; line-height: 1.45455; } } h5, .heading-5 { font-size: 16px; line-height: 1.5; letter-spacing: .01em; } @media (min-width: 768px) { h5, .heading-5 { font-size: 22px; line-height: 1.27273; } } h6, .heading-6 { font-family: "Poppins", sans-serif; font-size: 14px; line-height: 1.8; letter-spacing: .2em; text-transform: uppercase; } @media (min-width: 768px) { h6, .heading-6 { font-size: 16px; line-height: 1.5; font-weight: 700; } } .title-decorated { position: relative; padding-left: 40px; text-align: left; } .title-decorated::before { content: ''; position: absolute; left: 0; top: 12px; width: 30px; border-bottom: 1px solid; } .title-docor-text { font-size: 20px; color: #fff; } @media (min-width: 576px) { .title-docor-text { font-size: 24px; } } @media (min-width: 768px) { .title-docor-text { padding-left: 96px; font-size: 37px; } .title-docor-text::before { width: 74px; border-bottom-width: 2px; } } @media (min-width: 768px) { .title-decorated-lg { padding-left: 80px; } .title-decorated-lg::before { width: 62px; } .title-decorated-lg + p { margin-top: 32px; } } small, .small { display: block; font-size: 12px; line-height: 1.5; } mark, .mark { padding: 3px 5px; color: #fff; background: #0045b6; } strong { font-weight: 700; color: #74757f; } .big { font-size: 16px; line-height: 1.55; } @media (min-width: 768px) { .big { font-size: 20px; line-height: 1.6; } } .lead { font-size: 24px; line-height: 34px; font-weight: 300; } code { padding: 4px 5px; border-radius: 0; font-size: 90%; color: #111111; background: #f5f6fa; } .text-large { font-family: "Poppins", sans-serif; font-size: 30px; font-weight: 500; letter-spacing: 0; line-height: 1.2; text-transform: uppercase; } @media (min-width: 576px) { .text-large { font-size: 55px; } } @media (min-width: 768px) { .text-large { font-size: 68px; } } @media (min-width: 992px) { .text-large { font-size: 80px; } } @media (min-width: 1200px) { .text-large { font-size: 100px; } } .context-dark .text-large, .bg-gray-700 .text-large, .bg-gray-800 .text-large, .bg-primary .text-large, .bg-primary-darker .text-large { color: #fff; } .text-extra-large { font-size: 100px; line-height: .8; font-weight: 800; } @media (min-width: 768px) { .text-extra-large { font-size: 130px; line-height: .7; } } @media (min-width: 992px) { .text-extra-large { font-size: 150px; } } @media (min-width: 1600px) { .text-extra-large { font-size: 180px; } } .wow-outer span { display: block; } .wow-outer .wow > span { display: inline; } p [data-toggle='tooltip'] { padding-left: .25em; padding-right: .25em; color: #0045b6; } p [style*='max-width'] { display: inline-block; } p a { color: inherit; } p .text-width-1 { display: inline-block; max-width: 730px; } .text-width-2 { display: inline-block; max-width: 350px; } .heading-subtitle { font-size: 24px; margin-top: 20px; } .about-subtitle { font-size: 20px; line-height: 32px; margin-top: 35px; } html .page .text-primary { color: #0045b6; } .price-property { font-size: 30px; font-family: "Poppins", sans-serif; line-height: 1; } @media (min-width: 768px) { .price-property { font-size: 40px; } } .page .text-danger { color: #dc0000; } .page .text-gray-700 { color: #495057; } .page .text-color-1 { color: #deef7f; } .context-dark .text-gray-700, .bg-gray-700 .text-gray-700, .bg-gray-800 .text-gray-700, .bg-primary .text-gray-700, .bg-primary-darker .text-gray-700 { color: #fff; } /* +* +* Brand +*/ .brand { display: inline-block; } .brand .brand-logo-light { display: none; } .brand .brand-logo-dark { display: block; } /* +* +* Links +*/ a { transition: all 0.3s ease-in-out; } a, a:focus, a:active, a:hover { text-decoration: none; } a, a:focus, a:active { color: #0045b6; } a:hover { color: #00286a; } a[href*='tel'], a[href*='mailto'] { white-space: nowrap; } .link-hover { color: #343db0; } .link-default, .link-default:active, .link-default:focus { color: #74757f; } .link-default:hover { color: #0045b6; } .context-dark .link-default, .bg-gray-700 .link-default, .bg-gray-800 .link-default, .bg-primary .link-default, .bg-primary-darker .link-default { color: #aeb1be; } .context-dark .link-default:hover, .bg-gray-700 .link-default:hover, .bg-gray-800 .link-default:hover, .bg-primary .link-default:hover, .bg-primary-darker .link-default:hover { color: #fff; } .link-image { display: block; max-width: 75%; margin-left: auto; margin-right: auto; opacity: .45; transition: all 0.3s ease-in-out; } .link-image:hover { opacity: 1; } .privacy-link { display: inline-block; color: #4854ed; } * + .privacy-link { margin-top: 25px; } /* +* +* Blocks +*/ .block-center, .block-sm, .block-lg { margin-left: auto; margin-right: auto; } .block-sm { max-width: 560px; } .block-lg { max-width: 768px; } .block-center { padding: 10px; } .block-center:hover .block-center-header { background-color: #0045b6; } .block-center-title { background-color: #fff; } @media (max-width: 1599.98px) { .block-center { padding: 20px; } .block-center:hover .block-center-header { background-color: #fff; } .block-center-header { background-color: #0045b6; } } /* +* +* Boxes +*/ .box-minimal { display: flex; margin-left: -15px; text-align: left; } .box-minimal > * { margin-left: 15px; } .box-chloe__icon { font-size: 26px; } .box-minimal-icon .box-chloe__icon { position: relative; top: 4px; min-width: 26px; font-size: 24px; line-height: 1; color: #0045b6; } .box-minimal-icon .box-chloe__icon.fl-bigmug-line-checkmark14 { font-size: 20px; } .box-minimal-icon .box-chloe__icon .box-chloe__icon { position: relative; top: 4px; min-width: 26px; font-size: 24px; line-height: 1; } .box-minimal-text { width: 100%; max-width: 320px; margin-left: auto; margin-right: auto; } * + .box-minimal { margin-top: 30px; } * + .box-minimal-title { margin-top: 10px; } * + .box-minimal-text { margin-top: 15px; } @media (max-width: 575.98px) { .box-minimal { max-width: 360px; margin-left: auto; margin-right: auto; } } @media (max-width: 767.98px) { .box-minimal * + p { margin-top: 7px; } } @media (min-width: 768px) { .box-minimal { padding-right: 15px; margin-left: -30px; } .box-minimal > * { margin-left: 30px; } } @media (min-width: 992px) and (max-width: 1199.98px) { .box-minimal { padding-right: 0; } .box-minimal-title { font-size: 22px; } } @media (min-width: 1200px) { .box-minimal-icon, .box-chloe__icon { top: 0; font-size: 36px; } .box-minimal-icon .box-chloe__icon, .box-chloe__icon .box-chloe__icon { font-size: 32px; top: 0; } .box-minimal-icon.fl-bigmug-line-checkmark14, .box-chloe__icon.fl-bigmug-line-checkmark14 { font-size: 30px; } } .box-light { text-align: left; } .box-light-icon { font-size: 30px; line-height: 1; color: #0045b6; } * + .box-light-title { margin-top: 10px; } @media (max-width: 575.98px) { .box-light { max-width: 300px; margin-left: auto; margin-right: auto; } } @media (min-width: 768px) { .box-light { max-width: 240px; } .box-chloe__icon { font-size: 32px; } * + .box-light-title { margin-top: 16px; } } @media (min-width: 768px) and (max-width: 1199.98px) { .box-light-title { font-size: 20px; } } @media (min-width: 992px) { .box-light-icon { font-size: 32px; } * + .box-light-title { margin-top: 25px; } } @media (min-width: 1200px) { .box-light-icon { font-size: 40px; } } .box-creative { max-width: 330px; margin-left: auto; margin-right: auto; text-align: center; cursor: default; } .box-creative:hover .box-creative-icon { color: #fff; } .box-creative:hover .box-creative-icon::after { opacity: 1; visibility: visible; transform: translate3d(0, 0, 0); } .box-creative-shadow .box-creative-icon { background: #fff; border: 0; box-shadow: 0 17px 24px 0 rgba(0, 0, 0, 0.05); } .box-creative-icon { position: relative; z-index: 0; display: inline-flex; align-items: center; justify-content: center; width: 2.8em; height: 2.8em; border: 2px solid #e8e9ee; border-radius: 50%; font-size: 24px; line-height: 1; color: #0045b6; } .box-creative-icon::before { position: relative; z-index: 2; } .box-creative-icon::after { content: ''; position: absolute; top: 0; right: 0; bottom: 0; left: 0; z-index: -1; border-radius: inherit; background: #0045b6; border-color: #0045b6; pointer-events: none; opacity: 0; visibility: hidden; transition: .15s ease-in; transform: scale3d(1.05, 1.05, 1.05); } * + .box-creative-icon { margin-top: 10px; } * + .box-creative-title { margin-top: 10px; } @media (min-width: 768px) { .box-creative-icon { font-size: 30px; } * + .box-creative-title { margin-top: 15px; } } @media (max-width: 991.98px) { .box-creative * + p { margin-top: 10px; } } @media (min-width: 1200px) { .box-creative-icon { width: 2.4em; height: 2.4em; font-size: 40px; } * + .box-creative-title { margin-top: 24px; } } .box-1 { max-width: 280px; margin-left: auto; margin-right: auto; } @media (min-width: 768px) { .box-2 { padding-right: 30px; } } @media (min-width: 992px) { .box-2 { padding-right: 35px; } } @media (min-width: 1200px) { .box-2 { padding-right: 100px; } } @media (min-width: 992px) { .box-3 { padding-left: 10px; } } @media (min-width: 1200px) { .box-3 { padding-left: 70px; } } div.box-cta-1 { display: inline-table; text-align: center; vertical-align: middle; margin-bottom: -25px; margin-left: -40px; } div.box-cta-1:empty { margin-bottom: 0; margin-left: 0; } div.box-cta-1 > * { display: inline-block; margin: 0 0 25px 40px; } div.box-cta-1 > * { margin-top: 0; vertical-align: middle; } div.box-cta-thin { display: inline-table; text-align: center; vertical-align: middle; margin-bottom: -25px; margin-left: -30px; } div.box-cta-thin:empty { margin-bottom: 0; margin-left: 0; } div.box-cta-thin > * { display: inline-block; margin: 0 0 25px 30px; } div.box-cta-thin > * { margin-top: 0; vertical-align: middle; } @media (min-width: 768px) and (max-width: 991.98px) { div.box-cta-thin > * { max-width: 500px; } } .box-indigo { position: relative; padding: 5px 0 22px; max-width: 500px; } .box-indigo::before { content: ''; position: absolute; top: 0; right: 100px; bottom: 0; left: 30px; z-index: -1; background: rgba(73, 80, 87, 0.12); pointer-events: none; } .box-indigo p { line-height: 1.35; } .box-indigo-header > * { padding-left: 60px; } .box-indigo-header > * + * { margin-top: 0; } .box-indigo-mark { font-weight: 300; color: #deef7f; } .box-indigo-title, .box-indigo-subtitle { font-weight: 700; letter-spacing: 0; color: #495057; } p.box-indigo-title { position: relative; font-size: 30px; line-height: 1.15; } p.box-indigo-title::before { content: ''; position: absolute; top: 50%; left: 0; transform: translate3d(0, -50%, 0); display: inline-block; width: 37px; height: 0.05em; margin-right: 15px; vertical-align: middle; background: #0045b6; } @media (min-width: 576px) { p.box-indigo-title { font-size: 60px; } } @media (min-width: 768px) { p.box-indigo-title { font-size: 75px; line-height: 1.2; } } p.box-indigo-subtitle { font-size: 24px; line-height: 1.2; } p.box-indigo-subtitle .box-indigo-mark { font-size: 0.85em; } @media (min-width: 576px) { p.box-indigo-subtitle { font-size: 34px; } } @media (min-width: 768px) { p.box-indigo-subtitle { font-size: 40px; } } .context-dark .box-indigo::before, .bg-gray-700 .box-indigo::before, .bg-gray-800 .box-indigo::before, .bg-primary .box-indigo::before, .bg-primary-darker .box-indigo::before { background: rgba(255, 255, 255, 0.12); } .context-dark .box-indigo p, .bg-gray-700 .box-indigo p, .bg-gray-800 .box-indigo p, .bg-primary .box-indigo p, .bg-primary-darker .box-indigo p { color: #aeb1be; } .context-dark p.box-indigo-title, .bg-gray-700 p.box-indigo-title, .bg-gray-800 p.box-indigo-title, .bg-primary p.box-indigo-title, .bg-primary-darker p.box-indigo-title, .context-dark p.box-indigo-subtitle, .bg-gray-700 p.box-indigo-subtitle, .bg-gray-800 p.box-indigo-subtitle, .bg-primary p.box-indigo-subtitle, .bg-primary-darker p.box-indigo-subtitle { color: #fff; } .box-promo { position: relative; display: flex; max-width: 750px; margin-left: auto; margin-right: auto; text-align: left; box-shadow: 0 2px 24px 0 rgba(0, 0, 0, 0.15); transition: .33s ease-in-out; } .box-promo h6, .box-promo .heading-6 { color: #ff747b; } .box-promo:hover { transform: translate(0, -5px); box-shadow: 0 2px 24px 1px rgba(0, 0, 0, 0.2); } .box-promo-mark { position: absolute; top: 14%; right: 6.5%; display: flex; align-items: center; justify-content: center; line-height: 1; border-radius: 50%; color: rgba(0, 69, 182, 0.1); border: 2px solid; pointer-events: none; } .box-promo-mark::before, .box-promo-mark::after { content: ''; position: absolute; top: 50%; left: 50%; transform: translate3d(-50%, -50%, 0); background: rgba(0, 69, 182, 0.1); } .box-promo-mark::before { width: 2px; height: 50%; } .box-promo-mark::after { height: 2px; width: 50%; } .box-promo-inner, .box-promo-dummy { width: 100%; flex-shrink: 0; } .box-promo-inner { display: flex; align-items: center; padding: 30px; } .box-promo-dummy { visibility: hidden; opacity: 0; pointer-events: none; } .box-promo-dummy::before { content: ''; display: block; padding-bottom: 48.53333%; } .box-promo-content { width: 100%; max-width: 500px; } .box-promo-content * + h3, .box-promo-content * + .heading-3 { margin-top: 15px; } .box-promo-content * + p { margin-top: 10px; } .box-promo-content * + .button { margin-top: 30px; } @media (min-width: 768px) { .box-promo-inner { padding: 39px 35px; } } @media (min-width: 1200px) { .box-promo-inner { padding: 60px 70px; } .box-promo-mark { top: 24%; right: 6.5%; width: 120px; height: 120px; } } /* +* Element groups +*/ html .group { margin-bottom: -15px; margin-left: -20px; } html .group:empty { margin-bottom: 0; margin-left: 0; } html .group > * { display: inline-block; margin: 0 0 15px 20px; } @media (min-width: 992px) { html .group { margin-left: -30px; } html .group > * { margin-left: 30px; } } html .group-xs { margin-bottom: -7px; margin-left: -7px; } html .group-xs:empty { margin-bottom: 0; margin-left: 0; } html .group-xs > * { display: inline-block; margin: 0 0 7px 7px; } html .group-sm { margin-bottom: -10px; margin-left: -10px; } html .group-sm:empty { margin-bottom: 0; margin-left: 0; } html .group-sm > * { display: inline-block; margin: 0 0 10px 10px; } html .group-lg { margin-bottom: -25px; margin-left: -40px; } html .group-lg:empty { margin-bottom: 0; margin-left: 0; } html .group-lg > * { display: inline-block; margin: 0 0 25px 40px; } html .group-xl { margin-bottom: -20px; margin-left: -30px; } html .group-xl:empty { margin-bottom: 0; margin-left: 0; } html .group-xl > * { display: inline-block; margin: 0 0 20px 30px; } @media (min-width: 992px) { html .group-xl { margin-bottom: -20px; margin-left: -45px; } html .group-xl > * { margin-bottom: 20px; margin-left: 45px; } } html .group-sm-justify .button { padding-left: 20px; padding-right: 20px; } @media (min-width: 768px) { html .group-sm-justify { display: flex; align-items: center; justify-content: center; } html .group-sm-justify > * { flex-grow: 1; flex-shrink: 0; } html .group-sm-justify .button { min-width: 130px; padding-left: 20px; padding-right: 20px; } } html .group-middle { display: table; vertical-align: middle; } html .group-middle > * { vertical-align: middle; } * + .group { margin-top: 30px; } * + .group-xs { margin-top: 35px; } * + .group-sm { margin-top: 30px; } * + .group-xl { margin-top: 20px; } /* +* +* Responsive units +*/ .unit { display: flex; flex: 0 1 100%; margin-bottom: -30px; margin-left: -20px; } .unit > * { margin-bottom: 30px; margin-left: 20px; } .unit:empty { margin-bottom: 0; margin-left: 0; } .unit-body { flex: 0 1 auto; } .unit-left, .unit-right { flex: 0 0 auto; max-width: 100%; } .unit-spacing-xs { margin-bottom: -15px; margin-left: -7px; } .unit-spacing-xs > * { margin-bottom: 15px; margin-left: 7px; } /* +* +* Lists +*/ .list > li + li { margin-top: 10px; } .list-xs > li + li { margin-top: 8px; } .list-sm > li + li { margin-top: 15px; } .list-md > li + li { margin-top: 18px; } .list-lg > li + li { margin-top: 25px; } .list-xl li + li { margin-top: 30px; } @media (min-width: 1200px) { .list-xl * + p { margin-top: 18px; } } @media (min-width: 1600px) { .list-xl * + p { margin-top: 24px; } } @media (min-width: 768px) { .list-xl > li + li { margin-top: 60px; } } @media (min-width: 1600px) { .list-xl > li + li { margin-top: 78px; } } .list-inline > li { display: inline-block; } html .list-inline-md { transform: translate3d(0, -8px, 0); margin-bottom: -8px; margin-left: -10px; margin-right: -10px; } html .list-inline-md > * { margin-top: 8px; padding-left: 10px; padding-right: 10px; } @media (min-width: 992px) { html .list-inline-md { margin-left: -14px; margin-right: -14px; } html .list-inline-md > * { padding-left: 14px; padding-right: 14px; } } .list-terms dt { font-size: 20px; line-height: 1.3; font-weight: 700; letter-spacing: 0; color: #495057; } .list-terms dt + dd { margin-top: 8px; } .list-terms dd + dt { margin-top: 25px; } * + .list-terms { margin-top: 25px; } .list-terms + .privacy-link { margin-top: 20px; } @media (min-width: 768px) { .list-terms dt { font-size: 24px; } .list-terms dd + dt { margin-top: 40px; } } @media (min-width: 992px) { .list-terms dt + dd { margin-top: 18px; } .list-terms dd + dt { margin-top: 55px; } } @media (min-width: 1200px) { .list-terms dt { max-width: 85%; } } @media (min-width: 1600px) { .list-terms dd + dt { margin-top: 75px; } .list-terms + .privacy-link { margin-top: 75px; } } .index-list { counter-reset: li; } .index-list > li .list-index-counter:before { content: counter(li, decimal-leading-zero); counter-increment: li; } .list-marked { text-align: left; } .list-marked > li::before { position: relative; top: 1px; display: inline-block; min-width: 25px; content: '\f14f'; font: 400 14px 'Material Design Icons'; line-height: inherit; color: #aeb1be; } .list-marked > li + li { margin-top: 9px; } * + .list-marked { margin-top: 15px; } .list-ordered { counter-reset: li; text-align: left; } .list-ordered > li { position: relative; padding-left: 25px; } .list-ordered > li:before { content: counter(li, decimal) "."; counter-increment: li; position: absolute; top: 0; left: 0; display: inline-block; width: 15px; color: #aeb1be; } .list-ordered > li + li { margin-top: 9px; } * + .list-ordered { margin-top: 15px; } .list-nav { transform: translate3d(0, -10px, 0); margin-bottom: -10px; margin-left: -10px; margin-right: -10px; font-family: "Poppins", sans-serif; font-weight: 400; font-size: .875em; letter-spacing: .08em; text-transform: uppercase; color: #495057; } .list-nav > * { margin-top: 10px; padding-left: 10px; padding-right: 10px; } .list-nav li { display: inline-block; vertical-align: middle; } .list-nav a { color: inherit; } .list-nav a:hover { color: #0045b6; } @media (min-width: 768px) { .list-nav { margin-left: -13px; margin-right: -13px; } .list-nav > * { padding-left: 13px; padding-right: 13px; } } @media (min-width: 992px) { .list-nav { margin-left: -28px; margin-right: -28px; } .list-nav > * { padding-left: 28px; padding-right: 28px; } } .context-dark .list-nav, .bg-gray-700 .list-nav, .bg-gray-800 .list-nav, .bg-primary .list-nav, .bg-primary-darker .list-nav { color: #fff; } .context-dark .list-nav a:hover, .bg-gray-700 .list-nav a:hover, .bg-gray-800 .list-nav a:hover, .bg-primary .list-nav a:hover, .bg-primary-darker .list-nav a:hover { color: #aeb1be; } .list-inline-bordered { margin-left: -18px; margin-right: -18px; color: #495057; } .list-inline-bordered > * { padding-left: 18px; padding-right: 18px; } .list-inline-bordered * { color: inherit; } .list-inline-bordered > li > button { background: none; border: none; display: inline-block; padding: 0; outline: none; outline-offset: 0; cursor: pointer; -webkit-appearance: none; transition: .22s; } .list-inline-bordered > li > button::-moz-focus-inner { border: none; padding: 0; } .list-inline-bordered > li { display: inline-block; line-height: 24px; } .list-inline-bordered > li:not(:last-child) { border-right: 1px solid #74757f; } .context-dark .list-inline-bordered, .bg-gray-700 .list-inline-bordered, .bg-gray-800 .list-inline-bordered, .bg-primary .list-inline-bordered, .bg-primary-darker .list-inline-bordered { color: #fff; } .context-dark .list-inline-bordered button.active, .bg-gray-700 .list-inline-bordered button.active, .bg-gray-800 .list-inline-bordered button.active, .bg-primary .list-inline-bordered button.active, .bg-primary-darker .list-inline-bordered button.active, .context-dark .list-inline-bordered button:hover, .bg-gray-700 .list-inline-bordered button:hover, .bg-gray-800 .list-inline-bordered button:hover, .bg-primary .list-inline-bordered button:hover, .bg-primary-darker .list-inline-bordered button:hover { color: #aeb1be; } .list-inline-comma > li { display: inline-block; color: #0045b6; } .list-inline-comma > li:not(:last-child)::after { content: '\002C'; } .list-inline-comma a { color: inherit; } .list-inline-comma a:hover { color: #343db0; } .list-inline-comma-default > li { color: #74757f; } .list-inline-comma-default a:hover { color: #0045b6; } /* +* +* Images +*/ img { display: inline-block; max-width: 100%; height: auto; } .img-responsive { width: 100%; } /* +* +* Icons +*/ .icon { display: inline-block; font-size: 16px; line-height: 1; } .icon::before { position: relative; display: inline-block; font-weight: 400; font-style: normal; speak: none; text-transform: none; } .icon-creative { display: inline-block; text-align: center; width: 2em; height: 2em; line-height: 2em; border-radius: 50%; } .icon-creative, .icon-creative:active, .icon-creative:focus { color: #aeb1be; } .icon-creative::after { content: ''; position: absolute; top: 0; right: 0; bottom: 0; left: 0; z-index: -1; border-radius: inherit; pointer-events: none; opacity: 0; transform: scale3d(0, 0, 0); transition: .22s; } .icon-creative:hover { color: #fff; } .icon-creative:hover::after { opacity: 1; transform: scale3d(1, 1, 1); } .icon-creative[class*='facebook']::after { background: #2059a0; } .icon-creative[class*='twitter']::after { background: #00aae6; } .icon-creative[class*='instagram']::after { background: linear-gradient(to top, #f15823 0%, #a7058e 100%); } .icon-creative[class*='google']::after { background: #ff3807; } .icon-creative[class*='linkedin']::after { background: #2881a8; } .icon-modern { width: 2em; height: 2em; line-height: 2em; text-align: center; border-radius: 50%; border: 1px solid #e8e9ee; color: #151515; } .icon-circle { border-radius: 50%; } .icon-sm { font-size: 20px; } .icon-md { font-size: 1.5em; } .icon-lg { font-size: 1.875em; } /* +* +* Tables custom +*/ .table { width: 100%; max-width: 100%; text-align: left; background: #fff; border-collapse: collapse; } .table th, .table td { color: #495057; vertical-align: middle; } .table th { padding: 13px 24px; border-top: 0; font-size: 16px; font-weight: 300; } .table thead th { border-bottom: 1px solid #aeb1be; } .table td { padding: 23px 24px; } .table tbody tr:first-child td { border-top: 0; } .table tr td { border-bottom: 1px solid #e8e9ee; } .table tfoot td { font-weight: 700; } * + .table-responsive { margin-top: 30px; } @media (min-width: 768px) { * + .table-responsive { margin-top: 40px; } } .table-hover tbody tr { transition: .55s; } .table-hover tbody tr:hover { background: #f5f6fa; } .table-job-positions th, .table-job-positions td { white-space: nowrap; } .table-job-positions td:first-child { width: 8%; padding-right: 6px; text-align: center; } .table-job-positions .position { color: #0045b6; } .table-job-positions time { display: block; white-space: nowrap; } @media (max-width: 575.98px) { .table-job-positions th, .table-job-positions td { padding-left: 12px; padding-right: 12px; } .table-job-positions td { padding-top: 10px; padding-bottom: 10px; } } .table-responsive + .button-lg { margin-top: 30px; } @media (min-width: 1200px) { .table-responsive + .button-lg { margin-top: 40px; } } @media (min-width: 1600px) { .table-responsive + .button-lg { margin-top: 55px; } } /* +* +* Dividers +*/ hr { margin-top: 0; margin-bottom: 0; border-top: 1px solid #e8e9ee; } .context-dark hr, .bg-gray-700 hr, .bg-gray-800 hr, .bg-primary hr, .bg-primary-darker hr { border-top-color: #74757f; } .divider-small { font-size: 0; line-height: 0; } .divider-small::after { content: ''; display: inline-block; width: 70px; height: 2px; background-color: #0045b6; } .divider-small + h3, .divider-small + .heading-3, .divider-small-outer + h3, .divider-small-outer + .heading-3 { margin-top: 20px; } /* +* +* Buttons +*/ .button { position: relative; overflow: hidden; display: inline-block; padding: 13px 35px; font-size: 14px; line-height: 1.25; border: 2px solid; border-radius: 6px; font-family: "Poppins", sans-serif; font-weight: 500; letter-spacing: 0.1em; text-transform: uppercase; white-space: nowrap; text-overflow: ellipsis; text-align: center; cursor: pointer; vertical-align: middle; user-select: none; transition: 0.25s cubic-bezier(0.2, 1, 0.3, 1); } .form-btn { display: block; width: 100%; height: 100%; } .button-block { display: block; width: 100%; } .button-default, .button-default:focus { color: #aeb1be; background-color: #495057; border-color: #495057; } .button-default:hover, .button-default:active { color: #fff; background-color: #0045b6; border-color: #0045b6; } .button.button-primary, .button.button-primary:focus { color: #fff; background-color: #0045b6; border-color: #0045b6; } .button.button-primary:hover, .button.button-primary:active { color: #fff; background-color: #232426; border-color: #232426; } html .button.button-primary-lighten, html .button.button-primary-lighten:focus { color: #fff; background-color: #0045b6; border-color: #0045b6; } html .button.button-primary-lighten:hover, html .button.button-primary-lighten:active { color: #fff; background-color: #232426; border-color: #232426; } .button-accent-outline, .button-accent-outline:focus { color: #151515; background-color: transparent; border-color: #0045b6; } .button-accent-outline:hover, .button-accent-outline:active { color: #fff; background-color: #0045b6; border-color: #0045b6; } .button-primary-outline, .button-primary-outline:focus { color: #151515; background-color: transparent; border-color: #151515; } .button-primary-outline:hover, .button-primary-outline:active { color: #fff; background-color: #0045b6; border-color: #0045b6; } .button-gray-bordered { border-width: 1px; } .button-gray-bordered, .button-gray-bordered:focus { color: #151515; background-color: transparent; border-color: #aeb1be; } .button-gray-bordered:hover, .button-gray-bordered:active { color: #fff; background-color: #0045b6; border-color: #0045b6; } .button-primary-white, .button-primary-white:focus { color: #111111; background-color: #fff; border-color: transparent; } .button-primary-white:hover, .button-primary-white:active { color: #fff; background-color: #0045b6; border-color: #0045b6; } .button.button-facebook, .button.button-facebook:focus { color: #fff; background-color: #2059a0; border-color: #2059a0; } .button.button-facebook:hover, .button.button-facebook:active { color: #fff; background-color: #2465b5; border-color: #2465b5; } .button.button-twitter, .button.button-twitter:focus { color: #fff; background-color: #00aae6; border-color: #00aae6; } .button.button-twitter:hover, .button.button-twitter:active { color: #fff; background-color: #01bdff; border-color: #01bdff; } .button.button-google, .button.button-google:focus { color: #fff; background-color: #ff3807; border-color: #ff3807; } .button.button-google:hover, .button.button-google:active { color: #fff; background-color: #ff4c21; border-color: #ff4c21; } .button-winona { position: relative; overflow: hidden; display: inline-block; vertical-align: middle; text-align: center; } .button-winona .content-original, .button-winona .content-dubbed { vertical-align: middle; transition: transform 0.3s cubic-bezier(0.2, 1, 0.3, 1), opacity 0.3s cubic-bezier(0.2, 1, 0.3, 1); } .button-winona .content-original { display: block; } .button-winona .content-dubbed { position: absolute; width: 100%; top: 50%; left: 0; opacity: 0; transform: translate3d(0, 0, 0); } .button-winona:hover .content-original { opacity: 0; transform: translate3d(0, -30%, 0); } .button-winona:hover .content-dubbed { opacity: 1; transform: translate3d(0, 0, 0) translateY(-50%); } .button-sm { padding: 9px 32px; font-size: 12px; line-height: 1.5; } .button-lg { padding: 14px 35px; font-size: 15px; line-height: 1.5; } @media (min-width: 576px) { .button-lg { font-size: 16px; padding-left: 55px; padding-right: 55px; } } .button-lg--smaller { padding-left: 35px; padding-right: 35px; } html .button.button-icon { display: inline-flex; } .button-icon { justify-content: center; align-items: center; vertical-align: middle; } .button-icon .icon { position: relative; top: -1px; display: inline-block; vertical-align: middle; color: inherit; font-size: 1.55em; line-height: 1em; transition: none; } .button-icon:hover .icon { color: inherit; } .button-icon-left .icon { padding-right: 5px; } .button-icon-right { flex-direction: row-reverse; } .button-icon-right .icon { padding-left: 8px; } .button-icon.button-icon-only { padding-left: 20px; padding-right: 20px; } .button-icon.button-lg .icon { padding-right: 8px; } .button-icon.button-lg.button-icon-right .icon { padding: 0 0 0 8px; } .button-icon.button-sm .icon { padding-right: 3px; font-size: 1.33em; } .button-icon.button-sm.button-icon-right .icon { padding: 0 0 0 3px; } .btn-primary { border-radius: 3px; font-family: "Poppins", sans-serif; font-weight: 500; letter-spacing: .05em; text-transform: uppercase; transition: .33s; } .btn-primary, .btn-primary:active, .btn-primary:focus { color: #fff; background: #0045b6; border-color: #0045b6; } .btn-primary:hover { color: #fff; background: #000; border-color: #000; } .button-group { display: flex; } .button-group .button { margin: 0; flex-grow: 1; } .button-group .button:not(:first-child) { border-top-left-radius: 0; border-bottom-left-radius: 0; } .button-group .button:not(:last-child) { border-top-right-radius: 0; border-bottom-right-radius: 0; } .button-video { position: relative; display: block; border-radius: 50%; transition: .33s; color: #495057; } .button-video::before, .button-video:after { display: block; content: '\f4fc'; font-family: 'Material Design Icons'; font-size: 64px; line-height: 1; border-radius: 50%; } .button-video::before { z-index: -1; pointer-events: none; } .button-video::after { position: absolute; width: 100%; top: 50%; left: 0; opacity: 0; transform: translate3d(0, -50%, 0); transition: opacity 0.3s cubic-bezier(0.2, 1, 0.3, 1); } .button-video:hover { color: #0045b6; } .button-video:hover::before { opacity: 0; transition: transform 0.9s cubic-bezier(0.2, 1, 0.3, 1), opacity 0.3s cubic-bezier(0.2, 1, 0.3, 1); transform: scale3d(1.5, 1.5, 1.5); } .button-video:hover::after { opacity: 1; } .button-video-lg::before, .button-video-lg:after { font-size: 90px; } .context-dark .button-video, .bg-gray-700 .button-video, .bg-gray-800 .button-video, .bg-primary .button-video, .bg-primary-darker .button-video { color: #fff; } /* +* +* Form styles +*/ .rd-form { position: relative; text-align: left; } .rd-form .button { min-height: 48px; } .rd-form .button-icon-only { padding-top: 11px; padding-bottom: 11px; } * + .rd-form { margin-top: 20px; } input:-webkit-autofill ~ label, input:-webkit-autofill ~ .form-validation { color: #000 !important; } .form-wrap { position: relative; } .form-wrap.has-error .form-input { border-color: #dc0000; } .form-wrap.has-focus .form-input { border-color: #0045b6; } * + .form-wrap { margin-top: 24px; } .form-input { display: block; width: 100%; min-height: 48px; padding: 11px 19px; font-size: 16px; font-weight: 400; line-height: 1.5; color: #495057; background-color: #fff; background-image: none; border-radius: 6px; -webkit-appearance: none; transition: .3s ease-in-out; font-family: "Work Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; border: 1px solid #c5c6cd; } .form-input:focus { outline: 0; } textarea.form-input { height: 150px; min-height: 48px; max-height: 255px; resize: vertical; } .form-label, .form-label-outside { margin-bottom: 0; font-weight: 300; } .form-label { position: absolute; top: 24px; left: 0; right: 0; padding-left: 19px; padding-right: 19px; font-size: 16px; font-weight: 400; line-height: 1.5; color: #495057; pointer-events: none; text-align: left; z-index: 9; transition: .25s; will-change: transform; transform: translateY(-50%); } .form-label.focus { opacity: 0; } .form-label.auto-fill { color: #495057; } .form-label-outside { width: 100%; padding-right: 150px; margin-bottom: 4px; font-size: 14px; color: #aeb1be; cursor: pointer; } .form-label-outside ~ .form-validation { top: 10px; } [data-x-mode='true'] .form-label { pointer-events: auto; } .form-validation { position: absolute; right: 15px; top: 0; z-index: 11; margin-top: 3px; font-size: 10px; font-weight: 500; line-height: 12px; letter-spacing: 0; color: #dc0000; transition: .3s; transform: scale3d(1, 1, 1); } .form-validation:empty { transform: scale3d(1, 0, 1); } .form-validation-left .form-validation { top: 2px; bottom: auto; right: auto; left: 14px; } #form-output-global { position: fixed; bottom: 30px; left: 15px; z-index: 2000; visibility: hidden; transform: translate3d(-500px, 0, 0); transition: .3s all ease; } #form-output-global.active { visibility: visible; transform: translate3d(0, 0, 0); } @media (min-width: 576px) { #form-output-global { left: 30px; } } .form-output { position: absolute; top: 100%; left: 0; font-size: 10px; font-weight: 400; line-height: 1.2; margin-top: 2px; transition: .3s; opacity: 0; visibility: hidden; } .form-output.active { opacity: 1; visibility: visible; } .form-output.error { color: #dc0000; } .form-output.success { color: #98bf44; } .radio .radio-custom, .radio-inline .radio-custom, .checkbox .checkbox-custom, .checkbox-inline .checkbox-custom { opacity: 0; } .radio .radio-custom, .radio .radio-custom-dummy, .radio-inline .radio-custom, .radio-inline .radio-custom-dummy, .checkbox .checkbox-custom, .checkbox .checkbox-custom-dummy, .checkbox-inline .checkbox-custom, .checkbox-inline .checkbox-custom-dummy { position: absolute; left: 0; width: 14px; height: 14px; outline: none; cursor: pointer; } .radio .radio-custom-dummy, .radio-inline .radio-custom-dummy, .checkbox .checkbox-custom-dummy, .checkbox-inline .checkbox-custom-dummy { pointer-events: none; background: #fff; } .radio .radio-custom-dummy::after, .radio-inline .radio-custom-dummy::after, .checkbox .checkbox-custom-dummy::after, .checkbox-inline .checkbox-custom-dummy::after { position: absolute; opacity: 0; transition: .22s; } .radio .radio-custom:focus, .radio-inline .radio-custom:focus, .checkbox .checkbox-custom:focus, .checkbox-inline .checkbox-custom:focus { outline: none; } .radio input, .radio-inline input, .checkbox input, .checkbox-inline input { position: absolute; width: 1px; height: 1px; padding: 0; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; clip-path: inset(50%); border: 0; } .radio-custom:checked + .radio-custom-dummy:after, .checkbox-custom:checked + .checkbox-custom-dummy:after { opacity: 1; } .radio, .radio-inline { padding-left: 28px; } .radio .radio-custom-dummy, .radio-inline .radio-custom-dummy { top: 1px; left: 0; width: 18px; height: 18px; border-radius: 50%; border: 1px solid #74757f; } .radio .radio-custom-dummy::after, .radio-inline .radio-custom-dummy::after { content: ''; top: 3px; right: 3px; bottom: 3px; left: 3px; background: #495057; border-radius: inherit; } .checkbox, .checkbox-inline { padding-left: 28px; } .checkbox .checkbox-custom-dummy, .checkbox-inline .checkbox-custom-dummy { left: 0; width: 18px; height: 18px; margin: 0; border: 1px solid #74757f; } .checkbox .checkbox-custom-dummy::after, .checkbox-inline .checkbox-custom-dummy::after { content: '\f222'; font-family: "Material Design Icons"; position: absolute; top: -1px; left: -2px; font-size: 20px; line-height: 18px; color: #aeb1be; } .toggle-custom { padding-left: 60px; -webkit-appearance: none; } .toggle-custom:checked ~ .checkbox-custom-dummy::after { background: #0045b6; transform: translate(20px, -50%); } .toggle-custom ~ .checkbox-custom-dummy { position: relative; display: inline-block; margin-top: -1px; width: 44px; height: 20px; background: #fff; cursor: pointer; } .toggle-custom ~ .checkbox-custom-dummy::after { content: ''; position: absolute; display: inline-block; width: 16px; height: 16px; left: 0; top: 50%; background: #74757f; transform: translate(4px, -50%); opacity: 1; transition: .22s; } .form-inline { display: flex; flex-wrap: wrap; align-items: stretch; text-align: center; margin-bottom: -8px; } .form-inline > * { margin-bottom: 8px; } .form-inline > * { margin-top: 0; margin-right: -6px; margin-left: -6px; } .form-inline .form-wrap { flex: 20 0; min-width: 220px; } .form-inline .form-wrap-select { text-align: left; } .form-inline .form-label { display: block; } .form-inline .form-input { border-color: #fff; } .form-inline .form-button { flex-grow: 1; min-height: 48px; } .form-inline .form-button .button { width: 100%; min-height: inherit; } @media (min-width: 576px) { .form-inline .button { display: block; } } .form-inline.form-inline-centered { justify-content: center; } .form-sm .form-input, .form-sm .button { padding-top: 9px; padding-bottom: 9px; min-height: 40px; } .form-sm .form-validation { top: -16px; } .form-sm .form-label { top: 20px; } .form-sm * + .button { margin-top: 10px; } .form-lg .form-input, .form-lg .select2-container--default .select2-selection--single .select2-selection__rendered { padding-top: 15px; padding-bottom: 15px; } .form-lg .form-input, .form-lg .select2-container--default .select2-selection--single .select2-selection__rendered, .form-lg .form-wrap-button { min-height: 56px; } .form-lg .form-label { top: 28px; } .form-layout-1 { margin-bottom: -20px; margin-left: -20px; } .form-layout-1:empty { margin-bottom: 0; margin-left: 0; } .form-layout-1 > * { display: inline-block; margin: 0 0 20px 20px; } .form-layout-1 > * { display: block; } .form-layout-1 .form-wrap-button { display: flex; } .form-layout-1 .button { display: block; width: 100%; } * + form.form-layout-1 { margin-top: 35px; } @media (min-width: 768px) { .form-layout-1 { display: flex; } .form-layout-1 > * { flex-shrink: 0; } .form-layout-1 .form-wrap-main { flex-grow: 1; } .form-layout-1 .form-wrap-select { width: 100%; max-width: 220px; } } @media (min-width: 992px) { * + form.form-layout-1 { margin-top: 50px; } } .schedule-form { margin-top: 0; } @media (min-width: 1200px) { .schedule-form { margin-top: 35px; } } .schedule-form .form-wrap { margin-top: 12px; } .schedule-form .form-wrap input { background-color: #f5f6fa; border-color: #e8e9ee; } .schedule-form .form-wrap input ::placeholder { color: #151515; opacity: 1; } .schedule-form .form-wrap input ::placeholder :-ms-input-placeholder { color: #151515; } /* +* +* Posts +*/ .post-inline { max-width: 313px; } .post-inline-title a { color: inherit; } .post-inline-meta { transform: translate3d(0, -5px, 0); margin-bottom: -5px; margin-left: -10px; margin-right: -10px; color: rgba(255, 255, 255, 0.3); } .post-inline-meta > * { margin-top: 5px; padding-left: 10px; padding-right: 10px; } .post-inline-meta > li { position: relative; display: inline-block; } .post-inline-meta > li:not(:last-child)::after { content: ''; position: absolute; right: 0; top: 50%; width: 1px; height: 1px; border-radius: 50%; background: #495057; } .post-inline-meta a { color: inherit; } * + .post-inline { margin-top: 15px; } * + .post-inline-meta { margin-top: 5px; } @media (min-width: 768px) { * + .post-inline { margin-top: 22px; } } .context-dark .post-inline-title, .bg-gray-700 .post-inline-title, .bg-gray-800 .post-inline-title, .bg-primary .post-inline-title, .bg-primary-darker .post-inline-title { color: #fff; } .context-dark .post-inline-title a:hover, .bg-gray-700 .post-inline-title a:hover, .bg-gray-800 .post-inline-title a:hover, .bg-primary .post-inline-title a:hover, .bg-primary-darker .post-inline-title a:hover { color: #aeb1be; } .context-dark .post-inline-meta > li:not(:last-child)::after, .bg-gray-700 .post-inline-meta > li:not(:last-child)::after, .bg-gray-800 .post-inline-meta > li:not(:last-child)::after, .bg-primary .post-inline-meta > li:not(:last-child)::after, .bg-primary-darker .post-inline-meta > li:not(:last-child)::after { background: #aeb1be; } .context-dark .post-inline-meta a:hover, .bg-gray-700 .post-inline-meta a:hover, .bg-gray-800 .post-inline-meta a:hover, .bg-primary .post-inline-meta a:hover, .bg-primary-darker .post-inline-meta a:hover { color: #fff; } .post-classic { text-align: left; } .post-classic-media { position: relative; display: block; } .post-classic-media::before { content: ''; position: absolute; top: 0; right: 0; bottom: 0; left: 0; background: rgba(73, 80, 87, 0.4); transition: .4s ease-in-out; opacity: 0; visibility: hidden; pointer-events: none; } .post-classic-media img { width: 100%; } .post-classic-media:hover::before { opacity: 1; visibility: visible; } .post-classic-meta { transform: translate3d(0, -3px, 0); margin-bottom: -3px; margin-left: -14px; margin-right: -14px; color: #aeb1be; } .post-classic-meta > * { margin-top: 3px; padding-left: 14px; padding-right: 14px; } .post-classic-meta > li { position: relative; display: inline-block; } .post-classic-meta > li:not(:last-child)::after { content: ''; position: absolute; right: 0; top: 50%; width: 2px; height: 2px; background: #aeb1be; transform: translate3d(0, -50%, 0); } .post-classic-meta a:hover { color: #343db0; } * + .post-classic-meta { margin-top: 15px; } * + .post-classic-title { margin-top: 9px; } @media (max-width: 575.98px) { .post-classic { max-width: 360px; margin-left: auto; margin-right: auto; } } @media (min-width: 576px) and (max-width: 767.98px) { .post-classic-title { font-size: 17px; } } @media (min-width: 768px) { .post-classic-meta { transform: translate3d(0, -5px, 0); margin-bottom: -5px; margin-left: -20px; margin-right: -20px; } .post-classic-meta > * { margin-top: 5px; padding-left: 20px; padding-right: 20px; } * + .post-classic-meta { margin-top: 25px; } } @media (min-width: 992px) and (max-width: 1199.98px) { .post-classic-meta { margin-left: -15px; margin-right: -15px; font-size: 14px; } .post-classic-meta > * { padding-left: 15px; padding-right: 15px; } } .post-modern { text-align: left; } .post-modern-title { color: #151515; font-weight: 400; } .post-modern-title a:hover { color: #0045b6; } .post-modern-media { position: relative; display: block; } .post-modern-media::before { content: ''; position: absolute; top: 0; right: 0; bottom: 0; left: 0; background: rgba(73, 80, 87, 0.4); transition: .22s ease-in-out; opacity: 0; visibility: hidden; pointer-events: none; } .post-modern-media img { width: 100%; } .post-modern-media:hover::before { opacity: 1; visibility: visible; } .post-modern-meta { transform: translate3d(0, -5px, 0); margin-bottom: -5px; margin-left: -15px; margin-right: -15px; color: #495057; font-weight: 500; } .post-modern-meta > * { margin-top: 5px; padding-left: 15px; padding-right: 15px; } .post-modern-meta > li { position: relative; display: inline-block; line-height: 24px; font-size: 18px; } .post-modern-meta > li:not(:last-child)::after { content: ''; position: absolute; right: 0; top: 50%; width: 1px; height: 1.5em; background: #e8e9ee; transform: translate3d(0, -50%, 0); } .post-modern-meta a:hover { color: #343db0; } .post-modern-meta + p { color: #151515; } * + .post-modern-title { margin-top: 9px; } * + .post-modern-meta { margin-top: 10px; } @media (max-width: 575.98px) { .post-modern { max-width: 430px; margin-left: auto; margin-right: auto; } } @media (min-width: 768px) { .post-modern-meta { transform: translate3d(0, -5px, 0); margin-bottom: -5px; margin-left: -20px; margin-right: -20px; } .post-modern-meta > * { margin-top: 5px; padding-left: 20px; padding-right: 20px; } * + .post-modern-title { margin-top: 15px; } } @media (min-width: 992px) and (max-width: 1199.98px) { .post-modern-meta { margin-left: -15px; margin-right: -15px; font-size: 14px; } .post-modern-meta > * { padding-left: 15px; padding-right: 15px; } } @media (min-width: 1200px) { * + .post-modern-title { margin-top: 25px; } * + .post-modern-meta { margin-top: 15px; } } .post-block { position: relative; z-index: 1; display: flex; overflow: hidden; max-width: 640px; border-radius: 6px; text-align: left; } .post-block::before { content: ''; position: absolute; top: 0; right: 0; bottom: 0; left: 0; z-index: 1; opacity: .3; background-image: linear-gradient(to bottom, rgba(0, 0, 0, 0) 0%, #000 100%); } .post-block::after { content: ''; position: absolute; top: 0; right: 0; bottom: 0; left: 0; pointer-events: none; border-radius: inherit; background: rgba(0, 0, 0, 0.5); z-index: 1; opacity: 0; visibility: hidden; transition: 1s ease-in-out; } .post-block:hover::after { opacity: 1; visibility: visible; } .post-block > * { width: 100%; flex-shrink: 0; } .post-block-dummy { width: 100%; } .post-block-dummy::before { content: ''; display: block; visibility: hidden; pointer-events: none; padding-bottom: 71.58556%; } .post-block-image { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); min-height: 100%; min-width: 100%; width: auto; height: auto; max-width: none; } @supports (object-fit: cover) { .post-block-image { left: 0; top: 0; width: 100%; height: 100%; transform: none; object-fit: cover; object-position: center center; } } .post-block-caption { position: relative; z-index: 2; display: flex; flex-direction: column; justify-content: flex-end; padding: 20px; } .post-block-meta { transform: translate3d(0, -5px, 0); margin-bottom: -5px; margin-left: -20px; margin-right: -20px; color: #495057; } .post-block-meta > * { margin-top: 5px; padding-left: 20px; padding-right: 20px; } .post-block-meta > li { position: relative; display: inline-block; line-height: 24px; } .post-block-meta > li:not(:last-child)::after { content: ''; position: absolute; right: 0; top: 50%; width: 1px; height: 1.5em; background: #e8e9ee; transform: translate3d(0, -50%, 0); } .post-block-meta a { color: inherit; } .post-block-meta a:hover { color: #0045b6; } .post-block-meta time { display: block; } .context-dark .post-block-meta, .bg-gray-700 .post-block-meta, .bg-gray-800 .post-block-meta, .bg-primary .post-block-meta, .bg-primary-darker .post-block-meta { color: #aeb1be; } .context-dark .post-block-meta a:hover, .bg-gray-700 .post-block-meta a:hover, .bg-gray-800 .post-block-meta a:hover, .bg-primary .post-block-meta a:hover, .bg-primary-darker .post-block-meta a:hover { color: #fff; } .context-dark .post-block-meta > li:not(:last-child)::after, .bg-gray-700 .post-block-meta > li:not(:last-child)::after, .bg-gray-800 .post-block-meta > li:not(:last-child)::after, .bg-primary .post-block-meta > li:not(:last-child)::after, .bg-primary-darker .post-block-meta > li:not(:last-child)::after { background: #aeb1be; } .context-dark .post-block-title a:hover, .bg-gray-700 .post-block-title a:hover, .bg-gray-800 .post-block-title a:hover, .bg-primary .post-block-title a:hover, .bg-primary-darker .post-block-title a:hover { color: #aeb1be; } @media (max-width: 767.98px) { .post-block { max-width: 420px; margin-left: auto; margin-right: auto; } } @media (min-width: 768px) { .post-block-meta { margin-left: -25px; margin-right: -25px; } .post-block-meta > * { padding-left: 25px; padding-right: 25px; } .post-block-caption { padding: 35px 40px; } } .post-light { display: flex; align-items: flex-start; max-width: 450px; margin-left: -20px; text-align: left; } .post-light > * { margin-left: 20px; } .post-light:hover .post-light-time { background: #0045b6; } .post-light-time { position: relative; overflow: hidden; display: flex; flex-direction: column; align-items: center; justify-content: center; flex-shrink: 0; min-width: 100px; width: 100px; padding: 20px; color: #fff; background: #aeb1be; border-radius: 6px; transition: .22s ease-in-out; } .post-light-time > * { line-height: 1; } .post-light-time > * + * { margin-top: 3px; } .post-light-time-big { font-size: 2.25em; font-weight: 700; } a.post-tiny-media { display: block; } .post-light-media { position: relative; } .post-light-media::before { content: ''; position: absolute; top: 0; right: 0; bottom: 0; left: 0; background: rgba(73, 80, 87, 0.1); transition: .22s ease-in-out; opacity: 0; visibility: hidden; pointer-events: none; } .post-light-media img { width: 100%; } .post-light-main { flex-grow: 1; } .post-light-title { color: #0045b6; } .post-light-title a:hover { color: #343db0; } .post-light-meta { transform: translate3d(0, -5px, 0); margin-bottom: -5px; margin-left: -20px; margin-right: -20px; color: #495057; } .post-light-meta > * { margin-top: 5px; padding-left: 20px; padding-right: 20px; } .post-light-meta > li { position: relative; display: inline-block; line-height: 24px; } .post-light-meta > li:not(:last-child)::after { content: ''; position: absolute; right: 0; top: 50%; width: 1px; height: 1.5em; background: #e8e9ee; transform: translate3d(0, -50%, 0); } .post-light-meta a { color: inherit; } .post-light-meta a:hover { color: #0045b6; } * + .post-light { margin-top: 30px; } * + .post-light-meta { margin-top: 9px; } @media (min-width: 768px) { .post-light { margin-left: -36px; } .post-light > * { margin-left: 36px; } } @media (min-width: 992px) { * + .post-light { margin-top: 38px; } } .post-tiny { text-align: left; } a.post-tiny-media { position: relative; display: block; } a.post-tiny-media::before { content: ''; position: absolute; top: 0; right: 0; bottom: 0; left: 0; background: rgba(73, 80, 87, 0.3); transition: .22s ease-in-out; opacity: 0; visibility: hidden; pointer-events: none; } a.post-tiny-media img { width: 100%; } a.post-tiny-media:hover::before { opacity: 1; visibility: visible; } .post-tiny-title { color: #0045b6; } .post-tiny-title a:hover { color: #343db0; } .post-tiny-time { display: block; color: #aeb1be; } * + .post-tiny-time { margin-top: 10px; } @media (max-width: 575.98px) { .post-tiny { max-width: 360px; margin-left: auto; margin-right: auto; } } .post-creative > img { width: 100%; } .post-creative > * + img { margin-top: 25px; } .post-creative > img + * { margin-top: 25px; } .post-creative * + h4, .post-creative * + .heading-4 { margin-top: 15px; } .post-creative-title { font-weight: 700; } .post-creative-meta { transform: translate3d(0, -5px, 0); margin-bottom: -5px; margin-left: -15px; margin-right: -15px; } .post-creative-meta > * { margin-top: 5px; padding-left: 15px; padding-right: 15px; } .post-creative-meta > li, .post-creative-meta > li > * { display: inline-block; vertical-align: middle; } .post-creative-meta > li > * + * { margin-left: 5px; } .post-creative-meta a { color: inherit; } .post-creative-meta a:hover { color: #0045b6; } .post-creative-meta .icon { font-size: 1.5em; color: #0045b6; } .post-creative-footer { transform: translate3d(0, -10px, 0); margin-bottom: -10px; margin-left: -15px; margin-right: -15px; } .post-creative-footer > * { margin-top: 10px; padding-left: 15px; padding-right: 15px; } .post-creative-footer > * { display: inline-block; vertical-align: middle; } * + .post-creative-meta { margin-top: 15px; } * + .post-creative-footer { margin-top: 25px; } @media (min-width: 768px) { .post-creative-title { max-width: 80%; } } @media (min-width: 992px) { * + .post-creative-meta { margin-top: 25px; } .post-creative + [class*='section'] { margin-top: 30px; } } @media (min-width: 1200px) { .post-creative h4, .post-creative .heading-4 { font-size: 22px; line-height: 1.46; } .post-creative > * + img { margin-top: 30px; } .post-creative > img + * { margin-top: 40px; } .post-creative * + h4, .post-creative * + .heading-4 { margin-top: 32px; } .post-creative > .quote-light + img { margin-top: 50px; } * + .post-creative-meta { margin-top: 30px; } * + .post-creative-footer { margin-top: 40px; } } @media (min-width: 768px) { .post-creative { font-size: 20px; } .post-creative h4, .post-creative .heading-4 { font-size: 20px; line-height: 1.56; } } @media (min-width: 1600px) { .post-creative + [class*='section'] { margin-top: 55px; } } /* +* +* Quotes +*/ .quote-light-mark { fill: #aeb1be; } .quote-light-cite { font-size: 20px; color: #0045b6; } .quote-light-caption { line-height: 1.2; } .quote-light-text { font-size: 16px; line-height: 1.5; letter-spacing: 0; color: #495057; } html img.quote-light-image { display: inline-block; } * + .quote-light { margin-top: 30px; } * + .quote-light-caption { margin-top: 3px; } * + .quote-light-mark { margin-top: 15px; } * + .quote-light-text { margin-top: 8px; } * + .quote-light-footer { margin-top: 20px; } @media (min-width: 576px) { .quote-light-cite { font-size: 20px; } } @media (min-width: 768px) { .quote-light-cite { font-size: 24px; } .quote-light-text { font-size: 21px; line-height: 1.45; } * + .quote-light { margin-top: 40px; } * + .quote-light-mark { margin-top: 35px; } * + .quote-light-text { margin-top: 15px; } * + .quote-light-footer { margin-top: 30px; } } @media (min-width: 1600px) { .quote-light-text { font-size: 24px; line-height: 1.33333; } * + .quote-light { margin-top: 50px; } * + .quote-light-mark { margin-top: 50px; } * + .quote-light-footer { margin-top: 45px; } } .quote-classic { text-align: left; } .quote-classic-meta { display: flex; align-items: center; flex-wrap: wrap; margin-bottom: -20px; margin-left: -20px; } .quote-classic-meta:empty { margin-bottom: 0; margin-left: 0; } .quote-classic-meta > * { display: inline-block; margin: 0 0 20px 20px; } .quote-classic-avatar { position: relative; overflow: hidden; width: 50px; height: 50px; border-radius: 50%; flex-shrink: 0; } .quote-classic-avatar img { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); min-height: 100%; min-width: 100%; width: auto; height: auto; max-width: none; } @supports (object-fit: cover) { .quote-classic-avatar img { left: 0; top: 0; width: 100%; height: 100%; transform: none; object-fit: cover; object-position: center center; } } .quote-classic-cite { font-size: 24px; line-height: 1.2; color: #151515; font-family: "Poppins", sans-serif; } .quote-classic-caption { color: #151515; } * + .quote-classic-text { margin-top: 25px; } * + .quote-classic-caption { margin-top: 0; } @media (max-width: 575.98px) { .quote-classic { margin-left: auto; margin-right: auto; } } .quote-modern { text-align: left; } .quote-modern-meta { display: flex; align-items: center; flex-wrap: wrap; margin-bottom: -20px; margin-left: -20px; } .quote-modern-meta:empty { margin-bottom: 0; margin-left: 0; } .quote-modern-meta > * { display: inline-block; margin: 0 0 20px 20px; } .quote-modern-avatar { position: relative; overflow: hidden; z-index: 2; width: 50px; height: 50px; border-radius: 50%; flex-shrink: 0; } .quote-modern-avatar img { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); min-height: 100%; min-width: 100%; width: auto; height: auto; max-width: none; } @supports (object-fit: cover) { .quote-modern-avatar img { left: 0; top: 0; width: 100%; height: 100%; transform: none; object-fit: cover; object-position: center center; } } .quote-modern-cite { font-size: 20px; line-height: 1.2; color: #0045b6; } .quote-modern-caption { color: #aeb1be; } .quote-modern-mark { fill: #aeb1be; } * + .quote-modern-meta { margin-top: 25px; } * + .quote-modern-caption { margin-top: 0; } * + .quote-modern-text { margin-top: 10px; } .quote-modern-big .quote-modern-text { font-size: 18px; line-height: 1.5; color: #495057; } @media (min-width: 992px) { .quote-modern-big .quote-modern-text { font-size: 20px; } } @media (min-width: 1200px) { .quote-modern-big .quote-modern-text { font-size: 24px; line-height: 1.33333; } .quote-modern-big * + .quote-modern-text { margin-top: 20px; } } /* +* +* Thumbnails +*/ .video-overlay { position: absolute; top: 0; right: 0; bottom: 0; left: 0; background-position: center center; background-size: cover; cursor: pointer; } .figure-light figcaption { padding-top: 15px; color: #74757f; } * + .figure-light { margin-top: 20px; } @media (min-width: 1200px) { * + .figure-light { margin-top: 40px; } } .figure-card { position: relative; overflow: hidden; display: flex; } .figure-card .figure-card-sizer { display: block; width: 100%; } .figure-card .figure-card-sizer::before { display: block; content: ''; visibility: hidden; padding-bottom: 56.25%; } .figure-card img { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); min-height: 100%; min-width: 100%; width: auto; height: auto; max-width: none; } @supports (object-fit: cover) { .figure-card img { height: 100%; width: 100%; left: 0; top: 0; transform: none; object-fit: cover; object-position: center center; } } .thumbnail-video-1 { position: relative; text-align: center; } .thumbnail-video-1 .embed-responsive::before { min-height: 250px; } .thumbnail-video-1 .video-overlay { display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 20px; z-index: 1; background-position: 80% center; } .thumbnail-video-1 .video-overlay::after { content: ''; position: absolute; top: 0; right: 0; bottom: 0; left: 0; background: rgba(0, 0, 0, 0.4); z-index: -1; } .thumbnail-video-1 * + .button-video { margin-top: 25px; } .thumbnail-minimal { position: relative; overflow: hidden; display: block; } .thumbnail-minimal::before { content: ''; display: block; padding-bottom: 100%; } .thumbnail-minimal-image { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); min-height: 100%; min-width: 100%; width: auto; height: auto; max-width: none; } @supports (object-fit: cover) { .thumbnail-minimal-image { left: 0; top: 0; width: 100%; height: 100%; transform: none; object-fit: cover; object-position: center center; } } .thumbnail-minimal-caption { position: absolute; top: 0; right: 0; bottom: 0; left: 0; z-index: 1; background: rgba(0, 69, 182, 0.6); display: flex; align-items: center; justify-content: center; opacity: 0; visibility: hidden; transition: all 0.3s ease-in-out; } .thumbnail-minimal-caption::before { content: '\f504'; font-family: 'Material Design Icons'; font-size: 30px; line-height: 1; color: #fff; } .thumbnail-minimal:hover .thumbnail-minimal-caption { opacity: 1; visibility: visible; } .thumbnail-classic { position: relative; overflow: hidden; display: flex; text-align: left; } .thumbnail-classic * { color: inherit; } .thumbnail-classic, .thumbnail-classic:active, .thumbnail-classic:focus, .thumbnail-classic:hover { color: #fff; } .thumbnail-classic:hover { color: #fff; } .thumbnail-classic > * { width: 100%; flex-shrink: 0; } .thumbnail-classic-sm .thumbnail-classic-dummy::before { padding-bottom: 69.18919%; } .thumbnail-classic-dummy { width: 100%; } .thumbnail-classic-dummy::before { content: ''; display: block; visibility: hidden; pointer-events: none; padding-bottom: 73.84615%; } .thumbnail-classic-image { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); min-height: 100%; min-width: 100%; width: auto; height: auto; max-width: none; } @supports (object-fit: cover) { .thumbnail-classic-image { left: 0; top: 0; width: 100%; height: 100%; transform: none; object-fit: cover; object-position: center center; } } .thumbnail-classic-caption { position: relative; z-index: 1; display: flex; flex-direction: column; justify-content: flex-end; width: 100%; padding: 20px; background: rgba(73, 80, 87, 0.4); font-size: 14px; } .thumbnail-classic-caption * + p { margin-top: 5px; } .thumbnail-classic-title { font-size: 20px; line-height: 1.3; } @media (max-width: 575.98px) { .thumbnail-classic { max-width: 360px; margin-left: auto; margin-right: auto; } } @media (min-width: 768px) { .thumbnail-classic-caption { padding: 25px 20px; } } @media (min-width: 992px) { .thumbnail-classic-lg .thumbnail-classic-dummy::before { padding-bottom: 147.69231%; } } @media (min-width: 1200px) { .thumbnail-classic-caption { padding: 30px 40px; font-size: 16px; } .thumbnail-classic-caption * + p { margin-top: 8px; } .thumbnail-classic-title { font-size: 24px; } } html:not(.tablet):not(.mobile) .thumbnail-classic-caption { opacity: 0; visibility: hidden; transition: .3s all linear; background: rgba(73, 80, 87, 0.6); } html:not(.tablet):not(.mobile) .thumbnail-classic:hover .thumbnail-classic-caption { opacity: 1; visibility: visible; } html:not(.tablet):not(.mobile) .hoverdir-item .thumbnail-classic-caption { opacity: 1; visibility: visible; transform: translate(-100%, 0); } html:not(.tablet):not(.mobile)[data-x-mode='true'] .hoverdir-item .thumbnail-classic-caption { opacity: 0; visibility: hidden; transform: none; } html:not(.tablet):not(.mobile)[data-x-mode='true'] .thumbnail-classic:hover .thumbnail-classic-caption { opacity: 1; visibility: visible; } .thumbnail-corporate { position: relative; overflow: hidden; display: flex; color: #fff; text-align: left; } .thumbnail-corporate * { color: inherit; } .thumbnail-corporate:hover { color: #fff; } .thumbnail-corporate > * { width: 100%; flex-shrink: 0; } .thumbnail-corporate-light { text-align: center; } .thumbnail-corporate-light .thumbnail-corporate-caption { display: flex; flex-direction: column; align-items: center; justify-content: center; } .thumbnail-corporate-light .thumbnail-corporate-caption > * { max-width: 180px; } .thumbnail-corporate-dummy { width: 100%; visibility: hidden; pointer-events: none; } .thumbnail-corporate-dummy::before { content: ''; display: block; width: 0; padding-bottom: 69.18919%; } .thumbnail-corporate-image { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); min-height: 100%; min-width: 100%; width: auto; height: auto; max-width: none; } @supports (object-fit: cover) { .thumbnail-corporate-image { left: 0; top: 0; transform: none; object-fit: cover; width: 100%; height: 100%; object-position: center center; } } .thumbnail-corporate-caption { position: relative; z-index: 1; display: flex; flex-direction: column; width: 100%; padding: 20px; background: rgba(73, 80, 87, 0.4); } .thumbnail-corporate-caption * + p { margin-top: 10px; } .thumbnail-corporate-link { position: relative; overflow: hidden; display: inline-flex; align-items: center; justify-content: center; width: 1.67em; height: 1.67em; text-align: center; border-radius: 50%; font-size: 24px; line-height: 1; background: #0045b6; } .thumbnail-corporate-link, .thumbnail-corporate-link:active, .thumbnail-corporate-link:focus, .thumbnail-corporate-link:hover { color: #fff; } .thumbnail-corporate-link .icon { font-size: inherit; } .thumbnail-corporate-link .icon:first-child, .thumbnail-corporate-link .icon:last-child { vertical-align: middle; transition: transform 0.3s cubic-bezier(0.2, 1, 0.3, 1), opacity 0.3s cubic-bezier(0.2, 1, 0.3, 1); } .thumbnail-corporate-link .icon:first-child { display: block; } .thumbnail-corporate-link .icon:last-child { position: absolute; width: 100%; top: 50%; left: 0; opacity: 0; transform: translate3d(0, 0, 0); } .thumbnail-corporate-link:hover { background: #4854ed; } .thumbnail-corporate-link:hover .icon:first-child { opacity: 0; transform: translate3d(0, -30%, 0); } .thumbnail-corporate-link:hover .icon:last-child { opacity: 1; transform: translate3d(0, 0, 0) translateY(-50%); } .thumbnail-corporate-title { font-size: 20px; line-height: 1.3; } * + .thumbnail-corporate-link { margin-top: 15px; } @media (max-width: 575.98px) { .thumbnail-corporate { max-width: 360px; margin-left: auto; margin-right: auto; } } @media (min-width: 768px) { .thumbnail-corporate-caption { padding: 25px 20px; } } @media (min-width: 992px) { .thumbnail-corporate-lg .thumbnail-corporate-dummy::before { padding-bottom: 125.40541%; } } @media (min-width: 1200px) { .thumbnail-corporate-caption { padding: 30px 40px; } .thumbnail-corporate-caption * + p { margin-top: 8px; } .thumbnail-corporate-title { font-size: 24px; } } html:not(.tablet):not(.mobile) .thumbnail-corporate-caption { opacity: 0; visibility: hidden; transition: .3s all linear; background: rgba(73, 80, 87, 0.6); } html:not(.tablet):not(.mobile) .thumbnail-corporate-caption > * { opacity: 0; visibility: hidden; transition: .25s .11s all linear; } html:not(.tablet):not(.mobile) .thumbnail-corporate:hover .thumbnail-corporate-caption, html:not(.tablet):not(.mobile) .thumbnail-corporate:hover .thumbnail-corporate-caption > * { opacity: 1; visibility: visible; } html:not(.tablet):not(.mobile) .hoverdir-item .thumbnail-corporate-caption { opacity: 1; visibility: visible; transform: translate(-100%, -100%); } html:not(.tablet):not(.mobile)[data-x-mode='true'] .hoverdir-item .thumbnail-corporate-caption { opacity: 0; visibility: hidden; transform: none; } html:not(.tablet):not(.mobile)[data-x-mode='true'] .thumbnail-corporate:hover .thumbnail-corporate-caption, html:not(.tablet):not(.mobile)[data-x-mode='true'] .thumbnail-corporate:hover .thumbnail-corporate-caption > * { opacity: 1; visibility: visible; } .thumbnail-light { text-align: left; } .thumbnail-light-title { color: #151515; } .thumbnail-light-title a { color: inherit; } .thumbnail-light-title a:hover { color: #0045b6; } .thumbnail-light-media { position: relative; overflow: hidden; display: block; border-radius: 6px; } .thumbnail-light-media::before { content: ''; position: absolute; top: 0; right: 0; bottom: 0; left: 0; background: rgba(73, 80, 87, 0.3); transition: .22s ease-in-out; opacity: 0; visibility: hidden; pointer-events: none; } .thumbnail-light-media:hover::before { opacity: 1; visibility: visible; } .thumbnail-light-image { width: 100%; } @media (max-width: 575.98px) { .thumbnail-light { max-width: 360px; margin-left: auto; margin-right: auto; } } .thumbnail-thin { position: relative; overflow: hidden; padding-bottom: 100%; } .thumbnail-thin::before, .thumbnail-thin::after { pointer-events: none; } .thumbnail-thin::before { content: ''; position: absolute; top: 0; right: 0; bottom: 0; left: 0; z-index: 1; background: rgba(73, 80, 87, 0.3); } .thumbnail-thin::after { position: absolute; top: 50%; left: 50%; transform: translate3d(-50%, -50%, 0); z-index: 2; content: '\f504'; font-family: 'Material Design Icons'; font-size: 40px; line-height: 1; color: #fff; } a.thumbnail-thin { display: block; } .thumbnail-thin-image { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); min-height: 100%; min-width: 100%; width: auto; height: auto; max-width: none; } @supports (object-fit: cover) { .thumbnail-thin-image { left: 0; top: 0; width: 100%; height: 100%; transform: none; object-fit: cover; object-position: center center; } } html:not(.tablet):not(.mobile) .thumbnail-thin::before, html:not(.tablet):not(.mobile) .thumbnail-thin::after { opacity: 0; transition: .22s ease-in; } html:not(.tablet):not(.mobile) .thumbnail-thin::before { background: rgba(73, 80, 87, 0.6); } html:not(.tablet):not(.mobile) .thumbnail-thin:hover::before, html:not(.tablet):not(.mobile) .thumbnail-thin:hover::after { opacity: 1; } .thumbnail-modern { position: relative; overflow: hidden; display: flex; text-align: left; } .thumbnail-modern * { color: inherit; } .thumbnail-modern, .thumbnail-modern:active, .thumbnail-modern:focus, .thumbnail-modern:hover { color: #fff; } .thumbnail-modern:hover { color: #fff; } .thumbnail-modern > * { width: 100%; flex-shrink: 0; } .thumbnail-modern-sm .thumbnail-modern-dummy::before { padding-bottom: 69.18919%; } .thumbnail-modern-dummy { visibility: hidden; pointer-events: none; width: 100%; } .thumbnail-modern-dummy::before { content: ''; display: block; padding-bottom: 73.84615%; } .thumbnail-modern-image { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); min-height: 100%; min-width: 100%; width: auto; height: auto; max-width: none; background-position: center center; background-size: cover; } @supports (object-fit: cover) { .thumbnail-modern-image { left: 0; top: 0; width: 100%; height: 100%; transform: none; object-fit: cover; object-position: center center; } } .thumbnail-modern-caption { position: relative; z-index: 1; display: flex; align-items: center; justify-content: center; width: 100%; padding: 20px; background: rgba(73, 80, 87, 0.4); } .thumbnail-modern-caption .icon { font-size: 30px; line-height: 1; color: #fff; } @media (max-width: 575.98px) { .thumbnail-modern { max-width: 360px; margin-left: auto; margin-right: auto; } } @media (min-width: 992px) { .thumbnail-modern-lg .thumbnail-modern-dummy::before { padding-bottom: 147.69231%; } } html:not(.tablet):not(.mobile) .thumbnail-modern-caption { opacity: 0; visibility: hidden; transition: .3s all linear; background: rgba(73, 80, 87, 0.6); } html:not(.tablet):not(.mobile) .thumbnail-modern:hover .thumbnail-modern-caption { opacity: 1; visibility: visible; } .thumbnail-creative { position: relative; display: block; max-width: 348px; margin-left: auto; margin-right: auto; } .thumbnail-creative:hover .thumbnail-creative-image { transform: translate(0, -5px); box-shadow: 0 2px 24px 1px rgba(0, 0, 0, 0.2); } .thumbnail-creative-wrap { padding: 26px; margin: -26px; } .thumbnail-creative-image { position: relative; display: block; width: 100%; transition: .33s ease-in-out; box-shadow: 0 2px 24px 0 rgba(0, 0, 0, 0.15); } * + .thumbnail-creative-title { margin-top: 16px; } @media (min-width: 768px) { * + .thumbnail-creative-title { margin-top: 24px; } } .thumbnail-indigo { position: relative; overflow: hidden; display: block; background: #495057; box-shadow: -1px 1px 21px 0px rgba(0, 0, 0, 0.08); background-position: center top; background-size: cover; } .thumbnail-indigo::before { content: ''; display: block; opacity: 0; visibility: hidden; padding-bottom: 148.75847%; } .thumbnail-indigo-sm::before { padding-bottom: 70.65463%; } .thumbnail-indigo-sm + .thumbnail-indigo-sm { margin-top: 7.4%; } .thumbnail-indigo-caption { position: absolute; top: auto; right: 0; bottom: 0; left: 0; display: flex; align-items: center; justify-content: center; padding: 20px; background: rgba(255, 255, 255, 0.95); } html:not(.tablet):not(.mobile) .thumbnail-indigo-caption { top: 0; background: rgba(255, 255, 255, 0.9); } html:not(.tablet):not(.mobile) .thumbnail-indigo-caption, html:not(.tablet):not(.mobile) .thumbnail-indigo-caption > * { opacity: 0; visibility: hidden; will-change: opacity; } html:not(.tablet):not(.mobile) .thumbnail-indigo-caption { transition: .2s all linear; } html:not(.tablet):not(.mobile) .thumbnail-indigo-caption > * { transition: .2s .1s all ease-in; } html:not(.tablet):not(.mobile) .thumbnail-indigo:hover .thumbnail-indigo-caption, html:not(.tablet):not(.mobile) .thumbnail-indigo:hover .thumbnail-indigo-caption > * { opacity: 1; visibility: visible; } /* +* +* Breadcrumbs +*/ .breadcrumbs-custom { position: relative; display: flex; background-color: #495057; background-position: 20% 20%; } .breadcrumbs-custom .breadcrumbs-custom-subtitle { color: #aeb1be; } @media (min-width: 576px) { .breadcrumbs-custom { padding: 60px 0; } } @media (min-width: 1200px) { .breadcrumbs-custom { padding: 0; } } .breadcrumbs-custom-inner { display: flex; min-height: inherit; width: 100%; padding: 50px 0 25px; } .breadcrumbs-custom-container { display: flex; flex-direction: column; justify-content: space-between; } .breadcrumbs-custom-container::before { content: ''; display: block; } .breadcrumbs-custom-path { margin-left: -15px; margin-right: -15px; } .breadcrumbs-custom-path > * { padding-left: 15px; padding-right: 15px; } .breadcrumbs-custom-path li { position: relative; display: inline-block; vertical-align: middle; color: #aeb1be; } .breadcrumbs-custom-path li::after { content: ""; font-family: "FontAwesome"; position: absolute; top: 51%; right: -5px; display: inline-block; font-size: inherit; font-style: normal; color: rgba(255, 255, 255, 0.5); transform: translate3d(0, -50%, 0); } .breadcrumbs-custom-path li:last-child:after { display: none; } .breadcrumbs-custom-path a { display: inline; vertical-align: middle; } .breadcrumbs-custom-path a, .breadcrumbs-custom-path a:active, .breadcrumbs-custom-path a:focus { color: #aeb1be; } .breadcrumbs-custom-path a:hover { color: #fff; } * + .breadcrumbs-custom-path { margin-top: 25px; } .breadcrumbs-custom-subtitle + .breadcrumbs-custom-title { margin-top: 15px; } @media (min-width: 576px) { .breadcrumbs-custom-path { margin-left: -30px; margin-right: -30px; } .breadcrumbs-custom-path > * { padding-left: 30px; padding-right: 30px; } * + .breadcrumbs-custom-path { margin-top: 30px; } } @media (min-width: 768px) { .breadcrumbs-custom { min-height: 23.75vw; } .breadcrumbs-custom-inner { padding: 80px 0 30px; } .breadcrumbs-custom-subtitle + .breadcrumbs-custom-title { margin-top: 5px; } } @media (min-width: 992px) { .breadcrumbs-custom-inner { padding-bottom: 40px; } .breadcrumbs-custom-subtitle + .breadcrumbs-custom-title { margin-top: 10px; } } @media (min-width: 1600px) { .breadcrumbs-custom { min-height: 520px; } .breadcrumbs-custom-inner { padding: 130px 0 40px 0; } .breadcrumbs-custom-subtitle + .breadcrumbs-custom-title { margin-top: 20px; } } /* +* +* Pagination custom +*/ .pagination { font-size: 0; line-height: 0; margin-bottom: -10px; margin-left: -10px; } .pagination:empty { margin-bottom: 0; margin-left: 0; } .pagination > * { display: inline-block; margin: 0 0 10px 10px; } .page-item { display: inline-block; vertical-align: middle; text-align: center; font-weight: 700; } .page-link { display: inline-flex; align-items: center; justify-content: center; min-width: 48px; min-height: 48px; padding: 10px 10px; border: 0; border-radius: 6px; font-family: "Poppins", sans-serif; font-size: 14px; font-weight: 500; line-height: 1.71429; background-color: #0045b6; color: #fff; transition: all 0.3s ease-in-out; } .page-link.button-winona * { line-height: inherit; } .page-link.button-winona .content-dubbed { margin-top: -1px; } .page-link:hover, .page-link:focus, .page-link:active { color: #fff; background-color: #151515; } .page-item.active > .page-link, .page-item.active > .page-link:hover, .page-item.active > .page-link:focus, .page-item.active > .page-link:active { color: #fff; background-color: #151515; } .page-item.disabled > .page-link, .page-item.disabled > .page-link:hover, .page-item.disabled > .page-link:focus, .page-item.disabled > .page-link:active { color: #74757f; background-color: #e8e9ee; } .page-item-control .icon::before { font-family: 'FontAwesome'; font-size: 11px; line-height: 24px; } .page-item-control:first-child .icon::before { content: '\f053'; margin-left: -1px; } .page-item-control:last-child .icon::before { content: '\f054'; margin-right: -1px; } * + .pagination { margin-top: 35px; } @media (min-width: 992px) { * + .pagination { margin-top: 55px; } } @media (min-width: 1600px) { * + .pagination { margin-top: 70px; } } /* +* +* Snackbars +*/ .snackbars { padding: 9px 16px; margin-left: auto; margin-right: auto; color: #fff; text-align: left; background-color: #0045b6; box-shadow: 0 1px 4px 0 rgba(0, 0, 0, 0.15); border-radius: 6px; font-size: 14px; } .snackbars .icon-xxs { font-size: 18px; } .snackbars p span:last-child { padding-left: 14px; } .snackbars-left { display: inline-block; margin-bottom: 0; } .snackbars-right { display: inline-block; float: right; text-transform: uppercase; } .snackbars-right:hover { text-decoration: underline; } @media (min-width: 576px) { .snackbars { max-width: 540px; padding: 12px 15px; font-size: 15px; } } /* +* +* Footers +*/ .footer-standard h4 + *, .footer-standard .heading-4 + * { margin-top: 14px; } .footer-standard-main { padding: 35px 0 15px; } .footer-standard-aside { padding: 30px 0; } .footer-standard-aside { margin-bottom: -18px; text-align: center; } .footer-standard-aside > * { margin-bottom: 18px; } .footer-standard-aside > * { margin-top: 0; } @media (max-width: 575.98px) { .footer-standard .box-1 { margin-left: 0; } } @media (min-width: 768px) and (max-width: 991.98px) { .footer-standard .box-1 { margin-left: 0; } } @media (min-width: 768px) { .footer-standard h4 + *, .footer-standard .heading-4 + * { margin-top: 25px; } .footer-standard-main { padding: 55px 0 20px; } .footer-standard-aside { display: flex; align-items: center; flex-wrap: wrap; justify-content: space-between; text-align: center; margin-left: -20px; } .footer-standard-aside > * { margin-left: 20px; } } @media (min-width: 1200px) { .footer-standard-main { padding: 80px 0 50px; } } .footer-minimal { padding: 30px 0; } .footer-minimal-inner { margin-bottom: -18px; text-align: center; } .footer-minimal-inner > * { margin-bottom: 18px; } .footer-minimal-inner > * { margin-top: 0; } .footer-minimal-inner .rights a { padding-bottom: 2px; position: relative; } .footer-minimal-inner .rights a:before { content: ''; position: absolute; left: 0; height: 1px; bottom: 0; width: 0; display: block; transition: all ease .35s; background-color: #fff; } .footer-minimal-inner .rights a:hover { color: #fff; } .footer-minimal-inner .rights a:hover:before { content: ''; width: 100%; transition: all ease .35s; } @media (min-width: 768px) { .footer-minimal-inner { display: flex; align-items: center; flex-wrap: wrap; justify-content: space-between; text-align: center; margin-left: -20px; } .footer-minimal-inner > * { margin-left: 20px; } } .footer-advanced.bg-gray-700 h1, .footer-advanced.bg-gray-700 h2, .footer-advanced.bg-gray-700 h3, .footer-advanced.bg-gray-700 h4, .footer-advanced.bg-gray-700 h5, .footer-advanced.bg-gray-700 h6, .footer-advanced.bg-gray-700 [class^='heading-'] { color: #aeb1be; } .footer-advanced h4 + *, .footer-advanced .heading-4 + * { margin-top: 14px; } .footer-advanced-main { padding: 35px 0 15px; } .footer-advanced-aside { padding: 30px 0; } .footer-advanced-layout { margin-bottom: -18px; text-align: center; } .footer-advanced-layout > * { margin-bottom: 18px; } .footer-advanced-layout > * { margin-top: 0; } @media (min-width: 768px) and (max-width: 991.98px) { .footer-advanced .box-1 { margin-left: 0; } } @media (min-width: 992px) and (max-width: 1199.98px) { .footer-advanced-text { font-size: 14px; line-height: 1.7; } } .footer-advanced-text { color: rgba(255, 255, 255, 0.3); } @media (min-width: 768px) { .footer-advanced-main { padding: 55px 0 20px; } .footer-advanced-aside { padding: 35px 0; } .footer-advanced-layout { display: flex; align-items: center; flex-wrap: wrap; justify-content: space-between; text-align: center; margin-left: -20px; } .footer-advanced-layout > * { margin-left: 20px; } .footer-advanced-aside + .container { margin-top: 10px; } } @media (min-width: 1200px) { .footer-advanced h4 + *, .footer-advanced .heading-4 + * { margin-top: 35px; } .footer-advanced-main { padding: 80px 0 60px; } .footer-advanced-aside + .container { margin-top: 20px; } } .footer-linked.bg-gray-700 h1, .footer-linked.bg-gray-700 h2, .footer-linked.bg-gray-700 h3, .footer-linked.bg-gray-700 h4, .footer-linked.bg-gray-700 h5, .footer-linked.bg-gray-700 h6, .footer-linked.bg-gray-700 [class^='heading-'] { color: #aeb1be; } .footer-linked.bg-gray-700 .list { color: #fff; } .footer-linked.bg-gray-700 .list a:hover { color: rgba(255, 255, 255, 0.6); } .footer-linked h4 + *, .footer-linked .heading-4 + * { margin-top: 14px; } .footer-linked h4 + hr, .footer-linked .heading-4 + hr { margin-top: 15px; } .footer-linked * + .row { margin-top: 25px; } .footer-linked-main { padding: 35px 0 15px; } .footer-linked-aside { padding: 30px 0; } .footer-linked-layout { margin-bottom: -18px; text-align: center; } .footer-linked-layout > * { margin-bottom: 18px; } .footer-linked-layout > * { margin-top: 0; } @media (min-width: 768px) and (max-width: 991.98px) { .footer-linked .box-1 { margin-left: 0; } } @media (min-width: 992px) and (max-width: 1199.98px) { .footer-linked-text { font-size: 14px; line-height: 1.7; } } @media (min-width: 768px) { .footer-linked-main { padding: 65px 0 40px; } .footer-linked-aside { padding: 30px 0; } .footer-linked-layout { display: flex; align-items: center; flex-wrap: wrap; justify-content: space-between; text-align: center; margin-left: -20px; } .footer-linked-layout > * { margin-left: 20px; } .footer-linked-aside + .container { margin-top: 10px; } } @media (min-width: 1200px) { .footer-linked h4 + *, .footer-linked .heading-4 + * { margin-top: 35px; } .footer-linked-main { padding: 90px 0 60px; } .footer-linked-aside + .container { margin-top: 20px; } } .foter-social-links .link-default { color: #fff; } .foter-social-links .link-default:hover { color: #0045b6; } .rights { color: #74757f; } .footer-minimal .rights { color: #fff; } /* +* +* Pricing tables +*/ .pricing-minimal { position: relative; z-index: 1; padding: 25px 20px; border: 1px solid #aeb1be; border-radius: 6px; text-align: left; transition: .22s; cursor: default; } .pricing-minimal:hover { border-color: #0045b6; } .pricing-minimal:hover::before { visibility: visible; transform: translate3d(0, 0, 0); } .pricing-minimal-price { font-size: 26px; line-height: 1; font-weight: 700; letter-spacing: 0; color: #495057; } .pricing-minimal-price span { display: inline; } .pricing-minimal-price-currency { font-size: .65em; vertical-align: top; } .pricing-minimal-divider { border-bottom: 1px solid #aeb1be; } * + .pricing-minimal-price { margin-top: 10px; } * + .pricing-minimal-divider { margin-top: 20px; } @media (max-width: 575.98px) { .pricing-minimal { max-width: 350px; margin-left: auto; margin-right: auto; } } @media (min-width: 576px) { .pricing-minimal { padding: 20px; } } @media (min-width: 768px) { .pricing-minimal-price { font-size: 36px; } } @media (min-width: 768px) { .pricing-minimal { padding: 30px 38px; } } @media (min-width: 992px) and (max-width: 1199.98px) { .pricing-minimal { padding: 25px 20px; } } @media (min-width: 1200px) { .pricing-minimal { padding: 38px 40px; } } .pricing-modern { padding: 35px 20px; border: 1px solid #aeb1be; border-radius: 6px; text-align: center; } .pricing-modern * + .button { margin-top: 20px; } .pricing-group-modern { border: 1px solid #aeb1be; border-radius: 6px; } .pricing-group-modern .pricing-modern { border: 0; border-radius: 0; } .pricing-modern-rating { color: #0045b6; } .pricing-modern-rating > li { display: inline-block; width: 26px; font-size: 30px; line-height: .9; } .pricing-modern-title { font-weight: 700; } .pricing-modern-table { display: inline-table; } .pricing-modern-table td { padding: 3px 8px; color: #74757f; } .pricing-modern-table td:first-child { text-align: right; color: #495057; } .pricing-modern-table td:last-child { text-align: left; } .pricing-modern-price { font-size: 24px; font-weight: 300; line-height: 1.2; } .pricing-modern-price span { display: inline; } .pricing-modern-price-currency { font-size: .65em; vertical-align: top; } * + .pricing-group-modern { margin-top: 35px; } * + .pricing-modern-title { margin-top: 5px; } * + .pricing-modern-table { margin-top: 20px; } * + .pricing-modern-price { margin-top: 15px; } @media (max-width: 575.98px) { .pricing-group-modern { max-width: 360px; margin-left: auto; margin-right: auto; } .pricing-group-modern .pricing-modern + .pricing-modern { border-top: 1px solid #aeb1be; } } @media (min-width: 576px) and (max-width: 991.98px) { .pricing-group-modern .pricing-modern:nth-child(odd) { border-right: 1px solid #aeb1be; } .pricing-group-modern .pricing-modern:nth-child(n + 3) { border-top: 1px solid #aeb1be; } } @media (min-width: 576px) { .pricing-group-modern { display: flex; flex-wrap: wrap; } .pricing-group-modern .pricing-modern { width: 100%; max-width: 50%; } .pricing-modern { padding: 55px 20px; } * + .pricing-group-modern { margin-top: 50px; } * + .pricing-modern-table { margin-top: 25px; } * + .pricing-modern-price { margin-top: 30px; } } @media (min-width: 768px) { .pricing-modern-price { font-size: 30px; } } @media (min-width: 992px) { .pricing-group-modern .pricing-modern { max-width: 25%; border-right: 1px solid #aeb1be; } .pricing-group-modern .pricing-modern:nth-child(4n) { border-right: 0; } .pricing-group-modern .pricing-modern:nth-child(n + 5) { border-top: 1px solid #aeb1be; } } @media (min-width: 992px) and (max-width: 1199.98px) { .pricing-modern .button { display: block; padding-left: 15px; padding-right: 15px; } } @media (min-width: 1200px) { .pricing-modern-price { font-size: 36px; } * + .pricing-modern-table { margin-top: 35px; } * + .pricing-modern-price { margin-top: 40px; } } @media (min-width: 1600px) { * + .pricing-group-modern { margin-top: 75px; } } /* +* +* Profiles +*/ .profile-classic { text-align: left; } .profile-classic-main { position: relative; overflow: hidden; z-index: 1; border-radius: 6px; } .profile-classic-image { min-width: 100%; } .profile-classic-caption { position: absolute; left: 0; right: 0; bottom: 0; padding: 10px; background: rgba(0, 69, 182, 0.6); } .profile-classic-caption .icon-creative { color: #fff; } .profile-classic-title { color: #0045b6; } .profile-classic-title a:hover { color: #343db0; } .profile-classic-position { color: #aeb1be; } * + .profile-classic-title { margin-top: 15px; } * + .profile-classic-position { margin-top: 5px; } @media (max-width: 575.98px) { .profile-classic { max-width: 360px; margin-left: auto; margin-right: auto; } } @media (min-width: 576px) and (max-width: 767.98px) { .profile-classic-caption { padding-left: 5px; padding-right: 5px; } .profile-classic-caption .icon-sm { font-size: 18px; } .profile-classic-caption .group { margin-left: -3px; } .profile-classic-caption .group > * { margin-left: 3px; } } @media (min-width: 992px) and (max-width: 1199.98px) { .profile-classic-caption { padding-left: 5px; padding-right: 5px; } .profile-classic-caption .icon-sm { font-size: 18px; } .profile-classic-caption .group { margin-left: -3px; } .profile-classic-caption .group > * { margin-left: 3px; } } @media (min-width: 1200px) { * + .profile-classic-title { margin-top: 20px; } } html:not(.tablet):not(.mobile) .profile-classic:hover .profile-classic-caption { opacity: 1; visibility: visible; } html:not(.tablet):not(.mobile) .profile-classic-caption { top: 0; display: flex; flex-direction: column; justify-content: flex-end; opacity: 0; visibility: hidden; transition: .33s ease-in-out; } .profile-creative { display: flex; margin-left: -15px; text-align: left; color: #74757f; } .profile-creative > * { margin-left: 15px; } .profile-creative-figure { position: relative; overflow: hidden; max-width: 80px; flex-shrink: 0; } .profile-creative-image { width: 100%; border-radius: 6px; } .profile-creative-main { flex-grow: 1; } .profile-creative-title a { color: #151515; font-weight: 300; } .profile-creative-title a:hover { color: #0045b6; } .profile-creative-position { color: #151515; } .profile-creative-contacts { color: #151515; } .profile-creative-contacts a { color: inherit; font-weight: 400; } .profile-creative-contacts a:hover { color: #0045b6; } * + .profile-creative-position { margin-top: 0; } * + .profile-creative-contacts { margin-top: 15px; } @media (min-width: 576px) { .profile-creative { margin-left: -30px; } .profile-creative > * { margin-left: 30px; } .profile-creative-figure { max-width: 22%; } } @media (min-width: 768px) { .profile-creative-figure { max-width: 29%; } .profile-creative-main { padding-right: 30px; } * + .profile-creative-contacts { margin-top: 22px; } } @media (min-width: 992px) and (max-width: 1199.98px) { .profile-creative { margin-left: -20px; } .profile-creative > * { margin-left: 20px; } .profile-creative-figure { max-width: 25%; } .profile-creative-main { padding-right: 10px; } } .profile-minimal { position: relative; overflow: hidden; border-radius: 6px; color: #fff; text-align: left; } .profile-minimal::before { content: ''; display: block; padding-bottom: 100%; } .profile-minimal-image { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); min-height: 100%; min-width: 100%; width: auto; height: auto; max-width: none; } @supports (object-fit: cover) { .profile-minimal-image { left: 0; top: 0; width: 100%; height: 100%; transform: none; object-fit: cover; object-position: center center; } } .profile-minimal-caption { position: absolute; top: 0; right: 0; bottom: 0; left: 0; display: flex; flex-direction: column; justify-content: flex-end; padding: 25px; background: rgba(73, 80, 87, 0.4); } .profile-minimal-title { color: #fff; } * + .profile-minimal-position { margin-top: 5px; } @media (max-width: 575.98px) { .profile-minimal { max-width: 360px; margin-left: auto; margin-right: auto; } } @media (min-width: 1200px) { .profile-minimal-caption { padding: 35px; } * + .profile-minimal-position { margin-top: 10px; } } html:not(.tablet):not(.mobile) .profile-minimal:hover .profile-minimal-caption { opacity: 1; visibility: visible; } html:not(.tablet):not(.mobile) .profile-minimal-caption { opacity: 0; visibility: hidden; transition: .35s ease-in; background: rgba(73, 80, 87, 0.6); } .profile-modern { text-align: left; } .profile-modern > * + * { margin-top: 35px; } .profile-modern-header { display: flex; justify-content: space-between; flex-wrap: wrap; margin-bottom: -10px; margin-left: -20px; } .profile-modern-header:empty { margin-bottom: 0; margin-left: 0; } .profile-modern-header > * { display: inline-block; margin: 0 0 10px 20px; } .profile-modern-header * + p { margin-top: 5px; } .profile-modern-figure { position: relative; overflow: hidden; border-radius: 6px; } .profile-modern-image { display: block; width: 100%; } .profile-modern-button { position: absolute; right: 30px; bottom: 30px; overflow: hidden; display: inline-flex; align-items: center; justify-content: center; width: 1.86em; height: 1.86em; text-align: center; border-radius: 50%; font-size: 32px; line-height: 1; background: #0045b6; } .profile-modern-button, .profile-modern-button:active, .profile-modern-button:focus, .profile-modern-button:hover { color: #fff; } .profile-modern-button .icon { font-size: inherit; } .profile-modern-button .icon:first-child, .profile-modern-button .icon:last-child { vertical-align: middle; transition: transform 0.3s cubic-bezier(0.2, 1, 0.3, 1), opacity 0.3s cubic-bezier(0.2, 1, 0.3, 1); } .profile-modern-button .icon:first-child { display: block; } .profile-modern-button .icon:last-child { position: absolute; width: 100%; top: 50%; left: 0; opacity: 0; transform: translate3d(0, 0, 0); } .profile-modern-button:hover { color: #fff; background: #343db0; } .profile-modern-button:hover .icon:first-child { opacity: 0; transform: translate3d(0, -30%, 0); } .profile-modern-button:hover .icon:last-child { opacity: 1; transform: translate3d(0, 0, 0) translateY(-50%); } @media (max-width: 575.98px) { .profile-modern { max-width: 360px; margin-left: auto; margin-right: auto; } .profile-modern * + .row { margin-top: 15px; } } @media (max-width: 767.98px) { .profile-modern-figure { max-width: 330px; margin-left: auto; margin-right: auto; } } @media (min-width: 768px) { .profile-modern { display: flex; align-items: flex-start; margin-left: -30px; } .profile-modern > * { margin-left: 30px; } .profile-modern > * + * { margin-top: 0; } .profile-modern-figure { max-width: 50%; flex-shrink: 0; } .profile-modern-main { flex-grow: 1; } * + .profile-modern-main { margin-top: 0; } } @media (min-width: 992px) { .profile-modern-figure { max-width: 28%; } } @media (min-width: 768px) and (max-width: 1199.98px) { .profile-modern { font-size: 14px; } .profile-modern * + .row { margin-top: 31px; } } @media (min-width: 1200px) { .profile-modern { margin-left: -60px; } .profile-modern > * { margin-left: 60px; } } .profile-thin { position: relative; display: block; padding: 30px 20px 45px; background: #f5f6fa; text-align: center; } .profile-thin * + p { margin-top: 20px; } .profile-thin * + .group { margin-top: 15px; } .profile-thin * + .button-sm { margin-top: 15px; } .profile-thin .group { margin-left: auto; margin-right: auto; } .profile-thin-aside { position: relative; display: inline-block; } .profile-thin-image { border-radius: 50%; } .profile-thin-contact-button { position: absolute; right: 0; bottom: 0; overflow: hidden; display: inline-flex; align-items: center; justify-content: center; width: 2em; height: 2em; text-align: center; border-radius: 50%; font-size: 20px; line-height: 1; background: #0045b6; } .profile-thin-contact-button, .profile-thin-contact-button:active, .profile-thin-contact-button:focus, .profile-thin-contact-button:hover { color: #fff; } .profile-thin-contact-button .icon { font-size: inherit; } .profile-thin-contact-button .icon:first-child, .profile-thin-contact-button .icon:last-child { vertical-align: middle; transition: transform 0.3s cubic-bezier(0.2, 1, 0.3, 1), opacity 0.3s cubic-bezier(0.2, 1, 0.3, 1); } .profile-thin-contact-button .icon:first-child { display: block; } .profile-thin-contact-button .icon:last-child { position: absolute; width: 100%; top: 50%; left: 0; opacity: 0; transform: translate3d(0, 0, 0); } .profile-thin-contact-button:hover { color: #fff; background: #343db0; } .profile-thin-contact-button:hover .icon:first-child { opacity: 0; transform: translate3d(0, -30%, 0); } .profile-thin-contact-button:hover .icon:last-child { opacity: 1; transform: translate3d(0, 0, 0) translateY(-50%); } .profile-thin-title { font-size: 22px; line-height: 1.47; } * + p.profile-thin-subtitle { margin-top: 0; } * + .profile-thin-main { margin-top: 20px; } @media (min-width: 576px) and (max-width: 991.98px) { .profile-thin { display: flex; align-items: flex-start; text-align: left; } .profile-thin .group { margin-left: -15px; } .profile-thin > * + * { margin-left: 30px; } .profile-thin-aside { flex-shrink: 0; max-width: 30%; } .profile-thin-main { flex-grow: 1; } * + .profile-thin-main { margin-top: 0; } } @media (min-width: 1200px) { .profile-thin { padding: 55px 35px; } .profile-thin * + .group { margin-top: 25px; } .profile-thin * + .button-sm { margin-top: 25px; } } /* +* +* Counters +*/ .counter-minimal { position: relative; text-align: center; color: #495057; } .counter-minimal-icon { font-size: 24px; line-height: 1; color: #0045b6; } .counter-minimal-main { font-family: "Poppins", sans-serif; font-size: 36px; font-weight: 500; line-height: 1.2; letter-spacing: 0; } .counter-minimal-main > * { display: inline; font: inherit; } .counter-minimal-main span { display: inline; } * + .counter-minimal-title { margin-top: 10px; } * + .counter-minimal-main { margin-top: 15px; } @media (max-width: 575.98px) { .counter-minimal-title { font-size: 16px; line-height: 1.4; } } @media (min-width: 768px) { .counter-minimal-icon { font-size: 28px; } } @media (min-width: 768px) { .counter-minimal-title { font-size: 18px; } .counter-minimal-main { font-size: 48px; } } @media (min-width: 1200px) { * + .counter-minimal-main { margin-top: 25px; } * + .counter-minimal-title { margin-top: 18px; } } /* +* +* Layouts +*/ .layout-bordered { text-align: center; } .layout-bordered-item { border: 0 solid #d3d5db; } .layout-bordered-item-inner { display: flex; flex-direction: column; justify-content: center; max-width: 290px; padding: 0 15px; margin-left: auto; margin-right: auto; } .layout-bordered-item-inner a { color: #151515; } .layout-bordered-item-inner > * + * { margin-top: 10px; } @media (max-width: 767.98px) { .layout-bordered-item { padding: 30px 0; } .layout-bordered-item:first-child { padding-top: 0; } .layout-bordered-item:last-child { padding-bottom: 0; } .layout-bordered-item + .layout-bordered-item { border-top-width: 1px; } } @media (min-width: 768px) { .layout-bordered { display: flex; } .layout-bordered-item { flex-grow: 1; } .layout-bordered-item-inner > * + * { margin-top: 15px; } } @media (min-width: 768px) and (max-width: 991.98px) { .layout-bordered-item { font-size: 15px; } } @media (min-width: 768px) { .layout-bordered-item { flex-basis: 33.333%; border-right-width: 1px; } .layout-bordered-item:first-child { border-left-width: 1px; } } .layout-navbar-demo { position: relative; overflow: hidden; min-height: 400px; background: #e8e9ee; } .layout-navbar-demo.layout-navbar-demo-lg { min-height: 650px; } .layout-navbar-demo .rd-navbar-fixed .rd-navbar-panel { position: relative; z-index: 10; } .layout-navbar-demo .rd-navbar-fixed .rd-navbar-nav-wrap { position: absolute; z-index: 9; } .layout-navbar-demo .rd-navbar-fixed .rd-navbar-main-outer, .layout-navbar-demo .rd-navbar-fixed .rd-navbar-main-element { position: static; } .layout-navbar-demo .rd-navbar-fixed .rd-navbar-search { position: static; } .layout-navbar-demo .rd-navbar-fixed .rd-navbar-panel .rd-navbar-search-toggle { position: static; z-index: 10; } .layout-navbar-demo .rd-navbar-fixed div.rd-navbar-block { top: -1px; right: 10px; white-space: nowrap; } .layout-navbar-demo .rd-navbar-fixed div.rd-navbar-block > * { display: inline-block; margin-top: 0; white-space: normal; vertical-align: middle; } .layout-navbar-demo .rd-navbar-fixed div.rd-navbar-block > * + * { margin-left: 8px; } .layout-navbar-demo .rd-navbar-fixed div.rd-navbar-block .rd-navbar-search-toggle { margin-top: 6px; } .layout-navbar-demo .rd-navbar-fixed div.rd-navbar-popup { position: absolute; } .layout-navbar-demo .rd-navbar-fixed.rd-navbar [class*='rd-navbar-fixed-element'] { position: absolute; z-index: 1000; } .layout-navbar-demo .rd-navbar-fixed.rd-navbar .rd-search { position: absolute; z-index: 1; } .layout-navbar-demo .rd-navbar-fixed .rd-navbar-collapse-content { position: absolute; z-index: 1; } @media (min-width: 1200px) { .layout-navbar-demo { min-height: 550px; } .layout-navbar-demo.layout-navbar-demo-lg { min-height: 650px; } } .container + .layout-navbar-demo { margin-top: 20px; } .blog-layout-aside { padding: 4px 0; background: #f5f6fa; } .blog-layout-aside .rd-search .form-input { border-color: transparent; } .blog-layout-aside-item { padding: 25px 30px; } .blog-layout-aside-item .blog-layout-aside-title + * { margin-top: 15px; } .blog-layout-aside-item a { color: #151515; } .blog-layout-aside-title { font-size: 22px; line-height: 1.5; } * + .blog-layout-aside { margin-top: 55px; } @media (min-width: 768px) { * + .blog-layout-aside { margin-top: 70px; } } @media (min-width: 768px) and (max-width: 991.98px) { .blog-layout-aside { columns: 2; break-inside: avoid; } .blog-layout-aside-item { display: inline-block; width: 100%; } } @media (min-width: 992px) { .blog-layout { display: flex; align-items: flex-start; margin-left: -30px; } .blog-layout > * { margin-left: 30px; } .blog-layout-main { flex-grow: 1; max-width: 66.66667%; } .blog-layout-aside { flex-shrink: 0; max-width: 33.33333%; } * + .blog-layout-aside { margin-top: 0; } } @media (min-width: 1200px) { .blog-layout-aside-item { padding: 35px 40px; } } .blog-layout-aside .list-marked * > a { transition: all ease .35s; display: inline-block; } .blog-layout-aside .list-marked * > a:hover { text-indent: 5px; transition: all ease .35s; } /* +* +* Timeline +*/ .timeline-classic { text-align: left; } .timeline-classic-item { position: relative; overflow: hidden; padding: 0 0 70px 35px; margin-bottom: -25px; } .timeline-classic-item:last-child { padding-bottom: 0; margin-bottom: 0; } .timeline-classic-item-image { width: 100%; } .timeline-classic-item-title { font-family: "Poppins", sans-serif; font-size: 24px; line-height: 1.2; font-weight: 300; letter-spacing: 0; text-transform: uppercase; } .thumbnail-classic-item-subtitle { font-size: 16px; line-height: 1.5; color: #151515; } .timeline-classic-item-divider { position: absolute; top: 0; left: 0; display: flex; flex-grow: 1; flex-direction: column; align-items: center; padding-top: 25px; } .timeline-classic-item-divider::before { content: ''; display: block; width: 15px; height: 15px; border: 2px solid #0045b6; border-radius: 50%; } .timeline-classic-item-divider::after { content: ''; position: absolute; top: 40px; left: 50%; margin-left: -1px; width: 2px; height: 300vh; background: #0045b6; min-height: 100px; } * + .timeline-classic-item-main { margin-top: 20px; } .context-dark .timeline-classic-item-divider::before, .bg-gray-700 .timeline-classic-item-divider::before, .bg-gray-800 .timeline-classic-item-divider::before, .bg-primary .timeline-classic-item-divider::before, .bg-primary-darker .timeline-classic-item-divider::before { border-color: #fff; } .context-dark .timeline-classic-item-divider::after, .bg-gray-700 .timeline-classic-item-divider::after, .bg-gray-800 .timeline-classic-item-divider::after, .bg-primary .timeline-classic-item-divider::after, .bg-primary-darker .timeline-classic-item-divider::after { background: #fff; } .context-dark .thumbnail-classic-item-subtitle, .bg-gray-700 .thumbnail-classic-item-subtitle, .bg-gray-800 .thumbnail-classic-item-subtitle, .bg-primary .thumbnail-classic-item-subtitle, .bg-primary-darker .thumbnail-classic-item-subtitle { color: #fff; } @media (max-width: 575.98px) { .timeline-classic-item { max-width: 450px; margin-left: auto; margin-right: auto; } } @media (max-width: 767.98px) { .timeline-classic-item { flex-wrap: wrap; } .timeline-classic-item-main, .timeline-classic-item-aside { width: 100%; } } @media (min-width: 768px) { .timeline-classic-item { display: flex; padding-left: 0; padding-bottom: 90px; } .timeline-classic-item-aside { max-width: 33.33%; } .timeline-classic-item-main { max-width: 60%; } .timeline-classic-item-divider { position: relative; padding: 25px 20px 0; } .timeline-classic-item-title { font-size: 32px; } .thumbnail-classic-item-subtitle { font-size: 18px; line-height: 1.4; } * + .timeline-classic-item-main { margin-top: 0; } } @media (min-width: 992px) { .timeline-classic-item * + h4, .timeline-classic-item * + .heading-4 { margin-top: 20px; } .timeline-classic-item-title { font-size: 36px; } .thumbnail-classic-item-subtitle { font-size: 20px; line-height: 1.4; } .timeline-classic-item-aside { max-width: 37%; } .timeline-classic-item-main { max-width: 50%; } } @media (min-width: 1200px) { .timeline-classic-item-aside { max-width: 33.33%; } .timeline-classic-item-title { font-size: 48px; } } @media (min-width: 1600px) { .timeline-classic-item { padding-bottom: 125px; } .timeline-classic-item * + h4, .timeline-classic-item * + .heading-4 { margin-top: 30px; } } /* +* +* Careers +*/ .career-classic { text-align: left; } .career-classic-divider { border-bottom: 1px solid #aeb1be; } .career-classic-list { transform: translate3d(0, -10px, 0); margin-bottom: -10px; margin-left: -20px; margin-right: -20px; } .career-classic-list > * { margin-top: 10px; padding-left: 20px; padding-right: 20px; } .career-classic-list > li { display: inline-block; vertical-align: middle; white-space: nowrap; } .career-classic-list > li > * { display: inline-block; vertical-align: middle; } .career-classic-list > li > * + * { margin-left: 8px; } .career-classic-list .icon { font-size: 1.25em; color: #0045b6; } * + .career-classic-divider { margin-top: 10px; } * + .career-classic-list { margin-top: 15px; } @media (max-width: 575.98px) { .career-classic { max-width: 360px; margin-left: auto; margin-right: auto; } } @media (min-width: 768px) { * + .career-classic-divider { margin-top: 20px; } * + .career-classic-list { margin-top: 20px; } } /* +* +* Comments +*/ .comment-classic { display: flex; align-items: flex-start; text-align: left; padding-bottom: 20px; border-bottom: 1px solid #e8e9ee; } .comment-classic > * + * { margin-left: 20px; } .comment-classic-figure { position: relative; flex-shrink: 0; width: 48px; height: 48px; } .comment-classic-image { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); min-height: 100%; min-width: 100%; width: auto; height: auto; border-radius: 50%; max-width: none; } @supports (object-fit: cover) { .comment-classic-image { left: 0; top: 0; width: 100%; height: 100%; transform: none; object-fit: cover; object-position: center center; } } .comment-classic-main { flex-grow: 1; } .comment-classic-name { color: #0045b6; } .comment-classic-text { color: #495057; } .comment-classic-meta { display: flex; align-items: center; justify-content: space-between; } .comment-classic-meta time { color: #74757f; } .comment-classic-reply { font-size: 24px; line-height: 1; color: #aeb1be; } .comment-classic-reply::before { content: '\f549'; font-family: 'Material Design Icons'; } .comment-classic-reply:hover { color: #0045b6; } * + .comment-classic-text { margin-top: 8px; } * + .comment-classic-meta { margin-top: 5px; } @media (min-width: 576px) { .comment-classic > * + * { margin-left: 25px; } .comment-classic-reply { font-size: 28px; } } .comment-classic-group .comment-classic-group { margin-left: 25px; } .comment-classic-group * + .comment-classic-group { margin-top: 30px; } * + .comment-classic-group { margin-top: 50px; } .comment-classic-group + .comment-classic-group { margin-top: 30px; } @media (min-width: 768px) { .comment-classic-group .comment-classic-group { margin-left: 75px; } } /* +* +* Jumbotron Classic +*/ .jumbotron-classic.bg-gray-700 h6, .jumbotron-classic.bg-gray-700 .heading-6 { color: #fff; } .jumbotron-classic.parallax-container { background-position: 60% center; } .jumbotron-classic .parallax-content { width: 100%; } .jumbotron-classic-content { max-width: 650px; display: flex; flex-direction: column; justify-content: center; height: 100%; } .jumbotron-classic-content .title-docor-text:before { top: 50%; } @media (max-width: 1599.98px) { .jumbotron-classic-content { padding: 120px 0; } } @media (max-width: 767.98px) { .jumbotron-classic-content { padding: 60px 0 80px 0; } } @media (max-width: 1199.98px) { .jumbotron-classic.parallax-container::before { content: ''; position: absolute; top: 0; right: 0; bottom: 0; left: 0; pointer-events: none; background: linear-gradient(to right, rgba(73, 80, 87, 0.6), rgba(73, 80, 87, 0.2)); } } @media (min-width: 768px) { .jumbotron-classic-inner { padding: 200px 0 140px; } } @media (min-width: 1600px) { .jumbotron-classic-content { padding: 200px 0 150px; } } .jumbotron-modern { position: relative; display: flex; text-align: center; } .jumbotron-modern-inner { position: relative; z-index: 1; display: flex; align-items: center; width: 100%; padding: 50px 0; } .jumbotron-modern-content { max-width: 770px; margin-left: auto; margin-right: auto; } @media (max-width: 767.98px) { .jumbotron-modern .form-layout-1 { display: inline-block; max-width: 360px; } } @media (max-width: 1199.98px) { .jumbotron-modern::before { content: ''; position: absolute; top: 0; right: 0; bottom: 0; left: 0; pointer-events: none; background: linear-gradient(to bottom, rgba(73, 80, 87, 0.4), rgba(73, 80, 87, 0)); } } @media (min-width: 768px) { .jumbotron-modern-inner { padding: 100px 0 140px; } } @media (min-width: 1200px) { .jumbotron-modern-inner { min-height: 41.66667vw; } } .jumbotron-indigo-header { padding: 40px 0; } .jumbotron-indigo-main { padding: 10px 0 40px; } @media (min-width: 768px) { .jumbotron-indigo-header { padding: 50px 0; } .jumbotron-indigo-main { padding: 50px 0 185px; } } /* +* +* Page layout +*/ .page { position: relative; overflow: hidden; min-height: 100vh; } .page.fadeIn { animation-timing-function: ease-out; } .page.fadeOut { animation-timing-function: ease-in; } [data-x-mode] .page { opacity: 1; } html.boxed body { background: #f5f6fa url(../images/bg-pattern-boxed.png) repeat fixed; } @media (min-width: 1630px) { html.boxed .page { max-width: 1600px; margin-left: auto; margin-right: auto; box-shadow: 0 0 23px 0 rgba(1, 1, 1, 0.1); } html.boxed .rd-navbar-static { max-width: 1600px; margin-left: auto; margin-right: auto; } html.boxed .rd-navbar-static.rd-navbar--is-stuck { max-width: 1600px; width: 100%; left: calc(50% - 1600px); right: calc(50% - 1600px); } } .page-header-navbar { position: relative; z-index: 1080; } .page-header-navbar .rd-navbar-wrap { position: absolute; top: 0; left: 0; right: 0; z-index: 1080; } /* +* +* Text styling +*/ .text-italic { font-style: italic; } .text-normal { font-style: normal; } .text-underline { text-decoration: underline; } .text-strike { text-decoration: line-through; } .font-weight-thin { font-weight: 100; } .font-weight-light { font-weight: 300; } .font-weight-regular { font-weight: 400; } .font-weight-medium { font-weight: 500; } .font-weight-sbold { font-weight: 600; } .font-weight-ubold { font-weight: 900; } .text-spacing-0 { letter-spacing: 0; } /* +* +* Offsets +*/ * + p, * + .list-inline-comma { margin-top: 16px; } h3 + p, .heading-3 + p, h3 + .p, .heading-3 + .p { margin-top: 18px; } h4 + p, .heading-4 + p, h4 + .p, .heading-4 + .p { margin-top: 16px; } * + h1, * + .heading-1 { margin-top: 15px; } h3 + h1, h3 + .heading-1, .heading-3 + h1, .heading-3 + .heading-1 { margin-top: 5px; } h6 + h1, h6 + .heading-1, .heading-6 + h1, .heading-6 + .heading-1 { margin-top: 10px; } * + h4, * + .heading-4 { margin-top: 15px; } * + h5, * + .heading-5 { margin-top: 15px; } h6 + p, .heading-6 + p { margin-top: 22px; } p + h2, p + .heading-2 { margin-top: 15px; } p + p { margin-top: 14px; } img + p { margin-top: 15px; } h3 + p { font-size: 16px; } h3 + img { margin-top: 42px; } p + hr { margin-top: 35px; } * + .row { margin-top: 30px; } * + .big { margin-top: 20px; } * + .text-block { margin-top: 25px; } * + .button, * + .button-outer { margin-top: 25px; } * + .list-sm { margin-top: 25px; } * + form.form-inline { margin-top: 18px; } * + form.rd-form-small { margin-top: 25px; } html * + .offset-top-1 { margin-top: 40px; } html * + .offset-top-2 { margin-top: 50px; } html * + .offset-top-3 { margin-top: 20px; } html * + .offset-top-4 { margin-top: 18px; } html .page * + .offset-top-5 { margin-top: 40px; } h3 + .row, .heading-3 + .row, h3 + .owl-carousel, .heading-3 + .owl-carousel { margin-top: 35px; } .container + .container { margin-top: 35px; } .container + .section { margin-top: 25px; } .row + .row { margin-top: 35px; } .row + .button-lg { margin-top: 30px; } .list-sm + .group-xs { margin-top: 20px; } @media (min-width: 768px) { * + p, * + .list-inline-comma { margin-top: 20px; } * + .big { margin-top: 25px; } * + .button, * + .button-outer { margin-top: 34px; } * + .row { margin-top: 40px; } * + .text-block { margin-top: 30px; } .container + .section { margin-top: 30px; } .row + .button-lg { margin-top: 45px; } html * + .offset-top-2 { margin-top: 50px; } } @media (min-width: 992px) { * + h1, * + .heading-1 { margin-top: 22px; } h3 + p, .heading-3 + p, h3 + .p, .heading-3 + .p { margin-top: 24px; } h3 + .row, .heading-3 + .row, h3 + .owl-carousel, .heading-3 + .owl-carousel { margin-top: 55px; } html * + .offset-top-1 { margin-top: 60px; } html * + .offset-top-3 { margin-top: 30px; } html * + .offset-top-4 { margin-top: 24px; } html .page * + .offset-top-5 { margin-top: 60px; } .progress-linear + .button { margin-top: 55px; } .row + .group { margin-top: 45px; } } @media (min-width: 1200px) { * + .button, * + .button-outer { margin-top: 40px; } * + .rd-form { margin-top: 40px; } h3 + p, .heading-3 + p, h3 + .p, .heading-3 + .p { margin-top: 20px; } html * + .offset-top-1 { margin-top: 70px; } .container + .container { margin-top: 60px; } .row + .row { margin-top: 60px; } } @media (min-width: 1600px) { h3 + .row, .heading-3 + .row, h3 + .owl-carousel, .heading-3 + .owl-carousel { margin-top: 75px; } html * + .offset-top-1 { margin-top: 144px; } html * + .offset-top-2 { margin-top: 50px; } .row + .button-lg { margin-top: 70px; } } .row-0 { margin-bottom: 0px; } .row-0:empty { margin-bottom: 0; } .row-0 > * { margin-bottom: 0px; } .row-10 { margin-bottom: -10px; } .row-10:empty { margin-bottom: 0; } .row-10 > * { margin-bottom: 10px; } .row-x-10 { margin-bottom: -10px; margin-left: -5px; margin-right: -5px; } .row-x-10:empty { margin-bottom: 0; } .row-x-10 > * { margin-bottom: 10px; } .row-x-10 > [class*='col'] { padding-left: 5px; padding-right: 5px; } .row-20 { margin-bottom: -20px; } .row-20:empty { margin-bottom: 0; } .row-20 > * { margin-bottom: 20px; } .row-30 { margin-bottom: -30px; } .row-30:empty { margin-bottom: 0; } .row-30 > * { margin-bottom: 30px; } .row-50 { margin-bottom: -50px; } .row-50:empty { margin-bottom: 0; } .row-50 > * { margin-bottom: 50px; } .row-60 { margin-bottom: -50px; } .row-60:empty { margin-bottom: 0; } .row-60 > * { margin-bottom: 50px; } .row-45 { margin-bottom: -45px; } .row-45:empty { margin-bottom: 0; } .row-45 > * { margin-bottom: 45px; } @media (min-width: 768px) { .row-md-50 { margin-bottom: -50px; } .row-md-50:empty { margin-bottom: 0; } .row-md-50 > * { margin-bottom: 50px; } } @media (min-width: 992px) { .row-md-30 { margin-bottom: -30px; } .row-md-30:empty { margin-bottom: 0; } .row-md-30 > * { margin-bottom: 30px; } } @media (min-width: 1600px) { .row-xxl-70 { margin-bottom: -70px; } .row-xxl-70:empty { margin-bottom: 0; } .row-xxl-70 > * { margin-bottom: 70px; } } /* +* +* Sections +*/ .section-xs { padding: 30px 0; } .section-sm { padding: 35px 0; } .section-sm.section-first { padding-top: 65px; } .section-sm.section-last { padding-bottom: 80px; } .section-md, .section-lg, .section-xl { padding: 50px 0; } .section-1 { padding: 35px 0; } .section-collapse + .section-collapse { padding-top: 0; } .section-collapse:last-child { padding-bottom: 0; } html [class*='section-'].section-bottom-0 { padding-bottom: 0; } @media (min-width: 576px) { .section-xs { padding: 40px 0; } } @media (min-width: 768px) { .section-sm { padding: 45px 0; } .section-sm.section-first { padding-top: 90px; } .section-sm.section-last { padding-bottom: 90px; } .section-md { padding: 60px 0; } .section-lg { padding: 72px 0; } .section-lg.section-first { padding-top: 100px; } .section-lg.section-last { padding-bottom: 100px; } .section-xl { padding: 80px 0; } .section-1 { padding: 60px 0; } } @media (min-width: 992px) { .section-md { padding: 60px 0 70px; } .section-xl { padding: 100px 0; } .section-1 { padding: 80px 0; } } @media (min-width: 1200px) { .section-sm { padding: 60px 0; } .section-sm.section-first { padding-top: 80px; } .section-sm.section-last { padding-bottom: 80px; } } @media (min-width: 1600px) { .section-sm.section-first { padding-top: 120px; } .section-sm.section-last { padding-bottom: 120px; } .section-md { padding: 85px 0 95px; } .section-lg { padding: 120px 0; } .section-lg.section-first { padding-top: 120px; } .section-lg.section-last { padding-bottom: 120px; } .section-xl { padding: 160px 0; } .section-1 { padding: 90px 0 100px; } } .section-single { display: flex; } .section-single::before { content: ''; position: absolute; top: 0; right: 0; bottom: 0; left: 0; pointer-events: none; background: rgba(21, 21, 21, 0.4); } .section-single * + .rd-mailform-wrap { margin-top: 35px; } .section-single * + .countdown-wrap { margin-top: 30px; } .section-single .countdown-wrap + * { margin-top: 35px; } .section-single-dummy { visibility: hidden; pointer-events: none; } .section-single-header { padding: calc(1em + 3vh) 0 calc(1em + 2vh); } .section-single-main { padding: calc(1em + 4vh) 0; } .section-single-footer { padding: calc(1em + 2vh) 0 calc(1em + 3vh); } .section-single-inner { position: relative; z-index: 1; display: flex; flex-direction: column; align-items: center; justify-content: space-between; width: 100%; min-height: 100vh; padding-top: 6vh; } .section-single-inner > * { width: 100%; } @supports (display: grid) { .section-single-inner { display: grid; justify-items: center; align-content: space-between; grid-template-columns: 1fr; } } @media (max-width: 575.98px) { .section-single .section-single-main-content { max-width: 240px; margin-left: auto; margin-right: auto; } } @media (max-width: 767.98px) { .section-single .button-lg { font-size: 15px; padding-left: 25px; padding-right: 25px; } } @media (min-width: 768px) { .section-single::before { display: none; } .section-single-inner { padding-top: 7vh; } } @media (min-width: 1200px) { .section-single * + .countdown-wrap { margin-top: 50px; } .section-single .countdown-wrap + * { margin-top: 50px; } } .section-overlap { position: relative; padding: 40px 0; } .section-overlap .row { flex-wrap: nowrap; flex-direction: column; align-items: flex-end; } .section-overlap [class*='col'] { flex-basis: auto; } .section-overlap * + [class*='col'] { margin-top: 10px; } .section-overlap * + .col-offset-1 { margin-top: 20px; } .section-overlap-image { position: absolute; top: 0; right: 0; bottom: 0; left: 0; background-size: cover; background-position: center center; background-repeat: no-repeat; } .section-overlap-content { position: relative; z-index: 1; } @media (max-width: 767.98px) { .section-overlap-content .container { padding: 20px 15px; background: linear-gradient(to right, rgba(255, 255, 255, 0.9), rgba(255, 255, 255, 0.8)); } .section-overlap-content .container > * { max-width: 380px; } } @media (max-width: 767.98px) and (min-width: 576px) { .section-overlap-content .container { padding: 40px 50px; } } @media (min-width: 768px) and (max-width: 991.98px) { .section-overlap-image::before { content: ''; position: absolute; top: 0; right: 0; bottom: 0; left: 0; z-index: 1; pointer-events: none; background: linear-gradient(to left, rgba(245, 246, 250, 0.4), rgba(245, 246, 250, 0)); } } @media (min-width: 768px) { .section-overlap { padding: 75px 0; } .section-overlap * + [class*='col'] { margin-top: 20px; } .section-overlap * + .col-offset-1 { margin-top: 40px; } .section-overlap-image { background-position: 80% 50%; width: calc(50% - 60px + 35px); } } @media (min-width: 992px) { .section-overlap { padding: 100px 0; } .section-overlap-image { width: calc(50% - -80px - 45px); } .ie-10 .section-overlap .col-lg-7, .ie-11 .section-overlap .col-lg-7 { max-width: 64%; } } @media (min-width: 1200px) { .section-overlap-image { width: calc(50% - -100px - 15px); } } @media (min-width: 1600px) { .section-overlap { padding: 140px 0; } } .section-halfscreen { position: relative; } .section-halfscreen-inner { padding: 40px 0; } .section-halfscreen-image { position: absolute; top: 0; right: 0; bottom: 0; left: 0; background-size: cover; background-position: center center; background-repeat: no-repeat; } .section-halfscreen-image::before { content: ''; position: absolute; top: 0; right: 0; bottom: 0; left: 0; z-index: 1; pointer-events: none; background: linear-gradient(to right, rgba(255, 255, 255, 0.6), rgba(255, 255, 255, 0.4)); } .section-halfscreen-content { position: relative; z-index: 1; } @media (max-width: 767.98px) { .section-halfscreen-content { max-width: 360px; } } @media (min-width: 576px) and (max-width: 767.98px) { .section-halfscreen-image::before { opacity: .8; } } @media (min-width: 768px) { .section-halfscreen-inner { padding: 75px 0; } .section-halfscreen-image { background-position: 45% 50%; left: auto; width: calc(50% - 60px + 35px); } .section-halfscreen-image::before { display: none; } } @media (min-width: 992px) { .section-halfscreen-inner { padding: 100px 0; } .section-halfscreen-image { width: 50%; } } @media (min-width: 1200px) { .section-halfscreen { display: flex; } .section-halfscreen-inner { display: flex; align-items: center; width: 100%; min-height: 35.41667vw; padding: 90px 0; } .section-halfscreen-content { width: 100%; } } /* +* +* Grid modules +*/ .range { display: flex; flex-wrap: wrap; } .cell-inner { width: 100%; padding-right: 15px; padding-left: 15px; } .range > [class*='cell'] { position: relative; width: 100%; min-height: 1px; } .range > [class*='cell'] > .row { margin: 0; } .range > [class*='cell'].container { padding: 0; } .range > [class*='col'] { padding: 0; } @media (min-width: 576px) { .cell-sm-6 { flex: 0 0 50%; max-width: 50%; } .cell-sm-6 .cell-inner { max-width: 270px; } .range > [class*='cell-sm']:nth-child(odd) .cell-inner { margin-left: auto; } .range.flex-sm-row-reverse > [class*='cell-sm']:nth-child(odd) .cell-inner { margin-left: 0; } .range.flex-sm-row-reverse > [class*='cell-sm']:nth-child(even) .cell-inner { margin-left: auto; } .range > [class*='cell-sm'].container { margin: 0; } } @media (min-width: 768px) { .range > [class*='cell-md']:nth-child(odd) .cell-inner { margin-left: auto; } .range.flex-md-row-reverse > [class*='cell-md']:nth-child(odd) .cell-inner { margin-left: 0; } .range.flex-md-row-reverse > [class*='cell-md']:nth-child(even) .cell-inner { margin-left: auto; } .range > [class*='cell-md'].container { margin: 0; } } @media (min-width: 992px) { [class*='cell-lg'] > .row > [class*='col'] { flex-basis: 100%; } .cell-lg-5 { flex: 0 0 calc(50% - 80px); max-width: calc(50% - 80px); } .cell-lg-5 .cell-inner { max-width: 400px; } .cell-lg-6 { flex: 0 0 50%; max-width: 50%; } .cell-lg-6 .cell-inner { max-width: 480px; } .range > [class*='cell-lg']:nth-child(odd) .cell-inner { margin-left: auto; } .range.flex-lg-row-reverse > [class*='cell-lg']:nth-child(odd) .cell-inner { margin-left: 0; } .range.flex-lg-row-reverse > [class*='cell-lg']:nth-child(even) .cell-inner { margin-left: auto; } .range > [class*='cell-lg'].container { margin: 0; } } @media (min-width: 1200px) { .range > [class*='cell-xl'].container { margin: 0; } .range > [class*='cell-xl']:nth-child(odd) .cell-inner { margin-left: auto; } .range .cell-inner { flex-basis: 100%; } .cell-xl-7 { flex: 0 0 calc(50% - -100px); max-width: calc(50% - -100px); } .cell-xl-7 .cell-inner { max-width: 700px; } .cell-lg-6, .cell-xl-6 { flex: 0 0 50%; max-width: 50%; } .cell-lg-6 .cell-inner, .cell-xl-6 .cell-inner { max-width: 600px; } .cell-xl-5 { flex: 0 0 calc(50% - 100px); max-width: calc(50% - 100px); } .cell-xl-5 .cell-inner { max-width: 500px; } .cell-xl-4 { flex: 0 0 calc(50% - 200px); max-width: calc(50% - 200px); } .cell-xl-4 .cell-inner { max-width: 400px; } } @media (min-width: 1600px) { .cell-xxl-6 { flex: 0 0 50%; max-width: 50%; } .cell-xxl-6 .cell-inner { max-width: 600px; } } /* +* +* Backgrounds +*/ .context-dark, .bg-gray-700, .bg-gray-800, .bg-primary, .bg-primary-darker, .context-dark h1, .bg-gray-700 h1, .bg-gray-800 h1, .bg-primary h1, .bg-primary-darker h1, .context-dark h2, .bg-gray-700 h2, .bg-gray-800 h2, .bg-primary h2, .bg-primary-darker h2, .context-dark h3, .bg-gray-700 h3, .bg-gray-800 h3, .bg-primary h3, .bg-primary-darker h3, .context-dark h4, .bg-gray-700 h4, .bg-gray-800 h4, .bg-primary h4, .bg-primary-darker h4, .context-dark h5, .bg-gray-700 h5, .bg-gray-800 h5, .bg-primary h5, .bg-primary-darker h5, .context-dark h6, .bg-gray-700 h6, .bg-gray-800 h6, .bg-primary h6, .bg-primary-darker h6, .context-dark [class^='heading-'], .bg-gray-700 [class^='heading-'], .bg-gray-800 [class^='heading-'], .bg-primary [class^='heading-'], .bg-primary-darker [class^='heading-'] { color: #fff; } .context-dark p a, .bg-gray-700 p a, .bg-gray-800 p a, .bg-primary p a, .bg-primary-darker p a { color: inherit; } .context-dark .big, .bg-gray-700 .big, .bg-gray-800 .big, .bg-primary .big, .bg-primary-darker .big { color: #fff; } .context-dark .brand .brand-logo-dark, .bg-gray-700 .brand .brand-logo-dark, .bg-gray-800 .brand .brand-logo-dark, .bg-primary .brand .brand-logo-dark, .bg-primary-darker .brand .brand-logo-dark { display: none; } .context-dark .brand .brand-logo-light, .bg-gray-700 .brand .brand-logo-light, .bg-gray-800 .brand .brand-logo-light, .bg-primary .brand .brand-logo-light, .bg-primary-darker .brand .brand-logo-light { display: block; } .context-dark .button-primary:hover, .bg-gray-700 .button-primary:hover, .bg-gray-800 .button-primary:hover, .bg-primary .button-primary:hover, .bg-primary-darker .button-primary:hover { background: #4854ed; border-color: #4854ed; } /** +* Light Backgrounds +*/ .bg-gray-100 { background-color: #f5f6fa; } /** +* Dark Backgrounds +*/ .bg-gray-700 { color: #aeb1be; background-color: #232426; } .bg-gray-700 a { color: inherit; } .bg-gray-700 a:hover { color: #fff; } .bg-gray-700 p a:hover { color: #fff; } .bg-gray-800 { background-color: #151515; } /** +* Accent Backgrounds +*/ .bg-primary { background-color: #0045b6; } .bg-primary-darker { background-color: #29293a; } /** +* Background Image +*/ [class*='bg-'] { background-size: cover; background-position: center center; } @media (max-width: 1199.98px) { .bg-image-light { position: relative; } .bg-image-light::before { content: ''; position: absolute; top: 0; right: 0; bottom: 0; left: 0; background: rgba(255, 255, 255, 0.1); } } .bg-image-1 { position: relative; background-position: 30% 100%; } .bg-image-1::before { content: ''; position: absolute; top: 0; right: 0; bottom: 0; left: 0; background: #495057; opacity: .6; } .bg-image-1 > * { position: relative; z-index: 1; } @media (min-width: 576px) { .bg-image-1 { background-position: 72% 100%; } .bg-image-1::before { opacity: .2; } } @media (min-width: 768px) { .bg-image-1 { background-position: 54% 100%; } .bg-image-1::before { display: none; } } @media (min-width: 992px) { html:not(.tablet):not(.mobile) .bg-fixed { background-attachment: fixed; } } .primary-overlay { position: relative; } .primary-overlay:before { position: absolute; content: ''; top: 0; right: 0; bottom: 0; left: 0; background-color: rgba(21, 21, 21, 0.45); pointer-events: none; z-index: 1; display: block; } /* +* +* Utilities custom +*/ .height-fill { position: relative; display: flex; flex-direction: column; align-items: stretch; } .height-fill > * { flex-grow: 1; flex-shrink: 0; } .object-inline { white-space: nowrap; } .object-inline > * { display: inline-block; min-width: 20px; vertical-align: top; white-space: normal; } .object-inline.wow span { display: inline-block; } .object-inline > * + * { margin-left: 11px; } .wow-outer .object-inline span { display: inline-block; } .oh { position: relative; overflow: hidden; } .text-decoration-lines { position: relative; overflow: hidden; width: 100%; text-align: center; } .text-decoration-lines-content { position: relative; display: inline-block; padding: 0 20px; } .text-decoration-lines-content::before, .text-decoration-lines-content::after { content: ''; position: absolute; height: 1px; background: #aeb1be; top: 50%; width: 100vw; } .text-decoration-lines-content::before { left: 0; transform: translate3d(-100%, 0, 0); } .text-decoration-lines-content::after { right: 0; transform: translate3d(100%, 0, 0); } * + .text-decoration-lines { margin-top: 30px; } .text-block > * { margin-left: .125em; margin-right: .125em; } @media (min-width: 992px) { .offset-right-1 { margin-right: 40px; } } .banner { background-position: center center; background-size: cover; } .phone-frame { max-width: 296px; padding: 0 5px; margin-left: auto; margin-right: auto; box-shadow: -46.037px 40.02px 21px 0px rgba(0, 0, 0, 0.02); } @media (max-width: 767.98px) { .phone-frame { display: none; } } .tip-mark { display: inline-flex; align-items: center; justify-content: center; width: 2em; height: 2em; padding-left: 2px; margin-bottom: 5px; font-size: 12px; line-height: 2em; vertical-align: middle; text-align: center; border-radius: 50%; border: 1px solid #e8e9ee; color: #0045b6; } /* +* +* Insets +*/ @media (min-width: 576px) { .inset-left-1 { padding-left: 10px; } } @media (min-width: 768px) { .inset-left-1 { padding-left: 30px; } } @media (min-width: 992px) { .inset-left-1 { padding-left: 0; } } @media (min-width: 992px) and (max-width: 1199.98px) { .inset-left-2 { padding-left: 30px; } } @media (min-width: 576px) { .inset-right-1 { padding-right: 20px; } } @media (min-width: 768px) { .inset-right-1 { padding-right: 30px; } } @media (min-width: 992px) and (max-width: 1199.98px) { .inset-right-1 { padding-right: 0; } } @media (min-width: 992px) { .inset-right-2 { padding-right: 30px; } } @media (min-width: 1200px) { .inset-right-2 { padding-right: 0; } } @media (min-width: 992px) { .inset-right-3 { padding-left: 5px; } } @media (min-width: 1200px) { .inset-right-3 { padding-right: 0; } } /* +* +* Animate.css +*/ .animated { animation-duration: 1s; animation-fill-mode: both; opacity: 1; } .animated.infinite { animation-iteration-count: infinite; } .animated.hinge { animation-duration: 2s; } html:not(.lt-ie10) .not-animated { opacity: 0; } @keyframes fadeIn { 0% { opacity: 0; } 100% { opacity: 1; } } .fadeIn { animation-name: fadeIn; } @keyframes fadeInUp { 0% { opacity: 0; transform: translate3d(0, 100%, 0); } 100% { opacity: 1; transform: none; } } .fadeInUp { animation-name: fadeInUp; } @keyframes fadeInDown { 0% { opacity: 0; transform: translate3d(0, -100%, 0); } 100% { opacity: 1; transform: none; } } .fadeInDown { animation-name: fadeInDown; } @keyframes fadeInLeft { 0% { opacity: 0; transform: translate3d(-100%, 0, 0); } 100% { opacity: 1; transform: none; } } .fadeInLeft { animation-name: fadeInLeft; } @keyframes fadeInRight { 0% { opacity: 0; transform: translate3d(100%, 0, 0); } 100% { opacity: 1; transform: none; } } .fadeInRight { animation-name: fadeInRight; } @keyframes fadeOut { 0% { opacity: 1; } 100% { opacity: 0; } } .fadeOut { animation-name: fadeOut; } @keyframes slideInDown { 0% { transform: translate3d(0, -100%, 0); visibility: visible; } 100% { transform: translate3d(0, 0, 0); } } .slideInDown { animation-name: slideInDown; } @keyframes slideInUp { 0% { transform: translate3d(0, 100%, 0); visibility: visible; } 100% { transform: translate3d(0, 0, 0); } } .slideInUp { animation-name: slideInUp; } @keyframes slideInLeft { 0% { transform: translate3d(-100%, 0, 0); visibility: visible; } 100% { transform: translate3d(0, 0, 0); } } .slideInLeft { animation-name: slideInLeft; } @keyframes slideInRight { 0% { transform: translate3d(100%, 0, 0); visibility: visible; } 100% { transform: translate3d(0, 0, 0); } } .slideInRight { animation-name: slideInRight; } @keyframes slideOutDown { 0% { transform: translate3d(0, 0, 0); } 100% { visibility: hidden; transform: translate3d(0, 100%, 0); } } .slideOutDown { animation-name: slideOutDown; } @keyframes scaleInY { 0% { top: 0; bottom: 100%; } 50% { transform: scale3d(1, 1, 1); } 100% { top: 100%; bottom: auto; transform: scale3d(1, 0, 1); } } @keyframes scaleFadeIn { 0% { height: 0; } 100% { height: 100%; } } .scaleFadeIn { display: flex; overflow: hidden; animation-name: scaleFadeIn; animation-timing-function: cubic-bezier(0.25, 0.46, 0.45, 0.94); } .scaleFadeInWrap { display: flex; align-items: stretch; } .wow-outer { position: relative; overflow: hidden; } @keyframes scaleInVertical { 0% { transform: scale3d(1, 0, 1); } 100% { transform: scale3d(1, 1, 1); } } .scaleInVertical { animation-name: scaleInVertical; } @keyframes fadeInLeftSmall { 0% { opacity: 0; transform: translate3d(-20px, 0, 0); } 100% { opacity: 1; transform: none; } } .fadeInLeftSmall { animation-name: fadeInLeftSmall; } @keyframes fadeInUpSmall { 0% { opacity: 0; transform: translate3d(0, 20px, 0); } 100% { opacity: 1; transform: none; } } .fadeInUpSmall { animation-name: fadeInUpSmall; } @keyframes fadeSlideInDown { 0% { opacity: 0; transform: translate3d(0, -50%, 0); visibility: visible; } 10% { opacity: 1; } 100% { transform: translate3d(0, 0, 0); } } .fadeSlideInDown { animation-name: fadeSlideInDown; } @keyframes fadeSlideInUp { 0% { opacity: 0; transform: translate3d(0, 50%, 0); visibility: visible; } 10% { opacity: 1; } 100% { transform: translate3d(0, 0, 0); } } .fadeSlideInUp { animation-name: fadeSlideInUp; } .wow { backface-visibility: hidden; } /* +* +* Preloader +*/ .preloader { position: fixed; left: 0; top: 0; bottom: 0; right: 0; z-index: 10000; display: flex; flex-direction: column; justify-content: center; align-items: center; padding: 20px; transition: .3s all ease; pointer-events: none; backface-visibility: hidden; } .preloader::before, .preloader::after { content: ''; position: absolute; left: 0; right: 0; transition: .2s linear; transform: translateY(0); pointer-events: none; } .preloader::before { top: 0; bottom: 50%; background: #fff; } .preloader::after { top: 50%; bottom: 0; background: #f5f6fa; } .preloader.loaded::before, .preloader.loaded::after { transition: .4s linear; } .preloader.loaded::before { transform: translateY(-100%); } .preloader.loaded::after { transform: translateY(100%); } .preloader.loaded .preloader-logo, .preloader.loaded .preloader-body { opacity: 0; visibility: hidden; transition: 0s; } .preloader-logo, .preloader-body { transition: 0s .2s; } .preloader-logo { position: relative; z-index: 10; transform: translate3d(0, -50%, 0); padding: 40px; } .preloader-body { position: absolute; top: 50%; transform: translate3d(0, -50%, 0); left: 0; right: 0; z-index: 10; text-align: center; } #loadingProgressG { width: 100vw; height: 5px; overflow: hidden; background: #e8e9ee; border-radius: 6px; margin: auto; } .loadingProgressG { background: #0045b6; margin-top: 0; margin-left: -100vw; animation-name: bounce_loadingProgressG; animation-duration: 2.5s; animation-iteration-count: infinite; animation-timing-function: linear; width: 100vw; height: 5px; transition: .5s; } @keyframes bounce_loadingProgressG { 0% { margin-left: -100vw; } 100% { margin-left: 100vw; } } /* +* +* ToTop +*/ .ui-to-top { position: fixed; right: 15px; bottom: 15px; z-index: 100; width: 48px; height: 48px; border-radius: 6px; color: #fff; background: #0045b6; overflow: hidden; text-align: center; text-decoration: none; transition: transform 0.45s ease-in-out, background 0.3s cubic-bezier(0.2, 1, 0.3, 1); transform: translate3d(0, 100px, 0); } .ui-to-top::before, .ui-to-top::after { content: '\f239'; font-family: 'Material Design Icons'; font-size: 20px; line-height: 48px; vertical-align: middle; transition: transform 0.3s cubic-bezier(0.2, 1, 0.3, 1), opacity 0.3s cubic-bezier(0.2, 1, 0.3, 1); } .ui-to-top::before { display: block; } .ui-to-top::after { position: absolute; width: 100%; top: 50%; left: 0; opacity: 0; transform: translate3d(0, 0, 0); } .ui-to-top:hover::before { opacity: 0; transform: translate3d(0, -30%, 0); } .ui-to-top:hover::after { opacity: 1; transform: translate3d(0, 0, 0) translateY(-50%); } .ui-to-top:hover { color: #fff; background: #4854ed; text-decoration: none; } .ui-to-top:focus { color: #fff; } .ui-to-top.active { transform: translate3d(0, 0, 0); } html.mobile .ui-to-top, html.tablet .ui-to-top { display: none !important; } @media (min-width: 576px) { .ui-to-top { right: 40px; bottom: 40px; } } /* +* +* RD Navbar +*/ @keyframes rd-navbar-slide-down { 0% { transform: translateY(-100%); } 100% { transform: translateY(0); } } @keyframes rd-navbar-slide-up { 0% { transform: translateY(0); } 100% { transform: translateY(-100%); } } .rd-navbar-wrap, .rd-navbar, .rd-menu, .rd-navbar-nav, .rd-navbar-panel, .rd-navbar-static .rd-menu, .rd-navbar-fixed .rd-navbar-nav-wrap, .rd-navbar-fixed .rd-navbar-submenu { transition: 0.35s all cubic-bezier(0.65, 0.05, 0.36, 1); } .rd-navbar--no-transition, .rd-navbar--no-transition * { transition: none !important; } .rd-navbar, .rd-navbar.rd-navbar--is-clone { display: none; } .rd-navbar.rd-navbar-fixed + .rd-navbar.rd-navbar--is-clone, .rd-navbar.rd-navbar-sidebar + .rd-navbar.rd-navbar--is-clone { display: none; } .rd-navbar { display: none; background: #fff; } .rd-navbar-outer { position: relative; z-index: 1080; } .rd-nav-link { font: 400 14px/20px "Poppins", sans-serif; letter-spacing: .08em; text-transform: uppercase; } .rd-navbar-toggle { display: inline-block; position: relative; width: 40px; height: 40px; line-height: 40px; cursor: pointer; color: #495057; background-color: transparent; border: none; display: none; } .rd-navbar-toggle span { position: relative; display: block; margin: auto; transition: .3s all ease; } .rd-navbar-toggle span::after, .rd-navbar-toggle span::before { content: ""; position: absolute; left: 0; top: -6px; transition: .3s all ease; } .rd-navbar-toggle span::after { top: 5px; } .rd-navbar-toggle span::after, .rd-navbar-toggle span::before, .rd-navbar-toggle span { width: 24px; height: 2px; background-color: #495057; backface-visibility: hidden; border-radius: 5; } .rd-navbar-toggle.active span { background: transparent; transition: .01s; } .rd-navbar-toggle.active span::before, .rd-navbar-toggle.active span::after { transform-origin: 50% 50%; top: 0; } .rd-navbar-toggle.active span::before { transform: rotate(45deg); } .rd-navbar-toggle.active span::after { transform: rotate(-45deg); } .rd-navbar-collapse-toggle { background: none; border: none; display: inline-block; padding: 0; outline: none; outline-offset: 0; cursor: pointer; -webkit-appearance: none; display: inline-block; position: relative; width: 40px; height: 40px; line-height: 40px; cursor: pointer; color: #495057; display: none; } .rd-navbar-collapse-toggle::-moz-focus-inner { border: none; padding: 0; } .rd-navbar-collapse-toggle span { top: 50%; margin-top: -2.5px; } .rd-navbar-collapse-toggle span, .rd-navbar-collapse-toggle span::before, .rd-navbar-collapse-toggle span::after { position: absolute; width: 5px; height: 5px; line-height: 5px; text-align: center; background: #495057; left: 50%; margin-left: -2.5px; border-radius: 50%; transition: .3s all ease; } .rd-navbar-collapse-toggle span::before, .rd-navbar-collapse-toggle span::after { content: ''; } .rd-navbar-collapse-toggle span::before { bottom: 100%; margin-bottom: 2.5px; } .rd-navbar-collapse-toggle span::after { top: 100%; margin-top: 2.5px; } .rd-navbar-collapse-toggle.active span { transform: scale(0.7); } .rd-navbar-collapse-toggle.active span::before { transform: translateY(15px); } .rd-navbar-collapse-toggle.active span::after { transform: translateY(-15px); } .rd-navbar-brand a { display: block; } .rd-navbar-search { position: relative; display: inline-flex; } .rd-navbar-search .form-input { padding-right: 50px; } .rd-navbar-search .rd-search-form-submit { position: absolute; top: 0; bottom: 0; right: 0; width: 50px; padding: 0; margin: 0; border: none; cursor: pointer; background-color: transparent; font-size: 20px; color: #aeb1be; transition: .33s; } .rd-navbar-search .rd-search-form-submit:hover { color: #0045b6; } .rd-navbar-search .rd-search-results-live { pointer-events: none; } .rd-navbar-search .rd-search-results-live > * { pointer-events: auto; } .rd-navbar-search-toggle { background: none; border: none; display: inline-block; padding: 0; outline: none; outline-offset: 0; cursor: pointer; -webkit-appearance: none; } .rd-navbar-search-toggle::-moz-focus-inner { border: none; padding: 0; } .rd-navbar-dropdown { display: none; } .rd-navbar-popup { position: absolute; padding: 15px 25px 20px 20px; width: calc(100vw - 10px); border-radius: 6px; opacity: 0; visibility: hidden; transition: .2s; overflow: hidden; background-color: #495057; } .rd-navbar-popup::before { content: ''; position: absolute; right: 0; left: 0; top: 0; bottom: 0; z-index: -1; transform: scale3d(1, 0.1, 1); transform-origin: 50% 0; background: #495057; border-radius: inherit; transition: .5s; } .rd-navbar-popup > * { position: relative; z-index: 1; opacity: 0; visibility: hidden; transition: .5s; transform-origin: 50% 0; transform: scale3d(1, 0.9, 1); } .rd-navbar-popup.active { opacity: 1; visibility: visible; } .rd-navbar-popup.active > * { opacity: 1; visibility: visible; transform: scale3d(1, 1, 1); } .rd-navbar-popup.active::before { transform: scale3d(1, 1, 1); } @media (min-width: 400px) { .rd-navbar-popup { width: 370px; } } @media (min-width: 768px) { .rd-navbar-popup { padding: 40px; } } .context-dark .rd-navbar-toggle, .bg-gray-700 .rd-navbar-toggle, .bg-gray-800 .rd-navbar-toggle, .bg-primary .rd-navbar-toggle, .bg-primary-darker .rd-navbar-toggle { display: inline-block; position: relative; width: 40px; height: 40px; line-height: 40px; cursor: pointer; color: #fff; display: none; } .context-dark .rd-navbar-toggle span, .bg-gray-700 .rd-navbar-toggle span, .bg-gray-800 .rd-navbar-toggle span, .bg-primary .rd-navbar-toggle span, .bg-primary-darker .rd-navbar-toggle span { position: relative; display: block; margin: auto; transition: .3s all ease; } .context-dark .rd-navbar-toggle span::after, .bg-gray-700 .rd-navbar-toggle span::after, .bg-gray-800 .rd-navbar-toggle span::after, .bg-primary .rd-navbar-toggle span::after, .bg-primary-darker .rd-navbar-toggle span::after, .context-dark .rd-navbar-toggle span::before, .bg-gray-700 .rd-navbar-toggle span::before, .bg-gray-800 .rd-navbar-toggle span::before, .bg-primary .rd-navbar-toggle span::before, .bg-primary-darker .rd-navbar-toggle span::before { content: ""; position: absolute; left: 0; top: -6px; transition: .3s all ease; } .context-dark .rd-navbar-toggle span::after, .bg-gray-700 .rd-navbar-toggle span::after, .bg-gray-800 .rd-navbar-toggle span::after, .bg-primary .rd-navbar-toggle span::after, .bg-primary-darker .rd-navbar-toggle span::after { top: 5px; } .context-dark .rd-navbar-toggle span::after, .bg-gray-700 .rd-navbar-toggle span::after, .bg-gray-800 .rd-navbar-toggle span::after, .bg-primary .rd-navbar-toggle span::after, .bg-primary-darker .rd-navbar-toggle span::after, .context-dark .rd-navbar-toggle span::before, .bg-gray-700 .rd-navbar-toggle span::before, .bg-gray-800 .rd-navbar-toggle span::before, .bg-primary .rd-navbar-toggle span::before, .bg-primary-darker .rd-navbar-toggle span::before, .context-dark .rd-navbar-toggle span, .bg-gray-700 .rd-navbar-toggle span, .bg-gray-800 .rd-navbar-toggle span, .bg-primary .rd-navbar-toggle span, .bg-primary-darker .rd-navbar-toggle span { width: 24px; height: 2px; background-color: #fff; backface-visibility: hidden; border-radius: 5; } .context-dark .rd-navbar-toggle.active span, .bg-gray-700 .rd-navbar-toggle.active span, .bg-gray-800 .rd-navbar-toggle.active span, .bg-primary .rd-navbar-toggle.active span, .bg-primary-darker .rd-navbar-toggle.active span { background: transparent; transition: .01s; } .context-dark .rd-navbar-toggle.active span::before, .bg-gray-700 .rd-navbar-toggle.active span::before, .bg-gray-800 .rd-navbar-toggle.active span::before, .bg-primary .rd-navbar-toggle.active span::before, .bg-primary-darker .rd-navbar-toggle.active span::before, .context-dark .rd-navbar-toggle.active span::after, .bg-gray-700 .rd-navbar-toggle.active span::after, .bg-gray-800 .rd-navbar-toggle.active span::after, .bg-primary .rd-navbar-toggle.active span::after, .bg-primary-darker .rd-navbar-toggle.active span::after { transform-origin: 50% 50%; top: 0; } .context-dark .rd-navbar-toggle.active span::before, .bg-gray-700 .rd-navbar-toggle.active span::before, .bg-gray-800 .rd-navbar-toggle.active span::before, .bg-primary .rd-navbar-toggle.active span::before, .bg-primary-darker .rd-navbar-toggle.active span::before { transform: rotate(45deg); } .context-dark .rd-navbar-toggle.active span::after, .bg-gray-700 .rd-navbar-toggle.active span::after, .bg-gray-800 .rd-navbar-toggle.active span::after, .bg-primary .rd-navbar-toggle.active span::after, .bg-primary-darker .rd-navbar-toggle.active span::after { transform: rotate(-45deg); } /* +* @subsection RD Navbar Static +*/ .rd-navbar-static { display: block; position: relative; } .rd-navbar-static .rd-nav-item { display: inline-block; } .rd-navbar-static .rd-nav-item.focus .rd-nav-link, .rd-navbar-static .rd-nav-item.opened .rd-nav-link { color: rgba(255, 255, 255, 0.5); background: transparent; } .rd-navbar-static .rd-nav-item.focus > .rd-navbar-submenu-toggle, .rd-navbar-static .rd-nav-item.opened > .rd-navbar-submenu-toggle { color: #fff; } .rd-navbar-static .rd-nav-item.active .rd-nav-link { color: rgba(255, 255, 255, 0.5); background: transparent; } .rd-navbar-static .rd-nav-item.active > .rd-navbar-submenu-toggle { color: #fff; } .rd-navbar-static .rd-nav-item.focus > .rd-navbar-submenu-toggle::before, .rd-navbar-static .rd-nav-item.opened > .rd-navbar-submenu-toggle::before, .rd-navbar-static .rd-nav-item .rd-nav-link:hover + .rd-navbar-submenu-toggle::before { transform: rotate(180deg); color: rgba(255, 255, 255, 0.5); } .rd-navbar-static .rd-nav-item > .rd-navbar-submenu-toggle { margin-left: 4px; font-family: "Material Design Icons"; font-size: 16px; cursor: pointer; color: #fff; } .rd-navbar-static .rd-nav-item > .rd-navbar-submenu-toggle::before { position: relative; display: inline-block; transition: .22s; content: '\f236'; } .rd-navbar-static .rd-nav-item > .rd-navbar-submenu-toggle:hover { color: rgba(255, 255, 255, 0.4); } .rd-navbar-static .rd-nav-item > .rd-navbar-submenu { margin-top: 20px; } .rd-navbar-static .rd-nav-item + .rd-nav-item { margin-left: 35px; } .rd-navbar-static .rd-nav-link { position: relative; display: inline-block; color: #fff; transition: .25s; padding-bottom: 2px; } .rd-navbar-static .rd-nav-link:hover { color: rgba(255, 255, 255, 0.4); } .rd-navbar-static .rd-menu { position: absolute; z-index: 15; display: block; padding: 16px 27px; margin-top: 22px; margin-left: -27px; visibility: hidden; opacity: 0; text-align: left; box-shadow: 0 5px 10px 1px rgba(20, 20, 28, 0.3); border-radius: 6px; transform: translate3d(0, 30px, 0); } .rd-navbar-static .rd-dropdown-item + .rd-dropdown-item, .rd-navbar-static .rd-megamenu-list-item + .rd-megamenu-list-item { margin-top: 7px; } .rd-navbar-static .rd-navbar-dropdown { position: absolute; left: 0; width: 290px; background: #232426; z-index: 5; } .rd-navbar-static .rd-navbar-dropdown .rd-navbar-dropdown { top: -1px; left: 100%; z-index: 2; margin-top: -15px; margin-left: 15px; transform: translate3d(30px, 0, 0); } .rd-navbar-static .rd-navbar-dropdown .rd-navbar-dropdown.rd-navbar-open-left { left: auto; right: 100%; margin-left: 0; margin-right: 15px; transform: translate3d(-30px, 0, 0); } .rd-navbar-static .rd-dropdown-item.focus .rd-dropdown-link, .rd-navbar-static .rd-dropdown-item.opened .rd-dropdown-link { color: #fff; } .rd-navbar-static .rd-dropdown-link { color: #aeb1be; } .rd-navbar-static .rd-dropdown-link:hover { color: #fff; } .rd-navbar-static .rd-navbar-dropdown li > a, .rd-navbar-static .rd-megamenu-list li > a { position: relative; left: -13px; display: flex; padding: 4px 18px 4px 0; text-align: left; } .rd-navbar-static .rd-navbar-dropdown li > a, .rd-navbar-static .rd-navbar-dropdown li > a:focus, .rd-navbar-static .rd-navbar-dropdown li > a:active, .rd-navbar-static .rd-megamenu-list li > a, .rd-navbar-static .rd-megamenu-list li > a:focus, .rd-navbar-static .rd-megamenu-list li > a:active { color: #aeb1be; } .rd-navbar-static .rd-navbar-dropdown li > a:hover, .rd-navbar-static .rd-megamenu-list li > a:hover { color: #fff; } .rd-navbar-static .rd-navbar-dropdown li > a, .rd-navbar-static .rd-navbar-dropdown li > a::before, .rd-navbar-static .rd-megamenu-list li > a, .rd-navbar-static .rd-megamenu-list li > a::before { transition: all .2s ease-in-out; } .rd-navbar-static .rd-navbar-dropdown li > a::before, .rd-navbar-static .rd-megamenu-list li > a::before { position: relative; top: -1px; left: -6px; display: inline-block; content: '\f238'; font-family: 'Material Design Icons'; font-size: inherit; line-height: inherit; color: #fff; opacity: 0; visibility: hidden; } .rd-navbar-static .rd-navbar-dropdown li.focus > a, .rd-navbar-static .rd-navbar-dropdown li.opened > a, .rd-navbar-static .rd-navbar-dropdown li > a:hover, .rd-navbar-static .rd-megamenu-list li.focus > a, .rd-navbar-static .rd-megamenu-list li.opened > a, .rd-navbar-static .rd-megamenu-list li > a:hover { left: -13px; padding-left: 18px; padding-right: 0; } .rd-navbar-static .rd-navbar-dropdown li.focus > a::before, .rd-navbar-static .rd-navbar-dropdown li.opened > a::before, .rd-navbar-static .rd-navbar-dropdown li > a:hover::before, .rd-navbar-static .rd-megamenu-list li.focus > a::before, .rd-navbar-static .rd-megamenu-list li.opened > a::before, .rd-navbar-static .rd-megamenu-list li > a:hover::before { left: -7px; opacity: 1; visibility: visible; } .rd-navbar-static .rd-navbar-megamenu { display: flex; left: 0; overflow: hidden; max-width: 700px; width: 100%; background: #232426; } @media (min-width: 768px) { .rd-navbar-static .rd-navbar-megamenu { left: 20px; } } .rd-navbar-static .rd-megamenu-item { flex-basis: 50%; flex-grow: 1; } .rd-navbar-static .rd-megamenu-item + .rd-megamenu-item { padding-left: 20px; } .rd-navbar-static .rd-megamenu-item { display: flex; } .rd-navbar-static .rd-megamenu-item .banner { position: relative; display: flex; align-items: flex-end; justify-content: center; text-align: center; min-width: 100%; padding: 35px; } .rd-navbar-static .rd-megamenu-item .banner::before { content: ''; position: absolute; top: 0; right: 0; bottom: 0; left: 0; background: rgba(0, 0, 0, 0.1); } .rd-navbar-static .rd-megamenu-item .banner .button-primary:hover { background: #4854ed; border-color: #4854ed; } .rd-navbar-static .rd-megamenu-item .banner > * { opacity: 0; visibility: hidden; transform: translate(0, -20%); transition: .22s; } .rd-navbar-static .rd-megamenu-item .banner:hover > * { opacity: 1; visibility: visible; transform: translate(0, 0); } .rd-navbar-static .rd-megamenu-item:first-child .banner, .rd-navbar-static .rd-megamenu-item:last-child .banner { margin-top: -16px; margin-right: -27px; margin-bottom: -16px; margin-left: -27px; } .rd-navbar-static .rd-megamenu-title { padding-bottom: 10px; border-bottom: 1px solid #e8e9ee; } .rd-navbar-static .rd-megamenu-list-link { color: #aeb1be; background: transparent; } .rd-navbar-static .rd-megamenu-list-link:hover { color: #fff; background: transparent; } .rd-navbar-static * + .rd-megamenu-list { margin-top: 10px; } .rd-navbar-static .rd-navbar-submenu.focus > .rd-menu, .rd-navbar-static .rd-navbar-submenu.opened > .rd-menu { opacity: 1; visibility: visible; transform: translate3d(0, 0, 0); } .rd-navbar-static .rd-navbar-search.active .rd-search { visibility: visible; opacity: 1; } .rd-navbar-static .rd-navbar-search.active .not-empty ~ .form-input { border-bottom-left-radius: 0; border-bottom-right-radius: 0; } .rd-navbar-static .rd-navbar-search .rd-search { margin-top: 40px; } .rd-navbar-static .rd-navbar-search .form-input { padding-right: 19px; } .rd-navbar-static .rd-navbar-search .rd-search-form-submit { display: none; } .rd-navbar-static .rd-search { position: absolute; top: 100%; right: 0; width: 370px; opacity: 0; visibility: hidden; transition: .3s; margin-top: 20px; z-index: 2; } .rd-navbar-static * + .rd-navbar-search { margin-left: 38px; } .rd-navbar-static .rd-navbar-search-toggle { display: inline-flex; color: #fff; } .rd-navbar-static .rd-navbar-search-toggle span { display: inline-block; position: relative; width: 32px; height: 32px; font-size: 26px; line-height: 32px; text-align: center; cursor: pointer; background: none; border: none; outline: none; padding: 0; } .rd-navbar-static .rd-navbar-search-toggle span, .rd-navbar-static .rd-navbar-search-toggle span::before, .rd-navbar-static .rd-navbar-search-toggle span::after { transition: .3s all ease-in-out; } .rd-navbar-static .rd-navbar-search-toggle span::before, .rd-navbar-static .rd-navbar-search-toggle span::after { position: absolute; top: 0; left: 0; right: 0; bottom: 0; } .rd-navbar-static .rd-navbar-search-toggle span::before { content: ""; transform: rotate(0deg) scale(1); opacity: 1; visibility: visible; font-family: "Material Design Icons"; } .rd-navbar-static .rd-navbar-search-toggle span::after { content: ""; transform: rotate(-90deg) scale(0.4); opacity: 0; visibility: hidden; font-family: "Material Design Icons"; } .rd-navbar-static .rd-navbar-search-toggle.active span::before { opacity: 0; visibility: hidden; transform: rotate(90deg) scale(0.4); } .rd-navbar-static .rd-navbar-search-toggle.active span::after { transform: rotate(0deg) scale(1); opacity: 1; visibility: visible; } .rd-navbar-static .rd-navbar-search-toggle:hover { color: #aeb1be; } @media (min-width: 1200px) { .rd-navbar-static .rd-nav-item + .rd-nav-item { margin-left: 45px; } } .rd-navbar-static.rd-navbar--is-clone { display: block; transform: translate3d(0, -100%, 0); } .rd-navbar-static.rd-navbar--is-clone.rd-navbar--is-stuck { transform: translate3d(0, 0, 0); } .rd-navbar-static.rd-navbar--is-stuck, .rd-navbar-static.rd-navbar--is-clone { position: fixed; left: 0; top: 0; right: 0; z-index: 1080; background: rgba(51, 51, 51, 0.7); will-change: transform; } .rd-navbar-static .rd-navbar--has-dropdown { position: relative; } /* +* +* RD Navbar Fixed +*/ .rd-navbar-fixed { display: block; width: 100%; } .rd-navbar-fixed .rd-navbar-toggle { display: inline-block; } .rd-navbar-fixed .rd-navbar-brand { position: relative; top: -2px; margin-left: 12px; font-size: 0; line-height: 0; text-align: left; } .rd-navbar-fixed .rd-navbar-brand img { max-width: 160px; max-height: 58px; width: auto; height: auto; } .rd-navbar-fixed .rd-navbar-panel { position: fixed; left: 0; top: 0; right: 0; z-index: 1080; display: flex; align-items: center; height: 56px; padding: 8px; } .rd-navbar-fixed .rd-navbar-nav-wrap { position: fixed; left: 0; top: -56px; bottom: -56px; z-index: 1079; width: 270px; padding: 112px 0 81px; overflow-y: auto; overflow-x: hidden; -webkit-overflow-scrolling: touch; font-size: 14px; line-height: 34px; transform: translateX(-110%); } .rd-navbar-fixed .rd-navbar-nav-wrap::-webkit-scrollbar { width: 4px; } .rd-navbar-fixed .rd-navbar-nav-wrap::-webkit-scrollbar-thumb { background: #1d73ff; border: none; border-radius: 0; opacity: .2; } .rd-navbar-fixed .rd-navbar-nav-wrap::-webkit-scrollbar-track { background: #0045b6; border: none; border-radius: 0; } .rd-navbar-fixed .rd-navbar-nav-wrap.active { transform: translateX(0); } .rd-navbar-fixed .rd-navbar-nav { display: block; margin: 15px 0; height: auto; text-align: left; } .rd-navbar-fixed .rd-nav-item { text-align: left; } .rd-navbar-fixed .rd-nav-item + .rd-nav-item { margin-top: 4px; } .rd-navbar-fixed .rd-nav-link { display: block; padding: 14px 56px 14px 18px; } .rd-navbar-fixed li.opened > .rd-navbar-dropdown { padding: 4px 0 0; } .rd-navbar-fixed li.opened > .rd-navbar-megamenu { padding-top: 15px; padding-bottom: 15px; } .rd-navbar-fixed li.opened > .rd-menu { opacity: 1; height: auto; } .rd-navbar-fixed li.opened > .rd-navbar-submenu-toggle::after { transform: rotate(180deg); } .rd-navbar-fixed .rd-menu { display: none; transition: opacity 0.3s, height 0.4s ease; opacity: 0; height: 0; overflow: hidden; } .rd-navbar-fixed .rd-navbar-submenu { position: relative; } .rd-navbar-fixed .rd-navbar-submenu .rd-navbar-dropdown .rd-navbar-submenu-toggle:after { height: 34px; line-height: 34px; } .rd-navbar-fixed .rd-navbar-submenu .rd-navbar-dropdown > li > a { padding-left: 30px; } .rd-navbar-fixed .rd-navbar-submenu .rd-navbar-dropdown li li > a, .rd-navbar-fixed .rd-navbar-submenu .rd-navbar-megamenu ul li li > a { padding-left: 46px; } .rd-navbar-fixed .rd-navbar-submenu.opened > .rd-navbar-dropdown, .rd-navbar-fixed .rd-navbar-submenu.opened > .rd-navbar-megamenu { display: block; } .rd-navbar-fixed .rd-megamenu-list > li > a, .rd-navbar-fixed .rd-navbar-dropdown > li > a { display: block; padding: 9px 56px 9px 16px; font-size: 14px; line-height: 1.5; } .rd-navbar-fixed .rd-megamenu-list > li + li, .rd-navbar-fixed .rd-navbar-dropdown > li + li { margin-top: 3px; } .rd-navbar-fixed .rd-megamenu-list > li > a { padding-left: 30px; } .rd-navbar-fixed .rd-navbar-megamenu .rd-megamenu-title { position: relative; display: block; padding: 0 18px 7px; font-size: 16px; line-height: 1.5; } .rd-navbar-fixed .rd-navbar-megamenu .rd-megamenu-title::after { content: ''; position: absolute; left: 20px; right: 20px; bottom: 0; border-bottom: 1px solid; } .rd-navbar-fixed .rd-navbar-megamenu * + .rd-megamenu-list { margin-top: 11px; } .rd-navbar-fixed .rd-navbar-megamenu * + .rd-megamenu-title { margin-top: 20px; } .rd-navbar-fixed .rd-navbar-megamenu > li + li { margin-top: 20px; } .rd-navbar-fixed .rd-navbar-submenu-toggle { cursor: pointer; } .rd-navbar-fixed .rd-navbar-submenu-toggle::after { content: '\f107'; position: absolute; top: 0; right: 0; width: 56px; height: 48px; font: 400 14px "FontAwesome"; line-height: 48px; text-align: center; transition: 0.4s all ease; z-index: 2; cursor: pointer; } .rd-navbar-fixed .rd-navbar-collapse-toggle { display: block; top: 8px; z-index: 1081; } .rd-navbar-fixed .rd-navbar-collapse-content { position: fixed; right: 0; top: 56px; z-index: 1079; transform: translate3d(0, -10px, 0); padding: 30px; width: auto; border-radius: 0 0 0 6px; text-align: left; font-size: 14px; opacity: 0; visibility: hidden; transition: .3s; } .rd-navbar-fixed .rd-navbar-collapse-content.active { transform: translate3d(0, 0, 0); opacity: 1; visibility: visible; } .rd-navbar-fixed .rd-navbar-collapse-content > * + * { margin-top: 25px; } .rd-navbar-fixed .rd-navbar-main-outer, .rd-navbar-fixed .rd-navbar-main-element { position: absolute; float: left; width: 100%; } .rd-navbar-fixed .rd-navbar-search .rd-search { position: fixed; right: 0; left: 0; top: 56px; z-index: 1079; width: 100%; opacity: 0; visibility: hidden; transition: .3s; transform: translate3d(0, -100%, 0); } .rd-navbar-fixed .rd-navbar-search .rd-search-results-live { display: none; } .rd-navbar-fixed .rd-navbar-search .form-input { padding-right: 60px; } .rd-navbar-fixed .rd-navbar-search .rd-search-form-submit { position: absolute; top: 0; bottom: 0; right: 7px; width: 50px; padding: 0; margin: 0; border: none; background-color: transparent; } .rd-navbar-fixed .rd-navbar-search.active .rd-search { opacity: 1; visibility: visible; transform: none; } .rd-navbar-fixed .rd-navbar-search-toggle { display: inline-flex; } .rd-navbar-fixed .rd-navbar-search-toggle span { display: inline-block; position: relative; width: 48px; height: 48px; font-size: 26px; line-height: 48px; text-align: center; cursor: pointer; background: none; border: none; outline: none; padding: 0; } .rd-navbar-fixed .rd-navbar-search-toggle span, .rd-navbar-fixed .rd-navbar-search-toggle span::before, .rd-navbar-fixed .rd-navbar-search-toggle span::after { transition: .3s all ease-in-out; } .rd-navbar-fixed .rd-navbar-search-toggle span::before, .rd-navbar-fixed .rd-navbar-search-toggle span::after { position: absolute; top: 0; left: 0; right: 0; bottom: 0; } .rd-navbar-fixed .rd-navbar-search-toggle span::before { content: ""; transform: rotate(0deg) scale(1); opacity: 1; visibility: visible; font-family: "Material Design Icons"; } .rd-navbar-fixed .rd-navbar-search-toggle span::after { content: ""; transform: rotate(-90deg) scale(0.4); opacity: 0; visibility: hidden; font-family: "Material Design Icons"; } .rd-navbar-fixed .rd-navbar-search-toggle.active span::before { opacity: 0; visibility: hidden; transform: rotate(90deg) scale(0.4); } .rd-navbar-fixed .rd-navbar-search-toggle.active span::after { transform: rotate(0deg) scale(1); opacity: 1; visibility: visible; } .rd-navbar-fixed [class*='rd-navbar-fixed-element'] { position: fixed; top: 8px; z-index: 1082; } .rd-navbar-fixed .rd-navbar-fixed-element-1 { right: 0; } .rd-navbar-fixed .rd-navbar-fixed-element-2 { right: 46px; } .rd-navbar-fixed.rd-navbar--is-clone { display: none; } .rd-navbar-fixed .rd-navbar-fixed--visible { display: block; } .rd-navbar-fixed .rd-navbar-fixed--hidden { display: none; } .rd-navbar-fixed .rd-navbar-panel { color: #495057; box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.05); background: #fff; } .rd-navbar-fixed .rd-navbar-nav-wrap { color: #fff; background: #fff; border-right: 1px solid #e8e9ee; box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.05); } .rd-navbar-fixed .rd-nav-item:hover .rd-nav-link, .rd-navbar-fixed .rd-nav-item.focus .rd-nav-link, .rd-navbar-fixed .rd-nav-item.active .rd-nav-link, .rd-navbar-fixed .rd-nav-item.opened .rd-nav-link { color: #fff; background: #0045b6; } .rd-navbar-fixed .rd-nav-item:hover > .rd-navbar-submenu-toggle, .rd-navbar-fixed .rd-nav-item.focus > .rd-navbar-submenu-toggle, .rd-navbar-fixed .rd-nav-item.active > .rd-navbar-submenu-toggle, .rd-navbar-fixed .rd-nav-item.opened > .rd-navbar-submenu-toggle { color: #fff; } .rd-navbar-fixed .rd-nav-link { color: #495057; } .rd-navbar-fixed .rd-megamenu-list > li > a, .rd-navbar-fixed .rd-navbar-dropdown > li > a { color: #495057; } .rd-navbar-fixed .rd-megamenu-list > li:hover > a, .rd-navbar-fixed .rd-megamenu-list > li.focus > a, .rd-navbar-fixed .rd-megamenu-list > li.active > a, .rd-navbar-fixed .rd-megamenu-list > li.opened > a, .rd-navbar-fixed .rd-navbar-dropdown > li:hover > a, .rd-navbar-fixed .rd-navbar-dropdown > li.focus > a, .rd-navbar-fixed .rd-navbar-dropdown > li.active > a, .rd-navbar-fixed .rd-navbar-dropdown > li.opened > a { color: #fff; background: #0045b6; } .rd-navbar-fixed .rd-navbar-megamenu .rd-megamenu-title { color: #aeb1be; } .rd-navbar-fixed .rd-navbar-megamenu .rd-megamenu-title::after { border-bottom: 1px solid #e8e9ee; } .rd-navbar-fixed .rd-navbar-megamenu .rd-megamenu-title a, .rd-navbar-fixed .rd-navbar-megamenu .rd-megamenu-title a:focus, .rd-navbar-fixed .rd-navbar-megamenu .rd-megamenu-title a:active { color: #fff; } .rd-navbar-fixed .rd-navbar-megamenu .rd-megamenu-title a:hover { color: #0045b6; } .rd-navbar-fixed .rd-navbar-submenu-toggle { color: #495057; } .rd-navbar-fixed .rd-navbar-search .rd-search-form-submit { color: #aeb1be; } .rd-navbar-fixed .rd-navbar-search .rd-search-form-submit:hover { color: #0045b6; } .rd-navbar-fixed .rd-navbar-search-toggle { color: #495057; } .rd-navbar-fixed .rd-navbar-search-toggle:hover { color: #0045b6; } .rd-navbar-fixed .rd-navbar-collapse-content { background-color: #fff; box-shadow: 0 0 22px -4px rgba(0, 0, 0, 0.17); } .rd-navbar-fixed .banner { position: relative; overflow: hidden; padding: 20px; margin: 10px; border-radius: 6px; text-align: center; } .rd-navbar-fixed .banner::before { content: ''; position: absolute; top: 0; right: 0; bottom: 0; left: 0; background: rgba(73, 80, 87, 0.4); border-radius: inherit; } html.rd-navbar-fixed-linked .page { padding-top: 56px; } .context-dark .rd-navbar-fixed .rd-navbar-panel, .bg-gray-700 .rd-navbar-fixed .rd-navbar-panel, .bg-gray-800 .rd-navbar-fixed .rd-navbar-panel, .bg-primary .rd-navbar-fixed .rd-navbar-panel, .bg-primary-darker .rd-navbar-fixed .rd-navbar-panel { background: #495057; } .context-dark .rd-navbar-fixed .rd-navbar-nav-wrap, .bg-gray-700 .rd-navbar-fixed .rd-navbar-nav-wrap, .bg-gray-800 .rd-navbar-fixed .rd-navbar-nav-wrap, .bg-primary .rd-navbar-fixed .rd-navbar-nav-wrap, .bg-primary-darker .rd-navbar-fixed .rd-navbar-nav-wrap { border-right-color: #495057; background: #444b51; } .context-dark .rd-navbar-fixed .rd-nav-link, .bg-gray-700 .rd-navbar-fixed .rd-nav-link, .bg-gray-800 .rd-navbar-fixed .rd-nav-link, .bg-primary .rd-navbar-fixed .rd-nav-link, .bg-primary-darker .rd-navbar-fixed .rd-nav-link, .context-dark .rd-navbar-fixed .rd-navbar-submenu-toggle, .bg-gray-700 .rd-navbar-fixed .rd-navbar-submenu-toggle, .bg-gray-800 .rd-navbar-fixed .rd-navbar-submenu-toggle, .bg-primary .rd-navbar-fixed .rd-navbar-submenu-toggle, .bg-primary-darker .rd-navbar-fixed .rd-navbar-submenu-toggle { color: #fff; } .context-dark .rd-navbar-fixed .rd-navbar-dropdown > li > a, .bg-gray-700 .rd-navbar-fixed .rd-navbar-dropdown > li > a, .bg-gray-800 .rd-navbar-fixed .rd-navbar-dropdown > li > a, .bg-primary .rd-navbar-fixed .rd-navbar-dropdown > li > a, .bg-primary-darker .rd-navbar-fixed .rd-navbar-dropdown > li > a, .context-dark .rd-navbar-fixed .rd-megamenu-list-link, .bg-gray-700 .rd-navbar-fixed .rd-megamenu-list-link, .bg-gray-800 .rd-navbar-fixed .rd-megamenu-list-link, .bg-primary .rd-navbar-fixed .rd-megamenu-list-link, .bg-primary-darker .rd-navbar-fixed .rd-megamenu-list-link { color: #e8e9ee; } /* +* +* RD Navbar Sidebar +*/ .rd-navbar-sidebar { display: block; background: #fff; } .rd-navbar-sidebar .rd-navbar-main-outer { padding-left: 15px; padding-right: 15px; } .rd-navbar-sidebar .rd-navbar-main { position: relative; display: flex; align-items: center; justify-content: space-between; padding: 22px 60px 22px 0; max-width: 1200px; margin-left: auto; margin-right: auto; transition: 0.5s cubic-bezier(0.4, 0, 0.2, 1); } .rd-navbar-sidebar .rd-navbar-main-element { display: flex; align-items: center; } .rd-navbar-sidebar .rd-navbar-main-element > * + * { margin-left: 20px; } .rd-navbar-sidebar .rd-navbar-toggle { position: absolute; right: 0; top: 50%; transform: translateY(-50%); display: block; z-index: 1102; } .rd-navbar-sidebar .rd-navbar-panel { min-width: 200px; text-align: center; } .rd-navbar-sidebar .rd-navbar-brand img { width: auto; height: auto; max-width: 200px; max-height: 200px; } .rd-navbar-sidebar .rd-navbar-nav-wrap { position: fixed; top: 0; bottom: 0; right: 0; z-index: 1101; width: 450px; padding: 94px 0 0 0; background: #fff; transition: 0.5s cubic-bezier(0.4, 0, 0.2, 1); transform: translateX(100%); box-shadow: 0 0 13px 0 rgba(174, 177, 190, 0.16); } .rd-navbar-sidebar .rd-navbar-nav-wrap.active { transition-delay: .1s; transform: translateX(0); } .rd-navbar-sidebar .rd-navbar-nav { height: calc(100vh - 100px); overflow-x: hidden; overflow-y: auto; -webkit-overflow-scrolling: touch; border-top: 1px solid #e8e9ee; } .rd-navbar-sidebar .rd-nav-item { position: relative; display: block; padding: 19px 30px 19px 40px; } .rd-navbar-sidebar .rd-nav-item.active .rd-nav-link { color: #0045b6; } .rd-navbar-sidebar .rd-nav-item .rd-nav-link, .rd-navbar-sidebar .rd-nav-item > .rd-navbar-submenu-toggle { font-size: 18px; } .rd-navbar-sidebar .rd-nav-item + .rd-nav-item { border-top: 1px solid #e8e9ee; } .rd-navbar-sidebar .rd-nav-item:last-child { border-bottom: 1px solid #e8e9ee; margin-bottom: 30px; } .rd-navbar-sidebar .rd-nav-link { max-width: calc(100% - 30px); word-break: break-all; font-size: 18px; line-height: 1.4; text-transform: none; color: #aeb1be; } .rd-navbar-sidebar .rd-nav-link:hover { color: #0045b6; } .rd-navbar-sidebar .rd-navbar-submenu .opened > .rd-navbar-submenu-toggle::after { transform: rotate(180deg); } .rd-navbar-sidebar .rd-navbar-submenu > .rd-navbar-dropdown, .rd-navbar-sidebar .rd-navbar-submenu > .rd-navbar-megamenu { display: none; opacity: 0; visibility: hidden; will-change: opacity, visibility; transition: opacity .2s; } .rd-navbar-sidebar .rd-navbar-submenu.opened > .rd-navbar-dropdown, .rd-navbar-sidebar .rd-navbar-submenu.opened > .rd-navbar-megamenu { display: block; opacity: 1; visibility: visible; } .rd-navbar-sidebar .rd-navbar-submenu > .rd-navbar-submenu > .rd-navbar-dropdown, .rd-navbar-sidebar .rd-navbar-submenu > .rd-navbar-submenu > .rd-navbar-megamenu { transform: translateY(30px); } .rd-navbar-sidebar .rd-navbar-submenu > .rd-navbar-submenu.opened > .rd-navbar-dropdown, .rd-navbar-sidebar .rd-navbar-submenu > .rd-navbar-submenu.opened > .rd-navbar-megamenu { transform: translateY(0); } .rd-navbar-sidebar .rd-navbar-submenu > .rd-navbar-submenu .rd-navbar-submenu > .rd-navbar-dropdown { transform: translateX(-20px); } .rd-navbar-sidebar .rd-navbar-submenu > .rd-navbar-submenu .rd-navbar-submenu.opened > .rd-navbar-dropdown { transform: translateX(0); } .rd-navbar-sidebar .rd-nav-item > .rd-navbar-submenu-toggle, .rd-navbar-sidebar .rd-nav-item .rd-navbar--has-dropdown > .rd-navbar-submenu-toggle { position: absolute; top: 0; right: 0; padding-top: inherit; padding-right: inherit; padding-left: 10px; margin-bottom: inherit; display: inline-block; width: 30px; margin-left: 5px; text-align: center; cursor: pointer; color: #495057; } .rd-navbar-sidebar .rd-nav-item > .rd-navbar-submenu-toggle:hover, .rd-navbar-sidebar .rd-nav-item .rd-navbar--has-dropdown > .rd-navbar-submenu-toggle:hover { color: #0045b6; } .rd-navbar-sidebar .rd-nav-item > .rd-navbar-submenu-toggle::after, .rd-navbar-sidebar .rd-nav-item .rd-navbar--has-dropdown > .rd-navbar-submenu-toggle::after { content: '\f107'; position: relative; display: inline-block; font-family: "FontAwesome"; font-size: inherit; line-height: inherit; text-align: center; vertical-align: middle; transition: 0.4s all ease; z-index: 2; will-change: transform; } .rd-navbar-sidebar .rd-nav-item .rd-navbar--has-dropdown > .rd-navbar-submenu-toggle { font-size: 14px; color: #aeb1be; margin-left: 7px; } .rd-navbar-sidebar .rd-menu { margin-top: 31px; } .rd-navbar-sidebar .rd-navbar-dropdown, .rd-navbar-sidebar .rd-megamenu-list { font-size: 14px; } .rd-navbar-sidebar .rd-navbar-dropdown li > a, .rd-navbar-sidebar .rd-megamenu-list li > a { position: relative; left: -13px; display: flex; padding: 4px 18px 4px 0; text-align: left; } .rd-navbar-sidebar .rd-navbar-dropdown li > a, .rd-navbar-sidebar .rd-navbar-dropdown li > a:focus, .rd-navbar-sidebar .rd-navbar-dropdown li > a:active, .rd-navbar-sidebar .rd-megamenu-list li > a, .rd-navbar-sidebar .rd-megamenu-list li > a:focus, .rd-navbar-sidebar .rd-megamenu-list li > a:active { color: #aeb1be; } .rd-navbar-sidebar .rd-navbar-dropdown li > a:hover, .rd-navbar-sidebar .rd-megamenu-list li > a:hover { color: #fff; } .rd-navbar-sidebar .rd-navbar-dropdown li > a, .rd-navbar-sidebar .rd-navbar-dropdown li > a::before, .rd-navbar-sidebar .rd-megamenu-list li > a, .rd-navbar-sidebar .rd-megamenu-list li > a::before { transition: all .2s ease-in-out; } .rd-navbar-sidebar .rd-navbar-dropdown li > a::before, .rd-navbar-sidebar .rd-megamenu-list li > a::before { position: relative; top: -1px; left: -6px; display: inline-block; content: '\f238'; font-family: 'Material Design Icons'; font-size: inherit; line-height: inherit; color: #fff; opacity: 0; visibility: hidden; } .rd-navbar-sidebar .rd-navbar-dropdown li.focus > a, .rd-navbar-sidebar .rd-navbar-dropdown li.opened > a, .rd-navbar-sidebar .rd-navbar-dropdown li > a:hover, .rd-navbar-sidebar .rd-megamenu-list li.focus > a, .rd-navbar-sidebar .rd-megamenu-list li.opened > a, .rd-navbar-sidebar .rd-megamenu-list li > a:hover { left: -13px; padding-left: 18px; padding-right: 0; } .rd-navbar-sidebar .rd-navbar-dropdown li.focus > a::before, .rd-navbar-sidebar .rd-navbar-dropdown li.opened > a::before, .rd-navbar-sidebar .rd-navbar-dropdown li > a:hover::before, .rd-navbar-sidebar .rd-megamenu-list li.focus > a::before, .rd-navbar-sidebar .rd-megamenu-list li.opened > a::before, .rd-navbar-sidebar .rd-megamenu-list li > a:hover::before { left: -7px; opacity: 1; visibility: visible; } .rd-navbar-sidebar .rd-navbar-megamenu { max-width: 450px; margin-bottom: -30px; } .rd-navbar-sidebar .rd-navbar-megamenu > * { margin-bottom: 30px; } .rd-navbar-sidebar .rd-navbar-megamenu > li { display: inline-block; vertical-align: top; width: 45%; } .rd-navbar-sidebar .rd-megamenu-title { display: none; } .rd-navbar-sidebar .rd-megamenu-list { margin-top: 0; } .rd-navbar-sidebar .rd-megamenu-list > li + li { margin-top: 10px; } .rd-navbar-sidebar .rd-navbar-dropdown .rd-navbar--has-dropdown > a { padding-right: 0; } .rd-navbar-sidebar .rd-navbar-dropdown .rd-navbar--has-dropdown > a::before { display: none; } .rd-navbar-sidebar .rd-navbar-dropdown .rd-navbar--has-dropdown > a:hover { padding-left: 0; } .rd-navbar-sidebar .rd-navbar-dropdown .rd-navbar--has-dropdown.focus > a { padding-left: 0; } .rd-navbar-sidebar .rd-navbar-dropdown .rd-navbar--has-dropdown.opened > .rd-navbar-submenu-toggle { color: #0045b6; } .rd-navbar-sidebar .rd-navbar-dropdown .rd-navbar--has-dropdown.opened > .rd-navbar-submenu-toggle::after { top: 1px; } .rd-navbar-sidebar .rd-navbar-dropdown > li + li { margin-top: 10px; } .rd-navbar-sidebar .rd-nav-item > .rd-navbar-dropdown { margin-top: 25px; } .rd-navbar-sidebar .rd-nav-item > .rd-navbar-dropdown .rd-navbar-dropdown { margin-top: 10px; padding-left: 15px; } .rd-navbar-sidebar .rd-nav-item > .rd-navbar-dropdown .rd-navbar-dropdown > li > a { font-size: 12px; } @media (min-width: 1200px) { .rd-navbar-sidebar .rd-navbar-megamenu { margin-bottom: -30px; } .rd-navbar-sidebar .rd-navbar-megamenu > * { margin-bottom: 30px; } .rd-navbar-sidebar .rd-navbar-dropdown > li + li, .rd-navbar-sidebar .rd-megamenu-list > li + li { margin-top: 18px; } } @media (min-width: 1200px) { .rd-navbar-sidebar .rd-navbar-nav-wrap { width: auto; left: calc(50% + 600px - 80px); } } .rd-navbar-sidebar.rd-navbar--is-clone { position: fixed; top: 0; left: 0; right: 0; z-index: 1000; transform: translateY(-101%); } .rd-navbar-sidebar.rd-navbar--is-clone.rd-navbar--is-stuck { transform: translateY(0); } .rd-navbar-sidebar.rd-navbar--is-stuck { position: fixed; left: 0; right: 0; top: 0; z-index: 1000; box-shadow: 0 2px 10px 1px rgba(0, 0, 0, 0.1); } .rd-navbar-sidebar.rd-navbar--is-stuck .rd-navbar-main { padding-top: 11px; padding-bottom: 11px; } /** +* +* RD Navbar Minimal +* +*/ .rd-navbar-minimal.rd-navbar-static { border-bottom: 1px solid transparent; } .rd-navbar-minimal.rd-navbar-static .rd-navbar-main-outer { padding-left: 15px; padding-right: 15px; } .rd-navbar-minimal.rd-navbar-static .rd-navbar-main { position: relative; display: flex; align-items: center; justify-content: space-between; max-width: 1170px; padding: 20px 0; margin-left: auto; margin-right: auto; } .rd-navbar-minimal.rd-navbar-static .rd-navbar-main-element { position: relative; display: flex; align-items: center; justify-content: space-between; min-width: 690px; } .rd-navbar-minimal.rd-navbar-static .rd-navbar-brand img { width: auto; height: auto; max-width: 200px; max-height: 200px; } .rd-navbar-minimal.rd-navbar-static .rd-navbar-megamenu { margin-top: 24px; width: calc(100% + 27px + 15px); } .rd-navbar-minimal.rd-navbar-static .rd-navbar-search-toggle span { font-size: 20px; } @media (min-width: 1200px) { .rd-navbar-minimal.rd-navbar-static .rd-navbar-main { padding: 30px 0; } .rd-navbar-minimal.rd-navbar-static .rd-menu { margin-top: 34px; } } .rd-navbar-minimal.rd-navbar-static.rd-navbar--is-stuck, .rd-navbar-minimal.rd-navbar-static.rd-navbar--is-clone { border-bottom-color: #e8e9ee; } .rd-navbar-minimal.rd-navbar-static.rd-navbar--is-stuck .rd-navbar-main, .rd-navbar-minimal.rd-navbar-static.rd-navbar--is-clone .rd-navbar-main { padding: 20px 0; } .rd-navbar-minimal.rd-navbar-static.rd-navbar--is-stuck .rd-menu, .rd-navbar-minimal.rd-navbar-static.rd-navbar--is-clone .rd-menu { margin-top: 24px; } .rd-navbar-minimal.rd-navbar-static.rd-navbar--is-stuck .rd-navbar-nav-item > .rd-navbar-submenu, .rd-navbar-minimal.rd-navbar-static.rd-navbar--is-clone .rd-navbar-nav-item > .rd-navbar-submenu { margin-top: 17px; } .rd-navbar-minimal.rd-navbar-fixed .rd-navbar-search-toggle { position: fixed; right: 10px; top: 5px; } .rd-navbar-minimal.rd-navbar-fixed .rd-navbar-search .form-input { border-width: 1px 0 1px 0; border-radius: 0; } .rd-navbar-minimal-wide.rd-navbar-static .rd-navbar-main { padding: 54px 0; } .rd-navbar-minimal-wide.rd-navbar-static.rd-navbar--is-stuck .rd-navbar-main { padding: 21px 0; } .context-dark .rd-navbar-minimal .rd-nav-link, .bg-gray-700 .rd-navbar-minimal .rd-nav-link, .bg-gray-800 .rd-navbar-minimal .rd-nav-link, .bg-primary .rd-navbar-minimal .rd-nav-link, .bg-primary-darker .rd-navbar-minimal .rd-nav-link, .context-dark .rd-navbar-minimal .rd-navbar-submenu-toggle, .bg-gray-700 .rd-navbar-minimal .rd-navbar-submenu-toggle, .bg-gray-800 .rd-navbar-minimal .rd-navbar-submenu-toggle, .bg-primary .rd-navbar-minimal .rd-navbar-submenu-toggle, .bg-primary-darker .rd-navbar-minimal .rd-navbar-submenu-toggle, .context-dark .rd-navbar-minimal .rd-navbar-search-toggle, .bg-gray-700 .rd-navbar-minimal .rd-navbar-search-toggle, .bg-gray-800 .rd-navbar-minimal .rd-navbar-search-toggle, .bg-primary .rd-navbar-minimal .rd-navbar-search-toggle, .bg-primary-darker .rd-navbar-minimal .rd-navbar-search-toggle { color: #fff; } .context-dark .rd-navbar-minimal.rd-navbar-static, .bg-gray-700 .rd-navbar-minimal.rd-navbar-static, .bg-gray-800 .rd-navbar-minimal.rd-navbar-static, .bg-primary .rd-navbar-minimal.rd-navbar-static, .bg-primary-darker .rd-navbar-minimal.rd-navbar-static { background-color: transparent; } .context-dark .rd-navbar-minimal.rd-navbar-static .rd-nav-item.opened .rd-nav-link, .bg-gray-700 .rd-navbar-minimal.rd-navbar-static .rd-nav-item.opened .rd-nav-link, .bg-gray-800 .rd-navbar-minimal.rd-navbar-static .rd-nav-item.opened .rd-nav-link, .bg-primary .rd-navbar-minimal.rd-navbar-static .rd-nav-item.opened .rd-nav-link, .bg-primary-darker .rd-navbar-minimal.rd-navbar-static .rd-nav-item.opened .rd-nav-link, .context-dark .rd-navbar-minimal.rd-navbar-static .rd-nav-item.focus .rd-nav-link, .bg-gray-700 .rd-navbar-minimal.rd-navbar-static .rd-nav-item.focus .rd-nav-link, .bg-gray-800 .rd-navbar-minimal.rd-navbar-static .rd-nav-item.focus .rd-nav-link, .bg-primary .rd-navbar-minimal.rd-navbar-static .rd-nav-item.focus .rd-nav-link, .bg-primary-darker .rd-navbar-minimal.rd-navbar-static .rd-nav-item.focus .rd-nav-link, .context-dark .rd-navbar-minimal.rd-navbar-static .rd-nav-item.active .rd-nav-link, .bg-gray-700 .rd-navbar-minimal.rd-navbar-static .rd-nav-item.active .rd-nav-link, .bg-gray-800 .rd-navbar-minimal.rd-navbar-static .rd-nav-item.active .rd-nav-link, .bg-primary .rd-navbar-minimal.rd-navbar-static .rd-nav-item.active .rd-nav-link, .bg-primary-darker .rd-navbar-minimal.rd-navbar-static .rd-nav-item.active .rd-nav-link, .context-dark .rd-navbar-minimal.rd-navbar-static .rd-nav-item.opened .rd-navbar-submenu-toggle, .bg-gray-700 .rd-navbar-minimal.rd-navbar-static .rd-nav-item.opened .rd-navbar-submenu-toggle, .bg-gray-800 .rd-navbar-minimal.rd-navbar-static .rd-nav-item.opened .rd-navbar-submenu-toggle, .bg-primary .rd-navbar-minimal.rd-navbar-static .rd-nav-item.opened .rd-navbar-submenu-toggle, .bg-primary-darker .rd-navbar-minimal.rd-navbar-static .rd-nav-item.opened .rd-navbar-submenu-toggle, .context-dark .rd-navbar-minimal.rd-navbar-static .rd-nav-item.focus .rd-navbar-submenu-toggle, .bg-gray-700 .rd-navbar-minimal.rd-navbar-static .rd-nav-item.focus .rd-navbar-submenu-toggle, .bg-gray-800 .rd-navbar-minimal.rd-navbar-static .rd-nav-item.focus .rd-navbar-submenu-toggle, .bg-primary .rd-navbar-minimal.rd-navbar-static .rd-nav-item.focus .rd-navbar-submenu-toggle, .bg-primary-darker .rd-navbar-minimal.rd-navbar-static .rd-nav-item.focus .rd-navbar-submenu-toggle, .context-dark .rd-navbar-minimal.rd-navbar-static .rd-nav-item.active .rd-navbar-submenu-toggle, .bg-gray-700 .rd-navbar-minimal.rd-navbar-static .rd-nav-item.active .rd-navbar-submenu-toggle, .bg-gray-800 .rd-navbar-minimal.rd-navbar-static .rd-nav-item.active .rd-navbar-submenu-toggle, .bg-primary .rd-navbar-minimal.rd-navbar-static .rd-nav-item.active .rd-navbar-submenu-toggle, .bg-primary-darker .rd-navbar-minimal.rd-navbar-static .rd-nav-item.active .rd-navbar-submenu-toggle, .context-dark .rd-navbar-minimal.rd-navbar-static .rd-nav-link:hover, .bg-gray-700 .rd-navbar-minimal.rd-navbar-static .rd-nav-link:hover, .bg-gray-800 .rd-navbar-minimal.rd-navbar-static .rd-nav-link:hover, .bg-primary .rd-navbar-minimal.rd-navbar-static .rd-nav-link:hover, .bg-primary-darker .rd-navbar-minimal.rd-navbar-static .rd-nav-link:hover, .context-dark .rd-navbar-minimal.rd-navbar-static .rd-nav-link:hover ~ .rd-navbar-submenu-toggle, .bg-gray-700 .rd-navbar-minimal.rd-navbar-static .rd-nav-link:hover ~ .rd-navbar-submenu-toggle, .bg-gray-800 .rd-navbar-minimal.rd-navbar-static .rd-nav-link:hover ~ .rd-navbar-submenu-toggle, .bg-primary .rd-navbar-minimal.rd-navbar-static .rd-nav-link:hover ~ .rd-navbar-submenu-toggle, .bg-primary-darker .rd-navbar-minimal.rd-navbar-static .rd-nav-link:hover ~ .rd-navbar-submenu-toggle, .context-dark .rd-navbar-minimal.rd-navbar-static .rd-navbar-search-toggle:hover, .bg-gray-700 .rd-navbar-minimal.rd-navbar-static .rd-navbar-search-toggle:hover, .bg-gray-800 .rd-navbar-minimal.rd-navbar-static .rd-navbar-search-toggle:hover, .bg-primary .rd-navbar-minimal.rd-navbar-static .rd-navbar-search-toggle:hover, .bg-primary-darker .rd-navbar-minimal.rd-navbar-static .rd-navbar-search-toggle:hover { color: #1d73ff; } .context-dark .rd-navbar-minimal.rd-navbar-static.rd-navbar--is-stuck, .bg-gray-700 .rd-navbar-minimal.rd-navbar-static.rd-navbar--is-stuck, .bg-gray-800 .rd-navbar-minimal.rd-navbar-static.rd-navbar--is-stuck, .bg-primary .rd-navbar-minimal.rd-navbar-static.rd-navbar--is-stuck, .bg-primary-darker .rd-navbar-minimal.rd-navbar-static.rd-navbar--is-stuck { background: #495057; border-bottom-color: #151515; } /** +* +* RD Navbar Corporate +* +*/ .rd-navbar-corporate.rd-navbar-static .rd-navbar-aside-outer, .rd-navbar-corporate.rd-navbar-static .rd-navbar-main-outer { padding-left: 15px; padding-right: 15px; } .rd-navbar-corporate.rd-navbar-static .rd-navbar-aside, .rd-navbar-corporate.rd-navbar-static .rd-navbar-main { max-width: 1170px; margin-left: auto; margin-right: auto; } .rd-navbar-corporate.rd-navbar-static .rd-navbar-aside { display: flex; align-items: center; justify-content: space-between; padding: 30px 0 40px; } .rd-navbar-corporate.rd-navbar-static .rd-navbar-main-outer { background: rgba(51, 51, 51, 0.1); position: absolute; top: 100%; z-index: 10; width: 100%; } .rd-navbar-corporate.rd-navbar-static .rd-navbar-main { position: relative; padding: 16px 0; } .rd-navbar-corporate.rd-navbar-static .rd-navbar-nav-wrap { position: relative; display: flex; flex-direction: row-reverse; align-items: center; justify-content: space-between; min-width: 690px; } .ie-10 .rd-navbar-corporate.rd-navbar-static .rd-navbar-collapse, .ie-11 .rd-navbar-corporate.rd-navbar-static .rd-navbar-collapse { width: 100%; max-width: 870px; } .ie-10 .rd-navbar-corporate.rd-navbar-static .unit, .ie-11 .rd-navbar-corporate.rd-navbar-static .unit { flex: auto; } .rd-navbar-corporate.rd-navbar-static .rd-navbar-panel + .rd-navbar-collapse { margin-left: 50px; } .rd-navbar-corporate.rd-navbar-static .rd-navbar-collapse-content { display: flex; align-items: center; justify-content: center; } .rd-navbar-corporate.rd-navbar-static .rd-navbar-collapse-content .button { flex-shrink: 0; } .rd-navbar-corporate.rd-navbar-static .rd-navbar-collapse-content > * { margin-top: 0; } .rd-navbar-corporate.rd-navbar-static .rd-navbar-collapse-content > * + * { margin-left: 50px; } .rd-navbar-corporate.rd-navbar-static .rd-menu { margin-top: 21px; } .rd-navbar-corporate.rd-navbar-static .rd-navbar-megamenu { margin-top: 24px; } @media (min-width: 1200px) { .rd-navbar-corporate.rd-navbar-static .rd-navbar-collapse-content > * + * { margin-left: 92px; } .rd-navbar-corporate.rd-navbar-static .rd-nav-item + .rd-nav-item { margin-left: 55px; } } .rd-navbar-corporate.rd-navbar-static.rd-navbar--is-stuck, .rd-navbar-corporate.rd-navbar-static.rd-navbar--is-clone { border-bottom-color: #e8e9ee; } .rd-navbar-corporate.rd-navbar-static.rd-navbar--is-stuck .rd-navbar-aside-outer, .rd-navbar-corporate.rd-navbar-static.rd-navbar--is-clone .rd-navbar-aside-outer { display: none; } .rd-navbar-corporate.rd-navbar-static.rd-navbar--is-stuck .rd-navbar-main-outer, .rd-navbar-corporate.rd-navbar-static.rd-navbar--is-clone .rd-navbar-main-outer { background: #232426; box-shadow: 0 5px 10px 0px rgba(20, 20, 28, 0.3); } .rd-navbar-corporate.rd-navbar-static.rd-navbar--is-stuck .rd-navbar-nav-item > .rd-navbar-submenu, .rd-navbar-corporate.rd-navbar-static.rd-navbar--is-clone .rd-navbar-nav-item > .rd-navbar-submenu { margin-top: 17px; } .rd-navbar-corporate.rd-navbar-fixed .rd-navbar-search { margin: 20px 5px; } .rd-navbar-corporate.rd-navbar-fixed .rd-navbar-search-toggle { display: none; } .rd-navbar-corporate.rd-navbar-fixed .rd-search { position: relative; top: 0; left: 0; transform: none; opacity: 1; visibility: visible; z-index: 1; } .rd-navbar-corporate.rd-navbar-fixed .rd-navbar-nav { margin: 0; } /** +* +* RD Navbar Thin +* +*/ .rd-navbar-thin.rd-navbar-static { background-color: #495057; border-bottom: 1px solid transparent; } .rd-navbar-thin.rd-navbar-static .rd-navbar-main-outer { padding-left: 15px; padding-right: 15px; } .rd-navbar-thin.rd-navbar-static .rd-navbar-main { position: relative; display: flex; align-items: center; justify-content: space-between; max-width: 1170px; padding: 23px 0; margin-left: auto; margin-right: auto; } .rd-navbar-thin.rd-navbar-static .rd-navbar-nav-wrap { position: absolute; top: 0; bottom: 0; left: 200px; right: 80px; z-index: 102; display: flex; align-items: center; justify-content: flex-end; min-width: 690px; background: #495057; opacity: 0; visibility: hidden; transition: .44s ease-in-out; } .rd-navbar-thin.rd-navbar-static .rd-navbar-nav-wrap .rd-navbar-brand { display: none; } .rd-navbar-thin.rd-navbar-static .rd-navbar-nav-wrap.active { opacity: 1; visibility: visible; } .rd-navbar-thin.rd-navbar-static .rd-navbar-panel { display: flex; align-items: center; justify-content: space-between; width: 100%; padding-right: 50px; } .ie-10 .rd-navbar-thin.rd-navbar-static .rd-navbar-panel, .ie-11 .rd-navbar-thin.rd-navbar-static .rd-navbar-panel { justify-content: flex-start; } .ie-10 .rd-navbar-thin.rd-navbar-static .rd-navbar-block, .ie-11 .rd-navbar-thin.rd-navbar-static .rd-navbar-block { width: 100%; justify-content: flex-end; } .rd-navbar-thin.rd-navbar-static .rd-navbar-toggle { position: absolute; right: 0; top: 50%; display: block; transform: translate3d(0, -50%, 0); } .rd-navbar-thin.rd-navbar-static .rd-navbar-block { display: flex; align-items: center; } .rd-navbar-thin.rd-navbar-static .rd-navbar-brand { max-width: 300px; } .rd-navbar-thin.rd-navbar-static .rd-navbar-brand img { width: auto; height: auto; max-width: 200px; max-height: 200px; } .rd-navbar-thin.rd-navbar-static .rd-nav-link, .rd-navbar-thin.rd-navbar-static .rd-navbar-submenu-toggle, .rd-navbar-thin.rd-navbar-static .rd-navbar-search-toggle { color: #fff; } .rd-navbar-thin.rd-navbar-static .rd-nav-item.opened .rd-nav-link, .rd-navbar-thin.rd-navbar-static .rd-nav-item.focus .rd-nav-link, .rd-navbar-thin.rd-navbar-static .rd-nav-item.active .rd-nav-link, .rd-navbar-thin.rd-navbar-static .rd-nav-item.opened .rd-navbar-submenu-toggle, .rd-navbar-thin.rd-navbar-static .rd-nav-item.focus .rd-navbar-submenu-toggle, .rd-navbar-thin.rd-navbar-static .rd-nav-item.active .rd-navbar-submenu-toggle, .rd-navbar-thin.rd-navbar-static .rd-nav-link:hover, .rd-navbar-thin.rd-navbar-static .rd-nav-link:hover ~ .rd-navbar-submenu-toggle, .rd-navbar-thin.rd-navbar-static .rd-navbar-search-toggle:hover { color: #1d73ff; } .rd-navbar-thin.rd-navbar-static .rd-menu { margin-top: 27px; } .rd-navbar-thin.rd-navbar-static .rd-navbar-megamenu { margin-top: 24px; width: calc(100% + 27px + 15px); } .rd-navbar-thin.rd-navbar-static .rd-navbar-search { position: static; } .rd-navbar-thin.rd-navbar-static * + .rd-navbar-search-toggle { margin-left: 60px; } .rd-navbar-thin.rd-navbar-static .rd-search { position: absolute; top: 0; bottom: 0; left: 0; right: 0; display: flex; width: calc(100% - 70px); margin: 0; } .rd-navbar-thin.rd-navbar-static .rd-search .form-wrap { display: flex; width: 100%; background: #495057; } .rd-navbar-thin.rd-navbar-static .rd-search .form-wrap::before { content: '\f43b'; position: absolute; top: 50%; transform: translate3d(0, -50%, 0); font-family: 'Material Design Icons'; font-size: 30px; line-height: 1; color: #fff; } .rd-navbar-thin.rd-navbar-static .rd-search .form-input, .rd-navbar-thin.rd-navbar-static .rd-search .form-label { padding-left: 40px; font-size: 30px; color: #fff; } .rd-navbar-thin.rd-navbar-static .rd-search .form-input { background-color: transparent; border: 0; border-radius: 0; } .rd-navbar-thin.rd-navbar-static .rd-search .form-label { top: 50%; transform: translate3d(0, -50%, 0); } .rd-navbar-thin.rd-navbar-static .rd-search-results-live { height: 100vh; margin: 1px 0 0 0; } .rd-navbar-thin.rd-navbar-static .search-list { display: flex; flex-wrap: wrap; margin-bottom: -20px; } .rd-navbar-thin.rd-navbar-static .search-list-item { width: 25%; padding-left: 15px; padding-right: 15px; margin-bottom: 20px; } .rd-navbar-thin.rd-navbar-static .search-list-item + .search-list-item { margin-top: 0; } .rd-navbar-thin.rd-navbar-static .rd-navbar-search-toggle { position: relative; z-index: 12; } .rd-navbar-thin.rd-navbar-static .rd-navbar-popup { left: 50%; z-index: 110; transform: translateX(-50%); margin-top: 30px; } .rd-navbar-thin.rd-navbar-static .list-inline-bordered { position: relative; } @media (min-width: 992px) and (max-width: 1199.98px) { .rd-navbar-thin.rd-navbar-static .rd-nav-item + .rd-nav-item { margin-left: 30px; } } @media (min-width: 1200px) { .rd-navbar-thin.rd-navbar-static .rd-navbar-panel { padding-right: 70px; } .rd-navbar-thin.rd-navbar-static .rd-navbar-nav-wrap { left: 300px; right: 70px; } } @media (min-width: 1600px) { .rd-navbar-thin.rd-navbar-static .rd-navbar-popup { right: auto; } } .rd-navbar-thin.rd-navbar-static.rd-navbar--is-stuck, .rd-navbar-thin.rd-navbar-static.rd-navbar--is-clone { border-bottom-color: #151515; } .rd-navbar-thin.rd-navbar-static.rd-navbar--is-stuck .rd-menu, .rd-navbar-thin.rd-navbar-static.rd-navbar--is-clone .rd-menu { margin-top: 27px; } .rd-navbar-thin.rd-navbar-fixed .rd-navbar-search .form-input { border-width: 0 0 1px 0; border-radius: 0; } .rd-navbar-thin.rd-navbar-fixed .rd-navbar-search-toggle { position: fixed; top: 5px; right: 8px; color: #fff; } .rd-navbar-thin.rd-navbar-fixed .rd-navbar-search-toggle span { font-size: 20px; } .rd-navbar-thin.rd-navbar-fixed .rd-navbar-block { position: absolute; top: 14px; right: 65px; } .rd-navbar-thin.rd-navbar-fixed .rd-navbar-popup { position: fixed; right: 5px; margin-top: 20px; overflow-y: auto; max-height: calc(100vh - 56px - 10px); } .rd-navbar-thin.rd-navbar-fixed .rd-navbar-brand { display: none; } .rd-navbar-thin.rd-navbar-fixed .rd-navbar-nav-wrap .rd-navbar-brand { display: inline-block; line-height: 0; font-size: 0; margin: 30px 20px 20px; } @media (min-width: 480px) { .rd-navbar-thin.rd-navbar-fixed .rd-navbar-brand { display: block; } .rd-navbar-thin.rd-navbar-fixed .rd-navbar-nav-wrap .rd-navbar-brand { display: none; } } /* +* +* Swiper +*/ .swiper-container { position: relative; display: flex; margin: 0 auto; overflow: hidden; list-style: none; padding: 0; /* Fix of Webkit flickering */ z-index: 1; pointer-events: none; } .swiper-container, .swiper-container * { backface-visibility: hidden; } .swiper-container-no-flexbox .swiper-slide { float: left; } .swiper-container-vertical > .swiper-wrapper { flex-direction: column; } .swiper-wrapper { position: relative; z-index: 1; display: flex; align-self: stretch; align-items: stretch; width: 100%; height: auto; min-height: inherit; transition-property: transform; box-sizing: content-box; } .swiper-container-android .swiper-slide, .swiper-wrapper { transform: translate3d(0px, 0, 0); } .swiper-container-multirow > .swiper-wrapper { flex-wrap: wrap; } .swiper-container-free-mode > .swiper-wrapper { transition-timing-function: ease-out; margin: 0 auto; } .swiper-slide { flex-shrink: 0; width: 100%; height: 100%; position: relative; background-size: cover; transition-property: transform, transform; pointer-events: auto; contain: paint; } .swiper-invisible-blank-slide { visibility: hidden; } /* Auto Height */ .swiper-container-autoheight, .swiper-container-autoheight .swiper-slide { height: auto; } .swiper-container-autoheight .swiper-wrapper { align-items: flex-start; transition-property: transform, height; } /* 3D Effects */ .swiper-container-3d { perspective: 1200px; } .swiper-container-3d .swiper-wrapper, .swiper-container-3d .swiper-slide, .swiper-container-3d .swiper-slide-shadow-left, .swiper-container-3d .swiper-slide-shadow-right, .swiper-container-3d .swiper-slide-shadow-top, .swiper-container-3d .swiper-slide-shadow-bottom, .swiper-container-3d .swiper-cube-shadow { transform-style: preserve-3d; } .swiper-container-3d .swiper-slide-shadow-left, .swiper-container-3d .swiper-slide-shadow-right, .swiper-container-3d .swiper-slide-shadow-top, .swiper-container-3d .swiper-slide-shadow-bottom { position: absolute; left: 0; top: 0; width: 100%; height: 100%; pointer-events: none; z-index: 10; } .swiper-container-3d .swiper-slide-shadow-left { background-image: linear-gradient(to left, rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0)); } .swiper-container-3d .swiper-slide-shadow-right { background-image: linear-gradient(to right, rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0)); } .swiper-container-3d .swiper-slide-shadow-top { background-image: linear-gradient(to top, rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0)); } .swiper-container-3d .swiper-slide-shadow-bottom { background-image: linear-gradient(to bottom, rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0)); } /* IE10 Windows Phone 8 Fixes */ .swiper-container-wp8-horizontal, .swiper-container-wp8-horizontal > .swiper-wrapper { touch-action: pan-y; } .swiper-container-wp8-vertical, .swiper-container-wp8-vertical > .swiper-wrapper { touch-action: pan-x; } .swiper-button-prev, .swiper-button-next { position: absolute; top: 50%; width: 27px; height: 44px; margin-top: -22px; z-index: 10; cursor: pointer; background-size: 27px 44px; background-position: center; background-repeat: no-repeat; } .swiper-button-prev.swiper-button-disabled, .swiper-button-next.swiper-button-disabled { opacity: 0.35; cursor: auto; pointer-events: none; } .swiper-button-lock { display: none; } .swiper-pagination { position: absolute; text-align: center; transition: 300ms opacity; transform: translate3d(0, 0, 0); z-index: 10; } .swiper-pagination.swiper-pagination-hidden { opacity: 0; } /* Common Styles */ .swiper-pagination-fraction, .swiper-pagination-custom, .swiper-container-horizontal > .swiper-pagination-bullets { bottom: 10px; left: 0; width: 100%; } /* Bullets */ .swiper-pagination-bullets-dynamic { overflow: hidden; font-size: 0; } .swiper-pagination-bullets-dynamic .swiper-pagination-bullet { transform: scale(0.33); position: relative; } .swiper-pagination-bullets-dynamic .swiper-pagination-bullet-active { transform: scale(1); } .swiper-pagination-bullets-dynamic .swiper-pagination-bullet-active-prev { transform: scale(0.66); } .swiper-pagination-bullets-dynamic .swiper-pagination-bullet-active-prev-prev { transform: scale(0.33); } .swiper-pagination-bullets-dynamic .swiper-pagination-bullet-active-next { transform: scale(0.66); } .swiper-pagination-bullets-dynamic .swiper-pagination-bullet-active-next-next { transform: scale(0.33); } .swiper-pagination-bullet { width: 8px; height: 8px; display: inline-block; border-radius: 100%; background: #000; } button.swiper-pagination-bullet { border: none; margin: 0; padding: 0; box-shadow: none; -webkit-appearance: none; -moz-appearance: none; appearance: none; } .swiper-pagination-clickable .swiper-pagination-bullet { cursor: pointer; } .swiper-pagination-bullet-active { opacity: 1; background: #007aff; } .swiper-container-vertical > .swiper-pagination-bullets { right: 10px; top: 50%; transform: translate3d(0px, -50%, 0); } .swiper-container-vertical > .swiper-pagination-bullets .swiper-pagination-bullet { margin: 6px 0; display: block; } .swiper-container-vertical > .swiper-pagination-bullets.swiper-pagination-bullets-dynamic { top: 50%; transform: translateY(-50%); width: 8px; } .swiper-container-vertical > .swiper-pagination-bullets.swiper-pagination-bullets-dynamic .swiper-pagination-bullet { display: inline-block; transition: 200ms transform, 200ms top; } .swiper-container-horizontal > .swiper-pagination-bullets .swiper-pagination-bullet { margin: 0 4px; } .swiper-container-horizontal > .swiper-pagination-bullets.swiper-pagination-bullets-dynamic { left: 50%; transform: translateX(-50%); white-space: nowrap; } .swiper-container-horizontal > .swiper-pagination-bullets.swiper-pagination-bullets-dynamic .swiper-pagination-bullet { transition: 200ms transform, 200ms left; } .swiper-container-horizontal.swiper-container-rtl > .swiper-pagination-bullets-dynamic .swiper-pagination-bullet { transition: 200ms transform, 200ms right; } /* Progress */ .swiper-pagination-progressbar { background: rgba(0, 0, 0, 0.25); position: absolute; } .swiper-pagination-progressbar .swiper-pagination-progressbar-fill { background: rgba(51, 51, 51, 0.3); position: absolute; left: 0; top: 0; width: 100%; height: 100%; transform: scale(0); transform-origin: left top; } .swiper-container-rtl .swiper-pagination-progressbar .swiper-pagination-progressbar-fill { transform-origin: right top; } .swiper-container-horizontal > .swiper-pagination-progressbar { width: 100%; height: 4px; left: 0; top: 0; } .swiper-container-vertical > .swiper-pagination-progressbar { width: 4px; height: 100%; left: 0; top: 0; } .swiper-pagination-white .swiper-pagination-bullet-active { background: #fff; } .swiper-pagination-progressbar.swiper-pagination-white { background: rgba(255, 255, 255, 0.25); } .swiper-pagination-progressbar.swiper-pagination-white .swiper-pagination-progressbar-fill { background: #fff; } .swiper-pagination-black .swiper-pagination-bullet-active { background: #000; } .swiper-pagination-progressbar.swiper-pagination-black { background: rgba(0, 0, 0, 0.25); } .swiper-pagination-progressbar.swiper-pagination-black .swiper-pagination-progressbar-fill { background: #000; } .swiper-pagination-lock { display: none; } /* Scrollbar */ .swiper-scrollbar { border-radius: 10px; position: relative; -ms-touch-action: none; background: rgba(0, 0, 0, 0.1); } .swiper-container-horizontal > .swiper-scrollbar { position: absolute; left: 1%; bottom: 3px; z-index: 50; height: 5px; width: 98%; } .swiper-container-vertical > .swiper-scrollbar { position: absolute; right: 3px; top: 1%; z-index: 50; width: 5px; height: 98%; } .swiper-scrollbar-drag { height: 100%; width: 100%; position: relative; background: rgba(0, 0, 0, 0.5); border-radius: 10px; left: 0; top: 0; } .swiper-scrollbar-cursor-drag { cursor: move; } .swiper-scrollbar-lock { display: none; } .swiper-zoom-container { display: flex; justify-content: center; align-items: center; width: 100%; height: 100%; text-align: center; } .swiper-zoom-container > img, .swiper-zoom-container > svg, .swiper-zoom-container > canvas { max-width: 100%; max-height: 100%; -o-object-fit: contain; object-fit: contain; } .swiper-slide-zoomed { cursor: move; } /* Preloader */ .swiper-lazy-preloader { width: 42px; height: 42px; position: absolute; left: 50%; top: 50%; margin-left: -21px; margin-top: -21px; z-index: 10; transform-origin: 50%; animation: swiper-preloader-spin 1s steps(12, end) infinite; } .swiper-lazy-preloader:after { display: block; content: ''; width: 100%; height: 100%; background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg%20viewBox%3D'0%200%20120%20120'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20xmlns%3Axlink%3D'http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink'%3E%3Cdefs%3E%3Cline%20id%3D'l'%20x1%3D'60'%20x2%3D'60'%20y1%3D'7'%20y2%3D'27'%20stroke%3D'%236c6c6c'%20stroke-width%3D'11'%20stroke-linecap%3D'round'%2F%3E%3C%2Fdefs%3E%3Cg%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.27'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.27'%20transform%3D'rotate(30%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.27'%20transform%3D'rotate(60%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.27'%20transform%3D'rotate(90%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.27'%20transform%3D'rotate(120%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.27'%20transform%3D'rotate(150%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.37'%20transform%3D'rotate(180%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.46'%20transform%3D'rotate(210%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.56'%20transform%3D'rotate(240%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.66'%20transform%3D'rotate(270%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.75'%20transform%3D'rotate(300%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.85'%20transform%3D'rotate(330%2060%2C60)'%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E"); background-position: 50%; background-size: 100%; background-repeat: no-repeat; } .swiper-lazy-preloader-white:after { background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg%20viewBox%3D'0%200%20120%20120'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20xmlns%3Axlink%3D'http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink'%3E%3Cdefs%3E%3Cline%20id%3D'l'%20x1%3D'60'%20x2%3D'60'%20y1%3D'7'%20y2%3D'27'%20stroke%3D'%23fff'%20stroke-width%3D'11'%20stroke-linecap%3D'round'%2F%3E%3C%2Fdefs%3E%3Cg%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.27'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.27'%20transform%3D'rotate(30%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.27'%20transform%3D'rotate(60%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.27'%20transform%3D'rotate(90%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.27'%20transform%3D'rotate(120%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.27'%20transform%3D'rotate(150%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.37'%20transform%3D'rotate(180%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.46'%20transform%3D'rotate(210%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.56'%20transform%3D'rotate(240%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.66'%20transform%3D'rotate(270%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.75'%20transform%3D'rotate(300%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.85'%20transform%3D'rotate(330%2060%2C60)'%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E"); } @keyframes swiper-preloader-spin { 100% { transform: rotate(360deg); } } /* a11y */ .swiper-container .swiper-notification { position: absolute; left: 0; top: 0; pointer-events: none; opacity: 0; z-index: -1000; } .swiper-container-fade .swiper-container-free-mode .swiper-slide { transition-timing-function: ease-out; } .swiper-container-fade .swiper-slide { pointer-events: none; transition-property: opacity; } .swiper-container-fade .swiper-slide .swiper-slide { pointer-events: none; } .swiper-container-fade .swiper-slide-active, .swiper-container-fade .swiper-slide-active .swiper-slide-active { pointer-events: auto; } .swiper-container-cube { overflow: visible; } .swiper-container-cube .swiper-slide { pointer-events: none; backface-visibility: hidden; z-index: 1; visibility: hidden; transform-origin: 0 0; width: 100%; height: 100%; } .swiper-container-cube .swiper-slide .swiper-slide { pointer-events: none; } .swiper-container-cube.swiper-container-rtl .swiper-slide { transform-origin: 100% 0; } .swiper-container-cube .swiper-slide-active, .swiper-container-cube .swiper-slide-active .swiper-slide-active { pointer-events: auto; } .swiper-container-cube .swiper-slide-active, .swiper-container-cube .swiper-slide-next, .swiper-container-cube .swiper-slide-prev, .swiper-container-cube .swiper-slide-next + .swiper-slide { pointer-events: auto; visibility: visible; } .swiper-container-cube .swiper-slide-shadow-top, .swiper-container-cube .swiper-slide-shadow-bottom, .swiper-container-cube .swiper-slide-shadow-left, .swiper-container-cube .swiper-slide-shadow-right { z-index: 0; backface-visibility: hidden; } .swiper-container-cube .swiper-cube-shadow { position: absolute; left: 0; bottom: 0px; width: 100%; height: 100%; background: #000; opacity: 0.6; filter: blur(50px); z-index: 0; } .swiper-container-flip { overflow: visible; } .swiper-container-flip .swiper-slide { pointer-events: none; backface-visibility: hidden; z-index: 1; } .swiper-container-flip .swiper-slide .swiper-slide { pointer-events: none; } .swiper-container-flip .swiper-slide-active, .swiper-container-flip .swiper-slide-active .swiper-slide-active { pointer-events: auto; } .swiper-container-flip .swiper-slide-shadow-top, .swiper-container-flip .swiper-slide-shadow-bottom, .swiper-container-flip .swiper-slide-shadow-left, .swiper-container-flip .swiper-slide-shadow-right { z-index: 0; backface-visibility: hidden; } .swiper-container-coverflow .swiper-wrapper { /* Windows 8 IE 10 fix */ -ms-perspective: 1200px; } .swiper-button-prev, .swiper-button-next { position: absolute; top: 50%; transform: translateY(-50%); display: none; font-size: 38px; line-height: 1; color: #fff; text-align: center; cursor: pointer; transition: .2s ease-in; will-change: transform; z-index: 10; } .swiper-button-prev:hover, .swiper-button-next:hover { color: #0045b6; } .swiper-button-prev::before, .swiper-button-next::before { font-family: 'Material Design Icons'; } @media (min-width: 768px) { .swiper-button-prev, .swiper-button-next { display: block; } } .swiper-button-prev.swiper-button-disabled, .swiper-button-next.swiper-button-disabled { opacity: 0.35; cursor: auto; pointer-events: none; } .swiper-button-prev { left: 30px; } .swiper-button-prev::before { content: "\f14a"; } .swiper-button-next { right: 30px; } .swiper-button-next::before { content: "\f14f"; } .swiper-slider.swiper-container-rtl .swiper-button-prev::before { content: "\f061"; } .swiper-slider.swiper-container-rtl .swiper-button-next::before { content: "\f060"; } .swiper-pagination { position: absolute; display: block; text-align: center; transition: .3s; transform: translate3d(0, 0, 0); z-index: 10; } .swiper-pagination.swiper-pagination-hidden { opacity: 0; } .swiper-pagination.swiper-pagination-clickable .swiper-pagination-bullet { cursor: pointer; } .swiper-pagination-bullet { width: 10px; height: 10px; display: inline-block; border-radius: 100%; background: rgba(255, 255, 255, 0.6); transition: .2s; } .swiper-pagination-bullet.swiper-pagination-bullet-active, .swiper-pagination-bullet:hover { background: white; } .swiper-container-vertical > .swiper-pagination { right: 10px; top: 50%; transform: translate3d(0px, -50%, 0); } .swiper-container-vertical > .swiper-pagination .swiper-pagination-bullet { margin: 5px 0; display: block; } .swiper-container-horizontal > .swiper-pagination { bottom: 20px; left: 0; width: 100%; } .swiper-container-horizontal > .swiper-pagination .swiper-pagination-bullet { margin: 0 5px; } .swiper-pagination-outer { position: absolute; left: 50%; transform: translate3d(-50%, 0, 0); z-index: 20; bottom: 5px; pointer-events: none; display: flex; justify-content: center; } .swiper-pagination-outer .swiper-pagination { pointer-events: auto; } .swiper-pagination.swiper-pagination-modern { position: relative; display: inline-block; width: auto; right: auto; left: auto; margin-left: -10px; } .swiper-pagination.swiper-pagination-modern .swiper-pagination-bullet, .swiper-pagination.swiper-pagination-modern .swiper-pagination-mark { width: 32px; height: 32px; border-radius: 50%; } .swiper-pagination.swiper-pagination-modern .swiper-pagination-bullet { display: inline-block; background: transparent; font-weight: 400; color: #e8e9ee; text-align: center; transition: .33s; line-height: 32px; margin: 0 10px; } .swiper-pagination.swiper-pagination-modern .swiper-pagination-mark { position: absolute; top: 0; bottom: 0; z-index: -1; left: -10px; display: block; background: rgba(21, 21, 21, 0.6); transition: .4s ease-in-out; } .swiper-pagination.swiper-pagination-modern .swiper-pagination-bullet:not(.swiper-pagination-bullet-active):hover { font-weight: 700; } .swiper-pagination.swiper-pagination-modern .swiper-pagination-bullet-active { color: #fff; } .swiper-pagination.swiper-pagination-modern .swiper-pagination-bullet-active:nth-child(1) ~ .swiper-pagination-mark { transform: translateX(20px); } .swiper-pagination.swiper-pagination-modern .swiper-pagination-bullet-active:nth-child(2) ~ .swiper-pagination-mark { transform: translateX(72px); } .swiper-pagination.swiper-pagination-modern .swiper-pagination-bullet-active:nth-child(3) ~ .swiper-pagination-mark { transform: translateX(124px); } .swiper-pagination.swiper-pagination-modern .swiper-pagination-bullet-active:nth-child(4) ~ .swiper-pagination-mark { transform: translateX(176px); } .swiper-pagination.swiper-pagination-modern .swiper-pagination-bullet-active:nth-child(5) ~ .swiper-pagination-mark { transform: translateX(228px); } .swiper-pagination.swiper-pagination-modern .swiper-pagination-bullet-active:nth-child(6) ~ .swiper-pagination-mark { transform: translateX(280px); } .swiper-pagination.swiper-pagination-modern .swiper-pagination-bullet-active:nth-child(7) ~ .swiper-pagination-mark { transform: translateX(332px); } .swiper-pagination.swiper-pagination-modern .swiper-pagination-bullet-active:nth-child(8) ~ .swiper-pagination-mark { transform: translateX(384px); } .swiper-pagination.swiper-pagination-modern .swiper-pagination-bullet-active:nth-child(9) ~ .swiper-pagination-mark { transform: translateX(436px); } .swiper-pagination.swiper-pagination-modern .swiper-pagination-bullet-active:nth-child(10) ~ .swiper-pagination-mark { transform: translateX(488px); } .swiper-pagination.swiper-pagination-modern .swiper-pagination-bullet-active:nth-child(11) ~ .swiper-pagination-mark { transform: translateX(540px); } .swiper-pagination.swiper-pagination-modern .swiper-pagination-bullet-active:nth-child(12) ~ .swiper-pagination-mark { transform: translateX(592px); } .context-dark .swiper-pagination .swiper-pagination-bullet, .bg-gray-700 .swiper-pagination .swiper-pagination-bullet, .bg-gray-800 .swiper-pagination .swiper-pagination-bullet, .bg-primary .swiper-pagination .swiper-pagination-bullet, .bg-primary-darker .swiper-pagination .swiper-pagination-bullet { color: #fff; } .swiper-slide { position: relative; display: flex; align-items: center; flex-shrink: 0; width: 100%; height: 100%; white-space: nowrap; background-position: 55% center; } .swiper-slide > * { width: 100%; white-space: normal; } .swiper-slide .container { width: 100%; } .swiper-slider-light { min-height: 40.41667vw; } .swiper-slider-light .swiper-slide::before { content: ''; position: absolute; top: 0; right: 0; bottom: 0; left: 0; background: linear-gradient(to right, rgba(255, 255, 255, 0.7), rgba(255, 255, 255, 0.3)); pointer-events: none; } .swiper-slider-light[data-custom-slide-effect='inter-leave-effect'] .slide-inner { background: inherit; } .swiper-slider-light .swiper-slide-caption { position: relative; z-index: 1; max-width: 650px; padding: 40px 0 80px; } .swiper-slider-light h1 span, .swiper-slider-light h2 span, .swiper-slider-light h3 span, .swiper-slider-light h4 span, .swiper-slider-light h5 span, .swiper-slider-light h6 span, .swiper-slider-light [class^='heading-'] span { display: block; } .container + .swiper-slider-light { margin-top: 20px; } @media (min-width: 576px) { .swiper-slider-light .swiper-slide::before { background: linear-gradient(to right, rgba(255, 255, 255, 0.7), rgba(255, 255, 255, 0)); } .swiper-slider-light .slider-slide-caption { padding: 45px 0 95px; } } @media (min-width: 768px) { .swiper-slider-light .swiper-slide { background-position: center center; } .swiper-slider-light .swiper-slide::before { display: none; } .swiper-slider-light .swiper-slide-caption { padding: 80px 0 165px; } .swiper-slider-light .swiper-pagination-outer { bottom: 50px; } } @media (min-width: 1200px) { .swiper-slider-light .swiper-pagination-outer { bottom: 72px; } } .swiper-slider-business { min-height: 41.66667vw; } .swiper-slider-business .swiper-slide { background-position: 60% 50%; } .swiper-slider-business .swiper-slide::before { content: ''; position: absolute; top: 0; right: 0; bottom: 0; left: 0; background: linear-gradient(to right, rgba(73, 80, 87, 0.7), rgba(73, 80, 87, 0.4)); pointer-events: none; } .swiper-slider-business .swiper-slide > * { position: relative; z-index: 1; } .swiper-slider-business .swiper-slide-caption { padding: 50px 0 100px; } .swiper-slider-business .swiper-caption-text { position: relative; overflow: hidden; max-width: 520px; padding-left: 35px; } .swiper-slider-business .swiper-caption-text p { width: 100%; } .swiper-slider-business .swiper-caption-text-inner { position: relative; overflow: hidden; } .swiper-slider-business .swiper-caption-text-sm { max-width: 420px; } .swiper-slider-business .swiper-caption-text-decoration { position: absolute; top: 0; bottom: 0; left: 0; width: 2px; background: #0045b6; } .swiper-slider-business .swiper-slider-nav { position: absolute; left: 50%; bottom: 20px; z-index: 20; width: 100%; transform: translate3d(-50%, 0, 0); pointer-events: none; text-align: left; } .swiper-slider-business .swiper-slider-nav > * + * { margin-left: 10px; } .swiper-slider-business .wow-outer { white-space: normal; } .swiper-slider-business .wow-outer span { display: inline-block; } .swiper-slider-business .swiper-button-prev, .swiper-slider-business .swiper-button-next { position: relative; transform: none; left: auto; top: auto; right: auto; display: inline-block; pointer-events: auto; } .swiper-slider-business * + .swiper-caption-text { margin-top: 20px; } @media (min-width: 768px) { .swiper-slider-business .swiper-slide-caption { padding: 130px 0 180px; } } @media (min-width: 992px) { .swiper-slider-business .swiper-slider-nav { bottom: 40px; } } @media (min-width: 1200px) { .swiper-slider-business .swiper-slide::before { display: none; } } @media (min-width: 1600px) { .swiper-slider-business * + .swiper-caption-text { margin-top: 45px; } } .swiper-slider-minimal { min-height: 41.66667vw; } .swiper-slider-minimal .swiper-slide { background-position: 50% 50%; justify-content: flex-start; } .swiper-slider-minimal .swiper-slide::before { content: ''; position: absolute; top: 0; right: 0; bottom: 0; left: 0; background: #000; opacity: .1; } .swiper-slider-minimal .swiper-slide > * { position: relative; z-index: 1; } .swiper-slider-minimal .swiper-slide_video { display: flex; align-items: stretch; } .swiper-slider-minimal .vide_bg { position: relative; display: flex; flex-direction: column; justify-content: center; } .swiper-slider-minimal .vide_bg::before { content: ''; position: absolute; top: 0; right: 0; bottom: 0; left: 0; background: #000; opacity: .4; } .swiper-slider-minimal .swiper-slide-caption { position: relative; z-index: 2; padding: 80px 0 120px; } .swiper-slider-minimal .swiper-slide-text { position: relative; overflow: hidden; width: auto; } .swiper-slider-minimal .swiper-slide-text > * { position: relative; transform: translateY(100%); } .swiper-slider-minimal .swiper-slide-active .swiper-slide-text > * { transform: translateY(0); transition: .5s .3s; } @media (min-width: 1200px) { .swiper-pagination-outer { bottom: 50px; } } @media (min-width: 768px) { .swiper-slider-minimal .swiper-slide-caption { padding: 170px 0 220px; } .swiper-slider-minimal .swiper-pagination-outer { bottom: 15px; justify-content: center; } } @media (min-width: 992px) { .swiper-slider-minimal .swiper-pagination-outer { bottom: 20px; display: flex; justify-content: flex-start; } } @media (min-width: 1600px) { .swiper-slider-minimal .swiper-pagination-outer { bottom: 90px; } } [data-custom-slide-effect='inter-leave-effect'] .swiper-slide { display: flex; will-change: transform; overflow: hidden; align-items: stretch; min-height: inherit; } [data-custom-slide-effect='inter-leave-effect'] .slide-inner { display: flex; flex-direction: column; align-items: center; justify-content: center; background-size: cover; } [data-custom-slide-effect='inter-leave-effect'] .slide-inner > * { width: 100%; } .swiper-slider-tiny { min-height: 400px; } .swiper-slider-tiny .swiper-slide { position: relative; } .swiper-slider-tiny .swiper-slide::before { content: ''; position: absolute; top: 0; right: 0; bottom: 0; left: 0; pointer-events: none; background: linear-gradient(to top, rgba(255, 255, 255, 0.3) 0%, rgba(255, 255, 255, 0) 40%); } .swiper-slider-tiny .swiper-slide > * { position: relative; z-index: 1; } @media (min-width: 768px) { .swiper-slider-tiny { min-height: 39.58333vw; } } @media (min-width: 992px) { .swiper-slider-tiny .swiper-pagination-outer { bottom: 65px; } } .centered-pagination .swiper-pagination-outer { justify-content: center; } /* +* +* RD Google Map +*/ .google-map { color: #000; height: 200px; } .google-map img { max-width: none !important; } @media (min-width: 576px) { .google-map { height: 250px; } } @media (min-width: 768px) { .google-map { height: 300px; } } @media (min-width: 992px) { .google-map { height: 350px; } } @media (min-width: 1200px) { .google-map { height: 766px; } } .ie-10 .google-map, .ie-11 .google-map { height: 770px; } .map_locations { display: none; } .google-map-container, .google-map { width: 100%; } .google-map-markers { display: none; } /* +* +* Search Results +*/ .rd-search-results-live { position: absolute; left: 0; right: 0; top: 100%; max-height: 600px; overflow-y: auto; margin: -3px 0 0; text-align: left; z-index: 998; } .rd-search-results-live #search-results { position: relative; padding: 10px 0 0; border-bottom-left-radius: 6px; border-bottom-right-radius: 6px; color: #495057; background: #fff; border: 1px solid #c5c6cd; border-top: 0; opacity: 0; visibility: hidden; transform-origin: 50% 0; transform: scale(1, 0.9); transition: .3s all ease; } .rd-search-results-live #search-results.active { opacity: 1; visibility: visible; transform: scale(1, 1); } .rd-search-results-live #search-results.active .form-input { border-bottom-left-radius: 0; } .rd-search-results-live .search-quick-result { padding-left: 15px; padding-right: 15px; font-family: "Poppins", sans-serif; font-size: 14px; font-weight: 500; letter-spacing: .08em; line-height: 30px; text-transform: uppercase; color: #151515; } .rd-search-results-live .search-list { margin-top: 11px; } .rd-search-results-live .search-list li:only-child { padding: 0 15px 15px; } .rd-search-results-live .search-link { color: #aeb1be; } .rd-search-results-live .search-link:hover { color: #0045b6; } .rd-search-results-live .search-error { padding: 0 0 20px; font-size: 14px; line-height: 1.6; } .rd-search-results-live .search-title { position: relative; font-size: 16px; font-weight: 400; color: #151515; } .rd-search-results-live .search-title a:hover { color: #0045b6; } .rd-search-results-live .search-link { display: block; } .rd-search-results-live .search-list-item-all { margin-top: 18px; width: 100%; } .rd-search-results-live .search-submit { position: relative; overflow: hidden; z-index: 0; display: block; padding: 10px 20px; margin: 0 -1px; border-bottom-left-radius: 6px; border-bottom-right-radius: 6px; font-family: "Poppins", sans-serif; font-size: 12px; font-weight: 500; letter-spacing: 0.1em; text-transform: uppercase; text-align: center; color: #fff; background: #0045b6; } .rd-search-results-live .search-submit:hover { color: #fff; background: #4854ed; } .rd-search-results-live .match { display: none; } .not-empty ~ .rd-search-results-live { visibility: visible; opacity: 1; } .rd-search-results-live p { font-size: 14px; } .rd-search-results-live .search-list-item { padding: 0 15px; } .rd-search-results-live * + p { margin-top: 3px; } .rd-search-results-live .search-list-item + .search-list-item { margin-top: 17px; } .rd-search-results .search-list { counter-reset: li; text-align: left; padding-left: 0; font-size: 18px; list-style-type: none; overflow: hidden; } .rd-search-results .search-list li div { overflow: hidden; text-overflow: ellipsis; max-width: 100%; white-space: nowrap; } .rd-search-results .search-list li:only-child::before { display: none; } .rd-search-results .search-list-item { position: relative; padding-left: 40px; font-size: 16px; color: #9b9b9b; } .rd-search-results .search-list-item::before { content: counter(li, decimal) "."; counter-increment: li; position: absolute; left: 0; top: -0.1em; line-height: inherit; } .rd-search-results .search-title { position: relative; color: #0045b6; } .rd-search-results .search-title a { color: inherit; } .rd-search-results .search-title a:hover { color: #343db0; } .rd-search-results .search { color: #fff; padding: 0 .25em; background: #0045b6; } .rd-search-results .match { padding: 5px; font-size: 12px; line-height: 1.7; letter-spacing: .1em; text-transform: uppercase; color: #000; } .rd-search-results .match em { margin: 0; font-style: normal; } .rd-search-results * + .match { margin-top: 10px; } .rd-search-results .search-list-item + .search-list-item { margin-top: 40px; } * + .rd-search-results { margin-top: 40px; } @media (min-width: 768px) { .rd-search-results .search-list-item::before { top: 4px; } .rd-search-results * + p { margin-top: 12px; } * + .rd-search-results { margin-top: 55px; } } @media (min-width: 992px) { .rd-search-results .search-list-item { padding-left: 50px; } .rd-search-results * + p { margin-top: 17px; } .rd-search-results .search-list-item + .search-list-item { margin-top: 50px; } } @media (min-width: 1200px) { * + .rd-search-results { margin-top: 70px; } } @media (min-width: 1600px) { .rd-search-results .search-list-item + .search-list-item { margin-top: 70px; } } .rd-search-classic { position: relative; } .rd-search-classic .form-wrap-inner { position: relative; } .rd-search-classic .form-input { padding-right: 50px; } .rd-search-classic .form-input, .rd-search-classic .form-label { letter-spacing: 0; } .rd-search-classic .rd-search-submit { background: none; border: none; display: inline-block; padding: 0; outline: none; outline-offset: 0; cursor: pointer; -webkit-appearance: none; position: absolute; top: 0; right: 0; bottom: 0; width: 50px; padding-top: 2px; padding-right: 3px; margin: 0; text-align: center; color: #aeb1be; font-size: 0; line-height: 0; } .rd-search-classic .rd-search-submit::-moz-focus-inner { border: none; padding: 0; } .rd-search-classic .rd-search-submit:before { position: relative; top: -1px; content: '\f43b'; font: 400 20px 'Material Design Icons'; line-height: 1; transition: .33s all ease; } .rd-search-classic .rd-search-submit:hover { color: #0045b6; } .rd-search.rd-search-inline { position: relative; } .rd-search.rd-search-inline .form-input { padding-right: 48px; } .rd-search.rd-search-inline .button-link { position: absolute; top: 50%; transform: translateY(-50%); width: 48px; right: 0; margin: 0; font-size: 21px; color: #aeb1be; transition: .33s; } .rd-search.rd-search-inline .button-link::before { display: block; margin: auto; } .rd-search.rd-search-inline .button-link:hover { color: #0045b6; } .rd-search.rd-search-inline.form-sm .form-input { padding-right: 40px; } .rd-search.rd-search-inline.form-sm .button-link { width: 40px; font-size: 18px; } .rd-search.rd-search-inline.form-lg .form-input { padding-right: 56px; } .rd-search.rd-search-inline.form-lg .button-link { width: 56px; } /* +* +* Isotope +*/ @keyframes sprite-animation { from { background-position: 0 0; } } [data-isotope-layout] { z-index: 0; display: block; overflow: hidden; min-height: 160px; margin-left: -15px; margin-right: -15px; transition: .4s all ease; } [data-isotope-layout]:after { content: ''; position: absolute; top: 50%; left: 50%; width: 64px; height: 64px; margin-top: 15px; background-image: url("../images/isotope-loader.png"); background-position: -1152px 0; animation: 0.7s sprite-animation steps(18) infinite; transition: .4s all ease; transform: translate3d(-50%, -50%, 0); } [data-isotope-layout] [class*="col-"] { display: block; opacity: 0; will-change: transform; backface-visibility: hidden; transition: .1s opacity ease-in; } [data-isotope-layout].isotope--loaded [class*="col-"] { opacity: 1; } [data-isotope-layout].isotope--loaded:after { opacity: 0; visibility: hidden; } .isotope.isotope-condensed { margin-left: 0; margin-right: 0; overflow: hidden; } .isotope.isotope-condensed [class*='col'] { padding: 0; margin: 0 0 30px 0; } @media (min-width: 576px) { .isotope.isotope-condensed [class*='col'] { margin: 0 -1px -1px 0; } } .isotope-filters-list { position: relative; font-size: 16px; margin-left: -20px; margin-bottom: -10px; } .isotope-filters-list > * { margin-left: 20px; } .isotope-filters-list::after { content: ''; position: absolute; left: 20px; right: 0; bottom: 10px; border-bottom: 1px solid #e8e9ee; } .isotope-filters-list li { display: inline-block; margin-bottom: 10px; } .isotope-filters-list a { position: relative; display: block; z-index: 1; padding-bottom: 10px; color: #74757f; } .isotope-filters-list a::after { content: ''; position: absolute; right: 0; bottom: 0; width: 0; height: 1px; transition: .4s; background: #0045b6; } .isotope-filters-list a:hover { color: #495057; } .isotope-filters-list a.active { color: #495057; } .isotope-filters-list a.active::after { right: auto; left: 0; width: 100%; } @media (min-width: 992px) { .isotope-filters-list { font-size: 18px; margin-left: -48px; } .isotope-filters-list > * { margin-left: 48px; } .isotope-filters-list::after { left: 45px; } } @media (min-width: 1200px) { .isotope-filters-list { font-size: 20px; } } @media (max-width: 767.98px) { .isotope-responsive { margin-left: -7px; margin-right: -7px; margin-bottom: -14px; } .isotope-responsive:empty { margin-bottom: 0; } .isotope-responsive > * { margin-bottom: 14px; } .isotope-responsive [class*='col'] { padding-left: 7px; padding-right: 7px; } } @media (min-width: 768px) { .isotope-responsive { margin-bottom: -30px; } .isotope-responsive:empty { margin-bottom: 0; } .isotope-responsive > * { margin-bottom: 30px; } } @media (max-width: 767.98px) { .isotope-filters-modern { position: relative; z-index: 10; } .isotope-filters-toggle { display: block; padding: 10px 20px; color: #fff; background: #0045b6; box-shadow: none; border: 0; } .isotope-filters-toggle .caret { position: relative; display: inline-block; margin-left: 5px; font-family: "FontAwesome"; font-size: 14px; transition: .22s; will-change: transform; } .isotope-filters-toggle .caret::before { content: '\f107'; } .isotope-filters-toggle.active .caret { transform: scale(1, -1); } .isotope-filters-list { position: absolute; top: 100%; left: 0; z-index: 10; min-width: 240px; margin: 5px 0 0 0; padding: 20px; border: 1px solid #e8e9ee; opacity: 0; visibility: hidden; background: #fff; transition: .3s ease-in; } .isotope-filters-list::after { display: none; } .isotope-filters-list.active { opacity: 1; visibility: visible; } .isotope-filters-list li { display: block; width: 100%; margin-left: 0; } .isotope-filters-list a { padding: 0; } .isotope-filters-list a::after { display: none; } } @media (min-width: 768px) { .isotope-filters-modern .isotope-filters-toggle { display: none; } } * + .isotope { margin-top: 30px; } @media (min-width: 768px) { * + .isotope { margin-top: 47px; } } .nav { display: block; } .tabs-custom { text-align: left; } .tabs-custom .nav-tabs { font-size: 0; line-height: 0; word-spacing: 0; border: 0; } .tabs-custom .nav-tabs:before, .tabs-custom .nav-tabs:after { display: none; } .tabs-custom .nav-item { float: none; border: 0; cursor: pointer; transition: .33s all ease; } .tabs-custom .nav-link { margin: 0; border-radius: 0; border: 0; } .tabs-custom .nav-link.active { cursor: default; border: 0; } .tab-content > .tab-pane { display: block; visibility: hidden; height: 0; overflow: hidden; } .tab-content > .active { visibility: visible; height: auto; overflow: visible; } .tabs-line .nav-tabs { position: relative; } .tabs-line .nav-link { font-size: 16px; line-height: 1.4; text-align: center; vertical-align: middle; } .tabs-line * + .tab-content { margin-top: 20px; } @media (max-width: 767.98px) { .tabs-line .nav-tabs { max-width: 100%; border: 1px solid #e8e9ee; } .tabs-line .nav-item { margin: -1px; } .tabs-line .nav-link { padding: 7px 10px; color: #74757f; background: transparent; } .tabs-line .nav-item + .nav-item .nav-link { border-top: 1px solid #e8e9ee; } .tabs-line .nav-link:hover, .tabs-line .nav-link.active { color: #fff; background: #0045b6; border-color: #0045b6; } } * + .tabs-line { margin-top: 30px; } .tabs-corporate .nav-tabs { position: relative; border: 1px solid #e5e7e9; } .tabs-corporate .nav-item { margin: -1px; } .tabs-corporate .nav-link { padding: 10px 10px; text-transform: uppercase; font-size: 12px; font-weight: 700; line-height: 1.4; color: #74757f; background: transparent; border-bottom: 1px solid #e5e7e9; text-align: center; vertical-align: middle; } .tabs-corporate .nav-link:first-child { border-top: 1px solid #e5e7e9; } .tabs-corporate .nav-link:hover, .tabs-corporate .nav-link.active { color: #fff; background: #0045b6; border-color: #0045b6; } .tabs-corporate .tab-content { padding: 30px 0 0; } @media (min-width: 768px) { * + .tabs-line { margin-top: 40px; } .tabs-horizontal.tabs-corporate .nav-tabs { position: relative; width: 100%; display: block; transform: translate3d(0, -10px, 0); margin-bottom: -10px; margin-left: -5px; margin-right: -5px; text-align: center; border: 0; will-change: transform; } .tabs-horizontal.tabs-corporate .nav-tabs > * { margin-top: 10px; padding-left: 5px; padding-right: 5px; } .tabs-horizontal.tabs-corporate .nav-item { display: inline-block; will-change: transform; } .tabs-horizontal.tabs-corporate .nav-link { display: block; position: relative; z-index: 1; min-width: 130px; letter-spacing: .1em; padding: 13px 20px; border: 2px solid #e5e7e9; } .tabs-horizontal.tabs-corporate .nav-link, .tabs-horizontal.tabs-corporate .nav-link::before { transition-timing-function: cubic-bezier(0.2, 1, 0.3, 1); } .tabs-horizontal.tabs-corporate .nav-link::before { content: ''; position: absolute; top: -1px; left: -1px; width: calc(100% + 2px); height: calc(100% + 2px); z-index: -1; opacity: 0; transform: scale3d(0.7, 1, 1); transition: transform 0.4s, opacity 0.4s; transition-timing-function: cubic-bezier(0.2, 1, 0.3, 1); background: #0045b6; } .tabs-horizontal.tabs-corporate .nav-link.active, .tabs-horizontal.tabs-corporate .nav-link:hover { color: #fff; border-color: #0045b6; background-color: transparent; } .tabs-horizontal.tabs-corporate .nav-link.active::before, .tabs-horizontal.tabs-corporate .nav-link:hover::before { opacity: 1; transform: translate3d(0, 0, 0) scale3d(1, 1, 1); } .tabs-horizontal.tabs-line .nav-tabs { transform: translateY(-20px); margin-bottom: -20px; border: 0; border-bottom: 1px solid #e8e9ee; } .tabs-horizontal.tabs-line .nav-item { display: inline-block; margin-top: 20px; } .tabs-horizontal.tabs-line .nav-item:not(:last-child) { margin-right: 52px; } .tabs-horizontal.tabs-line .nav-link { position: relative; padding: 0 0 13px 0; font-size: 20px; background-color: transparent; color: #9b9b9b; border: 0; } .tabs-horizontal.tabs-line .nav-link::after { content: ''; position: absolute; bottom: 0; right: 0; height: 1px; width: 0; background: #0045b6; transition: .22s ease; } .tabs-horizontal.tabs-line .nav-link.active, .tabs-horizontal.tabs-line .nav-link:hover { color: #495057; } .tabs-horizontal.tabs-line .nav-link.active::after { right: auto; left: 0; width: 100%; } .tabs-horizontal.tabs-line * + .tab-content { margin-top: 25px; } .tabs-vertical { display: flex; align-items: flex-start; } .tabs-vertical .nav-tabs { display: flex; flex-direction: column; align-items: stretch; flex-shrink: 0; max-width: 50%; } .tabs-vertical .nav-item { border: 0; width: 100%; text-align: left; } .tabs-vertical .nav-link.active, .tabs-vertical .nav-link:hover { box-shadow: 0 9px 21px 0 rgba(30, 30, 30, 0.13); } .tabs-vertical .tab-content { flex-grow: 1; } .tabs-vertical.tabs-corporate .nav-tabs { width: auto; min-width: 260px; border: 0; } .tabs-vertical.tabs-corporate .nav-item { margin: 0; } .tabs-vertical.tabs-corporate .nav-link { position: relative; padding: 17px 30px; border: 0; overflow: hidden; text-align: left; } .tabs-vertical.tabs-corporate .nav-item + .nav-item { margin-top: 2px; } .tabs-vertical.tabs-corporate .tab-content { padding: 0 0 0 30px; } } @media (min-width: 992px) { .tabs-horizontal.tabs-corporate .tab-content { padding: 60px 30px 0; } .tabs-vertical.tabs-corporate .tab-content { padding: 0 0 0 45px; } } .card-group-custom { margin-bottom: 0; } .card-group-custom .card-header + .collapse > .card-body, .card-group-custom .card-header + .collapse > .list-group { border-top: 0; } .card-group-custom .card + .card { margin-top: 0; } .card-title { margin-bottom: 0; } .card-header { background-color: transparent; } .card-custom { display: block; margin: 0; background: inherit; border: 0; border-radius: 0; box-shadow: none; text-align: left; } .card-custom a { display: block; } .card-custom .card-header { padding: 0; border-bottom: 0; border-top-left-radius: 0; border-top-right-radius: 0; } .card-custom .card-body { padding: 0; border: 0; } * + .card-group-custom { margin-top: 30px; } @media (min-width: 768px) { * + .card-group-custom { margin-top: 40px; } } /* +* Card corporate +*/ .card-corporate { border-top: 1px solid #f5f6fa; text-align: left; } .card-corporate:last-child { border-bottom: 1px solid #f5f6fa; } .card-corporate .card-collapse { background: #fff; } .card-corporate .card-title a { position: relative; z-index: 1; padding: 15px 40px 15px 70px; font-size: 17px; line-height: 1.5; letter-spacing: 0; color: #495057; transition: 1.3s all ease; } .card-corporate .card-title a[aria-expanded='true'], .card-corporate .card-title a:hover { color: #0045b6; } .card-corporate .card-title a .card-arrow { transition: .33s; } .card-corporate .card-title a.collapsed { color: #495057; border-bottom-width: 0; } .card-corporate .card-title a.collapsed .card-arrow::before { opacity: 1; } .card-corporate .card-title a.collapsed .card-arrow::after { opacity: 0; } .card-corporate .card-arrow { position: absolute; top: 16px; left: 13px; z-index: 2; width: 20px; height: 20px; color: #0045b6; transition: .33s all ease; text-align: center; } .card-corporate .card-arrow::before, .card-corporate .card-arrow::after { position: absolute; z-index: 4; font-family: 'Material Design Icons'; font-size: 24px; line-height: 1; transition: .22s; } .card-corporate .card-arrow::before { content: '\f505'; opacity: 0; } .card-corporate .card-arrow::after { content: '\f466'; opacity: 1; } .card-corporate .card-collapse { position: relative; z-index: 1; color: #74757f; } .card-corporate .card-body { padding: 2px 44px 25px 70px; } @media (max-width: 767.98px) { .card-corporate .card-arrow { left: 0; } .card-corporate .card-title a, .card-corporate .card-body { padding-left: 50px; } } @media (min-width: 768px) { .card-corporate .card-title a { font-size: 20px; line-height: 1.4; } .card-corporate .card-arrow { top: 18px; } } /* +* +* Tooltop Custom +*/ .tooltip { font-size: 16px; line-height: 1.2; } .tooltip-inner { background-color: #0045b6; } .bs-tooltip-top .arrow::before { border-top-color: #0045b6; } .bs-tooltip-right .arrow::before { border-right-color: #0045b6; } .bs-tooltip-bottom .arrow::before { border-bottom-color: #0045b6; } .bs-tooltip-left .arrow::before { border-left-color: #0045b6; } @media (min-width: 1200px) { .tooltip-inner { padding: 9px 18px; } } /* +* +* Modal Custom +*/ .modal { display: flex !important; padding: 30px 15px 7vh; visibility: hidden; } .modal .close { cursor: pointer; color: #74757f; transition: .22s; } .modal .close span::before { content: '\f24c'; font-family: 'Material Design Icons'; } .modal .close:hover { color: #495057; } .modal.show { visibility: visible; } .modal.fade .modal-dialog { transform: translate3d(0, -30px, 0); } .modal.show .modal-dialog { transform: translate3d(0, 0, 0); } .modal-dialog { display: flex; align-items: flex-start; justify-content: center; margin: 0 auto; } .modal-content { height: 100%; border-radius: 6px; } .modal-body { height: 100%; overflow-y: auto; } @media (min-width: 768px) { .modal-header, .modal-body { padding-left: 30px; padding-right: 30px; } .modal-header { padding-top: 20px; padding-bottom: 20px; } .modal-body { padding-top: 30px; padding-bottom: 30px; } .modal-dialog { max-width: 800px; } } /* +* +* Progress Bars +*/ .progress-linear { position: relative; text-align: left; } .progress-linear .progress-header { display: flex; align-items: center; justify-content: space-between; transform: translate3d(0, -10px, 0); margin-bottom: -10px; margin-left: -5px; margin-right: -5px; } .progress-linear .progress-header > * { margin-top: 10px; padding-left: 5px; padding-right: 5px; } .progress-linear .progress-bar-linear-wrap { height: 2px; background: #f5f6fa; } .progress-linear .progress-bar-linear { width: 0; height: inherit; background: #0045b6; transition: .5s all ease-in-out; } .progress-linear .progress-value::after { content: '\0025'; } * + .progress-linear { margin-top: 25px; } * + .progress-linear-outer { margin-top: 25px; } * + .progress-bar-linear-wrap { margin-top: 8px; } .progress-linear + .progress-linear { margin-top: 20px; } .progress-linear-outer + .progress-linear-outer { margin-top: 20px; } @media (min-width: 768px) { * + .progress-linear { margin-top: 40px; } * + .progress-linear-outer { margin-top: 40px; } } @media (min-width: 1200px) { .progress-linear + .progress-linear { margin-top: 34px; } .progress-linear-outer + .progress-linear-outer { margin-top: 34px; } } /* +* +* Time Circles +*/ .time_circles { position: relative; width: 100%; } .time_circles > div { position: absolute; top: 0 !important; bottom: 0; display: flex; align-items: center; flex-wrap: wrap; justify-content: center; margin-top: -10px; text-align: center; } @media (min-width: 1200px) { #DateCountdown { width: 100%; } } .time_circles > div > h4 { position: absolute; left: 0; top: calc(100% + 14px); right: 0; transform: translateY(-100%); margin: 0; padding: 0; text-align: center; font-family: "Work Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; font-size: 14px !important; line-height: 1.2 !important; color: #868e96; } .time_circles > div > h4 + * { margin-top: 0; } @media (min-width: 576px) { .time_circles > div > h4 { font-size: 16px !important; } } .time_circles > div > span { display: block; text-align: center; letter-spacing: 0; font-size: 24px !important; font-weight: 700; line-height: 1 !important; color: #111111; } .context-dark .time_circles > div > span, .bg-gray-700 .time_circles > div > span, .bg-gray-800 .time_circles > div > span, .bg-primary .time_circles > div > span, .bg-primary-darker .time_circles > div > span { color: #fff; } .context-dark .time_circles > div > h4, .bg-gray-700 .time_circles > div > h4, .bg-gray-800 .time_circles > div > h4, .bg-primary .time_circles > div > h4, .bg-primary-darker .time_circles > div > h4 { color: #fff; } .countdown-wrap { max-width: 530px; padding-bottom: 20px; } * + .countdown-wrap { margin-top: 35px; } .countdown-wrap + .button { margin-top: 40px; } /* +* jQuery mousewheel plugin +*/ /* +------------------------------------------------------------------------------------------------------------------------ +1. BASIC STYLE +------------------------------------------------------------------------------------------------------------------------ +*/ .mCustomScrollbar { -ms-touch-action: pinch-zoom; touch-action: pinch-zoom; /* direct pointer events to js */ } .mCustomScrollbar.mCS_no_scrollbar, .mCustomScrollbar.mCS_touch_action { -ms-touch-action: auto; touch-action: auto; } .mCustomScrollBox { /* contains plugin's markup */ position: relative; overflow: hidden; height: 100%; max-width: 100%; outline: none; direction: ltr; } .mCSB_container { /* contains the original content */ overflow: hidden; width: auto; height: auto; } /* +------------------------------------------------------------------------------------------------------------------------ +2. VERTICAL SCROLLBAR +y-axis +------------------------------------------------------------------------------------------------------------------------ +*/ .mCSB_inside > .mCSB_container { margin-right: 30px; } .mCSB_container.mCS_no_scrollbar_y.mCS_y_hidden { margin-right: 0; } /* non-visible scrollbar */ .mCS-dir-rtl-custom > .mCSB_inside > .mCSB_container { /* RTL direction/left-side scrollbar */ margin-right: 0; margin-left: 30px; } .mCS-dir-rtl-custom > .mCSB_inside > .mCSB_container.mCS_no_scrollbar_y.mCS_y_hidden { margin-left: 0; } /* RTL direction/left-side scrollbar */ .mCSB_scrollTools { /* contains scrollbar markup (draggable element, dragger rail, buttons etc.) */ position: absolute; width: 16px; height: auto; left: auto; top: 0; right: 0; bottom: 0; } .mCSB_outside + .mCSB_scrollTools { right: -26px; } /* scrollbar position: outside */ .mCS-dir-rtl-custom > .mCSB_inside > .mCSB_scrollTools, .mCS-dir-rtl-custom > .mCSB_outside + .mCSB_scrollTools { /* RTL direction/left-side scrollbar */ right: auto; left: 0; } .mCS-dir-rtl-custom > .mCSB_outside + .mCSB_scrollTools { left: -26px; } /* RTL direction/left-side scrollbar (scrollbar position: outside) */ .mCSB_scrollTools .mCSB_draggerContainer { /* contains the draggable element and dragger rail markup */ position: absolute; top: 0; left: 0; bottom: 0; right: 0; height: auto; } .mCSB_scrollTools a + .mCSB_draggerContainer { margin: 20px 0; } .mCSB_scrollTools .mCSB_draggerRail { width: 16px; height: 100%; margin: 0 auto; border-radius: 0; } .mCSB_scrollTools .mCSB_dragger { /* the draggable element */ cursor: pointer; width: 100%; height: 30px; /* minimum dragger height */ z-index: 1; } .mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar { /* the dragger element */ position: relative; width: 16px; height: 100%; margin: 0 auto; border-radius: 0; text-align: center; } .mCSB_scrollTools_vertical.mCSB_scrollTools_onDrag_expand .mCSB_dragger.mCSB_dragger_onDrag_expanded .mCSB_dragger_bar, .mCSB_scrollTools_vertical.mCSB_scrollTools_onDrag_expand .mCSB_draggerContainer:hover .mCSB_dragger .mCSB_dragger_bar { width: 12px; /* auto-expanded scrollbar */ } .mCSB_scrollTools_vertical.mCSB_scrollTools_onDrag_expand .mCSB_dragger.mCSB_dragger_onDrag_expanded + .mCSB_draggerRail, .mCSB_scrollTools_vertical.mCSB_scrollTools_onDrag_expand .mCSB_draggerContainer:hover .mCSB_draggerRail { width: 8px; /* auto-expanded scrollbar */ } .mCSB_scrollTools .mCSB_buttonUp, .mCSB_scrollTools .mCSB_buttonDown { display: block; position: absolute; height: 20px; width: 100%; overflow: hidden; margin: 0 auto; cursor: pointer; } .mCSB_scrollTools .mCSB_buttonDown { bottom: 0; } /* +------------------------------------------------------------------------------------------------------------------------ +3. HORIZONTAL SCROLLBAR +x-axis +------------------------------------------------------------------------------------------------------------------------ +*/ .mCSB_horizontal.mCSB_inside > .mCSB_container { margin-right: 0; margin-bottom: 30px; } .mCSB_horizontal.mCSB_outside > .mCSB_container { min-height: 100%; } .mCSB_horizontal > .mCSB_container.mCS_no_scrollbar_x.mCS_x_hidden { margin-bottom: 0; } /* non-visible scrollbar */ .mCSB_scrollTools.mCSB_scrollTools_horizontal { width: auto; height: 16px; top: auto; right: 0; bottom: 0; left: 0; } .mCustomScrollBox + .mCSB_scrollTools.mCSB_scrollTools_horizontal, .mCustomScrollBox + .mCSB_scrollTools + .mCSB_scrollTools.mCSB_scrollTools_horizontal { bottom: -26px; } /* scrollbar position: outside */ .mCSB_scrollTools.mCSB_scrollTools_horizontal a + .mCSB_draggerContainer { margin: 0 20px; } .mCSB_scrollTools.mCSB_scrollTools_horizontal .mCSB_draggerRail { width: 100%; height: 2px; margin: 7px 0; } .mCSB_scrollTools.mCSB_scrollTools_horizontal .mCSB_dragger { width: 30px; /* minimum dragger width */ height: 100%; left: 0; } .mCSB_scrollTools.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar { width: 100%; height: 4px; margin: 6px auto; } .mCSB_scrollTools_horizontal.mCSB_scrollTools_onDrag_expand .mCSB_dragger.mCSB_dragger_onDrag_expanded .mCSB_dragger_bar, .mCSB_scrollTools_horizontal.mCSB_scrollTools_onDrag_expand .mCSB_draggerContainer:hover .mCSB_dragger .mCSB_dragger_bar { height: 12px; /* auto-expanded scrollbar */ margin: 2px auto; } .mCSB_scrollTools_horizontal.mCSB_scrollTools_onDrag_expand .mCSB_dragger.mCSB_dragger_onDrag_expanded + .mCSB_draggerRail, .mCSB_scrollTools_horizontal.mCSB_scrollTools_onDrag_expand .mCSB_draggerContainer:hover .mCSB_draggerRail { height: 8px; /* auto-expanded scrollbar */ margin: 4px 0; } .mCSB_scrollTools.mCSB_scrollTools_horizontal .mCSB_buttonLeft, .mCSB_scrollTools.mCSB_scrollTools_horizontal .mCSB_buttonRight { display: block; position: absolute; width: 20px; height: 100%; overflow: hidden; margin: 0 auto; cursor: pointer; } .mCSB_scrollTools.mCSB_scrollTools_horizontal .mCSB_buttonLeft { left: 0; } .mCSB_scrollTools.mCSB_scrollTools_horizontal .mCSB_buttonRight { right: 0; } /* +------------------------------------------------------------------------------------------------------------------------ +4. VERTICAL AND HORIZONTAL SCROLLBARS +yx-axis +------------------------------------------------------------------------------------------------------------------------ +*/ .mCSB_container_wrapper { position: absolute; height: auto; width: auto; overflow: hidden; top: 0; left: 0; right: 0; bottom: 0; margin-right: 30px; margin-bottom: 30px; } .mCSB_container_wrapper > .mCSB_container { padding-right: 30px; padding-bottom: 30px; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; } .mCSB_vertical_horizontal > .mCSB_scrollTools.mCSB_scrollTools_vertical { bottom: 20px; } .mCSB_vertical_horizontal > .mCSB_scrollTools.mCSB_scrollTools_horizontal { right: 20px; } /* non-visible horizontal scrollbar */ .mCSB_container_wrapper.mCS_no_scrollbar_x.mCS_x_hidden + .mCSB_scrollTools.mCSB_scrollTools_vertical { bottom: 0; } /* non-visible vertical scrollbar/RTL direction/left-side scrollbar */ .mCSB_container_wrapper.mCS_no_scrollbar_y.mCS_y_hidden + .mCSB_scrollTools ~ .mCSB_scrollTools.mCSB_scrollTools_horizontal, .mCS-dir-rtl-custom > .mCustomScrollBox.mCSB_vertical_horizontal.mCSB_inside > .mCSB_scrollTools.mCSB_scrollTools_horizontal { right: 0; } /* RTL direction/left-side scrollbar */ .mCS-dir-rtl-custom > .mCustomScrollBox.mCSB_vertical_horizontal.mCSB_inside > .mCSB_scrollTools.mCSB_scrollTools_horizontal { left: 20px; } /* non-visible scrollbar/RTL direction/left-side scrollbar */ .mCS-dir-rtl-custom > .mCustomScrollBox.mCSB_vertical_horizontal.mCSB_inside > .mCSB_container_wrapper.mCS_no_scrollbar_y.mCS_y_hidden + .mCSB_scrollTools ~ .mCSB_scrollTools.mCSB_scrollTools_horizontal { left: 0; } .mCS-dir-rtl-custom > .mCSB_inside > .mCSB_container_wrapper { /* RTL direction/left-side scrollbar */ margin-right: 0; margin-left: 30px; } .mCSB_container_wrapper.mCS_no_scrollbar_y.mCS_y_hidden > .mCSB_container { padding-right: 0; } .mCSB_container_wrapper.mCS_no_scrollbar_x.mCS_x_hidden > .mCSB_container { padding-bottom: 0; } .mCustomScrollBox.mCSB_vertical_horizontal.mCSB_inside > .mCSB_container_wrapper.mCS_no_scrollbar_y.mCS_y_hidden { margin-right: 0; /* non-visible scrollbar */ margin-left: 0; } /* non-visible horizontal scrollbar */ .mCustomScrollBox.mCSB_vertical_horizontal.mCSB_inside > .mCSB_container_wrapper.mCS_no_scrollbar_x.mCS_x_hidden { margin-bottom: 0; } /* +------------------------------------------------------------------------------------------------------------------------ +5. TRANSITIONS +------------------------------------------------------------------------------------------------------------------------ +*/ .mCSB_scrollTools, .mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar, .mCSB_scrollTools .mCSB_buttonUp, .mCSB_scrollTools .mCSB_buttonDown, .mCSB_scrollTools .mCSB_buttonLeft, .mCSB_scrollTools .mCSB_buttonRight { -webkit-transition: opacity .2s ease-in-out, background-color .2s ease-in-out; -moz-transition: opacity .2s ease-in-out, background-color .2s ease-in-out; -o-transition: opacity .2s ease-in-out, background-color .2s ease-in-out; transition: opacity .2s ease-in-out, background-color .2s ease-in-out; } .mCSB_scrollTools_vertical.mCSB_scrollTools_onDrag_expand .mCSB_dragger_bar, .mCSB_scrollTools_vertical.mCSB_scrollTools_onDrag_expand .mCSB_draggerRail, .mCSB_scrollTools_horizontal.mCSB_scrollTools_onDrag_expand .mCSB_dragger_bar, .mCSB_scrollTools_horizontal.mCSB_scrollTools_onDrag_expand .mCSB_draggerRail { -webkit-transition: width .2s ease-out .2s, height .2s ease-out .2s, margin-left .2s ease-out .2s, margin-right .2s ease-out .2s, margin-top .2s ease-out .2s, margin-bottom .2s ease-out .2s, opacity .2s ease-in-out, background-color .2s ease-in-out; -moz-transition: width .2s ease-out .2s, height .2s ease-out .2s, margin-left .2s ease-out .2s, margin-right .2s ease-out .2s, margin-top .2s ease-out .2s, margin-bottom .2s ease-out .2s, opacity .2s ease-in-out, background-color .2s ease-in-out; -o-transition: width .2s ease-out .2s, height .2s ease-out .2s, margin-left .2s ease-out .2s, margin-right .2s ease-out .2s, margin-top .2s ease-out .2s, margin-bottom .2s ease-out .2s, opacity .2s ease-in-out, background-color .2s ease-in-out; transition: width .2s ease-out .2s, height .2s ease-out .2s, margin-left .2s ease-out .2s, margin-right .2s ease-out .2s, margin-top .2s ease-out .2s, margin-bottom .2s ease-out .2s, opacity .2s ease-in-out, background-color .2s ease-in-out; } /* +------------------------------------------------------------------------------------------------------------------------ +6. SCROLLBAR COLORS, OPACITY AND BACKGROUNDS +------------------------------------------------------------------------------------------------------------------------ +*/ /* +---------------------------------------- +6.1 THEMES +---------------------------------------- +*/ /* default theme ("light") */ .mCSB_scrollTools .mCSB_draggerRail { background-color: transparent; } .mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar { background-color: #f2f3f8; } .mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar, .mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar, .mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar { background-color: #d1d4e6; } .mCSB_scrollTools .mCSB_buttonUp, .mCSB_scrollTools .mCSB_buttonDown, .mCSB_scrollTools .mCSB_buttonLeft, .mCSB_scrollTools .mCSB_buttonRight { background-image: url(../images/mCSB_buttons.png); /* css sprites */ background-repeat: no-repeat; opacity: 0.4; } .mCSB_scrollTools .mCSB_buttonUp { background-position: 0 0; } .mCSB_scrollTools .mCSB_buttonDown { background-position: 0 -20px; } .mCSB_scrollTools .mCSB_buttonLeft { background-position: 0 -40px; } .mCSB_scrollTools .mCSB_buttonRight { background-position: 0 -56px; } .mCSB_scrollTools .mCSB_buttonUp:hover, .mCSB_scrollTools .mCSB_buttonDown:hover, .mCSB_scrollTools .mCSB_buttonLeft:hover, .mCSB_scrollTools .mCSB_buttonRight:hover { opacity: 0.75; } .mCSB_scrollTools .mCSB_buttonUp:active, .mCSB_scrollTools .mCSB_buttonDown:active, .mCSB_scrollTools .mCSB_buttonLeft:active, .mCSB_scrollTools .mCSB_buttonRight:active { opacity: 0.9; } /* theme: "dark" */ .mCS-dark.mCSB_scrollTools .mCSB_draggerRail { background-color: #000000; background-color: rgba(0, 0, 0, 0.15); } .mCS-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar { background-color: #000000; background-color: rgba(0, 0, 0, 0.75); } .mCS-dark.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar { background-color: rgba(0, 0, 0, 0.85); } .mCS-dark.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar, .mCS-dark.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar { background-color: rgba(0, 0, 0, 0.9); } .mCS-dark.mCSB_scrollTools .mCSB_buttonUp { background-position: -80px 0; } .mCS-dark.mCSB_scrollTools .mCSB_buttonDown { background-position: -80px -20px; } .mCS-dark.mCSB_scrollTools .mCSB_buttonLeft { background-position: -80px -40px; } .mCS-dark.mCSB_scrollTools .mCSB_buttonRight { background-position: -80px -56px; } /* +* +* Owl Carousel +*/ .owl-carousel .animated { animation-duration: 1000ms; animation-fill-mode: both; } .owl-carousel .owl-animated-in { z-index: 0; } .owl-carousel .owl-animated-out { z-index: 1; } .owl-carousel .fadeOut { animation-name: fadeOut; } @keyframes fadeOut { 0% { opacity: 1; } 100% { opacity: 0; } } /* + * Owl Carousel - Auto Height Plugin + */ .owl-height { transition: height 500ms ease-in-out; } /* + * Core Owl Carousel CSS File + */ .owl-carousel { display: none; width: 100%; -webkit-tap-highlight-color: transparent; /* position relative and z-index fix webkit rendering fonts issue */ position: relative; z-index: 1; } .owl-carousel .owl-stage { position: relative; -ms-touch-action: pan-Y; } .owl-carousel .owl-stage:after { content: "."; display: block; clear: both; visibility: hidden; line-height: 0; height: 0; } .owl-carousel .owl-stage-outer { position: relative; overflow: hidden; /* fix for flashing background */ -webkit-transform: translate3d(0px, 0px, 0px); } .owl-carousel .owl-controls .owl-nav .owl-prev, .owl-carousel .owl-controls .owl-nav .owl-next, .owl-carousel .owl-controls .owl-dot { cursor: pointer; user-select: none; } .owl-carousel.owl-loaded { display: block; } .owl-carousel.owl-loading { opacity: 0; display: block; } .owl-carousel.owl-hidden { opacity: 0; } .owl-carousel .owl-refresh .owl-item { display: none; } .owl-carousel .owl-item { position: relative; min-height: 1px; float: left; -webkit-tap-highlight-color: transparent; -webkit-touch-callout: none; } .owl-carousel .owl-grab { cursor: move; cursor: -webkit-grab; cursor: grab; } .owl-carousel.owl-rtl { direction: rtl; } .owl-carousel.owl-rtl .owl-item { float: right; } /* No Js */ .no-js .owl-carousel { display: block; } /* + * Owl Carousel - Lazy Load Plugin + */ .owl-carousel .owl-item .owl-lazy { opacity: 0; transition: opacity 400ms ease; } /* + * Owl Carousel - Video Plugin + */ .owl-carousel .owl-video-wrapper { position: relative; height: 100%; background: #000000; } .owl-carousel .owl-video-play-icon { position: absolute; height: 80px; width: 80px; left: 50%; top: 50%; margin-left: -40px; margin-top: -40px; font: 400 40px/80px 'FontAwesome'; cursor: pointer; z-index: 1; transition: scale 100ms ease; } .owl-carousel .owl-video-play-icon:before { content: '\f144'; } .owl-carousel .owl-video-play-icon:hover { transform: scale(1.3); } .owl-carousel .owl-video-playing .owl-video-tn, .owl-carousel .owl-video-playing .owl-video-play-icon { display: none; } .owl-carousel .owl-video-tn { opacity: 0; height: 100%; background-position: center center; background-repeat: no-repeat; background-size: contain; transition: opacity 400ms ease; } .owl-carousel .owl-video-frame { position: relative; z-index: 1; } /* + * Owl Navigation + */ .owl-nav.disabled { display: none !important; } .owl-prev, .owl-next { position: absolute; top: 50%; transform: translateY(-50%); font: 400 46px/50px 'FontAwesome'; color: #aeb1be; } .owl-prev:hover, .owl-next:hover { color: #0045b6; } .owl-prev { left: 0; } .owl-prev::before { content: '\f104'; } .owl-next { right: 0; } .owl-next::before { content: '\f105'; } /* + * Owl Pagination + */ .owl-dots { margin-top: 10px; } .owl-dots.disabled { display: none !important; } @media (min-width: 576px) { .owl-dots { margin-top: 25px; } } button.owl-dot { background: none; border: none; display: inline-block; padding: 0; outline: none; outline-offset: 0; cursor: pointer; -webkit-appearance: none; } button.owl-dot::-moz-focus-inner { border: none; padding: 0; } .owl-dot { position: relative; display: inline-flex; align-items: center; justify-content: center; vertical-align: middle; width: 14px; height: 14px; border-radius: 50%; margin-left: 14px; margin-right: 14px; text-align: center; outline: none; cursor: pointer; } .owl-dot::before { content: ''; display: block; width: 100%; height: 100%; transform: scale3d(0.56, 0.56, 0.56); background: #e8e9ee; border-radius: inherit; transition: .2s; } .owl-dot:only-child { display: none; } .owl-dot:hover::before, .owl-dot.active::before { transform: scale3d(1, 1, 1); background-color: #0045b6; } * + .owl-carousel { margin-top: 30px; } .owl-carousel-1 { text-align: center; } .owl-carousel-1 .owl-stage { position: relative; display: flex; align-items: center; } .owl-carousel-centered-pagination .owl-dots { text-align: center; } .owl-carousel-indigo { position: relative; overflow: hidden; max-width: 100vw; padding: 0 15px; margin-left: auto; margin-right: auto; pointer-events: none; } .owl-carousel-indigo, .owl-carousel-indigo * { backface-visibility: hidden; } .owl-carousel-indigo .owl-stage-outer { padding: 23px; margin: -23px; } .owl-carousel-indigo .owl-item.active { pointer-events: auto; } .owl-carousel-indigo .owl-dots { pointer-events: auto; } .owl-carousel-indigo * + .owl-dots { margin-top: 25px; } @media (max-width: 575.98px) { .owl-carousel-indigo .owl-dot { width: 11px; height: 11px; margin: 0 6px; } } @media (min-width: 768px) { .owl-carousel-indigo { padding: 0 0; } .owl-carousel-indigo * + .owl-dots { margin-top: 60px; } } .owl-carousel-dots-space .owl-dots { margin-bottom: 25px; } .lg-sub-html, .lg-toolbar { background-color: rgba(0, 0, 0, 0.45); } @font-face { font-family: lg; src: url(../fonts/lg.eot?n1z373); src: url(../fonts/lg.eot?#iefixn1z373) format("embedded-opentype"), url(../fonts/lg.woff?n1z373) format("woff"), url(../fonts/lg.ttf?n1z373) format("truetype"), url(../fonts/lg.svg?n1z373#lg) format("svg"); font-weight: 400; font-style: normal; } .lg-icon { font-family: lg; speak: none; font-style: normal; font-weight: 400; font-variant: normal; text-transform: none; line-height: 1; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } .lg-actions .lg-next, .lg-actions .lg-prev { background-color: rgba(0, 0, 0, 0.45); border-radius: 2px; color: #999; cursor: pointer; display: block; font-size: 22px; margin-top: -10px; padding: 8px 10px 9px; position: absolute; top: 50%; z-index: 1180; border: none; outline: 0; } .lg-actions .lg-next.disabled, .lg-actions .lg-prev.disabled { pointer-events: none; opacity: .5; } .lg-actions .lg-next:hover, .lg-actions .lg-prev:hover { color: #fff; } .lg-actions .lg-next { right: 20px; } .lg-actions .lg-next:before { content: "\e095"; } .lg-actions .lg-prev { left: 20px; } .lg-actions .lg-prev:after { content: "\e094"; } @-webkit-keyframes lg-right-end { 0%, 100% { left: 0; } 50% { left: -30px; } } @-moz-keyframes lg-right-end { 0%, 100% { left: 0; } 50% { left: -30px; } } @-ms-keyframes lg-right-end { 0%, 100% { left: 0; } 50% { left: -30px; } } @keyframes lg-right-end { 0%, 100% { left: 0; } 50% { left: -30px; } } @-webkit-keyframes lg-left-end { 0%, 100% { left: 0; } 50% { left: 30px; } } @-moz-keyframes lg-left-end { 0%, 100% { left: 0; } 50% { left: 30px; } } @-ms-keyframes lg-left-end { 0%, 100% { left: 0; } 50% { left: 30px; } } @keyframes lg-left-end { 0%, 100% { left: 0; } 50% { left: 30px; } } .lg-outer.lg-right-end .lg-object { -webkit-animation: lg-right-end .3s; -o-animation: lg-right-end .3s; animation: lg-right-end .3s; position: relative; } .lg-outer.lg-left-end .lg-object { -webkit-animation: lg-left-end .3s; -o-animation: lg-left-end .3s; animation: lg-left-end .3s; position: relative; } .lg-toolbar { z-index: 1182; left: 0; position: absolute; top: 0; width: 100%; } .lg-toolbar .lg-icon { color: #999; cursor: pointer; float: right; font-size: 24px; height: 47px; line-height: 27px; padding: 10px 0; text-align: center; width: 50px; text-decoration: none !important; outline: 0; transition: color .2s linear; } @media (max-width: 575.98px) { .lg-toolbar .lg-icon { font-size: 21px; width: 36px; } } @media (max-width: 575.98px) { #lg-counter { font-size: 14px; line-height: 27px; } } .lg-toolbar .lg-icon:hover { color: #fff; } .lg-toolbar .lg-close:after { content: "\e070"; } .lg-toolbar .lg-download:after { content: "\e0f2"; } .lg-sub-html { bottom: 0; color: #eee; font-size: 16px; left: 0; padding: 10px 40px; position: fixed; right: 0; text-align: center; z-index: 1180; } .lg-sub-html h4 { margin: 0; font-size: 13px; font-weight: 700; } .lg-sub-html p { font-size: 12px; margin: 5px 0 0; } #lg-counter { color: #999; display: inline-block; font-size: 16px; padding-left: 20px; padding-top: 12px; vertical-align: middle; } .lg-next, .lg-prev, .lg-toolbar { opacity: 1; transition: transform 0.35s cubic-bezier(0, 0, 0.25, 1) 0s, opacity 0.35s cubic-bezier(0, 0, 0.25, 1) 0s, color 0.2s linear; } .lg-hide-items .lg-prev { opacity: 0; transform: translate3d(-10px, 0, 0); } .lg-hide-items .lg-next { opacity: 0; transform: translate3d(10px, 0, 0); } .lg-hide-items .lg-toolbar { opacity: 0; transform: translate3d(0, -10px, 0); } body:not(.lg-from-hash) .lg-outer.lg-start-zoom .lg-object { transform: scale3d(0.5, 0.5, 0.5); opacity: 0; transition: transform 250ms cubic-bezier(0, 0, 0.25, 1) 0s, opacity 250ms cubic-bezier(0, 0, 0.25, 1) !important; -moz-transform-origin: 50% 50%; -ms-transform-origin: 50% 50%; transform-origin: 50% 50%; } body:not(.lg-from-hash) .lg-outer.lg-start-zoom .lg-item.lg-complete .lg-object { transform: scale3d(1, 1, 1); opacity: 1; } .lg-outer .lg-thumb-outer { background-color: #0d0a0a; bottom: 0; position: absolute; width: 100%; z-index: 1180; max-height: 350px; transform: translate3d(0, 100%, 0); transition: transform 0.25s cubic-bezier(0, 0, 0.25, 1) 0s; } .lg-outer .lg-thumb-outer.lg-grab .lg-thumb-item { cursor: -webkit-grab; cursor: -moz-grab; cursor: -o-grab; cursor: -ms-grab; cursor: grab; } .lg-outer .lg-thumb-outer.lg-grabbing .lg-thumb-item { cursor: move; cursor: -webkit-grabbing; cursor: -moz-grabbing; cursor: -o-grabbing; cursor: -ms-grabbing; cursor: grabbing; } .lg-outer .lg-thumb-outer.lg-dragging .lg-thumb { transition-duration: 0s !important; } .lg-outer.lg-thumb-open .lg-thumb-outer { transform: translate3d(0, 0, 0); } .lg-outer .lg-thumb { padding: 10px 0; height: 100%; margin-bottom: -5px; margin-left: auto; margin-right: auto; } .lg-outer .lg-thumb-item { cursor: pointer; float: left; overflow: hidden; height: 100%; border: 2px solid #fff; border-radius: 4px; margin-bottom: 5px; } @media (min-width: 1025px) { .lg-outer .lg-thumb-item { transition: border-color .25s ease; } } .lg-outer .lg-thumb-item.active, .lg-outer .lg-thumb-item:hover { border-color: #a90707; } .lg-outer .lg-thumb-item img { width: 100%; height: 100%; object-fit: cover; } .lg-outer.lg-has-thumb .lg-item { padding-bottom: 120px; } .lg-outer.lg-can-toggle .lg-item { padding-bottom: 0; } .lg-outer.lg-pull-caption-up .lg-sub-html { transition: bottom .25s ease; } .lg-outer.lg-pull-caption-up.lg-thumb-open .lg-sub-html { bottom: 100px; } .lg-outer .lg-toogle-thumb { background-color: #0d0a0a; border-radius: 2px 2px 0 0; color: #999; cursor: pointer; font-size: 24px; height: 39px; line-height: 27px; padding: 5px 0; position: absolute; right: 20px; text-align: center; top: -39px; width: 50px; } .lg-outer .lg-toogle-thumb:hover, .lg-outer.lg-dropdown-active #lg-share { color: #fff; } .lg-outer .lg-toogle-thumb:after { content: "\e1ff"; } .lg-outer .lg-video-cont { display: inline-block; vertical-align: middle; max-width: 1140px; max-height: 100%; width: 100%; padding: 0 5px; } .lg-outer .lg-video { width: 100%; height: 0; padding-bottom: 56.25%; overflow: hidden; position: relative; } .lg-outer .lg-video .lg-object { display: inline-block; position: absolute; top: 0; left: 0; width: 100% !important; height: 100% !important; } .lg-outer .lg-video .lg-video-play { width: 84px; height: 59px; position: absolute; left: 50%; top: 50%; margin-left: -42px; margin-top: -30px; z-index: 1180; cursor: pointer; } .lg-outer .lg-has-iframe .lg-video { -webkit-overflow-scrolling: touch; overflow: auto; } .lg-outer .lg-has-vimeo .lg-video-play { background: url(../images/vimeo-play.png) no-repeat; } .lg-outer .lg-has-vimeo:hover .lg-video-play { background: url(../images/vimeo-play.png) 0 -58px no-repeat; } .lg-outer .lg-has-html5 .lg-video-play { background: url(../images/video-play.png) no-repeat; height: 64px; margin-left: -32px; margin-top: -32px; width: 64px; opacity: .8; } .lg-outer .lg-has-html5:hover .lg-video-play { opacity: 1; } .lg-outer .lg-has-youtube .lg-video-play { background: url(../images/youtube-play.png) no-repeat; } .lg-outer .lg-has-youtube:hover .lg-video-play { background: url(../images/youtube-play.png) 0 -60px no-repeat; } .lg-outer .lg-video-object { width: 100% !important; height: 100% !important; position: absolute; top: 0; left: 0; } .lg-outer .lg-has-video .lg-video-object { visibility: hidden; } .lg-outer .lg-has-video.lg-video-playing .lg-object, .lg-outer .lg-has-video.lg-video-playing .lg-video-play { display: none; } .lg-outer .lg-has-video.lg-video-playing .lg-video-object { visibility: visible; } .lg-progress-bar { background-color: #333; height: 5px; left: 0; position: absolute; top: 0; width: 100%; z-index: 1183; opacity: 0; transition: opacity 80ms ease 0s; } .lg-progress-bar .lg-progress { background-color: #a90707; height: 5px; width: 0; } .lg-progress-bar.lg-start .lg-progress { width: 100%; } .lg-show-autoplay .lg-progress-bar { opacity: 1; } .lg-autoplay-button:after { content: "\e01d"; } .lg-show-autoplay .lg-autoplay-button:after { content: "\e01a"; } .lg-outer.lg-css3.lg-zoom-dragging .lg-item.lg-complete.lg-zoomable .lg-image, .lg-outer.lg-css3.lg-zoom-dragging .lg-item.lg-complete.lg-zoomable .lg-img-wrap { transition-duration: 0s; } .lg-outer.lg-use-transition-for-zoom .lg-item.lg-complete.lg-zoomable .lg-img-wrap { transition: transform 0.3s cubic-bezier(0, 0, 0.25, 1) 0s; } .lg-outer.lg-use-left-for-zoom .lg-item.lg-complete.lg-zoomable .lg-img-wrap { transition: left 0.3s cubic-bezier(0, 0, 0.25, 1) 0s, top 0.3s cubic-bezier(0, 0, 0.25, 1) 0s; } .lg-outer .lg-item.lg-complete.lg-zoomable .lg-img-wrap { transform: translate3d(0, 0, 0); -webkit-backface-visibility: hidden; -moz-backface-visibility: hidden; backface-visibility: hidden; } .lg-outer .lg-item.lg-complete.lg-zoomable .lg-image { transform: scale3d(1, 1, 1); transition: transform 0.3s cubic-bezier(0, 0, 0.25, 1) 0s, opacity 0.15s !important; -moz-transform-origin: 0 0; -ms-transform-origin: 0 0; transform-origin: 0 0; -webkit-backface-visibility: hidden; -moz-backface-visibility: hidden; backface-visibility: hidden; } #lg-zoom-in:after { content: "\e311"; } #lg-actual-size { font-size: 20px; } #lg-actual-size:after { content: "\e033"; } #lg-zoom-out { opacity: .5; pointer-events: none; } #lg-zoom-out:after { content: "\e312"; } .lg-zoomed #lg-zoom-out { opacity: 1; pointer-events: auto; } .lg-outer .lg-pager-outer { bottom: 60px; left: 0; position: absolute; right: 0; text-align: center; z-index: 1180; height: 10px; } .lg-outer .lg-pager-outer.lg-pager-hover .lg-pager-cont { overflow: visible; } .lg-outer .lg-pager-cont { cursor: pointer; display: inline-block; overflow: hidden; position: relative; vertical-align: top; margin: 0 5px; } .lg-outer .lg-pager-cont:hover .lg-pager-thumb-cont { opacity: 1; transform: translate3d(0, 0, 0); } .lg-outer .lg-pager-cont.lg-pager-active .lg-pager { box-shadow: 0 0 0 2px #fff inset; } .lg-outer .lg-pager-thumb-cont { background-color: #fff; color: #fff; bottom: 100%; height: 83px; left: 0; margin-bottom: 20px; margin-left: -60px; opacity: 0; padding: 5px; position: absolute; width: 120px; border-radius: 3px; transition: opacity .15s ease 0s, transform .15s ease 0s; transform: translate3d(0, 5px, 0); } .lg-outer .lg-pager-thumb-cont img { width: 100%; height: 100%; } .lg-outer .lg-pager { background-color: rgba(255, 255, 255, 0.5); border-radius: 50%; box-shadow: 0 0 0 8px rgba(255, 255, 255, 0.7) inset; display: block; height: 12px; transition: box-shadow .3s ease 0s; width: 12px; } .lg-outer .lg-pager:focus, .lg-outer .lg-pager:hover { box-shadow: 0 0 0 8px #fff inset; } .lg-outer .lg-caret { border-left: 10px solid transparent; border-right: 10px solid transparent; border-top: 10px dashed; bottom: -10px; display: inline-block; height: 0; left: 50%; margin-left: -5px; position: absolute; vertical-align: middle; width: 0; } .lg-fullscreen:after { content: "\e20c"; } .lg-fullscreen-on .lg-fullscreen:after { content: "\e20d"; } .lg-outer #lg-dropdown-overlay { background-color: rgba(0, 0, 0, 0.25); bottom: 0; cursor: default; left: 0; position: fixed; right: 0; top: 0; z-index: 1181; opacity: 0; visibility: hidden; transition: visibility 0s linear .18s, opacity .18s linear 0s; } .lg-outer.lg-dropdown-active #lg-dropdown-overlay, .lg-outer.lg-dropdown-active .lg-dropdown { transition-delay: 0s; -moz-transform: translate3d(0, 0, 0); -ms-transform: translate3d(0, 0, 0); transform: translate3d(0, 0, 0); opacity: 1; visibility: visible; } .lg-outer .lg-dropdown { background-color: #fff; border-radius: 2px; font-size: 14px; list-style-type: none; margin: 0; padding: 10px 0; position: absolute; right: 0; text-align: left; top: 50px; opacity: 0; visibility: hidden; -moz-transform: translate3d(0, 5px, 0); -ms-transform: translate3d(0, 5px, 0); transform: translate3d(0, 5px, 0); transition: transform .18s linear 0s, visibility 0s linear .5s, opacity .18s linear 0s; } .lg-outer .lg-dropdown:after { content: ""; display: block; height: 0; width: 0; position: absolute; border: 8px solid transparent; border-bottom-color: #fff; right: 16px; top: -16px; } .lg-outer .lg-dropdown > li:last-child { margin-bottom: 0; } .lg-outer .lg-dropdown > li:hover .lg-icon, .lg-outer .lg-dropdown > li:hover a { color: #333; } .lg-outer .lg-dropdown a { color: #333; display: block; white-space: pre; padding: 4px 12px; font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 12px; } .lg-outer .lg-dropdown a:hover { background-color: rgba(0, 0, 0, 0.07); } .lg-outer .lg-dropdown .lg-dropdown-text { display: inline-block; line-height: 1; margin-top: -3px; vertical-align: middle; } .lg-outer .lg-dropdown .lg-icon { color: #333; display: inline-block; float: none; font-size: 20px; height: auto; line-height: 1; margin-right: 8px; padding: 0; vertical-align: middle; width: auto; } .lg-outer, .lg-outer .lg, .lg-outer .lg-inner { width: 100%; height: 100%; } .lg-outer #lg-share { position: relative; } .lg-outer #lg-share:after { content: "\e80d"; } .lg-outer #lg-share-facebook .lg-icon { color: #3b5998; } .lg-outer #lg-share-facebook .lg-icon:after { content: "\e901"; } .lg-outer #lg-share-twitter .lg-icon { color: #00aced; } .lg-outer #lg-share-twitter .lg-icon:after { content: "\e904"; } .lg-outer #lg-share-googleplus .lg-icon { color: #dd4b39; } .lg-outer #lg-share-googleplus .lg-icon:after { content: "\e902"; } .lg-outer #lg-share-pinterest .lg-icon { color: #cb2027; } .lg-outer #lg-share-pinterest .lg-icon:after { content: "\e903"; } .lg-group:after { content: ""; display: table; clear: both; } .lg-outer { position: fixed; top: 0; left: 0; z-index: 1150; text-align: left; opacity: 0; transition: opacity .15s ease 0s; } .lg-outer * { -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; } .lg-outer.lg-visible { opacity: 1; } .lg-outer.lg-css3 .lg-item.lg-current, .lg-outer.lg-css3 .lg-item.lg-next-slide, .lg-outer.lg-css3 .lg-item.lg-prev-slide { transition-duration: inherit !important; transition-timing-function: inherit !important; } .lg-outer.lg-css3.lg-dragging .lg-item.lg-current, .lg-outer.lg-css3.lg-dragging .lg-item.lg-next-slide, .lg-outer.lg-css3.lg-dragging .lg-item.lg-prev-slide { transition-duration: 0s !important; opacity: 1; } .lg-outer.lg-grab img.lg-object { cursor: -webkit-grab; cursor: -moz-grab; cursor: -o-grab; cursor: -ms-grab; cursor: grab; } .lg-outer.lg-grabbing img.lg-object { cursor: move; cursor: -webkit-grabbing; cursor: -moz-grabbing; cursor: -o-grabbing; cursor: -ms-grabbing; cursor: grabbing; } .lg-outer .lg { position: relative; overflow: hidden; margin-left: auto; margin-right: auto; max-width: 100%; max-height: 100%; } .lg-outer .lg-inner { position: absolute; left: 0; top: 0; white-space: nowrap; } .lg-outer .lg-item { background: url(../images/loading.gif) center center no-repeat; display: none !important; } .lg-outer.lg-css .lg-current, .lg-outer.lg-css3 .lg-current, .lg-outer.lg-css3 .lg-next-slide, .lg-outer.lg-css3 .lg-prev-slide { display: inline-block !important; } .lg-outer .lg-img-wrap, .lg-outer .lg-item { display: inline-block; text-align: center; position: absolute; width: 100%; height: 100%; } .lg-outer .lg-img-wrap:before, .lg-outer .lg-item:before { content: ""; display: inline-block; height: 50%; width: 1px; margin-right: -1px; } .lg-outer .lg-img-wrap { position: absolute; padding: 0 5px; left: 0; right: 0; top: 0; bottom: 0; } .lg-outer .lg-item.lg-complete { background-image: none; } .lg-outer .lg-item.lg-current { z-index: 1160; } .lg-outer .lg-image { display: inline-block; vertical-align: middle; max-width: 100%; max-height: 100%; width: auto !important; height: auto !important; } .lg-outer.lg-show-after-load .lg-item .lg-object, .lg-outer.lg-show-after-load .lg-item .lg-video-play { opacity: 0; transition: opacity .15s ease 0s; } .lg-outer.lg-show-after-load .lg-item.lg-complete .lg-object, .lg-outer.lg-show-after-load .lg-item.lg-complete .lg-video-play { opacity: 1; } .lg-outer .lg-empty-html, .lg-outer.lg-hide-download #lg-download { display: none; } .lg-backdrop { position: fixed; top: 0; left: 0; right: 0; bottom: 0; z-index: 1140; background-color: #000; opacity: 0; transition: opacity .15s ease 0s; } .lg-css3.lg-no-trans .lg-current, .lg-css3.lg-no-trans .lg-next-slide, .lg-css3.lg-no-trans .lg-prev-slide { transition: none 0s ease 0s !important; } .lg-css3.lg-use-css3 .lg-item, .lg-css3.lg-use-left .lg-item { -webkit-backface-visibility: hidden; -moz-backface-visibility: hidden; backface-visibility: hidden; } .lg-css3.lg-fade .lg-item { opacity: 0; } .lg-css3.lg-fade .lg-item.lg-current { opacity: 1; } .lg-css3.lg-fade .lg-item.lg-current, .lg-css3.lg-fade .lg-item.lg-next-slide, .lg-css3.lg-fade .lg-item.lg-prev-slide { transition: opacity .1s ease 0s; } .lg-css3.lg-slide.lg-use-css3 .lg-item { opacity: 0; } .lg-css3.lg-slide.lg-use-css3 .lg-item.lg-prev-slide { transform: translate3d(-100%, 0, 0); } .lg-css3.lg-slide.lg-use-css3 .lg-item.lg-next-slide { transform: translate3d(100%, 0, 0); } .lg-css3.lg-slide.lg-use-css3 .lg-item.lg-current { transform: translate3d(0, 0, 0); opacity: 1; } .lg-css3.lg-slide.lg-use-css3 .lg-item.lg-current, .lg-css3.lg-slide.lg-use-css3 .lg-item.lg-next-slide, .lg-css3.lg-slide.lg-use-css3 .lg-item.lg-prev-slide { transition: transform 1s cubic-bezier(0, 0, 0.25, 1) 0s, opacity 0.1s ease 0s; } .lg-css3.lg-slide.lg-use-left .lg-item { opacity: 0; position: absolute; left: 0; } .lg-css3.lg-slide.lg-use-left .lg-item.lg-prev-slide { left: -100%; } .lg-css3.lg-slide.lg-use-left .lg-item.lg-next-slide { left: 100%; } .lg-css3.lg-slide.lg-use-left .lg-item.lg-current { left: 0; opacity: 1; } .lg-css3.lg-slide.lg-use-left .lg-item.lg-current, .lg-css3.lg-slide.lg-use-left .lg-item.lg-next-slide, .lg-css3.lg-slide.lg-use-left .lg-item.lg-prev-slide { transition: left 1s cubic-bezier(0, 0, 0.25, 1) 0s, opacity 0.1s ease 0s; } .lg-sub-html, .lg-toolbar { background-color: rgba(0, 0, 0, 0.45); } @font-face { font-family: lg; src: url(../fonts/lg.eot?n1z373); src: url(../fonts/lg.eot?#iefixn1z373) format("embedded-opentype"), url(../fonts/lg.woff?n1z373) format("woff"), url(../fonts/lg.ttf?n1z373) format("truetype"), url(../fonts/lg.svg?n1z373#lg) format("svg"); font-weight: 400; font-style: normal; } .lg-icon { font-family: lg; speak: none; font-style: normal; font-weight: 400; font-variant: normal; text-transform: none; line-height: 1; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } .lg-actions .lg-next, .lg-actions .lg-prev { background-color: rgba(0, 0, 0, 0.45); border-radius: 2px; color: #999; cursor: pointer; display: block; font-size: 22px; margin-top: -10px; padding: 8px 10px 9px; position: absolute; top: 50%; z-index: 1180; border: none; outline: 0; } .lg-actions .lg-next.disabled, .lg-actions .lg-prev.disabled { pointer-events: none; opacity: .5; } .lg-actions .lg-next:hover, .lg-actions .lg-prev:hover { color: #fff; } .lg-actions .lg-next { right: 20px; } .lg-actions .lg-next:before { content: "\e095"; } .lg-actions .lg-prev { left: 20px; } .lg-actions .lg-prev:after { content: "\e094"; } @-webkit-keyframes lg-right-end { 0%, 100% { left: 0; } 50% { left: -30px; } } @-moz-keyframes lg-right-end { 0%, 100% { left: 0; } 50% { left: -30px; } } @-ms-keyframes lg-right-end { 0%, 100% { left: 0; } 50% { left: -30px; } } @keyframes lg-right-end { 0%, 100% { left: 0; } 50% { left: -30px; } } @-webkit-keyframes lg-left-end { 0%, 100% { left: 0; } 50% { left: 30px; } } @-moz-keyframes lg-left-end { 0%, 100% { left: 0; } 50% { left: 30px; } } @-ms-keyframes lg-left-end { 0%, 100% { left: 0; } 50% { left: 30px; } } @keyframes lg-left-end { 0%, 100% { left: 0; } 50% { left: 30px; } } .lg-outer.lg-right-end .lg-object { -webkit-animation: lg-right-end .3s; -o-animation: lg-right-end .3s; animation: lg-right-end .3s; position: relative; } .lg-outer.lg-left-end .lg-object { -webkit-animation: lg-left-end .3s; -o-animation: lg-left-end .3s; animation: lg-left-end .3s; position: relative; } .lg-toolbar { z-index: 1182; left: 0; position: absolute; top: 0; width: 100%; } .lg-toolbar .lg-icon:hover { color: #fff; } .lg-toolbar .lg-close:after { content: "\e070"; } .lg-toolbar .lg-download:after { content: "\e0f2"; } .lg-sub-html { bottom: 0; color: #eee; font-size: 16px; left: 0; padding: 10px 40px; position: fixed; right: 0; text-align: center; z-index: 1180; } .lg-sub-html h4 { margin: 0; font-size: 13px; font-weight: 700; } .lg-sub-html p { font-size: 12px; margin: 5px 0 0; } #lg-counter { color: #999; display: inline-block; font-size: 16px; padding-left: 20px; padding-top: 12px; vertical-align: middle; } .lg-next, .lg-prev, .lg-toolbar { opacity: 1; transition: transform 0.35s cubic-bezier(0, 0, 0.25, 1) 0s, opacity 0.35s cubic-bezier(0, 0, 0.25, 1) 0s, color 0.2s linear; } .lg-hide-items .lg-prev { opacity: 0; transform: translate3d(-10px, 0, 0); } .lg-hide-items .lg-next { opacity: 0; transform: translate3d(10px, 0, 0); } .lg-hide-items .lg-toolbar { opacity: 0; transform: translate3d(0, -10px, 0); } body:not(.lg-from-hash) .lg-outer.lg-start-zoom .lg-object { transform: scale3d(0.5, 0.5, 0.5); opacity: 0; transition: transform 250ms cubic-bezier(0, 0, 0.25, 1) 0s, opacity 250ms cubic-bezier(0, 0, 0.25, 1) !important; -moz-transform-origin: 50% 50%; -ms-transform-origin: 50% 50%; transform-origin: 50% 50%; } body:not(.lg-from-hash) .lg-outer.lg-start-zoom .lg-item.lg-complete .lg-object { transform: scale3d(1, 1, 1); opacity: 1; } .lg-outer .lg-thumb-outer { background-color: #0d0a0a; bottom: 0; position: absolute; width: 100%; z-index: 1180; max-height: 350px; transform: translate3d(0, 100%, 0); transition: transform 0.25s cubic-bezier(0, 0, 0.25, 1) 0s; } .lg-outer .lg-thumb-outer.lg-grab .lg-thumb-item { cursor: -webkit-grab; cursor: -moz-grab; cursor: -o-grab; cursor: -ms-grab; cursor: grab; } .lg-outer .lg-thumb-outer.lg-grabbing .lg-thumb-item { cursor: move; cursor: -webkit-grabbing; cursor: -moz-grabbing; cursor: -o-grabbing; cursor: -ms-grabbing; cursor: grabbing; } .lg-outer .lg-thumb-outer.lg-dragging .lg-thumb { transition-duration: 0s !important; } .lg-outer.lg-thumb-open .lg-thumb-outer { transform: translate3d(0, 0, 0); } .lg-outer .lg-thumb { padding: 10px 0; height: 100%; margin-bottom: -5px; } .lg-outer .lg-thumb-item { cursor: pointer; float: left; overflow: hidden; height: 100%; border: 2px solid rgba(255, 255, 255, 0.4); border-radius: 0; margin-bottom: 5px; } @media (min-width: 1025px) { .lg-outer .lg-thumb-item { transition: border-color .25s ease; } } .lg-outer .lg-thumb-item.active, .lg-outer .lg-thumb-item:hover { border-color: #0045b6; } .lg-outer .lg-thumb-item img { width: 100%; height: 100%; object-fit: cover; border-radius: 0; } .lg-outer.lg-has-thumb .lg-item { padding-bottom: 120px; } .lg-outer.lg-can-toggle .lg-item { padding-bottom: 0; } .lg-outer.lg-pull-caption-up .lg-sub-html { transition: bottom .25s ease; } .lg-outer.lg-pull-caption-up.lg-thumb-open .lg-sub-html { bottom: 100px; } .lg-outer .lg-toogle-thumb { background-color: #0d0a0a; border-radius: 2px 2px 0 0; color: #999; cursor: pointer; font-size: 24px; height: 39px; line-height: 27px; padding: 5px 0; position: absolute; right: 20px; text-align: center; top: -39px; width: 50px; } .lg-outer .lg-toogle-thumb:hover, .lg-outer.lg-dropdown-active #lg-share { color: #fff; } .lg-outer .lg-toogle-thumb:after { content: "\e1ff"; } .lg-outer .lg-video-cont { display: inline-block; vertical-align: middle; max-width: 1140px; max-height: 100%; width: 100%; padding: 0 5px; } .lg-outer .lg-video { width: 100%; height: 0; padding-bottom: 56.25%; overflow: hidden; position: relative; } .lg-outer .lg-video .lg-object { display: inline-block; position: absolute; top: 0; left: 0; width: 100% !important; height: 100% !important; } .lg-outer .lg-video .lg-video-play { width: 84px; height: 59px; position: absolute; left: 50%; top: 50%; margin-left: -42px; margin-top: -30px; z-index: 1180; cursor: pointer; } .lg-outer .lg-has-iframe .lg-video { -webkit-overflow-scrolling: touch; overflow: auto; } .lg-outer .lg-has-vimeo .lg-video-play { background: url(../images/vimeo-play.png) no-repeat; } .lg-outer .lg-has-vimeo:hover .lg-video-play { background: url(../images/vimeo-play.png) 0 -58px no-repeat; } .lg-outer .lg-has-html5 .lg-video-play { background: url(../images/video-play.png) no-repeat; height: 64px; margin-left: -32px; margin-top: -32px; width: 64px; opacity: .8; } .lg-outer .lg-has-html5:hover .lg-video-play { opacity: 1; } .lg-outer .lg-has-youtube .lg-video-play { background: url(../images/youtube-play.png) no-repeat; } .lg-outer .lg-has-youtube:hover .lg-video-play { background: url(../images/youtube-play.png) 0 -60px no-repeat; } .lg-outer .lg-video-object { width: 100% !important; height: 100% !important; position: absolute; top: 0; left: 0; } .lg-outer .lg-has-video .lg-video-object { visibility: hidden; } .lg-outer .lg-has-video.lg-video-playing .lg-object, .lg-outer .lg-has-video.lg-video-playing .lg-video-play { display: none; } .lg-outer .lg-has-video.lg-video-playing .lg-video-object { visibility: visible; } .lg-progress-bar { background-color: #333; height: 5px; left: 0; position: absolute; top: 0; width: 100%; z-index: 1183; opacity: 0; transition: opacity 80ms ease 0s; } .lg-progress-bar .lg-progress { background-color: #0045b6; height: 5px; width: 0; } .lg-progress-bar.lg-start .lg-progress { width: 100%; } .lg-show-autoplay .lg-progress-bar { opacity: 1; } .lg-autoplay-button:after { content: "\e01d"; } .lg-show-autoplay .lg-autoplay-button:after { content: "\e01a"; } .lg-outer.lg-css3.lg-zoom-dragging .lg-item.lg-complete.lg-zoomable .lg-image, .lg-outer.lg-css3.lg-zoom-dragging .lg-item.lg-complete.lg-zoomable .lg-img-wrap { transition-duration: 0s; } .lg-outer.lg-use-transition-for-zoom .lg-item.lg-complete.lg-zoomable .lg-img-wrap { transition: transform 0.3s cubic-bezier(0, 0, 0.25, 1) 0s; } .lg-outer.lg-use-left-for-zoom .lg-item.lg-complete.lg-zoomable .lg-img-wrap { transition: left 0.3s cubic-bezier(0, 0, 0.25, 1) 0s, top 0.3s cubic-bezier(0, 0, 0.25, 1) 0s; } .lg-outer .lg-item.lg-complete.lg-zoomable .lg-img-wrap { transform: translate3d(0, 0, 0); -webkit-backface-visibility: hidden; -moz-backface-visibility: hidden; backface-visibility: hidden; } .lg-outer .lg-item.lg-complete.lg-zoomable .lg-image { transform: scale3d(1, 1, 1); transition: transform 0.3s cubic-bezier(0, 0, 0.25, 1) 0s, opacity 0.15s !important; -moz-transform-origin: 0 0; -ms-transform-origin: 0 0; transform-origin: 0 0; -webkit-backface-visibility: hidden; -moz-backface-visibility: hidden; backface-visibility: hidden; } #lg-zoom-in:after { content: "\e311"; } #lg-actual-size { font-size: 20px; } #lg-actual-size:after { content: "\e033"; } #lg-zoom-out { opacity: .5; pointer-events: none; } #lg-zoom-out:after { content: "\e312"; } .lg-zoomed #lg-zoom-out { opacity: 1; pointer-events: auto; } .lg-outer .lg-pager-outer { bottom: 60px; left: 0; position: absolute; right: 0; text-align: center; z-index: 1180; height: 10px; } .lg-outer .lg-pager-outer.lg-pager-hover .lg-pager-cont { overflow: visible; } .lg-outer .lg-pager-cont { cursor: pointer; display: inline-block; overflow: hidden; position: relative; vertical-align: top; margin: 0 5px; } .lg-outer .lg-pager-cont:hover .lg-pager-thumb-cont { opacity: 1; transform: translate3d(0, 0, 0); } .lg-outer .lg-pager-cont.lg-pager-active .lg-pager { box-shadow: 0 0 0 2px #fff inset; } .lg-outer .lg-pager-thumb-cont { background-color: #fff; color: #fff; bottom: 100%; height: 83px; left: 0; margin-bottom: 20px; margin-left: -60px; opacity: 0; padding: 5px; position: absolute; width: 120px; border-radius: 3px; transition: opacity .15s ease 0s, transform .15s ease 0s; transform: translate3d(0, 5px, 0); } .lg-outer .lg-pager-thumb-cont img { width: 100%; height: 100%; } .lg-outer .lg-pager { background-color: rgba(255, 255, 255, 0.5); border-radius: 50%; box-shadow: 0 0 0 8px rgba(255, 255, 255, 0.7) inset; display: block; height: 12px; transition: box-shadow .3s ease 0s; width: 12px; } .lg-outer .lg-pager:focus, .lg-outer .lg-pager:hover { box-shadow: 0 0 0 8px #fff inset; } .lg-outer .lg-caret { border-left: 10px solid transparent; border-right: 10px solid transparent; border-top: 10px dashed; bottom: -10px; display: inline-block; height: 0; left: 50%; margin-left: -5px; position: absolute; vertical-align: middle; width: 0; } .lg-fullscreen:after { content: "\e20c"; } .lg-fullscreen-on .lg-fullscreen:after { content: "\e20d"; } .lg-outer #lg-dropdown-overlay { background-color: rgba(0, 0, 0, 0.25); bottom: 0; cursor: default; left: 0; position: fixed; right: 0; top: 0; z-index: 1181; opacity: 0; visibility: hidden; transition: visibility 0s linear .18s, opacity .18s linear 0s; } .lg-outer.lg-dropdown-active #lg-dropdown-overlay, .lg-outer.lg-dropdown-active .lg-dropdown { transition-delay: 0s; -moz-transform: translate3d(0, 0, 0); -ms-transform: translate3d(0, 0, 0); transform: translate3d(0, 0, 0); opacity: 1; visibility: visible; } .lg-outer .lg-dropdown { background-color: #fff; border-radius: 2px; font-size: 14px; list-style-type: none; margin: 0; padding: 10px 0; position: absolute; right: 0; text-align: left; top: 50px; opacity: 0; visibility: hidden; -moz-transform: translate3d(0, 5px, 0); -ms-transform: translate3d(0, 5px, 0); transform: translate3d(0, 5px, 0); transition: transform .18s linear 0s, visibility 0s linear .5s, opacity .18s linear 0s; } .lg-outer .lg-dropdown:after { content: ""; display: block; height: 0; width: 0; position: absolute; border: 8px solid transparent; border-bottom-color: #fff; right: 16px; top: -16px; } .lg-outer .lg-dropdown > li:last-child { margin-bottom: 0; } .lg-outer .lg-dropdown > li:hover .lg-icon, .lg-outer .lg-dropdown > li:hover a { color: #333; } .lg-outer .lg-dropdown a { color: #333; display: block; white-space: pre; padding: 4px 12px; font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 12px; } .lg-outer .lg-dropdown a:hover { background-color: rgba(0, 0, 0, 0.07); } .lg-outer .lg-dropdown .lg-dropdown-text { display: inline-block; line-height: 1; margin-top: -3px; vertical-align: middle; } .lg-outer .lg-dropdown .lg-icon { color: #333; display: inline-block; float: none; font-size: 20px; height: auto; line-height: 1; margin-right: 8px; padding: 0; vertical-align: middle; width: auto; } .lg-outer, .lg-outer .lg, .lg-outer .lg-inner { width: 100%; height: 100%; } .lg-outer #lg-share { position: relative; } .lg-outer #lg-share:after { content: "\e80d"; } .lg-outer #lg-share-facebook .lg-icon { color: #3b5998; } .lg-outer #lg-share-facebook .lg-icon:after { content: "\e901"; } .lg-outer #lg-share-twitter .lg-icon { color: #00aced; } .lg-outer #lg-share-twitter .lg-icon:after { content: "\e904"; } .lg-outer #lg-share-googleplus .lg-icon { color: #dd4b39; } .lg-outer #lg-share-googleplus .lg-icon:after { content: "\e902"; } .lg-outer #lg-share-pinterest .lg-icon { color: #cb2027; } .lg-outer #lg-share-pinterest .lg-icon:after { content: "\e903"; } .lg-group:after { content: ""; display: table; clear: both; } .lg-outer { position: fixed; top: 0; left: 0; z-index: 1150; text-align: left; opacity: 0; transition: opacity .15s ease 0s; } .lg-outer * { -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; } .lg-outer.lg-visible { opacity: 1; } .lg-outer.lg-css3 .lg-item.lg-current, .lg-outer.lg-css3 .lg-item.lg-next-slide, .lg-outer.lg-css3 .lg-item.lg-prev-slide { transition-duration: inherit !important; transition-timing-function: inherit !important; } .lg-outer.lg-css3.lg-dragging .lg-item.lg-current, .lg-outer.lg-css3.lg-dragging .lg-item.lg-next-slide, .lg-outer.lg-css3.lg-dragging .lg-item.lg-prev-slide { transition-duration: 0s !important; opacity: 1; } .lg-outer.lg-grab img.lg-object { cursor: grab; } .lg-outer.lg-grabbing img.lg-object { cursor: grabbing; } .lg-outer .lg { position: relative; overflow: hidden; margin-left: auto; margin-right: auto; max-width: 100%; max-height: 100%; } .lg-outer .lg-inner { position: absolute; left: 0; top: 0; white-space: nowrap; } .lg-outer .lg-item { background: url(../images/loading.gif) center center no-repeat; display: none !important; } .lg-outer.lg-css .lg-current, .lg-outer.lg-css3 .lg-current, .lg-outer.lg-css3 .lg-next-slide, .lg-outer.lg-css3 .lg-prev-slide { display: inline-block !important; } .lg-outer .lg-img-wrap, .lg-outer .lg-item { display: inline-block; text-align: center; position: absolute; width: 100%; height: 100%; } .lg-outer .lg-img-wrap:before, .lg-outer .lg-item:before { content: ""; display: inline-block; height: 50%; width: 1px; margin-right: -1px; } .lg-outer .lg-img-wrap { position: absolute; padding: 0 5px; left: 0; right: 0; top: 0; bottom: 0; } .lg-outer .lg-item.lg-complete { background-image: none; } .lg-outer .lg-item.lg-current { z-index: 1160; } .lg-outer .lg-image { display: inline-block; vertical-align: middle; max-width: 100%; max-height: 100%; width: auto !important; height: auto !important; } .lg-outer.lg-show-after-load .lg-item .lg-object, .lg-outer.lg-show-after-load .lg-item .lg-video-play { opacity: 0; transition: opacity .15s ease 0s; } .lg-outer.lg-show-after-load .lg-item.lg-complete .lg-object, .lg-outer.lg-show-after-load .lg-item.lg-complete .lg-video-play { opacity: 1; } .lg-outer .lg-empty-html, .lg-outer.lg-hide-download #lg-download { display: none; } .lg-backdrop { position: fixed; top: 0; left: 0; right: 0; bottom: 0; z-index: 1140; background-color: #000; opacity: 0; transition: opacity .15s ease 0s; } .lg-backdrop.in { opacity: .8; } .lg-css3.lg-no-trans .lg-current, .lg-css3.lg-no-trans .lg-next-slide, .lg-css3.lg-no-trans .lg-prev-slide { transition: none 0s ease 0s !important; } .lg-css3.lg-use-css3 .lg-item, .lg-css3.lg-use-left .lg-item { -webkit-backface-visibility: hidden; -moz-backface-visibility: hidden; backface-visibility: hidden; } .lg-css3.lg-fade .lg-item { opacity: 0; } .lg-css3.lg-fade .lg-item.lg-current { opacity: 1; } .lg-css3.lg-fade .lg-item.lg-current, .lg-css3.lg-fade .lg-item.lg-next-slide, .lg-css3.lg-fade .lg-item.lg-prev-slide { transition: opacity .1s ease 0s; } .lg-css3.lg-slide.lg-use-css3 .lg-item { opacity: 0; } .lg-css3.lg-slide.lg-use-css3 .lg-item.lg-prev-slide { transform: translate3d(-100%, 0, 0); } .lg-css3.lg-slide.lg-use-css3 .lg-item.lg-next-slide { transform: translate3d(100%, 0, 0); } .lg-css3.lg-slide.lg-use-css3 .lg-item.lg-current { transform: translate3d(0, 0, 0); opacity: 1; } .lg-css3.lg-slide.lg-use-css3 .lg-item.lg-current, .lg-css3.lg-slide.lg-use-css3 .lg-item.lg-next-slide, .lg-css3.lg-slide.lg-use-css3 .lg-item.lg-prev-slide { transition: transform 1s cubic-bezier(0, 0, 0.25, 1) 0s, opacity 0.1s ease 0s; } .lg-css3.lg-slide.lg-use-left .lg-item { opacity: 0; position: absolute; left: 0; } .lg-css3.lg-slide.lg-use-left .lg-item.lg-prev-slide { left: -100%; } .lg-css3.lg-slide.lg-use-left .lg-item.lg-next-slide { left: 100%; } .lg-css3.lg-slide.lg-use-left .lg-item.lg-current { left: 0; opacity: 1; } .lg-css3.lg-slide.lg-use-left .lg-item.lg-current, .lg-css3.lg-slide.lg-use-left .lg-item.lg-next-slide, .lg-css3.lg-slide.lg-use-left .lg-item.lg-prev-slide { transition: left 1s cubic-bezier(0, 0, 0.25, 1) 0s, opacity 0.1s ease 0s; } /* +* +* Material Parallax +*/ .parallax-content { position: relative; z-index: 1; } .parallax-container { position: relative; overflow: hidden; background-position: center center; background-size: cover; } .material-parallax { position: absolute; top: 0; left: -1px; right: -1px; bottom: 0; z-index: 0; background-position: center center; } .ipad .parallax-container, .iphone .parallax-container { background-attachment: scroll !important; } .material-parallax img { display: none; position: absolute; left: 50%; bottom: 0; min-width: 101%; min-height: 101%; max-width: none; transform: translate3d(-50%, 0, 0); } /* +* +* Slick Carousel +*/ /*rtl:begin:ignore*/ .slick-slider { position: relative; display: block; box-sizing: border-box; -webkit-touch-callout: none; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; -ms-touch-action: pan-y; touch-action: pan-y; -webkit-tap-highlight-color: transparent; } .slick-list { position: relative; overflow: hidden; display: block; margin: 0; padding: 0; } .slick-list:focus { outline: none; } .slick-list.dragging { cursor: pointer; cursor: hand; } .slick-slider .slick-track, .slick-slider .slick-list { transform: translate3d(0, 0, 0); } .slick-track { position: relative; left: 0; top: 0; display: block; } .slick-track::before, .slick-track::after { content: ""; display: table; } .slick-track::after { clear: both; } .slick-loading .slick-track { visibility: hidden; } .slick-slide { float: left; height: 100%; min-height: 1px; display: none; } [dir="rtl"] .slick-slide { float: right; } .slick-slide img { display: block; } .slick-slide.slick-loading img { display: none; } .slick-slide.dragging img { pointer-events: none; } .slick-initialized .slick-slide { display: block; } .slick-loading .slick-slide { visibility: hidden; } .slick-vertical .slick-slide { display: block; height: auto; border: 1px solid transparent; } .slick-arrow.slick-hidden { display: none; } .slick-loading .slick-list { background: #fff url("./../images/ajax-loader.gif") center center no-repeat; } /* Icons */ /* Arrows */ .slick-prev, .slick-next { position: absolute; display: block; height: 20px; width: 20px; line-height: 0; font-size: 0; cursor: pointer; color: transparent; background-color: transparent; top: 50%; transform: translate(0, -50%); padding: 0; border: none; outline: none; z-index: 9; } .slick-prev.slick-disabled:before, .slick-next.slick-disabled:before { opacity: 0.25; } .slick-prev::before, .slick-next::before { font-family: "fl-bigmug-line"; font-size: 24px; line-height: 1; color: #0045b6; opacity: 0.75; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; transition: .22s; } .slick-prev:hover::before, .slick-next:hover::before { color: #343db0; } .slick-prev { left: 0; } [dir="rtl"] .slick-prev { left: auto; right: 0; } .slick-prev:before { content: ""; } [dir="rtl"] .slick-prev:before { content: ""; } .slick-next { right: 0; } [dir="rtl"] .slick-next { left: 0; right: auto; } .slick-next:before { content: ""; } [dir="rtl"] .slick-next:before { content: ""; } /* Dots */ .slick-dots { position: absolute; bottom: -45px; list-style: none; display: block; text-align: center; padding: 0; width: 100%; } .slick-dots li { position: relative; display: inline-block; height: 20px; width: 20px; margin: 0 5px; padding: 0; cursor: pointer; } .slick-dots li button { border: 0; background: transparent; display: block; height: 20px; width: 20px; outline: none; line-height: 0; font-size: 0; color: transparent; padding: 5px; cursor: pointer; } .slick-dots li button:hover, .slick-dots li button:focus { outline: none; } .slick-dots li button:hover:before, .slick-dots li button:focus:before { opacity: 1; } .slick-dots li button:before { position: absolute; top: 0; left: 0; content: ""; font-family: "fl-bigmug-line"; font-size: 6px; line-height: 20px; text-align: center; color: #0045b6; opacity: 0.25; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } .slick-dots li.slick-active button:before { color: #0045b6; opacity: 0.75; } /*rtl:end:ignore*/ .slick-widget-testimonials .carousel-child { max-width: 570px; padding: 0 50px; margin-left: auto; margin-right: auto; } .slick-widget-testimonials .carousel-child .item { border-radius: 50%; cursor: pointer; transform: scale3d(0.66, 0.66, 0.66); transition: .22s; text-align: center; } .slick-widget-testimonials .carousel-child .slick-center { transform: scale3d(1, 1, 1); } .slick-widget-testimonials .carousel-child img { border-radius: inherit; max-width: 100%; margin: 0 auto; } @media (max-width: 575.98px) { .slick-widget-testimonials .carousel-child .slick-prev { left: 30px; } .slick-widget-testimonials .carousel-child .slick-next { right: 30px; } } .slick-widget-testimonials .carousel-parent { max-width: 780px; margin-left: auto; margin-right: auto; } .slick-widget-testimonials .carousel-parent .item > * { transform-origin: 50% 0; opacity: 0; transition: .44s; } .slick-widget-testimonials .carousel-parent .slick-active > * { opacity: 1; } .slick-widget-testimonials > * + * { margin-top: 20px; } * + .slick-widget-testimonials { margin-top: 30px; } @media (min-width: 768px) { * + .slick-widget-testimonials { margin-top: 55px; } } @media (min-width: 1200px) { * + .slick-widget-testimonials { margin-top: 75px; } } /* +* +* Select 2 v4 +*/ .select2 { width: 100% !important; } .select2-container { box-sizing: border-box; display: inline-block; margin: 0; position: relative; vertical-align: middle; } .select2-container .select2-container--default .select2-selection--single { width: 100% !important; } .select2-container .select2-selection--single { display: block; cursor: pointer; user-select: none; } .select2-container .select2-selection--single .select2-selection__rendered { display: block; padding-left: 8px; padding-right: 20px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .select2-container .select2-selection--single .select2-selection__clear { position: relative; } .select2-container[dir="rtl"] .select2-selection--single .select2-selection__rendered { padding-right: 8px; padding-left: 20px; } .select2-container .select2-selection--multiple { display: block; cursor: pointer; min-height: 32px; user-select: none; -webkit-user-select: none; } .select2-container .select2-selection--multiple .select2-selection__rendered { display: inline-block; overflow: hidden; padding-left: 8px; text-overflow: ellipsis; white-space: nowrap; } .select2-container .select2-search--inline { float: left; } .select2-container .select2-search--inline .select2-search__field { border: none; font-size: 100%; margin-top: 5px; padding: 0; } .select2-container .select2-search--inline .select2-search__field::-webkit-search-cancel-button { -webkit-appearance: none; } .select2-dropdown { position: absolute; left: -100000px; z-index: 1051; display: block; overflow: hidden; width: 100%; border: 1px; border-radius: 6px; background-color: #fff; box-shadow: 0 0 5px 0 rgba(73, 80, 87, 0.2); } .select2-results { display: block; } .select2-results__options { list-style: none; margin: 0; padding: 0; } .select2-results__option { padding: 6px; user-select: none; -webkit-user-select: none; } .select2-results__option[aria-selected] { cursor: pointer; } .select2-container--open .select2-dropdown { left: 0; } .select2-container--open .select2-dropdown--above { border-bottom: none; border-bottom-left-radius: 0; border-bottom-right-radius: 0; } .select2-container--open .select2-dropdown--below { border-top: none; border-top-left-radius: 0; border-top-right-radius: 0; } .select2-search--dropdown { display: block; padding: 4px; } .select2-search--dropdown .select2-search__field { padding: 4px; width: 100%; box-sizing: border-box; } .select2-search--dropdown .select2-search__field::-webkit-search-cancel-button { -webkit-appearance: none; } .select2-search--dropdown.select2-search--hide { display: none; } .select2-close-mask { border: 0; margin: 0; padding: 0; display: block; position: fixed; left: 0; top: 0; min-height: 100%; min-width: 100%; height: auto; width: auto; opacity: 0; z-index: 99; background-color: #fff; filter: alpha(opacity=0); } .select2-hidden-accessible { border: 0 !important; clip: rect(0 0 0 0) !important; height: 1px !important; margin: -1px !important; overflow: hidden !important; padding: 0 !important; position: absolute !important; width: 1px !important; } .select2-container--default .select2-selection--single { background-color: #fff; border: 1px; border-radius: 6px; border-style: solid; border-color: #aeb1be; } .select2-container--default .select2-selection--single .select2-selection__rendered { display: block; padding: 11px 29px 11px 19px; color: #495057; font-size: 16px; line-height: 1.5; font-weight: 400; min-height: 48px; } .select2-container--default .select2-selection--single .select2-selection__clear { cursor: pointer; float: right; font-weight: 700; } .select2-container--default .select2-selection--single .select2-selection__placeholder { color: #74757f; } .select2-container--default .select2-selection--single .select2-selection__arrow { position: absolute; top: 50%; transform: translate3d(0, -50%, 0); right: 5px; width: 20px; } .select2-container--default .select2-selection--single .select2-selection__arrow b { pointer-events: none; color: #495057; } .select2-container--default .select2-selection--single .select2-selection__arrow b::before { position: relative; display: inline-block; content: ""; font-family: "Material Design Icons"; font-size: 18px; transform: rotate(0deg); transition: .1s; } .select2-container--default[dir="rtl"] .select2-selection--single .select2-selection__clear { float: left; } .select2-container--default[dir="rtl"] .select2-selection--single .select2-selection__arrow { left: 1px; right: auto; } .select2-container--default.select2-container--disabled .select2-selection--single { background-color: #f5f6fa; cursor: default; } .select2-container--default.select2-container--disabled .select2-selection--single .select2-selection__clear { display: none; } .select2-container--default.select2-container--open .select2-selection--single .select2-selection__arrow b::before { transform: rotate(180deg); } .select2-container--default .select2-selection--multiple { background-color: #fff; border: 1px solid #aaa; border-radius: 6px; cursor: text; } .select2-container--default .select2-selection--multiple .select2-selection__rendered { box-sizing: border-box; list-style: none; margin: 0; padding: 0 5px; width: 100%; } .select2-container--default .select2-selection--multiple .select2-selection__rendered li { list-style: none; } .select2-container--default .select2-selection--multiple .select2-selection__placeholder { color: #999; margin-top: 5px; float: left; } .select2-container--default .select2-selection--multiple .select2-selection__clear { cursor: pointer; float: right; font-weight: bold; margin-top: 5px; margin-right: 10px; } .select2-container--default .select2-selection--multiple .select2-selection__choice { background-color: #e4e4e4; border: 1px solid #aaa; border-radius: 4px; cursor: default; float: left; margin-right: 5px; margin-top: 5px; padding: 0 5px; } .select2-container--default .select2-selection--multiple .select2-selection__choice__remove { color: #999; cursor: pointer; display: inline-block; font-weight: bold; margin-right: 2px; } .select2-container--default .select2-selection--multiple .select2-selection__choice__remove:hover { color: #333; } .select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice, .select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__placeholder, .select2-container--default[dir="rtl"] .select2-selection--multiple .select2-search--inline { float: right; } .select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice { margin-left: 5px; margin-right: auto; } .select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice__remove { margin-left: 2px; margin-right: auto; } .select2-container--default.select2-container--focus .select2-selection--multiple { border: solid black 1px; outline: 0; } .select2-container--default.select2-container--disabled .select2-selection--multiple { background-color: #eee; cursor: default; } .select2-container--default.select2-container--disabled .select2-selection__choice__remove { display: none; } .select2-container--default.select2-container--open.select2-container--above .select2-selection--single, .select2-container--default.select2-container--open.select2-container--above .select2-selection--multiple { border-top-left-radius: 0; border-top-right-radius: 0; } .select2-container--default.select2-container--open.select2-container--below .select2-selection--single, .select2-container--default.select2-container--open.select2-container--below .select2-selection--multiple { border-bottom-left-radius: 0; border-bottom-right-radius: 0; } .select2-container--default .select2-search--dropdown { border-top: 0; } .select2-container--default .select2-search--dropdown .select2-search__field { border: 1px solid #c5c6cd; } .select2-container--default .select2-search--inline .select2-search__field { background: transparent; border: none; outline: 0; box-shadow: none; -webkit-appearance: textfield; } .select2-container--default .select2-results > .select2-results__options { max-height: 200px; overflow-y: auto; } .select2-container--default .select2-results__option[role=group] { padding: 0; } .select2-container--default .select2-results__option[aria-disabled=true] { color: #9b9b9b; } .select2-container--default .select2-results__option[aria-selected=true] { color: #fff; background-color: #0058e9; } .select2-container--default .select2-results__option .select2-results__option { padding-left: 1em; } .select2-container--default .select2-results__option .select2-results__option .select2-results__group { padding-left: 0; } .select2-container--default .select2-results__option .select2-results__option .select2-results__option { margin-left: -1em; padding-left: 2em; } .select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option { margin-left: -2em; padding-left: 3em; } .select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option { margin-left: -3em; padding-left: 4em; } .select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option { margin-left: -4em; padding-left: 5em; } .select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option { margin-left: -5em; padding-left: 6em; } .select2-container--default .select2-results__option--highlighted[aria-selected] { color: #fff; background-color: #0045b6; } .select2-container--default .select2-results__group { cursor: default; display: block; padding: 6px; } /* +* +* D3 Charts +*/ .c3 svg { padding-left: 4px; width: 100%; -webkit-tap-highlight-color: transparent; } .c3 path, .c3 line { fill: none; stroke: #9f9f9f; } .c3 text { font-family: "Work Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; font-size: 16px; fill: #9b9b9b; user-select: none; } .c3-legend-item-tile, .c3-xgrid-focus, .c3-ygrid, .c3-event-rect, .c3-bars path { shape-rendering: crispEdges; } .c3-chart-arc path { stroke: #fff; } .c3-chart-arc text { fill: #fff; font-size: 14px; } /*-- Axis --*/ /*-- Grid --*/ .c3-grid line { stroke: #eaebee; } .c3-grid text { fill: #aaa; } .c3-xgrid, .c3-ygrid { stroke-dasharray: 0 0; } /*-- Text on Chart --*/ .c3-text.c3-empty { fill: #808080; font-size: 2em; } /*-- Line --*/ .c3-line { stroke-width: 3px; } /*-- Point --*/ .c3-circle._expanded_ { stroke-width: 2px; stroke: white; } .c3-selected-circle { fill: white; stroke-width: 2px; } /*-- Bar --*/ .c3-bar { stroke-width: 0; } .c3-bar._expanded_ { fill-opacity: 0.75; } /*-- Focus --*/ .c3-target.c3-focused { opacity: 1; } .c3-target.c3-focused path.c3-line, .c3-target.c3-focused path.c3-step { stroke-width: 2px; } .c3-target.c3-defocused { opacity: 0.3 !important; } /*-- Region --*/ .c3-region { fill: steelblue; fill-opacity: .1; } /*-- Brush --*/ .c3-brush .extent { fill-opacity: .1; fill: #000; } /*-- Select - Drag --*/ /*-- Legend --*/ .c3-legend-item { font-size: 12px; } .c3-legend-item-hidden { opacity: 0.15; } .c3-legend-background { opacity: 0.75; fill: white; stroke: lightgray; stroke-width: 1; } /*-- Title --*/ .c3-title { font: 14px sans-serif; } /*-- Tooltip --*/ .c3-tooltip-container { z-index: 10; } .c3-tooltip { border-collapse: collapse; border-spacing: 0; background-color: #fff; empty-cells: show; opacity: 0.9; border: 1px solid #ccc; } .c3-tooltip tr { border: 1px solid #ccc; } .c3-tooltip th { background-color: #aaa; font-size: 14px; line-height: 1.2; padding: 2px 5px; text-align: left; color: #fff; } .c3-tooltip td { font-size: 13px; padding: 3px 6px; background-color: #fff; color: #000; border-left: 1px dotted #999; } .c3-tooltip td > span { display: inline-block; width: 10px; height: 10px; margin-right: 6px; } .c3-tooltip td.value { text-align: right; } /*-- Area --*/ .c3-area { stroke-width: 0; opacity: 0.2; } /*-- Arc --*/ .c3-chart-arcs-title { dominant-baseline: middle; font-size: 1.3em; } .c3-chart-arcs .c3-chart-arcs-background { fill: #e0e0e0; stroke: none; } .c3-chart-arcs .c3-chart-arcs-gauge-unit { fill: #000; font-size: 16px; } .c3-chart-arcs .c3-chart-arcs-gauge-max { fill: #777; } .c3-chart-arcs .c3-chart-arcs-gauge-min { fill: #777; } .c3-chart-arc .c3-gauge-value { fill: #000; } .c3 .domain { stroke: #c8c8c8; } .d3-chart .tick line { display: none; } .d3-chart .c3-tooltip { opacity: 1; color: #fff; background: #0045b6; border: 1px solid #aeb1be; } .d3-chart .c3-tooltip tr th { padding: 8px 10px; color: #fff; background: #495057; } .d3-chart .c3-tooltip tr td { padding: 5px 10px; color: #838386; background: #fff; } .d3-chart .c3-tooltip .value { border-left: 1px solid #f2f3f9; } #line-chart .c3-tooltip-container { border-radius: 6px; overflow: hidden; box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.05); } #line-chart .c3-tooltip-container tr { border: 0; } #line-chart .c3-tooltip-container .name { border-left: 0; } #line-chart .c3-tooltip-container table { border: 0 solid transparent; border-radius: 6px; } #line-chart .c3-axis-y-label { transform: translateY(-10px); } #line-chart .c3-axis-y .tick text { transform: translateX(1px); } #line-chart .c3-axis-x { text-align: left; } #line-chart .c3-axis-x text { transform: translateX(-1px); text-anchor: start !important; } #line-chart .c3-legend-item { display: none !important; transform: translateY(15px); } #line-chart .c3-legend-item text { font-size: 16px; color: #29293a; } .d3-chart-wrap { width: 100%; max-width: 100%; overflow-y: hidden; overflow-x: auto; padding-bottom: 30px; } .d3-chart-wrap > .d3-chart { min-width: 520px; } .d3-chart-legend { transform: translateY(-5px); margin-bottom: -5px; margin-left: -30px; text-align: center; } .d3-chart-legend > * { display: inline-block; margin-top: 5px; margin-left: 30px; } .d3-chart-legend span { position: relative; display: inline-block; font-size: 16px; color: #74757f; cursor: pointer; } .d3-chart-legend span::before { content: ''; display: inline-block; height: 3px; width: 30px; margin-right: 12px; vertical-align: middle; } .d3-chart-legend span:nth-child(1)::before { background: #0045b6; } .d3-chart-legend span:nth-child(2)::before { background: #aeb1be; } * + .d3-chart-legend { margin-top: 20px; } @media (min-width: 1600px) { .d3-chart-legend span { font-size: 18px; } } .rd-range-input { border: none; background: none; width: auto; } .price-devider { padding: 0 5px 0 4px; display: flex; flex-direction: column; justify-content: flex-start; vertical-align: baseline; height: 100%; width: 20px; text-align: left; } .rd-range-form-wrap { display: flex; font-family: "Work Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; align-items: baseline; color: #151515; } .rd-range-form-wrap span { font-weight: 400; } .rd-range-input-value-1 { text-align: left; } .rd-range-input-value-2 { text-align: left; } .ios .rd-range-input { width: 100% !important; max-width: 45px; } .ios .price-devider { padding: 0 15px 0 0; width: auto; text-align: left; } .rd-range-wrap { width: 100%; display: flex; justify-content: center; align-items: center; vertical-align: middle; } .rd-range { height: 18px; display: -ms-flexbox; display: -webkit-flex; display: flex; -webkit-align-items: center; -ms-flex-align: center; align-items: center; } .rd-range__wrap { position: relative; width: 100%; background-color: #bdbdbd; transition: background 100ms cubic-bezier(0.23, 1, 0.32, 1); height: 5px; } .rd-range:hover .rd-range__wrap { background-color: #9e9e9e; } .rd-range:hover .rd-range__pointer:before { transform: scale(1); -webkit-animation-name: pulse; animation-name: pulse; } .rd-range__pointer { position: absolute; cursor: pointer; top: 1px; left: 6%; z-index: 1; width: 19px; height: 19px; border-radius: 50%; transform: translate(-50%, -50%); transform-origin: 0% 15%; transition: transform 0.2s cubic-bezier(0.35, 0, 0.25, 1); overflow: visible; background-color: #0045b6; background-clip: padding-box; } @media (min-width: 768px) { .rd-range__pointer { width: 12px; height: 12px; } .rd-range__pointer:before { content: ''; height: 26px; width: 26px; pointer-events: none; background: rgba(0, 69, 182, 0.16); position: absolute; top: -7px; left: -7px; border-radius: 50%; transform: scale(0); transition: transform 100ms cubic-bezier(0.445, 0.05, 0.55, 0.95) 0ms, opacity 60ms cubic-bezier(0.445, 0.05, 0.55, 0.95) 0ms; pointer-events: none; -webkit-animation-duration: 1s; animation-duration: 1s; -webkit-animation-fill-mode: both; animation-fill-mode: both; -webkit-animation-iteration-count: infinite; animation-iteration-count: infinite; } .rd-range__pointer.active:before { animation: none; transform: scale(0); opacity: 0; } } .rd-range__line { background-color: #0045b6; position: absolute; top: 0; left: 0; height: 5px; } .rd-range.hasTooltip .rd-range__pointer-tooltip { font-size: 12px; color: #ffffff; opacity: 0; transition: color 0.3s cubic-bezier(0.35, 0, 0.25, 1); display: none; } .rd-range.hasTooltip .rd-range__pointer { display: -webkit-flex; display: -ms-flexbox; display: flex; -webkit-align-items: center; -ms-flex-align: center; align-items: center; -webkit-justify-content: center; -ms-flex-pack: center; justify-content: center; position: absolute; width: 17px; height: 17px; border-radius: 28px; } .rd-range.hasTooltip .rd-range__pointer:before { content: none; } .rd-range.hasTooltip .rd-range__pointer:after { position: absolute; content: ''; left: 0; border-radius: 16px; top: 19px; border-left: 14px solid transparent; border-right: 14px solid transparent; border-top: 16px solid #0045b6; opacity: 0; -webkit-transform: translate3d(0, -8px, 0); transform: translate3d(0, -8px, 0); transition: all 0.2s cubic-bezier(0.35, 0, 0.25, 1); display: none; } .rd-range.hasTooltip .rd-range__pointer.active { opacity: 1; } .rd-range.hasTooltip .rd-range__pointer.active:after { opacity: 1; } .rd-range.hasTooltip .rd-range__pointer.active .rd-range__pointer-tooltip { opacity: 1; } \ No newline at end of file diff --git a/site/monica_site/static/css/theme-overrides.css b/site/monica_site/static/css/theme-overrides.css new file mode 100644 index 0000000..5894930 --- /dev/null +++ b/site/monica_site/static/css/theme-overrides.css @@ -0,0 +1,199 @@ +/* EXIT Realty / MKDRealtor public brand overrides on Real Estate theme */ + +:root { + --monica-primary: #00626c; + --monica-primary-light: #008898; + --monica-ink: #212121; + --monica-muted: #6b7280; + --monica-surface: #f4f7f7; + --monica-border: #d9e3e4; +} + +.footer-brand-row { + display: flex; + align-items: center; + gap: 12px; + margin-bottom: 8px; +} +.footer-brand-row img { + height: 40px; + width: auto; +} + +.about-subtitle { + font-size: 18px; + color: var(--monica-muted); +} + +.page .button-primary, +.page .button-primary:focus, +.page .button-primary:active { + background-color: #00626c !important; + border-color: #00626c !important; + color: #fff !important; +} +.page .button-primary:hover { + background-color: #008898 !important; + border-color: #008898 !important; + color: #fff !important; +} +.page .breadcrumbs-custom-path a, +.page a.link-default { + color: #00626c; +} + +/* Django flash messages */ +.flash { + list-style: none; + margin: 0; + padding: 12px 0 0; +} +.flash li { + max-width: 1200px; + margin: 0 auto 8px; + padding: 12px 16px; + background: #e8f4f5; + border-left: 3px solid #00626c; + color: #0d4a52; + font-size: 14px; +} +.flash li.error, +.flash li.danger { + background: #fee2e2; + border-left-color: #b91c1c; + color: #991b1b; +} + +/* Form field errors */ +.form-wrap .errorlist { + list-style: none; + margin: 6px 0 0; + padding: 0; + color: #b91c1c; + font-size: 13px; +} + +/* Cookie consent — light theme fit */ +.cookie-consent-banner { + position: fixed; + bottom: 0; + left: 0; + right: 0; + z-index: 11000; + padding: 1rem; + background: rgba(8, 8, 8, 0.96); + border-top: 1px solid rgba(0, 98, 108, 0.45); +} +.cookie-consent-content { + max-width: 960px; + margin: 0 auto; + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: space-between; + gap: 1rem; +} +.cookie-consent-text { + flex: 1 1 420px; + color: #cbd5e1; + font-size: 0.95rem; + margin: 0; + font-family: "Work Sans", sans-serif; +} +.cookie-consent-text-short { + display: none; +} +.cookie-consent-actions { + display: flex; + gap: 0.75rem; + flex-shrink: 0; +} +.visually-hidden { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; +} +.footer-link-btn { + background: none; + border: none; + padding: 0; + color: #7dd3da; + font: inherit; + font-size: 13px; + cursor: pointer; +} +.footer-link-btn:hover { + text-decoration: underline; +} + +.footer-aside-copy { + text-align: right; +} +.footer-aside-copy .rights { + margin: 0; +} +.footer-made-by { + margin: 6px 0 0; + font-size: 13px; + color: #9ca3af; + font-family: "Work Sans", sans-serif; +} +.footer-made-by a { + color: #d1d5db; + text-decoration: underline; +} +.footer-made-by a:hover { + color: #fff; +} +.footer-minimal .footer-made-by { + margin-top: 8px; +} +.footer-minimal-inner { + flex-wrap: wrap; + gap: 12px; +} + +.pref-check { + display: flex; + align-items: center; + gap: 10px; + font-size: 15px; + color: var(--monica-ink); + cursor: pointer; +} +.pref-check input { + width: 16px; + height: 16px; +} + +@media (max-width: 640px) { + .cookie-consent-banner { + padding: 0.625rem 0.75rem; + padding-bottom: max(0.625rem, env(safe-area-inset-bottom)); + } + .cookie-consent-content { + flex-direction: column; + align-items: stretch; + gap: 0.625rem; + } + .cookie-consent-text-short { + display: inline; + } + .cookie-consent-text-full { + display: none; + } + .cookie-consent-text { + flex: none; + font-size: 0.8125rem; + line-height: 1.35; + } + .cookie-consent-actions { + width: 100%; + } +} diff --git a/site/monica_site/static/fonts/FontAwesome.otf b/site/monica_site/static/fonts/FontAwesome.otf new file mode 100755 index 0000000..401ec0f Binary files /dev/null and b/site/monica_site/static/fonts/FontAwesome.otf differ diff --git a/site/monica_site/static/fonts/Linearicons.eot b/site/monica_site/static/fonts/Linearicons.eot new file mode 100755 index 0000000..513cef7 Binary files /dev/null and b/site/monica_site/static/fonts/Linearicons.eot differ diff --git a/site/monica_site/static/fonts/Linearicons.svg b/site/monica_site/static/fonts/Linearicons.svg new file mode 100755 index 0000000..942c1d6 --- /dev/null +++ b/site/monica_site/static/fonts/Linearicons.svg @@ -0,0 +1,1028 @@ + + + + + + +{ + "fontFamily": "Linearicons", + "majorVersion": 1, + "minorVersion": 0, + "version": "Version 1.0", + "fontId": "Linearicons", + "psName": "Linearicons", + "subFamily": "Regular", + "fullName": "Linearicons", + "description": "Font generated by IcoMoon." +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/site/monica_site/static/fonts/Linearicons.ttf b/site/monica_site/static/fonts/Linearicons.ttf new file mode 100755 index 0000000..73ec944 Binary files /dev/null and b/site/monica_site/static/fonts/Linearicons.ttf differ diff --git a/site/monica_site/static/fonts/Linearicons.woff b/site/monica_site/static/fonts/Linearicons.woff new file mode 100755 index 0000000..2425dc0 Binary files /dev/null and b/site/monica_site/static/fonts/Linearicons.woff differ diff --git a/site/monica_site/static/fonts/fl-bigmug-line.eot b/site/monica_site/static/fonts/fl-bigmug-line.eot new file mode 100755 index 0000000..7781397 Binary files /dev/null and b/site/monica_site/static/fonts/fl-bigmug-line.eot differ diff --git a/site/monica_site/static/fonts/fl-bigmug-line.svg b/site/monica_site/static/fonts/fl-bigmug-line.svg new file mode 100755 index 0000000..9a9e941 --- /dev/null +++ b/site/monica_site/static/fonts/fl-bigmug-line.svg @@ -0,0 +1,3 @@ + + + diff --git a/site/monica_site/static/fonts/fl-bigmug-line.ttf b/site/monica_site/static/fonts/fl-bigmug-line.ttf new file mode 100755 index 0000000..26678a5 Binary files /dev/null and b/site/monica_site/static/fonts/fl-bigmug-line.ttf differ diff --git a/site/monica_site/static/fonts/fl-bigmug-line.woff b/site/monica_site/static/fonts/fl-bigmug-line.woff new file mode 100755 index 0000000..3b5f0a4 Binary files /dev/null and b/site/monica_site/static/fonts/fl-bigmug-line.woff differ diff --git a/site/monica_site/static/fonts/fontawesome-webfont.eot b/site/monica_site/static/fonts/fontawesome-webfont.eot new file mode 100755 index 0000000..e9f60ca Binary files /dev/null and b/site/monica_site/static/fonts/fontawesome-webfont.eot differ diff --git a/site/monica_site/static/fonts/fontawesome-webfont.svg b/site/monica_site/static/fonts/fontawesome-webfont.svg new file mode 100755 index 0000000..855c845 --- /dev/null +++ b/site/monica_site/static/fonts/fontawesome-webfont.svg @@ -0,0 +1,2671 @@ + + + + +Created by FontForge 20120731 at Mon Oct 24 17:37:40 2016 + By ,,, +Copyright Dave Gandy 2016. All rights reserved. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/site/monica_site/static/fonts/fontawesome-webfont.ttf b/site/monica_site/static/fonts/fontawesome-webfont.ttf new file mode 100755 index 0000000..35acda2 Binary files /dev/null and b/site/monica_site/static/fonts/fontawesome-webfont.ttf differ diff --git a/site/monica_site/static/fonts/fontawesome-webfont.woff b/site/monica_site/static/fonts/fontawesome-webfont.woff new file mode 100755 index 0000000..400014a Binary files /dev/null and b/site/monica_site/static/fonts/fontawesome-webfont.woff differ diff --git a/site/monica_site/static/fonts/fontawesome-webfont.woff2 b/site/monica_site/static/fonts/fontawesome-webfont.woff2 new file mode 100755 index 0000000..4d13fc6 Binary files /dev/null and b/site/monica_site/static/fonts/fontawesome-webfont.woff2 differ diff --git a/site/monica_site/static/fonts/lg.eot b/site/monica_site/static/fonts/lg.eot new file mode 100755 index 0000000..51264c4 Binary files /dev/null and b/site/monica_site/static/fonts/lg.eot differ diff --git a/site/monica_site/static/fonts/lg.svg b/site/monica_site/static/fonts/lg.svg new file mode 100755 index 0000000..22b1a1f --- /dev/null +++ b/site/monica_site/static/fonts/lg.svg @@ -0,0 +1,47 @@ + + + + + + +{ + "fontFamily": "lg", + "majorVersion": 1, + "minorVersion": 0, + "fontURL": "https://github.com/sachinchoolur/lightGallery", + "copyright": "sachin", + "license": "MLT", + "licenseURL": "http://opensource.org/licenses/MIT", + "version": "Version 1.0", + "fontId": "lg", + "psName": "lg", + "subFamily": "Regular", + "fullName": "lg", + "description": "Font generated by IcoMoon." +} + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/site/monica_site/static/fonts/lg.ttf b/site/monica_site/static/fonts/lg.ttf new file mode 100755 index 0000000..8ad8199 Binary files /dev/null and b/site/monica_site/static/fonts/lg.ttf differ diff --git a/site/monica_site/static/fonts/lg.woff b/site/monica_site/static/fonts/lg.woff new file mode 100755 index 0000000..d98ff60 Binary files /dev/null and b/site/monica_site/static/fonts/lg.woff differ diff --git a/site/monica_site/static/fonts/materialdesignicons-webfont.eot b/site/monica_site/static/fonts/materialdesignicons-webfont.eot new file mode 100755 index 0000000..15fa077 Binary files /dev/null and b/site/monica_site/static/fonts/materialdesignicons-webfont.eot differ diff --git a/site/monica_site/static/fonts/materialdesignicons-webfont.svg b/site/monica_site/static/fonts/materialdesignicons-webfont.svg new file mode 100755 index 0000000..2d9f0ba --- /dev/null +++ b/site/monica_site/static/fonts/materialdesignicons-webfont.svg @@ -0,0 +1,4383 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/site/monica_site/static/fonts/materialdesignicons-webfont.ttf b/site/monica_site/static/fonts/materialdesignicons-webfont.ttf new file mode 100755 index 0000000..1fc3931 Binary files /dev/null and b/site/monica_site/static/fonts/materialdesignicons-webfont.ttf differ diff --git a/site/monica_site/static/fonts/materialdesignicons-webfont.woff b/site/monica_site/static/fonts/materialdesignicons-webfont.woff new file mode 100755 index 0000000..554be39 Binary files /dev/null and b/site/monica_site/static/fonts/materialdesignicons-webfont.woff differ diff --git a/site/monica_site/static/fonts/materialdesignicons-webfont.woff2 b/site/monica_site/static/fonts/materialdesignicons-webfont.woff2 new file mode 100755 index 0000000..b2246e9 Binary files /dev/null and b/site/monica_site/static/fonts/materialdesignicons-webfont.woff2 differ diff --git a/site/monica_site/static/images/bg-image-4.jpg b/site/monica_site/static/images/bg-image-4.jpg new file mode 100755 index 0000000..e3701cd Binary files /dev/null and b/site/monica_site/static/images/bg-image-4.jpg differ diff --git a/site/monica_site/static/images/bg-image-7.jpg b/site/monica_site/static/images/bg-image-7.jpg new file mode 100755 index 0000000..c140827 Binary files /dev/null and b/site/monica_site/static/images/bg-image-7.jpg differ diff --git a/site/monica_site/static/images/breadcrumbs-image-1.jpg b/site/monica_site/static/images/breadcrumbs-image-1.jpg new file mode 100755 index 0000000..afefa36 Binary files /dev/null and b/site/monica_site/static/images/breadcrumbs-image-1.jpg differ diff --git a/site/monica_site/static/images/careers-1-570x388.jpg b/site/monica_site/static/images/careers-1-570x388.jpg new file mode 100755 index 0000000..dddc32d Binary files /dev/null and b/site/monica_site/static/images/careers-1-570x388.jpg differ diff --git a/site/monica_site/static/images/grid-layout-1-370x256.jpg b/site/monica_site/static/images/grid-layout-1-370x256.jpg new file mode 100755 index 0000000..0631bf0 Binary files /dev/null and b/site/monica_site/static/images/grid-layout-1-370x256.jpg differ diff --git a/site/monica_site/static/images/grid-layout-2-370x256.jpg b/site/monica_site/static/images/grid-layout-2-370x256.jpg new file mode 100755 index 0000000..cac24f5 Binary files /dev/null and b/site/monica_site/static/images/grid-layout-2-370x256.jpg differ diff --git a/site/monica_site/static/images/grid-layout-3-370x256.jpg b/site/monica_site/static/images/grid-layout-3-370x256.jpg new file mode 100755 index 0000000..6277861 Binary files /dev/null and b/site/monica_site/static/images/grid-layout-3-370x256.jpg differ diff --git a/site/monica_site/static/images/slider-minimal-slide-1-1920x968.jpg b/site/monica_site/static/images/slider-minimal-slide-1-1920x968.jpg new file mode 100755 index 0000000..ef47dbd Binary files /dev/null and b/site/monica_site/static/images/slider-minimal-slide-1-1920x968.jpg differ diff --git a/site/monica_site/static/images/slider-minimal-slide-2-1920x968.jpg b/site/monica_site/static/images/slider-minimal-slide-2-1920x968.jpg new file mode 100755 index 0000000..29dde29 Binary files /dev/null and b/site/monica_site/static/images/slider-minimal-slide-2-1920x968.jpg differ diff --git a/site/monica_site/static/js/address-autocomplete.js b/site/monica_site/static/js/address-autocomplete.js new file mode 100644 index 0000000..1e87362 --- /dev/null +++ b/site/monica_site/static/js/address-autocomplete.js @@ -0,0 +1,149 @@ +/** + * Address autocomplete via Django /api/address-suggest/ (Nominatim proxy). + * Never calls Nominatim from the browser. + * + * Markup: wrap fields in [data-address-autocomplete][data-suggest-url="..."]. + * Mark inputs with data-ac="line1|line2|city|state|zip|country". + * The line1 input is the typeahead trigger. + */ +(function () { + "use strict"; + + var DEBOUNCE_MS = 280; + var MIN_CHARS = 3; + + function debounce(fn, wait) { + var t; + return function () { + var ctx = this; + var args = arguments; + clearTimeout(t); + t = setTimeout(function () { + fn.apply(ctx, args); + }, wait); + }; + } + + function field(root, key) { + return root.querySelector('[data-ac="' + key + '"]'); + } + + function ensureList(root) { + var list = root.querySelector(".address-ac-list"); + if (list) { + return list; + } + list = document.createElement("ul"); + list.className = "address-ac-list"; + list.hidden = true; + list.setAttribute("role", "listbox"); + var line1 = field(root, "line1"); + var wrap = line1 && line1.closest(".form-wrap, .field"); + (wrap || root).appendChild(list); + if (wrap) { + wrap.classList.add("address-ac-wrap"); + } else { + root.classList.add("address-ac-wrap"); + } + return list; + } + + function hide(list) { + list.hidden = true; + list.innerHTML = ""; + } + + function fill(root, item) { + var map = { + line1: item.line1 || "", + line2: item.line2 || "", + city: item.city || "", + state: item.state || "", + zip: item.zip || "", + country: item.country || "US", + }; + Object.keys(map).forEach(function (key) { + var el = field(root, key); + if (el) { + el.value = map[key]; + el.dispatchEvent(new Event("input", { bubbles: true })); + el.dispatchEvent(new Event("change", { bubbles: true })); + } + }); + } + + function render(root, list, results) { + list.innerHTML = ""; + if (!results.length) { + hide(list); + return; + } + results.forEach(function (item, index) { + var li = document.createElement("li"); + li.className = "address-ac-item"; + li.setAttribute("role", "option"); + li.setAttribute("tabindex", "-1"); + li.dataset.index = String(index); + li.textContent = item.label || item.line1 || "Address"; + li.addEventListener("mousedown", function (ev) { + ev.preventDefault(); + fill(root, item); + hide(list); + }); + list.appendChild(li); + }); + list.hidden = false; + } + + function bind(root) { + var url = root.getAttribute("data-suggest-url"); + var input = field(root, "line1"); + if (!url || !input) { + return; + } + var list = ensureList(root); + input.setAttribute("autocomplete", "off"); + input.setAttribute("aria-autocomplete", "list"); + + var fetchSuggest = debounce(function () { + var q = (input.value || "").trim(); + if (q.length < MIN_CHARS) { + hide(list); + return; + } + var req = url + (url.indexOf("?") >= 0 ? "&" : "?") + "q=" + encodeURIComponent(q) + "&limit=5"; + fetch(req, { headers: { Accept: "application/json" }, credentials: "same-origin" }) + .then(function (res) { + return res.json(); + }) + .then(function (data) { + render(root, list, (data && data.results) || []); + }) + .catch(function () { + hide(list); + }); + }, DEBOUNCE_MS); + + input.addEventListener("input", fetchSuggest); + input.addEventListener("keydown", function (ev) { + if (ev.key === "Escape") { + hide(list); + } + }); + document.addEventListener("click", function (ev) { + if (!root.contains(ev.target)) { + hide(list); + } + }); + } + + function init() { + document.querySelectorAll("[data-address-autocomplete]").forEach(bind); + } + + if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", init); + } else { + init(); + } +})(); diff --git a/site/monica_site/static/js/core.min.js b/site/monica_site/static/js/core.min.js new file mode 100755 index 0000000..b160f8d --- /dev/null +++ b/site/monica_site/static/js/core.min.js @@ -0,0 +1,299 @@ +/** + * @module jQuery + * @author jQuery Foundation and other contributors + * @see https://code.jquery.com/jquery/ + * @license MIT (jquery.org/license) + * @version 3.3.1 + */ +!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(e,t){"use strict";var n=[],r=e.document,i=Object.getPrototypeOf,o=n.slice,a=n.concat,s=n.push,u=n.indexOf,l={},c=l.toString,f=l.hasOwnProperty,p=f.toString,d=p.call(Object),h={},g=function e(t){return"function"==typeof t&&"number"!=typeof t.nodeType},y=function e(t){return null!=t&&t===t.window},v={type:!0,src:!0,noModule:!0};function m(e,t,n){var i,o=(t=t||r).createElement("script");if(o.text=e,n)for(i in v)n[i]&&(o[i]=n[i]);t.head.appendChild(o).parentNode.removeChild(o)}function x(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?l[c.call(e)]||"object":typeof e}var b="3.3.1",w=function(e,t){return new w.fn.init(e,t)},T=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g;w.fn=w.prototype={jquery:"3.3.1",constructor:w,length:0,toArray:function(){return o.call(this)},get:function(e){return null==e?o.call(this):e<0?this[e+this.length]:this[e]},pushStack:function(e){var t=w.merge(this.constructor(),e);return t.prevObject=this,t},each:function(e){return w.each(this,e)},map:function(e){return this.pushStack(w.map(this,function(t,n){return e.call(t,n,t)}))},slice:function(){return this.pushStack(o.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(e){var t=this.length,n=+e+(e<0?t:0);return this.pushStack(n>=0&&n0&&t-1 in e)}var E=function(e){var t,n,r,i,o,a,s,u,l,c,f,p,d,h,g,y,v,m,x,b="sizzle"+1*new Date,w=e.document,T=0,C=0,E=ae(),k=ae(),S=ae(),D=function(e,t){return e===t&&(f=!0),0},N={}.hasOwnProperty,A=[],j=A.pop,q=A.push,L=A.push,H=A.slice,O=function(e,t){for(var n=0,r=e.length;n+~]|"+M+")"+M+"*"),z=new RegExp("="+M+"*([^\\]'\"]*?)"+M+"*\\]","g"),X=new RegExp(W),U=new RegExp("^"+R+"$"),V={ID:new RegExp("^#("+R+")"),CLASS:new RegExp("^\\.("+R+")"),TAG:new RegExp("^("+R+"|[*])"),ATTR:new RegExp("^"+I),PSEUDO:new RegExp("^"+W),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+P+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},G=/^(?:input|select|textarea|button)$/i,Y=/^h\d$/i,Q=/^[^{]+\{\s*\[native \w/,J=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,K=/[+~]/,Z=new RegExp("\\\\([\\da-f]{1,6}"+M+"?|("+M+")|.)","ig"),ee=function(e,t,n){var r="0x"+t-65536;return r!==r||n?t:r<0?String.fromCharCode(r+65536):String.fromCharCode(r>>10|55296,1023&r|56320)},te=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ne=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},re=function(){p()},ie=me(function(e){return!0===e.disabled&&("form"in e||"label"in e)},{dir:"parentNode",next:"legend"});try{L.apply(A=H.call(w.childNodes),w.childNodes),A[w.childNodes.length].nodeType}catch(e){L={apply:A.length?function(e,t){q.apply(e,H.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function oe(e,t,r,i){var o,s,l,c,f,h,v,m=t&&t.ownerDocument,T=t?t.nodeType:9;if(r=r||[],"string"!=typeof e||!e||1!==T&&9!==T&&11!==T)return r;if(!i&&((t?t.ownerDocument||t:w)!==d&&p(t),t=t||d,g)){if(11!==T&&(f=J.exec(e)))if(o=f[1]){if(9===T){if(!(l=t.getElementById(o)))return r;if(l.id===o)return r.push(l),r}else if(m&&(l=m.getElementById(o))&&x(t,l)&&l.id===o)return r.push(l),r}else{if(f[2])return L.apply(r,t.getElementsByTagName(e)),r;if((o=f[3])&&n.getElementsByClassName&&t.getElementsByClassName)return L.apply(r,t.getElementsByClassName(o)),r}if(n.qsa&&!S[e+" "]&&(!y||!y.test(e))){if(1!==T)m=t,v=e;else if("object"!==t.nodeName.toLowerCase()){(c=t.getAttribute("id"))?c=c.replace(te,ne):t.setAttribute("id",c=b),s=(h=a(e)).length;while(s--)h[s]="#"+c+" "+ve(h[s]);v=h.join(","),m=K.test(e)&&ge(t.parentNode)||t}if(v)try{return L.apply(r,m.querySelectorAll(v)),r}catch(e){}finally{c===b&&t.removeAttribute("id")}}}return u(e.replace(B,"$1"),t,r,i)}function ae(){var e=[];function t(n,i){return e.push(n+" ")>r.cacheLength&&delete t[e.shift()],t[n+" "]=i}return t}function se(e){return e[b]=!0,e}function ue(e){var t=d.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function le(e,t){var n=e.split("|"),i=n.length;while(i--)r.attrHandle[n[i]]=t}function ce(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function fe(e){return function(t){return"input"===t.nodeName.toLowerCase()&&t.type===e}}function pe(e){return function(t){var n=t.nodeName.toLowerCase();return("input"===n||"button"===n)&&t.type===e}}function de(e){return function(t){return"form"in t?t.parentNode&&!1===t.disabled?"label"in t?"label"in t.parentNode?t.parentNode.disabled===e:t.disabled===e:t.isDisabled===e||t.isDisabled!==!e&&ie(t)===e:t.disabled===e:"label"in t&&t.disabled===e}}function he(e){return se(function(t){return t=+t,se(function(n,r){var i,o=e([],n.length,t),a=o.length;while(a--)n[i=o[a]]&&(n[i]=!(r[i]=n[i]))})})}function ge(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}n=oe.support={},o=oe.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement;return!!t&&"HTML"!==t.nodeName},p=oe.setDocument=function(e){var t,i,a=e?e.ownerDocument||e:w;return a!==d&&9===a.nodeType&&a.documentElement?(d=a,h=d.documentElement,g=!o(d),w!==d&&(i=d.defaultView)&&i.top!==i&&(i.addEventListener?i.addEventListener("unload",re,!1):i.attachEvent&&i.attachEvent("onunload",re)),n.attributes=ue(function(e){return e.className="i",!e.getAttribute("className")}),n.getElementsByTagName=ue(function(e){return e.appendChild(d.createComment("")),!e.getElementsByTagName("*").length}),n.getElementsByClassName=Q.test(d.getElementsByClassName),n.getById=ue(function(e){return h.appendChild(e).id=b,!d.getElementsByName||!d.getElementsByName(b).length}),n.getById?(r.filter.ID=function(e){var t=e.replace(Z,ee);return function(e){return e.getAttribute("id")===t}},r.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&g){var n=t.getElementById(e);return n?[n]:[]}}):(r.filter.ID=function(e){var t=e.replace(Z,ee);return function(e){var n="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return n&&n.value===t}},r.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&g){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),r.find.TAG=n.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):n.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},r.find.CLASS=n.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&g)return t.getElementsByClassName(e)},v=[],y=[],(n.qsa=Q.test(d.querySelectorAll))&&(ue(function(e){h.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&y.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||y.push("\\["+M+"*(?:value|"+P+")"),e.querySelectorAll("[id~="+b+"-]").length||y.push("~="),e.querySelectorAll(":checked").length||y.push(":checked"),e.querySelectorAll("a#"+b+"+*").length||y.push(".#.+[+~]")}),ue(function(e){e.innerHTML="";var t=d.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&y.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&y.push(":enabled",":disabled"),h.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&y.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),y.push(",.*:")})),(n.matchesSelector=Q.test(m=h.matches||h.webkitMatchesSelector||h.mozMatchesSelector||h.oMatchesSelector||h.msMatchesSelector))&&ue(function(e){n.disconnectedMatch=m.call(e,"*"),m.call(e,"[s!='']:x"),v.push("!=",W)}),y=y.length&&new RegExp(y.join("|")),v=v.length&&new RegExp(v.join("|")),t=Q.test(h.compareDocumentPosition),x=t||Q.test(h.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},D=t?function(e,t){if(e===t)return f=!0,0;var r=!e.compareDocumentPosition-!t.compareDocumentPosition;return r||(1&(r=(e.ownerDocument||e)===(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!n.sortDetached&&t.compareDocumentPosition(e)===r?e===d||e.ownerDocument===w&&x(w,e)?-1:t===d||t.ownerDocument===w&&x(w,t)?1:c?O(c,e)-O(c,t):0:4&r?-1:1)}:function(e,t){if(e===t)return f=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e===d?-1:t===d?1:i?-1:o?1:c?O(c,e)-O(c,t):0;if(i===o)return ce(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?ce(a[r],s[r]):a[r]===w?-1:s[r]===w?1:0},d):d},oe.matches=function(e,t){return oe(e,null,null,t)},oe.matchesSelector=function(e,t){if((e.ownerDocument||e)!==d&&p(e),t=t.replace(z,"='$1']"),n.matchesSelector&&g&&!S[t+" "]&&(!v||!v.test(t))&&(!y||!y.test(t)))try{var r=m.call(e,t);if(r||n.disconnectedMatch||e.document&&11!==e.document.nodeType)return r}catch(e){}return oe(t,d,null,[e]).length>0},oe.contains=function(e,t){return(e.ownerDocument||e)!==d&&p(e),x(e,t)},oe.attr=function(e,t){(e.ownerDocument||e)!==d&&p(e);var i=r.attrHandle[t.toLowerCase()],o=i&&N.call(r.attrHandle,t.toLowerCase())?i(e,t,!g):void 0;return void 0!==o?o:n.attributes||!g?e.getAttribute(t):(o=e.getAttributeNode(t))&&o.specified?o.value:null},oe.escape=function(e){return(e+"").replace(te,ne)},oe.error=function(e){throw new Error("Syntax error, unrecognized expression: "+e)},oe.uniqueSort=function(e){var t,r=[],i=0,o=0;if(f=!n.detectDuplicates,c=!n.sortStable&&e.slice(0),e.sort(D),f){while(t=e[o++])t===e[o]&&(i=r.push(o));while(i--)e.splice(r[i],1)}return c=null,e},i=oe.getText=function(e){var t,n="",r=0,o=e.nodeType;if(o){if(1===o||9===o||11===o){if("string"==typeof e.textContent)return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=i(e)}else if(3===o||4===o)return e.nodeValue}else while(t=e[r++])n+=i(t);return n},(r=oe.selectors={cacheLength:50,createPseudo:se,match:V,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(Z,ee),e[3]=(e[3]||e[4]||e[5]||"").replace(Z,ee),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||oe.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&oe.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return V.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=a(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(Z,ee).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=E[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&E(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(e,t,n){return function(r){var i=oe.attr(r,e);return null==i?"!="===t:!t||(i+="","="===t?i===n:"!="===t?i!==n:"^="===t?n&&0===i.indexOf(n):"*="===t?n&&i.indexOf(n)>-1:"$="===t?n&&i.slice(-n.length)===n:"~="===t?(" "+i.replace($," ")+" ").indexOf(n)>-1:"|="===t&&(i===n||i.slice(0,n.length+1)===n+"-"))}},CHILD:function(e,t,n,r,i){var o="nth"!==e.slice(0,3),a="last"!==e.slice(-4),s="of-type"===t;return 1===r&&0===i?function(e){return!!e.parentNode}:function(t,n,u){var l,c,f,p,d,h,g=o!==a?"nextSibling":"previousSibling",y=t.parentNode,v=s&&t.nodeName.toLowerCase(),m=!u&&!s,x=!1;if(y){if(o){while(g){p=t;while(p=p[g])if(s?p.nodeName.toLowerCase()===v:1===p.nodeType)return!1;h=g="only"===e&&!h&&"nextSibling"}return!0}if(h=[a?y.firstChild:y.lastChild],a&&m){x=(d=(l=(c=(f=(p=y)[b]||(p[b]={}))[p.uniqueID]||(f[p.uniqueID]={}))[e]||[])[0]===T&&l[1])&&l[2],p=d&&y.childNodes[d];while(p=++d&&p&&p[g]||(x=d=0)||h.pop())if(1===p.nodeType&&++x&&p===t){c[e]=[T,d,x];break}}else if(m&&(x=d=(l=(c=(f=(p=t)[b]||(p[b]={}))[p.uniqueID]||(f[p.uniqueID]={}))[e]||[])[0]===T&&l[1]),!1===x)while(p=++d&&p&&p[g]||(x=d=0)||h.pop())if((s?p.nodeName.toLowerCase()===v:1===p.nodeType)&&++x&&(m&&((c=(f=p[b]||(p[b]={}))[p.uniqueID]||(f[p.uniqueID]={}))[e]=[T,x]),p===t))break;return(x-=i)===r||x%r==0&&x/r>=0}}},PSEUDO:function(e,t){var n,i=r.pseudos[e]||r.setFilters[e.toLowerCase()]||oe.error("unsupported pseudo: "+e);return i[b]?i(t):i.length>1?(n=[e,e,"",t],r.setFilters.hasOwnProperty(e.toLowerCase())?se(function(e,n){var r,o=i(e,t),a=o.length;while(a--)e[r=O(e,o[a])]=!(n[r]=o[a])}):function(e){return i(e,0,n)}):i}},pseudos:{not:se(function(e){var t=[],n=[],r=s(e.replace(B,"$1"));return r[b]?se(function(e,t,n,i){var o,a=r(e,null,i,[]),s=e.length;while(s--)(o=a[s])&&(e[s]=!(t[s]=o))}):function(e,i,o){return t[0]=e,r(t,null,o,n),t[0]=null,!n.pop()}}),has:se(function(e){return function(t){return oe(e,t).length>0}}),contains:se(function(e){return e=e.replace(Z,ee),function(t){return(t.textContent||t.innerText||i(t)).indexOf(e)>-1}}),lang:se(function(e){return U.test(e||"")||oe.error("unsupported lang: "+e),e=e.replace(Z,ee).toLowerCase(),function(t){var n;do{if(n=g?t.lang:t.getAttribute("xml:lang")||t.getAttribute("lang"))return(n=n.toLowerCase())===e||0===n.indexOf(e+"-")}while((t=t.parentNode)&&1===t.nodeType);return!1}}),target:function(t){var n=e.location&&e.location.hash;return n&&n.slice(1)===t.id},root:function(e){return e===h},focus:function(e){return e===d.activeElement&&(!d.hasFocus||d.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},enabled:de(!1),disabled:de(!0),checked:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&!!e.checked||"option"===t&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,!0===e.selected},empty:function(e){for(e=e.firstChild;e;e=e.nextSibling)if(e.nodeType<6)return!1;return!0},parent:function(e){return!r.pseudos.empty(e)},header:function(e){return Y.test(e.nodeName)},input:function(e){return G.test(e.nodeName)},button:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&"button"===e.type||"button"===t},text:function(e){var t;return"input"===e.nodeName.toLowerCase()&&"text"===e.type&&(null==(t=e.getAttribute("type"))||"text"===t.toLowerCase())},first:he(function(){return[0]}),last:he(function(e,t){return[t-1]}),eq:he(function(e,t,n){return[n<0?n+t:n]}),even:he(function(e,t){for(var n=0;n=0;)e.push(r);return e}),gt:he(function(e,t,n){for(var r=n<0?n+t:n;++r1?function(t,n,r){var i=e.length;while(i--)if(!e[i](t,n,r))return!1;return!0}:e[0]}function be(e,t,n){for(var r=0,i=t.length;r-1&&(o[l]=!(a[l]=f))}}else v=we(v===a?v.splice(h,v.length):v),i?i(null,a,v,u):L.apply(a,v)})}function Ce(e){for(var t,n,i,o=e.length,a=r.relative[e[0].type],s=a||r.relative[" "],u=a?1:0,c=me(function(e){return e===t},s,!0),f=me(function(e){return O(t,e)>-1},s,!0),p=[function(e,n,r){var i=!a&&(r||n!==l)||((t=n).nodeType?c(e,n,r):f(e,n,r));return t=null,i}];u1&&xe(p),u>1&&ve(e.slice(0,u-1).concat({value:" "===e[u-2].type?"*":""})).replace(B,"$1"),n,u0,i=e.length>0,o=function(o,a,s,u,c){var f,h,y,v=0,m="0",x=o&&[],b=[],w=l,C=o||i&&r.find.TAG("*",c),E=T+=null==w?1:Math.random()||.1,k=C.length;for(c&&(l=a===d||a||c);m!==k&&null!=(f=C[m]);m++){if(i&&f){h=0,a||f.ownerDocument===d||(p(f),s=!g);while(y=e[h++])if(y(f,a||d,s)){u.push(f);break}c&&(T=E)}n&&((f=!y&&f)&&v--,o&&x.push(f))}if(v+=m,n&&m!==v){h=0;while(y=t[h++])y(x,b,a,s);if(o){if(v>0)while(m--)x[m]||b[m]||(b[m]=j.call(u));b=we(b)}L.apply(u,b),c&&!o&&b.length>0&&v+t.length>1&&oe.uniqueSort(u)}return c&&(T=E,l=w),x};return n?se(o):o}return s=oe.compile=function(e,t){var n,r=[],i=[],o=S[e+" "];if(!o){t||(t=a(e)),n=t.length;while(n--)(o=Ce(t[n]))[b]?r.push(o):i.push(o);(o=S(e,Ee(i,r))).selector=e}return o},u=oe.select=function(e,t,n,i){var o,u,l,c,f,p="function"==typeof e&&e,d=!i&&a(e=p.selector||e);if(n=n||[],1===d.length){if((u=d[0]=d[0].slice(0)).length>2&&"ID"===(l=u[0]).type&&9===t.nodeType&&g&&r.relative[u[1].type]){if(!(t=(r.find.ID(l.matches[0].replace(Z,ee),t)||[])[0]))return n;p&&(t=t.parentNode),e=e.slice(u.shift().value.length)}o=V.needsContext.test(e)?0:u.length;while(o--){if(l=u[o],r.relative[c=l.type])break;if((f=r.find[c])&&(i=f(l.matches[0].replace(Z,ee),K.test(u[0].type)&&ge(t.parentNode)||t))){if(u.splice(o,1),!(e=i.length&&ve(u)))return L.apply(n,i),n;break}}}return(p||s(e,d))(i,t,!g,n,!t||K.test(e)&&ge(t.parentNode)||t),n},n.sortStable=b.split("").sort(D).join("")===b,n.detectDuplicates=!!f,p(),n.sortDetached=ue(function(e){return 1&e.compareDocumentPosition(d.createElement("fieldset"))}),ue(function(e){return e.innerHTML="","#"===e.firstChild.getAttribute("href")})||le("type|href|height|width",function(e,t,n){if(!n)return e.getAttribute(t,"type"===t.toLowerCase()?1:2)}),n.attributes&&ue(function(e){return e.innerHTML="",e.firstChild.setAttribute("value",""),""===e.firstChild.getAttribute("value")})||le("value",function(e,t,n){if(!n&&"input"===e.nodeName.toLowerCase())return e.defaultValue}),ue(function(e){return null==e.getAttribute("disabled")})||le(P,function(e,t,n){var r;if(!n)return!0===e[t]?t.toLowerCase():(r=e.getAttributeNode(t))&&r.specified?r.value:null}),oe}(e);w.find=E,w.expr=E.selectors,w.expr[":"]=w.expr.pseudos,w.uniqueSort=w.unique=E.uniqueSort,w.text=E.getText,w.isXMLDoc=E.isXML,w.contains=E.contains,w.escapeSelector=E.escape;var k=function(e,t,n){var r=[],i=void 0!==n;while((e=e[t])&&9!==e.nodeType)if(1===e.nodeType){if(i&&w(e).is(n))break;r.push(e)}return r},S=function(e,t){for(var n=[];e;e=e.nextSibling)1===e.nodeType&&e!==t&&n.push(e);return n},D=w.expr.match.needsContext;function N(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()}var A=/^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,t,n){return g(t)?w.grep(e,function(e,r){return!!t.call(e,r,e)!==n}):t.nodeType?w.grep(e,function(e){return e===t!==n}):"string"!=typeof t?w.grep(e,function(e){return u.call(t,e)>-1!==n}):w.filter(t,e,n)}w.filter=function(e,t,n){var r=t[0];return n&&(e=":not("+e+")"),1===t.length&&1===r.nodeType?w.find.matchesSelector(r,e)?[r]:[]:w.find.matches(e,w.grep(t,function(e){return 1===e.nodeType}))},w.fn.extend({find:function(e){var t,n,r=this.length,i=this;if("string"!=typeof e)return this.pushStack(w(e).filter(function(){for(t=0;t1?w.uniqueSort(n):n},filter:function(e){return this.pushStack(j(this,e||[],!1))},not:function(e){return this.pushStack(j(this,e||[],!0))},is:function(e){return!!j(this,"string"==typeof e&&D.test(e)?w(e):e||[],!1).length}});var q,L=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/;(w.fn.init=function(e,t,n){var i,o;if(!e)return this;if(n=n||q,"string"==typeof e){if(!(i="<"===e[0]&&">"===e[e.length-1]&&e.length>=3?[null,e,null]:L.exec(e))||!i[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(i[1]){if(t=t instanceof w?t[0]:t,w.merge(this,w.parseHTML(i[1],t&&t.nodeType?t.ownerDocument||t:r,!0)),A.test(i[1])&&w.isPlainObject(t))for(i in t)g(this[i])?this[i](t[i]):this.attr(i,t[i]);return this}return(o=r.getElementById(i[2]))&&(this[0]=o,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):g(e)?void 0!==n.ready?n.ready(e):e(w):w.makeArray(e,this)}).prototype=w.fn,q=w(r);var H=/^(?:parents|prev(?:Until|All))/,O={children:!0,contents:!0,next:!0,prev:!0};w.fn.extend({has:function(e){var t=w(e,this),n=t.length;return this.filter(function(){for(var e=0;e-1:1===n.nodeType&&w.find.matchesSelector(n,e))){o.push(n);break}return this.pushStack(o.length>1?w.uniqueSort(o):o)},index:function(e){return e?"string"==typeof e?u.call(w(e),this[0]):u.call(this,e.jquery?e[0]:e):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(e,t){return this.pushStack(w.uniqueSort(w.merge(this.get(),w(e,t))))},addBack:function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}});function P(e,t){while((e=e[t])&&1!==e.nodeType);return e}w.each({parent:function(e){var t=e.parentNode;return t&&11!==t.nodeType?t:null},parents:function(e){return k(e,"parentNode")},parentsUntil:function(e,t,n){return k(e,"parentNode",n)},next:function(e){return P(e,"nextSibling")},prev:function(e){return P(e,"previousSibling")},nextAll:function(e){return k(e,"nextSibling")},prevAll:function(e){return k(e,"previousSibling")},nextUntil:function(e,t,n){return k(e,"nextSibling",n)},prevUntil:function(e,t,n){return k(e,"previousSibling",n)},siblings:function(e){return S((e.parentNode||{}).firstChild,e)},children:function(e){return S(e.firstChild)},contents:function(e){return N(e,"iframe")?e.contentDocument:(N(e,"template")&&(e=e.content||e),w.merge([],e.childNodes))}},function(e,t){w.fn[e]=function(n,r){var i=w.map(this,t,n);return"Until"!==e.slice(-5)&&(r=n),r&&"string"==typeof r&&(i=w.filter(r,i)),this.length>1&&(O[e]||w.uniqueSort(i),H.test(e)&&i.reverse()),this.pushStack(i)}});var M=/[^\x20\t\r\n\f]+/g;function R(e){var t={};return w.each(e.match(M)||[],function(e,n){t[n]=!0}),t}w.Callbacks=function(e){e="string"==typeof e?R(e):w.extend({},e);var t,n,r,i,o=[],a=[],s=-1,u=function(){for(i=i||e.once,r=t=!0;a.length;s=-1){n=a.shift();while(++s-1)o.splice(n,1),n<=s&&s--}),this},has:function(e){return e?w.inArray(e,o)>-1:o.length>0},empty:function(){return o&&(o=[]),this},disable:function(){return i=a=[],o=n="",this},disabled:function(){return!o},lock:function(){return i=a=[],n||t||(o=n=""),this},locked:function(){return!!i},fireWith:function(e,n){return i||(n=[e,(n=n||[]).slice?n.slice():n],a.push(n),t||u()),this},fire:function(){return l.fireWith(this,arguments),this},fired:function(){return!!r}};return l};function I(e){return e}function W(e){throw e}function $(e,t,n,r){var i;try{e&&g(i=e.promise)?i.call(e).done(t).fail(n):e&&g(i=e.then)?i.call(e,t,n):t.apply(void 0,[e].slice(r))}catch(e){n.apply(void 0,[e])}}w.extend({Deferred:function(t){var n=[["notify","progress",w.Callbacks("memory"),w.Callbacks("memory"),2],["resolve","done",w.Callbacks("once memory"),w.Callbacks("once memory"),0,"resolved"],["reject","fail",w.Callbacks("once memory"),w.Callbacks("once memory"),1,"rejected"]],r="pending",i={state:function(){return r},always:function(){return o.done(arguments).fail(arguments),this},"catch":function(e){return i.then(null,e)},pipe:function(){var e=arguments;return w.Deferred(function(t){w.each(n,function(n,r){var i=g(e[r[4]])&&e[r[4]];o[r[1]](function(){var e=i&&i.apply(this,arguments);e&&g(e.promise)?e.promise().progress(t.notify).done(t.resolve).fail(t.reject):t[r[0]+"With"](this,i?[e]:arguments)})}),e=null}).promise()},then:function(t,r,i){var o=0;function a(t,n,r,i){return function(){var s=this,u=arguments,l=function(){var e,l;if(!(t=o&&(r!==W&&(s=void 0,u=[e]),n.rejectWith(s,u))}};t?c():(w.Deferred.getStackHook&&(c.stackTrace=w.Deferred.getStackHook()),e.setTimeout(c))}}return w.Deferred(function(e){n[0][3].add(a(0,e,g(i)?i:I,e.notifyWith)),n[1][3].add(a(0,e,g(t)?t:I)),n[2][3].add(a(0,e,g(r)?r:W))}).promise()},promise:function(e){return null!=e?w.extend(e,i):i}},o={};return w.each(n,function(e,t){var a=t[2],s=t[5];i[t[1]]=a.add,s&&a.add(function(){r=s},n[3-e][2].disable,n[3-e][3].disable,n[0][2].lock,n[0][3].lock),a.add(t[3].fire),o[t[0]]=function(){return o[t[0]+"With"](this===o?void 0:this,arguments),this},o[t[0]+"With"]=a.fireWith}),i.promise(o),t&&t.call(o,o),o},when:function(e){var t=arguments.length,n=t,r=Array(n),i=o.call(arguments),a=w.Deferred(),s=function(e){return function(n){r[e]=this,i[e]=arguments.length>1?o.call(arguments):n,--t||a.resolveWith(r,i)}};if(t<=1&&($(e,a.done(s(n)).resolve,a.reject,!t),"pending"===a.state()||g(i[n]&&i[n].then)))return a.then();while(n--)$(i[n],s(n),a.reject);return a.promise()}});var B=/^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;w.Deferred.exceptionHook=function(t,n){e.console&&e.console.warn&&t&&B.test(t.name)&&e.console.warn("jQuery.Deferred exception: "+t.message,t.stack,n)},w.readyException=function(t){e.setTimeout(function(){throw t})};var F=w.Deferred();w.fn.ready=function(e){return F.then(e)["catch"](function(e){w.readyException(e)}),this},w.extend({isReady:!1,readyWait:1,ready:function(e){(!0===e?--w.readyWait:w.isReady)||(w.isReady=!0,!0!==e&&--w.readyWait>0||F.resolveWith(r,[w]))}}),w.ready.then=F.then;function _(){r.removeEventListener("DOMContentLoaded",_),e.removeEventListener("load",_),w.ready()}"complete"===r.readyState||"loading"!==r.readyState&&!r.documentElement.doScroll?e.setTimeout(w.ready):(r.addEventListener("DOMContentLoaded",_),e.addEventListener("load",_));var z=function(e,t,n,r,i,o,a){var s=0,u=e.length,l=null==n;if("object"===x(n)){i=!0;for(s in n)z(e,t,s,n[s],!0,o,a)}else if(void 0!==r&&(i=!0,g(r)||(a=!0),l&&(a?(t.call(e,r),t=null):(l=t,t=function(e,t,n){return l.call(w(e),n)})),t))for(;s1,null,!0)},removeData:function(e){return this.each(function(){K.remove(this,e)})}}),w.extend({queue:function(e,t,n){var r;if(e)return t=(t||"fx")+"queue",r=J.get(e,t),n&&(!r||Array.isArray(n)?r=J.access(e,t,w.makeArray(n)):r.push(n)),r||[]},dequeue:function(e,t){t=t||"fx";var n=w.queue(e,t),r=n.length,i=n.shift(),o=w._queueHooks(e,t),a=function(){w.dequeue(e,t)};"inprogress"===i&&(i=n.shift(),r--),i&&("fx"===t&&n.unshift("inprogress"),delete o.stop,i.call(e,a,o)),!r&&o&&o.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return J.get(e,n)||J.access(e,n,{empty:w.Callbacks("once memory").add(function(){J.remove(e,[t+"queue",n])})})}}),w.fn.extend({queue:function(e,t){var n=2;return"string"!=typeof e&&(t=e,e="fx",n--),arguments.length\x20\t\r\n\f]+)/i,he=/^$|^module$|\/(?:java|ecma)script/i,ge={option:[1,""],thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};ge.optgroup=ge.option,ge.tbody=ge.tfoot=ge.colgroup=ge.caption=ge.thead,ge.th=ge.td;function ye(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&N(e,t)?w.merge([e],n):n}function ve(e,t){for(var n=0,r=e.length;n-1)i&&i.push(o);else if(l=w.contains(o.ownerDocument,o),a=ye(f.appendChild(o),"script"),l&&ve(a),n){c=0;while(o=a[c++])he.test(o.type||"")&&n.push(o)}return f}!function(){var e=r.createDocumentFragment().appendChild(r.createElement("div")),t=r.createElement("input");t.setAttribute("type","radio"),t.setAttribute("checked","checked"),t.setAttribute("name","t"),e.appendChild(t),h.checkClone=e.cloneNode(!0).cloneNode(!0).lastChild.checked,e.innerHTML="",h.noCloneChecked=!!e.cloneNode(!0).lastChild.defaultValue}();var be=r.documentElement,we=/^key/,Te=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,Ce=/^([^.]*)(?:\.(.+)|)/;function Ee(){return!0}function ke(){return!1}function Se(){try{return r.activeElement}catch(e){}}function De(e,t,n,r,i,o){var a,s;if("object"==typeof t){"string"!=typeof n&&(r=r||n,n=void 0);for(s in t)De(e,s,n,r,t[s],o);return e}if(null==r&&null==i?(i=n,r=n=void 0):null==i&&("string"==typeof n?(i=r,r=void 0):(i=r,r=n,n=void 0)),!1===i)i=ke;else if(!i)return e;return 1===o&&(a=i,(i=function(e){return w().off(e),a.apply(this,arguments)}).guid=a.guid||(a.guid=w.guid++)),e.each(function(){w.event.add(this,t,i,r,n)})}w.event={global:{},add:function(e,t,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,y=J.get(e);if(y){n.handler&&(n=(o=n).handler,i=o.selector),i&&w.find.matchesSelector(be,i),n.guid||(n.guid=w.guid++),(u=y.events)||(u=y.events={}),(a=y.handle)||(a=y.handle=function(t){return"undefined"!=typeof w&&w.event.triggered!==t.type?w.event.dispatch.apply(e,arguments):void 0}),l=(t=(t||"").match(M)||[""]).length;while(l--)d=g=(s=Ce.exec(t[l])||[])[1],h=(s[2]||"").split(".").sort(),d&&(f=w.event.special[d]||{},d=(i?f.delegateType:f.bindType)||d,f=w.event.special[d]||{},c=w.extend({type:d,origType:g,data:r,handler:n,guid:n.guid,selector:i,needsContext:i&&w.expr.match.needsContext.test(i),namespace:h.join(".")},o),(p=u[d])||((p=u[d]=[]).delegateCount=0,f.setup&&!1!==f.setup.call(e,r,h,a)||e.addEventListener&&e.addEventListener(d,a)),f.add&&(f.add.call(e,c),c.handler.guid||(c.handler.guid=n.guid)),i?p.splice(p.delegateCount++,0,c):p.push(c),w.event.global[d]=!0)}},remove:function(e,t,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,y=J.hasData(e)&&J.get(e);if(y&&(u=y.events)){l=(t=(t||"").match(M)||[""]).length;while(l--)if(s=Ce.exec(t[l])||[],d=g=s[1],h=(s[2]||"").split(".").sort(),d){f=w.event.special[d]||{},p=u[d=(r?f.delegateType:f.bindType)||d]||[],s=s[2]&&new RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"),a=o=p.length;while(o--)c=p[o],!i&&g!==c.origType||n&&n.guid!==c.guid||s&&!s.test(c.namespace)||r&&r!==c.selector&&("**"!==r||!c.selector)||(p.splice(o,1),c.selector&&p.delegateCount--,f.remove&&f.remove.call(e,c));a&&!p.length&&(f.teardown&&!1!==f.teardown.call(e,h,y.handle)||w.removeEvent(e,d,y.handle),delete u[d])}else for(d in u)w.event.remove(e,d+t[l],n,r,!0);w.isEmptyObject(u)&&J.remove(e,"handle events")}},dispatch:function(e){var t=w.event.fix(e),n,r,i,o,a,s,u=new Array(arguments.length),l=(J.get(this,"events")||{})[t.type]||[],c=w.event.special[t.type]||{};for(u[0]=t,n=1;n=1))for(;l!==this;l=l.parentNode||this)if(1===l.nodeType&&("click"!==e.type||!0!==l.disabled)){for(o=[],a={},n=0;n-1:w.find(i,this,null,[l]).length),a[i]&&o.push(r);o.length&&s.push({elem:l,handlers:o})}return l=this,u\x20\t\r\n\f]*)[^>]*)\/>/gi,Ae=/\s*$/g;function Le(e,t){return N(e,"table")&&N(11!==t.nodeType?t:t.firstChild,"tr")?w(e).children("tbody")[0]||e:e}function He(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function Oe(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Pe(e,t){var n,r,i,o,a,s,u,l;if(1===t.nodeType){if(J.hasData(e)&&(o=J.access(e),a=J.set(t,o),l=o.events)){delete a.handle,a.events={};for(i in l)for(n=0,r=l[i].length;n1&&"string"==typeof y&&!h.checkClone&&je.test(y))return e.each(function(i){var o=e.eq(i);v&&(t[0]=y.call(this,i,o.html())),Re(o,t,n,r)});if(p&&(i=xe(t,e[0].ownerDocument,!1,e,r),o=i.firstChild,1===i.childNodes.length&&(i=o),o||r)){for(u=(s=w.map(ye(i,"script"),He)).length;f")},clone:function(e,t,n){var r,i,o,a,s=e.cloneNode(!0),u=w.contains(e.ownerDocument,e);if(!(h.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||w.isXMLDoc(e)))for(a=ye(s),r=0,i=(o=ye(e)).length;r0&&ve(a,!u&&ye(e,"script")),s},cleanData:function(e){for(var t,n,r,i=w.event.special,o=0;void 0!==(n=e[o]);o++)if(Y(n)){if(t=n[J.expando]){if(t.events)for(r in t.events)i[r]?w.event.remove(n,r):w.removeEvent(n,r,t.handle);n[J.expando]=void 0}n[K.expando]&&(n[K.expando]=void 0)}}}),w.fn.extend({detach:function(e){return Ie(this,e,!0)},remove:function(e){return Ie(this,e)},text:function(e){return z(this,function(e){return void 0===e?w.text(this):this.empty().each(function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=e)})},null,e,arguments.length)},append:function(){return Re(this,arguments,function(e){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||Le(this,e).appendChild(e)})},prepend:function(){return Re(this,arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=Le(this,e);t.insertBefore(e,t.firstChild)}})},before:function(){return Re(this,arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this)})},after:function(){return Re(this,arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this.nextSibling)})},empty:function(){for(var e,t=0;null!=(e=this[t]);t++)1===e.nodeType&&(w.cleanData(ye(e,!1)),e.textContent="");return this},clone:function(e,t){return e=null!=e&&e,t=null==t?e:t,this.map(function(){return w.clone(this,e,t)})},html:function(e){return z(this,function(e){var t=this[0]||{},n=0,r=this.length;if(void 0===e&&1===t.nodeType)return t.innerHTML;if("string"==typeof e&&!Ae.test(e)&&!ge[(de.exec(e)||["",""])[1].toLowerCase()]){e=w.htmlPrefilter(e);try{for(;n=0&&(u+=Math.max(0,Math.ceil(e["offset"+t[0].toUpperCase()+t.slice(1)]-o-u-s-.5))),u}function et(e,t,n){var r=$e(e),i=Fe(e,t,r),o="border-box"===w.css(e,"boxSizing",!1,r),a=o;if(We.test(i)){if(!n)return i;i="auto"}return a=a&&(h.boxSizingReliable()||i===e.style[t]),("auto"===i||!parseFloat(i)&&"inline"===w.css(e,"display",!1,r))&&(i=e["offset"+t[0].toUpperCase()+t.slice(1)],a=!0),(i=parseFloat(i)||0)+Ze(e,t,n||(o?"border":"content"),a,r,i)+"px"}w.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=Fe(e,"opacity");return""===n?"1":n}}}},cssNumber:{animationIterationCount:!0,columnCount:!0,fillOpacity:!0,flexGrow:!0,flexShrink:!0,fontWeight:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{},style:function(e,t,n,r){if(e&&3!==e.nodeType&&8!==e.nodeType&&e.style){var i,o,a,s=G(t),u=Xe.test(t),l=e.style;if(u||(t=Je(s)),a=w.cssHooks[t]||w.cssHooks[s],void 0===n)return a&&"get"in a&&void 0!==(i=a.get(e,!1,r))?i:l[t];"string"==(o=typeof n)&&(i=ie.exec(n))&&i[1]&&(n=ue(e,t,i),o="number"),null!=n&&n===n&&("number"===o&&(n+=i&&i[3]||(w.cssNumber[s]?"":"px")),h.clearCloneStyle||""!==n||0!==t.indexOf("background")||(l[t]="inherit"),a&&"set"in a&&void 0===(n=a.set(e,n,r))||(u?l.setProperty(t,n):l[t]=n))}},css:function(e,t,n,r){var i,o,a,s=G(t);return Xe.test(t)||(t=Je(s)),(a=w.cssHooks[t]||w.cssHooks[s])&&"get"in a&&(i=a.get(e,!0,n)),void 0===i&&(i=Fe(e,t,r)),"normal"===i&&t in Ve&&(i=Ve[t]),""===n||n?(o=parseFloat(i),!0===n||isFinite(o)?o||0:i):i}}),w.each(["height","width"],function(e,t){w.cssHooks[t]={get:function(e,n,r){if(n)return!ze.test(w.css(e,"display"))||e.getClientRects().length&&e.getBoundingClientRect().width?et(e,t,r):se(e,Ue,function(){return et(e,t,r)})},set:function(e,n,r){var i,o=$e(e),a="border-box"===w.css(e,"boxSizing",!1,o),s=r&&Ze(e,t,r,a,o);return a&&h.scrollboxSize()===o.position&&(s-=Math.ceil(e["offset"+t[0].toUpperCase()+t.slice(1)]-parseFloat(o[t])-Ze(e,t,"border",!1,o)-.5)),s&&(i=ie.exec(n))&&"px"!==(i[3]||"px")&&(e.style[t]=n,n=w.css(e,t)),Ke(e,n,s)}}}),w.cssHooks.marginLeft=_e(h.reliableMarginLeft,function(e,t){if(t)return(parseFloat(Fe(e,"marginLeft"))||e.getBoundingClientRect().left-se(e,{marginLeft:0},function(){return e.getBoundingClientRect().left}))+"px"}),w.each({margin:"",padding:"",border:"Width"},function(e,t){w.cssHooks[e+t]={expand:function(n){for(var r=0,i={},o="string"==typeof n?n.split(" "):[n];r<4;r++)i[e+oe[r]+t]=o[r]||o[r-2]||o[0];return i}},"margin"!==e&&(w.cssHooks[e+t].set=Ke)}),w.fn.extend({css:function(e,t){return z(this,function(e,t,n){var r,i,o={},a=0;if(Array.isArray(t)){for(r=$e(e),i=t.length;a1)}});function tt(e,t,n,r,i){return new tt.prototype.init(e,t,n,r,i)}w.Tween=tt,tt.prototype={constructor:tt,init:function(e,t,n,r,i,o){this.elem=e,this.prop=n,this.easing=i||w.easing._default,this.options=t,this.start=this.now=this.cur(),this.end=r,this.unit=o||(w.cssNumber[n]?"":"px")},cur:function(){var e=tt.propHooks[this.prop];return e&&e.get?e.get(this):tt.propHooks._default.get(this)},run:function(e){var t,n=tt.propHooks[this.prop];return this.options.duration?this.pos=t=w.easing[this.easing](e,this.options.duration*e,0,1,this.options.duration):this.pos=t=e,this.now=(this.end-this.start)*t+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),n&&n.set?n.set(this):tt.propHooks._default.set(this),this}},tt.prototype.init.prototype=tt.prototype,tt.propHooks={_default:{get:function(e){var t;return 1!==e.elem.nodeType||null!=e.elem[e.prop]&&null==e.elem.style[e.prop]?e.elem[e.prop]:(t=w.css(e.elem,e.prop,""))&&"auto"!==t?t:0},set:function(e){w.fx.step[e.prop]?w.fx.step[e.prop](e):1!==e.elem.nodeType||null==e.elem.style[w.cssProps[e.prop]]&&!w.cssHooks[e.prop]?e.elem[e.prop]=e.now:w.style(e.elem,e.prop,e.now+e.unit)}}},tt.propHooks.scrollTop=tt.propHooks.scrollLeft={set:function(e){e.elem.nodeType&&e.elem.parentNode&&(e.elem[e.prop]=e.now)}},w.easing={linear:function(e){return e},swing:function(e){return.5-Math.cos(e*Math.PI)/2},_default:"swing"},w.fx=tt.prototype.init,w.fx.step={};var nt,rt,it=/^(?:toggle|show|hide)$/,ot=/queueHooks$/;function at(){rt&&(!1===r.hidden&&e.requestAnimationFrame?e.requestAnimationFrame(at):e.setTimeout(at,w.fx.interval),w.fx.tick())}function st(){return e.setTimeout(function(){nt=void 0}),nt=Date.now()}function ut(e,t){var n,r=0,i={height:e};for(t=t?1:0;r<4;r+=2-t)i["margin"+(n=oe[r])]=i["padding"+n]=e;return t&&(i.opacity=i.width=e),i}function lt(e,t,n){for(var r,i=(pt.tweeners[t]||[]).concat(pt.tweeners["*"]),o=0,a=i.length;o1)},removeAttr:function(e){return this.each(function(){w.removeAttr(this,e)})}}),w.extend({attr:function(e,t,n){var r,i,o=e.nodeType;if(3!==o&&8!==o&&2!==o)return"undefined"==typeof e.getAttribute?w.prop(e,t,n):(1===o&&w.isXMLDoc(e)||(i=w.attrHooks[t.toLowerCase()]||(w.expr.match.bool.test(t)?dt:void 0)),void 0!==n?null===n?void w.removeAttr(e,t):i&&"set"in i&&void 0!==(r=i.set(e,n,t))?r:(e.setAttribute(t,n+""),n):i&&"get"in i&&null!==(r=i.get(e,t))?r:null==(r=w.find.attr(e,t))?void 0:r)},attrHooks:{type:{set:function(e,t){if(!h.radioValue&&"radio"===t&&N(e,"input")){var n=e.value;return e.setAttribute("type",t),n&&(e.value=n),t}}}},removeAttr:function(e,t){var n,r=0,i=t&&t.match(M);if(i&&1===e.nodeType)while(n=i[r++])e.removeAttribute(n)}}),dt={set:function(e,t,n){return!1===t?w.removeAttr(e,n):e.setAttribute(n,n),n}},w.each(w.expr.match.bool.source.match(/\w+/g),function(e,t){var n=ht[t]||w.find.attr;ht[t]=function(e,t,r){var i,o,a=t.toLowerCase();return r||(o=ht[a],ht[a]=i,i=null!=n(e,t,r)?a:null,ht[a]=o),i}});var gt=/^(?:input|select|textarea|button)$/i,yt=/^(?:a|area)$/i;w.fn.extend({prop:function(e,t){return z(this,w.prop,e,t,arguments.length>1)},removeProp:function(e){return this.each(function(){delete this[w.propFix[e]||e]})}}),w.extend({prop:function(e,t,n){var r,i,o=e.nodeType;if(3!==o&&8!==o&&2!==o)return 1===o&&w.isXMLDoc(e)||(t=w.propFix[t]||t,i=w.propHooks[t]),void 0!==n?i&&"set"in i&&void 0!==(r=i.set(e,n,t))?r:e[t]=n:i&&"get"in i&&null!==(r=i.get(e,t))?r:e[t]},propHooks:{tabIndex:{get:function(e){var t=w.find.attr(e,"tabindex");return t?parseInt(t,10):gt.test(e.nodeName)||yt.test(e.nodeName)&&e.href?0:-1}}},propFix:{"for":"htmlFor","class":"className"}}),h.optSelected||(w.propHooks.selected={get:function(e){var t=e.parentNode;return t&&t.parentNode&&t.parentNode.selectedIndex,null},set:function(e){var t=e.parentNode;t&&(t.selectedIndex,t.parentNode&&t.parentNode.selectedIndex)}}),w.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){w.propFix[this.toLowerCase()]=this});function vt(e){return(e.match(M)||[]).join(" ")}function mt(e){return e.getAttribute&&e.getAttribute("class")||""}function xt(e){return Array.isArray(e)?e:"string"==typeof e?e.match(M)||[]:[]}w.fn.extend({addClass:function(e){var t,n,r,i,o,a,s,u=0;if(g(e))return this.each(function(t){w(this).addClass(e.call(this,t,mt(this)))});if((t=xt(e)).length)while(n=this[u++])if(i=mt(n),r=1===n.nodeType&&" "+vt(i)+" "){a=0;while(o=t[a++])r.indexOf(" "+o+" ")<0&&(r+=o+" ");i!==(s=vt(r))&&n.setAttribute("class",s)}return this},removeClass:function(e){var t,n,r,i,o,a,s,u=0;if(g(e))return this.each(function(t){w(this).removeClass(e.call(this,t,mt(this)))});if(!arguments.length)return this.attr("class","");if((t=xt(e)).length)while(n=this[u++])if(i=mt(n),r=1===n.nodeType&&" "+vt(i)+" "){a=0;while(o=t[a++])while(r.indexOf(" "+o+" ")>-1)r=r.replace(" "+o+" "," ");i!==(s=vt(r))&&n.setAttribute("class",s)}return this},toggleClass:function(e,t){var n=typeof e,r="string"===n||Array.isArray(e);return"boolean"==typeof t&&r?t?this.addClass(e):this.removeClass(e):g(e)?this.each(function(n){w(this).toggleClass(e.call(this,n,mt(this),t),t)}):this.each(function(){var t,i,o,a;if(r){i=0,o=w(this),a=xt(e);while(t=a[i++])o.hasClass(t)?o.removeClass(t):o.addClass(t)}else void 0!==e&&"boolean"!==n||((t=mt(this))&&J.set(this,"__className__",t),this.setAttribute&&this.setAttribute("class",t||!1===e?"":J.get(this,"__className__")||""))})},hasClass:function(e){var t,n,r=0;t=" "+e+" ";while(n=this[r++])if(1===n.nodeType&&(" "+vt(mt(n))+" ").indexOf(t)>-1)return!0;return!1}});var bt=/\r/g;w.fn.extend({val:function(e){var t,n,r,i=this[0];{if(arguments.length)return r=g(e),this.each(function(n){var i;1===this.nodeType&&(null==(i=r?e.call(this,n,w(this).val()):e)?i="":"number"==typeof i?i+="":Array.isArray(i)&&(i=w.map(i,function(e){return null==e?"":e+""})),(t=w.valHooks[this.type]||w.valHooks[this.nodeName.toLowerCase()])&&"set"in t&&void 0!==t.set(this,i,"value")||(this.value=i))});if(i)return(t=w.valHooks[i.type]||w.valHooks[i.nodeName.toLowerCase()])&&"get"in t&&void 0!==(n=t.get(i,"value"))?n:"string"==typeof(n=i.value)?n.replace(bt,""):null==n?"":n}}}),w.extend({valHooks:{option:{get:function(e){var t=w.find.attr(e,"value");return null!=t?t:vt(w.text(e))}},select:{get:function(e){var t,n,r,i=e.options,o=e.selectedIndex,a="select-one"===e.type,s=a?null:[],u=a?o+1:i.length;for(r=o<0?u:a?o:0;r-1)&&(n=!0);return n||(e.selectedIndex=-1),o}}}}),w.each(["radio","checkbox"],function(){w.valHooks[this]={set:function(e,t){if(Array.isArray(t))return e.checked=w.inArray(w(e).val(),t)>-1}},h.checkOn||(w.valHooks[this].get=function(e){return null===e.getAttribute("value")?"on":e.value})}),h.focusin="onfocusin"in e;var wt=/^(?:focusinfocus|focusoutblur)$/,Tt=function(e){e.stopPropagation()};w.extend(w.event,{trigger:function(t,n,i,o){var a,s,u,l,c,p,d,h,v=[i||r],m=f.call(t,"type")?t.type:t,x=f.call(t,"namespace")?t.namespace.split("."):[];if(s=h=u=i=i||r,3!==i.nodeType&&8!==i.nodeType&&!wt.test(m+w.event.triggered)&&(m.indexOf(".")>-1&&(m=(x=m.split(".")).shift(),x.sort()),c=m.indexOf(":")<0&&"on"+m,t=t[w.expando]?t:new w.Event(m,"object"==typeof t&&t),t.isTrigger=o?2:3,t.namespace=x.join("."),t.rnamespace=t.namespace?new RegExp("(^|\\.)"+x.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,t.result=void 0,t.target||(t.target=i),n=null==n?[t]:w.makeArray(n,[t]),d=w.event.special[m]||{},o||!d.trigger||!1!==d.trigger.apply(i,n))){if(!o&&!d.noBubble&&!y(i)){for(l=d.delegateType||m,wt.test(l+m)||(s=s.parentNode);s;s=s.parentNode)v.push(s),u=s;u===(i.ownerDocument||r)&&v.push(u.defaultView||u.parentWindow||e)}a=0;while((s=v[a++])&&!t.isPropagationStopped())h=s,t.type=a>1?l:d.bindType||m,(p=(J.get(s,"events")||{})[t.type]&&J.get(s,"handle"))&&p.apply(s,n),(p=c&&s[c])&&p.apply&&Y(s)&&(t.result=p.apply(s,n),!1===t.result&&t.preventDefault());return t.type=m,o||t.isDefaultPrevented()||d._default&&!1!==d._default.apply(v.pop(),n)||!Y(i)||c&&g(i[m])&&!y(i)&&((u=i[c])&&(i[c]=null),w.event.triggered=m,t.isPropagationStopped()&&h.addEventListener(m,Tt),i[m](),t.isPropagationStopped()&&h.removeEventListener(m,Tt),w.event.triggered=void 0,u&&(i[c]=u)),t.result}},simulate:function(e,t,n){var r=w.extend(new w.Event,n,{type:e,isSimulated:!0});w.event.trigger(r,null,t)}}),w.fn.extend({trigger:function(e,t){return this.each(function(){w.event.trigger(e,t,this)})},triggerHandler:function(e,t){var n=this[0];if(n)return w.event.trigger(e,t,n,!0)}}),h.focusin||w.each({focus:"focusin",blur:"focusout"},function(e,t){var n=function(e){w.event.simulate(t,e.target,w.event.fix(e))};w.event.special[t]={setup:function(){var r=this.ownerDocument||this,i=J.access(r,t);i||r.addEventListener(e,n,!0),J.access(r,t,(i||0)+1)},teardown:function(){var r=this.ownerDocument||this,i=J.access(r,t)-1;i?J.access(r,t,i):(r.removeEventListener(e,n,!0),J.remove(r,t))}}});var Ct=e.location,Et=Date.now(),kt=/\?/;w.parseXML=function(t){var n;if(!t||"string"!=typeof t)return null;try{n=(new e.DOMParser).parseFromString(t,"text/xml")}catch(e){n=void 0}return n&&!n.getElementsByTagName("parsererror").length||w.error("Invalid XML: "+t),n};var St=/\[\]$/,Dt=/\r?\n/g,Nt=/^(?:submit|button|image|reset|file)$/i,At=/^(?:input|select|textarea|keygen)/i;function jt(e,t,n,r){var i;if(Array.isArray(t))w.each(t,function(t,i){n||St.test(e)?r(e,i):jt(e+"["+("object"==typeof i&&null!=i?t:"")+"]",i,n,r)});else if(n||"object"!==x(t))r(e,t);else for(i in t)jt(e+"["+i+"]",t[i],n,r)}w.param=function(e,t){var n,r=[],i=function(e,t){var n=g(t)?t():t;r[r.length]=encodeURIComponent(e)+"="+encodeURIComponent(null==n?"":n)};if(Array.isArray(e)||e.jquery&&!w.isPlainObject(e))w.each(e,function(){i(this.name,this.value)});else for(n in e)jt(n,e[n],t,i);return r.join("&")},w.fn.extend({serialize:function(){return w.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var e=w.prop(this,"elements");return e?w.makeArray(e):this}).filter(function(){var e=this.type;return this.name&&!w(this).is(":disabled")&&At.test(this.nodeName)&&!Nt.test(e)&&(this.checked||!pe.test(e))}).map(function(e,t){var n=w(this).val();return null==n?null:Array.isArray(n)?w.map(n,function(e){return{name:t.name,value:e.replace(Dt,"\r\n")}}):{name:t.name,value:n.replace(Dt,"\r\n")}}).get()}});var qt=/%20/g,Lt=/#.*$/,Ht=/([?&])_=[^&]*/,Ot=/^(.*?):[ \t]*([^\r\n]*)$/gm,Pt=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,Mt=/^(?:GET|HEAD)$/,Rt=/^\/\//,It={},Wt={},$t="*/".concat("*"),Bt=r.createElement("a");Bt.href=Ct.href;function Ft(e){return function(t,n){"string"!=typeof t&&(n=t,t="*");var r,i=0,o=t.toLowerCase().match(M)||[];if(g(n))while(r=o[i++])"+"===r[0]?(r=r.slice(1)||"*",(e[r]=e[r]||[]).unshift(n)):(e[r]=e[r]||[]).push(n)}}function _t(e,t,n,r){var i={},o=e===Wt;function a(s){var u;return i[s]=!0,w.each(e[s]||[],function(e,s){var l=s(t,n,r);return"string"!=typeof l||o||i[l]?o?!(u=l):void 0:(t.dataTypes.unshift(l),a(l),!1)}),u}return a(t.dataTypes[0])||!i["*"]&&a("*")}function zt(e,t){var n,r,i=w.ajaxSettings.flatOptions||{};for(n in t)void 0!==t[n]&&((i[n]?e:r||(r={}))[n]=t[n]);return r&&w.extend(!0,e,r),e}function Xt(e,t,n){var r,i,o,a,s=e.contents,u=e.dataTypes;while("*"===u[0])u.shift(),void 0===r&&(r=e.mimeType||t.getResponseHeader("Content-Type"));if(r)for(i in s)if(s[i]&&s[i].test(r)){u.unshift(i);break}if(u[0]in n)o=u[0];else{for(i in n){if(!u[0]||e.converters[i+" "+u[0]]){o=i;break}a||(a=i)}o=o||a}if(o)return o!==u[0]&&u.unshift(o),n[o]}function Ut(e,t,n,r){var i,o,a,s,u,l={},c=e.dataTypes.slice();if(c[1])for(a in e.converters)l[a.toLowerCase()]=e.converters[a];o=c.shift();while(o)if(e.responseFields[o]&&(n[e.responseFields[o]]=t),!u&&r&&e.dataFilter&&(t=e.dataFilter(t,e.dataType)),u=o,o=c.shift())if("*"===o)o=u;else if("*"!==u&&u!==o){if(!(a=l[u+" "+o]||l["* "+o]))for(i in l)if((s=i.split(" "))[1]===o&&(a=l[u+" "+s[0]]||l["* "+s[0]])){!0===a?a=l[i]:!0!==l[i]&&(o=s[0],c.unshift(s[1]));break}if(!0!==a)if(a&&e["throws"])t=a(t);else try{t=a(t)}catch(e){return{state:"parsererror",error:a?e:"No conversion from "+u+" to "+o}}}return{state:"success",data:t}}w.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:Ct.href,type:"GET",isLocal:Pt.test(Ct.protocol),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":$t,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/\bxml\b/,html:/\bhtml/,json:/\bjson\b/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":JSON.parse,"text xml":w.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(e,t){return t?zt(zt(e,w.ajaxSettings),t):zt(w.ajaxSettings,e)},ajaxPrefilter:Ft(It),ajaxTransport:Ft(Wt),ajax:function(t,n){"object"==typeof t&&(n=t,t=void 0),n=n||{};var i,o,a,s,u,l,c,f,p,d,h=w.ajaxSetup({},n),g=h.context||h,y=h.context&&(g.nodeType||g.jquery)?w(g):w.event,v=w.Deferred(),m=w.Callbacks("once memory"),x=h.statusCode||{},b={},T={},C="canceled",E={readyState:0,getResponseHeader:function(e){var t;if(c){if(!s){s={};while(t=Ot.exec(a))s[t[1].toLowerCase()]=t[2]}t=s[e.toLowerCase()]}return null==t?null:t},getAllResponseHeaders:function(){return c?a:null},setRequestHeader:function(e,t){return null==c&&(e=T[e.toLowerCase()]=T[e.toLowerCase()]||e,b[e]=t),this},overrideMimeType:function(e){return null==c&&(h.mimeType=e),this},statusCode:function(e){var t;if(e)if(c)E.always(e[E.status]);else for(t in e)x[t]=[x[t],e[t]];return this},abort:function(e){var t=e||C;return i&&i.abort(t),k(0,t),this}};if(v.promise(E),h.url=((t||h.url||Ct.href)+"").replace(Rt,Ct.protocol+"//"),h.type=n.method||n.type||h.method||h.type,h.dataTypes=(h.dataType||"*").toLowerCase().match(M)||[""],null==h.crossDomain){l=r.createElement("a");try{l.href=h.url,l.href=l.href,h.crossDomain=Bt.protocol+"//"+Bt.host!=l.protocol+"//"+l.host}catch(e){h.crossDomain=!0}}if(h.data&&h.processData&&"string"!=typeof h.data&&(h.data=w.param(h.data,h.traditional)),_t(It,h,n,E),c)return E;(f=w.event&&h.global)&&0==w.active++&&w.event.trigger("ajaxStart"),h.type=h.type.toUpperCase(),h.hasContent=!Mt.test(h.type),o=h.url.replace(Lt,""),h.hasContent?h.data&&h.processData&&0===(h.contentType||"").indexOf("application/x-www-form-urlencoded")&&(h.data=h.data.replace(qt,"+")):(d=h.url.slice(o.length),h.data&&(h.processData||"string"==typeof h.data)&&(o+=(kt.test(o)?"&":"?")+h.data,delete h.data),!1===h.cache&&(o=o.replace(Ht,"$1"),d=(kt.test(o)?"&":"?")+"_="+Et+++d),h.url=o+d),h.ifModified&&(w.lastModified[o]&&E.setRequestHeader("If-Modified-Since",w.lastModified[o]),w.etag[o]&&E.setRequestHeader("If-None-Match",w.etag[o])),(h.data&&h.hasContent&&!1!==h.contentType||n.contentType)&&E.setRequestHeader("Content-Type",h.contentType),E.setRequestHeader("Accept",h.dataTypes[0]&&h.accepts[h.dataTypes[0]]?h.accepts[h.dataTypes[0]]+("*"!==h.dataTypes[0]?", "+$t+"; q=0.01":""):h.accepts["*"]);for(p in h.headers)E.setRequestHeader(p,h.headers[p]);if(h.beforeSend&&(!1===h.beforeSend.call(g,E,h)||c))return E.abort();if(C="abort",m.add(h.complete),E.done(h.success),E.fail(h.error),i=_t(Wt,h,n,E)){if(E.readyState=1,f&&y.trigger("ajaxSend",[E,h]),c)return E;h.async&&h.timeout>0&&(u=e.setTimeout(function(){E.abort("timeout")},h.timeout));try{c=!1,i.send(b,k)}catch(e){if(c)throw e;k(-1,e)}}else k(-1,"No Transport");function k(t,n,r,s){var l,p,d,b,T,C=n;c||(c=!0,u&&e.clearTimeout(u),i=void 0,a=s||"",E.readyState=t>0?4:0,l=t>=200&&t<300||304===t,r&&(b=Xt(h,E,r)),b=Ut(h,b,E,l),l?(h.ifModified&&((T=E.getResponseHeader("Last-Modified"))&&(w.lastModified[o]=T),(T=E.getResponseHeader("etag"))&&(w.etag[o]=T)),204===t||"HEAD"===h.type?C="nocontent":304===t?C="notmodified":(C=b.state,p=b.data,l=!(d=b.error))):(d=C,!t&&C||(C="error",t<0&&(t=0))),E.status=t,E.statusText=(n||C)+"",l?v.resolveWith(g,[p,C,E]):v.rejectWith(g,[E,C,d]),E.statusCode(x),x=void 0,f&&y.trigger(l?"ajaxSuccess":"ajaxError",[E,h,l?p:d]),m.fireWith(g,[E,C]),f&&(y.trigger("ajaxComplete",[E,h]),--w.active||w.event.trigger("ajaxStop")))}return E},getJSON:function(e,t,n){return w.get(e,t,n,"json")},getScript:function(e,t){return w.get(e,void 0,t,"script")}}),w.each(["get","post"],function(e,t){w[t]=function(e,n,r,i){return g(n)&&(i=i||r,r=n,n=void 0),w.ajax(w.extend({url:e,type:t,dataType:i,data:n,success:r},w.isPlainObject(e)&&e))}}),w._evalUrl=function(e){return w.ajax({url:e,type:"GET",dataType:"script",cache:!0,async:!1,global:!1,"throws":!0})},w.fn.extend({wrapAll:function(e){var t;return this[0]&&(g(e)&&(e=e.call(this[0])),t=w(e,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){var e=this;while(e.firstElementChild)e=e.firstElementChild;return e}).append(this)),this},wrapInner:function(e){return g(e)?this.each(function(t){w(this).wrapInner(e.call(this,t))}):this.each(function(){var t=w(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=g(e);return this.each(function(n){w(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(e){return this.parent(e).not("body").each(function(){w(this).replaceWith(this.childNodes)}),this}}),w.expr.pseudos.hidden=function(e){return!w.expr.pseudos.visible(e)},w.expr.pseudos.visible=function(e){return!!(e.offsetWidth||e.offsetHeight||e.getClientRects().length)},w.ajaxSettings.xhr=function(){try{return new e.XMLHttpRequest}catch(e){}};var Vt={0:200,1223:204},Gt=w.ajaxSettings.xhr();h.cors=!!Gt&&"withCredentials"in Gt,h.ajax=Gt=!!Gt,w.ajaxTransport(function(t){var n,r;if(h.cors||Gt&&!t.crossDomain)return{send:function(i,o){var a,s=t.xhr();if(s.open(t.type,t.url,t.async,t.username,t.password),t.xhrFields)for(a in t.xhrFields)s[a]=t.xhrFields[a];t.mimeType&&s.overrideMimeType&&s.overrideMimeType(t.mimeType),t.crossDomain||i["X-Requested-With"]||(i["X-Requested-With"]="XMLHttpRequest");for(a in i)s.setRequestHeader(a,i[a]);n=function(e){return function(){n&&(n=r=s.onload=s.onerror=s.onabort=s.ontimeout=s.onreadystatechange=null,"abort"===e?s.abort():"error"===e?"number"!=typeof s.status?o(0,"error"):o(s.status,s.statusText):o(Vt[s.status]||s.status,s.statusText,"text"!==(s.responseType||"text")||"string"!=typeof s.responseText?{binary:s.response}:{text:s.responseText},s.getAllResponseHeaders()))}},s.onload=n(),r=s.onerror=s.ontimeout=n("error"),void 0!==s.onabort?s.onabort=r:s.onreadystatechange=function(){4===s.readyState&&e.setTimeout(function(){n&&r()})},n=n("abort");try{s.send(t.hasContent&&t.data||null)}catch(e){if(n)throw e}},abort:function(){n&&n()}}}),w.ajaxPrefilter(function(e){e.crossDomain&&(e.contents.script=!1)}),w.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/\b(?:java|ecma)script\b/},converters:{"text script":function(e){return w.globalEval(e),e}}}),w.ajaxPrefilter("script",function(e){void 0===e.cache&&(e.cache=!1),e.crossDomain&&(e.type="GET")}),w.ajaxTransport("script",function(e){if(e.crossDomain){var t,n;return{send:function(i,o){t=w(" +{% endif %} +{% block tracking_events %}{% endblock %} + + +{% block extra_js %}{% endblock %} + + diff --git a/site/monica_site/templates/portal_base.html b/site/monica_site/templates/portal_base.html new file mode 100644 index 0000000..d15bd4b --- /dev/null +++ b/site/monica_site/templates/portal_base.html @@ -0,0 +1,74 @@ +{% load static %} + + + + + + {% block title %}Portal · {{ SITE_NAME }}{% endblock %} + + + + {% if tianji_enabled %} + + {% endif %} + {% block extra_head %}{% endblock %} + + +{% block body %} +
+ +
+
+

{% block topbar_title %}Portal{% endblock %}

+
+ Signed in as {{ user.get_username }} + · +
+ {% csrf_token %} + +
+
+
+
+ {% if messages %} +
    + {% for message in messages %} +
  • {{ message }}
  • + {% endfor %} +
+ {% endif %} + {% block portal_content %}{% endblock %} +
+
+
+{% endblock %} +{% if tianji_enabled %} + +{% endif %} +{% block tracking_events %}{% endblock %} +{% block extra_js %}{% endblock %} + + diff --git a/site/monica_site/urls.py b/site/monica_site/urls.py new file mode 100644 index 0000000..cdec3f0 --- /dev/null +++ b/site/monica_site/urls.py @@ -0,0 +1,23 @@ +"""URL configuration for monica_site.""" + +from django.contrib import admin +from django.urls import include, path + +from contacts.views import address_suggest +from core.views import healthz + +urlpatterns = [ + path("healthz/", healthz, name="healthz"), + path("api/address-suggest/", address_suggest, name="address_suggest"), + path("admin/", admin.site.urls), + path("accounts/", include("accounts.urls")), + path("portal/", include("dashboard.urls")), + path("portal/leads/", include("leads.urls")), + path("portal/contacts/", include("contacts.urls")), + path("portal/messaging/", include("messaging.urls")), + path("portal/social/", include("social.urls")), + path("portal/analytics/", include("analytics.urls")), + path("", include("public.urls")), +] + +handler404 = "public.views.page_not_found" diff --git a/site/monica_site/wsgi.py b/site/monica_site/wsgi.py new file mode 100644 index 0000000..f4da7f3 --- /dev/null +++ b/site/monica_site/wsgi.py @@ -0,0 +1,9 @@ +"""WSGI config for monica_site.""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "monica_site.settings") + +application = get_wsgi_application() diff --git a/site/public/__init__.py b/site/public/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/site/public/admin.py b/site/public/admin.py new file mode 100644 index 0000000..8add86c --- /dev/null +++ b/site/public/admin.py @@ -0,0 +1 @@ +from django.contrib import admin # noqa: F401 diff --git a/site/public/apps.py b/site/public/apps.py new file mode 100644 index 0000000..ce864bb --- /dev/null +++ b/site/public/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class PublicConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "public" diff --git a/site/public/context_processors.py b/site/public/context_processors.py new file mode 100644 index 0000000..e20f0c3 --- /dev/null +++ b/site/public/context_processors.py @@ -0,0 +1,66 @@ +import re + +from django.conf import settings + + +def _phone_tel(phone: str) -> str: + digits = re.sub(r"[^\d+]", "", phone or "") + return digits + + +def site_branding(_request): + phone = settings.CONTACT_PHONE or "" + return { + "SITE_NAME": settings.SITE_NAME, + "SITE_TAGLINE": settings.SITE_TAGLINE, + "CONTACT_PHONE": phone, + "CONTACT_PHONE_TEL": _phone_tel(phone), + "CONTACT_EMAIL": settings.CONTACT_EMAIL or "", + "CONTACT_SERVICE_AREA": getattr( + settings, "CONTACT_SERVICE_AREA", "Serving Greater Metro Area" + ), + "CREDIT_NAME": getattr(settings, "CREDIT_NAME", "AI ML Operations, LLC"), + "CREDIT_URL": getattr( + settings, "CREDIT_URL", "https://aimloperations.com" + ), + "SITE_UNDER_CONSTRUCTION": settings.SITE_UNDER_CONSTRUCTION, + } + + +def tianji_tracking(request): + match = getattr(request, "resolver_match", None) + url_name = getattr(match, "url_name", "") or "" + namespace = getattr(match, "namespace", "") or "" + full = f"{namespace}:{url_name}" if namespace else url_name + + nav_section = "" + if full == "dashboard:home": + nav_section = "dashboard" + elif namespace == "leads": + nav_section = "leads" + elif full == "analytics:report": + nav_section = "analytics" + elif namespace == "contacts": + nav_section = "contacts" + elif full == "messaging:postcard_designer": + nav_section = "postcard" + elif namespace == "messaging": + nav_section = "campaigns" + elif full == "social:account_list": + nav_section = "social_accounts" + elif namespace == "social": + nav_section = "social" + + return { + "tianji_enabled": getattr(settings, "TIANJI_ENABLED", False) + and bool(getattr(settings, "TIANJI_WEBSITE_ID", "")) + and bool(getattr(settings, "TIANJI_TRACKER_URL", "")), + "tianji_tracker_url": getattr( + settings, + "TIANJI_TRACKER_URL", + "https://tianji.aimloperations.com/tracker.js", + ), + "tianji_website_id": getattr(settings, "TIANJI_WEBSITE_ID", ""), + "page_name": url_name, + "nav_section": nav_section, + } diff --git a/site/public/forms.py b/site/public/forms.py new file mode 100644 index 0000000..b293a27 --- /dev/null +++ b/site/public/forms.py @@ -0,0 +1,115 @@ +from django import forms +from django.conf import settings + + +class ContactForm(forms.Form): + INTEREST_CHOICES = [ + ("buying", "Buying a home"), + ("selling", "Selling a home"), + ("both", "Both / not sure yet"), + ("other", "Something else"), + ] + + first_name = forms.CharField( + max_length=100, + widget=forms.TextInput(attrs={"class": "form-input", "id": "contact-first-name"}), + ) + last_name = forms.CharField( + max_length=100, + required=False, + widget=forms.TextInput(attrs={"class": "form-input", "id": "contact-last-name"}), + ) + email = forms.EmailField( + widget=forms.EmailInput(attrs={"class": "form-input", "id": "contact-email"}), + ) + phone = forms.CharField( + max_length=32, + required=False, + widget=forms.TextInput(attrs={"class": "form-input", "id": "contact-phone"}), + ) + address_line1 = forms.CharField( + max_length=200, + required=False, + label="Street address", + widget=forms.TextInput( + attrs={ + "class": "form-input", + "id": "contact-address-line1", + "autocomplete": "off", + "data-ac": "line1", + } + ), + ) + address_line2 = forms.CharField( + max_length=200, + required=False, + label="Apt / suite", + widget=forms.TextInput( + attrs={ + "class": "form-input", + "id": "contact-address-line2", + "autocomplete": "address-line2", + "data-ac": "line2", + } + ), + ) + address_city = forms.CharField( + max_length=100, + required=False, + label="City", + widget=forms.TextInput( + attrs={ + "class": "form-input", + "id": "contact-address-city", + "autocomplete": "address-level2", + "data-ac": "city", + } + ), + ) + address_state = forms.CharField( + max_length=32, + required=False, + label="State", + widget=forms.TextInput( + attrs={ + "class": "form-input", + "id": "contact-address-state", + "autocomplete": "address-level1", + "data-ac": "state", + } + ), + ) + address_zip = forms.CharField( + max_length=20, + required=False, + label="ZIP", + widget=forms.TextInput( + attrs={ + "class": "form-input", + "id": "contact-address-zip", + "autocomplete": "postal-code", + "data-ac": "zip", + } + ), + ) + interest = forms.ChoiceField( + choices=INTEREST_CHOICES, + widget=forms.Select(attrs={"class": "form-input", "id": "contact-interest"}), + ) + message = forms.CharField( + widget=forms.Textarea(attrs={"class": "form-input", "id": "contact-message"}), + ) + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + if settings.RECAPTCHA_PUBLIC_KEY and settings.RECAPTCHA_PRIVATE_KEY: + from django_recaptcha.fields import ReCaptchaField + from django_recaptcha.widgets import ReCaptchaV3 + + self.fields["captcha"] = ReCaptchaField(widget=ReCaptchaV3) + + +class NotifyForm(forms.Form): + email = forms.EmailField( + widget=forms.EmailInput(attrs={"class": "form-input"}), + ) diff --git a/site/public/middleware.py b/site/public/middleware.py new file mode 100644 index 0000000..e3d1a7d --- /dev/null +++ b/site/public/middleware.py @@ -0,0 +1,46 @@ +from django.conf import settings +from django.shortcuts import redirect + + +class UnderConstructionMiddleware: + """ + When SITE_UNDER_CONSTRUCTION is true, redirect public traffic to the holding page. + + Exempt: health, static/media, admin, auth login/logout, SMS webhook, and the + under-construction page itself (so notify-me POSTs work). + """ + + EXEMPT_PREFIXES = ( + "/healthz", + "/static/", + "/media/", + "/admin/", + "/accounts/login", + "/accounts/logout", + "/under-construction", + "/portal/messaging/webhooks/", + "/unsubscribe/", + "/api/address-suggest", + ) + + def __init__(self, get_response): + self.get_response = get_response + + def __call__(self, request): + if settings.SITE_UNDER_CONSTRUCTION and not self._is_exempt(request): + return redirect("public:under_construction") + return self.get_response(request) + + def _is_exempt(self, request) -> bool: + path = request.path + if any(path.startswith(prefix) for prefix in self.EXEMPT_PREFIXES): + return True + user = getattr(request, "user", None) + if ( + user is not None + and getattr(user, "is_authenticated", False) + and getattr(user, "is_staff", False) + and path.startswith("/portal/") + ): + return True + return False diff --git a/site/public/migrations/__init__.py b/site/public/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/site/public/models.py b/site/public/models.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/site/public/models.py @@ -0,0 +1 @@ + diff --git a/site/public/templates/public/404.html b/site/public/templates/public/404.html new file mode 100644 index 0000000..732cf79 --- /dev/null +++ b/site/public/templates/public/404.html @@ -0,0 +1,46 @@ +{% extends "base.html" %} +{% load static %} +{% block title %}404 · {{ SITE_NAME }}{% endblock %} +{% block body %} +
+
+
+
+
+
+
+
+

404

+
+
+
+
Page not found
+

That page may have moved or the link is outdated. Let’s get you back on track.

+ Back to home + Contact Monica +
+
+
+
+
+ +
+
+
+{% endblock %} diff --git a/site/public/templates/public/about.html b/site/public/templates/public/about.html new file mode 100644 index 0000000..0bb6c3d --- /dev/null +++ b/site/public/templates/public/about.html @@ -0,0 +1,34 @@ +{% extends "base.html" %} +{% load static %} +{% block title %}About · {{ SITE_NAME }}{% endblock %} +{% block content %} + +
+
+
+
+

Local realtor,
personal service

+

I help families buy and sell homes with clear communication and marketing that actually reaches people — not just another postcard blast.

+

This site replaces a costly third-party campaign platform. Visitors reach me through a protected contact form; every inquiry lands in my portal with source tracking so I know which campaigns work.

+ Work with me +
+
+ {{ SITE_NAME }} +
+
+
+
+{% endblock %} diff --git a/site/public/templates/public/contact.html b/site/public/templates/public/contact.html new file mode 100644 index 0000000..ae489c4 --- /dev/null +++ b/site/public/templates/public/contact.html @@ -0,0 +1,163 @@ +{% extends "base.html" %} +{% load static %} +{% block title %}Contact · {{ SITE_NAME }}{% endblock %} +{% block content %} + +
+
+
+ {% if CONTACT_PHONE %} + + {% endif %} + {% if CONTACT_EMAIL %} + + {% endif %} +
+
+
+ {{ CONTACT_SERVICE_AREA }} +
+
+
+
+
+
+
+
+
+

Send a message

+
+ {% csrf_token %} +
+
+
+ + {{ form.first_name }} + {{ form.first_name.errors }} +
+
+
+
+ + {{ form.last_name }} + {{ form.last_name.errors }} +
+
+
+
+ + {{ form.email }} + {{ form.email.errors }} +
+
+
+
+ + {{ form.phone }} + {{ form.phone.errors }} +
+
+
+

Mailing address (optional — for postcards)

+
+
+
+ + {{ form.address_line1 }} + {{ form.address_line1.errors }} +
+
+
+
+ + {{ form.address_line2 }} + {{ form.address_line2.errors }} +
+
+
+
+ + {{ form.address_city }} + {{ form.address_city.errors }} +
+
+
+
+ + {{ form.address_state }} + {{ form.address_state.errors }} +
+
+
+
+ + {{ form.address_zip }} + {{ form.address_zip.errors }} +
+
+
+
+
+
+ + {{ form.interest }} + {{ form.interest.errors }} +
+
+
+
+ + {{ form.message }} + {{ form.message.errors }} +
+
+ {% if form.captcha %} +
{{ form.captcha }}{{ form.captcha.errors }}
+ {% endif %} +
+

By submitting, you agree I may contact you about your inquiry by email and SMS (if you provide a phone number). You can change preferences or unsubscribe anytime from links in messages, or reply STOP to SMS.{% if form.captcha %} Protected by reCAPTCHA.{% endif %}

+ +
+
+
+
+
+{% endblock %} +{% block extra_head %} + +{% endblock %} +{% block tracking_events %} +{% if "sent" in request.GET %} + +{% endif %} +{% endblock %} +{% block extra_js %} + +{% endblock %} diff --git a/site/public/templates/public/home.html b/site/public/templates/public/home.html new file mode 100644 index 0000000..5f77dc1 --- /dev/null +++ b/site/public/templates/public/home.html @@ -0,0 +1,81 @@ +{% extends "base.html" %} +{% load static %} +{% block title %}Home · {{ SITE_NAME }}{% endblock %} +{% block content %} +
+
+
+
+
+
+
{{ SITE_NAME }} · MKDRealtor.com
+
+

Your Home,
Guided Well

+

EXIT Realty Redefined — buying or selling in the metro area with clear advice, strong marketing, and a team that answers the phone.

+ +
+
+
+
+
+
+
+
Local Market Insight
+
+

Sell With
Confidence

+

Pricing strategy, staging guidance, and multi-channel outreach so the right buyers see your listing.

+ +
+
+
+
+
+
+
+
+
+
+

How I can help

+
+
+
+
+
+

Buyers

+

Neighborhood tours, offer strategy, and negotiation that protects your timeline and budget.

+
+
+
+
+
+
+
+

Sellers

+

Listing prep, professional marketing, and outreach across email, SMS, postcard, and social.

+
+
+
+
+
+
+
+

Market guidance

+

Honest comps and timing advice — whether you move this season or plan ahead.

+
+
+
+
+
+
+
+
+

Ready when you are

+

Tell me what you are looking for. I will follow up personally — your message becomes a lead in my private portal.

+ Contact Monica +
+
+{% endblock %} diff --git a/site/public/templates/public/under_construction.html b/site/public/templates/public/under_construction.html new file mode 100644 index 0000000..7494142 --- /dev/null +++ b/site/public/templates/public/under_construction.html @@ -0,0 +1,56 @@ +{% extends "base.html" %} +{% load static %} +{% block title %}Under construction · {{ SITE_NAME }}{% endblock %} +{% block body %} +
+
+
+
+
+
+
+
+
We’re getting ready to launch
+

{{ SITE_NAME }} · MKDRealtor.com’s new site is almost here. Leave your email and I’ll let you know when it’s live — or reach out now if you need help buying or selling.

+
+
+ {% csrf_token %} +
+ + + {{ form.email.errors }} +
+
+ +
+
+
+

+ Contact Monica now +

+
+
+
+
+ +
+
+
+{% endblock %} diff --git a/site/public/templates/public/unsubscribe.html b/site/public/templates/public/unsubscribe.html new file mode 100644 index 0000000..1d0a952 --- /dev/null +++ b/site/public/templates/public/unsubscribe.html @@ -0,0 +1,55 @@ +{% extends "base.html" %} +{% block title %}Communication preferences · {{ SITE_NAME }}{% endblock %} +{% block content %} +
+
+

Communication preferences

+ {% if not valid %} +

This preferences link is invalid or has expired.

+

If you still get messages, reply STOP on SMS or contact Monica directly.

+

+ Contact Monica + Back to home +

+ {% else %} +

You are subscribed as {{ identity }}.

+
+ {% csrf_token %} +
+ +
+
+ +
+
+ +
+

+ SMS: reply STOP anytime. Changes take effect immediately. + Postcard mailings also need a postal address on file. +

+
+ + +
+
+

+ Back to home +

+ {% endif %} +
+
+{% endblock %} diff --git a/site/public/urls.py b/site/public/urls.py new file mode 100644 index 0000000..34feedb --- /dev/null +++ b/site/public/urls.py @@ -0,0 +1,18 @@ +from django.urls import path + +from public import views + +app_name = "public" + +urlpatterns = [ + path("", views.home, name="home"), + path("about/", views.about, name="about"), + path("contact/", views.contact, name="contact"), + path("under-construction/", views.under_construction, name="under_construction"), + path("unsubscribe//", views.unsubscribe, name="unsubscribe"), + path( + "unsubscribe//one-click/", + views.unsubscribe_one_click, + name="unsubscribe_one_click", + ), +] diff --git a/site/public/views.py b/site/public/views.py new file mode 100644 index 0000000..2c20eb1 --- /dev/null +++ b/site/public/views.py @@ -0,0 +1,178 @@ +from django.contrib import messages +from django.shortcuts import redirect, render +from django.urls import reverse +from django.views.decorators.csrf import csrf_exempt +from django.views.decorators.http import require_http_methods + +from analytics.services import attribute_lead_from_request +from contacts.models import Channel, ConsentRecord, Contact +from leads.models import Lead +from messaging.services import ( + channel_preferences, + parse_unsubscribe_token, + process_unsubscribe_token, + set_channel_preferences, + unsubscribe_all, +) +from public.forms import ContactForm, NotifyForm + + +def home(request): + return render(request, "public/home.html") + + +def about(request): + return render(request, "public/about.html") + + +@require_http_methods(["GET", "POST"]) +def contact(request): + if request.method == "POST": + form = ContactForm(request.POST) + if form.is_valid(): + data = form.cleaned_data + defaults = { + "first_name": data["first_name"], + "last_name": data.get("last_name") or "", + "phone": data.get("phone") or "", + "source": Contact.Source.CONTACT_FORM, + } + postal = Contact.make_postal_address( + line1=data.get("address_line1") or "", + line2=data.get("address_line2") or "", + city=data.get("address_city") or "", + state=data.get("address_state") or "", + zip_code=data.get("address_zip") or "", + ) + if Contact.postal_address_has_content(postal): + defaults["postal_address"] = postal + contact_obj, _ = Contact.objects.update_or_create( + email=data["email"].lower(), + defaults=defaults, + ) + ConsentRecord.objects.update_or_create( + contact=contact_obj, + channel=Channel.EMAIL, + defaults={"opted_in": True, "reason": "contact_form"}, + ) + if (data.get("phone") or "").strip(): + ConsentRecord.objects.update_or_create( + contact=contact_obj, + channel=Channel.SMS, + defaults={"opted_in": True, "reason": "contact_form"}, + ) + interest = data.get("interest") or "" + interest_label = dict(ContactForm.INTEREST_CHOICES).get(interest, interest) + body = data.get("message") or "" + if interest_label: + body = f"Interest: {interest_label}\n\n{body}".strip() + lead = Lead.objects.create( + contact=contact_obj, + message=body, + status=Lead.Status.NEW, + ) + attribute_lead_from_request(request, lead) + messages.success(request, "Thanks — Monica will be in touch soon.") + return redirect(f"{reverse('public:contact')}?sent=1") + else: + form = ContactForm() + return render(request, "public/contact.html", {"form": form}) + + +@require_http_methods(["GET", "POST"]) +def under_construction(request): + """Direct route (also used by middleware). Accepts notify-me emails.""" + if request.method == "POST": + form = NotifyForm(request.POST) + if form.is_valid(): + email = form.cleaned_data["email"].lower() + Contact.objects.get_or_create( + email=email, + defaults={ + "first_name": "", + "source": Contact.Source.NOTIFY_ME, + }, + ) + messages.success(request, "You're on the list — we'll email when we launch.") + return redirect("public:under_construction") + else: + form = NotifyForm() + return render(request, "public/under_construction.html", {"form": form}) + + +@csrf_exempt +@require_http_methods(["GET", "POST"]) +def unsubscribe_one_click(request, token: str): + """ + One-click opt-out for the channel encoded in the token. + + CSRF-exempt so mail clients can POST List-Unsubscribe=One-Click (RFC 8058). + """ + ok = process_unsubscribe_token(token) + if not ok: + return render(request, "public/unsubscribe.html", {"valid": False}) + return redirect("public:unsubscribe", token=token) + + +@require_http_methods(["GET", "POST"]) +def unsubscribe(request, token: str): + """ + Signed-token preference center for email / SMS / postcard. + + GET ?one_click=1 opts out the token channel then redirects here. + POST saves checkboxes or unsubscribes from all channels. + """ + contact, token_channel = parse_unsubscribe_token(token) + if not contact: + return render( + request, + "public/unsubscribe.html", + {"valid": False}, + ) + + if request.method == "GET" and request.GET.get("one_click") in { + "1", + "true", + "yes", + }: + process_unsubscribe_token(token) + return redirect("public:unsubscribe", token=token) + + if request.method == "POST": + action = (request.POST.get("action") or "save").strip() + if action == "unsubscribe_all": + unsubscribe_all(contact, reason="preferences_unsubscribe_all") + messages.success( + request, "You are unsubscribed from all marketing channels." + ) + else: + prefs = { + Channel.EMAIL: "consent_email" in request.POST, + Channel.SMS: "consent_sms" in request.POST, + Channel.POSTCARD: "consent_postcard" in request.POST, + } + set_channel_preferences( + contact, prefs, reason="preferences_save" + ) + messages.success(request, "Your communication preferences were saved.") + return redirect("public:unsubscribe", token=token) + + contact = Contact.objects.prefetch_related("consents").get(pk=contact.pk) + prefs = channel_preferences(contact) + identity = contact.email or contact.phone or contact.full_name or "your profile" + return render( + request, + "public/unsubscribe.html", + { + "valid": True, + "contact": contact, + "identity": identity, + "prefs": prefs, + "token_channel": token_channel, + "token": token, + }, + ) + + +def page_not_found(request, exception): + return render(request, "public/404.html", status=404) diff --git a/site/social/__init__.py b/site/social/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/site/social/admin.py b/site/social/admin.py new file mode 100644 index 0000000..52ad8e1 --- /dev/null +++ b/site/social/admin.py @@ -0,0 +1,21 @@ +from django.contrib import admin + +from social.models import SocialAccount, SocialPost, SocialPostTarget + + +class SocialPostTargetInline(admin.TabularInline): + model = SocialPostTarget + extra = 0 + + +@admin.register(SocialAccount) +class SocialAccountAdmin(admin.ModelAdmin): + list_display = ("label", "platform", "is_active", "external_id") + list_filter = ("platform", "is_active") + + +@admin.register(SocialPost) +class SocialPostAdmin(admin.ModelAdmin): + list_display = ("pk", "status", "scheduled_for", "created_at") + list_filter = ("status",) + inlines = [SocialPostTargetInline] diff --git a/site/social/apps.py b/site/social/apps.py new file mode 100644 index 0000000..567a89b --- /dev/null +++ b/site/social/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class SocialConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "social" diff --git a/site/social/connectors/__init__.py b/site/social/connectors/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/site/social/connectors/base.py b/site/social/connectors/base.py new file mode 100644 index 0000000..4eff144 --- /dev/null +++ b/site/social/connectors/base.py @@ -0,0 +1,10 @@ +from typing import Protocol + + +class SocialConnector(Protocol): + platform: str + + def publish(self, post, target) -> str: + """Publish and return remote post id.""" + + def refresh_token(self, account) -> None: ... diff --git a/site/social/connectors/linkedin.py b/site/social/connectors/linkedin.py new file mode 100644 index 0000000..aec4045 --- /dev/null +++ b/site/social/connectors/linkedin.py @@ -0,0 +1,48 @@ +"""LinkedIn API connector.""" + +import json +import logging + +import requests + +from social.crypto import decrypt_tokens + +logger = logging.getLogger(__name__) + + +class LinkedInConnector: + platform = "linkedin" + + def publish(self, post, target) -> str: + tokens = json.loads(decrypt_tokens(target.account.encrypted_tokens) or "{}") + access_token = tokens.get("access_token") + author_urn = target.account.external_id or tokens.get("author_urn") + if not access_token or not author_urn: + raise RuntimeError("LinkedIn account missing access_token/author_urn") + + payload = { + "author": author_urn, + "lifecycleState": "PUBLISHED", + "specificContent": { + "com.linkedin.ugc.ShareContent": { + "shareCommentary": {"text": post.body}, + "shareMediaCategory": "NONE", + } + }, + "visibility": {"com.linkedin.ugc.MemberNetworkVisibility": "PUBLIC"}, + } + response = requests.post( + "https://api.linkedin.com/v2/ugcPosts", + json=payload, + headers={ + "Authorization": f"Bearer {access_token}", + "X-Restli-Protocol-Version": "2.0.0", + "Content-Type": "application/json", + }, + timeout=30, + ) + response.raise_for_status() + return str(response.headers.get("x-restli-id") or response.json().get("id") or "") + + def refresh_token(self, account) -> None: + logger.info("LinkedIn token refresh stub for %s", account.pk) diff --git a/site/social/connectors/meta.py b/site/social/connectors/meta.py new file mode 100644 index 0000000..11ef8f0 --- /dev/null +++ b/site/social/connectors/meta.py @@ -0,0 +1,43 @@ +"""Meta Graph API connector (Facebook Page + Instagram Business).""" + +import logging + +import requests +from django.conf import settings + +from social.crypto import decrypt_tokens +import json + +logger = logging.getLogger(__name__) + + +class MetaConnector: + platform = "meta" + GRAPH = "https://graph.facebook.com/v21.0" + + def publish(self, post, target) -> str: + tokens = json.loads(decrypt_tokens(target.account.encrypted_tokens) or "{}") + access_token = tokens.get("access_token") + page_id = target.account.external_id or tokens.get("page_id") + if not access_token or not page_id: + raise RuntimeError("Meta account missing access_token/page_id") + + if target.platform == "instagram": + # IG content publishing is a multi-step Graph flow; stub container create. + raise NotImplementedError( + "Instagram publish requires IG business account wiring — complete OAuth first" + ) + + response = requests.post( + f"{self.GRAPH}/{page_id}/feed", + data={"message": post.body, "access_token": access_token}, + timeout=30, + ) + response.raise_for_status() + return str(response.json().get("id") or "") + + def refresh_token(self, account) -> None: + # Long-lived token exchange when META_APP_ID/SECRET are set. + if not settings.META_APP_ID or not settings.META_APP_SECRET: + return + logger.info("Meta token refresh not yet implemented for account %s", account.pk) diff --git a/site/social/crypto.py b/site/social/crypto.py new file mode 100644 index 0000000..5cd6423 --- /dev/null +++ b/site/social/crypto.py @@ -0,0 +1,27 @@ +"""Token encryption helpers for SocialAccount.""" + +from cryptography.fernet import Fernet, InvalidToken +from django.conf import settings + + +def _fernet() -> Fernet: + key = settings.SOCIAL_TOKEN_ENCRYPTION_KEY + if not key: + # Dev-only fallback — generate is not stable across restarts; set the env var. + key = Fernet.generate_key().decode() + if isinstance(key, str): + key = key.encode() + return Fernet(key) + + +def encrypt_tokens(plaintext: str) -> str: + return _fernet().encrypt(plaintext.encode()).decode() + + +def decrypt_tokens(ciphertext: str) -> str: + if not ciphertext: + return "" + try: + return _fernet().decrypt(ciphertext.encode()).decode() + except InvalidToken as exc: + raise ValueError("Unable to decrypt social tokens") from exc diff --git a/site/social/management/__init__.py b/site/social/management/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/site/social/management/commands/__init__.py b/site/social/management/commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/site/social/migrations/0001_initial.py b/site/social/migrations/0001_initial.py new file mode 100644 index 0000000..05defc7 --- /dev/null +++ b/site/social/migrations/0001_initial.py @@ -0,0 +1,70 @@ +# Generated by Django 6.1 on 2026-08-06 18:01 + +import django.db.models.deletion +import uuid +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='SocialAccount', + fields=[ + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('platform', models.CharField(choices=[('facebook', 'Facebook'), ('instagram', 'Instagram'), ('linkedin', 'LinkedIn')], max_length=16)), + ('label', models.CharField(max_length=120)), + ('external_id', models.CharField(blank=True, max_length=255)), + ('encrypted_tokens', models.TextField(blank=True)), + ('is_active', models.BooleanField(default=True)), + ('owner', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='social_accounts', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'ordering': ['platform', 'label'], + }, + ), + migrations.CreateModel( + name='SocialPost', + fields=[ + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('body', models.TextField()), + ('media', models.JSONField(blank=True, default=list)), + ('scheduled_for', models.DateTimeField(blank=True, null=True)), + ('status', models.CharField(choices=[('draft', 'Draft'), ('scheduled', 'Scheduled'), ('queued', 'Queued'), ('publishing', 'Publishing'), ('published', 'Published'), ('failed', 'Failed'), ('cancelled', 'Cancelled')], default='draft', max_length=16)), + ('ollama_prompt', models.TextField(blank=True)), + ('error', models.TextField(blank=True)), + ('created_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)), + ], + options={ + 'ordering': ['-created_at'], + }, + ), + migrations.CreateModel( + name='SocialPostTarget', + fields=[ + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('platform', models.CharField(choices=[('facebook', 'Facebook'), ('instagram', 'Instagram'), ('linkedin', 'LinkedIn')], max_length=16)), + ('status', models.CharField(choices=[('pending', 'Pending'), ('published', 'Published'), ('failed', 'Failed')], default='pending', max_length=16)), + ('remote_id', models.CharField(blank=True, max_length=255)), + ('error', models.TextField(blank=True)), + ('account', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='targets', to='social.socialaccount')), + ('post', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='targets', to='social.socialpost')), + ], + options={ + 'unique_together': {('post', 'account')}, + }, + ), + ] diff --git a/site/social/migrations/__init__.py b/site/social/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/site/social/models.py b/site/social/models.py new file mode 100644 index 0000000..ddd1e4c --- /dev/null +++ b/site/social/models.py @@ -0,0 +1,87 @@ +from django.conf import settings +from django.db import models + +from core.models import TimeStampedModel, UUIDPrimaryKeyModel + + +class Platform(models.TextChoices): + FACEBOOK = "facebook", "Facebook" + INSTAGRAM = "instagram", "Instagram" + LINKEDIN = "linkedin", "LinkedIn" + + +class SocialAccount(UUIDPrimaryKeyModel, TimeStampedModel): + platform = models.CharField(max_length=16, choices=Platform.choices) + label = models.CharField(max_length=120) + external_id = models.CharField(max_length=255, blank=True) + # Fernet-encrypted JSON blob of OAuth tokens + encrypted_tokens = models.TextField(blank=True) + is_active = models.BooleanField(default=True) + owner = models.ForeignKey( + settings.AUTH_USER_MODEL, + null=True, + blank=True, + on_delete=models.SET_NULL, + related_name="social_accounts", + ) + + class Meta: + ordering = ["platform", "label"] + + def __str__(self) -> str: + return f"{self.platform}: {self.label}" + + +class SocialPost(UUIDPrimaryKeyModel, TimeStampedModel): + class Status(models.TextChoices): + DRAFT = "draft", "Draft" + SCHEDULED = "scheduled", "Scheduled" + QUEUED = "queued", "Queued" + PUBLISHING = "publishing", "Publishing" + PUBLISHED = "published", "Published" + FAILED = "failed", "Failed" + CANCELLED = "cancelled", "Cancelled" + + body = models.TextField() + media = models.JSONField(default=list, blank=True) + scheduled_for = models.DateTimeField(null=True, blank=True) + status = models.CharField( + max_length=16, choices=Status.choices, default=Status.DRAFT + ) + created_by = models.ForeignKey( + settings.AUTH_USER_MODEL, + null=True, + blank=True, + on_delete=models.SET_NULL, + ) + ollama_prompt = models.TextField(blank=True) + error = models.TextField(blank=True) + + class Meta: + ordering = ["-created_at"] + + def __str__(self) -> str: + return f"Post {self.pk} ({self.status})" + + +class SocialPostTarget(UUIDPrimaryKeyModel, TimeStampedModel): + class Status(models.TextChoices): + PENDING = "pending", "Pending" + PUBLISHED = "published", "Published" + FAILED = "failed", "Failed" + + post = models.ForeignKey( + SocialPost, on_delete=models.CASCADE, related_name="targets" + ) + account = models.ForeignKey( + SocialAccount, on_delete=models.CASCADE, related_name="targets" + ) + platform = models.CharField(max_length=16, choices=Platform.choices) + status = models.CharField( + max_length=16, choices=Status.choices, default=Status.PENDING + ) + remote_id = models.CharField(max_length=255, blank=True) + error = models.TextField(blank=True) + + class Meta: + unique_together = ("post", "account") diff --git a/site/social/ollama.py b/site/social/ollama.py new file mode 100644 index 0000000..3183d9b --- /dev/null +++ b/site/social/ollama.py @@ -0,0 +1,63 @@ +"""Ollama client for drafting social posts.""" + +from __future__ import annotations + +import logging + +import requests +from django.conf import settings + +logger = logging.getLogger(__name__) + + +class OllamaError(RuntimeError): + pass + + +def generate_social_post( + prompt: str, + *, + platform: str = "", + tone: str = "professional, warm, local realtor", +) -> str: + """ + Call the LAN Ollama endpoint (default http://10.0.0.128:11434) to draft copy. + + Uses the standard Ollama /api/generate HTTP API. + """ + base = (settings.OLLAMA_BASE_URL or "").rstrip("/") + if not base: + raise OllamaError("OLLAMA_BASE_URL is not configured") + + system = ( + "You write short social media posts for a residential realtor. " + f"Tone: {tone}. " + "Return only the post text, no preamble." + ) + if platform: + system += f" Optimize for {platform}." + + full_prompt = f"{system}\n\nUser request:\n{prompt}" + + url = f"{base}/api/generate" + payload = { + "model": settings.OLLAMA_MODEL, + "prompt": full_prompt, + "stream": False, + } + try: + response = requests.post( + url, + json=payload, + timeout=settings.OLLAMA_TIMEOUT_SECONDS, + ) + response.raise_for_status() + except requests.RequestException as exc: + logger.exception("Ollama request failed") + raise OllamaError(f"Ollama unreachable at {url}: {exc}") from exc + + data = response.json() + text = (data.get("response") or "").strip() + if not text: + raise OllamaError("Ollama returned empty response") + return text diff --git a/site/social/tasks.py b/site/social/tasks.py new file mode 100644 index 0000000..1a1abde --- /dev/null +++ b/site/social/tasks.py @@ -0,0 +1,59 @@ +from django.tasks import task + +from social.connectors.linkedin import LinkedInConnector +from social.connectors.meta import MetaConnector +from social.models import Platform, SocialPost, SocialPostTarget + + +def _connector_for(platform: str): + if platform in {Platform.FACEBOOK, Platform.INSTAGRAM}: + return MetaConnector() + if platform == Platform.LINKEDIN: + return LinkedInConnector() + raise ValueError(f"Unknown platform: {platform}") + + +@task +def publish_social_post(post_id: str) -> None: + try: + post = SocialPost.objects.prefetch_related("targets__account").get(pk=post_id) + except SocialPost.DoesNotExist: + return + + post.status = SocialPost.Status.PUBLISHING + post.save(update_fields=["status", "updated_at"]) + + any_ok = False + for target in post.targets.all(): + try: + remote_id = _connector_for(target.platform).publish(post, target) + target.status = SocialPostTarget.Status.PUBLISHED + target.remote_id = remote_id + target.error = "" + target.save(update_fields=["status", "remote_id", "error", "updated_at"]) + any_ok = True + except Exception as exc: # noqa: BLE001 + target.status = SocialPostTarget.Status.FAILED + target.error = str(exc)[:2000] + target.save(update_fields=["status", "error", "updated_at"]) + + post.status = ( + SocialPost.Status.PUBLISHED if any_ok else SocialPost.Status.FAILED + ) + if not any_ok: + post.error = "All targets failed" + post.save(update_fields=["status", "error", "updated_at"]) + + +@task +def publish_social_target(target_id: str) -> None: + try: + target = SocialPostTarget.objects.select_related("post", "account").get( + pk=target_id + ) + except SocialPostTarget.DoesNotExist: + return + remote_id = _connector_for(target.platform).publish(target.post, target) + target.status = SocialPostTarget.Status.PUBLISHED + target.remote_id = remote_id + target.save(update_fields=["status", "remote_id", "updated_at"]) diff --git a/site/social/templates/social/account_list.html b/site/social/templates/social/account_list.html new file mode 100644 index 0000000..14ea0c0 --- /dev/null +++ b/site/social/templates/social/account_list.html @@ -0,0 +1,94 @@ +{% extends "portal_base.html" %} +{% block title %}Social accounts · Portal{% endblock %} +{% block topbar_title %}Social accounts{% endblock %} +{% block portal_content %} +
+

Add account

+
+
+ + + +
+
+
+ +
+

Connected accounts

+
+ + + + + + + + + + + {% for account in accounts %} + + + + + + + {% empty %} + + {% endfor %} + +
AccountPlatformStatus
+ {{ account.label }}
+ {{ account.external_id }} +
+ + {{ account.get_platform_display }} + + + {% if account.is_active %} + Active + {% else %} + Inactive + {% endif %} + Connect / disconnect in functionality pass
No accounts connected yet.
+
+
+ +
+
+

When to re-auth

+
+
    +
  • LinkedIn access tokens expire on a short cycle
  • +
  • Meta password changes or app review updates
  • +
  • Publishing fails with an auth error
  • +
+
+
+
+

Health checks

+
+

Token test buttons wire up with the connectors next.

+ Open composer → +
+
+
+{% endblock %} diff --git a/site/social/templates/social/composer.html b/site/social/templates/social/composer.html new file mode 100644 index 0000000..2bd8b6c --- /dev/null +++ b/site/social/templates/social/composer.html @@ -0,0 +1,285 @@ +{% extends "portal_base.html" %} +{% load static %} +{% block title %}Compose · Social{% endblock %} +{% block topbar_title %}Compose & preview{% endblock %} +{% block portal_content %} +
+ LinkedIn needs re-auth. Token expired — + Re-authorize in Accounts before scheduling to LinkedIn. +
+ +
+
+
+ +
+
+ +
0 characters · IG soft limit ~2,200
+
+
+ +
+
+
+ + + +
+
+
+
+ +
+
+
+
+ +
+ +
+
+
+ +
+
+
+

AI post assistant

+ Beta mockup +
+
+
+
Assistant
+
+ I can draft or refine captions for Facebook, Instagram, and LinkedIn. + Tell me the listing, tone (warm / bold / luxury), and any must-include details — + or tap a quick prompt below. +
+
+
+
+ + + + + +
+
+ + +
+
+
+ +
+
+

Live post preview

Facebook
+
+ + + +
+
+
+

Scheduled

+
+ + + + + + +
PostPlatformsStatus
Open house Willow LaneFB · IGScheduled
Just sold — Birch AveFB · IG · LIPublished
+
+
+
+
+ +
+

Save draft (server)

+
+ {% if error %}
  • {{ error }}
{% endif %} + {% if saved %}{% endif %} +
+ {% csrf_token %} +
+
+ +
+
+
+ + + Scheduled / drafts +
+
+
+
+{% endblock %} +{% block extra_js %} + +{% endblock %} diff --git a/site/social/templates/social/post_detail.html b/site/social/templates/social/post_detail.html new file mode 100644 index 0000000..e98d316 --- /dev/null +++ b/site/social/templates/social/post_detail.html @@ -0,0 +1,42 @@ +{% extends "portal_base.html" %} +{% block title %}Post · Portal{% endblock %} +{% block topbar_title %}Social post{% endblock %} +{% block portal_content %} +
+ {{ post.get_status_display }} + ← All posts +
+
+
+

Caption

+
+
{{ post.body }}
+ {% if post.scheduled_for %} +

Scheduled {{ post.scheduled_for|date:"M j, Y g:i A" }}

+ {% endif %} + {% if post.ollama_prompt %} +

AI prompt: {{ post.ollama_prompt }}

+ {% endif %} +
+
+
+

Targets

+
+ + + + {% for target in post.targets.all %} + + + + + + {% empty %} + + {% endfor %} + +
AccountPlatformStatus
{{ target.account.label }}{{ target.get_platform_display }}{{ target.get_status_display }}
No targets attached.
+
+
+
+{% endblock %} diff --git a/site/social/templates/social/post_list.html b/site/social/templates/social/post_list.html new file mode 100644 index 0000000..9bbedb0 --- /dev/null +++ b/site/social/templates/social/post_list.html @@ -0,0 +1,30 @@ +{% extends "portal_base.html" %} +{% block title %}Social posts · Portal{% endblock %} +{% block topbar_title %}Scheduled & drafts{% endblock %} +{% block portal_content %} +
+

Posts saved from the composer.

+ Compose +
+
+
+ + + + + + {% for post in posts %} + + + + + + + {% empty %} + + {% endfor %} + +
PreviewStatusScheduled
{{ post.body|truncatechars:80 }}{{ post.get_status_display }}{% if post.scheduled_for %}{{ post.scheduled_for|date:"M j, g:i A" }}{% else %}—{% endif %}Open
No posts yet. Compose one.
+
+
+{% endblock %} diff --git a/site/social/urls.py b/site/social/urls.py new file mode 100644 index 0000000..dae722f --- /dev/null +++ b/site/social/urls.py @@ -0,0 +1,13 @@ +from django.urls import path + +from social import views + +app_name = "social" + +urlpatterns = [ + path("", views.post_list, name="post_list"), + path("accounts/", views.account_list, name="account_list"), + path("compose/", views.composer, name="composer"), + path("api/generate/", views.api_generate, name="api_generate"), + path("/", views.post_detail, name="post_detail"), +] diff --git a/site/social/views.py b/site/social/views.py new file mode 100644 index 0000000..1aee107 --- /dev/null +++ b/site/social/views.py @@ -0,0 +1,85 @@ +import json + +from django.contrib.auth.decorators import login_required +from django.http import JsonResponse +from django.shortcuts import get_object_or_404, render +from django.views.decorators.http import require_http_methods, require_POST + +from social.models import SocialAccount, SocialPost +from social.ollama import OllamaError, generate_social_post + + +@login_required +def post_list(request): + posts = SocialPost.objects.all()[:100] + return render(request, "social/post_list.html", {"posts": posts}) + + +@login_required +def post_detail(request, pk): + post = get_object_or_404(SocialPost.objects.prefetch_related("targets"), pk=pk) + return render(request, "social/post_detail.html", {"post": post}) + + +@login_required +def account_list(request): + accounts = SocialAccount.objects.all() + return render(request, "social/account_list.html", {"accounts": accounts}) + + +@login_required +@require_http_methods(["GET", "POST"]) +def composer(request): + """Portal composer — optional Ollama draft assist.""" + draft = "" + error = "" + prompt = "" + if request.method == "POST": + prompt = (request.POST.get("prompt") or "").strip() + platform = (request.POST.get("platform") or "").strip() + if prompt: + try: + draft = generate_social_post(prompt, platform=platform) + except OllamaError as exc: + error = str(exc) + body = (request.POST.get("body") or draft).strip() + if request.POST.get("action") == "save" and body: + post = SocialPost.objects.create( + body=body, + ollama_prompt=prompt, + created_by=request.user, + ) + return render( + request, + "social/composer.html", + {"saved": post, "draft": body, "prompt": prompt}, + ) + return render( + request, + "social/composer.html", + {"draft": draft, "prompt": prompt, "error": error}, + ) + + +@login_required +@require_POST +def api_generate(request): + """ + JSON endpoint for the portal to draft posts via Ollama. + + POST JSON: {"prompt": "...", "platform": "facebook"|...} + """ + try: + payload = json.loads(request.body.decode() or "{}") + except json.JSONDecodeError: + payload = request.POST.dict() + prompt = (payload.get("prompt") or "").strip() + if not prompt: + return JsonResponse({"error": "prompt required"}, status=400) + try: + text = generate_social_post( + prompt, platform=(payload.get("platform") or "").strip() + ) + except OllamaError as exc: + return JsonResponse({"error": str(exc)}, status=502) + return JsonResponse({"text": text}) diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..92d61ad --- /dev/null +++ b/uv.lock @@ -0,0 +1,589 @@ +version = 1 +revision = 3 +requires-python = ">=3.12" + +[[package]] +name = "asgiref" +version = "3.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e6/26/3b59f2bdae5f640389becb1f673cded775287f5fc4f816309d9ca9a3f93d/asgiref-3.12.1.tar.gz", hash = "sha256:59dcb51c272ad209d59bed5708a64a333083e86017d7fcdd67498eeab7784340", size = 42378, upload-time = "2026-07-14T09:56:18.087Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c0/1b/54f4ad77cd8a584fa70746c47df988e002cf1ee1eba43364d46f87803647/asgiref-3.12.1-py3-none-any.whl", hash = "sha256:fe386d1c2bff7259ea95929266d12a8cf9a8b5a1c2598402967d8792e7a7c094", size = 25478, upload-time = "2026-07-14T09:56:16.926Z" }, +] + +[[package]] +name = "certifi" +version = "2026.7.22" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a3/c2/24167ea9858356b47a87a50d39908bfdb72ceeefe0041586e704e5376b3a/certifi-2026.7.22.tar.gz", hash = "sha256:741e2c3b351ddf169a738da9f2c048608ff7f2c5cc02f1ebc6b118bb090d5d55", size = 138112, upload-time = "2026-07-22T03:35:12.644Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0b/a7/71ac2cff56fec219ed242bb11b8efb69fcc4bec75db06fb7bfe35de520e6/certifi-2026.7.22-py3-none-any.whl", hash = "sha256:62f22742b58a1a33014a2b6b706588a8d7e2a88ae7bd1a6ebe8c992928483775", size = 136983, upload-time = "2026-07-22T03:35:11.276Z" }, +] + +[[package]] +name = "cffi" +version = "2.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycparser", marker = "implementation_name != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9e/ef/008a1939e372c06329a3fce4279c02f328488f3526744906eeec3da7ad5f/cffi-2.1.1.tar.gz", hash = "sha256:dd31f52ea1086513bb9df30f8fcee9b8918323ae067a3d5b78bc826a000712be", size = 530807, upload-time = "2026-08-03T21:21:18.939Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/69/43965eccfdead3b9220015fd1320e117be8c6ed01a62ffab76eeb752f5d5/cffi-2.1.1-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:c8c69575568085ba0b1b10c0249d779a214aea6f6522e949a0fc9fb0fcb449d0", size = 184821, upload-time = "2026-08-03T21:19:44.887Z" }, + { url = "https://files.pythonhosted.org/packages/54/7d/16e5a096677b5e313ca80cd5e5170efa3ea44624a82bb111925522da64b1/cffi-2.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f81b3b8f3d4e343550fa4baa0e479bba9f2d29ce9c2e9b51d1ce1718d7442fcf", size = 184719, upload-time = "2026-08-03T21:19:46.129Z" }, + { url = "https://files.pythonhosted.org/packages/56/e6/8941622732edec876dd17d0453dce07317ae96db34f2ec1436c9d3785986/cffi-2.1.1-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:811bd1e21d32de12efca32393a0ab3f5133b54fce9bd44b8bd77ab07da14bf6a", size = 214799, upload-time = "2026-08-03T21:19:47.218Z" }, + { url = "https://files.pythonhosted.org/packages/44/de/f98430906df1545ffde0d543dd124a7a439bc2cd32b36b9c53f805df7333/cffi-2.1.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:68e62fe11f30d5ca8289242866f0a5291402d8529ca2178ab8afc5c9694ae890", size = 222389, upload-time = "2026-08-03T21:19:48.331Z" }, + { url = "https://files.pythonhosted.org/packages/6a/5b/717f1526b9957b34456313c31645c5b82b8fb5c3fe9e4752999be7128bfc/cffi-2.1.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:4a7c934f7360e8cd64fe9efadcbd10c7c6364f531e432b9a4bf5ccbc9e0e8b50", size = 210249, upload-time = "2026-08-03T21:19:49.543Z" }, + { url = "https://files.pythonhosted.org/packages/64/b3/f8aa4f3e34986c7e4ec45072d1b1b9dd295b6b18007b45518d79726dd725/cffi-2.1.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:3143d81e29e1e20a9ce10901ec369012947876596f75a222235965f2b7ae832e", size = 208775, upload-time = "2026-08-03T21:19:50.918Z" }, + { url = "https://files.pythonhosted.org/packages/b1/db/dceb9dd5b231e1da801793f8acc9f3c52a7e1afe40bb1aae37e02b0faad5/cffi-2.1.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c1453022f490d2459a11819d83ad1d586e9ff65a12ac3e705ffebd46d3685dcf", size = 221822, upload-time = "2026-08-03T21:19:52.054Z" }, + { url = "https://files.pythonhosted.org/packages/a0/d2/6cd24ae3be000a634109c247d1475d62e5616d0dc78c82770942ec384248/cffi-2.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:208f941bb9d18e768138677f0a6d2ce01f590df56043dda1df1535ac57c88517", size = 225232, upload-time = "2026-08-03T21:19:53.109Z" }, + { url = "https://files.pythonhosted.org/packages/cb/52/3fa190537004dd7f0ab860a6dc7c0175b8667f68d1e618a46f5498d30250/cffi-2.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:210019b6c7cf07f081b4c54635c8cf744377001350e29cc0f81c4377b4797735", size = 223597, upload-time = "2026-08-03T21:19:54.515Z" }, + { url = "https://files.pythonhosted.org/packages/80/fb/0bb75b7039588c074b37ae99f40d9bfddf990ecb2fbc346ebccd2e56b9be/cffi-2.1.1-cp312-cp312-win32.whl", hash = "sha256:046bfc24911b37851ee1b51aab8bffe713d89c68c6a057b09484ce9fd5f69b4e", size = 175292, upload-time = "2026-08-03T21:19:55.566Z" }, + { url = "https://files.pythonhosted.org/packages/d9/79/615cc094e2fb508cade7de88d3b4f6c4ec2bab695c97bce9153dc65aadf5/cffi-2.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:f53e442b08449d42821fa4a4fba000095af9f62742a500f978a9f557ec44339a", size = 185919, upload-time = "2026-08-03T21:19:56.89Z" }, + { url = "https://files.pythonhosted.org/packages/70/c6/d0ea84713fe46b243a436a18fcd47d639732747e21635c8a27191b06dc30/cffi-2.1.1-cp312-cp312-win_arm64.whl", hash = "sha256:7bde5e4cc5c10140859842b9d383af292b22639a4dffb725314baf45968cef80", size = 180093, upload-time = "2026-08-03T21:19:58.155Z" }, + { url = "https://files.pythonhosted.org/packages/9d/f4/035513d4117049066b4779dc3b7c0c0fdad175fa13731c9f4003f1cd1478/cffi-2.1.1-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:b5bdfd1c873d4e093aabc0ca84c4ca6dbc4f752afb5c86f146d9742580c9da2e", size = 194248, upload-time = "2026-08-03T21:19:59.399Z" }, + { url = "https://files.pythonhosted.org/packages/76/af/2aeb4dbb5fc41a04161ae9ff1518de7cec08e164f44a8ce6a4cf7fd2cd1d/cffi-2.1.1-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:31348097ff5bbe827ccc41795d4dd099d9f0625e7def00ee653c137a490c2a6c", size = 196908, upload-time = "2026-08-03T21:20:00.746Z" }, + { url = "https://files.pythonhosted.org/packages/a7/46/2e5fdde8555706dd98139a910ca11be02809f3f605ce956f655d0214e100/cffi-2.1.1-cp313-cp313-macosx_10_15_x86_64.whl", hash = "sha256:9d2055050ea716bd38b7f7f1579c275386646b4894c155a3e2f3cd62ed41b7c6", size = 184805, upload-time = "2026-08-03T21:20:02.02Z" }, + { url = "https://files.pythonhosted.org/packages/55/41/4c7042f317b9217502988f0873af87e16ad606dc20f84e546e3e6ce9764c/cffi-2.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:19ee6127ee34de7d83ce3d371ebc5ed91addbdcc39f9ab15ce4eb35a4e534971", size = 184764, upload-time = "2026-08-03T21:20:03.141Z" }, + { url = "https://files.pythonhosted.org/packages/43/1f/1c3d90d91811c8f86ced9ed637956c54bfe5b79ca98fe976d7f8c8979f6b/cffi-2.1.1-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:6a8dddef476fab96d066d578fc88526767b836ab5ab21754e1d5bf3879c31c7c", size = 214722, upload-time = "2026-08-03T21:20:04.377Z" }, + { url = "https://files.pythonhosted.org/packages/37/6f/3b5ce4c3b2192d250f04908f2bfd91ef34552ec8f7716a5d4abdb8d67bb2/cffi-2.1.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f16c709686a78c727bbbf059f92b0bf41c6fc60deec706d2dc19f529175a6125", size = 222369, upload-time = "2026-08-03T21:20:05.544Z" }, + { url = "https://files.pythonhosted.org/packages/02/10/4b3c75dde3d9663c9e02ba05c2668b954f671d4bbe346413ca8c696b295a/cffi-2.1.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:fcd22650c908d7b7da162bbfaab594a1227a15d1643a98c68b122ac642fa2264", size = 210175, upload-time = "2026-08-03T21:20:06.75Z" }, + { url = "https://files.pythonhosted.org/packages/df/62/14f74b9543e605d17701dc797b815958b8bb70b7624ce1b832ddad48ed6c/cffi-2.1.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:aa9511c62d14da7aacc9b4bf51f3f697a621e83b2d6919008243c3aad168eea3", size = 208670, upload-time = "2026-08-03T21:20:08.04Z" }, + { url = "https://files.pythonhosted.org/packages/95/95/86342356ff5953b3fb06f7ef7c5bee212d45e770abc7218d451b9148313c/cffi-2.1.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a931079504ecc49efed7744c476a5c343a92fabf66dec2db95edb1b2fdc770e2", size = 221824, upload-time = "2026-08-03T21:20:09.274Z" }, + { url = "https://files.pythonhosted.org/packages/eb/ff/7b3429ff53aafe931ed8a5fc69f481bbef7ba6de87ddcbb63d08f483f613/cffi-2.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a2d7755bef5a12ed488f4ef1f1b69ee9191d7396083b755a5d2295f6edb4768b", size = 225148, upload-time = "2026-08-03T21:20:10.7Z" }, + { url = "https://files.pythonhosted.org/packages/34/34/a95870b9221e09cf4f2ce3178b1a210abdfe63a1bd357da940418d7b8d15/cffi-2.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e0bcb7e0f677f543555d2adff3bf19c05f66cdb4796e5ff602442ab2fe3c4ef7", size = 223564, upload-time = "2026-08-03T21:20:12.165Z" }, + { url = "https://files.pythonhosted.org/packages/70/ea/839b50531021a647fb5e929f72cf97bc1ff702b5472166164b5b6e76b851/cffi-2.1.1-cp313-cp313-win32.whl", hash = "sha256:334644fbac4eff73d985a17a91226df55d0f394160c4cfb880e084c8f7161cac", size = 175263, upload-time = "2026-08-03T21:20:13.559Z" }, + { url = "https://files.pythonhosted.org/packages/60/a6/8b149b2c3f2e11aaa1618ef64500b45f50f22c57a977a4dff1aff1f91042/cffi-2.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:1aa5645c30469b09530c4ebca77ebf8f17618293c58f8549cb1a543a50236e7d", size = 185688, upload-time = "2026-08-03T21:20:14.69Z" }, + { url = "https://files.pythonhosted.org/packages/01/9a/11f687cb39d6a3504060d5242f04f48c735afb4d3d533958a20594890cb2/cffi-2.1.1-cp313-cp313-win_arm64.whl", hash = "sha256:63bbfd5ded17c4840ac07cd8f1c21ba9d9708141f840b324f422f41b207e3973", size = 180078, upload-time = "2026-08-03T21:20:15.917Z" }, + { url = "https://files.pythonhosted.org/packages/d3/7b/d6bbf82b8b96e7391438898c42f5bd96dd02030fd5b64937d248220003e2/cffi-2.1.1-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:7dbb61fe3a7699468030f71bbe5f8a0e326a151daa91beb11a6fc1f980c55e1c", size = 194064, upload-time = "2026-08-03T21:20:17.148Z" }, + { url = "https://files.pythonhosted.org/packages/94/e6/bcc91b283be94735e268487a054004f0aa19947b6348fa367db53230abc8/cffi-2.1.1-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:f24fb43132a4c6b4cb4eb029492919b2db645be6808d738f244fd146c03c32cb", size = 196720, upload-time = "2026-08-03T21:20:18.268Z" }, + { url = "https://files.pythonhosted.org/packages/d9/99/c4b0c17cacdc9c3b8f280026286a9826d6a208c0f047591a3c3ce99b91fd/cffi-2.1.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:d28630f5854ab07ab1fd4aba756de52326c82e6be15d414b12793f1975048b54", size = 184964, upload-time = "2026-08-03T21:20:19.708Z" }, + { url = "https://files.pythonhosted.org/packages/b3/a9/9db617d05d7367c1ad0ab00b3aa6e6f9281edd689b4ee9ea0e5a84e89c97/cffi-2.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:661c298b4821edebead0c91edd2b00374d67ad7c5a1f7a91d4442633b79d6a72", size = 184962, upload-time = "2026-08-03T21:20:20.833Z" }, + { url = "https://files.pythonhosted.org/packages/67/b8/b42132ca113dc567d37684437b46ca1dafc885902b02a110a02d5b511857/cffi-2.1.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:58acb8ab8e295e6c5ea12f888cbb13cf21511ef2a3303a23f4325c29d17fe5c1", size = 222328, upload-time = "2026-08-03T21:20:22.118Z" }, + { url = "https://files.pythonhosted.org/packages/80/10/c5c0cbf0a657aecf59ef511409734230bf556f05a0d6c9eed7aa5c0a0166/cffi-2.1.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:456a61fa52d579ebf9df2e9552ead5129855dbaff6c1e5a9b1bc408809bdc062", size = 209985, upload-time = "2026-08-03T21:20:23.401Z" }, + { url = "https://files.pythonhosted.org/packages/d5/6c/bfa0b87b03b9238148beca990292843c9396ba069b54496596594173de7b/cffi-2.1.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:a4f00aa42f75d6e4595e8866e748cc1705adc0cddfeb2ca86d0d03993d63ba03", size = 208530, upload-time = "2026-08-03T21:20:24.628Z" }, + { url = "https://files.pythonhosted.org/packages/e9/02/4e7d553a7ac4b4238b38b3c1b80d486e9d4436f8d2acbf87a0997fe3f402/cffi-2.1.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b0431303acaea1089ad4b3e9ce4e6518193def1118d4073ca848635ee4ea2e96", size = 221525, upload-time = "2026-08-03T21:20:25.758Z" }, + { url = "https://files.pythonhosted.org/packages/82/1d/a4aaf9babd75acb4d5f223bff71533bee748dd770a382619a798960ee9ba/cffi-2.1.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:64faea20f4e2613363a1a9b9c7dd73058f3ecd00133a511e72ad7c511658f527", size = 225053, upload-time = "2026-08-03T21:20:26.985Z" }, + { url = "https://files.pythonhosted.org/packages/81/10/5dc0e7bdd18e22107054288283380fc97a06ae3f1656a106908d666a3c88/cffi-2.1.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5c58fe613dc5e5336357eff555824a314d8e43282600435c8d1cb6a7a2fedd13", size = 223213, upload-time = "2026-08-03T21:20:28.277Z" }, + { url = "https://files.pythonhosted.org/packages/0b/e9/d0061c364cde06ee43168a0d076ac1da512cbc380d44767b844ba34fe2b6/cffi-2.1.1-cp314-cp314-win32.whl", hash = "sha256:1a18a57b58cfb21fc28d72e876acf10eaed67a1ed96226f92af4df681d571c4c", size = 177682, upload-time = "2026-08-03T21:20:44.288Z" }, + { url = "https://files.pythonhosted.org/packages/a7/06/1c3e01e3ba14c39f6d10bfbac52753b7e22259e38088e5cfe1d704918690/cffi-2.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:3222ba5d678f80a030e6afbcc33dc1ae5cb45facabb61cee2c7016b8432fde48", size = 187949, upload-time = "2026-08-03T21:20:45.623Z" }, + { url = "https://files.pythonhosted.org/packages/87/5b/da4e39efe18eeb89cf580ea9cfc66b6a7c3eadb808fc0cc1d3a295cb5a5d/cffi-2.1.1-cp314-cp314-win_arm64.whl", hash = "sha256:ab36d55f9ed2d067327667c2fea18dda018eb628dd6347aa01dda6cf1f5d3836", size = 182947, upload-time = "2026-08-03T21:20:46.955Z" }, + { url = "https://files.pythonhosted.org/packages/23/59/40338bf421c5accea1d45158170c87006ef1cd371b05c077e76476949728/cffi-2.1.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:7750c6449dff7864bb9bb27ddfb0267756189201a3afc911d82b3caacd70dfc3", size = 188504, upload-time = "2026-08-03T21:20:29.495Z" }, + { url = "https://files.pythonhosted.org/packages/7d/47/5ecf1023850036e674c77ec4de86182d309ae344e39e7cba984b7df5d647/cffi-2.1.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0beceaabe56af686895136a2de78db54ecd8e4046b236b8fd6d6cb61389e9bf2", size = 188259, upload-time = "2026-08-03T21:20:31.291Z" }, + { url = "https://files.pythonhosted.org/packages/2a/9c/92934c3bea9f785b23eba304538c0b4d37a2a96d2431eb3a1bc87a11aa19/cffi-2.1.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:49cbc70e6542d4ccccb936558d1064a8012541e78f821f955cff24e357776c94", size = 223864, upload-time = "2026-08-03T21:20:32.571Z" }, + { url = "https://files.pythonhosted.org/packages/4d/45/ba4c93527bc38616a8bd36488acb69a2212d60486794f0c1f318949bbb76/cffi-2.1.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:e2d65b31f36619cda3999b78b2aa9632e76b78448e7a56fc4240824200e7c4fc", size = 211538, upload-time = "2026-08-03T21:20:33.808Z" }, + { url = "https://files.pythonhosted.org/packages/80/e9/b6ef565e452acb932fb0cb5443f44a78efbd1233e566f02b5a83855e9115/cffi-2.1.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:28907ab9bfb6aa13184cfc17c6b8e1023c5ab6fd7076d8c20a35e59fe04f8f29", size = 210688, upload-time = "2026-08-03T21:20:34.974Z" }, + { url = "https://files.pythonhosted.org/packages/9a/95/eff5f0cee78d2eabc7eebffec40d3fc1876b5f3c95582e018bb4b99601f2/cffi-2.1.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:51b31d1c98274844cfd7838ce00bfc27c7423a4dc00fc0772fc3331c2cc90676", size = 223803, upload-time = "2026-08-03T21:20:36.564Z" }, + { url = "https://files.pythonhosted.org/packages/fa/01/579d39fb8bef00a335a23d83757b44feb24cd6345a2c451b64cb67b9c362/cffi-2.1.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5e7cecbaadb83884793e05828cee59b210b24583b9c7425d0ba6a754fe22eb4e", size = 226763, upload-time = "2026-08-03T21:20:37.816Z" }, + { url = "https://files.pythonhosted.org/packages/8d/b0/0b44f47c60b01b57b6e2bbd92343f13a85a1d93bc46ccf6e47e244acd99c/cffi-2.1.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:25792eac27877609e7bb06d42ff88278a6624fff2ba9bbb523c09616b117e80f", size = 225688, upload-time = "2026-08-03T21:20:38.959Z" }, + { url = "https://files.pythonhosted.org/packages/eb/d2/3b7176cb570a1d3e27faf67b72f591af508036e0d8b2be2ef9af9e8c84bb/cffi-2.1.1-cp314-cp314t-win32.whl", hash = "sha256:8ef53b2de9bcb9197d31854256575d59dbac0cba72ac627bb291ef5eceb74be4", size = 182868, upload-time = "2026-08-03T21:20:40.388Z" }, + { url = "https://files.pythonhosted.org/packages/56/78/31f00c1bcd97c9bbf55f1bfdf5bc809a5de8887473e90bb9960dca825e80/cffi-2.1.1-cp314-cp314t-win_amd64.whl", hash = "sha256:616f097f2fe415bc92a247f02e11f634e1f9e9a83d327e3c915c15089c87869e", size = 194104, upload-time = "2026-08-03T21:20:41.725Z" }, + { url = "https://files.pythonhosted.org/packages/7b/1b/58496f2ed0a35de575250c02a43ab3cc2c04d494a88fed31c1cabc0fd176/cffi-2.1.1-cp314-cp314t-win_arm64.whl", hash = "sha256:ad2c86c495b899d862ea0f4b42891b8713a3bd45dd4105c7fd51c2a72f39f3a5", size = 186402, upload-time = "2026-08-03T21:20:43.042Z" }, + { url = "https://files.pythonhosted.org/packages/c1/8f/9ebe220eab48a093d1a5a5e339ab0dc7316eef3bb04d63c42f0251b61f50/cffi-2.1.1-cp315-cp315-ios_13_0_arm64_iphoneos.whl", hash = "sha256:dddad92b554513a31f272570678ba307fb9f618f05e3d4a5eacafff9eae03e1d", size = 194043, upload-time = "2026-08-03T21:20:48.179Z" }, + { url = "https://files.pythonhosted.org/packages/ff/69/844bad3ece306c4782c2ecb93597035b6690d48704b803914c199da1e8b3/cffi-2.1.1-cp315-cp315-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:da0e573f9f97159390c89d9f1a9e41908b66d408cc5b58d08cf3847d844c531b", size = 196737, upload-time = "2026-08-03T21:20:49.457Z" }, + { url = "https://files.pythonhosted.org/packages/1b/8a/af668013284634733f02d683458a0728739c7d6ddb5e14cb0c20832266fe/cffi-2.1.1-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:fb92203a88b3d3053034db775110081c49d28be6551923805e039924093761e4", size = 184933, upload-time = "2026-08-03T21:20:50.639Z" }, + { url = "https://files.pythonhosted.org/packages/0c/75/2f5207ff6d1a613133b23a5203cc0c2a628313b5eb3974d7956ae3c57950/cffi-2.1.1-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:2ae64be792b8966f2c69538199728b290e34726562896df1e5dc8ffd8d8188e8", size = 185002, upload-time = "2026-08-03T21:20:52.173Z" }, + { url = "https://files.pythonhosted.org/packages/e2/31/9e1313b0a6e30e91b3b3d3fff51ae99c857c07738e3afcce1f7334e1b7ab/cffi-2.1.1-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:507a24c282e0f42f8ed737cf048572cbf580468da5555764a8331735e9c736b6", size = 222271, upload-time = "2026-08-03T21:20:53.462Z" }, + { url = "https://files.pythonhosted.org/packages/50/e3/f6234a833e6e08c7007003074723c406559eecf9b48dfc97471e5a8eb7a0/cffi-2.1.1-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:246fa40ce8645a614ff682e0b70f37134e460eaf93a775e0cbe3cca585a67a80", size = 209919, upload-time = "2026-08-03T21:20:54.783Z" }, + { url = "https://files.pythonhosted.org/packages/0d/fc/5f74e293fced6edb51af3a46c4ccf6c23c9943774ecb375ddbd522c76add/cffi-2.1.1-cp315-cp315-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:471cee653ae88de62096552e6d24ccb4a5adb8c8c9f10b5054d0122c15bf2779", size = 208529, upload-time = "2026-08-03T21:20:56.066Z" }, + { url = "https://files.pythonhosted.org/packages/44/16/29e6d01b388bef055ecd6ca8244b3f4d336bd09e92d5d892187b9601084e/cffi-2.1.1-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aeae0e330c9f6acd681f647d46cefd30c29f93e3392882e792e82080c9691399", size = 221630, upload-time = "2026-08-03T21:20:57.336Z" }, + { url = "https://files.pythonhosted.org/packages/a4/18/fa7f1f6857d5eb88a4ca99ffcbfb7c387a287ccc154c64a73e86314745d7/cffi-2.1.1-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:42a494cee34437f05546455144f2b5d9ac09b1face62bcfce597d2e521066688", size = 225134, upload-time = "2026-08-03T21:20:58.675Z" }, + { url = "https://files.pythonhosted.org/packages/e0/9f/e8e3dfa04a1b4c241f8c91faacad872b4d4efd051d49764ad4e2fd4b9fea/cffi-2.1.1-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:cc572dace3f60ef98d7b12ff411d20f5362feb31a0439eab0085bbfd349982d7", size = 223197, upload-time = "2026-08-03T21:20:59.968Z" }, + { url = "https://files.pythonhosted.org/packages/f8/7e/8debeb04f1ab9fe2a6963964cd6f1aaf7192627b83926586a6a4e089c9fa/cffi-2.1.1-cp315-cp315-win32.whl", hash = "sha256:4f42141fc14250de6dde5ee7ea4432be017252d91f19c5ad043c084cea629cac", size = 177683, upload-time = "2026-08-03T21:21:14.901Z" }, + { url = "https://files.pythonhosted.org/packages/e0/31/5158704cc474ab65c1647932e88be78dc0873f47130e253be38bcaf13d01/cffi-2.1.1-cp315-cp315-win_amd64.whl", hash = "sha256:e6e8cff14d6fb0be70a09c0bdc58096f501952d04624ebf867e0e56da2df8960", size = 187897, upload-time = "2026-08-03T21:21:16.108Z" }, + { url = "https://files.pythonhosted.org/packages/cc/4b/b3a2da8570c704ffc0f9762cdc3ec0f02c8573798e0b5cf7f11c82bbb70f/cffi-2.1.1-cp315-cp315-win_arm64.whl", hash = "sha256:27350daa11d4f10c540e6e89dada4c54feb7256ad03e9a4dc075ebad7ba360d1", size = 182935, upload-time = "2026-08-03T21:21:17.271Z" }, + { url = "https://files.pythonhosted.org/packages/d0/ef/5443574510a1207e6f6bc38ba6e1f1de36cb48fef07b2728bb896a21f430/cffi-2.1.1-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:c26608d2222fb1e94487e4a387d85f13eb55d5ed725cb25a0c589ac4ee60e7bc", size = 188464, upload-time = "2026-08-03T21:21:01.163Z" }, + { url = "https://files.pythonhosted.org/packages/7e/ae/a56fa8c4686ad50e148fcbc8d3ae0d03915ff5c30d795058988c24118cef/cffi-2.1.1-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:4be96343e422f2dfcd12ab5c9f5aebe03f82f737c6bffeca6830b3875cb44aab", size = 188262, upload-time = "2026-08-03T21:21:02.382Z" }, + { url = "https://files.pythonhosted.org/packages/53/b2/6187f46f2912276a3ae284076109cc5c8680482f11f766ccf26db4a86427/cffi-2.1.1-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:937c0052c05a31ca1daf18de3158eed4dbfcb9cc107adbea227728d647be701e", size = 223779, upload-time = "2026-08-03T21:21:03.553Z" }, + { url = "https://files.pythonhosted.org/packages/8a/f6/c3ad28bd19f77047a03084424fbd4cbe997303267c14423737324be0385d/cffi-2.1.1-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:df423d40ee8654634421812bc3b196da3f9bd7d32929da813f8394c4348a5358", size = 211520, upload-time = "2026-08-03T21:21:04.863Z" }, + { url = "https://files.pythonhosted.org/packages/a0/cd/ccac9013a5bd9fd764de118674ab9c805b5ca10c19270d90ee273f8b2240/cffi-2.1.1-cp315-cp315t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:a730a083190634c65cca36ba5f489531576ebd79bcd5c8e172130f6453127231", size = 210673, upload-time = "2026-08-03T21:21:06.223Z" }, + { url = "https://files.pythonhosted.org/packages/52/86/2976131c639aead931c5bee5aba67e4b09fbeb8018b6f282f70803f923a7/cffi-2.1.1-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:363e05fa78e15116c3c32c210ee36884fd6b9afa6d440e47112c3bd511d64cb6", size = 223835, upload-time = "2026-08-03T21:21:07.539Z" }, + { url = "https://files.pythonhosted.org/packages/ac/0c/33a7aeab2f9c76918c52e084beb39c570db3588133412929e8ec06fab90b/cffi-2.1.1-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:770de9db11e84213beec501cfcaa013b019820ca881e03344dea5844f7876d94", size = 226705, upload-time = "2026-08-03T21:21:08.774Z" }, + { url = "https://files.pythonhosted.org/packages/e3/26/2cde30fdde421130bfc18f70395731a6e6b2053c6a1978a5258ff04e72fa/cffi-2.1.1-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:7da0c5eff80f0197f3b3d1232ec5a682a9325f4ae9016a78f5f5ca35f9ced1f5", size = 225539, upload-time = "2026-08-03T21:21:09.911Z" }, + { url = "https://files.pythonhosted.org/packages/6d/cd/a361394c94b2129d604bb846f624a8e88255a3ee33129c434a00d715e64f/cffi-2.1.1-cp315-cp315t-win32.whl", hash = "sha256:06c72bb76605a4b0cd0aad6930b69d4baf7dd5d806cfc409b824191099700e66", size = 182707, upload-time = "2026-08-03T21:21:11.226Z" }, + { url = "https://files.pythonhosted.org/packages/9b/b5/ba2b299993c26577d529b6ae29841f9e15b9fcf004d65f423f4fcf94ade9/cffi-2.1.1-cp315-cp315t-win_amd64.whl", hash = "sha256:d9c275eaacd24aa73f94ffd6de08fc3f932424d8b6c376f4bed7cde376fe7bc3", size = 193772, upload-time = "2026-08-03T21:21:12.39Z" }, + { url = "https://files.pythonhosted.org/packages/aa/29/35e016098c814cd93de9cd320c66b5bfba14dc6ecedd3cb518fa7c408c69/cffi-2.1.1-cp315-cp315t-win_arm64.whl", hash = "sha256:d18e5ac0f2f03f4f518d3e23db0f0cad7faa1da8620e9c09461d443bbf6e6692", size = 186360, upload-time = "2026-08-03T21:21:13.636Z" }, +] + +[[package]] +name = "cfgv" +version = "3.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4e/b5/721b8799b04bf9afe054a3899c6cf4e880fcf8563cc71c15610242490a0c/cfgv-3.5.0.tar.gz", hash = "sha256:d5b1034354820651caa73ede66a6294d6e95c1b00acc5e9b098e917404669132", size = 7334, upload-time = "2025-11-19T20:55:51.612Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl", hash = "sha256:a8dc6b26ad22ff227d2634a65cb388215ce6cc96bbcc5cfde7641ae87e8dacc0", size = 7445, upload-time = "2025-11-19T20:55:50.744Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bd/2a/23f34ec9d04624958e137efdc394888716353190e75f25dd22c7a2c7a8aa/charset_normalizer-3.4.9.tar.gz", hash = "sha256:673611bbd43f0810bec0b0f028ddeaaa501190339cac411f347ac76917c3ae7b", size = 152439, upload-time = "2026-07-07T14:34:58.454Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/70/4a/ecbd131485c07fcdfad54e28946d513e3da22ef3b4bd854dcafae54ec739/charset_normalizer-3.4.9-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:45b0cc4e3556cd875e09102988d1ab8356c998b596c9fced84547c8138b487a0", size = 319300, upload-time = "2026-07-07T14:33:15.666Z" }, + { url = "https://files.pythonhosted.org/packages/ec/96/5d9364e3342d69f3a045e1777bc47c85c383e6e9466d561b33fdb419d1f9/charset_normalizer-3.4.9-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9b2aff1c7b3884512b9512c3eaadd9bab39fb45042ffaaa1dd08ff2b9f8109d9", size = 215802, upload-time = "2026-07-07T14:33:17.031Z" }, + { url = "https://files.pythonhosted.org/packages/4b/4c/5361f9aa7f2cb58d94f2ab831b3d493f69efb1d239654b4744e3c09527cb/charset_normalizer-3.4.9-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9104ed0bd76a429d46f9ec0dbc9b08ad1d2dcdf2b00a5a0daa1c145329b35b44", size = 237171, upload-time = "2026-07-07T14:33:18.576Z" }, + { url = "https://files.pythonhosted.org/packages/50/78/ce342ca4ff30b2eb49fe6d9578df85974f90c67d294113e94efdd9664cbd/charset_normalizer-3.4.9-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7b86a2b16095d250c6f58b3d9b2eee6f4147754344f3dab0922f7c9bf7d226c9", size = 233075, upload-time = "2026-07-07T14:33:20.084Z" }, + { url = "https://files.pythonhosted.org/packages/01/c4/4fa4c8b3097a11f3c5f09a35b72ed6855fb1d332469504962ab7bafcc702/charset_normalizer-3.4.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e226f6218febc71f6c1fc2fafb91c226f75bdc1d8fb12d66823716e891608fd", size = 224256, upload-time = "2026-07-07T14:33:21.747Z" }, + { url = "https://files.pythonhosted.org/packages/87/3a/ad914516df7e358a81aae018caa5e0470ba827fa6d763b1d2e87d920a5f6/charset_normalizer-3.4.9-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:90c44bc373b7687f6948b693cceaea1348ae0975d7474746559494468e3c1d84", size = 208784, upload-time = "2026-07-07T14:33:23.313Z" }, + { url = "https://files.pythonhosted.org/packages/d7/74/3c12f9755717dfe5c5c87da63f35d765fa0c00382ec26bf23f7fae34f2ba/charset_normalizer-3.4.9-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9cdef90ae47919cae358d8ab15797a800ed41da7aba5d72419fb510729e2ed4b", size = 219928, upload-time = "2026-07-07T14:33:24.814Z" }, + { url = "https://files.pythonhosted.org/packages/33/9a/895095b83e7907abd6d3d99aad3a38ad0d9686cc186cb0c94c24320fe63e/charset_normalizer-3.4.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:60f44ade2cf573dad7a277e6f8ca9a51a21dda572b13bd7d8539bb3cd5dbedde", size = 218489, upload-time = "2026-07-07T14:33:26.42Z" }, + { url = "https://files.pythonhosted.org/packages/a1/34/ef5c05f412f42520d7709b7d3784d19640839eb7366ded1755511585429f/charset_normalizer-3.4.9-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:a1786910334ed46ab1dd73222f2cd1e05c2c3bb39f6dddb4f8b36fc382058a39", size = 210267, upload-time = "2026-07-07T14:33:27.952Z" }, + { url = "https://files.pythonhosted.org/packages/83/dc/9b29fa4412b318bf3bfea985c35d67eb55e04b59a7c3f2237168b0e0be6f/charset_normalizer-3.4.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:03d07803992c6c7bbc976327f34b18b6160327fc81cb82c9d504720ac0be3b62", size = 226030, upload-time = "2026-07-07T14:33:29.397Z" }, + { url = "https://files.pythonhosted.org/packages/0e/42/6dbc00b8cd16011691203e33570fa42ed5746599a2e878112d16eab403a3/charset_normalizer-3.4.9-cp312-cp312-win32.whl", hash = "sha256:78841cccf1af7b40f6f716338d50c0902dbe88d9f800b3c973b7a9a0a693a642", size = 151185, upload-time = "2026-07-07T14:33:30.781Z" }, + { url = "https://files.pythonhosted.org/packages/80/cc/f920afd1a23c58ccd53c1d36085a71893a4737ff5e66e0371efab6809850/charset_normalizer-3.4.9-cp312-cp312-win_amd64.whl", hash = "sha256:4b3dac63058cc36820b0dd072f89898604e2d39686fe05321729d00d8ac185a0", size = 162557, upload-time = "2026-07-07T14:33:32.176Z" }, + { url = "https://files.pythonhosted.org/packages/f0/e6/0386d43a261ff4e4b30c5857af7df877254b46bec7b9d1b74b6bf969a90b/charset_normalizer-3.4.9-cp312-cp312-win_arm64.whl", hash = "sha256:78fa18e436a1a0e58dbd7e02fc4473f3f32cceb12df9dfca542d075961c307d2", size = 152665, upload-time = "2026-07-07T14:33:33.711Z" }, + { url = "https://files.pythonhosted.org/packages/b2/06/97ec2aeae780b31d742b6352218b43841a6871e2564578ca522dce4a45c3/charset_normalizer-3.4.9-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:440eede837960000d74978f0eba527be106b5b9aee0daf779d395276ed0b0614", size = 317688, upload-time = "2026-07-07T14:33:35.408Z" }, + { url = "https://files.pythonhosted.org/packages/d0/39/8ff066c672434225f8d25f8b739f992af250944392173dcc88362681c9bf/charset_normalizer-3.4.9-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:21e764fd1e70b6a3e205a0e46f3051701f98a8cb3fad66eeb80e48bb502f8698", size = 214982, upload-time = "2026-07-07T14:33:36.996Z" }, + { url = "https://files.pythonhosted.org/packages/92/8f/3a47a3667c83c2df9483d91644c6c107de3bf8874aa1793da9d3012eb986/charset_normalizer-3.4.9-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e4fd89cc178bced6ad29cb3e6dd4aa63fa5017c3524dbd0b25998fb64a87cc8b", size = 236460, upload-time = "2026-07-07T14:33:38.536Z" }, + { url = "https://files.pythonhosted.org/packages/f1/60/b22cdbee7e4013dab8b0d7647fc6181120fbbbc8f7025c226d15bd5a47fc/charset_normalizer-3.4.9-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bd47ba7fc3ca94896759ea0109775132d3e7ab921fbf54038e1bab2e46c313c9", size = 232003, upload-time = "2026-07-07T14:33:40.059Z" }, + { url = "https://files.pythonhosted.org/packages/ea/f8/72eb13dcabe7257035cea8aefd922caad2f110d252bf9f67c4c2ca763aee/charset_normalizer-3.4.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:84fd18bcc17526fc2b3c1af7d2b9217d32c9c04448c16ec693b9b4f1985c3d33", size = 223149, upload-time = "2026-07-07T14:33:41.631Z" }, + { url = "https://files.pythonhosted.org/packages/b0/3e/faee8f9de92b14ee1198e9163252bb15efee7301b31256a3b6d9ebfdd0dd/charset_normalizer-3.4.9-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:5b10cd92fc5c498b35a8635df6d5a100207f88b63a4dc1de7ef9a548e1e2cd63", size = 207901, upload-time = "2026-07-07T14:33:43.209Z" }, + { url = "https://files.pythonhosted.org/packages/3a/25/45f30093ae27dd7b92a793b61882a38685f993700113ca36e0c9c14965e1/charset_normalizer-3.4.9-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a4fbdde9dd4a9ce5fd52c2b3a347bb50cc89483ef783f1cb00d408c13f7a96c0", size = 219176, upload-time = "2026-07-07T14:33:44.725Z" }, + { url = "https://files.pythonhosted.org/packages/48/18/c8f397329c35e32f6a837e488986f4ae03bd2abebc453b48714991630c2f/charset_normalizer-3.4.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:416c229f77e5ea25b3dfd4b582f8d73d7e43c22320302b9ab128a2d3a0b38efe", size = 217356, upload-time = "2026-07-07T14:33:46.192Z" }, + { url = "https://files.pythonhosted.org/packages/86/7e/5ce0bba863470fd1902d5e5843968951bddf38abe4742fc97116ef4598b3/charset_normalizer-3.4.9-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:75286256590a6320cf106a0d28970d3560aad9ee09aa7b34fb40524792436d35", size = 209614, upload-time = "2026-07-07T14:33:47.705Z" }, + { url = "https://files.pythonhosted.org/packages/6c/ef/2473d3c4d869155be4af1191111d59c4d5c4e0173026f7e85b176e23bf65/charset_normalizer-3.4.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:69b157c5d3292bcd443faca052f3096f637f1e074b98212a933c074ae23dc3b8", size = 224991, upload-time = "2026-07-07T14:33:49.238Z" }, + { url = "https://files.pythonhosted.org/packages/d0/a3/53ddae3db108a088156aa8ddfafd411ebbc1340f48c5573f697b27f69a39/charset_normalizer-3.4.9-cp313-cp313-win32.whl", hash = "sha256:51307f5c71007673a2bf8232ad973483d281e74cb99c8c5a990af1eefa6277d9", size = 150622, upload-time = "2026-07-07T14:33:50.711Z" }, + { url = "https://files.pythonhosted.org/packages/e8/ef/6953a77c7cf2c2ff9998e6f575ab3e380119f100223381565a4f94c1f836/charset_normalizer-3.4.9-cp313-cp313-win_amd64.whl", hash = "sha256:fe2c7201c642b7c308f1675355ad7ff7b66acfe3541625efe5a3ad38f29d6115", size = 161947, upload-time = "2026-07-07T14:33:52.197Z" }, + { url = "https://files.pythonhosted.org/packages/6e/fb/d560d1d1555debbfe7849d9cac6145c1b537709d79576bf22557ed803b82/charset_normalizer-3.4.9-cp313-cp313-win_arm64.whl", hash = "sha256:611057cc5d5c0afc743ba8be6bd828c17e0aaa8643f9d0a9b9bb7dea80eb8012", size = 152594, upload-time = "2026-07-07T14:33:53.486Z" }, + { url = "https://files.pythonhosted.org/packages/7e/8d/496817fa0944239ecae662dd57ea765cfeaec6a735f9f025d4b7b72e7143/charset_normalizer-3.4.9-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:0327fcd59a935777d83410750c50600ee9571af2846f71ce40f25b13da1ef380", size = 317253, upload-time = "2026-07-07T14:33:54.994Z" }, + { url = "https://files.pythonhosted.org/packages/2b/f9/ef4a69ea338ad3c0deceea0f5f7d2380ae8b52132b06d652cb0d2cd86706/charset_normalizer-3.4.9-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a79d9f4d8001473a30c163556b3c3bfebec837495a412dde78b51672f6134f9", size = 215898, upload-time = "2026-07-07T14:33:56.334Z" }, + { url = "https://files.pythonhosted.org/packages/8c/e7/5ddfd76fc061eb52de219658a4aa431cbacadf0a0219c8854f00da50d289/charset_normalizer-3.4.9-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:33bdcc2a32c0a0e861f60841a512c8acc658c87c2ac59d89e3a46dacf7d866e4", size = 236718, upload-time = "2026-07-07T14:33:57.9Z" }, + { url = "https://files.pythonhosted.org/packages/49/ba/768fa3f36048d81c477a0ce61f813bc1454d80917ccfe550abd9f44f5e24/charset_normalizer-3.4.9-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f840ed6d8ecba8255df8c42b87fadeda98ddfc6eeec05e2dc66e26d46dd6f58a", size = 232519, upload-time = "2026-07-07T14:33:59.811Z" }, + { url = "https://files.pythonhosted.org/packages/f4/c4/b3e049d2aa3766180c78507110543d9d50894cc97f57de543f1be521dcdc/charset_normalizer-3.4.9-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c25fe15c70c59eb7c5ce8c06a1f3fa1da0ecc5ea1e7a5922c40fd2fa9b0d5046", size = 223143, upload-time = "2026-07-07T14:34:01.517Z" }, + { url = "https://files.pythonhosted.org/packages/19/79/55c32d06d76ae4feafe053f061f3e3ab70bcf19f4007797ce8c3efda7830/charset_normalizer-3.4.9-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:f7fb7d750cfa0a070d2c24e831fd3481019a60dd317ea2b39acbcebc08b6ed81", size = 206742, upload-time = "2026-07-07T14:34:03.04Z" }, + { url = "https://files.pythonhosted.org/packages/10/e0/47c079dd82d217c807479cd59ffd30af56307ea31c108b75758970459ad3/charset_normalizer-3.4.9-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4d1c96a7a18b9690a4d46df09e3e3382406ae3213727cd1019ebade1c4a81917", size = 219191, upload-time = "2026-07-07T14:34:04.657Z" }, + { url = "https://files.pythonhosted.org/packages/42/ab/b9bc2e77d6b44a7e46ef62ec5cac1c9a6ba7b9135a5d560f002696ec9995/charset_normalizer-3.4.9-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a4cfde78a9f2880208d16a93b795726a3017d5977e08d1e162a7a31322479c41", size = 218328, upload-time = "2026-07-07T14:34:06.115Z" }, + { url = "https://files.pythonhosted.org/packages/f1/78/c9c71d599f5aa2d42bcdd35cbbd46d7f535351a57e40ff7d8e5a7e219401/charset_normalizer-3.4.9-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:d4d6fcde76f94f5cb9e43e9e9a61f16dacefd228cbbf6f1a09bd9b219a92f1a1", size = 207406, upload-time = "2026-07-07T14:34:07.554Z" }, + { url = "https://files.pythonhosted.org/packages/f6/39/c914445c321a845097ce4f6ac7de9a18228a77b766272125a1ce00d851eb/charset_normalizer-3.4.9-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:898f0e9068ca27d37f8e83a5b962821df851532e6c4a7d615c1c033f9da6eedf", size = 225157, upload-time = "2026-07-07T14:34:09.061Z" }, + { url = "https://files.pythonhosted.org/packages/9b/f2/c0d4b8508565a36bc5c624e88ed297f5b0b1095011034d7f5b83a69908b5/charset_normalizer-3.4.9-cp314-cp314-win32.whl", hash = "sha256:c1c948747b03be832dceed96ca815cef7360de9aa19d37c730f8e3f6101aca48", size = 151095, upload-time = "2026-07-07T14:34:10.901Z" }, + { url = "https://files.pythonhosted.org/packages/49/fd/a1d26144398c67486422a72bf5812cda22cb4ccfcd95a290fb41ceb4b8e2/charset_normalizer-3.4.9-cp314-cp314-win_amd64.whl", hash = "sha256:16b65ea0f2465b6fb52aa22de5eca612aa964ddfec00a912e26f4656cbef890b", size = 162796, upload-time = "2026-07-07T14:34:12.47Z" }, + { url = "https://files.pythonhosted.org/packages/20/95/d75e82f8ce9fd323ebf059c16c9aadefb22a1ecde13b7840b35835e4886c/charset_normalizer-3.4.9-cp314-cp314-win_arm64.whl", hash = "sha256:40a126142a56b2dfc0aacbad1de8310cbf60da7656db0e6b16eebd48e3e93519", size = 153334, upload-time = "2026-07-07T14:34:14.044Z" }, + { url = "https://files.pythonhosted.org/packages/00/5e/17398df3a139985ba9d11ed072531986f408c8fca952835ef1ab1820c02b/charset_normalizer-3.4.9-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:609b3ba8fcc0fb5ab7af00719d0fb6ad0cb518e48e7712d12fd68f1327951198", size = 338848, upload-time = "2026-07-07T14:34:15.688Z" }, + { url = "https://files.pythonhosted.org/packages/cd/91/7253a32e86b7e1d1239b1b36ba6dd0f021a21107ab33054b53119cc083b9/charset_normalizer-3.4.9-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:51447e9aa2684679af07ca5021c3db526e0284347ebf4ffcec1154c3350cfe32", size = 223022, upload-time = "2026-07-07T14:34:17.248Z" }, + { url = "https://files.pythonhosted.org/packages/cb/32/2e64bd2be10e89c61e57ebe6a93fd98ae88eb7ebe414b5121f22c96c69eb/charset_normalizer-3.4.9-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cc1b0fff8ead343dae06305f954eb8468ba0ec1a97881f42489d198e4ce3c632", size = 241590, upload-time = "2026-07-07T14:34:18.813Z" }, + { url = "https://files.pythonhosted.org/packages/3d/ef/d96ec496cfea0c21db43b0ad03891308b02388d054cc902cf0e5a1ad6a88/charset_normalizer-3.4.9-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fa36ec09ef71d158186bc79e359ff5fdd6e7996fe8ab638f00d6b93139ba4fcf", size = 239584, upload-time = "2026-07-07T14:34:20.52Z" }, + { url = "https://files.pythonhosted.org/packages/d4/ce/9af95f7876194bd7a14e3dfe4a4de2e0bff02666a3910d72beafd06cc297/charset_normalizer-3.4.9-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:df115d4d83168fdf2cae48ef1ff6d1cb4c466364e30861b37121de0f3bf1b990", size = 230224, upload-time = "2026-07-07T14:34:22.189Z" }, + { url = "https://files.pythonhosted.org/packages/52/94/af74dde74a3996bd959c350709bfe50e297823d70a8c1cbd54b838880863/charset_normalizer-3.4.9-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:f86c6358749bd4fda175388691e3ba8c46e24c5347d0afd20f9b7edfc9faf07d", size = 212667, upload-time = "2026-07-07T14:34:23.857Z" }, + { url = "https://files.pythonhosted.org/packages/ee/f0/f1c4fe746c395922961b5916ed1d7d6e7d4c84851d19ed43cc89980ec953/charset_normalizer-3.4.9-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:32286a2c8d167e897177b673176c1e3e00d4057caf5d2b64eef9a3666b03018e", size = 227179, upload-time = "2026-07-07T14:34:25.586Z" }, + { url = "https://files.pythonhosted.org/packages/e4/56/6c745619ac397e8871e2bcd3cea1eec86b877488f33888b3aef5c3ed506e/charset_normalizer-3.4.9-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:83aed2c10721ddd90f68140685391b50811a880af20654c59af6b6c66c40513c", size = 225372, upload-time = "2026-07-07T14:34:27.212Z" }, + { url = "https://files.pythonhosted.org/packages/78/ad/98aae8630ac71f16711968e38a5acfecce41b778bf2f0312851020f565a8/charset_normalizer-3.4.9-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cd6c3d4b783c556fa00bf540854e42f135e2f256abd29669fcd0da0f2dec79c2", size = 215222, upload-time = "2026-07-07T14:34:28.774Z" }, + { url = "https://files.pythonhosted.org/packages/f7/40/9593d54209765207a7f11073c06494c1721e4ca4a0a426c597679bf7f91e/charset_normalizer-3.4.9-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ee2f2a527e3c1a6e6411eb4209642e138b544a2d72fe5d0d76daf77b24063534", size = 231958, upload-time = "2026-07-07T14:34:30.345Z" }, + { url = "https://files.pythonhosted.org/packages/b1/27/693ee5e8a18191eb38647360c51cd505013e2bd3b366aa43fd5344c21e3c/charset_normalizer-3.4.9-cp314-cp314t-win32.whl", hash = "sha256:0d861473f743244d349b50f850d10eb87aeb22bbdcc8e64f79273c94af5a8226", size = 155580, upload-time = "2026-07-07T14:34:31.884Z" }, + { url = "https://files.pythonhosted.org/packages/80/3f/bd97d3d9c613013d07cb7733d299385b41df37f0471310f5a73dc359f0b8/charset_normalizer-3.4.9-cp314-cp314t-win_amd64.whl", hash = "sha256:9b8e0f3107e2200b76f6054de99016eac3ee6762713587b36baaa7e4bd2ae177", size = 167620, upload-time = "2026-07-07T14:34:33.438Z" }, + { url = "https://files.pythonhosted.org/packages/3d/c6/eee9dca4439b1061f76373f06ea855678cc4a64c1c3c90b50e479edbb8eb/charset_normalizer-3.4.9-cp314-cp314t-win_arm64.whl", hash = "sha256:19ac87f93086ce37b86e098888555c4b4bc48102279bae3350098c0ed664b501", size = 158037, upload-time = "2026-07-07T14:34:35.018Z" }, + { url = "https://files.pythonhosted.org/packages/98/2b/f97f1c193fb855c345d678f5077d6926034db0722df74c8f057020e05a25/charset_normalizer-3.4.9-py3-none-any.whl", hash = "sha256:68e5f26a1ad57ded6d1cfb85331d1c1a195314756471d97758c48498bb4dcdf5", size = 64538, upload-time = "2026-07-07T14:34:56.993Z" }, +] + +[[package]] +name = "cryptography" +version = "50.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/de/41/6cbdcf9142d00fe82836fbb51e503e58088575cf7a0fe1dbff6695bf0840/cryptography-50.0.0.tar.gz", hash = "sha256:eeac2acb5a20ed25e0ad6d1df9891a520b78b404266b6d11778f25d5d691a6c9", size = 880201, upload-time = "2026-07-31T14:25:10.11Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c5/5c/59086b4aac5e879d38ddbcf74e4be7ade89cebc3eb199a55da998c3bb46a/cryptography-50.0.0-cp311-abi3-macosx_11_0_arm64.whl", hash = "sha256:031e2d5dd4bb9caa3ca9c82e5a197fd8ae680232cee62603d1a813f3f07e3d03", size = 4001252, upload-time = "2026-07-31T14:23:33.331Z" }, + { url = "https://files.pythonhosted.org/packages/57/ef/8f2df13c7216bcad3e1c74e07f6e193d93e998e114f524a53877c9af27ad/cryptography-50.0.0-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fd9192b7b70c573d7f214eb1ae35e00d359f6f5e4b27c7e21e30de1fc6204645", size = 4719554, upload-time = "2026-07-31T14:23:35.611Z" }, + { url = "https://files.pythonhosted.org/packages/d9/41/029086c34d91052fc3b88bcc8056f709a7c915c7a23b235a54eb800b1c97/cryptography-50.0.0-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:06a32a980526a6ab9a4b9bf8f7385800791e2bb960903cb6b530e4817509a3b7", size = 4702130, upload-time = "2026-07-31T14:23:37.635Z" }, + { url = "https://files.pythonhosted.org/packages/7d/ff/b6ce0954962e7f7b969f850a883744197bb3910bdfd7b6da162eab7d9f68/cryptography-50.0.0-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:a1b30560f2acc95aa8b2e06e716a13dbfc97314747b80d9707e307f77b40d6b3", size = 4725244, upload-time = "2026-07-31T14:23:39.471Z" }, + { url = "https://files.pythonhosted.org/packages/06/1e/63a1027cb7fec360a182208e1b7767d5aa1fe57be3d6aa856e69a321edc0/cryptography-50.0.0-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:8d89f3976b10b4ce31118de72329025f70d2c6ead14a8217c5514dd2c6d5a78f", size = 5342265, upload-time = "2026-07-31T14:23:41.286Z" }, + { url = "https://files.pythonhosted.org/packages/6b/72/a1116d683a6d7ece94590013882515de087edf9ef0e6292aae615a44df73/cryptography-50.0.0-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:b42a28c1844fd9de8f3f7d540e36b66f3a9c83fceac7170ebc7a6a19edd9dcae", size = 4734609, upload-time = "2026-07-31T14:23:43.139Z" }, + { url = "https://files.pythonhosted.org/packages/15/37/36a9c479bbe49acea2636c7fd3360d20f7b7e079c300352011c44850b181/cryptography-50.0.0-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:900131fafd8aead39ac7dd3a7e833be754c17a95cfd91221636949fe4eb0aa8a", size = 4356517, upload-time = "2026-07-31T14:23:44.939Z" }, + { url = "https://files.pythonhosted.org/packages/32/98/8a151d64367204cbc63ec65d37502f1d9c53cf4bfc6ec3c532614dbec60d/cryptography-50.0.0-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:07949c449a1abcf60d1ee6e88956d89404c7df3c8258f46589e912988e551987", size = 4724529, upload-time = "2026-07-31T14:23:46.93Z" }, + { url = "https://files.pythonhosted.org/packages/22/f6/ec13b470172126464a86bf54d2294a46d29837fc51ba3e45d4047946fb5e/cryptography-50.0.0-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:f89831ef99dd7dd169ab06d63a831adb9e20a87aac6d380266bbda5823349169", size = 5299852, upload-time = "2026-07-31T14:23:48.851Z" }, + { url = "https://files.pythonhosted.org/packages/da/3a/f05e32c99d440c9bb891ea0e36c9091891e36be5a9a87ab2ee6ea20729f6/cryptography-50.0.0-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:82148ec5bddac30b51a5b3c1945075f896fa022cb93f8e4a01e9f6ee95292c5f", size = 4734462, upload-time = "2026-07-31T14:23:50.861Z" }, + { url = "https://files.pythonhosted.org/packages/ca/dc/bd72b26be8953f80625f63151efd38eee71c76ca6cf591c08ff34615a79e/cryptography-50.0.0-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1489e263a8048bb8b6a8bac662eb2d402ea5d2b7b4699b72f385f1e2772db105", size = 4852708, upload-time = "2026-07-31T14:23:52.715Z" }, + { url = "https://files.pythonhosted.org/packages/27/20/c930314a2ab476d15dec966ec87e2e9637bb02b06106b12c0396c57bb603/cryptography-50.0.0-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:7cec5b856506da6defb290f30c9ee687d5f5e8cb0bd3f6459dde43b0b4fa40ef", size = 5004179, upload-time = "2026-07-31T14:23:54.887Z" }, + { url = "https://files.pythonhosted.org/packages/32/2e/c9db68a0c4bfa28e310707527c0ee3a2bd254104d2e02e68f368e197aa4c/cryptography-50.0.0-cp311-abi3-win_amd64.whl", hash = "sha256:bd1c592e4d5974f0d08d4888e432157adba757c66da0246918e43677fafa2d30", size = 3840395, upload-time = "2026-07-31T14:23:56.677Z" }, + { url = "https://files.pythonhosted.org/packages/c3/fb/951032a3bf22a5697c83183fb6294a4843772947a70e616c57b3ff5f522e/cryptography-50.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:49e7d93abdbd2990caced757e5fade25302f719c3c8fb6e6fff2dde98999fc41", size = 3989258, upload-time = "2026-07-31T14:23:58.881Z" }, + { url = "https://files.pythonhosted.org/packages/d4/67/91eb047e69c5e845f2f14b8a2e4a1aab0f283cb885531e9e22c8adb176bc/cryptography-50.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:19736989797678c6af1e55cd49055cdbcb55d8f6b5583ac5335f933aba9101dc", size = 4700648, upload-time = "2026-07-31T14:24:00.702Z" }, + { url = "https://files.pythonhosted.org/packages/30/82/85f0f7425c856b9f96459411eb12e74ef72df9caf6f8f15bf23a33ff131f/cryptography-50.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:80b63928fa35083b33966ce1efb70e5b9607181e49dcd1c22c8c005e319f667f", size = 4682442, upload-time = "2026-07-31T14:24:02.538Z" }, + { url = "https://files.pythonhosted.org/packages/1a/28/b555a365adff1cca2fbe7b9e487d68a40de6bc67ff2cb587473eb43de0e7/cryptography-50.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:d58c3db7cd6eed54e6c06744db55456b65ebd7492ddeae9c1e93cfca7aa857d3", size = 4707596, upload-time = "2026-07-31T14:24:04.394Z" }, + { url = "https://files.pythonhosted.org/packages/72/d8/f52538140cc719df62a01cf87d1c7142318d235817109d6f4054d7c352d6/cryptography-50.0.0-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:df2a58a472f332225671c35b0a830208b86d004f82baa8530fa3782c85646533", size = 5314552, upload-time = "2026-07-31T14:24:06.31Z" }, + { url = "https://files.pythonhosted.org/packages/38/14/6120e5bd7c5aa022ad15424ba4d5c5269d0d9448ed4d55e492ea91e3c1c4/cryptography-50.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:11b74db56cdbe3cdee6e3f6982ecb70334fa10dce99ed58bf7894aaaa3b2a037", size = 4717113, upload-time = "2026-07-31T14:24:08.349Z" }, + { url = "https://files.pythonhosted.org/packages/fa/71/190bf38c3ee2e0f8efc9860ae100c9df4169742eef274b91e7aa1cb133b9/cryptography-50.0.0-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:f59e38625469987d7ef6d495323c55e7db6c212eaf6112267e0d3b565a2e9c9f", size = 4338580, upload-time = "2026-07-31T14:24:10.227Z" }, + { url = "https://files.pythonhosted.org/packages/3a/63/504ccfbbe61fd8aa983f7f146399cdf034c72c2fc55f5b2dfdcdcdb20c99/cryptography-50.0.0-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:ecfed7367f965a0328cfbdd70da860f15441f002f613185668c6e6ebf5a0ac11", size = 4707038, upload-time = "2026-07-31T14:24:12.169Z" }, + { url = "https://files.pythonhosted.org/packages/01/77/2cf79bbfc4d12ca106437a6e170d6aaa01a373e93093118aaaef0e801bd4/cryptography-50.0.0-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:9aa87839c383bdbab6ef865787a1fb877af8dd03464c4400322726feaaadfc6d", size = 5273110, upload-time = "2026-07-31T14:24:14.38Z" }, + { url = "https://files.pythonhosted.org/packages/e5/45/8aae2972c520145377ea3559a605a899bebe227bf070b33cdb445929a9b9/cryptography-50.0.0-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:6ba6a53445bd3cfa809ef3ef5f1589aa6ba08784a1d962bf47d0940e871dab1c", size = 4716439, upload-time = "2026-07-31T14:24:16.415Z" }, + { url = "https://files.pythonhosted.org/packages/7b/20/4fe50b619a48c2525cc46e2dbc1ac490708d704be5d467bdaac6dc955682/cryptography-50.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:3f5735ffe4996d28b809371756219f5354864902a3b9e7c0b9ee87041209fc9c", size = 4837383, upload-time = "2026-07-31T14:24:18.553Z" }, + { url = "https://files.pythonhosted.org/packages/92/91/3a31366e183343d3703f8995c095f5734676bd6938118047e50fcf279eb4/cryptography-50.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:1b4a266766514614f8aa60416e71f2fc6e575d36e7bdc90f644fadb2f4b75b95", size = 4985772, upload-time = "2026-07-31T14:24:20.385Z" }, + { url = "https://files.pythonhosted.org/packages/74/9a/02ffe35b2853d121689871eb5dce862092562b3a1ed5cc98f1aaed441506/cryptography-50.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:12b9c6996425c76ea6c457ace4f3073e715b8c545add07cd1a8f3a4f90691269", size = 3816291, upload-time = "2026-07-31T14:24:22.125Z" }, + { url = "https://files.pythonhosted.org/packages/03/37/73d005be173aff344af30e9fd2a576575cb2391a7101d9cd3842e1fa8cce/cryptography-50.0.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:ccdc4a71a4dabae05de219404f9f4abc38e3b58422177ff93d0da05967dafa07", size = 4036009, upload-time = "2026-07-31T14:24:24.122Z" }, + { url = "https://files.pythonhosted.org/packages/ff/c6/7a6202a534e32103a285b7834a120869557fe198d51d7cfe59754c8bda9c/cryptography-50.0.0-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:910e1d2668e7de9648f2bcee30e180db2a6b15c30f887d7c4c93ddf96e3992e3", size = 4745252, upload-time = "2026-07-31T14:24:26.118Z" }, + { url = "https://files.pythonhosted.org/packages/85/4f/0fa8c2f4428198f15d9ff8d63400e27afbf94ce833f6108da1eb3753f945/cryptography-50.0.0-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a91296cb61e8df6f86d0c19cc4068228da256bf59bf86049fbd821084565327f", size = 4728939, upload-time = "2026-07-31T14:24:27.994Z" }, + { url = "https://files.pythonhosted.org/packages/d1/63/54dd723490ba2dc09b299682c10b38db38f159728bcaae8c591b8af2f22d/cryptography-50.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:e722f16708d854fe924790e051061f6704a472c3bac347b6fd88033ea8dd0dc5", size = 4748483, upload-time = "2026-07-31T14:24:30.254Z" }, + { url = "https://files.pythonhosted.org/packages/1d/dd/7c77d26285cc7f6991efce64a0f5b4f9383bfa5dd8c5033003eaf7db4cdb/cryptography-50.0.0-cp39-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:d764dcf130c428ef66786f866dd750f53182bc608813489915e9fc106bb0c82f", size = 5367599, upload-time = "2026-07-31T14:24:32.457Z" }, + { url = "https://files.pythonhosted.org/packages/46/c9/f60aed34c013f317f92817b6c171c2d22a78270fa41109bd4b08af26b194/cryptography-50.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:105110f43a471dbd0060b9c9516cb8a6a79233631a04cc2ba16f28323ac6e025", size = 4762647, upload-time = "2026-07-31T14:24:34.599Z" }, + { url = "https://files.pythonhosted.org/packages/be/f3/f9a0173b139372c3a48ed98154b45cc6b9de17c789d5ab552e621c293609/cryptography-50.0.0-cp39-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:828743d939e9629bc267b8e2d08d8bb67cd4319c771a33d4b18b22dd8fb7440a", size = 4385197, upload-time = "2026-07-31T14:24:36.647Z" }, + { url = "https://files.pythonhosted.org/packages/d8/36/83bb81f6e569bc38e1e4a7bc80f29b46bb9601920bc455fc8e888f5d5742/cryptography-50.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:2a8183b489dc1f7f80f135780fadc1108f14b31b8a40411c7a5b17425f65f28b", size = 4748095, upload-time = "2026-07-31T14:24:39.493Z" }, + { url = "https://files.pythonhosted.org/packages/6b/16/d3008eff98c764979865834c3d386d4fd041b5f52e7f34fc29ac1a5eb515/cryptography-50.0.0-cp39-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:6e7d61120573a7f2cd94cc095f9e81f6967c61ccdf194285aa143ecec8e0b708", size = 5325948, upload-time = "2026-07-31T14:24:41.556Z" }, + { url = "https://files.pythonhosted.org/packages/9c/f8/d97f9603efda3888187bfdb893f26c41be4735c10631d05d284ee6b047c4/cryptography-50.0.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:37fdb0d0111f1e2ff07139dfb79f1b49531f8e213c46f1163dd7642979b58c47", size = 4762400, upload-time = "2026-07-31T14:24:43.636Z" }, + { url = "https://files.pythonhosted.org/packages/64/a2/4615c8f7d81a00b1d6e6afe19f694e1543582349fb5f4076f6cb5dc36485/cryptography-50.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c87f62a3d3b9888ed0fdde100ec06aa61ca9cd44bad9057d1dff9a516b5f5bb9", size = 4878208, upload-time = "2026-07-31T14:24:45.522Z" }, + { url = "https://files.pythonhosted.org/packages/d2/1a/efcfb02f91407149a0dacffffab791f7e19bf6385f63b3666dc8b5e5c9c8/cryptography-50.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:65c2c3add92b45fd0709db8594536aea39c2a67af0e27ffcf049c498501140b7", size = 5037050, upload-time = "2026-07-31T14:24:47.697Z" }, + { url = "https://files.pythonhosted.org/packages/57/30/4a22984d4f1bdfb8c054f07a92bc176b97a3134cc1d6c4b3bffb1f3688b4/cryptography-50.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:d24fead1d4d076e1bfb006dcec392074a3cd8d7b4fc8a595aa64073b2b7a96ba", size = 3874135, upload-time = "2026-07-31T14:24:50.085Z" }, +] + +[[package]] +name = "distlib" +version = "0.4.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c9/02/bd72be9134d25ed783ecbbc38a539ffaefbf90c78418c7fb7229600dbac7/distlib-0.4.3.tar.gz", hash = "sha256:f152097224a0ae24be5a0f6bae1b9359af82133bce63f98a95f86cae1aede9ed", size = 615141, upload-time = "2026-06-12T08:04:52.847Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl", hash = "sha256:4b0ce306c966eb73bc3a7b6abad017c556dadd92c44701562cd528ac7fde4d5b", size = 470628, upload-time = "2026-06-12T08:04:50.506Z" }, +] + +[[package]] +name = "dj-queue" +version = "0.13.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "django" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/57/f5/930f49e03813469aec0c8035149bf4ca32004b418069ba02114bfa11347d/dj_queue-0.13.0.tar.gz", hash = "sha256:c2d9bb97ed0da564e6e85995aef0810cdb55cb9b86dc179aef882e20f15847ca", size = 124704, upload-time = "2026-06-09T15:39:49.595Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/86/a6e9f461a53c6230c91279402c9f56fb3e53e0215e227ab073476c77dd3a/dj_queue-0.13.0-py3-none-any.whl", hash = "sha256:df00b48109ee52990368ffa3ca89cc0dee404175ee974e313dc7afe59011428a", size = 150989, upload-time = "2026-06-09T15:39:48.322Z" }, +] + +[[package]] +name = "django" +version = "6.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "asgiref" }, + { name = "sqlparse" }, + { name = "tzdata", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e2/42/6cb20996733984c1f6661daeda3877990836c76c633c6c8879d39f7120eb/django-6.1.tar.gz", hash = "sha256:86a2aacd59b817e4d6ac2ebfe22356c58f66f7b24e503f71b7c2fead677ee48b", size = 11223034, upload-time = "2026-08-05T19:21:53.789Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/9c/ce847620134cfab903e75690c498af73b46abbede2912ea89bd76d5c1e76/django-6.1-py3-none-any.whl", hash = "sha256:6c132cd980c9392b06807d4ca52d72530d631dc65a85d9dacede00a780cefbbe", size = 8417399, upload-time = "2026-08-05T19:21:47.285Z" }, +] + +[[package]] +name = "django-phonenumber-field" +version = "8.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "django" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0f/82/479b070133cd493cad0f59bc04d6dd3e2e88b2c1f48cd6f54fd897225199/django_phonenumber_field-8.5.0.tar.gz", hash = "sha256:19c606fda7bb25630c4913f7ac83c1824186ea17e2e4c6dc9aa9373e8f5843c5", size = 46311, upload-time = "2026-07-23T12:24:16.471Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/77/14/4e33f4055afbf517ac3223371334f6f3226beecc92b781131a6eb6eeda1c/django_phonenumber_field-8.5.0-py3-none-any.whl", hash = "sha256:a6dd71250f7fd0724aad995df01bdb4ac457a65622f120d0d623792fe8283798", size = 69514, upload-time = "2026-07-23T12:24:14.955Z" }, +] + +[[package]] +name = "django-recaptcha" +version = "4.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "django" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bd/a2/480cd2df8031c30afe0d67b0a5e6299ced20f2e65135a3c160c4031d7052/django_recaptcha-4.1.0.tar.gz", hash = "sha256:73097b958733f65b729d4e4630c51fc2bf147aca6f026017d18898dfc25cb4c5", size = 28676, upload-time = "2025-03-28T13:52:26.589Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8b/e7/0765f98e4953e26573512284995648c439b0eb252877b655683d28c90be2/django_recaptcha-4.1.0-py3-none-any.whl", hash = "sha256:463aa65967e973de466b28ce8e8b6abb2faffb1ce0c7faa1bf0e5b041e0497cb", size = 39473, upload-time = "2025-03-28T13:52:24.92Z" }, +] + +[[package]] +name = "filelock" +version = "3.32.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/57/3ba6e6cb097f85b855b00163d169f35365f44277df044dcf96d55b8f62a3/filelock-3.32.2.tar.gz", hash = "sha256:c33351e1f49cae33414acbc6d56784e6ecee82514ec90795da1161fc4836b5b8", size = 217172, upload-time = "2026-07-29T22:46:04.895Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/e8/72f8cef9fdfeffe06213fe8508039396ee48daa0e3259457ed766173bfd6/filelock-3.32.2-py3-none-any.whl", hash = "sha256:87dd94cf281e586d135fa51132b8e3d9a598b316e90377a288663c9321036c82", size = 98830, upload-time = "2026-07-29T22:46:03.52Z" }, +] + +[[package]] +name = "gunicorn" +version = "26.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6d/b7/a4a3f632f823e432ce6bc65f62961b7980c898c77f075a2f7118cb3846fe/gunicorn-26.0.0.tar.gz", hash = "sha256:ca9346f85e3a4aeeb64d491045c16b9a35647abd37ea15efe53080eb8b090baf", size = 727286, upload-time = "2026-05-05T06:38:25.529Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e6/40/9c2384fc2be4ad25dd4a49decd5ad9ea5a3639814c11bd40ab77cb9f0a14/gunicorn-26.0.0-py3-none-any.whl", hash = "sha256:40233d26a5f0d1872916188c276e21641155111c2853f0c2cd55260aec0d24fc", size = 212009, upload-time = "2026-05-05T06:38:23.007Z" }, +] + +[[package]] +name = "identify" +version = "2.6.19" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/52/63/51723b5f116cc04b061cb6f5a561790abf249d25931d515cd375e063e0f4/identify-2.6.19.tar.gz", hash = "sha256:6be5020c38fcb07da56c53733538a3081ea5aa70d36a156f83044bfbf9173842", size = 99567, upload-time = "2026-04-17T18:39:50.265Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/84/d9273cd09688070a6523c4aee4663a8538721b2b755c4962aafae0011e72/identify-2.6.19-py2.py3-none-any.whl", hash = "sha256:20e6a87f786f768c092a721ad107fc9df0eb89347be9396cadf3f4abbd1fb78a", size = 99397, upload-time = "2026-04-17T18:39:49.221Z" }, +] + +[[package]] +name = "idna" +version = "3.18" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/63/9496c57188a2ee585e0f1db071d75089a11e98aa86eb99d9d7618fc1edce/idna-3.18.tar.gz", hash = "sha256:ffb385a7e039654cef1ab9ef32c6fafe283c0c0467bba1d9029738ce4a14a848", size = 196711, upload-time = "2026-06-02T14:34:07.794Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/5e/d4e9f1a599fb8e573b7b87160658329fbf28d19eac2718f51fc3def3aa5a/idna-3.18-py3-none-any.whl", hash = "sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2", size = 65455, upload-time = "2026-06-02T14:34:06.319Z" }, +] + +[[package]] +name = "monica-site" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "cryptography" }, + { name = "dj-queue" }, + { name = "django" }, + { name = "django-phonenumber-field" }, + { name = "django-recaptcha" }, + { name = "gunicorn" }, + { name = "phonenumbers" }, + { name = "psycopg2-binary" }, + { name = "requests" }, + { name = "whitenoise" }, +] + +[package.dev-dependencies] +dev = [ + { name = "pre-commit" }, +] + +[package.metadata] +requires-dist = [ + { name = "cryptography", specifier = ">=44.0.0" }, + { name = "dj-queue", specifier = ">=0.13.0" }, + { name = "django", specifier = ">=6.0,<7" }, + { name = "django-phonenumber-field", specifier = ">=8.1.0" }, + { name = "django-recaptcha", specifier = ">=4.1.0" }, + { name = "gunicorn", specifier = ">=23.0.0" }, + { name = "phonenumbers", specifier = ">=9.0.0" }, + { name = "psycopg2-binary", specifier = ">=2.9.10" }, + { name = "requests", specifier = ">=2.32.0" }, + { name = "whitenoise", specifier = ">=6.9.0" }, +] + +[package.metadata.requires-dev] +dev = [{ name = "pre-commit", specifier = ">=4.1.0" }] + +[[package]] +name = "nodeenv" +version = "1.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/24/bf/d1bda4f6168e0b2e9e5958945e01910052158313224ada5ce1fb2e1113b8/nodeenv-1.10.0.tar.gz", hash = "sha256:996c191ad80897d076bdfba80a41994c2b47c68e224c542b48feba42ba00f8bb", size = 55611, upload-time = "2025-12-20T14:08:54.006Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl", hash = "sha256:5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827", size = 23438, upload-time = "2025-12-20T14:08:52.782Z" }, +] + +[[package]] +name = "packaging" +version = "26.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/fa/3944b40b07da9ce895c0e6303a5ab7d53da063554f534556b134a54d6093/packaging-26.3.tar.gz", hash = "sha256:94edc256424af38762eb31306eed28beb9f0efc50a8837492c9d6fd6004aed79", size = 313412, upload-time = "2026-08-04T18:15:28.737Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/63/34/ba1c580383c9eada3711951fef0795c80b829a078d72188184bcab9dd527/packaging-26.3-py3-none-any.whl", hash = "sha256:d7193f7c8e4e93f444fde0262bf90af30e16fa0ad0ad44cb553c87339b23cd1c", size = 129956, upload-time = "2026-08-04T18:15:27.159Z" }, +] + +[[package]] +name = "phonenumbers" +version = "9.0.36" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0f/21/3f1561d3527b27121d6872aeecb6df2a8de8e11af9c36f5a0bd3483d2655/phonenumbers-9.0.36.tar.gz", hash = "sha256:60ca2a6870d2532d6e960105790e85e9d69270ede81746d10cf57f5e437b93ec", size = 2307271, upload-time = "2026-08-01T06:13:44.954Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/39/5d/74fbcb16b29927bf378f4ec4d47c7b48488b93e1f1af64b26544daf1d55f/phonenumbers-9.0.36-py2.py3-none-any.whl", hash = "sha256:c16a5b95178345ec2df1954578a4ac09e937443b2ee992dd7b15a8e8c81f4acf", size = 2595528, upload-time = "2026-08-01T06:13:41.591Z" }, +] + +[[package]] +name = "platformdirs" +version = "4.11.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/78/9b/560e4be8e26f6fd133a03630a8df0c663b9e8d61b4ade152b72005aec83b/platformdirs-4.11.0.tar.gz", hash = "sha256:0555d18370482847566ffabcaa53ad7c6c1c29f195989ae1ed634a05f76ea1e0", size = 31953, upload-time = "2026-07-21T13:09:36.565Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7d/68/d8d58938dfb1370b266a1a729e6d77a985be23689a0496498ee17b2cbf90/platformdirs-4.11.0-py3-none-any.whl", hash = "sha256:360ccded2b7fce0af0ff80cc8f5942a1c5d99b0e856033acb030bfc634709e74", size = 23247, upload-time = "2026-07-21T13:09:35.422Z" }, +] + +[[package]] +name = "pre-commit" +version = "4.6.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cfgv" }, + { name = "identify" }, + { name = "nodeenv" }, + { name = "pyyaml" }, + { name = "virtualenv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/25/3a/ddb78f32a0814e66b18a099377a106a2dcdce92d86a034d69d65df9b256e/pre_commit-4.6.1.tar.gz", hash = "sha256:03e809865c7d178b9979d06c761fcbfe6808fdaded8581a745bb110e52050421", size = 198646, upload-time = "2026-07-21T20:56:58.225Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/49/bc925106abcdac498074f2cbe6137e94e09f418dd2b7775df5b577dc0313/pre_commit-4.6.1-py2.py3-none-any.whl", hash = "sha256:0e3b2942510d1fb34eec167a3ec57331bf8442122f1153a9fb8b58f5c49b2717", size = 226186, upload-time = "2026-07-21T20:56:57.064Z" }, +] + +[[package]] +name = "psycopg2-binary" +version = "2.9.12" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2a/60/a3624f79acea344c16fbef3a94d28b89a8042ddfb8f3e4ca83f538671409/psycopg2_binary-2.9.12.tar.gz", hash = "sha256:5ac9444edc768c02a6b6a591f070b8aae28ff3a99be57560ac996001580f294c", size = 379686, upload-time = "2026-04-21T09:40:34.304Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e2/9f/ef4ef3c8e15083df90ca35265cfd1a081a2f0cc07bb229c6314c6af817f4/psycopg2_binary-2.9.12-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5cdc05117180c5fa9c40eea8ea559ce64d73824c39d928b7da9fb5f6a9392433", size = 3712459, upload-time = "2026-04-20T23:34:30.549Z" }, + { url = "https://files.pythonhosted.org/packages/b5/01/3dd14e46ba48c1e1a6ec58ee599fa1b5efa00c246d5046cd903d0eeb1af1/psycopg2_binary-2.9.12-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d3227a3bc228c10d21011a99245edca923e4e8bf461857e869a507d9a41fe9f6", size = 3822936, upload-time = "2026-04-20T23:34:32.77Z" }, + { url = "https://files.pythonhosted.org/packages/a6/f7/0640e4901119d8a9f7a1784b927f494e2198e213ceb593753d1f2c8b1b30/psycopg2_binary-2.9.12-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:995ce929eede89db6254b50827e2b7fd61e50d11f0b116b29fffe4a2e53c4580", size = 4578676, upload-time = "2026-04-20T23:34:35.18Z" }, + { url = "https://files.pythonhosted.org/packages/b0/55/44df3965b5f297c50cc0b1b594a31c67d6127a9d133045b8a66611b14dfb/psycopg2_binary-2.9.12-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9fe06d93e72f1c048e731a2e3e7854a5bfaa58fc736068df90b352cefe66f03f", size = 4274917, upload-time = "2026-04-20T23:34:37.982Z" }, + { url = "https://files.pythonhosted.org/packages/b0/4b/74535248b1eac0c9336862e8617c765ac94dac76f9e25d7c4a79588c8907/psycopg2_binary-2.9.12-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:40e7b28b63aaf737cb3a1edc3a9bbc9a9f4ad3dcb7152e8c1130e4050eddcb7d", size = 5894843, upload-time = "2026-04-20T23:34:40.856Z" }, + { url = "https://files.pythonhosted.org/packages/f2/ba/f1bf8d2ae71868ad800b661099086ee52bc0f8d9f05be1acd8ebb06757cc/psycopg2_binary-2.9.12-cp312-cp312-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:89d19a9f7899e8eb0656a2b3a08e0da04c720a06db6e0033eab5928aabe60fa9", size = 4110556, upload-time = "2026-04-20T23:34:44.016Z" }, + { url = "https://files.pythonhosted.org/packages/45/46/c15706c338403b7c420bcc0c2905aad116cc064545686d8bf85f1999ea00/psycopg2_binary-2.9.12-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:612b965daee295ae2da8f8218ce1d274645dc76ef3f1abf6a0a94fd57eff876d", size = 3655714, upload-time = "2026-04-20T23:34:46.233Z" }, + { url = "https://files.pythonhosted.org/packages/b3/7c/a2d5dc09b64a4564db242a0fe418fde7d33f6f8259dd2c5b9d7def00fb5a/psycopg2_binary-2.9.12-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:b9a339b79d37c1b45f3235265f07cdeb0cb5ad7acd2ac7720a5920989c17c24e", size = 3301154, upload-time = "2026-04-20T23:34:49.528Z" }, + { url = "https://files.pythonhosted.org/packages/c0/e8/cc8c9a4ce71461f9ec548d38cadc41dc184b34c73e6455450775a9334ccd/psycopg2_binary-2.9.12-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:3471336e1acfd9c7fe507b8bad5af9317b6a89294f9eb37bd9a030bb7bebcdc6", size = 3048882, upload-time = "2026-04-20T23:34:51.86Z" }, + { url = "https://files.pythonhosted.org/packages/19/6a/31e2296bc0787c5ab75d3d118e40b239db8151b5192b90b77c72bc9256e9/psycopg2_binary-2.9.12-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7af18183109e23502c8b2ae7f6926c0882766f35b5175a4cd737ad825e4d7a1b", size = 3351298, upload-time = "2026-04-20T23:34:54.124Z" }, + { url = "https://files.pythonhosted.org/packages/5f/a8/75f4e3e11203b590150abed2cf7794b9c9c9f7eceddae955191138b44dde/psycopg2_binary-2.9.12-cp312-cp312-win_amd64.whl", hash = "sha256:398fcd4db988c7d7d3713e2b8e18939776fd3fb447052daae4f24fa39daede4c", size = 2757230, upload-time = "2026-04-20T23:34:56.242Z" }, + { url = "https://files.pythonhosted.org/packages/91/bb/4608c96f970f6e0c56572e87027ef4404f709382a3503e9934526d7ba051/psycopg2_binary-2.9.12-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7c729a73c7b1b84de3582f73cdd27d905121dc2c531f3d9a3c32a3011033b965", size = 3712419, upload-time = "2026-04-20T23:34:58.754Z" }, + { url = "https://files.pythonhosted.org/packages/5e/af/48f76af9d50d61cf390f8cd657b503168b089e2e9298e48465d029fcc713/psycopg2_binary-2.9.12-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4413d0caef93c5cf50b96863df4c2efe8c269bf2267df353225595e7e15e8df7", size = 3822990, upload-time = "2026-04-20T23:35:00.821Z" }, + { url = "https://files.pythonhosted.org/packages/7a/df/aba0f99397cd811d32e06fc0cc781f1f3ce98bc0e729cb423925085d781a/psycopg2_binary-2.9.12-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:4dfcf8e45ebb0c663be34a3442f65e17311f3367089cd4e5e3a3e8e62c978777", size = 4578696, upload-time = "2026-04-20T23:35:03.409Z" }, + { url = "https://files.pythonhosted.org/packages/95/9c/eaa74021ac4e4d5c2f83d82fc6615a63f4fe6c94dc4e94c3990427053f67/psycopg2_binary-2.9.12-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c41321a14dd74aceb6a9a643b9253a334521babfa763fa873e33d89cfa122fb5", size = 4274982, upload-time = "2026-04-20T23:35:05.583Z" }, + { url = "https://files.pythonhosted.org/packages/35/ed/c25deff98bd26187ba48b3b250a3ffc3037c46c5b89362534a15d200e0db/psycopg2_binary-2.9.12-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:83946ba43979ebfdc99a3cd0ee775c89f221df026984ba19d46133d8d75d3cd9", size = 5894867, upload-time = "2026-04-20T23:35:07.902Z" }, + { url = "https://files.pythonhosted.org/packages/9a/81/8d0e21ca77373c6c9589e5c4528f6e8f0c08c62cafc76fb0bddb7a2cee22/psycopg2_binary-2.9.12-cp313-cp313-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:411e85815652d13560fbe731878daa5d92378c4995a22302071890ec3397d019", size = 4110578, upload-time = "2026-04-20T23:35:10.149Z" }, + { url = "https://files.pythonhosted.org/packages/00/fc/f481e2435bd8f742d0123309174aae4165160ad3ef17c1b99c3622c241d2/psycopg2_binary-2.9.12-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1c8ad4c08e00f7679559eaed7aff1edfffc60c086b976f93972f686384a95e2c", size = 3655816, upload-time = "2026-04-20T23:35:12.56Z" }, + { url = "https://files.pythonhosted.org/packages/53/79/b9f46466bdbe9f239c96cde8be33c1aace4842f06013b47b730dc9759187/psycopg2_binary-2.9.12-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:00814e40fa23c2b37ef0a1e3c749d89982c73a9cb5046137f0752a22d432e82f", size = 3301307, upload-time = "2026-04-20T23:35:15.029Z" }, + { url = "https://files.pythonhosted.org/packages/3f/19/7dc003b32fe35024df89b658104f7c8538a8b2dcbde7a4e746ce929742e7/psycopg2_binary-2.9.12-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:98062447aebc20ed20add1f547a364fd0ef8933640d5372ff1873f8deb9b61be", size = 3048968, upload-time = "2026-04-20T23:35:16.757Z" }, + { url = "https://files.pythonhosted.org/packages/91/58/2dbd7db5c604d45f4950d988506aae672a14126ec22998ced5021cbb76bb/psycopg2_binary-2.9.12-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:66a7685d7e548f10fb4ce32fb01a7b7f4aa702134de92a292c7bd9e0d3dbd290", size = 3351369, upload-time = "2026-04-20T23:35:18.933Z" }, + { url = "https://files.pythonhosted.org/packages/42/ee/dee8dcaad07f735824de3d6563bc67119fa6c28257b17977a8d624f02fab/psycopg2_binary-2.9.12-cp313-cp313-win_amd64.whl", hash = "sha256:b6937f5fe4e180aeee87de907a2fa982ded6f7f15d7218f78a083e4e1d68f2a0", size = 2757347, upload-time = "2026-04-20T23:35:21.283Z" }, + { url = "https://files.pythonhosted.org/packages/13/1b/708c0dca874acfad6d65314271859899a79007686f3a1f74e82a2ed4b645/psycopg2_binary-2.9.12-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:6f3b3de8a74ef8db215f22edffb19e32dc6fa41340456de7ec99efdc8a7b3ec2", size = 3712428, upload-time = "2026-04-20T23:35:23.453Z" }, + { url = "https://files.pythonhosted.org/packages/d6/39/ddbea9d4b4de6aca9431b6ed253f530f8a02d3b8f9bcfd0dbfe2b3de6fe4/psycopg2_binary-2.9.12-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1006fb62f0f0bc5ce256a832356c6262e91be43f5e4eb15b5eaf38079464caf2", size = 3823184, upload-time = "2026-04-20T23:35:25.92Z" }, + { url = "https://files.pythonhosted.org/packages/bf/a0/bc2fef74b106fa345567122a0659e6d94512ed7dc0131ec44c9e5aba3725/psycopg2_binary-2.9.12-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:840066105706cd2eb29b9a1c2329620056582a4bf3e8169dec5c447042d0869f", size = 4579157, upload-time = "2026-04-20T23:35:28.542Z" }, + { url = "https://files.pythonhosted.org/packages/57/d7/d4e3b2005d3de607ca4fbb0e8742e248056e52184a6b94ebda3c1c2c329b/psycopg2_binary-2.9.12-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:863f5d12241ebe1c76a72a04c2113b6dc905f90b9cef0e9be0efd994affd9354", size = 4274970, upload-time = "2026-04-20T23:35:30.418Z" }, + { url = "https://files.pythonhosted.org/packages/2e/42/c9853f8db3967fe08bcde11f53d53b85d351750cae726ce001cb68afa9c1/psycopg2_binary-2.9.12-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a99eaab34a9010f1a086b126de467466620a750634d114d20455f3a824aae033", size = 5895175, upload-time = "2026-04-20T23:35:33.584Z" }, + { url = "https://files.pythonhosted.org/packages/eb/fd/b82b5601a97630308bef079f545ffec481bbbc795c2ba5ec416a01d03f60/psycopg2_binary-2.9.12-cp314-cp314-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ffdd7dc5463ccd61845ac37b7012d0f35a1548df9febe14f8dd549be4a0bc81e", size = 4110658, upload-time = "2026-04-20T23:35:35.638Z" }, + { url = "https://files.pythonhosted.org/packages/62/8c/32ca69b0389ef25dd22937bf9e8fbe2ce27aea20b05ded48c4ce4cb42475/psycopg2_binary-2.9.12-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:54a0dfecab1b48731f934e06139dfe11e24219fb6d0ceb32177cf0375f14c7b5", size = 3656251, upload-time = "2026-04-20T23:35:37.854Z" }, + { url = "https://files.pythonhosted.org/packages/c4/29/96992a2b59e3b9d730fcf9612d0a387305025dc867a9fc490a9e496e074e/psycopg2_binary-2.9.12-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:96937c9c5d891f772430f418a7a8b4691a90c3e6b93cf72b5bd7cad8cbca32a5", size = 3301810, upload-time = "2026-04-20T23:35:39.927Z" }, + { url = "https://files.pythonhosted.org/packages/56/ad/44b06659949b243ae10112cd3b20a197f9bf3e81d5651379b9eb889bfaad/psycopg2_binary-2.9.12-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:77b348775efd4cdab410ec6609d81ccecd1139c90265fa583a7255c8064bc03d", size = 3048977, upload-time = "2026-04-20T23:35:41.806Z" }, + { url = "https://files.pythonhosted.org/packages/1d/f2/10a1bcebadb6aa55e280e1f58975c36a7b560ea525184c7aa4064c466633/psycopg2_binary-2.9.12-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:527e6342b3e44c2f0544f6b8e927d60de7f163f5723b8f1dfa7d2a84298738cd", size = 3351466, upload-time = "2026-04-20T23:35:43.993Z" }, + { url = "https://files.pythonhosted.org/packages/20/be/b732c8418ffa5bcfda002890f5dc4c869fc17db66ff11f53b17cfe44afc0/psycopg2_binary-2.9.12-cp314-cp314-win_amd64.whl", hash = "sha256:f12ae41fcafadb39b2785e64a40f9db05d6de2ac114077457e0e7c597f3af980", size = 2848762, upload-time = "2026-04-20T23:35:46.421Z" }, +] + +[[package]] +name = "pycparser" +version = "3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" }, +] + +[[package]] +name = "python-discovery" +version = "1.5.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "filelock" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/04/b7/1581a8103855c43567776aa34135e5ec3c597346c23bfd10c7eb5e0b10a4/python_discovery-1.5.1.tar.gz", hash = "sha256:e2ea8b884cd1701f386eda8cf327b87743f1dc21b7f784470799537d95635384", size = 77200, upload-time = "2026-07-31T22:06:02.48Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/07/a89b539750a159d5101c4eb9fc84e2961f65cefbd5e0b7440b284471c0b0/python_discovery-1.5.1-py3-none-any.whl", hash = "sha256:ac07f44cade589d954e9d6a1e1468539fdddd2cf676beb51da73e0f156b7c932", size = 35752, upload-time = "2026-07-31T22:06:01.116Z" }, +] + +[[package]] +name = "pyyaml" +version = "6.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, + { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, + { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, + { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, + { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, + { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, + { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, + { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, + { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, + { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, + { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, + { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, + { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, + { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, + { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, + { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, + { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, + { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, + { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, + { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, + { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, + { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, + { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, + { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, + { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, + { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, + { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, + { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, + { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, + { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, + { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, +] + +[[package]] +name = "requests" +version = "2.34.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ac/c3/e2a2b89f2d3e2179abd6d00ebd70bff6273f37fb3e0cc209f48b39d00cbf/requests-2.34.2.tar.gz", hash = "sha256:f288924cae4e29463698d6d60bc6a4da69c89185ad1e0bcc4104f584e960b9ed", size = 142856, upload-time = "2026-05-14T19:25:27.735Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/f4/c67b0b3f1b9245e8d266f0f112c500d50e5b4e83cb6f3b71b6528104182a/requests-2.34.2-py3-none-any.whl", hash = "sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0", size = 73075, upload-time = "2026-05-14T19:25:26.443Z" }, +] + +[[package]] +name = "sqlparse" +version = "0.5.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/90/76/437d71068094df0726366574cf3432a4ed754217b436eb7429415cf2d480/sqlparse-0.5.5.tar.gz", hash = "sha256:e20d4a9b0b8585fdf63b10d30066c7c94c5d7a7ec47c889a2d83a3caa93ff28e", size = 120815, upload-time = "2025-12-19T07:17:45.073Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/49/4b/359f28a903c13438ef59ebeee215fb25da53066db67b305c125f1c6d2a25/sqlparse-0.5.5-py3-none-any.whl", hash = "sha256:12a08b3bf3eec877c519589833aed092e2444e68240a3577e8e26148acc7b1ba", size = 46138, upload-time = "2025-12-19T07:17:46.573Z" }, +] + +[[package]] +name = "tzdata" +version = "2026.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/92/ff/5a28bdfd8c3ebec42564ac7d0e54ca3db65044a9314a97f9564fa7a1e926/tzdata-2026.3.tar.gz", hash = "sha256:4a1518b8993086a7982523e071643f3c0e5f213e75b21318e78bcabfff9d1415", size = 198674, upload-time = "2026-07-10T08:50:37.887Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/6d/b53b99a9f2766d095985947a5782f1702cabb129a34f7a802d7197af832f/tzdata-2026.3-py2.py3-none-any.whl", hash = "sha256:dc096730c87af6cab1b171c9d532be840741ff5d459015e7f6947bd7d7e54931", size = 348168, upload-time = "2026-07-10T08:50:36.46Z" }, +] + +[[package]] +name = "urllib3" +version = "2.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/53/0c/06f8b233b8fd13b9e5ee11424ef85419ba0d8ba0b3138bf360be2ff56953/urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c", size = 433602, upload-time = "2026-05-07T16:13:18.596Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087, upload-time = "2026-05-07T16:13:17.151Z" }, +] + +[[package]] +name = "virtualenv" +version = "21.7.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "distlib" }, + { name = "filelock" }, + { name = "platformdirs" }, + { name = "python-discovery" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ea/fa/18004e5cb15541ad2a68ff219c755233b012b12d4ec8663d06a258082bec/virtualenv-21.7.1.tar.gz", hash = "sha256:d0dbfaa5483487baea28d7210ef8d24c9d1bd0f10f449eeb215568825a9b334e", size = 5525237, upload-time = "2026-07-30T15:40:36.36Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4b/a7/ded126c19495158a05c7202b3389139839d4cf78d622d453867778e0f7a8/virtualenv-21.7.1-py3-none-any.whl", hash = "sha256:6394973f990536e34c05157179146c020284c42fe01da1dfeb0ba16c345280d9", size = 5504576, upload-time = "2026-07-30T15:40:34.512Z" }, +] + +[[package]] +name = "whitenoise" +version = "6.12.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cb/2a/55b3f3a4ec326cd077c1c3defeee656b9298372a69229134d930151acd01/whitenoise-6.12.0.tar.gz", hash = "sha256:f723ebb76a112e98816ff80fcea0a6c9b8ecde835f8ddda25df7a30a3c2db6ad", size = 26841, upload-time = "2026-02-27T00:05:42.028Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/db/eb/d5583a11486211f3ebd4b385545ae787f32363d453c19fffd81106c9c138/whitenoise-6.12.0-py3-none-any.whl", hash = "sha256:fc5e8c572e33ebf24795b47b6a7da8da3c00cff2349f5b04c02f28d0cc5a3cc2", size = 20302, upload-time = "2026-02-27T00:05:40.086Z" }, +]