+ baseline to the git

This commit is contained in:
Itsigo
2025-11-04 13:52:36 +01:00
commit b3193a10f9
29 changed files with 2079 additions and 0 deletions

39
cmd/web/apiinput.templ Normal file
View File

@@ -0,0 +1,39 @@
package web
templ Apikeyinput(apierror string) {
@Html("Please input your TMDB Api key - PocketMovie") {
<div class="flex-center" style="height:100vh">
<div style="width: 1000px">
<h1>API Read Access Token needed</h1>
<p>To use this application you need to enter your TMDB API Read Access Token. If you do not have a Token you can get one by creating a TMDB account.</p>
<p>It is needed to search movies, pull metadata and stream providers.</p>
<form method="POST">
<fieldset
if apierror != "" {
aria-invalid="true"
aria-describedby="invalid-helper"
}
role="group"
>
<input
type="password"
name="apikey"
placeholder="API Read Access Token"
required
if apierror != "" {
aria-invalid="true"
}
/>
<input
type="submit"
value="Submit"
/>
</fieldset>
if apierror != "" {
<small id="invalid-helper">{ apierror }</small>
}
</form>
</div>
</div>
}
}

View File

@@ -0,0 +1,80 @@
:root {
--ctp-macchiato-rosewater: #f4dbd6;
--ctp-macchiato-rosewater-rgb: 244 219 214;
--ctp-macchiato-rosewater-hsl: 10.000 57.692% 89.804%;
--ctp-macchiato-flamingo: #f0c6c6;
--ctp-macchiato-flamingo-rgb: 240 198 198;
--ctp-macchiato-flamingo-hsl: 0.000 58.333% 85.882%;
--ctp-macchiato-pink: #f5bde6;
--ctp-macchiato-pink-rgb: 245 189 230;
--ctp-macchiato-pink-hsl: 316.071 73.684% 85.098%;
--ctp-macchiato-mauve: #c6a0f6;
--ctp-macchiato-mauve-rgb: 198 160 246;
--ctp-macchiato-mauve-hsl: 266.512 82.692% 79.608%;
--ctp-macchiato-red: #ed8796;
--ctp-macchiato-red-rgb: 237 135 150;
--ctp-macchiato-red-hsl: 351.176 73.913% 72.941%;
--ctp-macchiato-maroon: #ee99a0;
--ctp-macchiato-maroon-rgb: 238 153 160;
--ctp-macchiato-maroon-hsl: 355.059 71.429% 76.667%;
--ctp-macchiato-peach: #f5a97f;
--ctp-macchiato-peach-rgb: 245 169 127;
--ctp-macchiato-peach-hsl: 21.356 85.507% 72.941%;
--ctp-macchiato-yellow: #eed49f;
--ctp-macchiato-yellow-rgb: 238 212 159;
--ctp-macchiato-yellow-hsl: 40.253 69.912% 77.843%;
--ctp-macchiato-green: #a6da95;
--ctp-macchiato-green-rgb: 166 218 149;
--ctp-macchiato-green-hsl: 105.217 48.252% 71.961%;
--ctp-macchiato-teal: #8bd5ca;
--ctp-macchiato-teal-rgb: 139 213 202;
--ctp-macchiato-teal-hsl: 171.081 46.835% 69.020%;
--ctp-macchiato-sky: #91d7e3;
--ctp-macchiato-sky-rgb: 145 215 227;
--ctp-macchiato-sky-hsl: 188.780 59.420% 72.941%;
--ctp-macchiato-sapphire: #7dc4e4;
--ctp-macchiato-sapphire-rgb: 125 196 228;
--ctp-macchiato-sapphire-hsl: 198.641 65.605% 69.216%;
--ctp-macchiato-blue: #8aadf4;
--ctp-macchiato-blue-rgb: 138 173 244;
--ctp-macchiato-blue-hsl: 220.189 82.813% 74.902%;
--ctp-macchiato-lavender: #b7bdf8;
--ctp-macchiato-lavender-rgb: 183 189 248;
--ctp-macchiato-lavender-hsl: 234.462 82.278% 84.510%;
--ctp-macchiato-text: #cad3f5;
--ctp-macchiato-text-rgb: 202 211 245;
--ctp-macchiato-text-hsl: 227.442 68.254% 87.647%;
--ctp-macchiato-subtext1: #b8c0e0;
--ctp-macchiato-subtext1-rgb: 184 192 224;
--ctp-macchiato-subtext1-hsl: 228.000 39.216% 80.000%;
--ctp-macchiato-subtext0: #a5adcb;
--ctp-macchiato-subtext0-rgb: 165 173 203;
--ctp-macchiato-subtext0-hsl: 227.368 26.761% 72.157%;
--ctp-macchiato-overlay2: #939ab7;
--ctp-macchiato-overlay2-rgb: 147 154 183;
--ctp-macchiato-overlay2-hsl: 228.333 20.000% 64.706%;
--ctp-macchiato-overlay1: #8087a2;
--ctp-macchiato-overlay1-rgb: 128 135 162;
--ctp-macchiato-overlay1-hsl: 227.647 15.455% 56.863%;
--ctp-macchiato-overlay0: #6e738d;
--ctp-macchiato-overlay0-rgb: 110 115 141;
--ctp-macchiato-overlay0-hsl: 230.323 12.351% 49.216%;
--ctp-macchiato-surface2: #5b6078;
--ctp-macchiato-surface2-rgb: 91 96 120;
--ctp-macchiato-surface2-hsl: 229.655 13.744% 41.373%;
--ctp-macchiato-surface1: #494d64;
--ctp-macchiato-surface1-rgb: 73 77 100;
--ctp-macchiato-surface1-hsl: 231.111 15.607% 33.922%;
--ctp-macchiato-surface0: #363a4f;
--ctp-macchiato-surface0-rgb: 54 58 79;
--ctp-macchiato-surface0-hsl: 230.400 18.797% 26.078%;
--ctp-macchiato-base: #24273a;
--ctp-macchiato-base-rgb: 36 39 58;
--ctp-macchiato-base-hsl: 231.818 23.404% 18.431%;
--ctp-macchiato-mantle: #1e2030;
--ctp-macchiato-mantle-rgb: 30 32 48;
--ctp-macchiato-mantle-hsl: 233.333 23.077% 15.294%;
--ctp-macchiato-crust: #181926;
--ctp-macchiato-crust-rgb: 24 25 38;
--ctp-macchiato-crust-hsl: 235.714 22.581% 12.157%;
}

