Compare commits
2 Commits
70e1dfc2b8
...
04a3c41bed
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
04a3c41bed | ||
|
|
b3002c8e12 |
@@ -44,6 +44,20 @@ html[data-phase="night"] {
|
||||
@layer base {
|
||||
body { font-family: "Nunito", system-ui, sans-serif; }
|
||||
html { scroll-behavior: smooth; }
|
||||
/* Belt-and-braces: never let stray wide content push horizontal scroll on
|
||||
the whole page (the channel nav scrolls inside itself instead). */
|
||||
html, body { overflow-x: hidden; }
|
||||
}
|
||||
|
||||
@layer components {
|
||||
/* Channel pill row may exceed viewport on mobile once enough channels are
|
||||
configured. Let it scroll inside its own rounded container and hide the
|
||||
scrollbar chrome so it still reads as a clean pill. */
|
||||
.pete-channel-nav {
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
}
|
||||
.pete-channel-nav::-webkit-scrollbar { display: none; }
|
||||
}
|
||||
|
||||
@layer utilities {
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -32,16 +32,16 @@
|
||||
<canvas id="pete-weather" class="pointer-events-none fixed inset-0 -z-[5] h-full w-full" aria-hidden="true"></canvas>
|
||||
|
||||
<header class="mx-auto max-w-6xl px-4 pt-8 pb-4 sm:pt-12">
|
||||
<div class="flex items-center justify-between gap-4">
|
||||
<a href="/" class="flex items-center gap-3 group">
|
||||
<div class="flex flex-wrap items-center justify-between gap-3 sm:gap-4">
|
||||
<a href="/" class="flex items-center gap-3 group min-w-0">
|
||||
<img src="/static/img/pete.avif" alt="Pete" width="48" height="48"
|
||||
class="h-12 w-12 rounded-2xl bg-[color:var(--card)] shadow-pete border-2 border-[color:var(--ink)]/10 object-cover group-hover:-rotate-6 transition-transform">
|
||||
class="h-12 w-12 shrink-0 rounded-2xl bg-[color:var(--card)] shadow-pete border-2 border-[color:var(--ink)]/10 object-cover group-hover:-rotate-6 transition-transform">
|
||||
<span class="font-display text-3xl font-bold tracking-tight">{{.SiteTitle}}</span>
|
||||
<span class="hidden sm:inline rounded-full bg-[color:var(--accent)]/20 px-3 py-1 text-xs font-semibold uppercase tracking-wider text-[color:var(--ink)]/70" data-phase-label>day</span>
|
||||
</a>
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="flex items-center gap-2 min-w-0 max-w-full">
|
||||
<button type="button" data-search-trigger title="Search (⌘K)"
|
||||
class="flex items-center gap-2 rounded-full bg-[color:var(--card)] px-3 py-2 text-sm shadow-pete border-2 border-[color:var(--ink)]/10 hover:bg-[color:var(--ink)]/5 transition">
|
||||
class="flex shrink-0 items-center gap-2 rounded-full bg-[color:var(--card)] px-3 py-2 text-sm shadow-pete border-2 border-[color:var(--ink)]/10 hover:bg-[color:var(--ink)]/5 transition">
|
||||
<svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
|
||||
stroke-linecap="round" stroke-linejoin="round" class="h-4 w-4 text-[color:var(--ink)]/60">
|
||||
<circle cx="11" cy="11" r="7"></circle>
|
||||
@@ -50,11 +50,11 @@
|
||||
<span class="hidden sm:inline text-[color:var(--ink)]/60 font-semibold">Search</span>
|
||||
<kbd class="hidden sm:inline-flex items-center rounded-md bg-[color:var(--ink)]/5 px-1.5 py-0.5 text-[10px] font-mono font-semibold text-[color:var(--ink)]/60">⌘K</kbd>
|
||||
</button>
|
||||
<nav class="flex items-center gap-1 sm:gap-2 rounded-full bg-[color:var(--card)] p-1 shadow-pete border-2 border-[color:var(--ink)]/10">
|
||||
<nav class="pete-channel-nav flex min-w-0 items-center gap-1 sm:gap-2 overflow-x-auto rounded-full bg-[color:var(--card)] p-1 shadow-pete border-2 border-[color:var(--ink)]/10">
|
||||
{{$active := .Active}}
|
||||
{{range .Channels}}
|
||||
<a href="/{{.Slug}}"
|
||||
class="rounded-full px-3 py-2 text-sm font-semibold transition
|
||||
class="shrink-0 rounded-full px-3 py-2 text-sm font-semibold transition
|
||||
{{if eq $active .Slug}}bg-theme-{{.Theme}} text-white shadow-sm{{else}}hover:bg-[color:var(--ink)]/5{{end}}">
|
||||
<span aria-hidden="true" class="mr-1">{{.Emoji}}</span><span class="hidden sm:inline">{{.Title}}</span>
|
||||
</a>
|
||||
|
||||
@@ -178,7 +178,7 @@ func buildAvifThumb(src, outPath string) error {
|
||||
}
|
||||
|
||||
if urlIsVideo || isVideoContentType(resp.Header.Get("Content-Type")) {
|
||||
return buildAvifFromVideoFrame(body, outPath)
|
||||
return buildAvifFromFFmpeg(body, outPath)
|
||||
}
|
||||
|
||||
if isAvif(body) {
|
||||
@@ -195,6 +195,13 @@ func buildAvifThumb(src, outPath string) error {
|
||||
}
|
||||
src2, _, err := image.Decode(bytes.NewReader(body))
|
||||
if err != nil {
|
||||
// Go's stdlib JPEG decoder rejects some valid-but-rare features
|
||||
// (e.g. 4:1:1 luma/chroma subsampling served by ANN's CDN). Hand
|
||||
// the bytes to ffmpeg as a permissive fallback — it produces the
|
||||
// same shape of output as the video path.
|
||||
if ffErr := buildAvifFromFFmpeg(body, outPath); ffErr == nil {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("decode: %w", err)
|
||||
}
|
||||
b := src2.Bounds()
|
||||
@@ -265,12 +272,13 @@ func isVideoContentType(ct string) bool {
|
||||
return strings.HasPrefix(strings.ToLower(strings.TrimSpace(ct)), "video/")
|
||||
}
|
||||
|
||||
// buildAvifFromVideoFrame writes the downloaded video to a temp file, asks
|
||||
// buildAvifFromFFmpeg writes the downloaded bytes to a temp file, asks
|
||||
// ffmpeg to extract a single resized frame as PNG, then hands it to avifenc.
|
||||
// ffmpeg is optional — if it's missing we return an error and the caller
|
||||
// falls back to redirecting to the source URL, just like any other build
|
||||
// failure.
|
||||
func buildAvifFromVideoFrame(body []byte, outPath string) error {
|
||||
// Used both for video sources and as a permissive fallback for images whose
|
||||
// JPEG features Go's stdlib decoder rejects. ffmpeg is optional — if it's
|
||||
// missing we return an error and the caller falls back to redirecting to
|
||||
// the source URL, just like any other build failure.
|
||||
func buildAvifFromFFmpeg(body []byte, outPath string) error {
|
||||
if _, err := exec.LookPath("ffmpeg"); err != nil {
|
||||
return fmt.Errorf("ffmpeg not available: %w", err)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user