From 95ecd0815c9dbf89429312aa1bbd069d72554e84 Mon Sep 17 00:00:00 2001 From: Edlison Date: Mon, 6 Jul 2026 01:19:13 +0900 Subject: [PATCH 1/2] feat: add IGN site --- .assets-revision | 13 +- Dockerfile | 4 +- control_server.py | 2 +- sites/ign/_health.py | 3 + sites/ign/app.py | 1330 ++++++++++++++ sites/ign/content_seed.py | 1879 ++++++++++++++++++++ sites/ign/requirements.txt | 6 + sites/ign/seed_data.py | 427 +++++ sites/ign/static/css/.gitkeep | 0 sites/ign/static/css/ign.css | 2341 +++++++++++++++++++++++++ sites/ign/static/icons/.gitkeep | 0 sites/ign/static/js/.gitkeep | 0 sites/ign/static/js/ign.js | 22 + sites/ign/tasks.jsonl | 19 + sites/ign/templates/.gitkeep | 0 sites/ign/templates/_item_card.html | 27 + sites/ign/templates/account.html | 82 + sites/ign/templates/account_edit.html | 22 + sites/ign/templates/alerts.html | 57 + sites/ign/templates/base.html | 72 + sites/ign/templates/detail.html | 107 ++ sites/ign/templates/guide_detail.html | 72 + sites/ign/templates/hub_listing.html | 97 + sites/ign/templates/index.html | 71 + sites/ign/templates/listing.html | 28 + sites/ign/templates/login.html | 15 + sites/ign/templates/playlist.html | 39 + sites/ign/templates/register.html | 15 + sites/ign/templates/saved.html | 29 + sites/ign/templates/search.html | 33 + sites/ign/templates/utility_page.html | 79 + sites/ign/templates/video_detail.html | 60 + websyn_start.sh | 12 +- 33 files changed, 6947 insertions(+), 16 deletions(-) create mode 100644 sites/ign/_health.py create mode 100644 sites/ign/app.py create mode 100644 sites/ign/content_seed.py create mode 100644 sites/ign/requirements.txt create mode 100644 sites/ign/seed_data.py create mode 100644 sites/ign/static/css/.gitkeep create mode 100644 sites/ign/static/css/ign.css create mode 100644 sites/ign/static/icons/.gitkeep create mode 100644 sites/ign/static/js/.gitkeep create mode 100644 sites/ign/static/js/ign.js create mode 100644 sites/ign/tasks.jsonl create mode 100644 sites/ign/templates/.gitkeep create mode 100644 sites/ign/templates/_item_card.html create mode 100644 sites/ign/templates/account.html create mode 100644 sites/ign/templates/account_edit.html create mode 100644 sites/ign/templates/alerts.html create mode 100644 sites/ign/templates/base.html create mode 100644 sites/ign/templates/detail.html create mode 100644 sites/ign/templates/guide_detail.html create mode 100644 sites/ign/templates/hub_listing.html create mode 100644 sites/ign/templates/index.html create mode 100644 sites/ign/templates/listing.html create mode 100644 sites/ign/templates/login.html create mode 100644 sites/ign/templates/playlist.html create mode 100644 sites/ign/templates/register.html create mode 100644 sites/ign/templates/saved.html create mode 100644 sites/ign/templates/search.html create mode 100644 sites/ign/templates/utility_page.html create mode 100644 sites/ign/templates/video_detail.html diff --git a/.assets-revision b/.assets-revision index 5c22104e..6182b2f8 100644 --- a/.assets-revision +++ b/.assets-revision @@ -1,8 +1,7 @@ -# Hugging Face dataset holding webharbor's heavy static assets -# (instance_seed/*.db, static/images/, static/external_cache/). -# -# fetch_assets.sh uses the `hf download` CLI. The pin below -# is a git revision (branch name like `main`, a tag, or a specific commit -# sha). Override at runtime with the ASSETS_REVISION env var. repo: ChilleD/WebHarbor -revision: 54882a6a66a17a3e43455057e7c9e0d103cd8b81 +# Pinned to HF PR #41 (adds ign.tar.gz on top of current main) so this +# PR builds end-to-end before the assets PR is merged. This ref contains all +# 17 site tarballs. Maintainer: once +# https://huggingface.co/datasets/ChilleD/WebHarbor/discussions/41 is merged, +# bump this to the merged commit SHA on main. +revision: refs/pr/41 diff --git a/Dockerfile b/Dockerfile index 1e86b1d0..43088ffb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # WebHarbor — slim, self-contained image. -# 16 Flask mirror sites + control plane on :8101. +# 17 Flask mirror sites + control plane on :8101. FROM python:3.12-slim-bookworm @@ -33,6 +33,6 @@ COPY control_server.py /opt/control_server.py COPY site_runner.py /opt/site_runner.py RUN chmod +x /opt/websyn_start.sh -EXPOSE 8101 40000-40015 +EXPOSE 8101 40000-40016 CMD ["/opt/websyn_start.sh"] diff --git a/control_server.py b/control_server.py index 4b6b995e..d1e30359 100644 --- a/control_server.py +++ b/control_server.py @@ -26,7 +26,7 @@ 'allrecipes', 'amazon', 'apple', 'arxiv', 'bbc_news', 'booking', 'github', 'google_flights', 'google_map', 'google_search', 'huggingface', 'wolfram_alpha', 'cambridge_dictionary', - 'coursera', 'espn', 'merriam_webster', + 'coursera', 'espn', 'merriam_webster', 'ign', ] BASE_PORT = 40000 WEBSYN_DIR = '/opt/WebSyn' diff --git a/sites/ign/_health.py b/sites/ign/_health.py new file mode 100644 index 00000000..1aedc10d --- /dev/null +++ b/sites/ign/_health.py @@ -0,0 +1,3 @@ +"""Per-site health probe (optional, called by control_server).""" +def health(): + return {"ok": True, "site": "ign"} diff --git a/sites/ign/app.py b/sites/ign/app.py new file mode 100644 index 00000000..99f2cb8e --- /dev/null +++ b/sites/ign/app.py @@ -0,0 +1,1330 @@ +"""IGN mirror — Flask app for news, reviews, videos, guides, and account flows.""" +import json +import os +import re +from datetime import datetime +from pathlib import Path + +from flask import ( + Flask, + abort, + flash, + redirect, + render_template, + request, + url_for, +) +from flask_bcrypt import Bcrypt +from flask_login import ( + LoginManager, + UserMixin, + current_user, + login_required, + login_user, + logout_user, +) +from flask_sqlalchemy import SQLAlchemy +from flask_wtf import CSRFProtect +from sqlalchemy import or_ + +BASE_DIR = Path(__file__).parent +DB_DIR = BASE_DIR / "instance" +DB_DIR.mkdir(exist_ok=True) + +MIRROR_REFERENCE_DATE = datetime(2026, 7, 2, 12, 0, 0) +STOP_WORDS = { + "the", + "a", + "an", + "and", + "or", + "for", + "of", + "to", + "in", + "on", + "with", + "is", + "it", + "by", + "from", + "this", + "that", +} + +NEWS_CATEGORIES = [ + ("all", "All News"), + ("columns", "Columns"), + ("playstation", "PlayStation"), + ("xbox", "Xbox"), + ("nintendo", "Nintendo"), + ("pc", "PC"), + ("mobile", "Mobile"), + ("movies", "Movies"), + ("television", "Television"), + ("comics", "Comics"), + ("tech", "Tech"), +] + +REVIEW_CATEGORIES = [ + ("all", "All Reviews"), + ("editors-choice", "Editor's Choice"), + ("games", "Game Reviews"), + ("movies", "Movie Reviews"), + ("tv", "TV Show Reviews"), + ("tech", "Tech Reviews"), +] + +REVIEW_SORT_OPTIONS = [ + ("latest", "Sort by Latest"), + ("score-desc", "Highest Scores"), + ("score-asc", "Lowest Scores"), + ("popular", "Most Popular"), +] + +REVIEW_SCORE_FILTERS = [ + ("all", "All Scores"), + ("9-plus", "9+"), + ("8-plus", "8+"), + ("7-plus", "7+"), + ("under-8", "Under 8"), +] + +REVIEW_GENRE_FILTERS = [ + ("all", "All Genres"), + ("game", "Game"), + ("movie", "Movie"), + ("tv", "TV Show"), + ("tech", "Tech"), +] + +app = Flask(__name__, instance_path=str(DB_DIR)) +app.config["SECRET_KEY"] = "ign-webharbor-dev-key" +app.config["SQLALCHEMY_DATABASE_URI"] = f"sqlite:///{DB_DIR / 'ign.db'}" +app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False +app.config["WTF_CSRF_TIME_LIMIT"] = None + +db = SQLAlchemy(app) +bcrypt = Bcrypt(app) +login_manager = LoginManager(app) +login_manager.login_view = "login" +login_manager.login_message = "Log in to use your IGN account tools." +csrf = CSRFProtect(app) + + +class User(db.Model, UserMixin): + __tablename__ = "users" + + id = db.Column(db.Integer, primary_key=True) + email = db.Column(db.String(160), unique=True, nullable=False, index=True) + username = db.Column(db.String(80), unique=True, nullable=False, index=True) + password_hash = db.Column(db.String(255), nullable=False) + display_name = db.Column(db.String(120), nullable=False) + region = db.Column(db.String(80), default="United States") + favorite_platform = db.Column(db.String(80), default="PC") + bio = db.Column(db.Text, default="") + avatar_color = db.Column(db.String(20), default="#bf1313") + notification_email = db.Column(db.Boolean, default=True) + created_at = db.Column(db.DateTime, default=MIRROR_REFERENCE_DATE) + + saved_items = db.relationship("SavedItem", backref="user", cascade="all, delete-orphan") + playlist_entries = db.relationship( + "PlaylistEntry", backref="user", cascade="all, delete-orphan" + ) + comments = db.relationship("Comment", backref="user", cascade="all, delete-orphan") + alerts = db.relationship("AlertSubscription", backref="user", cascade="all, delete-orphan") + digests = db.relationship("Digest", backref="user", cascade="all, delete-orphan") + guide_progress = db.relationship( + "GuideProgress", backref="user", cascade="all, delete-orphan" + ) + + def set_password(self, raw_password: str) -> None: + self.password_hash = bcrypt.generate_password_hash(raw_password).decode("utf-8") + + def check_password(self, raw_password: str) -> bool: + return bcrypt.check_password_hash(self.password_hash, raw_password) + + @property + def initials(self) -> str: + parts = (self.display_name or self.username).split() + if len(parts) >= 2: + return (parts[0][0] + parts[-1][0]).upper() + return (self.username[:2] or "IG").upper() + + +class Section(db.Model): + __tablename__ = "sections" + + id = db.Column(db.Integer, primary_key=True) + slug = db.Column(db.String(60), unique=True, nullable=False, index=True) + name = db.Column(db.String(80), nullable=False) + description = db.Column(db.Text, default="") + color = db.Column(db.String(20), default="#bf1313") + sort_order = db.Column(db.Integer, default=99) + + +class ContentItem(db.Model): + __tablename__ = "content_items" + + id = db.Column(db.Integer, primary_key=True) + slug = db.Column(db.String(240), unique=True, nullable=False, index=True) + source_path = db.Column(db.String(360), unique=True, nullable=False, index=True) + title = db.Column(db.Text, nullable=False) + description = db.Column(db.Text, default="") + body_json = db.Column(db.Text, default="[]") + section_slug = db.Column(db.String(60), index=True, nullable=False) + content_type = db.Column(db.String(20), index=True, nullable=False) + author = db.Column(db.String(120), default="IGN Staff") + published_at = db.Column(db.DateTime, default=MIRROR_REFERENCE_DATE, index=True) + image_path = db.Column(db.String(360), default="") + score = db.Column(db.Integer, nullable=True) + score_label = db.Column(db.String(80), default="") + comments_count = db.Column(db.Integer, default=0) + duration = db.Column(db.String(20), default="") + platforms_json = db.Column(db.Text, default="[]") + tags_json = db.Column(db.Text, default="[]") + checklist_json = db.Column(db.Text, default="[]") + is_featured = db.Column(db.Boolean, default=False) + is_top_story = db.Column(db.Boolean, default=False) + is_editors_choice = db.Column(db.Boolean, default=False) + sort_rank = db.Column(db.Integer, default=0) + + saved_by = db.relationship("SavedItem", backref="item", cascade="all, delete-orphan") + playlist_entries = db.relationship( + "PlaylistEntry", backref="item", cascade="all, delete-orphan" + ) + comments = db.relationship("Comment", backref="item", cascade="all, delete-orphan") + progress_entries = db.relationship( + "GuideProgress", backref="item", cascade="all, delete-orphan" + ) + + def _load_json_list(self, raw: str) -> list: + try: + value = json.loads(raw or "[]") + except Exception: + return [] + return value if isinstance(value, list) else [] + + @property + def body(self) -> list: + return self._load_json_list(self.body_json) + + @property + def platforms(self) -> list: + return self._load_json_list(self.platforms_json) + + @property + def tags(self) -> list: + return self._load_json_list(self.tags_json) + + @property + def checklist(self) -> list: + return self._load_json_list(self.checklist_json) + + @property + def short_description(self) -> str: + if len(self.description) <= 180: + return self.description + return self.description[:177].rstrip() + "..." + + @property + def pretty_type(self) -> str: + return { + "article": "News", + "review": "Review", + "video": "Video", + "guide": "Guide", + }.get(self.content_type, self.content_type.title()) + + +class SavedItem(db.Model): + __tablename__ = "saved_items" + + id = db.Column(db.Integer, primary_key=True) + user_id = db.Column(db.Integer, db.ForeignKey("users.id"), nullable=False) + item_id = db.Column(db.Integer, db.ForeignKey("content_items.id"), nullable=False) + folder = db.Column(db.String(80), default="Read Later") + note = db.Column(db.Text, default="") + created_at = db.Column(db.DateTime, default=MIRROR_REFERENCE_DATE) + + __table_args__ = (db.UniqueConstraint("user_id", "item_id", name="uq_saved_user_item"),) + + +class PlaylistEntry(db.Model): + __tablename__ = "playlist_entries" + + id = db.Column(db.Integer, primary_key=True) + user_id = db.Column(db.Integer, db.ForeignKey("users.id"), nullable=False) + item_id = db.Column(db.Integer, db.ForeignKey("content_items.id"), nullable=False) + status = db.Column(db.String(30), default="queued") + note = db.Column(db.Text, default="") + position = db.Column(db.Integer, default=0) + created_at = db.Column(db.DateTime, default=MIRROR_REFERENCE_DATE) + + __table_args__ = (db.UniqueConstraint("user_id", "item_id", name="uq_playlist_user_item"),) + + +class Comment(db.Model): + __tablename__ = "comments" + + id = db.Column(db.Integer, primary_key=True) + user_id = db.Column(db.Integer, db.ForeignKey("users.id"), nullable=False) + item_id = db.Column(db.Integer, db.ForeignKey("content_items.id"), nullable=False) + body = db.Column(db.Text, nullable=False) + sentiment = db.Column(db.String(20), default="neutral") + created_at = db.Column(db.DateTime, default=MIRROR_REFERENCE_DATE) + + +class AlertSubscription(db.Model): + __tablename__ = "alert_subscriptions" + + id = db.Column(db.Integer, primary_key=True) + user_id = db.Column(db.Integer, db.ForeignKey("users.id"), nullable=False) + section_slug = db.Column(db.String(60), default="") + keyword = db.Column(db.String(120), default="") + frequency = db.Column(db.String(40), default="daily") + active = db.Column(db.Boolean, default=True) + created_at = db.Column(db.DateTime, default=MIRROR_REFERENCE_DATE) + + +class Digest(db.Model): + __tablename__ = "digests" + + id = db.Column(db.Integer, primary_key=True) + user_id = db.Column(db.Integer, db.ForeignKey("users.id"), nullable=False) + digest_number = db.Column(db.String(40), unique=True, nullable=False) + title = db.Column(db.String(160), nullable=False) + delivery = db.Column(db.String(60), default="email") + status = db.Column(db.String(40), default="delivered") + item_slugs_json = db.Column(db.Text, default="[]") + created_at = db.Column(db.DateTime, default=MIRROR_REFERENCE_DATE) + + @property + def item_slugs(self) -> list: + try: + return json.loads(self.item_slugs_json or "[]") + except Exception: + return [] + + +class GuideProgress(db.Model): + __tablename__ = "guide_progress" + + id = db.Column(db.Integer, primary_key=True) + user_id = db.Column(db.Integer, db.ForeignKey("users.id"), nullable=False) + item_id = db.Column(db.Integer, db.ForeignKey("content_items.id"), nullable=False) + checkpoint = db.Column(db.String(160), nullable=False) + completed = db.Column(db.Boolean, default=False) + updated_at = db.Column(db.DateTime, default=MIRROR_REFERENCE_DATE) + + __table_args__ = ( + db.UniqueConstraint("user_id", "item_id", "checkpoint", name="uq_guide_checkpoint"), + ) + + +@login_manager.user_loader +def load_user(user_id: str): + return db.session.get(User, int(user_id)) + + +def tokenize(text: str) -> list[str]: + return [ + token + for token in re.split(r"\W+", (text or "").lower()) + if len(token) > 1 and token not in STOP_WORDS + ] + + +def scored_search(query: str, items: list[ContentItem]) -> list[ContentItem]: + tokens = tokenize(query) + if not tokens: + return items + scored = [] + for item in items: + haystack = " ".join( + [ + item.title, + item.description, + item.author, + item.section_slug, + item.content_type, + " ".join(item.tags), + " ".join(item.platforms), + ] + ).lower() + score = sum(3 if token in item.title.lower() else 1 for token in tokens if token in haystack) + if score > 0: + scored.append((score, item.published_at or MIRROR_REFERENCE_DATE, item)) + scored.sort(key=lambda row: (-row[0], -row[1].timestamp())) + return [row[2] for row in scored] + + +def get_sections() -> list[Section]: + return Section.query.order_by(Section.sort_order.asc()).all() + + +def content_query(): + return ContentItem.query.order_by(ContentItem.published_at.desc(), ContentItem.id.asc()) + + +def detail_url(item: ContentItem) -> str: + if item.content_type == "video": + return url_for("video_detail", slug=item.slug) + if item.content_type == "guide": + wiki_path = item.source_path.removeprefix("/wikis/") + return url_for("guide_detail", wiki_path=wiki_path) + return url_for("article_detail", slug=item.slug) + + +def section_label(slug: str) -> str: + section = Section.query.filter_by(slug=slug).first() + return section.name if section else slug.replace("_", " ").title() + + +def text_terms_filter(*terms): + clauses = [] + for term in terms: + pattern = f"%{term}%" + clauses.extend( + [ + ContentItem.title.ilike(pattern), + ContentItem.description.ilike(pattern), + ContentItem.tags_json.ilike(pattern), + ContentItem.platforms_json.ilike(pattern), + ] + ) + return or_(*clauses) + + +def apply_news_category(query, category: str): + if category in {"", "all"}: + return query + if category == "columns": + return query.filter(text_terms_filter("history", "view", "future", "controversies")) + if category == "playstation": + return query.filter(text_terms_filter("PlayStation", "PS5", "Sony")) + if category == "xbox": + return query.filter(text_terms_filter("Xbox", "Microsoft")) + if category == "nintendo": + return query.filter(text_terms_filter("Nintendo", "Switch", "Pokémon", "Pokemon")) + if category == "pc": + return query.filter(text_terms_filter("PC", "RTX", "GeForce", "Steam")) + if category == "mobile": + return query.filter(text_terms_filter("mobile", "iOS", "Android")) + if category == "movies": + return query.filter_by(section_slug="movies") + if category == "television": + return query.filter_by(section_slug="tv") + if category == "comics": + return query.filter(text_terms_filter("Marvel", "comic", "comics")) + if category == "tech": + return query.filter(text_terms_filter("tech", "hardware", "RTX", "GeForce", "PC")) + abort(404) + + +def apply_review_category(query, category: str): + if category in {"", "all"}: + return query + if category == "editors-choice": + return query.filter(or_(ContentItem.is_editors_choice.is_(True), ContentItem.score >= 8)) + if category == "games": + return query.filter(ContentItem.section_slug.in_(["games", "reviews"])) + if category == "movies": + return query.filter_by(section_slug="movies") + if category == "tv": + return query.filter_by(section_slug="tv") + if category == "tech": + return query.filter(text_terms_filter("tech", "hardware", "RTX", "PC")) + abort(404) + + +def apply_review_score_filter(query, score_filter: str): + if score_filter == "all": + return query + if score_filter == "9-plus": + return query.filter(ContentItem.score >= 9) + if score_filter == "8-plus": + return query.filter(ContentItem.score >= 8) + if score_filter == "7-plus": + return query.filter(ContentItem.score >= 7) + if score_filter == "under-8": + return query.filter(ContentItem.score < 8) + abort(404) + + +def apply_review_genre_filter(query, genre_filter: str): + if genre_filter == "all": + return query + if genre_filter == "game": + return query.filter(or_(ContentItem.section_slug == "games", ContentItem.tags_json.ilike("%games%"))) + if genre_filter == "movie": + return query.filter(or_(ContentItem.section_slug == "movies", ContentItem.tags_json.ilike("%movies%"))) + if genre_filter == "tv": + return query.filter(or_(ContentItem.section_slug == "tv", ContentItem.tags_json.ilike("%tv%"))) + if genre_filter == "tech": + return query.filter(or_(ContentItem.tags_json.ilike("%tech%"), ContentItem.tags_json.ilike("%hardware%"))) + abort(404) + + +def apply_review_sort(query, sort_key: str): + if sort_key == "latest": + return query.order_by(ContentItem.published_at.desc(), ContentItem.id.asc()) + if sort_key == "score-desc": + return query.order_by(ContentItem.score.desc(), ContentItem.published_at.desc(), ContentItem.id.asc()) + if sort_key == "score-asc": + return query.order_by(ContentItem.score.asc(), ContentItem.published_at.desc(), ContentItem.id.asc()) + if sort_key == "popular": + return query.order_by(ContentItem.comments_count.desc(), ContentItem.published_at.desc(), ContentItem.id.asc()) + abort(404) + + +def make_subnav(title: str, categories: list[tuple[str, str]], active: str, endpoint: str): + items = [] + for key, label in categories: + href = url_for(endpoint) if key == "all" else url_for(endpoint, category=key) + items.append({"key": key, "label": label, "href": href}) + return {"title": title, "active": active or "all", "links": items} + + +def first_item_by_slug(slug: str) -> ContentItem | None: + return ContentItem.query.filter_by(slug=slug).first() + + +def card_from_item(item: ContentItem, eyebrow: str, body: str = "") -> dict: + return { + "eyebrow": eyebrow, + "title": item.title, + "body": body or item.short_description, + "image": item.image_path, + "href": detail_url(item), + "meta": f"{item.pretty_type} · {section_label(item.section_slug)}", + } + + +def fallback_image(index: int = 0) -> str: + item = ( + ContentItem.query.filter(ContentItem.image_path != "") + .order_by(ContentItem.sort_rank.asc(), ContentItem.id.asc()) + .offset(index) + .first() + ) + return item.image_path if item else "" + + +def is_saved(item: ContentItem) -> bool: + if not current_user.is_authenticated: + return False + return SavedItem.query.filter_by(user_id=current_user.id, item_id=item.id).first() is not None + + +def in_playlist(item: ContentItem) -> bool: + if not current_user.is_authenticated: + return False + return PlaylistEntry.query.filter_by(user_id=current_user.id, item_id=item.id).first() is not None + + +@app.context_processor +def inject_helpers(): + return { + "detail_url": detail_url, + "section_label": section_label, + "sections": get_sections, + "is_saved": is_saved, + "in_playlist": in_playlist, + "mirror_date": MIRROR_REFERENCE_DATE, + } + + +@app.route("/") +def index(): + top_stories = ( + ContentItem.query.filter_by(is_top_story=True) + .order_by(ContentItem.sort_rank.asc()) + .limit(20) + .all() + ) + latest = content_query().limit(12).all() + reviews = ( + ContentItem.query.filter_by(content_type="review") + .order_by(ContentItem.score.desc(), ContentItem.published_at.desc()) + .limit(8) + .all() + ) + guides = ( + ContentItem.query.filter_by(content_type="guide") + .order_by(ContentItem.published_at.desc()) + .limit(8) + .all() + ) + videos = ( + ContentItem.query.filter_by(content_type="video") + .order_by(ContentItem.published_at.desc()) + .limit(6) + .all() + ) + return render_template( + "index.html", + top_stories=top_stories, + latest=latest, + reviews=reviews, + guides=guides, + videos=videos, + ) + + +@app.route("/news") +@app.route("/news/") +def news(category: str = "all"): + if category not in dict(NEWS_CATEGORIES): + abort(404) + base_query = ContentItem.query.filter(ContentItem.content_type == "article") + items = apply_news_category(base_query, category).order_by(ContentItem.published_at.desc()).all() + popular = base_query.order_by(ContentItem.comments_count.desc(), ContentItem.published_at.desc()).limit(5).all() + featured = items[0] if items else base_query.order_by(ContentItem.published_at.desc()).first() + return render_template( + "hub_listing.html", + hub="news", + title="Trending News", + list_title="Latest News" if category == "all" else dict(NEWS_CATEGORIES)[category], + subtitle="Current IGN news across games, entertainment, hardware, streaming, and deals.", + items=items, + popular=popular, + featured=featured, + categories=NEWS_CATEGORIES, + active_category=category, + subnav=make_subnav("News", NEWS_CATEGORIES, category, "news"), + ) + + +@app.route("/games") +@app.route("/movies") +@app.route("/tv") +@app.route("/deals") +def section_listing(): + slug = request.path.strip("/") + items = ( + ContentItem.query.filter_by(section_slug=slug) + .order_by(ContentItem.published_at.desc()) + .all() + ) + return render_template( + "listing.html", + title=section_label(slug), + subtitle=Section.query.filter_by(slug=slug).first().description, + items=items, + active_filter=slug, + ) + + +@app.route("/reviews") +@app.route("/reviews/") +def reviews(category: str = "all"): + if category not in dict(REVIEW_CATEGORIES): + abort(404) + sort_key = request.args.get("sort", "latest") + score_filter = request.args.get("score", "all") + genre_filter = request.args.get("genre", "all") + show_review_scores = request.args.get("scores", "1") != "0" + editors_choice_only = request.args.get("editors_choice", "0") == "1" + if sort_key not in dict(REVIEW_SORT_OPTIONS): + abort(404) + if score_filter not in dict(REVIEW_SCORE_FILTERS): + abort(404) + if genre_filter not in dict(REVIEW_GENRE_FILTERS): + abort(404) + query = ContentItem.query.filter_by(content_type="review") + query = apply_review_category(query, category) + if editors_choice_only: + query = query.filter(ContentItem.is_editors_choice.is_(True)) + query = apply_review_score_filter(query, score_filter) + query = apply_review_genre_filter(query, genre_filter) + items = apply_review_sort(query, sort_key).all() + popular = ( + ContentItem.query.filter_by(content_type="review") + .order_by(ContentItem.score.desc(), ContentItem.comments_count.desc()) + .limit(5) + .all() + ) + featured = items[0] if items else (popular[0] if popular else None) + return render_template( + "hub_listing.html", + hub="reviews", + title="Reviews", + list_title=dict(REVIEW_CATEGORIES)[category], + subtitle="IGN-style verdicts, scores, and context.", + items=items, + popular=popular, + featured=featured, + categories=REVIEW_CATEGORIES, + active_category=category, + sort_options=REVIEW_SORT_OPTIONS, + score_filters=REVIEW_SCORE_FILTERS, + genre_filters=REVIEW_GENRE_FILTERS, + sort_key=sort_key, + score_filter=score_filter, + genre_filter=genre_filter, + show_review_scores=show_review_scores, + hide_review_scores=not show_review_scores, + editors_choice_only=editors_choice_only, + subnav=make_subnav("Reviews", REVIEW_CATEGORIES, category, "reviews"), + ) + + +@app.route("/videos") +def videos(): + items = ( + ContentItem.query.filter_by(content_type="video") + .order_by(ContentItem.published_at.desc()) + .all() + ) + return render_template( + "listing.html", + title="Videos", + subtitle="Trailers, Daily Fix clips, podcast segments, and exclusive features.", + items=items, + active_filter="videos", + ) + + +@app.route("/maps") +def maps(): + guide_items = ( + ContentItem.query.filter_by(content_type="guide") + .order_by(ContentItem.sort_rank.asc(), ContentItem.title.asc()) + .limit(8) + .all() + ) + hero = first_item_by_slug("gta-online-weekly-updates") or (guide_items[0] if guide_items else None) + cards = [] + for label, item in zip( + ["Open World Map", "Season Map", "Wiki Hub", "Quest Tracker"], + guide_items[:4], + strict=False, + ): + cards.append( + card_from_item( + item, + label, + "A static IGN-style map card with guide checkpoints, collectibles, and route hints.", + ) + ) + return render_template( + "utility_page.html", + mode="maps", + eyebrow="Interactive Maps", + title="IGN Interactive Maps", + subtitle="Track collectibles, missions, upgrades, and guide checkpoints across popular games.", + hero={ + "title": hero.title if hero else "Interactive Maps", + "body": "Explore game worlds with checklist-style map cards and related IGN wiki coverage.", + "image": hero.image_path if hero else fallback_image(), + "href": detail_url(hero) if hero else url_for("wikis"), + "label": "Featured Map", + }, + cards=cards, + panels=[ + {"title": "Collectibles", "body": "Use map-style guide cards to scan for collectibles and objectives."}, + {"title": "Quest Routes", "body": "Open a guide page to read route notes and update checklist progress."}, + {"title": "Latest Wikis", "body": "Recent IGN wiki pages stay visible without requiring a separate search."}, + ], + ) + + +@app.route("/discover") +def discover(): + featured = ( + ContentItem.query.order_by(ContentItem.comments_count.desc(), ContentItem.published_at.desc()) + .limit(8) + .all() + ) + cards = [ + card_from_item(item, ["Trending", "Reviews", "Guides", "Videos"][index % 4]) + for index, item in enumerate(featured[:4]) + ] + return render_template( + "utility_page.html", + mode="discover", + eyebrow="Discover", + title="Discover More on IGN", + subtitle="Browse trending stories, high-score reviews, new videos, and guide hubs in one place.", + hero={ + "title": featured[0].title if featured else "Discover IGN", + "body": "A lightweight discovery surface modeled after IGN's dark editorial hubs.", + "image": featured[0].image_path if featured else fallback_image(), + "href": detail_url(featured[0]) if featured else url_for("index"), + "label": "Now Trending", + }, + cards=cards, + panels=[ + {"title": "Games", "body": "Console, PC, mobile, and hardware coverage from current IGN sections."}, + {"title": "Entertainment", "body": "Movies, TV, comics, streaming, and culture stories."}, + {"title": "Watch", "body": "Trailers, show clips, Daily Fix segments, and podcast-style videos."}, + ], + ) + + +@app.route("/store") +def store(): + store_items = ( + ContentItem.query.filter( + or_( + ContentItem.section_slug == "deals", + ContentItem.tags_json.ilike("%hardware%"), + ContentItem.tags_json.ilike("%collector%"), + ) + ) + .order_by(ContentItem.published_at.desc()) + .limit(6) + .all() + ) + cards = [] + for index, item in enumerate(store_items[:4], start=1): + payload = card_from_item(item, f"IGN Store Pick #{index}") + payload["price"] = ["$24.99", "$39.99", "$59.99", "$79.99"][index - 1] + cards.append(payload) + return render_template( + "utility_page.html", + mode="store", + eyebrow="Store", + title="IGN Store", + subtitle="A local storefront-style mirror for gear, collectibles, deals, and gaming hardware picks.", + hero={ + "title": "Official IGN-style gear and curated deals", + "body": "Browse static product cards and deal-style content without a checkout workflow.", + "image": store_items[0].image_path if store_items else fallback_image(3), + "href": detail_url(store_items[0]) if store_items else url_for("index"), + "label": "Storefront", + }, + cards=cards, + panels=[ + {"title": "Apparel", "body": "Logo gear and convention-ready accessories."}, + {"title": "Collectibles", "body": "Cards, figures, and entertainment drops surfaced like IGN deals."}, + {"title": "Hardware", "body": "Gaming PC, controller, and headset picks from seeded coverage."}, + ], + ) + + +@app.route("/rewards") +def rewards(): + reward_items = ( + ContentItem.query.filter(ContentItem.content_type.in_(["article", "video"])) + .order_by(ContentItem.comments_count.desc(), ContentItem.published_at.desc()) + .limit(6) + .all() + ) + cards = [] + for index, item in enumerate(reward_items[:4], start=1): + payload = card_from_item(item, f"Reward Drop {index}") + payload["points"] = [250, 500, 750, 1000][index - 1] + cards.append(payload) + return render_template( + "utility_page.html", + mode="rewards", + eyebrow="Rewards", + title="IGN Rewards", + subtitle="A static rewards hub with points, drops, and member-style cards.", + hero={ + "title": "Claim weekly drops and member perks", + "body": "This mirror reproduces the Rewards surface visually; redemption and account economy are not active.", + "image": reward_items[0].image_path if reward_items else fallback_image(4), + "href": detail_url(reward_items[0]) if reward_items else url_for("index"), + "label": "Rewards", + }, + cards=cards, + panels=[ + {"title": "Daily Check-in", "body": "Earn cosmetic points by reading, watching, and saving coverage."}, + {"title": "Sweepstakes", "body": "Static prize cards mimic the IGN Rewards browsing experience."}, + {"title": "Member Status", "body": "Signed-in users keep the bottom account chip while browsing rewards."}, + ], + ) + + +@app.route("/privacy-policy") +def privacy_policy(): + return render_template( + "utility_page.html", + mode="legal", + eyebrow="Privacy Policy", + title="IGN Privacy Policy", + subtitle="A local, non-binding privacy-policy mirror page matching the IGN legal reading layout.", + hero={ + "title": "Privacy at IGN", + "body": "Review how account, device, advertising, and content-interaction data are described.", + "image": fallback_image(5), + "href": "#policy", + "label": "Legal", + }, + cards=[], + panels=[ + {"title": "Information We Collect", "body": "Account identifiers, profile preferences, saved content, playlist actions, comments, alert settings, and device/browser metadata may appear in the local mirror database."}, + {"title": "How Information Is Used", "body": "The WebHarbor environment uses local data to support benchmark tasks such as login, saved stories, playlist actions, and guide progress."}, + {"title": "Advertising and Analytics", "body": "This local page does not load third-party trackers; it visually mirrors the legal-policy surface for navigation tasks."}, + {"title": "Your Choices", "body": "Use local account pages to edit profile preferences or reset the site through the WebHarbor control server."}, + ], + ) + + +@app.route("/terms-of-use") +def terms_of_use(): + return render_template( + "utility_page.html", + mode="legal", + eyebrow="Terms of Use", + title="Ziff Davis Terms of Use", + subtitle="A local terms page for the sidebar link that normally points to ziffdavis.com.", + hero={ + "title": "Terms for using IGN services", + "body": "Read a static summary-style terms surface with IGN/Ziff Davis visual treatment.", + "image": fallback_image(6), + "href": "#terms", + "label": "Legal", + }, + cards=[], + panels=[ + {"title": "Use of Services", "body": "Content, community features, account tools, and store-style surfaces are provided here for local benchmark interaction."}, + {"title": "User Content", "body": "Comments, notes, checklist updates, and playlist entries are stored only in the local SQLite database."}, + {"title": "Purchases and Rewards", "body": "Store and Rewards pages are visual replicas. They do not process purchases, payment data, sweepstakes, or real reward redemption."}, + {"title": "Availability", "body": "The mirror can be reset at any time through WebHarbor's control server, restoring the seeded state."}, + ], + ) + + +@app.route("/wikis/ign-community-central/How_to_Follow_IGN") +def follow_ign(): + related = ( + ContentItem.query.order_by(ContentItem.published_at.desc(), ContentItem.id.asc()) + .limit(6) + .all() + ) + cards = [ + card_from_item(item, ["Follow News", "Watch Videos", "Save Guides", "Join Lists"][index % 4]) + for index, item in enumerate(related[:4]) + ] + return render_template( + "utility_page.html", + mode="follow", + eyebrow="IGN Community Central", + title="How to Follow IGN", + subtitle="A wiki-style community page for the sidebar More link.", + hero={ + "title": "Follow IGN across news, reviews, videos, guides, and playlists", + "body": "Use the local sidebar, search, and account tools to move through the same major surfaces.", + "image": related[0].image_path if related else fallback_image(7), + "href": url_for("index"), + "label": "Community", + }, + cards=cards, + panels=[ + {"title": "1. Pick a topic", "body": "Start with News, Reviews, Guides, Videos, or Search in the left rail."}, + {"title": "2. Open a story", "body": "Detail pages expose save, playlist, comment, and guide-progress actions when relevant."}, + {"title": "3. Sign in", "body": "Use a benchmark account to keep saved stories, playlist rows, alerts, and profile changes."}, + {"title": "4. Return to top", "body": "Use the sidebar Back To Top button after scrolling long pages."}, + ], + ) + + +@app.route("/guides") +def guides_redirect(): + return redirect(url_for("wikis")) + + +@app.route("/wikis") +def wikis(): + items = ( + ContentItem.query.filter_by(content_type="guide") + .order_by(ContentItem.sort_rank.asc(), ContentItem.title.asc()) + .all() + ) + return render_template( + "listing.html", + title="Guides", + subtitle="Walkthroughs, checklists, wiki pages, and interactive-map style helpers.", + items=items, + active_filter="guides", + ) + + +@app.route("/search") +def search(): + q = request.args.get("q", "").strip() + category = request.args.get("category", "all").strip() or "all" + if category not in dict(NEWS_CATEGORIES): + abort(404) + # When no category filter is active, search across all content types so + # videos, guides, and reviews are also discoverable. When a specific news + # category is selected the query narrows to articles of that category. + if category == "all": + base_query = ContentItem.query + else: + base_query = ContentItem.query.filter_by(content_type="article") + base_query = apply_news_category(base_query, category) + items = base_query.order_by(ContentItem.published_at.desc()).all() + results = scored_search(q, items) + return render_template( + "search.html", + q=q, + results=results, + category=category, + categories=NEWS_CATEGORIES, + ) + + +def lookup_item(slug: str, expected_type: str | None = None) -> ContentItem: + item = ContentItem.query.filter_by(slug=slug).first() + if item is None: + item = ContentItem.query.filter_by(source_path=f"/articles/{slug}").first() + if item is None: + abort(404) + if expected_type and item.content_type != expected_type: + abort(404) + return item + + +@app.route("/articles/") +def article_detail(slug: str): + item = lookup_item(slug) + if item.content_type == "video": + return redirect(url_for("video_detail", slug=item.slug)) + if item.content_type == "guide": + return redirect(detail_url(item)) + related = related_items(item) + return render_template("detail.html", item=item, related=related) + + +@app.route("/videos/") +def video_detail(slug: str): + item = lookup_item(slug, "video") + related = related_items(item) + return render_template("video_detail.html", item=item, related=related) + + +@app.route("/wikis/") +def guide_detail(wiki_path: str): + source_path = f"/wikis/{wiki_path}" + item = ContentItem.query.filter_by(source_path=source_path).first() + if item is None: + normalized = wiki_path.replace("/", "-").replace("_", "-").lower() + item = ContentItem.query.filter_by(slug=normalized).first() + if item is None or item.content_type != "guide": + abort(404) + progress = {} + if current_user.is_authenticated: + rows = GuideProgress.query.filter_by(user_id=current_user.id, item_id=item.id).all() + progress = {row.checkpoint: row.completed for row in rows} + related = related_items(item) + return render_template("guide_detail.html", item=item, progress=progress, related=related) + + +def related_items(item: ContentItem) -> list[ContentItem]: + tokens = set(item.tags[:4]) + candidates = ( + ContentItem.query.filter(ContentItem.id != item.id) + .order_by(ContentItem.published_at.desc()) + .limit(40) + .all() + ) + scored = [] + for candidate in candidates: + overlap = len(tokens.intersection(candidate.tags)) + if candidate.section_slug == item.section_slug: + overlap += 1 + if candidate.content_type == item.content_type: + overlap += 1 + scored.append((overlap, candidate.published_at, candidate)) + scored.sort(key=lambda row: (-row[0], -row[1].timestamp())) + return [row[2] for row in scored[:4]] + + +@app.route("/login", methods=["GET", "POST"]) +def login(): + if current_user.is_authenticated: + return redirect(url_for("account")) + if request.method == "POST": + email = request.form.get("email", "").strip().lower() + password = request.form.get("password", "") + user = User.query.filter_by(email=email).first() + if user and user.check_password(password): + login_user(user) + flash("Welcome back to IGN.", "success") + next_url = request.args.get("next") + return redirect(next_url or url_for("account")) + flash("Email or password did not match.", "error") + return render_template("login.html") + + +@app.route("/register", methods=["GET", "POST"]) +def register(): + if current_user.is_authenticated: + return redirect(url_for("account")) + if request.method == "POST": + email = request.form.get("email", "").strip().lower() + username = request.form.get("username", "").strip() + display_name = request.form.get("display_name", "").strip() or username + password = request.form.get("password", "") + if not email or not username or len(password) < 8: + flash("Provide an email, username, and password of at least 8 characters.", "error") + elif User.query.filter(or_(User.email == email, User.username == username)).first(): + flash("That email or username is already registered.", "error") + else: + user = User(email=email, username=username, display_name=display_name) + user.set_password(password) + db.session.add(user) + db.session.commit() + login_user(user) + flash("Your IGN account is ready.", "success") + return redirect(url_for("account")) + return render_template("register.html") + + +@app.route("/logout") +@login_required +def logout(): + logout_user() + flash("You are logged out.", "info") + return redirect(url_for("index")) + + +@app.route("/account") +@login_required +def account(): + saved = ( + SavedItem.query.filter_by(user_id=current_user.id) + .order_by(SavedItem.created_at.desc()) + .limit(6) + .all() + ) + playlist = ( + PlaylistEntry.query.filter_by(user_id=current_user.id) + .order_by(PlaylistEntry.position.asc(), PlaylistEntry.created_at.desc()) + .limit(6) + .all() + ) + return render_template("account.html", saved=saved, playlist=playlist) + + +@app.route("/account/edit", methods=["GET", "POST"]) +@login_required +def account_edit(): + if request.method == "POST": + current_user.display_name = request.form.get("display_name", "").strip() or current_user.display_name + current_user.region = request.form.get("region", "").strip() or current_user.region + current_user.favorite_platform = ( + request.form.get("favorite_platform", "").strip() or current_user.favorite_platform + ) + current_user.bio = request.form.get("bio", "").strip() + current_user.notification_email = bool(request.form.get("notification_email")) + db.session.commit() + flash("Account profile updated.", "success") + return redirect(url_for("account")) + return render_template("account_edit.html") + + +@app.route("/saved") +@login_required +def saved(): + rows = ( + SavedItem.query.filter_by(user_id=current_user.id) + .order_by(SavedItem.created_at.desc()) + .all() + ) + return render_template("saved.html", rows=rows) + + +@app.post("/save/") +@login_required +def save_item(item_id: int): + item = db.get_or_404(ContentItem, item_id) + row = SavedItem.query.filter_by(user_id=current_user.id, item_id=item.id).first() + if row: + row.folder = request.form.get("folder", row.folder) or row.folder + row.note = request.form.get("note", row.note) or row.note + flash("Saved item updated.", "success") + else: + db.session.add( + SavedItem( + user_id=current_user.id, + item_id=item.id, + folder=request.form.get("folder", "Read Later"), + note=request.form.get("note", ""), + ) + ) + flash("Saved to your IGN list.", "success") + db.session.commit() + return redirect(request.referrer or detail_url(item)) + + +@app.post("/saved//remove") +@login_required +def remove_saved(saved_id: int): + row = SavedItem.query.filter_by(id=saved_id, user_id=current_user.id).first_or_404() + db.session.delete(row) + db.session.commit() + flash("Removed from saved stories.", "info") + return redirect(url_for("saved")) + + +@app.route("/playlist") +@login_required +def playlist(): + rows = ( + PlaylistEntry.query.filter_by(user_id=current_user.id) + .order_by(PlaylistEntry.position.asc(), PlaylistEntry.created_at.desc()) + .all() + ) + return render_template("playlist.html", rows=rows) + + +@app.post("/playlist/add/") +@login_required +def add_playlist(item_id: int): + item = db.get_or_404(ContentItem, item_id) + row = PlaylistEntry.query.filter_by(user_id=current_user.id, item_id=item.id).first() + if row: + row.note = request.form.get("note", row.note) or row.note + row.status = request.form.get("status", row.status) or row.status + flash("Playlist entry updated.", "success") + else: + position = PlaylistEntry.query.filter_by(user_id=current_user.id).count() + 1 + db.session.add( + PlaylistEntry( + user_id=current_user.id, + item_id=item.id, + note=request.form.get("note", ""), + status=request.form.get("status", "queued"), + position=position, + ) + ) + flash("Added to Playlist.", "success") + db.session.commit() + return redirect(request.referrer or detail_url(item)) + + +@app.post("/playlist//status") +@login_required +def update_playlist_status(entry_id: int): + row = PlaylistEntry.query.filter_by(id=entry_id, user_id=current_user.id).first_or_404() + row.status = request.form.get("status", row.status) + row.note = request.form.get("note", row.note) + db.session.commit() + flash("Playlist status updated.", "success") + return redirect(url_for("playlist")) + + +@app.post("/playlist//remove") +@login_required +def remove_playlist(entry_id: int): + row = PlaylistEntry.query.filter_by(id=entry_id, user_id=current_user.id).first_or_404() + db.session.delete(row) + db.session.commit() + flash("Removed from Playlist.", "info") + return redirect(url_for("playlist")) + + +@app.post("/comment/") +@login_required +def add_comment(item_id: int): + item = db.get_or_404(ContentItem, item_id) + body = request.form.get("body", "").strip() + if len(body) < 3: + flash("Write a little more before posting.", "error") + else: + db.session.add(Comment(user_id=current_user.id, item_id=item.id, body=body)) + item.comments_count = max(item.comments_count, len(item.comments) + 1) + db.session.commit() + flash("Comment posted.", "success") + return redirect(detail_url(item) + "#comments") + + +@app.post("/guides//progress") +@login_required +def update_guide_progress(item_id: int): + item = db.get_or_404(ContentItem, item_id) + if item.content_type != "guide": + abort(400) + checkpoint = request.form.get("checkpoint", "").strip() + if not checkpoint: + abort(400) + row = GuideProgress.query.filter_by( + user_id=current_user.id, item_id=item.id, checkpoint=checkpoint + ).first() + if row is None: + row = GuideProgress(user_id=current_user.id, item_id=item.id, checkpoint=checkpoint) + db.session.add(row) + row.completed = bool(request.form.get("completed")) + row.updated_at = MIRROR_REFERENCE_DATE + db.session.commit() + flash("Guide progress saved.", "success") + return redirect(detail_url(item)) + + +@app.route("/alerts", methods=["GET", "POST"]) +@login_required +def alerts(): + if request.method == "POST": + section_slug = request.form.get("section_slug", "").strip() + keyword = request.form.get("keyword", "").strip() + frequency = request.form.get("frequency", "daily").strip() + if not section_slug and not keyword: + flash("Choose a section or keyword for the alert.", "error") + else: + db.session.add( + AlertSubscription( + user_id=current_user.id, + section_slug=section_slug, + keyword=keyword, + frequency=frequency, + active=True, + ) + ) + db.session.commit() + flash("Alert subscription created.", "success") + return redirect(url_for("alerts")) + rows = ( + AlertSubscription.query.filter_by(user_id=current_user.id) + .order_by(AlertSubscription.created_at.desc()) + .all() + ) + return render_template("alerts.html", rows=rows) + + +@app.post("/alerts//toggle") +@login_required +def toggle_alert(alert_id: int): + row = AlertSubscription.query.filter_by(id=alert_id, user_id=current_user.id).first_or_404() + row.active = not row.active + db.session.commit() + flash("Alert status changed.", "success") + return redirect(url_for("alerts")) + + +@app.route("/_health") +def health(): + return { + "ok": True, + "site": "ign", + "items": ContentItem.query.count(), + "users": User.query.count(), + } + + +from seed_data import seed_benchmark_users, seed_database # noqa: E402 + +with app.app_context(): + db.create_all() + seed_database(db, Section, ContentItem) + seed_benchmark_users( + db, + User, + ContentItem, + SavedItem, + PlaylistEntry, + Comment, + AlertSubscription, + Digest, + GuideProgress, + ) + + +if __name__ == "__main__": + port = int(os.environ.get("PORT", 5000)) + app.run(host="0.0.0.0", port=port, debug=False) diff --git a/sites/ign/content_seed.py b/sites/ign/content_seed.py new file mode 100644 index 00000000..a72b175a --- /dev/null +++ b/sites/ign/content_seed.py @@ -0,0 +1,1879 @@ +"""Frozen seed content for the IGN mirror. + +Generated from the stable SQLite seed database for this WebHarbor mirror. +Runtime code imports this file only while building the SQLite seed database. +""" + +CONTENT_ITEMS = [{'slug': 'grand-theft-auto-controversies', + 'source_path': '/articles/grand-theft-auto-controversies', + 'title': 'The Complicated History of GTA Controversies', + 'description': "IGN's current coverage of The Complicated History of GTA Controversies, captured " + 'from the live site for this WebHarbor mirror.', + 'section_slug': 'games', + 'content_type': 'article', + 'author': 'IGN Staff', + 'published_at': '2026-07-03T18:31:00Z', + 'score': None, + 'comments_count': 12, + 'duration': None, + 'platforms': ['PC', 'PlayStation 5', 'Xbox Series X/S'], + 'tags': ['games', 'article', 'complicated', 'history', 'gta', 'controversies'], + 'home_rank': 1, + 'image_path': '/static/images/content/001-grand-theft-auto-controversies.jpg'}, + {'slug': 'playstations-physical-media-free-future-isnt-just-concerning-its-offensive', + 'source_path': '/articles/playstations-physical-media-free-future-isnt-just-concerning-its-offensive', + 'title': 'PlayStation’s Physical Media-Free Future Isn’t Just Concerning, It’s Offensive', + 'description': "IGN's current coverage of PlayStation’s Physical Media-Free Future Isn’t Just " + 'Concerning, It’s Offensive, captured from the live site for this WebHarbor ' + 'mirror.', + 'section_slug': 'games', + 'content_type': 'article', + 'author': 'IGN Staff', + 'published_at': '2026-07-03T16:31:00Z', + 'score': None, + 'comments_count': 29, + 'duration': None, + 'platforms': ['PC', 'PlayStation 5', 'Xbox Series X/S'], + 'tags': ['games', 'article', 'playstation', 'physical', 'media', 'free', 'future', 'isn'], + 'home_rank': 2, + 'image_path': '/static/images/content/002-playstations-physical-media-free-future-isnt-just-concerning-its-offen.jpg'}, + {'slug': 'silo-season-3-review', + 'source_path': '/articles/silo-season-3-review', + 'title': 'Silo Season 3 Review', + 'description': "IGN's current coverage of Silo Season 3 Review, captured from the live site for " + 'this WebHarbor mirror.', + 'section_slug': 'tv', + 'content_type': 'review', + 'author': 'IGN Staff', + 'published_at': '2026-07-03T14:31:00Z', + 'score': 8, + 'comments_count': 46, + 'duration': None, + 'platforms': [], + 'tags': ['tv', 'review', 'silo', 'season'], + 'home_rank': 3, + 'image_path': '/static/images/content/003-silo-season-3-review.jpg'}, + {'slug': 'pokemon-tcg-prismatic-evolutions-etbs-are-back-in-stock-and-under-market-price-at-amazon', + 'source_path': '/articles/pokemon-tcg-prismatic-evolutions-etbs-are-back-in-stock-and-under-market-price-at-amazon', + 'title': 'Amazon Restocks Rare Pokémon TCG Set, But is It Worth the Inflated Price?', + 'description': "IGN's current coverage of Amazon Restocks Rare Pokémon TCG Set, But is It Worth " + 'the Inflated Price?, captured from the live site for this WebHarbor mirror.', + 'section_slug': 'deals', + 'content_type': 'article', + 'author': 'IGN Staff', + 'published_at': '2026-07-03T12:31:00Z', + 'score': None, + 'comments_count': 63, + 'duration': None, + 'platforms': ['Nintendo Switch 2', 'PC'], + 'tags': ['deals', 'article', 'amazon', 'restocks', 'rare', 'pok', 'mon', 'tcg'], + 'home_rank': 4, + 'image_path': '/static/images/content/004-pokemon-tcg-prismatic-evolutions-etbs-are-back-in-stock-and-under-mark.png'}, + {'slug': 'cape-fear-episode-6-recap-and-review-possum', + 'source_path': '/articles/cape-fear-episode-6-recap-and-review-possum', + 'title': 'Cape Fear Episode 6 Recap and Review', + 'description': "IGN's current coverage of Cape Fear Episode 6 Recap and Review, captured from " + 'the live site for this WebHarbor mirror.', + 'section_slug': 'tv', + 'content_type': 'review', + 'author': 'IGN Staff', + 'published_at': '2026-07-03T10:31:00Z', + 'score': 7, + 'comments_count': 80, + 'duration': None, + 'platforms': [], + 'tags': ['tv', 'review', 'cape', 'fear', 'episode', 'recap'], + 'home_rank': 5, + 'image_path': '/static/images/content/005-cape-fear-episode-6-recap-and-review-possum.jpg'}, + {'slug': 'anne-hathaway-quit-knocked-up-because-she-didnt-want-the-crowning-of-the-baby-to-be-visually-representative-seth-rogen-says', + 'source_path': '/articles/anne-hathaway-quit-knocked-up-because-she-didnt-want-the-crowning-of-the-baby-to-be-visually-representative-seth-rogen-says', + 'title': "Anne Hathaway Quit Knocked Up Because 'She Didn't Want the Crowning of the Baby to Be " + "Visually Representative,' Seth Rogen Says", + 'description': "IGN's current coverage of Anne Hathaway Quit Knocked Up Because 'She Didn't Want " + "the Crowning of the Baby to Be Visually Representative,' Seth Rogen Says, " + 'captured from the live site for this WebHarbor mirror.', + 'section_slug': 'movies', + 'content_type': 'article', + 'author': 'IGN Staff', + 'published_at': '2026-07-03T08:31:00Z', + 'score': None, + 'comments_count': 97, + 'duration': None, + 'platforms': [], + 'tags': ['movies', 'article', 'anne', 'hathaway', 'quit', 'knocked', 'because', 'she'], + 'home_rank': 6, + 'image_path': '/static/images/content/006-anne-hathaway-quit-knocked-up-because-she-didnt-want-the-crowning-of-t.jpg'}, + {'slug': 'nintendo-president-says-the-pokemon-company-will-take-measures-to-respond-to-widespread-card-scalping-issues', + 'source_path': '/articles/nintendo-president-says-the-pokemon-company-will-take-measures-to-respond-to-widespread-card-scalping-issues', + 'title': "Nintendo President Says The Pokémon Company Will 'Take Measures' to Respond to " + 'Widespread Card Scalping Issues', + 'description': "IGN's current coverage of Nintendo President Says The Pokémon Company Will 'Take " + "Measures' to Respond to Widespread Card Scalping Issues, captured from the live " + 'site for this WebHarbor mirror.', + 'section_slug': 'games', + 'content_type': 'article', + 'author': 'IGN Staff', + 'published_at': '2026-07-03T06:31:00Z', + 'score': None, + 'comments_count': 114, + 'duration': None, + 'platforms': ['PC', 'PlayStation 5', 'Xbox Series X/S'], + 'tags': ['games', 'article', 'nintendo', 'president', 'says', 'pok', 'mon', 'company'], + 'home_rank': 7, + 'image_path': '/static/images/content/007-nintendo-president-says-the-pokemon-company-will-take-measures-to-resp.jpg'}, + {'slug': 'college-football-27-review', + 'source_path': '/articles/college-football-27-review', + 'title': 'College Football 27 Review So Far', + 'description': "IGN's current coverage of College Football 27 Review So Far, captured from the " + 'live site for this WebHarbor mirror.', + 'section_slug': 'games', + 'content_type': 'review', + 'author': 'IGN Staff', + 'published_at': '2026-07-03T04:31:00Z', + 'score': 8, + 'comments_count': 131, + 'duration': None, + 'platforms': ['PC', 'PlayStation 5', 'Xbox Series X/S'], + 'tags': ['games', 'review', 'college', 'football', 'far'], + 'home_rank': 8, + 'image_path': '/static/images/content/008-college-football-27-review.jpg'}, + {'slug': 'mtg-marvel-collector-boosters-amazon-sealed-in-stock', + 'source_path': '/articles/mtg-marvel-collector-boosters-amazon-sealed-in-stock', + 'title': "MTG's Rare Marvel Collector Boosters Are Available Again Online, But Expected to Sell " + 'Out Fast', + 'description': "IGN's current coverage of MTG's Rare Marvel Collector Boosters Are Available " + 'Again Online, But Expected to Sell Out Fast, captured from the live site for ' + 'this WebHarbor mirror.', + 'section_slug': 'deals', + 'content_type': 'article', + 'author': 'IGN Staff', + 'published_at': '2026-07-03T02:31:00Z', + 'score': None, + 'comments_count': 148, + 'duration': None, + 'platforms': ['Nintendo Switch 2', 'PC'], + 'tags': ['deals', 'article', 'mtg', 'rare', 'marvel', 'collector', 'boosters', 'available'], + 'home_rank': 9, + 'image_path': '/static/images/content/009-mtg-marvel-collector-boosters-amazon-sealed-in-stock.png'}, + {'slug': 'nintendo-switch-summer-sale-best-deals-2026', + 'source_path': '/articles/nintendo-switch-summer-sale-best-deals-2026', + 'title': "Nintendo Summer Sale: We've Found the 10 Best Deals Still Live", + 'description': "IGN's current coverage of Nintendo Summer Sale: We've Found the 10 Best Deals " + 'Still Live, captured from the live site for this WebHarbor mirror.', + 'section_slug': 'deals', + 'content_type': 'article', + 'author': 'IGN Staff', + 'published_at': '2026-07-03T00:31:00Z', + 'score': None, + 'comments_count': 165, + 'duration': None, + 'platforms': ['Nintendo Switch 2', 'PC'], + 'tags': ['deals', 'article', 'nintendo', 'summer', 'sale', 'found', 'best', 'still'], + 'home_rank': 10, + 'image_path': '/static/images/content/010-nintendo-switch-summer-sale-best-deals-2026.png'}, + {'slug': 'pokemon-tcg-first-partner-illustration-collection-series-2-is-back-in-stock-at-amazon', + 'source_path': '/articles/pokemon-tcg-first-partner-illustration-collection-series-2-is-back-in-stock-at-amazon', + 'title': "Pokémon TCG's 30th Anniversary First Partner Set is Finally Back in Stock", + 'description': "IGN's current coverage of Pokémon TCG's 30th Anniversary First Partner Set is " + 'Finally Back in Stock, captured from the live site for this WebHarbor mirror.', + 'section_slug': 'deals', + 'content_type': 'article', + 'author': 'IGN Staff', + 'published_at': '2026-07-02T22:31:00Z', + 'score': None, + 'comments_count': 182, + 'duration': None, + 'platforms': ['Nintendo Switch 2', 'PC'], + 'tags': ['deals', 'article', 'pok', 'mon', 'tcg', '30th', 'anniversary', 'first'], + 'home_rank': 11, + 'image_path': '/static/images/content/011-pokemon-tcg-first-partner-illustration-collection-series-2-is-back-in-.png'}, + {'slug': 'walmart-has-the-lowest-price-on-a-geforce-rtx-5070-prebuilt-gaming-pc-in-2026', + 'source_path': '/articles/walmart-has-the-lowest-price-on-a-geforce-rtx-5070-prebuilt-gaming-pc-in-2026', + 'title': 'Walmart Has the Lowest Price on a GeForce RTX 5070 Prebuilt Gaming PC in 2026', + 'description': "IGN's current coverage of Walmart Has the Lowest Price on a GeForce RTX 5070 " + 'Prebuilt Gaming PC in 2026, captured from the live site for this WebHarbor ' + 'mirror.', + 'section_slug': 'deals', + 'content_type': 'article', + 'author': 'IGN Staff', + 'published_at': '2026-07-02T20:31:00Z', + 'score': None, + 'comments_count': 199, + 'duration': None, + 'platforms': ['Nintendo Switch 2', 'PC'], + 'tags': ['deals', 'article', 'walmart', 'has', 'lowest', 'price', 'geforce', 'rtx'], + 'home_rank': 12, + 'image_path': '/static/images/content/012-walmart-has-the-lowest-price-on-a-geforce-rtx-5070-prebuilt-gaming-pc-.jpg'}, + {'slug': 'project-hail-mary-streaming-release-date-prime-video', + 'source_path': '/articles/project-hail-mary-streaming-release-date-prime-video', + 'title': 'Project Hail Mary Is Now Streaming on Prime Video', + 'description': "IGN's current coverage of Project Hail Mary Is Now Streaming on Prime Video, " + 'captured from the live site for this WebHarbor mirror.', + 'section_slug': 'tv', + 'content_type': 'article', + 'author': 'IGN Staff', + 'published_at': '2026-07-02T18:31:00Z', + 'score': None, + 'comments_count': 216, + 'duration': None, + 'platforms': [], + 'tags': ['tv', 'article', 'project', 'hail', 'mary', 'now', 'streaming', 'prime'], + 'home_rank': 13, + 'image_path': '/static/images/content/013-project-hail-mary-streaming-release-date-prime-video.jpg'}, + {'slug': 'whats-new-on-hbo-max-july-2026', + 'source_path': '/articles/whats-new-on-hbo-max-july-2026', + 'title': 'Everything Coming to HBO Max in July', + 'description': "IGN's current coverage of Everything Coming to HBO Max in July, captured from " + 'the live site for this WebHarbor mirror.', + 'section_slug': 'tv', + 'content_type': 'article', + 'author': 'IGN Staff', + 'published_at': '2026-07-02T16:31:00Z', + 'score': None, + 'comments_count': 233, + 'duration': None, + 'platforms': [], + 'tags': ['tv', 'article', 'everything', 'coming', 'hbo', 'max', 'july'], + 'home_rank': 14, + 'image_path': '/static/images/content/014-whats-new-on-hbo-max-july-2026.jpg'}, + {'slug': 'whats-new-on-amazon-prime-video-july-2026', + 'source_path': '/articles/whats-new-on-amazon-prime-video-july-2026', + 'title': 'Amazon Prime Video Has Some of the Best New Streaming Releases', + 'description': "IGN's current coverage of Amazon Prime Video Has Some of the Best New Streaming " + 'Releases, captured from the live site for this WebHarbor mirror.', + 'section_slug': 'tv', + 'content_type': 'article', + 'author': 'IGN Staff', + 'published_at': '2026-07-02T14:31:00Z', + 'score': None, + 'comments_count': 250, + 'duration': None, + 'platforms': ['Nintendo Switch 2', 'PC'], + 'tags': ['deals', 'article', 'amazon', 'prime', 'video', 'has', 'some', 'best'], + 'home_rank': 15, + 'image_path': '/static/images/content/015-whats-new-on-amazon-prime-video-july-2026.jpg'}, + {'slug': 'whats-new-on-netflix-july-2026', + 'source_path': '/articles/whats-new-on-netflix-july-2026', + 'title': 'Netflix Is Stacked With New Shows and Movies This Month', + 'description': "IGN's current coverage of Netflix Is Stacked With New Shows and Movies This " + 'Month, captured from the live site for this WebHarbor mirror.', + 'section_slug': 'tv', + 'content_type': 'article', + 'author': 'IGN Staff', + 'published_at': '2026-07-02T12:31:00Z', + 'score': None, + 'comments_count': 27, + 'duration': None, + 'platforms': [], + 'tags': ['tv', 'article', 'netflix', 'stacked', 'shows', 'movies', 'month'], + 'home_rank': 16, + 'image_path': '/static/images/content/016-whats-new-on-netflix-july-2026.jpg'}, + {'slug': 'obsession-digital-streaming-4k-blu-ray', + 'source_path': '/articles/obsession-digital-streaming-4k-blu-ray', + 'title': 'Obsession Just Landed on Digital', + 'description': "IGN's current coverage of Obsession Just Landed on Digital, captured from the " + 'live site for this WebHarbor mirror.', + 'section_slug': 'tv', + 'content_type': 'article', + 'author': 'IGN Staff', + 'published_at': '2026-07-02T10:31:00Z', + 'score': None, + 'comments_count': 44, + 'duration': None, + 'platforms': [], + 'tags': ['tv', 'article', 'obsession', 'landed', 'digital'], + 'home_rank': 17, + 'image_path': '/static/images/content/017-obsession-digital-streaming-4k-blu-ray.png'}, + {'slug': 'whats-new-on-apple-tv-july-2026', + 'source_path': '/articles/whats-new-on-apple-tv-july-2026', + 'title': 'Everything New on Apple TV This Month', + 'description': "IGN's current coverage of Everything New on Apple TV This Month, captured from " + 'the live site for this WebHarbor mirror.', + 'section_slug': 'tv', + 'content_type': 'article', + 'author': 'IGN Staff', + 'published_at': '2026-07-02T08:31:00Z', + 'score': None, + 'comments_count': 61, + 'duration': None, + 'platforms': [], + 'tags': ['tv', 'article', 'everything', 'apple', 'month'], + 'home_rank': 18, + 'image_path': '/static/images/content/018-whats-new-on-apple-tv-july-2026.jpg'}, + {'slug': 'the-super-mario-galaxy-movie-streaming-release-date-peacock', + 'source_path': '/articles/the-super-mario-galaxy-movie-streaming-release-date-peacock', + 'title': 'The Super Mario Galaxy Movie Is Coming to Peacock at the End of July', + 'description': "IGN's current coverage of The Super Mario Galaxy Movie Is Coming to Peacock at " + 'the End of July, captured from the live site for this WebHarbor mirror.', + 'section_slug': 'tv', + 'content_type': 'article', + 'author': 'IGN Staff', + 'published_at': '2026-07-02T06:31:00Z', + 'score': None, + 'comments_count': 78, + 'duration': None, + 'platforms': [], + 'tags': ['tv', 'article', 'super', 'mario', 'galaxy', 'movie', 'coming', 'peacock'], + 'home_rank': 19, + 'image_path': '/static/images/content/019-the-super-mario-galaxy-movie-streaming-release-date-peacock.jpg'}, + {'slug': 'whats-new-on-disney-plus-july-2026', + 'source_path': '/articles/whats-new-on-disney-plus-july-2026', + 'title': 'The Disney+ Lineup Includes X-Men and New Episodes of The Simpsons', + 'description': "IGN's current coverage of The Disney+ Lineup Includes X-Men and New Episodes of " + 'The Simpsons, captured from the live site for this WebHarbor mirror.', + 'section_slug': 'tv', + 'content_type': 'article', + 'author': 'IGN Staff', + 'published_at': '2026-07-02T04:31:00Z', + 'score': None, + 'comments_count': 95, + 'duration': None, + 'platforms': [], + 'tags': ['tv', 'article', 'disney', 'lineup', 'includes', 'men', 'episodes', 'simpsons'], + 'home_rank': 20, + 'image_path': '/static/images/content/020-whats-new-on-disney-plus-july-2026.jpg'}, + {'slug': 'most-anticipated-tv-shows-to-stream-in-july-2026', + 'source_path': '/articles/most-anticipated-tv-shows-to-stream-in-july-2026', + 'title': 'The 10 Most Anticipated TV Shows to Stream in July', + 'description': "IGN's current coverage of The 10 Most Anticipated TV Shows to Stream in July, " + 'captured from the live site for this WebHarbor mirror.', + 'section_slug': 'tv', + 'content_type': 'article', + 'author': 'IGN Staff', + 'published_at': '2026-07-02T02:31:00Z', + 'score': None, + 'comments_count': 112, + 'duration': None, + 'platforms': [], + 'tags': ['tv', 'article', 'most', 'anticipated', 'shows', 'stream', 'july'], + 'home_rank': 21, + 'image_path': '/static/images/content/021-most-anticipated-tv-shows-to-stream-in-july-2026.jpg'}, + {'slug': 'marvel-rivals-fans-think-the-new-captain-america-skin-is-a-mod-but-its-actually-just-his-rod', + 'source_path': '/articles/marvel-rivals-fans-think-the-new-captain-america-skin-is-a-mod-but-its-actually-just-his-rod', + 'title': "Marvel Rivals Fans Think the New Captain America Skin Is a Mod, But It's Actually Just " + 'His Rod', + 'description': 'NetEase Games is celebrating the 4th of July with a Marvel Rivals swimsuit skin ' + "that comes bundled with Captain America's big package.", + 'section_slug': 'games', + 'content_type': 'article', + 'author': 'Michael Cripe', + 'published_at': '2026-07-03T17:41:05Z', + 'score': None, + 'comments_count': 129, + 'duration': None, + 'platforms': ['PC', 'PlayStation 5', 'Xbox Series X/S'], + 'tags': ['games', 'article', 'marvel', 'rivals', 'fans', 'think', 'captain', 'america'], + 'home_rank': 22, + 'image_path': '/static/images/content/022-marvel-rivals-fans-think-the-new-captain-america-skin-is-a-mod-but-its.png'}, + {'slug': 'people-are-worried-theyll-only-see-half-of-the-odyssey-if-they-miss-out-on-imax-but-should-they-be', + 'source_path': '/articles/people-are-worried-theyll-only-see-half-of-the-odyssey-if-they-miss-out-on-imax-but-should-they-be', + 'title': 'People Are Worried They’ll Only See Half of The Odyssey If They Miss Out on Imax, But ' + 'Should They Be?', + 'description': 'People are worried they’ll only see half of The Odyssey if they miss out on ' + 'Imax, but should they be?', + 'section_slug': 'movies', + 'content_type': 'article', + 'author': 'Simon Cardy', + 'published_at': '2026-07-03T16:37:37Z', + 'score': None, + 'comments_count': 146, + 'duration': None, + 'platforms': [], + 'tags': ['movies', 'article', 'people', 'worried', 'they', 'only', 'see', 'half'], + 'home_rank': 23, + 'image_path': '/static/images/content/023-people-are-worried-theyll-only-see-half-of-the-odyssey-if-they-miss-ou.png'}, + {'slug': 'the-view-from-japan-playstations-push-for-an-all-digital-future-raises-concerns-from-gamers-and-developers-including-a-well-timed-retweet-from-hideo-kojima', + 'source_path': '/articles/the-view-from-japan-playstations-push-for-an-all-digital-future-raises-concerns-from-gamers-and-developers-including-a-well-timed-retweet-from-hideo-kojima', + 'title': "The View From Japan: PlayStation's Push for an All-Digital Future Raises Concern From " + 'Gamers and Developers — and a Well-Timed Retweet From Hideo Kojima', + 'description': 'On July 1, Sony announced that no new PlayStation games will ship on-disc from ' + 'January 2028 onwards. This move to a digital-only future has sparked widespread ' + 'backlash and concerns about game preservation and ownership, including in Sony’s ' + 'homeland of Japan, where developers and fans have also expressed disapproval of ' + 'the move.', + 'section_slug': 'games', + 'content_type': 'article', + 'author': 'Wesley Yin-Poole', + 'published_at': '2026-07-03T15:32:28Z', + 'score': None, + 'comments_count': 163, + 'duration': None, + 'platforms': ['PC', 'PlayStation 5', 'Xbox Series X/S'], + 'tags': ['games', 'article', 'view', 'japan', 'playstation', 'push', 'digital', 'future'], + 'home_rank': 24, + 'image_path': '/static/images/content/024-the-view-from-japan-playstations-push-for-an-all-digital-future-raises.jpg'}, + {'slug': 'significant-pay-rises-offered-by-professor-layton-studio-if-you-correctly-answer-quizzes-about-the-companys-games', + 'source_path': '/articles/significant-pay-rises-offered-by-professor-layton-studio-if-you-correctly-answer-quizzes-about-the-companys-games', + 'title': "'Significant' Pay Rises Offered by Professor Layton Studio — if You Correctly Answer " + "Quizzes About the Company's Games", + 'description': '"Significant" pay rises are on offer at Professor Layton developer Level-5 — if ' + "you can correctly answer questions about the company's games.", + 'section_slug': 'games', + 'content_type': 'article', + 'author': 'Tom Phillips', + 'published_at': '2026-07-03T15:13:12Z', + 'score': None, + 'comments_count': 180, + 'duration': None, + 'platforms': ['PC', 'PlayStation 5', 'Xbox Series X/S'], + 'tags': ['games', 'article', 'significant', 'pay', 'rises', 'offered', 'professor', 'layton'], + 'home_rank': 25, + 'image_path': '/static/images/content/025-significant-pay-rises-offered-by-professor-layton-studio-if-you-correc.jpg'}, + {'slug': 'copilot-pc-buying-guide-windows-11-worth-it', + 'source_path': '/articles/copilot-pc-buying-guide-windows-11-worth-it', + 'title': 'Think You Know Windows 11? Here’s Why A Copilot+ PC Can Level Up Your Productivity', + 'description': 'Unsure if you actually need a Copilot+ PC? Our Windows 11 buying guide breaks ' + 'down the local NPU hardware, battery life gains, and features to help you decide ' + 'if it’s worth the upgrade.', + 'section_slug': 'games', + 'content_type': 'article', + 'author': 'Robert Anderson', + 'published_at': '2026-07-03T15:10:54Z', + 'score': None, + 'comments_count': 197, + 'duration': None, + 'platforms': ['PC', 'PlayStation 5', 'Xbox Series X/S'], + 'tags': ['games', 'article', 'think', 'know', 'windows', 'here', 'why', 'copilot'], + 'home_rank': 26, + 'image_path': '/static/images/content/026-copilot-pc-buying-guide-windows-11-worth-it.jpg'}, + {'slug': 'scuf-instinct-pro-xbox-controller-five-year-restrospective', + 'source_path': '/articles/scuf-instinct-pro-xbox-controller-five-year-restrospective', + 'title': 'Scuf Instinct Pro: Why I Still Choose This Xbox Controller After Five Years', + 'description': 'Rated 9/10 by IGN as the best competitive Xbox gamepad, the Scuf Instinct Pro is ' + 'still worth the investment after five years of heavy use. Here is why.', + 'section_slug': 'games', + 'content_type': 'article', + 'author': 'Robert Anderson', + 'published_at': '2026-07-03T14:51:33Z', + 'score': None, + 'comments_count': 214, + 'duration': None, + 'platforms': ['PC', 'PlayStation 5', 'Xbox Series X/S'], + 'tags': ['games', 'article', 'scuf', 'instinct', 'pro', 'why', 'still', 'choose'], + 'home_rank': 27, + 'image_path': '/static/images/content/027-scuf-instinct-pro-xbox-controller-five-year-restrospective.jpg'}, + {'slug': 'pokemon-tcg-30th-celebration-product-lineup', + 'source_path': '/articles/pokemon-tcg-30th-celebration-product-lineup', + 'title': 'Pokémon TCG: 30th Celebration Product Lineup Revealed', + 'description': "IGN's current coverage of Pokémon TCG: 30th Celebration Product Lineup Revealed, " + 'captured from the live site for this WebHarbor mirror.', + 'section_slug': 'deals', + 'content_type': 'article', + 'author': 'Sara Heritage', + 'published_at': '2026-07-03T14:21:38Z', + 'score': None, + 'comments_count': 231, + 'duration': None, + 'platforms': ['Nintendo Switch 2', 'PC'], + 'tags': ['deals', 'article', 'pok', 'mon', 'tcg', '30th', 'celebration', 'product'], + 'home_rank': 28, + 'image_path': '/static/images/content/028-pokemon-tcg-30th-celebration-product-lineup.png'}, + {'slug': 'a-great-foundation-for-our-upcoming-projects-in-this-universe-cd-projekt-hails-cyberpunk-2077-as-it-hits-an-incredible-40-million-copies-sold', + 'source_path': '/articles/a-great-foundation-for-our-upcoming-projects-in-this-universe-cd-projekt-hails-cyberpunk-2077-as-it-hits-an-incredible-40-million-copies-sold', + 'title': "'A Great Foundation for Our Upcoming Projects in This Universe' — CD Projekt Hails " + 'Cyberpunk 2077 as It Hits an Incredible 40 Million Copies Sold', + 'description': 'CD Projekt has hailed the success of Cyberpunk 2077, announcing it has now hit ' + 'the 40 million copies sold mark. That’s up 5 million copies from the last time ' + 'the developer announced Cyberpunk 2077 sales, back in November when it hit 35 ' + 'million.', + 'section_slug': 'games', + 'content_type': 'article', + 'author': 'Wesley Yin-Poole', + 'published_at': '2026-07-03T13:09:48Z', + 'score': None, + 'comments_count': 248, + 'duration': None, + 'platforms': ['PC', 'PlayStation 5', 'Xbox Series X/S'], + 'tags': ['games', 'article', 'great', 'foundation', 'our', 'upcoming', 'projects', 'universe'], + 'home_rank': 29, + 'image_path': '/static/images/content/029-a-great-foundation-for-our-upcoming-projects-in-this-universe-cd-proje.jpg'}, + {'slug': 'after-sony-kills-ps5-discs-and-gta-6-is-just-a-code-in-a-box-xbox-is-using-halo-campaign-evolveds-physical-disc-as-a-selling-point', + 'source_path': '/articles/after-sony-kills-ps5-discs-and-gta-6-is-just-a-code-in-a-box-xbox-is-using-halo-campaign-evolveds-physical-disc-as-a-selling-point', + 'title': 'After Sony Kills PS5 Discs and GTA 6 is Just a Code in a Box, Xbox Is Using Halo: ' + "Campaign Evolved's Physical Disc as a Selling Point", + 'description': 'Microsoft is using the fact that Halo: Campaign Evolved will offer a physical ' + 'game disc in its box as a selling point.', + 'section_slug': 'games', + 'content_type': 'article', + 'author': 'Tom Phillips', + 'published_at': '2026-07-03T12:40:43Z', + 'score': None, + 'comments_count': 25, + 'duration': None, + 'platforms': ['PC', 'PlayStation 5', 'Xbox Series X/S'], + 'tags': ['games', 'article', 'after', 'sony', 'kills', 'ps5', 'discs', 'gta'], + 'home_rank': 30, + 'image_path': '/static/images/content/030-after-sony-kills-ps5-discs-and-gta-6-is-just-a-code-in-a-box-xbox-is-u.png'}, + {'slug': 'over-90-of-igns-audience-do-not-want-an-all-digital-gaming-future-but-playstation-is-unlikely-to-u-turn-on-its-decision-to-kill-discs', + 'source_path': '/articles/over-90-of-igns-audience-do-not-want-an-all-digital-gaming-future-but-playstation-is-unlikely-to-u-turn-on-its-decision-to-kill-discs', + 'title': "Over 90% of IGN's Audience Do Not Want an All-Digital Gaming Future, but PlayStation " + 'Is Unlikely to U-Turn on Its Decision to Kill Discs', + 'description': 'Sony has sparked a backlash for its decision to do away with video game discs on ' + "PlayStation consoles from 2028 — and the IGN audience's feeling on the matter is " + 'crystal clear, too.', + 'section_slug': 'games', + 'content_type': 'article', + 'author': 'Wesley Yin-Poole', + 'published_at': '2026-07-03T11:12:24Z', + 'score': None, + 'comments_count': 42, + 'duration': None, + 'platforms': ['PC', 'PlayStation 5', 'Xbox Series X/S'], + 'tags': ['games', 'article', 'over', 'ign', 'audience', 'not', 'want', 'digital'], + 'home_rank': 31, + 'image_path': '/static/images/content/031-over-90-of-igns-audience-do-not-want-an-all-digital-gaming-future-but-.jpg'}, + {'slug': 'as-sony-kills-physical-ps5-games-disc-production-factory-workers-are-set-to-be-reassigned', + 'source_path': '/articles/as-sony-kills-physical-ps5-games-disc-production-factory-workers-are-set-to-be-reassigned', + 'title': 'As Sony Kills Physical PS5 Games, Disc Production Factory Workers Are Set to be ' + 'Reassigned', + 'description': 'Sony has already begun plans to reassign staff at a major disc production plant, ' + 'following its decision to kill off production of physical PS5 games.', + 'section_slug': 'games', + 'content_type': 'article', + 'author': 'Tom Phillips', + 'published_at': '2026-07-03T10:51:14Z', + 'score': None, + 'comments_count': 59, + 'duration': None, + 'platforms': ['PC', 'PlayStation 5', 'Xbox Series X/S'], + 'tags': ['games', 'article', 'sony', 'kills', 'physical', 'ps5', 'disc', 'production'], + 'home_rank': 32, + 'image_path': '/static/images/content/032-as-sony-kills-physical-ps5-games-disc-production-factory-workers-are-s.jpg'}, + {'slug': 'assassins-creed-black-flag-resynced-global-release-and-preload-times-confirmed', + 'source_path': '/articles/assassins-creed-black-flag-resynced-global-release-and-preload-times-confirmed', + 'title': "Assassin's Creed Black Flag Resynced Global Release and Preload Times Confirmed", + 'description': "Assassin's Creed Black Flag Resynced publisher Ubisoft has confirmed the game's " + 'global release and preload times.', + 'section_slug': 'games', + 'content_type': 'article', + 'author': 'Tom Phillips', + 'published_at': '2026-07-03T09:57:43Z', + 'score': None, + 'comments_count': 76, + 'duration': None, + 'platforms': ['PC', 'PlayStation 5', 'Xbox Series X/S'], + 'tags': ['games', 'article', 'assassin', 'creed', 'black', 'flag', 'resynced', 'global'], + 'home_rank': 33, + 'image_path': '/static/images/content/033-assassins-creed-black-flag-resynced-global-release-and-preload-times-c.jpg'}, + {'slug': 'i-am-getting-at-least-5x-more-offers-for-video-game-adaptations-than-comic-book-adaptations-the-batman-2-co-writer-says-hollywood-is-shifting-from-superheroes-t', + 'source_path': '/articles/i-am-getting-at-least-5x-more-offers-for-video-game-adaptations-than-comic-book-adaptations-the-batman-2-co-writer-says-hollywood-is-shifting-from-superheroes-to-video-games', + 'title': "'I Am Getting at Least 5x More Offers for Video Game Adaptations Than Comic Book " + "Adaptations' — The Batman 2 Co-Writer Says Hollywood Is Shifting From Superheroes to " + 'Video Games', + 'description': 'The co-writer of The Batman: Part II has said Hollywood is shifting from a comic ' + 'book obsession to a video game obsession.', + 'section_slug': 'games', + 'content_type': 'article', + 'author': 'Wesley Yin-Poole', + 'published_at': '2026-07-03T09:57:38Z', + 'score': None, + 'comments_count': 93, + 'duration': None, + 'platforms': ['PC', 'PlayStation 5', 'Xbox Series X/S'], + 'tags': ['games', 'article', 'getting', 'least', 'more', 'offers', 'video', 'game'], + 'home_rank': 34, + 'image_path': '/static/images/content/034-i-am-getting-at-least-5x-more-offers-for-video-game-adaptations-than-c.jpg'}, + {'slug': 'spider-man-brand-new-day-director-gives-best-look-yet-at-villains-boomerang-and-the-hand-but-are-the-photos-a-misdirect', + 'source_path': '/articles/spider-man-brand-new-day-director-gives-best-look-yet-at-villains-boomerang-and-the-hand-but-are-the-photos-a-misdirect', + 'title': 'Spider-Man: Brand New Day Director Gives Best Look Yet at Villains Boomerang and the ' + 'Hand — but Are the Photos a Misdirect?', + 'description': 'Spider-Man: Brand New Day director Destin Daniel Cretton has given fans their ' + "best look yet at more of the film's villains, including two whose roles in the " + 'story remain particularly mysterious.', + 'section_slug': 'games', + 'content_type': 'article', + 'author': 'Tom Phillips', + 'published_at': '2026-07-03T09:04:03Z', + 'score': None, + 'comments_count': 110, + 'duration': None, + 'platforms': ['PC', 'PlayStation 5', 'Xbox Series X/S'], + 'tags': ['games', 'article', 'spider', 'man', 'brand', 'day', 'director', 'gives'], + 'home_rank': 35, + 'image_path': '/static/images/content/035-spider-man-brand-new-day-director-gives-best-look-yet-at-villains-boom.jpg'}, + {'slug': 'nintendo-brain-training-professor-explains-why-driving-a-manual-transmission-car-is-better-for-your-brain-than-an-automatic', + 'source_path': '/articles/nintendo-brain-training-professor-explains-why-driving-a-manual-transmission-car-is-better-for-your-brain-than-an-automatic', + 'title': 'Nintendo Brain Training Professor Explains Why Driving a Manual Transmission Car Is ' + 'Better for Your Brain Than an Automatic', + 'description': 'A study led by Professor Ryuta Kawashima suggests that driving a manual ' + 'transmission car is beneficial for the brain, and could help prevent dementia. ' + 'Kawashima is a neuroscientist known for the Brain Age and Dr Kawashima’s Brain ' + 'Training games, released primarily on Nintendo platforms between 2003 and 2020.', + 'section_slug': 'games', + 'content_type': 'article', + 'author': 'Wesley Yin-Poole', + 'published_at': '2026-07-03T08:14:40Z', + 'score': None, + 'comments_count': 127, + 'duration': None, + 'platforms': ['PC', 'PlayStation 5', 'Xbox Series X/S'], + 'tags': ['games', 'article', 'nintendo', 'brain', 'training', 'professor', 'explains', 'why'], + 'home_rank': 36, + 'image_path': '/static/images/content/036-nintendo-brain-training-professor-explains-why-driving-a-manual-transm.jpg'}, + {'slug': 'will-nintendo-ever-stop-making-physical-games-nvc-clips', + 'source_path': '/videos/will-nintendo-ever-stop-making-physical-games-nvc-clips', + 'title': 'Will Nintendo Ever Stop Making Physical Games? - NVC Clips', + 'description': 'Following PlayStation’s bombshell news that it’s ending physical game production ' + 'in 2028, the panel discusses what that means for the future of games ownership ' + 'and if Nintendo will ever follow suit. #nintendo #gaming #switch2', + 'section_slug': 'games', + 'content_type': 'video', + 'author': 'IGN Staff', + 'published_at': '2026-07-03T18:00:00Z', + 'score': None, + 'comments_count': 144, + 'duration': '3:12', + 'platforms': ['PC', 'PlayStation 5', 'Xbox Series X/S'], + 'tags': ['games', 'video', 'nintendo', 'ever', 'stop', 'making', 'physical', 'nvc'], + 'home_rank': 37, + 'image_path': '/static/images/content/001-grand-theft-auto-controversies.jpg'}, + {'slug': 'everything-major-added-to-final-fantasy-7-rebirth-since-launch', + 'source_path': '/videos/everything-major-added-to-final-fantasy-7-rebirth-since-launch', + 'title': 'Everything Major Added to Final Fantasy 7 Rebirth Since Launch', + 'description': "Now is the perfect time to explore Midgar and beyond as we await spring 2027's " + "FF7 Revelation. Here's everything major added to Final Fantasy 7 Rebirth since " + 'launch!Check out our fully interactive map and more FF7 Rebirth tips on our Wiki ' + 'guide: https://www.ign.com/wikis/final-fantasy-7-rebirth', + 'section_slug': 'games', + 'content_type': 'video', + 'author': 'IGN Staff', + 'published_at': '2026-07-03T17:54:00Z', + 'score': None, + 'comments_count': 161, + 'duration': '3:12', + 'platforms': ['PC', 'PlayStation 5', 'Xbox Series X/S'], + 'tags': ['games', 'video', 'everything', 'major', 'added', 'final', 'fantasy', 'rebirth'], + 'home_rank': 38, + 'image_path': '/static/images/content/002-playstations-physical-media-free-future-isnt-just-concerning-its-offen.jpg'}, + {'slug': 'playstations-physical-media-free-future-isnt-just-concerning-its-offensive-video', + 'source_path': '/videos/playstations-physical-media-free-future-isnt-just-concerning-its-offensive', + 'title': 'PlayStation’s Physical Media-Free Future Isn’t Just Concerning, It’s Offensive', + 'description': "PlayStation games are going fully digital in 2028. That's not just concerning " + "for physical media fans - it's offensive to players everywhere. With rising " + 'hardware prices due to AI and RAM shortages, delisted games, and future game ' + "consoles being prohibitively expensive, we're not thrilled about the idea of a " + 'PS6 being digital-only and having to leave our old', + 'section_slug': 'games', + 'content_type': 'video', + 'author': 'IGN Staff', + 'published_at': '2026-07-03T15:34:16Z', + 'score': None, + 'comments_count': 178, + 'duration': '3:12', + 'platforms': ['PC', 'PlayStation 5', 'Xbox Series X/S'], + 'tags': ['games', 'video', 'playstation', 'physical', 'media', 'free', 'future', 'isn'], + 'home_rank': 39, + 'image_path': '/static/images/content/003-silo-season-3-review.jpg'}, + {'slug': 'the-complicated-history-of-gta-controversies', + 'source_path': '/videos/the-complicated-history-of-gta-controversies', + 'title': 'The Complicated History of GTA Controversies', + 'description': "You don't become one of the biggest, most profitable media franchises in human " + 'history without stepping on a few toes, especially when there’s literally a ' + 'violent felony in your name.Grand Theft Auto was destined to collide with ' + 'controversy. Lawsuits, international incidents, and full-on moral panics have ' + 'followed in the wake of the series’ unparalleled succ', + 'section_slug': 'games', + 'content_type': 'video', + 'author': 'IGN Staff', + 'published_at': '2026-07-03T15:30:00Z', + 'score': None, + 'comments_count': 195, + 'duration': '3:12', + 'platforms': ['PC', 'PlayStation 5', 'Xbox Series X/S'], + 'tags': ['games', 'video', 'complicated', 'history', 'gta', 'controversies'], + 'home_rank': 40, + 'image_path': '/static/images/content/004-pokemon-tcg-prismatic-evolutions-etbs-are-back-in-stock-and-under-mark.png'}, + {'slug': 'dragon-ball-xenoverse-2-official-future-saga-chapter-4-trailer', + 'source_path': '/videos/dragon-ball-xenoverse-2-official-future-saga-chapter-4-trailer', + 'title': 'Dragon Ball Xenoverse 2 - Official Future Saga Chapter 4 Trailer', + 'description': "Don't miss this trailer for Dragon Ball Xenoverse 2 Future Saga Chapter 4! The " + 'finale of the Future Saga storyline, introducing new characters, new quests, new ' + 'moves, new costmes, new accessories and more! Dragon Ball Xenoverse 2 Future ' + 'Saga Chapter 4 releases July 8th 2026 for PlayStation 4 (PS4), PlayStation 5 ' + '(PS5), Xbox One, Xbox Series X|S, Nintendo Swi', + 'section_slug': 'videos', + 'content_type': 'video', + 'author': 'IGN Staff', + 'published_at': '2026-07-03T14:16:39Z', + 'score': None, + 'comments_count': 212, + 'duration': '3:12', + 'platforms': ['Nintendo Switch 2', 'PC'], + 'tags': ['videos', 'video', 'dragon', 'ball', 'xenoverse', 'official', 'future', 'saga'], + 'home_rank': 41, + 'image_path': '/static/images/content/005-cape-fear-episode-6-recap-and-review-possum.jpg'}, + {'slug': 'honkai-star-rail-official-in-ravages-does-the-whistle-sound-version-44-trailer', + 'source_path': '/videos/honkai-star-rail-official-in-ravages-does-the-whistle-sound-version-44-trailer', + 'title': 'Honkai: Star Rail - Official "In Ravages Does the Whistle Sound" Version 4.4 Trailer', + 'description': 'Check out the new "In Ravages Does the Whistle Sound" Version 4.4 trailer for ' + 'the strategy RPG. Honkai: Star Rail is available now on PC, iOS, Android, and ' + 'PlayStation 5 (PS5).', + 'section_slug': 'videos', + 'content_type': 'video', + 'author': 'IGN Staff', + 'published_at': '2026-07-03T13:43:43Z', + 'score': None, + 'comments_count': 229, + 'duration': '3:12', + 'platforms': ['Nintendo Switch 2', 'PC'], + 'tags': ['videos', 'video', 'honkai', 'star', 'rail', 'official', 'ravages', 'does'], + 'home_rank': 42, + 'image_path': '/static/images/content/006-anne-hathaway-quit-knocked-up-because-she-didnt-want-the-crowning-of-t.jpg'}, + {'slug': 'is-switch-2s-second-year-starting-off-strong-nvc-819', + 'source_path': '/videos/is-switch-2s-second-year-starting-off-strong-nvc-819', + 'title': 'Is Switch 2’s Second Year Starting Off Strong? - NVC 819', + 'description': 'After the Splatoon Raiders Direct, Rhythm Heaven Groove’s release, and another ' + 'week with Star Fox, Switch 2’s summer is starting to fully take shape. Join ' + 'Logan Plant, Peer Schneider, Miranda Sanchez, and Seth Macy as they discuss if ' + 'Switch 2’s sophomore year is off to a good start. Plus, following PlayStation’s ' + 'bombshell news that it’s ending physical game ', + 'section_slug': 'games', + 'content_type': 'video', + 'author': 'IGN Staff', + 'published_at': '2026-07-03T13:00:00Z', + 'score': None, + 'comments_count': 246, + 'duration': '3:12', + 'platforms': ['PC', 'PlayStation 5', 'Xbox Series X/S'], + 'tags': ['games', 'video', 'switch', 'second', 'year', 'starting', 'off', 'strong'], + 'home_rank': 43, + 'image_path': '/static/images/content/007-nintendo-president-says-the-pokemon-company-will-take-measures-to-resp.jpg'}, + {'slug': 'only-nintendo-can-save-physical-games-next-gen-console-watch', + 'source_path': '/videos/only-nintendo-can-save-physical-games-next-gen-console-watch', + 'title': 'Only Nintendo Can Save Physical Games - Next-Gen Console Watch', + 'description': 'This week we got the surprise announcement from Sony that it is ending ' + 'production of PlayStation discs in early 2028. So in 18 months, going forward ' + 'PlayStation will be an all-digital ecosystem. Your existing discs will work in ' + 'your PS5 if it has a disc drive, but the PlayStation 6 will presumably not play ' + "discs at all. We expect Xbox's Project Helix to go t", + 'section_slug': 'games', + 'content_type': 'video', + 'author': 'IGN Staff', + 'published_at': '2026-07-03T13:00:00Z', + 'score': None, + 'comments_count': 23, + 'duration': '3:12', + 'platforms': ['PC', 'PlayStation 5', 'Xbox Series X/S'], + 'tags': ['games', 'video', 'only', 'nintendo', 'can', 'save', 'physical', 'next'], + 'home_rank': 44, + 'image_path': '/static/images/content/008-college-football-27-review.jpg'}, + {'slug': 'avatar-the-last-airbender-cast-take-the-true-avatar-challenge-part-4', + 'source_path': '/videos/avatar-the-last-airbender-cast-take-the-true-avatar-challenge-part-4', + 'title': 'Avatar: The Last Airbender Cast Take the True Avatar Challenge Part 4', + 'description': "IGN's current coverage of Avatar: The Last Airbender Cast Take the True Avatar " + 'Challenge Part 4, captured from the live site for this WebHarbor mirror.', + 'section_slug': 'videos', + 'content_type': 'video', + 'author': 'IGN Staff', + 'published_at': '2026-07-03T01:56:29Z', + 'score': None, + 'comments_count': 40, + 'duration': '3:12', + 'platforms': ['Nintendo Switch 2', 'PC'], + 'tags': ['videos', 'video', 'avatar', 'last', 'airbender', 'cast', 'take', 'true'], + 'home_rank': 45, + 'image_path': '/static/images/content/009-mtg-marvel-collector-boosters-amazon-sealed-in-stock.png'}, + {'slug': 'avatar-the-last-airbender-cast-take-the-true-avatar-challenge-part-3', + 'source_path': '/videos/avatar-the-last-airbender-cast-take-the-true-avatar-challenge-part-3', + 'title': 'Avatar: The Last Airbender Cast Take the True Avatar Challenge Part 3', + 'description': "IGN's current coverage of Avatar: The Last Airbender Cast Take the True Avatar " + 'Challenge Part 3, captured from the live site for this WebHarbor mirror.', + 'section_slug': 'videos', + 'content_type': 'video', + 'author': 'IGN Staff', + 'published_at': '2026-07-03T01:56:04Z', + 'score': None, + 'comments_count': 57, + 'duration': '3:12', + 'platforms': ['Nintendo Switch 2', 'PC'], + 'tags': ['videos', 'video', 'avatar', 'last', 'airbender', 'cast', 'take', 'true'], + 'home_rank': 46, + 'image_path': '/static/images/content/010-nintendo-switch-summer-sale-best-deals-2026.png'}, + {'slug': 'ian-ousley-on-sokkas-iconic-cactus-juice-scene', + 'source_path': '/videos/ian-ousley-on-sokkas-iconic-cactus-juice-scene', + 'title': "Ian Ousley On Sokka's Iconic Cactus Juice Scene", + 'description': "IGN's current coverage of Ian Ousley On Sokka's Iconic Cactus Juice Scene, " + 'captured from the live site for this WebHarbor mirror.', + 'section_slug': 'videos', + 'content_type': 'video', + 'author': 'IGN Staff', + 'published_at': '2026-07-03T01:55:23Z', + 'score': None, + 'comments_count': 74, + 'duration': '3:12', + 'platforms': ['Nintendo Switch 2', 'PC'], + 'tags': ['videos', 'video', 'ian', 'ousley', 'sokka', 'iconic', 'cactus', 'juice'], + 'home_rank': 47, + 'image_path': '/static/images/content/011-pokemon-tcg-first-partner-illustration-collection-series-2-is-back-in-.png'}, + {'slug': 'danger-ehren-on-his-most-famous-jackass-stunt', + 'source_path': '/videos/danger-ehren-on-his-most-famous-jackass-stunt', + 'title': 'Danger Ehren On His Most Famous Jackass Stunt', + 'description': "IGN's current coverage of Danger Ehren On His Most Famous Jackass Stunt, " + 'captured from the live site for this WebHarbor mirror.', + 'section_slug': 'videos', + 'content_type': 'video', + 'author': 'IGN Staff', + 'published_at': '2026-07-03T01:54:01Z', + 'score': None, + 'comments_count': 91, + 'duration': '3:12', + 'platforms': ['Nintendo Switch 2', 'PC'], + 'tags': ['videos', 'video', 'danger', 'ehren', 'his', 'most', 'famous', 'jackass'], + 'home_rank': 48, + 'image_path': '/static/images/content/012-walmart-has-the-lowest-price-on-a-geforce-rtx-5070-prebuilt-gaming-pc-.jpg'}, + {'slug': 'avatar-the-last-airbender-cast-take-the-true-avatar-challenge-part-2', + 'source_path': '/videos/avatar-the-last-airbender-cast-take-the-true-avatar-challenge-part-2', + 'title': 'Avatar: The Last Airbender Cast Take the True Avatar Challenge Part 2', + 'description': "IGN's current coverage of Avatar: The Last Airbender Cast Take the True Avatar " + 'Challenge Part 2, captured from the live site for this WebHarbor mirror.', + 'section_slug': 'videos', + 'content_type': 'video', + 'author': 'IGN Staff', + 'published_at': '2026-07-03T01:53:12Z', + 'score': None, + 'comments_count': 108, + 'duration': '3:12', + 'platforms': ['Nintendo Switch 2', 'PC'], + 'tags': ['videos', 'video', 'avatar', 'last', 'airbender', 'cast', 'take', 'true'], + 'home_rank': 49, + 'image_path': '/static/images/content/013-project-hail-mary-streaming-release-date-prime-video.jpg'}, + {'slug': 'avatar-the-last-airbender-cast-take-the-true-avatar-challenge-part-1', + 'source_path': '/videos/avatar-the-last-airbender-cast-take-the-true-avatar-challenge-part-1', + 'title': 'Avatar: The Last Airbender Cast Take the True Avatar Challenge Part 1', + 'description': "IGN's current coverage of Avatar: The Last Airbender Cast Take the True Avatar " + 'Challenge Part 1, captured from the live site for this WebHarbor mirror.', + 'section_slug': 'videos', + 'content_type': 'video', + 'author': 'IGN Staff', + 'published_at': '2026-07-03T01:52:33Z', + 'score': None, + 'comments_count': 125, + 'duration': '3:12', + 'platforms': ['Nintendo Switch 2', 'PC'], + 'tags': ['videos', 'video', 'avatar', 'last', 'airbender', 'cast', 'take', 'true'], + 'home_rank': 50, + 'image_path': '/static/images/content/014-whats-new-on-hbo-max-july-2026.jpg'}, + {'slug': 'wee-mans-favorite-jackass-stunt', + 'source_path': '/videos/wee-mans-favorite-jackass-stunt', + 'title': "Wee-Man's Favorite Jackass Stunt", + 'description': "IGN's current coverage of Wee-Man's Favorite Jackass Stunt, captured from the " + 'live site for this WebHarbor mirror.', + 'section_slug': 'videos', + 'content_type': 'video', + 'author': 'IGN Staff', + 'published_at': '2026-07-03T01:50:44Z', + 'score': None, + 'comments_count': 142, + 'duration': '3:12', + 'platforms': ['Nintendo Switch 2', 'PC'], + 'tags': ['videos', 'video', 'wee', 'man', 'favorite', 'jackass', 'stunt'], + 'home_rank': 51, + 'image_path': '/static/images/content/015-whats-new-on-amazon-prime-video-july-2026.jpg'}, + {'slug': 'schrodingers-call-official-accolades-trailer', + 'source_path': '/videos/schrodingers-call-official-accolades-trailer', + 'title': "Schrodinger's Call - Official Accolades Trailer", + 'description': "Here's a look at the Accolades Trailer for Schrodinger's Call, an emotional " + 'narrative-driven visual novel developed by Acrobatic Chirimenjako. Players will ' + 'experience the 21 nanoseconds that exist between life and death as the world ' + "comes to a close. Take a look at the critical reception for Schrodinger's Call, " + 'available now for Nintendo Switch and PC (Steam', + 'section_slug': 'videos', + 'content_type': 'video', + 'author': 'IGN Staff', + 'published_at': '2026-07-02T23:24:30Z', + 'score': None, + 'comments_count': 159, + 'duration': '3:12', + 'platforms': ['Nintendo Switch 2', 'PC'], + 'tags': ['videos', 'video', 'schrodinger', 'call', 'official', 'accolades', 'trailer'], + 'home_rank': 52, + 'image_path': '/static/images/content/016-whats-new-on-netflix-july-2026.jpg'}, + {'slug': 'the-most-immersive-way-to-play-ac-black-flag-resynced', + 'source_path': '/videos/the-most-immersive-way-to-play-ac-black-flag-resynced', + 'title': 'The Most Immersive Way to Play AC Black Flag Resynced', + 'description': "Assassin's Creed Black Flag Resynced isn't just getting a PS5 Pro patch. Ubisoft " + "says it's using the console to completely transform how the Caribbean looks!", + 'section_slug': 'videos', + 'content_type': 'video', + 'author': 'IGN Staff', + 'published_at': '2026-07-02T23:07:00Z', + 'score': None, + 'comments_count': 176, + 'duration': '3:12', + 'platforms': ['Nintendo Switch 2', 'PC'], + 'tags': ['videos', 'video', 'most', 'immersive', 'way', 'play', 'black', 'flag'], + 'home_rank': 53, + 'image_path': '/static/images/content/017-obsession-digital-streaming-4k-blu-ray.png'}, + {'slug': 'clairvoyage-the-lighthouse-official-reveal-trailer', + 'source_path': '/videos/clairvoyage-the-lighthouse-official-reveal-trailer', + 'title': 'Clairvoyage: The Lighthouse - Official Reveal Trailer', + 'description': 'Check out the Reveal Trailer for Clairvoyage: The Lighthouse, a time-loop horror ' + 'puzzle game developed by Volpe Software. Players will return to the same ten ' + 'minutes to restore the lighthouse before a ship hits the rocks. Solve puzzles, ' + 'encounter strange anomalies, and explore the stormy island on that fateful ' + 'night. Clairvoyage: The Lighthouse is coming soo', + 'section_slug': 'videos', + 'content_type': 'video', + 'author': 'IGN Staff', + 'published_at': '2026-07-02T22:55:04Z', + 'score': None, + 'comments_count': 193, + 'duration': '3:12', + 'platforms': ['Nintendo Switch 2', 'PC'], + 'tags': ['videos', 'video', 'clairvoyage', 'lighthouse', 'official', 'reveal', 'trailer'], + 'home_rank': 54, + 'image_path': '/static/images/content/018-whats-new-on-apple-tv-july-2026.jpg'}, + {'slug': 'silver-palace-official-beta-test-gameplay-showcase-trailer', + 'source_path': '/videos/silver-palace-official-beta-test-gameplay-showcase-trailer', + 'title': 'Silver Palace - Official Beta Test Gameplay Showcase Trailer', + 'description': 'Take a look at gameplay from Silver Palace in this latest trailer for the ' + 'upcoming open-world fantasy adventure RPG. The new trailer shows us elements of ' + 'the story through the eyes of the female protagonist, while giving us a peek at ' + 'the lore, traversal, combat, a glimpse at a formidable enemy, sleuthing ' + 'gameplay, mini0games and side stories in the world of ', + 'section_slug': 'games', + 'content_type': 'video', + 'author': 'IGN Staff', + 'published_at': '2026-07-02T22:40:19Z', + 'score': None, + 'comments_count': 210, + 'duration': '3:12', + 'platforms': ['PC', 'PlayStation 5', 'Xbox Series X/S'], + 'tags': ['games', 'video', 'silver', 'palace', 'official', 'beta', 'test', 'gameplay'], + 'home_rank': 55, + 'image_path': '/static/images/content/019-the-super-mario-galaxy-movie-streaming-release-date-peacock.jpg'}, + {'slug': 'expeditions-samurai-official-behind-the-art-video', + 'source_path': '/videos/expeditions-samurai-official-behind-the-art-video', + 'title': "Expeditions: Samurai - Official 'Behind the Art' Video", + 'description': 'Join members of the development team at Campfire Cabal for a behind-the-scenes ' + 'look at the art direction of Expeditions: Samurai in this latest video for the ' + 'upcoming strategy RPG game. Expeditions: Samurai will be available on PC in ' + '2026.', + 'section_slug': 'videos', + 'content_type': 'video', + 'author': 'IGN Staff', + 'published_at': '2026-07-02T21:34:39Z', + 'score': None, + 'comments_count': 227, + 'duration': '3:12', + 'platforms': ['Nintendo Switch 2', 'PC'], + 'tags': ['videos', 'video', 'expeditions', 'samurai', 'official', 'behind', 'art'], + 'home_rank': 56, + 'image_path': '/static/images/content/020-whats-new-on-disney-plus-july-2026.jpg'}, + {'slug': 'rubber-bird-official-announcement-trailer', + 'source_path': '/videos/rubber-bird-official-announcement-trailer', + 'title': 'Rubber Bird - Official Announcement Trailer', + 'description': 'Take a look at the Announcement Trailer for Rubber Bird, a vibrant physics-based ' + '3D platformer developed by Outretaupe. Players will overcome bite-sized levels ' + 'as a bird with elastic arms. Jump, stretch, and throw items to collect items ' + 'from level to level. Rubber Bird is coming soon to PC (Steam).', + 'section_slug': 'videos', + 'content_type': 'video', + 'author': 'IGN Staff', + 'published_at': '2026-07-02T20:36:45Z', + 'score': None, + 'comments_count': 244, + 'duration': '3:12', + 'platforms': ['Nintendo Switch 2', 'PC'], + 'tags': ['videos', 'video', 'rubber', 'bird', 'official', 'announcement', 'trailer'], + 'home_rank': 57, + 'image_path': '/static/images/content/021-most-anticipated-tv-shows-to-stream-in-july-2026.jpg'}, + {'slug': 'strikers-club-official-release-date-trailer', + 'source_path': '/videos/strikers-club-official-release-date-trailer', + 'title': 'Strikers Club - Official Release Date Trailer', + 'description': "Here's a look at the Release Date Trailer for Strikers Club, a multiplayer " + 'free-to-play football (soccer) game developed by Oddshot Games. Players will ' + 'take to the pitch to master advanced movements from dribbling, passing, and ' + 'shooting to outplay the opposition and attain the win. Strikers Club is ' + 'launching on July 17 for PC (Steam).', + 'section_slug': 'videos', + 'content_type': 'video', + 'author': 'IGN Staff', + 'published_at': '2026-07-02T20:15:50Z', + 'score': None, + 'comments_count': 21, + 'duration': '3:12', + 'platforms': ['Nintendo Switch 2', 'PC'], + 'tags': ['videos', 'video', 'strikers', 'club', 'official', 'release', 'date', 'trailer'], + 'home_rank': 58, + 'image_path': '/static/images/content/022-marvel-rivals-fans-think-the-new-captain-america-skin-is-a-mod-but-its.png'}, + {'slug': 'taillights-official-reveal-trailer', + 'source_path': '/videos/taillights-official-reveal-trailer', + 'title': 'Taillights - Official Reveal Trailer', + 'description': "Don't miss the Reveal Trailer for Taillights, a physics-based co-op survival " + 'road trip game developed by Linx Interactive. Players will hit the road to ' + 'gather supplies, repair the vehicle when necessary, and uncover strange ' + 'mysteries along the way. Survive the road in Taillights, coming soon to PC ' + '(Steam).', + 'section_slug': 'videos', + 'content_type': 'video', + 'author': 'IGN Staff', + 'published_at': '2026-07-02T20:03:30Z', + 'score': None, + 'comments_count': 38, + 'duration': '3:12', + 'platforms': ['Nintendo Switch 2', 'PC'], + 'tags': ['videos', 'video', 'taillights', 'official', 'reveal', 'trailer'], + 'home_rank': 59, + 'image_path': '/static/images/content/023-people-are-worried-theyll-only-see-half-of-the-odyssey-if-they-miss-ou.png'}, + {'slug': 'dont-touch-grass-official-announcement-trailer', + 'source_path': '/videos/dont-touch-grass-official-announcement-trailer', + 'title': "Don't Touch Grass - Official Announcement Trailer", + 'description': "Here's your look at the Don't Touch Grass announcement trailer for this upcoming " + 'first-person parkour platformer game. Equipped with a high-tech exoskeleton, ' + "attempt to save gaming in Don't Touch Grass. Jump, wall-run and slide to build " + 'momentum. The faster you go, the more special abilities you can use. Set faster ' + "times and remember, Don't Touch Grass. Don'", + 'section_slug': 'videos', + 'content_type': 'video', + 'author': 'IGN Staff', + 'published_at': '2026-07-02T19:46:52Z', + 'score': None, + 'comments_count': 55, + 'duration': '3:12', + 'platforms': ['Nintendo Switch 2', 'PC'], + 'tags': ['videos', 'video', 'don', 'touch', 'grass', 'official', 'announcement', 'trailer'], + 'home_rank': 60, + 'image_path': '/static/images/content/024-the-view-from-japan-playstations-push-for-an-all-digital-future-raises.jpg'}, + {'slug': 'beyond-the-blight-official-announcement-trailer', + 'source_path': '/videos/beyond-the-blight-official-announcement-trailer', + 'title': 'Beyond The Blight - Official Announcement Trailer', + 'description': 'Take a look at the Announcement Trailer for Beyond The Blight, a 2.5D ' + 'first-person dungeon crawler developed by Midnight Steep. Players will head into ' + "dungeons to slay enemies, attain loot, and defeat the Blight's inception at its " + 'core. Beyond The Blight is coming soon to PC (Steam).', + 'section_slug': 'videos', + 'content_type': 'video', + 'author': 'IGN Staff', + 'published_at': '2026-07-02T19:46:46Z', + 'score': None, + 'comments_count': 72, + 'duration': '3:12', + 'platforms': ['Nintendo Switch 2', 'PC'], + 'tags': ['videos', 'video', 'beyond', 'blight', 'official', 'announcement', 'trailer'], + 'home_rank': 61, + 'image_path': '/static/images/content/025-significant-pay-rises-offered-by-professor-layton-studio-if-you-correc.jpg'}, + {'slug': 'the-odyssey-official-completely-in-imax-featurette', + 'source_path': '/videos/the-odyssey-official-completely-in-imax-featurette', + 'title': "The Odyssey - Official 'Completely in IMAX' Featurette", + 'description': "Take a look at the 'Completely in IMAX' Featurette for The Odyssey, the latest " + "mythological action retelling of Homer's tale from the mind of Christopher " + 'Nolan. Check out the behind-the-scenes look of the process of filming the first ' + 'movie entirely in IMAX. The Odyssey is in theaters on July 17.Christopher ' + 'Nolan’s next film, The Odyssey, is a mythic action e', + 'section_slug': 'videos', + 'content_type': 'video', + 'author': 'IGN Staff', + 'published_at': '2026-07-02T19:19:39Z', + 'score': None, + 'comments_count': 89, + 'duration': '3:12', + 'platforms': ['Nintendo Switch 2', 'PC'], + 'tags': ['videos', 'video', 'odyssey', 'official', 'completely', 'imax', 'featurette'], + 'home_rank': 62, + 'image_path': '/static/images/content/026-copilot-pc-buying-guide-windows-11-worth-it.jpg'}, + {'slug': 'maddmadd-official-trailer', + 'source_path': '/videos/maddmadd-official-trailer', + 'title': 'MADDMADD - Official Trailer', + 'description': 'Check out gameplay and more from MADDMADD in this trailer for the upcoming ' + 'third-person run-and-gun boomer shooter with fast-paced movement, beautiful ' + 'giblets and a comedic tone. Blow stuff up in your tank and then get ' + 'up-close-and-personal with your boomstick! MADDMADD will be available on PC. A ' + 'demo is out now on Steam.', + 'section_slug': 'videos', + 'content_type': 'video', + 'author': 'IGN Staff', + 'published_at': '2026-07-02T19:19:07Z', + 'score': None, + 'comments_count': 106, + 'duration': '3:12', + 'platforms': ['Nintendo Switch 2', 'PC'], + 'tags': ['videos', 'video', 'maddmadd', 'official', 'trailer'], + 'home_rank': 63, + 'image_path': '/static/images/content/027-scuf-instinct-pro-xbox-controller-five-year-restrospective.jpg'}, + {'slug': 'unwelcome-guests-official-demo-trailer', + 'source_path': '/videos/unwelcome-guests-official-demo-trailer', + 'title': 'Unwelcome Guests - Official Demo Trailer', + 'description': 'Take a trek through the dungeon in this new Unwelcome Guests trailer for the ' + 'upcoming Mörk Borg-inspired first-person shooter game that combines brutal ' + 'dungeon runs with narrative progression and a stylized hand-drawn 3D aesthetic. ' + 'Unwelcome Guests will be available on PC. A demo is out now on Steam.', + 'section_slug': 'videos', + 'content_type': 'video', + 'author': 'IGN Staff', + 'published_at': '2026-07-02T19:02:52Z', + 'score': None, + 'comments_count': 123, + 'duration': '3:12', + 'platforms': ['Nintendo Switch 2', 'PC'], + 'tags': ['videos', 'video', 'unwelcome', 'guests', 'official', 'demo', 'trailer'], + 'home_rank': 64, + 'image_path': '/static/images/content/028-pokemon-tcg-30th-celebration-product-lineup.png'}, + {'slug': 'dream-corps-official-launch-trailer', + 'source_path': '/videos/dream-corps-official-launch-trailer', + 'title': 'Dream Corps - Official Launch Trailer', + 'description': "Don't miss the Launch Trailer for Dream Corps, a liminal surreal horror game " + 'developed by 98 Redd Games. Players will explore a variety of abstract ' + 'environments from the nostalgic to the vibrant. Explore the mystery of a ' + 'sinister corporation in Dream Corps, available now for macOS, SteamVR, and PC ' + '(Steam).', + 'section_slug': 'videos', + 'content_type': 'video', + 'author': 'IGN Staff', + 'published_at': '2026-07-02T19:00:06Z', + 'score': None, + 'comments_count': 140, + 'duration': '3:12', + 'platforms': ['Nintendo Switch 2', 'PC'], + 'tags': ['videos', 'video', 'dream', 'corps', 'official', 'launch', 'trailer'], + 'home_rank': 65, + 'image_path': '/static/images/content/029-a-great-foundation-for-our-upcoming-projects-in-this-universe-cd-proje.jpg'}, + {'slug': 'apathema-official-demo-trailer', + 'source_path': '/videos/apathema-official-demo-trailer', + 'title': 'Apathema - Official Demo Trailer', + 'description': 'Get a fresh look at Apathema in this trailer for the upcoming grunge fantasy ' + "turn-based RPG, inspired by God Husk, Baroque, and King's Field. Cursed to " + 'repair the inner organs of The Chasm, you fight to uncover the secrets of your ' + 'existence and end a self-devouring cycle of Apathy. Apathema will be available ' + 'on PC. A demo is out now on Steam.', + 'section_slug': 'videos', + 'content_type': 'video', + 'author': 'IGN Staff', + 'published_at': '2026-07-02T18:51:25Z', + 'score': None, + 'comments_count': 157, + 'duration': '3:12', + 'platforms': ['Nintendo Switch 2', 'PC'], + 'tags': ['videos', 'video', 'apathema', 'official', 'demo', 'trailer'], + 'home_rank': 66, + 'image_path': '/static/images/content/030-after-sony-kills-ps5-discs-and-gta-6-is-just-a-code-in-a-box-xbox-is-u.png'}, + {'slug': 'optek-official-gameplay-trailer', + 'source_path': '/videos/optek-official-gameplay-trailer', + 'title': 'OpTek - Official Gameplay Trailer', + 'description': "Don't miss the Gameplay Trailer for OpTek, a co-op repair horror game developed " + 'by Yesclip Games. Players will embody engineers to complete routine maintenance ' + 'work as part of the OpTek Team. Screw bolts, calibrate parts, and return to the ' + 'Panopticon alone or with friends. OpTek is coming soon to PC (Steam).', + 'section_slug': 'games', + 'content_type': 'video', + 'author': 'IGN Staff', + 'published_at': '2026-07-02T18:37:50Z', + 'score': None, + 'comments_count': 174, + 'duration': '3:12', + 'platforms': ['PC', 'PlayStation 5', 'Xbox Series X/S'], + 'tags': ['games', 'video', 'optek', 'official', 'gameplay', 'trailer'], + 'home_rank': 67, + 'image_path': '/static/images/content/031-over-90-of-igns-audience-do-not-want-an-all-digital-gaming-future-but-.jpg'}, + {'slug': 'neurospasm-official-demo-trailer', + 'source_path': '/videos/neurospasm-official-demo-trailer', + 'title': 'Neurospasm - Official Demo Trailer', + 'description': 'Take a look at action-packed gameplay from Neurospasm in this trailer for the ' + 'upcoming first-person roguelike horde shooter game. Hook your brain into your ' + 'computer, dive into a game inside a game, unbox and stack unique items, use up ' + 'to 6 weapons at once, kill weird fleshy enemies, and uncover what the shady ' + 'company behind the game is hiding. Neurospasm wil', + 'section_slug': 'videos', + 'content_type': 'video', + 'author': 'IGN Staff', + 'published_at': '2026-07-02T18:36:47Z', + 'score': None, + 'comments_count': 191, + 'duration': '3:12', + 'platforms': ['Nintendo Switch 2', 'PC'], + 'tags': ['videos', 'video', 'neurospasm', 'official', 'demo', 'trailer'], + 'home_rank': 68, + 'image_path': '/static/images/content/032-as-sony-kills-physical-ps5-games-disc-production-factory-workers-are-s.jpg'}, + {'slug': 'the-henchmen-official-launch-trailer', + 'source_path': '/videos/the-henchmen-official-launch-trailer', + 'title': 'The Henchmen - Official Launch Trailer', + 'description': 'Relive the ‘90s crime sandbox alone, or with a partner by your side. Prepare for ' + 'a head-on collision of the classic and the contemporary in The Henchmen, ' + 'available now on PC. Watch The Henchmen launch trailer for this chaotic, ' + 'co-operative crime thriller game.', + 'section_slug': 'videos', + 'content_type': 'video', + 'author': 'IGN Staff', + 'published_at': '2026-07-02T18:23:12Z', + 'score': None, + 'comments_count': 208, + 'duration': '3:12', + 'platforms': ['Nintendo Switch 2', 'PC'], + 'tags': ['videos', 'video', 'henchmen', 'official', 'launch', 'trailer'], + 'home_rank': 69, + 'image_path': '/static/images/content/033-assassins-creed-black-flag-resynced-global-release-and-preload-times-c.jpg'}, + {'slug': 'stonewards-official-demo-launch-trailer', + 'source_path': '/videos/stonewards-official-demo-launch-trailer', + 'title': 'Stonewards - Official Demo Launch Trailer', + 'description': 'Take a look at the Demo Launch Trailer for Stonewards, a co-op mining roguelite ' + 'game developed by Banana Tiger Studio. Players will command dwarves to mine for ' + 'resources, attain powerful upgrades, and defend the kingdom from monsters. ' + 'Stonewards is coming soon to PC (Steam), with a demo available now on Steam.', + 'section_slug': 'videos', + 'content_type': 'video', + 'author': 'IGN Staff', + 'published_at': '2026-07-02T18:15:32Z', + 'score': None, + 'comments_count': 225, + 'duration': '3:12', + 'platforms': ['Nintendo Switch 2', 'PC'], + 'tags': ['videos', 'video', 'stonewards', 'official', 'demo', 'launch', 'trailer'], + 'home_rank': 70, + 'image_path': '/static/images/content/034-i-am-getting-at-least-5x-more-offers-for-video-game-adaptations-than-c.jpg'}, + {'slug': 'clown-camp-official-launch-trailer', + 'source_path': '/videos/clown-camp-official-launch-trailer', + 'title': 'Clown Camp - Official Launch Trailer', + 'description': 'Clown Camp is available now on PC. Watch the Clown Camp launch trailer for this ' + "90's style, first-person survival horror game. Hostile clowns lurk the woods. " + 'Fight-or-flight to uncover their gruesome secrets throughout a chilling ' + 'single-player story. Clever inventory management and resourcefulness are your ' + 'only hopes to escape the Summer of 1999 alive.', + 'section_slug': 'videos', + 'content_type': 'video', + 'author': 'IGN Staff', + 'published_at': '2026-07-02T18:10:58Z', + 'score': None, + 'comments_count': 242, + 'duration': '3:12', + 'platforms': ['Nintendo Switch 2', 'PC'], + 'tags': ['videos', 'video', 'clown', 'camp', 'official', 'launch', 'trailer'], + 'home_rank': 71, + 'image_path': '/static/images/content/035-spider-man-brand-new-day-director-gives-best-look-yet-at-villains-boom.jpg'}, + {'slug': 'blue-wyrm-official-release-date-trailer', + 'source_path': '/videos/blue-wyrm-official-release-date-trailer', + 'title': 'Blue Wyrm - Official Release Date Trailer', + 'description': 'Blue Wyrm gets a new trailer and a release date announcement. Watch the latest ' + 'Blue Wyrm trailer to see gameplay and more from this upcoming psychedelic ' + 'first-person souls-like with a retro aesthetic and skill-heavy combat system. Do ' + 'you have what it takes to save your wife in the crypt of the Blue Wyrm? Blue ' + 'Wyrm will be available on PC on July 16, 2026.', + 'section_slug': 'videos', + 'content_type': 'video', + 'author': 'IGN Staff', + 'published_at': '2026-07-02T18:01:36Z', + 'score': None, + 'comments_count': 19, + 'duration': '3:12', + 'platforms': ['Nintendo Switch 2', 'PC'], + 'tags': ['videos', 'video', 'blue', 'wyrm', 'official', 'release', 'date', 'trailer'], + 'home_rank': 72, + 'image_path': '/static/images/content/036-nintendo-brain-training-professor-explains-why-driving-a-manual-transm.jpg'}, + {'slug': 'company-of-heroes-3-final-stand-official-announcement-trailer', + 'source_path': '/videos/company-of-heroes-3-final-stand-official-announcement-trailer', + 'title': 'Company of Heroes 3: Final Stand - Official Announcement Trailer', + 'description': 'Watch the Company of Heroes 3: Final Stand announcement trailer for this ' + 'upcoming all-new standalone wave defense experience for solo and cooperative ' + "play. Final Stand expands the series' signature combat with roguelite " + 'progression that encourages experimentation, adaptation, and replayability. ' + 'Players must dig in, build layered defenses, and survive against', + 'section_slug': 'videos', + 'content_type': 'video', + 'author': 'IGN Staff', + 'published_at': '2026-07-02T17:34:48Z', + 'score': None, + 'comments_count': 36, + 'duration': '3:12', + 'platforms': ['Nintendo Switch 2', 'PC'], + 'tags': ['videos', 'video', 'company', 'heroes', 'final', 'stand', 'official', 'announcement'], + 'home_rank': 73, + 'image_path': '/static/images/content/001-grand-theft-auto-controversies.jpg'}, + {'slug': 'virtua-fighter-crossroads-official-narrative-overview-trailer', + 'source_path': '/videos/virtua-fighter-crossroads-official-narrative-overview-trailer', + 'title': 'Virtua Fighter Crossroads - Official Narrative Overview Trailer', + 'description': 'Take a look at the Narrative Overview Trailer for Virtua Fighter Crossroads, the ' + 'upcoming installment in the hit action fighting game franchise developed by Ryu ' + 'Ga Gotoku Studio. Players can hear from key figures in the development team ' + "about exploring the game's core narrative that sets out to provide proper " + 'context for why the characters fight. Explore the', + 'section_slug': 'videos', + 'content_type': 'video', + 'author': 'IGN Staff', + 'published_at': '2026-07-02T17:27:09Z', + 'score': None, + 'comments_count': 53, + 'duration': '3:12', + 'platforms': ['Nintendo Switch 2', 'PC'], + 'tags': ['videos', + 'video', + 'virtua', + 'fighter', + 'crossroads', + 'official', + 'narrative', + 'overview'], + 'home_rank': 74, + 'image_path': '/static/images/content/002-playstations-physical-media-free-future-isnt-just-concerning-its-offen.jpg'}, + {'slug': 'remothered-tormented-fathers-broken-porcelain-remastered-official-announcement-trailer', + 'source_path': '/videos/remothered-tormented-fathers-broken-porcelain-remastered-official-announcement-trailer', + 'title': 'Remothered: Tormented Fathers & Broken Porcelain Remastered - Official Announcement ' + 'Trailer', + 'description': "Don't miss the Announcement Trailer for Remothered: Tormented Fathers & Broken " + 'Porcelain Remastered, two modern versions of the survival horror series ' + 'developed by Stormind Games. Players can enjoy new features with Unreal Engine 5 ' + 'technology, Lumen-based lighting, enhanced character models, and more. ' + 'Remothered: Tormented Fathers & Broken Porcelain Remaster', + 'section_slug': 'videos', + 'content_type': 'video', + 'author': 'IGN Staff', + 'published_at': '2026-07-02T17:14:17Z', + 'score': None, + 'comments_count': 70, + 'duration': '3:12', + 'platforms': ['Nintendo Switch 2', 'PC'], + 'tags': ['videos', + 'video', + 'remothered', + 'tormented', + 'fathers', + 'broken', + 'porcelain', + 'remastered'], + 'home_rank': 75, + 'image_path': '/static/images/content/003-silo-season-3-review.jpg'}, + {'slug': 'biped-2-official-version-20-update-trailer', + 'source_path': '/videos/biped-2-official-version-20-update-trailer', + 'title': 'Biped 2 - Official Version 2.0 Update Trailer', + 'description': 'Watch the latest Biped 2 trailer for this co-op puzzle platformer game for up to ' + "four players to see what's available in version 2.0, bringing revamped puzzles, " + 'mechanics, along with improvements and enhanced performance on Nintendo Switch ' + '2. Biped 2 is available now on PC via Steam, PlayStation 4 (PS4), PlayStation 5 ' + '(PS5), Xbox One, Xbox Series X/S, Ninten', + 'section_slug': 'videos', + 'content_type': 'video', + 'author': 'IGN Staff', + 'published_at': '2026-07-02T17:12:44Z', + 'score': None, + 'comments_count': 87, + 'duration': '3:12', + 'platforms': ['Nintendo Switch 2', 'PC'], + 'tags': ['videos', 'video', 'biped', 'official', 'version', 'update', 'trailer'], + 'home_rank': 76, + 'image_path': '/static/images/content/004-pokemon-tcg-prismatic-evolutions-etbs-are-back-in-stock-and-under-mark.png'}, + {'slug': 'gta-5', + 'source_path': '/wikis/gta-5', + 'title': 'GTA 5 Guide', + 'description': "IGN's current wiki coverage for GTA 5 Guide, with walkthrough notes, tracking " + 'steps, and related guide links.', + 'section_slug': 'guides', + 'content_type': 'guide', + 'author': 'Danielle Abraham', + 'published_at': '2026-06-27T10:31:00Z', + 'score': None, + 'comments_count': 104, + 'duration': None, + 'platforms': ['Nintendo Switch 2', 'PC'], + 'tags': ['guides', 'guide', 'gta'], + 'home_rank': 77, + 'image_path': '/static/images/content/005-cape-fear-episode-6-recap-and-review-possum.jpg'}, + {'slug': 'all-gta-5-cheat-codes-and-secrets-for-pc-and-console', + 'source_path': '/wikis/gta-5/All_GTA_5_Cheat_Codes_and_Secrets_For_PC_and_Console', + 'title': 'All GTA 5 Cheat Codes and Secrets For PC and Console', + 'description': "IGN's current wiki coverage for All GTA 5 Cheat Codes and Secrets For PC and " + 'Console, with walkthrough notes, tracking steps, and related guide links.', + 'section_slug': 'guides', + 'content_type': 'guide', + 'author': 'Will Borger', + 'published_at': '2026-06-27T08:31:00Z', + 'score': None, + 'comments_count': 121, + 'duration': None, + 'platforms': ['Nintendo Switch 2', 'PC'], + 'tags': ['guides', 'guide', 'gta', 'cheat', 'codes', 'secrets', 'console'], + 'home_rank': 78, + 'image_path': '/static/images/content/006-anne-hathaway-quit-knocked-up-because-she-didnt-want-the-crowning-of-t.jpg'}, + {'slug': 'persona-3-reload', + 'source_path': '/wikis/persona-3-reload', + 'title': 'Persona 3 Reload Guide', + 'description': "IGN's current wiki coverage for Persona 3 Reload Guide, with walkthrough notes, " + 'tracking steps, and related guide links.', + 'section_slug': 'guides', + 'content_type': 'guide', + 'author': 'Scott White', + 'published_at': '2026-06-27T06:31:00Z', + 'score': None, + 'comments_count': 138, + 'duration': None, + 'platforms': ['Nintendo Switch 2', 'PC'], + 'tags': ['guides', 'guide', 'persona', 'reload'], + 'home_rank': 79, + 'image_path': '/static/images/content/007-nintendo-president-says-the-pokemon-company-will-take-measures-to-resp.jpg'}, + {'slug': 'persona-3-reload-classroom-answers', + 'source_path': '/wikis/persona-3-reload/Persona_3_Reload_Classroom_Answers', + 'title': 'Persona 3 Reload Classroom Answers', + 'description': "IGN's current wiki coverage for Persona 3 Reload Classroom Answers, with " + 'walkthrough notes, tracking steps, and related guide links.', + 'section_slug': 'guides', + 'content_type': 'guide', + 'author': 'Luke Reilly', + 'published_at': '2026-06-27T04:31:00Z', + 'score': None, + 'comments_count': 155, + 'duration': None, + 'platforms': ['Nintendo Switch 2', 'PC'], + 'tags': ['guides', 'guide', 'persona', 'reload', 'classroom', 'answers'], + 'home_rank': 80, + 'image_path': '/static/images/content/008-college-football-27-review.jpg'}, + {'slug': 'gta-online-weekly-updates', + 'source_path': '/wikis/gta-5/GTA_Online_Weekly_Updates', + 'title': 'GTA Online Weekly Updates', + 'description': "IGN's current wiki coverage for GTA Online Weekly Updates, with walkthrough " + 'notes, tracking steps, and related guide links.', + 'section_slug': 'guides', + 'content_type': 'guide', + 'author': 'Matt Purslow', + 'published_at': '2026-06-27T02:31:00Z', + 'score': None, + 'comments_count': 172, + 'duration': None, + 'platforms': ['Nintendo Switch 2', 'PC'], + 'tags': ['guides', 'guide', 'gta', 'online', 'weekly', 'updates'], + 'home_rank': 81, + 'image_path': '/static/images/content/009-mtg-marvel-collector-boosters-amazon-sealed-in-stock.png'}, + {'slug': 'animal-hospital-roblox', + 'source_path': '/wikis/animal-hospital-roblox', + 'title': 'Animal Hospital [Roblox] Guide', + 'description': "IGN's current wiki coverage for Animal Hospital [Roblox] Guide, with walkthrough " + 'notes, tracking steps, and related guide links.', + 'section_slug': 'guides', + 'content_type': 'guide', + 'author': 'Sarah Thwaites', + 'published_at': '2026-06-27T00:31:00Z', + 'score': None, + 'comments_count': 189, + 'duration': None, + 'platforms': ['Nintendo Switch 2', 'PC'], + 'tags': ['guides', 'guide', 'animal', 'hospital', 'roblox'], + 'home_rank': 82, + 'image_path': '/static/images/content/010-nintendo-switch-summer-sale-best-deals-2026.png'}, + {'slug': 'all-anomalies', + 'source_path': '/wikis/animal-hospital-roblox/All_Anomalies', + 'title': 'All Anomalies', + 'description': "IGN's current wiki coverage for All Anomalies, with walkthrough notes, tracking " + 'steps, and related guide links.', + 'section_slug': 'guides', + 'content_type': 'guide', + 'author': 'Jesse Schedeen', + 'published_at': '2026-06-26T22:31:00Z', + 'score': None, + 'comments_count': 206, + 'duration': None, + 'platforms': ['Nintendo Switch 2', 'PC'], + 'tags': ['guides', 'guide', 'anomalies'], + 'home_rank': 83, + 'image_path': '/static/images/content/011-pokemon-tcg-first-partner-illustration-collection-series-2-is-back-in-.png'}, + {'slug': 'crystal-caves', + 'source_path': '/wikis/donkey-kong-64/Crystal_Caves', + 'title': 'Crystal Caves', + 'description': "IGN's current wiki coverage for Crystal Caves, with walkthrough notes, tracking " + 'steps, and related guide links.', + 'section_slug': 'guides', + 'content_type': 'guide', + 'author': 'Tom Phillips', + 'published_at': '2026-06-26T20:31:00Z', + 'score': None, + 'comments_count': 223, + 'duration': None, + 'platforms': ['Nintendo Switch 2', 'PC'], + 'tags': ['guides', 'guide', 'crystal', 'caves'], + 'home_rank': 84, + 'image_path': '/static/images/content/012-walmart-has-the-lowest-price-on-a-geforce-rtx-5070-prebuilt-gaming-pc-.jpg'}, + {'slug': 'donkey-kong-64', + 'source_path': '/wikis/donkey-kong-64', + 'title': 'Donkey Kong 64 Guide', + 'description': "IGN's current wiki coverage for Donkey Kong 64 Guide, with walkthrough notes, " + 'tracking steps, and related guide links.', + 'section_slug': 'guides', + 'content_type': 'guide', + 'author': 'Virginia Glaze', + 'published_at': '2026-06-26T18:31:00Z', + 'score': None, + 'comments_count': 240, + 'duration': None, + 'platforms': ['Nintendo Switch 2', 'PC'], + 'tags': ['guides', 'guide', 'donkey', 'kong'], + 'home_rank': 85, + 'image_path': '/static/images/content/013-project-hail-mary-streaming-release-date-prime-video.jpg'}, + {'slug': 'crystal-caves-tiny-kong-walkthrough', + 'source_path': '/wikis/donkey-kong-64/Crystal_Caves_Tiny_Kong_Walkthrough', + 'title': 'Crystal Caves Tiny Kong Walkthrough', + 'description': "IGN's current wiki coverage for Crystal Caves Tiny Kong Walkthrough, with " + 'walkthrough notes, tracking steps, and related guide links.', + 'section_slug': 'guides', + 'content_type': 'guide', + 'author': 'Eric Song', + 'published_at': '2026-06-26T16:31:00Z', + 'score': None, + 'comments_count': 17, + 'duration': None, + 'platforms': ['Nintendo Switch 2', 'PC'], + 'tags': ['guides', 'guide', 'crystal', 'caves', 'tiny', 'kong', 'walkthrough'], + 'home_rank': 86, + 'image_path': '/static/images/content/014-whats-new-on-hbo-max-july-2026.jpg'}, + {'slug': 'crystal-caves-lanky-kong-walkthrough', + 'source_path': '/wikis/donkey-kong-64/Crystal_Caves_Lanky_Kong_Walkthrough', + 'title': 'Crystal Caves Lanky Kong Walkthrough', + 'description': "IGN's current wiki coverage for Crystal Caves Lanky Kong Walkthrough, with " + 'walkthrough notes, tracking steps, and related guide links.', + 'section_slug': 'guides', + 'content_type': 'guide', + 'author': 'Daemon Hatfield', + 'published_at': '2026-06-26T14:31:00Z', + 'score': None, + 'comments_count': 34, + 'duration': None, + 'platforms': ['Nintendo Switch 2', 'PC'], + 'tags': ['guides', 'guide', 'crystal', 'caves', 'lanky', 'kong', 'walkthrough'], + 'home_rank': 87, + 'image_path': '/static/images/content/015-whats-new-on-amazon-prime-video-july-2026.jpg'}, + {'slug': 'secrets-and-unlocks', + 'source_path': '/wikis/star-fox/Secrets_and_Unlocks', + 'title': 'Secrets and Unlocks', + 'description': "IGN's current wiki coverage for Secrets and Unlocks, with walkthrough notes, " + 'tracking steps, and related guide links.', + 'section_slug': 'guides', + 'content_type': 'guide', + 'author': 'Michael Higham', + 'published_at': '2026-06-26T12:31:00Z', + 'score': None, + 'comments_count': 51, + 'duration': None, + 'platforms': ['Nintendo Switch 2', 'PC'], + 'tags': ['guides', 'guide', 'secrets', 'unlocks'], + 'home_rank': 88, + 'image_path': '/static/images/content/016-whats-new-on-netflix-july-2026.jpg'}, + {'slug': 'star-fox', + 'source_path': '/wikis/star-fox', + 'title': 'Star Fox Guide', + 'description': "IGN's current wiki coverage for Star Fox Guide, with walkthrough notes, tracking " + 'steps, and related guide links.', + 'section_slug': 'guides', + 'content_type': 'guide', + 'author': 'Jada Griffin', + 'published_at': '2026-06-26T10:31:00Z', + 'score': None, + 'comments_count': 68, + 'duration': None, + 'platforms': ['Nintendo Switch 2', 'PC'], + 'tags': ['guides', 'guide', 'star', 'fox'], + 'home_rank': 89, + 'image_path': '/static/images/content/017-obsession-digital-streaming-4k-blu-ray.png'}, + {'slug': 'stage-list', + 'source_path': '/wikis/rhythm-heaven-groove/Stage_List', + 'title': 'Stage List', + 'description': "IGN's current wiki coverage for Stage List, with walkthrough notes, tracking " + 'steps, and related guide links.', + 'section_slug': 'guides', + 'content_type': 'guide', + 'author': 'Nico Vergara', + 'published_at': '2026-06-26T08:31:00Z', + 'score': None, + 'comments_count': 85, + 'duration': None, + 'platforms': ['Nintendo Switch 2', 'PC'], + 'tags': ['guides', 'guide', 'stage', 'list'], + 'home_rank': 90, + 'image_path': '/static/images/content/018-whats-new-on-apple-tv-july-2026.jpg'}, + {'slug': 'rhythm-heaven-groove', + 'source_path': '/wikis/rhythm-heaven-groove', + 'title': 'Rhythm Heaven Groove Guide', + 'description': "IGN's current wiki coverage for Rhythm Heaven Groove Guide, with walkthrough " + 'notes, tracking steps, and related guide links.', + 'section_slug': 'guides', + 'content_type': 'guide', + 'author': 'Wesley Yin-Poole', + 'published_at': '2026-06-26T06:31:00Z', + 'score': None, + 'comments_count': 102, + 'duration': None, + 'platforms': ['Nintendo Switch 2', 'PC'], + 'tags': ['guides', 'guide', 'rhythm', 'heaven', 'groove'], + 'home_rank': 91, + 'image_path': '/static/images/content/019-the-super-mario-galaxy-movie-streaming-release-date-peacock.jpg'}, + {'slug': 'minecraft', + 'source_path': '/wikis/minecraft', + 'title': 'Minecraft Guide', + 'description': "IGN's current wiki coverage for Minecraft Guide, with walkthrough notes, " + 'tracking steps, and related guide links.', + 'section_slug': 'guides', + 'content_type': 'guide', + 'author': 'Carlos Morales', + 'published_at': '2026-06-26T04:31:00Z', + 'score': None, + 'comments_count': 119, + 'duration': None, + 'platforms': ['Nintendo Switch 2', 'PC'], + 'tags': ['guides', 'guide', 'minecraft'], + 'home_rank': 92, + 'image_path': '/static/images/content/020-whats-new-on-disney-plus-july-2026.jpg'}, + {'slug': 'dress-to-impress', + 'source_path': '/wikis/dress-to-impress', + 'title': 'Dress To Impress Guide', + 'description': "IGN's current wiki coverage for Dress To Impress Guide, with walkthrough notes, " + 'tracking steps, and related guide links.', + 'section_slug': 'guides', + 'content_type': 'guide', + 'author': 'Danielle Abraham', + 'published_at': '2026-06-26T02:31:00Z', + 'score': None, + 'comments_count': 136, + 'duration': None, + 'platforms': ['Nintendo Switch 2', 'PC'], + 'tags': ['guides', 'guide', 'dress', 'impress'], + 'home_rank': 93, + 'image_path': '/static/images/content/021-most-anticipated-tv-shows-to-stream-in-july-2026.jpg'}, + {'slug': 'fortnite', + 'source_path': '/wikis/fortnite', + 'title': 'Fortnite Guide', + 'description': "IGN's current wiki coverage for Fortnite Guide, with walkthrough notes, tracking " + 'steps, and related guide links.', + 'section_slug': 'guides', + 'content_type': 'guide', + 'author': 'Will Borger', + 'published_at': '2026-06-26T00:31:00Z', + 'score': None, + 'comments_count': 153, + 'duration': None, + 'platforms': ['Nintendo Switch 2', 'PC'], + 'tags': ['guides', 'guide', 'fortnite'], + 'home_rank': 94, + 'image_path': '/static/images/content/022-marvel-rivals-fans-think-the-new-captain-america-skin-is-a-mod-but-its.png'}, + {'slug': 'valorant', + 'source_path': '/wikis/valorant', + 'title': 'Valorant Guide', + 'description': "IGN's current wiki coverage for Valorant Guide, with walkthrough notes, tracking " + 'steps, and related guide links.', + 'section_slug': 'guides', + 'content_type': 'guide', + 'author': 'Scott White', + 'published_at': '2026-06-25T22:31:00Z', + 'score': None, + 'comments_count': 170, + 'duration': None, + 'platforms': ['Nintendo Switch 2', 'PC'], + 'tags': ['guides', 'guide', 'valorant'], + 'home_rank': 95, + 'image_path': '/static/images/content/023-people-are-worried-theyll-only-see-half-of-the-odyssey-if-they-miss-ou.png'}, + {'slug': 'rocket-league', + 'source_path': '/wikis/rocket-league', + 'title': 'Rocket League Guide', + 'description': "IGN's current wiki coverage for Rocket League Guide, with walkthrough notes, " + 'tracking steps, and related guide links.', + 'section_slug': 'guides', + 'content_type': 'guide', + 'author': 'Luke Reilly', + 'published_at': '2026-06-25T20:31:00Z', + 'score': None, + 'comments_count': 187, + 'duration': None, + 'platforms': ['Nintendo Switch 2', 'PC'], + 'tags': ['guides', 'guide', 'rocket', 'league'], + 'home_rank': 96, + 'image_path': '/static/images/content/024-the-view-from-japan-playstations-push-for-an-all-digital-future-raises.jpg'}, + {'slug': 'counter-strike-2', + 'source_path': '/wikis/counter-strike-2', + 'title': 'Counter-Strike 2 Guide', + 'description': "IGN's current wiki coverage for Counter-Strike 2 Guide, with walkthrough notes, " + 'tracking steps, and related guide links.', + 'section_slug': 'guides', + 'content_type': 'guide', + 'author': 'Matt Purslow', + 'published_at': '2026-06-25T18:31:00Z', + 'score': None, + 'comments_count': 204, + 'duration': None, + 'platforms': ['Nintendo Switch 2', 'PC'], + 'tags': ['guides', 'guide', 'counter', 'strike'], + 'home_rank': 97, + 'image_path': '/static/images/content/025-significant-pay-rises-offered-by-professor-layton-studio-if-you-correc.jpg'}, + {'slug': '1-speed-keyboard-escape-roblox', + 'source_path': '/wikis/1-speed-keyboard-escape-roblox', + 'title': '+1 Speed Keyboard Escape [Roblox] Guide', + 'description': "IGN's current wiki coverage for +1 Speed Keyboard Escape [Roblox] Guide, with " + 'walkthrough notes, tracking steps, and related guide links.', + 'section_slug': 'guides', + 'content_type': 'guide', + 'author': 'Sarah Thwaites', + 'published_at': '2026-06-25T16:31:00Z', + 'score': None, + 'comments_count': 221, + 'duration': None, + 'platforms': ['Nintendo Switch 2', 'PC'], + 'tags': ['guides', 'guide', 'speed', 'keyboard', 'escape', 'roblox'], + 'home_rank': 98, + 'image_path': '/static/images/content/026-copilot-pc-buying-guide-windows-11-worth-it.jpg'}, + {'slug': 'red-dead-redemption-2', + 'source_path': '/wikis/red-dead-redemption-2', + 'title': 'Red Dead Redemption 2 Guide', + 'description': "IGN's current wiki coverage for Red Dead Redemption 2 Guide, with walkthrough " + 'notes, tracking steps, and related guide links.', + 'section_slug': 'guides', + 'content_type': 'guide', + 'author': 'Jesse Schedeen', + 'published_at': '2026-06-25T14:31:00Z', + 'score': None, + 'comments_count': 238, + 'duration': None, + 'platforms': ['Nintendo Switch 2', 'PC'], + 'tags': ['guides', 'guide', 'red', 'dead', 'redemption'], + 'home_rank': 99, + 'image_path': '/static/images/content/027-scuf-instinct-pro-xbox-controller-five-year-restrospective.jpg'}, + {'slug': 'hogwarts-legacy', + 'source_path': '/wikis/hogwarts-legacy', + 'title': 'Hogwarts Legacy Guide', + 'description': "IGN's current wiki coverage for Hogwarts Legacy Guide, with walkthrough notes, " + 'tracking steps, and related guide links.', + 'section_slug': 'guides', + 'content_type': 'guide', + 'author': 'Tom Phillips', + 'published_at': '2026-06-25T12:31:00Z', + 'score': None, + 'comments_count': 15, + 'duration': None, + 'platforms': ['Nintendo Switch 2', 'PC'], + 'tags': ['guides', 'guide', 'hogwarts', 'legacy'], + 'home_rank': 100, + 'image_path': '/static/images/content/028-pokemon-tcg-30th-celebration-product-lineup.png'}, + {'slug': 'iphone-gaming-controller-buying-guide-2026', + 'source_path': '/articles/iphone-gaming-controller-buying-guide-2026', + 'title': 'Best iPhone and Android Controllers for Cloud Gaming in 2026', + 'description': "IGN's mobile coverage compares Backbone, Razer, and console-style controllers " + 'for iOS and Android cloud gaming sessions.', + 'section_slug': 'games', + 'content_type': 'article', + 'author': 'IGN Staff', + 'published_at': '2026-06-25T10:31:00Z', + 'score': None, + 'comments_count': 54, + 'duration': None, + 'platforms': ['iOS', 'Android', 'Mobile'], + 'tags': ['mobile', 'article', 'ios', 'android', 'cloud', 'gaming', 'controller'], + 'home_rank': 101, + 'image_path': '/static/images/content/026-copilot-pc-buying-guide-windows-11-worth-it.jpg'}, + {'slug': 'pokemon-go-summer-event-ios-android-bonuses', + 'source_path': '/articles/pokemon-go-summer-event-ios-android-bonuses', + 'title': 'Pokémon Go Summer Event Adds New Mobile Bonuses for iOS and Android', + 'description': "IGN's mobile news desk tracks the latest Pokémon Go event bonuses, raids, and " + 'limited-time rewards for phone players.', + 'section_slug': 'games', + 'content_type': 'article', + 'author': 'IGN Staff', + 'published_at': '2026-06-25T08:31:00Z', + 'score': None, + 'comments_count': 71, + 'duration': None, + 'platforms': ['iOS', 'Android', 'Mobile'], + 'tags': ['mobile', 'article', 'pokemon', 'go', 'ios', 'android', 'event'], + 'home_rank': 102, + 'image_path': '/static/images/content/011-pokemon-tcg-first-partner-illustration-collection-series-2-is-back-in-.png'}, + {'slug': 'netflix-games-mobile-lineup-july-2026', + 'source_path': '/articles/netflix-games-mobile-lineup-july-2026', + 'title': 'Netflix Games Reveals Its July Mobile Lineup', + 'description': "IGN's mobile category rounds up the latest Android and iOS releases coming to " + 'Netflix Games this month.', + 'section_slug': 'tv', + 'content_type': 'article', + 'author': 'IGN Staff', + 'published_at': '2026-06-25T06:31:00Z', + 'score': None, + 'comments_count': 38, + 'duration': None, + 'platforms': ['iOS', 'Android', 'Mobile'], + 'tags': ['mobile', 'article', 'netflix', 'games', 'ios', 'android'], + 'home_rank': 103, + 'image_path': '/static/images/content/016-whats-new-on-netflix-july-2026.jpg'}, + {'slug': 'minions-monsters-review', + 'source_path': '/articles/minions-monsters-review', + 'title': 'Minions & Monsters Review', + 'description': "IGN's movie review weighs the animated sequel's visual energy, comedy timing, " + 'and family-audience appeal.', + 'section_slug': 'movies', + 'content_type': 'review', + 'author': 'Carlos Morales', + 'published_at': '2026-06-25T04:31:00Z', + 'score': 7, + 'comments_count': 92, + 'duration': None, + 'platforms': [], + 'tags': ['movies', 'review', 'minions', 'monsters', 'animation'], + 'home_rank': 104, + 'image_path': '/static/images/content/002-minions-monsters-review.jpg'}, + {'slug': 'the-super-mario-galaxy-movie-review', + 'source_path': '/articles/the-super-mario-galaxy-movie-review', + 'title': 'The Super Mario Galaxy Movie Review', + 'description': "IGN's movie review checks whether the Mushroom Kingdom's space adventure keeps " + 'its platforming charm on the big screen.', + 'section_slug': 'movies', + 'content_type': 'review', + 'author': 'Jesse Schedeen', + 'published_at': '2026-06-25T02:31:00Z', + 'score': 8, + 'comments_count': 116, + 'duration': None, + 'platforms': [], + 'tags': ['movies', 'review', 'mario', 'galaxy', 'nintendo'], + 'home_rank': 105, + 'image_path': '/static/images/content/019-the-super-mario-galaxy-movie-streaming-release-date-peacock.jpg'}, + {'slug': 'obsession-4k-blu-ray-review', + 'source_path': '/articles/obsession-4k-blu-ray-review', + 'title': 'Obsession 4K Blu-ray Review', + 'description': "IGN's movie review evaluates the thriller restoration, feature set, and whether " + 'the new disc is worth adding to a collector shelf.', + 'section_slug': 'movies', + 'content_type': 'review', + 'author': 'Scott White', + 'published_at': '2026-06-25T00:31:00Z', + 'score': 8, + 'comments_count': 64, + 'duration': None, + 'platforms': [], + 'tags': ['movies', 'review', 'obsession', '4k', 'blu-ray'], + 'home_rank': 106, + 'image_path': '/static/images/content/017-obsession-digital-streaming-4k-blu-ray.png'}, + {'slug': 'turtle-beach-stealth-pro-ii-review', + 'source_path': '/articles/turtle-beach-stealth-pro-ii-review', + 'title': 'Turtle Beach Stealth Pro II Review', + 'description': "IGN's tech review tests microphone clarity, battery life, PC compatibility, " + 'console support, and long-session comfort.', + 'section_slug': 'reviews', + 'content_type': 'review', + 'author': 'Eric Song', + 'published_at': '2026-06-24T22:31:00Z', + 'score': 8, + 'comments_count': 83, + 'duration': None, + 'platforms': ['PC', 'PlayStation 5', 'Xbox Series X/S'], + 'tags': ['tech', 'hardware', 'review', 'headset', 'pc', 'audio'], + 'home_rank': 107, + 'image_path': '/static/images/content/016-turtle-beach-stealth-pro-ii-review.jpg'}, + {'slug': 'corsair-one-a600-review', + 'source_path': '/articles/corsair-one-a600-review', + 'title': 'Corsair One A600 Review', + 'description': "IGN's tech review benchmarks the compact gaming PC's RTX performance, thermals, " + 'ports, and upgrade tradeoffs.', + 'section_slug': 'reviews', + 'content_type': 'review', + 'author': 'Michael Higham', + 'published_at': '2026-06-24T20:31:00Z', + 'score': 9, + 'comments_count': 109, + 'duration': None, + 'platforms': ['PC'], + 'tags': ['tech', 'hardware', 'review', 'corsair', 'pc', 'rtx'], + 'home_rank': 108, + 'image_path': '/static/images/content/017-corsair-one-a600-review.jpg'}, + {'slug': 'nex-playground-review', + 'source_path': '/articles/nex-playground-review', + 'title': 'Nex Playground Review', + 'description': "IGN's tech review looks at motion controls, family games, setup friction, and " + 'how the camera hardware handles small living rooms.', + 'section_slug': 'reviews', + 'content_type': 'review', + 'author': 'Danielle Abraham', + 'published_at': '2026-06-24T18:31:00Z', + 'score': 7, + 'comments_count': 58, + 'duration': None, + 'platforms': ['PC'], + 'tags': ['tech', 'hardware', 'review', 'nex', 'playground', 'camera'], + 'home_rank': 109, + 'image_path': '/static/images/content/018-nex-playground-review.jpg'}] diff --git a/sites/ign/requirements.txt b/sites/ign/requirements.txt new file mode 100644 index 00000000..24a84ac9 --- /dev/null +++ b/sites/ign/requirements.txt @@ -0,0 +1,6 @@ +Flask +Flask-SQLAlchemy +Flask-Login +Flask-WTF +Flask-Bcrypt +SQLAlchemy diff --git a/sites/ign/seed_data.py b/sites/ign/seed_data.py new file mode 100644 index 00000000..0494ec94 --- /dev/null +++ b/sites/ign/seed_data.py @@ -0,0 +1,427 @@ +"""Idempotent seed routines for the IGN mirror.""" +from __future__ import annotations + +import json +import re +from datetime import datetime, timedelta + +from content_seed import CONTENT_ITEMS + +MIRROR_REFERENCE_DATE = datetime(2026, 7, 2, 12, 0, 0) +PASSWORD = "TestPass123!" + +SECTIONS = [ + { + "slug": "games", + "name": "Games", + "description": "Console, PC, and mobile gaming news, previews, and analysis.", + "color": "#bf1313", + "sort_order": 1, + }, + { + "slug": "reviews", + "name": "Reviews", + "description": "IGN verdicts and score cards across games, tech, movies, and TV.", + "color": "#ff4d4d", + "sort_order": 2, + }, + { + "slug": "guides", + "name": "Guides", + "description": "Walkthroughs, wiki updates, map tasks, and checklist-style guides.", + "color": "#2457ff", + "sort_order": 3, + }, + { + "slug": "videos", + "name": "Videos", + "description": "Trailers, Daily Fix episodes, podcasts, and exclusive clips.", + "color": "#7c3aed", + "sort_order": 4, + }, + { + "slug": "movies", + "name": "Movies", + "description": "Film news, interviews, trailers, and theatrical reviews.", + "color": "#dd6b20", + "sort_order": 5, + }, + { + "slug": "tv", + "name": "TV", + "description": "Streaming, anime, episode recaps, and show reviews.", + "color": "#991e8d", + "sort_order": 6, + }, + { + "slug": "deals", + "name": "Deals", + "description": "Daily deal posts, gaming hardware, collectibles, and entertainment offers.", + "color": "#087f5b", + "sort_order": 7, + }, +] + +AUTHORS = [ + "Luke Reilly", + "Matt Purslow", + "Sarah Thwaites", + "Jesse Schedeen", + "Tom Phillips", + "Virginia Glaze", + "Eric Song", + "Daemon Hatfield", + "Michael Higham", + "Jada Griffin", + "Nico Vergara", + "Wesley Yin-Poole", + "Carlos Morales", + "Danielle Abraham", + "Will Borger", + "Scott White", +] + +USERS = [ + { + "username": "alice_j", + "email": "alice.j@test.com", + "display_name": "Alice Johnson", + "region": "Seattle, WA", + "favorite_platform": "PlayStation 5", + "bio": "Tracks PlayStation, prestige TV, and guide checklists.", + }, + { + "username": "bob_c", + "email": "bob.c@test.com", + "display_name": "Bob Chen", + "region": "Austin, TX", + "favorite_platform": "PC", + "bio": "Keeps a queue of strategy videos and RPG reviews.", + }, + { + "username": "carol_d", + "email": "carol.d@test.com", + "display_name": "Carol Davis", + "region": "Brooklyn, NY", + "favorite_platform": "Nintendo Switch", + "bio": "Compares family games, streaming picks, and weekend deals.", + }, + { + "username": "david_k", + "email": "david.k@test.com", + "display_name": "David Kim", + "region": "Los Angeles, CA", + "favorite_platform": "Xbox Series X/S", + "bio": "Follows trailers, sci-fi TV, and new hardware coverage.", + }, +] + + +def parse_datetime(value: str, offset: int) -> datetime: + if value: + try: + return datetime.fromisoformat(value.replace("Z", "+00:00")).replace(tzinfo=None) + except ValueError: + pass + return MIRROR_REFERENCE_DATE - timedelta(hours=offset * 6) + + +def clean_author(raw: str, title: str, index: int) -> str: + author = re.sub(r"^by\s+", "", raw or "", flags=re.IGNORECASE).strip() + bad_bits = ["guide", "tracking", "share", "rating", "start", "open", "contributors"] + if ( + not author + or len(author) > 32 + or any(bit in author.lower() for bit in bad_bits) + or author.lower() in title.lower() + ): + return AUTHORS[index % len(AUTHORS)] + return author + + +def score_label(score: int | None) -> str: + if score is None: + return "" + if score >= 10: + return "Masterpiece" + if score >= 9: + return "Amazing" + if score >= 8: + return "Great" + if score >= 7: + return "Good" + if score >= 6: + return "Okay" + return "Mediocre" + + +def guide_checklist(item: dict) -> list[str]: + title = item["title"].lower() + if "gta online" in title: + return [ + "Claim Independence Day freebies", + "Review Fine Art Collector bonuses", + "Check discounted vehicles", + "Open the related GTA Online map", + ] + if "fortnite" in title: + return [ + "Check weekly quests", + "Review battle pass rewards", + "Mark the map update section", + "Save a rotation note", + ] + if "mass effect" in title or "citadel" in title or "illium" in title: + return [ + "Read mission prerequisites", + "Track squad conversation choices", + "Mark collectible objective complete", + "Check follow-up assignment", + ] + return [ + "Read overview", + "Check latest updates", + "Mark first objective complete", + "Review related links", + ] + + +def body_for(item: dict, section_name: str) -> list[str]: + title = item["title"] + description = item["description"].strip() + tags = ", ".join(item.get("tags", [])[2:5]) or section_name.lower() + content_type = item["content_type"] + if content_type == "review": + detail = ( + "The review page separates the verdict from the score card, platforms, " + "reader discussion, and related coverage so agents must inspect the detail page." + ) + elif content_type == "video": + detail = ( + "The video page presents a duration, related clips, source game or show context, " + "and a playlist action for account workflows." + ) + elif content_type == "guide": + detail = ( + "The guide page includes checklist progress and wiki-style update notes that can " + "be changed only after signing in." + ) + else: + detail = ( + "The article page expands the summary with context, related coverage, and account " + "actions for saving, commenting, and playlist planning." + ) + return [ + description, + f"This local IGN mirror files the story under {section_name} and tags it with {tags}.", + detail, + ( + "Cross-links and near-miss recommendations are intentionally included so browsing " + "and search tasks require comparison instead of clicking the first result." + ), + ] + + +def seed_database(db, Section, ContentItem) -> None: + if ContentItem.query.count() > 0: + return + + for section in SECTIONS: + db.session.add(Section(**section)) + db.session.flush() + + section_names = {section["slug"]: section["name"] for section in SECTIONS} + for index, item in enumerate(CONTENT_ITEMS, start=1): + section_slug = item["section_slug"] + if section_slug not in section_names: + section_slug = "games" + body = body_for(item, section_names[section_slug]) + checklist = guide_checklist(item) if item["content_type"] == "guide" else [] + db.session.add( + ContentItem( + slug=item["slug"], + source_path=item["source_path"], + title=item["title"], + description=item["description"], + body_json=json.dumps(body), + section_slug=section_slug, + content_type=item["content_type"], + author=clean_author(item.get("author", ""), item["title"], index), + published_at=parse_datetime(item.get("published_at", ""), index), + image_path=item.get("image_path", ""), + score=item.get("score"), + score_label=score_label(item.get("score")), + comments_count=int(item.get("comments_count") or 0), + duration=item.get("duration") or "", + platforms_json=json.dumps(item.get("platforms") or []), + tags_json=json.dumps(item.get("tags") or []), + checklist_json=json.dumps(checklist), + is_featured=index <= 4, + is_top_story=index <= 20, + is_editors_choice=(item.get("score") or 0) >= 9, + sort_rank=item.get("home_rank") or index, + ) + ) + db.session.commit() + + +def first_item(ContentItem, slug: str): + return ContentItem.query.filter_by(slug=slug).first() + + +def find_item(ContentItem, title_part: str): + return ContentItem.query.filter(ContentItem.title.ilike(f"%{title_part}%")).first() + + +def seed_benchmark_users( + db, + User, + ContentItem, + SavedItem, + PlaylistEntry, + Comment, + AlertSubscription, + Digest, + GuideProgress, +) -> None: + if User.query.filter_by(email="alice.j@test.com").first(): + return + + all_items = ContentItem.query.order_by(ContentItem.id.asc()).all() + guides = ContentItem.query.filter_by(content_type="guide").order_by(ContentItem.id.asc()).all() + videos = ContentItem.query.filter_by(content_type="video").order_by(ContentItem.id.asc()).all() + reviews = ContentItem.query.filter_by(content_type="review").order_by(ContentItem.id.asc()).all() + + selected = { + "gta_history": find_item(ContentItem, "Complicated History of GTA"), + "playstation": find_item(ContentItem, "Physical Media-Free Future"), + "silo": find_item(ContentItem, "Silo Season 3 Review"), + "pokemon_deal": find_item(ContentItem, "Amazon Restocks Rare Pokémon"), + "cape_fear": find_item(ContentItem, "Cape Fear Episode 6"), + "anne_hathaway": find_item(ContentItem, "Anne Hathaway Quit Knocked Up"), + "college_football": find_item(ContentItem, "College Football 27 Review"), + "walmart_rtx": find_item(ContentItem, "GeForce RTX 5070"), + "nvc": find_item(ContentItem, "Will Nintendo Ever Stop Making Physical Games"), + "hbo": find_item(ContentItem, "HBO Max"), + "prime_video": find_item(ContentItem, "Amazon Prime Video"), + "gta_weekly": first_item(ContentItem, "gta-online-weekly-updates"), + "gta_cheats": first_item(ContentItem, "all-gta-5-cheat-codes-and-secrets-for-pc-and-console"), + "fortnite": first_item(ContentItem, "fortnite"), + } + + users = [] + for index, payload in enumerate(USERS): + user = User( + username=payload["username"], + email=payload["email"], + display_name=payload["display_name"], + region=payload["region"], + favorite_platform=payload["favorite_platform"], + bio=payload["bio"], + avatar_color=["#bf1313", "#2457ff", "#087f5b", "#7c3aed"][index], + ) + user.set_password(PASSWORD) + db.session.add(user) + users.append(user) + db.session.flush() + + def add_saved(user, item, folder, note=""): + if item: + db.session.add(SavedItem(user_id=user.id, item_id=item.id, folder=folder, note=note)) + + def add_playlist(user, item, position, status="queued", note=""): + if item: + db.session.add( + PlaylistEntry( + user_id=user.id, + item_id=item.id, + position=position, + status=status, + note=note, + ) + ) + + for index, user in enumerate(users): + pool = [item for item in selected.values() if item] + all_items[index * 6 : index * 6 + 8] + unique = [] + for item in pool: + if item and item.id not in {x.id for x in unique}: + unique.append(item) + + for saved_index, item in enumerate(unique[:5], start=1): + add_saved( + user, + item, + ["Read Later", "Weekend", "Reviews", "Deals", "Guides"][saved_index - 1], + f"Benchmark saved item {saved_index} for {user.username}.", + ) + + queue = (videos + reviews + guides + unique)[index : index + 5] + for position, item in enumerate(queue[:4], start=1): + add_playlist( + user, + item, + position, + status="watched" if position == 1 else "queued", + note=f"{user.display_name.split()[0]}'s playlist note {position}.", + ) + + for alert in [ + ("games", user.favorite_platform), + ("reviews", "review"), + ("guides", "weekly update"), + ]: + db.session.add( + AlertSubscription( + user_id=user.id, + section_slug=alert[0], + keyword=alert[1], + frequency="daily" if alert[0] != "guides" else "weekly", + active=True, + ) + ) + + digest_items = unique[:3] + db.session.add( + Digest( + user_id=user.id, + digest_number=f"IGN-{index + 1:04d}", + title=f"{user.display_name.split()[0]}'s IGN Digest", + delivery="email", + status="delivered", + item_slugs_json=json.dumps([item.slug for item in digest_items]), + created_at=MIRROR_REFERENCE_DATE - timedelta(days=index + 1), + ) + ) + + if unique: + db.session.add( + Comment( + user_id=user.id, + item_id=unique[0].id, + body="Saving this for a comparison task later.", + sentiment="positive", + ) + ) + if len(unique) > 1: + db.session.add( + Comment( + user_id=user.id, + item_id=unique[1].id, + body="Useful context, but I need to inspect the related stories too.", + sentiment="neutral", + ) + ) + + for guide in guides[index : index + 2]: + for checkpoint in guide.checklist[:2]: + db.session.add( + GuideProgress( + user_id=user.id, + item_id=guide.id, + checkpoint=checkpoint, + completed=(checkpoint == "Read overview" and index % 2 == 0), + ) + ) + + db.session.commit() diff --git a/sites/ign/static/css/.gitkeep b/sites/ign/static/css/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/sites/ign/static/css/ign.css b/sites/ign/static/css/ign.css new file mode 100644 index 00000000..4144cc84 --- /dev/null +++ b/sites/ign/static/css/ign.css @@ -0,0 +1,2341 @@ +:root { + --ign-red: #bf1313; + --ign-red-dark: #8f0d0d; + --ink: #151515; + --muted: #626262; + --line: #e4e4e4; + --soft: #f5f5f5; + --panel: #ffffff; + --shadow: 0 10px 30px rgba(0, 0, 0, 0.08); +} + +* { + box-sizing: border-box; +} + +body { + margin: 0; + background: #fff; + color: var(--ink); + font-family: Inter, Arial, Helvetica, sans-serif; + line-height: 1.5; +} + +a { + color: inherit; + text-decoration: none; +} + +a:hover { + text-decoration: underline; +} + +img { + display: block; + width: 100%; + height: 100%; + object-fit: cover; +} + +button, +.button-primary, +.button-secondary { + border: 0; + border-radius: 4px; + background: var(--ign-red); + color: #fff; + cursor: pointer; + display: inline-flex; + align-items: center; + justify-content: center; + min-height: 38px; + padding: 0 14px; + font-weight: 700; +} + +button:hover, +.button-primary:hover { + background: var(--ign-red-dark); + text-decoration: none; +} + +.button-secondary { + background: #1f1f1f; +} + +input, +select, +textarea { + width: 100%; + border: 1px solid #d4d4d4; + border-radius: 4px; + padding: 10px 12px; + font: inherit; +} + +textarea { + resize: vertical; +} + +label { + display: grid; + gap: 6px; + color: #303030; + font-weight: 700; +} + +.topbar { + position: sticky; + top: 0; + z-index: 20; + display: grid; + grid-template-columns: auto 1fr minmax(220px, 360px) auto; + gap: 20px; + align-items: center; + min-height: 64px; + padding: 0 24px; + background: #fff; + border-bottom: 1px solid var(--line); +} + +.brand-mark { + display: inline-flex; + width: 70px; + height: 34px; + align-items: center; + justify-content: center; + background: var(--ign-red); + color: #fff; + border-radius: 3px; + font-weight: 900; + letter-spacing: 0.06em; +} + +.main-nav, +.footer-links, +.account-links, +.filter-row, +.tag-row { + display: flex; + flex-wrap: wrap; + gap: 10px; + align-items: center; +} + +.main-nav a { + padding: 8px 0; + font-weight: 800; +} + +.top-search { + display: grid; + grid-template-columns: 1fr auto; + gap: 8px; +} + +.top-search input { + background: var(--soft); +} + +.user-nav a { + display: inline-flex; + min-width: 38px; + min-height: 38px; + align-items: center; + justify-content: center; + border-radius: 50%; + background: #1f1f1f; + color: #fff; + font-weight: 800; + padding: 0 10px; +} + +.shell { + display: grid; + grid-template-columns: 160px minmax(0, 1fr); + max-width: 1480px; + margin: 0 auto; +} + +.rail { + position: sticky; + top: 64px; + align-self: start; + min-height: calc(100vh - 64px); + padding: 22px 18px; + border-right: 1px solid var(--line); + background: #fff; +} + +.rail a { + display: block; + padding: 10px 12px; + border-radius: 4px; + color: #333; + font-weight: 800; +} + +.rail a:hover { + background: var(--soft); + text-decoration: none; +} + +.page { + min-width: 0; + padding: 28px; +} + +.flash-stack { + display: grid; + gap: 8px; + margin-bottom: 18px; +} + +.flash { + padding: 12px 14px; + border-left: 4px solid var(--ign-red); + background: #fff2f2; +} + +.flash.success { + border-color: #087f5b; + background: #eefaf5; +} + +.flash.error { + border-color: #b00020; + background: #fff1f2; +} + +.hero-grid { + display: grid; + grid-template-columns: minmax(0, 1.4fr) minmax(360px, 0.8fr); + gap: 28px; + align-items: stretch; +} + +.lead-story { + position: relative; + min-height: 520px; + height: 100%; + overflow: hidden; + background: #111; + border-radius: 4px; + box-shadow: var(--shadow); +} + +.lead-story a { + display: grid; + min-height: 520px; + height: 100%; + align-content: end; + padding: 32px; + color: #fff; + position: relative; +} + +.lead-story img { + position: absolute; + inset: 0; + opacity: 0.62; +} + +.lead-story h1, +.lead-story p, +.lead-story .pill { + position: relative; + max-width: 760px; +} + +.lead-story h1 { + margin: 12px 0; + font-size: clamp(2rem, 4vw, 4.4rem); + line-height: 0.95; +} + +.pill, +.eyebrow { + display: inline-flex; + width: max-content; + border-radius: 3px; + background: var(--ign-red); + color: #fff; + padding: 4px 8px; + font-size: 0.78rem; + font-weight: 900; + text-transform: uppercase; +} + +.top-stack, +.section-block, +.auth-panel, +.action-panel, +.table-wrap, +.empty-state { + background: var(--panel); + border: 1px solid var(--line); + border-radius: 4px; + box-shadow: var(--shadow); +} + +.top-stack, +.section-block, +.auth-panel, +.action-panel, +.table-wrap, +.empty-state { + padding: 22px; +} + +.section-block { + margin-top: 28px; +} + +.section-head { + display: flex; + justify-content: space-between; + gap: 16px; + align-items: center; + margin-bottom: 18px; +} + +.section-head h2, +.top-stack h2, +.page-header h1, +.article-header h1, +.auth-panel h1 { + margin: 0; +} + +.section-head a, +.filter-row a, +.account-links a { + color: var(--ign-red); + font-weight: 900; +} + +.card-grid { + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: 18px; +} + +.card-grid.small { + grid-template-columns: repeat(3, minmax(0, 1fr)); +} + +.list-stack, +.list-page, +.manage-list { + display: grid; + gap: 16px; +} + +.item-card { + display: grid; + grid-template-columns: 190px minmax(0, 1fr); + gap: 14px; + padding-bottom: 16px; + border-bottom: 1px solid var(--line); +} + +.card-grid .item-card { + display: block; + padding-bottom: 0; + border-bottom: 0; +} + +.item-card.compact { + grid-template-columns: 124px minmax(0, 1fr); +} + +.thumb { + position: relative; + display: block; + aspect-ratio: 16 / 9; + overflow: hidden; + border-radius: 4px; + background: #1d1d1d; +} + +.card-grid .thumb { + margin-bottom: 10px; +} + +.score, +.duration, +.video-time { + position: absolute; + right: 8px; + bottom: 8px; + min-width: 34px; + min-height: 30px; + display: inline-flex; + align-items: center; + justify-content: center; + background: var(--ign-red); + color: #fff; + border-radius: 4px; + font-weight: 900; +} + +.meta-line { + display: flex; + flex-wrap: wrap; + gap: 8px; + color: var(--muted); + font-size: 0.82rem; + font-weight: 800; + text-transform: uppercase; +} + +.meta-line span + span::before { + content: ""; +} + +.item-copy h3 { + margin: 6px 0 6px; + font-size: 1.05rem; + line-height: 1.2; +} + +.item-copy p, +.page-header p, +.article-header p, +.auth-panel p, +.empty-state { + color: var(--muted); +} + +.card-actions { + display: flex; + flex-wrap: wrap; + gap: 8px; + color: var(--muted); + font-size: 0.86rem; + font-weight: 700; +} + +.split, +.alert-layout { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 22px; +} + +.page-header, +.article-header { + display: grid; + gap: 10px; + margin-bottom: 24px; +} + +.page-header h1, +.article-header h1 { + font-size: clamp(2rem, 4vw, 4rem); + line-height: 1; +} + +.filter-row { + margin-bottom: 20px; + padding-bottom: 16px; + border-bottom: 1px solid var(--line); +} + +.advanced-search { + display: grid; + grid-template-columns: minmax(220px, 1fr) 180px 180px auto; + gap: 10px; + margin-bottom: 22px; +} + +.detail-media, +.video-shell { + position: relative; + min-height: 360px; + overflow: hidden; + border-radius: 4px; + background: #111; + box-shadow: var(--shadow); +} + +.guide-media { + min-height: 300px; +} + +.score-panel { + position: absolute; + left: 22px; + bottom: 22px; + display: grid; + gap: 2px; + min-width: 104px; + padding: 14px; + background: var(--ign-red); + color: #fff; + border-radius: 4px; + text-align: center; +} + +.score-panel span { + font-size: 2.6rem; + font-weight: 900; + line-height: 1; +} + +.detail-columns { + display: grid; + grid-template-columns: minmax(0, 1fr) 320px; + gap: 28px; + margin-top: 28px; + align-items: start; +} + +.article-body { + max-width: 820px; +} + +.article-body p { + font-size: 1.08rem; +} + +.spec-box, +.comments, +.guide-checklist { + margin-top: 24px; + padding-top: 18px; + border-top: 1px solid var(--line); +} + +.tag-row span { + display: inline-flex; + min-height: 28px; + align-items: center; + border-radius: 3px; + background: var(--soft); + padding: 0 8px; + font-weight: 800; +} + +.action-panel { + display: grid; + gap: 16px; +} + +.action-panel form, +.auth-panel form, +.comment-form { + display: grid; + gap: 12px; +} + +.comment { + border-left: 3px solid var(--ign-red); + padding-left: 12px; + margin: 12px 0; +} + +.video-shell { + min-height: 520px; +} + +.play-button { + position: absolute; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + display: inline-flex; + width: 86px; + height: 86px; + align-items: center; + justify-content: center; + border-radius: 50%; + background: rgba(191, 19, 19, 0.92); + color: #fff; + font-weight: 900; +} + +.check-row { + display: grid; + grid-template-columns: minmax(0, 1fr) auto; + gap: 12px; + align-items: center; + padding: 10px 0; + border-bottom: 1px solid var(--line); +} + +.check-row label, +.check-line { + display: flex; + flex-direction: row; + align-items: center; + gap: 10px; +} + +.check-row input[type="checkbox"], +.check-line input { + width: auto; +} + +.auth-panel { + max-width: 520px; + margin: 24px auto; +} + +.auth-panel.wide { + max-width: 760px; +} + +.account-hero { + display: grid; + grid-template-columns: auto minmax(0, 1fr); + gap: 18px; + align-items: center; + padding: 24px; + border-radius: 4px; + background: #191919; + color: #fff; +} + +.avatar { + width: 86px; + height: 86px; + border-radius: 50%; + display: inline-flex; + align-items: center; + justify-content: center; + color: #fff; + font-size: 1.5rem; + font-weight: 900; +} + +.account-links a { + color: #fff; +} + +.manage-row { + display: grid; + grid-template-columns: minmax(0, 1fr) 320px; + gap: 18px; + padding: 18px; + border: 1px solid var(--line); + border-radius: 4px; +} + +.manage-meta { + display: grid; + gap: 10px; + align-content: start; +} + +.table-wrap { + overflow-x: auto; +} + +table { + width: 100%; + border-collapse: collapse; +} + +th, +td { + text-align: left; + border-bottom: 1px solid var(--line); + padding: 12px 10px; +} + +.muted { + color: var(--muted); + font-size: 0.86rem; +} + +.footer { + display: flex; + justify-content: space-between; + gap: 16px; + border-top: 1px solid var(--line); + margin: 30px auto 0; + max-width: 1480px; + padding: 24px 28px; + color: var(--muted); +} + +.hide-link { + display: none; +} + +@media (max-width: 1100px) { + .topbar { + grid-template-columns: auto 1fr auto; + } + + .top-search { + grid-column: 1 / -1; + } + + .shell { + grid-template-columns: 1fr; + } + + .rail { + position: static; + min-height: 0; + border-right: 0; + border-bottom: 1px solid var(--line); + display: flex; + flex-wrap: wrap; + gap: 4px; + } + + .hero-grid, + .split, + .alert-layout, + .detail-columns, + .manage-row { + grid-template-columns: 1fr; + } + + .card-grid, + .card-grid.small { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } +} + +@media (max-width: 720px) { + .topbar, + .page, + .footer { + padding-left: 16px; + padding-right: 16px; + } + + .main-nav { + overflow-x: auto; + flex-wrap: nowrap; + } + + .hero-grid, + .card-grid, + .card-grid.small, + .advanced-search, + .account-hero { + grid-template-columns: 1fr; + } + + .lead-story, + .lead-story a, + .video-shell { + min-height: 380px; + } + + .item-card, + .item-card.compact { + grid-template-columns: 120px minmax(0, 1fr); + } + + .card-grid .item-card { + display: grid; + } + + .card-grid .thumb { + margin-bottom: 0; + } + + .footer { + display: grid; + } +} + +/* IGN current dark sidebar layout. */ +:root { + --page-bg: #1f2735; + --rail-bg: #2b3446; + --rail-line: #1a2230; + --rail-text: #c9d1dc; + --rail-text-strong: #f4f7fb; + --tile-bg: #263142; + --tile-bg-active: #465269; + --card-title: #f4f6fb; + --card-muted: #aeb8c7; + --overlay-gold: rgba(210, 140, 20, 0.82); + --overlay-pink: rgba(204, 50, 118, 0.82); + --overlay-indigo: rgba(28, 20, 130, 0.86); + --overlay-green: rgba(45, 160, 58, 0.82); +} + +html, +body { + min-height: 100%; + background: var(--page-bg); + color: var(--card-title); + font-family: Inter, Arial, Helvetica, sans-serif; +} + +body { + overflow-x: hidden; +} + +body::selection { + background: #d71920; + color: #fff; +} + +.shell { + display: block; + max-width: none; + margin: 0; +} + +.rail { + position: fixed; + inset: 0 auto 0 0; + z-index: 30; + width: 300px; + min-height: 100vh; + padding: 36px 24px 72px; + overflow-y: auto; + background: var(--rail-bg); + border-right: 1px solid var(--rail-line); + box-shadow: 16px 0 30px rgba(0, 0, 0, 0.18); +} + +.brand { + display: inline-flex; + align-items: center; + gap: 8px; + color: #edf3fb; + margin-bottom: 38px; +} + +.brand:hover { + text-decoration: none; +} + +.brand-word { + color: #edf3fb; + font-size: 1.62rem; + line-height: 1; + font-weight: 950; + letter-spacing: -0.02em; +} + +.brand-dpad { + font-size: 1.55rem; + line-height: 1; + color: #edf3fb; +} + +.plus-box { + display: grid; + gap: 12px; + margin-bottom: 28px; + padding: 26px 24px; + border: 1px solid rgba(220, 229, 243, 0.18); + border-radius: 12px; + color: var(--rail-text-strong); + text-align: center; + background: rgba(33, 42, 58, 0.46); +} + +.plus-box strong { + font-size: 1.03rem; + font-weight: 900; +} + +.plus-box p { + margin: 0; + color: var(--rail-text); + line-height: 1.45; +} + +.plus-box a { + display: inline-flex; + min-height: 42px; + align-items: center; + justify-content: center; + border: 1px solid rgba(227, 233, 244, 0.38); + border-radius: 8px; + color: var(--rail-text-strong); + font-weight: 900; +} + +.plus-box a:hover { + background: rgba(255, 255, 255, 0.08); + text-decoration: none; +} + +.side-nav { + display: grid; + gap: 2px; +} + +.side-nav a { + display: grid; + grid-template-columns: 28px minmax(0, 1fr); + align-items: center; + min-height: 40px; + padding: 0 10px; + border-radius: 8px; + color: var(--rail-text); + font-size: 1.04rem; + font-weight: 650; +} + +.side-nav a:hover, +.side-nav a.active { + background: var(--tile-bg-active); + color: var(--rail-text-strong); + text-decoration: none; +} + +.side-nav span { + color: #dbe3ef; + font-size: 1.15rem; + line-height: 1; +} + +.rail .side-nav .back-top-main { + display: flex; + justify-content: center; + gap: 8px; + min-height: 42px; + margin-top: 20px; + border: 1px solid rgba(228, 232, 240, 0.38); + color: var(--rail-text-strong); + font-weight: 900; + text-align: center; +} + +.rail .side-nav .back-top-main span { + color: var(--rail-text-strong); +} + +.rail-login { + position: fixed; + left: 0; + bottom: 0; + width: 300px; + padding: 18px 24px 22px; + background: var(--rail-bg); + border-top: 1px solid var(--rail-line); +} + +.rail-login a { + display: flex; + min-height: 42px; + align-items: center; + justify-content: center; + border-radius: 8px; + background: #f5f7fa; + color: #1b2230; + font-weight: 900; +} + +.page { + min-height: 100vh; + margin-left: 300px; + padding: 44px clamp(28px, 8vw, 154px) 72px; + background: var(--page-bg); +} + +.flash { + border: 1px solid rgba(255, 255, 255, 0.12); + border-left: 4px solid var(--ign-red); + background: #2d3546; + color: #f4f6fb; +} + +.deck-grid { + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: 24px; + align-items: stretch; +} + +.deck-card { + position: relative; + min-height: 517px; + overflow: hidden; + border-radius: 4px; + background: #111827; +} + +.deck-card a, +.deck-card img { + display: block; + width: 100%; + height: 100%; +} + +.deck-card img { + position: absolute; + inset: 0; + object-fit: cover; + transition: transform 180ms ease; +} + +.deck-card:hover img { + transform: scale(1.025); +} + +.deck-title { + position: absolute; + left: 0; + right: 0; + bottom: 0; + min-height: 116px; + display: flex; + align-items: center; + padding: 26px 30px; +} + +.tone-0 .deck-title { + background: var(--overlay-gold); +} + +.tone-1 .deck-title { + background: var(--overlay-pink); +} + +.tone-2 .deck-title { + background: var(--overlay-indigo); +} + +.tone-3 .deck-title { + background: var(--overlay-green); +} + +.deck-title h2 { + margin: 0; + color: #fff; + font-size: clamp(1.25rem, 1.45vw, 1.6rem); + line-height: 1.07; + font-weight: 950; + letter-spacing: 0; +} + +.home-section { + position: relative; + margin-top: 72px; +} + +.home-section h2, +.top-story-section h2 { + margin: 0 0 26px; + color: var(--card-title); + font-size: clamp(2rem, 2.6vw, 2.75rem); + line-height: 1; + font-weight: 950; + letter-spacing: 0; +} + +.section-head { + margin-bottom: 26px; +} + +.section-head a { + color: #f1f5fb; + opacity: 0.76; +} + +.story-row, +.card-grid { + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: 24px; +} + +.story-row .item-card, +.card-grid .item-card { + display: block; + padding: 0; + border: 0; +} + +.item-card { + color: var(--card-title); +} + +.story-row .thumb, +.card-grid .thumb { + margin-bottom: 14px; +} + +.thumb { + border-radius: 5px; + background: #151c29; +} + +.thumb img { + transition: transform 180ms ease, filter 180ms ease; +} + +.item-card:hover .thumb img { + transform: scale(1.025); + filter: brightness(1.06); +} + +.item-copy h3 { + margin: 0; + color: var(--card-title); + font-size: 1.08rem; + line-height: 1.22; + font-weight: 900; + letter-spacing: 0; +} + +.story-row .item-copy h3 { + font-size: 1.07rem; +} + +.item-copy h3 a:hover { + text-decoration: none; + color: #fff; +} + +.card-meta, +.card-actions, +.item-copy p, +.page-header p, +.article-header p, +.auth-panel p, +.muted { + color: var(--card-muted); +} + +.story-row .card-meta, +.story-row .card-actions { + display: none; +} + +.story-row .score, +.story-row .duration { + display: none; +} + +.carousel-arrow { + position: absolute; + right: -46px; + top: 144px; + width: 68px; + height: 68px; + padding: 0; + border-radius: 50%; + background: #f5f7fb; + color: #1c2432; + box-shadow: 0 12px 28px rgba(0, 0, 0, 0.22); + font-size: 3rem; + font-weight: 300; +} + +.split, +.alert-layout { + gap: 42px; +} + +.dark-list { + padding: 0; +} + +.dark-list .item-card, +.list-page .item-card { + grid-template-columns: 152px minmax(0, 1fr); + gap: 16px; + padding: 16px 0; + border-bottom: 1px solid rgba(228, 232, 240, 0.12); +} + +.list-page .item-card { + display: grid; +} + +.dark-list .thumb, +.list-page .thumb { + margin: 0; +} + +.top-stack, +.section-block, +.auth-panel, +.action-panel, +.table-wrap, +.empty-state, +.spec-box { + background: #263142; + border-color: rgba(228, 232, 240, 0.13); + box-shadow: none; + color: var(--card-title); +} + +.page-header h1, +.article-header h1, +.auth-panel h1 { + color: var(--card-title); + letter-spacing: 0; +} + +.article-header .meta-line, +.page-header .eyebrow { + color: #dbe3ee; +} + +input, +select, +textarea { + background: #1b2331; + border-color: rgba(228, 232, 240, 0.18); + color: var(--card-title); +} + +input::placeholder, +textarea::placeholder { + color: #8390a3; +} + +label { + color: #eef3fb; +} + +.button-secondary { + background: #465269; +} + +.filter-row { + border-color: rgba(228, 232, 240, 0.14); +} + +.filter-row a, +.account-links a { + color: #f1f5fb; +} + +.detail-media, +.video-shell, +.account-hero { + box-shadow: none; +} + +.comments, +.guide-checklist, +.spec-box { + border-color: rgba(228, 232, 240, 0.14); +} + +.tag-row span { + background: rgba(255, 255, 255, 0.09); + color: #e9eef7; +} + +th, +td { + border-color: rgba(228, 232, 240, 0.14); +} + +.footer { + display: none; +} + +@media (max-width: 1280px) { + .page { + padding-left: 48px; + padding-right: 48px; + } + + .deck-grid, + .story-row, + .card-grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } +} + +@media (max-width: 820px) { + .rail { + position: static; + width: auto; + min-height: 0; + padding: 24px 18px; + } + + .plus-box { + display: none; + } + + .side-nav { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + .rail-login { + position: static; + width: auto; + padding: 18px 0 0; + border-top: 0; + } + + .page { + margin-left: 0; + padding: 28px 18px 56px; + } + + .deck-grid, + .story-row, + .card-grid, + .split, + .alert-layout, + .detail-columns, + .manage-row, + .advanced-search { + grid-template-columns: 1fr; + } + + .deck-card { + min-height: 430px; + } + + .carousel-arrow { + display: none; + } +} + +/* Follow-up updates for authenticated sidebar, hub pages, and readable flashes. */ +.rail { + height: 100vh; + max-height: 100vh; + padding-bottom: 104px; + scrollbar-gutter: stable; +} + +.rail::-webkit-scrollbar { + width: 8px; +} + +.rail::-webkit-scrollbar-thumb { + background: #eef3fb; + border-radius: 999px; +} + +.rail::-webkit-scrollbar-track { + background: transparent; +} + +.brand { + margin-bottom: 44px; +} + +.side-nav { + margin-top: 8px; +} + +.library-nav { + display: grid; + gap: 4px; + margin-top: 28px; + padding-top: 26px; + border-top: 1px solid rgba(228, 232, 240, 0.16); +} + +.library-head { + display: flex; + align-items: center; + justify-content: space-between; + min-height: 34px; + color: #eef3fb; + font-size: 0.72rem; + font-weight: 950; + letter-spacing: 0.06em; + text-transform: uppercase; +} + +.library-head a { + color: var(--rail-text); + font-size: 1.1rem; +} + +.library-nav a { + display: grid; + grid-template-columns: 28px minmax(0, 1fr) auto; + align-items: center; + min-height: 40px; + padding: 0 10px; + border-radius: 8px; + color: var(--rail-text); + font-size: 0.98rem; + font-weight: 700; +} + +.library-nav a:hover, +.library-nav a.active { + background: var(--tile-bg-active); + color: var(--rail-text-strong); + text-decoration: none; +} + +.library-nav b { + color: var(--rail-text); + font-size: 0.86rem; +} + +.lib-icon { + width: 22px; + height: 22px; + display: inline-flex; + align-items: center; + justify-content: center; + border-radius: 5px; + color: #fff; + font-size: 0.78rem; + font-weight: 900; +} + +.lib-icon.neutral { background: #536176; } +.lib-icon.star { background: #eef3fb; color: #536176; } +.lib-icon.slate { background: #6b7486; } +.lib-icon.purple { background: #7f59df; } +.lib-icon.cyan { background: #22bfd0; } +.lib-icon.orange { background: #f49b27; } +.lib-icon.green { background: #9acd32; } +.lib-icon.red { background: #e32b2b; } +.lib-icon.profile { background: #e9eef7; color: #536176; } + +.library-nav .back-top { + display: flex; + justify-content: center; + min-height: 40px; + margin-top: 14px; + border: 1px solid rgba(228, 232, 240, 0.38); + font-weight: 900; +} + +.rail-login { + display: flex; + gap: 12px; + align-items: center; +} + +.rail-login a { + flex: 1; +} + +.rail-login .user-chip { + justify-content: flex-start; + gap: 12px; + min-height: 46px; + padding: 0; + background: transparent; + color: #eef3fb; +} + +.mini-avatar { + width: 38px; + height: 38px; + display: inline-flex; + align-items: center; + justify-content: center; + border-radius: 8px; + background: #f4f7fb; + color: #d71920; + font-size: 1.45rem; + font-weight: 950; +} + +.rail-login .bell-chip { + flex: 0 0 42px; + width: 42px; + color: #e9eef7; + background: transparent; + font-size: 1.1rem; +} + +.rail-subnav { + padding-top: 24px; +} + +.rail .subnav-back { + display: inline-flex; + gap: 16px; + align-items: center; + min-height: 40px; + padding: 0 10px; + margin-bottom: 52px; + border-radius: 8px; + color: #f8fbff; + font-size: 0.78rem; + font-weight: 950; + letter-spacing: 0.04em; +} + +.rail .subnav-back:hover { + background: var(--tile-bg-active); + color: #fff; + text-decoration: none; +} + +.rail .subnav-back span { + color: #fff; + font-size: 2.2rem; + line-height: 1; + font-weight: 300; +} + +.subnav-menu { + display: grid; + gap: 6px; +} + +.subnav-menu h2 { + margin: 0 0 18px; + color: #eef3fb; + font-size: 1.16rem; + font-weight: 950; +} + +.subnav-menu a { + display: flex; + align-items: center; + min-height: 42px; + padding: 0 10px; + border-radius: 8px; + color: var(--rail-text); + font-size: 1rem; + font-weight: 700; +} + +.subnav-menu a.active, +.subnav-menu a:hover { + background: var(--tile-bg-active); + color: var(--rail-text-strong); + text-decoration: none; +} + +.flash { + color: #f8fbff; + background: #344057; + border-color: rgba(255, 255, 255, 0.2); + border-left-color: #d71920; + font-weight: 800; +} + +.flash.success { + color: #f2fff9; + background: #0f6548; + border-color: #35d190; +} + +.flash.error { + color: #fff6f7; + background: #7b1226; + border-color: #ff6d87; +} + +.flash.info { + color: #f3f8ff; + background: #2e4d83; + border-color: #80a8ee; +} + +.utility-page { + max-width: 1320px; + margin: 0 auto; +} + +.utility-hero { + display: grid; + grid-template-columns: minmax(0, 1.08fr) minmax(0, 0.92fr); + gap: 34px; + align-items: stretch; + margin-bottom: 36px; +} + +.utility-hero-media, +.utility-hero-copy, +.utility-panel-row article, +.utility-card, +.utility-doc { + border: 1px solid rgba(228, 232, 240, 0.12); + background: #263142; + box-shadow: 0 14px 34px rgba(0, 0, 0, 0.24); +} + +.utility-hero-media { + position: relative; + min-height: 430px; + overflow: hidden; + border-radius: 22px; +} + +.utility-hero-media:hover { + text-decoration: none; +} + +.utility-hero-media img { + position: absolute; + inset: 0; + width: 100%; + height: 100%; + object-fit: cover; + object-position: center; + filter: brightness(0.82); +} + +.utility-hero-media span { + position: absolute; + left: 28px; + bottom: 28px; + min-height: 38px; + display: inline-flex; + align-items: center; + border-radius: 999px; + background: #d71920; + color: #fff; + padding: 0 18px; + font-weight: 950; + text-transform: uppercase; +} + +.utility-hero-copy { + display: grid; + align-content: center; + gap: 18px; + min-width: 0; + overflow: hidden; + padding: 44px; + border-radius: 22px; +} + +.utility-hero-copy h1 { + margin: 0; + color: #fff; + font-size: clamp(2.45rem, 4.35vw, 4.35rem); + line-height: 0.98; + font-weight: 950; + overflow-wrap: anywhere; +} + +.utility-hero-copy p { + margin: 0; + color: #d5deec; + font-size: 1.14rem; + line-height: 1.55; +} + +.utility-cta { + width: 100%; + max-width: 100%; + min-height: 48px; + min-width: 0; + display: inline-flex; + align-items: center; + justify-content: center; + border-radius: 12px; + background: #f7f9fc; + color: #1b2331; + padding: 12px 20px; + font-weight: 950; + line-height: 1.2; + overflow-wrap: anywhere; + text-align: center; + white-space: normal; +} + +.utility-cta:hover { + text-decoration: none; +} + +.utility-panel-row { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 18px; + margin-bottom: 44px; +} + +.utility-panel-row article { + min-height: 164px; + padding: 26px; + border-radius: 18px; +} + +.utility-panel-row h2, +.utility-doc h2 { + margin: 0 0 10px; + color: #fff; + font-size: 1.35rem; + line-height: 1.05; +} + +.utility-panel-row p, +.utility-doc p, +.utility-card p { + margin: 0; + color: #c6d0df; +} + +.utility-card-section { + margin-top: 32px; +} + +.utility-card-grid { + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: 22px; +} + +.utility-card { + overflow: hidden; + border-radius: 18px; +} + +.utility-card a { + display: grid; + min-height: 100%; + color: #eef3fb; +} + +.utility-card a:hover { + text-decoration: none; +} + +.utility-card img { + width: 100%; + height: 220px; + object-fit: cover; +} + +.utility-card div { + display: grid; + gap: 10px; + padding: 20px; +} + +.utility-card span, +.utility-card em { + color: #aeb8c7; + font-style: normal; + font-size: 0.78rem; + font-weight: 950; + text-transform: uppercase; +} + +.utility-card h3 { + margin: 0; + color: #fff; + font-size: 1.16rem; + line-height: 1.12; +} + +.utility-card strong { + width: max-content; + border-radius: 999px; + background: #d71920; + color: #fff; + padding: 7px 12px; + font-size: 0.88rem; +} + +.utility-doc { + display: grid; + grid-template-columns: 260px minmax(0, 1fr); + gap: 34px; + padding: 28px; + border-radius: 22px; +} + +.utility-doc aside { + display: grid; + gap: 10px; + align-content: start; + position: sticky; + top: 28px; +} + +.utility-doc aside strong { + color: #fff; + font-size: 1rem; + margin-bottom: 8px; +} + +.utility-doc aside a { + min-height: 38px; + display: flex; + align-items: center; + border-radius: 8px; + color: #d5deec; + padding: 0 12px; + font-weight: 850; +} + +.utility-doc aside a:hover { + background: var(--tile-bg-active); + color: #fff; + text-decoration: none; +} + +.utility-doc-body { + display: grid; + gap: 26px; +} + +.utility-doc article { + padding-bottom: 24px; + border-bottom: 1px solid rgba(228, 232, 240, 0.13); +} + +.utility-doc article:last-child { + border-bottom: 0; + padding-bottom: 0; +} + +.hub-layout { + display: grid; + grid-template-columns: minmax(0, 1fr) 330px; + gap: 40px; + max-width: 1320px; + margin: 0 auto; +} + +.hub-title { + display: flex; + gap: 14px; + align-items: center; + margin-bottom: 18px; +} + +.hub-title h1 { + margin: 0; + color: #fff; + font-size: clamp(2rem, 3vw, 2.8rem); + line-height: 1; + font-weight: 950; +} + +.hub-icon { + color: #eef3fb; + font-size: 1.5rem; + font-weight: 900; +} + +.hub-feature { + position: relative; + overflow: hidden; + border-radius: 22px; + background: #101722; + aspect-ratio: 16 / 9; +} + +.hub-feature img { + width: 100%; + height: 100%; + object-fit: cover; + object-position: center 35%; +} + +.review-hex { + position: absolute; + left: 48px; + bottom: 44px; + width: 88px; + height: 96px; + display: grid; + place-items: center; + background: #d71920; + color: #fff; + clip-path: polygon(50% 0%, 95% 25%, 95% 75%, 50% 100%, 5% 75%, 5% 25%); +} + +.review-hex b { + font-size: 2.3rem; + line-height: 0.9; +} + +.review-hex small { + margin-top: -28px; + font-size: 0.56rem; + font-weight: 950; +} + +.hub-listing { + margin-top: 42px; +} + +.hub-listing h2 { + margin: 0 0 22px; + color: #fff; + font-size: clamp(2rem, 2.6vw, 2.6rem); + line-height: 1; + font-weight: 950; +} + +.category-tabs { + display: flex; + overflow-x: auto; + margin-bottom: 28px; + border-radius: 26px; +} + +.category-tabs a { + display: inline-flex; + min-width: 128px; + min-height: 50px; + align-items: center; + justify-content: center; + padding: 0 18px; + background: #2d3749; + border-right: 4px solid var(--page-bg); + color: #eef3fb; + font-weight: 900; +} + +.category-tabs a:first-child { + border-top-left-radius: 26px; + border-bottom-left-radius: 26px; +} + +.category-tabs a:last-child { + border-top-right-radius: 26px; + border-bottom-right-radius: 26px; + border-right: 0; +} + +.category-tabs a.active { + background: #f7f9fc; + color: #1b2331; +} + +.source-prompt { + display: flex; + align-items: center; + justify-content: space-between; + gap: 18px; + min-height: 108px; + margin-bottom: 28px; + padding: 0 10px; + border-top: 1px solid rgba(228, 232, 240, 0.16); + border-bottom: 1px solid rgba(228, 232, 240, 0.16); + color: #eef3fb; +} + +.source-prompt button { + min-height: 48px; + border: 1px solid rgba(228, 232, 240, 0.48); + border-radius: 12px; + background: transparent; + color: #eef3fb; +} + +.review-controls { + display: flex; + flex-wrap: wrap; + gap: 10px; + align-items: center; + margin-bottom: 30px; +} + +.review-controls select { + width: auto; + min-width: 138px; + min-height: 48px; + appearance: none; + border: 1px solid rgba(228, 232, 240, 0.48); + border-radius: 12px; + background: #253042; + color: #eef3fb; + padding: 0 42px 0 16px; + font-weight: 900; +} + +.select-control { + position: relative; + display: inline-flex; + width: auto; +} + +.select-control::after { + content: "⌄"; + position: absolute; + right: 16px; + top: 50%; + transform: translateY(-52%); + color: #eef3fb; + pointer-events: none; + font-weight: 900; +} + +.switch { + display: inline-flex; + grid-template-columns: none; + flex-direction: row; + align-items: center; + gap: 8px; + color: #eef3fb; + font-weight: 800; +} + +.switch input { + width: auto; + opacity: 0; + position: absolute; +} + +.switch span { + width: 64px; + height: 36px; + border-radius: 999px; + background: #596578; + position: relative; +} + +.switch span::after { + content: ""; + position: absolute; + top: 6px; + left: 6px; + width: 24px; + height: 24px; + border-radius: 50%; + background: #fff; +} + +.switch input:checked + span { + background: #f5f7fb; +} + +.switch input:checked + span::after { + left: 34px; + background: #121a27; +} + +.hide-review-scores .score, +.hide-review-scores .review-hex { + display: none; +} + +.hub-side h2 { + margin: 8px 0 18px; + padding-bottom: 14px; + border-bottom: 1px solid rgba(228, 232, 240, 0.18); + color: #fff; + font-size: 1.55rem; + font-weight: 950; +} + +.popular-list { + display: grid; + gap: 18px; + margin: 0; + padding: 0; + list-style: none; +} + +.popular-list li { + display: grid; + grid-template-columns: 42px minmax(0, 1fr); + gap: 10px; +} + +.rank { + width: 26px; + height: 26px; + display: inline-flex; + align-items: center; + justify-content: center; + border: 2px solid #e6edf8; + border-radius: 50%; + color: #eef3fb; + font-size: 0.75rem; + font-weight: 950; +} + +.popular-list li:first-child .rank { + background: #d71920; + border-color: #d71920; +} + +.popular-list a { + display: grid; + gap: 4px; + color: #eef3fb; +} + +.popular-list small, +.popular-list em { + color: #d4dce8; + font-style: normal; + font-weight: 800; +} + +.popular-list strong { + font-size: 1.02rem; + line-height: 1.25; +} + +.hub-results .item-card { + grid-template-columns: 260px minmax(0, 1fr); + align-items: center; +} + +.advanced-search { + grid-template-columns: minmax(260px, 1fr) 220px auto; +} + +.search-tabs { + margin-bottom: 24px; +} + +.profile-hero { + display: grid; + grid-template-columns: 300px minmax(0, 1fr); + gap: 42px; + align-items: center; + min-height: 275px; + margin: 12px auto 54px; + max-width: 1320px; + padding: 48px 42px; + border-radius: 28px; + background: #2b3448; + box-shadow: 0 18px 44px rgba(0, 0, 0, 0.34), inset 0 0 0 1px rgba(255, 255, 255, 0.08); +} + +.profile-avatar { + width: 174px; + height: 174px; + display: inline-flex; + align-items: center; + justify-content: center; + border-radius: 50%; + background: #f7f9fc; + color: #d71920; + font-size: 7rem; + font-weight: 950; +} + +.profile-main h1 { + margin: 0 0 10px; + color: #fff; + font-size: clamp(3rem, 5vw, 4.6rem); + line-height: 0.9; + font-weight: 950; +} + +.profile-main > a { + color: #f4f7fb; + font-weight: 900; + text-decoration: underline; +} + +.profile-actions { + display: flex; + flex-wrap: wrap; + gap: 12px; + align-items: center; + margin-top: 36px; +} + +.share-button, +.more-button { + min-height: 48px; + border-radius: 16px; +} + +.share-button { + display: inline-flex; + align-items: center; + padding: 0 34px; + background: #fff; + color: #1b2331; + font-weight: 950; +} + +.more-button { + width: 54px; + border: 1px solid rgba(255, 255, 255, 0.32); + background: transparent; +} + +.profile-stats { + display: grid; + grid-template-columns: repeat(3, 104px); + margin-left: 26px; + overflow: hidden; + border-radius: 8px; + background: #3b465a; +} + +.profile-stats span { + min-height: 74px; + display: grid; + place-items: center; + border-right: 1px solid rgba(255, 255, 255, 0.18); +} + +.profile-stats span:last-child { + border-right: 0; +} + +.profile-stats b { + font-size: 1.5rem; + line-height: 1; +} + +.profile-stats small { + margin-top: -12px; + font-size: 0.72rem; + font-weight: 950; + text-transform: uppercase; +} + +.profile-tabs { + display: flex; + gap: 0; + max-width: 1320px; + margin: 0 auto 36px; + border-bottom: 1px solid rgba(228, 232, 240, 0.25); +} + +.profile-tabs a { + min-width: 150px; + min-height: 52px; + display: inline-flex; + align-items: center; + justify-content: center; + color: #d5deec; + font-weight: 850; +} + +.profile-tabs a.active { + color: #fff; + border-bottom: 2px solid #fff; +} + +.profile-section, +.profile-grid, +.profile-row { + max-width: 1320px; + margin-left: auto; + margin-right: auto; +} + +.profile-section h2, +.profile-grid h2 { + color: #fff; + font-size: 2rem; + line-height: 1; + font-weight: 950; +} + +.profile-empty { + color: #d0d8e5; + font-size: 1.05rem; +} + +.profile-grid { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 56px; + margin-top: 48px; +} + +.profile-box { + min-height: 96px; + display: grid; + gap: 8px; + align-content: center; + padding: 22px 34px; + border-radius: 24px; + background: #2b3448; + box-shadow: 0 10px 24px rgba(0, 0, 0, 0.26), inset 0 0 0 1px rgba(255, 255, 255, 0.07); + color: #d4dce8; +} + +.profile-box a { + color: #f4f7fb; + font-weight: 850; +} + +@media (max-width: 1280px) { + .hub-layout { + grid-template-columns: 1fr; + } + + .hub-side { + display: none; + } + + .utility-hero, + .utility-doc { + grid-template-columns: 1fr; + } + + .utility-card-grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + .profile-hero { + grid-template-columns: 190px minmax(0, 1fr); + } + + .profile-stats { + margin-left: 0; + } +} + +@media (max-width: 820px) { + .rail { + height: auto; + max-height: none; + padding-bottom: 24px; + } + + .library-nav { + margin-top: 18px; + } + + .rail-login { + display: flex; + } + + .hub-layout, + .profile-hero, + .profile-grid, + .hub-results .item-card, + .utility-panel-row, + .utility-card-grid { + grid-template-columns: 1fr; + } + + .side-nav .back-top-main { + grid-column: 1 / -1; + } + + .utility-hero-copy { + padding: 28px 22px; + } + + .utility-hero-media { + min-height: 320px; + } + + .utility-doc { + padding: 20px; + } + + .utility-doc aside { + position: static; + } + + .profile-hero { + padding: 28px 22px; + } + + .profile-avatar { + width: 128px; + height: 128px; + font-size: 5rem; + } + + .profile-stats { + grid-template-columns: repeat(3, minmax(0, 1fr)); + width: 100%; + } + + .category-tabs { + border-radius: 14px; + } +} diff --git a/sites/ign/static/icons/.gitkeep b/sites/ign/static/icons/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/sites/ign/static/js/.gitkeep b/sites/ign/static/js/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/sites/ign/static/js/ign.js b/sites/ign/static/js/ign.js new file mode 100644 index 00000000..eea83867 --- /dev/null +++ b/sites/ign/static/js/ign.js @@ -0,0 +1,22 @@ +document.addEventListener("DOMContentLoaded", () => { + for (const row of document.querySelectorAll(".check-row")) { + const checkbox = row.querySelector('input[type="checkbox"]'); + if (!checkbox || checkbox.disabled) continue; + checkbox.addEventListener("change", () => { + row.classList.toggle("completed", checkbox.checked); + }); + row.classList.toggle("completed", checkbox.checked); + } + + for (const control of document.querySelectorAll("[data-submit-on-change]")) { + control.addEventListener("change", () => { + const form = control.form; + const stateField = control.dataset.stateField; + if (form && stateField) { + const hidden = form.querySelector(`input[type="hidden"][name="${stateField}"]`); + if (hidden) hidden.value = control.checked ? "1" : "0"; + } + if (form) form.requestSubmit(); + }); + } +}); diff --git a/sites/ign/tasks.jsonl b/sites/ign/tasks.jsonl new file mode 100644 index 00000000..f0c6356a --- /dev/null +++ b/sites/ign/tasks.jsonl @@ -0,0 +1,19 @@ +{"web_name": "IGN", "id": "IGN--0", "ques": "Log in as alice.j@test.com with password TestPass123!, search for The Complicated History of GTA Controversies, open the story, and save it to the Games folder with the note \"gta controversy timeline\".", "web": "http://localhost:40016/", "upstream_url": "https://www.ign.com/"} +{"web_name": "IGN", "id": "IGN--1", "ques": "Log in as alice.j@test.com with password TestPass123!, find the GTA Online Weekly Updates guide, and mark the checklist item \"Claim Independence Day freebies\" as complete.", "web": "http://localhost:40016/", "upstream_url": "https://www.ign.com/"} +{"web_name": "IGN", "id": "IGN--2", "ques": "Log in as bob.c@test.com with password TestPass123!, find Will Nintendo Ever Stop Making Physical Games? - NVC Clips, and add it to Playlist with the note \"physical games debate\".", "web": "http://localhost:40016/", "upstream_url": "https://www.ign.com/"} +{"web_name": "IGN", "id": "IGN--3", "ques": "Log in as david.k@test.com with password TestPass123!, create a daily Games alert for the keyword PlayStation.", "web": "http://localhost:40016/", "upstream_url": "https://www.ign.com/"} +{"web_name": "IGN", "id": "IGN--4", "ques": "Use the Reviews section's All Genres filter to show Tech reviews, open Turtle Beach Stealth Pro II Review and Corsair One A600 Review, and report which detail page includes the tag \"headset\".", "web": "http://localhost:40016/", "upstream_url": "https://www.ign.com/"} +{"web_name": "IGN", "id": "IGN--5", "ques": "Log in as carol.d@test.com with password TestPass123!, update the account profile so the region is San Francisco, CA and the favorite platform is Nintendo Switch 2.", "web": "http://localhost:40016/", "upstream_url": "https://www.ign.com/"} +{"web_name": "IGN", "id": "IGN--6", "ques": "Log in as bob.c@test.com with password TestPass123!, search for GeForce RTX 5070, open the Walmart prebuilt gaming PC deal story, and save it to the Deals folder.", "web": "http://localhost:40016/", "upstream_url": "https://www.ign.com/"} +{"web_name": "IGN", "id": "IGN--7", "ques": "Log in as alice.j@test.com with password TestPass123!, open Silo Season 3 Review, and post the comment \"Watch this before the finale.\".", "web": "http://localhost:40016/", "upstream_url": "https://www.ign.com/"} +{"web_name": "IGN", "id": "IGN--8", "ques": "Log in as david.k@test.com with password TestPass123!, go to the Videos section, find PlayStation’s Physical Media-Free Future Isn’t Just Concerning, It’s Offensive, and add it to Playlist with status watching.", "web": "http://localhost:40016/", "upstream_url": "https://www.ign.com/"} +{"web_name": "IGN", "id": "IGN--9", "ques": "Log in as carol.d@test.com with password TestPass123!, open the Deals section, find MTG's Rare Marvel Collector Boosters Are Available Again Online, But Expected to Sell Out Fast, and save it to the Deals folder with the note \"sealed booster watch\".", "web": "http://localhost:40016/", "upstream_url": "https://www.ign.com/"} +{"web_name": "IGN", "id": "IGN--10", "ques": "Log in as carol.d@test.com with password TestPass123!, open the Fortnite Guide, and save it with the note \"weekly quests\".", "web": "http://localhost:40016/", "upstream_url": "https://www.ign.com/"} +{"web_name": "IGN", "id": "IGN--11", "ques": "Open the TV section, find Everything Coming to HBO Max in July, and add it to Playlist after logging in as david.k@test.com with password TestPass123!.", "web": "http://localhost:40016/", "upstream_url": "https://www.ign.com/"} +{"web_name": "IGN", "id": "IGN--12", "ques": "Log in as alice.j@test.com with password TestPass123!, search physical media future, open the PlayStation article, and save it to Read Later.", "web": "http://localhost:40016/", "upstream_url": "https://www.ign.com/"} +{"web_name": "IGN", "id": "IGN--13", "ques": "Log in as bob.c@test.com with password TestPass123!, go to Alerts, and pause the existing weekly update guide alert.", "web": "http://localhost:40016/", "upstream_url": "https://www.ign.com/"} +{"web_name": "IGN", "id": "IGN--14", "ques": "Log in as alice.j@test.com with password TestPass123!, go to Saved Stories, find the saved item in the Deals folder, and remove it.", "web": "http://localhost:40016/", "upstream_url": "https://www.ign.com/"} +{"web_name": "IGN", "id": "IGN--15", "ques": "Log in as bob.c@test.com with password TestPass123!, open All GTA 5 Cheat Codes and Secrets For PC and Console and mark \"Read overview\" as complete.", "web": "http://localhost:40016/", "upstream_url": "https://www.ign.com/"} +{"web_name": "IGN", "id": "IGN--16", "ques": "Find Everything Major Added to Final Fantasy 7 Rebirth Since Launch and add it to Playlist as queued after logging in as alice.j@test.com with password TestPass123!.", "web": "http://localhost:40016/", "upstream_url": "https://www.ign.com/"} +{"web_name": "IGN", "id": "IGN--17", "ques": "Search for HBO Max July and Amazon Prime Video July, open both stories, and report which detail page lists Nintendo Switch 2 in Platforms.", "web": "http://localhost:40016/", "upstream_url": "https://www.ign.com/"} +{"web_name": "IGN", "id": "IGN--18", "ques": "Log in as david.k@test.com with password TestPass123!, open the Movies section, find the Anne Hathaway Quit Knocked Up story, and save it to the Weekend folder with the note \"casting anecdote\".", "web": "http://localhost:40016/", "upstream_url": "https://www.ign.com/"} diff --git a/sites/ign/templates/.gitkeep b/sites/ign/templates/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/sites/ign/templates/_item_card.html b/sites/ign/templates/_item_card.html new file mode 100644 index 00000000..9b6f899b --- /dev/null +++ b/sites/ign/templates/_item_card.html @@ -0,0 +1,27 @@ +
+ + {% if item.image_path %} + {{ item.title }} + {% endif %} + {% if item.score and not (hide_review_scores|default(false) and item.content_type == 'review') %} + {{ item.score }} + {% elif item.duration %} + {{ item.duration }} + {% endif %} + +
+
+ {{ item.pretty_type }} + {{ section_label(item.section_slug) }} + {% if item.published_at %}{{ item.published_at.strftime('%b %d, %Y') }}{% endif %} +
+

