From e708bec6cd76c2686de4158dde4d04f72a3c300d Mon Sep 17 00:00:00 2001 From: Tyler Hoang Date: Wed, 6 May 2026 12:21:26 -0700 Subject: init: lumiere film diary --- static/app.js | 131 +++++++++ static/styles.css | 789 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 920 insertions(+) create mode 100644 static/app.js create mode 100644 static/styles.css (limited to 'static') 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; + } +} -- cgit v1.3-2-g0d8e