mirror of
https://tangled.org/evan.jarrett.net/at-container-registry
synced 2026-09-02 00:06:58 +00:00
more pagespeed fixes
This commit is contained in:
@@ -352,7 +352,8 @@ func serveRegistry(cmd *cobra.Command, args []string) error {
|
||||
// Mount static files if UI is enabled
|
||||
if uiSessionStore != nil && uiTemplates != nil {
|
||||
// Register dynamic routes for root-level files (favicons, manifests, etc.)
|
||||
publicHandler := appview.PublicHandler()
|
||||
// Wrap with cache middleware for 1 year (cache busting via query string hashes in templates)
|
||||
publicHandler := appview.CacheMiddleware(appview.PublicHandler(), 31536000)
|
||||
rootFiles, err := appview.PublicRootFiles()
|
||||
if err != nil {
|
||||
slog.Warn("Failed to scan static root files", "error", err)
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 7.5 KiB |
File diff suppressed because one or more lines are too long
Vendored
+3
-3
File diff suppressed because one or more lines are too long
@@ -9,12 +9,6 @@
|
||||
|
||||
@plugin "@tailwindcss/typography";
|
||||
|
||||
/*@layer base {
|
||||
.container {
|
||||
max-width: 1920px;
|
||||
}
|
||||
}*/
|
||||
|
||||
@plugin "daisyui" {
|
||||
themes:
|
||||
light --default,
|
||||
|
||||
+46
-10
@@ -518,7 +518,8 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
});
|
||||
|
||||
// Featured carousel - scroll-based with proper wrap-around
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// Deferred initialization to avoid blocking main thread during page load
|
||||
function initFeaturedCarousel() {
|
||||
const carousel = document.getElementById('featured-carousel');
|
||||
const prevBtn = document.getElementById('carousel-prev');
|
||||
const nextBtn = document.getElementById('carousel-next');
|
||||
@@ -530,23 +531,50 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
let intervalId = null;
|
||||
const intervalMs = 5000;
|
||||
|
||||
function getItemWidth() {
|
||||
// Cache dimensions to avoid forced reflow on every navigation
|
||||
let cachedItemWidth = 0;
|
||||
let cachedContainerWidth = 0;
|
||||
let cachedScrollWidth = 0;
|
||||
|
||||
function updateCachedDimensions() {
|
||||
const item = items[0];
|
||||
if (!item) return 0;
|
||||
if (!item) return;
|
||||
// Batch all geometric reads together to minimize reflow
|
||||
const style = getComputedStyle(carousel);
|
||||
const gap = parseFloat(style.gap) || 24;
|
||||
return item.offsetWidth + gap;
|
||||
cachedItemWidth = item.offsetWidth + gap;
|
||||
cachedContainerWidth = carousel.offsetWidth;
|
||||
cachedScrollWidth = carousel.scrollWidth;
|
||||
}
|
||||
|
||||
// Initial measurement after layout is stable (double-rAF)
|
||||
requestAnimationFrame(() => {
|
||||
requestAnimationFrame(() => {
|
||||
updateCachedDimensions();
|
||||
startInterval();
|
||||
});
|
||||
});
|
||||
|
||||
// Update on resize (debounced to avoid excessive recalculation)
|
||||
let resizeTimeout;
|
||||
window.addEventListener('resize', () => {
|
||||
clearTimeout(resizeTimeout);
|
||||
resizeTimeout = setTimeout(updateCachedDimensions, 150);
|
||||
});
|
||||
|
||||
function getItemWidth() {
|
||||
if (!cachedItemWidth) updateCachedDimensions();
|
||||
return cachedItemWidth;
|
||||
}
|
||||
|
||||
function getVisibleCount() {
|
||||
const containerWidth = carousel.offsetWidth;
|
||||
const itemWidth = getItemWidth();
|
||||
if (itemWidth === 0) return 1;
|
||||
return Math.round(containerWidth / itemWidth);
|
||||
if (!cachedContainerWidth || !cachedItemWidth) updateCachedDimensions();
|
||||
return Math.round(cachedContainerWidth / cachedItemWidth) || 1;
|
||||
}
|
||||
|
||||
function getMaxScroll() {
|
||||
return carousel.scrollWidth - carousel.offsetWidth;
|
||||
if (!cachedScrollWidth || !cachedContainerWidth) updateCachedDimensions();
|
||||
return cachedScrollWidth - cachedContainerWidth;
|
||||
}
|
||||
|
||||
function advance() {
|
||||
@@ -587,9 +615,17 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
if (intervalId) { clearInterval(intervalId); intervalId = null; }
|
||||
}
|
||||
|
||||
startInterval();
|
||||
carousel.addEventListener('mouseenter', stopInterval);
|
||||
carousel.addEventListener('mouseleave', startInterval);
|
||||
}
|
||||
|
||||
// Defer carousel setup to after critical path
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
if ('requestIdleCallback' in window) {
|
||||
requestIdleCallback(initFeaturedCarousel, { timeout: 2000 });
|
||||
} else {
|
||||
setTimeout(initFeaturedCarousel, 100);
|
||||
}
|
||||
});
|
||||
|
||||
// Export functions that are called from templates via onclick handlers
|
||||
|
||||
@@ -6,7 +6,8 @@
|
||||
<section class="hero bg-base-200 min-h-[60vh] py-16 pb-24 relative overflow-hidden">
|
||||
<!-- Background mascot -->
|
||||
<picture>
|
||||
<source srcset="/amathea_manatee-576w.webp 576w,
|
||||
<source srcset="/amathea_manatee-384w.webp 384w,
|
||||
/amathea_manatee-576w.webp 576w,
|
||||
/amathea_manatee-768w.webp 768w,
|
||||
/amathea_manatee-1152w.webp 1152w"
|
||||
sizes="(max-width: 640px) 384px, (max-width: 1024px) 448px, 576px"
|
||||
@@ -32,7 +33,7 @@
|
||||
|
||||
<div class="flex items-center justify-center gap-4 mt-8">
|
||||
<a href="/auth/oauth/login?return_to=/" class="btn btn-primary btn-lg">Get Started</a>
|
||||
<a href="/learn-more" class="btn btn-ghost btn-lg">Learn More</a>
|
||||
<a href="/learn-more" class="btn btn-ghost btn-lg" aria-label="Learn more about the ATCR container registry">Learn More</a>
|
||||
</div>
|
||||
|
||||
<!-- Benefit Cards -->
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{{ define "manifest-modal" }}
|
||||
<dialog class="modal modal-open" onclick="if(event.target===this)this.remove()">
|
||||
<div class="modal-box">
|
||||
<button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2" onclick="this.closest('dialog').remove()" aria-label="Close dialog">✕</button>
|
||||
<button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2" onclick="this.closest('dialog').remove()" aria-label="Close manifest details">✕</button>
|
||||
|
||||
<h2 class="text-xl font-semibold mb-4">Manifest Details</h2>
|
||||
|
||||
|
||||
@@ -20,9 +20,9 @@
|
||||
<div class="card card-interactive bg-base-200 border-2 border-base-300 p-6 flex flex-col justify-between min-h-60 w-full" onclick="window.location='/r/{{ .OwnerHandle }}/{{ .Repository }}'">
|
||||
<div class="flex gap-4 items-start">
|
||||
{{ if .IconURL }}
|
||||
<img src="{{ resizeImage .IconURL 96 }}" alt="{{ .Repository }}" loading="lazy" class="w-12 rounded-lg object-cover shrink-0">
|
||||
<img src="{{ resizeImage .IconURL 96 }}" alt="{{ .Repository }}" loading="lazy" width="48" height="48" class="w-12 rounded-lg object-cover shrink-0">
|
||||
{{ else if .OwnerAvatarURL }}
|
||||
<img src="{{ resizeImage .OwnerAvatarURL 96 }}" alt="{{ .OwnerHandle }}" loading="lazy" class="w-12 rounded-lg object-cover shrink-0">
|
||||
<img src="{{ resizeImage .OwnerAvatarURL 96 }}" alt="{{ .OwnerHandle }}" loading="lazy" width="48" height="48" class="w-12 rounded-lg object-cover shrink-0">
|
||||
{{ else }}
|
||||
<div class="avatar avatar-placeholder">
|
||||
<div class="bg-neutral text-neutral-content w-12 rounded-lg shadow-sm uppercase">
|
||||
|
||||
@@ -17,7 +17,8 @@
|
||||
{{ end }}
|
||||
hx-swap="outerHTML"
|
||||
hx-on::before-request="this.disabled=true"
|
||||
hx-on::after-request="if(event.detail.xhr.status===401) window.location='/auth/oauth/login'">
|
||||
hx-on::after-request="if(event.detail.xhr.status===401) window.location='/auth/oauth/login'"
|
||||
aria-label="{{ if .IsStarred }}Unstar{{ else }}Star{{ end }} {{ .Handle }}/{{ .Repository }}">
|
||||
<i data-lucide="star" class="size-4 text-amber-400 stroke-amber-400 transition-transform group-hover:scale-110{{ if .IsStarred }} fill-amber-400{{ end }}" id="star-icon"></i>
|
||||
<span id="star-count">{{ .StarCount }}</span>
|
||||
</button>
|
||||
|
||||
@@ -59,7 +59,7 @@
|
||||
|
||||
<p class="text-center text-base-content/60 mt-6">
|
||||
Don't have an account? Create one at
|
||||
<a href="https://bsky.app" target="_blank" class="link link-primary">bsky.app</a>
|
||||
<a href="https://bsky.app" target="_blank" rel="noopener noreferrer" class="link link-primary" aria-label="Create an account on bsky.app (opens in new tab)">bsky.app</a>
|
||||
</p>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
@@ -93,12 +93,12 @@
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ if .Repository.SourceURL }}
|
||||
<a href="{{ .Repository.SourceURL }}" target="_blank" class="link link-primary text-sm">
|
||||
<a href="{{ .Repository.SourceURL }}" target="_blank" rel="noopener noreferrer" class="link link-primary text-sm" aria-label="View source code (opens in new tab)">
|
||||
Source
|
||||
</a>
|
||||
{{ end }}
|
||||
{{ if .Repository.DocumentationURL }}
|
||||
<a href="{{ .Repository.DocumentationURL }}" target="_blank" class="link link-primary text-sm">
|
||||
<a href="{{ .Repository.DocumentationURL }}" target="_blank" rel="noopener noreferrer" class="link link-primary text-sm" aria-label="View documentation (opens in new tab)">
|
||||
Documentation
|
||||
</a>
|
||||
{{ end }}
|
||||
@@ -174,7 +174,7 @@
|
||||
hx-confirm="Delete tag {{ .Tag.Tag }}?"
|
||||
hx-target="#tag-{{ sanitizeID .Tag.Tag }}"
|
||||
hx-swap="outerHTML"
|
||||
aria-label="Delete tag">
|
||||
aria-label="Delete tag {{ .Tag.Tag }}">
|
||||
<i data-lucide="trash-2" class="size-4"></i>
|
||||
</button>
|
||||
{{ end }}
|
||||
@@ -184,7 +184,7 @@
|
||||
<div class="flex flex-wrap justify-between items-center gap-2">
|
||||
<div class="flex items-center gap-2">
|
||||
<code class="font-mono text-xs text-base-content/60 truncate max-w-40" title="{{ .Tag.Digest }}">{{ .Tag.Digest }}</code>
|
||||
<button class="btn btn-ghost btn-xs" onclick="copyToClipboard('{{ .Tag.Digest }}')" aria-label="Copy digest to clipboard"><i data-lucide="copy" class="size-3"></i></button>
|
||||
<button class="btn btn-ghost btn-xs" onclick="copyToClipboard('{{ .Tag.Digest }}')" aria-label="Copy tag digest to clipboard"><i data-lucide="copy" class="size-3"></i></button>
|
||||
</div>
|
||||
{{ if .Platforms }}
|
||||
<div class="flex flex-wrap gap-1">
|
||||
@@ -247,7 +247,7 @@
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<code class="font-mono text-xs text-base-content/60 truncate max-w-40" title="{{ .Manifest.Digest }}">{{ .Manifest.Digest }}</code>
|
||||
<button class="btn btn-ghost btn-xs" onclick="copyToClipboard('{{ .Manifest.Digest }}')" aria-label="Copy digest to clipboard"><i data-lucide="copy" class="size-3"></i></button>
|
||||
<button class="btn btn-ghost btn-xs" onclick="copyToClipboard('{{ .Manifest.Digest }}')" aria-label="Copy manifest digest to clipboard"><i data-lucide="copy" class="size-3"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
@@ -257,7 +257,7 @@
|
||||
{{ if $.IsOwner }}
|
||||
<button class="btn btn-ghost btn-sm text-error"
|
||||
onclick="deleteManifest('{{ $.Repository.Name }}', '{{ .Manifest.Digest }}', '{{ sanitizeID .Manifest.Digest }}')"
|
||||
aria-label="Delete manifest">
|
||||
aria-label="Delete manifest {{ .Manifest.Digest | truncateDigest }}">
|
||||
<i data-lucide="trash-2" class="size-4"></i>
|
||||
</button>
|
||||
{{ end }}
|
||||
|
||||
@@ -337,7 +337,7 @@
|
||||
<td class="font-mono text-sm">${escapeHtml(device.ip_address || 'Unknown')}</td>
|
||||
<td>${createdDate}</td>
|
||||
<td>${lastUsed}</td>
|
||||
<td><button class="btn btn-error btn-xs" onclick="revokeDevice('${device.id}')">Revoke</button></td>
|
||||
<td><button class="btn btn-error btn-xs" onclick="revokeDevice('${device.id}')" aria-label="Revoke access for device ${escapeHtml(device.name)}">Revoke</button></td>
|
||||
</tr>
|
||||
`;
|
||||
}).join('');
|
||||
|
||||
@@ -59,10 +59,10 @@
|
||||
</td>
|
||||
<td class="date-cell">{{formatTime .AddedAt}}</td>
|
||||
<td class="actions">
|
||||
<a href="/admin/crew/{{.RKey}}" class="btn btn-icon" title="Edit">
|
||||
<a href="/admin/crew/{{.RKey}}" class="btn btn-icon" title="Edit" aria-label="Edit crew member {{if .Handle}}{{.Handle}}{{else}}{{.DID}}{{end}}">
|
||||
<i data-lucide="pencil"></i>
|
||||
</a>
|
||||
<button class="btn btn-icon btn-danger" title="Delete" hx-post="/admin/crew/{{.RKey}}/delete" hx-confirm="Remove this crew member?" hx-target="#crew-{{.RKey}}" hx-swap="outerHTML">
|
||||
<button class="btn btn-icon btn-danger" title="Delete" aria-label="Remove crew member {{if .Handle}}{{.Handle}}{{else}}{{.DID}}{{end}}" hx-post="/admin/crew/{{.RKey}}/delete" hx-confirm="Remove this crew member?" hx-target="#crew-{{.RKey}}" hx-swap="outerHTML">
|
||||
<i data-lucide="trash-2"></i>
|
||||
</button>
|
||||
</td>
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
<label for="did">DID</label>
|
||||
<div class="input-with-lookup">
|
||||
<input type="text" id="did" name="did" placeholder="did:plc:..." required>
|
||||
<button type="button" id="lookup-btn" class="btn btn-sm" title="Lookup handle">
|
||||
<button type="button" id="lookup-btn" class="btn btn-sm" title="Lookup handle" aria-label="Lookup handle from DID">
|
||||
<i data-lucide="search"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -8,10 +8,6 @@ module.exports = {
|
||||
darkMode: 'class',
|
||||
theme: {
|
||||
extend: {
|
||||
// Only keep custom extensions not covered by DaisyUI
|
||||
colors: {
|
||||
star: 'var(--star)',
|
||||
},
|
||||
fontFamily: {
|
||||
mono: ['Monaco', 'Menlo', 'Consolas', 'Liberation Mono', 'Courier New', 'monospace'],
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user