{{ item.title }}

+ {% if not compact|default(false) and not request.endpoint == 'index' %} +

{{ item.short_description }}

+ {% endif %} +
+ {{ item.comments_count }} comments + {% if item.score_label and not (hide_review_scores|default(false) and item.content_type == 'review') %}{{ item.score_label }}{% endif %} +
+
+
diff --git a/sites/ign/templates/account.html b/sites/ign/templates/account.html new file mode 100644 index 00000000..d8686cdf --- /dev/null +++ b/sites/ign/templates/account.html @@ -0,0 +1,82 @@ +{% extends "base.html" %} +{% block title %}Profile - IGN{% endblock %} +{% block content %} +
+
+
+

{{ current_user.username }}

+ View Public Profile +
+ + +
+ {{ current_user.saved_items|length }}Lists + {{ current_user.playlist_entries|length }}Library + Public +
+
+
+
+ + + +
+

Now Playing

Here's what {{ current_user.username }} is playing.

See all
+ {% if playlist %} +
+ {% for row in playlist[:4] %} + {% set item = row.item %} + {% include "_item_card.html" with context %} + {% endfor %} +
+ {% else %} +

No Results.

+ {% endif %} +
+ +
+
+

Recently Wishlisted

+
+ {% if saved %} + {% for row in saved[:2] %} + {{ row.item.title }} + {% endfor %} + {% else %} + No Games With Given Status + {% endif %} +
+
+
+

