@@ -51,7 +51,7 @@ func AudioReader(user User, metadata ReaderMetadata) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime . WriteString ( templ_7745c5c3_Buffer , 2 , " · Bookhoard</title><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"><link href=\"/static/style.css\" rel=\"stylesheet\"><script type=\"module\" src=\"/static/main.js\"></script><style>\n\t\t\t\t/* Layered scrubber (plan §1): the native range stays for\n\t\t\t\t a11y (focus, arrow keys, aria) but its track is\n\t\t\t\t transparent so the tick/buffer/fill layers beneath show\n\t\t\t\t through. Static page CSS — templ owns the look. */\n\t\t\t\t#audio-scrub { -webkit-appearance: none; appearance: none; background: transparent; cursor: pointer; }\n\t\t\t\t#audio-scrub::-webkit-slider-runnable-track { background: transparent; border: none; }\n\t\t\t\t#audio-scrub::-webkit-slider-thumb { -webkit-appearance: none; appearance: none; height: 14px; width: 14px; border-radius: 9999px; background: var(--accent); border: 2px solid var(--bg-primary); box-shadow: 0 1px 4px rgba(0,0,0,0.4); }\n\t\t\t\t#audio-scrub::-moz-range-track { background: transparent; border: none; }\n\t\t\t\t#audio-scrub::-moz-range-thumb { height: 14px; width: 14px; border-radius: 9999px; background: var(--accent); border: 2px solid var(--bg-primary); box-shadow: 0 1px 4px rgba(0,0,0,0.4); }\n\t\t\t</style></head>" )
templ_7745c5c3_Err = templruntime . WriteString ( templ_7745c5c3_Buffer , 2 , " · Bookhoard</title><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"><link href=\"/static/style.css\" rel=\"stylesheet\"><link rel=\"icon\" type=\"image/svg+xml\" href=\"/static/favicon.svg\">< script type=\"module\" src=\"/static/main.js\"></script><style>\n\t\t\t\t/* Layered scrubber (plan §1): the native range stays for\n\t\t\t\t a11y (focus, arrow keys, aria) but its track is\n\t\t\t\t transparent so the tick/buffer/fill layers beneath show\n\t\t\t\t through. Static page CSS — templ owns the look. */\n\t\t\t\t#audio-scrub { -webkit-appearance: none; appearance: none; background: transparent; cursor: pointer; }\n\t\t\t\t#audio-scrub::-webkit-slider-runnable-track { background: transparent; border: none; }\n\t\t\t\t#audio-scrub::-webkit-slider-thumb { -webkit-appearance: none; appearance: none; height: 14px; width: 14px; border-radius: 9999px; background: var(--accent); border: 2px solid var(--bg-primary); box-shadow: 0 1px 4px rgba(0,0,0,0.4); }\n\t\t\t\t#audio-scrub::-moz-range-track { background: transparent; border: none; }\n\t\t\t\t#audio-scrub::-moz-range-thumb { height: 14px; width: 14px; border-radius: 9999px; background: var(--accent); border: 2px solid var(--bg-primary); box-shadow: 0 1px 4px rgba(0,0,0,0.4); }\n\t\t\t</style></head>" )
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -80,7 +80,7 @@ func AudioReader(user User, metadata ReaderMetadata) templ.Component {
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5 , templ_7745c5c3_Err = templ . ResolveAttributeValue ( metadata . MediaItemID )
if templ_7745c5c3_Err != nil {
return templ . Error { Err : templ_7745c5c3_Err , FileName : ` templates/audio_reader.templ ` , Line : 38 , Col : 44 }
return templ . Error { Err : templ_7745c5c3_Err , FileName : ` templates/audio_reader.templ ` , Line : 39 , Col : 44 }
}
_ , templ_7745c5c3_Err = templ_7745c5c3_Buffer . WriteString ( templ_7745c5c3_Var5 )
if templ_7745c5c3_Err != nil {
@@ -93,7 +93,7 @@ func AudioReader(user User, metadata ReaderMetadata) templ.Component {
var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6 , templ_7745c5c3_Err = templ . ResolveAttributeValue ( metadata . Title )
if templ_7745c5c3_Err != nil {
return templ . Error { Err : templ_7745c5c3_Err , FileName : ` templates/audio_reader.templ ` , Line : 39 , Col : 30 }
return templ . Error { Err : templ_7745c5c3_Err , FileName : ` templates/audio_reader.templ ` , Line : 40 , Col : 30 }
}
_ , templ_7745c5c3_Err = templ_7745c5c3_Buffer . WriteString ( templ_7745c5c3_Var6 )
if templ_7745c5c3_Err != nil {
@@ -106,7 +106,7 @@ func AudioReader(user User, metadata ReaderMetadata) templ.Component {
var templ_7745c5c3_Var7 string
templ_7745c5c3_Var7 , templ_7745c5c3_Err = templ . ResolveAttributeValue ( metadata . CoverImagePath )
if templ_7745c5c3_Err != nil {
return templ . Error { Err : templ_7745c5c3_Err , FileName : ` templates/audio_reader.templ ` , Line : 40 , Col : 39 }
return templ . Error { Err : templ_7745c5c3_Err , FileName : ` templates/audio_reader.templ ` , Line : 41 , Col : 39 }
}
_ , templ_7745c5c3_Err = templ_7745c5c3_Buffer . WriteString ( templ_7745c5c3_Var7 )
if templ_7745c5c3_Err != nil {
@@ -119,7 +119,7 @@ func AudioReader(user User, metadata ReaderMetadata) templ.Component {
var templ_7745c5c3_Var8 string
templ_7745c5c3_Var8 , templ_7745c5c3_Err = templ . ResolveAttributeValue ( metadata . FileURL )
if templ_7745c5c3_Err != nil {
return templ . Error { Err : templ_7745c5c3_Err , FileName : ` templates/audio_reader.templ ` , Line : 41 , Col : 35 }
return templ . Error { Err : templ_7745c5c3_Err , FileName : ` templates/audio_reader.templ ` , Line : 42 , Col : 35 }
}
_ , templ_7745c5c3_Err = templ_7745c5c3_Buffer . WriteString ( templ_7745c5c3_Var8 )
if templ_7745c5c3_Err != nil {
@@ -134,13 +134,13 @@ func AudioReader(user User, metadata ReaderMetadata) templ.Component {
"/api/media-items/" + metadata . MediaItemID + "/chapters" ,
"/api/media-items/" + metadata . MediaItemID + "/progress" ) )
if templ_7745c5c3_Err != nil {
return templ . Error { Err : templ_7745c5c3_Err , FileName : ` templates/audio_reader.templ ` , Line : 44 , Col : 57 }
return templ . Error { Err : templ_7745c5c3_Err , FileName : ` templates/audio_reader.templ ` , Line : 45 , Col : 57 }
}
_ , templ_7745c5c3_Err = templ_7745c5c3_Buffer . WriteString ( templ_7745c5c3_Var9 )
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime . WriteString ( templ_7745c5c3_Buffer , 9 , "\"><div class=\"h-full flex flex-col items-center justify-center px-4 gap-6 select-none\"><!-- Header: back to the detail page (reader-style exit) and the\n\t\t\t chapter launcher. Floating drawer, never in-flow (A2b). --><div class=\"absolute top-0 inset-x-0 z-30 flex items-center justify-between px-4 py-3\"><button type=\"button\" @click=\"backToDetail()\" class=\"rounded-full p-2 hover:bg-surface-hover transition-colors\" style=\"color: var(--text-primary);\" aria-label=\"Back to book details\"><svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M19 12H5\"></path> <path d=\"m12 19-7-7 7-7\"></path></svg></button> <span class=\"inline-flex items-center gap-2\"><button type=\"button\" @click=\"bookmarksOpen = !bookmarksOpen\" class=\"rounded-full p-2 hover:bg-surface-hover transition-colors\" style=\"color: var(--text-primary); border: 1px solid var(--border); background-color: color-mix(in srgb, var(--bg-secondary) 80%, transparent);\" :aria-label=\"'Open bookmarks (' + bookmarks.length + ')'\" title=\"Bookmarks\"><svg width=\"16\" height=\"16\" 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></svg></button> <button type=\"button\" @click=\"chaptersOpen = !chaptersOpen\" class=\"text-sm font-semibold px-4 py-2 rounded-full hover:bg-surface-hover transition-colors inline-flex items-center gap-1.5\" style=\"color: var(--text-primary); border: 1px solid var(--border); background-color: color-mix(in srgb, var(--bg-secondary) 80%, transparent);\" :aria-label=\"'Open chapter list (currently chapter ' + (currentChapterIdx + 1) + ' of ' + (chapters.length || 1) + ')'\" title=\"Chapters\"><span>Chapter</span> <span class=\"tabular-nums\" x-text=\"(currentChapterIdx + 1) + '/' + (chapters.length || 1)\">1/1</span> <svg width=\"16\" height=\"16\" 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> <button type=\"button\" @click=\"helpOpen = !helpOpen\" class=\"rounded-full p-2 hover:bg-surface-hover transition-colors\" style=\"color: var(--text-primary); border: 1px solid var(--border); background-color: color-mix(in srgb, var(--bg-secondary) 80%, transparent);\" aria-label=\"Shortcuts & interactions\" title=\"Shortcuts & interactions (?)\"><svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><rect x=\"2\" y=\"4\" width=\"20\" height=\"16\" rx=\"2\"></rect> <path d=\"M6 8h.01M10 8h.01M14 8h.01M18 8h.01M6 12h.01M10 12h.01M14 12h.01M18 12h.01M8 16h8\"></path></svg></button><div class=\"relative\" @click.outside=\"settingsOpen = false\"><button type=\"button\" @click=\"settingsOpen = !settingsOpen\" class=\"rounded-full p-2 hover:bg-surface-hover transition-colors\" :class=\"settingsOpen ? 'opacity-100' : 'opacity-90'\" style=\"color: var(--text-primary); border: 1px solid var(--border); background-color: color-mix(in srgb, var(--bg-secondary) 80%, transparent);\" aria-label=\"Player settings\" title=\"Settings\"><svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z\"></path> <circle cx=\"12\" cy=\"12\" r=\"3\"></circle></svg></button><div x-show=\"settingsOpen\" x-cloak x-transition class=\"absolute right-0 top-full mt-2 w-72 rounded-xl shadow-2xl p-3 z-50\" style=\"background-color: var(--bg-secondary); border: 1px solid var(--border);\"><div class=\"flex items-center justify-between mb-2 px-1\"><span class=\"text-sm font-semibold\" style=\"color: var(--text-primary);\">Player settings</span> <button type=\"button\" @click=\"settingsOpen = false\" class=\"rounded-full p-1.5 hover:bg-surface-hover\" style=\"color: var(--text-secondary);\" aria-label=\"Close settings\"><svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stro Line truncated
templ_7745c5c3_Err = templruntime . WriteString ( templ_7745c5c3_Buffer , 9 , "\"><!-- §17 wind-down dim: the sleep ticker eases this toward 35%\n\t\t\t black across a countdown's final 30s; pointer-events-none\n\t\t\t keeps the page usable, stop/finish resets it. --><div class=\"fixed inset-0 pointer-events-none z-40\" :style=\"'opacity:' + sleepDim + '; background: #000; transition: opacity 1s linear;'\"></div><div class=\"h-full flex flex-col items-center justify-center px-4 gap-6 select-none\"><!-- Header: back to the detail page (reader-style exit) and the\n\t\t\t chapter launcher. Floating drawer, never in-flow (A2b). --><div class=\"absolute top-0 inset-x-0 z-30 flex items-center justify-between px-4 py-3\"><button type=\"button\" @click=\"backToDetail()\" class=\"rounded-full p-2 hover:bg-surface-hover transition-colors\" style=\"color: var(--text-primary);\" aria-label=\"Back to book details\"><svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M19 12H5\"></path> <path d=\"m12 19-7-7 7-7\"></path></svg></button> <span class=\"inline-flex items-center gap-2\"><button type=\"button\" @click=\"bookmarksOpen = !bookmarksOpen\" class=\"rounded-full p-2 hover:bg-surface-hover transition-colors\" style=\"color: var(--text-primary); border: 1px solid var(--border); background-color: color-mix(in srgb, var(--bg-secondary) 80%, transparent);\" :aria-label=\"'Open bookmarks (' + bookmarks.length + ')'\" title=\"Bookmarks\"><svg width=\"16\" height=\"16\" 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></svg></button> <button type=\"button\" @click=\"chaptersOpen = !chaptersOpen\" class=\"text-sm font-semibold px-4 py-2 rounded-full hover:bg-surface-hover transition-colors inline-flex items-center gap-1.5\" style=\"color: var(--text-primary); border: 1px solid var(--border); background-color: color-mix(in srgb, var(--bg-secondary) 80%, transparent);\" :aria-label=\"'Open chapter list (currently chapter ' + (currentChapterIdx + 1) + ' of ' + (chapters.length || 1) + ')'\" title=\"Chapters\"><span>Chapter</span> <span class=\"tabular-nums\" x-text=\"(currentChapterIdx + 1) + '/' + (chapters.length || 1)\">1/1</span> <svg width=\"16\" height=\"16\" 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> <button type=\"button\" @click=\"helpOpen = !helpOpen\" class=\"rounded-full p-2 hover:bg-surface-hover transition-colors\" style=\"color: var(--text-primary); border: 1px solid var(--border); background-color: color-mix(in srgb, var(--bg-secondary) 80%, transparent);\" aria-label=\"Shortcuts & interactions\" title=\"Shortcuts & interactions (?)\"><svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><rect x=\"2\" y=\"4\" width=\"20\" height=\"16\" rx=\"2\"></rect> <path d=\"M6 8h.01M10 8h.01M14 8h.01M18 8h.01M6 12h.01M10 12h.01M14 12h.01M18 12h.01M8 16h8\"></path></svg></button><div class=\"relative\" @click.outside=\"settingsOpen = false\"><button type=\"button\" @click=\"settingsOpen = !settingsOpen\" class=\"rounded-full p-2 hover:bg-surface-hover transition-colors\" :class=\"settingsOpen ? 'opacity-100' : 'opacity-90'\" style=\"color: var(--text-primary); border: 1px solid var(--border); background-color: color-mix(in srgb, var(--bg-secondary) 80%, transparent);\" aria-label=\"Player settings\" title=\"Settings\"><svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z\"></path> <circle cx=\"12\" cy=\"12\" r=\"3\"></circle></svg></button><div x-show=\"settingsOpen\" x-cloak x-transition class=\"absolute right-0 top-full mt-2 w-72 rounded-xl shadow-2xl p-3 z-50\" style=\"background-color: var(--bg-secondary); border: 1px solid var(--border);\"><div class=\"flex items-center justify-between mb-2 px-1\"><span class=\" text - sm font - sem Line truncated
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -152,7 +152,7 @@ func AudioReader(user User, metadata ReaderMetadata) templ.Component {
var templ_7745c5c3_Var10 string
templ_7745c5c3_Var10 , templ_7745c5c3_Err = templ . ResolveAttributeValue ( fmt . Sprintf ( "%g" , rate ) )
if templ_7745c5c3_Err != nil {
return templ . Error { Err : templ_7745c5c3_Err , FileName : ` templates/audio_reader.templ ` , Line : 161 , Col : 41 }
return templ . Error { Err : templ_7745c5c3_Err , FileName : ` templates/audio_reader.templ ` , Line : 168 , Col : 41 }
}
_ , templ_7745c5c3_Err = templ_7745c5c3_Buffer . WriteString ( templ_7745c5c3_Var10 )
if templ_7745c5c3_Err != nil {
@@ -165,7 +165,7 @@ func AudioReader(user User, metadata ReaderMetadata) templ.Component {
var templ_7745c5c3_Var11 string
templ_7745c5c3_Var11 , templ_7745c5c3_Err = templ . ResolveAttributeValue ( fmt . Sprintf ( "Number(defaultSpeed) === %g" , rate ) )
if templ_7745c5c3_Err != nil {
return templ . Error { Err : templ_7745c5c3_Err , FileName : ` templates/audio_reader.templ ` , Line : 162 , Col : 70 }
return templ . Error { Err : templ_7745c5c3_Err , FileName : ` templates/audio_reader.templ ` , Line : 169 , Col : 70 }
}
_ , templ_7745c5c3_Err = templ_7745c5c3_Buffer . WriteString ( templ_7745c5c3_Var11 )
if templ_7745c5c3_Err != nil {
@@ -178,7 +178,7 @@ func AudioReader(user User, metadata ReaderMetadata) templ.Component {
var templ_7745c5c3_Var12 string
templ_7745c5c3_Var12 , templ_7745c5c3_Err = templ . JoinStringErrs ( fmt . Sprintf ( "%g× " , rate ) )
if templ_7745c5c3_Err != nil {
return templ . Error { Err : templ_7745c5c3_Err , FileName : ` templates/audio_reader.templ ` , Line : 163 , Col : 37 }
return templ . Error { Err : templ_7745c5c3_Err , FileName : ` templates/audio_reader.templ ` , Line : 170 , Col : 37 }
}
_ , templ_7745c5c3_Err = templ_7745c5c3_Buffer . WriteString ( templ . EscapeString ( templ_7745c5c3_Var12 ) )
if templ_7745c5c3_Err != nil {
@@ -201,7 +201,7 @@ func AudioReader(user User, metadata ReaderMetadata) templ.Component {
var templ_7745c5c3_Var13 string
templ_7745c5c3_Var13 , templ_7745c5c3_Err = templ . ResolveAttributeValue ( metadata . CoverImagePath )
if templ_7745c5c3_Err != nil {
return templ . Error { Err : templ_7745c5c3_Err , FileName : ` templates/audio_reader.templ ` , Line : 177 , Col : 36 }
return templ . Error { Err : templ_7745c5c3_Err , FileName : ` templates/audio_reader.templ ` , Line : 184 , Col : 36 }
}
_ , templ_7745c5c3_Err = templ_7745c5c3_Buffer . WriteString ( templ_7745c5c3_Var13 )
if templ_7745c5c3_Err != nil {
@@ -214,7 +214,7 @@ func AudioReader(user User, metadata ReaderMetadata) templ.Component {
var templ_7745c5c3_Var14 string
templ_7745c5c3_Var14 , templ_7745c5c3_Err = templ . ResolveAttributeValue ( metadata . Title )
if templ_7745c5c3_Err != nil {
return templ . Error { Err : templ_7745c5c3_Err , FileName : ` templates/audio_reader.templ ` , Line : 178 , Col : 27 }
return templ . Error { Err : templ_7745c5c3_Err , FileName : ` templates/audio_reader.templ ` , Line : 185 , Col : 27 }
}
_ , templ_7745c5c3_Err = templ_7745c5c3_Buffer . WriteString ( templ_7745c5c3_Var14 )
if templ_7745c5c3_Err != nil {
@@ -237,13 +237,13 @@ func AudioReader(user User, metadata ReaderMetadata) templ.Component {
var templ_7745c5c3_Var15 string
templ_7745c5c3_Var15 , templ_7745c5c3_Err = templ . JoinStringErrs ( metadata . Title )
if templ_7745c5c3_Err != nil {
return templ . Error { Err : templ_7745c5c3_Err , FileName : ` templates/audio_reader.templ ` , Line : 187 , Col : 117 }
return templ . Error { Err : templ_7745c5c3_Err , FileName : ` templates/audio_reader.templ ` , Line : 194 , Col : 117 }
}
_ , templ_7745c5c3_Err = templ_7745c5c3_Buffer . WriteString ( templ . EscapeString ( templ_7745c5c3_Var15 ) )
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime . WriteString ( templ_7745c5c3_Buffer , 20 , "</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=\"scrubbing = true; scrubValue = Number($event.target.value)\" @change=\"seekTo(scrubValue); scrubbing = false\" 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=\"'left:' + ((tick.start - railFrom) / Math.max(1, railTo - railFrom) * 100) + '%;background-color:var(--text-secondary);opacity:0.5;'\"></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 Line truncated
templ_7745c5c3_Err = templruntime . WriteString ( templ_7745c5c3_Buffer , 20 , "</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=\"scrubbing = true; scrubValue = Number($event.target.value)\" @change=\"seekTo(scrubValue); scrubbing = false\" 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=\"'left:' + ((tick.start - railFrom) / Math.max(1, railTo - railFrom) * 100) + '%;background-color:var(--text-secondary);opacity:0.5;'\"></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 Line truncated
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}