2026-10-04 19:27:36 +01:00
( ( ) => {
const data = window . VALLEY _DATA ;
2026-10-04 20:19:03 +01:00
const sync = window . ValleySync ;
2026-10-05 19:10:19 +01:00
const availability = window . ValleyAvailability ;
2026-10-05 19:54:15 +01:00
const calendar = window . ValleyCalendar ;
2026-10-04 19:27:36 +01:00
const weekdays = [ "Monday" , "Tuesday" , "Wednesday" , "Thursday" , "Friday" , "Saturday" , "Sunday" ] ;
const seasonNames = { spring : "Spring" , summer : "Summer" , fall : "Fall" , winter : "Winter" } ;
const state = {
year : Number ( localStorage . getItem ( "valley-year" ) ) || 1 ,
season : localStorage . getItem ( "valley-season" ) || "spring" ,
day : Number ( localStorage . getItem ( "valley-day" ) ) || 1 ,
filter : "all" ,
checks : { } ,
revision : - 1 ,
saving : new Set ( )
} ;
const $ = ( selector , root = document ) => root . querySelector ( selector ) ;
const $$ = ( selector , root = document ) => [ ... root . querySelectorAll ( selector ) ] ;
const escapeHtml = ( value ) => String ( value ) . replace ( /[&<>'"]/g , char => ( { "&" : "&" , "<" : "<" , ">" : ">" , "'" : "'" , '"' : """ } [ char ] ) ) ;
const itemKey = crop => crop . sharedId || crop . id ;
function saveDate ( ) {
localStorage . setItem ( "valley-year" , state . year ) ;
localStorage . setItem ( "valley-season" , state . season ) ;
localStorage . setItem ( "valley-day" , state . day ) ;
}
function weekday ( day = state . day ) { return weekdays [ ( day - 1 ) % 7 ] ; }
function setDate ( { year = state . year , season = state . season , day = state . day } ) {
state . year = Math . max ( 1 , Math . min ( 2 , year ) ) ;
state . season = data . seasons . includes ( season ) ? season : "spring" ;
state . day = Math . max ( 1 , Math . min ( 28 , day ) ) ;
saveDate ( ) ;
renderDate ( ) ;
renderToday ( ) ;
renderCrops ( ) ;
2026-10-05 19:10:19 +01:00
renderBundles ( ) ;
2026-10-04 19:27:36 +01:00
}
function moveDay ( delta ) {
let index = data . seasons . indexOf ( state . season ) ;
let day = state . day + delta ;
let year = state . year ;
if ( day > 28 ) { day = 1 ; index += 1 ; }
if ( day < 1 ) { day = 28 ; index -= 1 ; }
if ( index > 3 ) { index = 0 ; year += 1 ; }
if ( index < 0 ) { index = 3 ; year -= 1 ; }
if ( year < 1 || year > 2 ) return ;
setDate ( { year , season : data . seasons [ index ] , day } ) ;
}
function renderDate ( ) {
document . body . dataset . season = state . season ;
$ ( "#season-title" ) . textContent = seasonNames [ state . season ] ;
$ ( "#day-title" ) . textContent = state . day ;
$ ( "#weekday-title" ) . textContent = ` ${ weekday ( ) } · Year ${ state . year } ` ;
$$ ( "[data-year]" ) . forEach ( button => button . setAttribute ( "aria-pressed" , String ( Number ( button . dataset . year ) === state . year ) ) ) ;
$$ ( "[data-season]" ) . forEach ( button => button . setAttribute ( "aria-selected" , String ( button . dataset . season === state . season ) ) ) ;
$ ( "#day-grid" ) . innerHTML = Array . from ( { length : 28 } , ( _ , index ) => {
const day = index + 1 ;
2026-10-05 19:54:15 +01:00
const birthday = birthdayOn ( day ) ;
const label = ` ${ seasonNames [ state . season ] } ${ day } , ${ weekday ( day ) } ${ birthday ? ` , ${ birthday } 's birthday ` : "" } ` ;
const marker = birthday ? ` <span class="birthday-marker" title=" ${ escapeHtml ( ` ${ birthday } 's birthday ` ) } " aria-hidden="true">🎂</span> ` : "" ;
return ` <button type="button" class=" ${ day === state . day ? "selected" : "" } " data-day=" ${ day } " aria-label=" ${ escapeHtml ( label ) } " aria-pressed=" ${ day === state . day } "><small> ${ weekdays [ index % 7 ] . slice ( 0 , 2 ) } </small><span class="day-number"> ${ day } </span> ${ marker } </button> ` ;
2026-10-04 19:27:36 +01:00
} ) . join ( "" ) ;
}
2026-10-05 19:54:15 +01:00
function birthdayOn ( day ) {
return calendar . birthdayOn ( data . birthdays , state . season , day , state . year ) ;
}
2026-10-04 19:27:36 +01:00
function currentBirthday ( ) {
2026-10-05 19:54:15 +01:00
return birthdayOn ( state . day ) ;
2026-10-04 19:27:36 +01:00
}
function currentEvent ( ) {
const entry = data . events [ state . season ] [ state . day ] ;
if ( ! entry ) return null ;
if ( entry [ 2 ] && entry [ 2 ] !== state . year ) return null ;
return entry ;
}
function reminders ( ) {
const name = weekday ( ) ;
const result = [ ] ;
if ( name === "Friday" || name === "Sunday" ) result . push ( [ "Traveling Cart" , "Cindersap Forest, 6am– 8pm. Check for off-season bundle items." , "cart" ] ) ;
if ( name === "Sunday" ) result . push ( [ "The Queen of Sauce" , "Watch TV for a new recipe during Years 1– 2." , "tv" ] ) ;
if ( name === "Wednesday" ) result . push ( [ "Recipe rerun" , "The Queen of Sauce repeats a recipe you may have missed." , "tv" ] ) ;
if ( name === "Monday" && ( state . year > 1 || state . season !== "spring" || state . day >= 2 ) ) result . push ( [ "New week" , "Special Order boards refresh on Monday once unlocked." , "board" ] ) ;
return result ;
}
function cropDeadlineCards ( ) {
return data . crops . filter ( crop => crop . season === state . season && crop . last === state . day && ( ! crop . year || crop . year === state . year ) ) ;
}
2026-10-04 20:19:03 +01:00
function dailyId ( kind , title ) {
const slug = title . toLowerCase ( ) . replace ( /[^a-z0-9]+/g , "-" ) . replace ( /^-|-$/g , "" ) . slice ( 0 , 36 ) ;
return ` daily- ${ state . year } - ${ state . season } - ${ state . day } - ${ kind } - ${ slug } ` ;
}
2026-10-05 19:54:15 +01:00
function giftGuide ( name ) {
const profile = data . giftProfiles [ name ] ;
if ( ! profile ) return "" ;
const giftList = entries => entries . map ( entry => ` <li> ${ escapeHtml ( entry ) } </li> ` ) . join ( "" ) ;
return ` <div class="gift-guide" aria-label="Gift preferences for ${ escapeHtml ( name ) } ">
< div > < strong > < span aria - hidden = "true" > ♥ < / s p a n > L o v e s < / s t r o n g > < u l > $ { g i f t L i s t ( p r o f i l e . l o v e s ) } < / u l > < / d i v >
< div > < strong > < span aria - hidden = "true" > × < / s p a n > A v o i d < / s t r o n g > < u l > $ { g i f t L i s t ( p r o f i l e . a v o i d ) } < / u l > < / d i v >
< / d i v > ` ;
}
function card ( kind , label , title , copy , itemId = null , extra = "" ) {
2026-10-04 20:19:03 +01:00
const checked = itemId ? Boolean ( state . checks [ itemId ] ) : false ;
2026-10-05 19:54:15 +01:00
const content = ` ${ itemId ? ` <input type="checkbox" data-check-id=" ${ itemId } " aria-label="Mark ${ escapeHtml ( title ) } complete" ${ checked ? "checked" : "" } ${ state . saving . has ( itemId ) ? "disabled" : "" } ><span class="checkmark" aria-hidden="true"></span> ` : "" } <div class="card-label"><span></span> ${ escapeHtml ( label ) } </div><h3> ${ escapeHtml ( title ) } </h3><p> ${ escapeHtml ( copy ) } </p> ${ extra } ` ;
2026-10-04 20:19:03 +01:00
return itemId
? ` <label class="today-card ${ kind } ${ checked ? "complete" : "" } "> ${ content } </label> `
: ` <article class="today-card ${ kind } "> ${ content } </article> ` ;
2026-10-04 19:27:36 +01:00
}
function renderToday ( ) {
const cards = [ ] ;
const birthday = currentBirthday ( ) ;
const event = currentEvent ( ) ;
2026-10-05 19:54:15 +01:00
if ( birthday ) cards . push ( card ( "birthday" , "Birthday" , birthday , "A birthday gift gives 8× the usual friendship effect." , dailyId ( "birthday" , birthday ) , giftGuide ( birthday ) ) ) ;
2026-10-04 20:19:03 +01:00
if ( event ) cards . push ( card ( "event" , "Event" , event [ 0 ] , event [ 1 ] , dailyId ( "event" , event [ 0 ] ) ) ) ;
reminders ( ) . forEach ( ( [ title , copy ] ) => cards . push ( card ( "reminder" , "Reminder" , title , copy , dailyId ( "reminder" , title ) ) ) ) ;
cropDeadlineCards ( ) . forEach ( crop => cards . push ( card ( "deadline" , "Plant by today" , crop . name , crop . purpose , itemKey ( crop ) ) ) ) ;
2026-10-04 19:27:36 +01:00
if ( ! cards . length ) cards . push ( card ( "quiet" , "Open day" , "Make your own luck" , "No fixed birthdays, festivals, or last planting dates today." ) ) ;
$ ( "#today-cards" ) . innerHTML = cards . join ( "" ) ;
const noun = cards . length === 1 ? "thing" : "things" ;
2026-10-04 20:19:03 +01:00
$ ( "#today-summary" ) . textContent = ` ${ cards . length } ${ noun } on the calendar · tick cards when done ` ;
2026-10-04 19:27:36 +01:00
}
function renderCrops ( ) {
const crops = data . crops . filter ( crop => crop . season === state . season && ( ! crop . year || crop . year === state . year ) ) ;
const rows = crops . map ( crop => {
const key = itemKey ( crop ) ;
const checked = Boolean ( state . checks [ key ] ) ;
const overdue = state . day > crop . last && ! checked ;
const today = state . day === crop . last && ! checked ;
return ` <label class="crop-row ${ checked ? "complete" : "" } ${ overdue ? "overdue" : "" } ">
2026-10-05 19:54:15 +01:00
< input type = "checkbox" data - check - id = "${key}" aria - label = "Mark ${escapeHtml(crop.name)} complete" $ { checked ? "checked" : "" } $ { state . saving . has ( key ) ? "disabled" : "" } >
2026-10-04 19:27:36 +01:00
< span class = "checkmark" aria - hidden = "true" > < / s p a n >
< span class = "crop-name" > < strong > $ { escapeHtml ( crop . name ) } < / s t r o n g > < s m a l l > $ { e s c a p e H t m l ( c r o p . p u r p o s e ) } < / s m a l l > < / s p a n >
< span class = "grow-time" > $ { crop . grow } days < small > to grow < / s m a l l > < / s p a n >
< span class = "plant-date ${today ? " today " : " "}" > $ { overdue ? "Passed" : ` ${ seasonNames [ crop . season ] } ${ crop . last } ` } < small > $ { overdue ? "for this season" : "plant by" } < / s m a l l > < / s p a n >
< / l a b e l > ` ;
} ) ;
$ ( "#crop-list" ) . innerHTML = rows . length ? rows . join ( "" ) : ` <div class="empty-state"><span>❄</span><h3>No mandatory field crops</h3><p>Use Winter for the foraging bundle, mines, fishing, and any unfinished animal or artisan goods.</p></div> ` ;
}
function groupProgress ( group ) {
const done = group . items . filter ( entry => state . checks [ entry . id ] ) . length ;
return { done , required : group . required , complete : done >= group . required } ;
}
function bundleTotals ( ) {
return data . bundles . reduce ( ( totals , room ) => room . groups . reduce ( ( inner , group ) => {
const { done , required } = groupProgress ( group ) ;
inner . done += Math . min ( done , required ) ;
inner . required += required ;
return inner ;
} , totals ) , { done : 0 , required : 0 } ) ;
}
2026-10-05 19:10:19 +01:00
function entryStatus ( entry , group ) {
const progress = groupProgress ( group ) ;
return availability . classify ( entry , {
checked : Boolean ( state . checks [ entry . id ] ) ,
chooseGroupComplete : Boolean ( group . choose && progress . complete ) ,
season : state . season
} ) ;
}
function allEntryStatuses ( ) {
return data . bundles . flatMap ( room => room . groups . flatMap ( group =>
group . items . map ( entry => ( { entry , group , status : entryStatus ( entry , group ) } ) )
) ) ;
}
function renderPrioritySummary ( ) {
const entries = allEntryStatuses ( ) ;
const count = filter => entries . filter ( ( { status } ) => availability . matchesFilter ( status , filter ) ) . length ;
$ ( "#needed-now-count" ) . textContent = count ( "now" ) ;
$ ( "#exclusive-count" ) . textContent = count ( "exclusive" ) ;
$ ( "#weather-count" ) . textContent = count ( "weather" ) ;
$ ( "#needed-now-detail" ) . textContent = ` Available in ${ seasonNames [ state . season ] } ` ;
$ ( "#exclusive-detail" ) . textContent = ` Only in ${ seasonNames [ state . season ] } ` ;
}
function seasonList ( seasons ) {
return seasons . map ( season => seasonNames [ season ] ) . join ( " + " ) ;
}
function statusBadges ( status ) {
if ( status . checked ) return "" ;
const badges = [ ] ;
if ( ! status . needed ) badges . push ( ` <span class="status-badge optional">Bundle ready</span> ` ) ;
else if ( status . seasonExclusive ) badges . push ( ` <span class="status-badge exclusive">Only ${ seasonNames [ state . season ] } </span> ` ) ;
else if ( status . inSeason ) badges . push ( ` <span class="status-badge in-season">In season</span> ` ) ;
else if ( status . seasonal ) badges . push ( ` <span class="status-badge later"> ${ seasonList ( status . seasons ) } </span> ` ) ;
if ( status . weather ) badges . push ( ` <span class="status-badge weather ${ status . weather . toLowerCase ( ) } "> ${ status . weather === "Rain" ? "Rain needed" : "Sunny only" } </span> ` ) ;
return badges . length ? ` <span class="item-badges"> ${ badges . join ( "" ) } </span> ` : "" ;
}
2026-10-04 19:27:36 +01:00
function renderBundles ( ) {
const html = data . bundles . map ( room => {
const groups = room . groups . map ( group => {
const progress = groupProgress ( group ) ;
let items = group . items ;
2026-10-05 19:10:19 +01:00
if ( state . filter !== "all" ) items = items . filter ( entry => availability . matchesFilter ( entryStatus ( entry , group ) , state . filter ) ) ;
2026-10-04 19:27:36 +01:00
if ( ! items . length ) return "" ;
return ` <article class="bundle-card ${ progress . complete ? "complete" : "" } ">
< header > < div > < h4 > $ { escapeHtml ( group . name ) } < / h 4 > $ { g r o u p . c h o o s e ? ` < s m a l l > C h o o s e $ { g r o u p . r e q u i r e d } < / s m a l l > ` : " " } < / d i v > < s p a n > $ { M a t h . m i n ( p r o g r e s s . d o n e , p r o g r e s s . r e q u i r e d ) } / $ { p r o g r e s s . r e q u i r e d } < / s p a n > < / h e a d e r >
< div class = "bundle-items" > $ { items . map ( entry => {
const checked = Boolean ( state . checks [ entry . id ] ) ;
2026-10-05 19:10:19 +01:00
const status = entryStatus ( entry , group ) ;
const classes = [ checked ? "complete" : "" , status . needed ? "needed" : "optional" , status . needed && status . availableNow ? "available-now" : "" , status . seasonExclusive ? "season-exclusive" : "" , status . weather ? "weather-specific" : "" ] . filter ( Boolean ) . join ( " " ) ;
2026-10-05 19:54:15 +01:00
return ` <label class="bundle-item ${ classes } "><input type="checkbox" data-check-id=" ${ entry . id } " aria-label="Mark ${ escapeHtml ( entry . label ) } complete" ${ checked ? "checked" : "" } ${ state . saving . has ( entry . id ) ? "disabled" : "" } ><span class="checkmark" aria-hidden="true"></span><span class="item-copy"><strong> ${ escapeHtml ( entry . label ) } </strong><small> ${ escapeHtml ( entry . meta ) } </small> ${ statusBadges ( status ) } </span></label> ` ;
2026-10-04 19:27:36 +01:00
} ) . join ( "" ) } < / d i v >
< / a r t i c l e > ` ;
} ) . filter ( Boolean ) . join ( "" ) ;
if ( ! groups ) return "" ;
const roomGroups = room . groups . map ( groupProgress ) ;
const roomDone = roomGroups . filter ( progress => progress . complete ) . length ;
return ` <section class="room"><div class="room-title"><span> ${ room . icon } </span><div><h3> ${ escapeHtml ( room . room ) } </h3><p> ${ roomDone } of ${ room . groups . length } bundles ready</p></div></div><div class="room-grid"> ${ groups } </div></section> ` ;
} ) . filter ( Boolean ) . join ( "" ) ;
2026-10-05 19:10:19 +01:00
const emptyMessages = {
now : [ "Nothing needed right now" , ` No unfinished bundle items are available from normal sources in ${ seasonNames [ state . season ] } . ` ] ,
exclusive : [ "No season-only items left" , ` You have covered every unfinished item exclusive to ${ seasonNames [ state . season ] } . ` ] ,
weather : [ "No weather windows left" , ` No unfinished fish in ${ seasonNames [ state . season ] } require rain or sun. ` ] ,
open : [ "Community Center covered" , "No required bundle items remain." ] ,
all : [ "Everything in this view is done" , "Switch filters to review the whole Community Center." ]
} ;
const empty = emptyMessages [ state . filter ] || emptyMessages . all ;
$ ( "#bundle-list" ) . innerHTML = html || ` <div class="empty-state"><span>✓</span><h3> ${ empty [ 0 ] } </h3><p> ${ empty [ 1 ] } </p></div> ` ;
renderPrioritySummary ( ) ;
2026-10-04 19:27:36 +01:00
const totals = bundleTotals ( ) ;
const percent = totals . required ? Math . round ( totals . done / totals . required * 100 ) : 0 ;
$ ( "#progress-label" ) . textContent = ` ${ totals . done } of ${ totals . required } essentials ` ;
$ ( "#progress-bar" ) . style . width = ` ${ percent } % ` ;
}
function setSyncStatus ( mode , text ) {
const node = $ ( "#sync-status" ) ;
node . dataset . mode = mode ;
node . lastElementChild . textContent = text ;
}
function toast ( message ) {
const node = $ ( "#toast" ) ;
node . textContent = message ;
node . classList . add ( "show" ) ;
clearTimeout ( toast . timer ) ;
toast . timer = setTimeout ( ( ) => node . classList . remove ( "show" ) , 2600 ) ;
}
async function loadState ( { quiet = false } = { } ) {
if ( ! quiet ) setSyncStatus ( "loading" , "Connecting…" ) ;
try {
const response = await fetch ( "/api/state" , { cache : "no-store" } ) ;
if ( ! response . ok ) throw new Error ( ` State request failed ( ${ response . status } ) ` ) ;
const snapshot = await response . json ( ) ;
2026-10-04 20:19:03 +01:00
const merged = sync . mergeSnapshot ( state . checks , state . saving , state . revision , snapshot ) ;
if ( merged . applied ) {
state . checks = merged . checks ;
state . revision = merged . revision ;
renderToday ( ) ;
2026-10-04 19:27:36 +01:00
renderCrops ( ) ;
renderBundles ( ) ;
}
setSyncStatus ( "ready" , "Shared progress synced" ) ;
} catch ( error ) {
console . error ( error ) ;
setSyncStatus ( "error" , "Sync unavailable" ) ;
}
}
async function updateCheck ( itemId , checked ) {
const previous = Boolean ( state . checks [ itemId ] ) ;
state . checks [ itemId ] = checked ;
state . saving . add ( itemId ) ;
2026-10-04 20:19:03 +01:00
renderToday ( ) ;
2026-10-04 19:27:36 +01:00
renderCrops ( ) ;
renderBundles ( ) ;
setSyncStatus ( "loading" , "Saving…" ) ;
try {
const response = await fetch ( "/api/checks" , {
method : "POST" ,
headers : { "Content-Type" : "application/json" } ,
body : JSON . stringify ( { itemId , checked } )
} ) ;
if ( ! response . ok ) throw new Error ( ` Save failed ( ${ response . status } ) ` ) ;
const result = await response . json ( ) ;
2026-10-04 20:19:03 +01:00
const confirmed = sync . confirmWrite ( state . checks , state . revision , result ) ;
state . checks = confirmed . checks ;
state . revision = confirmed . revision ;
2026-10-04 19:27:36 +01:00
setSyncStatus ( "ready" , "Shared progress synced" ) ;
} catch ( error ) {
console . error ( error ) ;
state . checks [ itemId ] = previous ;
setSyncStatus ( "error" , "Couldn’ t save" ) ;
toast ( "That change wasn’ t saved. Your previous shared state is restored." ) ;
2026-10-04 20:19:03 +01:00
void loadState ( { quiet : true } ) ;
2026-10-04 19:27:36 +01:00
} finally {
state . saving . delete ( itemId ) ;
2026-10-04 20:19:03 +01:00
renderToday ( ) ;
2026-10-04 19:27:36 +01:00
renderCrops ( ) ;
renderBundles ( ) ;
}
}
function installWebMcp ( ) {
const context = document . modelContext ;
if ( ! context ? . registerTool ) return ;
const knownIds = new Set ( data . bundles . flatMap ( room => room . groups . flatMap ( group => group . items . map ( entry => entry . id ) ) ) ) ;
void Promise . resolve ( context . registerTool ( {
name : "get_valley_progress" ,
title : "Get Valley progress" ,
description : "Read the currently selected game date and shared Community Center checklist progress." ,
inputSchema : { type : "object" , properties : { } , additionalProperties : false } ,
annotations : { readOnlyHint : true , untrustedContentHint : false } ,
execute : ( ) => ( { year : state . year , season : state . season , day : state . day , progress : bundleTotals ( ) , completedItemIds : Object . keys ( state . checks ) . filter ( id => state . checks [ id ] ) } )
} ) ) . catch ( console . error ) ;
void Promise . resolve ( context . registerTool ( {
name : "set_valley_checklist_item" ,
title : "Update Valley checklist" ,
description : "Check or uncheck one known Community Center item in the shared household plan." ,
inputSchema : { type : "object" , properties : { itemId : { type : "string" } , checked : { type : "boolean" } } , required : [ "itemId" , "checked" ] , additionalProperties : false } ,
annotations : { readOnlyHint : false , untrustedContentHint : false } ,
async execute ( input ) {
if ( ! input || ! knownIds . has ( input . itemId ) || typeof input . checked !== "boolean" ) throw new Error ( "Unknown checklist item or invalid checked value" ) ;
await updateCheck ( input . itemId , input . checked ) ;
return { itemId : input . itemId , checked : Boolean ( state . checks [ input . itemId ] ) , revision : state . revision } ;
}
} ) ) . catch ( console . error ) ;
}
document . addEventListener ( "click" , event => {
2026-10-05 20:07:28 +01:00
const yearButton = event . target . closest ( "button[data-year]" ) ;
2026-10-04 19:27:36 +01:00
if ( yearButton ) setDate ( { year : Number ( yearButton . dataset . year ) } ) ;
2026-10-05 20:07:28 +01:00
const seasonButton = event . target . closest ( "button[data-season]" ) ;
2026-10-04 19:27:36 +01:00
if ( seasonButton ) setDate ( { season : seasonButton . dataset . season } ) ;
2026-10-05 20:07:28 +01:00
const dayButton = event . target . closest ( "button[data-day]" ) ;
2026-10-04 19:27:36 +01:00
if ( dayButton ) setDate ( { day : Number ( dayButton . dataset . day ) } ) ;
2026-10-05 20:07:28 +01:00
const filterButton = event . target . closest ( "button[data-filter]" ) ;
2026-10-04 19:27:36 +01:00
if ( filterButton ) {
state . filter = filterButton . dataset . filter ;
2026-10-05 19:10:19 +01:00
$$ ( "[data-filter]" ) . forEach ( button => button . setAttribute ( "aria-pressed" , String ( button . dataset . filter === state . filter ) ) ) ;
2026-10-04 19:27:36 +01:00
renderBundles ( ) ;
}
const navLink = event . target . closest ( ".section-nav a" ) ;
if ( navLink ) {
$$ ( ".section-nav a" ) . forEach ( link => link . classList . toggle ( "active" , link === navLink ) ) ;
}
} ) ;
document . addEventListener ( "change" , event => {
const input = event . target . closest ( "[data-check-id]" ) ;
if ( input ) updateCheck ( input . dataset . checkId , input . checked ) ;
} ) ;
$ ( "#previous-day" ) . addEventListener ( "click" , ( ) => moveDay ( - 1 ) ) ;
$ ( "#next-day" ) . addEventListener ( "click" , ( ) => moveDay ( 1 ) ) ;
renderDate ( ) ;
renderToday ( ) ;
renderCrops ( ) ;
renderBundles ( ) ;
loadState ( ) ;
setInterval ( ( ) => loadState ( { quiet : true } ) , 4000 ) ;
installWebMcp ( ) ;
} ) ( ) ;