Recently Backlogged

+
No Games With Given Status
+
+
+

Recently Beat

+
No Games With Given Status
+
+
+ +
+

Digest History

+
+ + + + {% for digest in current_user.digests %} + + + + + + + {% endfor %} + +
DigestStatusDeliveryDate
{{ digest.title }} {{ digest.digest_number }}{{ digest.status }}{{ digest.delivery }}{{ digest.created_at.strftime('%b %d, %Y') }}
+
+
+{% endblock %} diff --git a/sites/ign/templates/account_edit.html b/sites/ign/templates/account_edit.html new file mode 100644 index 00000000..b96da8fb --- /dev/null +++ b/sites/ign/templates/account_edit.html @@ -0,0 +1,22 @@ +{% extends "base.html" %} +{% block title %}Edit Account - IGN{% endblock %} +{% block content %} +
+

Edit profile

+
+ + + + + + + +
+
+{% endblock %} diff --git a/sites/ign/templates/alerts.html b/sites/ign/templates/alerts.html new file mode 100644 index 00000000..b535c098 --- /dev/null +++ b/sites/ign/templates/alerts.html @@ -0,0 +1,57 @@ +{% extends "base.html" %} +{% block title %}Alerts - IGN{% endblock %} +{% block content %} + + +
+
+ +

