more hardening, inline tangled svg into the sprite
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 13 KiB |
@@ -253,22 +253,6 @@
|
||||
--btn-border: transparent;
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
THEME-AWARE ICON SWITCHING
|
||||
======================================== */
|
||||
.icon-light {
|
||||
display: block;
|
||||
}
|
||||
.icon-dark {
|
||||
display: none;
|
||||
}
|
||||
[data-theme="dark"] .icon-light {
|
||||
display: none;
|
||||
}
|
||||
[data-theme="dark"] .icon-dark {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
STICKY FOOTER LAYOUT + BASE TYPE
|
||||
======================================== */
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{{ define "footer" }}
|
||||
<footer class="footer footer-center bg-base-200 text-base-content p-6 pt-20 mt-auto relative">
|
||||
<img src="/static/wave-pattern.svg" alt="" width="1440" height="64" decoding="async" class="absolute top-0 left-0 w-full h-16 pointer-events-none rotate-180" aria-hidden="true">
|
||||
<img src="/static/wave-pattern.svg" alt="" width="1440" height="64" loading="lazy" decoding="async" class="absolute top-0 left-0 w-full h-16 pointer-events-none rotate-180" aria-hidden="true">
|
||||
<nav class="flex flex-wrap justify-center items-center gap-x-2 gap-y-1 text-sm">
|
||||
<a href="/privacy" class="link link-hover">Privacy</a>
|
||||
<span aria-hidden="true" class="text-base-content/30">·</span>
|
||||
@@ -13,8 +13,7 @@
|
||||
{{ with .SourceURL }}
|
||||
<span aria-hidden="true" class="text-base-content/30">·</span>
|
||||
<a href="{{ . }}" target="_blank" rel="noopener" class="link link-hover inline-flex items-center gap-1">
|
||||
<img src="/static/tangled-black.svg" alt="" width="14" height="14" loading="lazy" decoding="async" class="size-3.5 icon-light" aria-hidden="true">
|
||||
<img src="/static/tangled-white.svg" alt="" width="14" height="14" loading="lazy" decoding="async" class="size-3.5 icon-dark" aria-hidden="true">
|
||||
{{ icon "tangled" "size-3.5" }}
|
||||
Source
|
||||
</a>
|
||||
{{ end }}
|
||||
|
||||
@@ -20,11 +20,11 @@
|
||||
</div>
|
||||
|
||||
{{ if or .FromFailed .ToFailed }}
|
||||
{{ template "alert" (dict "Type" "warning" "Message" (printf "We couldn't fetch details for %s%s%s — showing what we have." (or (and .FromFailed .FromTag) "") (or (and .FromFailed .ToFailed) " and ") (or (and .ToFailed .ToTag) ""))) }}
|
||||
{{ template "alert" (dict "Type" "warning" "Message" (printf "We couldn't fetch details for %s%s%s. Showing what we have." (or (and .FromFailed .FromTag) "") (or (and .FromFailed .ToFailed) " and ") (or (and .ToFailed .ToTag) ""))) }}
|
||||
{{ end }}
|
||||
|
||||
{{ if and (not .FromFailed) (not .ToFailed) (eq .FromDigest .ToDigest) }}
|
||||
{{ template "alert" (dict "Type" "info" "Message" "These manifests are identical — no layers or vulnerabilities changed.") }}
|
||||
{{ template "alert" (dict "Type" "info" "Message" "These manifests are identical. No layers or vulnerabilities changed.") }}
|
||||
{{ end }}
|
||||
|
||||
<!-- Summary Card -->
|
||||
|
||||
@@ -166,8 +166,7 @@
|
||||
<a href="/install" class="btn btn-primary btn-lg">Get Started</a>
|
||||
{{ with .SourceURL }}
|
||||
<a href="{{ . }}" target="_blank" rel="noopener noreferrer" class="btn btn-ghost btn-lg">
|
||||
<img src="/static/tangled-black.svg" alt="" aria-hidden="true" width="20" height="20" loading="lazy" decoding="async" class="size-5 mr-2 icon-light">
|
||||
<img src="/static/tangled-white.svg" alt="" aria-hidden="true" width="20" height="20" loading="lazy" decoding="async" class="size-5 mr-2 icon-dark">
|
||||
{{ icon "tangled" "size-5 mr-2" }}
|
||||
View Source
|
||||
</a>
|
||||
{{ end }}
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
{{ else if eq .Error "invalid_handle" }}
|
||||
That handle doesn't look right. Check for typos and try again.
|
||||
{{ else if eq .Error "pds_unreachable" }}
|
||||
We couldn't reach your PDS. It may be offline — try again in a minute.
|
||||
We couldn't reach your PDS. It may be offline; try again in a minute.
|
||||
{{ else if eq .Error "state_mismatch" }}
|
||||
Your sign-in session expired before we finished. Please start over.
|
||||
{{ else if eq .Error "access_denied" }}
|
||||
@@ -44,7 +44,9 @@
|
||||
<input type="hidden" name="return_to" value="{{ .ReturnTo }}" />
|
||||
|
||||
<div class="sailor-typeahead relative order-1">
|
||||
<label for="handle" class="sr-only">Atmosphere handle or DID</label>
|
||||
<label for="handle" class="block text-xs font-medium uppercase tracking-wider text-base-content/60 mb-2">
|
||||
Atmosphere handle
|
||||
</label>
|
||||
<input type="text"
|
||||
id="handle"
|
||||
name="handle"
|
||||
|
||||
@@ -32,11 +32,15 @@
|
||||
</div>
|
||||
</details>
|
||||
{{ else if .Size }}
|
||||
<p class="text-sm text-base-content/70">Binary content ({{ humanizeBytes .Size }}) — cannot display inline</p>
|
||||
<p class="text-sm text-base-content/70">Binary content ({{ humanizeBytes .Size }}); cannot display inline</p>
|
||||
{{ end }}
|
||||
</div>
|
||||
{{ else }}
|
||||
<p class="text-base-content/60">No attestations attached to this manifest.</p>
|
||||
{{ template "state-empty" (dict
|
||||
"Icon" "fingerprint"
|
||||
"Title" "No attestations attached"
|
||||
"Subtext" "Signed provenance or SBOM attestations will appear here once attached to this manifest."
|
||||
) }}
|
||||
{{ end }}
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
@@ -40,7 +40,11 @@
|
||||
</table>
|
||||
</div>
|
||||
{{ else }}
|
||||
<p class="text-base-content/60">No layer information available</p>
|
||||
{{ template "state-empty" (dict
|
||||
"Icon" "history"
|
||||
"Title" "No layer history"
|
||||
"Subtext" "Neither side of this diff reports layer history."
|
||||
) }}
|
||||
{{ end }}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -37,7 +37,11 @@
|
||||
</table>
|
||||
</div>
|
||||
{{ else }}
|
||||
<p class="text-base-content">No layer information available</p>
|
||||
{{ template "state-empty" (dict
|
||||
"Icon" "history"
|
||||
"Title" "No layer history"
|
||||
"Subtext" "This manifest has no layer history recorded."
|
||||
) }}
|
||||
{{ end }}
|
||||
</div>
|
||||
|
||||
@@ -49,7 +53,11 @@
|
||||
{{ if .VulnData }}
|
||||
{{ template "vuln-details" .VulnData }}
|
||||
{{ else }}
|
||||
<p class="text-base-content">No vulnerability scan data available</p>
|
||||
{{ template "state-empty" (dict
|
||||
"Icon" "shield-check"
|
||||
"Title" "No scan data yet"
|
||||
"Subtext" "The scanner hasn't reported results for this manifest."
|
||||
) }}
|
||||
{{ end }}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -8,13 +8,13 @@
|
||||
{{/* Branch on classified reason so the tooltip tells operators what
|
||||
kind of failure they're seeing, not just "it's down". */}}
|
||||
{{ if eq .Reason "dns" }}
|
||||
<span class="badge badge-sm badge-warning" role="status" title="DNS lookup failed — the hostname can't be resolved">{{ icon "triangle-alert" "size-3" }} DNS failed</span>
|
||||
<span class="badge badge-sm badge-warning" role="status" title="DNS lookup failed: the hostname can't be resolved">{{ icon "triangle-alert" "size-3" }} DNS failed</span>
|
||||
{{ else if eq .Reason "tls" }}
|
||||
<span class="badge badge-sm badge-warning" role="status" title="TLS handshake failed — certificate may be expired or invalid">{{ icon "triangle-alert" "size-3" }} TLS error</span>
|
||||
<span class="badge badge-sm badge-warning" role="status" title="TLS handshake failed: certificate may be expired or invalid">{{ icon "triangle-alert" "size-3" }} TLS error</span>
|
||||
{{ else if eq .Reason "refused" }}
|
||||
<span class="badge badge-sm badge-warning" role="status" title="Connection refused — nothing is listening on the endpoint">{{ icon "triangle-alert" "size-3" }} Refused</span>
|
||||
<span class="badge badge-sm badge-warning" role="status" title="Connection refused: nothing is listening on the endpoint">{{ icon "triangle-alert" "size-3" }} Refused</span>
|
||||
{{ else if eq .Reason "timeout" }}
|
||||
<span class="badge badge-sm badge-warning" role="status" title="Request timed out — the hold is slow or unreachable">{{ icon "triangle-alert" "size-3" }} Timeout</span>
|
||||
<span class="badge badge-sm badge-warning" role="status" title="Request timed out: the hold is slow or unreachable">{{ icon "triangle-alert" "size-3" }} Timeout</span>
|
||||
{{ else if eq .Reason "http" }}
|
||||
<span class="badge badge-sm badge-warning" role="status" title="The hold responded with an error status">{{ icon "triangle-alert" "size-3" }} HTTP error</span>
|
||||
{{ else }}
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
{{ if .Command }}
|
||||
<code class="font-mono text-xs break-all line-clamp-2" title="{{ .Command }}">{{ .Command }}</code>
|
||||
{{ else if not .EmptyLayer }}
|
||||
<span class="text-xs text-base-content/40 italic">— no command recorded</span>
|
||||
<span class="text-xs text-base-content/40 italic">no command recorded</span>
|
||||
{{ end }}
|
||||
</td>
|
||||
<td class="text-right text-sm whitespace-nowrap" data-bytes="{{ .Size }}">{{ humanizeBytes .Size }}</td>
|
||||
@@ -41,10 +41,11 @@
|
||||
</table>
|
||||
</div>
|
||||
{{ else }}
|
||||
<div class="py-8 text-sm text-base-content/70 max-w-prose">
|
||||
<p class="font-medium text-base-content">No layer history available</p>
|
||||
<p class="mt-1">This image was built without history metadata — common for squashed images or tarballs imported with <code class="font-mono text-xs bg-base-300 px-1 py-0.5 rounded">docker load</code>.</p>
|
||||
</div>
|
||||
{{ template "state-empty" (dict
|
||||
"Icon" "history"
|
||||
"Title" "No layer history"
|
||||
"Subtext" "This image was built without history metadata, common for squashed images or tarballs imported with docker load."
|
||||
) }}
|
||||
{{ end }}
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
@@ -67,10 +67,11 @@
|
||||
|
||||
{{ else }}
|
||||
<!-- No tags exist -->
|
||||
<div class="text-center py-8 text-base-content/60">
|
||||
<p class="text-lg">No tags yet</p>
|
||||
<p class="text-sm mt-1">Push an image to get started.</p>
|
||||
</div>
|
||||
{{ template "state-empty" (dict
|
||||
"Icon" "tag"
|
||||
"Title" "No tags yet"
|
||||
"Subtext" "Push an image to this repository and its tags will appear here."
|
||||
) }}
|
||||
{{ end }}
|
||||
|
||||
<!-- Tab Navigation -->
|
||||
|
||||
@@ -18,8 +18,12 @@
|
||||
{{ end }}
|
||||
</div>
|
||||
{{ else }}
|
||||
<div class="card bg-base-200 shadow-sm p-6 text-center text-base-content/60">
|
||||
No holds configured. Push an image to get started.
|
||||
<div class="card bg-base-200 shadow-sm">
|
||||
{{ template "state-empty" (dict
|
||||
"Icon" "anchor"
|
||||
"Title" "No holds configured"
|
||||
"Subtext" "Push an image to atcr.io and your first hold will appear here."
|
||||
) }}
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{{ define "vuln-badge" }}
|
||||
{{ if .Error }}
|
||||
{{/* Hold unreachable. Warning color distinguishes from "not scanned" (gray). */}}
|
||||
<span class="badge badge-sm badge-warning" title="Hold is unreachable — try again in a moment">
|
||||
<span class="badge badge-sm badge-warning" title="Hold is unreachable; try again in a moment">
|
||||
{{ icon "wifi-off" "size-3" }} Hold offline
|
||||
</span>
|
||||
{{ else if .NotScanned }}
|
||||
|
||||
@@ -146,7 +146,10 @@ func AssetHash(path string) string {
|
||||
|
||||
// fontPreloadPaths returns the list of /fonts/*.woff2 files that exist in
|
||||
// the resolved public FS. Skips *-ext (extended glyph) subsets to avoid
|
||||
// preloading weights the primary latin face already covers.
|
||||
// preloading weights the primary latin face already covers, and skips
|
||||
// non-canonical weights so the critical preload burst stays focused on
|
||||
// the faces needed above the fold. Commit Mono 700 (~47KB) is loaded
|
||||
// on demand via @font-face with font-display: swap.
|
||||
func fontPreloadPaths(overrides *BrandingOverrides) []string {
|
||||
fsys := resolvePublicFS(overrides)
|
||||
entries, err := fs.ReadDir(fsys, "public/fonts")
|
||||
@@ -168,6 +171,9 @@ func fontPreloadPaths(overrides *BrandingOverrides) []string {
|
||||
if strings.Contains(name, "italic") {
|
||||
continue
|
||||
}
|
||||
if strings.Contains(name, "-700") {
|
||||
continue
|
||||
}
|
||||
paths = append(paths, "/fonts/"+name)
|
||||
}
|
||||
return paths
|
||||
|
||||
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 25 KiB |
@@ -72,14 +72,37 @@ function discoverIcons() {
|
||||
|
||||
const ICONS = discoverIcons();
|
||||
|
||||
// Custom Helm icon (from Simple Icons - official Helm logo)
|
||||
// Custom icon registry. Entries can inline `content`, or reference a source
|
||||
// SVG via `sourceFile` — the sprite builder extracts the inner markup of the
|
||||
// <svg> and wraps it in a <symbol> with the resolved viewBox.
|
||||
const CUSTOM_ICONS = {
|
||||
'helm': {
|
||||
viewBox: '0 0 24 24',
|
||||
content: '<path d="M12.337 0c-.475 0-.861 1.016-.861 2.269 0 .527.069 1.011.183 1.396a8.514 8.514 0 0 0-3.961 1.22 5.229 5.229 0 0 0-.595-1.093c-.606-.866-1.34-1.436-1.79-1.43a.381.381 0 0 0-.217.066c-.39.273-.123 1.326.596 2.353.267.381.559.705.84.948a8.683 8.683 0 0 0-1.528 1.716h1.734a7.179 7.179 0 0 1 5.381-2.421 7.18 7.18 0 0 1 5.382 2.42h1.733a8.687 8.687 0 0 0-1.32-1.53c.35-.249.735-.643 1.078-1.133.719-1.027.986-2.08.596-2.353a.382.382 0 0 0-.217-.065c-.45-.007-1.184.563-1.79 1.43a4.897 4.897 0 0 0-.676 1.325 8.52 8.52 0 0 0-3.899-1.42c.12-.39.193-.887.193-1.429 0-1.253-.386-2.269-.862-2.269zM1.624 9.443v5.162h1.358v-1.968h1.64v1.968h1.357V9.443H4.62v1.838H2.98V9.443zm5.912 0v5.162h3.21v-1.108H8.893v-.95h1.64v-1.142h-1.64v-.84h1.853V9.443zm4.698 0v5.162h3.218v-1.362h-1.86v-3.8zm4.706 0v5.162h1.364v-2.643l1.357 1.225 1.35-1.232v2.65h1.365V9.443h-.614l-2.1 1.914-2.109-1.914zm-11.82 7.28a8.688 8.688 0 0 0 1.412 1.548 5.206 5.206 0 0 0-.841.948c-.719 1.027-.985 2.08-.596 2.353.39.273 1.289-.338 2.007-1.364a5.23 5.23 0 0 0 .595-1.092 8.514 8.514 0 0 0 3.961 1.219 5.01 5.01 0 0 0-.183 1.396c0 1.253.386 2.269.861 2.269.476 0 .862-1.016.862-2.269 0-.542-.072-1.04-.193-1.43a8.52 8.52 0 0 0 3.9-1.42c.121.4.352.865.675 1.327.719 1.026 1.617 1.637 2.007 1.364.39-.273.123-1.326-.596-2.353-.343-.49-.727-.885-1.077-1.135a8.69 8.69 0 0 0 1.202-1.36h-1.771a7.174 7.174 0 0 1-5.227 2.252 7.174 7.174 0 0 1-5.226-2.252z" fill="currentColor" stroke="none"/>'
|
||||
},
|
||||
'tangled': {
|
||||
sourceFile: 'scripts/icons/tangled.svg'
|
||||
}
|
||||
};
|
||||
|
||||
// Read a source SVG and extract its viewBox + inner markup.
|
||||
function loadSvgSource(relPath) {
|
||||
const fullPath = path.join(__dirname, '..', relPath);
|
||||
const raw = fs.readFileSync(fullPath, 'utf8');
|
||||
const viewBoxMatch = raw.match(/viewBox="([^"]+)"/);
|
||||
if (!viewBoxMatch) {
|
||||
throw new Error(`No viewBox in ${relPath}`);
|
||||
}
|
||||
const innerMatch = raw.match(/<svg[^>]*>([\s\S]*)<\/svg>/);
|
||||
if (!innerMatch) {
|
||||
throw new Error(`Could not extract inner markup from ${relPath}`);
|
||||
}
|
||||
return {
|
||||
viewBox: viewBoxMatch[1],
|
||||
content: innerMatch[1].trim()
|
||||
};
|
||||
}
|
||||
|
||||
// Lucide icon name to Pascal case mapping (for require path)
|
||||
function toPascalCase(str) {
|
||||
return str.split('-').map(word =>
|
||||
@@ -133,7 +156,8 @@ function generateSprite() {
|
||||
|
||||
// Process custom icons
|
||||
for (const [name, icon] of Object.entries(CUSTOM_ICONS)) {
|
||||
symbols.push(` <symbol id="${name}" viewBox="${icon.viewBox}">${icon.content}</symbol>`);
|
||||
const resolved = icon.sourceFile ? loadSvgSource(icon.sourceFile) : icon;
|
||||
symbols.push(` <symbol id="${name}" viewBox="${resolved.viewBox}">${resolved.content}</symbol>`);
|
||||
}
|
||||
|
||||
const sprite = `<svg xmlns="http://www.w3.org/2000/svg" style="display:none">
|
||||
|
||||
5
scripts/icons/tangled.svg
Normal file
|
After Width: | Height: | Size: 9.5 KiB |