diff --git a/.env b/.env
new file mode 100644
index 0000000..ae851c3
--- /dev/null
+++ b/.env
@@ -0,0 +1,15 @@
+anthropic_api_key=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
+
+# Saxo Bank SIM API - 24 hour token (refresh daily at developer.saxobank.com/openapi/token)
+SAXO_TOKEN=eyJhbGciOiJFUzI1NiIsIng1dCI6IjY3NEM0MjFEMzZEMUE1OUNFNjFBRTIzMjMyOTVFRTAyRTc3MDMzNTkifQ.eyJvYWEiOiI3Nzc3NSIsImlzcyI6Im9hIiwiYWlkIjoiMTA5IiwidWlkIjoiYVFHTDhPUTR2LUU1cjRnTVVFUlltQT09IiwiY2lkIjoiYVFHTDhPUTR2LUU1cjRnTVVFUlltQT09IiwiaXNhIjoiRmFsc2UiLCJ0aWQiOiIyMDAyIiwic2lkIjoiNjE4ZjdjY2YwM2QyNDI0ZTgwMjUyOGM2NjQzYmE3ZjciLCJkZ2kiOiI4NCIsImV4cCI6IjE3Nzk3NTMxNzkiLCJvYWwiOiIxRiIsImlpZCI6ImRmMTdkNTZkMmNkYTRlZmE2ZTM5MDhkZWI4YjExZDYxIn0.F1ltcLpAr_724NqYYpa1Th4A-ibftPpYHcI7vAFQBI-wbOY_VNqakRuCFLAcNN73A_dV99RF-lz6vrrXoIyFkQ
+SAXO_BASE=https://gateway.saxobank.com/sim/openapi
+
+# Saxo OAuth2 App credentials (MoneyMakerHJess)
+SAXO_APP_KEY=bce4a7d403b84bc1b000461b25b2824a
+SAXO_APP_SECRET_1=43d67cc692c6470796db3e81e493b12e
+SAXO_APP_SECRET_2=9ef4f78b87eb4d948c2abdeaea920f14
+SAXO_AUTH_URL=https://sim.logonvalidation.net/authorize
+SAXO_TOKEN_URL=https://sim.logonvalidation.net/token
+SAXO_REDIRECT=http://localhost:8765/callback
+MM_DB_PASS=919129fd5326dfc7817be588
+DATABASE_URL=postgresql://moneymaker:919129fd5326dfc7817be588@int.i80.dk:5432/moneymaker
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..a3eff6c
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+.saxo_token.json
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..73e9ac0
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,68 @@
+PY := .venv/bin/python
+
+.PHONY: help run signals buy fetch rss analyze force dry company saxo saxo-buy saxo-sell saxo-login saxo-status
+
+help:
+ @echo ""
+ @echo " make → fetch + analyze new articles + vis board"
+ @echo " make run → samme som make"
+ @echo " make force → gen-analyser ALLE artikler (med Claude)"
+ @echo " make dry → dry-run uden at gemme noget"
+ @echo " make signals → vis signal board"
+ @echo " make buy → vis kun køb-kandidater"
+ @echo " make fetch → hent nye artikler fra Ground News"
+ @echo " make rss → hent danske RSS feeds (Børsen, Finans, Politiken)"
+ @echo " make orders → vis dagens køb/sælg/hold forslag"
+ @echo " make portfolio → vis åbne positioner + P&L"
+ @echo " make company → TICKER=NOVO-B make company"
+ @echo " make saxo → vis Saxo SIM konto status + positioner"
+ @echo " make saxo-buy → TICKER=NOVO-B N=5 make saxo-buy"
+ @echo " make saxo-sell → TICKER=NOVO-B N=5 make saxo-sell"
+ @echo ""
+
+run: analyze signals
+
+analyze:
+ $(PY) analyze.py
+
+force:
+ $(PY) analyze.py --force
+
+dry:
+ $(PY) analyze.py --dry-run --force
+
+signals:
+ $(PY) signals.py board
+
+buy:
+ $(PY) signals.py buy
+
+fetch:
+ $(PY) -c "from ground_news import fetch_all, get_db; db=get_db(); fetch_all(db, force=True); print('Done')"
+
+rss:
+ $(PY) rss_feeds.py
+
+orders:
+ $(PY) portfolio.py orders
+
+portfolio:
+ $(PY) portfolio.py status
+
+company:
+ $(PY) signals.py company $(TICKER)
+
+saxo-login:
+ $(PY) saxo_auth.py login
+
+saxo-status:
+ $(PY) saxo_auth.py status
+
+saxo:
+ $(PY) saxo_broker.py status
+
+saxo-buy:
+ $(PY) saxo_broker.py buy $(TICKER) $(N)
+
+saxo-sell:
+ $(PY) saxo_broker.py sell $(TICKER) $(N)
diff --git a/__pycache__/analyze.cpython-314.pyc b/__pycache__/analyze.cpython-314.pyc
new file mode 100644
index 0000000..ea2a727
Binary files /dev/null and b/__pycache__/analyze.cpython-314.pyc differ
diff --git a/__pycache__/dashboard.cpython-314.pyc b/__pycache__/dashboard.cpython-314.pyc
new file mode 100644
index 0000000..cf13413
Binary files /dev/null and b/__pycache__/dashboard.cpython-314.pyc differ
diff --git a/__pycache__/db.cpython-314.pyc b/__pycache__/db.cpython-314.pyc
new file mode 100644
index 0000000..542d38e
Binary files /dev/null and b/__pycache__/db.cpython-314.pyc differ
diff --git a/__pycache__/ground_news.cpython-314.pyc b/__pycache__/ground_news.cpython-314.pyc
new file mode 100644
index 0000000..0abb2ee
Binary files /dev/null and b/__pycache__/ground_news.cpython-314.pyc differ
diff --git a/__pycache__/portfolio.cpython-314.pyc b/__pycache__/portfolio.cpython-314.pyc
new file mode 100644
index 0000000..11e328c
Binary files /dev/null and b/__pycache__/portfolio.cpython-314.pyc differ
diff --git a/__pycache__/report.cpython-314.pyc b/__pycache__/report.cpython-314.pyc
new file mode 100644
index 0000000..c134232
Binary files /dev/null and b/__pycache__/report.cpython-314.pyc differ
diff --git a/__pycache__/rss_feeds.cpython-314.pyc b/__pycache__/rss_feeds.cpython-314.pyc
new file mode 100644
index 0000000..6a6fbce
Binary files /dev/null and b/__pycache__/rss_feeds.cpython-314.pyc differ
diff --git a/__pycache__/runner.cpython-314.pyc b/__pycache__/runner.cpython-314.pyc
new file mode 100644
index 0000000..402db3f
Binary files /dev/null and b/__pycache__/runner.cpython-314.pyc differ
diff --git a/__pycache__/saxo_auth.cpython-314.pyc b/__pycache__/saxo_auth.cpython-314.pyc
new file mode 100644
index 0000000..132e84d
Binary files /dev/null and b/__pycache__/saxo_auth.cpython-314.pyc differ
diff --git a/__pycache__/saxo_broker.cpython-314.pyc b/__pycache__/saxo_broker.cpython-314.pyc
new file mode 100644
index 0000000..e580ec1
Binary files /dev/null and b/__pycache__/saxo_broker.cpython-314.pyc differ
diff --git a/__pycache__/signals.cpython-314.pyc b/__pycache__/signals.cpython-314.pyc
new file mode 100644
index 0000000..d06c908
Binary files /dev/null and b/__pycache__/signals.cpython-314.pyc differ
diff --git a/analyze.py b/analyze.py
new file mode 100644
index 0000000..6313121
--- /dev/null
+++ b/analyze.py
@@ -0,0 +1,562 @@
+"""
+analyze.py — C25 Financial Signal Extractor
+
+Pipeline (v2):
+ 1. Alias screen on title+desc for C25 mentions
+ 2. Coverage-spread filter: skip low-quality / one-sided articles
+ 3. NER upgrade: BERT-NER to confirm and expand matches
+ 4. Full-text fetch + re-screen
+ 5. FinBERT: quick sentiment — drop neutral < FINBERT_MIN_CONF
+ 6. Claude: structured extraction (tickers, magnitude, timeframe)
+ 7. yfinance: momentum check — direction alignment
+ 8. signal_score = sentiment_confidence × coverage_spread × momentum_alignment
+ 9. Alert if signal_score > ALERT_THRESHOLD
+
+Usage:
+ python3 analyze.py # analyze new articles only
+ python3 analyze.py --force # re-analyze everything
+ python3 analyze.py --limit 20 # limit to 20 articles
+ python3 analyze.py --dry-run # show matches without storing
+ python3 analyze.py --no-claude # skip Claude step (no API cost)
+"""
+
+import re
+import os
+import sys
+import json
+import time
+import math
+import sqlite3
+import logging
+import warnings
+from pathlib import Path
+
+# Silence transformer noise before importing
+os.environ.setdefault("TRANSFORMERS_VERBOSITY", "error")
+os.environ.setdefault("HF_HUB_DISABLE_PROGRESS_BARS", "1")
+warnings.filterwarnings("ignore")
+logging.getLogger("transformers").setLevel(logging.ERROR)
+
+import torch
+from transformers import pipeline
+from dotenv import load_dotenv
+
+# Load .env (supports both ANTHROPIC_API_KEY and anthropic_api_key)
+_env_file = Path(__file__).parent / ".env"
+if _env_file.exists():
+ load_dotenv(_env_file, override=False)
+ for _k, _v in list(os.environ.items()):
+ if _k.lower() == "anthropic_api_key" and "ANTHROPIC_API_KEY" not in os.environ:
+ os.environ["ANTHROPIC_API_KEY"] = _v
+
+# Ground News helpers
+sys.path.insert(0, str(Path(__file__).parent))
+from ground_news import get_db, fetch_article_text, fetch_all
+from rss_feeds import fetch_all_rss
+
+# ---------------------------------------------------------------------------
+# Config
+# ---------------------------------------------------------------------------
+
+C25_PATH = Path(__file__).parent / "c25.json"
+_c25_raw = json.loads(C25_PATH.read_text())
+C25: dict[str, dict] = {k: v for k, v in _c25_raw.items() if not k.startswith("_")}
+
+# Build alias → ticker lookup (lower-cased for matching)
+ALIAS_MAP: dict[str, str] = {}
+for _ticker, _data in C25.items():
+ for _alias in _data["aliases"]:
+ al = _alias.lower()
+ if al not in ALIAS_MAP: # first alias wins (most specific first in c25.json)
+ ALIAS_MAP[al] = _ticker
+
+DEVICE = -1 # always CPU — Quadro P400 (CC 6.1) too old + too little VRAM for these models
+
+# Quality thresholds
+MIN_SOURCES = 1 # coverage_spread naturally weights single-source articles near zero
+MIN_COVERAGE_SPREAD = 0.0 # disabled: signal_score naturally zeros out single-source articles
+FINBERT_MIN_CONF = 0.70 # drop neutral articles below this FinBERT confidence
+ALERT_THRESHOLD = 0.35 # signal_score > this → alert
+
+
+# ---------------------------------------------------------------------------
+# Model loading
+# ---------------------------------------------------------------------------
+
+_ner_model = None
+_finbert_model = None
+
+
+def get_ner():
+ global _ner_model
+ if _ner_model is None:
+ print("[analyze] Loading dslim/bert-base-NER …", flush=True)
+ _ner_model = pipeline(
+ "ner",
+ model="dslim/bert-base-NER",
+ aggregation_strategy="simple",
+ device=DEVICE,
+ )
+ return _ner_model
+
+
+def get_finbert():
+ global _finbert_model
+ if _finbert_model is None:
+ print("[analyze] Loading ProsusAI/finbert …", flush=True)
+ _finbert_model = pipeline(
+ "sentiment-analysis",
+ model="ProsusAI/finbert",
+ device=DEVICE,
+ truncation=True,
+ max_length=512,
+ )
+ return _finbert_model
+
+
+# ---------------------------------------------------------------------------
+# C25 alias matching
+# ---------------------------------------------------------------------------
+
+def match_c25(text: str) -> dict[str, float]:
+ """
+ Find C25 companies mentioned in text.
+ Returns {ticker: confidence_score}.
+ """
+ text_lower = text.lower()
+ matches: dict[str, float] = {}
+
+ for alias_lower, ticker in ALIAS_MAP.items():
+ if ticker in matches:
+ continue # already found this company
+
+ # Always use word boundaries — prevents "sonic" matching "hypersonic",
+ # "net" matching "internet", "iss" matching "mission", etc.
+ pat = r"(? dict[str, float]:
+ """
+ Cross-reference NER ORG entities with alias map.
+ Requires whole-token match to avoid 'EMA' matching 'd-ema-nt'.
+ """
+ merged = dict(base)
+ for ent in ner_result:
+ if ent.get("entity_group") not in ("ORG", "PER"):
+ continue
+ word_tokens = set(re.split(r"[\s\-_/]+", ent["word"].lower().strip("##")))
+ for alias_lower, ticker in ALIAS_MAP.items():
+ if len(alias_lower) < 4:
+ continue
+ alias_tokens = set(re.split(r"[\s\-_/]+", alias_lower))
+ # Need significant token overlap, not just substring containment
+ overlap = alias_tokens & word_tokens
+ if overlap and len(overlap) / max(len(alias_tokens), len(word_tokens)) >= 0.5:
+ score = ent.get("score", 0.7)
+ if score > merged.get(ticker, 0):
+ merged[ticker] = score
+ return merged
+
+
+
+
+# ---------------------------------------------------------------------------
+# Coverage spread scoring
+# ---------------------------------------------------------------------------
+
+def coverage_spread_score(row) -> float:
+ """
+ Quality score (0–1) based on source count and bias diversity.
+ High = many sources from left + right + centre. Low = few or echo chamber.
+ """
+ src = row["source_count"] or 0
+ left = row["left_src_count"] or 0
+ right = row["right_src_count"] or 0
+ ctr = row["ctr_src_count"] or 0
+
+ if src < MIN_SOURCES:
+ return 0.0
+
+ quantity = min(1.0, math.log(max(1, src)) / math.log(50))
+ fl, fr, fc = left / src, right / src, ctr / src
+ diversity = min(1.0, (fl * fr * fc) ** (1 / 3) * 9) # peaks at equal thirds
+
+ return round(quantity * 0.6 + diversity * 0.4, 3)
+
+
+# ---------------------------------------------------------------------------
+# Claude structured extraction
+# ---------------------------------------------------------------------------
+
+def claude_extract(title: str, text: str, tickers: list[str]) -> dict:
+ """
+ Use Claude Haiku to extract structured financial signal.
+ Returns {"confirmed_tickers", "magnitude", "timeframe", "reasoning"}.
+ """
+ import anthropic
+
+ api_key = os.environ.get("ANTHROPIC_API_KEY")
+ if not api_key:
+ return {"confirmed_tickers": tickers, "magnitude": 5, "timeframe": "days", "reasoning": "(no API key)"}
+
+ client = anthropic.Anthropic(api_key=api_key)
+ ticker_ctx = "\n".join(
+ f" {t}: {C25[t]['name']} ({C25[t]['sector']})" for t in tickers if t in C25
+ )
+
+ prompt = f"""You are a financial analyst specializing in Scandinavian equities.
+
+Analyze this news article and assess its financial impact on the listed Danish C25 companies.
+
+## Companies to analyze:
+{ticker_ctx}
+
+## Article:
+Title: {title}
+{text[:1500]}
+
+Respond ONLY with valid JSON (no markdown fences):
+{{
+ "confirmed_tickers": ["NOVO-B"],
+ "magnitude": 7,
+ "timeframe": "days",
+ "reasoning": "Two sentences max on financial impact and direction."
+}}
+
+Fields:
+- confirmed_tickers: only companies truly affected (can be [])
+- magnitude: 1–10 (1=irrelevant, 10=major market mover)
+- timeframe: "hours", "days", "weeks", or "months"
+- reasoning: brief analyst note"""
+
+ try:
+ msg = client.messages.create(
+ model="claude-haiku-4-5",
+ max_tokens=256,
+ messages=[{"role": "user", "content": prompt}],
+ )
+ raw = msg.content[0].text.strip()
+ raw = re.sub(r"^```(?:json)?\n?", "", raw)
+ raw = re.sub(r"\n?```$", "", raw)
+ return json.loads(raw)
+ except Exception as e:
+ print(f" [warn] Claude failed: {e}")
+ return {"confirmed_tickers": tickers, "magnitude": 5, "timeframe": "days", "reasoning": str(e)[:120]}
+
+
+# ---------------------------------------------------------------------------
+# yfinance momentum
+# ---------------------------------------------------------------------------
+
+_momentum_cache: dict[str, dict] = {}
+
+
+def momentum_check(ticker: str) -> dict:
+ """5-day price momentum for a C25 ticker via yfinance."""
+ if ticker in _momentum_cache:
+ return _momentum_cache[ticker]
+
+ import yfinance as yf
+
+ company = C25.get(ticker, {})
+ yahoo_ticker = company.get("yahoo_ticker", ticker + ".CO")
+ result: dict = {"direction": "unknown", "pct_5d": 0.0, "pct_20d": 0.0}
+
+ try:
+ hist = yf.Ticker(yahoo_ticker).history(period="1mo", auto_adjust=True)
+ if len(hist) >= 5:
+ close = hist["Close"]
+ pct_5d = float((close.iloc[-1] / close.iloc[-5] - 1) * 100)
+ pct_20d = float((close.iloc[-1] / close.iloc[0] - 1) * 100) if len(hist) >= 20 else 0.0
+ direction = "up" if pct_5d > 1.5 else ("down" if pct_5d < -1.5 else "flat")
+ result = {"direction": direction, "pct_5d": round(pct_5d, 2), "pct_20d": round(pct_20d, 2)}
+ except Exception:
+ pass
+
+ _momentum_cache[ticker] = result
+ return result
+
+
+# ---------------------------------------------------------------------------
+# Signal score
+# ---------------------------------------------------------------------------
+
+def calc_signal_score(sent_score: float, sentiment: str, coverage: float, momentum: dict) -> float:
+ """signal_score = sentiment_confidence × coverage_spread × momentum_alignment"""
+ d = momentum.get("direction", "unknown")
+ if d == "unknown":
+ alignment = 0.5
+ elif d == "flat":
+ alignment = 0.7
+ elif (sentiment == "positive" and d == "up") or (sentiment == "negative" and d == "down"):
+ alignment = 1.0
+ else:
+ alignment = 0.4 # contrarian
+
+ return round(sent_score * coverage * alignment, 3)
+
+
+# ---------------------------------------------------------------------------
+# DB schema migration
+# ---------------------------------------------------------------------------
+
+def migrate_db(db) -> None:
+ """Apply schema migrations for SQLite. No-op for Postgres (schema managed by db.py)."""
+ if hasattr(db, "db_type") and db.db_type == "postgres":
+ return
+ existing = {row[1] for row in db.execute("PRAGMA table_info(article_signals)").fetchall()}
+ new_cols = [
+ ("coverage_spread", "REAL DEFAULT 0"),
+ ("claude_tickers", "TEXT"),
+ ("claude_magnitude", "INTEGER DEFAULT 5"),
+ ("claude_timeframe", "TEXT"),
+ ("claude_reasoning", "TEXT"),
+ ("momentum_dir", "TEXT"),
+ ("momentum_pct_5d", "REAL DEFAULT 0"),
+ ("signal_score", "REAL DEFAULT 0"),
+ ("alert", "INTEGER DEFAULT 0"),
+ ]
+ for col_name, col_def in new_cols:
+ if col_name not in existing:
+ db.execute(f"ALTER TABLE article_signals ADD COLUMN {col_name} {col_def}")
+ db.commit()
+
+
+# ---------------------------------------------------------------------------
+# Main pipeline
+# ---------------------------------------------------------------------------
+
+def analyze_articles(
+ *,
+ force: bool = False,
+ limit: int | None = None,
+ dry_run: bool = False,
+ use_claude: bool = True,
+ auto_fetch: bool = True,
+) -> None:
+ db = get_db()
+ migrate_db(db)
+
+ # Auto-refresh articles (respects 30-min cache TTL)
+ if auto_fetch and not dry_run:
+ before = db.execute("SELECT COUNT(*) AS cnt FROM articles").fetchone()["cnt"]
+ print("[analyze] Refreshing Ground News feed …")
+ fetch_all(db)
+ print("[analyze] Henter danske RSS feeds …")
+ fetch_all_rss(db)
+ after = db.execute("SELECT COUNT(*) AS cnt FROM articles").fetchone()["cnt"]
+ if after > before:
+ print(f"[analyze] +{after - before} new articles")
+
+ base_q = """
+ SELECT slug, title, description, source_count,
+ left_src_count, right_src_count, ctr_src_count,
+ left_pct, right_pct, ctr_pct
+ FROM articles {where}
+ ORDER BY source_count DESC
+ """
+ rows = db.execute(
+ base_q.format(where="" if force else
+ "WHERE slug NOT IN (SELECT DISTINCT article_slug FROM article_signals)")
+ ).fetchall()
+ if limit:
+ rows = rows[:limit]
+
+ total = len(rows)
+ print(f"[analyze] {total} articles to process (force={force} dry_run={dry_run} claude={use_claude})")
+ if total == 0:
+ print("[analyze] Nothing to do.")
+ db.close()
+ return
+
+ # ------------------------------------------------------------------
+ # Phase 1 — Alias screen + coverage spread filter
+ # ------------------------------------------------------------------
+ print("[analyze] Phase 1: alias screen + coverage filter …")
+ screened: list[tuple] = []
+ dropped_cov = 0
+
+ for row in rows:
+ cov = coverage_spread_score(row)
+ if cov < MIN_COVERAGE_SPREAD:
+ dropped_cov += 1
+ continue
+ text = f"{row['title']}. {row['description'] or ''}"
+ matches = match_c25(text)
+ if matches:
+ screened.append((row, matches, cov))
+
+ print(f"[analyze] {len(screened)}/{total} passed ({dropped_cov} dropped by coverage filter)")
+ if not screened:
+ db.close()
+ return
+
+ # ------------------------------------------------------------------
+ # Phase 2 — NER upgrade
+ # ------------------------------------------------------------------
+ print("[analyze] Phase 2: NER upgrade …")
+ ner = get_ner()
+ texts = [f"{r['title']}. {r['description'] or ''}" for r, _, _ in screened]
+
+ BATCH = 16
+ all_ner = []
+ for i in range(0, len(texts), BATCH):
+ all_ner.extend(ner(texts[i : i + BATCH]))
+
+ enriched = [
+ (row, merge_ner_matches(ner_res, base), cov)
+ for (row, base, cov), ner_res in zip(screened, all_ner)
+ ]
+
+ # ------------------------------------------------------------------
+ # Phase 3 — Full text + re-screen
+ # ------------------------------------------------------------------
+ print(f"[analyze] Phase 3: fetching full text for {len(enriched)} articles …")
+ final: list[tuple] = []
+
+ for idx, (row, matches, cov) in enumerate(enriched, 1):
+ slug = row["slug"]
+ if idx % 5 == 0 or idx == len(enriched):
+ print(f" {idx}/{len(enriched)}: {slug[:55]}")
+
+ # RSS artikler har teksten gemt i page_cache som "rss:{slug}"
+ cats = row["categories"] if "categories" in row.keys() else ""
+ if cats and cats.startswith("rss:"):
+ cache_row = db.execute(
+ "SELECT content FROM page_cache WHERE url = ?",
+ (f"rss:{slug}",),
+ ).fetchone()
+ full_text = cache_row["content"] if cache_row else f"{row['title']}. {row['description'] or ''}"
+ else:
+ full_text = fetch_article_text(slug, db)
+
+ full_matches = match_c25(full_text)
+ for ticker, score in full_matches.items():
+ if score > matches.get(ticker, 0):
+ matches[ticker] = score
+ if matches:
+ final.append((row, matches, cov, full_text))
+
+ print(f"[analyze] {len(final)} articles with confirmed C25 mentions")
+
+ # ------------------------------------------------------------------
+ # Phase 4 — FinBERT sentiment (confidence filter)
+ # ------------------------------------------------------------------
+ print("[analyze] Phase 4: FinBERT sentiment …")
+ finbert = get_finbert()
+ now = int(time.time())
+ signals_written = 0
+ alerts_triggered = 0
+
+ for row, matches, cov, full_text in final:
+ slug = row["slug"]
+ title = row["title"]
+
+ try:
+ fb = finbert(" ".join(full_text.split()[:400]))[0]
+ sentiment = fb["label"].lower()
+ sent_score = round(fb["score"], 4)
+ except Exception as e:
+ print(f" [warn] FinBERT: {e}")
+ sentiment, sent_score = "neutral", 0.5
+
+ if sentiment == "neutral" and sent_score < FINBERT_MIN_CONF:
+ continue # drop low-confidence neutral noise
+
+ # ------------------------------------------------------------------
+ # Phase 5 — Claude extraction
+ # ------------------------------------------------------------------
+ claude_data: dict = {}
+ if use_claude and not dry_run and os.environ.get("ANTHROPIC_API_KEY"):
+ print(f" [claude] {slug[:50]}")
+ claude_data = claude_extract(title, full_text, list(matches.keys()))
+
+ # ------------------------------------------------------------------
+ # Phase 6 — yfinance momentum + scoring
+ # ------------------------------------------------------------------
+ for ticker, entity_score in matches.items():
+ company = C25[ticker]
+ full_lower = full_text.lower()
+ mention_count = max(1, sum(
+ len(re.findall(
+ r"(? ALERT_THRESHOLD and sentiment != "neutral"
+
+ if dry_run:
+ print(
+ f" DRY: {slug[:38]:<38} | {ticker:<8} | "
+ f"{sentiment:<8} {sent_score:.2f} | cov={cov:.2f} | sig={sig_score:.3f}"
+ f"{' ⚡' if alert else ''}"
+ )
+ else:
+ db.upsert(
+ "article_signals",
+ ["article_slug", "ticker"],
+ [
+ "article_slug", "ticker", "company_name", "sector",
+ "sentiment", "sentiment_score", "entity_score",
+ "mention_count", "full_text_used", "analyzed_at",
+ "coverage_spread", "claude_tickers", "claude_magnitude",
+ "claude_timeframe", "claude_reasoning",
+ "momentum_dir", "momentum_pct_5d", "signal_score", "alert",
+ ],
+ (
+ slug, ticker, company["name"], company["sector"],
+ sentiment, float(sent_score), round(float(entity_score), 4),
+ mention_count, 1, now,
+ float(cov),
+ json.dumps(claude_data.get("confirmed_tickers", [])) or None,
+ claude_data.get("magnitude", 5),
+ claude_data.get("timeframe", "days"),
+ claude_data.get("reasoning", ""),
+ momentum.get("direction", "unknown"),
+ float(momentum.get("pct_5d", 0.0)),
+ float(sig_score),
+ int(alert),
+ ),
+ )
+ signals_written += 1
+ if alert:
+ alerts_triggered += 1
+ icon = "↑" if sentiment == "positive" else "↓"
+ print(
+ f" ⚡ ALERT: {icon} {ticker} ({company['name']}) | "
+ f"{sentiment} {sent_score:.2f} | sig={sig_score:.3f} | {slug[:40]}"
+ )
+
+ if not dry_run:
+ db.commit()
+ print(f"[analyze] Done. {signals_written} signals written, {alerts_triggered} alerts triggered.")
+ else:
+ print(f"[analyze] Dry-run complete. {len(final)} articles matched.")
+
+ db.close()
+
+
+# ---------------------------------------------------------------------------
+# CLI — brug Makefile i stedet for at huske flags
+# ---------------------------------------------------------------------------
+
+def main() -> None:
+ import sys
+ force = "--force" in sys.argv
+ dry_run = "--dry" in sys.argv
+ analyze_articles(force=force, dry_run=dry_run)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/c25.json b/c25.json
new file mode 100644
index 0000000..a42d827
--- /dev/null
+++ b/c25.json
@@ -0,0 +1,339 @@
+{
+ "_meta": {
+ "description": "C25 company universe with NER aliases, sector tags, price tiers, and leveraged instruments",
+ "updated": "2026-05-25",
+ "price_source": "Yahoo Finance (DKK, indicative — refresh with yfinance)",
+ "leveraged_source": "WisdomTree ETP catalogue (Xetra/Euronext Paris)",
+ "tiers": {
+ "budget": "< 200 DKK — buy directly, multiple shares",
+ "accessible": "200-500 DKK — buy 1-2 shares",
+ "expensive": "500-2000 DKK — consider leveraged ETP",
+ "inaccessible": "> 2000 DKK — leveraged ETP strongly recommended"
+ }
+ },
+
+ "AMBU-B": {
+ "name": "Ambu A/S",
+ "ticker_yahoo": "AMBU-B.CO",
+ "sector": "medtech",
+ "tier": "budget",
+ "price_dkk_approx": 68,
+ "aliases": [
+ "Ambu", "Ambu A/S", "Ambu B", "AMBU"
+ ],
+ "keywords": ["endoscopy", "single-use", "endoscope", "bronchoscope", "disposable medical"],
+ "leveraged": []
+ },
+
+ "GN": {
+ "name": "GN Store Nord A/S",
+ "ticker_yahoo": "GN.CO",
+ "sector": "medtech",
+ "tier": "budget",
+ "price_dkk_approx": 95,
+ "aliases": [
+ "GN Store Nord", "GN Audio", "Jabra", "GN Group", "SteelSeries", "Beltone"
+ ],
+ "keywords": ["hearing aids", "headsets", "audio", "cochlear implants", "Jabra headset"],
+ "leveraged": []
+ },
+
+ "TRYG": {
+ "name": "Tryg A/S",
+ "ticker_yahoo": "TRYG.CO",
+ "sector": "insurance",
+ "tier": "budget",
+ "price_dkk_approx": 156,
+ "aliases": [
+ "Tryg", "Tryg Forsikring", "Tryg A/S", "TrygVesta"
+ ],
+ "keywords": ["insurance", "Danish insurance", "P&C insurance", "Nordic insurance"],
+ "leveraged": []
+ },
+
+ "ORSTED": {
+ "name": "Ørsted A/S",
+ "ticker_yahoo": "ORSTED.CO",
+ "sector": "energy",
+ "tier": "budget",
+ "price_dkk_approx": 167,
+ "aliases": [
+ "Ørsted", "Orsted", "DONG Energy", "Ørsted A/S", "Oersted"
+ ],
+ "keywords": ["offshore wind", "wind energy", "renewable energy", "green energy",
+ "wind farm", "hydrogen", "North Sea wind"],
+ "leveraged": [
+ {"ticker": "3ORE", "name": "WisdomTree Ørsted 3x Daily Long", "exchange": "Xetra", "direction": "long"},
+ {"ticker": "SORE", "name": "WisdomTree Ørsted -3x Daily Short", "exchange": "Xetra", "direction": "short"}
+ ]
+ },
+
+ "ROCK-B": {
+ "name": "Rockwool International B",
+ "ticker_yahoo": "ROCK-B.CO",
+ "sector": "materials",
+ "tier": "budget",
+ "price_dkk_approx": 192,
+ "aliases": [
+ "Rockwool", "ROCKWOOL", "Rockwool International", "Rockfon", "Roxul"
+ ],
+ "keywords": ["insulation", "stone wool", "building materials", "energy efficiency",
+ "fire safety insulation"],
+ "leveraged": []
+ },
+
+ "VWS": {
+ "name": "Vestas Wind Systems A/S",
+ "ticker_yahoo": "VWS.CO",
+ "sector": "energy",
+ "tier": "budget",
+ "price_dkk_approx": 195,
+ "aliases": [
+ "Vestas", "Vestas Wind", "Vestas Wind Systems", "VWS"
+ ],
+ "keywords": ["wind turbines", "wind power", "offshore wind", "onshore wind",
+ "turbine manufacturer", "renewable energy"],
+ "leveraged": [
+ {"ticker": "3VWS", "name": "WisdomTree Vestas Wind Systems 3x Daily Long", "exchange": "Xetra", "direction": "long"},
+ {"ticker": "SVWS", "name": "WisdomTree Vestas Wind Systems -3x Daily Short", "exchange": "Xetra", "direction": "short"}
+ ]
+ },
+
+ "BAVA": {
+ "name": "Bavarian Nordic A/S",
+ "ticker_yahoo": "BAVA.CO",
+ "sector": "pharma",
+ "tier": "budget",
+ "price_dkk_approx": 197,
+ "aliases": [
+ "Bavarian Nordic", "BavNordic", "BAVA", "Imvamune", "Imvanex", "Jynneos",
+ "BN Biopharma"
+ ],
+ "keywords": ["vaccines", "mpox vaccine", "smallpox vaccine", "RSV vaccine",
+ "cancer immunotherapy", "monkeypox"],
+ "leveraged": []
+ },
+
+ "DEMANT": {
+ "name": "Demant A/S",
+ "ticker_yahoo": "DEMANT.CO",
+ "sector": "medtech",
+ "tier": "accessible",
+ "price_dkk_approx": 247,
+ "aliases": [
+ "Demant", "William Demant", "Oticon", "Bernafon", "Sonic", "Philips Hearing"
+ ],
+ "keywords": ["hearing aids", "hearing implants", "audiological equipment",
+ "cochlear implants", "Oticon hearing"],
+ "leveraged": []
+ },
+
+ "ISS": {
+ "name": "ISS A/S",
+ "ticker_yahoo": "ISS.CO",
+ "sector": "services",
+ "tier": "accessible",
+ "price_dkk_approx": 265,
+ "aliases": [
+ "ISS", "ISS A/S", "ISS World Services", "ISS Facility Services"
+ ],
+ "keywords": ["facility services", "cleaning services", "facility management",
+ "workplace services"],
+ "leveraged": []
+ },
+
+ "NOVO-B": {
+ "name": "Novo Nordisk B",
+ "ticker_yahoo": "NOVO-B.CO",
+ "sector": "pharma",
+ "tier": "accessible",
+ "price_dkk_approx": 289,
+ "aliases": [
+ "Novo Nordisk", "Novo", "NVO", "NNO", "NovoNordisk",
+ "Ozempic", "Wegovy", "Rybelsus", "Victoza", "Semaglutide",
+ "Tresiba", "Levemir", "NovoLog", "Fiasp"
+ ],
+ "keywords": ["GLP-1", "diabetes", "obesity", "insulin", "semaglutide",
+ "weight loss drug", "FDA approval", "EMA approval",
+ "weight management", "cardiovascular"],
+ "leveraged": [
+ {"ticker": "3NOV", "name": "WisdomTree Novo Nordisk 3x Daily Long", "exchange": "Xetra", "direction": "long"},
+ {"ticker": "SNOV", "name": "WisdomTree Novo Nordisk -3x Daily Short", "exchange": "Xetra", "direction": "short"}
+ ]
+ },
+
+ "ZEAL": {
+ "name": "Zealand Pharma A/S",
+ "ticker_yahoo": "ZEAL.CO",
+ "sector": "pharma",
+ "tier": "accessible",
+ "price_dkk_approx": 318,
+ "aliases": [
+ "Zealand Pharma", "Zealand", "ZEAL", "Zegalogue", "Dasiglucagon",
+ "Petrelintide", "Glepaglutide"
+ ],
+ "keywords": ["peptide drugs", "GLP-1", "obesity", "short bowel syndrome",
+ "glucagon", "clinical trials", "FDA"],
+ "leveraged": []
+ },
+
+ "DANSKE": {
+ "name": "Danske Bank A/S",
+ "ticker_yahoo": "DANSKE.CO",
+ "sector": "finance",
+ "tier": "accessible",
+ "price_dkk_approx": 332,
+ "aliases": [
+ "Danske Bank", "Danske", "Danica Pension", "Northern Bank",
+ "Realkredit Danmark"
+ ],
+ "keywords": ["Danish bank", "banking", "mortgage", "money laundering",
+ "Nordic finance", "interest rates"],
+ "leveraged": []
+ },
+
+ "NETC": {
+ "name": "Netcompany Group A/S",
+ "ticker_yahoo": "NETC.CO",
+ "sector": "technology",
+ "tier": "accessible",
+ "price_dkk_approx": 340,
+ "aliases": [
+ "Netcompany", "NETC", "Netcompany Group"
+ ],
+ "keywords": ["IT consulting", "digital transformation", "public sector IT",
+ "cloud services", "Danish IT"],
+ "leveraged": []
+ },
+
+ "COLO-B": {
+ "name": "Coloplast B",
+ "ticker_yahoo": "COLO-B.CO",
+ "sector": "medtech",
+ "tier": "accessible",
+ "price_dkk_approx": 404,
+ "aliases": [
+ "Coloplast", "Coloplast B", "COLO", "Coloplast A/S"
+ ],
+ "keywords": ["ostomy", "wound care", "continence care", "urology",
+ "medical devices", "stoma"],
+ "leveraged": []
+ },
+
+ "FLS": {
+ "name": "FLSmidth & Co. A/S",
+ "ticker_yahoo": "FLS.CO",
+ "sector": "industrials",
+ "tier": "expensive",
+ "price_dkk_approx": 516,
+ "aliases": [
+ "FLSmidth", "FL Smidth", "FLS", "FLSmidth & Co"
+ ],
+ "keywords": ["cement equipment", "mining equipment", "green transition mining",
+ "cement plant", "copper mining"],
+ "leveraged": []
+ },
+
+ "PNDORA": {
+ "name": "Pandora A/S",
+ "ticker_yahoo": "PNDORA.CO",
+ "sector": "retail",
+ "tier": "expensive",
+ "price_dkk_approx": 556,
+ "aliases": [
+ "Pandora", "Pandora A/S", "Pandora jewelry", "Pandora charms",
+ "Pandora bracelet"
+ ],
+ "keywords": ["jewelry", "silver jewelry", "charm bracelets",
+ "luxury goods", "consumer discretionary"],
+ "leveraged": []
+ },
+
+ "CARL-B": {
+ "name": "Carlsberg B",
+ "ticker_yahoo": "CARL-B.CO",
+ "sector": "beverages",
+ "tier": "expensive",
+ "price_dkk_approx": 878,
+ "aliases": [
+ "Carlsberg", "Carlsberg B", "Carlsberg Group", "Tuborg",
+ "Kronenbourg", "1664", "Baltika", "Astra"
+ ],
+ "keywords": ["beer", "brewery", "lager", "alcohol", "beverage company",
+ "Western European beer", "Eastern European beer"],
+ "leveraged": [
+ {"ticker": "3CAR", "name": "WisdomTree Carlsberg 3x Daily Long", "exchange": "Xetra", "direction": "long"},
+ {"ticker": "SCAR", "name": "WisdomTree Carlsberg -3x Daily Short", "exchange": "Xetra", "direction": "short"}
+ ]
+ },
+
+ "NKT": {
+ "name": "NKT A/S",
+ "ticker_yahoo": "NKT.CO",
+ "sector": "industrials",
+ "tier": "expensive",
+ "price_dkk_approx": 1112,
+ "aliases": [
+ "NKT", "NKT A/S", "NKT Cables", "NKT cable"
+ ],
+ "keywords": ["power cables", "high voltage cables", "offshore cables",
+ "subsea cables", "grid infrastructure", "energy transition"],
+ "leveraged": []
+ },
+
+ "DSV": {
+ "name": "DSV A/S",
+ "ticker_yahoo": "DSV.CO",
+ "sector": "logistics",
+ "tier": "inaccessible",
+ "price_dkk_approx": 1522,
+ "aliases": [
+ "DSV", "DSV A/S", "DSV Panalpina", "Panalpina", "DSV Air & Sea",
+ "DSV Road", "Schenker", "DB Schenker"
+ ],
+ "keywords": ["freight", "logistics", "supply chain", "air freight",
+ "sea freight", "global logistics", "3PL"],
+ "leveraged": [
+ {"ticker": "3DSV", "name": "WisdomTree DSV 3x Daily Long", "exchange": "Xetra", "direction": "long"},
+ {"ticker": "SDSV", "name": "WisdomTree DSV -3x Daily Short", "exchange": "Xetra", "direction": "short"}
+ ]
+ },
+
+ "GMAB": {
+ "name": "Genmab A/S",
+ "ticker_yahoo": "GMAB.CO",
+ "sector": "pharma",
+ "tier": "inaccessible",
+ "price_dkk_approx": 1724,
+ "aliases": [
+ "Genmab", "GMAB", "Genmab A/S", "Darzalex", "Daratumumab",
+ "Epkinly", "Epcoritamab", "Kesimpta", "Ofatumumab"
+ ],
+ "keywords": ["antibody drugs", "oncology", "multiple myeloma",
+ "cancer treatment", "bispecific antibodies", "FDA approval"],
+ "leveraged": [
+ {"ticker": "3GEN", "name": "WisdomTree Genmab 3x Daily Long", "exchange": "Xetra", "direction": "long"},
+ {"ticker": "SGEN", "name": "WisdomTree Genmab -3x Daily Short", "exchange": "Xetra", "direction": "short"}
+ ]
+ },
+
+ "MAERSK-B": {
+ "name": "A.P. Møller - Mærsk B",
+ "ticker_yahoo": "MAERSK-B.CO",
+ "sector": "shipping",
+ "tier": "inaccessible",
+ "price_dkk_approx": 15280,
+ "aliases": [
+ "Mærsk", "Maersk", "A.P. Moller", "A.P. Møller", "AP Moller",
+ "Møller-Mærsk", "Moller-Maersk", "APM", "APM Terminals",
+ "Maersk Line", "Mærsk Line", "Sealand"
+ ],
+ "keywords": ["container shipping", "shipping rates", "ocean freight",
+ "logistics", "Red Sea", "Suez Canal", "port congestion",
+ "supply chain", "container rates", "spot rates"],
+ "leveraged": [
+ {"ticker": "3MRS", "name": "WisdomTree A.P. Møller-Mærsk 3x Daily Long", "exchange": "Xetra", "direction": "long"},
+ {"ticker": "SMRS", "name": "WisdomTree A.P. Møller-Mærsk -3x Daily Short", "exchange": "Xetra", "direction": "short"}
+ ]
+ }
+}
diff --git a/dashboard.py b/dashboard.py
new file mode 100644
index 0000000..a4345a0
--- /dev/null
+++ b/dashboard.py
@@ -0,0 +1,327 @@
+"""
+dashboard.py — MoneyMaker live monitoring dashboard.
+
+Usage:
+ python dashboard.py # starts on http://localhost:5001
+ python dashboard.py --port 5002
+
+Auto-refreshes every 60 seconds. Shows:
+ • Portfolio P&L + C25 benchmark
+ • Open positions with live prices
+ • Closed trades (win/loss)
+ • Signal accuracy
+ • Recent runner log tail
+"""
+import argparse
+import json
+import time
+from datetime import datetime, timezone
+from pathlib import Path
+
+import yfinance as yf
+from flask import Flask, render_template_string
+
+from db import get_conn, DB_TYPE
+from report import _c25_day_return, _unrealised_pnl, _realised_pnl, _total_fees, _signal_accuracy
+
+CAPITAL = 10_000
+LOG_DIR = Path(__file__).parent / "logs"
+REFRESH = 60 # seconds
+
+app = Flask(__name__)
+
+# ── HTML template ────────────────────────────────────────────────────────────
+
+TEMPLATE = """
+
+
+
+
+
+
+ MoneyMaker Dashboard
+
+
+
+ 📈 MoneyMaker
+ DB: {{ db_type }} · Opdateret: {{ now }} · Refresh om {{ refresh }}s
+
+
+
+
+
Net P&L
+
{{ "{:+,.0f}".format(net_pnl) }} kr
+
{{ "{:+.2f}%".format(net_pct) }}
+
+
+
Urealiseret
+
{{ "{:+,.0f}".format(unreal) }} kr
+
{{ open_count }} åben{{ 'e' if open_count != 1 else '' }} position{{ 'er' if open_count != 1 else '' }}
+
+
+
Realiseret
+
{{ "{:+,.0f}".format(realised) }} kr
+
Gebyrer: {{ "{:,.0f}".format(fees) }} kr
+
+
+
C25 i dag
+ {% if c25_ret is not none %}
+
{{ "{:+.2f}%".format(c25_ret) }}
+
+ vs benchmark: {{ "{:+.2f}%".format(vs_bench) if vs_bench is not none else "—" }}
+
+ {% else %}
+
—
+
Marked lukket?
+ {% endif %}
+
+
+
Signal accuracy
+ {% if sig.total_trades > 0 %}
+
{{ "{:.0f}%".format(sig.accuracy_pct) }}
+
{{ sig.correct }} / {{ sig.total_trades }} handler korrekte
+ {% else %}
+
—
+
Ingen lukkede handler
+ {% endif %}
+
+
+
Kapital
+
{{ "{:,.0f}".format(capital) }} kr
+
Kontant: {{ "{:,.0f}".format(cash) }} kr
+
+
+
+
+
+
📊 Åbne positioner
+ {% if positions %}
+
+ | Ticker | Antal | Købt | Nu | P&L | Ændring | Stop | Take | Status |
+ {% for p in positions %}
+
+ | {{ p.ticker }} |
+ {{ "{:.0f}".format(p.shares) }} |
+ {{ "{:,.0f}".format(p.entry) }} |
+ {{ "{:,.0f}".format(p.last) }} |
+ {{ "{:+,.0f}".format(p.unreal) }} |
+ {{ "{:+.1f}%".format(p.pct) }} |
+ {{ "{:,.0f}".format(p.stop) }} |
+ {{ "{:,.0f}".format(p.take) }} |
+
+ {% if p.stop_hit %}🔴 STOP
+ {% elif p.take_hit %}🟡 TAKE
+ {% else %}⏳ HOLD{% endif %}
+ |
+
+ {% endfor %}
+
+ {% else %}
+
Ingen åbne positioner.
+ {% endif %}
+
+
+
+
+
🏁 Lukkede handler
+ {% if trades %}
+
+ | Ticker | Handling | Antal | Kurs | Total | P&L | Signal | Dato |
+ {% for t in trades %}
+
+ | {{ t.ticker }} |
+ {{ t.action.upper() }} |
+ {{ "{:.0f}".format(t.shares) }} |
+ {{ "{:,.0f}".format(t.price) }} |
+ {{ "{:,.0f}".format(t.total_dkk) }} |
+
+ {{ "{:+,.0f}".format(t.pnl_dkk) if t.pnl_dkk is not none else "—" }}
+ |
+
+ {% if t.signal_correct == 1 %}✅ korrekt
+ {% elif t.signal_correct == 0 %}❌ forkert
+ {% else %}—{% endif %}
+ |
+ {{ t.event_date }} |
+
+ {% endfor %}
+
+ {% else %}
+
Ingen handler endnu.
+ {% endif %}
+
+
+
+
+
🔬 NLP signal pipeline
+
+ | Analyserede signaler | Alert-triggers (≥threshold) | Gns. score |
+
+ | {{ sig.total }} |
+ {{ sig.alerts }} |
+ {{ "{:.3f}".format(sig.avg_score) }} |
+
+
+
+
+
+ {% if log_tail %}
+
+
📋 Seneste log ({{ log_file }})
+
{{ log_tail }}
+
+ {% endif %}
+
+
+
+
+"""
+
+
+# ── Data helpers ─────────────────────────────────────────────────────────────
+
+def _latest_log_tail(lines: int = 40) -> tuple[str, str]:
+ """Return (filename, last N lines) from most recent runner log."""
+ if not LOG_DIR.exists():
+ return "", ""
+ logs = sorted(LOG_DIR.glob("runner_*.log"), reverse=True)
+ if not logs:
+ return "", ""
+ path = logs[0]
+ try:
+ content = path.read_text(errors="replace")
+ tail = "\n".join(content.splitlines()[-lines:])
+ return path.name, tail
+ except Exception:
+ return path.name, ""
+
+
+def _open_positions_live(db) -> list[dict]:
+ """Fetch open positions with live yfinance prices."""
+ rows = db.execute("SELECT * FROM positions").fetchall()
+ result = []
+ for p in rows:
+ ticker = p["ticker"]
+ from report import C25
+ yf_ticker = C25.get(ticker, {}).get("ticker_yahoo", ticker + ".CO")
+ try:
+ last = yf.Ticker(yf_ticker).fast_info.get("lastPrice") or p["entry_price"]
+ except Exception:
+ last = p["entry_price"]
+ entry = float(p["entry_price"])
+ shares = float(p["shares"])
+ pct = (last - entry) / entry * 100
+ result.append({
+ "ticker": ticker,
+ "shares": shares,
+ "entry": entry,
+ "last": last,
+ "unreal": shares * (last - entry),
+ "pct": pct,
+ "stop": float(p["stop_loss"]),
+ "take": float(p["take_profit"]),
+ "stop_hit": last <= p["stop_loss"],
+ "take_hit": last >= p["take_profit"],
+ })
+ return result
+
+
+def _closed_trades(db, limit: int = 20) -> list[dict]:
+ rows = db.execute("""
+ SELECT ticker, action, shares, price, total_dkk, pnl_dkk, signal_correct, event_date
+ FROM position_events
+ ORDER BY id DESC LIMIT ?
+ """, (limit,)).fetchall()
+ return [dict(r) for r in rows]
+
+
+# ── Routes ────────────────────────────────────────────────────────────────────
+
+@app.route("/")
+def index():
+ db = get_conn()
+ try:
+ positions = _open_positions_live(db)
+ trades = _closed_trades(db)
+ unreal = sum(p["unreal"] for p in positions)
+ invested = sum(p["shares"] * p["entry"] for p in positions)
+ realised = _realised_pnl(db)
+ fees = _total_fees(db)
+ sig = _signal_accuracy(db)
+ c25_ret = _c25_day_return()
+ net_pnl = realised + unreal - fees
+ net_pct = net_pnl / CAPITAL * 100
+ vs_bench = (net_pct - c25_ret) if c25_ret is not None else None
+ cash = CAPITAL - invested
+ log_file, log_tail = _latest_log_tail()
+ now = datetime.now(timezone.utc).strftime("%d %b %Y %H:%M UTC")
+
+ return render_template_string(
+ TEMPLATE,
+ now=now,
+ db_type=DB_TYPE,
+ refresh=REFRESH,
+ capital=CAPITAL,
+ cash=cash,
+ unreal=unreal,
+ realised=realised,
+ fees=fees,
+ net_pnl=net_pnl,
+ net_pct=net_pct,
+ c25_ret=c25_ret,
+ vs_bench=vs_bench,
+ positions=positions,
+ open_count=len(positions),
+ trades=trades,
+ sig=type("Sig", (), sig)(),
+ log_file=log_file,
+ log_tail=log_tail,
+ )
+ finally:
+ db.close()
+
+
+@app.route("/health")
+def health():
+ return {"status": "ok", "db": DB_TYPE, "ts": time.time()}
+
+
+# ── Main ──────────────────────────────────────────────────────────────────────
+
+def main():
+ parser = argparse.ArgumentParser(description="MoneyMaker Dashboard")
+ parser.add_argument("--port", type=int, default=5001)
+ parser.add_argument("--host", default="0.0.0.0")
+ args = parser.parse_args()
+ print(f"\n MoneyMaker Dashboard → http://localhost:{args.port}\n")
+ app.run(host=args.host, port=args.port, debug=False)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/db.py b/db.py
new file mode 100644
index 0000000..55235c3
--- /dev/null
+++ b/db.py
@@ -0,0 +1,491 @@
+"""
+db.py — Database abstraction layer for MoneyMaker.
+
+Supports both PostgreSQL (production, int.i80.dk) and SQLite (local dev).
+
+Usage:
+ from db import get_conn, DB_TYPE
+
+ conn = get_conn()
+ rows = conn.execute("SELECT * FROM articles").fetchall()
+ conn.commit()
+ conn.close()
+
+Set DATABASE_URL in .env to use PostgreSQL:
+ DATABASE_URL=postgresql://moneymaker:pass@int.i80.dk:5432/moneymaker
+
+Without DATABASE_URL, falls back to SQLite (ground_news.db).
+"""
+import os
+import sqlite3
+from pathlib import Path
+from dotenv import load_dotenv
+
+load_dotenv()
+
+DATABASE_URL = os.getenv("DATABASE_URL", "")
+SQLITE_PATH = Path(__file__).parent / "ground_news.db"
+DB_TYPE = "postgres" if DATABASE_URL else "sqlite"
+
+
+# ── DBConn wrapper ────────────────────────────────────────────────────────────
+
+class DBConn:
+ """
+ Unified connection wrapper for SQLite and PostgreSQL.
+
+ - execute(sql, params): normalises ? → %s for Postgres; returns dict-row cursor
+ - upsert(table, pk, cols, vals): cross-DB INSERT OR REPLACE / ON CONFLICT DO UPDATE
+ - commit() / rollback() / close()
+ """
+
+ def __init__(self, conn, db_type: str):
+ self._conn = conn
+ self.db_type = db_type
+
+ def _sql(self, sql: str) -> str:
+ """Translate ? placeholders to %s for Postgres."""
+ if self.db_type == "postgres":
+ return sql.replace("?", "%s")
+ return sql
+
+ def execute(self, sql: str, params=None):
+ sql = self._sql(sql)
+ if self.db_type == "postgres":
+ import psycopg2.extras
+ cur = self._conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor)
+ cur.execute(sql, params)
+ return cur
+ else:
+ return self._conn.execute(sql, params)
+
+ def executemany(self, sql: str, seq):
+ sql = self._sql(sql)
+ if self.db_type == "postgres":
+ cur = self._conn.cursor()
+ cur.executemany(sql, seq)
+ else:
+ self._conn.executemany(sql, seq)
+
+ def upsert(self, table: str, pk, cols: list, vals: tuple):
+ """
+ Cross-DB upsert.
+
+ pk: single column name (str) or list of column names for composite PK.
+ SQLite: INSERT OR REPLACE INTO table (cols) VALUES (?,...)
+ Postgres: INSERT INTO table (cols) VALUES (%s,...) ON CONFLICT (pk) DO UPDATE SET ...
+ """
+ if isinstance(pk, str):
+ pk = [pk]
+ if self.db_type == "postgres":
+ col_sql = ", ".join(cols)
+ val_sql = ", ".join(["%s"] * len(cols))
+ non_pk = [c for c in cols if c not in pk]
+ pk_sql = ", ".join(pk)
+ if non_pk:
+ update = ", ".join(f"{c}=EXCLUDED.{c}" for c in non_pk)
+ sql = (
+ f"INSERT INTO {table} ({col_sql}) VALUES ({val_sql}) "
+ f"ON CONFLICT ({pk_sql}) DO UPDATE SET {update}"
+ )
+ else:
+ sql = (
+ f"INSERT INTO {table} ({col_sql}) VALUES ({val_sql}) "
+ f"ON CONFLICT ({pk_sql}) DO NOTHING"
+ )
+ cur = self._conn.cursor()
+ cur.execute(sql, vals)
+ else:
+ col_sql = ", ".join(cols)
+ val_sql = ", ".join(["?"] * len(cols))
+ self._conn.execute(
+ f"INSERT OR REPLACE INTO {table} ({col_sql}) VALUES ({val_sql})",
+ vals,
+ )
+
+ def commit(self):
+ self._conn.commit()
+
+ def rollback(self):
+ self._conn.rollback()
+
+ def close(self):
+ self._conn.close()
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, exc_type, *_):
+ if exc_type:
+ self.rollback()
+ else:
+ self.commit()
+ self.close()
+
+
+# ── Placeholder helpers (kept for legacy usage) ───────────────────────────────
+
+def ph(n: int = 1) -> str:
+ """Return query placeholder: %s for postgres, ? for sqlite."""
+ return "%s" if DB_TYPE == "postgres" else "?"
+
+
+def placeholders(n: int) -> str:
+ """Return comma-separated placeholders for n values."""
+ p = ph()
+ return ", ".join([p] * n)
+
+
+# ── Connection ────────────────────────────────────────────────────────────────
+
+def get_conn() -> DBConn:
+ """Return a DBConn wrapper (Postgres or SQLite)."""
+ if DB_TYPE == "postgres":
+ import psycopg2
+ conn = psycopg2.connect(DATABASE_URL)
+ conn.autocommit = False
+ return DBConn(conn, "postgres")
+ else:
+ conn = sqlite3.connect(str(SQLITE_PATH))
+ conn.row_factory = sqlite3.Row
+ return DBConn(conn, "sqlite")
+
+
+# ── Schema ────────────────────────────────────────────────────────────────────
+
+SCHEMA_SQLITE = """
+CREATE TABLE IF NOT EXISTS page_cache (
+ url TEXT PRIMARY KEY,
+ page_type TEXT NOT NULL,
+ fetched_at INTEGER NOT NULL,
+ content TEXT NOT NULL
+);
+
+CREATE TABLE IF NOT EXISTS rss_feed_cache (
+ feed_id TEXT PRIMARY KEY,
+ fetched_at INTEGER NOT NULL
+);
+
+CREATE TABLE IF NOT EXISTS articles (
+ slug TEXT PRIMARY KEY,
+ story_id TEXT,
+ title TEXT NOT NULL,
+ description TEXT,
+ start_date TEXT,
+ source_count INTEGER,
+ bias_src_count INTEGER,
+ left_pct REAL,
+ ctr_pct REAL,
+ right_pct REAL,
+ left_src_count INTEGER,
+ ctr_src_count INTEGER,
+ right_src_count INTEGER,
+ overall_bias REAL,
+ blindspot TEXT,
+ factuality_json TEXT,
+ interests_json TEXT,
+ categories TEXT,
+ first_seen INTEGER,
+ last_seen INTEGER
+);
+
+CREATE TABLE IF NOT EXISTS article_signals (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ article_slug TEXT NOT NULL,
+ ticker TEXT NOT NULL,
+ company_name TEXT NOT NULL,
+ sector TEXT,
+ sentiment TEXT,
+ sentiment_score REAL,
+ entity_score REAL,
+ mention_count INTEGER,
+ full_text_used INTEGER,
+ analyzed_at INTEGER NOT NULL,
+ coverage_spread REAL,
+ claude_tickers TEXT,
+ claude_magnitude INTEGER,
+ claude_timeframe TEXT,
+ claude_reasoning TEXT,
+ momentum_dir TEXT,
+ momentum_pct_5d REAL,
+ signal_score REAL,
+ alert INTEGER
+);
+
+CREATE TABLE IF NOT EXISTS positions (
+ ticker TEXT PRIMARY KEY,
+ shares REAL NOT NULL,
+ entry_price REAL NOT NULL,
+ entry_date TEXT NOT NULL,
+ stop_loss REAL NOT NULL,
+ take_profit REAL NOT NULL,
+ note TEXT
+);
+
+CREATE TABLE IF NOT EXISTS position_events (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ ticker TEXT NOT NULL,
+ action TEXT NOT NULL,
+ shares REAL NOT NULL,
+ price REAL NOT NULL,
+ total_dkk REAL NOT NULL,
+ fee_dkk REAL,
+ pnl_dkk REAL,
+ signal_correct INTEGER,
+ event_date TEXT NOT NULL
+);
+
+CREATE TABLE IF NOT EXISTS saxo_orders (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ ticker TEXT NOT NULL,
+ direction TEXT NOT NULL,
+ shares REAL NOT NULL,
+ price_dkk REAL,
+ total_dkk REAL,
+ fee_dkk REAL,
+ saxo_order_id TEXT,
+ status TEXT NOT NULL,
+ signal_score REAL,
+ analyst_rec TEXT,
+ placed_at TEXT NOT NULL,
+ filled_at TEXT,
+ note TEXT
+);
+"""
+
+SCHEMA_POSTGRES = """
+CREATE TABLE IF NOT EXISTS page_cache (
+ url TEXT PRIMARY KEY,
+ page_type TEXT NOT NULL,
+ fetched_at BIGINT NOT NULL,
+ content TEXT NOT NULL
+);
+
+CREATE TABLE IF NOT EXISTS rss_feed_cache (
+ feed_id TEXT PRIMARY KEY,
+ fetched_at BIGINT NOT NULL
+);
+
+CREATE TABLE IF NOT EXISTS articles (
+ slug TEXT PRIMARY KEY,
+ story_id TEXT,
+ title TEXT NOT NULL,
+ description TEXT,
+ start_date TEXT,
+ source_count INTEGER,
+ bias_src_count INTEGER,
+ left_pct REAL,
+ ctr_pct REAL,
+ right_pct REAL,
+ left_src_count INTEGER,
+ ctr_src_count INTEGER,
+ right_src_count INTEGER,
+ overall_bias REAL,
+ blindspot TEXT,
+ factuality_json TEXT,
+ interests_json TEXT,
+ categories TEXT,
+ first_seen BIGINT,
+ last_seen BIGINT
+);
+
+CREATE TABLE IF NOT EXISTS article_signals (
+ id SERIAL PRIMARY KEY,
+ article_slug TEXT NOT NULL,
+ ticker TEXT NOT NULL,
+ company_name TEXT NOT NULL,
+ sector TEXT,
+ sentiment TEXT,
+ sentiment_score REAL,
+ entity_score REAL,
+ mention_count INTEGER,
+ full_text_used INTEGER,
+ analyzed_at BIGINT NOT NULL,
+ coverage_spread REAL,
+ claude_tickers TEXT,
+ claude_magnitude INTEGER,
+ claude_timeframe TEXT,
+ claude_reasoning TEXT,
+ momentum_dir TEXT,
+ momentum_pct_5d REAL,
+ signal_score REAL,
+ alert INTEGER,
+ UNIQUE(article_slug, ticker)
+);
+
+CREATE TABLE IF NOT EXISTS positions (
+ ticker TEXT PRIMARY KEY,
+ shares REAL NOT NULL,
+ entry_price REAL NOT NULL,
+ entry_date TEXT NOT NULL,
+ stop_loss REAL NOT NULL,
+ take_profit REAL NOT NULL,
+ note TEXT
+);
+
+CREATE TABLE IF NOT EXISTS position_events (
+ id SERIAL PRIMARY KEY,
+ ticker TEXT NOT NULL,
+ action TEXT NOT NULL,
+ shares REAL NOT NULL,
+ price REAL NOT NULL,
+ total_dkk REAL NOT NULL,
+ fee_dkk REAL,
+ pnl_dkk REAL,
+ signal_correct SMALLINT,
+ event_date TEXT NOT NULL
+);
+
+CREATE TABLE IF NOT EXISTS saxo_orders (
+ id SERIAL PRIMARY KEY,
+ ticker TEXT NOT NULL,
+ direction TEXT NOT NULL,
+ shares REAL NOT NULL,
+ price_dkk REAL,
+ total_dkk REAL,
+ fee_dkk REAL,
+ saxo_order_id TEXT,
+ status TEXT NOT NULL,
+ signal_score REAL,
+ analyst_rec TEXT,
+ placed_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
+ filled_at TIMESTAMPTZ,
+ note TEXT
+);
+"""
+
+
+def init_schema():
+ """Create all tables if they don't exist. Safe to run multiple times."""
+ if DB_TYPE == "postgres":
+ import psycopg2
+ conn = psycopg2.connect(DATABASE_URL)
+ cur = conn.cursor()
+ for stmt in SCHEMA_POSTGRES.split(";"):
+ stmt = stmt.strip()
+ if stmt:
+ cur.execute(stmt)
+ # Add UNIQUE constraint for article_signals (article_slug, ticker) if missing
+ cur.execute("""
+ SELECT 1 FROM pg_constraint
+ WHERE conname = 'uq_article_ticker'
+ AND conrelid = 'article_signals'::regclass
+ """)
+ if not cur.fetchone():
+ cur.execute(
+ "ALTER TABLE article_signals "
+ "ADD CONSTRAINT uq_article_ticker UNIQUE (article_slug, ticker)"
+ )
+ conn.commit()
+ cur.close()
+ conn.close()
+ else:
+ conn = sqlite3.connect(str(SQLITE_PATH))
+ conn.executescript(SCHEMA_SQLITE)
+ conn.commit()
+ conn.close()
+ print(f" Schema initialiseret ({DB_TYPE})")
+
+
+# ── SQLite → Postgres migration ───────────────────────────────────────────────
+
+def migrate_sqlite_to_postgres():
+ """
+ One-time migration: copy all data from local SQLite to Postgres.
+ Safe to run multiple times (uses INSERT OR IGNORE / ON CONFLICT DO NOTHING).
+ """
+ if DB_TYPE != "postgres":
+ print("DATABASE_URL ikke sat — kører ikke migration")
+ return
+
+ import psycopg2
+ sqlite_conn = sqlite3.connect(str(SQLITE_PATH))
+ sqlite_conn.row_factory = sqlite3.Row
+ pg_conn = get_conn()
+ pg_cur = pg_conn.cursor()
+
+ tables = [
+ "page_cache", "rss_feed_cache", "articles",
+ "article_signals", "positions", "position_events"
+ ]
+
+ for table in tables:
+ rows = sqlite_conn.execute(f"SELECT * FROM {table}").fetchall()
+ if not rows:
+ print(f" {table}: 0 rækker — skip")
+ continue
+
+ cols = list(rows[0].keys())
+ cols_sql = ", ".join(cols)
+ vals_sql = ", ".join(["%s"] * len(cols))
+
+ # SERIAL columns (id) — let Postgres auto-assign
+ insert_cols = [c for c in cols if c != "id"] if "id" in cols else cols
+ insert_vals = ", ".join(["%s"] * len(insert_cols))
+ insert_cols_sql = ", ".join(insert_cols)
+
+ inserted = 0
+ skipped = 0
+ for row in rows:
+ # Coerce bytes (SQLite blob) to float/None for Postgres REAL columns
+ values = []
+ for c in insert_cols:
+ v = row[c]
+ if isinstance(v, (bytes, bytearray)):
+ try:
+ import struct
+ v = struct.unpack('d', v)[0]
+ except Exception:
+ v = None
+ values.append(v)
+ values = tuple(values)
+ try:
+ pg_cur.execute(
+ f"INSERT INTO {table} ({insert_cols_sql}) VALUES ({insert_vals}) "
+ f"ON CONFLICT DO NOTHING",
+ values
+ )
+ if pg_cur.rowcount > 0:
+ inserted += 1
+ else:
+ skipped += 1
+ except Exception as e:
+ skipped += 1
+
+ pg_conn.commit()
+ print(f" {table}: {inserted} indsat, {skipped} sprunget over")
+
+ sqlite_conn.close()
+ pg_conn.close()
+ print(" Migration færdig!")
+
+
+if __name__ == "__main__":
+ import sys
+ cmd = sys.argv[1] if len(sys.argv) > 1 else "status"
+
+ if cmd == "init":
+ init_schema()
+ elif cmd == "migrate":
+ print(f"Migrerer SQLite → PostgreSQL ...")
+ init_schema()
+ migrate_sqlite_to_postgres()
+ elif cmd == "status":
+ conn = get_conn()
+ if DB_TYPE == "postgres":
+ raw = conn._conn.cursor()
+ raw.execute("SELECT tablename FROM pg_tables WHERE schemaname='public'")
+ tables = [r[0] for r in raw.fetchall()]
+ for t in sorted(tables):
+ raw.execute(f"SELECT COUNT(*) FROM {t}")
+ print(f" {t}: {raw.fetchone()[0]} rækker")
+ raw.close()
+ else:
+ tables = conn.execute("SELECT name FROM sqlite_master WHERE type='table'").fetchall()
+ for t in tables:
+ n = conn.execute(f"SELECT COUNT(*) AS cnt FROM {t['name']}").fetchone()["cnt"]
+ print(f" {t['name']}: {n} rækker")
+ conn.close()
+ print(f"\n DB type: {DB_TYPE}")
+ else:
+ print("Brug: python db.py [init|migrate|status]")
diff --git a/ground-news-ext/extension.xpi b/ground-news-ext/extension.xpi
new file mode 100644
index 0000000..2ff406f
Binary files /dev/null and b/ground-news-ext/extension.xpi differ
diff --git a/ground-news-ext/src/128.png b/ground-news-ext/src/128.png
new file mode 100644
index 0000000..923b2b7
Binary files /dev/null and b/ground-news-ext/src/128.png differ
diff --git a/ground-news-ext/src/16.png b/ground-news-ext/src/16.png
new file mode 100644
index 0000000..8b5a419
Binary files /dev/null and b/ground-news-ext/src/16.png differ
diff --git a/ground-news-ext/src/32.png b/ground-news-ext/src/32.png
new file mode 100644
index 0000000..88a29c0
Binary files /dev/null and b/ground-news-ext/src/32.png differ
diff --git a/ground-news-ext/src/48.png b/ground-news-ext/src/48.png
new file mode 100644
index 0000000..9d758f7
Binary files /dev/null and b/ground-news-ext/src/48.png differ
diff --git a/ground-news-ext/src/META-INF/cose.manifest b/ground-news-ext/src/META-INF/cose.manifest
new file mode 100644
index 0000000..3644fdf
--- /dev/null
+++ b/ground-news-ext/src/META-INF/cose.manifest
@@ -0,0 +1,277 @@
+Manifest-Version: 1.0
+
+Name: manifest.json
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: V2IFvyo6JSf58YUHJqeIHIVd8E0=
+SHA256-Digest: M69JukMYgZUNWY5jUT7q2g/FMX2PXGWy9PwNjwI0vug=
+
+Name: ground-symbol.svg
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: 5uwl9dTCzc0AQhNv/F3NJ9ut+9o=
+SHA256-Digest: cZMyBTOYwSPONcdGNpzsYniDOrCRIZYzmj2lHi6GuD0=
+
+Name: popup.html
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: zd6EwEaQEtVLluTkn9Qa1cmSlBM=
+SHA256-Digest: Cl3i0OIY42Ua17VkMTW5fV5ibdGpMC0XZdTOa46FQTc=
+
+Name: ground-symbol.png
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: kJidXptlh8QBtcvPwMXpE7/cmcg=
+SHA256-Digest: ey8u+oxBDz1R4Q3ufxVSro2lR/OEJveygFDU+iGWYF8=
+
+Name: 48.png
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: 7TqQN9wX+SMdWlfHtDc8ERPuqEQ=
+SHA256-Digest: giJa2wAqDjcuFUhFHWVEGzt8MBA5lgnTPCTGdCS9Vn0=
+
+Name: menu.html
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: 1ey0NcYZx5NDTSD76kqaCyW8f/A=
+SHA256-Digest: Z7g61Y7ThdKbvxy3ynCZVDBM4LBnsyL7I0gya/dVXHw=
+
+Name: 16.png
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: 28sU5Whyop4p03vu+iOpWePL8Kc=
+SHA256-Digest: 2AbeMUuiz3Oel0suwHBBLXUw5lOgSgcHRlVnxRoB08k=
+
+Name: frame.html
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: qQtfJt0TPw66a+OZUy/f9TcKMkg=
+SHA256-Digest: YAl5l7WMe0/XJuynQ4TXKF+/V42PwZG73R/s8/vX/18=
+
+Name: 128.png
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: tZEoUW0ARt4P+NYAyqj0WEq71sQ=
+SHA256-Digest: pqWObGJS7LTHpHYP1cdonw3V+V8FQp8BpReuEVEQkh4=
+
+Name: 32.png
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: QaTkY7NOk/8VVnaX0an28ddSDcs=
+SHA256-Digest: jRtP4s1aGgeYFbSUJJ5WhS3+EWixi9CI3GP1iLiCqM0=
+
+Name: options.html
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: akU7E1yGbFYopo5ni9ddunplHqE=
+SHA256-Digest: w49o/x8U5+6stagI/4bUiUFeWQ7kDIZnDnrtTBG/ocE=
+
+Name: js/facebook.js
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: cj7aAD1Z5g0H0Urujpec9+aBqt0=
+SHA256-Digest: gnS69LtD/hZle1w6Mkc+gvCfQ7CiydY1pXbyoHywVT0=
+
+Name: js/popup.js
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: AwazIGkUjD2NGwgDmaHhOTngBW4=
+SHA256-Digest: t7v8gi72iMcKYn4FD+Wil3Aa6HSTASE7RB4/k74R0Ls=
+
+Name: js/background.js
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: /TYelJW8I50597wbu69V9S45zLM=
+SHA256-Digest: 0z3cbY19uJhPqmCJgh1jji6VLOg70wZVmgw9HnsnDis=
+
+Name: js/bluesky.js
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: MNCsuEQO93hn6xAjNksh/3678wY=
+SHA256-Digest: gRf74GPcfIql/AriGau6sFJJWi/cBfTGWoQ5euJ43pU=
+
+Name: js/ground.js
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: Z61BeUMG3Zx3wbpDXvmhd0Cw0fA=
+SHA256-Digest: USQGUOKIO2vkzT4m1lkwvHotj22y1vZi1Mz6/+EaWN0=
+
+Name: js/index.js
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: 2jmj7l5rSw0yVb/vlWAYkK/YBwk=
+SHA256-Digest: 47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=
+
+Name: js/reddit.js
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: saXwwYIfh/QNKgEN0MDsI1YlvVA=
+SHA256-Digest: BvhbExjoLWewpuaKseghfprRLpcUXWrD6YNNzFDS/a4=
+
+Name: js/options.js
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: 2jmj7l5rSw0yVb/vlWAYkK/YBwk=
+SHA256-Digest: 47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=
+
+Name: js/content_script.js
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: 9R3v5pGCWxLFQn+25JVUN2fv5/s=
+SHA256-Digest: oGvKwnOBHK95FBzYz+wE57a3T8hsERlX48rv4nSDW8Q=
+
+Name: js/bluesky.css
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: 0EXvmh97NhU9/2mZOAsjDNTnkCQ=
+SHA256-Digest: ltCM6CqPE2jp8ayZEwoXWseUp6KfqtXOcIEJ9428nAI=
+
+Name: js/content_script.css
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: 0EXvmh97NhU9/2mZOAsjDNTnkCQ=
+SHA256-Digest: ltCM6CqPE2jp8ayZEwoXWseUp6KfqtXOcIEJ9428nAI=
+
+Name: js/linkedin.js
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: rujsrH7/oW7qcOTozsJFrdw3nkw=
+SHA256-Digest: cStig9r2199Q+qT/pnz/0XWmWIMMJ5p/XajjdEjt7Zg=
+
+Name: js/menu.js.LICENSE.txt
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: pZUsR2vKEMwO/5CvnF81Xe0Y0nc=
+SHA256-Digest: d9DmR6YgLL2mplsHeBjLZlK49guK2hllnzO1Gro2mjA=
+
+Name: js/reddit.css
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: 0EXvmh97NhU9/2mZOAsjDNTnkCQ=
+SHA256-Digest: ltCM6CqPE2jp8ayZEwoXWseUp6KfqtXOcIEJ9428nAI=
+
+Name: js/linkedin.css
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: 0EXvmh97NhU9/2mZOAsjDNTnkCQ=
+SHA256-Digest: ltCM6CqPE2jp8ayZEwoXWseUp6KfqtXOcIEJ9428nAI=
+
+Name: js/twitter.js
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: iGu05BP2isx45kVOVcb/O68TWWc=
+SHA256-Digest: +9FaLzeoca1V/mGIKuxZz2uPzm/36aVki/vX3/JSXCo=
+
+Name: js/menu.js
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: FeWa454d9X6GXyH7HkFSwTrTFnQ=
+SHA256-Digest: TH42cXnLQY3wjmXLd0MCK+IXHLGOuDBHKnPuF8QR4ts=
+
+Name: js/twitter.css
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: 0EXvmh97NhU9/2mZOAsjDNTnkCQ=
+SHA256-Digest: ltCM6CqPE2jp8ayZEwoXWseUp6KfqtXOcIEJ9428nAI=
+
+Name: js/facebook.css
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: 0EXvmh97NhU9/2mZOAsjDNTnkCQ=
+SHA256-Digest: ltCM6CqPE2jp8ayZEwoXWseUp6KfqtXOcIEJ9428nAI=
+
+Name: fonts/UniversalSans-800.woff
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: R01ScgAtNGc7qQFIUyrZGcCHE4s=
+SHA256-Digest: s/7cq8YWjWQRWg4xJ0JZFBw17O5NxNZXG7O5/F5lggE=
+
+Name: fonts/UniversalSans-680.woff
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: Lwp0bnjR0mnTLP0g/j4H1iMpIBk=
+SHA256-Digest: ynJEQsy28v6X9Y/gqOs0lQ9hFK7AkwuMUMvXN0JA8Do=
+
+Name: fonts/UniversalSans-800.ttf
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: z8F3l76le5ETg3V6kpTcmcvKLg4=
+SHA256-Digest: eCmjTqBZPuAgdgXawQu5+l6GbGVqCsd5x8RbhAVdabM=
+
+Name: fonts/UniversalSans-480.woff
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: hOizxWx9ZO5V4hb+ur3xcOCh7ls=
+SHA256-Digest: ALRea9OYPxfKRpuUwYuikx4D2NPuYQxxGmtDKvtBH7A=
+
+Name: fonts/UniversalSans-680.ttf
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: Z3iFwBl47GvgfYXY3CCtip8EoMA=
+SHA256-Digest: CX6/qn4t6JdXyMGjleJB19YwZgw0hujgG7CgRhT2a4Q=
+
+Name: fonts/UniversalSans-480.ttf
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: QHIRDYioCWTm+u0Y4XHmy2pUdEE=
+SHA256-Digest: 0+IQVOpNNsM1YiCLxuLsZKEHsQIuAemWVwgu1vnFXLY=
+
+Name: icons/info.svg
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: xSN0y22E9y+SnvukSpK5lVPmkgI=
+SHA256-Digest: uzYDK5vpqHyFwtDSDbBu7XcOXxPiR2yYchY85rtR8LY=
+
+Name: icons/bookmark-1-black.svg
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: lpcL3lTckaLVyyWFmNyvtncPlRc=
+SHA256-Digest: EdsShQWkFxTvHJ5HdshXS3G6RpW0x43Zp9SaJm2DMYo=
+
+Name: icons/copy.svg
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: OXhh/FV76zBJ7bPARhO1kXbJ8mc=
+SHA256-Digest: I5ZkF6AVqZ6CBh9tDNQXVLWKDiORSsz14S1ozDCsY10=
+
+Name: icons/bar-chart-2.svg
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: ymsSQfHsB2Yhrc/9oYGvTC4QCYg=
+SHA256-Digest: wbOZE5ZTvjXZ33eI8MpdL7GuVwG5NPzh0sIWuyVSj/E=
+
+Name: icons/twitter.svg
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: sdv/f+LCM/KDPUdStFcbQs2S1Qs=
+SHA256-Digest: LfCEPjbtgSnkkXQ/424WfEqTGU485RFa7QndyYK4XlQ=
+
+Name: icons/factuality-graph.svg
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: Jw9QdSBN9QQAhNutol7WcXsfSNs=
+SHA256-Digest: UIIYoCko4OPvm8EkoooE24pG6YNN+qYdtCP+Mdp4xEY=
+
+Name: icons/bookmark-1-white.svg
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: Gri9o5cr44Z8yMLvXMruu6lZbFM=
+SHA256-Digest: SBcRZjzR9t2TMdk0k7fkjGn1cqmnntmltV/2Siulhdw=
+
+Name: icons/question-solid.svg
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: ztagl+8AjUba4r4vcu8MPPB99Os=
+SHA256-Digest: A+asnnSbjJRZb0szivuTdmsjq4DMHBnNTIkezTR8oPk=
+
+Name: icons/close.svg
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: qBwoyv33PlWAH497qkZ9EZ4f7fQ=
+SHA256-Digest: xJdqjC9p05q/gLVPX5vsn53mJGTcCrajg8CPIZizJc4=
+
+Name: icons/question-solid-white.svg
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: YcWQZb3R3LPG5WfArPacQzhy0pM=
+SHA256-Digest: q5y7JCL0wywNCGPR76k/EPqdRLlLIohdIXDEIZKh2+o=
+
+Name: icons/facebook.svg
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: 7AaS3nkHGIhqC3LRrIAEFIucH/s=
+SHA256-Digest: +PGwndbOb6KBtu5rWKrifGt4vgcmi4TuwHZqEKTu7jM=
+
+Name: icons/circle-dashed-black.svg
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: p4Ah/FbsnwfeKDMa+w+lEdnMlIc=
+SHA256-Digest: 26vt1XYxiy1OBY7sh3EGvAwsy3a85ZM7pCK5g75mY3M=
+
+Name: icons/book-open-1-black.svg
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: UI1c5voVCUTx4Az1THzMZIyxJbA=
+SHA256-Digest: rM+8F1keTjIr9EVBwHNdMjdZGL0X9zuu31YN1m/RY70=
+
+Name: icons/bookmark-all-white.svg
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: xJqkz+nuT8QIJI5Davu4lOfk2So=
+SHA256-Digest: U2HWym+rA41IBX+vP2doYA2j57iNjRH9sW0LaT2noL0=
+
+Name: icons/linkedin.svg
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: RWz56AE4nLkOHRP3Yn4FNNOs6To=
+SHA256-Digest: Y+y19EnAQP4YOb+I/ElIJzlpTrwC7+dnoqxjgB5iFks=
+
+Name: icons/triangle-exclamation-solid.svg
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: ArXBoIpovMCiYpnmC1D04se37fo=
+SHA256-Digest: qKKteohNK7X+43+NvMPNHSgnoUH3PL/MN2Alfv40u7c=
+
+Name: icons/reddit.svg
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: mjVp6/sQZ4APXhqDy6fBA8e7Z2g=
+SHA256-Digest: CFH+sfL4ZCfy9bFGUuV2dGbN2yZQ1zegphlrEh9OSR4=
+
+Name: icons/access-block.png
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: llyHO/XPtj9GiKooWGUICDgcmkY=
+SHA256-Digest: rYFcWtObJ6u7OaLbvemwaO37VNihaZjugHs2zFFJUBQ=
+
+Name: icons/lock.svg
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: Np+svInXuBwn2mqXKUQaJK3+6Sk=
+SHA256-Digest: tHS1JJz2NJwYgcpM6VUA7Q9XhskazpUTAi3CzeICZK8=
+
diff --git a/ground-news-ext/src/META-INF/cose.sig b/ground-news-ext/src/META-INF/cose.sig
new file mode 100644
index 0000000..8fb911e
Binary files /dev/null and b/ground-news-ext/src/META-INF/cose.sig differ
diff --git a/ground-news-ext/src/META-INF/manifest.mf b/ground-news-ext/src/META-INF/manifest.mf
new file mode 100644
index 0000000..9b7dba5
--- /dev/null
+++ b/ground-news-ext/src/META-INF/manifest.mf
@@ -0,0 +1,287 @@
+Manifest-Version: 1.0
+
+Name: manifest.json
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: V2IFvyo6JSf58YUHJqeIHIVd8E0=
+SHA256-Digest: M69JukMYgZUNWY5jUT7q2g/FMX2PXGWy9PwNjwI0vug=
+
+Name: ground-symbol.svg
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: 5uwl9dTCzc0AQhNv/F3NJ9ut+9o=
+SHA256-Digest: cZMyBTOYwSPONcdGNpzsYniDOrCRIZYzmj2lHi6GuD0=
+
+Name: popup.html
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: zd6EwEaQEtVLluTkn9Qa1cmSlBM=
+SHA256-Digest: Cl3i0OIY42Ua17VkMTW5fV5ibdGpMC0XZdTOa46FQTc=
+
+Name: ground-symbol.png
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: kJidXptlh8QBtcvPwMXpE7/cmcg=
+SHA256-Digest: ey8u+oxBDz1R4Q3ufxVSro2lR/OEJveygFDU+iGWYF8=
+
+Name: 48.png
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: 7TqQN9wX+SMdWlfHtDc8ERPuqEQ=
+SHA256-Digest: giJa2wAqDjcuFUhFHWVEGzt8MBA5lgnTPCTGdCS9Vn0=
+
+Name: menu.html
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: 1ey0NcYZx5NDTSD76kqaCyW8f/A=
+SHA256-Digest: Z7g61Y7ThdKbvxy3ynCZVDBM4LBnsyL7I0gya/dVXHw=
+
+Name: 16.png
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: 28sU5Whyop4p03vu+iOpWePL8Kc=
+SHA256-Digest: 2AbeMUuiz3Oel0suwHBBLXUw5lOgSgcHRlVnxRoB08k=
+
+Name: frame.html
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: qQtfJt0TPw66a+OZUy/f9TcKMkg=
+SHA256-Digest: YAl5l7WMe0/XJuynQ4TXKF+/V42PwZG73R/s8/vX/18=
+
+Name: 128.png
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: tZEoUW0ARt4P+NYAyqj0WEq71sQ=
+SHA256-Digest: pqWObGJS7LTHpHYP1cdonw3V+V8FQp8BpReuEVEQkh4=
+
+Name: 32.png
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: QaTkY7NOk/8VVnaX0an28ddSDcs=
+SHA256-Digest: jRtP4s1aGgeYFbSUJJ5WhS3+EWixi9CI3GP1iLiCqM0=
+
+Name: options.html
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: akU7E1yGbFYopo5ni9ddunplHqE=
+SHA256-Digest: w49o/x8U5+6stagI/4bUiUFeWQ7kDIZnDnrtTBG/ocE=
+
+Name: js/facebook.js
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: cj7aAD1Z5g0H0Urujpec9+aBqt0=
+SHA256-Digest: gnS69LtD/hZle1w6Mkc+gvCfQ7CiydY1pXbyoHywVT0=
+
+Name: js/popup.js
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: AwazIGkUjD2NGwgDmaHhOTngBW4=
+SHA256-Digest: t7v8gi72iMcKYn4FD+Wil3Aa6HSTASE7RB4/k74R0Ls=
+
+Name: js/background.js
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: /TYelJW8I50597wbu69V9S45zLM=
+SHA256-Digest: 0z3cbY19uJhPqmCJgh1jji6VLOg70wZVmgw9HnsnDis=
+
+Name: js/bluesky.js
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: MNCsuEQO93hn6xAjNksh/3678wY=
+SHA256-Digest: gRf74GPcfIql/AriGau6sFJJWi/cBfTGWoQ5euJ43pU=
+
+Name: js/ground.js
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: Z61BeUMG3Zx3wbpDXvmhd0Cw0fA=
+SHA256-Digest: USQGUOKIO2vkzT4m1lkwvHotj22y1vZi1Mz6/+EaWN0=
+
+Name: js/index.js
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: 2jmj7l5rSw0yVb/vlWAYkK/YBwk=
+SHA256-Digest: 47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=
+
+Name: js/reddit.js
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: saXwwYIfh/QNKgEN0MDsI1YlvVA=
+SHA256-Digest: BvhbExjoLWewpuaKseghfprRLpcUXWrD6YNNzFDS/a4=
+
+Name: js/options.js
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: 2jmj7l5rSw0yVb/vlWAYkK/YBwk=
+SHA256-Digest: 47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=
+
+Name: js/content_script.js
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: 9R3v5pGCWxLFQn+25JVUN2fv5/s=
+SHA256-Digest: oGvKwnOBHK95FBzYz+wE57a3T8hsERlX48rv4nSDW8Q=
+
+Name: js/bluesky.css
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: 0EXvmh97NhU9/2mZOAsjDNTnkCQ=
+SHA256-Digest: ltCM6CqPE2jp8ayZEwoXWseUp6KfqtXOcIEJ9428nAI=
+
+Name: js/content_script.css
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: 0EXvmh97NhU9/2mZOAsjDNTnkCQ=
+SHA256-Digest: ltCM6CqPE2jp8ayZEwoXWseUp6KfqtXOcIEJ9428nAI=
+
+Name: js/linkedin.js
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: rujsrH7/oW7qcOTozsJFrdw3nkw=
+SHA256-Digest: cStig9r2199Q+qT/pnz/0XWmWIMMJ5p/XajjdEjt7Zg=
+
+Name: js/menu.js.LICENSE.txt
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: pZUsR2vKEMwO/5CvnF81Xe0Y0nc=
+SHA256-Digest: d9DmR6YgLL2mplsHeBjLZlK49guK2hllnzO1Gro2mjA=
+
+Name: js/reddit.css
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: 0EXvmh97NhU9/2mZOAsjDNTnkCQ=
+SHA256-Digest: ltCM6CqPE2jp8ayZEwoXWseUp6KfqtXOcIEJ9428nAI=
+
+Name: js/linkedin.css
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: 0EXvmh97NhU9/2mZOAsjDNTnkCQ=
+SHA256-Digest: ltCM6CqPE2jp8ayZEwoXWseUp6KfqtXOcIEJ9428nAI=
+
+Name: js/twitter.js
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: iGu05BP2isx45kVOVcb/O68TWWc=
+SHA256-Digest: +9FaLzeoca1V/mGIKuxZz2uPzm/36aVki/vX3/JSXCo=
+
+Name: js/menu.js
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: FeWa454d9X6GXyH7HkFSwTrTFnQ=
+SHA256-Digest: TH42cXnLQY3wjmXLd0MCK+IXHLGOuDBHKnPuF8QR4ts=
+
+Name: js/twitter.css
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: 0EXvmh97NhU9/2mZOAsjDNTnkCQ=
+SHA256-Digest: ltCM6CqPE2jp8ayZEwoXWseUp6KfqtXOcIEJ9428nAI=
+
+Name: js/facebook.css
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: 0EXvmh97NhU9/2mZOAsjDNTnkCQ=
+SHA256-Digest: ltCM6CqPE2jp8ayZEwoXWseUp6KfqtXOcIEJ9428nAI=
+
+Name: fonts/UniversalSans-800.woff
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: R01ScgAtNGc7qQFIUyrZGcCHE4s=
+SHA256-Digest: s/7cq8YWjWQRWg4xJ0JZFBw17O5NxNZXG7O5/F5lggE=
+
+Name: fonts/UniversalSans-680.woff
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: Lwp0bnjR0mnTLP0g/j4H1iMpIBk=
+SHA256-Digest: ynJEQsy28v6X9Y/gqOs0lQ9hFK7AkwuMUMvXN0JA8Do=
+
+Name: fonts/UniversalSans-800.ttf
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: z8F3l76le5ETg3V6kpTcmcvKLg4=
+SHA256-Digest: eCmjTqBZPuAgdgXawQu5+l6GbGVqCsd5x8RbhAVdabM=
+
+Name: fonts/UniversalSans-480.woff
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: hOizxWx9ZO5V4hb+ur3xcOCh7ls=
+SHA256-Digest: ALRea9OYPxfKRpuUwYuikx4D2NPuYQxxGmtDKvtBH7A=
+
+Name: fonts/UniversalSans-680.ttf
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: Z3iFwBl47GvgfYXY3CCtip8EoMA=
+SHA256-Digest: CX6/qn4t6JdXyMGjleJB19YwZgw0hujgG7CgRhT2a4Q=
+
+Name: fonts/UniversalSans-480.ttf
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: QHIRDYioCWTm+u0Y4XHmy2pUdEE=
+SHA256-Digest: 0+IQVOpNNsM1YiCLxuLsZKEHsQIuAemWVwgu1vnFXLY=
+
+Name: icons/info.svg
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: xSN0y22E9y+SnvukSpK5lVPmkgI=
+SHA256-Digest: uzYDK5vpqHyFwtDSDbBu7XcOXxPiR2yYchY85rtR8LY=
+
+Name: icons/bookmark-1-black.svg
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: lpcL3lTckaLVyyWFmNyvtncPlRc=
+SHA256-Digest: EdsShQWkFxTvHJ5HdshXS3G6RpW0x43Zp9SaJm2DMYo=
+
+Name: icons/copy.svg
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: OXhh/FV76zBJ7bPARhO1kXbJ8mc=
+SHA256-Digest: I5ZkF6AVqZ6CBh9tDNQXVLWKDiORSsz14S1ozDCsY10=
+
+Name: icons/bar-chart-2.svg
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: ymsSQfHsB2Yhrc/9oYGvTC4QCYg=
+SHA256-Digest: wbOZE5ZTvjXZ33eI8MpdL7GuVwG5NPzh0sIWuyVSj/E=
+
+Name: icons/twitter.svg
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: sdv/f+LCM/KDPUdStFcbQs2S1Qs=
+SHA256-Digest: LfCEPjbtgSnkkXQ/424WfEqTGU485RFa7QndyYK4XlQ=
+
+Name: icons/factuality-graph.svg
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: Jw9QdSBN9QQAhNutol7WcXsfSNs=
+SHA256-Digest: UIIYoCko4OPvm8EkoooE24pG6YNN+qYdtCP+Mdp4xEY=
+
+Name: icons/bookmark-1-white.svg
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: Gri9o5cr44Z8yMLvXMruu6lZbFM=
+SHA256-Digest: SBcRZjzR9t2TMdk0k7fkjGn1cqmnntmltV/2Siulhdw=
+
+Name: icons/question-solid.svg
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: ztagl+8AjUba4r4vcu8MPPB99Os=
+SHA256-Digest: A+asnnSbjJRZb0szivuTdmsjq4DMHBnNTIkezTR8oPk=
+
+Name: icons/close.svg
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: qBwoyv33PlWAH497qkZ9EZ4f7fQ=
+SHA256-Digest: xJdqjC9p05q/gLVPX5vsn53mJGTcCrajg8CPIZizJc4=
+
+Name: icons/question-solid-white.svg
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: YcWQZb3R3LPG5WfArPacQzhy0pM=
+SHA256-Digest: q5y7JCL0wywNCGPR76k/EPqdRLlLIohdIXDEIZKh2+o=
+
+Name: icons/facebook.svg
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: 7AaS3nkHGIhqC3LRrIAEFIucH/s=
+SHA256-Digest: +PGwndbOb6KBtu5rWKrifGt4vgcmi4TuwHZqEKTu7jM=
+
+Name: icons/circle-dashed-black.svg
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: p4Ah/FbsnwfeKDMa+w+lEdnMlIc=
+SHA256-Digest: 26vt1XYxiy1OBY7sh3EGvAwsy3a85ZM7pCK5g75mY3M=
+
+Name: icons/book-open-1-black.svg
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: UI1c5voVCUTx4Az1THzMZIyxJbA=
+SHA256-Digest: rM+8F1keTjIr9EVBwHNdMjdZGL0X9zuu31YN1m/RY70=
+
+Name: icons/bookmark-all-white.svg
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: xJqkz+nuT8QIJI5Davu4lOfk2So=
+SHA256-Digest: U2HWym+rA41IBX+vP2doYA2j57iNjRH9sW0LaT2noL0=
+
+Name: icons/linkedin.svg
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: RWz56AE4nLkOHRP3Yn4FNNOs6To=
+SHA256-Digest: Y+y19EnAQP4YOb+I/ElIJzlpTrwC7+dnoqxjgB5iFks=
+
+Name: icons/triangle-exclamation-solid.svg
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: ArXBoIpovMCiYpnmC1D04se37fo=
+SHA256-Digest: qKKteohNK7X+43+NvMPNHSgnoUH3PL/MN2Alfv40u7c=
+
+Name: icons/reddit.svg
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: mjVp6/sQZ4APXhqDy6fBA8e7Z2g=
+SHA256-Digest: CFH+sfL4ZCfy9bFGUuV2dGbN2yZQ1zegphlrEh9OSR4=
+
+Name: icons/access-block.png
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: llyHO/XPtj9GiKooWGUICDgcmkY=
+SHA256-Digest: rYFcWtObJ6u7OaLbvemwaO37VNihaZjugHs2zFFJUBQ=
+
+Name: icons/lock.svg
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: Np+svInXuBwn2mqXKUQaJK3+6Sk=
+SHA256-Digest: tHS1JJz2NJwYgcpM6VUA7Q9XhskazpUTAi3CzeICZK8=
+
+Name: META-INF/cose.manifest
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: 9HFTS/zlmlOHYcXdrE2m6pb876E=
+SHA256-Digest: xe5g1rLlpVs7kCmv4iRPiKvxuwSbtWWFBnG66CwPtiE=
+
+Name: META-INF/cose.sig
+Digest-Algorithms: SHA1 SHA256
+SHA1-Digest: bRbKU9HRi1Cv/+d42kRoiVlgNa8=
+SHA256-Digest: Am6hYP/HPPR/sGPHTwynjPLnWqk9RC9Dn7wMM9SMn/k=
+
diff --git a/ground-news-ext/src/META-INF/mozilla.rsa b/ground-news-ext/src/META-INF/mozilla.rsa
new file mode 100644
index 0000000..081be36
Binary files /dev/null and b/ground-news-ext/src/META-INF/mozilla.rsa differ
diff --git a/ground-news-ext/src/META-INF/mozilla.sf b/ground-news-ext/src/META-INF/mozilla.sf
new file mode 100644
index 0000000..409f3b1
--- /dev/null
+++ b/ground-news-ext/src/META-INF/mozilla.sf
@@ -0,0 +1,4 @@
+Signature-Version: 1.0
+SHA1-Digest-Manifest: aja76xPF0ppgA5xWeuljtiTIUOY=
+SHA256-Digest-Manifest: fiRUfRWTKMY9g1E304egtSv6nkOpo7iqARA8//5xfeM=
+
diff --git a/ground-news-ext/src/fonts/UniversalSans-480.ttf b/ground-news-ext/src/fonts/UniversalSans-480.ttf
new file mode 100644
index 0000000..9821ec6
Binary files /dev/null and b/ground-news-ext/src/fonts/UniversalSans-480.ttf differ
diff --git a/ground-news-ext/src/fonts/UniversalSans-480.woff b/ground-news-ext/src/fonts/UniversalSans-480.woff
new file mode 100644
index 0000000..61d58df
Binary files /dev/null and b/ground-news-ext/src/fonts/UniversalSans-480.woff differ
diff --git a/ground-news-ext/src/fonts/UniversalSans-680.ttf b/ground-news-ext/src/fonts/UniversalSans-680.ttf
new file mode 100644
index 0000000..936502a
Binary files /dev/null and b/ground-news-ext/src/fonts/UniversalSans-680.ttf differ
diff --git a/ground-news-ext/src/fonts/UniversalSans-680.woff b/ground-news-ext/src/fonts/UniversalSans-680.woff
new file mode 100644
index 0000000..e4ba38c
Binary files /dev/null and b/ground-news-ext/src/fonts/UniversalSans-680.woff differ
diff --git a/ground-news-ext/src/fonts/UniversalSans-800.ttf b/ground-news-ext/src/fonts/UniversalSans-800.ttf
new file mode 100644
index 0000000..ccf8515
Binary files /dev/null and b/ground-news-ext/src/fonts/UniversalSans-800.ttf differ
diff --git a/ground-news-ext/src/fonts/UniversalSans-800.woff b/ground-news-ext/src/fonts/UniversalSans-800.woff
new file mode 100644
index 0000000..5daf2aa
Binary files /dev/null and b/ground-news-ext/src/fonts/UniversalSans-800.woff differ
diff --git a/ground-news-ext/src/frame.html b/ground-news-ext/src/frame.html
new file mode 100644
index 0000000..6d7ac50
--- /dev/null
+++ b/ground-news-ext/src/frame.html
@@ -0,0 +1,18 @@
+
+
+
+
diff --git a/ground-news-ext/src/ground-symbol.png b/ground-news-ext/src/ground-symbol.png
new file mode 100644
index 0000000..0a6264b
Binary files /dev/null and b/ground-news-ext/src/ground-symbol.png differ
diff --git a/ground-news-ext/src/ground-symbol.svg b/ground-news-ext/src/ground-symbol.svg
new file mode 100644
index 0000000..eeca573
--- /dev/null
+++ b/ground-news-ext/src/ground-symbol.svg
@@ -0,0 +1,17 @@
+
diff --git a/ground-news-ext/src/icons/access-block.png b/ground-news-ext/src/icons/access-block.png
new file mode 100644
index 0000000..05c1180
Binary files /dev/null and b/ground-news-ext/src/icons/access-block.png differ
diff --git a/ground-news-ext/src/icons/bar-chart-2.svg b/ground-news-ext/src/icons/bar-chart-2.svg
new file mode 100644
index 0000000..935a814
--- /dev/null
+++ b/ground-news-ext/src/icons/bar-chart-2.svg
@@ -0,0 +1,5 @@
+
diff --git a/ground-news-ext/src/icons/book-open-1-black.svg b/ground-news-ext/src/icons/book-open-1-black.svg
new file mode 100644
index 0000000..7c3da14
--- /dev/null
+++ b/ground-news-ext/src/icons/book-open-1-black.svg
@@ -0,0 +1,4 @@
+
diff --git a/ground-news-ext/src/icons/bookmark-1-black.svg b/ground-news-ext/src/icons/bookmark-1-black.svg
new file mode 100644
index 0000000..39c2975
--- /dev/null
+++ b/ground-news-ext/src/icons/bookmark-1-black.svg
@@ -0,0 +1,3 @@
+
diff --git a/ground-news-ext/src/icons/bookmark-1-white.svg b/ground-news-ext/src/icons/bookmark-1-white.svg
new file mode 100644
index 0000000..58e193d
--- /dev/null
+++ b/ground-news-ext/src/icons/bookmark-1-white.svg
@@ -0,0 +1,3 @@
+
diff --git a/ground-news-ext/src/icons/bookmark-all-white.svg b/ground-news-ext/src/icons/bookmark-all-white.svg
new file mode 100644
index 0000000..9726691
--- /dev/null
+++ b/ground-news-ext/src/icons/bookmark-all-white.svg
@@ -0,0 +1,3 @@
+
\ No newline at end of file
diff --git a/ground-news-ext/src/icons/circle-dashed-black.svg b/ground-news-ext/src/icons/circle-dashed-black.svg
new file mode 100644
index 0000000..b6ebdec
--- /dev/null
+++ b/ground-news-ext/src/icons/circle-dashed-black.svg
@@ -0,0 +1,5 @@
+
diff --git a/ground-news-ext/src/icons/close.svg b/ground-news-ext/src/icons/close.svg
new file mode 100644
index 0000000..7c414ba
--- /dev/null
+++ b/ground-news-ext/src/icons/close.svg
@@ -0,0 +1,4 @@
+
diff --git a/ground-news-ext/src/icons/copy.svg b/ground-news-ext/src/icons/copy.svg
new file mode 100644
index 0000000..857dc41
--- /dev/null
+++ b/ground-news-ext/src/icons/copy.svg
@@ -0,0 +1,4 @@
+
diff --git a/ground-news-ext/src/icons/facebook.svg b/ground-news-ext/src/icons/facebook.svg
new file mode 100644
index 0000000..49b7451
--- /dev/null
+++ b/ground-news-ext/src/icons/facebook.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/ground-news-ext/src/icons/factuality-graph.svg b/ground-news-ext/src/icons/factuality-graph.svg
new file mode 100644
index 0000000..ff29e37
--- /dev/null
+++ b/ground-news-ext/src/icons/factuality-graph.svg
@@ -0,0 +1,17 @@
+
diff --git a/ground-news-ext/src/icons/info.svg b/ground-news-ext/src/icons/info.svg
new file mode 100644
index 0000000..f0d63dd
--- /dev/null
+++ b/ground-news-ext/src/icons/info.svg
@@ -0,0 +1,5 @@
+
diff --git a/ground-news-ext/src/icons/linkedin.svg b/ground-news-ext/src/icons/linkedin.svg
new file mode 100644
index 0000000..bdfee85
--- /dev/null
+++ b/ground-news-ext/src/icons/linkedin.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/ground-news-ext/src/icons/lock.svg b/ground-news-ext/src/icons/lock.svg
new file mode 100644
index 0000000..be05c10
--- /dev/null
+++ b/ground-news-ext/src/icons/lock.svg
@@ -0,0 +1,5 @@
+
diff --git a/ground-news-ext/src/icons/question-solid-white.svg b/ground-news-ext/src/icons/question-solid-white.svg
new file mode 100644
index 0000000..507d658
--- /dev/null
+++ b/ground-news-ext/src/icons/question-solid-white.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/ground-news-ext/src/icons/question-solid.svg b/ground-news-ext/src/icons/question-solid.svg
new file mode 100644
index 0000000..d2bbc31
--- /dev/null
+++ b/ground-news-ext/src/icons/question-solid.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/ground-news-ext/src/icons/reddit.svg b/ground-news-ext/src/icons/reddit.svg
new file mode 100644
index 0000000..2199d80
--- /dev/null
+++ b/ground-news-ext/src/icons/reddit.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/ground-news-ext/src/icons/triangle-exclamation-solid.svg b/ground-news-ext/src/icons/triangle-exclamation-solid.svg
new file mode 100644
index 0000000..6d6b1f1
--- /dev/null
+++ b/ground-news-ext/src/icons/triangle-exclamation-solid.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/ground-news-ext/src/icons/twitter.svg b/ground-news-ext/src/icons/twitter.svg
new file mode 100644
index 0000000..1e22f49
--- /dev/null
+++ b/ground-news-ext/src/icons/twitter.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/ground-news-ext/src/js/background.js b/ground-news-ext/src/js/background.js
new file mode 100644
index 0000000..43de284
--- /dev/null
+++ b/ground-news-ext/src/js/background.js
@@ -0,0 +1 @@
+(()=>{var e={6815:function(e,r){var s,t,a;"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self&&self,t=[e],s=function(e){"use strict";if(!globalThis.chrome?.runtime?.id)throw new Error("This script should only be loaded in a browser extension.");if(void 0===globalThis.browser||Object.getPrototypeOf(globalThis.browser)!==Object.prototype){const r="The message port closed before a response was received.",s=e=>{const s={alarms:{clear:{minArgs:0,maxArgs:1},clearAll:{minArgs:0,maxArgs:0},get:{minArgs:0,maxArgs:1},getAll:{minArgs:0,maxArgs:0}},bookmarks:{create:{minArgs:1,maxArgs:1},get:{minArgs:1,maxArgs:1},getChildren:{minArgs:1,maxArgs:1},getRecent:{minArgs:1,maxArgs:1},getSubTree:{minArgs:1,maxArgs:1},getTree:{minArgs:0,maxArgs:0},move:{minArgs:2,maxArgs:2},remove:{minArgs:1,maxArgs:1},removeTree:{minArgs:1,maxArgs:1},search:{minArgs:1,maxArgs:1},update:{minArgs:2,maxArgs:2}},browserAction:{disable:{minArgs:0,maxArgs:1,fallbackToNoCallback:!0},enable:{minArgs:0,maxArgs:1,fallbackToNoCallback:!0},getBadgeBackgroundColor:{minArgs:1,maxArgs:1},getBadgeText:{minArgs:1,maxArgs:1},getPopup:{minArgs:1,maxArgs:1},getTitle:{minArgs:1,maxArgs:1},openPopup:{minArgs:0,maxArgs:0},setBadgeBackgroundColor:{minArgs:1,maxArgs:1,fallbackToNoCallback:!0},setBadgeText:{minArgs:1,maxArgs:1,fallbackToNoCallback:!0},setIcon:{minArgs:1,maxArgs:1},setPopup:{minArgs:1,maxArgs:1,fallbackToNoCallback:!0},setTitle:{minArgs:1,maxArgs:1,fallbackToNoCallback:!0}},browsingData:{remove:{minArgs:2,maxArgs:2},removeCache:{minArgs:1,maxArgs:1},removeCookies:{minArgs:1,maxArgs:1},removeDownloads:{minArgs:1,maxArgs:1},removeFormData:{minArgs:1,maxArgs:1},removeHistory:{minArgs:1,maxArgs:1},removeLocalStorage:{minArgs:1,maxArgs:1},removePasswords:{minArgs:1,maxArgs:1},removePluginData:{minArgs:1,maxArgs:1},settings:{minArgs:0,maxArgs:0}},commands:{getAll:{minArgs:0,maxArgs:0}},contextMenus:{remove:{minArgs:1,maxArgs:1},removeAll:{minArgs:0,maxArgs:0},update:{minArgs:2,maxArgs:2}},cookies:{get:{minArgs:1,maxArgs:1},getAll:{minArgs:1,maxArgs:1},getAllCookieStores:{minArgs:0,maxArgs:0},remove:{minArgs:1,maxArgs:1},set:{minArgs:1,maxArgs:1}},devtools:{inspectedWindow:{eval:{minArgs:1,maxArgs:2,singleCallbackArg:!1}},panels:{create:{minArgs:3,maxArgs:3,singleCallbackArg:!0},elements:{createSidebarPane:{minArgs:1,maxArgs:1}}}},downloads:{cancel:{minArgs:1,maxArgs:1},download:{minArgs:1,maxArgs:1},erase:{minArgs:1,maxArgs:1},getFileIcon:{minArgs:1,maxArgs:2},open:{minArgs:1,maxArgs:1,fallbackToNoCallback:!0},pause:{minArgs:1,maxArgs:1},removeFile:{minArgs:1,maxArgs:1},resume:{minArgs:1,maxArgs:1},search:{minArgs:1,maxArgs:1},show:{minArgs:1,maxArgs:1,fallbackToNoCallback:!0}},extension:{isAllowedFileSchemeAccess:{minArgs:0,maxArgs:0},isAllowedIncognitoAccess:{minArgs:0,maxArgs:0}},history:{addUrl:{minArgs:1,maxArgs:1},deleteAll:{minArgs:0,maxArgs:0},deleteRange:{minArgs:1,maxArgs:1},deleteUrl:{minArgs:1,maxArgs:1},getVisits:{minArgs:1,maxArgs:1},search:{minArgs:1,maxArgs:1}},i18n:{detectLanguage:{minArgs:1,maxArgs:1},getAcceptLanguages:{minArgs:0,maxArgs:0}},identity:{launchWebAuthFlow:{minArgs:1,maxArgs:1}},idle:{queryState:{minArgs:1,maxArgs:1}},management:{get:{minArgs:1,maxArgs:1},getAll:{minArgs:0,maxArgs:0},getSelf:{minArgs:0,maxArgs:0},setEnabled:{minArgs:2,maxArgs:2},uninstallSelf:{minArgs:0,maxArgs:1}},notifications:{clear:{minArgs:1,maxArgs:1},create:{minArgs:1,maxArgs:2},getAll:{minArgs:0,maxArgs:0},getPermissionLevel:{minArgs:0,maxArgs:0},update:{minArgs:2,maxArgs:2}},pageAction:{getPopup:{minArgs:1,maxArgs:1},getTitle:{minArgs:1,maxArgs:1},hide:{minArgs:1,maxArgs:1,fallbackToNoCallback:!0},setIcon:{minArgs:1,maxArgs:1},setPopup:{minArgs:1,maxArgs:1,fallbackToNoCallback:!0},setTitle:{minArgs:1,maxArgs:1,fallbackToNoCallback:!0},show:{minArgs:1,maxArgs:1,fallbackToNoCallback:!0}},permissions:{contains:{minArgs:1,maxArgs:1},getAll:{minArgs:0,maxArgs:0},remove:{minArgs:1,maxArgs:1},request:{minArgs:1,maxArgs:1}},runtime:{getBackgroundPage:{minArgs:0,maxArgs:0},getPlatformInfo:{minArgs:0,maxArgs:0},openOptionsPage:{minArgs:0,maxArgs:0},requestUpdateCheck:{minArgs:0,maxArgs:0},sendMessage:{minArgs:1,maxArgs:3},sendNativeMessage:{minArgs:2,maxArgs:2},setUninstallURL:{minArgs:1,maxArgs:1}},sessions:{getDevices:{minArgs:0,maxArgs:1},getRecentlyClosed:{minArgs:0,maxArgs:1},restore:{minArgs:0,maxArgs:1}},storage:{local:{clear:{minArgs:0,maxArgs:0},get:{minArgs:0,maxArgs:1},getBytesInUse:{minArgs:0,maxArgs:1},remove:{minArgs:1,maxArgs:1},set:{minArgs:1,maxArgs:1}},managed:{get:{minArgs:0,maxArgs:1},getBytesInUse:{minArgs:0,maxArgs:1}},sync:{clear:{minArgs:0,maxArgs:0},get:{minArgs:0,maxArgs:1},getBytesInUse:{minArgs:0,maxArgs:1},remove:{minArgs:1,maxArgs:1},set:{minArgs:1,maxArgs:1}}},tabs:{captureVisibleTab:{minArgs:0,maxArgs:2},create:{minArgs:1,maxArgs:1},detectLanguage:{minArgs:0,maxArgs:1},discard:{minArgs:0,maxArgs:1},duplicate:{minArgs:1,maxArgs:1},executeScript:{minArgs:1,maxArgs:2},get:{minArgs:1,maxArgs:1},getCurrent:{minArgs:0,maxArgs:0},getZoom:{minArgs:0,maxArgs:1},getZoomSettings:{minArgs:0,maxArgs:1},goBack:{minArgs:0,maxArgs:1},goForward:{minArgs:0,maxArgs:1},highlight:{minArgs:1,maxArgs:1},insertCSS:{minArgs:1,maxArgs:2},move:{minArgs:2,maxArgs:2},query:{minArgs:1,maxArgs:1},reload:{minArgs:0,maxArgs:2},remove:{minArgs:1,maxArgs:1},removeCSS:{minArgs:1,maxArgs:2},sendMessage:{minArgs:2,maxArgs:3},setZoom:{minArgs:1,maxArgs:2},setZoomSettings:{minArgs:1,maxArgs:2},update:{minArgs:1,maxArgs:2}},topSites:{get:{minArgs:0,maxArgs:0}},webNavigation:{getAllFrames:{minArgs:1,maxArgs:1},getFrame:{minArgs:1,maxArgs:1}},webRequest:{handlerBehaviorChanged:{minArgs:0,maxArgs:0}},windows:{create:{minArgs:0,maxArgs:1},get:{minArgs:1,maxArgs:2},getAll:{minArgs:0,maxArgs:1},getCurrent:{minArgs:0,maxArgs:1},getLastFocused:{minArgs:0,maxArgs:1},remove:{minArgs:1,maxArgs:1},update:{minArgs:2,maxArgs:2}}};if(0===Object.keys(s).length)throw new Error("api-metadata.json has not been included in browser-polyfill");class t extends WeakMap{constructor(e,r=void 0){super(r),this.createItem=e}get(e){return this.has(e)||this.set(e,this.createItem(e)),super.get(e)}}const a=e=>e&&"object"==typeof e&&"function"==typeof e.then,n=(r,s)=>(...t)=>{e.runtime.lastError?r.reject(new Error(e.runtime.lastError.message)):s.singleCallbackArg||t.length<=1&&!1!==s.singleCallbackArg?r.resolve(t[0]):r.resolve(t)},i=e=>1==e?"argument":"arguments",o=(e,r)=>function(s,...t){if(t.lengthr.maxArgs)throw new Error(`Expected at most ${r.maxArgs} ${i(r.maxArgs)} for ${e}(), got ${t.length}`);return new Promise(((a,i)=>{if(r.fallbackToNoCallback)try{s[e](...t,n({resolve:a,reject:i},r))}catch(n){s[e](...t),r.fallbackToNoCallback=!1,r.noCallback=!0,a()}else r.noCallback?(s[e](...t),a()):s[e](...t,n({resolve:a,reject:i},r))}))},g=(e,r,s)=>new Proxy(r,{apply:(r,t,a)=>s.call(t,e,...a)});let m=Function.call.bind(Object.prototype.hasOwnProperty);const l=(e,r={},s={})=>{let t=Object.create(null),a={has:(r,s)=>s in e||s in t,get(a,n,i){if(n in t)return t[n];if(!(n in e))return;let A=e[n];if("function"==typeof A)if("function"==typeof r[n])A=g(e,e[n],r[n]);else if(m(s,n)){let r=o(n,s[n]);A=g(e,e[n],r)}else A=A.bind(e);else if("object"==typeof A&&null!==A&&(m(r,n)||m(s,n)))A=l(A,r[n],s[n]);else{if(!m(s,"*"))return Object.defineProperty(t,n,{configurable:!0,enumerable:!0,get:()=>e[n],set(r){e[n]=r}}),A;A=l(A,r[n],s["*"])}return t[n]=A,A},set:(r,s,a,n)=>(s in t?t[s]=a:e[s]=a,!0),defineProperty:(e,r,s)=>Reflect.defineProperty(t,r,s),deleteProperty:(e,r)=>Reflect.deleteProperty(t,r)},n=Object.create(e);return new Proxy(n,a)},A=e=>({addListener(r,s,...t){r.addListener(e.get(s),...t)},hasListener:(r,s)=>r.hasListener(e.get(s)),removeListener(r,s){r.removeListener(e.get(s))}}),c=new t((e=>"function"!=typeof e?e:function(r){const s=l(r,{},{getContent:{minArgs:0,maxArgs:0}});e(s)})),u=new t((e=>"function"!=typeof e?e:function(r,s,t){let n,i,o=!1,g=new Promise((e=>{n=function(r){o=!0,e(r)}}));try{i=e(r,s,n)}catch(e){i=Promise.reject(e)}const m=!0!==i&&a(i);if(!0!==i&&!m&&!o)return!1;const l=e=>{e.then((e=>{t(e)}),(e=>{let r;r=e&&(e instanceof Error||"string"==typeof e.message)?e.message:"An unexpected error occurred",t({__mozWebExtensionPolyfillReject__:!0,message:r})})).catch((e=>{}))};return l(m?i:g),!0})),d=({reject:s,resolve:t},a)=>{e.runtime.lastError?e.runtime.lastError.message===r?t():s(new Error(e.runtime.lastError.message)):a&&a.__mozWebExtensionPolyfillReject__?s(new Error(a.message)):t(a)},x=(e,r,s,...t)=>{if(t.lengthr.maxArgs)throw new Error(`Expected at most ${r.maxArgs} ${i(r.maxArgs)} for ${e}(), got ${t.length}`);return new Promise(((e,r)=>{const a=d.bind(null,{resolve:e,reject:r});t.push(a),s.sendMessage(...t)}))},p={devtools:{network:{onRequestFinished:A(c)}},runtime:{onMessage:A(u),onMessageExternal:A(u),sendMessage:x.bind(null,"sendMessage",{minArgs:1,maxArgs:3})},tabs:{sendMessage:x.bind(null,"sendMessage",{minArgs:2,maxArgs:3})}},f={clear:{minArgs:1,maxArgs:1},get:{minArgs:1,maxArgs:1},set:{minArgs:1,maxArgs:1}};return s.privacy={network:{"*":f},services:{"*":f},websites:{"*":f}},l(e,p,s)};e.exports=s(chrome)}else e.exports=globalThis.browser},void 0===(a="function"==typeof s?s.apply(r,t):s)||(e.exports=a)}},r={};function s(t){var a=r[t];if(void 0!==a)return a.exports;var n=r[t]={exports:{}};return e[t].call(n.exports,n,n.exports,s),n.exports}(()=>{"use strict";var e,r=s(6815);!function(e){e.OpenPopup="OPEN_POPUP",e.ClosePopup="CLOSE_POPUP",e.CheckIfFrameExists="CHECK_IF_FRAME_EXISTS"}(e||(e={}));const t=async(e,r)=>{try{let s=`https://extension.ground.news/search?url=${encodeURIComponent(e)}`;r&&(s=`${s}&title=${encodeURIComponent(r)}`);const t=await fetch(s);return await(null==t?void 0:t.json())}catch(e){}},a=new Map;r.storage.local.remove("sessionID");const n=async e=>{const r={status:"processing",timestamp:Date.now()};a.set(e,r);const s=await t(e);return(null==s?void 0:s.event)?(r.status="valid",r.payload=s,r.timestamp=Date.now(),r):(r.status="invalid",r.timestamp=Date.now(),r)};setInterval((()=>{for(const[e,r]of a)Date.now()-r.timestamp>36e5&&a.delete(e)})),r.runtime.onMessageExternal.addListener((async e=>{if("auth"===e.type){if(!e.sessionId)return;if(await r.storage.local.set({sessionID:e.sessionId}),"groundExtension"===e.utmSource){let e=await(async()=>{const e=await r.tabs.query({active:!0,currentWindow:!0});if(0===e.length);else if(!(e.length>1))return e[0].id})();if(!e)return;await r.tabs.remove(e),await r.tabs.reload()}}})),r.tabs.onUpdated.addListener((async(s,t,i)=>{try{if(!i.url)return;let t=a.get(i.url);if(!t){if(!(!!(o=i.url)&&!o.includes("http://")&&!o.includes("newtab")))return t={status:"invalid",timestamp:Date.now()},void a.set(i.url,t);t=await n(i.url)}if("processing"===t.status)return;if(Date.now()-t.timestamp>3e5&&(t=await n(i.url)),"invalid"===t.status)return;const g=t.payload;if(!(null==g?void 0:g.event))return;const m=await(async e=>await r.tabs.sendMessage(e,{action:"CHECK_IF_FRAME_EXISTS"}))(s);if(m)return;await(async(s,t)=>{await r.tabs.sendMessage(s,{action:e.OpenPopup,event:t.event,source:t.source})})(s,{event:g.event,source:g.source});const{sessionID:l}=await r.storage.local.get("sessionID");l&&g.refId&&await(async(e,r)=>{try{await fetch(`https://production.checkitt.news/api/v04/article/trackOpen/${e}`,{headers:{Authorization:r}})}catch(e){}})(g.refId,l),g.event.sourceCount&&await r.action.setBadgeText({text:g.event.sourceCount.toString(),tabId:s})}catch(e){}var o})),r.runtime.onMessage.addListener((async(e,s)=>{var a,n,i,o,g,m,l;if(e.menuStatus){const[e]=await r.tabs.query({active:!0,lastFocusedWindow:!0});if((null===(a=e.url)||void 0===a?void 0:a.includes("reddit.com"))||(null===(n=e.url)||void 0===n?void 0:n.includes("twitter.com"))||(null===(i=e.url)||void 0===i?void 0:i.includes("facebook.com"))||(null===(o=e.url)||void 0===o?void 0:o.includes("linkedin.com"))||(null===(g=e.url)||void 0===g?void 0:g.includes("https://ground.news")))return{url:e.url};if((null===(m=e.url)||void 0===m?void 0:m.includes("newtab"))&&!e.url.includes("http"))return{url:e.url};if(e.url){const r=await fetch(`https://extension.ground.news/search?url=${encodeURIComponent(e.url)}`);return await(null==r?void 0:r.json())}}if(e.sourceHidden&&r.tabs.reload(),"search"===e.action){if(!e.link)return;return await t(e.link)}if("auth"===e.action){if(!e.sessionId)return!1;await r.storage.local.set({sessionID:e.sessionId});const s=await r.tabs.query({active:!0,currentWindow:!0});if(!s.length)return!0;const t=s[0];return!(null==t?void 0:t.id)||(await r.tabs.remove(t.id),await r.tabs.reload(),!0)}if("unauth"===e.action){await r.storage.local.remove("sessionID");const{logoutTabId:e,logoutTriggerTabId:s}=await(async()=>{const{logoutTriggerTabId:e,logoutTabId:s}=await r.storage.local.get(["logoutTriggerTabId","logoutTabId"]);return{logoutTriggerTabId:e,logoutTabId:s}})();return e&&await r.tabs.remove(e),s&&await r.tabs.reload(s),await(async()=>{await r.storage.local.remove(["logoutTriggerTabId","logoutTabId"])})(),!0}if("getPopupData"===e.action){if(!e.storyId)return;return await(async e=>{try{const r=await fetch(`https://production.checkitt.news/api/public/extension/popupData/${e}`);return await(null==r?void 0:r.json())}catch(e){}})(e.storyId)}if("saveArticle"===e.action){if(!e.storyId||!e.sessionId)return;return await(async(e,s)=>{try{return await fetch(`https://production.checkitt.news/api/v04/eventRoom/follow/${e}?source=extension`,{headers:{Authorization:s}}),await r.storage.local.set({isSaved:!0}),!0}catch(e){return!1}})(e.storyId,e.sessionId)}if("unsaveArticle"===e.action){if(!e.storyId||!e.sessionId)return;return await(async(e,s)=>{try{return await fetch(`https://production.checkitt.news/api/v04/eventRoom/unfollow/${e}?source=extension`,{headers:{Authorization:s}}),await r.storage.local.set({isSaved:!1}),!1}catch(e){return!0}})(e.storyId,e.sessionId)}if("getArticleIsSaved"===e.action){if(!e.storyId||!e.sessionId)return;const{isSaved:r}=await(async(e,r)=>{try{const s=await fetch(`https://production.checkitt.news/api/public/extension/articleIsSaved?id=${r}`,{headers:{Authorization:e}});return await(null==s?void 0:s.json())}catch(e){return{isSaved:!1}}})(e.sessionId,e.storyId);return r}if("logout"===e.action){const e=await(async()=>(await r.tabs.create({url:"https://ground.news/account",active:!0})).id)();await(async(e,s)=>{e&&await r.storage.local.set({logoutTriggerTabId:e}),s&&await r.storage.local.set({logoutTabId:s})})(null===(l=s.tab)||void 0===l?void 0:l.id,e)}})),r.runtime.onSuspend.addListener((()=>{a.clear()}))})()})();
\ No newline at end of file
diff --git a/ground-news-ext/src/js/bluesky.css b/ground-news-ext/src/js/bluesky.css
new file mode 100644
index 0000000..9242741
--- /dev/null
+++ b/ground-news-ext/src/js/bluesky.css
@@ -0,0 +1,405 @@
+
+ @font-face {
+ font-family: 'UniversalSans';
+ src: url('moz-extension://__MSG_@@extension_id__/fonts/UniversalSans-800.woff') format('woff'),
+ url('moz-extension://__MSG_@@extension_id__/fonts/UniversalSans-800.ttf') format('ttf');
+ font-weight: 800;
+ }
+ @font-face {
+ font-family: 'UniversalSans';
+ src: url('moz-extension://__MSG_@@extension_id__/fonts/UniversalSans-680.woff') format('woff'),
+ url('moz-extension://__MSG_@@extension_id__/fonts/UniversalSans-680.ttf') format('ttf');
+ font-weight: 680;
+ }
+ @font-face {
+ font-family: 'UniversalSans';
+ src: url('moz-extension://__MSG_@@extension_id__/fonts/UniversalSans-480.woff') format('woff'),
+ url('moz-extension://__MSG_@@extension_id__/fonts/UniversalSans-480.ttf') format('ttf');
+ font-weight: 480;
+ }
+.tw-static {
+ position: static !important;
+}
+.tw-fixed {
+ position: fixed !important;
+}
+.tw-absolute {
+ position: absolute !important;
+}
+.tw-relative {
+ position: relative !important;
+}
+.tw-left-0 {
+ left: 0px !important;
+}
+.tw-z-\[1000000000\] {
+ z-index: 1000000000 !important;
+}
+.tw-z-\[10000\] {
+ z-index: 10000 !important;
+}
+.tw-float-right {
+ float: right !important;
+}
+.tw-my-\[13px\] {
+ margin-top: 13px !important;
+ margin-bottom: 13px !important;
+}
+.tw-mb-0 {
+ margin-bottom: 0px !important;
+}
+.tw-mb-\[20px\] {
+ margin-bottom: 20px !important;
+}
+.tw-box-border {
+ box-sizing: border-box !important;
+}
+.tw-inline-block {
+ display: inline-block !important;
+}
+.tw-flex {
+ display: flex !important;
+}
+.tw-inline-flex {
+ display: inline-flex !important;
+}
+.tw-hidden {
+ display: none !important;
+}
+.tw-h-\[1px\] {
+ height: 1px !important;
+}
+.tw-h-\[20px\] {
+ height: 20px !important;
+}
+.tw-h-\[30px\] {
+ height: 30px !important;
+}
+.tw-h-\[35px\] {
+ height: 35px !important;
+}
+.tw-h-\[54px\] {
+ height: 54px !important;
+}
+.tw-h-full {
+ height: 100% !important;
+}
+.tw-min-h-\[30px\] {
+ min-height: 30px !important;
+}
+.tw-w-\[200px\] {
+ width: 200px !important;
+}
+.tw-w-\[20px\] {
+ width: 20px !important;
+}
+.tw-w-\[30px\] {
+ width: 30px !important;
+}
+.tw-w-\[35px\] {
+ width: 35px !important;
+}
+.tw-w-\[420px\] {
+ width: 420px !important;
+}
+.tw-w-\[450px\] {
+ width: 450px !important;
+}
+.tw-w-full {
+ width: 100% !important;
+}
+.\!tw-max-w-none {
+ max-width: none !important;
+}
+.tw-flex-initial {
+ flex: 0 1 auto !important;
+}
+.tw-flex-none {
+ flex: none !important;
+}
+.tw-flex-shrink-0 {
+ flex-shrink: 0 !important;
+}
+.tw-flex-grow-0 {
+ flex-grow: 0 !important;
+}
+.tw-cursor-pointer {
+ cursor: pointer !important;
+}
+.tw-items-center {
+ align-items: center !important;
+}
+.tw-justify-end {
+ justify-content: flex-end !important;
+}
+.tw-justify-center {
+ justify-content: center !important;
+}
+.tw-justify-between {
+ justify-content: space-between !important;
+}
+.tw-gap-x-3 {
+ -moz-column-gap: 0.75rem !important;
+ column-gap: 0.75rem !important;
+}
+.tw-gap-x-\[10px\] {
+ -moz-column-gap: 10px !important;
+ column-gap: 10px !important;
+}
+.tw-gap-x-\[5px\] {
+ -moz-column-gap: 5px !important;
+ column-gap: 5px !important;
+}
+.tw-overflow-hidden {
+ overflow: hidden !important;
+}
+.tw-truncate {
+ overflow: hidden !important;
+ text-overflow: ellipsis !important;
+ white-space: nowrap !important;
+}
+.tw-whitespace-nowrap {
+ white-space: nowrap !important;
+}
+.tw-rounded {
+ border-radius: 0.25rem !important;
+}
+.tw-rounded-full {
+ border-radius: 9999px !important;
+}
+.tw-rounded-md {
+ border-radius: 0.375rem !important;
+}
+.\!tw-border {
+ border-width: 1px !important;
+}
+.tw-border {
+ border-width: 1px !important;
+}
+.tw-border-0 {
+ border-width: 0px !important;
+}
+.tw-border-solid {
+ border-style: solid !important;
+}
+.\!tw-border-light {
+ --tw-border-opacity: 1 !important;
+ border-color: rgb(238 239 233 / var(--tw-border-opacity)) !important;
+}
+.tw-border-light {
+ --tw-border-opacity: 1 !important;
+ border-color: rgb(238 239 233 / var(--tw-border-opacity)) !important;
+}
+.\!tw-bg-light {
+ --tw-bg-opacity: 1 !important;
+ background-color: rgb(238 239 233 / var(--tw-bg-opacity)) !important;
+}
+.tw-bg-biasBarCenter {
+ --tw-bg-opacity: 1 !important;
+ background-color: rgb(255 255 255 / var(--tw-bg-opacity)) !important;
+}
+.tw-bg-biasBarLeft {
+ --tw-bg-opacity: 1 !important;
+ background-color: rgb(32 73 134 / var(--tw-bg-opacity)) !important;
+}
+.tw-bg-biasBarRight {
+ --tw-bg-opacity: 1 !important;
+ background-color: rgb(128 39 39 / var(--tw-bg-opacity)) !important;
+}
+.tw-bg-center {
+ --tw-bg-opacity: 1 !important;
+ background-color: rgb(255 255 255 / var(--tw-bg-opacity)) !important;
+}
+.tw-bg-dark {
+ --tw-bg-opacity: 1 !important;
+ background-color: rgb(38 38 38 / var(--tw-bg-opacity)) !important;
+}
+.tw-bg-farLeft {
+ --tw-bg-opacity: 1 !important;
+ background-color: rgb(77 109 158 / var(--tw-bg-opacity)) !important;
+}
+.tw-bg-farRight {
+ --tw-bg-opacity: 1 !important;
+ background-color: rgb(153 82 82 / var(--tw-bg-opacity)) !important;
+}
+.tw-bg-focus {
+ --tw-bg-opacity: 1 !important;
+ background-color: rgb(209 189 145 / var(--tw-bg-opacity)) !important;
+}
+.tw-bg-leanLeft {
+ --tw-bg-opacity: 1 !important;
+ background-color: rgb(210 219 231 / var(--tw-bg-opacity)) !important;
+}
+.tw-bg-leanRight {
+ --tw-bg-opacity: 1 !important;
+ background-color: rgb(217 190 190 / var(--tw-bg-opacity)) !important;
+}
+.tw-bg-left {
+ --tw-bg-opacity: 1 !important;
+ background-color: rgb(144 164 195 / var(--tw-bg-opacity)) !important;
+}
+.tw-bg-light {
+ --tw-bg-opacity: 1 !important;
+ background-color: rgb(238 239 233 / var(--tw-bg-opacity)) !important;
+}
+.tw-bg-right {
+ --tw-bg-opacity: 1 !important;
+ background-color: rgb(192 147 147 / var(--tw-bg-opacity)) !important;
+}
+.tw-bg-center {
+ background-position: center !important;
+}
+.tw-bg-left {
+ background-position: left !important;
+}
+.tw-bg-right {
+ background-position: right !important;
+}
+.tw-fill-dark {
+ fill: #262626 !important;
+}
+.tw-fill-light {
+ fill: #EEEFE9 !important;
+}
+.tw-p-\[20px\] {
+ padding: 20px !important;
+}
+.tw-px-2 {
+ padding-left: 0.5rem !important;
+ padding-right: 0.5rem !important;
+}
+.tw-px-\[5px\] {
+ padding-left: 5px !important;
+ padding-right: 5px !important;
+}
+.tw-text-center {
+ text-align: center !important;
+}
+.tw-align-middle {
+ vertical-align: middle !important;
+}
+.tw-font-ground {
+ font-family: UniversalSans !important;
+}
+.tw-text-\[10px\] {
+ font-size: 10px !important;
+}
+.tw-text-\[12px\] {
+ font-size: 12px !important;
+}
+.tw-text-\[15px\] {
+ font-size: 15px !important;
+}
+.tw-text-\[18px\] {
+ font-size: 18px !important;
+}
+.tw-text-\[20px\] {
+ font-size: 20px !important;
+}
+.tw-text-\[30px\] {
+ font-size: 30px !important;
+}
+.tw-font-\[480\] {
+ font-weight: 480 !important;
+}
+.tw-font-\[600\] {
+ font-weight: 600 !important;
+}
+.tw-leading-\[20px\] {
+ line-height: 20px !important;
+}
+.tw-leading-\[25px\] {
+ line-height: 25px !important;
+}
+.tw-leading-\[30px\] {
+ line-height: 30px !important;
+}
+.tw-leading-\[54px\] {
+ line-height: 54px !important;
+}
+.tw-text-center {
+ --tw-text-opacity: 1 !important;
+ color: rgb(255 255 255 / var(--tw-text-opacity)) !important;
+}
+.tw-text-dark {
+ --tw-text-opacity: 1 !important;
+ color: rgb(38 38 38 / var(--tw-text-opacity)) !important;
+}
+.tw-text-light {
+ --tw-text-opacity: 1 !important;
+ color: rgb(238 239 233 / var(--tw-text-opacity)) !important;
+}
+.tw-opacity-0 {
+ opacity: 0 !important;
+}
+.tw-opacity-100 {
+ opacity: 1 !important;
+}
+.tw-transition-\[width\,background-color\,color\] {
+ transition-property: width,background-color,color !important;
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1) !important;
+ transition-duration: 150ms !important;
+}
+.tw-transition-all {
+ transition-property: all !important;
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1) !important;
+ transition-duration: 150ms !important;
+}
+.tw-transition-opacity {
+ transition-property: opacity !important;
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1) !important;
+ transition-duration: 150ms !important;
+}
+.tw-duration-300 {
+ transition-duration: 300ms !important;
+}
+.tw-duration-500 {
+ transition-duration: 500ms !important;
+}
+.tw-ease-in-out {
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1) !important;
+}
+.\[top\:-108px\] {
+ top: -108px !important;
+}
+.\[top\:0px\] {
+ top: 0px !important;
+}
+.\[transition\:top_500ms_cubic-bezier\(0\2c 0\2c 0\2c 1\)\] {
+ transition: top 500ms cubic-bezier(0,0,0,1) !important;
+}
+.hover\:tw-bg-dark:hover {
+ --tw-bg-opacity: 1 !important;
+ background-color: rgb(38 38 38 / var(--tw-bg-opacity)) !important;
+}
+.hover\:tw-bg-light:hover {
+ --tw-bg-opacity: 1 !important;
+ background-color: rgb(238 239 233 / var(--tw-bg-opacity)) !important;
+}
+.hover\:tw-text-dark:hover {
+ --tw-text-opacity: 1 !important;
+ color: rgb(38 38 38 / var(--tw-text-opacity)) !important;
+}
+.hover\:tw-text-light:hover {
+ --tw-text-opacity: 1 !important;
+ color: rgb(238 239 233 / var(--tw-text-opacity)) !important;
+}
+@media (max-width: 1340px) {
+ .max-\[1340px\]\:tw-hidden {
+ display: none !important;
+ }
+}
+@media (min-width: 1280px) {
+ .xl\:tw-inline-block {
+ display: inline-block !important;
+ }
+ .xl\:tw-gap-x-\[10px\] {
+ -moz-column-gap: 10px !important;
+ column-gap: 10px !important;
+ }
+}
+@media (min-width: 1341px) {
+ .min-\[1341px\]\:tw-hidden {
+ display: none !important;
+ }
+}
diff --git a/ground-news-ext/src/js/bluesky.js b/ground-news-ext/src/js/bluesky.js
new file mode 100644
index 0000000..b9f29f8
--- /dev/null
+++ b/ground-news-ext/src/js/bluesky.js
@@ -0,0 +1 @@
+(()=>{var t={6815:function(t,e){var n,r,s;"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self&&self,r=[t],n=function(t){"use strict";if(!globalThis.chrome?.runtime?.id)throw new Error("This script should only be loaded in a browser extension.");if(void 0===globalThis.browser||Object.getPrototypeOf(globalThis.browser)!==Object.prototype){const e="The message port closed before a response was received.",n=t=>{const n={alarms:{clear:{minArgs:0,maxArgs:1},clearAll:{minArgs:0,maxArgs:0},get:{minArgs:0,maxArgs:1},getAll:{minArgs:0,maxArgs:0}},bookmarks:{create:{minArgs:1,maxArgs:1},get:{minArgs:1,maxArgs:1},getChildren:{minArgs:1,maxArgs:1},getRecent:{minArgs:1,maxArgs:1},getSubTree:{minArgs:1,maxArgs:1},getTree:{minArgs:0,maxArgs:0},move:{minArgs:2,maxArgs:2},remove:{minArgs:1,maxArgs:1},removeTree:{minArgs:1,maxArgs:1},search:{minArgs:1,maxArgs:1},update:{minArgs:2,maxArgs:2}},browserAction:{disable:{minArgs:0,maxArgs:1,fallbackToNoCallback:!0},enable:{minArgs:0,maxArgs:1,fallbackToNoCallback:!0},getBadgeBackgroundColor:{minArgs:1,maxArgs:1},getBadgeText:{minArgs:1,maxArgs:1},getPopup:{minArgs:1,maxArgs:1},getTitle:{minArgs:1,maxArgs:1},openPopup:{minArgs:0,maxArgs:0},setBadgeBackgroundColor:{minArgs:1,maxArgs:1,fallbackToNoCallback:!0},setBadgeText:{minArgs:1,maxArgs:1,fallbackToNoCallback:!0},setIcon:{minArgs:1,maxArgs:1},setPopup:{minArgs:1,maxArgs:1,fallbackToNoCallback:!0},setTitle:{minArgs:1,maxArgs:1,fallbackToNoCallback:!0}},browsingData:{remove:{minArgs:2,maxArgs:2},removeCache:{minArgs:1,maxArgs:1},removeCookies:{minArgs:1,maxArgs:1},removeDownloads:{minArgs:1,maxArgs:1},removeFormData:{minArgs:1,maxArgs:1},removeHistory:{minArgs:1,maxArgs:1},removeLocalStorage:{minArgs:1,maxArgs:1},removePasswords:{minArgs:1,maxArgs:1},removePluginData:{minArgs:1,maxArgs:1},settings:{minArgs:0,maxArgs:0}},commands:{getAll:{minArgs:0,maxArgs:0}},contextMenus:{remove:{minArgs:1,maxArgs:1},removeAll:{minArgs:0,maxArgs:0},update:{minArgs:2,maxArgs:2}},cookies:{get:{minArgs:1,maxArgs:1},getAll:{minArgs:1,maxArgs:1},getAllCookieStores:{minArgs:0,maxArgs:0},remove:{minArgs:1,maxArgs:1},set:{minArgs:1,maxArgs:1}},devtools:{inspectedWindow:{eval:{minArgs:1,maxArgs:2,singleCallbackArg:!1}},panels:{create:{minArgs:3,maxArgs:3,singleCallbackArg:!0},elements:{createSidebarPane:{minArgs:1,maxArgs:1}}}},downloads:{cancel:{minArgs:1,maxArgs:1},download:{minArgs:1,maxArgs:1},erase:{minArgs:1,maxArgs:1},getFileIcon:{minArgs:1,maxArgs:2},open:{minArgs:1,maxArgs:1,fallbackToNoCallback:!0},pause:{minArgs:1,maxArgs:1},removeFile:{minArgs:1,maxArgs:1},resume:{minArgs:1,maxArgs:1},search:{minArgs:1,maxArgs:1},show:{minArgs:1,maxArgs:1,fallbackToNoCallback:!0}},extension:{isAllowedFileSchemeAccess:{minArgs:0,maxArgs:0},isAllowedIncognitoAccess:{minArgs:0,maxArgs:0}},history:{addUrl:{minArgs:1,maxArgs:1},deleteAll:{minArgs:0,maxArgs:0},deleteRange:{minArgs:1,maxArgs:1},deleteUrl:{minArgs:1,maxArgs:1},getVisits:{minArgs:1,maxArgs:1},search:{minArgs:1,maxArgs:1}},i18n:{detectLanguage:{minArgs:1,maxArgs:1},getAcceptLanguages:{minArgs:0,maxArgs:0}},identity:{launchWebAuthFlow:{minArgs:1,maxArgs:1}},idle:{queryState:{minArgs:1,maxArgs:1}},management:{get:{minArgs:1,maxArgs:1},getAll:{minArgs:0,maxArgs:0},getSelf:{minArgs:0,maxArgs:0},setEnabled:{minArgs:2,maxArgs:2},uninstallSelf:{minArgs:0,maxArgs:1}},notifications:{clear:{minArgs:1,maxArgs:1},create:{minArgs:1,maxArgs:2},getAll:{minArgs:0,maxArgs:0},getPermissionLevel:{minArgs:0,maxArgs:0},update:{minArgs:2,maxArgs:2}},pageAction:{getPopup:{minArgs:1,maxArgs:1},getTitle:{minArgs:1,maxArgs:1},hide:{minArgs:1,maxArgs:1,fallbackToNoCallback:!0},setIcon:{minArgs:1,maxArgs:1},setPopup:{minArgs:1,maxArgs:1,fallbackToNoCallback:!0},setTitle:{minArgs:1,maxArgs:1,fallbackToNoCallback:!0},show:{minArgs:1,maxArgs:1,fallbackToNoCallback:!0}},permissions:{contains:{minArgs:1,maxArgs:1},getAll:{minArgs:0,maxArgs:0},remove:{minArgs:1,maxArgs:1},request:{minArgs:1,maxArgs:1}},runtime:{getBackgroundPage:{minArgs:0,maxArgs:0},getPlatformInfo:{minArgs:0,maxArgs:0},openOptionsPage:{minArgs:0,maxArgs:0},requestUpdateCheck:{minArgs:0,maxArgs:0},sendMessage:{minArgs:1,maxArgs:3},sendNativeMessage:{minArgs:2,maxArgs:2},setUninstallURL:{minArgs:1,maxArgs:1}},sessions:{getDevices:{minArgs:0,maxArgs:1},getRecentlyClosed:{minArgs:0,maxArgs:1},restore:{minArgs:0,maxArgs:1}},storage:{local:{clear:{minArgs:0,maxArgs:0},get:{minArgs:0,maxArgs:1},getBytesInUse:{minArgs:0,maxArgs:1},remove:{minArgs:1,maxArgs:1},set:{minArgs:1,maxArgs:1}},managed:{get:{minArgs:0,maxArgs:1},getBytesInUse:{minArgs:0,maxArgs:1}},sync:{clear:{minArgs:0,maxArgs:0},get:{minArgs:0,maxArgs:1},getBytesInUse:{minArgs:0,maxArgs:1},remove:{minArgs:1,maxArgs:1},set:{minArgs:1,maxArgs:1}}},tabs:{captureVisibleTab:{minArgs:0,maxArgs:2},create:{minArgs:1,maxArgs:1},detectLanguage:{minArgs:0,maxArgs:1},discard:{minArgs:0,maxArgs:1},duplicate:{minArgs:1,maxArgs:1},executeScript:{minArgs:1,maxArgs:2},get:{minArgs:1,maxArgs:1},getCurrent:{minArgs:0,maxArgs:0},getZoom:{minArgs:0,maxArgs:1},getZoomSettings:{minArgs:0,maxArgs:1},goBack:{minArgs:0,maxArgs:1},goForward:{minArgs:0,maxArgs:1},highlight:{minArgs:1,maxArgs:1},insertCSS:{minArgs:1,maxArgs:2},move:{minArgs:2,maxArgs:2},query:{minArgs:1,maxArgs:1},reload:{minArgs:0,maxArgs:2},remove:{minArgs:1,maxArgs:1},removeCSS:{minArgs:1,maxArgs:2},sendMessage:{minArgs:2,maxArgs:3},setZoom:{minArgs:1,maxArgs:2},setZoomSettings:{minArgs:1,maxArgs:2},update:{minArgs:1,maxArgs:2}},topSites:{get:{minArgs:0,maxArgs:0}},webNavigation:{getAllFrames:{minArgs:1,maxArgs:1},getFrame:{minArgs:1,maxArgs:1}},webRequest:{handlerBehaviorChanged:{minArgs:0,maxArgs:0}},windows:{create:{minArgs:0,maxArgs:1},get:{minArgs:1,maxArgs:2},getAll:{minArgs:0,maxArgs:1},getCurrent:{minArgs:0,maxArgs:1},getLastFocused:{minArgs:0,maxArgs:1},remove:{minArgs:1,maxArgs:1},update:{minArgs:2,maxArgs:2}}};if(0===Object.keys(n).length)throw new Error("api-metadata.json has not been included in browser-polyfill");class r extends WeakMap{constructor(t,e=void 0){super(e),this.createItem=t}get(t){return this.has(t)||this.set(t,this.createItem(t)),super.get(t)}}const s=t=>t&&"object"==typeof t&&"function"==typeof t.then,o=(e,n)=>(...r)=>{t.runtime.lastError?e.reject(new Error(t.runtime.lastError.message)):n.singleCallbackArg||r.length<=1&&!1!==n.singleCallbackArg?e.resolve(r[0]):e.resolve(r)},i=t=>1==t?"argument":"arguments",a=(t,e)=>function(n,...r){if(r.lengthe.maxArgs)throw new Error(`Expected at most ${e.maxArgs} ${i(e.maxArgs)} for ${t}(), got ${r.length}`);return new Promise(((s,i)=>{if(e.fallbackToNoCallback)try{n[t](...r,o({resolve:s,reject:i},e))}catch(o){n[t](...r),e.fallbackToNoCallback=!1,e.noCallback=!0,s()}else e.noCallback?(n[t](...r),s()):n[t](...r,o({resolve:s,reject:i},e))}))},l=(t,e,n)=>new Proxy(e,{apply:(e,r,s)=>n.call(r,t,...s)});let c=Function.call.bind(Object.prototype.hasOwnProperty);const g=(t,e={},n={})=>{let r=Object.create(null),s={has:(e,n)=>n in t||n in r,get(s,o,i){if(o in r)return r[o];if(!(o in t))return;let m=t[o];if("function"==typeof m)if("function"==typeof e[o])m=l(t,t[o],e[o]);else if(c(n,o)){let e=a(o,n[o]);m=l(t,t[o],e)}else m=m.bind(t);else if("object"==typeof m&&null!==m&&(c(e,o)||c(n,o)))m=g(m,e[o],n[o]);else{if(!c(n,"*"))return Object.defineProperty(r,o,{configurable:!0,enumerable:!0,get:()=>t[o],set(e){t[o]=e}}),m;m=g(m,e[o],n["*"])}return r[o]=m,m},set:(e,n,s,o)=>(n in r?r[n]=s:t[n]=s,!0),defineProperty:(t,e,n)=>Reflect.defineProperty(r,e,n),deleteProperty:(t,e)=>Reflect.deleteProperty(r,e)},o=Object.create(t);return new Proxy(o,s)},m=t=>({addListener(e,n,...r){e.addListener(t.get(n),...r)},hasListener:(e,n)=>e.hasListener(t.get(n)),removeListener(e,n){e.removeListener(t.get(n))}}),u=new r((t=>"function"!=typeof t?t:function(e){const n=g(e,{},{getContent:{minArgs:0,maxArgs:0}});t(n)})),d=new r((t=>"function"!=typeof t?t:function(e,n,r){let o,i,a=!1,l=new Promise((t=>{o=function(e){a=!0,t(e)}}));try{i=t(e,n,o)}catch(t){i=Promise.reject(t)}const c=!0!==i&&s(i);if(!0!==i&&!c&&!a)return!1;const g=t=>{t.then((t=>{r(t)}),(t=>{let e;e=t&&(t instanceof Error||"string"==typeof t.message)?t.message:"An unexpected error occurred",r({__mozWebExtensionPolyfillReject__:!0,message:e})})).catch((t=>{}))};return g(c?i:l),!0})),f=({reject:n,resolve:r},s)=>{t.runtime.lastError?t.runtime.lastError.message===e?r():n(new Error(t.runtime.lastError.message)):s&&s.__mozWebExtensionPolyfillReject__?n(new Error(s.message)):r(s)},p=(t,e,n,...r)=>{if(r.lengthe.maxArgs)throw new Error(`Expected at most ${e.maxArgs} ${i(e.maxArgs)} for ${t}(), got ${r.length}`);return new Promise(((t,e)=>{const s=f.bind(null,{resolve:t,reject:e});r.push(s),n.sendMessage(...r)}))},h={devtools:{network:{onRequestFinished:m(u)}},runtime:{onMessage:m(d),onMessageExternal:m(d),sendMessage:p.bind(null,"sendMessage",{minArgs:1,maxArgs:3})},tabs:{sendMessage:p.bind(null,"sendMessage",{minArgs:2,maxArgs:3})}},A={clear:{minArgs:1,maxArgs:1},get:{minArgs:1,maxArgs:1},set:{minArgs:1,maxArgs:1}};return n.privacy={network:{"*":A},services:{"*":A},websites:{"*":A}},g(t,h,n)};t.exports=n(chrome)}else t.exports=globalThis.browser},void 0===(s="function"==typeof n?n.apply(e,r):n)||(t.exports=s)}},e={};function n(r){var s=e[r];if(void 0!==s)return s.exports;var o=e[r]={exports:{}};return t[r].call(o.exports,o,o.exports,n),o.exports}(()=>{"use strict";new Set;const t="undefined"!=typeof window?window:"undefined"!=typeof globalThis?globalThis:global;class e{_listeners="WeakMap"in t?new WeakMap:void 0;_observer=void 0;options;constructor(t){this.options=t}observe(t,e){return this._listeners.set(t,e),this._getObserver().observe(t,this.options),()=>{this._listeners.delete(t),this._observer.unobserve(t)}}_getObserver(){return this._observer??(this._observer=new ResizeObserver((t=>{for(const n of t)e.entries.set(n.target,n),this._listeners.get(n.target)?.(n)})))}}e.entries="WeakMap"in t?new WeakMap:void 0;let r=!1;function s(t,e){t.appendChild(e)}function o(t,e,n){t.insertBefore(e,n||null)}function i(t){t.parentNode&&t.parentNode.removeChild(t)}function a(t){return document.createElement(t)}function l(t){return document.createElementNS("http://www.w3.org/2000/svg",t)}function c(t){return document.createTextNode(t)}function g(){return c(" ")}function m(){return c("")}function u(t,e,n,r){return t.addEventListener(e,n,r),()=>t.removeEventListener(e,n,r)}function d(t,e,n){null==n?t.removeAttribute(e):t.getAttribute(e)!==n&&t.setAttribute(e,n)}function f(t,e){for(const n in e)d(t,n,e[n])}function p(t,e){e=""+e,t.data!==e&&(t.data=e)}function h(t,e,n,r){null==n?t.style.removeProperty(e):t.style.setProperty(e,n,r?"important":"")}function A(t,e){return new t(e)}new Map;function $(){}function x(t,e){for(const n in e)t[n]=e[n];return t}function w(t){return t()}function b(){return Object.create(null)}function y(t){t.forEach(w)}function v(t){return"function"==typeof t}function k(t,e){return t!=t?e==e:t!==e||t&&"object"==typeof t||"function"==typeof t}let E;function C(t,e){return t===e||(E||(E=document.createElement("a")),E.href=e,t===E.href)}function _(t,e,n,r){if(t){const s=M(t,e,n,r);return t[0](s)}}function M(t,e,n,r){return t[1]&&r?x(n.ctx.slice(),t[1](r(e))):n.ctx}function L(t,e,n,r){if(t[2]&&r){const s=t[2](r(n));if(void 0===e.dirty)return s;if("object"==typeof s){const t=[],n=Math.max(e.dirty.length,s.length);for(let r=0;r32){const e=[],n=t.ctx.length/32;for(let t=0;t{X.delete(t),r&&(n&&t.d(1),r())})),t.o(e)}else r&&r()}function nt(t){return void 0!==t?.length?t:Array.from(t)}function rt(t,e){const n={},r={},s={$$scope:1};let o=t.length;for(;o--;){const i=t[o],a=e[o];if(a){for(const t in i)t in a||(r[t]=1);for(const t in a)s[t]||(n[t]=a[t],s[t]=1);t[o]=a}else for(const t in i)s[t]=1}for(const t in r)t in n||(n[t]=void 0);return n}new Set(["allowfullscreen","allowpaymentrequest","async","autofocus","autoplay","checked","controls","default","defer","disabled","formnovalidate","hidden","inert","ismap","loop","multiple","muted","nomodule","novalidate","open","playsinline","readonly","required","reversed","selected"]);let st;function ot(t){t&&t.c()}function it(t,e,n){const{fragment:r,after_update:s}=t.$$;r&&r.m(e,n),q((()=>{const e=t.$$.on_mount.map(w).filter(v);t.$$.on_destroy?t.$$.on_destroy.push(...e):y(e),t.$$.on_mount=[]})),s.forEach(q)}function at(t,e){const n=t.$$;null!==n.fragment&&(!function(t){const e=[],n=[];B.forEach((r=>-1===t.indexOf(r)?e.push(r):n.push(r))),n.forEach((t=>t())),B=e}(n.after_update),y(n.on_destroy),n.fragment&&n.fragment.d(e),n.on_destroy=n.fragment=null,n.ctx=[])}function lt(t,e,n,s,o,a,l=null,c=[-1]){const g=S;P(t);const m=t.$$={fragment:null,ctx:[],props:a,update:$,not_equal:o,bound:b(),on_mount:[],on_destroy:[],on_disconnect:[],before_update:[],after_update:[],context:new Map(e.context||(g?g.$$.context:[])),callbacks:b(),dirty:c,skip_bound:!1,root:e.target||g.$$.root};l&&l(m.root);let u=!1;if(m.ctx=n?n(t,e.props||{},((e,n,...r)=>{const s=r.length?r[0]:n;return m.ctx&&o(m.ctx[e],m.ctx[e]=s)&&(!m.skip_bound&&m.bound[e]&&m.bound[e](s),u&&function(t,e){-1===t.$$.dirty[0]&&(V.push(t),D(),t.$$.dirty.fill(0)),t.$$.dirty[e/31|0]|=1<{let e;return{c:function(){e=a("slot"),"default"!==t&&d(e,"name",t)},m:function(t,n){o(t,e,n)},d:function(t){t&&i(e)}}}}const e={},n=function(t){const e={};return t.childNodes.forEach((t=>{e[t.slot||"default"]=!0})),e}(this);for(const s of this.$$s)s in n&&(e[s]=[t(s)]);for(const l of this.attributes){const c=this.$$g_p(l.name);c in this.$$d||(this.$$d[c]=ct(c,l.value,this.$$p_d,"toProp"))}for(const g in this.$$p_d)g in this.$$d||void 0===this[g]||(this.$$d[g]=this[g],delete this[g]);this.$$c=new this.$$ctor({target:this.shadowRoot||this,props:{...this.$$d,$$slots:e,$$scope:{ctx:[]}}});const r=()=>{this.$$r=!0;for(const t in this.$$p_d)if(this.$$d[t]=this.$$c.$$.ctx[this.$$c.$$.props[t]],this.$$p_d[t].reflect){const e=ct(t,this.$$d[t],this.$$p_d,"toAttribute");null==e?this.removeAttribute(this.$$p_d[t].attribute||t):this.setAttribute(this.$$p_d[t].attribute||t,e)}this.$$r=!1};this.$$c.$$.after_update.push(r),r();for(const m in this.$$l)for(const u of this.$$l[m]){const f=this.$$c.$on(m,u);this.$$l_u.set(u,f)}this.$$l={}}}attributeChangedCallback(t,e,n){this.$$r||(t=this.$$g_p(t),this.$$d[t]=ct(t,n,this.$$p_d,"toProp"),this.$$c?.$set({[t]:this.$$d[t]}))}disconnectedCallback(){this.$$cn=!1,Promise.resolve().then((()=>{this.$$cn||(this.$$c.$destroy(),this.$$c=void 0)}))}$$g_p(t){return Object.keys(this.$$p_d).find((e=>this.$$p_d[e].attribute===t||!this.$$p_d[e].attribute&&e.toLowerCase()===t))||t}});class gt{$$=void 0;$$set=void 0;$destroy(){at(this,1),this.$destroy=$}$on(t,e){if(!v(e))return $;const n=this.$$.callbacks[t]||(this.$$.callbacks[t]=[]);return n.push(e),()=>{const t=n.indexOf(e);-1!==t&&n.splice(t,1)}}$set(t){var e;this.$$set&&(e=t,0!==Object.keys(e).length)&&(this.$$.skip_bound=!0,this.$$set(t),this.$$.skip_bound=!1)}}"undefined"!=typeof window&&(window.__svelte||(window.__svelte={v:new Set})).v.add("4");const mt=t=>t<=10?"":t<=15?t:`${t}%`;function ut(t){let e;return{c(){e=c("")},m(t,n){o(t,e,n)},p:$,d(t){t&&i(e)}}}function dt(t){let e;return{c(){e=l("path"),d(e,"d","M132,32V64a4,4,0,0,1-8,0V32a4,4,0,0,1,8,0Zm92,92H192a4,4,0,0,0,0,8h32a4,4,0,0,0,0-8Zm-47.92,46.43a4,4,0,1,0-5.65,5.65l22.62,22.63a4,4,0,0,0,5.66-5.66ZM128,188a4,4,0,0,0-4,4v32a4,4,0,0,0,8,0V192A4,4,0,0,0,128,188ZM79.92,170.43,57.29,193.05A4,4,0,0,0,63,198.71l22.62-22.63a4,4,0,1,0-5.65-5.65ZM68,128a4,4,0,0,0-4-4H32a4,4,0,0,0,0,8H64A4,4,0,0,0,68,128ZM63,57.29A4,4,0,0,0,57.29,63L79.92,85.57a4,4,0,1,0,5.65-5.65Z")},m(t,n){o(t,e,n)},p:$,d(t){t&&i(e)}}}function ft(t){let e;return{c(){e=l("path"),d(e,"d","M136,32V64a8,8,0,0,1-16,0V32a8,8,0,0,1,16,0Zm88,88H192a8,8,0,0,0,0,16h32a8,8,0,0,0,0-16Zm-45.09,47.6a8,8,0,0,0-11.31,11.31l22.62,22.63a8,8,0,0,0,11.32-11.32ZM128,184a8,8,0,0,0-8,8v32a8,8,0,0,0,16,0V192A8,8,0,0,0,128,184ZM77.09,167.6,54.46,190.22a8,8,0,0,0,11.32,11.32L88.4,178.91A8,8,0,0,0,77.09,167.6ZM72,128a8,8,0,0,0-8-8H32a8,8,0,0,0,0,16H64A8,8,0,0,0,72,128ZM65.78,54.46A8,8,0,0,0,54.46,65.78L77.09,88.4A8,8,0,0,0,88.4,77.09Z")},m(t,n){o(t,e,n)},p:$,d(t){t&&i(e)}}}function pt(t){let e;return{c(){e=l("path"),d(e,"d","M134,32V64a6,6,0,0,1-12,0V32a6,6,0,0,1,12,0Zm90,90H192a6,6,0,0,0,0,12h32a6,6,0,0,0,0-12Zm-46.5,47A6,6,0,0,0,169,177.5l22.63,22.62a6,6,0,0,0,8.48-8.48ZM128,186a6,6,0,0,0-6,6v32a6,6,0,0,0,12,0V192A6,6,0,0,0,128,186ZM78.5,169,55.88,191.64a6,6,0,1,0,8.48,8.48L87,177.5A6,6,0,1,0,78.5,169ZM70,128a6,6,0,0,0-6-6H32a6,6,0,0,0,0,12H64A6,6,0,0,0,70,128ZM64.36,55.88a6,6,0,0,0-8.48,8.48L78.5,87A6,6,0,1,0,87,78.5Z")},m(t,n){o(t,e,n)},p:$,d(t){t&&i(e)}}}function ht(t){let e;return{c(){e=l("path"),d(e,"d","M128,24A104,104,0,1,0,232,128,104.11,104.11,0,0,0,128,24ZM48,136a8,8,0,0,1,0-16H72a8,8,0,0,1,0,16Zm46.06,37.25-17,17a8,8,0,0,1-11.32-11.32l17-17a8,8,0,0,1,11.31,11.31Zm0-79.19a8,8,0,0,1-11.31,0l-17-17A8,8,0,0,1,77.09,65.77l17,17A8,8,0,0,1,94.06,94.06ZM136,208a8,8,0,0,1-16,0V184a8,8,0,0,1,16,0Zm0-136a8,8,0,0,1-16,0V48a8,8,0,0,1,16,0Zm54.23,118.23a8,8,0,0,1-11.32,0l-17-17a8,8,0,0,1,11.31-11.31l17,17A8,8,0,0,1,190.23,190.23ZM208,136H184a8,8,0,0,1,0-16h24a8,8,0,0,1,0,16Z")},m(t,n){o(t,e,n)},p:$,d(t){t&&i(e)}}}function At(t){let e,n;return{c(){e=l("path"),n=l("path"),d(e,"d","M224,128a96,96,0,1,1-96-96A96,96,0,0,1,224,128Z"),d(e,"opacity","0.2"),d(n,"d","M136,32V64a8,8,0,0,1-16,0V32a8,8,0,0,1,16,0Zm88,88H192a8,8,0,0,0,0,16h32a8,8,0,0,0,0-16Zm-45.09,47.6a8,8,0,0,0-11.31,11.31l22.62,22.63a8,8,0,0,0,11.32-11.32ZM128,184a8,8,0,0,0-8,8v32a8,8,0,0,0,16,0V192A8,8,0,0,0,128,184ZM77.09,167.6,54.46,190.22a8,8,0,0,0,11.32,11.32L88.4,178.91A8,8,0,0,0,77.09,167.6ZM72,128a8,8,0,0,0-8-8H32a8,8,0,0,0,0,16H64A8,8,0,0,0,72,128ZM65.78,54.46A8,8,0,0,0,54.46,65.78L77.09,88.4A8,8,0,0,0,88.4,77.09Z")},m(t,r){o(t,e,r),o(t,n,r)},p:$,d(t){t&&(i(e),i(n))}}}function $t(t){let e;return{c(){e=l("path"),d(e,"d","M140,32V64a12,12,0,0,1-24,0V32a12,12,0,0,1,24,0Zm84,84H192a12,12,0,0,0,0,24h32a12,12,0,0,0,0-24Zm-42.26,48.77a12,12,0,1,0-17,17l22.63,22.63a12,12,0,0,0,17-17ZM128,180a12,12,0,0,0-12,12v32a12,12,0,0,0,24,0V192A12,12,0,0,0,128,180ZM74.26,164.77,51.63,187.4a12,12,0,0,0,17,17l22.63-22.63a12,12,0,1,0-17-17ZM76,128a12,12,0,0,0-12-12H32a12,12,0,0,0,0,24H64A12,12,0,0,0,76,128ZM68.6,51.63a12,12,0,1,0-17,17L74.26,91.23a12,12,0,0,0,17-17Z")},m(t,n){o(t,e,n)},p:$,d(t){t&&i(e)}}}function xt(t){let e,n,r,a;const c=t[7].default,g=_(c,t,t[6],null);function m(t,e){return"bold"===t[0]?$t:"duotone"===t[0]?At:"fill"===t[0]?ht:"light"===t[0]?pt:"regular"===t[0]?ft:"thin"===t[0]?dt:ut}let u=m(t),p=u(t),h=[{xmlns:"http://www.w3.org/2000/svg"},{width:t[2]},{height:t[2]},{fill:t[1]},{transform:r=t[3]?"scale(-1, 1)":void 0},{viewBox:"0 0 256 256"},t[4],t[5]],A={};for(let t=0;t{e=x(x({},e),T(t)),n(5,s=F(e,r)),"weight"in t&&n(0,u=t.weight),"color"in t&&n(1,d=t.color),"size"in t&&n(2,f=t.size),"mirrored"in t&&n(3,p=t.mirrored),"$$scope"in t&&n(6,i=t.$$scope)},[u,d,f,p,m,s,i,o]}const bt=class extends gt{constructor(t){super(),lt(this,t,wt,xt,k,{weight:0,color:1,size:2,mirrored:3})}};function yt(t){let e,n,r,l,m,f,h,A=t[1]&&kt(t);return{c(){e=a("button"),A&&A.c(),n=g(),r=c(t[0]),d(e,"class",l=`\n ${t[4]}\n !tw-border\n tw-border-solid\n !tw-border-light\n tw-h-[30px]\n tw-rounded\n tw-text-[15px]\n tw-leading-[30px]\n tw-font-[480]\n tw-px-2\n tw-transition-all\n tw-duration-500\n tw-inline-flex\n tw-items-center\n tw-font-ground\n tw-cursor-pointer\n `)},m(i,a){o(i,e,a),A&&A.m(e,null),s(e,n),s(e,r),m=!0,f||(h=[u(e,"mouseenter",t[6]),u(e,"mouseleave",t[6]),u(e,"click",(function(){v(t[2])&&t[2].apply(this,arguments)}))],f=!0)},p(s,o){(t=s)[1]?A?(A.p(t,o),2&o&&tt(A,1)):(A=kt(t),A.c(),tt(A,1),A.m(e,n)):A&&(Q(),et(A,1,1,(()=>{A=null})),Y()),(!m||1&o)&&p(r,t[0]),(!m||16&o&&l!==(l=`\n ${t[4]}\n !tw-border\n tw-border-solid\n !tw-border-light\n tw-h-[30px]\n tw-rounded\n tw-text-[15px]\n tw-leading-[30px]\n tw-font-[480]\n tw-px-2\n tw-transition-all\n tw-duration-500\n tw-inline-flex\n tw-items-center\n tw-font-ground\n tw-cursor-pointer\n `))&&d(e,"class",l)},i(t){m||(tt(A),m=!0)},o(t){et(A),m=!1},d(t){t&&i(e),A&&A.d(),f=!1,y(h)}}}function vt(t){let e,n;return e=new bt({props:{weight:"regular",color:"#EEEFE9",size:"20px",class:"\n tw-inline-block\n tw-text-[18px]\n ",$$slots:{default:[Et]},$$scope:{ctx:t}}}),{c(){ot(e.$$.fragment)},m(t,r){it(e,t,r),n=!0},p(t,n){const r={};512&n&&(r.$$scope={dirty:n,ctx:t}),e.$set(r)},i(t){n||(tt(e.$$.fragment,t),n=!0)},o(t){et(e.$$.fragment,t),n=!1},d(t){at(e,t)}}}function kt(t){let e,n,r;var s=t[1];function a(t,e){return{props:{weight:"regular",class:`\n tw-inline-block\n tw-text-[18px]\n tw-w-[20px]\n ${t[5]}\n `}}}return s&&(e=A(s,a(t))),{c(){e&&ot(e.$$.fragment),n=m()},m(t,s){e&&it(e,t,s),o(t,n,s),r=!0},p(t,r){if(2&r&&s!==(s=t[1])){if(e){Q();const t=e;et(t.$$.fragment,1,0,(()=>{at(t,1)})),Y()}s?(e=A(s,a(t)),ot(e.$$.fragment),tt(e.$$.fragment,1),it(e,n.parentNode,n)):e=null}else if(s){const n={};32&r&&(n.class=`\n tw-inline-block\n tw-text-[18px]\n tw-w-[20px]\n ${t[5]}\n `),e.$set(n)}},i(t){r||(e&&tt(e.$$.fragment,t),r=!0)},o(t){e&&et(e.$$.fragment,t),r=!1},d(t){t&&i(n),e&&at(e,t)}}}function Et(t){let e;return{c(){e=l("animateTransform"),d(e,"attributeName","transform"),d(e,"attributeType","XML"),d(e,"type","rotate"),d(e,"dur","5s"),d(e,"from","0 0 0"),d(e,"to","360 0 0"),d(e,"repeatCount","indefnite")},m(t,n){o(t,e,n)},p:$,d(t){t&&i(e)}}}function Ct(t){let e,n,r,s;const a=[vt,yt],l=[];function c(t,e){return t[3]?0:1}return e=c(t),n=l[e]=a[e](t),{c(){n.c(),r=m()},m(t,n){l[e].m(t,n),o(t,r,n),s=!0},p(t,[s]){let o=e;e=c(t),e===o?l[e].p(t,s):(Q(),et(l[o],1,1,(()=>{l[o]=null})),Y(),n=l[e],n?n.p(t,s):(n=l[e]=a[e](t),n.c()),tt(n,1),n.m(r.parentNode,r))},i(t){s||(tt(n),s=!0)},o(t){et(n),s=!1},d(t){t&&i(r),l[e].d(t)}}}function _t(t,e,n){let r,{text:s}=e,{Icon:o=null}=e,{onClick:i=(()=>{})}=e,{theme:a="dark"}=e,{isLoading:l=!1}=e,c="tw-text-light tw-bg-dark hover:tw-bg-light hover:tw-text-dark";"light"===a?c="tw-text-dark !tw-bg-light hover:tw-bg-dark hover:tw-text-light":"focus"===a&&(c="tw-text-dark tw-bg-focus hover:tw-bg-light hover:tw-text-dark tw-border-0");let g=!1;return t.$$set=t=>{"text"in t&&n(0,s=t.text),"Icon"in t&&n(1,o=t.Icon),"onClick"in t&&n(2,i=t.onClick),"theme"in t&&n(7,a=t.theme),"isLoading"in t&&n(3,l=t.isLoading)},t.$$.update=()=>{384&t.$$.dirty&&n(5,r="dark"===a?g?"tw-fill-dark":"tw-fill-light":g?"tw-fill-light":"tw-fill-dark")},[s,o,i,l,c,r,()=>n(8,g=!g),a,g]}const Mt=class extends gt{constructor(t){super(),lt(this,t,_t,Ct,k,{text:0,Icon:1,onClick:2,theme:7,isLoading:3})}};function Lt(t){let e,n;return e=new Mt({props:{text:t[0],Icon:t[1],theme:t[5],onClick:t[4],isLoading:t[6]}}),{c(){ot(e.$$.fragment)},m(t,r){it(e,t,r),n=!0},p(t,n){const r={};1&n&&(r.text=t[0]),2&n&&(r.Icon=t[1]),32&n&&(r.theme=t[5]),16&n&&(r.onClick=t[4]),64&n&&(r.isLoading=t[6]),e.$set(r)},i(t){n||(tt(e.$$.fragment,t),n=!0)},o(t){et(e.$$.fragment,t),n=!1},d(t){at(e,t)}}}function Zt(t){let e,n,r,s;return n=new Mt({props:{text:t[0],Icon:t[1],theme:t[5],onClick:t[4]}}),{c(){e=a("a"),ot(n.$$.fragment),d(e,"href",t[2]),d(e,"target",r=t[3]?"_blank":"_self"),h(e,"text-decoration","none"),h(e,"display","inherit")},m(t,r){o(t,e,r),it(n,e,null),s=!0},p(t,o){const i={};1&o&&(i.text=t[0]),2&o&&(i.Icon=t[1]),32&o&&(i.theme=t[5]),16&o&&(i.onClick=t[4]),n.$set(i),(!s||4&o)&&d(e,"href",t[2]),(!s||8&o&&r!==(r=t[3]?"_blank":"_self"))&&d(e,"target",r)},i(t){s||(tt(n.$$.fragment,t),s=!0)},o(t){et(n.$$.fragment,t),s=!1},d(t){t&&i(e),at(n)}}}function It(t){let e,n,r,s;const a=[Zt,Lt],l=[];function c(t,e){return t[2]?0:1}return e=c(t),n=l[e]=a[e](t),{c(){n.c(),r=m()},m(t,n){l[e].m(t,n),o(t,r,n),s=!0},p(t,[s]){let o=e;e=c(t),e===o?l[e].p(t,s):(Q(),et(l[o],1,1,(()=>{l[o]=null})),Y(),n=l[e],n?n.p(t,s):(n=l[e]=a[e](t),n.c()),tt(n,1),n.m(r.parentNode,r))},i(t){s||(tt(n),s=!0)},o(t){et(n),s=!1},d(t){t&&i(r),l[e].d(t)}}}function Tt(t,e,n){let{text:r}=e,{Icon:s=null}=e,{href:o=""}=e,{openInNewTab:i=!1}=e,{onClick:a=(()=>{})}=e,{theme:l="dark"}=e,{isLoading:c=!1}=e;return t.$$set=t=>{"text"in t&&n(0,r=t.text),"Icon"in t&&n(1,s=t.Icon),"href"in t&&n(2,o=t.href),"openInNewTab"in t&&n(3,i=t.openInNewTab),"onClick"in t&&n(4,a=t.onClick),"theme"in t&&n(5,l=t.theme),"isLoading"in t&&n(6,c=t.isLoading)},[r,s,o,i,a,l,c]}const Ft=class extends gt{constructor(t){super(),lt(this,t,Tt,It,k,{text:0,Icon:1,href:2,openInNewTab:3,onClick:4,theme:5,isLoading:6})}};function St(t){let e;return{c(){e=c("")},m(t,n){o(t,e,n)},p:$,d(t){t&&i(e)}}}function Pt(t){let e;return{c(){e=l("path"),d(e,"d","M184,36H72A12,12,0,0,0,60,48V224a4,4,0,0,0,6.12,3.39L128,188.72l61.89,38.67A4,4,0,0,0,192,228a4.06,4.06,0,0,0,1.94-.5A4,4,0,0,0,196,224V48A12,12,0,0,0,184,36Zm4,180.78-57.89-36.17a4,4,0,0,0-4.24,0L68,216.78V48a4,4,0,0,1,4-4H184a4,4,0,0,1,4,4Z")},m(t,n){o(t,e,n)},p:$,d(t){t&&i(e)}}}function Nt(t){let e;return{c(){e=l("path"),d(e,"d","M184,32H72A16,16,0,0,0,56,48V224a8,8,0,0,0,12.24,6.78L128,193.43l59.77,37.35A8,8,0,0,0,200,224V48A16,16,0,0,0,184,32Zm0,177.57-51.77-32.35a8,8,0,0,0-8.48,0L72,209.57V48H184Z")},m(t,n){o(t,e,n)},p:$,d(t){t&&i(e)}}}function Ht(t){let e;return{c(){e=l("path"),d(e,"d","M184,34H72A14,14,0,0,0,58,48V224a6,6,0,0,0,9.18,5.09l60.81-38,60.83,38A6,6,0,0,0,198,224V48A14,14,0,0,0,184,34Zm2,179.17-54.83-34.26a6,6,0,0,0-6.36,0L70,213.17V48a2,2,0,0,1,2-2H184a2,2,0,0,1,2,2Z")},m(t,n){o(t,e,n)},p:$,d(t){t&&i(e)}}}function Vt(t){let e;return{c(){e=l("path"),d(e,"d","M184,32H72A16,16,0,0,0,56,48V224a8,8,0,0,0,12.24,6.78L128,193.43l59.77,37.35A8,8,0,0,0,200,224V48A16,16,0,0,0,184,32Z")},m(t,n){o(t,e,n)},p:$,d(t){t&&i(e)}}}function jt(t){let e,n;return{c(){e=l("path"),n=l("path"),d(e,"d","M192,48V224l-64-40L64,224V48a8,8,0,0,1,8-8H184A8,8,0,0,1,192,48Z"),d(e,"opacity","0.2"),d(n,"d","M184,32H72A16,16,0,0,0,56,48V224a8,8,0,0,0,12.24,6.78L128,193.43l59.77,37.35A8,8,0,0,0,200,224V48A16,16,0,0,0,184,32Zm0,177.57-51.77-32.35a8,8,0,0,0-8.48,0L72,209.57V48H184Z")},m(t,r){o(t,e,r),o(t,n,r)},p:$,d(t){t&&(i(e),i(n))}}}function Bt(t){let e;return{c(){e=l("path"),d(e,"d","M184,28H72A20,20,0,0,0,52,48V224a12,12,0,0,0,18.36,10.18l57.63-36,57.65,36A12,12,0,0,0,204,224V48A20,20,0,0,0,184,28Zm-4,174.35-45.65-28.53a12,12,0,0,0-12.72,0L76,202.35V52H180Z")},m(t,n){o(t,e,n)},p:$,d(t){t&&i(e)}}}function Ot(t){let e,n,r,a;const c=t[7].default,g=_(c,t,t[6],null);function m(t,e){return"bold"===t[0]?Bt:"duotone"===t[0]?jt:"fill"===t[0]?Vt:"light"===t[0]?Ht:"regular"===t[0]?Nt:"thin"===t[0]?Pt:St}let u=m(t),p=u(t),h=[{xmlns:"http://www.w3.org/2000/svg"},{width:t[2]},{height:t[2]},{fill:t[1]},{transform:r=t[3]?"scale(-1, 1)":void 0},{viewBox:"0 0 256 256"},t[4],t[5]],A={};for(let t=0;t{e=x(x({},e),T(t)),n(5,s=F(e,r)),"weight"in t&&n(0,u=t.weight),"color"in t&&n(1,d=t.color),"size"in t&&n(2,f=t.size),"mirrored"in t&&n(3,p=t.mirrored),"$$scope"in t&&n(6,i=t.$$scope)},[u,d,f,p,m,s,i,o]}const Rt=class extends gt{constructor(t){super(),lt(this,t,zt,Ot,k,{weight:0,color:1,size:2,mirrored:3})}};var Dt=n(6815);function qt(t,e,n){const r=t.slice();return r[14]=e[n],r}function Wt(t){let e,n;return e=new bt({props:{weight:"regular",color:["Twitter","Bluesky"].includes(t[1])?"#EEEFE9":"#262626",size:"30px",class:"\n tw-inline-block\n tw-text-[30px]\n tw-float-right\n ",$$slots:{default:[Gt]},$$scope:{ctx:t}}}),{c(){ot(e.$$.fragment)},m(t,r){it(e,t,r),n=!0},p(t,n){const r={};2&n&&(r.color=["Twitter","Bluesky"].includes(t[1])?"#EEEFE9":"#262626"),131072&n&&(r.$$scope={dirty:n,ctx:t}),e.$set(r)},i(t){n||(tt(e.$$.fragment,t),n=!0)},o(t){et(e.$$.fragment,t),n=!1},d(t){at(e,t)}}}function Ut(t){let e,n,r,l,m,u,f,A,$,x,w,b,y,v,k,E,C,_,M,L,Z,I,T,F,S,P,N,H,V,j,B,O=t[2].title+"",z=t[2].sourceCount+"",R=t[2].biasSourceCount+"",D=t[2].left&&Jt(t),q=t[2].center&&Xt(t),W=t[2].right&&Kt(t),U=t[6].length&&Qt(t);const G=[re,ne,ee],J=[];function X(t,e){return t[8]?t[3]?2:1:0}return F=X(t),S=J[F]=G[F](t),N=new Ft({props:{text:"Full Coverage",href:`https://ground.news/article/${t[2].slug??t[0]}`,theme:"focus",onClick:t[12]}}),{c(){e=a("div"),n=a("p"),r=c(O),l=g(),m=a("hr"),u=g(),f=a("p"),A=c("This story has been covered by "),$=c(z),x=c(" news sources including "),w=c(R),b=c(" with bias reviews."),y=g(),v=a("span"),k=a("span"),D&&D.c(),E=g(),q&&q.c(),C=g(),W&&W.c(),_=g(),M=a("p"),M.textContent="Sources",L=g(),Z=a("div"),U&&U.c(),I=g(),T=a("div"),S.c(),P=g(),ot(N.$$.fragment),H=g(),V=a("div"),V.innerHTML='Powered by
',d(n,"class","tw-text-light tw-text-[20px] tw-font-[600] tw-mb-[20px]"),d(m,"class","tw-h-[1px] tw-bg-light tw-w-full tw-mb-[20px]"),d(f,"class","tw-text-light tw-text-[15px] tw-text-center tw-mb-[20px]"),d(k,"class","tw-w-full tw-h-[30px] tw-inline-flex tw-text-center tw-mb-[20px]"),h(k,"line-height","30px"),d(M,"class","tw-text-light tw-text-[15px] tw-mb-[20px]"),d(T,"class","tw-flex tw-gap-x-3 tw-justify-end"),d(Z,"class","tw-flex tw-justify-between tw-mb-[20px]"),d(V,"class","tw-flex tw-justify-center tw-gap-x-[10px]"),d(e,"class",j=`tw-bg-dark tw-rounded tw-p-[20px] tw-w-[450px] ${t[9]} tw-z-[10000] tw-font-ground tw-box-border`)},m(t,i){o(t,e,i),s(e,n),s(n,r),s(e,l),s(e,m),s(e,u),s(e,f),s(f,A),s(f,$),s(f,x),s(f,w),s(f,b),s(e,y),s(e,v),s(v,k),D&&D.m(k,null),s(k,E),q&&q.m(k,null),s(k,C),W&&W.m(k,null),s(e,_),s(e,M),s(e,L),s(e,Z),U&&U.m(Z,null),s(Z,I),s(Z,T),J[F].m(T,null),s(T,P),it(N,T,null),s(e,H),s(e,V),B=!0},p(t,e){(!B||4&e)&&O!==(O=t[2].title+"")&&p(r,O),(!B||4&e)&&z!==(z=t[2].sourceCount+"")&&p($,z),(!B||4&e)&&R!==(R=t[2].biasSourceCount+"")&&p(w,R),t[2].left?D?D.p(t,e):(D=Jt(t),D.c(),D.m(k,E)):D&&(D.d(1),D=null),t[2].center?q?q.p(t,e):(q=Xt(t),q.c(),q.m(k,C)):q&&(q.d(1),q=null),t[2].right?W?W.p(t,e):(W=Kt(t),W.c(),W.m(k,null)):W&&(W.d(1),W=null),t[6].length?U?U.p(t,e):(U=Qt(t),U.c(),U.m(Z,I)):U&&(U.d(1),U=null);let n=F;F=X(t),F===n?J[F].p(t,e):(Q(),et(J[n],1,1,(()=>{J[n]=null})),Y(),S=J[F],S?S.p(t,e):(S=J[F]=G[F](t),S.c()),tt(S,1),S.m(T,P));const s={};5&e&&(s.href=`https://ground.news/article/${t[2].slug??t[0]}`),N.$set(s)},i(t){B||(tt(S),tt(N.$$.fragment,t),B=!0)},o(t){et(S),et(N.$$.fragment,t),B=!1},d(t){t&&i(e),D&&D.d(),q&&q.d(),W&&W.d(),U&&U.d(),J[F].d(),at(N)}}}function Gt(t){let e;return{c(){e=l("animateTransform"),d(e,"attributeName","transform"),d(e,"attributeType","XML"),d(e,"type","rotate"),d(e,"dur","5s"),d(e,"from","0 0 0"),d(e,"to","360 0 0"),d(e,"repeatCount","indefnite")},m(t,n){o(t,e,n)},p:$,d(t){t&&i(e)}}}function Jt(t){let e,n,r,l=mt(t[2].left)+"";return{c(){e=a("span"),n=c(l),d(e,"style",r=`width: ${t[2].left}%; background-color: #204986; color: #EEEFE9`),d(e,"class","tw-h-[30px] tw-truncate")},m(t,r){o(t,e,r),s(e,n)},p(t,s){4&s&&l!==(l=mt(t[2].left)+"")&&p(n,l),4&s&&r!==(r=`width: ${t[2].left}%; background-color: #204986; color: #EEEFE9`)&&d(e,"style",r)},d(t){t&&i(e)}}}function Xt(t){let e,n,r,l=mt(t[2].center)+"";return{c(){e=a("span"),n=c(l),d(e,"style",r=`width: ${t[2].center}%; background-color: #FFFFFF; color: #262626;`),d(e,"class","tw-h-[30px] tw-truncate")},m(t,r){o(t,e,r),s(e,n)},p(t,s){4&s&&l!==(l=mt(t[2].center)+"")&&p(n,l),4&s&&r!==(r=`width: ${t[2].center}%; background-color: #FFFFFF; color: #262626;`)&&d(e,"style",r)},d(t){t&&i(e)}}}function Kt(t){let e,n,r,l=mt(t[2].right)+"";return{c(){e=a("span"),n=c(l),d(e,"style",r=`width: ${t[2].right}%; background-color: #802727; color: #EEEFE9`),d(e,"class","tw-h-[30px] tw-truncate")},m(t,r){o(t,e,r),s(e,n)},p(t,s){4&s&&l!==(l=mt(t[2].right)+"")&&p(n,l),4&s&&r!==(r=`width: ${t[2].right}%; background-color: #802727; color: #EEEFE9`)&&d(e,"style",r)},d(t){t&&i(e)}}}function Qt(t){let e,n,r,l=nt(t[6].slice(0,4)),c=[];for(let e=0;e4&&te(t);return{c(){e=a("div"),n=a("span");for(let t=0;t4?m?m.p(t,e):(m=te(t),m.c(),m.m(n,null)):m&&(m.d(1),m=null)},d(t){t&&i(e),function(t,e){for(let n=0;n{l[o]=null})),Y(),n=l[e],n?n.p(t,s):(n=l[e]=a[e](t),n.c()),tt(n,1),n.m(r.parentNode,r))},i(t){s||(tt(n),s=!0)},o(t){et(n),s=!1},d(t){t&&i(r),l[e].d(t)}}}function oe(t,e,n){let{storyId:r=""}=e,{sm:s="Reddit"}=e,o={id:"",left:0,center:0,right:0,biasSourceCount:0,sourceCount:0,title:""},i=!1,a=!1,l=!1,c=[],g=!1,m="";const u=["Twitter","Facebook"].includes(s)?"tw-static":"tw-absolute";var d;d=async()=>{n(7,g=!0);const t=await(async t=>await Dt.runtime.sendMessage({action:"getPopupData",storyId:t}))(r);if(!t)return;const e=await Dt.storage.local.get("sessionID");n(8,m=e.sessionID),n(2,o=t.story),m&&n(3,i=await(async(t,e)=>await Dt.runtime.sendMessage({action:"getArticleIsSaved",storyId:e,sessionId:t}))(m,o.id)),n(6,c=t.topSources),n(7,g=!1)},N().$$.on_mount.push(d);return t.$$set=t=>{"storyId"in t&&n(0,r=t.storyId),"sm"in t&&n(1,s=t.sm)},[r,s,o,i,a,l,c,g,m,u,async t=>{n(4,a=!0),n(3,i=await(async(t,e)=>await Dt.runtime.sendMessage({action:"saveArticle",storyId:e,sessionId:t}))(m,o.id)),n(4,a=!1)},async t=>{n(5,l=!0),n(3,i=await(async(t,e)=>await Dt.runtime.sendMessage({action:"unsaveArticle",storyId:e,sessionId:t}))(m,o.id)),n(5,l=!1)},t=>{t.stopPropagation()},t=>{t.stopPropagation()}]}const ie=class extends gt{constructor(t){super(),lt(this,t,oe,se,k,{storyId:0,sm:1})}};function ae(t){let e,n,r,l,m,u,f,A,$,x,w,b=t[1]&&le(t),y=t[2]&&ce(t),v=t[3]&&ge(t);return{c(){e=a("div"),n=a("img"),l=g(),m=a("span"),u=c(t[0]),f=g(),A=a("span"),$=a("span"),b&&b.c(),x=g(),y&&y.c(),w=g(),v&&v.c(),C(n.src,r="https://groundnews.b-cdn.net/assets/logo/Ground%20G%20Monogram.svg")||d(n,"src","https://groundnews.b-cdn.net/assets/logo/Ground%20G%20Monogram.svg"),d(n,"alt","Ground Monogram Logo"),d(n,"class","tw-w-[30px] tw-h-[30px] tw-flex-initial"),d(m,"class","tw-h-[30px] tw-inline-block tw-box-border tw-text-[15px] tw-text-dark tw-rounded tw-text-center "),h(m,"background-color","#D1BD91"),h(m,"color","#262626"),h(m,"border-radius","4px"),h(m,"width","30px"),h(m,"line-height","30px"),d($,"class","tw-w-[200px] tw-h-[30px] tw-inline-flex tw-text-center"),h($,"line-height","30px"),d(e,"class","tw-flex tw-justify-end tw-h-[30px]"),h(e,"column-gap","5px")},m(t,r){o(t,e,r),s(e,n),s(e,l),s(e,m),s(m,u),s(e,f),s(e,A),s(A,$),b&&b.m($,null),s($,x),y&&y.m($,null),s($,w),v&&v.m($,null)},p(t,e){1&e&&p(u,t[0]),t[1]?b?b.p(t,e):(b=le(t),b.c(),b.m($,x)):b&&(b.d(1),b=null),t[2]?y?y.p(t,e):(y=ce(t),y.c(),y.m($,w)):y&&(y.d(1),y=null),t[3]?v?v.p(t,e):(v=ge(t),v.c(),v.m($,null)):v&&(v.d(1),v=null)},d(t){t&&i(e),b&&b.d(),y&&y.d(),v&&v.d()}}}function le(t){let e,n,r,l=mt(t[1])+"";return{c(){e=a("span"),n=c(l),d(e,"style",r=`width: ${t[1]}%; background-color: #204986; color: #EEEFE9`),d(e,"class","h-[30px] truncate")},m(t,r){o(t,e,r),s(e,n)},p(t,s){2&s&&l!==(l=mt(t[1])+"")&&p(n,l),2&s&&r!==(r=`width: ${t[1]}%; background-color: #204986; color: #EEEFE9`)&&d(e,"style",r)},d(t){t&&i(e)}}}function ce(t){let e,n,r,l=mt(t[2])+"";return{c(){e=a("span"),n=c(l),d(e,"style",r=`width: ${t[2]}%; background-color: #FFFFFF; color: #262626;`),d(e,"class","h-[30px] truncate")},m(t,r){o(t,e,r),s(e,n)},p(t,s){4&s&&l!==(l=mt(t[2])+"")&&p(n,l),4&s&&r!==(r=`width: ${t[2]}%; background-color: #FFFFFF; color: #262626;`)&&d(e,"style",r)},d(t){t&&i(e)}}}function ge(t){let e,n,r,l=mt(t[3])+"";return{c(){e=a("span"),n=c(l),d(e,"style",r=`width: ${t[3]}%; background-color: #802727; color: #EEEFE9`),d(e,"class","h-[30px] truncate")},m(t,r){o(t,e,r),s(e,n)},p(t,s){8&s&&l!==(l=mt(t[3])+"")&&p(n,l),8&s&&r!==(r=`width: ${t[3]}%; background-color: #802727; color: #EEEFE9`)&&d(e,"style",r)},d(t){t&&i(e)}}}function me(t){let e,n;return e=new ie({props:{storyId:t[4],sm:t[5]}}),{c(){ot(e.$$.fragment)},m(t,r){it(e,t,r),n=!0},p(t,n){const r={};16&n&&(r.storyId=t[4]),32&n&&(r.sm=t[5]),e.$set(r)},i(t){n||(tt(e.$$.fragment,t),n=!0)},o(t){et(e.$$.fragment,t),n=!1},d(t){at(e,t)}}}function ue(t){let e,n,r,l,c,m=(!t[6]||"LinkedInHome"===t[5])&&ae(t),f=t[6]&&"LinkedInHome"!==t[5]&&me(t);return{c(){e=a("div"),m&&m.c(),n=g(),f&&f.c(),d(e,"role","button"),d(e,"tabindex","0"),d(e,"class","tw-w-[450px] tw-min-h-[30px] tw-relative tw-font-ground")},m(i,a){o(i,e,a),m&&m.m(e,null),s(e,n),f&&f.m(e,null),r=!0,l||(c=[u(e,"mouseenter",t[7]),u(e,"mouseleave",t[8])],l=!0)},p(t,[r]){t[6]&&"LinkedInHome"!==t[5]?m&&(m.d(1),m=null):m?m.p(t,r):(m=ae(t),m.c(),m.m(e,n)),t[6]&&"LinkedInHome"!==t[5]?f?(f.p(t,r),96&r&&tt(f,1)):(f=me(t),f.c(),tt(f,1),f.m(e,null)):f&&(Q(),et(f,1,1,(()=>{f=null})),Y())},i(t){r||(tt(f),r=!0)},o(t){et(f),r=!1},d(t){t&&i(e),m&&m.d(),f&&f.d(),l=!1,y(c)}}}function de(t,e,n){let{numSources:r=0}=e,{left:s=0}=e,{center:o=0}=e,{right:i=0}=e,{storyId:a=""}=e,{sm:l="Reddit"}=e,c=!1;return t.$$set=t=>{"numSources"in t&&n(0,r=t.numSources),"left"in t&&n(1,s=t.left),"center"in t&&n(2,o=t.center),"right"in t&&n(3,i=t.right),"storyId"in t&&n(4,a=t.storyId),"sm"in t&&n(5,l=t.sm)},[r,s,o,i,a,l,c,()=>n(6,c=!0),()=>n(6,c=!1)]}const fe=class extends gt{constructor(t){super(),lt(this,t,de,ue,k,{numSources:0,left:1,center:2,right:3,storyId:4,sm:5})}},pe=new Map,he=async(t,e)=>{var n;if(!e)return;const r=null===(n=t.parentElement)||void 0===n?void 0:n.lastChild;if(!r)return;const s=`article-${e.id}`;if(t.querySelector(`#${s}`))return;const o=document.createElement("div");o.id=s,o.style.display="flex",o.style.justifyContent="flex-end",o.style.marginTop="12px",r.after(o),new fe({target:o,props:{numSources:e.sourceCount,left:e.blindspotData.leftPercent,center:e.blindspotData.centerPercent,right:e.blindspotData.rightPercent,storyId:e.id,sm:"Twitter"}})},Ae=async t=>{const e=t.href;if(!e||e.includes("https://bsky.app"))return;if(pe.has(e))return pe.get(e);pe.set(e,{});const n=await(async t=>await Dt.runtime.sendMessage({action:"search",link:t}))(e);return pe.set(e,null!=n?n:{}),n},$e=async t=>{const e=t.querySelectorAll("a");for(const n of e){const e=await Ae(n);if(null==e?void 0:e.event)return void he(t,e.event)}};new MutationObserver((async(t,e)=>{for(const e of t)if(e.addedNodes.length)for(const t of e.addedNodes){if(1!==t.nodeType)continue;const e=t.querySelectorAll('[data-testid="contentHider-post"]');if(e.length)for(const t of e)await $e(t)}})).observe(document,{subtree:!0,childList:!0})})()})();
\ No newline at end of file
diff --git a/ground-news-ext/src/js/content_script.css b/ground-news-ext/src/js/content_script.css
new file mode 100644
index 0000000..9242741
--- /dev/null
+++ b/ground-news-ext/src/js/content_script.css
@@ -0,0 +1,405 @@
+
+ @font-face {
+ font-family: 'UniversalSans';
+ src: url('moz-extension://__MSG_@@extension_id__/fonts/UniversalSans-800.woff') format('woff'),
+ url('moz-extension://__MSG_@@extension_id__/fonts/UniversalSans-800.ttf') format('ttf');
+ font-weight: 800;
+ }
+ @font-face {
+ font-family: 'UniversalSans';
+ src: url('moz-extension://__MSG_@@extension_id__/fonts/UniversalSans-680.woff') format('woff'),
+ url('moz-extension://__MSG_@@extension_id__/fonts/UniversalSans-680.ttf') format('ttf');
+ font-weight: 680;
+ }
+ @font-face {
+ font-family: 'UniversalSans';
+ src: url('moz-extension://__MSG_@@extension_id__/fonts/UniversalSans-480.woff') format('woff'),
+ url('moz-extension://__MSG_@@extension_id__/fonts/UniversalSans-480.ttf') format('ttf');
+ font-weight: 480;
+ }
+.tw-static {
+ position: static !important;
+}
+.tw-fixed {
+ position: fixed !important;
+}
+.tw-absolute {
+ position: absolute !important;
+}
+.tw-relative {
+ position: relative !important;
+}
+.tw-left-0 {
+ left: 0px !important;
+}
+.tw-z-\[1000000000\] {
+ z-index: 1000000000 !important;
+}
+.tw-z-\[10000\] {
+ z-index: 10000 !important;
+}
+.tw-float-right {
+ float: right !important;
+}
+.tw-my-\[13px\] {
+ margin-top: 13px !important;
+ margin-bottom: 13px !important;
+}
+.tw-mb-0 {
+ margin-bottom: 0px !important;
+}
+.tw-mb-\[20px\] {
+ margin-bottom: 20px !important;
+}
+.tw-box-border {
+ box-sizing: border-box !important;
+}
+.tw-inline-block {
+ display: inline-block !important;
+}
+.tw-flex {
+ display: flex !important;
+}
+.tw-inline-flex {
+ display: inline-flex !important;
+}
+.tw-hidden {
+ display: none !important;
+}
+.tw-h-\[1px\] {
+ height: 1px !important;
+}
+.tw-h-\[20px\] {
+ height: 20px !important;
+}
+.tw-h-\[30px\] {
+ height: 30px !important;
+}
+.tw-h-\[35px\] {
+ height: 35px !important;
+}
+.tw-h-\[54px\] {
+ height: 54px !important;
+}
+.tw-h-full {
+ height: 100% !important;
+}
+.tw-min-h-\[30px\] {
+ min-height: 30px !important;
+}
+.tw-w-\[200px\] {
+ width: 200px !important;
+}
+.tw-w-\[20px\] {
+ width: 20px !important;
+}
+.tw-w-\[30px\] {
+ width: 30px !important;
+}
+.tw-w-\[35px\] {
+ width: 35px !important;
+}
+.tw-w-\[420px\] {
+ width: 420px !important;
+}
+.tw-w-\[450px\] {
+ width: 450px !important;
+}
+.tw-w-full {
+ width: 100% !important;
+}
+.\!tw-max-w-none {
+ max-width: none !important;
+}
+.tw-flex-initial {
+ flex: 0 1 auto !important;
+}
+.tw-flex-none {
+ flex: none !important;
+}
+.tw-flex-shrink-0 {
+ flex-shrink: 0 !important;
+}
+.tw-flex-grow-0 {
+ flex-grow: 0 !important;
+}
+.tw-cursor-pointer {
+ cursor: pointer !important;
+}
+.tw-items-center {
+ align-items: center !important;
+}
+.tw-justify-end {
+ justify-content: flex-end !important;
+}
+.tw-justify-center {
+ justify-content: center !important;
+}
+.tw-justify-between {
+ justify-content: space-between !important;
+}
+.tw-gap-x-3 {
+ -moz-column-gap: 0.75rem !important;
+ column-gap: 0.75rem !important;
+}
+.tw-gap-x-\[10px\] {
+ -moz-column-gap: 10px !important;
+ column-gap: 10px !important;
+}
+.tw-gap-x-\[5px\] {
+ -moz-column-gap: 5px !important;
+ column-gap: 5px !important;
+}
+.tw-overflow-hidden {
+ overflow: hidden !important;
+}
+.tw-truncate {
+ overflow: hidden !important;
+ text-overflow: ellipsis !important;
+ white-space: nowrap !important;
+}
+.tw-whitespace-nowrap {
+ white-space: nowrap !important;
+}
+.tw-rounded {
+ border-radius: 0.25rem !important;
+}
+.tw-rounded-full {
+ border-radius: 9999px !important;
+}
+.tw-rounded-md {
+ border-radius: 0.375rem !important;
+}
+.\!tw-border {
+ border-width: 1px !important;
+}
+.tw-border {
+ border-width: 1px !important;
+}
+.tw-border-0 {
+ border-width: 0px !important;
+}
+.tw-border-solid {
+ border-style: solid !important;
+}
+.\!tw-border-light {
+ --tw-border-opacity: 1 !important;
+ border-color: rgb(238 239 233 / var(--tw-border-opacity)) !important;
+}
+.tw-border-light {
+ --tw-border-opacity: 1 !important;
+ border-color: rgb(238 239 233 / var(--tw-border-opacity)) !important;
+}
+.\!tw-bg-light {
+ --tw-bg-opacity: 1 !important;
+ background-color: rgb(238 239 233 / var(--tw-bg-opacity)) !important;
+}
+.tw-bg-biasBarCenter {
+ --tw-bg-opacity: 1 !important;
+ background-color: rgb(255 255 255 / var(--tw-bg-opacity)) !important;
+}
+.tw-bg-biasBarLeft {
+ --tw-bg-opacity: 1 !important;
+ background-color: rgb(32 73 134 / var(--tw-bg-opacity)) !important;
+}
+.tw-bg-biasBarRight {
+ --tw-bg-opacity: 1 !important;
+ background-color: rgb(128 39 39 / var(--tw-bg-opacity)) !important;
+}
+.tw-bg-center {
+ --tw-bg-opacity: 1 !important;
+ background-color: rgb(255 255 255 / var(--tw-bg-opacity)) !important;
+}
+.tw-bg-dark {
+ --tw-bg-opacity: 1 !important;
+ background-color: rgb(38 38 38 / var(--tw-bg-opacity)) !important;
+}
+.tw-bg-farLeft {
+ --tw-bg-opacity: 1 !important;
+ background-color: rgb(77 109 158 / var(--tw-bg-opacity)) !important;
+}
+.tw-bg-farRight {
+ --tw-bg-opacity: 1 !important;
+ background-color: rgb(153 82 82 / var(--tw-bg-opacity)) !important;
+}
+.tw-bg-focus {
+ --tw-bg-opacity: 1 !important;
+ background-color: rgb(209 189 145 / var(--tw-bg-opacity)) !important;
+}
+.tw-bg-leanLeft {
+ --tw-bg-opacity: 1 !important;
+ background-color: rgb(210 219 231 / var(--tw-bg-opacity)) !important;
+}
+.tw-bg-leanRight {
+ --tw-bg-opacity: 1 !important;
+ background-color: rgb(217 190 190 / var(--tw-bg-opacity)) !important;
+}
+.tw-bg-left {
+ --tw-bg-opacity: 1 !important;
+ background-color: rgb(144 164 195 / var(--tw-bg-opacity)) !important;
+}
+.tw-bg-light {
+ --tw-bg-opacity: 1 !important;
+ background-color: rgb(238 239 233 / var(--tw-bg-opacity)) !important;
+}
+.tw-bg-right {
+ --tw-bg-opacity: 1 !important;
+ background-color: rgb(192 147 147 / var(--tw-bg-opacity)) !important;
+}
+.tw-bg-center {
+ background-position: center !important;
+}
+.tw-bg-left {
+ background-position: left !important;
+}
+.tw-bg-right {
+ background-position: right !important;
+}
+.tw-fill-dark {
+ fill: #262626 !important;
+}
+.tw-fill-light {
+ fill: #EEEFE9 !important;
+}
+.tw-p-\[20px\] {
+ padding: 20px !important;
+}
+.tw-px-2 {
+ padding-left: 0.5rem !important;
+ padding-right: 0.5rem !important;
+}
+.tw-px-\[5px\] {
+ padding-left: 5px !important;
+ padding-right: 5px !important;
+}
+.tw-text-center {
+ text-align: center !important;
+}
+.tw-align-middle {
+ vertical-align: middle !important;
+}
+.tw-font-ground {
+ font-family: UniversalSans !important;
+}
+.tw-text-\[10px\] {
+ font-size: 10px !important;
+}
+.tw-text-\[12px\] {
+ font-size: 12px !important;
+}
+.tw-text-\[15px\] {
+ font-size: 15px !important;
+}
+.tw-text-\[18px\] {
+ font-size: 18px !important;
+}
+.tw-text-\[20px\] {
+ font-size: 20px !important;
+}
+.tw-text-\[30px\] {
+ font-size: 30px !important;
+}
+.tw-font-\[480\] {
+ font-weight: 480 !important;
+}
+.tw-font-\[600\] {
+ font-weight: 600 !important;
+}
+.tw-leading-\[20px\] {
+ line-height: 20px !important;
+}
+.tw-leading-\[25px\] {
+ line-height: 25px !important;
+}
+.tw-leading-\[30px\] {
+ line-height: 30px !important;
+}
+.tw-leading-\[54px\] {
+ line-height: 54px !important;
+}
+.tw-text-center {
+ --tw-text-opacity: 1 !important;
+ color: rgb(255 255 255 / var(--tw-text-opacity)) !important;
+}
+.tw-text-dark {
+ --tw-text-opacity: 1 !important;
+ color: rgb(38 38 38 / var(--tw-text-opacity)) !important;
+}
+.tw-text-light {
+ --tw-text-opacity: 1 !important;
+ color: rgb(238 239 233 / var(--tw-text-opacity)) !important;
+}
+.tw-opacity-0 {
+ opacity: 0 !important;
+}
+.tw-opacity-100 {
+ opacity: 1 !important;
+}
+.tw-transition-\[width\,background-color\,color\] {
+ transition-property: width,background-color,color !important;
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1) !important;
+ transition-duration: 150ms !important;
+}
+.tw-transition-all {
+ transition-property: all !important;
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1) !important;
+ transition-duration: 150ms !important;
+}
+.tw-transition-opacity {
+ transition-property: opacity !important;
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1) !important;
+ transition-duration: 150ms !important;
+}
+.tw-duration-300 {
+ transition-duration: 300ms !important;
+}
+.tw-duration-500 {
+ transition-duration: 500ms !important;
+}
+.tw-ease-in-out {
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1) !important;
+}
+.\[top\:-108px\] {
+ top: -108px !important;
+}
+.\[top\:0px\] {
+ top: 0px !important;
+}
+.\[transition\:top_500ms_cubic-bezier\(0\2c 0\2c 0\2c 1\)\] {
+ transition: top 500ms cubic-bezier(0,0,0,1) !important;
+}
+.hover\:tw-bg-dark:hover {
+ --tw-bg-opacity: 1 !important;
+ background-color: rgb(38 38 38 / var(--tw-bg-opacity)) !important;
+}
+.hover\:tw-bg-light:hover {
+ --tw-bg-opacity: 1 !important;
+ background-color: rgb(238 239 233 / var(--tw-bg-opacity)) !important;
+}
+.hover\:tw-text-dark:hover {
+ --tw-text-opacity: 1 !important;
+ color: rgb(38 38 38 / var(--tw-text-opacity)) !important;
+}
+.hover\:tw-text-light:hover {
+ --tw-text-opacity: 1 !important;
+ color: rgb(238 239 233 / var(--tw-text-opacity)) !important;
+}
+@media (max-width: 1340px) {
+ .max-\[1340px\]\:tw-hidden {
+ display: none !important;
+ }
+}
+@media (min-width: 1280px) {
+ .xl\:tw-inline-block {
+ display: inline-block !important;
+ }
+ .xl\:tw-gap-x-\[10px\] {
+ -moz-column-gap: 10px !important;
+ column-gap: 10px !important;
+ }
+}
+@media (min-width: 1341px) {
+ .min-\[1341px\]\:tw-hidden {
+ display: none !important;
+ }
+}
diff --git a/ground-news-ext/src/js/content_script.js b/ground-news-ext/src/js/content_script.js
new file mode 100644
index 0000000..43859af
--- /dev/null
+++ b/ground-news-ext/src/js/content_script.js
@@ -0,0 +1 @@
+(()=>{var a={9558:a=>{"use strict";const e="[a-fA-F\\d:]",o=a=>a&&a.includeBoundaries?`(?:(?<=\\s|^)(?=${e})|(?<=${e})(?=\\s|$))`:"",n="(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}",i="[a-fA-F\\d]{1,4}",t=`\n(?:\n(?:${i}:){7}(?:${i}|:)| // 1:2:3:4:5:6:7:: 1:2:3:4:5:6:7:8\n(?:${i}:){6}(?:${n}|:${i}|:)| // 1:2:3:4:5:6:: 1:2:3:4:5:6::8 1:2:3:4:5:6::8 1:2:3:4:5:6::1.2.3.4\n(?:${i}:){5}(?::${n}|(?::${i}){1,2}|:)| // 1:2:3:4:5:: 1:2:3:4:5::7:8 1:2:3:4:5::8 1:2:3:4:5::7:1.2.3.4\n(?:${i}:){4}(?:(?::${i}){0,1}:${n}|(?::${i}){1,3}|:)| // 1:2:3:4:: 1:2:3:4::6:7:8 1:2:3:4::8 1:2:3:4::6:7:1.2.3.4\n(?:${i}:){3}(?:(?::${i}){0,2}:${n}|(?::${i}){1,4}|:)| // 1:2:3:: 1:2:3::5:6:7:8 1:2:3::8 1:2:3::5:6:7:1.2.3.4\n(?:${i}:){2}(?:(?::${i}){0,3}:${n}|(?::${i}){1,5}|:)| // 1:2:: 1:2::4:5:6:7:8 1:2::8 1:2::4:5:6:7:1.2.3.4\n(?:${i}:){1}(?:(?::${i}){0,4}:${n}|(?::${i}){1,6}|:)| // 1:: 1::3:4:5:6:7:8 1::8 1::3:4:5:6:7:1.2.3.4\n(?::(?:(?::${i}){0,5}:${n}|(?::${i}){1,7}|:)) // ::2:3:4:5:6:7:8 ::2:3:4:5:6:7:8 ::8 ::1.2.3.4\n)(?:%[0-9a-zA-Z]{1,})? // %eth0 %1\n`.replace(/\s*\/\/.*$/gm,"").replace(/\n/g,"").trim(),s=new RegExp(`(?:^${n}$)|(?:^${t}$)`),r=new RegExp(`^${n}$`),l=new RegExp(`^${t}$`),c=a=>a&&a.exact?s:new RegExp(`(?:${o(a)}${n}${o(a)})|(?:${o(a)}${t}${o(a)})`,"g");c.v4=a=>a&&a.exact?r:new RegExp(`${o(a)}${n}${o(a)}`,"g"),c.v6=a=>a&&a.exact?l:new RegExp(`${o(a)}${t}${o(a)}`,"g"),a.exports=c},7385:(a,e,o)=>{"use strict";const n=o(9558),i=a=>n({exact:!0}).test(a);i.v4=a=>n.v4({exact:!0}).test(a),i.v6=a=>n.v6({exact:!0}).test(a),i.version=a=>i(a)?i.v4(a)?4:6:void 0,a.exports=i},6815:function(a,e){var o,n,i;"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self&&self,n=[a],o=function(a){"use strict";if(!globalThis.chrome?.runtime?.id)throw new Error("This script should only be loaded in a browser extension.");if(void 0===globalThis.browser||Object.getPrototypeOf(globalThis.browser)!==Object.prototype){const e="The message port closed before a response was received.",o=a=>{const o={alarms:{clear:{minArgs:0,maxArgs:1},clearAll:{minArgs:0,maxArgs:0},get:{minArgs:0,maxArgs:1},getAll:{minArgs:0,maxArgs:0}},bookmarks:{create:{minArgs:1,maxArgs:1},get:{minArgs:1,maxArgs:1},getChildren:{minArgs:1,maxArgs:1},getRecent:{minArgs:1,maxArgs:1},getSubTree:{minArgs:1,maxArgs:1},getTree:{minArgs:0,maxArgs:0},move:{minArgs:2,maxArgs:2},remove:{minArgs:1,maxArgs:1},removeTree:{minArgs:1,maxArgs:1},search:{minArgs:1,maxArgs:1},update:{minArgs:2,maxArgs:2}},browserAction:{disable:{minArgs:0,maxArgs:1,fallbackToNoCallback:!0},enable:{minArgs:0,maxArgs:1,fallbackToNoCallback:!0},getBadgeBackgroundColor:{minArgs:1,maxArgs:1},getBadgeText:{minArgs:1,maxArgs:1},getPopup:{minArgs:1,maxArgs:1},getTitle:{minArgs:1,maxArgs:1},openPopup:{minArgs:0,maxArgs:0},setBadgeBackgroundColor:{minArgs:1,maxArgs:1,fallbackToNoCallback:!0},setBadgeText:{minArgs:1,maxArgs:1,fallbackToNoCallback:!0},setIcon:{minArgs:1,maxArgs:1},setPopup:{minArgs:1,maxArgs:1,fallbackToNoCallback:!0},setTitle:{minArgs:1,maxArgs:1,fallbackToNoCallback:!0}},browsingData:{remove:{minArgs:2,maxArgs:2},removeCache:{minArgs:1,maxArgs:1},removeCookies:{minArgs:1,maxArgs:1},removeDownloads:{minArgs:1,maxArgs:1},removeFormData:{minArgs:1,maxArgs:1},removeHistory:{minArgs:1,maxArgs:1},removeLocalStorage:{minArgs:1,maxArgs:1},removePasswords:{minArgs:1,maxArgs:1},removePluginData:{minArgs:1,maxArgs:1},settings:{minArgs:0,maxArgs:0}},commands:{getAll:{minArgs:0,maxArgs:0}},contextMenus:{remove:{minArgs:1,maxArgs:1},removeAll:{minArgs:0,maxArgs:0},update:{minArgs:2,maxArgs:2}},cookies:{get:{minArgs:1,maxArgs:1},getAll:{minArgs:1,maxArgs:1},getAllCookieStores:{minArgs:0,maxArgs:0},remove:{minArgs:1,maxArgs:1},set:{minArgs:1,maxArgs:1}},devtools:{inspectedWindow:{eval:{minArgs:1,maxArgs:2,singleCallbackArg:!1}},panels:{create:{minArgs:3,maxArgs:3,singleCallbackArg:!0},elements:{createSidebarPane:{minArgs:1,maxArgs:1}}}},downloads:{cancel:{minArgs:1,maxArgs:1},download:{minArgs:1,maxArgs:1},erase:{minArgs:1,maxArgs:1},getFileIcon:{minArgs:1,maxArgs:2},open:{minArgs:1,maxArgs:1,fallbackToNoCallback:!0},pause:{minArgs:1,maxArgs:1},removeFile:{minArgs:1,maxArgs:1},resume:{minArgs:1,maxArgs:1},search:{minArgs:1,maxArgs:1},show:{minArgs:1,maxArgs:1,fallbackToNoCallback:!0}},extension:{isAllowedFileSchemeAccess:{minArgs:0,maxArgs:0},isAllowedIncognitoAccess:{minArgs:0,maxArgs:0}},history:{addUrl:{minArgs:1,maxArgs:1},deleteAll:{minArgs:0,maxArgs:0},deleteRange:{minArgs:1,maxArgs:1},deleteUrl:{minArgs:1,maxArgs:1},getVisits:{minArgs:1,maxArgs:1},search:{minArgs:1,maxArgs:1}},i18n:{detectLanguage:{minArgs:1,maxArgs:1},getAcceptLanguages:{minArgs:0,maxArgs:0}},identity:{launchWebAuthFlow:{minArgs:1,maxArgs:1}},idle:{queryState:{minArgs:1,maxArgs:1}},management:{get:{minArgs:1,maxArgs:1},getAll:{minArgs:0,maxArgs:0},getSelf:{minArgs:0,maxArgs:0},setEnabled:{minArgs:2,maxArgs:2},uninstallSelf:{minArgs:0,maxArgs:1}},notifications:{clear:{minArgs:1,maxArgs:1},create:{minArgs:1,maxArgs:2},getAll:{minArgs:0,maxArgs:0},getPermissionLevel:{minArgs:0,maxArgs:0},update:{minArgs:2,maxArgs:2}},pageAction:{getPopup:{minArgs:1,maxArgs:1},getTitle:{minArgs:1,maxArgs:1},hide:{minArgs:1,maxArgs:1,fallbackToNoCallback:!0},setIcon:{minArgs:1,maxArgs:1},setPopup:{minArgs:1,maxArgs:1,fallbackToNoCallback:!0},setTitle:{minArgs:1,maxArgs:1,fallbackToNoCallback:!0},show:{minArgs:1,maxArgs:1,fallbackToNoCallback:!0}},permissions:{contains:{minArgs:1,maxArgs:1},getAll:{minArgs:0,maxArgs:0},remove:{minArgs:1,maxArgs:1},request:{minArgs:1,maxArgs:1}},runtime:{getBackgroundPage:{minArgs:0,maxArgs:0},getPlatformInfo:{minArgs:0,maxArgs:0},openOptionsPage:{minArgs:0,maxArgs:0},requestUpdateCheck:{minArgs:0,maxArgs:0},sendMessage:{minArgs:1,maxArgs:3},sendNativeMessage:{minArgs:2,maxArgs:2},setUninstallURL:{minArgs:1,maxArgs:1}},sessions:{getDevices:{minArgs:0,maxArgs:1},getRecentlyClosed:{minArgs:0,maxArgs:1},restore:{minArgs:0,maxArgs:1}},storage:{local:{clear:{minArgs:0,maxArgs:0},get:{minArgs:0,maxArgs:1},getBytesInUse:{minArgs:0,maxArgs:1},remove:{minArgs:1,maxArgs:1},set:{minArgs:1,maxArgs:1}},managed:{get:{minArgs:0,maxArgs:1},getBytesInUse:{minArgs:0,maxArgs:1}},sync:{clear:{minArgs:0,maxArgs:0},get:{minArgs:0,maxArgs:1},getBytesInUse:{minArgs:0,maxArgs:1},remove:{minArgs:1,maxArgs:1},set:{minArgs:1,maxArgs:1}}},tabs:{captureVisibleTab:{minArgs:0,maxArgs:2},create:{minArgs:1,maxArgs:1},detectLanguage:{minArgs:0,maxArgs:1},discard:{minArgs:0,maxArgs:1},duplicate:{minArgs:1,maxArgs:1},executeScript:{minArgs:1,maxArgs:2},get:{minArgs:1,maxArgs:1},getCurrent:{minArgs:0,maxArgs:0},getZoom:{minArgs:0,maxArgs:1},getZoomSettings:{minArgs:0,maxArgs:1},goBack:{minArgs:0,maxArgs:1},goForward:{minArgs:0,maxArgs:1},highlight:{minArgs:1,maxArgs:1},insertCSS:{minArgs:1,maxArgs:2},move:{minArgs:2,maxArgs:2},query:{minArgs:1,maxArgs:1},reload:{minArgs:0,maxArgs:2},remove:{minArgs:1,maxArgs:1},removeCSS:{minArgs:1,maxArgs:2},sendMessage:{minArgs:2,maxArgs:3},setZoom:{minArgs:1,maxArgs:2},setZoomSettings:{minArgs:1,maxArgs:2},update:{minArgs:1,maxArgs:2}},topSites:{get:{minArgs:0,maxArgs:0}},webNavigation:{getAllFrames:{minArgs:1,maxArgs:1},getFrame:{minArgs:1,maxArgs:1}},webRequest:{handlerBehaviorChanged:{minArgs:0,maxArgs:0}},windows:{create:{minArgs:0,maxArgs:1},get:{minArgs:1,maxArgs:2},getAll:{minArgs:0,maxArgs:1},getCurrent:{minArgs:0,maxArgs:1},getLastFocused:{minArgs:0,maxArgs:1},remove:{minArgs:1,maxArgs:1},update:{minArgs:2,maxArgs:2}}};if(0===Object.keys(o).length)throw new Error("api-metadata.json has not been included in browser-polyfill");class n extends WeakMap{constructor(a,e=void 0){super(e),this.createItem=a}get(a){return this.has(a)||this.set(a,this.createItem(a)),super.get(a)}}const i=a=>a&&"object"==typeof a&&"function"==typeof a.then,t=(e,o)=>(...n)=>{a.runtime.lastError?e.reject(new Error(a.runtime.lastError.message)):o.singleCallbackArg||n.length<=1&&!1!==o.singleCallbackArg?e.resolve(n[0]):e.resolve(n)},s=a=>1==a?"argument":"arguments",r=(a,e)=>function(o,...n){if(n.lengthe.maxArgs)throw new Error(`Expected at most ${e.maxArgs} ${s(e.maxArgs)} for ${a}(), got ${n.length}`);return new Promise(((i,s)=>{if(e.fallbackToNoCallback)try{o[a](...n,t({resolve:i,reject:s},e))}catch(t){o[a](...n),e.fallbackToNoCallback=!1,e.noCallback=!0,i()}else e.noCallback?(o[a](...n),i()):o[a](...n,t({resolve:i,reject:s},e))}))},l=(a,e,o)=>new Proxy(e,{apply:(e,n,i)=>o.call(n,a,...i)});let c=Function.call.bind(Object.prototype.hasOwnProperty);const m=(a,e={},o={})=>{let n=Object.create(null),i={has:(e,o)=>o in a||o in n,get(i,t,s){if(t in n)return n[t];if(!(t in a))return;let u=a[t];if("function"==typeof u)if("function"==typeof e[t])u=l(a,a[t],e[t]);else if(c(o,t)){let e=r(t,o[t]);u=l(a,a[t],e)}else u=u.bind(a);else if("object"==typeof u&&null!==u&&(c(e,t)||c(o,t)))u=m(u,e[t],o[t]);else{if(!c(o,"*"))return Object.defineProperty(n,t,{configurable:!0,enumerable:!0,get:()=>a[t],set(e){a[t]=e}}),u;u=m(u,e[t],o["*"])}return n[t]=u,u},set:(e,o,i,t)=>(o in n?n[o]=i:a[o]=i,!0),defineProperty:(a,e,o)=>Reflect.defineProperty(n,e,o),deleteProperty:(a,e)=>Reflect.deleteProperty(n,e)},t=Object.create(a);return new Proxy(t,i)},u=a=>({addListener(e,o,...n){e.addListener(a.get(o),...n)},hasListener:(e,o)=>e.hasListener(a.get(o)),removeListener(e,o){e.removeListener(a.get(o))}}),g=new n((a=>"function"!=typeof a?a:function(e){const o=m(e,{},{getContent:{minArgs:0,maxArgs:0}});a(o)})),d=new n((a=>"function"!=typeof a?a:function(e,o,n){let t,s,r=!1,l=new Promise((a=>{t=function(e){r=!0,a(e)}}));try{s=a(e,o,t)}catch(a){s=Promise.reject(a)}const c=!0!==s&&i(s);if(!0!==s&&!c&&!r)return!1;const m=a=>{a.then((a=>{n(a)}),(a=>{let e;e=a&&(a instanceof Error||"string"==typeof a.message)?a.message:"An unexpected error occurred",n({__mozWebExtensionPolyfillReject__:!0,message:e})})).catch((a=>{}))};return m(c?s:l),!0})),h=({reject:o,resolve:n},i)=>{a.runtime.lastError?a.runtime.lastError.message===e?n():o(new Error(a.runtime.lastError.message)):i&&i.__mozWebExtensionPolyfillReject__?o(new Error(i.message)):n(i)},p=(a,e,o,...n)=>{if(n.lengthe.maxArgs)throw new Error(`Expected at most ${e.maxArgs} ${s(e.maxArgs)} for ${a}(), got ${n.length}`);return new Promise(((a,e)=>{const i=h.bind(null,{resolve:a,reject:e});n.push(i),o.sendMessage(...n)}))},k={devtools:{network:{onRequestFinished:u(g)}},runtime:{onMessage:u(d),onMessageExternal:u(d),sendMessage:p.bind(null,"sendMessage",{minArgs:1,maxArgs:3})},tabs:{sendMessage:p.bind(null,"sendMessage",{minArgs:2,maxArgs:3})}},b={clear:{minArgs:1,maxArgs:1},get:{minArgs:1,maxArgs:1},set:{minArgs:1,maxArgs:1}};return o.privacy={network:{"*":b},services:{"*":b},websites:{"*":b}},m(a,k,o)};a.exports=o(chrome)}else a.exports=globalThis.browser},void 0===(i="function"==typeof o?o.apply(e,n):o)||(a.exports=i)}},e={};function o(n){var i=e[n];if(void 0!==i)return i.exports;var t=e[n]={exports:{}};return a[n].call(t.exports,t,t.exports,o),t.exports}(()=>{"use strict";var a=o(6815);const e=(a,e)=>{const o=a.slice(),n=[];let i=e;for(;0!==o.length;){const a=o.pop(),e=a.toLowerCase();if(i.children.has("*")){if(i.children.has("!"+e))break;i=i.children.get("*")}else{if(!1===i.children.has(e))break;i=i.children.get(e)}n.unshift(a)}return n};var n=o(7385);const i=".",t=new TextEncoder;var s,r,l;!function(a){a.Lax="LAX",a.Strict="STRICT"}(s||(s={})),function(a){a.NoHostname="NO_HOSTNAME",a.DomainMaxLength="DOMAIN_MAX_LENGTH",a.LabelMinLength="LABEL_MIN_LENGTH",a.LabelMaxLength="LABEL_MAX_LENGTH",a.LabelInvalidCharacter="LABEL_INVALID_CHARACTER",a.LastLabelInvalid="LAST_LABEL_INVALID"}(r||(r={})),function(a){a.ValidIp="VALID_IP",a.ValidDomain="VALID_DOMAIN",a.Error="ERROR"}(l||(l={}));const c=a=>({type:r.NoHostname,message:`The given input ${String(a)} does not look like a hostname.`,column:1}),m=(a,e)=>{const o=a.length;return{type:r.LabelMinLength,message:`Label "${a}" is too short. Label is ${o} octets long but should be at least 1.`,column:e}},u=(a,e)=>{const o=a.length;return{type:r.LabelMaxLength,message:`Label "${a}" is too long. Label is ${o} octets long but should not be longer than 63.`,column:e}},g=(a,e,o)=>({type:r.LabelInvalidCharacter,message:`Label "${a}" contains invalid character "${e}" at column ${o}.`,column:o}),d={[s.Lax]:a=>{const e=[];let o=1;for(const n of a){const a=t.encode(n);a.length<1?e.push(m(n,o)):a.length>63&&e.push(u(n,o)),o+=n.length+1}return e},[s.Strict]:a=>{const e=[];let o,n=1;for(const i of a){const a=/[^\da-z-]/i.exec(i);a&&e.push(g(i,a[0],a.index+1)),i.startsWith("-")?e.push(g(i,"-",n)):i.endsWith("-")&&e.push(g(i,"-",n+i.length-1)),i.length<1?e.push(m(i,n)):i.length>63&&e.push(u(i,n)),n+=i.length+1,o=i}return void 0!==o&&!1===/[a-z-]/iu.test(o)&&e.push(((a,e)=>({type:r.LabelInvalidCharacter,message:`Last label "${a}" must not be all-numeric.`,column:e}))(o,n-o.length-1)),e}},h=Symbol("ROOT"),p=Symbol("CHILD"),k=a=>{const e={type:h,children:new Map};let o="",n=e,i=e;const t=()=>{i=((a,e)=>{let o=a.children.get(e);return void 0===o&&(o={type:p,label:e,children:new Map,parent:a},a.children.set(e,o)),o})(n,o),o=""};for(let s=0;s":t(),n=i;continue;case"|":t(),n=e;continue;case"<":if(n.type===h)throw new Error(`Error in serialized trie at position ${s}: Cannot go up, current parent node is already root`);t(),n=n.parent;continue}o+=r}return""!==o&&t(),e},b=["localhost","local","example","invalid","test"];var f;!function(a){a.Invalid="INVALID",a.Ip="IP",a.Reserved="RESERVED",a.NotListed="NOT_LISTED",a.Listed="LISTED"}(f||(f={}));const w=(a,e)=>e>=0&&e({subDomains:a.slice(0,Math.max(0,e)),domain:w(a,e),topLevelDomains:a.slice(e+1)});let x,v;const $=(a,o)=>{const t=((a,e={})=>{if("string"!=typeof a)return{type:l.Error,errors:[c(a)]};const o=a.trim();if(""===o)return{type:l.ValidDomain,domain:o,labels:[]};const t=o.replace(/^\[|]$/g,""),m=(0,n.version)(t);if(void 0!==m)return{type:l.ValidIp,ip:t,ipVersion:m};const u=o.charAt(o.length-1)===i?o.slice(0,-1):o,g=(new TextEncoder).encode(u);if(g.length>253)return{type:l.Error,errors:[(h=o,p=g.length,{type:r.DomainMaxLength,message:`Domain "${h}" is too long. Domain is ${p} octets long but should not be longer than 253.`,column:p})]};var h,p;const k=u.split(i),{validation:b=s.Strict}=e,f=d[b](k);return f.length>0?{type:l.Error,errors:f}:{type:l.ValidDomain,domain:o,labels:k}})(a,o);if(t.type===l.Error)return{type:f.Invalid,hostname:a,errors:t.errors};if(t.type===l.ValidIp)return{type:f.Ip,hostname:t.ip,ipVersion:t.ipVersion};const{labels:m,domain:u}=t;if(""===a||b.includes(m[m.length-1]))return{type:f.Reserved,hostname:u,labels:m};x=null!=x?x:k("ac>com,edu,gov,net,mil,orgnomco,net,org,sch,ac,gov,milaccident-investigation,accident-prevention,aerobatic,aeroclub,aerodrome,agents,aircraft,airline,airport,air-surveillance,airtraffic,air-traffic-control,ambulance,amusement,association,author,ballooning,broker,caa,cargo,catering,certification,championship,charter,civilaviation,club,conference,consultant,consulting,control,council,crew,design,dgca,educator,emergency,engine,engineer,entertainment,equipment,exchange,express,federation,flight,fuel,gliding,government,groundhandling,group,hanggliding,homebuilt,insurance,journal,journalist,leasing,logistics,magazine,maintenance,media,microlight,modelling,navigation,parachuting,paragliding,passenger-association,pilot,press,production,recreation,repbody,res,research,rotorcraft,safety,scientist,services,show,skydiving,software,student,trader,trading,trainer,union,workinggroup,worksgov,com,org,net,educom,org,net,co,nomoff,com,net,orgcom,edu,gov,mil,net,orgco,com,commune,net,orged,gv,og,co,pb,itbet,com,coop,edu,gob,gov,int,mil,musica,mutual,net,org,senasa,ture164,in-addr,ip6,iris,uri,urngovac>sthcom,net,org,edu>act,catholic,nsw>schoolsqld,sa,tas,vic,wacomcom,net,int,gov,org,edu,info,pp,mil,name,pro,bizcom,edu,gov,mil,net,orgbiz,co,com,edu,gov,info,net,org,store,tv*acgova,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z,0,1,2,3,4,5,6,7,8,9com,edu,net,org,govco,com,edu,or,orgasso,barreau,gouvcom,edu,gov,net,orgcom,edu,gov,net,orgcom,edu,gob,int,org,net,mil,tv,web,academia,agro,arte,blog,bolivia,ciencia,cooperativa,democracia,deporte,ecologia,economia,empresa,indigena,industria,info,medicina,movimiento,musica,natural,nombre,noticias,patria,politica,profesional,plurinacional,pueblo,revista,salud,tecnologia,tksat,transporte,wiki
9guacu,abc,adm,adv,agr,aju,am,anani,aparecida,app,arq,art,ato,b,barueri,belem,bhz,bib,bio,blog,bmd,boavista,bsb,campinagrande,campinas,caxias,cim,cng,cnt,com,contagem,coop,coz,cri,cuiaba,curitiba,def,des,det,dev,ecn,eco,edu,emp,enf,eng,esp,etc,eti,far,feira,flog,floripa,fm,fnd,fortal,fot,foz,fst,g12,geo,ggf,goiania,gov>ac,al,am,ap,ba,ce,df,es,go,ma,mg,ms,mt,pa,pb,pe,pi,pr,rj,rn,ro,rr,rs,sc,se,sp,to*com,net,org,edu,govcom,edu,gov,net,orgco,orggov,mil,com,ofcom,net,org,edu,govab,bc,mb,nb,nf,nl,ns,nt,nu,on,pe,qc,sk,yk,gcgovorg,or,com,co,edu,ed,ac,net,go,asso,xn--aroport-bya,int,presse,md,gouv*,!wwwco,gob,gov,milco,com,gov,netac,com,edu,gov,net,org,mil,xn--55qx5d,xn--io0a7i,xn--od0alg,ah,bj,cq,fj,gd,gs,gz,gx,ha,hb,he,hi,hl,hn,jl,js,jx,ln,nm,nx,qh,sc,sd,sh,sn,sx,tj,xj,xz,yn,zj,hk,mo,twarts,com,edu,firm,gov,info,int,mil,net,nom,org,rec,webac,co,ed,fi,go,or,sacom,edu,org,net,gov,infcom,edu,int,nome,orgcom,edu,net,orggovac,biz,com,ekloges,gov,ltd,name,net,org,parliament,press,pro,tmcom,net,org,edu,govart,com,edu,gob,gov,mil,net,org,sld,webart,asso,com,edu,gov,org,net,pol,soc,tmcom,info,net,fin,k12,med,pro,org,edu,gov,gob,miledu,gov,riik,lib,med,com,pri,aip,org,fiecom,edu,eun,gov,mil,name,net,org,sci*com,nom,org,gob,educom,gov,org,edu,biz,name,info,netalandac,biz,com,gov,info,mil,name,net,org,pro*com,edu,net,orgasso,com,gouv,nom,prd,tm,aeroport,avocat,avoues,cci,chambagri,chirurgiens-dentistes,experts-comptables,geometre-expert,greta,huissier-justice,medecin,notaires,pharmacien,port,veterinaireedu,govcom,edu,gov,org,mil,net,pvtco,net,orgcom,edu,gov,org,milcom,ltd,gov,mod,edu,orgco,com,edu,net,orgac,com,edu,gov,org,netcom,net,mobi,edu,org,assocom,edu,net,org,govcom,edu,gob,ind,mil,net,orgcom,edu,gov,guam,info,net,org,webco,com,edu,gov,net,orgcom,edu,gov,idv,net,org,xn--55qx5d,xn--wcvs22d,xn--lcvr32d,xn--mxtq1m,xn--gmqw5a,xn--ciqpn,xn--gmq050i,xn--zf0avx,xn--io0a7i,xn--mk0axi,xn--od0alg,xn--od0aq3b,xn--tn0ag,xn--uc0atv,xn--uc0ay4acom,edu,org,net,mil,gob
iz,from,name,comcom,shop,firm,info,adult,net,pro,org,med,art,coop,pol,asso,edu,rel,gouv,persoco,info,org,priv,sport,tm,2000,agrar,bolt,casino,city,erotica,erotika,film,forum,games,hotel,ingatlan,jogasz,konyvelo,lakas,media,news,reklam,sex,shop,suli,szex,tozsde,utazas,videoac,biz,co,desa,go,mil,my,net,or,ponpes,sch,webgovac,co,gov,idf,k12,muni,net,orgac,co>ltd,plcco,firm,net,org,gen,ind,nic,ac,edu,res,gov,mileucomgov,edu,mil,com,org,netac,co,gov,id,net,org,sch,xn--mgba3a4f16a,xn--mgba3a4franet,com,edu,gov,org,intgov,edu,abr,abruzzo,aosta-valley,aostavalley,bas,basilicata,cal,calabria,cam,campania,emilia-romagna,emiliaromagna,emr,friuli-v-giulia,friuli-ve-giulia,friuli-vegiulia,friuli-venezia-giulia,friuli-veneziagiulia,friuli-vgiulia,friuliv-giulia,friulive-giulia,friulivegiulia,friulivenezia-giulia,friuliveneziagiulia,friulivgiulia,fvg,laz,lazio,lig,liguria,lom,lombardia,lombardy,lucania,mar,marche,mol,molise,piedmont,piemonte,pmn,pug,puglia,sar,sardegna,sardinia,sic,sicilia,sicily,taa,tos,toscana,trentin-sud-tirol,xn--trentin-sd-tirol-rzb,trentin-sudtirol,xn--trentin-sdtirol-7vb,trentin-sued-tirol,trentin-suedtirol,trentino-a-adige,trentino-aadige,trentino-alto-adige,trentino-altoadige,trentino-s-tirol,trentino-stirol,trentino-sud-tirol,xn--trentino-sd-tirol-c3b,trentino-sudtirol,xn--trentino-sdtirol-szb,trentino-sued-tirol,trentino-suedtirol,trentino,trentinoa-adige,trentinoaadige,trentinoalto-adige,trentinoaltoadige,trentinos-tirol,trentinostirol,trentinosud-tirol,xn--trentinosd-tirol-rzb,trentinosudtirol,xn--trentinosdtirol-7vb,trentinosued-tirol,trentinosuedtirol,trentinsud-tirol,xn--trentinsd-tirol-6vb,trentinsudtirol,xn--trentinsdtirol-nsb,trentinsued-tirol,trentinsuedtirol,tuscany,umb,umbria,val-d-aosta,val-daosta,vald-aosta,valdaosta,valle-aosta,valle-d-aosta,valle-daosta,valleaosta,valled-aosta,valledaosta,vallee-aoste,xn--valle-aoste-ebb,vallee-d-aoste,xn--valle-d-aoste-ehb,valleeaoste,xn--valleaoste-e7a,valleedaoste,xn--valledaoste-ebb,vao,vda,ven,veneto,ag,agrigento,al,alessandria,alto-adige,altoadige,an,ancona,andria-barletta-trani,andria-trani-barletta,andriabarlettatrani,andriatranibarletta,ao,aosta,aoste,ap,aq,aquila,ar,arezzo,ascoli-piceno,ascolipiceno,asti,at,av,avellino,ba,balsan-sudtirol,xn--balsan-sdtirol-nsb,balsan-suedtirol,balsan,bari,barletta-trani-andria,barlettatraniandria,belluno,benevento,bergamo,bg,bi,biella,bl,bn,bo,bologna,bolzano-altoadige,bolzano,bozen-sudtirol,xn--bozen-sdtirol-2ob,bozen-suedtirol,bozen,br,brescia,brindisi,bs,bt,bulsan-sudtirol,xn--bulsan-sdtirol-nsb,bulsan-suedtirol,bulsan,bz,ca,cagliari,caltanissetta,campidano-medio,campidanomedio,campobasso,carbonia-iglesias,carboniaiglesias,carrara-massa,carraramassa,caserta,catania,catanzaro,cb,ce,cesena-forli,xn--cesena-forl-mcb,cesenaforli,xn--cesenaforl-i8a,ch,chieti,ci,cl,cn,co,como,cosenza,cr,cremona,crotone,cs,ct,cuneo,cz,dell-ogliastra,dellogliastra,en,enna,fc,fe,fermo,ferrara,fg,fi,firenze,florence,fm,foggia,forli-cesena,xn--forl-cesena-fcb,forlicesena,xn--forlcesena-c8a,fr,frosinone,ge,genoa,genova,go,gorizia,gr,grosseto,iglesias-carbonia,iglesiascarbonia,im,imperia,is,isernia,kr,la-spezia,laquila,laspezia,latina,lc,le,lecce,lecco,li,livorno,lo,lodi,lt,lu,lucca,macerata,mantova,massa-carrara,massacarrara,matera,mb,mc,me,medio-campidano,mediocampidano,messina,mi,milan,milano,mn,mo,modena,monza-brianza,monza-e-della-brianza,monza,monzabrianza,monzaebrianza,monzaedellabrianza,ms,mt,na,naples,napoli,no,novara,nu,nuoro,og,ogliastra,olbia-tempio,olbiatempio,or,oristano,ot,pa,padova,padua,palermo,parma,pavia,pc,pd,pe,perugia,pesaro-urbino,pesarourbino,pescara,pg,pi,piacenza,pisa,pistoia,pn,po,pordenone,potenza,pr,prato,pt,pu,pv,pz,ra,ragusa,ravenna,rc,re,reggio-calabria,reggio-emilia,reggiocalabria,reggioemilia,rg,ri,rieti,rimini,rm,rn,ro,roma,rome,rovigo,sa,salerno,sassari,savona,si,siena,siracusa,so,sondrio,sp,sr,ss,suedtirol,xn--sdtirol-n2a,sv,ta,taranto,te,tempio-olbia,tempioolbia,teramo,terni,tn,to,torino,tp,tr,trani-andria-barletta,trani-barletta-andria,traniandriabarletta,tranibarlettaandria,trapani,trento,treviso,trieste,ts,turin,tv,ud,udine,urbino-pesaro,urbinopesaro,va,varese,vb,vc,ve,venezia,venice,verbania,vercelli,verona,vi,vibo-valentia,vibovalentia,vicenza,viterbo,vr,vs,vt,vvco,net,org*com,org,net,edu,sch,gov,mil,nameac,ad,co,ed,go,gr,lg,ne,or,aichi>aisai,ama,anjo,asuke,chiryu,chita,fuso,gamagori,handa,hazu,hekinan,higashiura,ichinomiya,inazawa,inuyama,isshiki,iwakura,kanie,kariya,kasugai,kira,kiyosu,komaki,konan,kota,mihama,miyoshi,nishio,nisshin,obu,oguchi,oharu,okazaki,owariasahi,seto,shikatsu,shinshiro,shitara,tahara,takahama,tobishima,toei,togo,tokai,tokoname,toyoake,toyohashi,toyokawa,toyone,toyota,tsushima,yatomiakita,daisen,fujisato,gojome,hachirogata,happou,higashinaruse,honjo,honjyo,ikawa,kamikoani,kamioka,katagami,kazuno,kitaakita,kosaka,kyowa,misato,mitane,moriyoshi,nikaho,noshiro,odate,oga,ogata,semboku,yokote,yurihonjoaomori,gonohe,hachinohe,hashikami,hiranai,hirosaki,itayanagi,kuroishi,misawa,mutsu,nakadomari,noheji,oirase,owani,rokunohe,sannohe,shichinohe,shingo,takko,towada,tsugaru,tsurutaabiko,asahi,chonan,chosei,choshi,chuo,funabashi,futtsu,hanamigawa,ichihara,ichikawa,ichinomiya,inzai,isumi,kamagaya,kamogawa,kashiwa,katori,katsuura,kimitsu,kisarazu,kozaki,kujukuri,kyonan,matsudo,midori,mihama,minamiboso,mobara,mutsuzawa,nagara,nagareyama,narashino,narita,noda,oamishirasato,omigawa,onjuku,otaki,sakae,sakura,shimofusa,shirako,shiroi,shisui,sodegaura,sosa,tako,tateyama,togane,tohnosho,tomisato,urayasu,yachimata,yachiyo,yokaichiba,yokoshibahikari,yotsukaidoainan,honai,ikata,imabari,iyo,kamijima,kihoku,kumakogen,masaki,matsuno,matsuyama,namikata,niihama,ozu,saijo,seiyo,shikokuchuo,tobe,toon,uchiko,uwajima,yawatahamaechizen,eiheiji,fukui,ikeda,katsuyama,mihama,minamiechizen,obama,ohi,ono,sabae,sakai,takahama,tsuruga,wakasaashiya,buzen,chikugo,chikuho,chikujo,chikushino,chikuzen,chuo,dazaifu,fukuchi,hakata,higashi,hirokawa,hisayama,iizuka,inatsuki,kaho,kasuga,kasuya,kawara,keisen,koga,kurate,kurogi,kurume,minami,miyako,miyama,miyawaka,mizumaki,munakata,nakagawa,nakama,nishi,nogata,ogori,okagaki,okawa,oki,omuta,onga,onojo,oto,saigawa,sasaguri,shingu,shinyoshitomi,shonai,soeda,sue,tachiarai,tagawa,takata,toho,toyotsu,tsuiki,ukiha,umi,usui,yamada,yame,yanagawa,yukuhashiaizubange,aizumisato,aizuwakamatsu,asakawa,bandai,date,fukushima,furudono,futaba,hanawa,higashi,hirata,hirono,iitate,inawashiro,ishikawa,iwaki,izumizaki,kagamiishi,kaneyama,kawamata,kitakata,kitashiobara,koori,koriyama,kunimi,miharu,mishima,namie,nango,nishiaizu,nishigo,okuma,omotego,ono,otama,samegawa,shimogo,shirakawa,showa,soma,sukagawa,taishin,tamakawa,tanagura,tenei,yabuki,yamato,yamatsuri,yanaizu,yugawaanpachi,ena,gifu,ginan,godo,gujo,hashima,hichiso,hida,higashishirakawa,ibigawa,ikeda,kakamigahara,kani,kasahara,kasamatsu,kawaue,kitagata,mino,minokamo,mitake,mizunami,motosu,nakatsugawa,ogaki,sakahogi,seki,sekigahara,shirakawa,tajimi,takayama,tarui,toki,tomika,wanouchi,yamagata,yaotsu,yoroannaka,chiyoda,fujioka,higashiagatsuma,isesaki,itakura,kanna,kanra,katashina,kawaba,kiryu,kusatsu,maebashi,meiwa,midori,minakami,naganohara,nakanojo,nanmoku,numata,oizumi,ora,ota,shibukawa,shimonita,shinto,showa,takasaki,takayama,tamamura,tatebayashi,tomioka,tsukiyono,tsumagoi,ueno,yoshiokaasaminami,daiwa,etajima,fuchu,fukuyama,hatsukaichi,higashihiroshima,hongo,jinsekikogen,kaita,kui,kumano,kure,mihara,miyoshi,naka,onomichi,osakikamijima,otake,saka,sera,seranishi,shinichi,shobara,takeharaabashiri,abira,aibetsu,akabira,akkeshi,asahikawa,ashibetsu,ashoro,assabu,atsuma,bibai,biei,bifuka,bihoro,biratori,chippubetsu,chitose,date,ebetsu,embetsu,eniwa,erimo,esan,esashi,fukagawa,fukushima,furano,furubira,haboro,hakodate,hamatonbetsu,hidaka,higashikagura,higashikawa,hiroo,hokuryu,hokuto,honbetsu,horokanai,horonobe,ikeda,imakane,ishikari,iwamizawa,iwanai,kamifurano,kamikawa,kamishihoro,kamisunagawa,kamoenai,kayabe,kembuchi,kikonai,kimobetsu,kitahiroshima,kitami,kiyosato,koshimizu,kunneppu,kuriyama,kuromatsunai,kushiro,kutchan,kyowa,mashike,matsumae,mikasa,minamifurano,mombetsu,moseushi,mukawa,muroran,naie,nakagawa,nakasatsunai,nakatombetsu,nanae,nanporo,nayoro,nemuro,niikappu,niki,nishiokoppe,noboribetsu,numata,obihiro,obira,oketo,okoppe,otaru,otobe,otofuke,otoineppu,oumu,ozora,pippu,rankoshi,rebun,rikubetsu,rishiri,rishirifuji,saroma,sarufutsu,shakotan,shari,shibecha,shibetsu,shikabe,shikaoi,shimamaki,shimizu,shimokawa,shinshinotsu,shintoku,shiranuka,shiraoi,shiriuchi,sobetsu,sunagawa,taiki,takasu,takikawa,takinoue,teshikaga,tobetsu,tohma,tomakomai,tomari,toya,toyako,toyotomi,toyoura,tsubetsu,tsukigata,urakawa,urausu,uryu,utashinai,wakkanai,wassamu,yakumo,yoichiaioi,akashi,ako,amagasaki,aogaki,asago,ashiya,awaji,fukusaki,goshiki,harima,himeji,ichikawa,inagawa,itami,kakogawa,kamigori,kamikawa,kasai,kasuga,kawanishi,miki,minamiawaji,nishinomiya,nishiwaki,ono,sanda,sannan,sasayama,sayo,shingu,shinonsen,shiso,sumoto,taishi,taka,takarazuka,takasago,takino,tamba,tatsuno,toyooka,yabu,yashiro,yoka,yokawaami,asahi,bando,chikusei,daigo,fujishiro,hitachi,hitachinaka,hitachiomiya,hitachiota,ibaraki,ina,inashiki,itako,iwama,joso,kamisu,kasama,kashima,kasumigaura,koga,miho,mito,moriya,naka,namegata,oarai,ogawa,omitama,ryugasaki,sakai,sakuragawa,shimodate,shimotsuma,shirosato,sowa,suifu,takahagi,tamatsukuri,tokai,tomobe,tone,toride,tsuchiura,tsukuba,uchihara,ushiku,yachiyo,yamagata,yawara,yukianamizu,hakui,hakusan,kaga,kahoku,kanazawa,kawakita,komatsu,nakanoto,nanao,nomi,nonoichi,noto,shika,suzu,tsubata,tsurugi,uchinada,wajimafudai,fujisawa,hanamaki,hiraizumi,hirono,ichinohe,ichinoseki,iwaizumi,iwate,joboji,kamaishi,kanegasaki,karumai,kawai,kitakami,kuji,kunohe,kuzumaki,miyako,mizusawa,morioka,ninohe,noda,ofunato,oshu,otsuchi,rikuzentakata,shiwa,shizukuishi,sumita,tanohata,tono,yahaba,yamadaayagawa,higashikagawa,kanonji,kotohira,manno,marugame,mitoyo,naoshima,sanuki,tadotsu,takamatsu,tonosho,uchinomi,utazu,zentsujiakune,amami,hioki,isa,isen,izumi,kagoshima,kanoya,kawanabe,kinko,kouyama,makurazaki,matsumoto,minamitane,nakatane,nishinoomote,satsumasendai,soo,tarumizu,yusuiaikawa,atsugi,ayase,chigasaki,ebina,fujisawa,hadano,hakone,hiratsuka,isehara,kaisei,kamakura,kiyokawa,matsuda,minamiashigara,miura,nakai,ninomiya,odawara,oi,oiso,sagamihara,samukawa,tsukui,yamakita,yamato,yokosuka,yugawara,zama,zushiaki,geisei,hidaka,higashitsuno,ino,kagami,kami,kitagawa,kochi,mihara,motoyama,muroto,nahari,nakamura,nankoku,nishitosa,niyodogawa,ochi,okawa,otoyo,otsuki,sakawa,sukumo,susaki,tosa,tosashimizu,toyo,tsuno,umaji,yasuda,yusuharaamakusa,arao,aso,choyo,gyokuto,kamiamakusa,kikuchi,kumamoto,mashiki,mifune,minamata,minamioguni,nagasu,nishihara,oguni,ozu,sumoto,takamori,uki,uto,yamaga,yamato,yatsushiroayabe,fukuchiyama,higashiyama,ide,ine,joyo,kameoka,kamo,kita,kizu,kumiyama,kyotamba,kyotanabe,kyotango,maizuru,minami,minamiyamashiro,miyazu,muko,nagaokakyo,nakagyo,nantan,oyamazaki,sakyo,seika,tanabe,uji,ujitawara,wazuka,yamashina,yawataasahi,inabe,ise,kameyama,kawagoe,kiho,kisosaki,kiwa,komono,kumano,kuwana,matsusaka,meiwa,mihama,minamiise,misugi,miyama,nabari,shima,suzuka,tado,taiki,taki,tamaki,toba,tsu,udono,ureshino,watarai,yokkaichifurukawa,higashimatsushima,ishinomaki,iwanuma,kakuda,kami,kawasaki,marumori,matsushima,minamisanriku,misato,murata,natori,ogawara,ohira,onagawa,osaki,rifu,semine,shibata,shichikashuku,shikama,shiogama,shiroishi,tagajo,taiwa,tome,tomiya,wakuya,watari,yamamoto,zaoaya,ebino,gokase,hyuga,kadogawa,kawaminami,kijo,kitagawa,kitakata,kitaura,kobayashi,kunitomi,kushima,mimata,miyakonojo,miyazaki,morotsuka,nichinan,nishimera,nobeoka,saito,shiiba,shintomi,takaharu,takanabe,takazaki,tsunoachi,agematsu,anan,aoki,asahi,azumino,chikuhoku,chikuma,chino,fujimi,hakuba,hara,hiraya,iida,iijima,iiyama,iizuna,ikeda,ikusaka,ina,karuizawa,kawakami,kiso,kisofukushima,kitaaiki,komagane,komoro,matsukawa,matsumoto,miasa,minamiaiki,minamimaki,minamiminowa,minowa,miyada,miyota,mochizuki,nagano,nagawa,nagiso,nakagawa,nakano,nozawaonsen,obuse,ogawa,okaya,omachi,omi,ookuwa,ooshika,otaki,otari,sakae,sakaki,saku,sakuho,shimosuwa,shinanomachi,shiojiri,suwa,suzaka,takagi,takamori,takayama,tateshina,tatsuno,togakushi,togura,tomi,ueda,wada,yamagata,yamanouchi,yasaka,yasuokachijiwa,futsu,goto,hasami,hirado,iki,isahaya,kawatana,kuchinotsu,matsuura,nagasaki,obama,omura,oseto,saikai,sasebo,seihi,shimabara,shinkamigoto,togitsu,tsushima,unzenando,gose,heguri,higashiyoshino,ikaruga,ikoma,kamikitayama,kanmaki,kashiba,kashihara,katsuragi,kawai,kawakami,kawanishi,koryo,kurotaki,mitsue,miyake,nara,nosegawa,oji,ouda,oyodo,sakurai,sango,shimoichi,shimokitayama,shinjo,soni,takatori,tawaramoto,tenkawa,tenri,uda,yamatokoriyama,yamatotakada,yamazoe,yoshinoaga,agano,gosen,itoigawa,izumozaki,joetsu,kamo,kariwa,kashiwazaki,minamiuonuma,mitsuke,muika,murakami,myoko,nagaoka,niigata,ojiya,omi,sado,sanjo,seiro,seirou,sekikawa,shibata,tagami,tainai,tochio,tokamachi,tsubame,tsunan,uonuma,yahiko,yoita,yuzawabeppu,bungoono,bungotakada,hasama,hiji,himeshima,hita,kamitsue,kokonoe,kuju,kunisaki,kusu,oita,saiki,taketa,tsukumi,usa,usuki,yufuakaiwa,asakuchi,bizen,hayashima,ibara,kagamino,kasaoka,kibichuo,kumenan,kurashiki,maniwa,misaki,nagi,niimi,nishiawakura,okayama,satosho,setouchi,shinjo,shoo,soja,takahashi,tamano,tsuyama,wake,yakageaguni,ginowan,ginoza,gushikami,haebaru,higashi,hirara,iheya,ishigaki,ishikawa,itoman,izena,kadena,kin,kitadaito,kitanakagusuku,kumejima,kunigami,minamidaito,motobu,nago,naha,nakagusuku,nakijin,nanjo,nishihara,ogimi,okinawa,onna,shimoji,taketomi,tarama,tokashiki,tomigusuku,tonaki,urasoe,uruma,yaese,yomitan,yonabaru,yonaguni,zamamiabeno,chihayaakasaka,chuo,daito,fujiidera,habikino,hannan,higashiosaka,higashisumiyoshi,higashiyodogawa,hirakata,ibaraki,ikeda,izumi,izumiotsu,izumisano,kadoma,kaizuka,kanan,kashiwara,katano,kawachinagano,kishiwada,kita,kumatori,matsubara,minato,minoh,misaki,moriguchi,neyagawa,nishi,nose,osakasayama,sakai,sayama,sennan,settsu,shijonawate,shimamoto,suita,tadaoka,taishi,tajiri,takaishi,takatsuki,tondabayashi,toyonaka,toyono,yaoariake,arita,fukudomi,genkai,hamatama,hizen,imari,kamimine,kanzaki,karatsu,kashima,kitagata,kitahata,kiyama,kouhoku,kyuragi,nishiarita,ogi,omachi,ouchi,saga,shiroishi,taku,tara,tosu,yoshinogariarakawa,asaka,chichibu,fujimi,fujimino,fukaya,hanno,hanyu,hasuda,hatogaya,hatoyama,hidaka,higashichichibu,higashimatsuyama,honjo,ina,iruma,iwatsuki,kamiizumi,kamikawa,kamisato,kasukabe,kawagoe,kawaguchi,kawajima,kazo,kitamoto,koshigaya,kounosu,kuki,kumagaya,matsubushi,minano,misato,miyashiro,miyoshi,moroyama,nagatoro,namegawa,niiza,ogano,ogawa,ogose,okegawa,omiya,otaki,ranzan,ryokami,saitama,sakado,satte,sayama,shiki,shiraoka,soka,sugito,toda,tokigawa,tokorozawa,tsurugashima,urawa,warabi,yashio,yokoze,yono,yorii,yoshida,yoshikawa,yoshimiaisho,gamo,higashiomi,hikone,koka,konan,kosei,koto,kusatsu,maibara,moriyama,nagahama,nishiazai,notogawa,omihachiman,otsu,ritto,ryuoh,takashima,takatsuki,torahime,toyosato,yasuakagi,ama,gotsu,hamada,higashiizumo,hikawa,hikimi,izumo,kakinoki,masuda,matsue,misato,nishinoshima,ohda,okinoshima,okuizumo,shimane,tamayu,tsuwano,unnan,yakumo,yasugi,yatsukaarai,atami,fuji,fujieda,fujikawa,fujinomiya,fukuroi,gotemba,haibara,hamamatsu,higashiizu,ito,iwata,izu,izunokuni,kakegawa,kannami,kawanehon,kawazu,kikugawa,kosai,makinohara,matsuzaki,minamiizu,mishima,morimachi,nishiizu,numazu,omaezaki,shimada,shimizu,shimoda,shizuoka,susono,yaizu,yoshidaashikaga,bato,haga,ichikai,iwafune,kaminokawa,kanuma,karasuyama,kuroiso,mashiko,mibu,moka,motegi,nasu,nasushiobara,nikko,nishikata,nogi,ohira,ohtawara,oyama,sakura,sano,shimotsuke,shioya,takanezawa,tochigi,tsuga,ujiie,utsunomiya,yaitaaizumi,anan,ichiba,itano,kainan,komatsushima,matsushige,mima,minami,miyoshi,mugi,nakagawa,naruto,sanagochi,shishikui,tokushima,wajikiadachi,akiruno,akishima,aogashima,arakawa,bunkyo,chiyoda,chofu,chuo,edogawa,fuchu,fussa,hachijo,hachioji,hamura,higashikurume,higashimurayama,higashiyamato,hino,hinode,hinohara,inagi,itabashi,katsushika,kita,kiyose,kodaira,koganei,kokubunji,komae,koto,kouzushima,kunitachi,machida,meguro,minato,mitaka,mizuho,musashimurayama,musashino,nakano,nerima,ogasawara,okutama,ome,oshima,ota,setagaya,shibuya,shinagawa,shinjuku,suginami,sumida,tachikawa,taito,tama,toshimachizu,hino,kawahara,koge,kotoura,misasa,nanbu,nichinan,sakaiminato,tottori,wakasa,yazu,yonagoasahi,fuchu,fukumitsu,funahashi,himi,imizu,inami,johana,kamiichi,kurobe,nakaniikawa,namerikawa,nanto,nyuzen,oyabe,taira,takaoka,tateyama,toga,tonami,toyama,unazuki,uozu,yamadaarida,aridagawa,gobo,hashimoto,hidaka,hirogawa,inami,iwade,kainan,kamitonda,katsuragi,kimino,kinokawa,kitayama,koya,koza,kozagawa,kudoyama,kushimoto,mihama,misato,nachikatsuura,shingu,shirahama,taiji,tanabe,wakayama,yuasa,yuraasahi,funagata,higashine,iide,kahoku,kaminoyama,kaneyama,kawanishi,mamurogawa,mikawa,murayama,nagai,nakayama,nanyo,nishikawa,obanazawa,oe,oguni,ohkura,oishida,sagae,sakata,sakegawa,shinjo,shirataka,shonai,takahata,tendo,tozawa,tsuruoka,yamagata,yamanobe,yonezawa,yuzaabu,hagi,hikari,hofu,iwakuni,kudamatsu,mitou,nagato,oshima,shimonoseki,shunan,tabuse,tokuyama,toyota,ube,yuuchuo,doshi,fuefuki,fujikawa,fujikawaguchiko,fujiyoshida,hayakawa,hokuto,ichikawamisato,kai,kofu,koshu,kosuge,minami-alps,minobu,nakamichi,nanbu,narusawa,nirasaki,nishikatsura,oshino,otsuki,showa,tabayama,tsuru,uenohara,yamanakako,yamanashi*,!city*,!city*,!city*,!city*,!city*,!city*,!city<ac,co,go,info,me,mobi,ne,or,scorg,net,com,edu,gov,mil*edu,biz,net,org,gov,info,comorg,nom,gov,prd,tm,edu,mil,ass,com,coop,asso,presse,medecin,notaires,pharmaciens,veterinaire,gouvnet,org,edu,govcom,edu,gov,org,rep,traac,co,es,go,hs,kg,mil,ms,ne,or,pe,re,sc,busan,chungbuk,chungnam,daegu,daejeon,gangwon,gwangju,gyeongbuk,gyeonggi,gyeongnam,incheon,jeju,jeonbuk,jeonnam,seoul,ulsancom,edu,emb,gov,ind,net,orgcom,edu,net,orgorg,edu,net,gov,mil,comint,net,info,edu,gov,per,com,orgcom,edu,gov,net,orgcom,net,co,org,edu,govgov,sch,net,int,com,org,edu,ngo,soc,web,ltd,assn,grp,hotel,accom,edu,gov,org,netac,biz,co,edu,gov,info,net,org,scgovcom,edu,gov,org,mil,id,net,asn,confcom,net,gov,plc,edu,sch,med,org,idco,net,gov,org,ac,presstm,assoco,net,org,edu,ac,gov,its,privorg,nom,gov,prd,tm,edu,mil,com,cocom,org,net,edu,gov,inf,namecom,edu,gouv,gov,net,org,presse*gov,edu,orgcom,net,org,edu,govgovcom,edu,gov,net,orgcom,edu,net,orgcom,net,org,gov,ac,co,oracademy,agriculture,air,airguard,alabama,alaska,amber,ambulance,american,americana,americanantiques,americanart,amsterdam,and,annefrank,anthro,anthropology,antiques,aquarium,arboretum,archaeological,archaeology,architecture,art,artanddesign,artcenter,artdeco,arteducation,artgallery,arts,artsandcrafts,asmatart,assassination,assisi,association,astronomy,atlanta,austin,australia,automotive,aviation,axis,badajoz,baghdad,bahn,bale,baltimore,barcelona,baseball,basel,baths,bauern,beauxarts,beeldengeluid,bellevue,bergbau,berkeley,berlin,bern,bible,bilbao,bill,birdart,birthplace,bonn,boston,botanical,botanicalgarden,botanicgarden,botany,brandywinevalley,brasil,bristol,british,britishcolumbia,broadcast,brunel,brussel,brussels,bruxelles,building,burghof,bus,bushey,cadaques,california,cambridge,can,canada,capebreton,carrier,cartoonart,casadelamoneda,castle,castres,celtic,center,chattanooga,cheltenham,chesapeakebay,chicago,children,childrens,childrensgarden,chiropractic,chocolate,christiansburg,cincinnati,cinema,circus,civilisation,civilization,civilwar,clinton,clock,coal,coastaldefence,cody,coldwar,collection,colonialwilliamsburg,coloradoplateau,columbia,columbus,communication,communications,community,computer,computerhistory,xn--comunicaes-v6a2o,contemporary,contemporaryart,convent,copenhagen,corporation,xn--correios-e-telecomunicaes-ghc29a,corvette,costume,countryestate,county,crafts,cranbrook,creation,cultural,culturalcenter,culture,cyber,cymru,dali,dallas,database,ddr,decorativearts,delaware,delmenhorst,denmark,depot,design,detroit,dinosaur,discovery,dolls,donostia,durham,eastafrica,eastcoast,education,educational,egyptian,eisenbahn,elburg,elvendrell,embroidery,encyclopedic,england,entomology,environment,environmentalconservation,epilepsy,essex,estate,ethnology,exeter,exhibition,family,farm,farmequipment,farmers,farmstead,field,figueres,filatelia,film,fineart,finearts,finland,flanders,florida,force,fortmissoula,fortworth,foundation,francaise,frankfurt,franziskaner,freemasonry,freiburg,fribourg,frog,fundacio,furniture,gallery,garden,gateway,geelvinck,gemological,geology,georgia,giessen,glas,glass,gorge,grandrapids,graz,guernsey,halloffame,hamburg,handson,harvestcelebration,hawaii,health,heimatunduhren,hellas,helsinki,hembygdsforbund,heritage,histoire,historical,historicalsociety,historichouses,historisch,historisches,history,historyofscience,horology,house,humanities,illustration,imageandsound,indian,indiana,indianapolis,indianmarket,intelligence,interactive,iraq,iron,isleofman,jamison,jefferson,jerusalem,jewelry,jewish,jewishart,jfk,journalism,judaica,judygarland,juedisches,juif,karate,karikatur,kids,koebenhavn,koeln,kunst,kunstsammlung,kunstunddesign,labor,labour,lajolla,lancashire,landes,lans,xn--lns-qla,larsson,lewismiller,lincoln,linz,living,livinghistory,localhistory,london,losangeles,louvre,loyalist,lucerne,luxembourg,luzern,mad,madrid,mallorca,manchester,mansion,mansions,manx,marburg,maritime,maritimo,maryland,marylhurst,media,medical,medizinhistorisches,meeres,memorial,mesaverde,michigan,midatlantic,military,mill,miners,mining,minnesota,missile,missoula,modern,moma,money,monmouth,monticello,montreal,moscow,motorcycle,muenchen,muenster,mulhouse,muncie,museet,museumcenter,museumvereniging,music,national,nationalfirearms,nationalheritage,nativeamerican,naturalhistory,naturalhistorymuseum,naturalsciences,nature,naturhistorisches,natuurwetenschappen,naumburg,naval,nebraska,neues,newhampshire,newjersey,newmexico,newport,newspaper,newyork,niepce,norfolk,north,nrw,nyc,nyny,oceanographic,oceanographique,omaha,online,ontario,openair,oregon,oregontrail,otago,oxford,pacific,paderborn,palace,paleo,palmsprings,panama,paris,pasadena,pharmacy,philadelphia,philadelphiaarea,philately,phoenix,photography,pilots,pittsburgh,planetarium,plantation,plants,plaza,portal,portland,portlligat,posts-and-telecommunications,preservation,presidio,press,project,public,pubol,quebec,railroad,railway,research,resistance,riodejaneiro,rochester,rockart,roma,russia,saintlouis,salem,salvadordali,salzburg,sandiego,sanfrancisco,santabarbara,santacruz,santafe,saskatchewan,satx,savannahga,schlesisches,schoenbrunn,schokoladen,school,schweiz,science,scienceandhistory,scienceandindustry,sciencecenter,sciencecenters,science-fiction,sciencehistory,sciences,sciencesnaturelles,scotland,seaport,settlement,settlers,shell,sherbrooke,sibenik,silk,ski,skole,society,sologne,soundandvision,southcarolina,southwest,space,spy,square,stadt,stalbans,starnberg,state,stateofdelaware,station,steam,steiermark,stjohn,stockholm,stpetersburg,stuttgart,suisse,surgeonshall,surrey,svizzera,sweden,sydney,tank,tcm,technology,telekommunikation,television,texas,textile,theater,time,timekeeping,topology,torino,touch,town,transport,tree,trolley,trust,trustee,uhren,ulm,undersea,university,usa,usantiques,usarts,uscountryestate,usculture,usdecorativearts,usgarden,ushistory,ushuaia,uslivinghistory,utah,uvic,valley,vantaa,versailles,viking,village,virginia,virtual,virtuel,vlaanderen,volkenkunde,wales,wallonie,war,washingtondc,watchandclock,watch-and-clock,western,westfalen,whaling,wildlife,williamsburg,windmill,workshop,york,yorkshire,yosemite,youth,zoological,zoology,xn--9dbhblg6di,xn--h1aeghaero,biz,com,coop,edu,gov,info,int,mil,museum,name,net,org,proac,biz,co,com,coop,edu,gov,int,museum,net,orgcom,org,gob,edu,netbiz,com,edu,gov,mil,name,net,orgac,adv,co,edu,gov,mil,net,orginfo,pro,name,school,or,dr,us,mx,ca,in,cc,tv,ws,mobi,co,com,orgasso,nomcom,net,per,rec,web,arts,firm,info,other,storecom,edu,gov,i,mil,mobi,name,net,org,schac,biz,co,com,edu,gob,in,info,int,mil,net,nom,org,webfhs,vgs,fylkesbibl,folkebibl,museum,idrett,priv,mil,stat,dep,kommune,herad,aa>gsgsgsgsgsgsgsgsgsgsgsgsgsgsgsgsgsgsgsgsgsbo,xn--b-5gabo,xn--b-5ga,heroy,xn--hery-iraheroy,sandexn--hery-ira,sandenesnesos,valer,xn--vler-qoaossandevalerxn--vler-qoa<*biz,info,gov,edu,org,net,comac,co,cri,geek,gen,govt,health,iwi,kiwi,maori,mil,xn--mori-qsa,net,org,parliament,schoolco,com,edu,gov,med,museum,net,org,proac,gob,com,org,sld,edu,net,ing,abo,med,nomedu,gob,nom,mil,org,com,netcom,org,edu*com,net,org,gov,edu,ngo,mil,icom,net,edu,org,fam,biz,web,gov,gob,gok,gon,gop,gos,infocom,net,org,aid,agro,atm,auto,biz,edu,gmina,gsm,info,mail,miasta,media,mil,nieruchomosci,nom,pc,powiat,priv,realestate,rel,sex,shop,sklep,sos,szkola,targi,tm,tourism,travel,turystyka,gov>ap,ic,is,us,kmpsp,kppsp,kwpsp,psp,wskr,kwp,mw,ug,um,umig,ugim,upow,uw,starostwo,pa,po,psse,pup,rzgw,sa,so,sr,wsa,sko,uzs,wiih,winb,pinb,wios,witd,wzmiuw,piw,wiw,griw,wif,oum,sdn,zp,uppo,mup,wuoz,konsulat,oirmgov,co,org,edu,netcom,net,org,gov,edu,isla,pro,biz,info,name,est,prof,acaaa,aca,acct,avocat,bar,cpa,eng,jur,law,med,recht