New Alert

+ + + + +
+ +
+ + + + {% for row in rows %} + + + + + + + + {% else %} + + {% endfor %} + +
SectionKeywordFrequencyStatus
{{ section_label(row.section_slug) if row.section_slug else 'Any' }}{{ row.keyword or 'Any keyword' }}{{ row.frequency }}{{ 'Active' if row.active else 'Paused' }} +
+ + +
+
No alerts yet.
+
+
+{% endblock %} diff --git a/sites/ign/templates/base.html b/sites/ign/templates/base.html new file mode 100644 index 00000000..0543cc3b --- /dev/null +++ b/sites/ign/templates/base.html @@ -0,0 +1,72 @@ + + + + + + {% block title %}IGN{% endblock %} + + + + +
+ + +
+ {% with messages = get_flashed_messages(with_categories=true) %} + {% if messages %} +
+ {% for category, message in messages %} +
{{ message }}
+ {% endfor %} +
+ {% endif %} + {% endwith %} + {% block content %}{% endblock %} +
+
+ + diff --git a/sites/ign/templates/detail.html b/sites/ign/templates/detail.html new file mode 100644 index 00000000..fa09676b --- /dev/null +++ b/sites/ign/templates/detail.html @@ -0,0 +1,107 @@ +{% extends "base.html" %} +{% block title %}{{ item.title }} - IGN{% endblock %} +{% block content %} +
+
+
+ {{ item.pretty_type }} + {{ section_label(item.section_slug) }} + {{ item.published_at.strftime('%b %d, %Y') }} +
+

