@@ -394,143 +394,147 @@ func AudioReader(user User, metadata ReaderMetadata) templ.Component {
return templ_7745c5c3_Err
return templ_7745c5c3_Err
}
}
}
}
templ_7745c5c3_Err = templruntime . WriteString ( templ_7745c5c3_Buffer , 32 , "</select></div></div></div></span></div><!-- Cover + title --><div class=\"flex flex-col items-center gap-3 text-center\"><div class=\"w-48 h-72 md:w-56 md:h-80 rounded-2xl overflow-hidden shadow-lg shrink-0 bg-cover bg-center\" style=\"background-color: var(--surface-hover); \">" )
templ_7745c5c3_Err = templruntime . WriteString ( templ_7745c5c3_Buffer , 32 , "</select></div><!-- Resume rewind — player-side control of the\n\t\t\t\t\t\t\t account setting (the profile page keeps\n\t\t\t\t\t\t\t its select; same PUT, applied live). --><div class=\"flex items-center justify-between gap-3 px-1 py-2 rounded-lg\"><span class=\"text-sm\" style=\"color: var(--text-secondary);\">Resume rewind</span> <select class=\"text-xs rounded-md px-1.5 py-1\" style=\"background-color: var(--surface-hover); color: var(--text-primary); border: 1px solid var(--border);\" aria-label=\"Resume rewind\" @change=\"applyResumeRewindFromSelect($event) \">" )
if templ_7745c5c3_Err != nil {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
return templ_7745c5c3_Err
}
}
if metadata . CoverImagePath ! = "" {
for _ , secs : = range ResumeRewindOptions {
templ_7745c5c3_Err = templruntime . WriteString ( templ_7745c5c3_Buffer , 33 , "<img src =\"" )
templ_7745c5c3_Err = templruntime . WriteString ( templ_7745c5c3_Buffer , 33 , "<option value =\"" )
if templ_7745c5c3_Err != nil {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
return templ_7745c5c3_Err
}
}
var templ_7745c5c3_Var20 string
var templ_7745c5c3_Var20 string
templ_7745c5c3_Var20 , templ_7745c5c3_Err = templ . ResolveAttributeValue ( metadata . CoverImagePath )
templ_7745c5c3_Var20 , templ_7745c5c3_Err = templ . ResolveAttributeValue ( fmt . Sprintf ( "%d" , secs ) )
if templ_7745c5c3_Err != nil {
if templ_7745c5c3_Err != nil {
return templ . Error { Err : templ_7745c5c3_Err , FileName : ` audio_reader.templ ` , Line : 268 , Col : 36 }
return templ . Error { Err : templ_7745c5c3_Err , FileName : ` audio_reader.templ ` , Line : 271 , Col : 41 }
}
}
_ , templ_7745c5c3_Err = templ_7745c5c3_Buffer . WriteString ( templ_7745c5c3_Var20 )
_ , templ_7745c5c3_Err = templ_7745c5c3_Buffer . WriteString ( templ_7745c5c3_Var20 )
if templ_7745c5c3_Err != nil {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime . WriteString ( templ_7745c5c3_Buffer , 34 , "\" alt =\"" )
templ_7745c5c3_Err = templruntime . WriteString ( templ_7745c5c3_Buffer , 34 , "\" :selected =\"" )
if templ_7745c5c3_Err != nil {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
return templ_7745c5c3_Err
}
}
var templ_7745c5c3_Var21 string
var templ_7745c5c3_Var21 string
templ_7745c5c3_Var21 , templ_7745c5c3_Err = templ . ResolveAttributeValue ( metadata . Title )
templ_7745c5c3_Var21 , templ_7745c5c3_Err = templ . ResolveAttributeValue ( fmt . Sprintf ( "resumeRewind === %d" , secs ) )
if templ_7745c5c3_Err != nil {
if templ_7745c5c3_Err != nil {
return templ . Error { Err : templ_7745c5c3_Err , FileName : ` audio_reader.templ ` , Line : 269 , Col : 27 }
return templ . Error { Err : templ_7745c5c3_Err , FileName : ` audio_reader.templ ` , Line : 272 , Col : 62 }
}
}
_ , templ_7745c5c3_Err = templ_7745c5c3_Buffer . WriteString ( templ_7745c5c3_Var21 )
_ , templ_7745c5c3_Err = templ_7745c5c3_Buffer . WriteString ( templ_7745c5c3_Var21 )
if templ_7745c5c3_Err != nil {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime . WriteString ( templ_7745c5c3_Buffer , 35 , "\" class=\"w-full h-full object-cover\" data-fallback=\"hide\" >" )
templ_7745c5c3_Err = templruntime . WriteString ( templ_7745c5c3_Buffer , 35 , "\">" )
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = templruntime . WriteString ( templ_7745c5c3_Buffer , 36 , "<span class=\"text-5xl flex items-center justify-center h-full\" style=\"color: var(--text-secondary);\">🎧</span>" )
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime . WriteString ( templ_7745c5c3_Buffer , 37 , "</div><div class=\"max-w-md px-4\"><h1 class=\"font-semibold text-lg leading-snug line-clamp-2\" style=\"color: var(--text-primary);\">" )
if templ_7745c5c3_Err != nil {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
return templ_7745c5c3_Err
}
}
var templ_7745c5c3_Var22 string
var templ_7745c5c3_Var22 string
templ_7745c5c3_Var22 , templ_7745c5c3_Err = templ . JoinStringErrs ( metadata . Title )
templ_7745c5c3_Var22 , templ_7745c5c3_Err = templ . JoinStringErrs ( ResumeRewindLabel ( secs ) )
if templ_7745c5c3_Err != nil {
if templ_7745c5c3_Err != nil {
return templ . Error { Err : templ_7745c5c3_Err , FileName : ` audio_reader.templ ` , Line : 278 , Col : 117 }
return templ . Error { Err : templ_7745c5c3_Err , FileName : ` audio_reader.templ ` , Line : 273 , Col : 35 }
}
}
_ , templ_7745c5c3_Err = templ_7745c5c3_Buffer . WriteString ( templ . EscapeString ( templ_7745c5c3_Var22 ) )
_ , templ_7745c5c3_Err = templ_7745c5c3_Buffer . WriteString ( templ . EscapeString ( templ_7745c5c3_Var22 ) )
if templ_7745c5c3_Err != nil {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime . WriteString ( templ_7745c5c3_Buffer , 38 , "</h1><p class=\"text-sm mt-0.5 truncate\" style=\"color: var(--text-secondary);\" x-text=\"chapterTitle()\">—</p></div></div><!-- Scrubber + transports --><div class=\"w-full max-w-xl flex flex-col gap-2\"><div class=\"flex items-center gap-2 text-xs tabular-nums\" style=\"color: var(--text-secondary);\"><span class=\"inline-block min-w-[4.75rem]\" x-text=\"fmtTime(scrubbing ? scrubValue : currentTime)\">0:00</span><div id=\"scrub-area\" class=\"relative flex-1 h-6 flex items-center cursor-pointer\" style=\"touch-action: none;\" @pointerdown=\"scrubPointerDown($event)\" @pointermove=\"scrubPointerMove($event)\" @pointerup=\"scrubPointerUp($event)\" @pointercancel=\"scrubPointerUp($event)\" @pointerleave=\"scrubPointerLeave()\" @wheel.prevent=\"onScrubWheel($event)\"><!-- Base track --><div class=\"absolute inset-x-0 top-1/2 -translate-y-1/2 h-1.5 rounded-full pointer-events-none\" style=\"background-color: var(--surface-hover);\"></div><!-- Elapsed fill --><div class=\"absolute top-1/2 -translate-y-1/2 h-1.5 rounded-full pointer-events-none\" :style=\"'left:0;width:' + scrubFillPct() + '%;background-color:var(--accent);'\"></div><!-- Buffered span: the ACTIVE track's element buffer only --><template x-if=\"bufferSpan()\"><div class=\"absolute top-1/2 -translate-y-1/2 h-1.5 rounded-full pointer-events-none\" :style=\"'left:' + bufferSpan().left + '%;width:' + bufferSpan().width + '%;background-color:var(--accent-muted);'\"></div></template><!-- Chapter ticks: density-decimated, current chapter tinted --><div id=\"scrub-ticks\" class=\"absolute inset-0 pointer-events-none\"><template x-for=\"idx in tickIndices\" :key=\"'tick' + idx\"><div class=\"absolute top-1/2 -translate-x-1/2 -translate-y-1/2 h-2 w-0.5 rounded-full\" :class=\"currentChapterIdx === idx ? 'opacity-100' : 'opacity-40'\" :style=\"'left:' + (chapters[idx].start / (chaptersDuration() || 1) * 100) + '%;background-color:' + (currentChapterIdx === idx ? 'var(--accent)' : 'var(--text-secondary)') + ';'\"></div></template></div><!-- §9 magnet: the line a click would land on (hover\n\t\t\t\t\t\t inside the pixel radius locks the tooltip here) --><div x-show=\"hoverSnapped && !scrubbing\" x-cloak class=\"absolute top-1/2 -translate-x-1/2 -translate-y-1/2 h-4 w-px rounded-full pointer-events-none\" :style=\"'left:' + hoverPct + '%;background-color:var(--accent);'\"></div><input id=\"audio-scrub\" type=\"range\" min=\"0\" step=\"0.1\" :max=\"chaptersDuration() || 1\" :value=\"scrubbing ? scrubValue : currentTime\" class=\"relative w-full\" @input=\"onScrubInput($event)\" @change=\"commitScrub()\" aria-label=\"Seek position\"><!-- Hover tooltip: time + chapter under the pointer --><div x-show=\"hoverSeconds !== null && !scrubbing\" x-cloak class=\"absolute bottom-full mb-1 -translate-x-1/2 whitespace-nowrap rounded-md px-2 py-1 text-[11px] shadow-lg pointer-events-none\" :style=\"'left:' + hoverPct + '%;background-color:var(--bg-secondary);border:1px solid var(--border);color:var(--text-primary);'\"><span x-text=\"hoverSeconds !== null ? fmtTime(hoverSeconds) : ''\"></span> <span style=\"color: var(--text-secondary);\" x-text=\"hoverSeconds !== null ? ' — ' + chapterTitleAt(hoverSeconds) : ''\"></span></div><!-- Precision rail (§9): Shift+drag on mouse, or a touch\n\t\t\t\t\t\t drag pulled off the bar — ±5 min or tighter, free\n\t\t\t\t\t\t mapping inside. Edge labels self-document the window\n\t\t\t\t\t\t (floats over the transports, page never shifts) --><div x-show=\"railVisible\" x-cloak class=\"absolute top-full mt-1 inset-x-0 h-12 rounded-lg shadow-lg p-1.5 pointer-events-none\" :style=\"'background-color: var(--bg-secondary); border: 1px solid var(--border); z-index: 30;' + (railLiftY ? ' transform: translateY(' + railLiftY + 'px);' : '')\"><div class=\"relative h-full\"><div class=\"absolute inset-x-0 top-1/2 h-px\" style=\"background-color: var(--border);\"></div><template x-for=\"tick in railTickList\" :key=\"'rtick' + tick.start\"><div class=\"absolute top-1 bottom-1 w-px\" :style=\"railTickStyle(tick)\"></div></template><div class=\"absolute top-0 bottom-0 w-0.5 -translate-x-1/2 rounded-full\" :style=\"'left:' + railMarkerPct() + '%;background-color:var(--accent);'\"></div><div class=\"absolute left-1 top-0.5 text-[10px] tabular-nums whitespace-nowrap\" style=\"color: var(--text-primary);\" x-text=\"fmtTime(scrubValue) + ' — ' + chapterTitleAt(scrubValue)\"></div><div class=\"absolute left-1 bottom-0.5 text-[9px] tabular-nums\" style=\"color: var(--text-secondary);\" x-text=\"fmtTime(railFrom)\"></div><div class=\"absolute right-1 bottom-0.5 text-[9px] tabular-nums\" style=\"color: var(--text-secondary);\" x-text=\"fmtTime(railTo)\"></div></div></div></div><button type=\"button\" class=\"inline-block min-w-[4.75rem] text-right cursor-pointer\" x-text=\"endTimeText()\" :aria-label=\"endTimeAria()\" :title=\"endTimeAria()\" @click=\" cycleEndT Line truncated
templ_7745c5c3_Err = templruntime . WriteString ( templ_7745c5c3_Buffer , 36 , "</option>" )
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if metadata . PodcastSequence != nil {
if metadata . PodcastSequence . PrevURL != "" {
templ_7745c5c3_Err = templruntime . WriteString ( templ_7745c5c3_Buffer , 39 , "<button type=\"button\" @click=\"goToPrevEpisode()\" class=\"rounded-full p-2.5 hover:bg-surface-hover transition-colors\" aria-label=\"Previous episode\" title=\"Previous episode\" style=\"color: var(--text-primary);\">" )
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = audioStepPrevIcon ( ) . Render ( ctx , templ_7745c5c3_Buffer )
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime . WriteString ( templ_7745c5c3_Buffer , 40 , "</button> " )
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = templruntime . WriteString ( templ_7745c5c3_Buffer , 41 , "<span class=\"rounded-full p-2.5 opacity-30 cursor-not-allowed\" aria-disabled=\"true\" title=\"No previous episode\">" )
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = audioStepPrevIcon ( ) . Render ( ctx , templ_7745c5c3_Buffer )
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime . WriteString ( templ_7745c5c3_Buffer , 42 , "</span> " )
if templ_7745c5c3_Err != nil {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
return templ_7745c5c3_Err
}
}
}
}
} else {
templ_7745c5c3_Err = templruntime . WriteString ( templ_7745c5c3_Buffer , 37 , "</select></div></div></div></span></div><!-- Cover + title --><div class=\"flex flex-col items-center gap-3 text-center\"><div class=\"w-48 h-72 md:w-56 md:h-80 rounded-2xl overflow-hidden shadow-lg shrink-0 bg-cover bg-center\" style=\"background-color: var(--surface-hover);\">" )
templ_7745c5c3_Err = templruntime . WriteString ( templ_7745c5c3_Buffer , 43 , "<button type=\"button\" @click=\"stepChapter(-1)\" class=\"rounded-full p-2.5 hover:bg-surface-hover transition-colors\" aria-label=\"Previous chapter\" title=\"Previous chapter\" style=\"color: var(--text-primary);\">" )
if templ_7745c5c3_Err != nil {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = audioStepPrevIcon ( ) . Render ( ctx , templ_7745c5c3_Buffer )
if metadata . CoverImagePath != "" {
if templ_7745c5c3_Err != nil {
templ_7745c5c3_Err = templruntime . WriteString ( templ_7745c5c3_Buffer , 38 , "<img src=\"" )
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime . WriteString ( templ_7745c5c3_Buffer , 44 , "</button> " )
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime . WriteString ( templ_7745c5c3_Buffer , 45 , "<button type=\"button\" @click=\"skip(-1)\" class=\"relative rounded-full h-14 w-14 grid place-items-center hover:bg-surface-hover transition-colors\" :aria-label=\"'Skip back ' + skipSeconds + 's'\" :title=\"'Skip back ' + skipSeconds + 's'\" style=\"color: var(--text-primary);\"><svg width=\"52\" height=\"52\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8\"></path> <path d=\"M3 3v5h5\"></path></svg> <span class=\"absolute inset-0 grid place-items-center text-[11px] font-semibold pointer-events-none\"><span x-text=\"skipSeconds\">10</span></span></button> <button id=\"audio-play\" type=\"button\" @click=\"togglePlay()\" class=\"rounded-full h-16 w-16 grid place-items-center\" style=\"background-color: var(--accent); color: var(--bg-primary);\" aria-label=\"Play or pause\"><span x-show=\"!playing\" class=\"grid place-items-center\"><svg width=\"28\" height=\"28\" viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden=\"true\" style=\"margin-left: 2px;\"><path d=\"M8 5.2v13.6L19.2 12z\"></path></svg></span> <span x-show=\"playing\" class=\"grid place-items-center\" x-cloak><svg width=\"28\" height=\"28\" viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden=\"true\"><rect x=\"6.2\" y=\"4.5\" width=\"4.2\" height=\"15\" rx=\"1.7\"></rect> <rect x=\"13.6\" y=\"4.5\" width=\"4.2\" height=\"15\" rx=\"1.7\"></rect></svg></span></button> <button type=\"button\" @click=\"skip(1)\" class=\"relative rounded-full h-14 w-14 grid place-items-center hover:bg-surface-hover transition-colors\" :aria-label=\"'Skip forward ' + skipSeconds + 's'\" :title=\"'Skip forward ' + skipSeconds + 's'\" style=\"color: var(--text-primary);\"><svg width=\"52\" height=\"52\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8\"></path> <path d=\"M21 3v5h-5\"></path></svg> <span class=\"absolute inset-0 grid place-items-center text-[11px] font-semibold pointer-events-none\"><span x-text=\"skipSeconds\">10</span></span></button><!-- Outer right button: books = next chapter; podcasts = next\n\t\t\t\t\t EPISODE (disabled at the end of the feed). -->" )
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if metadata . PodcastSequence != nil {
if metadata . PodcastSequence . NextURL != "" {
templ_7745c5c3_Err = templruntime . WriteString ( templ_7745c5c3_Buffer , 46 , "<button type=\"button\" @click=\"goToNextEpisode(false)\" class=\"rounded-full p-2.5 hover:bg-surface-hover transition-colors\" aria-label=\"" )
if templ_7745c5c3_Err != nil {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
return templ_7745c5c3_Err
}
}
var templ_7745c5c3_Var23 string
var templ_7745c5c3_Var23 string
templ_7745c5c3_Var23 , templ_7745c5c3_Err = templ . ResolveAttributeValue ( "Next episode: " + metadata . PodcastSequence . NextTitle )
templ_7745c5c3_Var23 , templ_7745c5c3_Err = templ . ResolveAttributeValue ( metadata . CoverImagePath )
if templ_7745c5c3_Err != nil {
if templ_7745c5c3_Err != nil {
return templ . Error { Err : templ_7745c5c3_Err , FileName : ` audio_reader.templ ` , Line : 471 , Col : 74 }
return templ . Error { Err : templ_7745c5c3_Err , FileName : ` audio_reader.templ ` , Line : 287 , Col : 36 }
}
}
_ , templ_7745c5c3_Err = templ_7745c5c3_Buffer . WriteString ( templ_7745c5c3_Var23 )
_ , templ_7745c5c3_Err = templ_7745c5c3_Buffer . WriteString ( templ_7745c5c3_Var23 )
if templ_7745c5c3_Err != nil {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime . WriteString ( templ_7745c5c3_Buffer , 47 , "\" title =\"" )
templ_7745c5c3_Err = templruntime . WriteString ( templ_7745c5c3_Buffer , 39 , "\" alt =\"" )
if templ_7745c5c3_Err != nil {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
return templ_7745c5c3_Err
}
}
var templ_7745c5c3_Var24 string
var templ_7745c5c3_Var24 string
templ_7745c5c3_Var24 , templ_7745c5c3_Err = templ . ResolveAttributeValue ( "Next: " + metadata . PodcastSequence . NextTitle )
templ_7745c5c3_Var24 , templ_7745c5c3_Err = templ . ResolveAttributeValue ( metadata . Title )
if templ_7745c5c3_Err != nil {
if templ_7745c5c3_Err != nil {
return templ . Error { Err : templ_7745c5c3_Err , FileName : ` audio_reader.templ ` , Line : 472 , Col : 61 }
return templ . Error { Err : templ_7745c5c3_Err , FileName : ` audio_reader.templ ` , Line : 288 , Col : 27 }
}
}
_ , templ_7745c5c3_Err = templ_7745c5c3_Buffer . WriteString ( templ_7745c5c3_Var24 )
_ , templ_7745c5c3_Err = templ_7745c5c3_Buffer . WriteString ( templ_7745c5c3_Var24 )
if templ_7745c5c3_Err != nil {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime . WriteString ( templ_7745c5c3_Buffer , 48 , "\" style=\"color: var(--text-primary); \">" )
templ_7745c5c3_Err = templruntime . WriteString ( templ_7745c5c3_Buffer , 40 , "\" class=\"w-full h-full object-cover\" data-fallback=\"hide \">" )
if templ_7745c5c3_Err != nil {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = audioStepNextIcon ( ) . Render ( ctx , templ_7745c5c3_Buffer )
} else {
templ_7745c5c3_Err = templruntime . WriteString ( templ_7745c5c3_Buffer , 41 , "<span class=\"text-5xl flex items-center justify-center h-full\" style=\"color: var(--text-secondary);\">🎧</span>" )
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime . WriteString ( templ_7745c5c3_Buffer , 42 , "</div><div class=\"max-w-md px-4\"><h1 class=\"font-semibold text-lg leading-snug line-clamp-2\" style=\"color: var(--text-primary);\">" )
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var25 string
templ_7745c5c3_Var25 , templ_7745c5c3_Err = templ . JoinStringErrs ( metadata . Title )
if templ_7745c5c3_Err != nil {
return templ . Error { Err : templ_7745c5c3_Err , FileName : ` audio_reader.templ ` , Line : 297 , Col : 117 }
}
_ , templ_7745c5c3_Err = templ_7745c5c3_Buffer . WriteString ( templ . EscapeString ( templ_7745c5c3_Var25 ) )
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime . WriteString ( templ_7745c5c3_Buffer , 43 , "</h1><p class=\"text-sm mt-0.5 truncate\" style=\"color: var(--text-secondary);\" x-text=\"chapterTitle()\">—</p></div></div><!-- Scrubber + transports --><div class=\"w-full max-w-xl flex flex-col gap-2\"><div class=\"flex items-center gap-2 text-xs tabular-nums\" style=\"color: var(--text-secondary);\"><span class=\"inline-block min-w-[4.75rem]\" x-text=\"fmtTime(scrubbing ? scrubValue : currentTime)\">0:00</span><div id=\"scrub-area\" class=\"relative flex-1 h-6 flex items-center cursor-pointer\" style=\"touch-action: none;\" @pointerdown=\"scrubPointerDown($event)\" @pointermove=\"scrubPointerMove($event)\" @pointerup=\"scrubPointerUp($event)\" @pointercancel=\"scrubPointerUp($event)\" @pointerleave=\"scrubPointerLeave()\" @wheel.prevent=\"onScrubWheel($event)\"><!-- Base track --><div class=\"absolute inset-x-0 top-1/2 -translate-y-1/2 h-1.5 rounded-full pointer-events-none\" style=\"background-color: var(--surface-hover);\"></div><!-- Elapsed fill --><div class=\"absolute top-1/2 -translate-y-1/2 h-1.5 rounded-full pointer-events-none\" :style=\"'left:0;width:' + scrubFillPct() + '%;background-color:var(--accent);'\"></div><!-- Buffered span: the ACTIVE track's element buffer only --><template x-if=\"bufferSpan()\"><div class=\"absolute top-1/2 -translate-y-1/2 h-1.5 rounded-full pointer-events-none\" :style=\"'left:' + bufferSpan().left + '%;width:' + bufferSpan().width + '%;background-color:var(--accent-muted);'\"></div></template><!-- Chapter ticks: density-decimated, current chapter tinted --><div id=\"scrub-ticks\" class=\"absolute inset-0 pointer-events-none\"><template x-for=\"idx in tickIndices\" :key=\"'tick' + idx\"><div class=\"absolute top-1/2 -translate-x-1/2 -translate-y-1/2 h-2 w-0.5 rounded-full\" :class=\"currentChapterIdx === idx ? 'opacity-100' : 'opacity-40'\" :style=\"'left:' + (chapters[idx].start / (chaptersDuration() || 1) * 100) + '%;background-color:' + (currentChapterIdx === idx ? 'var(--accent)' : 'var(--text-secondary)') + ';'\"></div></template></div><!-- §9 magnet: the line a click would land on (hover\n\t\t\t\t\t\t inside the pixel radius locks the tooltip here) --><div x-show=\"hoverSnapped && !scrubbing\" x-cloak class=\"absolute top-1/2 -translate-x-1/2 -translate-y-1/2 h-4 w-px rounded-full pointer-events-none\" :style=\"'left:' + hoverPct + '%;background-color:var(--accent);'\"></div><input id=\"audio-scrub\" type=\"range\" min=\"0\" step=\"0.1\" :max=\"chaptersDuration() || 1\" :value=\"scrubbing ? scrubValue : currentTime\" class=\"relative w-full\" @input=\"onScrubInput($event)\" @change=\"commitScrub()\" aria-label=\"Seek position\"><!-- Hover tooltip: time + chapter under the pointer --><div x-show=\"hoverSeconds !== null && !scrubbing\" x-cloak class=\"absolute bottom-full mb-1 -translate-x-1/2 whitespace-nowrap rounded-md px-2 py-1 text-[11px] shadow-lg pointer-events-none\" :style=\"'left:' + hoverPct + '%;background-color:var(--bg-secondary);border:1px solid var(--border);color:var(--text-primary);'\"><span x-text=\"hoverSeconds !== null ? fmtTime(hoverSeconds) : ''\"></span> <span style=\"color: var(--text-secondary);\" x-text=\"hoverSeconds !== null ? ' — ' + chapterTitleAt(hoverSeconds) : ''\"></span></div><!-- Precision rail (§9): Shift+drag on mouse, or a touch\n\t\t\t\t\t\t drag pulled off the bar — ±5 min or tighter, free\n\t\t\t\t\t\t mapping inside. Edge labels self-document the window\n\t\t\t\t\t\t (floats over the transports, page never shifts) --><div x-show=\"railVisible\" x-cloak class=\"absolute top-full mt-1 inset-x-0 h-12 rounded-lg shadow-lg p-1.5 pointer-events-none\" :style=\"'background-color: var(--bg-secondary); border: 1px solid var(--border); z-index: 30;' + (railLiftY ? ' transform: translateY(' + railLiftY + 'px);' : '')\"><div class=\"relative h-full\"><div class=\"absolute inset-x-0 top-1/2 h-px\" style=\"background-color: var(--border);\"></div><template x-for=\"tick in railTickList\" :key=\"'rtick' + tick.start\"><div class=\"absolute top-1 bottom-1 w-px\" :style=\"railTickStyle(tick)\"></div></template><div class=\"absolute top-0 bottom-0 w-0.5 -translate-x-1/2 rounded-full\" :style=\"'left:' + railMarkerPct() + '%;background-color:var(--accent);'\"></div><div class=\"absolute left-1 top-0.5 text-[10px] tabular-nums whitespace-nowrap\" style=\"color: var(--text-primary);\" x-text=\"fmtTime(scrubValue) + ' — ' + chapterTitleAt(scrubValue)\"></div><div class=\"absolute left-1 bottom-0.5 text-[9px] tabular-nums\" style=\"color: var(--text-secondary);\" x-text=\"fmtTime(railFrom)\"></div><div class=\"absolute right-1 bottom-0.5 text-[9px] tabular-nums\" style=\"color: var(--text-secondary);\" x-text=\"fmtTime(railTo)\"></div></div></div></div><button type=\"button\" class=\"inline-block min-w-[4.75rem] text-right cursor-pointer\" x-text=\"endTimeText()\" :aria-label=\"endTimeAria()\" :title=\"endTimeAria()\" @click=\" cycleEndT Line truncated
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if metadata . PodcastSequence != nil {
if metadata . PodcastSequence . PrevURL != "" {
templ_7745c5c3_Err = templruntime . WriteString ( templ_7745c5c3_Buffer , 44 , "<button type=\"button\" @click=\"goToPrevEpisode()\" class=\"rounded-full p-2.5 hover:bg-surface-hover transition-colors\" aria-label=\"Previous episode\" title=\"Previous episode\" style=\"color: var(--text-primary);\">" )
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = audioStepPrevIcon ( ) . Render ( ctx , templ_7745c5c3_Buffer )
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime . WriteString ( templ_7745c5c3_Buffer , 45 , "</button> " )
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = templruntime . WriteString ( templ_7745c5c3_Buffer , 46 , "<span class=\"rounded-full p-2.5 opacity-30 cursor-not-allowed\" aria-disabled=\"true\" title=\"No previous episode\">" )
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = audioStepPrevIcon ( ) . Render ( ctx , templ_7745c5c3_Buffer )
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime . WriteString ( templ_7745c5c3_Buffer , 47 , "</span> " )
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
} else {
templ_7745c5c3_Err = templruntime . WriteString ( templ_7745c5c3_Buffer , 48 , "<button type=\"button\" @click=\"stepChapter(-1)\" class=\"rounded-full p-2.5 hover:bg-surface-hover transition-colors\" aria-label=\"Previous chapter\" title=\"Previous chapter\" style=\"color: var(--text-primary);\">" )
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = audioStepPrevIcon ( ) . Render ( ctx , templ_7745c5c3_Buffer )
if templ_7745c5c3_Err != nil {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
return templ_7745c5c3_Err
}
}
@@ -538,8 +542,40 @@ func AudioReader(user User, metadata ReaderMetadata) templ.Component {
if templ_7745c5c3_Err != nil {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
return templ_7745c5c3_Err
}
}
} else {
}
templ_7745c5c3_Err = templruntime . WriteString ( templ_7745c5c3_Buffer , 50 , "<span class=\"rounded-full p-2.5 opacity-30 cursor-not-allowed\" aria-disabled=\"true\" title=\"No next episode\" >" )
templ_7745c5c3_Err = templruntime . WriteString ( templ_7745c5c3_Buffer , 50 , "<button type=\"button\" @click=\"skip(-1)\" class=\"relative rounded-full h-14 w-14 grid place-items-center hover:bg-surface-hover transition-colors\" :aria-label=\"'Skip back ' + skipSeconds + 's'\" :title=\"'Skip back ' + skipSeconds + 's'\" style=\"color: var(--text-primary);\"><svg width=\"52\" height=\"52\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8\"></path> <path d=\"M3 3v5h5\"></path></svg> <span class=\"absolute inset-0 grid place-items-center text-[11px] font-semibold pointer-events-none\"><span x-text=\"skipSeconds\">10</span></span></button> <button id=\"audio-play\" type=\"button\" @click=\"togglePlay()\" class=\"rounded-full h-16 w-16 grid place-items-center\" style=\"background-color: var(--accent); color: var(--bg-primary);\" aria-label=\"Play or pause\"><span x-show=\"!playing\" class=\"grid place-items-center\"><svg width=\"28\" height=\"28\" viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden=\"true\" style=\"margin-left: 2px;\"><path d=\"M8 5.2v13.6L19.2 12z\"></path></svg></span> <span x-show=\"playing\" class=\"grid place-items-center\" x-cloak><svg width=\"28\" height=\"28\" viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden=\"true\"><rect x=\"6.2\" y=\"4.5\" width=\"4.2\" height=\"15\" rx=\"1.7\"></rect> <rect x=\"13.6\" y=\"4.5\" width=\"4.2\" height=\"15\" rx=\"1.7\"></rect></svg></span></button> <button type=\"button\" @click=\"skip(1)\" class=\"relative rounded-full h-14 w-14 grid place-items-center hover:bg-surface-hover transition-colors\" :aria-label=\"'Skip forward ' + skipSeconds + 's'\" :title=\"'Skip forward ' + skipSeconds + 's'\" style=\"color: var(--text-primary);\"><svg width=\"52\" height=\"52\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8\"></path> <path d=\"M21 3v5h-5\"></path></svg> <span class=\"absolute inset-0 grid place-items-center text-[11px] font-semibold pointer-events-none\"><span x-text=\"skipSeconds\">10</span></span></button><!-- Outer right button: books = next chapter; podcasts = next\n\t\t\t\t\t EPISODE (disabled at the end of the feed). -- >" )
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if metadata . PodcastSequence != nil {
if metadata . PodcastSequence . NextURL != "" {
templ_7745c5c3_Err = templruntime . WriteString ( templ_7745c5c3_Buffer , 51 , "<button type=\"button\" @click=\"goToNextEpisode(false)\" class=\"rounded-full p-2.5 hover:bg-surface-hover transition-colors\" aria-label=\"" )
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var26 string
templ_7745c5c3_Var26 , templ_7745c5c3_Err = templ . ResolveAttributeValue ( "Next episode: " + metadata . PodcastSequence . NextTitle )
if templ_7745c5c3_Err != nil {
return templ . Error { Err : templ_7745c5c3_Err , FileName : ` audio_reader.templ ` , Line : 490 , Col : 74 }
}
_ , templ_7745c5c3_Err = templ_7745c5c3_Buffer . WriteString ( templ_7745c5c3_Var26 )
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime . WriteString ( templ_7745c5c3_Buffer , 52 , "\" title=\"" )
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var27 string
templ_7745c5c3_Var27 , templ_7745c5c3_Err = templ . ResolveAttributeValue ( "Next: " + metadata . PodcastSequence . NextTitle )
if templ_7745c5c3_Err != nil {
return templ . Error { Err : templ_7745c5c3_Err , FileName : ` audio_reader.templ ` , Line : 491 , Col : 61 }
}
_ , templ_7745c5c3_Err = templ_7745c5c3_Buffer . WriteString ( templ_7745c5c3_Var27 )
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime . WriteString ( templ_7745c5c3_Buffer , 53 , "\" style=\"color: var(--text-primary);\">" )
if templ_7745c5c3_Err != nil {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
return templ_7745c5c3_Err
}
}
@@ -547,13 +583,12 @@ func AudioReader(user User, metadata ReaderMetadata) templ.Component {
if templ_7745c5c3_Err != nil {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime . WriteString ( templ_7745c5c3_Buffer , 51 , "</spa n>" )
templ_7745c5c3_Err = templruntime . WriteString ( templ_7745c5c3_Buffer , 54 , "</butto n>" )
if templ_7745c5c3_Err != nil {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
return templ_7745c5c3_Err
}
}
}
} else {
} else {
templ_7745c5c3_Err = templruntime . WriteString ( templ_7745c5c3_Buffer , 52 , "<button type=\"button\" @click=\"stepChapter(1)\" class=\"rounded-full p-2.5 hover:bg-surface-hover transition-colors\" aria-label=\"Next chapter \" title=\"Next chapter\" style=\"color: var(--text-primary); \">" )
templ_7745c5c3_Err = templruntime . WriteString ( templ_7745c5c3_Buffer , 55 , "<span class=\"rounded-full p-2.5 opacity-30 cursor-not-allowed\" aria-disabled=\"true \" title=\"No n ext episode \">" )
if templ_7745c5c3_Err != nil {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
return templ_7745c5c3_Err
}
}
@@ -561,12 +596,26 @@ func AudioReader(user User, metadata ReaderMetadata) templ.Component {
if templ_7745c5c3_Err != nil {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime . WriteString ( templ_7745c5c3_Buffer , 53 , "</butto n>" )
templ_7745c5c3_Err = templruntime . WriteString ( templ_7745c5c3_Buffer , 56 , "</spa n>" )
if templ_7745c5c3_Err != nil {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
return templ_7745c5c3_Err
}
}
}
}
templ_7745c5c3_Err = templruntime . WriteString ( templ_7745c5c3_Buffer , 54 , "</div></div><!-- Settings strip: Skip ▾ · Speed ▾ · Bookmark · Volume ▾ --><div class=\"w-full max-w-xl flex items-center justify-center gap-2\"><div class=\"relative\" @click.outside=\"skipOpen = false\"><button type=\"button\" @click=\"skipOpen = !skipOpen\" class=\"text-xs px-3 py-2 rounded-lg hover:bg-surface-hover transition-colors inline-flex items-center gap-1.5\" :aria-label=\"'Skip duration, currently ' + skipSeconds + ' seconds'\" style=\"color: var(--text-secondary);\"><span>Skip</span> <span class=\"font-semibold\" style=\"color: var(--text-primary);\" x-text=\"skipSeconds + 's'\">10s</span> <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"m6 9 6 6 6-6\"></path></svg></button><div x-show=\"skipOpen\" x-cloak class=\"absolute bottom-full mb-2 left-1/2 -translate-x-1/2 rounded-xl shadow-lg p-1.5 flex flex-col min-w-[7.5rem]\" style=\"background-color: var(--bg-secondary); border: 1px solid var(--border); z-index: 30;\"><template x-for=\"secs in skipChoices\" :key=\"'skip' + secs\"><button type=\"button\" @click=\"pickSkip(secs)\" class=\"w-full text-left px-3 py-1.5 rounded-md text-sm\" :class=\"isSkipActive(secs) ? 'font-semibold' : ''\" :style=\"isSkipActive(secs) ? 'background-color: var(--accent-muted); color: var(--text-primary);' : 'color: var(--text-secondary);'\" x-text=\"secs + ' seconds'\"></button></template></div></div><div class=\"relative\" @click.outside=\"speedOpen = false\"><button type=\"button\" @click=\"speedOpen = !speedOpen\" class=\"text-xs px-3 py-2 rounded-lg hover:bg-surface-hover transition-colors inline-flex items-center gap-1.5\" :aria-label=\"'Playback speed, currently ' + speed + ' times'\" style=\"color: var(--text-secondary);\"><span>Speed</span> <span class=\"font-semibold\" style=\"color: var(--text-primary);\" x-text=\"speed + '× '\">1× </span> <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"m6 9 6 6 6-6\"></path></svg></button><div x-show=\"speedOpen\" x-cloak class=\"absolute bottom-full mb-2 left-1/2 -translate-x-1/2 rounded-xl shadow-lg p-1.5 flex flex-col min-w-[7rem]\" style=\"background-color: var(--bg-secondary); border: 1px solid var(--border); z-index: 30;\"><template x-for=\"rate in speeds\" :key=\"'speed' + rate\"><button type=\"button\" @click=\"pickSpeed(rate)\" class=\"w-full text-left px-3 py-1.5 rounded-md text-sm\" :class=\"isSpeedActive(rate) ? 'font-semibold' : ''\" :style=\"isSpeedActive(rate) ? 'background-color: var(--accent-muted); color: var(--text-primary);' : 'color: var(--text-secondary);'\" x-text=\"rate + '× ' + (isDefaultSpeed(rate) ? ' · default' : '')\"></button></template><!-- §8: pin the current rate as the user default —\n\t\t\t\t\t\t books without their own speed pick it up --><button type=\"button\" x-show=\"!isDefaultSpeed(speed)\" @click=\"pickDefaultSpeed(speed)\" class=\"w-full text-left px-3 py-1.5 mt-1 rounded-md text-xs border-t\" style=\"border-color: var(--border); color: var(--text-secondary);\" x-text=\"'Make ' + speed + '× my default'\"></button></div></div><button type=\"button\" @click=\"toggleBookmarkCapture()\" class=\"text-xs px-3 py-2 rounded-lg hover:bg-surface-hover transition-colors inline-flex items-center gap-1.5\" :class=\"bookmarkCaptureOpen ? 'opacity-100' : 'opacity-70'\" aria-label=\"Add bookmark at current position\" style=\"color: var(--text-secondary);\"><svg width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"m19 21-7-4-7 4V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z\"></path> <path d=\"M12 7v6M9 10h6\"></path></svg> <span>Bookmark</span></button><div class=\"relative\" @click.outside=\"sleepOpen = false\"><button type=\"button\" @click=\"sleepOpen = !sleepOpen\" class=\"text-xs px-3 py-2 rounded-lg hover:bg-surface-hover transition-colors inline-flex items-center gap-1.5\" :aria-label=\"'Sleep timer, currently ' + sleepLabelStr\" style=\"color: var(--text-secondary);\"><svg width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z\"></path></svg> <span class=\"font-semibold tabular-nums\" style=\"color: var(--text-primary);\" x-text=\"sleepLabelStr === 'Off' ? 'Sleep' : sleepLabelStr\">Sleep</span> <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"m6 9 6 6 6-6\" > < / path > < / svg > < / button > < div x Line truncated
} else {
templ_7745c5c3_Err = templruntime . WriteString ( templ_7745c5c3_Buffer , 57 , "<button type=\"button\" @click=\"stepChapter(1)\" class=\"rounded-full p-2.5 hover:bg-surface-hover transition-colors\" aria-label=\"Next chapter\" title=\"Next chapter\" style=\"color: var(--text-primary);\">" )
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = audioStepNextIcon ( ) . Render ( ctx , templ_7745c5c3_Buffer )
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime . WriteString ( templ_7745c5c3_Buffer , 58 , "</button>" )
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime . WriteString ( templ_7745c5c3_Buffer , 59 , "</div></div><!-- Settings strip: Skip ▾ · Speed ▾ · Bookmark · Volume ▾ --><div class=\"w-full max-w-xl flex items-center justify-center gap-2\"><div class=\"relative\" @click.outside=\"skipOpen = false\"><button type=\"button\" @click=\"skipOpen = !skipOpen\" class=\"text-xs px-3 py-2 rounded-lg hover:bg-surface-hover transition-colors inline-flex items-center gap-1.5\" :aria-label=\"'Skip duration, currently ' + skipSeconds + ' seconds'\" style=\"color: var(--text-secondary);\"><span>Skip</span> <span class=\"font-semibold\" style=\"color: var(--text-primary);\" x-text=\"skipSeconds + 's'\">10s</span> <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"m6 9 6 6 6-6\"></path></svg></button><div x-show=\"skipOpen\" x-cloak class=\"absolute bottom-full mb-2 left-1/2 -translate-x-1/2 rounded-xl shadow-lg p-1.5 flex flex-col min-w-[7.5rem]\" style=\"background-color: var(--bg-secondary); border: 1px solid var(--border); z-index: 30;\"><template x-for=\"secs in skipChoices\" :key=\"'skip' + secs\"><button type=\"button\" @click=\"pickSkip(secs)\" class=\"w-full text-left px-3 py-1.5 rounded-md text-sm\" :class=\"isSkipActive(secs) ? 'font-semibold' : ''\" :style=\"isSkipActive(secs) ? 'background-color: var(--accent-muted); color: var(--text-primary);' : 'color: var(--text-secondary);'\" x-text=\"secs + ' seconds'\"></button></template></div></div><div class=\"relative\" @click.outside=\"speedOpen = false\"><button type=\"button\" @click=\"speedOpen = !speedOpen\" class=\"text-xs px-3 py-2 rounded-lg hover:bg-surface-hover transition-colors inline-flex items-center gap-1.5\" :aria-label=\"'Playback speed, currently ' + speed + ' times'\" style=\"color: var(--text-secondary);\"><span>Speed</span> <span class=\"font-semibold\" style=\"color: var(--text-primary);\" x-text=\"speed + '× '\">1× </span> <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"m6 9 6 6 6-6\"></path></svg></button><div x-show=\"speedOpen\" x-cloak class=\"absolute bottom-full mb-2 left-1/2 -translate-x-1/2 rounded-xl shadow-lg p-1.5 flex flex-col min-w-[7rem]\" style=\"background-color: var(--bg-secondary); border: 1px solid var(--border); z-index: 30;\"><template x-for=\"rate in speeds\" :key=\"'speed' + rate\"><button type=\"button\" @click=\"pickSpeed(rate)\" class=\"w-full text-left px-3 py-1.5 rounded-md text-sm\" :class=\"isSpeedActive(rate) ? 'font-semibold' : ''\" :style=\"isSpeedActive(rate) ? 'background-color: var(--accent-muted); color: var(--text-primary);' : 'color: var(--text-secondary);'\" x-text=\"rate + '× ' + (isDefaultSpeed(rate) ? ' · default' : '')\"></button></template><!-- §8: pin the current rate as the user default —\n\t\t\t\t\t\t books without their own speed pick it up --><button type=\"button\" x-show=\"!isDefaultSpeed(speed)\" @click=\"pickDefaultSpeed(speed)\" class=\"w-full text-left px-3 py-1.5 mt-1 rounded-md text-xs border-t\" style=\"border-color: var(--border); color: var(--text-secondary);\" x-text=\"'Make ' + speed + '× my default'\"></button></div></div><button type=\"button\" @click=\"toggleBookmarkCapture()\" class=\"text-xs px-3 py-2 rounded-lg hover:bg-surface-hover transition-colors inline-flex items-center gap-1.5\" :class=\"bookmarkCaptureOpen ? 'opacity-100' : 'opacity-70'\" aria-label=\"Add bookmark at current position\" style=\"color: var(--text-secondary);\"><svg width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"m19 21-7-4-7 4V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z\"></path> <path d=\"M12 7v6M9 10h6\"></path></svg> <span>Bookmark</span></button><div class=\"relative\" @click.outside=\"sleepOpen = false\"><button type=\"button\" @click=\"sleepOpen = !sleepOpen\" class=\"text-xs px-3 py-2 rounded-lg hover:bg-surface-hover transition-colors inline-flex items-center gap-1.5\" :aria-label=\"'Sleep timer, currently ' + sleepLabelStr\" style=\"color: var(--text-secondary);\"><svg width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z\"></path></svg> <span class=\"font-semibold tabular-nums\" style=\"color: var(--text-primary);\" x-text=\"sleepLabelStr === 'Off' ? 'Sleep' : sleepLabelStr\">Sleep</span> <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"m6 9 6 6 6-6\" > < / path > < / svg > < / button > < div x Line truncated
if templ_7745c5c3_Err != nil {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
return templ_7745c5c3_Err
}
}