diff options
Diffstat (limited to 'templates')
| -rw-r--r-- | templates/_feed_partial.html | 12 | ||||
| -rw-r--r-- | templates/_film_card.html | 112 | ||||
| -rw-r--r-- | templates/_public_feed_partial.html | 71 | ||||
| -rw-r--r-- | templates/base.html | 66 | ||||
| -rw-r--r-- | templates/detail.html | 455 | ||||
| -rw-r--r-- | templates/form.html | 226 | ||||
| -rw-r--r-- | templates/index.html | 65 | ||||
| -rw-r--r-- | templates/profile.html | 292 |
8 files changed, 785 insertions, 514 deletions
diff --git a/templates/_feed_partial.html b/templates/_feed_partial.html index e22cdde..47800be 100644 --- a/templates/_feed_partial.html +++ b/templates/_feed_partial.html @@ -1,10 +1,14 @@ {% if active_shelf == 'diary' and grouped_films %} {% for group in grouped_films %} <div class="month-group" data-month="{{ group.month }}"> - <p class="month-label">{{ group.month }}</p> - {% for film in group.films %} - {% include "_film_card.html" %} - {% endfor %} + <div class="month-rail"> + <p class="month-label">{{ group.month }}</p> + </div> + <div class="month-stack"> + {% for film in group.films %} + {% include "_film_card.html" %} + {% endfor %} + </div> </div> {% endfor %} {% else %} diff --git a/templates/_film_card.html b/templates/_film_card.html index a4e206f..6723821 100644 --- a/templates/_film_card.html +++ b/templates/_film_card.html @@ -7,8 +7,8 @@ {% endif %} </a> <div class="film-card-body"> - <div class="film-card-header"> - <div> + <div class="film-card-main"> + <div class="film-card-header"> <h2><a href="/films/{{ film.id }}">{{ film.title }}</a></h2> <p class="muted"> {% if film.year %}{{ film.year }}{% endif %} @@ -21,6 +21,55 @@ {% endif %} </p> </div> + + <div class="ledger-strip"> + <span>{{ film.shelf|title }}</span> + {% if film.date_watched %}<span>{{ film.date_watched }}</span>{% endif %} + {% if film.runtime %}<span>{{ film.runtime }} min</span>{% endif %} + {% if film.rewatch %}<span>Rewatch{% if film.rewatch_count %} #{{ film.rewatch_count }}{% endif %}</span>{% endif %} + </div> + + {% if film.language or film.genre or film.context or film.how_found or film.watched_with %} + <div class="fact-list fact-list-compact"> + {% if film.language %} + <div class="fact-row"> + <span class="fact-label">Lang</span> + <span class="fact-value">{{ film.language }}</span> + </div> + {% endif %} + {% if film.genre %} + <div class="fact-row"> + <span class="fact-label">Genre</span> + <span class="fact-value">{{ film.genre }}</span> + </div> + {% endif %} + {% if film.context %} + <div class="fact-row"> + <span class="fact-label">Context</span> + <span class="fact-value">{{ film.context }}</span> + </div> + {% endif %} + {% if film.how_found %} + <div class="fact-row"> + <span class="fact-label">Found</span> + <span class="fact-value">{{ film.how_found }}</span> + </div> + {% endif %} + {% if film.watched_with %} + <div class="fact-row"> + <span class="fact-label">With</span> + <span class="fact-value">{{ film.watched_with }}</span> + </div> + {% endif %} + </div> + {% endif %} + + {% if film.notes %} + <p class="notes-preview">{{ film.notes[:220] }}{% if film.notes|length > 220 %}...{% endif %}</p> + {% endif %} + </div> + + <div class="film-card-side"> {% if film.shelf == 'diary' %} <div class="star-control" role="group" aria-label="Rate film" data-film-id="{{ film.id }}" data-current-stars="{{ film.stars }}"> {% for value in range(1, 4) %} @@ -36,48 +85,27 @@ {% elif film.stars %} <span class="rating">{% for _ in range(film.stars) %}✦{% endfor %}</span> {% endif %} - </div> - - <div class="meta-row"> - <span>{{ film.shelf|title }}</span> - {% if film.date_watched %}<span>{{ film.date_watched }}</span>{% endif %} - {% if film.runtime %}<span>{{ film.runtime }} min</span>{% endif %} - {% if film.language %}<span>{{ film.language }}</span>{% endif %} - {% if film.rewatch %}<span>Rewatch{% if film.rewatch_count %} #{{ film.rewatch_count }}{% endif %}</span>{% endif %} - </div> - {% if film.genre or film.context or film.how_found or film.watched_with %} - <div class="tag-row"> - {% if film.genre %}<span>{{ film.genre }}</span>{% endif %} - {% if film.context %}<span>{{ film.context }}</span>{% endif %} - {% if film.how_found %}<span>{{ film.how_found }}</span>{% endif %} - {% if film.watched_with %}<span>With {{ film.watched_with }}</span>{% endif %} + <div class="inline-actions"> + {% if film.shelf == 'queue' %} + <a class="small-button" href="/films/{{ film.id }}/edit?shelf=diary">Mark watched</a> + <form method="post" action="/films/{{ film.id }}/shelf/abandoned"> + <button class="secondary-button" type="submit">Abandon</button> + </form> + {% elif film.shelf == 'diary' %} + <form method="post" action="/films/{{ film.id }}/shelf/queue"> + <button class="secondary-button" type="submit">Move to queue</button> + </form> + <form method="post" action="/films/{{ film.id }}/shelf/abandoned"> + <button class="secondary-button" type="submit">Mark abandoned</button> + </form> + {% else %} + <a class="small-button" href="/films/{{ film.id }}/edit?shelf=diary">Mark watched</a> + <form method="post" action="/films/{{ film.id }}/shelf/queue"> + <button class="secondary-button" type="submit">Move to queue</button> + </form> + {% endif %} </div> - {% endif %} - - {% if film.notes %} - <p class="notes-preview">{{ film.notes[:220] }}{% if film.notes|length > 220 %}...{% endif %}</p> - {% endif %} - - <div class="inline-actions"> - {% if film.shelf == 'queue' %} - <a class="small-button" href="/films/{{ film.id }}/edit?shelf=diary">Mark watched</a> - <form method="post" action="/films/{{ film.id }}/shelf/abandoned"> - <button class="secondary-button" type="submit">Abandon</button> - </form> - {% elif film.shelf == 'diary' %} - <form method="post" action="/films/{{ film.id }}/shelf/queue"> - <button class="secondary-button" type="submit">Move to queue</button> - </form> - <form method="post" action="/films/{{ film.id }}/shelf/abandoned"> - <button class="secondary-button" type="submit">Mark abandoned</button> - </form> - {% else %} - <a class="small-button" href="/films/{{ film.id }}/edit?shelf=diary">Mark watched</a> - <form method="post" action="/films/{{ film.id }}/shelf/queue"> - <button class="secondary-button" type="submit">Move to queue</button> - </form> - {% endif %} </div> </div> </article> diff --git a/templates/_public_feed_partial.html b/templates/_public_feed_partial.html new file mode 100644 index 0000000..ba84c61 --- /dev/null +++ b/templates/_public_feed_partial.html @@ -0,0 +1,71 @@ +{% for film in films %} +<article class="public-film-row"> + <div class="poster-frame public-film-poster"> + {% if film.poster_url %} + <img src="{{ film.poster_url }}" alt="{{ film.title }} poster" loading="lazy"> + {% else %} + <span>{{ film.title[:1] }}</span> + {% endif %} + </div> + <div class="public-film-body"> + <div class="public-film-head"> + <div> + <h2>{{ film.title }}</h2> + <p> + {% if film.year %}{{ film.year }}{% endif %} + {% set directors = split_credit_names(film.director) %} + {% if directors %} + {% if film.year %} · {% endif %} + {{ directors|join(", ") }} + {% endif %} + </p> + </div> + {% if film.stars %} + <span class="rating">{% for _ in range(film.stars) %}✦{% endfor %}</span> + {% endif %} + </div> + + <div class="ledger-strip"> + <span>Diary</span> + {% if film.date_watched %}<span>{{ film.date_watched }}</span>{% endif %} + {% if film.runtime %}<span>{{ film.runtime }} min</span>{% endif %} + {% if film.rewatch %}<span>Rewatch{% if film.rewatch_count %} #{{ film.rewatch_count }}{% endif %}</span>{% endif %} + </div> + + {% if film.language or film.genre or film.context or film.how_found or film.watched_with %} + <div class="fact-list fact-list-compact"> + {% if film.language %} + <div class="fact-row"> + <span class="fact-label">Lang</span> + <span class="fact-value">{{ film.language }}</span> + </div> + {% endif %} + {% if film.genre %} + <div class="fact-row"> + <span class="fact-label">Genre</span> + <span class="fact-value">{{ film.genre }}</span> + </div> + {% endif %} + {% if film.context %} + <div class="fact-row"> + <span class="fact-label">Context</span> + <span class="fact-value">{{ film.context }}</span> + </div> + {% endif %} + {% if film.how_found %} + <div class="fact-row"> + <span class="fact-label">Found</span> + <span class="fact-value">{{ film.how_found }}</span> + </div> + {% endif %} + {% if film.watched_with %} + <div class="fact-row"> + <span class="fact-label">With</span> + <span class="fact-value">{{ film.watched_with }}</span> + </div> + {% endif %} + </div> + {% endif %} + </div> +</article> +{% endfor %} diff --git a/templates/base.html b/templates/base.html index 4e814ad..1b48f06 100644 --- a/templates/base.html +++ b/templates/base.html @@ -10,26 +10,56 @@ </head> <body> <div class="shell"> - <header class="topbar"> - <a class="brand" href="/">Lumière</a> - <button id="menu-toggle" class="menu-toggle" aria-label="Menu" aria-expanded="false"></button> - <nav class="nav-actions" id="nav-actions" aria-label="Primary"> - <a class="{% if active_shelf == 'diary' %}is-active{% endif %}" href="/diary">Diary</a> - <a class="{% if active_shelf == 'queue' %}is-active{% endif %}" href="/queue">Queue</a> - <a class="{% if active_shelf == 'abandoned' %}is-active{% endif %}" href="/abandoned">Abandoned</a> - <a class="{% if active_page == 'stats' %}is-active{% endif %}" href="/stats">Stats</a> - <a class="{% if active_page == 'about' %}is-active{% endif %}" href="/about">About</a> - <a class="{% if active_page == 'import' %}is-active{% endif %}" href="/import">Import</a> - <a class="button-link" href="/films/new">Add Film</a> - <form method="post" action="/logout" style="display: contents;"> - <button type="submit" style="background: none; border: none; color: var(--muted); cursor: pointer; font-size: inherit; padding: 0;">Logout</button> + <aside class="app-sidebar"> + <div class="app-sidebar-inner"> + <a class="brand" href="/">Lumière</a> + <p class="sidebar-intro">A private film ledger for what stayed with you.</p> + + <a class="button-link sidebar-cta" href="/films/new">Add Film</a> + + <nav class="sidebar-nav" aria-label="Primary"> + <div class="sidebar-group"> + <p class="sidebar-label">Shelves</p> + <a class="{% if active_shelf == 'diary' %}is-active{% endif %}" href="/diary">Diary</a> + <a class="{% if active_shelf == 'queue' %}is-active{% endif %}" href="/queue">Queue</a> + <a class="{% if active_shelf == 'abandoned' %}is-active{% endif %}" href="/abandoned">Abandoned</a> + </div> + <div class="sidebar-group"> + <p class="sidebar-label">Library</p> + <a class="{% if active_page == 'stats' %}is-active{% endif %}" href="/stats">Stats</a> + <a class="{% if active_page == 'about' %}is-active{% endif %}" href="/about">About</a> + <a class="{% if active_page == 'import' %}is-active{% endif %}" href="/import">Import</a> + </div> + </nav> + + <form class="sidebar-logout" method="post" action="/logout"> + <button type="submit" class="sidebar-logout-button">Logout</button> </form> - </nav> - </header> + </div> + </aside> + + <div class="app-main"> + <header class="topbar"> + <a class="brand" href="/">Lumière</a> + <button id="menu-toggle" class="menu-toggle" aria-label="Menu" aria-expanded="false"></button> + <nav class="nav-actions" id="nav-actions" aria-label="Primary"> + <a class="{% if active_shelf == 'diary' %}is-active{% endif %}" href="/diary">Diary</a> + <a class="{% if active_shelf == 'queue' %}is-active{% endif %}" href="/queue">Queue</a> + <a class="{% if active_shelf == 'abandoned' %}is-active{% endif %}" href="/abandoned">Abandoned</a> + <a class="{% if active_page == 'stats' %}is-active{% endif %}" href="/stats">Stats</a> + <a class="{% if active_page == 'about' %}is-active{% endif %}" href="/about">About</a> + <a class="{% if active_page == 'import' %}is-active{% endif %}" href="/import">Import</a> + <a class="button-link" href="/films/new">Add Film</a> + <form method="post" action="/logout" class="nav-logout"> + <button type="submit" class="nav-logout-button">Logout</button> + </form> + </nav> + </header> - <main> - {% block content %}{% endblock %} - </main> + <main class="app-content"> + {% block content %}{% endblock %} + </main> + </div> </div> {% block scripts %}{% endblock %} </body> diff --git a/templates/detail.html b/templates/detail.html index 9c558f7..f51372d 100644 --- a/templates/detail.html +++ b/templates/detail.html @@ -17,236 +17,335 @@ <button type="button" id="change-poster-btn" - class="secondary-button" - style="width: 100%; margin-top: 12px;" + class="secondary-button detail-poster-button" data-tmdb-id="{{ film.tmdb_id }}" data-film-id="{{ film.id }}" >Change Poster</button> - - <div id="poster-picker" style="display: none; margin-top: 12px;"> - <p id="poster-picker-status" style="color: var(--muted); font-size: 0.86rem; margin: 0 0 10px;">Loading posters…</p> - <div id="poster-grid" style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px;"></div> - </div> {% endif %} - - <div class="detail-aside-meta"> - <div> - <span class="summary-label">Shelf</span> - <strong>{{ film.shelf|title }}</strong> - </div> - <div> - <span class="summary-label">Watched</span> - <strong>{% if film.date_watched %}{{ film.date_watched }}{% else %}Not set{% endif %}</strong> - </div> - <div> - <span class="summary-label">Stars</span> - <strong>{% if film.stars %}{% for _ in range(film.stars) %}✦{% endfor %}{% else %}Unstarred{% endif %}</strong> - </div> - </div> </aside> <section class="detail-body"> - <p class="eyebrow">{{ film.shelf|title }} Entry</p> - <h1>{{ film.title }}</h1> - {% if film.original_title %} - <p class="original-title">{{ film.original_title }}</p> - {% endif %} - <div class="detail-subtitle"> - <p class="subtitle"> - {% if film.year %}{{ film.year }}{% endif %} - {% set directors = split_credit_names(film.director) %} - {% if directors %} - {% if film.year %} · {% endif %} - {% for director in directors %} - <a class="inline-link" href="{{ director_href(director) }}">{{ director }}</a>{% if not loop.last %}, {% endif %} - {% endfor %} + <section class="detail-hero"> + <div class="detail-hero-copy"> + <p class="eyebrow">{{ film.shelf|title }} Entry</p> + <h1>{{ film.title }}</h1> + {% if film.original_title %} + <p class="original-title">{{ film.original_title }}</p> {% endif %} - </p> - {% if ratings %} - <div class="ratings-inline"> - {% if ratings.imdb %} - <span class="rating-badge"> - <img src="/static/logos/imdb.svg" alt="IMDb" height="18"> - <span>{{ ratings.imdb }}</span> - </span> - {% endif %} - {% if ratings.rt %} - <span class="rating-badge"> - <img src="/static/logos/rt.svg" alt="Rotten Tomatoes" height="18"> - <span>{{ ratings.rt }}</span> - </span> - {% endif %} - {% if ratings.metacritic %} - <span class="rating-badge"> - <img src="/static/logos/metacritic.svg" alt="Metacritic" height="18"> - <span>{{ ratings.metacritic }}</span> - </span> + <div class="detail-subtitle"> + <p class="subtitle"> + {% if film.year %}{{ film.year }}{% endif %} + {% set directors = split_credit_names(film.director) %} + {% if directors %} + {% if film.year %} · {% endif %} + {% for director in directors %} + <a class="inline-link" href="{{ director_href(director) }}">{{ director }}</a>{% if not loop.last %}, {% endif %} + {% endfor %} + {% endif %} + </p> + {% if ratings %} + <div class="ratings-inline"> + {% if ratings.imdb %} + <span class="rating-badge"> + <img src="/static/logos/imdb.svg" alt="IMDb" height="18"> + <span>{{ ratings.imdb }}</span> + </span> + {% endif %} + {% if ratings.rt %} + <span class="rating-badge"> + <img src="/static/logos/rt.svg" alt="Rotten Tomatoes" height="18"> + <span>{{ ratings.rt }}</span> + </span> + {% endif %} + {% if ratings.metacritic %} + <span class="rating-badge"> + <img src="/static/logos/metacritic.svg" alt="Metacritic" height="18"> + <span>{{ ratings.metacritic }}</span> + </span> + {% endif %} + </div> + {% endif %} + </div> + </div> + + <div class="detail-actions"> + <a class="secondary-button" href="/films/{{ film.id }}/edit">Edit</a> + {% if film.shelf == 'queue' %} + <a class="button-link" href="/films/{{ film.id }}/edit?shelf=diary">Mark watched</a> + <form method="post" action="/films/{{ film.id }}/shelf/abandoned"> + <button class="secondary-button" type="submit">Abandon</button> + </form> + {% elif film.shelf == 'diary' %} + <form method="post" action="/films/{{ film.id }}/shelf/queue"> + <button class="secondary-button" type="submit">Move to queue</button> + </form> + <form method="post" action="/films/{{ film.id }}/shelf/abandoned"> + <button class="secondary-button" type="submit">Mark abandoned</button> + </form> + {% else %} + <a class="button-link" href="/films/{{ film.id }}/edit?shelf=diary">Mark watched</a> + <form method="post" action="/films/{{ film.id }}/shelf/queue"> + <button class="secondary-button" type="submit">Move to queue</button> + </form> {% endif %} + <form method="post" action="/films/{{ film.id }}/delete"> + <button class="danger-button" type="submit">Delete</button> + </form> </div> - {% endif %} - </div> + </section> - <section class="detail-grid"> - <article class="detail-panel"> - <p class="eyebrow">Watch log</p> - <div class="detail-meta"> - {% if film.date_watched %}<span>{{ film.date_watched }}</span>{% endif %} - {% if film.runtime %}<span>{{ film.runtime }} min</span>{% endif %} - {% if film.rewatch %}<span>Rewatch{% if film.rewatch_count %} #{{ film.rewatch_count }}{% endif %}</span>{% endif %} - {% if film.watched_with %}<span>With {{ film.watched_with }}</span>{% endif %} - {% if film.context %}<span>{{ film.context }}</span>{% endif %} - {% if film.how_found %}<span>{{ film.how_found }}</span>{% endif %} + <section class="detail-columns"> + <aside class="detail-sidebar"> + <div class="detail-aside-meta"> + <div> + <span class="summary-label">Shelf</span> + <strong>{{ film.shelf|title }}</strong> + </div> + <div> + <span class="summary-label">Watched</span> + <strong>{% if film.date_watched %}{{ film.date_watched }}{% else %}Not set{% endif %}</strong> + </div> + <div> + <span class="summary-label">Stars</span> + <strong>{% if film.stars %}{% for _ in range(film.stars) %}✦{% endfor %}{% else %}Unstarred{% endif %}</strong> + </div> </div> - </article> - <article class="detail-panel"> - <p class="eyebrow">Production</p> - <div class="detail-meta"> - {% if film.genre %}<span>{{ film.genre }}</span>{% endif %} - {% if film.country %}<span>{{ film.country }}</span>{% endif %} - {% if film.language %}<span>{{ film.language }}</span>{% endif %} - {% if film.year %}<span>{{ film.year }}</span>{% endif %} - {% if film.tmdb_id %}<span>TMDB {{ film.tmdb_id }}</span>{% endif %} - </div> - </article> - </section> + <article class="detail-panel"> + <p class="eyebrow">Watch log</p> + <div class="fact-list"> + {% if film.date_watched %} + <div class="fact-row"> + <span class="fact-label">Watched</span> + <span class="fact-value">{{ film.date_watched }}</span> + </div> + {% endif %} + {% if film.runtime %} + <div class="fact-row"> + <span class="fact-label">Runtime</span> + <span class="fact-value">{{ film.runtime }} min</span> + </div> + {% endif %} + {% if film.rewatch %} + <div class="fact-row"> + <span class="fact-label">Rewatch</span> + <span class="fact-value">Yes{% if film.rewatch_count %} · #{{ film.rewatch_count }}{% endif %}</span> + </div> + {% endif %} + {% if film.watched_with %} + <div class="fact-row"> + <span class="fact-label">With</span> + <span class="fact-value">{{ film.watched_with }}</span> + </div> + {% endif %} + {% if film.context %} + <div class="fact-row"> + <span class="fact-label">Context</span> + <span class="fact-value">{{ film.context }}</span> + </div> + {% endif %} + {% if film.how_found %} + <div class="fact-row"> + <span class="fact-label">Found</span> + <span class="fact-value">{{ film.how_found }}</span> + </div> + {% endif %} + </div> + </article> - {% if rewatch_history|length > 1 %} - <section class="detail-panel"> - <p class="eyebrow">Rewatches</p> - <div class="rewatch-list"> - {% for entry in rewatch_history %} - <div class="rewatch-row"> - <div> - <p class="rewatch-meta">{{ entry.date_watched }}</p> - <p class="rewatch-rating"> - <span class="rewatch-stars">{% for _ in range(entry.stars) %}✦{% endfor %}</span> - {% if entry.watched_with %}<span class="rewatch-companion">with {{ entry.watched_with }}</span>{% endif %} - </p> + <article class="detail-panel"> + <p class="eyebrow">Production</p> + <div class="fact-list"> + {% if film.genre %} + <div class="fact-row"> + <span class="fact-label">Genre</span> + <span class="fact-value">{{ film.genre }}</span> + </div> + {% endif %} + {% if film.country %} + <div class="fact-row"> + <span class="fact-label">Country</span> + <span class="fact-value">{{ film.country }}</span> + </div> + {% endif %} + {% if film.language %} + <div class="fact-row"> + <span class="fact-label">Lang</span> + <span class="fact-value">{{ film.language }}</span> + </div> + {% endif %} + {% if film.year %} + <div class="fact-row"> + <span class="fact-label">Year</span> + <span class="fact-value">{{ film.year }}</span> + </div> + {% endif %} + {% if film.tmdb_id %} + <div class="fact-row"> + <span class="fact-label">TMDB</span> + <span class="fact-value">{{ film.tmdb_id }}</span> + </div> + {% endif %} </div> - {% if not loop.first %} - <span class="rewatch-delta"> - {% set prev_entry = rewatch_history[loop.index - 2] %} - {% if prev_entry.date_watched %} - {% set days = (entry.date_watched - prev_entry.date_watched).days %} - {{ days }}d - {% if entry.stars != prev_entry.stars %} - <span class="rewatch-delta-rating">({{ prev_entry.stars }}→{{ entry.stars }})</span> + </article> + </aside> + + <div class="detail-main"> + {% if tmdb_context %} + <section class="detail-panel detail-panel-feature"> + <p class="eyebrow">Summary</p> + {% if tmdb_context.tagline %} + <p class="detail-tagline">{{ tmdb_context.tagline }}</p> + {% endif %} + {% if tmdb_context.overview %} + <p class="detail-overview">{{ tmdb_context.overview }}</p> {% endif %} + {% if tmdb_context.cast %} + <div class="detail-cast"> + <span class="summary-label">Cast</span> + <p>{{ tmdb_context.cast|join(", ") }}</p> + </div> {% endif %} - </span> + </section> + {% endif %} + + <section class="detail-panel detail-panel-feature"> + <p class="eyebrow">Notes</p> + {% if film.notes %} + <div class="notes-body">{{ film.notes }}</div> + {% else %} + <p class="muted">No notes saved.</p> {% endif %} - </div> - {% endfor %} - </div> - </section> - {% endif %} + </section> - {% if tmdb_context %} - <section class="detail-panel"> - <p class="eyebrow">Summary</p> - {% if tmdb_context.tagline %} - <p class="detail-tagline">{{ tmdb_context.tagline }}</p> - {% endif %} - {% if tmdb_context.overview %} - <p class="detail-overview">{{ tmdb_context.overview }}</p> - {% endif %} - {% if tmdb_context.cast %} - <div class="detail-cast"> - <span class="summary-label">Cast</span> - <p>{{ tmdb_context.cast|join(", ") }}</p> + {% if rewatch_history|length > 1 %} + <section class="detail-panel"> + <p class="eyebrow">Rewatches</p> + <div class="rewatch-list"> + {% for entry in rewatch_history %} + <div class="rewatch-row"> + <div> + <p class="rewatch-meta">{{ entry.date_watched }}</p> + <p class="rewatch-rating"> + <span class="rewatch-stars">{% for _ in range(entry.stars) %}✦{% endfor %}</span> + {% if entry.watched_with %}<span class="rewatch-companion">with {{ entry.watched_with }}</span>{% endif %} + </p> + </div> + {% if not loop.first %} + <span class="rewatch-delta"> + {% set prev_entry = rewatch_history[loop.index - 2] %} + {% if prev_entry.date_watched %} + {% set days = (entry.date_watched - prev_entry.date_watched).days %} + {{ days }}d + {% if entry.stars != prev_entry.stars %} + <span class="rewatch-delta-rating">({{ prev_entry.stars }}→{{ entry.stars }})</span> + {% endif %} + {% endif %} + </span> + {% endif %} + </div> + {% endfor %} </div> + </section> {% endif %} - </section> - {% endif %} - - <section class="detail-panel"> - <p class="eyebrow">Notes</p> - {% if film.notes %} - <div class="notes-body">{{ film.notes }}</div> - {% else %} - <p class="muted">No notes saved.</p> - {% endif %} + </div> </section> - - <div class="detail-actions"> - <a class="secondary-button" href="/films/{{ film.id }}/edit">Edit</a> - {% if film.shelf == 'queue' %} - <a class="button-link" href="/films/{{ film.id }}/edit?shelf=diary">Mark watched</a> - <form method="post" action="/films/{{ film.id }}/shelf/abandoned"> - <button class="secondary-button" type="submit">Abandon</button> - </form> - {% elif film.shelf == 'diary' %} - <form method="post" action="/films/{{ film.id }}/shelf/queue"> - <button class="secondary-button" type="submit">Move to queue</button> - </form> - <form method="post" action="/films/{{ film.id }}/shelf/abandoned"> - <button class="secondary-button" type="submit">Mark abandoned</button> - </form> - {% else %} - <a class="button-link" href="/films/{{ film.id }}/edit?shelf=diary">Mark watched</a> - <form method="post" action="/films/{{ film.id }}/shelf/queue"> - <button class="secondary-button" type="submit">Move to queue</button> - </form> - {% endif %} - <form method="post" action="/films/{{ film.id }}/delete"> - <button class="danger-button" type="submit">Delete</button> - </form> - </div> </section> </article> + {% if film.tmdb_id %} + <div id="poster-picker-modal" class="poster-picker-modal" hidden> + <div class="poster-picker-backdrop" data-close-poster-picker></div> + <div class="poster-picker-dialog" role="dialog" aria-modal="true" aria-labelledby="poster-picker-title"> + <div class="poster-picker-head"> + <div> + <p class="eyebrow">Poster Archive</p> + <h2 id="poster-picker-title">Choose a poster for {{ film.title }}</h2> + </div> + <button type="button" class="secondary-button poster-picker-close" data-close-poster-picker>Close</button> + </div> + <p id="poster-picker-status" class="poster-picker-status">Loading posters…</p> + <div id="poster-grid" class="poster-grid"></div> + </div> + </div> + {% endif %} + {% if film.tmdb_id %} <script> (function () { const btn = document.getElementById("change-poster-btn"); - const picker = document.getElementById("poster-picker"); + const modal = document.getElementById("poster-picker-modal"); const grid = document.getElementById("poster-grid"); const status = document.getElementById("poster-picker-status"); const filmId = btn.dataset.filmId; const tmdbId = btn.dataset.tmdbId; + const closeControls = document.querySelectorAll("[data-close-poster-picker]"); + const posterImg = document.querySelector(".poster-large img"); let loaded = false; + let lastFocused = null; + let currentPosterUrl = posterImg ? posterImg.src : ""; - btn.addEventListener("click", async () => { - if (picker.style.display === "none") { - picker.style.display = "block"; - btn.textContent = "Close"; + function syncCurrentPosterOption(url) { + grid.querySelectorAll(".poster-option").forEach((button) => { + const isCurrent = button.dataset.url === url; + button.classList.toggle("is-current", isCurrent); + const badge = button.querySelector(".poster-option-badge"); + if (badge) { + badge.hidden = !isCurrent; + } + }); + } + + function openPicker() { + lastFocused = document.activeElement; + modal.hidden = false; + document.body.classList.add("modal-open"); + } + + function closePicker() { + modal.hidden = true; + document.body.classList.remove("modal-open"); + btn.textContent = "Change Poster"; + if (lastFocused instanceof HTMLElement) { + lastFocused.focus(); } else { - picker.style.display = "none"; - btn.textContent = "Change Poster"; - return; + btn.focus(); } + } + + btn.addEventListener("click", async () => { + openPicker(); if (loaded) return; status.textContent = "Loading posters…"; - status.style.display = "block"; + status.hidden = false; try { const resp = await fetch(`/tmdb/posters?tmdb_id=${tmdbId}`); if (!resp.ok) throw new Error("fetch failed"); const data = await resp.json(); - status.style.display = "none"; + status.hidden = true; if (!data.posters.length) { status.textContent = "No posters found."; - status.style.display = "block"; + status.hidden = false; return; } grid.innerHTML = data.posters.map((url) => ` - <button type="button" class="poster-option" data-url="${url}" style="padding: 0; border: 2px solid transparent; border-radius: 6px; overflow: hidden; cursor: pointer; background: none;"> - <img src="${url}" alt="Poster option" loading="lazy" style="width: 100%; display: block; aspect-ratio: 2/3; object-fit: cover;"> + <button type="button" class="poster-option" data-url="${url}"> + <img src="${url}" alt="Poster option" loading="lazy"> + <span class="poster-option-badge" ${currentPosterUrl === url ? "" : "hidden"}>Current</span> </button> `).join(""); + syncCurrentPosterOption(currentPosterUrl); + grid.querySelectorAll(".poster-option").forEach((optBtn) => { optBtn.addEventListener("click", async () => { const url = optBtn.dataset.url; - grid.querySelectorAll(".poster-option").forEach((b) => b.style.borderColor = "transparent"); - optBtn.style.borderColor = "var(--accent)"; - try { const saveResp = await fetch(`/films/${filmId}/poster`, { method: "POST", @@ -255,10 +354,10 @@ }); if (!saveResp.ok) return; - const posterImg = document.querySelector(".poster-large img"); if (posterImg) posterImg.src = url; - picker.style.display = "none"; - btn.textContent = "Change Poster"; + currentPosterUrl = url; + syncCurrentPosterOption(url); + closePicker(); } catch (err) { console.error("Failed to save poster", err); } @@ -271,6 +370,16 @@ console.error(err); } }); + + closeControls.forEach((control) => { + control.addEventListener("click", closePicker); + }); + + document.addEventListener("keydown", (event) => { + if (event.key === "Escape" && !modal.hidden) { + closePicker(); + } + }); })(); </script> {% endif %} diff --git a/templates/form.html b/templates/form.html index cd29e67..f7bbbf1 100644 --- a/templates/form.html +++ b/templates/form.html @@ -13,138 +13,168 @@ {% set current_stars = film.stars if film and film.stars else 0 %} <input type="hidden" id="stars" name="stars" value="{{ current_stars }}"> - <div class="form-heading"> - <p class="eyebrow">Diary Entry</p> - <div class="form-heading-row"> - <h1>{{ page_title }}</h1> - <div class="star-control" role="group" aria-label="Rate film" data-form-stars data-current-stars="{{ current_stars }}"> - {% for value in range(1, 4) %} - <button - type="button" - class="star-button {% if current_stars >= value %}is-active{% endif %}" - data-stars="{{ value }}" - aria-label="{{ value }} star{% if value > 1 %}s{% endif %}" - aria-pressed="{% if current_stars >= value %}true{% else %}false{% endif %}" - >✦</button> - {% endfor %} + <section class="form-hero"> + <div class="form-heading"> + <p class="eyebrow">Diary Entry</p> + <div class="form-heading-row"> + <div> + <h1>{{ page_title }}</h1> + <p class="form-intro">Search TMDB first, then tune the entry like a ledger instead of filling a blank spreadsheet.</p> + </div> + <div class="star-control" role="group" aria-label="Rate film" data-form-stars data-current-stars="{{ current_stars }}"> + {% for value in range(1, 4) %} + <button + type="button" + class="star-button {% if current_stars >= value %}is-active{% endif %}" + data-stars="{{ value }}" + aria-label="{{ value }} star{% if value > 1 %}s{% endif %}" + aria-pressed="{% if current_stars >= value %}true{% else %}false{% endif %}" + >✦</button> + {% endfor %} + </div> </div> </div> - </div> + </section> - <div class="tmdb-panel"> - <label for="tmdb-query">TMDB title search</label> + <section class="form-panel tmdb-panel"> + <div class="form-panel-head"> + <p class="eyebrow">Lookup</p> + <h2>TMDB title search</h2> + </div> <div class="search-row"> <input id="tmdb-query" type="search" autocomplete="off" placeholder="Search by title"> <button id="tmdb-search" type="button">Search</button> </div> <div id="tmdb-results" class="tmdb-results" aria-live="polite"></div> <div id="duplicate-notice" class="notice" hidden></div> - </div> + </section> - <div class="form-grid"> - <div class="field span-2"> - <label for="title">Display title</label> - <input id="title" name="title" required value="{{ film.title if film else '' }}"> + <section class="form-panel"> + <div class="form-panel-head"> + <p class="eyebrow">Identity</p> + <h2>Core film record</h2> </div> + <div class="form-grid"> + <div class="field span-2"> + <label for="title">Display title</label> + <input id="title" name="title" required value="{{ film.title if film else '' }}"> + </div> - <div class="field span-2"> - <label for="original_title">Original title</label> - <input id="original_title" name="original_title" value="{{ film.original_title if film and film.original_title else '' }}"> - </div> + <div class="field span-2"> + <label for="original_title">Original title</label> + <input id="original_title" name="original_title" value="{{ film.original_title if film and film.original_title else '' }}"> + </div> - <div class="field"> - <label for="director">Director</label> - <input id="director" name="director" value="{{ film.director if film and film.director else '' }}"> - </div> + <div class="field"> + <label for="director">Director</label> + <input id="director" name="director" value="{{ film.director if film and film.director else '' }}"> + </div> - <div class="field"> - <label for="year">Year</label> - <input id="year" name="year" inputmode="numeric" value="{{ film.year if film and film.year else '' }}"> - </div> + <div class="field"> + <label for="year">Year</label> + <input id="year" name="year" inputmode="numeric" value="{{ film.year if film and film.year else '' }}"> + </div> - <div class="field"> - <label for="country">Country</label> - <input id="country" name="country" value="{{ film.country if film and film.country else '' }}"> - </div> + <div class="field"> + <label for="country">Country</label> + <input id="country" name="country" value="{{ film.country if film and film.country else '' }}"> + </div> - <div class="field"> - <label for="genre">Genre</label> - <input id="genre" name="genre" value="{{ film.genre if film and film.genre else '' }}"> - </div> + <div class="field"> + <label for="genre">Genre</label> + <input id="genre" name="genre" value="{{ film.genre if film and film.genre else '' }}"> + </div> - <div class="field"> - <label for="language">Language</label> - <input id="language" name="language" value="{{ film.language if film and film.language else '' }}"> - </div> + <div class="field"> + <label for="language">Language</label> + <input id="language" name="language" value="{{ film.language if film and film.language else '' }}"> + </div> - <div class="field"> - <label for="runtime">Runtime</label> - <input id="runtime" name="runtime" type="number" min="0" inputmode="numeric" value="{{ film.runtime if film and film.runtime is not none else '' }}"> + <div class="field"> + <label for="runtime">Runtime</label> + <input id="runtime" name="runtime" type="number" min="0" inputmode="numeric" value="{{ film.runtime if film and film.runtime is not none else '' }}"> + </div> </div> + </section> - <div class="field"> - <label for="date_watched">Watched</label> - <input id="date_watched" name="date_watched" type="date" value="{{ film.date_watched if film and film.date_watched else '' }}" - {% if not (film and film.date_watched) %}data-default-today{% endif %}> + <section class="form-panel"> + <div class="form-panel-head"> + <p class="eyebrow">Log</p> + <h2>Watch context</h2> </div> + <div class="form-grid"> + <div class="field"> + <label for="date_watched">Watched</label> + <input id="date_watched" name="date_watched" type="date" value="{{ film.date_watched if film and film.date_watched else '' }}" + {% if not (film and film.date_watched) %}data-default-today{% endif %}> + </div> - <div class="field"> - <label for="shelf">Shelf</label> - {% set current_shelf = shelf_override if shelf_override else (film.shelf if film and film.shelf else 'diary') %} - <select id="shelf" name="shelf"> - <option value="diary" {% if current_shelf == 'diary' %}selected{% endif %}>Diary</option> - <option value="queue" {% if current_shelf == 'queue' %}selected{% endif %}>Queue</option> - <option value="abandoned" {% if current_shelf == 'abandoned' %}selected{% endif %}>Abandoned</option> - </select> - </div> + <div class="field"> + <label for="shelf">Shelf</label> + {% set current_shelf = shelf_override if shelf_override else (film.shelf if film and film.shelf else 'diary') %} + <select id="shelf" name="shelf"> + <option value="diary" {% if current_shelf == 'diary' %}selected{% endif %}>Diary</option> + <option value="queue" {% if current_shelf == 'queue' %}selected{% endif %}>Queue</option> + <option value="abandoned" {% if current_shelf == 'abandoned' %}selected{% endif %}>Abandoned</option> + </select> + </div> - <div class="field checkbox-field"> - <label class="check-label" for="rewatch"> - <input id="rewatch" name="rewatch" type="checkbox" value="on" {% if film and film.rewatch %}checked{% endif %}> - Rewatch - </label> - </div> + <div class="field checkbox-field"> + <label class="check-label" for="rewatch"> + <input id="rewatch" name="rewatch" type="checkbox" value="on" {% if film and film.rewatch %}checked{% endif %}> + Rewatch + </label> + </div> - <div class="field"> - <label for="rewatch_count">Rewatch count</label> - <input id="rewatch_count" name="rewatch_count" type="number" min="0" inputmode="numeric" value="{{ film.rewatch_count if film and film.rewatch_count is not none else 0 }}"> - </div> + <div class="field"> + <label for="rewatch_count">Rewatch count</label> + <input id="rewatch_count" name="rewatch_count" type="number" min="0" inputmode="numeric" value="{{ film.rewatch_count if film and film.rewatch_count is not none else 0 }}"> + </div> - <div class="field span-2"> - <label for="watched_with">Watched with</label> - <input id="watched_with" name="watched_with" placeholder="Solo, Maya, Film club" value="{{ film.watched_with if film and film.watched_with else '' }}"> - </div> + <div class="field span-2"> + <label for="watched_with">Watched with</label> + <input id="watched_with" name="watched_with" placeholder="Solo, Maya, Film club" value="{{ film.watched_with if film and film.watched_with else '' }}"> + </div> - <div class="field"> - <label for="how_found">How found</label> - <input id="how_found" name="how_found" placeholder="Recommendation, festival, streaming queue" value="{{ film.how_found if film and film.how_found else '' }}"> - </div> + <div class="field"> + <label for="how_found">How found</label> + <input id="how_found" name="how_found" placeholder="Recommendation, festival, streaming queue" value="{{ film.how_found if film and film.how_found else '' }}"> + </div> - <div class="field"> - <label for="context">Context</label> - <input id="context" name="context" placeholder="Criterion, Festival, Kiarostami deep-dive" value="{{ film.context if film and film.context else '' }}"> + <div class="field"> + <label for="context">Context</label> + <input id="context" name="context" placeholder="Criterion, Festival, Kiarostami deep-dive" value="{{ film.context if film and film.context else '' }}"> + </div> </div> + </section> - <div class="field span-2"> - <label for="poster_url">Poster URL</label> - <input id="poster_url" name="poster_url" value="{{ film.poster_url if film and film.poster_url else '' }}"> + <section class="form-panel"> + <div class="form-panel-head"> + <p class="eyebrow">Archive</p> + <h2>Poster and notes</h2> </div> + <div class="form-grid form-grid-archive"> + <div class="field span-2"> + <label for="poster_url">Poster URL</label> + <input id="poster_url" name="poster_url" value="{{ film.poster_url if film and film.poster_url else '' }}"> + </div> - <div class="poster-preview-field"> - <div class="poster-frame poster-preview"> - {% if film and film.poster_url %} - <img id="poster-preview" src="{{ film.poster_url }}" alt="Poster preview"> - {% else %} - <img id="poster-preview" alt="Poster preview"> - {% endif %} + <div class="poster-preview-field"> + <div class="poster-frame poster-preview"> + {% if film and film.poster_url %} + <img id="poster-preview" src="{{ film.poster_url }}" alt="Poster preview"> + {% else %} + <img id="poster-preview" alt="Poster preview"> + {% endif %} + </div> </div> - </div> - <div class="field notes-field"> - <label for="notes">Notes</label> - <textarea id="notes" name="notes" rows="10">{{ film.notes if film and film.notes else '' }}</textarea> + <div class="field notes-field"> + <label for="notes">Notes</label> + <textarea id="notes" name="notes" rows="10">{{ film.notes if film and film.notes else '' }}</textarea> + </div> </div> - </div> + </section> <div class="form-actions"> <a href="{{ '/films/' ~ film.id if film and film.id else '/' }}">Cancel</a> diff --git a/templates/index.html b/templates/index.html index dc76e5d..7e74c07 100644 --- a/templates/index.html +++ b/templates/index.html @@ -3,35 +3,44 @@ {% block title %}{{ shelf_meta.title }} · Lumière{% endblock %} {% block content %} - <section class="page-heading"> - <p class="eyebrow">{{ shelf_meta.eyebrow }}</p> - <div class="page-heading-row"> + <section class="shelf-hero"> + <div class="shelf-hero-copy"> + <p class="eyebrow">{{ shelf_meta.eyebrow }}</p> <h1>{{ shelf_meta.title }}</h1> + <p class="shelf-hero-text">{{ shelf_meta.empty_text }}</p> + </div> + <div class="shelf-hero-meta"> + <div class="shelf-stat"> + <span class="summary-label">Entries</span> + <strong>{{ total_films or 0 }}</strong> + </div> {% if active_shelf == 'queue' %} <a class="button-link" href="/queue/random">Surprise me</a> {% endif %} </div> </section> - <div class="search-row" style="margin-bottom: 20px;"> - <input - type="search" - id="film-search" - placeholder="Search by title or director…" - autocomplete="off" - > - <select id="film-sort"> - <option value="">Default order</option> - <option value="date_watched_desc">Date watched — newest</option> - <option value="date_watched_asc">Date watched — oldest</option> - <option value="title_asc">Title — A → Z</option> - <option value="title_desc">Title — Z → A</option> - <option value="year_desc">Year — newest</option> - <option value="year_asc">Year — oldest</option> - <option value="stars_desc">Stars — highest</option> - <option value="stars_asc">Stars — lowest</option> - </select> - </div> + <section class="feed-toolbar"> + <div class="search-row"> + <input + type="search" + id="film-search" + placeholder="Search by title or director…" + autocomplete="off" + > + <select id="film-sort"> + <option value="">Default order</option> + <option value="date_watched_desc">Date watched — newest</option> + <option value="date_watched_asc">Date watched — oldest</option> + <option value="title_asc">Title — A → Z</option> + <option value="title_desc">Title — Z → A</option> + <option value="year_desc">Year — newest</option> + <option value="year_asc">Year — oldest</option> + <option value="stars_desc">Stars — highest</option> + <option value="stars_asc">Stars — lowest</option> + </select> + </div> + </section> {% if imported is not none %} <div class="notice">{{ imported }} entries imported.</div> @@ -62,10 +71,14 @@ {% if active_shelf == 'diary' and grouped_films %} {% for group in grouped_films %} <div class="month-group" data-month="{{ group.month }}"> - <p class="month-label">{{ group.month }}</p> - {% for film in group.films %} - {% include "_film_card.html" %} - {% endfor %} + <div class="month-rail"> + <p class="month-label">{{ group.month }}</p> + </div> + <div class="month-stack"> + {% for film in group.films %} + {% include "_film_card.html" %} + {% endfor %} + </div> </div> {% endfor %} {% else %} diff --git a/templates/profile.html b/templates/profile.html index 7c847f4..bbf70a2 100644 --- a/templates/profile.html +++ b/templates/profile.html @@ -3,199 +3,175 @@ <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> - <title>Tyler's Film Diary - Lumière</title> + <title>Tyler's Film Diary · Lumière</title> <link rel="icon" href="/static/favicon.svg" type="image/svg+xml"> <link rel="stylesheet" href="/static/styles.css" /> </head> - <body> - <div class="shell"> - <header class="topbar"> - <a class="brand" href="/">Lumière</a> - <nav class="nav-actions" aria-label="Primary"> - <a href="/tyler">Profile</a> + <body class="public-profile-page"> + <div class="public-shell"> + <header class="public-topbar"> + <a class="brand" href="/tyler">Lumière</a> + <nav class="public-nav" aria-label="Primary"> + <a class="is-active" href="/tyler">Profile</a> <a href="/about">About</a> + <a href="/login">Private Log In</a> </nav> </header> - <main> - <div style="max-width: 1200px; margin: 0 auto; padding: 40px 20px;"> - <!-- Hero Section --> - <div style="margin-bottom: 40px; padding-bottom: 24px; border-bottom: 1px solid var(--line);"> - <h1 style="margin: 0 0 8px 0; font-size: 40px; color: var(--text);">Tyler's Film Diary</h1> - <p style="margin: 0 0 24px 0; color: var(--muted); font-size: 18px;">A curated collection of films watched and loved</p> - - <!-- Tabs --> - <div style="display: flex; gap: 24px; border-bottom: 1px solid var(--line); padding-bottom: 16px;"> - <button id="tab-overview" class="profile-tab is-active" data-tab="overview" style="background: none; border: none; color: var(--text); cursor: pointer; font-size: 16px; padding: 0; border-bottom: 2px solid var(--accent);">Overview</button> - <button id="tab-films" class="profile-tab" data-tab="films" style="background: none; border: none; color: var(--muted); cursor: pointer; font-size: 16px; padding: 0; border-bottom: 2px solid transparent;">All Films</button> + <main class="public-main"> + <section class="public-hero"> + <div class="public-hero-copy"> + <p class="eyebrow">Public Ledger</p> + <h1>Tyler's Film Diary</h1> + <p class="public-hero-text">A public cut of the diary: recent watches, recurring directors, and the films that keep returning.</p> + </div> + <div class="public-hero-metrics"> + <div class="public-metric-card"> + <span class="summary-label">Watched</span> + <strong>{{ total_watched }}</strong> + </div> + <div class="public-metric-card"> + <span class="summary-label">Average</span> + <strong>{{ average_stars }}</strong> </div> </div> + </section> - <!-- Overview Tab --> - <div id="panel-overview" class="tab-panel is-active" style="display: block;"> - <!-- Hero Section Stats --> - <div style="margin-bottom: 60px;"> + <section class="public-tabs"> + <button id="tab-overview" class="public-tab is-active" data-tab="overview" type="button">Overview</button> + <button id="tab-films" class="public-tab" data-tab="films" type="button">All Films</button> + </section> - - <!-- Summary Stats --> - <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 24px; margin-top: 32px;"> - <div style="padding: 20px; background: var(--panel); border: 1px solid var(--line); border-radius: 8px;"> - <div style="font-size: 32px; font-weight: bold; color: var(--text);">{{ total_watched }}</div> - <div style="color: var(--muted); margin-top: 4px;">Films Watched</div> + <section id="panel-overview" class="public-panel is-active"> + <div class="public-grid"> + <section class="public-section"> + <div class="public-section-head"> + <p class="eyebrow">Rotation</p> + <h2>Most watched directors</h2> </div> - <div style="padding: 20px; background: var(--panel); border: 1px solid var(--line); border-radius: 8px;"> - <div style="font-size: 32px; font-weight: bold; color: var(--text);"><span class="rating">{% for _ in range(average_stars|int) %}✦{% endfor %}</span> {{ average_stars }}</div> - <div style="color: var(--muted); margin-top: 4px;">Average Rating</div> + <div class="public-list"> + {% for item in most_watched_directors %} + <div class="public-list-row"> + <span>{{ item.director }}</span> + <strong>{{ item.count }}</strong> + </div> + {% endfor %} </div> - </div> - </div> + </section> - <!-- Top Directors --> - {% if most_watched_directors %} - <div style="margin-bottom: 60px;"> - <h2 style="margin: 0 0 24px 0; font-size: 24px; color: var(--text);">Top Directors</h2> - <div style="display: grid; gap: 12px;"> - {% for item in most_watched_directors %} - <div style="padding: 12px 16px; background: var(--panel); border: 1px solid var(--line); border-radius: 6px; display: flex; justify-content: space-between; align-items: center;"> - <span style="color: var(--text);">{{ item.director }}</span> - <span style="color: var(--muted); font-size: 14px;">{{ item.count }} film{{ 's' if item.count > 1 else '' }}</span> + <section class="public-section"> + <div class="public-section-head"> + <p class="eyebrow">Distribution</p> + <h2>Ratings</h2> </div> - {% endfor %} - </div> - </div> - {% endif %} - - <!-- Star Distribution --> - <div style="margin-bottom: 60px;"> - <h2 style="margin: 0 0 24px 0; font-size: 24px; color: var(--text);">Rating Distribution</h2> - <div style="display: grid; gap: 16px;"> - {% for item in star_distribution %} - <div> - <div style="display: flex; justify-content: space-between; margin-bottom: 8px;"> - <span style="color: var(--text);"> - {% if item.stars == 0 %}No rating{% elif item.stars == 1 %}<span class="rating">✦</span>{% elif item.stars == 2 %}<span class="rating">✦✦</span>{% elif item.stars == 3 %}<span class="rating">✦✦✦</span>{% endif %} + <div class="public-bars"> + {% for item in star_distribution %} + <div class="public-bar-row"> + <span> + {% if item.stars == 0 %}Unrated{% else %}{% for _ in range(item.stars) %}✦{% endfor %}{% endif %} </span> - <span style="color: var(--muted);">{{ item.count }}</span> - </div> - <div style="background: var(--panel-soft); height: 24px; border-radius: 4px; overflow: hidden;"> - {% if total_watched > 0 %} - <div style="background: var(--accent); height: 100%; width: {{ (item.count / total_watched * 100) }}%; border-radius: 4px;"></div> - {% endif %} + <div class="public-bar-track"> + {% if total_watched > 0 %} + <div class="public-bar-fill" style="width: {{ (item.count / total_watched * 100) }}%;"></div> + {% endif %} + </div> + <strong>{{ item.count }}</strong> </div> + {% endfor %} </div> - {% endfor %} - </div> - </div> + </section> - <!-- Top Countries --> - {% if films_per_country %} - <div style="margin-bottom: 60px;"> - <h2 style="margin: 0 0 24px 0; font-size: 24px; color: var(--text);">Top Countries</h2> - <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 16px;"> - {% for item in films_per_country %} - <div style="padding: 16px; background: var(--panel); border: 1px solid var(--line); border-radius: 6px; text-align: center;"> - <div style="font-size: 20px; font-weight: bold; color: var(--text);">{{ item.count }}</div> - <div style="color: var(--muted); margin-top: 4px; font-size: 14px;">{{ item.country }}</div> + {% if films_per_country %} + <section class="public-section public-section-wide"> + <div class="public-section-head"> + <p class="eyebrow">Geography</p> + <h2>Most watched countries</h2> </div> - {% endfor %} - </div> - </div> - {% endif %} - - <!-- Recent Films --> - {% if recent_films %} - <div> - <h2 style="margin: 0 0 24px 0; font-size: 24px; color: var(--text);">Recently Watched</h2> - <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 24px;"> - {% for film in recent_films %} - <div style="text-align: center;"> - {% if film.poster_url %} - <img src="{{ film.poster_url }}" alt="{{ film.title }}" loading="lazy" style="width: 100%; border-radius: 8px; margin-bottom: 12px; aspect-ratio: 2/3; object-fit: cover;" /> - {% else %} - <div style="width: 100%; aspect-ratio: 2/3; background: var(--panel); border: 1px solid var(--line); border-radius: 8px; margin-bottom: 12px; display: flex; align-items: center; justify-content: center; color: var(--muted);">No poster</div> - {% endif %} - <h3 style="margin: 0; font-size: 14px; line-height: 1.4; color: var(--text);">{{ film.title }}</h3> - {% if film.stars %} - <div style="color: var(--accent); font-size: 12px; margin-top: 4px; font-weight: 800;"> - <span class="rating">{% for i in range(film.stars) %}✦{% endfor %}</span> + <div class="public-country-grid"> + {% for item in films_per_country %} + <div class="public-country-card"> + <strong>{{ item.count }}</strong> + <span>{{ item.country }}</span> </div> - {% endif %} - {% if film.date_watched %} - <div style="color: var(--muted); font-size: 12px; margin-top: 4px;">{{ film.date_watched[:10] }}</div> - {% endif %} + {% endfor %} </div> - {% endfor %} - </div> - </div> - {% endif %} - </div> - <!-- End Overview Tab --> + </section> + {% endif %} - <!-- All Films Tab --> - <div id="panel-films" class="tab-panel" style="display: none;"> - <div id="films-feed" style="display: grid; gap: 20px;"> - <!-- Films will be loaded here via infinite scroll --> - </div> - <div id="films-sentinel" data-shelf="diary" data-offset="0" data-total="{{ total_watched }}" style="height: 1px; margin: 20px 0;"></div> + {% if recent_films %} + <section class="public-section public-section-wide"> + <div class="public-section-head"> + <p class="eyebrow">Recent</p> + <h2>Recently watched</h2> + </div> + <div class="public-poster-grid"> + {% for film in recent_films %} + <article class="public-poster-card"> + <div class="poster-frame public-poster-frame"> + {% if film.poster_url %} + <img src="{{ film.poster_url }}" alt="{{ film.title }} poster" loading="lazy"> + {% else %} + <span>{{ film.title[:1] }}</span> + {% endif %} + </div> + <h3>{{ film.title }}</h3> + <p>{{ film.year }}{% if film.director %} · {{ film.director }}{% endif %}</p> + {% if film.date_watched %} + <span class="public-poster-meta">{{ film.date_watched[:10] }}</span> + {% endif %} + </article> + {% endfor %} + </div> + </section> + {% endif %} </div> - <!-- End All Films Tab --> - </div> + </section> + + <section id="panel-films" class="public-panel" hidden> + <div id="public-films-feed" class="public-feed"></div> + <div id="public-films-sentinel" data-offset="0" data-total="{{ total_watched }}" class="public-feed-sentinel"></div> + </section> </main> - <footer style="text-align: center; padding: 40px 20px; color: var(--muted); font-size: 12px; border-top: 1px solid var(--line); margin-top: 60px;"> - <p style="margin: 0;">Made with <a href="https://git.tylerhoang.xyz/lumi.git" style="color: var(--accent); text-decoration: none;">Lumière</a></p> + <footer class="public-footer"> + <p>Made with <a href="https://git.tylerhoang.xyz/lumi.git">Lumière</a></p> </footer> </div> <script> - // Tab switching - document.querySelectorAll(".profile-tab").forEach((tab) => { - tab.addEventListener("click", () => { - const tabName = tab.dataset.tab; - - // Update tab buttons - document.querySelectorAll(".profile-tab").forEach((t) => { - t.classList.remove("is-active"); - t.style.color = "var(--muted)"; - t.style.borderBottomColor = "transparent"; - }); - tab.classList.add("is-active"); - tab.style.color = "var(--text)"; - tab.style.borderBottomColor = "var(--accent)"; + const tabs = document.querySelectorAll(".public-tab"); + const panels = document.querySelectorAll(".public-panel"); + let filmsLoaded = false; - // Update panels - document.querySelectorAll(".tab-panel").forEach((panel) => { - panel.style.display = "none"; - }); - document.getElementById(`panel-${tabName}`).style.display = "block"; - - // Load films if switching to all films tab - if (tabName === "films" && !tab.dataset.loaded) { - loadInitialFilms(); - tab.dataset.loaded = "true"; - } + function setActiveTab(tabName) { + tabs.forEach((tab) => { + const active = tab.dataset.tab === tabName; + tab.classList.toggle("is-active", active); }); - }); + panels.forEach((panel) => { + const active = panel.id === `panel-${tabName}`; + panel.hidden = !active; + }); + } - // Load all films with infinite scroll - function loadInitialFilms() { - const sentinel = document.querySelector("#films-sentinel"); - const feed = document.querySelector("#films-feed"); + async function loadPublicFilms() { + if (filmsLoaded) return; + filmsLoaded = true; + + const sentinel = document.querySelector("#public-films-sentinel"); + const feed = document.querySelector("#public-films-feed"); let loading = false; const loadMore = async () => { if (loading) return; loading = true; - - const offset = Number(sentinel.dataset.offset); - const total = Number(sentinel.dataset.total); + const offset = Number(sentinel.dataset.offset || 0); try { - const response = await fetch(`/films/partial?shelf=diary&offset=${offset}&limit=20`); + const response = await fetch(`/tyler/films/partial?offset=${offset}&limit=20`); if (!response.ok) return; - - const hasMore = response.headers.get("X-Has-More") === "true"; const html = await response.text(); + const hasMore = response.headers.get("X-Has-More") === "true"; if (html.trim()) { feed.insertAdjacentHTML("beforeend", html); @@ -208,7 +184,7 @@ sentinel.dataset.offset = String(offset + 20); } } catch (error) { - console.error("Failed to load films", error); + console.error("Failed to load public films", error); } finally { loading = false; } @@ -223,8 +199,18 @@ }, { rootMargin: "100px" }); observer.observe(sentinel); - loadMore(); // Load first batch immediately + loadMore(); } + + tabs.forEach((tab) => { + tab.addEventListener("click", () => { + const tabName = tab.dataset.tab; + setActiveTab(tabName); + if (tabName === "films") { + loadPublicFilms(); + } + }); + }); </script> </body> </html> |