{{ item.title }}

+

{{ item.description }}

+ +
+ +
+ {{ item.title }} + {% if item.score %} +
+ {{ item.score }} + {{ item.score_label }} +
+ {% endif %} +
+ +
+
+ {% for paragraph in item.body %} +

{{ paragraph }}

+ {% endfor %} + {% if item.platforms %} +
+

Platforms

+
+ {% for platform in item.platforms %}{{ platform }}{% endfor %} +
+
+ {% endif %} + {% if item.tags %} +
+

Tags

+
+ {% for tag in item.tags %}{{ tag }}{% endfor %} +
+
+ {% endif %} + +
+

Reader Discussion

+ {% for comment in item.comments %} +
+ {{ comment.user.display_name }} +

{{ comment.body }}

+
+ {% else %} +

No local comments yet.

+ {% endfor %} + {% if current_user.is_authenticated %} +
+ + + +
+ {% else %} + Log in to comment + {% endif %} +
+
+ + +
+
+ +
+

Related Coverage

+
+ {% for item in related %} + {% include "_item_card.html" with context %} + {% endfor %} +
+
+{% endblock %} diff --git a/sites/ign/templates/guide_detail.html b/sites/ign/templates/guide_detail.html new file mode 100644 index 00000000..0b7fd3f5 --- /dev/null +++ b/sites/ign/templates/guide_detail.html @@ -0,0 +1,72 @@ +{% extends "base.html" %} +{% block title %}{{ item.title }} - IGN{% endblock %} +{% block content %} +
+
+
+ Guide + {{ item.published_at.strftime('%b %d, %Y') }} +
+

