Compare commits

..
1 Commits
Author SHA1 Message Date
westfarn bd6aa4b3f3 Update public About bio, Wheaton address, and headshot.
CI / test (pull_request) Successful in 14s
Closes #1 — refresh About copy and photo, surface the Butterfield Rd office on Home/Contact/nav, and align service-area defaults with Chicago's western suburbs.
2026-08-20 05:05:16 -05:00
60 changed files with 376 additions and 3342 deletions

No files matched your search

-16
View File
@@ -67,11 +67,6 @@ PCM_RETURN_ADDRESS=
# PCM_RETURN_CITY=
# PCM_RETURN_STATE=
# PCM_RETURN_ZIP=
# Fallback per-piece dollars when PCM design payload has no pricing[].
# PCM_POSTCARD_PRICE_FIRSTCLASS=0.89
# PCM_POSTCARD_PRICE_STANDARD=0.55
# Occupant list type for carrier-route counts (GET /list/types).
# PCM_LIST_TYPE=IRL
# Social
# LinkedIn / Instagram App ID + Secret: Portal → Social accounts (not env).
@@ -84,14 +79,6 @@ SOCIAL_TOKEN_ENCRYPTION_KEY=
OLLAMA_BASE_URL=http://10.0.0.128:11434
OLLAMA_MODEL=llama3.2
# URL shortener (piha.lc). Empty locally = long UTM URLs in campaigns.
# Call the short host: POST /api/links/ (Bearer) and recipients GET /<code>.
SHORTENER_BASE_URL=
SHORTENER_API_TOKEN=
# Local shortener compose:
# SHORTENER_BASE_URL=http://127.0.0.1:8005
# SHORTENER_API_TOKEN=monica:dev-only-token
# 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.
@@ -105,7 +92,4 @@ TIANJI_ENABLED=false
TIANJI_TRACKER_URL=https://tianji.aimloperations.com/tracker.js
TIANJI_WEBSITE_ID=cmshzhxdf6gee10qzkzrfn9iw
# Worker poll interval for due scheduled campaigns / social posts (seconds).
# DISPATCH_DUE_INTERVAL_SECONDS=15
GUNICORN_WORKERS=2
-12
View File
@@ -68,9 +68,6 @@ PCM_WEBHOOK_SECRETS=replace-me,replace-me-2
# JSON return address for postcard orders (quote so compose keeps commas)
PCM_RETURN_ADDRESS='{"firstName":"Monica","lastName":"Dhillon","address":"replace-me","city":"replace-me","state":"IL","zipCode":"replace-me"}'
# PCM_POSTCARD_PRICE_STANDARD=0.55
# PCM_POSTCARD_PRICE_FIRSTCLASS=0.89
# PCM_LIST_TYPE=IRL
# LOB_API_KEY= # only if POSTCARD_PROVIDER=lob
# CLICK2MAIL_API_KEY=
# POSTGRID_API_KEY=
@@ -90,13 +87,6 @@ OLLAMA_BASE_URL=http://10.0.0.128:11434
OLLAMA_MODEL=llama3.2
OLLAMA_TIMEOUT_SECONDS=120
# URL shortener — mint short links for campaign SMS / email / postcard QR.
# POST /api/links/ on the same host phones hit. Recipients use https://piha.lc/<code>.
# Token must match SHORTENER_API_TOKENS on url_shortening_service (name:secret).
# Generate: python -c "import secrets; print(secrets.token_urlsafe(32))"
SHORTENER_BASE_URL=https://piha.lc
SHORTENER_API_TOKEN=monica:replace-me
# Nominatim address suggest (server-side proxy only; not called from browser)
NOMINATIM_BASE_URL=http://10.0.0.128:8089
NOMINATIM_TIMEOUT_SECONDS=8
@@ -133,5 +123,3 @@ GUNICORN_BIND=0.0.0.0:8000
# OLLAMA_BASE_URL=http://10.0.0.128:11434
# NOMINATIM_BASE_URL=http://10.0.0.128:8089
# NOMINATIM_COUNTRY_CODES=us
# SHORTENER_BASE_URL=https://beta.piha.li
# SHORTENER_API_TOKEN=monica:replace-with-a-different-token
+1 -2
View File
@@ -9,8 +9,7 @@ ENV PYTHONDONTWRITEBYTECODE=1 \
WORKDIR /app
RUN apt-get update \
&& apt-get install -y --no-install-recommends ca-certificates libpq5 \
&& update-ca-certificates \
&& 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
+4 -16
View File
@@ -25,14 +25,13 @@ uv sync
cd site
uv run python manage.py migrate
uv run python manage.py runserver
# optional: docker compose up # web + postgres + worker (hot reload)
# optional: docker compose up # web + postgres (hot reload)
# optional worker: docker compose --profile worker up worker
```
Docker compose mounts `./site` into the container and runs Django `runserver` when
`DJANGO_ENV=dev` (the default). The **worker** service polls `dispatch_due` every 15s so
scheduled campaigns and social posts fire without a manual command. Edit Python/templates
→ auto-reload; no image rebuild.
Rebuild only when Dockerfile, entrypoint scripts, or deps (`pyproject.toml`, `uv.lock`) change:
`DJANGO_ENV=dev` (the default). Edit Python/templates → auto-reload; no image rebuild.
Rebuild only when Dockerfile / deps (`pyproject.toml`, `uv.lock`) change:
```bash
docker compose up --build
@@ -74,17 +73,6 @@ Templates: `.env.prod.example` (full var list). Validate with:
./scripts/validate-env.sh ~/Documents/secrets/monica_site/monica_site_prod.env
```
Campaign UTM short links (piha.li) need **both** sides:
| File | Vars |
|------|------|
| `monica_site_prod.env` | `SHORTENER_BASE_URL=https://piha.lc` · `SHORTENER_API_TOKEN=monica:<secret>` |
| `monica_site_beta.env` | `SHORTENER_BASE_URL=https://beta.piha.li` · distinct `SHORTENER_API_TOKEN` |
| `url_shortening_service_prod.env` | `SHORTENER_API_TOKENS=monica:<same-secret>` · `SHORT_ALLOWED_HOSTS` includes `mkdrealtor.com` · `PUBLIC_SHORT_URL=https://piha.lc` |
| `url_shortening_service_beta.env` | matching beta token · `PUBLIC_SHORT_URL=https://beta.piha.li` |
Generate the secret with `python -c "import secrets; print(secrets.token_urlsafe(32))"`. Do not put it in git. Mint and redirects share the short host; Bearer is required on `/api/`.
## Deploy
CI on merge to `main` → tests → `server-infra/scripts/deploy.sh --app monica_site --env prod --ref <sha>`.
-1
View File
@@ -4,7 +4,6 @@
# 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
# Worker also polls dispatch_due so scheduled campaigns/social posts fire.
services:
web:
build: .
+1 -12
View File
@@ -58,20 +58,16 @@ services:
PCM_RETURN_CITY: ${PCM_RETURN_CITY:-}
PCM_RETURN_STATE: ${PCM_RETURN_STATE:-}
PCM_RETURN_ZIP: ${PCM_RETURN_ZIP:-}
PCM_POSTCARD_PRICE_FIRSTCLASS: ${PCM_POSTCARD_PRICE_FIRSTCLASS:-}
PCM_POSTCARD_PRICE_STANDARD: ${PCM_POSTCARD_PRICE_STANDARD:-}
PCM_LIST_TYPE: ${PCM_LIST_TYPE:-IRL}
META_APP_ID: ${META_APP_ID:-}
META_APP_SECRET: ${META_APP_SECRET:-}
SOCIAL_TOKEN_ENCRYPTION_KEY: ${SOCIAL_TOKEN_ENCRYPTION_KEY:-}
SHORTENER_BASE_URL: ${SHORTENER_BASE_URL:-}
SHORTENER_API_TOKEN: ${SHORTENER_API_TOKEN:-}
depends_on:
db:
condition: service_healthy
worker:
build: .
profiles: ["worker"]
entrypoint: ["/worker-entrypoint.sh"]
volumes:
- ./site:/app/site
@@ -81,8 +77,6 @@ services:
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}
PUBLIC_SITE_URL: ${PUBLIC_SITE_URL:-http://127.0.0.1:8000}
DISPATCH_DUE_INTERVAL_SECONDS: ${DISPATCH_DUE_INTERVAL_SECONDS:-15}
EMAIL_HOST: ${EMAIL_HOST:-mail.smtp2go.com}
EMAIL_HOST_USER: ${EMAIL_HOST_USER:-}
EMAIL_HOST_PASSWORD: ${EMAIL_HOST_PASSWORD:-}
@@ -104,14 +98,9 @@ services:
PCM_RETURN_CITY: ${PCM_RETURN_CITY:-}
PCM_RETURN_STATE: ${PCM_RETURN_STATE:-}
PCM_RETURN_ZIP: ${PCM_RETURN_ZIP:-}
PCM_POSTCARD_PRICE_FIRSTCLASS: ${PCM_POSTCARD_PRICE_FIRSTCLASS:-}
PCM_POSTCARD_PRICE_STANDARD: ${PCM_POSTCARD_PRICE_STANDARD:-}
PCM_LIST_TYPE: ${PCM_LIST_TYPE:-IRL}
META_APP_ID: ${META_APP_ID:-}
META_APP_SECRET: ${META_APP_SECRET:-}
SOCIAL_TOKEN_ENCRYPTION_KEY: ${SOCIAL_TOKEN_ENCRYPTION_KEY:-}
SHORTENER_BASE_URL: ${SHORTENER_BASE_URL:-}
SHORTENER_API_TOKEN: ${SHORTENER_API_TOKEN:-}
depends_on:
db:
condition: service_healthy
-2
View File
@@ -28,8 +28,6 @@ if [[ "$DJANGO_ENV" == "prod" || "$DJANGO_ENV" == "beta" ]]; then
RECAPTCHA_PRIVATE_KEY
EMAIL_HOST_USER
EMAIL_HOST_PASSWORD
SHORTENER_BASE_URL
SHORTENER_API_TOKEN
)
fi
+1 -20
View File
@@ -47,24 +47,5 @@ uv run python manage.py migrate --noinput
# worker must too or hashed lookups raise ValueError.
uv run python manage.py collectstatic --noinput
interval="${DISPATCH_DUE_INTERVAL_SECONDS:-15}"
echo "Starting dispatch_due poller (every ${interval}s)."
uv run python manage.py dispatch_due --loop --interval "${interval}" &
dispatch_pid=$!
cleanup() {
echo "Stopping dispatch_due poller..."
kill "${dispatch_pid}" 2>/dev/null || true
wait "${dispatch_pid}" 2>/dev/null || true
}
trap cleanup EXIT INT TERM
# Dev uses ImmediateBackend: enqueue runs inline in dispatch_due. No dj-queue.
if [[ "${DJANGO_ENV:-}" == "dev" ]]; then
echo "Dev worker: dispatch_due loop publishes scheduled work."
wait "${dispatch_pid}"
exit $?
fi
# dj-queue supervisor (workers + dispatcher + scheduler). Run on ONE host only.
uv run python manage.py dj_queue
exec uv run python manage.py dj_queue
+1 -10
View File
@@ -1,15 +1,6 @@
from django.contrib import admin
from analytics.models import Attribution, PageView, UTMVisit
@admin.register(PageView)
class PageViewAdmin(admin.ModelAdmin):
list_display = ("path", "created_at")
list_filter = ("created_at",)
search_fields = ("path",)
date_hierarchy = "created_at"
readonly_fields = ("id", "path", "created_at", "updated_at")
from analytics.models import Attribution, UTMVisit
@admin.register(UTMVisit)
+1 -45
View File
@@ -1,54 +1,10 @@
import logging
from django.utils.crypto import get_random_string
from analytics.models import Attribution, PageView, UTMVisit
from analytics.models import Attribution, UTMVisit
logger = logging.getLogger(__name__)
CORRELATION_COOKIE = "ms_cid"
# Portal, auth, health, assets, and transactional public endpoints.
_SKIP_PREFIXES = (
"/portal/",
"/admin/",
"/accounts/",
"/api/",
"/healthz",
"/static/",
"/media/",
"/unsubscribe/",
)
_SKIP_PATHS = frozenset({"/robots.txt", "/sitemap.xml", "/favicon.ico"})
class PublicPageViewMiddleware:
"""Record successful GET hits on public marketing pages."""
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
response = self.get_response(request)
if _should_record_page_view(request, response):
try:
PageView.objects.create(path=request.path[:512])
except Exception:
logger.exception("Failed to record page view for %s", request.path)
return response
def _should_record_page_view(request, response) -> bool:
if request.method != "GET":
return False
if getattr(response, "status_code", 0) != 200:
return False
path = request.path
if path in _SKIP_PATHS:
return False
return not any(path.startswith(prefix) for prefix in _SKIP_PREFIXES)
class UTMTrackingMiddleware:
"""Capture UTM params into UTMVisit and stash a correlation id cookie."""
@@ -1,27 +0,0 @@
# Generated by Django 6.1 on 2026-08-13 12:38
import uuid
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('analytics', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='PageView',
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)),
('path', models.CharField(db_index=True, max_length=512)),
],
options={
'ordering': ['-created_at'],
'indexes': [models.Index(fields=['created_at', 'path'], name='analytics_p_created_9e8b64_idx')],
},
),
]
-15
View File
@@ -4,21 +4,6 @@ from core.models import TimeStampedModel, UUIDPrimaryKeyModel
from leads.models import Lead
class PageView(UUIDPrimaryKeyModel, TimeStampedModel):
"""One successful GET of a public marketing page."""
path = models.CharField(max_length=512, db_index=True)
class Meta:
ordering = ["-created_at"]
indexes = [
models.Index(fields=["created_at", "path"]),
]
def __str__(self) -> str:
return self.path or "/"
class UTMVisit(UUIDPrimaryKeyModel, TimeStampedModel):
correlation_id = models.CharField(max_length=64, db_index=True)
path = models.CharField(max_length=512, blank=True)
+2 -28
View File
@@ -2,37 +2,11 @@
{% block title %}Analytics · Portal{% endblock %}
{% block topbar_title %}Analytics{% endblock %}
{% block portal_content %}
<div class="analytics-overview">
<div class="stat-row">
<div class="stat-card">
<div class="label">Views (last 30 days)</div>
<div class="value">{{ pageviews_last_30_days }}</div>
<div class="value">{{ visits_last_30_days }}</div>
</div>
<div class="panel">
<div class="panel-h"><h2>Top pages</h2><span class="muted" style="font-size:12px">Public visits, last 30 days</span></div>
<div class="panel-b" style="padding:0">
<table class="table">
<thead>
<tr>
<th>Page</th>
<th>Visits</th>
</tr>
</thead>
<tbody>
{% for row in top_pages %}
<tr>
<td>{{ row.path }}</td>
<td>{{ row.count }}</td>
</tr>
{% empty %}
<tr><td colspan="2" class="empty-state">No public page views in the last 30 days.</td></tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
<div class="stat-row">
<div class="stat-card">
<div class="label">UTM landings</div>
<div class="value">{{ total_visits }}</div>
-84
View File
@@ -1,84 +0,0 @@
from datetime import timedelta
from django.contrib.auth import get_user_model
from django.test import Client, TestCase
from django.urls import reverse
from django.utils import timezone
from analytics.models import PageView, UTMVisit
class PublicPageViewTests(TestCase):
def setUp(self):
self.client = Client()
def test_home_records_page_view(self):
response = self.client.get("/")
self.assertEqual(response.status_code, 200)
self.assertEqual(PageView.objects.count(), 1)
self.assertEqual(PageView.objects.get().path, "/")
def test_about_records_page_view(self):
self.client.get(reverse("public:about"))
self.assertEqual(PageView.objects.filter(path="/about/").count(), 1)
def test_plain_visit_does_not_create_utm_visit(self):
self.client.get("/")
self.assertEqual(PageView.objects.count(), 1)
self.assertEqual(UTMVisit.objects.count(), 0)
def test_utm_hit_records_both(self):
self.client.get("/?utm_source=test&utm_campaign=demo")
self.assertEqual(PageView.objects.count(), 1)
self.assertEqual(UTMVisit.objects.count(), 1)
def test_healthz_not_recorded(self):
self.client.get("/healthz/")
self.assertEqual(PageView.objects.count(), 0)
def test_portal_and_admin_not_recorded(self):
self.client.get("/portal/")
self.client.get("/admin/")
self.assertEqual(PageView.objects.count(), 0)
def test_robots_and_sitemap_not_recorded(self):
self.client.get("/robots.txt")
self.client.get("/sitemap.xml")
self.assertEqual(PageView.objects.count(), 0)
def test_missing_page_not_recorded(self):
response = self.client.get("/not-a-real-page/")
self.assertEqual(response.status_code, 404)
self.assertEqual(PageView.objects.count(), 0)
def test_contact_post_not_recorded(self):
self.client.post(reverse("public:contact"), {})
self.assertEqual(PageView.objects.count(), 0)
class AnalyticsReportTests(TestCase):
def setUp(self):
self.client = Client()
user = get_user_model().objects.create_user(
username="monica", password="pass-word-1"
)
self.client.force_login(user)
def test_views_card_uses_public_pageviews(self):
self.client.get("/")
self.client.get(reverse("public:about"))
self.client.get(reverse("public:about"))
stale = PageView.objects.create(path="/terms/")
PageView.objects.filter(pk=stale.pk).update(
created_at=timezone.now() - timedelta(days=31)
)
response = self.client.get(reverse("analytics:report"))
self.assertEqual(response.status_code, 200)
self.assertEqual(response.context["pageviews_last_30_days"], 3)
top = {row["path"]: row["count"] for row in response.context["top_pages"]}
self.assertEqual(top["/about/"], 2)
self.assertEqual(top["/"], 1)
self.assertNotIn("/terms/", top)
self.assertContains(response, "Top pages")
self.assertContains(response, "/about/")
+3 -8
View File
@@ -5,7 +5,7 @@ from django.db.models import Count
from django.shortcuts import render
from django.utils import timezone
from analytics.models import Attribution, PageView, UTMVisit
from analytics.models import Attribution, UTMVisit
def _bar_pct(rows, key="count"):
@@ -18,11 +18,7 @@ def _bar_pct(rows, key="count"):
@login_required
def report(request):
since_30d = timezone.now() - timedelta(days=30)
pageviews_qs = PageView.objects.filter(created_at__gte=since_30d)
pageviews_last_30_days = pageviews_qs.count()
top_pages = list(
pageviews_qs.values("path").annotate(count=Count("id")).order_by("-count")[:20]
)
visits_last_30_days = UTMVisit.objects.filter(created_at__gte=since_30d).count()
visits_by_source = _bar_pct(
list(
@@ -64,8 +60,7 @@ def report(request):
request,
"analytics/report.html",
{
"pageviews_last_30_days": pageviews_last_30_days,
"top_pages": top_pages,
"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)",
+1 -33
View File
@@ -1,5 +1,3 @@
import time
from django.core.management.base import BaseCommand
from django.utils import timezone
@@ -12,37 +10,10 @@ from social.tasks import publish_social_post
class Command(BaseCommand):
help = (
"Enqueue due scheduled campaign messages and social posts. "
"Pass --loop to poll until stopped (worker entrypoint)."
)
def add_arguments(self, parser):
parser.add_argument(
"--loop",
action="store_true",
help="Poll forever until SIGINT/SIGTERM.",
)
parser.add_argument(
"--interval",
type=int,
default=15,
help="Seconds between polls when --loop is set (default 15).",
"Optional when the task backend supports run_after defer; useful as a safety net."
)
def handle(self, *args, **options):
self.verbosity = int(options.get("verbosity", 1))
if options["loop"]:
interval = max(1, int(options["interval"] or 15))
self.stdout.write(f"Polling due scheduled work every {interval}s.")
try:
while True:
self._dispatch_once()
time.sleep(interval)
except KeyboardInterrupt:
self.stdout.write("dispatch_due loop stopped.")
return
self._dispatch_once()
def _dispatch_once(self):
now = timezone.now()
enqueued = 0
@@ -64,7 +35,4 @@ class Command(BaseCommand):
publish_social_post.enqueue(post_id=str(post.pk))
enqueued += 1
if enqueued:
self.stdout.write(self.style.SUCCESS(f"Enqueued {enqueued} due item(s)."))
elif self.verbosity >= 2:
self.stdout.write("No due items.")
-48
View File
@@ -1,13 +1,5 @@
from datetime import timedelta
from io import StringIO
from unittest.mock import patch
from django.core.management import call_command
from django.test import Client, TestCase, override_settings
from django.urls import reverse
from django.utils import timezone
from social.models import SocialPost
class HealthzTests(TestCase):
@@ -35,43 +27,3 @@ class PublicSmokeTests(TestCase):
client = Client()
self.assertEqual(client.get(reverse("public:about")).status_code, 200)
self.assertEqual(client.get(reverse("public:contact")).status_code, 200)
class DispatchDueTests(TestCase):
def test_enqueues_past_scheduled_social_post(self):
post = SocialPost.objects.create(
body="Open house",
status=SocialPost.Status.SCHEDULED,
scheduled_for=timezone.now() - timedelta(minutes=5),
)
mock_task = patch(
"core.management.commands.dispatch_due.publish_social_post"
).start()
self.addCleanup(patch.stopall)
out = StringIO()
call_command("dispatch_due", stdout=out)
post.refresh_from_db()
self.assertEqual(post.status, SocialPost.Status.QUEUED)
mock_task.enqueue.assert_called_once_with(post_id=str(post.pk))
self.assertIn("Enqueued 1 due item(s).", out.getvalue())
def test_skips_future_scheduled_social_post(self):
post = SocialPost.objects.create(
body="Later",
status=SocialPost.Status.SCHEDULED,
scheduled_for=timezone.now() + timedelta(hours=1),
)
mock_task = patch(
"core.management.commands.dispatch_due.publish_social_post"
).start()
self.addCleanup(patch.stopall)
call_command("dispatch_due", stdout=StringIO())
post.refresh_from_db()
self.assertEqual(post.status, SocialPost.Status.SCHEDULED)
mock_task.enqueue.assert_not_called()
def test_quiet_when_nothing_due(self):
out = StringIO()
call_command("dispatch_due", stdout=out)
self.assertEqual(out.getvalue(), "")
-38
View File
@@ -35,23 +35,6 @@ webhook events match the correct recipient row. Invalid / missing header values
no longer 500 the endpoint (SMTP2GO “Test this webhook” often sends a sample
non-UUID).
## Campaign tracked links (UTM + piha.li)
Composer auto-inserts a homepage link:
| Param | Value |
|-------|--------|
| `utm_source` | `monica` |
| `utm_medium` | `email` / `sms` / `postcard` |
| `utm_campaign` | hyphenated campaign name |
When `SHORTENER_BASE_URL` + `SHORTENER_API_TOKEN` are set, the app POSTs that
long HTTPS URL to `url_shortening_service` (`POST /api/links/`, Bearer
`monica:<secret>`) and puts `short_url` (`https://piha.li/<code>`) in SMS,
email hrefs, and postcard QR codes. Empty env → long UTM URL (local default).
Mint against the short host (`https://piha.lc` / `https://beta.piha.li`). See `.env.example` / `.env.prod.example`.
SMS correlation uses `message_id` (SMS id), then `destination_number` phone
fallback. Do **not** treat webhook `id` as the SMS id.
@@ -110,27 +93,6 @@ HMAC-SHA256 of body) matching **any** listed secret.
Correlation: we send `extRefNbr=<Message.uuid>` on each recipient; webhooks should
echo that (or `orderID`, matched to `Message.provider_message_id`).
### Carrier-route (neighborhood) mailings
Postcard compose can target **mailing-list contacts** or **USPS carrier routes**.
Route mode calls PCM `POST /list/count/zipcode` (or `/radius`) with
`breakdownType=ZipCRRT`, then recounts selected routes via
`POST /list/count/carrier-route` and sends
`POST /order/postcard/with-list-count`. Estimated cost uses design `pricing[]`
or `PCM_POSTCARD_PRICE_FIRSTCLASS` / `PCM_POSTCARD_PRICE_STANDARD`. Default
purchased list type is `IRL` (`PCM_LIST_TYPE`). This is not official EDDM postage.
Compose also joins USPS EDDM GIS stats (businesses; household size / median
income when USPS fills them) and links to
[the EDDM map](https://eddm.usps.com/eddm/select-routes.htm) for the official
route table.
| Extra env | Purpose |
|-----------|---------|
| `PCM_POSTCARD_PRICE_FIRSTCLASS` | Fallback $/piece if PCM omits FirstClass pricing |
| `PCM_POSTCARD_PRICE_STANDARD` | Fallback $/piece for Standard |
| `PCM_LIST_TYPE` | PCM list type key (default `IRL`) |
### Campaign completion email
When a campaign reaches **completed** (email, SMS, or postcard), one summary email
@@ -1,41 +0,0 @@
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("messaging", "0006_storedfile_social_kinds"),
]
operations = [
migrations.AddField(
model_name="campaign",
name="route_targeting",
field=models.JSONField(blank=True, default=dict),
),
migrations.AlterField(
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"),
("carrier_route", "USPS carrier routes"),
],
default="",
max_length=32,
),
),
migrations.AlterField(
model_name="message",
name="contact",
field=models.ForeignKey(
blank=True,
null=True,
on_delete=models.CASCADE,
related_name="messages",
to="contacts.contact",
),
),
]
+2 -6
View File
@@ -29,7 +29,6 @@ class Campaign(UUIDPrimaryKeyModel, TimeStampedModel):
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"
CARRIER_ROUTE = "carrier_route", "USPS carrier routes"
name = models.CharField(max_length=120)
channel = models.CharField(max_length=16, choices=Channel.choices)
@@ -60,8 +59,6 @@ class Campaign(UUIDPrimaryKeyModel, TimeStampedModel):
body_override = models.TextField(blank=True)
# Set when realtor summary email is sent (campaign COMPLETED).
notify_sent_at = models.DateTimeField(null=True, blank=True)
# PCM purchased-list targeting (ZIP/CRRT codes, listCountID, quote).
route_targeting = models.JSONField(default=dict, blank=True)
class Meta:
ordering = ["-created_at"]
@@ -87,7 +84,7 @@ class Message(UUIDPrimaryKeyModel, TimeStampedModel):
Campaign, on_delete=models.CASCADE, related_name="messages"
)
contact = models.ForeignKey(
Contact, null=True, blank=True, on_delete=models.CASCADE, related_name="messages"
Contact, on_delete=models.CASCADE, related_name="messages"
)
channel = models.CharField(max_length=16, choices=Channel.choices)
status = models.CharField(
@@ -104,8 +101,7 @@ class Message(UUIDPrimaryKeyModel, TimeStampedModel):
ordering = ["-created_at"]
def __str__(self) -> str:
who = self.contact or "carrier-route mailing"
return f"{self.channel} → {who} ({self.status})"
return f"{self.channel} → {self.contact} ({self.status})"
class ProviderEvent(TimeStampedModel):
+6 -247
View File
@@ -338,210 +338,6 @@ def get_order(order_id: int | str) -> dict[str, Any]:
return data if isinstance(data, dict) else {}
def get_design(design_id: int | str) -> dict[str, Any]:
data = pcm_request("GET", f"/design/{design_id}")
return data if isinstance(data, dict) else {}
FIRST_CLASS_ONLY_SIZES = frozenset({"46", "46S"})
MAX_SELECTED_ROUTES = 2
DEFAULT_LIST_TYPE = "IRL"
def mail_class_for_size(size: str | None) -> str:
key = str(size or "").strip()
if key in FIRST_CLASS_ONLY_SIZES:
return "FirstClass"
return "Standard"
def _env_unit_price(mail_class: str) -> float | None:
attr = (
"PCM_POSTCARD_PRICE_FIRSTCLASS"
if mail_class == "FirstClass"
else "PCM_POSTCARD_PRICE_STANDARD"
)
raw = str(getattr(settings, attr, "") or "").strip()
if not raw:
return None
try:
value = float(raw)
except ValueError:
return None
return value if value >= 0 else None
def pick_unit_price(
pricing: list[dict] | None,
*,
mail_class: str,
quantity: int,
) -> tuple[float | None, str]:
"""Return (dollars per piece, source). Source is pcm|fallback|missing."""
rows: list[dict] = []
for item in pricing or []:
if not isinstance(item, dict):
continue
if str(item.get("mailClass") or item.get("mail_class") or "") != mail_class:
continue
try:
price = float(item.get("price"))
break_qty = int(item.get("breakQty") or item.get("break_qty") or 0)
except (TypeError, ValueError):
continue
rows.append({"price": price, "breakQty": break_qty})
qty = max(int(quantity or 0), 0)
if rows:
eligible = [row for row in rows if qty >= row["breakQty"]]
chosen = max(eligible, key=lambda row: row["breakQty"]) if eligible else min(
rows, key=lambda row: row["breakQty"]
)
return chosen["price"], "pcm"
fallback = _env_unit_price(mail_class)
if fallback is not None:
return fallback, "fallback"
return None, "missing"
def design_pricing(design_id: int | None) -> list[dict]:
"""Load PCM per-piece pricing for a design (gallery payload preferred)."""
if not design_id:
return []
try:
design = get_design(design_id)
except PcmApiError:
design = {}
pricing = design.get("pricing") if isinstance(design, dict) else None
if isinstance(pricing, list) and pricing:
return [row for row in pricing if isinstance(row, dict)]
try:
for item in list_designs(product_type="postcard"):
if not isinstance(item, dict):
continue
did = item.get("designID") or item.get("design_id") or item.get("id")
if str(did) != str(design_id):
continue
extra = item.get("pricing")
if isinstance(extra, list):
return [row for row in extra if isinstance(row, dict)]
except PcmApiError:
logger.exception("PCM list_designs failed while loading pricing")
return []
def extract_crrt_rows(payload: dict[str, Any]) -> list[dict[str, Any]]:
"""Flatten ZipCRRT (or first) breakdown into {code, text, total} rows."""
breakdowns = payload.get("breakdown") if isinstance(payload, dict) else None
if not isinstance(breakdowns, list):
return []
preferred = None
fallback = None
for block in breakdowns:
if not isinstance(block, dict):
continue
kind = str(block.get("breakdownType") or "").upper().replace("_", "")
data = block.get("data") if isinstance(block.get("data"), list) else []
rows = []
for item in data:
if not isinstance(item, dict):
continue
code = str(item.get("code") or "").strip()
if not code:
continue
try:
total = int(item.get("total") or 0)
except (TypeError, ValueError):
total = 0
rows.append(
{
"code": code,
"text": str(item.get("text") or code).strip(),
"total": total,
}
)
if not rows:
continue
if kind in {"ZIPCRRT", "CRRT", "CARRIERROUTE"}:
preferred = rows
break
if fallback is None:
fallback = rows
return preferred or fallback or []
def _list_type() -> str:
return (getattr(settings, "PCM_LIST_TYPE", None) or DEFAULT_LIST_TYPE).strip() or DEFAULT_LIST_TYPE
def list_count_zipcode(*, zip_codes: list[str], list_type: str | None = None) -> dict[str, Any]:
data = pcm_request(
"POST",
"/list/count/zipcode",
json_body={
"listType": list_type or _list_type(),
"breakdownType": "ZipCRRT",
"zipCodes": zip_codes,
"demographics": [],
},
)
return data if isinstance(data, dict) else {}
def list_count_radius(
*,
address: str,
city: str,
state: str,
zip_code: str,
miles: float,
list_type: str | None = None,
) -> dict[str, Any]:
data = pcm_request(
"POST",
"/list/count/radius",
json_body={
"listType": list_type or _list_type(),
"breakdownType": "ZipCRRT",
"radius": {
"radius": miles,
"address": address,
"city": city,
"state": state,
"zip": zip_code,
},
"demographics": [],
},
)
return data if isinstance(data, dict) else {}
def list_count_carrier_route(
*,
carrier_routes: list[str],
list_type: str | None = None,
) -> dict[str, Any]:
data = pcm_request(
"POST",
"/list/count/carrier-route",
json_body={
"listType": list_type or _list_type(),
"breakdownType": "ZipCRRT",
"carrierRoutes": carrier_routes,
"demographics": [],
},
)
return data if isinstance(data, dict) else {}
def _order_id_from_response(data: dict[str, Any]) -> str:
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 place_postcard_order(
*,
design_id: int,
@@ -562,32 +358,13 @@ def place_postcard_order(
data = pcm_request("POST", "/order/postcard", json_body=payload)
if not isinstance(data, dict):
raise PcmApiError("Unexpected order response from PCM")
return _order_id_from_response(data)
def place_postcard_order_with_list_count(
*,
design_id: int,
list_count_id: int,
record_count: int,
ext_ref: str,
mail_class: str = "Standard",
) -> str:
"""Place a postcard order against a PCM purchased list count."""
payload = {
"designID": design_id,
"mailClass": mail_class,
"extRefNbr": ext_ref,
"listCountID": list_count_id,
"recordCount": record_count,
"returnAddress": return_address_from_settings(),
}
data = pcm_request(
"POST", "/order/postcard/with-list-count", json_body=payload
)
if not isinstance(data, dict):
raise PcmApiError("Unexpected list-count order response from PCM")
return _order_id_from_response(data)
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:
@@ -619,24 +396,6 @@ class PcmProvider:
"(save a design from the postcard designer first)"
)
targeting = {}
if message.campaign_id and isinstance(
getattr(message.campaign, "route_targeting", None), dict
):
targeting = message.campaign.route_targeting
list_count_id = targeting.get("list_count_id")
record_count = targeting.get("record_count")
if list_count_id and record_count:
mail_class = str(targeting.get("mail_class") or "Standard")
order_id = place_postcard_order_with_list_count(
design_id=design_id,
list_count_id=int(list_count_id),
record_count=int(record_count),
ext_ref=str(message.pk),
mail_class=mail_class,
)
return PostcardResult(provider_id=order_id)
recipient = contact_to_pcm_recipient(
message.contact, ext_ref=str(message.pk)
)
@@ -1,151 +0,0 @@
"""USPS EDDM map link + public GIS stats for carrier-route compose."""
from __future__ import annotations
import logging
import time
from typing import Any
import requests
logger = logging.getLogger(__name__)
USPS_EDDM_MAP_URL = "https://eddm.usps.com/eddm/select-routes.htm"
USPS_EDDM_GIS_URL = (
"https://gis.usps.com/arcgis/rest/services/EDDM/selectZIP/GPServer/routes/execute"
)
_CACHE_TTL_SECONDS = 1800
_cache: dict[str, tuple[float, dict[str, dict[str, Any]]]] = {}
def zip5(raw: str | None) -> str:
digits = "".join(ch for ch in str(raw or "") if ch.isdigit())
return digits[:5] if len(digits) >= 5 else ""
def pcm_code_from_zip_crid(*, zip_code: str, crid_id: str) -> str:
return f"{zip_code}:{crid_id}"
def zips_from_route_codes(codes: list[str], *, hint: str = "") -> list[str]:
found: list[str] = []
z = zip5(hint)
if z:
found.append(z)
for code in codes:
z = zip5(str(code).split(":", 1)[0])
if z:
found.append(z)
return list(dict.fromkeys(found))
def _as_int(raw: Any) -> int | None:
if raw in (None, ""):
return None
try:
return int(float(raw))
except (TypeError, ValueError):
return None
def _as_float(raw: Any) -> float | None:
if raw in (None, ""):
return None
try:
return float(raw)
except (TypeError, ValueError):
return None
def _stats_from_attrs(attrs: dict[str, Any]) -> dict[str, Any] | None:
zip_code = zip5(attrs.get("ZIP_CODE"))
crid = str(attrs.get("CRID_ID") or "").strip().upper()
if not zip_code or not crid:
zip_crid = str(attrs.get("ZIP_CRID") or "").strip().upper()
if len(zip_crid) >= 9:
zip_code = zip_crid[:5]
crid = zip_crid[5:]
if not zip_code or not crid:
return None
return {
"code": pcm_code_from_zip_crid(zip_code=zip_code, crid_id=crid),
"businesses": _as_int(attrs.get("BUS_CNT")),
"residential": _as_int(attrs.get("RES_CNT")),
"usps_total": _as_int(attrs.get("TOT_CNT")),
"household_size": _as_float(attrs.get("AVG_HH_SIZ")),
"median_income": _as_int(attrs.get("MED_INCOME")),
"city_state": str(attrs.get("CITY_STATE") or "").strip(),
}
def _fetch_one_zip(zip_code: str) -> dict[str, dict[str, Any]]:
response = requests.get(
USPS_EDDM_GIS_URL,
params={"f": "json", "ZIP": zip_code, "Rte_Box": "R", "UserName": "EDDM"},
timeout=25,
headers={"Accept": "application/json"},
)
response.raise_for_status()
payload = response.json()
results = payload.get("results") if isinstance(payload, dict) else None
if not isinstance(results, list) or not results:
return {}
value = results[0].get("value") if isinstance(results[0], dict) else None
features = value.get("features") if isinstance(value, dict) else None
if not isinstance(features, list):
return {}
out: dict[str, dict[str, Any]] = {}
for feature in features:
if not isinstance(feature, dict):
continue
attrs = feature.get("attributes")
if not isinstance(attrs, dict):
continue
stats = _stats_from_attrs(attrs)
if not stats:
continue
out[stats["code"]] = stats
return out
def fetch_eddm_route_stats(zip_codes: list[str]) -> dict[str, dict[str, Any]]:
"""ZIP → PCM-style ``60540:C001`` stats from USPS EDDM GIS (fail open)."""
out: dict[str, dict[str, Any]] = {}
now = time.monotonic()
for zip_code in zip_codes:
z = zip5(zip_code)
if not z:
continue
cached = _cache.get(z)
if cached and now - cached[0] < _CACHE_TTL_SECONDS:
out.update(cached[1])
continue
try:
part = _fetch_one_zip(z)
except Exception: # noqa: BLE001 — compose still works without GIS
logger.exception("USPS EDDM GIS lookup failed for ZIP %s", z)
part = {}
_cache[z] = (now, part)
out.update(part)
return out
def enrich_crrt_rows(rows: list[dict[str, Any]], *, zip_hint: str = "") -> list[dict[str, Any]]:
"""Attach USPS businesses / size / income onto PCM ZipCRRT rows."""
codes = [str(row.get("code") or "") for row in rows]
stats = fetch_eddm_route_stats(zips_from_route_codes(codes, hint=zip_hint))
enriched = []
for row in rows:
extra = stats.get(str(row.get("code") or ""), {})
merged = {
**row,
"businesses": extra.get("businesses"),
"residential": extra.get("residential"),
"household_size": extra.get("household_size"),
"median_income": extra.get("median_income"),
}
if extra.get("city_state") and not (row.get("text") or "").strip():
merged["text"] = extra["city_state"]
enriched.append(merged)
return enriched
-404
View File
@@ -2,13 +2,10 @@
from __future__ import annotations
import html
import re
from datetime import datetime
from typing import TYPE_CHECKING
from urllib.parse import urlencode
from django.conf import settings
from django.core import signing
from django.db.models import QuerySet
from django.urls import reverse
@@ -16,7 +13,6 @@ from django.utils import timezone
from contacts.models import Channel, ConsentRecord, Contact, Suppression
from messaging.models import Campaign, Message, MessageTemplate
from messaging.shortener import resolve_display_url
if TYPE_CHECKING:
from django.contrib.auth.models import AbstractBaseUser
@@ -25,7 +21,6 @@ AUDIENCE_CHANNEL = {
Campaign.Audience.EMAIL_OPT_IN: Channel.EMAIL,
Campaign.Audience.SMS_OPT_IN: Channel.SMS,
Campaign.Audience.POSTCARD_OPT_IN: Channel.POSTCARD,
Campaign.Audience.CARRIER_ROUTE: Channel.POSTCARD,
}
UNSUB_SALT = "monica-site-unsubscribe"
@@ -38,21 +33,6 @@ _MERGE_TAG_RE = re.compile(
re.IGNORECASE,
)
# Campaign tracked links: utm_source is always the brand; medium = channel.
UTM_SOURCE = "monica"
UTM_CAMPAIGN_SLUG_MAX = 80
_UTM_ANCHOR_RE = re.compile(r"(<a\b[^>]*>)(.*?)(</a>)", re.IGNORECASE | re.DOTALL)
_UTM_TEXT_URL_RE = re.compile(
r"https?://[^\s<>\"]+utm_source=" + re.escape(UTM_SOURCE) + r"[^\s<>\"]*",
re.IGNORECASE,
)
# Minted short links in SMS (piha.li prod, beta.piha.li, local compose).
_SHORT_TEXT_URL_RE = re.compile(
r"https?://(?:(?:www\.)?(?:beta\.)?piha\.li|(?:127\.0\.0\.1|localhost):\d+)"
r"/[a-z0-9]{4,8}",
re.IGNORECASE,
)
REMOVABLE_MESSAGE_STATUSES = frozenset(
{
Message.Status.DRAFT,
@@ -86,149 +66,6 @@ def render_merge_tags(text: str, contact: Contact | None) -> str:
return _MERGE_TAG_RE.sub(_replace, text)
def public_site_base_url() -> str:
"""Public homepage used as the UTM landing URL."""
base = (getattr(settings, "PUBLIC_SITE_URL", None) or "").rstrip("/")
if not base:
base = "https://mkdrealtor.com"
return base
def public_site_link_label() -> str:
"""Visible email-link text (host), with a stable label in local/dev."""
host = (
public_site_base_url()
.replace("https://", "")
.replace("http://", "")
.split("/")[0]
)
if host.startswith("127.") or host.startswith("localhost") or host.startswith("0.0.0.0"):
return "MKDRealtor.com"
return host or "MKDRealtor.com"
def campaign_utm_slug(name: str) -> str:
"""Lowercase hyphenated utm_campaign from the campaign name."""
slug = re.sub(r"[^a-z0-9]+", "-", (name or "").strip().lower())
slug = re.sub(r"-{2,}", "-", slug).strip("-")
return (slug or "campaign")[:UTM_CAMPAIGN_SLUG_MAX]
def build_campaign_utm_url(
*,
name: str,
medium: str,
base_url: str = "",
) -> str:
"""Homepage URL with utm_source / utm_medium / utm_campaign."""
base = (base_url or public_site_base_url()).rstrip("/")
channel = (medium or "").strip().lower()
if channel not in Channel.values:
channel = Channel.EMAIL
query = urlencode(
{
"utm_source": UTM_SOURCE,
"utm_medium": channel,
"utm_campaign": campaign_utm_slug(name),
}
)
return f"{base}/?{query}"
def campaign_shortener_ref(*, campaign_id=None, user_id=None, medium: str = "", name: str = "") -> str:
"""external_ref for the shortener (max 64). Prefer campaign UUID."""
if campaign_id:
return str(campaign_id)[:64]
slug = campaign_utm_slug(name)[:40]
ch = (medium or "email").strip().lower()[:8]
uid = "" if user_id is None else str(user_id)
return f"p{uid}-{ch}-{slug}"[:64]
def resolve_campaign_tracked_url(
*,
name: str,
medium: str,
campaign_id=None,
user_id=None,
shorten: bool = True,
) -> tuple[str, str]:
"""Return (long UTM URL, display URL). Display is short when mint works."""
target = build_campaign_utm_url(name=name, medium=medium)
if not shorten:
return target, target
display = resolve_display_url(
target,
title=f"{(name or 'campaign').strip()} ({medium})"[:200],
external_ref=campaign_shortener_ref(
campaign_id=campaign_id, user_id=user_id, medium=medium, name=name
),
)
return target, display
def _is_our_utm_anchor(attrs: str) -> bool:
lower = attrs.lower()
return "data-monica-utm" in lower or f"utm_source={UTM_SOURCE}" in lower
def ensure_campaign_utm_in_html(body: str, url: str, label: str) -> str:
"""Insert or refresh the tracked <a> in an HTML (email) body."""
href = html.escape(url, quote=True)
label_html = html.escape(label)
replaced = False
def _repl(match: re.Match[str]) -> str:
nonlocal replaced
if replaced or not _is_our_utm_anchor(match.group(1)):
return match.group(0)
replaced = True
inner = match.group(2) if (match.group(2) or "").strip() else label_html
return f'<a href="{href}" data-monica-utm="1">{inner}</a>'
out = _UTM_ANCHOR_RE.sub(_repl, body or "")
if replaced:
return out
anchor = f'<a href="{href}" data-monica-utm="1">{label_html}</a>'
text = (body or "").rstrip()
if text:
return f"{text}\n<p>{anchor}</p>"
return f"<p>{anchor}</p>"
def ensure_campaign_utm_in_text(body: str, url: str) -> str:
"""Insert or refresh the tracked URL in a plain-text (SMS) body."""
raw = body or ""
if _UTM_TEXT_URL_RE.search(raw):
return _UTM_TEXT_URL_RE.sub(url, raw, count=1)
if _SHORT_TEXT_URL_RE.search(raw):
return _SHORT_TEXT_URL_RE.sub(url, raw, count=1)
text = raw.rstrip()
return f"{text}\n\n{url}" if text else url
def ensure_campaign_utm_link(
body: str,
*,
name: str,
medium: str,
html: bool,
campaign_id=None,
shorten: bool | None = None,
) -> str:
"""Keep a tracked site link in the body, matching campaign name + channel."""
do_shorten = bool(campaign_id) if shorten is None else shorten
_target, url = resolve_campaign_tracked_url(
name=name,
medium=medium,
campaign_id=campaign_id,
shorten=do_shorten,
)
if html:
return ensure_campaign_utm_in_html(body, url, public_site_link_label())
return ensure_campaign_utm_in_text(body, url)
def message_is_removable(message: Message) -> bool:
return message.status in REMOVABLE_MESSAGE_STATUSES
@@ -382,223 +219,6 @@ def channel_for_audience(audience: str) -> str:
raise ValueError(f"Unknown audience: {audience}") from exc
def parse_route_targeting(raw: str | dict | None) -> dict:
"""Parse compose JSON for a carrier-route campaign."""
from messaging.providers.postcard.pcm import MAX_SELECTED_ROUTES
data: dict = {}
if isinstance(raw, dict):
data = raw
elif isinstance(raw, str) and raw.strip():
import json
try:
parsed = json.loads(raw)
except json.JSONDecodeError as exc:
raise ValueError("Carrier route selection is invalid JSON.") from exc
if not isinstance(parsed, dict):
raise ValueError("Carrier route selection must be an object.")
data = parsed
codes = [
str(code).strip()
for code in (data.get("selected_codes") or data.get("routes") or [])
if str(code).strip()
]
# Allow routes as list of objects from the quote endpoint.
if not codes and isinstance(data.get("routes"), list):
codes = [
str(row.get("code") or "").strip()
for row in data["routes"]
if isinstance(row, dict)
]
codes = [c for c in codes if c]
codes = list(dict.fromkeys(codes))
if not codes:
raise ValueError("Select one or two USPS carrier routes.")
if len(codes) > MAX_SELECTED_ROUTES:
raise ValueError("Pick at most two carrier routes.")
try:
list_count_id = int(data.get("list_count_id") or 0)
record_count = int(data.get("record_count") or 0)
except (TypeError, ValueError) as exc:
raise ValueError("Route quote is missing list count.") from exc
if list_count_id <= 0 or record_count <= 0:
raise ValueError("Find routes and select them before saving (need a PCM quote).")
estimated = data.get("estimated") if isinstance(data.get("estimated"), dict) else {}
route_rows = data.get("route_rows") if isinstance(data.get("route_rows"), list) else []
return {
"selected_codes": codes,
"list_count_id": list_count_id,
"record_count": record_count,
"mail_class": str(data.get("mail_class") or "Standard"),
"list_type": str(data.get("list_type") or "IRL"),
"zip": str(data.get("zip") or ""),
"estimated": estimated,
"route_rows": route_rows,
}
def money_total(per_piece: float | None, quantity: int) -> float | None:
if per_piece is None:
return None
return round(float(per_piece) * max(int(quantity or 0), 0), 2)
def postcard_quote(*, action: str, payload: dict) -> dict:
"""Search routes or price a mailing-list / selected-route postcard."""
from messaging.providers.postcard.pcm import (
MAX_SELECTED_ROUTES,
design_pricing,
extract_crrt_rows,
list_count_carrier_route,
list_count_radius,
list_count_zipcode,
mail_class_for_size,
pick_unit_price,
)
from messaging.providers.postcard.usps_eddm import (
USPS_EDDM_MAP_URL,
enrich_crrt_rows,
)
template_id = str(payload.get("template_id") or "").strip()
design_id = payload.get("design_id")
size = str(payload.get("size") or "").strip()
if template_id and not design_id:
# Views may pass a compose value t:<uuid> / d:<id>; ignore if unresolved.
pass
try:
design_id_int = int(design_id) if design_id not in (None, "") else None
except (TypeError, ValueError):
design_id_int = None
requested_class = str(payload.get("mail_class") or "").strip()
mail_class = requested_class or mail_class_for_size(size)
if mail_class not in {"FirstClass", "Standard"}:
mail_class = mail_class_for_size(size)
pricing = design_pricing(design_id_int)
def _price(qty: int) -> tuple[float | None, str]:
return pick_unit_price(pricing, mail_class=mail_class, quantity=qty)
action = (action or "mailing_list").strip()
if action == "mailing_list":
count = opted_in_contacts(Channel.POSTCARD).count()
per_piece, source = _price(count)
return {
"action": action,
"mail_class": mail_class,
"count": count,
"per_piece": per_piece,
"total": money_total(per_piece, count),
"price_source": source,
}
if action == "search":
zip_code = str(payload.get("zip") or "").strip()
address = str(payload.get("address") or "").strip()
city = str(payload.get("city") or "").strip()
state = str(payload.get("state") or "").strip()
try:
miles = float(payload.get("radius") or 0)
except (TypeError, ValueError):
miles = 0
if address and city and state and zip_code and miles > 0:
data = list_count_radius(
address=address,
city=city,
state=state,
zip_code=zip_code,
miles=miles,
)
elif zip_code:
data = list_count_zipcode(zip_codes=[zip_code])
else:
raise ValueError("Enter a ZIP code (or address + ZIP + radius).")
rows = enrich_crrt_rows(extract_crrt_rows(data), zip_hint=zip_code)
priced = []
for row in rows:
per_piece, source = _price(row["total"])
priced.append(
{
**row,
"per_piece": per_piece,
"cost": money_total(per_piece, row["total"]),
"price_source": source,
}
)
return {
"action": action,
"mail_class": mail_class,
"zip": zip_code,
"routes": priced,
"eddm_map_url": USPS_EDDM_MAP_URL,
"discovery_list_count_id": data.get("listCountID") or data.get("list_count_id"),
}
if action == "select":
codes = [
str(c).strip()
for c in (payload.get("routes") or [])
if str(c).strip()
]
codes = list(dict.fromkeys(codes))
if not codes:
raise ValueError("Select at least one carrier route.")
if len(codes) > MAX_SELECTED_ROUTES:
raise ValueError("Pick at most two carrier routes.")
data = list_count_carrier_route(carrier_routes=codes)
rows = extract_crrt_rows(data)
if not rows:
# PCM may return a single total without per-route breakdown.
try:
record_count = int(data.get("recordCount") or data.get("record_count") or 0)
except (TypeError, ValueError):
record_count = 0
rows = [{"code": code, "text": code, "total": 0} for code in codes]
if record_count and len(rows) == 1:
rows[0]["total"] = record_count
rows = enrich_crrt_rows(rows, zip_hint=codes[0] if codes else "")
record_count = int(data.get("recordCount") or data.get("record_count") or 0)
if not record_count:
record_count = sum(int(r.get("total") or 0) for r in rows)
per_piece, source = _price(record_count)
priced = []
for row in rows:
total = int(row.get("total") or 0)
priced.append(
{
**row,
"per_piece": per_piece,
"cost": money_total(per_piece, total),
"price_source": source,
}
)
list_count_id = data.get("listCountID") or data.get("list_count_id")
return {
"action": action,
"mail_class": mail_class,
"selected_codes": codes,
"routes": priced,
"route_rows": priced,
"list_count_id": list_count_id,
"record_count": record_count,
"per_piece": per_piece,
"total": money_total(per_piece, record_count),
"price_source": source,
"eddm_map_url": USPS_EDDM_MAP_URL,
"estimated": {
"per_piece": per_piece,
"pieces": record_count,
"total": money_total(per_piece, record_count),
"currency": "USD",
"source": source,
},
}
raise ValueError(f"Unknown quote action: {action}")
def opted_in_contacts(channel: str) -> QuerySet[Contact]:
"""Contacts opted in for channel and not actively suppressed."""
suppressed = Suppression.objects.filter(
@@ -653,11 +273,9 @@ def create_campaign_draft(
scheduled_for=None,
created_by: AbstractBaseUser | None = None,
template: MessageTemplate | None = None,
route_targeting: dict | None = None,
) -> Campaign:
"""Persist a draft campaign and per-recipient Message stubs."""
channel = channel_for_audience(audience)
targeting = route_targeting if isinstance(route_targeting, dict) else {}
campaign = Campaign.objects.create(
name=name,
channel=channel,
@@ -668,29 +286,7 @@ def create_campaign_draft(
body_override=body,
created_by=created_by,
template=template,
route_targeting=targeting,
)
if channel in (Channel.EMAIL, Channel.SMS):
body = ensure_campaign_utm_link(
body,
name=name,
medium=channel,
html=(channel == Channel.EMAIL),
campaign_id=campaign.pk,
)
if body != campaign.body_override:
campaign.body_override = body
campaign.save(update_fields=["body_override", "updated_at"])
if audience == Campaign.Audience.CARRIER_ROUTE:
Message.objects.create(
campaign=campaign,
contact=None,
channel=channel,
status=Message.Status.DRAFT,
scheduled_for=scheduled_for,
body_snapshot=body,
)
return campaign
contacts = list(opted_in_contacts(channel))
Message.objects.bulk_create(
[
-87
View File
@@ -1,87 +0,0 @@
"""Server-to-server client for url_shortening_service (piha.lc).
Call SHORTENER_BASE_URL (https://piha.lc / https://beta.piha.li).
Auth: Authorization: Bearer <SHORTENER_API_TOKEN> (name:secret).
"""
from __future__ import annotations
import logging
import requests
from django.conf import settings
logger = logging.getLogger(__name__)
def is_configured() -> bool:
base = (getattr(settings, "SHORTENER_BASE_URL", None) or "").strip()
token = (getattr(settings, "SHORTENER_API_TOKEN", None) or "").strip()
return bool(base and token)
def mint_short_url(
target_url: str,
*,
title: str = "",
external_ref: str = "",
) -> str | None:
"""POST /api/links/. Return short_url, or None if unconfigured / failed.
Does not raise. Compose must still work when the shortener is down or unset.
"""
if not is_configured():
return None
target = (target_url or "").strip()
if not target.lower().startswith("https://"):
logger.info("shortener skip: target is not https")
return None
base = str(settings.SHORTENER_BASE_URL).rstrip("/")
token = str(settings.SHORTENER_API_TOKEN).strip()
timeout = int(getattr(settings, "SHORTENER_TIMEOUT_SECONDS", 10) or 10)
try:
response = requests.post(
f"{base}/api/links/",
headers={
"Authorization": f"Bearer {token}",
"Content-Type": "application/json",
},
json={
"target_url": target,
"title": (title or "")[:200],
"external_ref": (external_ref or "")[:64],
},
timeout=timeout,
)
except requests.RequestException:
logger.exception("shortener request failed")
return None
if response.status_code not in (200, 201):
logger.warning(
"shortener mint failed status=%s",
response.status_code,
)
return None
try:
data = response.json()
except ValueError:
logger.warning("shortener mint returned non-json")
return None
if not isinstance(data, dict):
return None
short = (data.get("short_url") or "").strip()
return short or None
def resolve_display_url(
target_url: str,
*,
title: str = "",
external_ref: str = "",
) -> str:
"""Short URL when mint succeeds, otherwise the original target."""
return mint_short_url(
target_url, title=title, external_ref=external_ref
) or target_url
+1 -3
View File
@@ -18,9 +18,7 @@ def send_campaign_message(message_id: str) -> None:
campaign = message.campaign
if message.contact_id is None:
pass
elif not contact_may_receive(message.contact, message.channel):
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"])
@@ -1,59 +0,0 @@
<div class="field" id="postcard-list-cost" hidden></div>
<div id="postcard-route-picker" hidden>
<p class="hint-block">
PCM mails a purchased occupant list on the USPS carrier routes you pick
(First Class / Standard — not official EDDM postage). Cost is per piece
times households.
</p>
<div class="form-grid cols-2">
<div class="field">
<label for="route-zip">ZIP code</label>
<input id="route-zip" type="text" inputmode="numeric" maxlength="10"
placeholder="60540" autocomplete="postal-code">
</div>
<div class="field">
<label for="route-radius">Radius miles <span class="muted">(optional)</span></label>
<input id="route-radius" type="number" min="0" step="0.5" placeholder="1">
</div>
</div>
<div class="form-grid cols-2">
<div class="field">
<label for="route-address">Street <span class="muted">(optional, with radius)</span></label>
<input id="route-address" type="text" placeholder="1245 Butterfield Rd">
</div>
<div class="field">
<label for="route-city">City</label>
<input id="route-city" type="text" placeholder="Wheaton">
</div>
</div>
<div class="field" style="max-width:120px">
<label for="route-state">State</label>
<input id="route-state" type="text" maxlength="2" placeholder="IL">
</div>
<button class="btn btn-ghost btn-sm" type="button" id="route-find-btn">Find routes</button>
<p class="hint" style="margin-top:8px">
<a id="eddm-map-link" href="https://eddm.usps.com/eddm/select-routes.htm"
target="_blank" rel="noopener noreferrer">Open USPS EDDM map</a>
— official route map. Use Show Table there for size / income filters.
PCM still mails a purchased occupant list, not EDDM postage.
</p>
<p class="hint" id="route-status" style="margin-top:8px"></p>
<div id="route-table-wrap" hidden style="margin-top:12px;overflow-x:auto">
<table class="table">
<thead>
<tr>
<th>Route</th>
<th>Area</th>
<th>Households</th>
<th>Size</th>
<th>Income</th>
<th>Businesses</th>
<th>Est. cost</th>
</tr>
</thead>
<tbody id="route-table-body"></tbody>
</table>
</div>
<p class="hint-block" id="route-selected-cost" hidden style="margin-top:12px"></p>
</div>
<input type="hidden" name="route_targeting" id="id_route_targeting" value="{{ form_data.route_targeting }}">
@@ -1,46 +0,0 @@
<div class="utm-panel" id="utm-link-panel"
data-base-url="{{ utm_base_url }}"
data-link-label="{{ utm_link_label }}"
data-medium="{{ utm_medium|default:'' }}"
data-campaign-name="{{ utm_campaign_name|default:'' }}"
data-shorten-url="{{ utm_shorten_url|default:'' }}"
data-campaign-id="{% if campaign %}{{ campaign.pk }}{% endif %}"
data-csrf="{{ csrf_token }}"
data-live="{% if utm_live %}1{% else %}0{% endif %}">
<div class="field" style="margin:0">
<label>Tracked site link</label>
<p class="hint" data-utm-email-hint hidden>
Auto-added to the email as a styled link. Updates as you type the campaign name.
<code>utm_medium=email</code> · <code>utm_campaign</code> matches the name.
Recipients tap a short <code>piha.li</code> link that redirects to this URL.
QR below encodes the same short link.
</p>
<p class="hint" data-utm-sms-hint hidden>
SMS is plain text — phones cannot show HTML links. A short <code>piha.li</code>
URL is in the message and phones make it tappable. Preview below shows it as a link.
</p>
<p class="hint" data-utm-postcard-hint hidden>
Copy the QR image into the postcard designer. Recipients scan it to open the
short tracked link (<code>piha.li</code>).
</p>
<code class="utm-url" data-utm-url>{{ utm_url|default:"" }}</code>
<div class="utm-actions">
<button type="button" class="btn btn-ghost btn-sm" data-utm-copy-url>Copy link</button>
</div>
<div class="utm-qr" data-utm-qr-wrap hidden>
<canvas data-utm-qr width="192" height="192" aria-label="Campaign QR code"></canvas>
<div class="utm-qr-actions">
<button type="button" class="btn btn-ghost btn-sm" data-utm-copy-qr>Copy QR image</button>
<button type="button" class="btn btn-ghost btn-sm" data-utm-download-qr>Download PNG</button>
<p class="hint" style="margin-top:8px" data-utm-qr-email-hint hidden>
Copy or download the PNG for print, signatures, or other materials.
</p>
<p class="hint" style="margin-top:8px" data-utm-qr-postcard-hint hidden>
Paste or upload the PNG in
<a href="{% url 'messaging:postcard_designer' %}">Postcard design</a>.
</p>
</div>
</div>
<p class="hint" data-utm-status hidden></p>
</div>
</div>
@@ -1,5 +1,4 @@
{% extends "portal_base.html" %}
{% load static %}
{% block title %}{{ campaign.name }} · Campaign{% endblock %}
{% block topbar_title %}{{ campaign.name }}{% endblock %}
{% block portal_content %}
@@ -36,13 +35,11 @@
{% endif %}
{% if can_send %}
<form method="post" action="{% url 'messaging:campaign_send' campaign.pk %}"
onsubmit="return confirm('{% if campaign.audience == "carrier_route" %}Place this PCM carrier-route postcard order now?{% else %}Send this campaign to all remaining recipients now?{% endif %}');">
onsubmit="return confirm('Send this campaign to all remaining recipients now?');">
{% csrf_token %}
<button class="btn btn-primary" type="submit">{% if campaign.audience == "carrier_route" %}Place route order{% else %}Send now to recipients{% endif %}</button>
<button class="btn btn-primary" type="submit">Send now to recipients</button>
<p class="hint-block" style="margin-top:8px">
{% if campaign.audience == "carrier_route" %}
Places one PCM list-count order for the selected USPS routes (not per contact).
{% elif campaign.channel == "postcard" %}
{% if campaign.channel == "postcard" %}
Enqueues draft / scheduled / failed messages via PCM Integrations.
{% else %}
Enqueues draft / scheduled / failed messages via SMTP2GO (dev ImmediateBackend runs inline).
@@ -54,42 +51,6 @@
</div>
{% endif %}
{% if campaign.audience == "carrier_route" %}
<div class="panel" style="margin-bottom:16px">
<div class="panel-h"><h2>Carrier routes</h2></div>
<div class="panel-b">
{% with t=campaign.route_targeting %}
<p style="margin-top:0">
{{ t.record_count|default:0 }} household{{ t.record_count|default:0|pluralize }}
· {{ t.mail_class|default:"Standard" }}
{% if t.estimated.total != None %}
· est. ${{ t.estimated.total }}
{% if t.estimated.per_piece %} (${{ t.estimated.per_piece }} / piece){% endif %}
{% endif %}
</p>
<p class="muted" style="margin:0">
Routes:
{% for code in t.selected_codes %}
<code>{{ code }}</code>{% if not forloop.last %}, {% endif %}
{% empty %}
—
{% endfor %}
</p>
{% if t.estimated.source == "fallback" %}
<p class="hint-block">Cost uses env fallback rates (PCM did not return design pricing).</p>
{% endif %}
{% endwith %}
</div>
</div>
{% endif %}
<div class="panel" style="margin-bottom:16px">
<div class="panel-h"><h2>Tracked site link</h2></div>
<div class="panel-b">
{% include "messaging/_utm_link_panel.html" with utm_live=False utm_medium=campaign.channel utm_campaign_name=campaign.name %}
</div>
</div>
<div class="stat-row" id="campaign-stats">
<div class="stat-card">
<div class="label">Messages</div>
@@ -247,10 +208,7 @@
</div>
{% endblock %}
{% block extra_js %}
<script src="https://cdn.jsdelivr.net/npm/qrcode@1.5.4/build/qrcode.min.js"></script>
<script src="{% static 'js/campaign-utm.js' %}"></script>
<script>
window.MonicaUtm.bindPanel(document.getElementById("utm-link-panel"));
(function () {
var panel = document.getElementById("recipients-panel");
var page = (panel && panel.getAttribute("data-page")) || "1";
@@ -1,5 +1,4 @@
{% extends "portal_base.html" %}
{% load static %}
{% block title %}Campaigns · Portal{% endblock %}
{% block topbar_title %}Campaign composer{% endblock %}
{% block extra_head %}
@@ -65,7 +64,6 @@
<label for="id_name">Campaign name</label>
<input id="id_name" name="name" type="text" required
placeholder="Spring seller tips" value="{{ form_data.name }}">
<div class="hint">Used as <code>utm_campaign</code> on the tracked site link.</div>
</div>
<div class="field" id="subject-field">
<label for="id_subject">Subject</label>
@@ -86,7 +84,7 @@
<textarea id="id_body_sms" style="min-height:140px"
placeholder="Hi {% templatetag openvariable %}first_name{% templatetag closevariable %}, …"
oninput="syncSmsBody()">{{ form_data.body }}</textarea>
<div class="hint">Plain text for SMS · keep it short · merge tags: <code>{% templatetag openvariable %}first_name{% templatetag closevariable %}</code>, <code>{% templatetag openvariable %}last_name{% templatetag closevariable %}</code>. Tracked site URL is added automatically (SMS cannot use HTML links).</div>
<div class="hint">Plain text for SMS · keep it short · merge tags: <code>{% templatetag openvariable %}first_name{% templatetag closevariable %}</code>, <code>{% templatetag openvariable %}last_name{% templatetag closevariable %}</code></div>
</div>
<div class="field" id="postcard-body-hint" hidden>
<p class="hint-block" style="margin:0">
@@ -129,9 +127,7 @@
<div class="hint">Date &amp; time · leave blank to keep as unscheduled draft</div>
</div>
</div>
{% include "messaging/_postcard_route_picker.html" %}
<p class="hint-block">Saves a draft campaign and recipient stubs. Send from the campaign report when ready. You’ll get an email when the send finishes.</p>
{% include "messaging/_utm_link_panel.html" with utm_live=True %}
<button class="btn btn-primary" type="submit">Save draft</button>
</form>
</div>
@@ -183,9 +179,6 @@
{% endblock %}
{% block extra_js %}
<script src="https://cdn.jsdelivr.net/npm/quill@2.0.3/dist/quill.js"></script>
<script src="https://cdn.jsdelivr.net/npm/qrcode@1.5.4/build/qrcode.min.js"></script>
<script src="{% static 'js/campaign-utm.js' %}"></script>
<script src="{% static 'js/postcard-routes.js' %}"></script>
<script>
(function () {
var uploadUrl = "{{ image_upload_url|escapejs }}";
@@ -193,37 +186,11 @@
var bodyField = document.getElementById('id_body');
var smsField = document.getElementById('id_body_sms');
var quill = null;
var utmLock = false;
var utmLinkLabel = "{{ utm_link_label|escapejs }}";
function csrfHeader() {
return { 'X-CSRFToken': csrfToken };
}
function audienceChannel() {
var audience = (document.getElementById('id_audience') || {}).value || '';
if (audience === 'sms_opt_in') return 'sms';
if (audience === 'postcard_opt_in' || audience === 'carrier_route') return 'postcard';
return 'email';
}
function applyUtmToBodies(url, channel, label) {
if (utmLock) return;
utmLock = true;
try {
if (channel === 'email' && quill) {
window.MonicaUtm.ensureQuillLink(quill, url, label || utmLinkLabel);
syncBodyFromQuill();
} else if (channel === 'sms' && smsField) {
smsField.value = window.MonicaUtm.replaceTextUrl(smsField.value, url);
if (bodyField) bodyField.value = smsField.value;
syncCampaignPreview();
}
} finally {
utmLock = false;
}
}
function syncBodyFromQuill() {
if (!quill || !bodyField) return;
var html = quill.root.innerHTML;
@@ -247,8 +214,7 @@
var subject = (document.getElementById('id_subject') || {}).value || '';
var audience = (document.getElementById('id_audience') || {}).value || '';
var isEmail = audience === 'email_opt_in';
var isSms = audience === 'sms_opt_in';
var isPostcard = audience === 'postcard_opt_in' || audience === 'carrier_route';
var isPostcard = audience === 'postcard_opt_in';
var body = '';
if (isEmail && quill) {
body = quill.root.innerHTML;
@@ -278,9 +244,6 @@
if (isEmail) {
bodyEl.style.whiteSpace = 'normal';
bodyEl.innerHTML = body;
} else if (isSms) {
bodyEl.style.whiteSpace = 'pre-wrap';
bodyEl.innerHTML = window.MonicaUtm.linkify(body);
} else {
bodyEl.style.whiteSpace = 'pre-wrap';
bodyEl.textContent = body;
@@ -289,7 +252,7 @@
window.syncComposeChannel = function () {
var audience = (document.getElementById('id_audience') || {}).value || '';
var isPostcard = audience === 'postcard_opt_in' || audience === 'carrier_route';
var isPostcard = audience === 'postcard_opt_in';
var isSms = audience === 'sms_opt_in';
var isEmail = audience === 'email_opt_in';
var tmplField = document.getElementById('postcard-template-field');
@@ -320,11 +283,6 @@
var active = (ch === 'email' && isEmail) || (ch === 'sms' && isSms) || (ch === 'postcard' && isPostcard);
a.classList.toggle('active', active);
});
var panel = document.getElementById('utm-link-panel');
if (panel && panel._utmApply) panel._utmApply();
if (window.MonicaPostcardRoutes && window.MonicaPostcardRoutes.syncVisibility) {
window.MonicaPostcardRoutes.syncVisibility();
}
syncCampaignPreview();
};
@@ -383,15 +341,12 @@
} else if (initial) {
quill.setText(initial);
}
quill.on('text-change', function () {
if (utmLock) return;
syncBodyFromQuill();
});
quill.on('text-change', syncBodyFromQuill);
document.getElementById('campaign-compose').addEventListener('submit', function () {
var audience = (document.getElementById('id_audience') || {}).value || '';
if (audience === 'email_opt_in') syncBodyFromQuill();
else if (audience === 'sms_opt_in' && smsField) bodyField.value = smsField.value;
else if (audience === 'postcard_opt_in' || audience === 'carrier_route') {
else if (audience === 'postcard_opt_in') {
var pc = document.getElementById('id_body_pc');
bodyField.value = pc ? pc.value : '';
}
@@ -415,18 +370,6 @@
if (tmplSelect) tmplSelect.addEventListener('change', syncCampaignPreview);
initQuill();
window.initPostcardRoutes({
quoteUrl: "{{ postcard_quote_url|escapejs }}",
csrfToken: csrfToken
});
window.MonicaUtm.bindPanel(document.getElementById('utm-link-panel'), {
getName: function () {
return (document.getElementById('id_name') || {}).value || '';
},
getMedium: audienceChannel,
csrfToken: csrfToken,
onUrlChange: applyUtmToBodies
});
syncComposeChannel();
})();
</script>
+2 -519
View File
@@ -1,8 +1,6 @@
from django.contrib.auth import get_user_model
from django.test import Client, TestCase, override_settings
from django.test import Client, TestCase
from django.urls import reverse
from unittest.mock import patch
import json
from contacts.models import Channel, ConsentRecord, Contact, Suppression
from messaging.models import Campaign, Message, MessageTemplate, ProviderEvent
@@ -1361,7 +1359,6 @@ class CampaignRecipientTableTests(TestCase):
self.assertNotContains(response, "Recent SMTP2GO events")
@override_settings(SHORTENER_BASE_URL="", SHORTENER_API_TOKEN="")
class Smtp2goSmsSendTests(TestCase):
def setUp(self):
self.contact = Contact.objects.create(
@@ -1419,9 +1416,7 @@ class Smtp2goSmsSendTests(TestCase):
payload = post.call_args.kwargs["json"]
self.assertEqual(payload["api_key"], "api-test-key")
self.assertEqual(payload["destination"], ["+13304022675"])
self.assertTrue(payload["content"].startswith("Hello Rufus"))
self.assertIn("utm_source=monica", payload["content"])
self.assertIn("utm_medium=sms", payload["content"])
self.assertEqual(payload["content"], "Hello Rufus")
self.assertNotIn("to", payload)
self.assertNotIn("text", payload)
@@ -1455,515 +1450,3 @@ class Smtp2goSmsSendTests(TestCase):
self.assertIn("Missing required field", str(ctx.exception))
self.assertIn("INVALID_REQUEST", str(ctx.exception))
@override_settings(SHORTENER_BASE_URL="", SHORTENER_API_TOKEN="")
class CampaignUtmLinkTests(TestCase):
def setUp(self):
User = get_user_model()
self.user = User.objects.create_user(
username="utm-composer", password="test-pass-123"
)
self.client = Client()
self.client.login(username="utm-composer", password="test-pass-123")
self.contact = Contact.objects.create(
email="pat@example.com",
phone="5550100199",
first_name="Pat",
)
set_channel_consent(
self.contact, Channel.EMAIL, opted_in=True, reason="test"
)
set_channel_consent(
self.contact, Channel.SMS, opted_in=True, reason="test"
)
def test_slug_and_url_shape(self):
from messaging.services import (
build_campaign_utm_url,
campaign_utm_slug,
)
self.assertEqual(campaign_utm_slug("Spring seller tips"), "spring-seller-tips")
with self.settings(PUBLIC_SITE_URL="https://mkdrealtor.com"):
url = build_campaign_utm_url(
name="Spring seller tips", medium="email"
)
self.assertEqual(
url,
"https://mkdrealtor.com/?utm_source=monica"
"&utm_medium=email&utm_campaign=spring-seller-tips",
)
def test_email_draft_inserts_html_link(self):
with self.settings(PUBLIC_SITE_URL="https://mkdrealtor.com"):
campaign = create_campaign_draft(
name="Spring seller tips",
audience=Campaign.Audience.EMAIL_OPT_IN,
subject="Hello",
body="<p>Hi there</p>",
created_by=self.user,
)
self.assertIn('data-monica-utm="1"', campaign.body_override)
self.assertIn("utm_medium=email", campaign.body_override)
self.assertIn("utm_campaign=spring-seller-tips", campaign.body_override)
self.assertIn("Hi there", campaign.body_override)
def test_sms_draft_inserts_plain_url(self):
with self.settings(PUBLIC_SITE_URL="https://mkdrealtor.com"):
campaign = create_campaign_draft(
name="Open house",
audience=Campaign.Audience.SMS_OPT_IN,
body="See you Saturday",
created_by=self.user,
)
self.assertIn("See you Saturday", campaign.body_override)
self.assertIn(
"https://mkdrealtor.com/?utm_source=monica&utm_medium=sms"
"&utm_campaign=open-house",
campaign.body_override,
)
self.assertNotIn("<a ", campaign.body_override)
def test_sms_replaces_existing_tracked_url(self):
from messaging.services import ensure_campaign_utm_link
with self.settings(PUBLIC_SITE_URL="https://mkdrealtor.com"):
old = (
"Hi\n\nhttps://mkdrealtor.com/?utm_source=monica"
"&utm_medium=sms&utm_campaign=old-name"
)
updated = ensure_campaign_utm_link(
old, name="New name", medium="sms", html=False
)
self.assertIn("utm_campaign=new-name", updated)
self.assertNotIn("old-name", updated)
self.assertEqual(updated.count("https://"), 1)
def test_postcard_does_not_inject_into_body(self):
campaign = create_campaign_draft(
name="March mailer",
audience=Campaign.Audience.POSTCARD_OPT_IN,
body="Internal note only",
created_by=self.user,
)
self.assertEqual(campaign.body_override, "Internal note only")
self.assertNotIn("utm_source", campaign.body_override)
def test_composer_shows_tracked_link_panel(self):
url = reverse("messaging:campaign_list")
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
self.assertContains(response, "Tracked site link")
self.assertContains(response, "campaign-utm.js")
self.assertContains(response, "qrcode.min.js")
self.assertContains(response, "data-utm-qr")
self.assertContains(response, "utm_campaign")
self.assertContains(response, reverse("messaging:campaign_short_link"))
self.assertContains(response, "https://eddm.usps.com/eddm/select-routes.htm")
self.assertContains(response, "Open USPS EDDM map")
def test_detail_shows_tracked_url(self):
with self.settings(PUBLIC_SITE_URL="https://mkdrealtor.com"):
campaign = create_campaign_draft(
name="Spring seller tips",
audience=Campaign.Audience.EMAIL_OPT_IN,
subject="Hello",
body="Hi",
created_by=self.user,
)
url = reverse(
"messaging:campaign_detail", kwargs={"pk": campaign.pk}
)
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
self.assertContains(response, "utm_campaign=spring-seller-tips")
self.assertContains(response, "utm_medium=email")
self.assertContains(response, "qrcode.min.js")
self.assertContains(response, "data-utm-qr")
self.assertContains(response, "QR below encodes the same short link")
def test_sms_replaces_existing_short_url(self):
from messaging.services import ensure_campaign_utm_in_text
updated = ensure_campaign_utm_in_text(
"Hi\n\nhttps://piha.li/a3k9xm",
"https://piha.li/zzzzzz",
)
self.assertEqual(updated, "Hi\n\nhttps://piha.li/zzzzzz")
def test_shortener_mints_into_sms_body(self):
from unittest.mock import MagicMock, patch
mock = MagicMock()
mock.status_code = 201
mock.json.return_value = {
"code": "a3k9xm",
"short_url": "https://piha.li/a3k9xm",
"target_url": (
"https://mkdrealtor.com/?utm_source=monica"
"&utm_medium=sms&utm_campaign=open-house"
),
}
with self.settings(
PUBLIC_SITE_URL="https://mkdrealtor.com",
SHORTENER_BASE_URL="https://piha.lc",
SHORTENER_API_TOKEN="monica:test-token",
):
with patch(
"messaging.shortener.requests.post", return_value=mock
) as post:
campaign = create_campaign_draft(
name="Open house",
audience=Campaign.Audience.SMS_OPT_IN,
body="See you Saturday",
created_by=self.user,
)
self.assertIn("https://piha.li/a3k9xm", campaign.body_override)
self.assertIn("See you Saturday", campaign.body_override)
self.assertNotIn("utm_medium=sms", campaign.body_override)
post.assert_called_once()
headers = post.call_args.kwargs["headers"]
self.assertEqual(headers["Authorization"], "Bearer monica:test-token")
payload = post.call_args.kwargs["json"]
self.assertIn("utm_medium=sms", payload["target_url"])
self.assertIn("utm_campaign=open-house", payload["target_url"])
self.assertEqual(payload["external_ref"], str(campaign.pk))
self.assertEqual(campaign.messages.get().body_snapshot, campaign.body_override)
def test_shortener_failure_falls_back_to_long_url(self):
from unittest.mock import MagicMock, patch
mock = MagicMock()
mock.status_code = 401
mock.text = '{"detail":"Unauthorized"}'
with self.settings(
PUBLIC_SITE_URL="https://mkdrealtor.com",
SHORTENER_BASE_URL="https://piha.lc",
SHORTENER_API_TOKEN="monica:bad",
):
with patch("messaging.shortener.requests.post", return_value=mock):
campaign = create_campaign_draft(
name="Open house",
audience=Campaign.Audience.SMS_OPT_IN,
body="See you Saturday",
created_by=self.user,
)
self.assertIn(
"https://mkdrealtor.com/?utm_source=monica&utm_medium=sms"
"&utm_campaign=open-house",
campaign.body_override,
)
def test_short_link_endpoint_requires_login(self):
client = Client()
url = reverse("messaging:campaign_short_link")
response = client.post(
url,
data='{"name":"Open house","medium":"sms"}',
content_type="application/json",
)
self.assertEqual(response.status_code, 302)
def test_short_link_endpoint_unconfigured_returns_long_url(self):
url = reverse("messaging:campaign_short_link")
with self.settings(
PUBLIC_SITE_URL="https://mkdrealtor.com",
SHORTENER_BASE_URL="",
SHORTENER_API_TOKEN="",
):
response = self.client.post(
url,
data='{"name":"Open house","medium":"sms"}',
content_type="application/json",
)
self.assertEqual(response.status_code, 200)
data = response.json()
self.assertFalse(data["shortened"])
self.assertIn("utm_medium=sms", data["display_url"])
self.assertIn("utm_campaign=open-house", data["display_url"])
def test_short_link_endpoint_mints(self):
from unittest.mock import MagicMock, patch
mock = MagicMock()
mock.status_code = 201
mock.json.return_value = {
"code": "a3k9xm",
"short_url": "https://piha.li/a3k9xm",
"target_url": "https://mkdrealtor.com/?utm_source=monica",
}
url = reverse("messaging:campaign_short_link")
with self.settings(
PUBLIC_SITE_URL="https://mkdrealtor.com",
SHORTENER_BASE_URL="https://piha.lc",
SHORTENER_API_TOKEN="monica:test-token",
):
with patch("messaging.shortener.requests.post", return_value=mock):
response = self.client.post(
url,
data='{"name":"Open house","medium":"sms"}',
content_type="application/json",
)
self.assertEqual(response.status_code, 200)
data = response.json()
self.assertTrue(data["shortened"])
self.assertEqual(data["display_url"], "https://piha.li/a3k9xm")
self.assertIn("utm_medium=sms", data["target_url"])
class CarrierRoutePostcardTests(TestCase):
def setUp(self):
User = get_user_model()
self.user = User.objects.create_user(
username="routes", password="test-pass-123"
)
self.client = Client()
self.client.login(username="routes", password="test-pass-123")
self.contact = Contact.objects.create(
email="mail@example.com",
postal_address=Contact.make_postal_address(line1="9 Main"),
)
self.eddm_patcher = patch(
"messaging.providers.postcard.usps_eddm.fetch_eddm_route_stats",
return_value={},
)
self.eddm_stats = self.eddm_patcher.start()
self.addCleanup(self.eddm_patcher.stop)
def test_pick_unit_price_uses_break_qty_and_fallback(self):
from messaging.providers.postcard import pcm as pcm_mod
price, source = pcm_mod.pick_unit_price(
[
{"mailClass": "Standard", "price": 0.8, "breakQty": 1},
{"mailClass": "Standard", "price": 0.5, "breakQty": 200},
],
mail_class="Standard",
quantity=250,
)
self.assertEqual(price, 0.5)
self.assertEqual(source, "pcm")
with self.settings(PCM_POSTCARD_PRICE_STANDARD="0.42"):
price, source = pcm_mod.pick_unit_price(
[], mail_class="Standard", quantity=10
)
self.assertEqual(price, 0.42)
self.assertEqual(source, "fallback")
def test_quote_search_returns_route_costs(self):
from unittest.mock import patch
payload = {
"listCountID": 11,
"recordCount": 300,
"breakdown": [
{
"breakdownType": "ZipCRRT",
"data": [
{"code": "60540:C001", "text": "NAPERVILLE", "total": 120},
{"code": "60540:C002", "text": "NAPERVILLE", "total": 180},
],
}
],
}
with self.settings(PCM_POSTCARD_PRICE_STANDARD="0.50"):
with patch(
"messaging.providers.postcard.pcm.design_pricing", return_value=[]
):
with patch(
"messaging.providers.postcard.pcm.list_count_zipcode",
return_value=payload,
):
response = self.client.post(
reverse("messaging:postcard_quote"),
data='{"action":"search","zip":"60540","size":"68"}',
content_type="application/json",
)
self.assertEqual(response.status_code, 200)
data = response.json()
self.assertEqual(len(data["routes"]), 2)
self.assertEqual(data["routes"][0]["cost"], 60.0)
self.assertEqual(data["mail_class"], "Standard")
self.assertEqual(
data["eddm_map_url"], "https://eddm.usps.com/eddm/select-routes.htm"
)
def test_quote_search_includes_eddm_demographics(self):
self.eddm_stats.return_value = {
"60540:C001": {
"businesses": 22,
"household_size": 2.6,
"median_income": 120000,
"residential": 108,
}
}
payload = {
"listCountID": 11,
"recordCount": 120,
"breakdown": [
{
"breakdownType": "ZipCRRT",
"data": [
{"code": "60540:C001", "text": "NAPERVILLE", "total": 120},
],
}
],
}
with self.settings(PCM_POSTCARD_PRICE_STANDARD="0.50"):
with patch(
"messaging.providers.postcard.pcm.design_pricing", return_value=[]
):
with patch(
"messaging.providers.postcard.pcm.list_count_zipcode",
return_value=payload,
):
response = self.client.post(
reverse("messaging:postcard_quote"),
data='{"action":"search","zip":"60540","size":"68"}',
content_type="application/json",
)
self.assertEqual(response.status_code, 200)
row = response.json()["routes"][0]
self.assertEqual(row["businesses"], 22)
self.assertEqual(row["household_size"], 2.6)
self.assertEqual(row["median_income"], 120000)
def test_quote_select_and_save_draft(self):
from unittest.mock import patch
select_payload = {
"listCountID": 99,
"recordCount": 300,
"breakdown": [
{
"breakdownType": "ZipCRRT",
"data": [
{"code": "60540:C001", "text": "A", "total": 120},
{"code": "60540:C002", "text": "B", "total": 180},
],
}
],
}
with self.settings(PCM_POSTCARD_PRICE_STANDARD="0.50"):
with patch(
"messaging.providers.postcard.pcm.design_pricing", return_value=[]
):
with patch(
"messaging.providers.postcard.pcm.list_count_carrier_route",
return_value=select_payload,
):
quoted = self.client.post(
reverse("messaging:postcard_quote"),
data=(
'{"action":"select","size":"68",'
'"routes":["60540:C001","60540:C002"]}'
),
content_type="application/json",
)
self.assertEqual(quoted.status_code, 200)
quote = quoted.json()
self.assertEqual(quote["list_count_id"], 99)
self.assertEqual(quote["total"], 150.0)
targeting = {
"selected_codes": quote["selected_codes"],
"list_count_id": quote["list_count_id"],
"record_count": quote["record_count"],
"mail_class": quote["mail_class"],
"estimated": quote["estimated"],
"route_rows": quote["routes"],
}
with patch(
"messaging.views._fetch_pcm_designs",
return_value=(
[{"design_id": "42", "name": "Spring card", "size": "68"}],
"",
),
):
response = self.client.post(
reverse("messaging:campaign_list"),
{
"name": "Farm drop",
"audience": Campaign.Audience.CARRIER_ROUTE,
"template_id": "d:42",
"route_targeting": json.dumps(targeting),
},
)
self.assertEqual(response.status_code, 302)
campaign = Campaign.objects.get(name="Farm drop")
self.assertEqual(campaign.audience, Campaign.Audience.CARRIER_ROUTE)
self.assertEqual(campaign.route_targeting["list_count_id"], 99)
self.assertEqual(campaign.messages.count(), 1)
self.assertIsNone(campaign.messages.get().contact_id)
def test_send_uses_list_count_endpoint(self):
from unittest.mock import patch
from messaging.providers.postcard import pcm as pcm_mod
from messaging.services import enqueue_campaign_send
template = MessageTemplate.objects.create(
name="Route design",
channel=Channel.POSTCARD,
body="Postcard",
postcard_front={"design_id": 42, "size": "68", "provider": "pcm"},
)
campaign = create_campaign_draft(
name="Routes",
audience=Campaign.Audience.CARRIER_ROUTE,
body="note",
created_by=self.user,
template=template,
route_targeting={
"selected_codes": ["60540:C001"],
"list_count_id": 77,
"record_count": 50,
"mail_class": "Standard",
},
)
pcm_mod.clear_token_cache()
with self.settings(
PCM_API_KEY="key",
PCM_API_SECRET="secret",
PCM_RETURN_ADDRESS=(
'{"firstName":"Mo","lastName":"D","address":"1 Main",'
'"city":"Naperville","state":"IL","zipCode":"60540"}'
),
):
with patch("messaging.providers.postcard.pcm.login", return_value="tok"):
with patch(
"messaging.providers.postcard.pcm.pcm_request",
return_value={"orderID": 555},
) as req:
enqueue_campaign_send(campaign)
path = req.call_args.args[1]
self.assertEqual(path, "/order/postcard/with-list-count")
body = req.call_args.kwargs["json_body"]
self.assertEqual(body["listCountID"], 77)
self.assertEqual(body["recordCount"], 50)
campaign.refresh_from_db()
self.assertEqual(campaign.messages.get().provider_message_id, "555")
def test_mailing_list_postcard_still_creates_contacts(self):
from unittest.mock import patch
with patch(
"messaging.views._fetch_pcm_designs",
return_value=(
[{"design_id": "42", "name": "Spring card", "size": "46"}],
"",
),
):
response = self.client.post(
reverse("messaging:campaign_list"),
{
"name": "List mailer",
"audience": Campaign.Audience.POSTCARD_OPT_IN,
"template_id": "d:42",
},
)
self.assertEqual(response.status_code, 302)
campaign = Campaign.objects.get(name="List mailer")
self.assertEqual(campaign.messages.count(), 1)
self.assertEqual(campaign.messages.get().contact_id, self.contact.pk)
-10
View File
@@ -6,16 +6,6 @@ app_name = "messaging"
urlpatterns = [
path("campaigns/", views.campaign_list, name="campaign_list"),
path(
"campaigns/postcard-quote/",
views.postcard_quote,
name="postcard_quote",
),
path(
"campaigns/short-link/",
views.campaign_short_link,
name="campaign_short_link",
),
path("campaigns/<uuid:pk>/", views.campaign_detail, name="campaign_detail"),
path(
"campaigns/<uuid:pk>/status.json",
+5 -166
View File
@@ -1,7 +1,6 @@
import hashlib
import hmac
import io
import json
import logging
from django.conf import settings
@@ -25,22 +24,14 @@ from messaging.providers.postcard.pcm import (
design_id_from_template,
get_design_embed_url,
list_designs,
mail_class_for_size,
)
from messaging.services import (
channel_for_audience,
create_campaign_draft,
enqueue_campaign_send,
ensure_campaign_utm_link,
message_is_removable,
opted_in_contacts,
parse_route_targeting,
parse_scheduled_for,
postcard_quote as build_postcard_quote,
public_site_base_url,
public_site_link_label,
record_sms_stop,
resolve_campaign_tracked_url,
send_campaign_test_email,
)
from messaging.webhooks import (
@@ -79,12 +70,6 @@ def _audience_choices() -> list[tuple[str, str]]:
choices.append(
(value, f"Mailing list · {label} opt-in ({count} {noun})")
)
choices.append(
(
Campaign.Audience.CARRIER_ROUTE,
"USPS carrier routes (find by ZIP)",
)
)
return choices
@@ -241,14 +226,10 @@ def _message_destination(message) -> str:
contact = message.contact
channel = message.channel or (message.campaign.channel if message.campaign_id else "")
if channel == Channel.EMAIL:
return (contact.email or "").strip() if contact else ""
return (contact.email or "").strip()
if channel == Channel.SMS:
return (contact.phone or "").strip() if contact else ""
return (contact.phone or "").strip()
if channel == Channel.POSTCARD:
if not contact:
targeting = getattr(message.campaign, "route_targeting", None) or {}
codes = targeting.get("selected_codes") or []
return ", ".join(str(c) for c in codes) or "Carrier routes"
return _format_postal_address(contact.postal_address)
return ""
@@ -288,7 +269,7 @@ def _campaign_report(campaign: Campaign, *, page: int = 1) -> dict:
recipient_messages = list(page_obj.object_list)
for msg in recipient_messages:
msg.destination = _message_destination(msg)
msg.can_remove = message_is_removable(msg) and msg.contact_id is not None
msg.can_remove = message_is_removable(msg)
stats = campaign_engagement_stats(campaign)
providers, events_title, events_empty = _events_provider_filter(campaign)
recent_events = (
@@ -444,7 +425,6 @@ def campaign_list(request):
"audience": Campaign.Audience.EMAIL_OPT_IN,
"scheduled_for": "",
"template_id": "",
"route_targeting": "",
}
if request.method == "POST":
@@ -454,7 +434,6 @@ def campaign_list(request):
audience = (request.POST.get("audience") or "").strip()
scheduled_raw = request.POST.get("scheduled_for") or ""
template_id = (request.POST.get("template_id") or "").strip()
route_raw = request.POST.get("route_targeting") or ""
form_data.update(
{
@@ -464,12 +443,10 @@ def campaign_list(request):
"audience": audience,
"scheduled_for": scheduled_raw,
"template_id": template_id,
"route_targeting": route_raw,
}
)
template = None
targeting: dict = {}
if template_id:
template = _resolve_postcard_template(template_id)
@@ -484,29 +461,7 @@ def campaign_list(request):
)
if not body:
body = "Postcard mailing"
elif audience == Campaign.Audience.CARRIER_ROUTE:
if not template or template.channel != Channel.POSTCARD:
form_errors.append(
"Choose a postcard design (create one under Postcard design)."
)
if not body:
body = "Carrier route postcard mailing"
try:
targeting = parse_route_targeting(route_raw)
except ValueError as exc:
form_errors.append(str(exc))
targeting = {}
else:
if audience in Campaign.Audience.values:
channel = channel_for_audience(audience)
if channel in (Channel.EMAIL, Channel.SMS):
body = ensure_campaign_utm_link(
body,
name=name or "campaign",
medium=channel,
html=(channel == Channel.EMAIL),
)
form_data["body"] = body
if not body:
form_errors.append("Body is required.")
if (
@@ -530,18 +485,7 @@ def campaign_list(request):
scheduled_for=scheduled_for,
created_by=request.user,
template=template,
route_targeting=targeting,
)
if audience == Campaign.Audience.CARRIER_ROUTE:
pieces = targeting.get("record_count") or 0
total = (targeting.get("estimated") or {}).get("total")
cost_bit = f", est. ${total:.2f}" if isinstance(total, (int, float)) else ""
messages.success(
request,
f'Draft “{campaign.name}” saved '
f"({pieces} household{'' if pieces == 1 else 's'}{cost_bit}).",
)
else:
recipient_count = campaign.messages.count()
messages.success(
request,
@@ -562,98 +506,10 @@ def campaign_list(request):
"form_data": form_data,
"form_errors": form_errors,
"image_upload_url": reverse("messaging:campaign_image_upload"),
"utm_base_url": public_site_base_url(),
"utm_link_label": public_site_link_label(),
"utm_shorten_url": reverse("messaging:campaign_short_link"),
"utm_url": "",
"postcard_quote_url": reverse("messaging:postcard_quote"),
"postcard_list_count": opted_in_contacts(Channel.POSTCARD).count(),
},
)
@login_required
@require_POST
def postcard_quote(request):
"""Search PCM carrier routes or price a postcard audience."""
try:
payload = json.loads(request.body.decode() or "{}")
except (json.JSONDecodeError, UnicodeDecodeError):
payload = {}
if not isinstance(payload, dict):
payload = {}
template_id = str(payload.get("template_id") or "").strip()
if template_id:
template = _resolve_postcard_template(template_id)
if template:
did = design_id_from_template(template)
if did and not payload.get("design_id"):
payload["design_id"] = did
front = template.postcard_front or {}
if not payload.get("size"):
payload["size"] = front.get("size") or ""
if not payload.get("mail_class") and front.get("mail_class"):
payload["mail_class"] = front.get("mail_class")
elif not payload.get("mail_class"):
payload["mail_class"] = mail_class_for_size(payload.get("size"))
action = str(payload.get("action") or "mailing_list").strip()
try:
result = build_postcard_quote(action=action, payload=payload)
except ValueError as exc:
return JsonResponse({"detail": str(exc)}, status=400)
except PcmApiError as exc:
return JsonResponse({"detail": str(exc)}, status=502)
return JsonResponse(result)
@login_required
@require_POST
def campaign_short_link(request):
"""Mint (or reuse) a short URL for the campaign tracked landing link.
Browser talks to this portal endpoint only. The shortener is server-to-server.
"""
try:
payload = json.loads(request.body.decode() or "{}")
except (json.JSONDecodeError, UnicodeDecodeError):
payload = {}
if not isinstance(payload, dict):
payload = {}
name = (payload.get("name") or request.POST.get("name") or "").strip()
medium = (payload.get("medium") or request.POST.get("medium") or "").strip()
campaign_id = (
payload.get("campaign_id") or request.POST.get("campaign_id") or ""
)
campaign_id = str(campaign_id).strip()
if campaign_id:
try:
campaign = Campaign.objects.filter(pk=campaign_id).first()
except (ValidationError, ValueError):
campaign = None
if campaign is None:
return JsonResponse({"detail": "Campaign not found."}, status=404)
name = name or campaign.name
medium = medium or campaign.channel
campaign_id = str(campaign.pk)
else:
campaign_id = None
target, display = resolve_campaign_tracked_url(
name=name or "campaign",
medium=medium,
campaign_id=campaign_id,
user_id=getattr(request.user, "pk", None),
shorten=True,
)
return JsonResponse(
{
"target_url": target,
"short_url": display if display != target else "",
"display_url": display,
"shortened": display != target,
}
)
@login_required
def campaign_detail(request, pk):
campaign = get_object_or_404(Campaign, pk=pk)
@@ -662,12 +518,6 @@ def campaign_detail(request, pk):
except (TypeError, ValueError):
page = 1
ctx = _campaign_report(campaign, page=page)
_target, display_url = resolve_campaign_tracked_url(
name=campaign.name,
medium=campaign.channel,
campaign_id=campaign.pk,
shorten=True,
)
return render(
request,
"messaging/campaign_detail.html",
@@ -679,10 +529,6 @@ def campaign_detail(request, pk):
"recent_events": ctx["recent_events"],
"events_title": ctx["events_title"],
"events_empty": ctx["events_empty"],
"utm_url": display_url,
"utm_base_url": public_site_base_url(),
"utm_link_label": public_site_link_label(),
"utm_shorten_url": reverse("messaging:campaign_short_link"),
"can_send": campaign.status
in {
Campaign.Status.DRAFT,
@@ -698,7 +544,6 @@ def campaign_detail(request, pk):
"suppressed",
}
).exists(),
"route_targeting": campaign.route_targeting or {},
},
)
@@ -729,9 +574,7 @@ def campaign_status_json(request, pk):
"messages": [
{
"id": str(m.pk),
"contact": (
str(m.contact) if m.contact_id else "Carrier route mailing"
),
"contact": str(m.contact),
"destination": getattr(m, "destination", "") or "",
"status": m.status,
"status_display": m.get_status_display(),
@@ -744,11 +587,7 @@ def campaign_status_json(request, pk):
"events": [
{
"event_type": e.event_type,
"contact": (
str(e.message.contact)
if e.message_id and e.message.contact_id
else "—"
),
"contact": str(e.message.contact) if e.message_id else "—",
"created_at": e.created_at.isoformat(),
}
for e in ctx["recent_events"]
-14
View File
@@ -134,7 +134,6 @@ MIDDLEWARE = [
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
"analytics.middleware.UTMTrackingMiddleware",
"analytics.middleware.PublicPageViewMiddleware",
"public.middleware.UnderConstructionMiddleware",
]
@@ -197,8 +196,6 @@ STORAGES = {
"BACKEND": "whitenoise.storage.CompressedManifestStaticFilesStorage",
},
}
# Hashed static filenames are immutable; long cache helps Lighthouse "efficient cache".
WHITENOISE_MAX_AGE = 31536000
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
@@ -302,10 +299,6 @@ 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", "")
# Fallback $/piece when PCM design pricing is missing.
PCM_POSTCARD_PRICE_FIRSTCLASS = env("PCM_POSTCARD_PRICE_FIRSTCLASS", "")
PCM_POSTCARD_PRICE_STANDARD = env("PCM_POSTCARD_PRICE_STANDARD", "")
PCM_LIST_TYPE = env("PCM_LIST_TYPE", "IRL")
# Legacy / alternate providers (POSTCARD_PROVIDER=lob|click2mail|postgrid)
LOB_API_KEY = env("LOB_API_KEY", "")
CLICK2MAIL_API_KEY = env("CLICK2MAIL_API_KEY", "")
@@ -336,13 +329,6 @@ NOMINATIM_USER_AGENT = env(
)
NOMINATIM_API_KEY = env("NOMINATIM_API_KEY", "")
# --- URL shortener (piha.lc) ---
# Same host as SMS links. Empty = skip minting (composer falls back to the
# long UTM URL). Token format: name:secret.
SHORTENER_BASE_URL = env("SHORTENER_BASE_URL", "")
SHORTENER_API_TOKEN = env("SHORTENER_API_TOKEN", "")
SHORTENER_TIMEOUT_SECONDS = int(env("SHORTENER_TIMEOUT_SECONDS", "10") or "10")
# --- Tianji analytics (pageviews + events; notice banner, not opt-in) ---
TIANJI_ENABLED = env_bool("TIANJI_ENABLED", not DEBUG)
TIANJI_TRACKER_URL = env(
+6 -1
View File
@@ -1 +1,6 @@
: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:5pLine truncated
/*!
* 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,Line truncated
+18 -1
View File
@@ -1 +1,18 @@
@font-face{font-display:swap;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'}.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}.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}.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-heLine truncated
/*
*
* 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:Line truncated
*
* 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 {Line truncated
* 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 { conteLine truncated
-78
View File
@@ -83,22 +83,6 @@ body.portal {
/* Portal widgets */
.stat-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 16px; margin-bottom: 24px; }
.analytics-overview {
display: grid;
grid-template-columns: minmax(140px, 1fr) 4fr;
gap: 16px;
margin-bottom: 24px;
align-items: stretch;
}
.analytics-overview .stat-card {
display: flex;
flex-direction: column;
justify-content: center;
}
.analytics-overview .panel { margin-bottom: 0; }
@media (max-width: 800px) {
.analytics-overview { grid-template-columns: 1fr; }
}
.stat-card {
background: #fff;
border: 1px solid var(--monica-border);
@@ -362,29 +346,6 @@ body.portal {
.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; }
.check-row.account-pick { align-items: flex-start; }
.account-pick-copy {
min-width: 0;
overflow-wrap: anywhere;
word-break: break-word;
}
.account-pick-copy strong { display: block; font-weight: 600; }
.account-rename {
display: flex;
gap: 6px;
align-items: center;
flex-wrap: wrap;
}
.account-rename input[type="text"] {
flex: 1;
min-width: 160px;
max-width: 100%;
padding: 8px 10px;
border: 1px solid var(--monica-border);
font: inherit;
background: #fff;
box-sizing: border-box;
}
.row-warn td { background: #fffbeb; }
.auth-alert {
background: #fff7ed;
@@ -690,42 +651,3 @@ body.portal {
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; }
.utm-panel {
margin-top: 4px;
padding-top: 4px;
}
.utm-url {
display: block;
font-size: 12px;
line-height: 1.45;
word-break: break-all;
background: #f8fafc;
border: 1px solid var(--monica-border);
padding: 8px 10px;
color: #0f172a;
}
.utm-actions {
display: flex;
gap: 8px;
flex-wrap: wrap;
margin-top: 8px;
}
.utm-qr {
display: flex;
gap: 16px;
align-items: flex-start;
flex-wrap: wrap;
margin-top: 12px;
}
.utm-qr[hidden] {
display: none !important;
}
.utm-qr canvas {
display: block;
border: 1px solid var(--monica-border);
background: #fff;
}
.utm-qr-actions { min-width: 160px; }
.utm-qr-actions .btn { margin: 0 8px 8px 0; }
#preview-body a { color: var(--monica-primary); }
@@ -1 +0,0 @@
: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:5pLine truncated
+239 -1
View File
@@ -1 +1,239 @@
@charset "UTF-8";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}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:#Line truncated
@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: .8Line truncated
*
* 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 { positionLine truncated
* 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-bLine truncated
*
* 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 8pxLine truncated
*
* 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 { contentLine truncated
*
* 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; marginLine truncated
*
* 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'] .Line truncated
*
* 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 Line truncated
*
* 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 Line truncated
*
* 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% - -80Line truncated
*
* 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: Line truncated
* @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: 7Line truncated
*
* 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; } Line truncated
*
* 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 .Line truncated
*
* 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-Line truncated
*
* 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-statLine truncated
*
* 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); } Line truncated
*
* 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; bottomLine truncated
*
* 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 { maLine truncated
* 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: Line truncated
*
* 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__arrowLine truncated
*
* 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 { paddinLine truncated
@@ -20,55 +20,9 @@
width: auto;
}
/* Footer section titles — keep h5 visual weight without skipping heading levels */
.footer-heading {
font-size: 18px;
line-height: 1.4;
letter-spacing: 0.06em;
margin: 0 0 0.75rem;
}
.about-subtitle {
font-size: 18px;
color: #4b5563; /* was muted grey; needs ≥4.5:1 on white/surface */
}
/* Contrast fixes (Lighthouse color-contrast) */
.page .bg-gray-100 .text-width-1,
.page section.bg-gray-100 p {
color: #4b5563;
}
.page .footer-advanced-text,
.page .footer-advanced .footer-advanced-text {
color: #c5c6ca !important;
}
.page .footer-advanced .rights,
.page .footer-advanced .rights span {
color: #c5c6ca !important;
}
.page .form-label-outside {
color: #374151 !important;
}
.page .form-hint {
font-weight: 400;
color: #4b5563;
}
.page .form-consent-note {
font-size: 13px;
color: #4b5563;
margin: 12px 0 20px;
}
.page .contact-form-title {
font-size: 1.75rem;
margin-bottom: 1rem;
}
/* Skip layout work for below-fold marketing sections */
.page > .section.section-lg,
.page > .section.section-sm,
.page > .section.bg-gray-100 {
content-visibility: auto;
contain-intrinsic-size: 800px;
color: var(--monica-muted);
}
.page .button-primary,
Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 139 KiB

-296
View File
@@ -1,296 +0,0 @@
/**
* Campaign UTM link helper (composer + report).
* utm_source=monica, utm_medium=email|sms|postcard, utm_campaign=slug(name).
* Short URLs come from the portal JSON endpoint (server mints via piha.li).
*/
(function (global) {
"use strict";
var SOURCE = "monica";
var SLUG_MAX = 80;
var SHORTEN_WAIT_MS = 400;
var UTM_URL_RE = new RegExp(
"https?:\\/\\/[^\\s<>\"]+utm_source=" + SOURCE + "[^\\s<>\"]*",
"i"
);
var SHORT_URL_RE =
/https?:\/\/(?:(?:www\.)?(?:beta\.)?piha\.li|(?:127\.0\.0\.1|localhost):\d+)\/[a-z0-9]{4,8}/i;
function slug(name) {
var s = String(name || "")
.trim()
.toLowerCase()
.replace(/[^a-z0-9]+/g, "-")
.replace(/^-+|-+$/g, "");
return (s || "campaign").slice(0, SLUG_MAX);
}
function buildUrl(base, medium, name) {
var root = String(base || "").replace(/\/+$/, "") || "https://mkdrealtor.com";
var ch = String(medium || "email").toLowerCase();
if (ch !== "email" && ch !== "sms" && ch !== "postcard") ch = "email";
var q =
"utm_source=" +
encodeURIComponent(SOURCE) +
"&utm_medium=" +
encodeURIComponent(ch) +
"&utm_campaign=" +
encodeURIComponent(slug(name));
return root + "/?" + q;
}
function replaceTextUrl(text, url) {
var raw = String(text || "");
if (UTM_URL_RE.test(raw)) {
return raw.replace(UTM_URL_RE, url);
}
if (SHORT_URL_RE.test(raw)) {
return raw.replace(SHORT_URL_RE, url);
}
var trimmed = raw.replace(/\s+$/, "");
return trimmed ? trimmed + "\n\n" + url : url;
}
function linkify(text) {
var esc = String(text || "").replace(/[&<>"']/g, function (c) {
return (
{ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" }[
c
] || c
);
});
return esc.replace(
/(https?:\/\/[^\s]+)/g,
'<a href="$1" rel="noopener noreferrer">$1</a>'
);
}
function ensureQuillLink(quill, url, label) {
if (!quill || !quill.root) return;
var existing = quill.root.querySelectorAll(
'a[data-monica-utm], a[href*="utm_source=' + SOURCE + '"], a[href*="piha.li/"]'
);
if (existing.length) {
existing.forEach(function (a) {
a.setAttribute("href", url);
a.setAttribute("data-monica-utm", "1");
});
return;
}
var labelText = label || "MKDRealtor.com";
var start = Math.max(0, quill.getLength() - 1);
quill.insertText(start, "\n" + labelText, "silent");
quill.formatText(start + 1, labelText.length, "link", url, "silent");
var links = quill.root.querySelectorAll("a");
var last = links[links.length - 1];
if (last) last.setAttribute("data-monica-utm", "1");
}
function renderQr(canvas, url) {
if (!canvas || !url || typeof QRCode === "undefined") return Promise.resolve();
return QRCode.toCanvas(canvas, url, {
width: 192,
margin: 1,
color: { dark: "#00626c", light: "#ffffff" },
});
}
function flash(el, okText, errText, ok) {
if (!el) return;
el.textContent = ok ? okText : errText;
el.hidden = false;
window.setTimeout(function () {
el.hidden = true;
}, 2200);
}
function copyText(text) {
if (navigator.clipboard && navigator.clipboard.writeText) {
return navigator.clipboard.writeText(text);
}
return Promise.reject(new Error("clipboard unavailable"));
}
function copyCanvas(canvas) {
if (!canvas || !canvas.toBlob) {
return Promise.reject(new Error("canvas unavailable"));
}
return new Promise(function (resolve, reject) {
canvas.toBlob(function (blob) {
if (!blob) {
reject(new Error("qr blob failed"));
return;
}
if (!navigator.clipboard || !navigator.clipboard.write || typeof ClipboardItem === "undefined") {
reject(new Error("image clipboard unavailable"));
return;
}
navigator.clipboard
.write([new ClipboardItem({ "image/png": blob })])
.then(resolve, reject);
}, "image/png");
});
}
function downloadCanvas(canvas, filename) {
if (!canvas) return;
var a = document.createElement("a");
a.href = canvas.toDataURL("image/png");
a.download = filename || "campaign-qr.png";
document.body.appendChild(a);
a.click();
a.remove();
}
function bindPanel(panel, opts) {
if (!panel) return;
opts = opts || {};
var urlEl = panel.querySelector("[data-utm-url]");
var canvas = panel.querySelector("[data-utm-qr]");
var qrWrap = panel.querySelector("[data-utm-qr-wrap]");
var smsHint = panel.querySelector("[data-utm-sms-hint]");
var emailHint = panel.querySelector("[data-utm-email-hint]");
var postcardHint = panel.querySelector("[data-utm-postcard-hint]");
var statusEl = panel.querySelector("[data-utm-status]");
var base = panel.getAttribute("data-base-url") || "";
var label = panel.getAttribute("data-link-label") || "MKDRealtor.com";
var live = panel.getAttribute("data-live") === "1";
var shortenUrl = panel.getAttribute("data-shorten-url") || "";
var csrf =
(opts.csrfToken || panel.getAttribute("data-csrf") || "").trim();
var shortenTimer = null;
var shortenSeq = 0;
function medium() {
if (typeof opts.getMedium === "function") return opts.getMedium();
return panel.getAttribute("data-medium") || "email";
}
function name() {
if (typeof opts.getName === "function") return opts.getName();
return panel.getAttribute("data-campaign-name") || "";
}
function campaignId() {
return (
(typeof opts.getCampaignId === "function" && opts.getCampaignId()) ||
panel.getAttribute("data-campaign-id") ||
""
);
}
function paint(url, inject) {
var ch = medium();
var showQr = ch === "email" || ch === "postcard";
if (urlEl) urlEl.textContent = url;
if (qrWrap) qrWrap.hidden = !showQr;
if (smsHint) smsHint.hidden = ch !== "sms";
if (emailHint) emailHint.hidden = ch !== "email";
if (postcardHint) postcardHint.hidden = ch !== "postcard";
panel.querySelectorAll("[data-utm-qr-email-hint]").forEach(function (el) {
el.hidden = ch !== "email";
});
panel.querySelectorAll("[data-utm-qr-postcard-hint]").forEach(function (el) {
el.hidden = ch !== "postcard";
});
if (showQr && canvas) {
renderQr(canvas, url).catch(function () {});
}
if (inject && typeof opts.onUrlChange === "function") {
opts.onUrlChange(url, ch, label);
}
}
function apply() {
var ch = medium();
var longUrl = buildUrl(base, ch, name());
if (!shortenUrl) {
paint(longUrl, true);
return longUrl;
}
paint(longUrl, false);
var seq = ++shortenSeq;
window.clearTimeout(shortenTimer);
shortenTimer = window.setTimeout(function () {
fetch(shortenUrl, {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
"X-CSRFToken": csrf,
},
credentials: "same-origin",
body: JSON.stringify({
name: name(),
medium: ch,
campaign_id: campaignId(),
}),
})
.then(function (r) {
return r.ok ? r.json() : Promise.reject();
})
.then(function (data) {
if (seq !== shortenSeq) return;
paint((data && data.display_url) || longUrl, true);
})
.catch(function () {
if (seq !== shortenSeq) return;
paint(longUrl, true);
});
}, SHORTEN_WAIT_MS);
return longUrl;
}
panel.querySelectorAll("[data-utm-copy-url]").forEach(function (btn) {
btn.addEventListener("click", function () {
var url = (urlEl && urlEl.textContent) || "";
copyText(url).then(
function () {
flash(statusEl, "Link copied.", "Could not copy.", true);
},
function () {
flash(statusEl, "", "Could not copy link.", false);
}
);
});
});
panel.querySelectorAll("[data-utm-copy-qr]").forEach(function (btn) {
btn.addEventListener("click", function () {
copyCanvas(canvas).then(
function () {
flash(statusEl, "QR image copied.", "Could not copy QR.", true);
},
function () {
flash(statusEl, "", "Copy failed — use Download PNG.", false);
}
);
});
});
panel.querySelectorAll("[data-utm-download-qr]").forEach(function (btn) {
btn.addEventListener("click", function () {
var slugName = slug(name());
downloadCanvas(canvas, "qr-" + slugName + ".png");
flash(statusEl, "QR downloaded.", "", true);
});
});
if (live) {
var nameField = document.getElementById("id_name");
if (nameField) nameField.addEventListener("input", apply);
}
panel._utmApply = apply;
apply();
return { apply: apply, buildUrl: buildUrl };
}
global.MonicaUtm = {
SOURCE: SOURCE,
slug: slug,
buildUrl: buildUrl,
replaceTextUrl: replaceTextUrl,
linkify: linkify,
ensureQuillLink: ensureQuillLink,
bindPanel: bindPanel,
};
})(window);
+2 -2
View File
@@ -5,7 +5,7 @@
* @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&&n<t?[this[n]]:[])},end:function(){return this.prevObject||this.constructor()},push:s,sort:n.sort,splice:n.splice},w.extend=w.fn.extend=function(){var e,t,n,r,i,o,a=arguments[0]||{},s=1,u=arguments.length,l=!1;for("boolean"==typeof a&&(l=a,a=arguments[s]||{},s++),"object"==typeof a||g(a)||(a={}),s===u&&(a=this,s--);s<u;s++)if(null!=(e=arguments[s]))for(t in e)n=a[t],a!==(r=e[t])&&(l&&r&&(w.isPlainObject(r)||(i=Array.isArray(r)))?(i?(i=!1,o=n&&Array.isArray(n)?n:[]):o=n&&w.isPlainObject(n)?n:{},a[t]=w.extend(l,o,r)):void 0!==r&&(a[t]=r));return a},w.extend({expando:"jQuery"+("3.3.1"+Math.random()).replace(/\D/g,""),isReady:!0,error:function(e){throw new Error(e)},noop:function(){},isPlainObject:function(e){var t,n;return!(!e||"[object Object]"!==c.call(e))&&(!(t=i(e))||"function"==typeof(n=f.call(t,"constructor")&&t.constructor)&&p.call(n)===d)},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},globalEval:function(e){m(e)},each:function(e,t){var n,r=0;if(C(e)){for(n=e.length;r<n;r++)if(!1===t.call(e[r],r,e[r]))break}else for(r in e)if(!1===t.call(e[r],r,e[r]))break;return e},trim:function(e){return null==e?"":(e+"").replace(T,"")},makeArray:function(e,t){var n=t||[];return null!=e&&(C(Object(e))?w.merge(n,"string"==typeof e?[e]:e):s.call(n,e)),n},inArray:function(e,t,n){return null==t?-1:u.call(t,e,n)},merge:function(e,t){for(var n=+t.length,r=0,i=e.length;r<n;r++)e[i++]=t[r];return e.length=i,e},grep:function(e,t,n){for(var r,i=[],o=0,a=e.length,s=!n;o<a;o++)(r=!t(e[o],o))!==s&&i.push(e[o]);return i},map:function(e,t,n){var r,i,o=0,s=[];if(C(e))for(r=e.length;o<r;o++)null!=(i=t(e[o],o,n))&&s.push(i);else for(o in e)null!=(i=t(e[o],o,n))&&s.push(i);return a.apply([],s)},guid:1,support:h}),"function"==typeof Symbol&&(w.fn[Symbol.iterator]=n[Symbol.iterator]),w.each("Boolean Number String Function Array Date RegExp Object Error Symbol".split(" "),function(e,t){l["[object "+t+"]"]=t.toLowerCase()});function C(e){var t=!!e&&"length"in e&&e.length,n=x(e);return!g(e)&&!y(e)&&("array"===n||0===t||"number"==typeof t&&t>0&&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<r;n++)if(e[n]===t)return n;return-1},P="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",M="[\\x20\\t\\r\\n\\f]",R="(?:\\\\.|[\\w-]|[^\0-\\xa0])+",I="\\["+M+"*("+R+")(?:"+M+"*([*^$|!~]?=)"+M+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+R+"))|)"+M+"*\\]",W=":("+R+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+I+")*)|.*)\\)|)",$=new RegExp(M+"+","g"),B=new RegExp("^"+M+"+|((?:^|[^\\\\])(?:\\\\.)*)"+M+"+$","g"),F=new RegExp("^"+M+"*,"+M+"*"),_=new RegExp("^"+M+"*([>+~]|"+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)Line truncated
!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&&n<t?[this[n]]:[])},end:function(){return this.prevObject||this.constructor()},push:s,sort:n.sort,splice:n.splice},w.extend=w.fn.extend=function(){var e,t,n,r,i,o,a=arguments[0]||{},s=1,u=arguments.length,l=!1;for("boolean"==typeof a&&(l=a,a=arguments[s]||{},s++),"object"==typeof a||g(a)||(a={}),s===u&&(a=this,s--);s<u;s++)if(null!=(e=arguments[s]))for(t in e)n=a[t],a!==(r=e[t])&&(l&&r&&(w.isPlainObject(r)||(i=Array.isArray(r)))?(i?(i=!1,o=n&&Array.isArray(n)?n:[]):o=n&&w.isPlainObject(n)?n:{},a[t]=w.extend(l,o,r)):void 0!==r&&(a[t]=r));return a},w.extend({expando:"jQuery"+("3.3.1"+Math.random()).replace(/\D/g,""),isReady:!0,error:function(e){throw new Error(e)},noop:function(){},isPlainObject:function(e){var t,n;return!(!e||"[object Object]"!==c.call(e))&&(!(t=i(e))||"function"==typeof(n=f.call(t,"constructor")&&t.constructor)&&p.call(n)===d)},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},globalEval:function(e){m(e)},each:function(e,t){var n,r=0;if(C(e)){for(n=e.length;r<n;r++)if(!1===t.call(e[r],r,e[r]))break}else for(r in e)if(!1===t.call(e[r],r,e[r]))break;return e},trim:function(e){return null==e?"":(e+"").replace(T,"")},makeArray:function(e,t){var n=t||[];return null!=e&&(C(Object(e))?w.merge(n,"string"==typeof e?[e]:e):s.call(n,e)),n},inArray:function(e,t,n){return null==t?-1:u.call(t,e,n)},merge:function(e,t){for(var n=+t.length,r=0,i=e.length;r<n;r++)e[i++]=t[r];return e.length=i,e},grep:function(e,t,n){for(var r,i=[],o=0,a=e.length,s=!n;o<a;o++)(r=!t(e[o],o))!==s&&i.push(e[o]);return i},map:function(e,t,n){var r,i,o=0,s=[];if(C(e))for(r=e.length;o<r;o++)null!=(i=t(e[o],o,n))&&s.push(i);else for(o in e)null!=(i=t(e[o],o,n))&&s.push(i);return a.apply([],s)},guid:1,support:h}),"function"==typeof Symbol&&(w.fn[Symbol.iterator]=n[Symbol.iterator]),w.each("Boolean Number String Function Array Date RegExp Object Error Symbol".split(" "),function(e,t){l["[object "+t+"]"]=t.toLowerCase()});function C(e){var t=!!e&&"length"in e&&e.length,n=x(e);return!g(e)&&!y(e)&&("array"===n||0===t||"number"==typeof t&&t>0&&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<r;n++)if(e[n]===t)return n;return-1},P="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",M="[\\x20\\t\\r\\n\\f]",R="(?:\\\\.|[\\w-]|[^\0-\\xa0])+",I="\\["+M+"*("+R+")(?:"+M+"*([*^$|!~]?=)"+M+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+R+"))|)"+M+"*\\]",W=":("+R+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+I+")*)|.*)\\)|)",$=new RegExp(M+"+","g"),B=new RegExp("^"+M+"+|((?:^|[^\\\\])(?:\\\\.)*)"+M+"+$","g"),F=new RegExp("^"+M+"*,"+M+"*"),_=new RegExp("^"+M+"*([>+~]|"+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)Line truncated
/**
@@ -282,7 +282,7 @@ function pageTransition(t){t=t||{},t.target=t.target||null,t.delay=t.delay||500,
* @link http://vodkabears.github.io/vide/
* @version 0.5.1
*/
!function(a,b){"function"==typeof define&&define.amd?define(["jquery"],b):b("object"==typeof exports?require("jquery"):a.jQuery)}(this,function(a){"use strict";function b(a){var b,c,d,e,f,g,h,i={};for(f=a.replace(/\s*:\s*/g,":").replace(/\s*,\s*/g,",").split(","),h=0,g=f.length;h<g&&(c=f[h],c.search(/^(http|https|ftp):\/\//)===-1&&c.search(":")!==-1);h++)b=c.indexOf(":"),d=c.substring(0,b),e=c.substring(b+1),e||(e=void 0),"string"==typeof e&&(e="true"===e||"false"!==e&&e),"string"==typeof e&&(e=isNaN(e)?e:+e),i[d]=e;return null==d&&null==e?a:i}function c(a){a=""+a;var b,c,d,e=a.split(/\s+/),f="50%",g="50%";for(d=0,b=e.length;d<b;d++)c=e[d],"left"===c?f="0%":"right"===c?f="100%":"top"===c?g="0%":"bottom"===c?g="100%":"center"===c?0===d?f="50%":g="50%":0===d?f=c:g=c;return{x:f,y:g}}function d(b,c){var d=function(){c(this.src)};a('<img src="'+b+'.gif">').on("load",d),a('<img src="'+b+'.jpg">').on("load",d),a('<img src="'+b+'.jpeg">').on("load",d),a('<img src="'+b+'.png">').on("load",d)}function e(c,d,e){if(this.$element=a(c),"string"==typeof d&&(d=b(d)),e?"string"==typeof e&&(e=b(e)):e={},"string"==typeof d)d=d.replace(/\.\w*$/,"");else if("object"==typeof d)for(var f in d)d.hasOwnProperty(f)&&(d[f]=d[f].replace(/\.\w*$/,""));this.settings=a.extend({},g,e),this.path=d;try{this.init()}catch(i){if(i.message!==h)throw i}}var f="vide",g={volume:1,playbackRate:1,muted:!0,loop:!0,autoplay:!0,position:"50% 50%",posterType:"detect",resizing:!0,bgColor:"transparent",className:""},h="Not implemented";e.prototype.init=function(){var b,e,f=this,g=f.path,i=g,j="",k=f.$element,l=f.settings,m=c(l.position),n=l.posterType;e=f.$wrapper=a("<div>").addClass(l.className).css({position:"absolute","z-index":-1,top:0,left:0,bottom:0,right:0,overflow:"hidden","-webkit-background-size":"cover","-moz-background-size":"cover","-o-background-size":"cover","background-size":"cover","background-color":l.bgColor,"background-repeat":"no-repeat","background-position":m.x+" "+m.y}),"object"==typeof g&&(g.poster?i=g.poster:g.mp4?i=g.mp4:g.webm?i=g.webm:g.ogv&&(i=g.ogv)),"detect"===n?d(i,function(a){e.css("background-image","url("+a+")")}):"none"!==n&&e.css("background-image","url("+i+"."+n+")"),"static"===k.css("position")&&k.css("position","relative"),k.prepend(e),"object"==typeof g?(g.mp4&&(j+='<source src="'+g.mp4+'.mp4" type="video/mp4">'),g.webm&&(j+='<source src="'+g.webm+'.webm" type="video/webm">'),g.ogv&&(j+='<source src="'+g.ogv+'.ogv" type="video/ogg">'),b=f.$video=a("<video>"+j+"</video>")):b=f.$video=a('<video><source src="'+g+'.mp4" type="video/mp4"><source src="'+g+'.webm" type="video/webm"><source src="'+g+'.ogv" type="video/ogg"></video>');try{b.prop({autoplay:l.autoplay,loop:l.loop,volume:l.volume,muted:l.muted,defaultMuted:l.muted,playbackRate:l.playbackRate,defaultPlaybackRate:l.playbackRate})}catch(o){throw new Error(h)}b.css({margin:"auto",position:"absolute","z-index":-1,top:m.y,left:m.x,"-webkit-transform":"translate(-"+m.x+", -"+m.y+")","-ms-transform":"translate(-"+m.x+", -"+m.y+")","-moz-transform":"translate(-"+m.x+", -"+m.y+")",transform:"translate(-"+m.x+", -"+m.y+")",visibility:"hidden",opacity:0}).one("canplaythrough.vide",function(){f.resize()}).one("playing.vide",function(){b.css({visibility:"visible",opacity:1}),e.css("background-image","none")}),k.on("resize.vide",function(){l.resizing&&f.resize()}),e.append(b)},e.prototype.getVideoObject=function(){return this.$video[0]},e.prototype.resize=function(){if(this.$video){var a=this.$wrapper,b=this.$video,c=b[0],d=c.videoHeight,e=c.videoWidth,f=a.height(),g=a.width();g/e>f/d?b.css({width:g+2,height:"auto"}):b.css({width:"auto",height:f+2})}},e.prototype.destroy=function(){delete a[f].lookup[this.index],this.$video&&this.$video.off(f),this.$element.off(f).removeData(f),this.$wrapper.remove()},a[f]={lookup:[]},a.fn[f]=function(b,c){var d;return this.each(function(){d=a.data(this,f),d&&d.destroy(),d=new e(this,b,c),d.index=a[f].lookup.push(d)-1,a.data(this,f,d)}),this},a(document).ready(function(){var b=a(window);b.on("resize.vide",function(){for(var b,c=a[f].lookup.length,d=0;d<c;d++)b=a[f].lookup[d],b&&b.settings.resizing&&b.resize()}),b.on("pagehide.vide",function(){return!1}),a(document).find("[data-vide-bg]").each(function(b,c){var d=a(c),e=d.data("vide-options"),g=d.data("vide-bg");d[f](g,e)})})});
!function(a,b){"function"==typeof define&&define.amd?define(["jquery"],b):b("object"==typeof exports?require("jquery"):a.jQuery)}(this,function(a){"use strict";function b(a){var b,c,d,e,f,g,h,i={};for(f=a.replace(/\s*:\s*/g,":").replace(/\s*,\s*/g,",").split(","),h=0,g=f.length;h<g&&(c=f[h],c.search(/^(http|https|ftp):\/\//)===-1&&c.search(":")!==-1);h++)b=c.indexOf(":"),d=c.substring(0,b),e=c.substring(b+1),e||(e=void 0),"string"==typeof e&&(e="true"===e||"false"!==e&&e),"string"==typeof e&&(e=isNaN(e)?e:+e),i[d]=e;return null==d&&null==e?a:i}function c(a){a=""+a;var b,c,d,e=a.split(/\s+/),f="50%",g="50%";for(d=0,b=e.length;d<b;d++)c=e[d],"left"===c?f="0%":"right"===c?f="100%":"top"===c?g="0%":"bottom"===c?g="100%":"center"===c?0===d?f="50%":g="50%":0===d?f=c:g=c;return{x:f,y:g}}function d(b,c){var d=function(){c(this.src)};a('<img src="'+b+'.gif">').on("load",d),a('<img src="'+b+'.jpg">').on("load",d),a('<img src="'+b+'.jpeg">').on("load",d),a('<img src="'+b+'.png">').on("load",d)}function e(c,d,e){if(this.$element=a(c),"string"==typeof d&&(d=b(d)),e?"string"==typeof e&&(e=b(e)):e={},"string"==typeof d)d=d.replace(/\.\w*$/,"");else if("object"==typeof d)for(var f in d)d.hasOwnProperty(f)&&(d[f]=d[f].replace(/\.\w*$/,""));this.settings=a.extend({},g,e),this.path=d;try{this.init()}catch(i){if(i.message!==h)throw i}}var f="vide",g={volume:1,playbackRate:1,muted:!0,loop:!0,autoplay:!0,position:"50% 50%",posterType:"detect",resizing:!0,bgColor:"transparent",className:""},h="Not implemented";e.prototype.init=function(){var b,e,f=this,g=f.path,i=g,j="",k=f.$element,l=f.settings,m=c(l.position),n=l.posterType;e=f.$wrapper=a("<div>").addClass(l.className).css({position:"absolute","z-index":-1,top:0,left:0,bottom:0,right:0,overflow:"hidden","-webkit-background-size":"cover","-moz-background-size":"cover","-o-background-size":"cover","background-size":"cover","background-color":l.bgColor,"background-repeat":"no-repeat","background-position":m.x+" "+m.y}),"object"==typeof g&&(g.poster?i=g.poster:g.mp4?i=g.mp4:g.webm?i=g.webm:g.ogv&&(i=g.ogv)),"detect"===n?d(i,function(a){e.css("background-image","url("+a+")")}):"none"!==n&&e.css("background-image","url("+i+"."+n+")"),"static"===k.css("position")&&k.css("position","relative"),k.prepend(e),"object"==typeof g?(g.mp4&&(j+='<source src="'+g.mp4+'.mp4" type="video/mp4">'),g.webm&&(j+='<source src="'+g.webm+'.webm" type="video/webm">'),g.ogv&&(j+='<source src="'+g.ogv+'.ogv" type="video/ogg">'),b=f.$video=a("<video>"+j+"</video>")):b=f.$video=a('<video><source src="'+g+'.mp4" type="video/mp4"><source src="'+g+'.webm" type="video/webm"><source src="'+g+'.ogv" type="video/ogg"></video>');try{b.prop({autoplay:l.autoplay,loop:l.loop,volume:l.volume,muted:l.muted,defaultMuted:l.muted,playbackRate:l.playbackRate,defaultPlaybackRate:l.playbackRate})}catch(o){throw new Error(h)}b.css({margin:"auto",position:"absolute","z-index":-1,top:m.y,left:m.x,"-webkit-transform":"translate(-"+m.x+", -"+m.y+")","-ms-transform":"translate(-"+m.x+", -"+m.y+")","-moz-transform":"translate(-"+m.x+", -"+m.y+")",transform:"translate(-"+m.x+", -"+m.y+")",visibility:"hidden",opacity:0}).one("canplaythrough.vide",function(){f.resize()}).one("playing.vide",function(){b.css({visibility:"visible",opacity:1}),e.css("background-image","none")}),k.on("resize.vide",function(){l.resizing&&f.resize()}),e.append(b)},e.prototype.getVideoObject=function(){return this.$video[0]},e.prototype.resize=function(){if(this.$video){var a=this.$wrapper,b=this.$video,c=b[0],d=c.videoHeight,e=c.videoWidth,f=a.height(),g=a.width();g/e>f/d?b.css({width:g+2,height:"auto"}):b.css({width:"auto",height:f+2})}},e.prototype.destroy=function(){delete a[f].lookup[this.index],this.$video&&this.$video.off(f),this.$element.off(f).removeData(f),this.$wrapper.remove()},a[f]={lookup:[]},a.fn[f]=function(b,c){var d;return this.each(function(){d=a.data(this,f),d&&d.destroy(),d=new e(this,b,c),d.index=a[f].lookup.push(d)-1,a.data(this,f,d)}),this},a(document).ready(function(){var b=a(window);b.on("resize.vide",function(){for(var b,c=a[f].lookup.length,d=0;d<c;d++)b=a[f].lookup[d],b&&b.settings.resizing&&b.resize()}),b.on("unload.vide",function(){return!1}),a(document).find("[data-vide-bg]").each(function(b,c){var d=a(c),e=d.data("vide-options"),g=d.data("vide-bg");d[f](g,e)})})});
/**
@@ -1,278 +0,0 @@
(function () {
function money(n) {
if (n === null || n === undefined || n === "") return "—";
var v = Number(n);
if (Number.isNaN(v)) return "—";
return "$" + v.toFixed(2);
}
function hhSize(n) {
if (n === null || n === undefined || n === "") return "—";
var v = Number(n);
if (Number.isNaN(v)) return "—";
return v.toFixed(1);
}
function income(n) {
if (n === null || n === undefined || n === "") return "—";
var v = Number(n);
if (Number.isNaN(v)) return "—";
return "$" + Math.round(v).toLocaleString("en-US");
}
function count(n) {
if (n === null || n === undefined || n === "") return "—";
var v = Number(n);
if (Number.isNaN(v)) return "—";
return String(v);
}
function initPostcardRoutes(opts) {
var form = document.getElementById("campaign-compose");
if (!form) return;
var audience = document.getElementById("id_audience");
var picker = document.getElementById("postcard-route-picker");
var listCost = document.getElementById("postcard-list-cost");
var hidden = document.getElementById("id_route_targeting");
var findBtn = document.getElementById("route-find-btn");
var status = document.getElementById("route-status");
var tableWrap = document.getElementById("route-table-wrap");
var tbody = document.getElementById("route-table-body");
var selectedBox = document.getElementById("route-selected-cost");
var eddmLink = document.getElementById("eddm-map-link");
var quoteUrl = opts.quoteUrl;
var csrfToken = opts.csrfToken;
var maxRoutes = 2;
var routes = [];
function isPostcardAudience() {
var v = (audience && audience.value) || "";
return v === "postcard_opt_in" || v === "carrier_route";
}
function isRouteAudience() {
return ((audience && audience.value) || "") === "carrier_route";
}
function templateId() {
return (document.getElementById("id_template_id") || {}).value || "";
}
function setStatus(text, isError) {
if (!status) return;
status.textContent = text || "";
status.classList.toggle("error", !!isError);
}
function selectedCodes() {
return Array.prototype.map.call(
form.querySelectorAll('input[name="route_code"]:checked'),
function (el) { return el.value; }
);
}
function postQuote(body) {
return fetch(quoteUrl, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-CSRFToken": csrfToken,
Accept: "application/json"
},
credentials: "same-origin",
body: JSON.stringify(body)
}).then(function (res) {
return res.json().then(function (json) {
if (!res.ok) throw new Error(json.detail || "Quote failed");
return json;
});
});
}
function renderListCost(data) {
if (!listCost) return;
if (!isPostcardAudience() || isRouteAudience()) {
listCost.hidden = true;
return;
}
listCost.hidden = false;
var src = data.price_source === "fallback" ? " (env fallback rate)" : "";
if (data.per_piece == null) {
listCost.innerHTML =
"<strong>Estimated cost:</strong> " + data.count +
" household" + (data.count === 1 ? "" : "s") +
" · per-piece price unavailable. Set PCM_POSTCARD_PRICE_STANDARD / FIRSTCLASS.";
return;
}
listCost.innerHTML =
"<strong>Estimated cost:</strong> " + data.count +
" × " + money(data.per_piece) + " = " + money(data.total) +
" (" + data.mail_class + src + ")";
}
function refreshListCost() {
if (!isPostcardAudience() || isRouteAudience()) {
if (listCost) listCost.hidden = true;
return;
}
postQuote({ action: "mailing_list", template_id: templateId() })
.then(renderListCost)
.catch(function (err) {
if (listCost) {
listCost.hidden = false;
listCost.textContent = err.message || "Could not load mailing-list cost.";
}
});
}
function writeHidden(payload) {
if (hidden) hidden.value = payload ? JSON.stringify(payload) : "";
}
function renderSelected(data) {
if (!selectedBox) return;
if (!data) {
selectedBox.hidden = true;
writeHidden("");
return;
}
selectedBox.hidden = false;
var src = data.price_source === "fallback" ? " using env fallback rate" : "";
var missing = data.per_piece == null
? " Per-piece price unavailable — set PCM_POSTCARD_PRICE_* before sending."
: "";
selectedBox.innerHTML =
"<strong>Selected:</strong> " + (data.record_count || 0) +
" household" + ((data.record_count || 0) === 1 ? "" : "s") +
" on " + (data.selected_codes || []).join(", ") +
" · " + (data.mail_class || "") +
" · est. " + money(data.total) + src + "." + missing;
writeHidden({
selected_codes: data.selected_codes,
list_count_id: data.list_count_id,
record_count: data.record_count,
mail_class: data.mail_class,
estimated: data.estimated,
route_rows: data.route_rows || data.routes
});
}
function recount() {
var codes = selectedCodes();
if (!codes.length) {
renderSelected(null);
setStatus("Select one or two routes.");
return;
}
setStatus("Updating quote…");
postQuote({
action: "select",
template_id: templateId(),
routes: codes
}).then(function (data) {
setStatus("");
renderSelected(data);
}).catch(function (err) {
renderSelected(null);
setStatus(err.message || "Could not quote selected routes.", true);
});
}
function renderRoutes(list) {
routes = list || [];
if (!tbody || !tableWrap) return;
if (!routes.length) {
tableWrap.hidden = true;
tbody.innerHTML = "";
return;
}
tableWrap.hidden = false;
tbody.innerHTML = routes.map(function (row) {
return "<tr>" +
"<td><label class=\"check-row\" style=\"margin:0\">" +
"<input type=\"checkbox\" name=\"route_code\" value=\"" +
String(row.code).replace(/"/g, "&quot;") + "\"> " +
String(row.code) + "</label></td>" +
"<td>" + String(row.text || "") + "</td>" +
"<td>" + String(row.total) + "</td>" +
"<td>" + hhSize(row.household_size) + "</td>" +
"<td>" + income(row.median_income) + "</td>" +
"<td>" + count(row.businesses) + "</td>" +
"<td>" + money(row.cost) + "</td></tr>";
}).join("");
tbody.querySelectorAll('input[name="route_code"]').forEach(function (box) {
box.addEventListener("change", function () {
var checked = selectedCodes();
if (checked.length > maxRoutes) {
box.checked = false;
setStatus("Pick at most two routes.", true);
return;
}
recount();
});
});
}
function findRoutes() {
var zip = (document.getElementById("route-zip") || {}).value || "";
var address = (document.getElementById("route-address") || {}).value || "";
var city = (document.getElementById("route-city") || {}).value || "";
var state = (document.getElementById("route-state") || {}).value || "";
var radius = (document.getElementById("route-radius") || {}).value || "";
setStatus("Looking up routes…");
tableWrap.hidden = true;
renderSelected(null);
postQuote({
action: "search",
template_id: templateId(),
zip: zip,
address: address,
city: city,
state: state,
radius: radius
}).then(function (data) {
renderRoutes(data.routes || []);
if (eddmLink && data.eddm_map_url) {
eddmLink.href = data.eddm_map_url;
}
if (!(data.routes || []).length) {
setStatus("No carrier routes for that search.", true);
} else {
setStatus("Select one or two routes. Cost uses " + (data.mail_class || "Standard") + ".");
}
}).catch(function (err) {
renderRoutes([]);
setStatus(err.message || "Route lookup failed.", true);
});
}
function syncVisibility() {
if (picker) picker.hidden = !isRouteAudience();
if (!isRouteAudience()) {
writeHidden("");
if (selectedBox) selectedBox.hidden = true;
}
refreshListCost();
}
if (findBtn) findBtn.addEventListener("click", function (e) {
e.preventDefault();
findRoutes();
});
var tmpl = document.getElementById("id_template_id");
if (tmpl) tmpl.addEventListener("change", function () {
syncVisibility();
if (isRouteAudience() && selectedCodes().length) recount();
});
if (audience) audience.addEventListener("change", syncVisibility);
syncVisibility();
window.MonicaPostcardRoutes = {
isPostcardAudience: isPostcardAudience,
isRouteAudience: isRouteAudience,
syncVisibility: syncVisibility
};
}
window.initPostcardRoutes = initPostcardRoutes;
})();
+1 -14
View File
@@ -1151,15 +1151,10 @@
url;
if (url = $this.attr("data-slide-bg")) {
// Eager-load first slide only; defer others until slide change.
if (j === 0) {
$this.css({
"background-image": "url(" + url + ")",
"background-size": "cover"
});
} else {
$this.attr("data-slide-bg-lazy", url).removeAttr("data-slide-bg");
}
})
}
}
@@ -1199,14 +1194,6 @@
transitionStart: function () {
toggleSwiperInnerVideos(this);
var active = $(this.slides[this.activeIndex]);
var lazyUrl = active.attr("data-slide-bg-lazy");
if (lazyUrl) {
active.css({
"background-image": "url(" + lazyUrl + ")",
"background-size": "cover"
}).removeAttr("data-slide-bg-lazy");
}
}
// TODO code review
// transitionEnd: function () {
+10 -13
View File
@@ -15,7 +15,7 @@
<meta property="og:description" content="{% block og_description %}{{ SITE_NAME }} — {{ SITE_TAGLINE }}. {{ CONTACT_SERVICE_AREA }}.{% endblock %}">
<meta property="og:url" content="{% block og_url %}{{ PUBLIC_SITE_URL }}{{ request.path }}{% endblock %}">
{% block og_image %}
<meta property="og:image" content="{{ PUBLIC_SITE_URL }}{% static 'images/slider-minimal-slide-1-1920x968.webp' %}">
<meta property="og:image" content="{{ PUBLIC_SITE_URL }}{% static 'images/slider-minimal-slide-1-1920x968.jpg' %}">
<meta property="og:image:alt" content="{{ SITE_NAME }} · EXIT Realty">
{% endblock %}
<meta name="twitter:card" content="summary_large_image">
@@ -23,14 +23,11 @@
<meta name="twitter:description" content="{% block twitter_description %}{{ SITE_NAME }} — {{ SITE_TAGLINE }}. {{ CONTACT_SERVICE_AREA }}.{% endblock %}">
<link rel="icon" href="{% static 'brand/favicon-32.png' %}" type="image/png">
<link rel="apple-touch-icon" href="{% static 'brand/apple-touch-icon.png' %}">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Work+Sans:wght@400;500;700&family=Poppins:wght@400;700&display=swap">
<link rel="stylesheet" href="{% static 'css/public-vendor.css' %}" id="main-styles-link">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Work+Sans:300,400,500,700,800%7CPoppins:300,400,700">
<link rel="stylesheet" href="{% static 'css/bootstrap.css' %}">
<link rel="stylesheet" href="{% static 'css/fonts.css' %}">
<link rel="stylesheet" href="{% static 'css/style.css' %}" id="main-styles-link">
<link rel="stylesheet" href="{% static 'css/theme-overrides.css' %}">
{# Icon font pack is large and non-critical for first paint #}
<link rel="stylesheet" href="{% static 'css/fonts.css' %}" media="print" onload="this.media='all'">
<noscript><link rel="stylesheet" href="{% static 'css/fonts.css' %}"></noscript>
{% block structured_data %}{% endblock %}
{% block extra_head %}{% endblock %}
</head>
@@ -52,13 +49,13 @@
<div class="rd-navbar-aside-outer">
<div class="rd-navbar-aside">
<div class="rd-navbar-panel">
<button class="rd-navbar-toggle" type="button" aria-label="Open menu" data-rd-navbar-toggle="#rd-navbar-nav-wrap-1"><span></span></button>
<button class="rd-navbar-toggle" data-rd-navbar-toggle="#rd-navbar-nav-wrap-1"><span></span></button>
<a class="rd-navbar-brand" href="{% url 'public:home' %}" data-tianji-event="nav_home">
<img src="{% static 'brand/exit_logo.png' %}" alt="EXIT Realty · {{ SITE_NAME }}" width="151" height="44"/>
</a>
</div>
<div class="rd-navbar-collapse">
<button class="rd-navbar-collapse-toggle rd-navbar-fixed-element-1" type="button" aria-label="Open contact details" data-rd-navbar-toggle="#rd-navbar-collapse-content-1"><span></span></button>
<button class="rd-navbar-collapse-toggle rd-navbar-fixed-element-1" data-rd-navbar-toggle="#rd-navbar-collapse-content-1"><span></span></button>
<div class="rd-navbar-collapse-content" id="rd-navbar-collapse-content-1">
{% if CONTACT_PHONE %}
<article class="unit align-items-center">
@@ -114,16 +111,16 @@
<div class="row row-50">
<div class="col-lg-6">
<div class="footer-brand-row">
<img src="{% static 'brand/exit_logo.png' %}" alt="EXIT Realty" width="151" height="44">
<img src="{% static 'brand/exit_logo.png' %}" alt="EXIT Realty">
<div>
<p class="footer-heading font-weight-bold text-uppercase text-white" style="margin:0">{{ SITE_NAME }}</p>
<h5 class="font-weight-bold text-uppercase text-white" style="margin:0">{{ SITE_NAME }}</h5>
<span style="color:#7dd3da;font-size:12px">{{ SITE_TAGLINE }}</span>
</div>
</div>
<p class="footer-advanced-text">Local expertise, clear communication, and a marketing platform built to bring serious buyers and sellers to you — not to a generic campaign factory.{% if CONTACT_ADDRESS %} {{ CONTACT_ADDRESS }}.{% endif %}</p>
</div>
<div class="col-lg-6">
<p class="footer-heading font-weight-bold text-uppercase text-white">Navigate</p>
<h5 class="font-weight-bold text-uppercase text-white">Navigate</h5>
<ul class="list-marked list-marked-sm">
<li><a href="{% url 'public:home' %}">Home</a></li>
<li><a href="{% url 'public:about' %}">About</a></li>
+4 -10
View File
@@ -7,12 +7,9 @@
{% block og_description %}Meet {{ SITE_NAME }}, your real estate partner in Chicago's western suburbs with EXIT Realty Redefined — buyers, sellers, and first-time homebuyers welcome. {{ CONTACT_SERVICE_AREA }}.{% endblock %}
{% block twitter_description %}Meet {{ SITE_NAME }}, your real estate partner in Chicago's western suburbs with EXIT Realty Redefined — buyers, sellers, and first-time homebuyers welcome. {{ CONTACT_SERVICE_AREA }}.{% endblock %}
{% block og_image %}
<meta property="og:image" content="{{ PUBLIC_SITE_URL }}{% static 'images/monica-dhillon-headshot.webp' %}">
<meta property="og:image" content="{{ PUBLIC_SITE_URL }}{% static 'images/monica-dhillon-headshot.jpg' %}">
<meta property="og:image:alt" content="Professional headshot of {{ SITE_NAME }}, Real Estate Agent">
{% endblock %}
{% block extra_head %}
<link rel="preload" as="image" type="image/webp" href="{% static 'images/monica-dhillon-headshot-640.webp' %}" imagesrcset="{% static 'images/monica-dhillon-headshot-640.webp' %} 640w, {% static 'images/monica-dhillon-headshot.webp' %} 816w" imagesizes="(max-width: 991px) 90vw, 480px" fetchpriority="high">
{% endblock %}
{% block structured_data %}
<script type="application/ld+json">
{
@@ -25,7 +22,7 @@
"@type": "RealEstateAgent",
"name": "{{ SITE_NAME|escapejs }}",
"url": "{{ PUBLIC_SITE_URL }}/",
"image": "{{ PUBLIC_SITE_URL }}{% static 'images/monica-dhillon-headshot.webp' %}",
"image": "{{ PUBLIC_SITE_URL }}{% static 'images/monica-dhillon-headshot.jpg' %}",
"telephone": "{{ CONTACT_PHONE|escapejs }}",
"email": "{{ CONTACT_EMAIL|escapejs }}",
{% if CONTACT_ADDRESS %}"address": "{{ CONTACT_ADDRESS|escapejs }}",{% endif %}
@@ -39,7 +36,7 @@
</script>
{% endblock %}
{% block content %}
<section class="breadcrumbs-custom bg-image context-dark" style="background-image: url({% static 'images/breadcrumbs-image-1.webp' %});">
<section class="breadcrumbs-custom bg-image context-dark" style="background-image: url({% static 'images/breadcrumbs-image-1.jpg' %});">
<div class="breadcrumbs-custom-inner">
<div class="container breadcrumbs-custom-container">
<div class="breadcrumbs-custom-main">
@@ -65,10 +62,7 @@
<a class="button button-lg button-primary button-winona" href="{% url 'public:contact' %}" data-tianji-event="about_cta" data-tianji-event-destination="contact">Work with me</a>
</div>
<div class="col-md-10 col-lg-6 col-xl-6">
<picture>
<source type="image/webp" srcset="{% static 'images/monica-dhillon-headshot-640.webp' %} 640w, {% static 'images/monica-dhillon-headshot.webp' %} 816w" sizes="(max-width: 991px) 90vw, 480px">
<img class="img-responsive" src="{% static 'images/monica-dhillon-headshot.jpg' %}" alt="Professional headshot of Mandeep &quot;Monica&quot; Dhillon, Real Estate Agent, wearing a blue cardigan against a black background." width="816" height="1024" decoding="async" fetchpriority="high"/>
</picture>
<img class="img-responsive" src="{% static 'images/monica-dhillon-headshot.jpg' %}" alt="Professional headshot of Mandeep &quot;Monica&quot; Dhillon, Real Estate Agent, wearing a blue cardigan against a black background." width="816" height="1024"/>
</div>
</div>
</div>
+4 -5
View File
@@ -35,7 +35,7 @@
</script>
{% endblock %}
{% block content %}
<section class="breadcrumbs-custom bg-image context-dark" style="background-image: url({% static 'images/breadcrumbs-image-1.webp' %});">
<section class="breadcrumbs-custom bg-image context-dark" style="background-image: url({% static 'images/breadcrumbs-image-1.jpg' %});">
<div class="breadcrumbs-custom-inner">
<div class="container breadcrumbs-custom-container">
<div class="breadcrumbs-custom-main">
@@ -83,7 +83,7 @@
<div class="container section-lg">
<div class="row">
<div class="col-lg-8">
<h2 class="text-uppercase contact-form-title">Send a message</h2>
<h3 class="text-uppercase">Send a message</h3>
<form class="rd-form contact-message-form" method="post" action="{% url 'public:contact' %}">
{% csrf_token %}
<div class="row row-10">
@@ -116,7 +116,7 @@
</div>
</div>
<div class="col-12">
<p class="form-label-outside" style="margin:8px 0 4px">Mailing address <span class="form-hint">(optional — for postcards)</span></p>
<p class="form-label-outside" style="margin:8px 0 4px">Mailing address <span style="font-weight:400;color:#6b7280">(optional — for postcards)</span></p>
</div>
<div class="col-12" data-address-autocomplete data-suggest-url="{% url 'address_suggest' %}">
<div class="form-wrap address-ac-wrap">
@@ -173,7 +173,7 @@
<div class="col-12">{{ form.captcha }}{{ form.captcha.errors }}</div>
{% endif %}
</div>
<p class="form-consent-note">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 %}</p>
<p style="font-size:13px;color:#6b7280;margin:12px 0 20px;">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 %}</p>
<button class="button button-primary button-winona" type="submit" data-tianji-event="contact_form_submit">Send message</button>
</form>
</div>
@@ -182,7 +182,6 @@
</section>
{% endblock %}
{% block extra_head %}
<link rel="preload" as="image" type="image/webp" href="{% static 'images/breadcrumbs-image-1.webp' %}" fetchpriority="high">
<link rel="stylesheet" href="{% static 'css/address-autocomplete.css' %}">
{% endblock %}
{% block tracking_events %}
+18 -17
View File
@@ -6,9 +6,6 @@
{% block meta_description %}{{ SITE_NAME }}, EXIT Realty — buying and selling homes with clear advice and multi-channel marketing. {{ CONTACT_SERVICE_AREA }}.{% endblock %}
{% block og_description %}{{ SITE_NAME }}, EXIT Realty — buying and selling homes with clear advice and multi-channel marketing. {{ CONTACT_SERVICE_AREA }}.{% endblock %}
{% block twitter_description %}{{ SITE_NAME }}, EXIT Realty — buying and selling homes with clear advice and multi-channel marketing. {{ CONTACT_SERVICE_AREA }}.{% endblock %}
{% block extra_head %}
<link rel="preload" as="image" type="image/webp" href="{% static 'images/slider-minimal-slide-1-1920x968.webp' %}" fetchpriority="high">
{% endblock %}
{% block structured_data %}
<script type="application/ld+json">
{
@@ -16,7 +13,7 @@
"@type": "RealEstateAgent",
"name": "{{ SITE_NAME|escapejs }}",
"url": "{{ PUBLIC_SITE_URL }}/",
"image": "{{ PUBLIC_SITE_URL }}{% static 'images/slider-minimal-slide-1-1920x968.webp' %}",
"image": "{{ PUBLIC_SITE_URL }}{% static 'images/slider-minimal-slide-1-1920x968.jpg' %}",
"telephone": "{{ CONTACT_PHONE|escapejs }}",
"email": "{{ CONTACT_EMAIL|escapejs }}",
{% if CONTACT_ADDRESS %}"address": "{{ CONTACT_ADDRESS|escapejs }}",{% endif %}
@@ -30,28 +27,32 @@
</script>
{% endblock %}
{% block content %}
<div class="swiper-container swiper-slider swiper-slider-minimal" data-loop="false" data-slide-effect="fade" data-autoplay="false" data-simulate-touch="true">
<div class="swiper-container swiper-slider swiper-slider-minimal" data-loop="true" data-slide-effect="fade" data-autoplay="false" data-simulate-touch="true">
<div class="swiper-wrapper">
<div class="swiper-slide" data-slide-bg="{% static 'images/slider-minimal-slide-1-1920x968.webp' %}">
<div class="swiper-slide" data-slide-bg="{% static 'images/slider-minimal-slide-1-1920x968.jpg' %}">
<div class="container">
<div class="jumbotron-classic-content">
<div class="title-docor-text font-weight-bold title-decorated text-uppercase text-white">{{ SITE_NAME }} · MKDRealtor.com</div>
<h1 class="text-uppercase text-white font-weight-bold">Your Home,<br>Guided Well</h1>
<p class="text-white">EXIT Realty Redefined — buying or selling in Chicago's western suburbs with clear advice, strong marketing, and a team that answers the phone.</p>
<div class="button-outer">
<a class="button button-md button-primary button-winona" href="{% url 'public:contact' %}" data-tianji-event="hero_cta" data-tianji-event-destination="contact">Start a conversation</a>
<div class="wow-outer">
<div class="title-docor-text font-weight-bold title-decorated text-uppercase wow slideInLeft text-white">{{ SITE_NAME }} · MKDRealtor.com</div>
</div>
<h1 class="text-uppercase text-white font-weight-bold wow-outer"><span class="wow slideInDown" data-wow-delay=".2s">Your Home,<br>Guided Well</span></h1>
<p class="text-white wow-outer"><span class="wow slideInDown" data-wow-delay=".35s">EXIT Realty Redefined — buying or selling in Chicago's western suburbs with clear advice, strong marketing, and a team that answers the phone.</span></p>
<div class="wow-outer button-outer">
<a class="button button-md button-primary button-winona wow slideInDown" href="{% url 'public:contact' %}" data-wow-delay=".4s" data-tianji-event="hero_cta" data-tianji-event-destination="contact">Start a conversation</a>
</div>
</div>
</div>
</div>
<div class="swiper-slide" data-slide-bg="{% static 'images/slider-minimal-slide-2-1920x968.webp' %}">
<div class="swiper-slide" data-slide-bg="{% static 'images/slider-minimal-slide-2-1920x968.jpg' %}">
<div class="container">
<div class="jumbotron-classic-content">
<div class="title-docor-text font-weight-bold title-decorated text-uppercase text-white">Local Market Insight</div>
<h2 class="text-uppercase text-white font-weight-bold">Sell With<br>Confidence</h2>
<p class="text-white">Pricing strategy, staging guidance, and multi-channel outreach so the right buyers see your listing.</p>
<div class="button-outer">
<a class="button button-md button-primary button-winona" href="{% url 'public:about' %}" data-tianji-event="hero_about">Meet Monica</a>
<div class="wow-outer">
<div class="title-docor-text font-weight-bold title-decorated text-uppercase wow slideInLeft text-white">Local Market Insight</div>
</div>
<h2 class="text-uppercase text-white font-weight-bold wow-outer"><span class="wow slideInDown" data-wow-delay=".2s">Sell With<br>Confidence</span></h2>
<p class="text-white wow-outer"><span class="wow slideInDown" data-wow-delay=".35s">Pricing strategy, staging guidance, and multi-channel outreach so the right buyers see your listing.</span></p>
<div class="wow-outer button-outer">
<a class="button button-md button-primary button-winona wow slideInDown" href="{% url 'public:about' %}" data-wow-delay=".4s" data-tianji-event="hero_about">Meet Monica</a>
</div>
</div>
</div>
+1 -1
View File
@@ -7,7 +7,7 @@
{% block og_description %}Terms of Service for {{ SITE_NAME }} / MKDRealtor.com — how we use analytics, protect your information, and never sell or share personal data with third parties for marketing.{% endblock %}
{% block twitter_description %}Terms of Service for {{ SITE_NAME }} / MKDRealtor.com — how we use analytics, protect your information, and never sell or share personal data with third parties for marketing.{% endblock %}
{% block content %}
<section class="breadcrumbs-custom bg-image context-dark" style="background-image: url({% static 'images/breadcrumbs-image-1.webp' %});">
<section class="breadcrumbs-custom bg-image context-dark" style="background-image: url({% static 'images/breadcrumbs-image-1.jpg' %});">
<div class="breadcrumbs-custom-inner">
<div class="container breadcrumbs-custom-container">
<div class="breadcrumbs-custom-main">
-39
View File
@@ -3,29 +3,6 @@ from django.db import models
from core.models import TimeStampedModel, UUIDPrimaryKeyModel
_GENERIC_ACCOUNT_LABELS = frozenset(
{
"linkedin member",
"instagram account",
"facebook page",
}
)
def is_generic_account_label(label: str) -> bool:
"""True when OAuth left a placeholder or unreadable name."""
text = (label or "").strip()
if not text:
return True
lowered = text.lower()
if lowered in _GENERIC_ACCOUNT_LABELS:
return True
if lowered.startswith("page ") and text.split()[-1].isdigit():
return True
if text.startswith("urn:"):
return True
return False
class Platform(models.TextChoices):
FACEBOOK = "facebook", "Facebook"
@@ -77,22 +54,6 @@ class SocialAccount(UUIDPrimaryKeyModel, TimeStampedModel):
def __str__(self) -> str:
return f"{self.platform}: {self.label}"
@property
def display_name(self) -> str:
return (self.label or "").strip() or self.get_platform_display()
@staticmethod
def resolve_label(existing: "SocialAccount | None", incoming: str) -> str:
"""Keep a custom rename; otherwise take the OAuth name."""
incoming = (incoming or "").strip()[:120]
if existing is not None and not is_generic_account_label(existing.label):
return existing.label
if incoming:
return incoming
if existing and existing.label:
return existing.label
return "Account"
class SocialPost(UUIDPrimaryKeyModel, TimeStampedModel):
class Status(models.TextChoices):
+2 -12
View File
@@ -137,18 +137,8 @@
{% for account in accounts %}
<tr {% if not account.is_active %}class="row-warn"{% endif %}>
<td>
<form method="post" class="account-rename">
{% csrf_token %}
<input type="hidden" name="action" value="rename">
<input type="hidden" name="account_id" value="{{ account.pk }}">
<input id="id_label_{{ account.pk }}" name="label" type="text" maxlength="120"
value="{{ account.label }}" required
aria-label="Display name for {{ account.get_platform_display }}">
<button class="btn btn-ghost btn-sm" type="submit">Rename</button>
</form>
{% if account.external_id %}
<span class="muted" style="display:block;margin-top:6px;overflow-wrap:anywhere">{{ account.external_id }}</span>
{% endif %}
<strong>{{ account.label }}</strong><br>
<span class="muted">{{ account.external_id }}</span>
</td>
<td>
<span class="platform-pill {% if account.platform == 'facebook' %}meta{% elif account.platform == 'instagram' %}ig{% else %}li{% endif %}">
+18 -23
View File
@@ -116,35 +116,34 @@
<div class="field">
<label>Accounts</label>
{% for account in accounts %}
<label class="check-row account-pick">
<label class="check-row">
<input type="checkbox" name="account_ids" value="{{ account.pk }}"
class="account-check"
data-platform="{{ account.platform }}"
{% if account.pk|stringformat:"s" in form.account_ids %}checked{% endif %}>
<span class="account-pick-copy">
<strong>{{ account.display_name }}</strong>
<span class="muted">{{ account.get_platform_display }}{% if account.external_id %} · {{ account.external_id }}{% endif %}</span>
</span>
{{ account.get_platform_display }} · {{ account.label }}
</label>
{% empty %}
<p class="muted">No active accounts.</p>
{% endfor %}
{% if accounts %}
<div class="hint">Name look wrong? <a href="{% url 'social:account_list' %}">Rename it on Social accounts</a>.</div>
{% endif %}
</div>
<div class="form-grid cols-2">
<div class="field">
<label for="id_scheduled_for">Schedule for <span class="muted">(optional)</span></label>
<label for="id_publish_mode">Publish</label>
<select id="id_publish_mode" name="publish_mode">
<option value="schedule"{% if form.publish_mode == "schedule" %} selected{% endif %}>Schedule</option>
<option value="now"{% if form.publish_mode == "now" %} selected{% endif %}>Publish now</option>
<option value="draft"{% if form.publish_mode == "draft" %} selected{% endif %}>Save draft only</option>
</select>
</div>
<div class="field">
<label for="id_scheduled_for">When</label>
<input id="id_scheduled_for" name="scheduled_for" type="datetime-local" step="60"
value="{{ form.scheduled_for }}">
<div class="hint">Needed only if you click Schedule. Leave blank to Send now.</div>
</div>
</div>
<div style="display:flex;gap:8px;flex-wrap:wrap">
<button class="btn btn-primary" type="submit" name="action" value="send_now">Send now</button>
<button class="btn btn-ghost" type="submit" name="action" value="schedule">Schedule</button>
<button class="btn btn-ghost" type="submit" name="action" value="save">Save draft</button>
<button class="btn btn-primary" type="submit" name="action" value="publish">Save / publish</button>
</div>
</form>
</div>
@@ -200,8 +199,8 @@
var previewBody = document.getElementById('preview-body');
var content = document.getElementById('preview-content');
var previewMedia = document.getElementById('preview-media');
var mode = document.getElementById('id_publish_mode');
var when = document.getElementById('id_scheduled_for');
var composeForm = document.getElementById('social-compose');
var mediaInput = document.getElementById('id_media');
var mediaJson = document.getElementById('id_media_json');
var thumbs = document.getElementById('media-thumbs');
@@ -297,14 +296,9 @@
previewBody.hidden = !hasContent;
}
function requireScheduleTime(event) {
var submitter = event.submitter;
var action = submitter && submitter.name === 'action' ? submitter.value : '';
if (action !== 'schedule') return;
if (when && when.value) return;
event.preventDefault();
if (when) when.focus();
window.alert('Pick a date and time to schedule, or choose Send now.');
function syncWhen() {
if (!mode || !when) return;
when.disabled = mode.value !== 'schedule';
}
function validateBeforeUpload(file) {
@@ -369,7 +363,7 @@
bodyEl.addEventListener('keyup', syncPreview);
bodyEl.addEventListener('change', syncPreview);
}
if (composeForm) composeForm.addEventListener('submit', requireScheduleTime);
if (mode) mode.addEventListener('change', syncWhen);
var generateForm = document.querySelector('form input[name=action][value=generate]')?.form;
if (generateForm) {
@@ -384,6 +378,7 @@
syncMediaField();
renderThumbs();
syncPreview();
syncWhen();
})();
</script>
{% endblock %}
@@ -38,12 +38,7 @@
<tbody>
{% for target in post.targets.all %}
<tr>
<td>
{{ target.account.display_name }}
{% if target.account.external_id %}
<br><span class="muted" style="overflow-wrap:anywhere">{{ target.account.external_id }}</span>
{% endif %}
</td>
<td>{{ target.account.label }}</td>
<td>{{ target.get_platform_display }}</td>
<td><span class="badge badge-{{ target.status }}">{{ target.get_status_display }}</span></td>
</tr>
-182
View File
@@ -1,182 +0,0 @@
from unittest.mock import patch
from django.contrib.auth import get_user_model
from django.test import Client, TestCase
from django.urls import reverse
from social.models import (
Platform,
SocialAccount,
SocialPost,
is_generic_account_label,
)
class AccountLabelTests(TestCase):
def test_generic_placeholders(self):
self.assertTrue(is_generic_account_label(""))
self.assertTrue(is_generic_account_label("LinkedIn member"))
self.assertTrue(is_generic_account_label("Instagram account"))
self.assertTrue(is_generic_account_label("Page 123456"))
self.assertTrue(is_generic_account_label("urn:li:person:abc"))
self.assertFalse(is_generic_account_label("Monica Dhillon"))
self.assertFalse(is_generic_account_label("@mkdrealtor"))
def test_resolve_keeps_custom_name(self):
existing = SocialAccount(
platform=Platform.LINKEDIN,
label="Monica (personal)",
external_id="urn:li:person:abc",
)
self.assertEqual(
SocialAccount.resolve_label(existing, "LinkedIn member"),
"Monica (personal)",
)
def test_resolve_replaces_generic(self):
existing = SocialAccount(
platform=Platform.LINKEDIN,
label="LinkedIn member",
external_id="urn:li:person:abc",
)
self.assertEqual(
SocialAccount.resolve_label(existing, "Monica Dhillon"),
"Monica Dhillon",
)
class SocialComposerTests(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.account = SocialAccount.objects.create(
platform=Platform.LINKEDIN,
label="LinkedIn member",
external_id="urn:li:person:abc123",
is_active=True,
owner=self.user,
)
def test_composer_shows_send_now_and_full_name(self):
response = self.client.get(reverse("social:composer"))
self.assertEqual(response.status_code, 200)
self.assertContains(response, "Send now")
self.assertContains(response, "Schedule")
self.assertContains(response, "LinkedIn member")
self.assertContains(response, "urn:li:person:abc123")
self.assertNotContains(response, "Save / publish")
def test_send_now_queues_without_schedule(self):
mock_task = patch("social.views.publish_social_post").start()
self.addCleanup(patch.stopall)
response = self.client.post(
reverse("social:composer"),
{
"body": "Open house Saturday",
"account_ids": [str(self.account.pk)],
"action": "send_now",
},
)
post = SocialPost.objects.get()
self.assertEqual(response.status_code, 302)
self.assertEqual(
response.url, reverse("social:post_detail", kwargs={"pk": post.pk})
)
self.assertEqual(post.status, SocialPost.Status.QUEUED)
self.assertIsNotNone(post.scheduled_for)
self.assertEqual(post.targets.count(), 1)
mock_task.enqueue.assert_called_once_with(post_id=str(post.pk))
def test_schedule_requires_datetime(self):
response = self.client.post(
reverse("social:composer"),
{
"body": "Open house Saturday",
"account_ids": [str(self.account.pk)],
"action": "schedule",
},
)
self.assertEqual(response.status_code, 200)
self.assertContains(response, "Pick a schedule date/time, or choose Send now.")
self.assertFalse(SocialPost.objects.exists())
def test_schedule_with_datetime(self):
response = self.client.post(
reverse("social:composer"),
{
"body": "Open house Saturday",
"account_ids": [str(self.account.pk)],
"action": "schedule",
"scheduled_for": "2026-08-10T09:30",
},
)
post = SocialPost.objects.get()
self.assertEqual(response.status_code, 302)
self.assertEqual(post.status, SocialPost.Status.SCHEDULED)
self.assertIsNotNone(post.scheduled_for)
def test_save_draft(self):
response = self.client.post(
reverse("social:composer"),
{
"body": "Draft caption",
"action": "save",
},
)
post = SocialPost.objects.get()
self.assertEqual(response.status_code, 302)
self.assertEqual(post.status, SocialPost.Status.DRAFT)
class SocialAccountRenameTests(TestCase):
def setUp(self):
User = get_user_model()
self.user = User.objects.create_user(
username="renamer", password="test-pass-123"
)
self.client = Client()
self.client.login(username="renamer", password="test-pass-123")
self.account = SocialAccount.objects.create(
platform=Platform.FACEBOOK,
label="Page 999",
external_id="999",
is_active=True,
owner=self.user,
)
def test_accounts_page_shows_rename_field(self):
response = self.client.get(reverse("social:account_list"))
self.assertEqual(response.status_code, 200)
self.assertContains(response, 'name="label"')
self.assertContains(response, "Page 999")
self.assertContains(response, "Rename")
def test_rename_updates_label(self):
response = self.client.post(
reverse("social:account_list"),
{
"action": "rename",
"account_id": str(self.account.pk),
"label": "Monica Dhillon Realty",
},
)
self.assertEqual(response.status_code, 302)
self.account.refresh_from_db()
self.assertEqual(self.account.label, "Monica Dhillon Realty")
def test_rename_rejects_blank(self):
response = self.client.post(
reverse("social:account_list"),
{
"action": "rename",
"account_id": str(self.account.pk),
"label": " ",
},
)
self.assertEqual(response.status_code, 302)
self.account.refresh_from_db()
self.assertEqual(self.account.label, "Page 999")
+9 -35
View File
@@ -162,17 +162,6 @@ def account_list(request):
if request.method == "POST":
action = (request.POST.get("action") or "connect").strip()
if action == "rename":
pk = request.POST.get("account_id")
account = get_object_or_404(SocialAccount, pk=pk)
label = (request.POST.get("label") or "").strip()[:120]
if not label:
messages.error(request, "Enter a display name.")
return redirect("social:account_list")
account.label = label
account.save(update_fields=["label", "updated_at"])
messages.success(request, f"Renamed account to {account.label}.")
return redirect("social:account_list")
if action == "disconnect":
pk = request.POST.get("account_id")
account = get_object_or_404(SocialAccount, pk=pk)
@@ -372,14 +361,11 @@ def linkedin_oauth_callback(request):
profile = linkedin_fetch_member_profile(token_payload["access_token"])
author_urn = profile["author_urn"]
blob = linkedin_token_blob_from_oauth(token_payload, author_urn=author_urn)
existing = SocialAccount.objects.filter(
platform=Platform.LINKEDIN, external_id=author_urn
).first()
account, created = SocialAccount.objects.update_or_create(
platform=Platform.LINKEDIN,
external_id=author_urn,
defaults={
"label": SocialAccount.resolve_label(existing, profile["label"]),
"label": profile["label"],
"encrypted_tokens": encrypt_tokens(json.dumps(blob)),
"is_active": True,
"owner": request.user,
@@ -487,14 +473,11 @@ def meta_oauth_complete(request):
)
for page in ig_pages:
blob = instagram_token_blob(page, user_token=user_token)
existing = SocialAccount.objects.filter(
platform=Platform.INSTAGRAM, external_id=blob["ig_user_id"]
).first()
account, created = SocialAccount.objects.update_or_create(
platform=Platform.INSTAGRAM,
external_id=blob["ig_user_id"],
defaults={
"label": SocialAccount.resolve_label(existing, blob["label"]),
"label": blob["label"],
"encrypted_tokens": encrypt_tokens(json.dumps(blob)),
"is_active": True,
"owner": request.user,
@@ -504,15 +487,12 @@ def meta_oauth_complete(request):
else:
for page in pages:
blob = facebook_token_blob(page, user_token=user_token)
incoming_label = (page.get("name") or "").strip() or f"Page {page['id']}"
existing = SocialAccount.objects.filter(
platform=Platform.FACEBOOK, external_id=str(page["id"])
).first()
label = (page.get("name") or "").strip() or f"Page {page['id']}"
account, created = SocialAccount.objects.update_or_create(
platform=Platform.FACEBOOK,
external_id=str(page["id"]),
defaults={
"label": SocialAccount.resolve_label(existing, incoming_label),
"label": label,
"encrypted_tokens": encrypt_tokens(json.dumps(blob)),
"is_active": True,
"owner": request.user,
@@ -545,7 +525,7 @@ def composer(request):
form = {
"body": "",
"prompt": "",
"publish_mode": "now",
"publish_mode": "schedule",
"scheduled_for": "",
"account_ids": [],
"media_json": "[]",
@@ -556,17 +536,11 @@ def composer(request):
if request.method == "POST":
form["body"] = (request.POST.get("body") or "").strip()
form["prompt"] = (request.POST.get("prompt") or "").strip()
form["publish_mode"] = (request.POST.get("publish_mode") or "now").strip()
form["publish_mode"] = (request.POST.get("publish_mode") or "schedule").strip()
form["scheduled_for"] = request.POST.get("scheduled_for") or ""
form["account_ids"] = request.POST.getlist("account_ids")
form["media_json"] = (request.POST.get("media_json") or "[]").strip() or "[]"
action = (request.POST.get("action") or "save").strip()
if action == "send_now":
form["publish_mode"] = "now"
elif action == "schedule":
form["publish_mode"] = "schedule"
elif action == "save":
form["publish_mode"] = "draft"
if action == "generate" and form["prompt"]:
try:
@@ -574,7 +548,7 @@ def composer(request):
form["body"] = draft
except OllamaError as exc:
error = str(exc)
elif action in {"save", "publish", "send_now", "schedule"}:
elif action in {"save", "publish"}:
media_items: list = []
try:
media_items = parse_media_json(form["media_json"])
@@ -597,7 +571,7 @@ def composer(request):
error = str(exc)
else:
if not scheduled_for:
error = "Pick a schedule date/time, or choose Send now."
error = "Pick a schedule date/time, or choose Publish now."
else:
status = SocialPost.Status.SCHEDULED
elif form["publish_mode"] == "now":
@@ -640,7 +614,7 @@ def composer(request):
publish_social_post.enqueue(post_id=str(post.pk))
messages.success(
request,
"Post sent — queued for publishing to selected accounts.",
"Post queued for publishing to selected accounts.",
)
elif status == SocialPost.Status.SCHEDULED:
messages.success(
Generated
+3 -3
View File
@@ -13,11 +13,11 @@ wheels = [
[[package]]
name = "certifi"
version = "2026.10.23"
version = "2026.7.22"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/fd/11/b524cbd9bbdaca58608e4cf4053fd463417549e1959fa8d3aa18319f9555/certifi-2026.10.23.tar.gz", hash = "sha256:a9895e6e2758b371e0545e321a3a8fd1fcfd9829ada8824a936dfb16aaaf36cd", size = 136225, upload-time = "2026-10-23T11:36:16.478Z" }
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/32/d1/a3652fe2c19134d0d15922177821d863cbd7e842d53a21bbe7e8e3a1b5ac/certifi-2026.10.23-py3-none-any.whl", hash = "sha256:e9b54de76e530bb6cf0ccd036787103acb58fd42ba930ea8bf948ddbaa2f61d1", size = 135082, upload-time = "2026-10-23T11:36:14.763Z" },
{ 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]]