{% extends "base.html" %} {% import '_start_page_form.html' as startpage %} {% block styles %} {% endblock %} {% block content %} {% if devinfo_warnings %}
{% for w in devinfo_warnings %}
{{ w.title }}
{{ w.body_html|safe }}
{% endfor %}
{% endif %}
{# ── "try the new home page" ─────────────────────────────────────────── Only rendered on the classic layout, and only until this account has either dismissed it or switched over once (app.py decides, see show_new_home_promo). Not rendered at all otherwise: a banner that the client hides after the fact still ships its text to everyone and still flashes on a slow paint. The switch is per ACCOUNT. Trying the new layout does not change what anybody else sees, which is the whole reason this is an invitation and not an announcement. #} {% if show_new_home_promo %}
{{ _('Beta') }}
{{ _('Try the new home page') }}
{{ _('Rows grouped by question instead of by source, with your watch progress and watchlist on top. Only you see the change — switch back at any time in Settings.') }}
{% endif %}

{{ _('What do you want to download?') }}

{{ _('One title is enough — MediaForge asks every active source at once.') }}

{# The Seerr modal reuses .search-bar, so the home-only pill styling hangs off the extra .home-searchbar class instead of touching .search-bar itself. #} {# The panel-bar keys below (panel_*, hp_*) live in this template rather than in routes/home_panels.py because babel.cfg extracts Jinja templates ONLY -- a _() in Python source never reaches a catalogue. The server sends the key, static/home_panels.js looks it up here. Placeholders are written as "{}" and not "%s": Flask-Babel installs newstyle gettext, which rewrites %s to {} in the rendered string while the catalogue keeps %s -- "{}" keeps template, catalogue and JS in step. NOTE: do not put a Jinja comment inside the dict below -- a comment is not allowed inside an expression and takes the whole page down with a TemplateSyntaxError. (Nor inside this one: the closing delimiter of a nested comment ends the outer one and the rest leaks onto the page.) #} {# Every string the home page renders client-side, translated here so it goes through the same Flask-Babel catalogue as the rest of the app instead of a hardcoded de/en pair in JavaScript. Rendered for both home variants -- the search box below is shared. #} {# The panel bar: buttons under the search field, one panel below them. Both are empty until /api/home-panels answers -- which panels an account may even see is decided server-side (Storage and System are admin-only), so rendering the buttons here would mean rendering buttons that 403. The bar hides itself when nothing is available. #} {% if new_home %}
{% endif %} {# Which sources a search actually hits. Filled by renderSourceChips() in app.js once /api/settings answered; empty (and invisible) until then. The new home page does NOT render this: its own chip row (below) says the same thing *and* filters, and two rows of identical source names stacked on top of each other is a puzzle, not a control. #} {% if not new_home %}
{# The way in to "which home page do I want", on the classic layout too. Not optional politeness: /settings redirects a non-admin to "/", so this modal is the ONLY place a normal account can pick a layout -- and the promo banner above is gone for good once it has been answered. Without this, dismissing the banner would be a one-way door onto v1. #}
{% endif %} {# Only rendered while something is downloading -- see renderHomeRunStrip(), fed by the queue poll that runs anyway. #}
{{ _('Searching...') }}
{# Dashboard widgets from enabled extensions (see register_thirdparty(dashboard_widget_template=...) in web/thirdparties/registry.py) — each entry's own template is included as-is, so the widget's markup/behaviour is entirely up to the extension; this just provides the slot + consistent spacing (.browse-provider-block, reused for its margin only — no provider dot styling implied). Nothing renders here if no extension registered one. #} {% for widget in dashboard_widgets %}
{# "ignore missing" -- a widget template that was removed/renamed after being registered (e.g. mid-upgrade) silently disappears instead of taking down the entire home page for every user. #} {% include widget.template ignore missing %}
{% endfor %} {# Settings -> General -> "Use the new home page": one row per question, every enabled source mixed into it, instead of one block per source. The classic block below stays the default and is what modules extend. #} {% if new_home %}
{# The chip row and the way in to "which rows do I even want". /settings is admin-only, so this is where a normal account reaches its own Start Page settings -- the same controls, same code, same storage. #}
{# Personal rows first: what you were doing beats what a site published. Each one hides itself when its source has nothing to say (no playback position yet, no favourites, calendar switched off), so a fresh install shows exactly the discovery rows below and no empty furniture. #}

{{ _('New this week') }}

{{ _('Popular right now') }}

{{ _('Movies') }}

{% else %}
AniWorld

{{ _('New Anime') }}

{{ _('Popular Anime') }}

SerienStream

{{ _('New Series') }}

{{ _('Popular Series') }}

FilmPalast

{{ _('New Movies') }}

MegaKino

{{ _('New Movies') }}

{{ _('Popular Movies') }}

{{ _('New Series') }}

{{ _('Popular Series') }}

{% endif %}
{# Start Page settings for this account. Same macro the Settings tab uses -- an admin sees the instance defaults there as well, everyone sees their own here. Rendered on BOTH layouts: /settings redirects a non-admin, so this is the only place a normal account can switch between them at all. #} {% include "shared_modals.html" %} {% endblock %} {% block scripts %} {% if new_home %} {% endif %} {{ startpage.start_page_i18n() }} {% if show_new_home_promo %} {% endif %} {% endblock %}