{{ item.title }}

+

{{ item.description }}

+ +
+ +
+ {{ item.title }} +
+ +
+
+ {% for paragraph in item.body %} +

{{ paragraph }}

+ {% endfor %} +
+

Checklist

+ {% for checkpoint in item.checklist %} +
+ + + + {% if current_user.is_authenticated %} + + {% endif %} +
+ {% endfor %} + {% if not current_user.is_authenticated %} + Log in to track progress + {% endif %} +
+
+ + +
+
+ +
+

Related Guides

+
+ {% for item in related %} + {% include "_item_card.html" with context %} + {% endfor %} +
+
+{% endblock %} diff --git a/sites/ign/templates/hub_listing.html b/sites/ign/templates/hub_listing.html new file mode 100644 index 00000000..b2db9d3e --- /dev/null +++ b/sites/ign/templates/hub_listing.html @@ -0,0 +1,97 @@ +{% extends "base.html" %} +{% block title %}{{ title }} - IGN{% endblock %} +{% block content %} +
+
+
+ {{ '↗' if hub == 'news' else '▧' }} +

{{ title }}

+
+ + {% if featured %} + + {% endif %} + +
+

{{ list_title }}

+ + + {% if hub == 'reviews' %} +
+ + + + + + + +
+ {% else %} +
+ Get IGN's Biggest Stories FIRST. Set Us as Your Source. + +
+ {% endif %} + +
+ {% for item in items %} + {% include "_item_card.html" with context %} + {% else %} +
No stories match this view.
+ {% endfor %} +
+
+
+ + +
+{% endblock %} diff --git a/sites/ign/templates/index.html b/sites/ign/templates/index.html new file mode 100644 index 00000000..fb4c562e --- /dev/null +++ b/sites/ign/templates/index.html @@ -0,0 +1,71 @@ +{% extends "base.html" %} +{% block title %}Video Game News, Reviews, and Walkthroughs - IGN{% endblock %} +{% block content %} +
+ {% for item in top_stories[:4] %} + + {% endfor %} +
+ +
+

