summaryrefslogtreecommitdiff
path: root/static
diff options
context:
space:
mode:
Diffstat (limited to 'static')
-rw-r--r--static/app.js131
-rw-r--r--static/styles.css789
2 files changed, 920 insertions, 0 deletions
diff --git a/static/app.js b/static/app.js
new file mode 100644
index 0000000..01b1d79
--- /dev/null
+++ b/static/app.js
@@ -0,0 +1,131 @@
+const tmdbQuery = document.querySelector("#tmdb-query");
+const tmdbButton = document.querySelector("#tmdb-search");
+const tmdbResults = document.querySelector("#tmdb-results");
+
+const setValue = (selector, value) => {
+ const element = document.querySelector(selector);
+ if (element && value !== null && value !== undefined) {
+ element.value = value;
+ }
+};
+
+const setPoster = (url) => {
+ setValue("#poster_url", url || "");
+ const preview = document.querySelector("#poster-preview");
+ if (!preview) return;
+
+ if (url) {
+ preview.src = url;
+ preview.classList.add("is-visible");
+ } else {
+ preview.removeAttribute("src");
+ preview.classList.remove("is-visible");
+ }
+};
+
+const clearResults = () => {
+ if (tmdbResults) {
+ tmdbResults.replaceChildren();
+ }
+};
+
+const renderMessage = (message) => {
+ clearResults();
+ const node = document.createElement("p");
+ node.className = "tmdb-message";
+ node.textContent = message;
+ tmdbResults.appendChild(node);
+};
+
+const applyResult = (film) => {
+ setValue("#title", film.title || "");
+ setValue("#original_title", film.original_title || "");
+ setValue("#director", film.director || "");
+ setValue("#year", film.year || "");
+ setValue("#country", film.country || "");
+ setValue("#language", film.language || "");
+ setValue("#runtime", film.runtime || "");
+ setValue("#tmdb_id", film.tmdb_id || "");
+ setPoster(film.poster_url);
+ clearResults();
+};
+
+const renderResults = (films) => {
+ clearResults();
+
+ if (!films.length) {
+ renderMessage("No matches found.");
+ return;
+ }
+
+ films.forEach((film) => {
+ const button = document.createElement("button");
+ button.type = "button";
+ button.className = "tmdb-result";
+ button.addEventListener("click", () => applyResult(film));
+
+ if (film.poster_url) {
+ const image = document.createElement("img");
+ image.src = film.poster_url;
+ image.alt = "";
+ button.appendChild(image);
+ }
+
+ const text = document.createElement("span");
+ const title = document.createElement("strong");
+ title.textContent = film.year ? `${film.title} (${film.year})` : film.title;
+ text.appendChild(title);
+
+ if (film.director) {
+ const director = document.createElement("small");
+ director.textContent = film.director;
+ text.appendChild(director);
+ }
+
+ button.appendChild(text);
+ tmdbResults.appendChild(button);
+ });
+};
+
+const searchTmdb = async () => {
+ if (!tmdbQuery || !tmdbResults) return;
+
+ const query = tmdbQuery.value.trim();
+ if (query.length < 2) {
+ renderMessage("Enter at least two characters.");
+ return;
+ }
+
+ renderMessage("Searching...");
+
+ try {
+ const response = await fetch(`/tmdb/search?q=${encodeURIComponent(query)}`);
+ const data = await response.json();
+ if (!response.ok) {
+ renderMessage(data.error || "Search failed.");
+ return;
+ }
+ renderResults(data.results || []);
+ } catch (error) {
+ renderMessage("Search failed.");
+ }
+};
+
+if (tmdbButton && tmdbQuery) {
+ tmdbButton.addEventListener("click", searchTmdb);
+ tmdbQuery.addEventListener("keydown", (event) => {
+ if (event.key === "Enter") {
+ event.preventDefault();
+ searchTmdb();
+ }
+ });
+}
+
+document.querySelectorAll("form[data-confirm]").forEach((form) => {
+ form.addEventListener("submit", (event) => {
+ const message = form.dataset.confirm;
+ if (message && !window.confirm(message)) {
+ event.preventDefault();
+ }
+ });
+});
diff --git a/static/styles.css b/static/styles.css
new file mode 100644
index 0000000..f7c6fbc
--- /dev/null
+++ b/static/styles.css
@@ -0,0 +1,789 @@
+@import url('https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,400;0,500;0,600;1,400;1,500&display=swap');
+:root {
+ color-scheme: dark;
+ --bg: #0b0b0a;
+ --panel: #171613;
+ --panel-soft: #201f1b;
+ --line: #312f28;
+ --text: #f4efe6;
+ --muted: #a9a197;
+ --subtle: #756f66;
+ --accent: #f0b84d;
+ --accent-strong: #ffcf73;
+ --green: #79a889;
+ --danger: #df6e62;
+ --shadow: 0 20px 70px rgba(0, 0, 0, 0.35);
+}
+
+* {
+ box-sizing: border-box;
+}
+
+body {
+ margin: 0;
+ min-height: 100vh;
+ background: var(--bg);
+ color: var(--text);
+ font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
+ line-height: 1.5;
+}
+
+a {
+ color: inherit;
+ text-decoration: none;
+}
+
+img {
+ display: block;
+ max-width: 100%;
+}
+
+button,
+input,
+select,
+textarea {
+ font: inherit;
+}
+
+.shell {
+ width: min(1120px, calc(100% - 32px));
+ margin: 0 auto;
+}
+
+.topbar {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 18px;
+ padding: 24px 0;
+ border-bottom: 1px solid var(--line);
+}
+
+.brand {
+ font-family: 'Cormorant Garamond', Georgia, serif;
+ font-size: 1.55rem;
+ color: var(--accent);
+}
+
+.nav-actions,
+.detail-actions,
+.form-actions,
+.search-row {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+}
+
+.nav-actions a,
+.form-actions a {
+ color: var(--muted);
+}
+
+.nav-actions a.is-active {
+ color: var(--text);
+}
+
+.nav-actions a.button-link {
+ color: #0e0a04;
+}
+
+.button-link,
+button {
+ border: 1px solid var(--accent);
+ border-radius: 6px;
+ background: var(--accent);
+ color: #0e0a04;
+ padding: 10px 14px;
+ cursor: pointer;
+ font-weight: 800;
+}
+
+.button-link:hover,
+button:hover {
+ background: var(--accent-strong);
+}
+
+.danger-button {
+ border-color: rgba(223, 110, 98, 0.5);
+ background: transparent;
+ color: var(--danger);
+}
+
+.danger-button:hover {
+ background: rgba(223, 110, 98, 0.12);
+}
+
+.secondary-button,
+.small-button {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ border: 1px solid var(--line);
+ border-radius: 6px;
+ background: transparent;
+ color: var(--muted);
+ font-weight: 700;
+}
+
+.secondary-button {
+ min-height: 44px;
+ padding: 10px 14px;
+}
+
+.small-button {
+ min-height: 32px;
+ padding: 6px 10px;
+ font-size: 0.84rem;
+}
+
+.secondary-button:hover,
+.small-button:hover {
+ border-color: var(--accent);
+ background: var(--panel-soft);
+ color: var(--text);
+}
+
+.page-heading,
+.form-heading {
+ padding: 48px 0 26px;
+}
+
+.page-heading-row,
+.stats-panel-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 16px;
+}
+
+.eyebrow {
+ margin: 0 0 8px;
+ color: var(--green);
+ font-size: 0.78rem;
+ font-weight: 800;
+ text-transform: uppercase;
+}
+
+
+h1 {
+ margin-bottom: 0;
+ font-family: 'Cormorant Garamond', Georgia, serif;
+ font-size: clamp(2.2rem, 5vw, 4rem);
+ font-weight: 500;
+ line-height: 1.05;
+}
+
+h2 {
+ margin-bottom: 4px;
+ font-size: 1.1rem;
+ line-height: 1.2;
+}
+
+.muted,
+.subtitle {
+ color: var(--muted);
+}
+
+.inline-link {
+ color: var(--text);
+ text-decoration: underline;
+ text-decoration-color: rgba(240, 184, 77, 0.45);
+ text-underline-offset: 0.14em;
+}
+
+.inline-link:hover {
+ color: var(--accent-strong);
+}
+
+.original-title {
+ margin-bottom: 8px;
+ color: var(--subtle);
+ font-family: Georgia, "Times New Roman", serif;
+ font-style: italic;
+}
+
+.notice {
+ margin-bottom: 20px;
+ border: 1px solid rgba(121, 168, 137, 0.4);
+ border-radius: 6px;
+ background: rgba(121, 168, 137, 0.1);
+ color: #d5f0dd;
+ padding: 12px 14px;
+}
+
+.notice.error {
+ border-color: rgba(223, 110, 98, 0.4);
+ background: rgba(223, 110, 98, 0.1);
+ color: #ffc7c0;
+}
+
+.diary-feed {
+ display: grid;
+ gap: 16px;
+ padding-bottom: 56px;
+}
+
+.month-label {
+ margin: 24px 0 14px;
+ color: var(--subtle);
+ font-size: 0.78rem;
+ font-weight: 700;
+ letter-spacing: 0.1em;
+ text-transform: uppercase;
+}
+
+.film-card {
+ display: grid;
+ grid-template-columns: 92px minmax(0, 1fr);
+ gap: 18px;
+ border-bottom: 1px solid var(--line);
+ padding: 0 0 18px;
+}
+
+.poster-frame {
+ display: grid;
+ place-items: center;
+ aspect-ratio: 2 / 3;
+ overflow: hidden;
+ border: 1px solid var(--line);
+ border-radius: 6px;
+ background: var(--panel);
+ color: var(--accent);
+ font-family: Georgia, "Times New Roman", serif;
+ font-size: 2rem;
+ box-shadow: var(--shadow);
+}
+
+.poster-frame img {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+}
+
+.film-card-body {
+ min-width: 0;
+ padding-top: 2px;
+}
+
+.film-card-header {
+ display: flex;
+ align-items: flex-start;
+ justify-content: space-between;
+ gap: 14px;
+}
+
+.rating {
+ flex: 0 0 auto;
+ color: var(--accent);
+ font-weight: 800;
+}
+
+.meta-row,
+.detail-meta {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px;
+ color: var(--muted);
+ font-size: 0.92rem;
+}
+
+.meta-row span,
+.detail-meta span {
+ border: 1px solid var(--line);
+ border-radius: 999px;
+ padding: 4px 9px;
+}
+
+.tag-row {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px;
+ margin-top: 12px;
+}
+
+.tag-row span {
+ border-radius: 999px;
+ background: var(--panel-soft);
+ color: var(--muted);
+ padding: 4px 9px;
+ font-size: 0.84rem;
+}
+
+.inline-actions {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px;
+ margin-top: 14px;
+}
+
+.inline-actions form {
+ margin: 0;
+}
+
+.notes-preview {
+ margin: 14px 0 0;
+ color: #d0c7ba;
+}
+
+.empty-state {
+ margin: 34px 0 64px;
+ border: 1px solid var(--line);
+ border-radius: 8px;
+ background: var(--panel);
+ padding: 30px;
+}
+
+.empty-state p {
+ color: var(--muted);
+}
+
+.director-summary {
+ display: grid;
+ grid-template-columns: repeat(3, minmax(0, 1fr));
+ gap: 14px;
+ margin-bottom: 28px;
+}
+
+.summary-card,
+.stats-panel {
+ border: 1px solid var(--line);
+ border-radius: 8px;
+ background: var(--panel);
+ padding: 18px;
+}
+
+.summary-card strong,
+.stats-metric strong {
+ display: block;
+ margin-top: 6px;
+ font-size: clamp(1.8rem, 4vw, 2.6rem);
+ font-family: 'Cormorant Garamond', Georgia, serif;
+ font-weight: 600;
+ color: var(--accent);
+}
+
+.summary-label {
+ color: var(--muted);
+ font-size: 0.82rem;
+ font-weight: 700;
+ text-transform: uppercase;
+}
+
+.detail-layout {
+ display: grid;
+ grid-template-columns: minmax(180px, 300px) minmax(0, 1fr);
+ gap: 42px;
+ padding: 52px 0 64px;
+}
+
+.poster-large {
+ width: 100%;
+}
+
+.detail-body h1 {
+ margin-bottom: 10px;
+}
+
+.detail-meta {
+ margin: 24px 0 0;
+}
+
+.detail-tags {
+ margin-top: 18px;
+}
+
+.notes-body {
+ margin-top: 28px;
+ max-width: 68ch;
+ color: #e3dacd;
+ white-space: pre-wrap;
+ font-family: Georgia, "Times New Roman", serif;
+ font-size: 1.14rem;
+}
+
+.detail-actions {
+ margin-top: 34px;
+}
+
+.detail-actions form {
+ margin: 0;
+}
+
+.form-shell {
+ width: min(900px, 100%);
+ padding-bottom: 64px;
+}
+
+.form-shell.narrow {
+ width: min(560px, 100%);
+}
+
+.danger-zone {
+ margin-top: 10px;
+ border-top: 1px solid var(--line);
+ padding-top: 28px;
+}
+
+.data-tools {
+ margin-top: 10px;
+ border-top: 1px solid var(--line);
+ padding-top: 28px;
+}
+
+.compact-heading {
+ padding: 0 0 14px;
+}
+
+.tmdb-panel {
+ margin-bottom: 24px;
+ border: 1px solid var(--line);
+ border-radius: 8px;
+ background: var(--panel);
+ padding: 18px;
+}
+
+label {
+ display: block;
+ margin-bottom: 8px;
+ color: var(--muted);
+ font-size: 0.86rem;
+ font-weight: 700;
+}
+
+input,
+select,
+textarea {
+ width: 100%;
+ border: 1px solid var(--line);
+ border-radius: 6px;
+ background: #11100e;
+ color: var(--text);
+ padding: 11px 12px;
+ outline: none;
+}
+
+textarea {
+ resize: vertical;
+}
+
+input:focus,
+select:focus,
+textarea:focus {
+ border-color: var(--accent);
+}
+
+.search-row input {
+ min-width: 0;
+}
+
+.tmdb-results {
+ display: grid;
+ gap: 8px;
+ margin-top: 12px;
+}
+
+.tmdb-result {
+ display: grid;
+ grid-template-columns: 38px minmax(0, 1fr);
+ gap: 10px;
+ align-items: center;
+ width: 100%;
+ border-color: var(--line);
+ background: #11100e;
+ color: var(--text);
+ padding: 8px;
+ text-align: left;
+}
+
+.tmdb-result:hover {
+ background: var(--panel-soft);
+}
+
+.tmdb-result img {
+ width: 38px;
+ aspect-ratio: 2 / 3;
+ border-radius: 4px;
+ object-fit: cover;
+}
+
+.tmdb-result small {
+ display: block;
+ color: var(--muted);
+}
+
+.tmdb-message {
+ margin: 0;
+ color: var(--muted);
+}
+
+.film-form {
+ display: grid;
+ gap: 20px;
+}
+
+.form-grid {
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ gap: 18px;
+}
+
+.field {
+ min-width: 0;
+}
+
+.checkbox-field {
+ display: flex;
+ align-items: flex-end;
+}
+
+.check-label {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ min-height: 45px;
+ margin: 0;
+ color: var(--text);
+}
+
+.check-label input {
+ width: auto;
+}
+
+.span-2 {
+ grid-column: 1 / -1;
+}
+
+.poster-preview-field {
+ grid-column: 1;
+}
+
+.poster-preview {
+ width: min(180px, 100%);
+ box-shadow: none;
+}
+
+.poster-preview img:not([src]) {
+ display: none;
+}
+
+.notes-field {
+ grid-column: 2;
+}
+
+.form-actions {
+ justify-content: flex-end;
+}
+
+.stats-layout {
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ gap: 18px;
+ padding-bottom: 64px;
+}
+
+.stats-panel-wide {
+ grid-column: 1 / -1;
+}
+
+.stats-panel h2 {
+ margin: 2px 0 0;
+}
+
+.stats-map {
+ position: relative;
+ min-height: 320px;
+}
+
+.stats-map svg {
+ display: block;
+ width: 100%;
+ height: auto;
+}
+
+.map-readout {
+ display: grid;
+ gap: 3px;
+ margin-top: 14px;
+ border-top: 1px solid var(--line);
+ padding-top: 14px;
+}
+
+.map-readout strong {
+ color: var(--text);
+ font-size: 1rem;
+}
+
+.map-readout span {
+ color: var(--muted);
+ font-size: 0.92rem;
+}
+
+.stats-tooltip {
+ position: fixed;
+ z-index: 40;
+ display: grid;
+ gap: 2px;
+ min-width: 120px;
+ border: 1px solid var(--line);
+ border-radius: 6px;
+ background: rgba(11, 11, 10, 0.96);
+ color: var(--text);
+ padding: 10px 12px;
+ pointer-events: none;
+ box-shadow: var(--shadow);
+}
+
+.stats-tooltip[hidden] {
+ display: none;
+}
+
+.heatmap-shell {
+ overflow: visible;
+}
+
+.heatmap-months {
+ display: grid;
+ grid-template-columns: repeat(53, 13px);
+ gap: 4px;
+ margin: 0 0 8px 42px;
+ color: var(--muted);
+ font-size: 0.72rem;
+}
+
+.heatmap-body {
+ display: grid;
+ grid-template-columns: 34px minmax(0, 1fr);
+ gap: 8px;
+ align-items: start;
+}
+
+.heatmap-weekdays {
+ display: grid;
+ grid-template-rows: repeat(7, 13px);
+ gap: 4px;
+ color: var(--muted);
+ font-size: 0.72rem;
+}
+
+.heatmap-grid {
+ display: grid;
+ grid-template-columns: repeat(53, 13px);
+ grid-template-rows: repeat(7, 13px);
+ gap: 4px;
+}
+
+.heatmap-cell {
+ border: 0;
+ border-radius: 3px;
+ padding: 0;
+}
+
+.stats-list {
+ display: grid;
+ gap: 10px;
+ margin: 0;
+ padding: 0;
+ list-style: none;
+}
+
+.stats-list li,
+.stats-bar-row {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr) auto;
+ gap: 12px;
+ align-items: center;
+}
+
+.stats-list li strong,
+.stats-bar-row strong {
+ color: var(--accent);
+}
+
+.stats-bars {
+ display: grid;
+ gap: 10px;
+}
+
+.stats-bar-row {
+ grid-template-columns: 64px minmax(0, 1fr) auto;
+}
+
+.stats-bar-track {
+ height: 10px;
+ overflow: hidden;
+ border-radius: 999px;
+ background: var(--panel-soft);
+}
+
+.stats-bar-fill {
+ height: 100%;
+ min-width: 8px;
+ border-radius: inherit;
+ background: linear-gradient(90deg, rgba(240, 184, 77, 0.4), var(--accent));
+}
+
+.stats-metric {
+ display: grid;
+ gap: 8px;
+}
+
+.stats-metric span {
+ color: var(--muted);
+}
+
+@media (max-width: 760px) {
+ .shell {
+ width: min(100% - 24px, 1120px);
+ }
+
+ .topbar {
+ align-items: flex-start;
+ flex-direction: column;
+ }
+
+ .page-heading,
+ .form-heading {
+ padding-top: 34px;
+ }
+
+ .film-card,
+ .detail-layout,
+ .form-grid,
+ .director-summary,
+ .stats-layout {
+ grid-template-columns: 1fr;
+ }
+
+ .film-card {
+ grid-template-columns: 72px minmax(0, 1fr);
+ }
+
+ .detail-layout {
+ gap: 28px;
+ }
+
+ .detail-poster {
+ width: min(220px, 70%);
+ }
+
+ .poster-preview-field,
+ .notes-field {
+ grid-column: 1;
+ }
+
+ .search-row,
+ .form-actions {
+ align-items: stretch;
+ flex-direction: column;
+ }
+
+ .page-heading-row,
+ .stats-panel-header {
+ align-items: flex-start;
+ flex-direction: column;
+ }
+
+ .heatmap-months {
+ margin-left: 38px;
+ }
+}