View File

@@ -0,0 +1,10 @@
/* jersey-15-regular - latin */
@font-face {
font-display: swap;
/* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
font-family: 'Jersey 15';
font-style: normal;
font-weight: 400;
src: url('./fonts/jersey15.woff2') format('woff2');
/* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
}

182
cmd/web/assets/css/main.css Normal file
View File

@@ -0,0 +1,182 @@
@import "fonts.css";
@import "catppuccin.css";
:root {
--pico-form-element-spacing-vertical: 0.5rem;
--pico-form-element-spacing-horizontal: 0.75rem;
--pico-color: var(--ctp-macchiato-text);
--pico-h1-color: var(--ctp-macchiato-text);
--pico-primary-inverse: var(--ctp-macchiato-base);
--pico-primary: var(--ctp-macchiato-blue);
--pico-primary-underline: var(--ctp-macchiato-blue);
--pico-primary-background: var(--ctp-macchiato-blue);
--pico-primary-hover: var(--ctp-macchiato-mauve);
--pico-primary-hover-background: var(--ctp-macchiato-mauve);
--pico-background-color: var(--ctp-macchiato-crust);
}
.bg-red{
color: hsl(351deg, 74%, 40%);
background-color: var(--ctp-macchiato-red) !important;
}
.bg-green{
color: hsl(105deg, 48%, 40%);
background-color: var(--ctp-macchiato-green) !important;
}
.red-link{
color: var(--ctp-macchiato-red);
text-decoration-color: var(--ctp-macchiato-red);
}
h1 {
font-family: "Jersey 15";
}
#search-dialog {
& article {
max-height: 700px;
}
& tr {
cursor: pointer;
& :hover {
background-color: var(--pico-hover-background);
}
}
}
.hidden {
display: none;
}
.center {
text-align: center;
}
.flex-center {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.m-0 {
margin: 0;
}
.grid-4 {
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-template-rows: 1fr;
grid-column-gap: 1rem;
grid-row-gap: 0px;
}
.grid-3 {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-template-rows: 1fr;
grid-column-gap: 1rem;
grid-row-gap: 0px;
}
.movie-grid {
display: grid;
grid-template-columns: repeat(5, 1fr);
grid-template-rows: 1fr;
grid-column-gap: 1.5rem;
grid-row-gap: 1.5rem;
& .movie-tile {
& div {
position: absolute;
top: 0;
left: 0;
background-color: rgba(0, 0, 0, 0.7);
padding: 0.25rem;
border-bottom-right-radius: var(--pico-border-radius);
border-top-left-radius: var(--pico-border-radius);
& svg {
width: 2rem;
}
}
& img {
border-radius: var(--pico-border-radius);
}
}
}
.watched {
text-align: center;
cursor: pointer;
& svg {
width: 3rem;
color: var(--pico-primary);
}
}
.ratingBox {
text-align: center;
i {
background-color: var(--pico-secondary);
cursor: pointer;
}
.rated {
background-color: var(--pico-primary);
}
>i {
&:hover .icon,
&:hover~.icon {
background-color: var(--pico-secondary) !important;
}
}
&:hover>.icon {
background-color: var(--pico-primary);
}
}
.listRating {
i {
width: 1.25rem;
height: 1.25rem;
background-color: var(--pico-secondary);
}
.rated {
background-color: var(--pico-primary);
}
}
.icon {
display: inline-block;
font-size: var(--pico-font-size);
width: 2rem;
height: 2rem;
&.star-full {
mask: url('data:image/svg+xml,<svg viewBox="0 0 24 24" fill="%231C274C" xmlns="http://www.w3.org/2000/svg"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"><path d="M9.15316 5.40838C10.4198 3.13613 11.0531 2 12 2C12.9469 2 13.5802 3.13612 14.8468 5.40837L15.1745 5.99623C15.5345 6.64193 15.7144 6.96479 15.9951 7.17781C16.2757 7.39083 16.6251 7.4699 17.3241 7.62805L17.9605 7.77203C20.4201 8.32856 21.65 8.60682 21.9426 9.54773C22.2352 10.4886 21.3968 11.4691 19.7199 13.4299L19.2861 13.9372C18.8096 14.4944 18.5713 14.773 18.4641 15.1177C18.357 15.4624 18.393 15.8341 18.465 16.5776L18.5306 17.2544C18.7841 19.8706 18.9109 21.1787 18.1449 21.7602C17.3788 22.3417 16.2273 21.8115 13.9243 20.7512L13.3285 20.4768C12.6741 20.1755 12.3469 20.0248 12 20.0248C11.6531 20.0248 11.3259 20.1755 10.6715 20.4768L10.0757 20.7512C7.77268 21.8115 6.62118 22.3417 5.85515 21.7602C5.08912 21.1787 5.21588 19.8706 5.4694 17.2544L5.53498 16.5776C5.60703 15.8341 5.64305 15.4624 5.53586 15.1177C5.42868 14.773 5.19043 14.4944 4.71392 13.9372L4.2801 13.4299C2.60325 11.4691 1.76482 10.4886 2.05742 9.54773C2.35002 8.60682 3.57986 8.32856 6.03954 7.77203L6.67589 7.62805C7.37485 7.4699 7.72433 7.39083 8.00494 7.17781C8.28555 6.96479 8.46553 6.64194 8.82547 5.99623L9.15316 5.40838Z" ></path></g></svg>');
}
}

View File

@@ -0,0 +1,60 @@
/*
* Modal
*
* Pico.css - https://picocss.com
* Copyright 2019-2024 - Licensed under MIT
*/
// Config
const isOpenClass = "modal-is-open";
const openingClass = "modal-is-opening";
const closingClass = "modal-is-closing";
const scrollbarWidthCssVar = "--pico-scrollbar-width";
const animationDuration = 400; // ms
let visibleModal = null;
// Toggle modal
const toggleModal = (event) => {
event.preventDefault();
const modal = document.getElementById(event.currentTarget.dataset.target);
if (!modal) return;
modal && (modal.open ? closeModal(modal) : openModal(modal));
};
// Open modal
const openModal = (modal) => {
const { documentElement: html } = document;
const scrollbarWidth = getScrollbarWidth();
if (scrollbarWidth) {
html.style.setProperty(scrollbarWidthCssVar, `${scrollbarWidth}px`);
}
html.classList.add(isOpenClass, openingClass);
setTimeout(() => {
visibleModal = modal;
html.classList.remove(openingClass);
}, animationDuration);
modal.showModal();
};
// Close modal
const closeModal = (modal) => {
visibleModal = null;
const { documentElement: html } = document;
html.classList.add(closingClass);
setTimeout(() => {
html.classList.remove(closingClass, isOpenClass);
html.style.removeProperty(scrollbarWidthCssVar);
modal.close();
}, animationDuration);
};
// Get scrollbar width
const getScrollbarWidth = () => {
const scrollbarWidth = window.innerWidth - document.documentElement.clientWidth;
return scrollbarWidth;
};
// Is scrollbar visible
const isScrollbarVisible = () => {
return document.body.scrollHeight > screen.height;
};

156
cmd/web/base.templ Normal file
View File

@@ -0,0 +1,156 @@
package web
import "strings"
import "git.itsigo.dev/istigo/pocketmovie/internal/apis"
import "fmt"
templ Html(title string) {
<!DOCTYPE html>
<html lang="en" data-theme="dark">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🎬</text></svg>"/>
<link rel="stylesheet" href="/assets/css/pico.min.css"/>
<link rel="stylesheet" href="/assets/css/main.css"/>
<script type="module" src="/assets/js/datastar.js"></script>
<script src="/assets/js/modal.js"></script>
<title>{ title }</title>
</head>
<body>
{ children... }
</body>
</html>
}
templ Base(title string) {
@Html(title) {
<header>
@header()
</header>
<main class="container">
{ children... }
</main>
// <footer>
// @footer()
// </footer>
<dialog
id="search-dialog"
data-signals="{id: ''}"
data-on-keydown__window="
if(evt.key !== 'Escape' || visibleModal === null) return;
closeModal(visibleModal);
$id = '';
"
>
<article
data-on-click__outside="
if (visibleModal === null) return;
closeModal(visibleModal);
$id = '';
"
>
<header>
<button
aria-label="Close"
rel="prev"
data-target="search-dialog"
data-on-click="$id = ''; toggleModal(event); "
></button>
<p>
<strong>🎬 Add Movie</strong>
</p>
</header>
<div data-show="$id == ''" id="search-container">
<form data-on-submit="@post('/apis/tmdb/searchMovie', {contentType: 'form'})" role="search">
<input type="search" id="search" name="search" placeholder="Search"/>
<input type="submit" value="Search"/>
</form>
<table class="striped">
<tbody id="search-results"></tbody>
</table>
</div>
<div data-show="$id != ''" id="add-options">
@AddMovieConfig()
</div>
</article>
</dialog>
}
}
templ header() {
<nav class="container">
<ul>
<li><strong style="font-family: 'Jersey 15'; font-size: 2rem">Pocket Movie</strong></li>
</ul>
<ul>
<li><a href="/">Movies</a></li>
<li><a href="/watchlist">Watchlist</a></li>
<li><a href="/settings">Settings</a></li>
</ul>
</nav>
}
templ SearchMovie(movies []apis.Movie) {
<tbody id="search-results">
for _, v := range movies {
<tr>
<td
class="add-movie"
data-on-click={ fmt.Sprintf("$id = '%d'", v.Id) }
>
{ v.Title }
if v.ReleaseDate != "" {
({ strings.Split(v.ReleaseDate, "-")[0] })
}
</td>
</tr>
}
</tbody>
}
templ AddMovieConfig() {
<form
id="addMovieForm"
class="add-movie"
data-on-submit="@post('/db/addMovie/' + $id, {contentType: 'form'}); closeModal(visibleModal)"
>
<fieldset>
<legend>
General
</legend>
<input type="checkbox" name="watched" id="watched"/>
<label style="padding-bottom: 0.5rem" htmlFor="watched">Watched</label>
<div class="grid">
<input type="number" name="watchcount" placeholder="Watchcount" aria-label="watchcount"/>
<input type="number" name="rating" placeholder="Rating" aria-label="rating"/>
</div>
</fieldset>
<fieldset>
<legend>
Physical data
</legend>
<input type="checkbox" name="owned" id="owned"/>
<label>Owned</label>
<input type="checkbox" name="ripped" id="ripped"/>
<label>Ripped</label>
</fieldset>
<select name="version" id="version" aria-label="Select physical version..">
@MediaSelectOptions()
</select>
<input id="submitMovieButton" type="submit" value="Add Movie"/>
</form>
}
templ addMovieButton() {
<button id="show-modal-dialog" data-target="search-dialog" onCLick="toggleModal(event);" type="button">Add Movie</button>
}
templ MediaSelectOptions() {
<option selected disabled>Select..</option>
<option value="">None</option>
<option value="DVD">DVD</option>
<option value="BD">BD</option>
<option value="4KBD">4KBD</option>
<option value="DL">DL</option>
}

6
cmd/web/efs.go Normal file
View File

@@ -0,0 +1,6 @@
package web
import "embed"
//go:embed assets/*
var Files embed.FS

138
cmd/web/home.templ Normal file
View File

@@ -0,0 +1,138 @@
package web
import "git.itsigo.dev/istigo/pocketmovie/internal/database"
import "strings"
import "fmt"
func getConfVal(settings []database.Setting, name string) string {
for _, s := range settings {
if s.Name == name {
return s.Value
}
}
return ""
}
templ Show(movies []database.Movie, settings []database.Setting) {
@Base("Movies - PocketMovie") {
//<pre id="data"></pre>
//<script>
// var data = JSON.parse({{ data }});
// var formated_data = JSON.stringify(data, null, 2);
// document.getElementById("data").textContent = formated_data;
//</script>
@addMovieButton()
{{ list, grid := "1", "" }}
if getConfVal(settings, "HOME_GRID_VIEW" ) == "true" {
{{ list, grid = "", "1" }}
}
<div
data-signals={ fmt.Sprintf("{list : '%s', grid : '%s'}", list, grid) }
style="float:right"
>
<button
data-show="$list != ''"
data-on-click="
@post('/db/updateTableView')
$list = '';
$grid = '1'
"
>List &#9776;</button>
<button
data-show="$grid != ''"
data-on-click="
@post('/db/updateTableView')
$list = '1';
$grid = ''
"
>Grid 田</button>
</div>
if grid != "" {
@MovieTiles(movies)
} else {
@MovieList(movies)
}
}
}
templ MovieList(movies []database.Movie) {
<table id="movie-list" class="striped">
<tr>
<th>Titel</th>
<th>Year</th>
<th>Rating</th>
<th>Watched</th>
<th>Owned</th>
<th></th>
</tr>
for _, item := range movies {
<tr>
<td><a href={ fmt.Sprintf("/movie/%d", item.ID) }>{ item.Title }</a></td>
<td>{ strings.Split(item.Year, "-")[0] }</td>
<td>
<div class="listRating">
{{ class := "icon star-full" }}
for i := range(5) {
<i
if int64(i) < item.Rating {
class={ class + " rated" }
} else {
class={ class }
}
></i>
}
</div>
</td>
{{ statusClasses := "bg-red" }}
if item.Status == 1 {
{{ statusClasses = "bg-green" }}
}
<td
class={ statusClasses }
>
{ item.Status != 0 }
</td>
{{ ownedClasses := "bg-red" }}
if item.Owned == 1 {
{{ ownedClasses = "bg-green" }}
}
<td
class={ ownedClasses }
>
{ item.Owned != 0 }
</td>
<td>
<a
class="red-link"
href="#"
data-on-click={ fmt.Sprintf("confirm('Are you sure?') && @delete('/db/deleteMovie/%d')", item.ID) }
>Delete</a>
</td>
</tr>
}
</table>
}
templ MovieTiles(movies []database.Movie) {
<div id="movie-list" class="movie-grid">
for _, item := range movies {
<a
class="movie-tile"
data-tooltip={ item.Title }
data-placement="bottom"
href={ fmt.Sprintf("/movie/%d", item.ID) }
title={ item.Title }
style="position:relative"
>
<div>
if item.Status == 1 {
@fullEye()
} else {
@closedEye()
}
</div>
<img alt={ item.Title } src={ fmt.Sprintf("/movie/posters/%d.jpg", item.ID) }/>
</a>
}
</div>
}

200
cmd/web/moviedetails.templ Normal file

File diff suppressed because one or more lines are too long

95
cmd/web/settings.templ Normal file
View File

@@ -0,0 +1,95 @@
package web
import "fmt"
import "git.itsigo.dev/istigo/pocketmovie/internal/database"
import "git.itsigo.dev/istigo/pocketmovie/internal/apis"
type SettingsConfig struct {
Providers []database.StreamingService
AvailableProviders []string
Regions []apis.Region
SelectedRegion string
APIKey string
}
templ Settings(settings SettingsConfig) {
@Base("Settings - PocketMovie") {
<h1>Settings</h1>
<div class="grid">
<div>
<h3>Stream Region</h3>
@Region(settings)
<h3>Stream Providers</h3>
@ProviderTable(settings)
</div>
<div>
<h3>API Token</h3>
@ApiKey(settings)
</div>
</div>
}
}
templ ApiKey(settings SettingsConfig) {
<form id="apikeyinput">
<fieldset role="group">
<input type="text" value={ settings.APIKey } name="apikey" placeholder="TMDB Read Access Token"/>
<input type="submit" value="Change" data-on-click="@post('/db/updateApiKey', {contentType: 'form'})"/>
</fieldset>
</form>
}
templ ProviderTable(settings SettingsConfig) {
<div id="providers">
<table class="striped">
<tr>
<th>Name</th>
<th></th>
</tr>
for _, p := range(settings.Providers) {
<tr>
<td>{ p.Title }</td>
<td class="center">
<a
class="red-link"
href="#"
data-on-click={ fmt.Sprintf("confirm('Are you sure?') && @delete('/db/deleteStreamingService/%d')", p.ID) }
>Delete</a>
</td>
</tr>
}
</table>
<form>
<fieldset role="group">
<select name="service" aria-label="Services" required>
<option selected disabled value="">Select</option>
for _, p := range(settings.AvailableProviders) {
<option>{ p }</option>
}
</select>
<input type="submit" value="Add" data-on-click="@post('/db/addStreamingService', {contentType: 'form'})"/>
</fieldset>
</form>
</div>
}
templ Region(settings SettingsConfig) {
<form id="regionselector">
<fieldset role="group">
<select
data-on-change={ fmt.Sprintf("@post('/db/updateRegion', {contentType: 'form'})") }
name="region"
aria-label="Regions"
>
for _, r := range(settings.Regions) {
<option
if settings.SelectedRegion == r.Iso {
selected
}
value={ r.Iso }
>{ r.EnglishName }</option>
}
</select>
</fieldset>
</form>
}

52
cmd/web/watchlist.templ Normal file
View File

@@ -0,0 +1,52 @@
package web
import "git.itsigo.dev/istigo/pocketmovie/internal/database"
import "fmt"
import "strings"
templ WatchList(movies []database.Movie) {
@Base("Watchlist - PocketMovie") {
@addMovieButton()
<button
data-on-click="@post('/db/updateStreamingServices')"
data-tooltip="Refresh streaming services"
type="button"
>
@refresh()
</button>
@List(movies)
}
}
templ List(movies []database.Movie) {
<table id="movie-list" class="striped">
<tr>
<th>Titel</th>
<th>Year</th>
<th>Genre</th>
<th>Owned</th>
<th>Streaming service</th>
</tr>
for _, item := range movies {
<tr>
<td><a href={ fmt.Sprintf("/movie/%d", item.ID) }>{ item.Title }</a></td>
<td>{ strings.Split(item.Year, "-")[0] }</td>
<td>{ item.Genre }</td>
{{ ownedClasses := "bg-red" }}
if item.Owned == 1 {
{{ ownedClasses = "bg-green" }}
}
<td
class={ ownedClasses }
>
{ item.Owned != 0 }
</td>
<td>{ item.StreamingServices }</td>
</tr>
}
</table>
}
templ refresh() {
<svg width="1em" height="1em" viewBox="0 0 24 24" stroke="var(--pico-primary-inverse)" fill="none" xmlns="http://www.w3.org/2000/svg"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"><path d="M12 21C16.9706 21 21 16.9706 21 12C21 9.69494 20.1334 7.59227 18.7083 6L16 3M12 3C7.02944 3 3 7.02944 3 12C3 14.3051 3.86656 16.4077 5.29168 18L8 21M21 3H16M16 3V8M3 21H8M8 21V16" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path> </g></svg>
}