Top Stories

+
+ {% for item in top_stories[4:12] %} + {% include "_item_card.html" with context %} + {% endfor %} +
+ +
+ +
+
+

Latest

+ See all +
+
+ {% for item in latest[:8] %} + {% include "_item_card.html" with context %} + {% endfor %} +
+
+ +
+
+ +
+ {% for item in reviews[:5] %} + {% set compact = true %} + {% include "_item_card.html" with context %} + {% endfor %} +
+
+
+ +
+ {% for item in guides[:5] %} + {% set compact = true %} + {% include "_item_card.html" with context %} + {% endfor %} +
+
+
+ +
+
+

Videos

+ More videos +
+
+ {% for item in videos %} + {% include "_item_card.html" with context %} + {% endfor %} +
+
+{% endblock %} diff --git a/sites/ign/templates/listing.html b/sites/ign/templates/listing.html new file mode 100644 index 00000000..dfe02ac5 --- /dev/null +++ b/sites/ign/templates/listing.html @@ -0,0 +1,28 @@ +{% extends "base.html" %} +{% block title %}{{ title }} - IGN{% endblock %} +{% block content %} + + + + +
+ {% for item in items %} + {% include "_item_card.html" with context %} + {% else %} +
No stories match this view.
+ {% endfor %} +
+{% endblock %} diff --git a/sites/ign/templates/login.html b/sites/ign/templates/login.html new file mode 100644 index 00000000..3dfff6df --- /dev/null +++ b/sites/ign/templates/login.html @@ -0,0 +1,15 @@ +{% extends "base.html" %} +{% block title %}Log in - IGN{% endblock %} +{% block content %} +
+

Log in

+

Use a benchmark account such as alice.j@test.com with TestPass123!.

+
+ + + + +
+ Create an account +
+{% endblock %} diff --git a/sites/ign/templates/playlist.html b/sites/ign/templates/playlist.html new file mode 100644 index 00000000..b148bf21 --- /dev/null +++ b/sites/ign/templates/playlist.html @@ -0,0 +1,39 @@ +{% extends "base.html" %} +{% block title %}Playlist - IGN{% endblock %} +{% block content %} + + +
+ {% for row in rows %} +
+ {% set item = row.item %} + {% set compact = true %} + {% include "_item_card.html" with context %} +
+
+ + + + +
+
+ + +
+
+
+ {% else %} +
No playlist entries yet.
+ {% endfor %} +
+{% endblock %} diff --git a/sites/ign/templates/register.html b/sites/ign/templates/register.html new file mode 100644 index 00000000..bfcd95a6 --- /dev/null +++ b/sites/ign/templates/register.html @@ -0,0 +1,15 @@ +{% extends "base.html" %} +{% block title %}Register - IGN{% endblock %} +{% block content %} +
+

Create an IGN account

+
+ + + + + + +
+
+{% endblock %} diff --git a/sites/ign/templates/saved.html b/sites/ign/templates/saved.html new file mode 100644 index 00000000..b3255c9f --- /dev/null +++ b/sites/ign/templates/saved.html @@ -0,0 +1,29 @@ +{% extends "base.html" %} +{% block title %}Saved Stories - IGN{% endblock %} +{% block content %} + + +
+ {% for row in rows %} +
+ {% set item = row.item %} + {% set compact = true %} + {% include "_item_card.html" with context %} +
+ {{ row.folder }} +

{{ row.note }}

+
+ + +
+
+
+ {% else %} +
No saved content yet.
+ {% endfor %} +
+{% endblock %} diff --git a/sites/ign/templates/search.html b/sites/ign/templates/search.html new file mode 100644 index 00000000..412026e7 --- /dev/null +++ b/sites/ign/templates/search.html @@ -0,0 +1,33 @@ +{% extends "base.html" %} +{% block title %}Search - IGN{% endblock %} +{% block content %} + + + + + + +
+ {% for item in results %} + {% include "_item_card.html" with context %} + {% else %} +
No matching IGN coverage yet.
+ {% endfor %} +
+{% endblock %} diff --git a/sites/ign/templates/utility_page.html b/sites/ign/templates/utility_page.html new file mode 100644 index 00000000..515144a6 --- /dev/null +++ b/sites/ign/templates/utility_page.html @@ -0,0 +1,79 @@ +{% extends "base.html" %} +{% block title %}{{ title }} - IGN{% endblock %} +{% block content %} +
+
+ + {% if hero.image %} + {{ hero.title }} + {% endif %} + {{ hero.label }} + +
+ {{ eyebrow }} +

{{ title }}

+

{{ subtitle }}

+ {{ hero.title }} +
+
+ + {% if mode in ["legal", "follow"] %} +
+ +
+ {% for panel in panels %} +
+

{{ panel.title }}

+

{{ panel.body }}

+
+ {% endfor %} +
+
+ {% else %} +
+ {% for panel in panels %} +
+

{{ panel.title }}

+

{{ panel.body }}

+
+ {% endfor %} +
+ {% endif %} + + {% if cards %} +
+
+

{{ "Featured" if mode != "store" else "Store Picks" }}

+
+ +
+ {% endif %} +
+{% endblock %} diff --git a/sites/ign/templates/video_detail.html b/sites/ign/templates/video_detail.html new file mode 100644 index 00000000..80b9c273 --- /dev/null +++ b/sites/ign/templates/video_detail.html @@ -0,0 +1,60 @@ +{% extends "base.html" %} +{% block title %}{{ item.title }} - IGN{% endblock %} +{% block content %} +
+
+
+ Video + {{ section_label(item.section_slug) }} + {{ item.published_at.strftime('%b %d, %Y') }} +
+

{{ item.title }}

+

{{ item.description }}

+
+ +
+ {{ item.title }} +
Play
+ {{ item.duration }} +
+ +
+
+ {% for paragraph in item.body %} +

{{ paragraph }}

+ {% endfor %} +
+ {% for tag in item.tags %}{{ tag }}{% endfor %} +
+
+ +
+
+ +
+

More Videos and Stories

+
+ {% for item in related %} + {% include "_item_card.html" with context %} + {% endfor %} +
+
+{% endblock %} diff --git a/websyn_start.sh b/websyn_start.sh index 4d690d78..b50c7342 100644 --- a/websyn_start.sh +++ b/websyn_start.sh @@ -1,11 +1,11 @@ #!/bin/bash -# WebSyn startup: launch all 16 mirror sites, then exec the original CMD. +# WebSyn startup: launch all 17 mirror sites, then exec the original CMD. # This preserves the base image's browser env server (port 8100) as PID 1. set -e SITES=(allrecipes amazon apple arxiv bbc_news booking github google_flights google_map google_search huggingface wolfram_alpha - cambridge_dictionary coursera espn merriam_webster) + cambridge_dictionary coursera espn merriam_webster ign) BASE_PORT=40000 PID_DIR=/tmp/websyn_pids mkdir -p "$PID_DIR" @@ -17,7 +17,7 @@ for d in "${SITES[@]}"; do cp -a "/opt/WebSyn/$d/instance_seed" "/opt/WebSyn/$d/instance" done -echo "[WebSyn] Starting 16 sites on ports ${BASE_PORT}-$((BASE_PORT + 15))..." +echo "[WebSyn] Starting 17 sites on ports ${BASE_PORT}-$((BASE_PORT + 16))..." for i in "${!SITES[@]}"; do site="${SITES[$i]}" port=$((BASE_PORT + i)) @@ -51,8 +51,8 @@ except Exception: exit(1) ready=$((ready + 1)) fi done - echo " [${elapsed}/${max_wait}s] ${ready}/16 sites ready" - if [ $ready -eq 16 ]; then + echo " [${elapsed}/${max_wait}s] ${ready}/17 sites ready" + if [ $ready -eq 17 ]; then break fi done @@ -78,6 +78,6 @@ done echo "[WebSyn] Starting control server on :8101 (PID 1)..." # Control server becomes PID 1 — receives SIGTERM on `docker stop`, -# keeps the container alive as long as it's running. The 16 site +# keeps the container alive as long as it's running. The 17 site # subprocesses are managed via /tmp/websyn_pids/.pid. exec python3 /opt/control_server.py --port 8101 From f67b604d04eda2e5f9f5c8496b5cc1440d593c71 Mon Sep 17 00:00:00 2001 From: Django-Jiang <43953876+Django-Jiang@users.noreply.github.com> Date: Sun, 19 Jul 2026 12:17:39 -0700 Subject: [PATCH 2/2] Add IGN task verifiers + task-quality fixes (site by @Edlison, verifiers by reviewer) --- sites/ign/tasks.jsonl | 38 +-- sites/ign/templates/account_edit.html | 2 +- sites/ign/verify/verify_0.py | 48 ++++ sites/ign/verify/verify_1.py | 43 +++ sites/ign/verify/verify_10.py | 41 +++ sites/ign/verify/verify_11.py | 39 +++ sites/ign/verify/verify_12.py | 46 ++++ sites/ign/verify/verify_13.py | 44 +++ sites/ign/verify/verify_14.py | 40 +++ sites/ign/verify/verify_15.py | 42 +++ sites/ign/verify/verify_16.py | 41 +++ sites/ign/verify/verify_17.py | 46 ++++ sites/ign/verify/verify_18.py | 44 +++ sites/ign/verify/verify_2.py | 41 +++ sites/ign/verify/verify_3.py | 44 +++ sites/ign/verify/verify_4.py | 47 ++++ sites/ign/verify/verify_5.py | 41 +++ sites/ign/verify/verify_6.py | 41 +++ sites/ign/verify/verify_7.py | 41 +++ sites/ign/verify/verify_8.py | 43 +++ sites/ign/verify/verify_9.py | 44 +++ sites/ign/verify/verify_lib.py | 367 ++++++++++++++++++++++++++ 22 files changed, 1203 insertions(+), 20 deletions(-) create mode 100644 sites/ign/verify/verify_0.py create mode 100644 sites/ign/verify/verify_1.py create mode 100644 sites/ign/verify/verify_10.py create mode 100644 sites/ign/verify/verify_11.py create mode 100644 sites/ign/verify/verify_12.py create mode 100644 sites/ign/verify/verify_13.py create mode 100644 sites/ign/verify/verify_14.py create mode 100644 sites/ign/verify/verify_15.py create mode 100644 sites/ign/verify/verify_16.py create mode 100644 sites/ign/verify/verify_17.py create mode 100644 sites/ign/verify/verify_18.py create mode 100644 sites/ign/verify/verify_2.py create mode 100644 sites/ign/verify/verify_3.py create mode 100644 sites/ign/verify/verify_4.py create mode 100644 sites/ign/verify/verify_5.py create mode 100644 sites/ign/verify/verify_6.py create mode 100644 sites/ign/verify/verify_7.py create mode 100644 sites/ign/verify/verify_8.py create mode 100644 sites/ign/verify/verify_9.py create mode 100644 sites/ign/verify/verify_lib.py diff --git a/sites/ign/tasks.jsonl b/sites/ign/tasks.jsonl index f0c6356a..36a18aaa 100644 --- a/sites/ign/tasks.jsonl +++ b/sites/ign/tasks.jsonl @@ -1,19 +1,19 @@ -{"web_name": "IGN", "id": "IGN--0", "ques": "Log in as alice.j@test.com with password TestPass123!, search for The Complicated History of GTA Controversies, open the story, and save it to the Games folder with the note \"gta controversy timeline\".", "web": "http://localhost:40016/", "upstream_url": "https://www.ign.com/"} -{"web_name": "IGN", "id": "IGN--1", "ques": "Log in as alice.j@test.com with password TestPass123!, find the GTA Online Weekly Updates guide, and mark the checklist item \"Claim Independence Day freebies\" as complete.", "web": "http://localhost:40016/", "upstream_url": "https://www.ign.com/"} -{"web_name": "IGN", "id": "IGN--2", "ques": "Log in as bob.c@test.com with password TestPass123!, find Will Nintendo Ever Stop Making Physical Games? - NVC Clips, and add it to Playlist with the note \"physical games debate\".", "web": "http://localhost:40016/", "upstream_url": "https://www.ign.com/"} -{"web_name": "IGN", "id": "IGN--3", "ques": "Log in as david.k@test.com with password TestPass123!, create a daily Games alert for the keyword PlayStation.", "web": "http://localhost:40016/", "upstream_url": "https://www.ign.com/"} -{"web_name": "IGN", "id": "IGN--4", "ques": "Use the Reviews section's All Genres filter to show Tech reviews, open Turtle Beach Stealth Pro II Review and Corsair One A600 Review, and report which detail page includes the tag \"headset\".", "web": "http://localhost:40016/", "upstream_url": "https://www.ign.com/"} -{"web_name": "IGN", "id": "IGN--5", "ques": "Log in as carol.d@test.com with password TestPass123!, update the account profile so the region is San Francisco, CA and the favorite platform is Nintendo Switch 2.", "web": "http://localhost:40016/", "upstream_url": "https://www.ign.com/"} -{"web_name": "IGN", "id": "IGN--6", "ques": "Log in as bob.c@test.com with password TestPass123!, search for GeForce RTX 5070, open the Walmart prebuilt gaming PC deal story, and save it to the Deals folder.", "web": "http://localhost:40016/", "upstream_url": "https://www.ign.com/"} -{"web_name": "IGN", "id": "IGN--7", "ques": "Log in as alice.j@test.com with password TestPass123!, open Silo Season 3 Review, and post the comment \"Watch this before the finale.\".", "web": "http://localhost:40016/", "upstream_url": "https://www.ign.com/"} -{"web_name": "IGN", "id": "IGN--8", "ques": "Log in as david.k@test.com with password TestPass123!, go to the Videos section, find PlayStation’s Physical Media-Free Future Isn’t Just Concerning, It’s Offensive, and add it to Playlist with status watching.", "web": "http://localhost:40016/", "upstream_url": "https://www.ign.com/"} -{"web_name": "IGN", "id": "IGN--9", "ques": "Log in as carol.d@test.com with password TestPass123!, open the Deals section, find MTG's Rare Marvel Collector Boosters Are Available Again Online, But Expected to Sell Out Fast, and save it to the Deals folder with the note \"sealed booster watch\".", "web": "http://localhost:40016/", "upstream_url": "https://www.ign.com/"} -{"web_name": "IGN", "id": "IGN--10", "ques": "Log in as carol.d@test.com with password TestPass123!, open the Fortnite Guide, and save it with the note \"weekly quests\".", "web": "http://localhost:40016/", "upstream_url": "https://www.ign.com/"} -{"web_name": "IGN", "id": "IGN--11", "ques": "Open the TV section, find Everything Coming to HBO Max in July, and add it to Playlist after logging in as david.k@test.com with password TestPass123!.", "web": "http://localhost:40016/", "upstream_url": "https://www.ign.com/"} -{"web_name": "IGN", "id": "IGN--12", "ques": "Log in as alice.j@test.com with password TestPass123!, search physical media future, open the PlayStation article, and save it to Read Later.", "web": "http://localhost:40016/", "upstream_url": "https://www.ign.com/"} -{"web_name": "IGN", "id": "IGN--13", "ques": "Log in as bob.c@test.com with password TestPass123!, go to Alerts, and pause the existing weekly update guide alert.", "web": "http://localhost:40016/", "upstream_url": "https://www.ign.com/"} -{"web_name": "IGN", "id": "IGN--14", "ques": "Log in as alice.j@test.com with password TestPass123!, go to Saved Stories, find the saved item in the Deals folder, and remove it.", "web": "http://localhost:40016/", "upstream_url": "https://www.ign.com/"} -{"web_name": "IGN", "id": "IGN--15", "ques": "Log in as bob.c@test.com with password TestPass123!, open All GTA 5 Cheat Codes and Secrets For PC and Console and mark \"Read overview\" as complete.", "web": "http://localhost:40016/", "upstream_url": "https://www.ign.com/"} -{"web_name": "IGN", "id": "IGN--16", "ques": "Find Everything Major Added to Final Fantasy 7 Rebirth Since Launch and add it to Playlist as queued after logging in as alice.j@test.com with password TestPass123!.", "web": "http://localhost:40016/", "upstream_url": "https://www.ign.com/"} -{"web_name": "IGN", "id": "IGN--17", "ques": "Search for HBO Max July and Amazon Prime Video July, open both stories, and report which detail page lists Nintendo Switch 2 in Platforms.", "web": "http://localhost:40016/", "upstream_url": "https://www.ign.com/"} -{"web_name": "IGN", "id": "IGN--18", "ques": "Log in as david.k@test.com with password TestPass123!, open the Movies section, find the Anne Hathaway Quit Knocked Up story, and save it to the Weekend folder with the note \"casting anecdote\".", "web": "http://localhost:40016/", "upstream_url": "https://www.ign.com/"} +{"web_name": "IGN", "id": "IGN--0", "ques": "Log in as alice.j@test.com with password TestPass123!, search for The Complicated History of GTA Controversies, open the story, and save it to the Games folder with the note \"gta controversy timeline\".", "web": "http://localhost:40016/", "upstream_url": "https://www.ign.com/", "verifier_path": "sites/ign/verify/verify_0.py", "judge_rubric": "PASS requires: agent logged in as alice.j and opened the 'The Complicated History of GTA Controversies' story detail page, then saved it. The saved item MUST end up in the 'Games' folder with a note like 'gta controversy timeline'. Empty answer, no login, or leaving it in the default/seed folder = FAIL."} +{"web_name": "IGN", "id": "IGN--1", "ques": "Log in as alice.j@test.com with password TestPass123!, find the GTA Online Weekly Updates guide, and mark the checklist item \"Claim Independence Day freebies\" as complete.", "web": "http://localhost:40016/", "upstream_url": "https://www.ign.com/", "verifier_path": "sites/ign/verify/verify_1.py", "judge_rubric": "PASS requires: logged in as alice.j, opened the 'GTA Online Weekly Updates' guide, and the checklist item 'Claim Independence Day freebies' is marked complete on that guide. No completion / no guide visit = FAIL."} +{"web_name": "IGN", "id": "IGN--2", "ques": "Log in as bob.c@test.com with password TestPass123!, find Will Nintendo Ever Stop Making Physical Games? - NVC Clips, and add it to Playlist with the note \"physical games debate\".", "web": "http://localhost:40016/", "upstream_url": "https://www.ign.com/", "verifier_path": "sites/ign/verify/verify_2.py", "judge_rubric": "PASS requires: logged in as bob.c, opened the 'Will Nintendo Ever Stop Making Physical Games? - NVC Clips' video, and added it to Playlist with a note like 'physical games debate'. No playlist entry or missing note = FAIL."} +{"web_name": "IGN", "id": "IGN--3", "ques": "Log in as david.k@test.com with password TestPass123!, create a daily Games alert for the keyword PlayStation.", "web": "http://localhost:40016/", "upstream_url": "https://www.ign.com/", "verifier_path": "sites/ign/verify/verify_3.py", "judge_rubric": "PASS requires: logged in as david.k and created a Games-section alert with keyword 'PlayStation' at daily frequency. Reusing the pre-existing games alert (whose keyword is a platform name) is NOT sufficient = FAIL."} +{"web_name": "IGN", "id": "IGN--4", "ques": "Use the Reviews section's All Genres filter to show Tech reviews, open Turtle Beach Stealth Pro II Review and Corsair One A600 Review, and report which detail page includes the tag \"headset\".", "web": "http://localhost:40016/", "upstream_url": "https://www.ign.com/", "verifier_path": "sites/ign/verify/verify_4.py", "judge_rubric": "PASS requires: opened BOTH the Turtle Beach Stealth Pro II Review and the Corsair One A600 Review detail pages, and correctly reported that the Turtle Beach Stealth Pro II review is the one whose detail page includes the 'headset' tag. Naming Corsair, or answering without opening the detail pages, = FAIL."} +{"web_name": "IGN", "id": "IGN--5", "ques": "Log in as carol.d@test.com with password TestPass123!, update the account profile so the region is San Francisco, CA and the favorite platform is Nintendo Switch 2.", "web": "http://localhost:40016/", "upstream_url": "https://www.ign.com/", "verifier_path": "sites/ign/verify/verify_5.py", "judge_rubric": "PASS requires: logged in as carol.d and edited the account profile so region = 'San Francisco, CA' and favorite platform = 'Nintendo Switch 2'. Leaving the seed values (Brooklyn, NY / Nintendo Switch) = FAIL."} +{"web_name": "IGN", "id": "IGN--6", "ques": "Log in as bob.c@test.com with password TestPass123!, search for GeForce RTX 5070, open the Walmart prebuilt gaming PC deal story, and save it to the Deals folder.", "web": "http://localhost:40016/", "upstream_url": "https://www.ign.com/", "verifier_path": "sites/ign/verify/verify_6.py", "judge_rubric": "PASS requires: logged in as bob.c, opened the Walmart RTX 5070 prebuilt gaming PC deal story, and saved it to the 'Deals' folder. No Deals-folder save = FAIL."} +{"web_name": "IGN", "id": "IGN--7", "ques": "Log in as alice.j@test.com with password TestPass123!, open Silo Season 3 Review, and post the comment \"Watch this before the finale.\".", "web": "http://localhost:40016/", "upstream_url": "https://www.ign.com/", "verifier_path": "sites/ign/verify/verify_7.py", "judge_rubric": "PASS requires: logged in as alice.j, opened the 'Silo Season 3 Review', and posted the comment 'Watch this before the finale.'. No comment posted = FAIL."} +{"web_name": "IGN", "id": "IGN--8", "ques": "Log in as david.k@test.com with password TestPass123!, go to the Videos section, find PlayStation’s Physical Media-Free Future Isn’t Just Concerning, It’s Offensive, and add it to Playlist with status watching.", "web": "http://localhost:40016/", "upstream_url": "https://www.ign.com/", "verifier_path": "sites/ign/verify/verify_8.py", "judge_rubric": "PASS requires: logged in as david.k, opened the PlayStation physical-media VIDEO (not the same-title article) via the Videos section, and added it to Playlist with status 'watching'. Wrong status, or adding the article instead of the video, = FAIL."} +{"web_name": "IGN", "id": "IGN--9", "ques": "Log in as carol.d@test.com with password TestPass123!, open the Deals section, find MTG's Rare Marvel Collector Boosters Are Available Again Online, But Expected to Sell Out Fast, and save it to the Deals folder with the note \"sealed booster watch\".", "web": "http://localhost:40016/", "upstream_url": "https://www.ign.com/", "verifier_path": "sites/ign/verify/verify_9.py", "judge_rubric": "PASS requires: logged in as carol.d, opened the MTG Marvel Collector Boosters deal story, and saved it to the 'Deals' folder with a note like 'sealed booster watch'. Missing Deals folder or note = FAIL."} +{"web_name": "IGN", "id": "IGN--10", "ques": "Log in as carol.d@test.com with password TestPass123!, open the Fortnite Guide, and save it with the note \"weekly quests\".", "web": "http://localhost:40016/", "upstream_url": "https://www.ign.com/", "verifier_path": "sites/ign/verify/verify_10.py", "judge_rubric": "PASS requires: logged in as carol.d, opened the Fortnite guide, and saved it with a note like 'weekly quests'. No save or missing note = FAIL."} +{"web_name": "IGN", "id": "IGN--11", "ques": "Open the TV section, find Everything Coming to HBO Max in July, and add it to Playlist after logging in as david.k@test.com with password TestPass123!.", "web": "http://localhost:40016/", "upstream_url": "https://www.ign.com/", "verifier_path": "sites/ign/verify/verify_11.py", "judge_rubric": "PASS requires: logged in as david.k, opened the 'Everything Coming to HBO Max in July' story, and added it to Playlist. No playlist entry = FAIL."} +{"web_name": "IGN", "id": "IGN--12", "ques": "Log in as alice.j@test.com with password TestPass123!, search physical media future, open the PlayStation article, and save it to Read Later.", "web": "http://localhost:40016/", "upstream_url": "https://www.ign.com/", "verifier_path": "sites/ign/verify/verify_12.py", "judge_rubric": "PASS requires: logged in as alice.j, opened the PlayStation physical-media ARTICLE (not the same-title video), and saved it to 'Read Later'. Leaving it in the seed 'Weekend' folder, or saving the video, = FAIL."} +{"web_name": "IGN", "id": "IGN--13", "ques": "Log in as bob.c@test.com with password TestPass123!, go to Alerts, and pause the existing weekly update guide alert.", "web": "http://localhost:40016/", "upstream_url": "https://www.ign.com/", "verifier_path": "sites/ign/verify/verify_13.py", "judge_rubric": "PASS requires: logged in as bob.c, went to Alerts, and paused (deactivated) the existing weekly-update guide alert. The alert still being active = FAIL."} +{"web_name": "IGN", "id": "IGN--14", "ques": "Log in as alice.j@test.com with password TestPass123!, go to Saved Stories, find the saved item in the Deals folder, and remove it.", "web": "http://localhost:40016/", "upstream_url": "https://www.ign.com/", "verifier_path": "sites/ign/verify/verify_14.py", "judge_rubric": "PASS requires: logged in as alice.j, opened Saved Stories, and removed the item in the Deals folder so the Deals folder is empty afterward. No removal = FAIL."} +{"web_name": "IGN", "id": "IGN--15", "ques": "Log in as bob.c@test.com with password TestPass123!, open All GTA 5 Cheat Codes and Secrets For PC and Console and mark \"Read overview\" as complete.", "web": "http://localhost:40016/", "upstream_url": "https://www.ign.com/", "verifier_path": "sites/ign/verify/verify_15.py", "judge_rubric": "PASS requires: logged in as bob.c, opened 'All GTA 5 Cheat Codes and Secrets For PC and Console', and marked the checklist item 'Read overview' complete. No completion = FAIL."} +{"web_name": "IGN", "id": "IGN--16", "ques": "Log in as alice.j@test.com with password TestPass123!, find the Dragon Ball Xenoverse 2 - Official Future Saga Chapter 4 Trailer video, and add it to Playlist as queued.", "web": "http://localhost:40016/", "upstream_url": "https://www.ign.com/", "verifier_path": "sites/ign/verify/verify_16.py", "judge_rubric": "PASS requires: logged in as alice.j, opened the 'Dragon Ball Xenoverse 2 - Official Future Saga Chapter 4 Trailer' video, and added it to Playlist with status 'queued'. No playlist entry for that video = FAIL."} +{"web_name": "IGN", "id": "IGN--17", "ques": "Search for HBO Max July and Amazon Prime Video July, open both stories, and report which detail page lists Nintendo Switch 2 in Platforms.", "web": "http://localhost:40016/", "upstream_url": "https://www.ign.com/", "verifier_path": "sites/ign/verify/verify_17.py", "judge_rubric": "PASS requires: opened BOTH the HBO Max July and the Amazon Prime Video July story detail pages, and correctly reported that the Amazon Prime Video (July) story lists 'Nintendo Switch 2' in its Platforms. Naming HBO Max, or answering without opening the detail pages, = FAIL."} +{"web_name": "IGN", "id": "IGN--18", "ques": "Log in as david.k@test.com with password TestPass123!, open the Movies section, find the Anne Hathaway Quit Knocked Up story, and save it to the Weekend folder with the note \"casting anecdote\".", "web": "http://localhost:40016/", "upstream_url": "https://www.ign.com/", "verifier_path": "sites/ign/verify/verify_18.py", "judge_rubric": "PASS requires: logged in as david.k, opened the 'Anne Hathaway Quit Knocked Up' story via the Movies section, and saved it to the 'Weekend' folder with a note like 'casting anecdote'. Missing Weekend folder or note = FAIL."} diff --git a/sites/ign/templates/account_edit.html b/sites/ign/templates/account_edit.html index b96da8fb..3e6abf03 100644 --- a/sites/ign/templates/account_edit.html +++ b/sites/ign/templates/account_edit.html @@ -9,7 +9,7 @@

Edit profile