ArchiveBox/archivebox/templates/core/snapshot.html
2026-08-30 15:39:35 -07:00

2229 lines
95 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{% load static tz core_tags %}
<!DOCTYPE html>
<html lang="en">
<head>
<title>{{title}}</title>
<meta charset="utf-8" name="viewport" content="width=device-width, initial-scale=1">
<style>
/* Keep this inline, don't move to external css file because this template is used to generate static exports that need to be usable as-is without an accompanying staticfiles dir */
* { box-sizing: border-box; }
html, body {
width: 100%;
height: 100%;
background-color: #ddd;
margin: 0;
padding: 0;
}
body {
display: flex;
flex-direction: column;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
}
a { color: inherit; text-decoration: none; }
a:hover { text-decoration: underline; }
.container-fluid { width: 100%; margin: 0 auto; padding: 0 12px; }
.row { display: flex; flex-wrap: wrap; width: 100%; margin: 0; }
.col-lg-2 { flex: 0 0 16.6667%; max-width: 16.6667%; }
.col-lg-4 { flex: 0 0 33.3333%; max-width: 33.3333%; }
.col-lg-8 { flex: 0 0 66.6667%; max-width: 66.6667%; }
.col-lg-12 { flex: 0 0 100%; max-width: 100%; }
.badge { display: inline-block; padding: 2px 6px; border-radius: 4px; font-size: 12px; line-height: 1.2; }
.badge-default { background: rgba(255,255,255,0.2); color: #f6f6f6; }
.badge-info { background: #dbe7ff; color: #2b4aa0; }
.badge-success { background: #d4edda; color: #1e7e34; }
.badge-warning { background: #fff3cd; color: #856404; }
.badge-danger { background: #f8d7da; color: #721c24; }
.alert { padding: 6px 10px; border-radius: 6px; background: #f5f5f5; color: #333; }
header {
background-color: #aa1e55;
order: 1;
flex: 0 0 auto;
container-type: inline-size;
container-name: snapshot-header;
}
small {
font-weight: 200;
}
header a:hover {
text-decoration: none;
}
.header-top {
width: 100%;
height: auto;
min-height: 40px;
margin: 0px;
text-align: center;
color: #f6f6f6;
font-size: calc(10px + 0.44vw);
font-weight: 200;
padding: 3px 4px;
background-color: #aa1e55;
}
.header-top .header-nav {
display: grid;
grid-template-columns: auto minmax(0, 1fr) auto auto;
align-items: start;
gap: 16px;
width: 100%;
}
.header-top .header-col {
min-height: 30px;
line-height: 1.2;
min-width: 0;
}
.header-top .header-left {
white-space: nowrap;
}
.header-top .header-main {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 4px;
min-width: 0;
}
.header-top .header-meta {
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 4px;
min-width: 0;
}
.header-top .header-right {
text-align: right;
white-space: nowrap;
padding-right: 10px;
}
.header-right .header-date {
text-align: center;
}
.header-right .snapshot-date-summary {
display: inline-flex;
align-items: center;
gap: 8px;
}
.header-right .snapshot-count-badge {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 18px;
height: 18px;
padding: 0 5px;
border-radius: 999px;
background: rgba(255,255,255,0.18);
color: #f6f6f6;
font-size: 11px;
font-weight: 600;
line-height: 1;
box-sizing: border-box;
}
.snapshot-variants {
position: relative;
display: inline-block;
white-space: nowrap;
}
.snapshot-variants summary {
list-style: none;
cursor: pointer;
color: #f6f6f6 !important;
}
.snapshot-variants summary:hover {
color: #f6f6f6 !important;
}
.header-top .snapshot-variants summary {
color: #f6f6f6 !important;
}
.snapshot-variants summary::-webkit-details-marker {
display: none;
}
.snapshot-variants-list {
position: absolute;
right: 0;
top: calc(100% + 6px);
background: rgba(18, 18, 18, 0.95);
border: 1px solid rgba(255,255,255,0.15);
border-radius: 8px;
min-width: 260px;
max-width: 420px;
max-height: 240px;
overflow: auto;
box-shadow: 0 6px 20px rgba(0,0,0,0.3);
z-index: 50;
padding: 6px;
}
.snapshot-variants-list a {
display: block;
padding: 6px 8px;
color: #f6f6f6;
font-size: 12px;
line-height: 1.3;
border-radius: 6px;
}
.snapshot-variants-list a:hover {
background: rgba(255,255,255,0.08);
color: #fff;
}
.header-top .snapshot-variants-list a {
color: #f6f6f6 !important;
}
.header-top .snapshot-variants-list a:hover {
color: #fff !important;
}
.year-variants summary {
list-style: none;
cursor: pointer;
}
.year-variants summary::-webkit-details-marker {
display: none;
}
.header-top .header-url {
width: 100%;
background-color: rgb(216, 216, 235, 0.05);
text-align: left;
line-height: 1.3;
font-family: monospace;
font-weight: 200;
margin-top: 0;
font-size: 23px;
opacity: 0.8;
border-radius: 0px 0px 8px 8px;
}
.header-top .header-url a.header-url-text {
display: block;
color: #f6f6f6;
user-select: all;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
padding: 2px 10px;
}
.header-top .header-url a.header-url-text:hover {
color: rgb(144, 161, 255);
}
.header-top a {
text-decoration: none;
color: rgba(0,0,0,0.6);
}
.header-top a:hover {
text-decoration: none;
color: rgba(0,0,0,0.9);
}
.header-title-line {
color: rgba(0,0,0,0.6);
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 6px;
min-width: 0;
width: 100%;
}
.header-title-text {
display: inline-block;
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
text-align: left;
}
.header-tags {
display: flex;
flex-wrap: wrap;
justify-content: flex-start;
gap: 4px;
align-items: center;
}
.header-tags .tag-pill {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 3px 8px;
border-radius: 999px;
background: var(--tag-bg, rgba(255,255,255,0.2));
color: var(--tag-fg, #fff);
border: 1px solid var(--tag-border, rgba(255,255,255,0.26));
font-size: 12px;
line-height: 1.2;
white-space: nowrap;
}
.header-tags .permission-pill {
border-style: dashed;
text-transform: uppercase;
}
.header-badges {
display: flex;
flex-wrap: wrap;
justify-content: flex-end;
align-items: center;
gap: 6px;
}
.header-mobile-badges {
display: none;
}
.badge-label,
.badge-mobile-text,
.header-date-label {
display: none;
}
.header-year-badges {
display: flex;
flex-wrap: wrap;
justify-content: flex-end;
align-items: center;
gap: 6px;
margin-top: 4px;
}
.header-top .favicon {
width: 20px;
height: 20px;
max-width: 30px;
max-height: 30px;
vertical-align: -4px;
margin-right: 6px;
object-fit: contain;
border-radius: 3px;
background: rgba(255,255,255,0.08);
}
.header-top .col-lg-4 {
text-align: center;
padding-top: 4px;
padding-bottom: 4px;
}
.header-archivebox img {
display: inline-block;
margin-right: 3px;
height: 30px;
margin-left: 12px;
margin-top: -4px;
margin-bottom: 2px;
}
.header-archivebox {
display: inline-flex;
align-items: center;
gap: 6px;
white-space: nowrap;
}
.header-right .header-date {
display: inline-block;
white-space: nowrap;
}
.header-archivebox img:hover {
opacity: 0.5;
}
header small code {
white-space: nowrap;
font-weight: 200;
display: block;
margin-top: -1px;
font-size: 13px;
opacity: 0.8;
user-select: all;
}
.header-toggle {
line-height: 1;
font-size: 24px;
margin-top: 0;
margin-left: 4px;
}
@container snapshot-header (max-width: 900px) {
.header-top .header-nav {
grid-template-columns: 1fr;
gap: 8px;
}
.header-top .header-left,
.header-top .header-main,
.header-top .header-meta,
.header-top .header-right {
width: 100%;
text-align: left;
align-items: flex-start;
}
.header-archivebox img {
margin-left: 0;
}
}
@container snapshot-header (max-width: 900px) {
.header-top {
padding: 10px 12px 12px;
font-size: 14px;
text-align: left;
}
.header-top .header-nav {
gap: 10px;
}
.header-top .header-left {
order: 1;
padding-bottom: 2px;
}
.header-top .header-main {
order: 2;
gap: 8px;
}
.header-top .header-meta {
order: 3;
gap: 8px;
}
.header-top .header-right {
order: 4;
display: grid;
grid-template-columns: minmax(0, 1fr);
gap: 8px;
padding: 0 !important;
}
.header-archivebox {
color: #fff !important;
font-size: 16px;
font-weight: 600;
}
.header-archivebox img {
width: 28px;
height: 28px;
margin: 0;
}
.header-top .header-url {
font-size: 15px;
background: rgba(0, 0, 0, 0.14);
border: 1px solid rgba(255, 255, 255, 0.12);
border-radius: 7px;
}
.header-title-text,
.header-top .header-url a.header-url-text {
white-space: normal;
overflow: hidden;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
.header-top .header-url a.header-url-text {
padding: 7px 9px;
line-height: 1.35;
}
.header-title-line {
display: grid;
grid-template-columns: 22px minmax(0, 1fr) 28px;
gap: 7px;
align-items: center;
color: #fff;
}
.header-title-text {
grid-column: 2;
grid-row: 1;
color: #fff;
font-size: 14px;
font-weight: 500;
line-height: 1.35;
}
.header-top .favicon {
grid-column: 1;
grid-row: 1;
width: 22px;
height: 22px;
margin: 0;
}
.header-tags {
grid-column: 1 / -1;
grid-row: 2;
width: 100%;
justify-content: flex-start;
}
.header-tags .tag-pill {
padding: 4px 8px;
font-size: 11px;
background: rgba(255, 255, 255, 0.15);
color: #fff;
}
.header-toggle {
grid-column: 3;
grid-row: 1;
display: inline-flex;
align-items: center;
justify-content: center;
width: 28px;
height: 28px;
margin: 0;
border-radius: 6px;
background: rgba(0, 0, 0, 0.14);
color: #fff !important;
font-size: 24px;
line-height: 1;
}
.header-badges {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(100%, 8rem), 1fr));
width: 100%;
gap: 6px;
}
header .header-badges .badge {
display: flex;
align-items: center;
justify-content: space-between;
min-width: 0;
min-height: 34px;
margin: 0;
padding: 6px 8px;
border: 1px solid rgba(255, 255, 255, 0.16);
font-family: inherit;
font-size: 12px;
font-weight: 600;
}
.badge-label,
.header-date-label {
display: inline;
margin-right: 6px;
color: currentColor;
font-size: 10px;
font-weight: 600;
letter-spacing: 0.04em;
opacity: 0.72;
text-transform: uppercase;
}
.badge-desktop-icon {
display: none;
}
.badge-mobile-text {
display: inline;
}
.header-year-badges {
width: 100%;
margin: 0;
padding: 1px 0 0;
justify-content: flex-start;
}
.header-year-badges::before {
content: "Years";
color: rgba(255, 255, 255, 0.7);
font-size: 10px;
font-weight: 600;
letter-spacing: 0.04em;
text-transform: uppercase;
}
header .header-year-badges .badge {
margin: 0;
padding: 4px 7px;
font-size: 11px;
}
.header-right .header-date,
.header-right .snapshot-variants {
display: block;
width: 100%;
min-width: 0;
white-space: normal;
}
.header-right .snapshot-date-summary,
.header-right > .header-date {
display: flex;
align-items: center;
justify-content: flex-start;
min-height: 34px;
padding: 6px 8px;
border: 1px solid rgba(255, 255, 255, 0.16);
border-radius: 6px;
background: rgba(0, 0, 0, 0.12);
color: #fff !important;
gap: 7px;
}
.header-right br {
display: none;
}
header .external-links {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 6px;
width: 100%;
padding: 0;
color: #fff;
font-size: 12px;
text-align: left;
overflow: visible;
}
header .external-links a {
display: inline-flex;
align-items: center;
min-height: 32px;
padding: 6px 9px;
border: 1px solid rgba(255, 255, 255, 0.16);
border-radius: 6px;
background: rgba(0, 0, 0, 0.12);
color: #fff;
}
.external-links-separator {
display: none;
}
.snapshot-variants-list {
right: auto;
left: 0;
width: min(360px, calc(100vw - 24px));
min-width: 0;
}
}
@container snapshot-header (max-width: 480px) {
.header-top {
padding: 6px 8px 7px;
font-size: 12px;
}
.header-top .header-nav {
grid-template-columns: 22px minmax(0, 1fr);
gap: 6px;
}
.header-top .header-left {
grid-column: 1;
grid-row: 1;
min-height: 0;
padding-bottom: 0;
line-height: 1 !important;
}
.header-top .header-main {
display: contents;
}
.header-top .header-meta {
grid-column: 1 / -1;
grid-row: 3;
gap: 4px;
}
.header-top .header-right {
grid-column: 1 / -1;
grid-row: 4;
grid-template-columns: repeat(2, minmax(0, 1fr));
align-items: stretch;
gap: 4px;
}
.header-archivebox {
display: flex;
align-items: center;
justify-content: center;
width: 22px;
height: 22px;
overflow: hidden;
font-size: 0;
line-height: 1;
}
.header-archivebox img {
display: block;
width: auto;
height: auto;
max-width: 20px;
max-height: 20px;
margin: 0;
object-fit: contain;
}
.header-top .header-url {
grid-column: 2;
grid-row: 1;
font-size: 13px;
}
.header-top .header-url a.header-url-text {
display: block;
padding: 5px 7px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.header-title-line {
grid-column: 1 / -1;
grid-row: 2;
grid-template-columns: 22px minmax(0, 1fr) 24px;
gap: 5px;
}
.header-top .favicon {
width: 18px;
height: 18px;
justify-self: center;
}
.header-title-text {
display: block;
font-size: 13px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.header-toggle {
width: 24px;
height: 24px;
font-size: 20px;
}
.header-tags {
gap: 3px;
}
.header-tags .tag-pill {
padding: 2px 6px;
font-size: 10px;
}
.header-badges {
display: none;
}
.header-mobile-badges {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 4px;
width: 100%;
}
.header-mobile-badge {
display: flex;
align-items: center;
justify-content: space-between;
min-width: 0;
min-height: 28px;
padding: 4px 7px;
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 6px;
background: rgba(255, 255, 255, 0.14);
color: #fff !important;
font-size: 11px;
font-weight: 600;
line-height: 1;
}
.header-mobile-badge:last-child {
background: rgba(212, 237, 218, 0.96);
color: #315d3b !important;
}
.header-mobile-badge-separator {
margin: 0 5px;
opacity: 0.45;
}
.badge-label,
.header-date-label,
.header-year-badges::before {
font-size: 9px;
}
.header-year-badges {
display: none;
}
.header-right .snapshot-date-summary,
.header-right > .header-date {
height: 100%;
min-height: 32px;
padding: 4px 6px;
gap: 5px;
font-size: 11px;
font-family: inherit;
font-weight: 600;
line-height: 1;
}
.header-right .snapshot-date-summary > * {
display: inline-flex;
align-items: center;
height: 16px;
margin: 0;
font-family: inherit;
font-size: 11px;
font-weight: 600;
line-height: 1;
letter-spacing: 0;
text-transform: none;
opacity: 1;
}
.header-right > .snapshot-variants,
.header-right > .header-date {
grid-column: 2;
grid-row: 1;
height: 100%;
}
.header-right .snapshot-count-badge {
min-width: 16px;
height: 16px;
padding: 0 4px;
font-size: 9px;
}
header .external-links {
grid-column: 1;
grid-row: 1;
display: grid;
grid-template-columns: auto minmax(0, 1fr);
align-items: stretch;
gap: 4px;
font-size: 11px;
flex-wrap: nowrap;
height: 100%;
}
header .external-links a {
justify-content: center;
height: 100%;
min-height: 32px;
font-family: inherit;
font-size: 11px;
font-weight: 600;
line-height: 1;
padding: 4px 7px;
}
}
.info-row {
margin-top: 2px;
margin-bottom: 5px;
}
.info-row .alert {
margin-bottom: 0px;
}
.row.header-bottom {
margin-left: -10px;
margin-right: -10px;
}
.header-bottom .col-lg-2 {
padding-left: 4px;
padding-right: 4px;
}
.header-bottom-frames .card {
box-shadow: 2px 2px 7px 0px rgba(0, 0, 0, 0.1);
margin-bottom: 6px;
border: 1px solid rgba(0, 0, 0, 0.06);
border-radius: 10px;
background-color: #efefef;
overflow: hidden;
min-height: 130px;
}
.header-bottom-frames .card:has([data-compact]) {
min-height: 0;
}
.card h4 {
font-size: 0.8em;
display: inline-block;
width: auto;
text-transform: uppercase;
margin-top: 0px;
margin-bottom: 5px;
color: #222;
}
.card-body {
font-size: 14px;
padding: 4px 10px;
padding-bottom: 0px;
/* padding-left: 3px; */
/* padding-right: 3px; */
/* padding-bottom: 3px; */
line-height: 1;
word-wrap: break-word;
max-height: 102px;
overflow: hidden;
text-overflow: ellipsis;
color: #222;
background-color: #f6f6f6;
}
.card-title {
margin-bottom: 4px;
text-transform: uppercase;
}
.card-img-top {
border: 0px;
padding: 0px;
margin: 0px;
overflow: hidden;
opacity: 0.8;
border-top: 1px solid rgba(0,0,0,0);
border-radius: 4px;
border-bottom: 1px solid rgba(0,0,0,0);
height: 430px;
width: 405%;
margin-bottom: -330px;
background-color: #333;
margin-left: -1%;
margin-right: -1%;
pointer-events: none;
transform: scale(0.25);
transform-origin: 0 0;
}
#main-frame {
border-top: 1px solid #ddd;
width: 100%;
height: 100%;
margin: 0px;
border: 0px;
border-top: 3px solid #aa1e55;
}
#main-frame-wrapper {
width: 100%;
height: auto;
border-top: 3px solid #aa1e55;
overflow: hidden;
order: 2;
flex: 1 1 auto;
min-height: 0;
display: flex;
flex-direction: column;
}
#main-frame-wrapper iframe {
width: 100%;
height: 100%;
border: none;
}
#snapshot-progress-wrapper {
flex: 0 0 auto;
width: 100%;
}
#main-frame-wrapper > #main-frame,
#main-frame-wrapper > #snapshot-empty-state {
flex: 1 1 auto;
min-height: 0;
}
.full-page-wrapper {
width: 100%;
min-height: 0;
}
.snapshot-empty-state {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
background: #f6f8fa;
color: #475569;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
}
.snapshot-empty-state-inner {
max-width: 520px;
padding: 32px 36px;
text-align: center;
}
.snapshot-empty-icon {
font-size: 56px;
line-height: 1;
margin-bottom: 12px;
}
.snapshot-empty-state h2 {
margin: 0 0 12px;
color: #1f2937;
font-size: 22px;
font-weight: 600;
}
.snapshot-empty-state p {
margin: 4px 0;
color: #64748b;
font-size: 14px;
line-height: 1.5;
}
.snapshot-empty-actions {
display: flex;
justify-content: center;
flex-wrap: wrap;
gap: 10px;
margin-top: 22px;
}
.snapshot-empty-btn {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 8px 16px;
background: #ffffff;
border: 1px solid #d0d7de;
border-radius: 6px;
color: #1f2937;
font-size: 13px;
font-weight: 500;
text-decoration: none;
transition: background-color 0.15s, border-color 0.15s;
}
.snapshot-empty-btn:hover {
background: #eef4fa;
border-color: #b7d3ea;
color: #004882;
text-decoration: none;
}
.thumbnail-wrapper {
height: 100px;
overflow: hidden;
background-color: #f2f2f2;
pointer-events: none;
}
.thumbnail-wrapper.compact {
height: 32px;
background-color: #e9e9e9;
}
.thumbnail-wrapper iframe {
width: 405%;
height: 430px;
margin-bottom: -330px;
margin-left: -1%;
transform: scale(0.25);
transform-origin: 0 0;
border: none;
background: #fff;
}
.thumbnail-wrapper img {
width: 100%;
height: 100%;
object-fit: cover;
object-position: top center;
}
.thumbnail-compact {
height: 32px;
display: flex;
align-items: center;
gap: 6px;
padding: 0 8px;
font-size: 13px;
line-height: 1;
color: #bdbdbd;
text-transform: uppercase;
letter-spacing: 0.02em;
}
.thumbnail-compact-label {
color: #e1e1e1;
}
.thumbnail-compact-meta {
color: #777;
font-size: 11px;
margin-left: auto;
}
.thumbnail-compact svg,
.thumbnail-compact img {
height: 12px;
width: 12px;
}
.thumbnail-text {
height: 100px;
background: #121212;
color: #d8d8d8;
padding: 6px 8px;
display: flex;
flex-direction: column;
gap: 4px;
font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
font-size: 11px;
line-height: 1.2;
overflow: hidden;
}
.thumbnail-text-header {
display: flex;
align-items: center;
gap: 6px;
font-size: 10px;
color: #9b9b9b;
text-transform: uppercase;
letter-spacing: 0.04em;
}
.thumbnail-text-pre {
margin: 0;
white-space: pre-wrap;
overflow: hidden;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 5;
}
.thumbnail-text[data-plugin="title"] .thumbnail-text-pre {
font-size: 13px;
font-weight: 600;
-webkit-line-clamp: 3;
}
.card.selected-card {
border: 2px solid orange;
box-shadow: 0px -6px 13px 1px rgba(0,0,0,0.05);
}
.thumb-compact .card-body {
display: block;
padding: 4px 8px;
font-size: 12px;
line-height: 1.2;
max-height: none;
}
.thumb-compact {
margin-bottom: 0px;
border-radius: 6px;
}
.thumb-compact .thumbnail-wrapper {
height: 32px;
}
.thumb-compact .thumbnail-compact,
.thumb-compact .thumbnail-text {
height: 32px;
max-height: 32px;
}
.preview-hidden {
display: none !important;
}
img.external {
height: 30px;
margin-right: -10px;
padding: 3px;
border-radius: 4px;
vertical-align: middle;
border: 4px solid rgba(0,0,0,0);
}
img.external:hover {
border: 4px solid green;
}
.screenshot {
background-color: #333;
transform: scale(1.05);
transform-origin: top center;
width: 100%;
min-height: 100px;
max-height: 100px;
margin-bottom: 0px;
object-fit: cover;
object-position: top center;
}
.thumb-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(clamp(180px, 14vw, 250px), 1fr));
grid-auto-flow: row dense;
grid-auto-rows: 42px;
gap: 6px;
width: 100%;
max-width: 100%;
margin-left: 0;
padding: 2px 6px 0;
}
.thumb-card {
box-shadow: 2px 2px 7px 0px rgba(0, 0, 0, 0.1);
border: 1px solid rgba(0, 0, 0, 0.06);
border-radius: 10px;
background-color: #efefef;
overflow: hidden;
width: 100%;
box-sizing: border-box;
height: 138px;
min-height: 138px;
max-height: 138px;
display: inline-flex;
flex-direction: column;
align-items: stretch;
grid-row: span 3;
break-inside: avoid;
margin-bottom: 0;
vertical-align: top;
content-visibility: auto;
contain-intrinsic-size: 138px;
}
.thumb-card:not([data-plugin-name]) {
order: 1;
}
.thumb-card:has([data-compact]) {
height: 42px;
min-height: 42px;
max-height: 42px;
grid-row: span 1;
order: 2;
contain-intrinsic-size: 42px;
}
.thumb-card .thumb-body {
display: grid;
grid-template-columns: minmax(0, 1fr) auto;
align-items: center;
gap: 6px;
font-size: 14px;
min-height: 30px;
padding: 4px 6px 4px 8px;
line-height: 1.2;
word-wrap: break-word;
overflow: hidden;
text-overflow: ellipsis;
color: #222;
background-color: #f6f6f6;
flex: 0 0 auto;
position: relative;
}
.thumb-actions {
position: static;
grid-column: 2;
grid-row: 1;
display: flex;
align-items: center;
gap: 3px;
font-size: 12px;
line-height: 1;
opacity: 0.8;
}
.thumb-actions a,
.thumb-actions button {
display: inline-flex;
align-items: center;
justify-content: center;
width: 22px;
height: 22px;
border-radius: 5px;
background: #e7ebef;
text-decoration: none;
color: inherit;
border: 0;
cursor: pointer;
}
.thumb-actions a:hover,
.thumb-actions button:hover {
background: #d9e0e7;
opacity: 1;
}
.thumb-actions .delete-pending {
width: auto;
min-width: 22px;
color: #b91c1c;
font-weight: 700;
}
.thumb-card .thumb-body > a:not(.thumb-actions a),
.thumb-card .thumb-body > h4 {
grid-column: 1;
grid-row: 1;
min-width: 0;
}
.thumb-card .thumb-body h4 {
display: flex;
align-items: center;
gap: 2px;
font-size: 0.8em;
text-transform: uppercase;
margin: 0 0 2px 0;
color: #222;
line-height: 1.1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.thumb-card .thumb-body h4 .abx-plugin-icon {
flex: 0 0 20px;
align-self: center;
line-height: 1;
vertical-align: middle;
transform: translateY(1px);
}
.thumb-card .thumbnail-wrapper,
.thumb-card iframe.card-img-top {
display: block;
width: 100%;
flex: 1 1 auto;
min-height: 0;
}
.thumb-card .thumbnail-wrapper {
display: flex;
align-items: stretch;
width: 100% !important;
min-width: 0;
max-width: 100%;
box-sizing: border-box;
height: auto !important;
position: relative;
overflow: hidden;
pointer-events: auto;
}
.thumb-card .thumbnail-wrapper > *,
.thumb-card iframe.card-img-top {
width: 100% !important;
height: 100%;
object-fit: cover;
max-width: 100%;
}
.thumb-card .card-img-top {
width: 100% !important;
max-width: 100% !important;
height: 100% !important;
margin: 0 !important;
transform: none !important;
opacity: 1 !important;
}
.thumb-card .thumbnail-wrapper img,
.thumb-card .thumbnail-wrapper video,
.thumb-card .thumbnail-wrapper canvas {
width: 100% !important;
height: 100% !important;
max-width: 100% !important;
object-fit: cover;
transform: none !important;
margin: 0 !important;
}
.thumb-card .thumbnail-wrapper iframe,
.thumb-card .thumbnail-wrapper object,
.thumb-card .thumbnail-wrapper embed {
width: 405% !important;
height: 405% !important;
transform: scale(0.25);
transform-origin: 0 0;
margin: 0 !important;
border: 0 !important;
pointer-events: none;
}
.thumb-card iframe.card-img-top {
width: 405% !important;
height: 405% !important;
transform: scale(0.25);
transform-origin: 0 0;
margin: 0 !important;
border: 0 !important;
pointer-events: none;
}
.thumb-card .thumbnail-click-overlay {
position: absolute;
inset: 0;
z-index: 2;
display: block;
background: transparent;
pointer-events: auto;
cursor: pointer;
}
.thumb-card .thumbnail-wrapper > :not(.thumbnail-click-overlay),
.thumb-card .thumbnail-wrapper > :not(.thumbnail-click-overlay) * {
pointer-events: none !important;
}
.thumb-card:has([data-compact]) .thumbnail-wrapper,
.thumb-card:has([data-compact]) .thumbnail-wrapper.compact {
height: 20px;
flex: 0 0 auto;
}
.thumb-card:has([data-compact]) .thumb-body {
min-height: 22px;
padding: 2px 4px 2px 6px;
font-size: 12px;
max-height: 24px;
}
.thumb-card:has([data-compact]) .thumb-actions a,
.thumb-card:has([data-compact]) .thumb-actions button {
width: 18px;
height: 18px;
border-radius: 4px;
}
.thumb-card:has([data-compact]) .thumb-actions .delete-pending {
width: auto;
}
.thumb-card:has([data-compact]) .thumb-body h4 {
font-size: 0.9em;
margin-bottom: 0px;
line-height: 1;
display: flex;
align-items: center;
gap: 4px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.thumb-card:has([data-compact]) .thumbnail-text-header,
.thumb-card:has([data-compact]) .thumbnail-compact-icon,
.thumb-card:has([data-compact]) .thumbnail-compact-label {
display: none;
}
.thumb-card.selected-card {
border: 2px solid orange;
box-shadow: 0px -6px 13px 1px rgba(0,0,0,0.05);
content-visibility: visible;
}
.loose-items {
display: flex;
flex-wrap: wrap;
gap: 4px 8px;
font-size: 12px;
line-height: 1.2;
max-height: 84px;
overflow: auto;
min-width: 0;
max-width: 100%;
}
.loose-items a {
color: #333;
text-decoration: none;
background: rgba(0, 0, 0, 0.04);
padding: 2px 6px;
border-radius: 6px;
min-width: 0;
max-width: 100%;
overflow-wrap: anywhere;
word-break: break-word;
}
.loose-items a:hover {
background: rgba(0, 0, 0, 0.08);
}
.failed-items a {
color: #b91c1c;
background: rgba(185, 28, 28, 0.08);
}
.failed-items a:hover {
background: rgba(185, 28, 28, 0.16);
}
.header-bottom {
border-top: 1px solid rgba(170, 30, 85, 0.9);
padding-bottom: 1px;
border-bottom: 5px solid rgb(170, 30, 85);
margin-bottom: -1px;
border-radius: 0px;
background-color: #f4eeee;
border: 1px solid rgba(0,0,0,0.2);
box-shadow: 4px 4px 4px rgba(0,0,0,0.2);
margin-top: 0px;
}
.header-bottom.container-fluid {
padding-left: 6px;
padding-right: 6px;
}
.header-bottom-info {
color: #6f6f6f;
padding-top: 0px;
padding-bottom: 0px;
margin: 0px -15px;
}
.header-bottom-info > div {
text-align: center;
}
.header-bottom-info h5 {
font-size: 12px;
font-weight: 400;
margin-top: 3px;
margin-bottom: 3px;
}
.info-chunk {
width: auto;
display: inline-block;
text-align: center;
margin: 8px 4px;
vertical-align: top;
font-size: 14px;
}
header .badge {
margin-top: 3px;
font-size: 0.9rem;
font-weight: 200;
font-family: monospace;
}
header .internal-links {
text-align: left;
opacity: 1;
background-color: rgba(0,0,0,0.03);
padding: 1px 3px;
}
header .external-links {
text-align: center;
opacity: 0.9;
/*background-color: rgba(0,0,0,0.03);*/
margin-top: 0px;
padding: 1px 3px;
font-size: 14px;
color: #ddd;
width: 100%;
overflow: hidden;
}
.row.header-bottom-frames {
padding-top: 5px;
display: block !important;
width: 100%;
max-width: 100%;
column-width: 180px;
column-gap: 8px;
column-fill: auto;
margin-left: 0px;
margin-right: 0px;
flex: none !important;
}
.header-bottom-frames .col-lg-2 {
padding-left: 0px;
padding-right: 0px;
max-width: 100%;
width: 100% !important;
display: inline-block !important;
float: none !important;
flex: none !important;
break-inside: avoid;
margin-bottom: 6px;
vertical-align: top;
}
.header-bottom-frames .card:has([data-compact]) .thumbnail-wrapper,
.header-bottom-frames .card:has([data-compact]) .thumbnail-wrapper.compact {
height: 32px;
}
.header-bottom-frames .card:has([data-compact]) .thumbnail-text {
height: auto;
max-height: 64px;
}
.header-bottom-frames .card:has([data-compact]) .card-body {
padding: 4px 8px;
max-height: 44px;
}
.header-bottom-frames .card-title {
width: 100%;
text-align: center;
font-size: 17px;
margin-bottom: 0px;
display: inline-block;
color: #222;
font-weight: 200;
vertical-align: 3px;
}
.header-bottom-frames .card-text {
/* width: 100%;
text-align: center;*/
font-size: 0.9em;
display: inline-block;
position: relative;
/* top: -11px;*/
}
.card-text code {
padding: .1rem .2rem;
font-size: 90%;
color: #bd4147;
background-color: rgb(204, 204, 204, 0.28);
border-radius: .25rem;
}
/*@media(max-width: 1092px) {
iframe {
display: none;
}
}*/
@media(max-width: 728px) {
.card h4 {
font-size: 5vw;
}
.card-body {
font-size: 4vw;
}
.card {
margin-bottom: 5px;
}
header > h1 > a.header-url, header > h1 > a.header-archivebox {
display: none;
}
}
</style>
</head>
<body>
<div id="main-frame-wrapper" class="full-page-wrapper" data-has-outputs="{{ has_outputs|yesno:'1,0' }}" data-snapshot-state="{{ snapshot_state }}">
{% if not STATIC_EXPORT %}
{% if snapshot_state == 'queued' or snapshot_state == 'started' or snapshot_state == 'paused' %}
<div id="snapshot-progress-wrapper">
{% include "progressmonitor/progress_monitor.html" with progress_endpoint=progress_endpoint progress_scope="snapshot" %}
</div>
{% endif %}
{% endif %}
{% if has_outputs %}
<iframe id="main-frame"
sandbox="allow-same-origin allow-top-navigation-by-user-activation allow-scripts allow-forms"
class="full-page-iframe"
src="about:blank"
data-default-src="{% if best_result.path %}{% snapshot_preview_url snapshot best_result.path %}{% elif STATIC_EXPORT %}{% snapshot_url snapshot 'index.jsonl' %}{% else %}{% snapshot_base_url snapshot %}/?files=1{% endif %}"
name="preview"
loading="eager"
fetchpriority="high"></iframe>
{% else %}
<div id="snapshot-empty-state" class="snapshot-empty-state" data-snapshot-state="{{ snapshot_state }}">
<div class="snapshot-empty-state-inner">
{% if snapshot_state == 'paused' %}
<div class="snapshot-empty-icon">⏸️</div>
<h2>Crawl paused</h2>
<p>This snapshot's crawl is paused — no extractors have run yet.</p>
<p>Resume the crawl to start archiving content.</p>
{% elif snapshot_state == 'started' %}
<div class="snapshot-empty-icon"></div>
<h2>Archiving in progress…</h2>
<p>Extractors are running. Results will appear automatically.</p>
{% elif snapshot_state == 'queued' %}
<div class="snapshot-empty-icon">🕓</div>
<h2>Queued for archiving</h2>
<p>This snapshot is waiting in the queue. Results will appear automatically once a worker picks it up.</p>
{% else %}
<div class="snapshot-empty-icon">📭</div>
<h2>No archive outputs yet</h2>
<p>This snapshot has no archived content to display.</p>
{% endif %}
<div class="snapshot-empty-actions">
<button type="button" id="snapshot-empty-refresh" class="snapshot-empty-btn">🔄 Refresh page</button>
<a href="{{ url }}" target="_blank" rel="noopener noreferrer" class="snapshot-empty-btn">🌐 Open original URL</a>
{% if not STATIC_EXPORT %}
<a href="{% admin_base_url %}/admin/core/snapshot/{{ snapshot_id }}/change/" class="snapshot-empty-btn">✏️ Edit in admin</a>
{% endif %}
</div>
</div>
</div>
{% endif %}
</div>
<script>
(() => {
const refreshBtn = document.getElementById('snapshot-empty-refresh');
if (refreshBtn) refreshBtn.addEventListener('click', () => location.reload());
const monitor = document.getElementById('progress-monitor');
if (!monitor) return;
const wrapper = document.getElementById('main-frame-wrapper');
const hasOutputs = wrapper && wrapper.dataset.hasOutputs === '1';
if (!hasOutputs) monitor.classList.remove('collapsed');
})();
</script>
<script>
(() => {
const frame = document.getElementById('main-frame')
if (!frame) return
const snapshotBaseUrlEarly = "{% snapshot_base_url snapshot %}"
const snapshotFilesUrlEarly = "{% if STATIC_EXPORT %}{% snapshot_url snapshot 'index.jsonl' %}{% else %}{% snapshot_base_url snapshot %}/?files=1{% endif %}"
const defaultSrc = frame.dataset.defaultSrc || snapshotFilesUrlEarly
const rawHash = window.location.hash ? window.location.hash.slice(1) : ''
function resolveEarlyPreviewUrl(raw) {
if (!raw) return defaultSrc
let value = raw
try {
value = decodeURIComponent(value)
} catch (err) {}
if (value === 'all') return snapshotFilesUrlEarly
if (value.startsWith('http://') || value.startsWith('https://')) return value
if (value.startsWith('//')) return window.location.protocol + value
value = value.replace(/^\/+/, '')
return `${snapshotBaseUrlEarly}/${value}`
}
const target = resolveEarlyPreviewUrl(rawHash)
if (target) {
if (target.endsWith('.pdf')) {
frame.removeAttribute('sandbox')
}
frame.src = target
frame.dataset.initialSrc = target
}
})()
</script>
<header>
<div class="header-top">
<div class="header-nav">
<div class="header-col header-left" style="line-height: 58px; vertical-align: middle">
{% web_base_url as web_base %}
{% static_export_root_url as static_root %}
<a href="{% if STATIC_EXPORT %}{{ static_root }}index.html{% elif web_base %}{{ web_base }}/public/{% else %}/{% endif %}" class="header-archivebox" title="Go to Public Index...">
<img src="{% if STATIC_EXPORT %}data:image/svg+xml;utf8,&lt;svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'&gt;&lt;rect x='8' y='5' width='48' height='54' rx='7' fill='%23fff' opacity='.95'/&gt;&lt;path d='M18 16h28M18 26h28M18 36h20M18 46h24' stroke='%23aa1e55' stroke-width='5' stroke-linecap='round'/&gt;&lt;/svg&gt;{% elif web_base %}{{ web_base }}/static/archive.png{% else %}{% static 'archive.png' %}{% endif %}" alt="Archive Icon">
ArchiveBox
</a>
</div>
<div class="header-col header-main">
<div class="header-url">
<a class="header-url-text" href="{{url}}" title="Open original URL in new window..." target="_blank" rel="noreferrer">
{{url}}
</a>
</div>
<div class="header-title-line header-toggle-trigger">
{% snapshot_archiveresult_url snapshot 'favicon' 'favicon.ico' as header_favicon_url %}
{% if header_favicon_url %}
<img src="{{ header_favicon_url }}" onerror="this.onerror=null;this.src=&quot;data:image/svg+xml;utf8,&lt;svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' fill='none' stroke='rgba(255,255,255,0.65)' stroke-width='1.4'&gt;&lt;circle cx='10' cy='10' r='7.25'/&gt;&lt;ellipse cx='10' cy='10' rx='3.25' ry='7.25'/&gt;&lt;line x1='2.75' y1='10' x2='17.25' y2='10'/&gt;&lt;/svg&gt;&quot;" alt="Favicon" class="favicon"/>
{% else %}
<img src="data:image/svg+xml;utf8,&lt;svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' fill='none' stroke='rgba(255,255,255,0.65)' stroke-width='1.4'&gt;&lt;circle cx='10' cy='10' r='7.25'/&gt;&lt;ellipse cx='10' cy='10' rx='3.25' ry='7.25'/&gt;&lt;line x1='2.75' y1='10' x2='17.25' y2='10'/&gt;&lt;/svg&gt;" alt="Favicon" class="favicon"/>
{% endif %}
<span class="header-title-text">{{title|truncatechars:120}}</span>
{% if title_tags %}
<span class="header-tags">
<span class="tag-pill permission-pill">{{ snapshot_permissions_icon }} {{ snapshot_permissions }}</span>
{% for tag in title_tags %}
<span class="tag-pill" style="{{ tag.style }}">{{ tag.name }}</span>
{% endfor %}
</span>
{% else %}
<span class="header-tags">
<span class="tag-pill permission-pill">{{ snapshot_permissions_icon }} {{ snapshot_permissions }}</span>
</span>
{% endif %}
<a href="#" class="header-toggle"></a>
</div>
</div>
<div class="header-col header-meta">
<div class="header-badges">
<div class="badge badge-default" style="font-weight: 200">
<span class="badge-label">Outputs</span>
<span class="badge-value">
{{num_outputs}}
{% if num_failures %}
+ {{num_failures}} <small>errors</small>
{% endif %}
</span>
</div>
<div class="badge badge-info">
<span class="badge-label">Size</span>
{% if STATIC_EXPORT %}
<span class="badge-value">{{size}}</span>
{% else %}
<a class="badge-value" href="{% admin_base_url %}/admin/core/snapshot/{{snapshot_id|default:id}}/change/" title="Click to edit this Snapshot in the Admin UI">
{{size}}
</a>
{% endif %}
</div>
{% if not STATIC_EXPORT %}
<div class="badge badge-default">
<span class="badge-label">Manage</span>
<a class="badge-value" href="{% admin_base_url %}/admin/core/snapshot/{{snapshot_id|default:id}}/change/" title="Click to edit this Snapshot in the Admin UI">
<span class="badge-desktop-icon">✏️</span><span class="badge-mobile-text">Edit</span>
</a>
</div>
{% endif %}
<div class="badge badge-{{status_color}}">
<span class="badge-label">Status</span>
{% if STATIC_EXPORT %}
<span class="badge-value">{{status|upper}}</span>
{% else %}
<a class="badge-value" href="{% admin_base_url %}/admin/core/snapshot/?q={{snapshot_id|default:id}}" title="Click to see options to pull, re-snapshot, or delete this Snapshot">
{{status|upper}}
</a>
{% endif %}
</div>
</div>
<div class="header-mobile-badges">
<a class="header-mobile-badge" href="{% if STATIC_EXPORT %}{% snapshot_url snapshot 'index.jsonl' %}{% else %}{% snapshot_base_url snapshot %}/?files=1{% endif %}" title="Browse all files for this snapshot">
<span>{{num_outputs}} Files</span>
<span class="header-mobile-badge-separator">|</span>
<span>{{size}}</span>
</a>
{% if not STATIC_EXPORT %}<a class="header-mobile-badge" href="{% admin_base_url %}/admin/core/snapshot/{{snapshot_id|default:id}}/change/" title="Edit this snapshot in the Admin UI">
<span>{{status|capfirst}}</span>
<span class="header-mobile-badge-separator">|</span>
<span>Edit</span>
</a>{% endif %}
</div>
{% if related_years %}
<div class="header-year-badges">
{% for entry in related_years %}
{% if entry.snapshots|length > 1 %}
<details class="snapshot-variants year-variants">
<summary class="badge badge-default">{{ entry.year }}</summary>
<div class="snapshot-variants-list">
{% for snap in entry.snapshots %}
<a href="{% snapshot_base_url snap %}/index.html" title="{{ snap.url }}">
{{ snap.bookmarked_at|default:snap.created_at|default:snap.downloaded_at|date:"Y-m-d H:i:s" }} &nbsp; 💾 {{ snap.output_size|filesizeformat }}
</a>
{% endfor %}
</div>
</details>
{% else %}
<div class="badge badge-default">
<a href="{% snapshot_base_url entry.latest %}/index.html" title="{{ entry.latest.url }}">
{{ entry.year }}
</a>
</div>
{% endif %}
{% endfor %}
</div>
{% endif %}
</div>
<div class="header-col header-right" style="padding-top: 4px">
{% if related_snapshots %}
<details class="snapshot-variants">
<summary class="header-date" title="Click to see other snapshots for this URL">
<span class="snapshot-date-summary">
<span class="snapshot-count-badge">{{ related_snapshots|length }}</span>
<span class="header-date-label">Captures</span>
<span>{{oldest_archive_date|default:downloaded_datestr|default:bookmarked_date|slice:":10"}}</span>
</span>
</summary>
<div class="snapshot-variants-list">
{% for snap in related_snapshots %}
<a href="{% snapshot_base_url snap %}/index.html" title="{{ snap.url }}">
{{ snap.bookmarked_at|default:snap.created_at|default:snap.downloaded_at|date:"Y-m-d H:i:s" }} &nbsp; 💾 {{ snap.output_size|filesizeformat }}
</a>
{% endfor %}
</div>
</details>
{% else %}
<a class="header-date" href="{% snapshot_base_url snapshot %}/index.html" title="Date Added: {{bookmarked_date}} | First Archived: {{oldest_archive_date|default:downloaded_datestr}} | Last Checked: {{downloaded_datestr}} (UTC)">
<span class="header-date-label">Captures</span>
{{oldest_archive_date|default:downloaded_datestr|default:bookmarked_date|slice:":10"}}
</a>
{% endif %}
<br/>
<div class="external-links">
<a href="{% if STATIC_EXPORT %}{% snapshot_url snapshot 'index.jsonl' %}{% else %}{% snapshot_base_url snapshot %}/?files=1{% endif %}" title="Browse the full SNAP_DIR for this snapshot" target="_blank">📁 {% if STATIC_EXPORT %}Snapshot manifest{% else %}See all files...{% endif %}</a>
<span class="external-links-separator">|</span>
<a href="https://web.archive.org/web/{{url}}" title="Search for a copy of the URL saved in Archive.org" target="_blank" rel="noreferrer">🏛️ Archive.org</a>
<!--<a href="https://archive.md/{{url}}" title="Search for a copy of the URL saved in Archive.today" target="_blank" rel="noreferrer">Archive.today</a> &nbsp;|&nbsp; -->
<!--<a href="https://ghostarchive.org/search?term={{url}}" title="Search for a copy of the URL saved in GhostArchive.org" target="_blank" rel="noreferrer">More...</a>-->
</div>
</div>
</div>
</div>
<div class="header-bottom container-fluid">
<div class="thumb-grid">
{% for result in archiveresults %}
{% with display_path=result.path display_url='' preview_url='' %}
{% if display_path %}{% snapshot_url snapshot display_path as display_url %}{% endif %}
{% if display_path %}{% snapshot_preview_url snapshot display_path result.result as preview_url %}{% endif %}
<div class="thumb-card{% if forloop.first %} selected-card{% endif %}" data-plugin-name="{{result.name|plugin_name}}"{% if preview_url %} data-preview-url="{{preview_url}}"{% endif %}{% if display_path %} data-output-path="{{display_path}}"{% endif %}>
<div class="thumb-body">
<div class="thumb-actions">
{% if not STATIC_EXPORT %}<a href="{% snapshot_url snapshot result.name %}/?files=1" data-no-preview="1" title="Open output folder" target="_blank" rel="noopener">📁</a>{% endif %}
{% if display_path %}
<a href="{{display_url}}" data-no-preview="1" title="Download output file" download>⬇️</a>
{% endif %}
{% if can_delete_outputs and result.result %}
<button type="button" data-no-preview="1" data-archive-result-ids="{{result.result_ids}}" data-delete-url="{% admin_base_url %}/admin/core/archiveresult/" data-delete-handoff="1" data-delete-snapshot-id="{{snapshot.id}}" title="Delete this output">×</button>
{% endif %}
</div>
{% if display_path %}
<a href="{{preview_url}}" target="preview" title="./{{display_path}} (downloaded {{result.ts}})">
<h4>{% plugin_icon result.name %} {{result.name|plugin_name|truncatechars:20}}{% if result.size %} <small>({{result.size|filesizeformat}})</small>{% endif %}</h4>
</a>
{% else %}
<h4>{% plugin_icon result.name %} {{result.name|plugin_name|truncatechars:20}}{% if result.size %} <small>({{result.size|filesizeformat}})</small>{% endif %}</h4>
{% endif %}
{% if result.result %}
{% with plugin_base=result.name|plugin_name %}
{% if plugin_base == 'ytdlp' or plugin_base == 'yt-dlp' or plugin_base == 'youtube-dl' %}
{% plugin_card result.result %}
{% endif %}
{% endwith %}
{% endif %}
</div>
{% if result.result and display_path %}
{% with plugin_base=result.name|plugin_name %}
{% if plugin_base != 'ytdlp' and plugin_base != 'yt-dlp' and plugin_base != 'youtube-dl' %}
{# Use plugin-specific card template when ArchiveResult is available #}
<div class="card-img-top thumbnail-wrapper">
<a class="thumbnail-click-overlay" href="{{preview_url}}" target="preview" title="./{{display_path}} (downloaded {{result.ts}})" aria-label="Preview {{result.name|plugin_name}}"></a>
{% plugin_card result.result %}
</div>
{% else %}
{# YT-DLP renders its file list in the body #}
{% endif %}
{% endwith %}
{% elif result.is_metadata and display_path %}
<div class="card-img-top thumbnail-wrapper">
<a class="thumbnail-click-overlay" href="{{preview_url}}" target="preview" title="./{{display_path}}" aria-label="Preview {{result.name|plugin_name}}"></a>
{% output_card snapshot display_path result.name %}
</div>
{% elif display_path %}
{# Fall back to generic iframe for filesystem-discovered files #}
<div class="card-img-top thumbnail-wrapper">
<a class="thumbnail-click-overlay" href="{{preview_url}}" target="preview" title="./{{display_path}}" aria-label="Preview {{result.name|plugin_name}}"></a>
<iframe class="card-img-top" src="{{display_url}}?autoplay=0" allow="autoplay 'none'; fullscreen 'none'; navigation-override 'none'; " sandbox="allow-same-origin allow-top-navigation-by-user-activation allow-scripts allow-forms" scrolling="no" loading="lazy" fetchpriority="low"></iframe>
</div>
{% endif %}
</div>
{% endwith %}
{% endfor %}
{% if loose_items %}
<div class="thumb-card">
<div class="thumb-body">
<div class="thumb-actions">
<a href="{% if STATIC_EXPORT %}{% snapshot_url snapshot 'index.jsonl' %}{% else %}{% snapshot_base_url snapshot %}/?files=1{% endif %}" data-no-preview="1" title="{% if STATIC_EXPORT %}Open snapshot manifest{% else %}Browse all snapshot files{% endif %}" target="_blank" rel="noopener">📁</a>
</div>
<h4>📦 Other files</h4>
<div class="loose-items">
{% for item in loose_items %}
{% if item.is_dir %}
<a href="{% if STATIC_EXPORT %}{% snapshot_url snapshot 'index.jsonl' %}{% else %}{% snapshot_url snapshot item.path %}/?files=1{% endif %}" data-no-preview="1" target="_blank" rel="noopener">📁 {{item.name}}</a>
{% else %}
<a href="{% snapshot_url snapshot item.path %}" data-no-preview="1" target="_blank" rel="noopener">📄 {{item.name}}</a>
{% endif %}
{% endfor %}
</div>
</div>
</div>
{% endif %}
{% if failed_items %}
<div class="thumb-card">
<div class="thumb-body">
<div class="thumb-actions">
<a href="{% if STATIC_EXPORT %}{% snapshot_url snapshot 'index.jsonl' %}{% else %}{% snapshot_base_url snapshot %}/?files=1{% endif %}" data-no-preview="1" title="{% if STATIC_EXPORT %}Open snapshot manifest{% else %}Browse all snapshot files{% endif %}" target="_blank" rel="noopener">📁</a>
</div>
<h4>⚠️ Failed</h4>
<div class="loose-items failed-items">
{% for item in failed_items %}
{% if item.is_dir %}
<a href="{% if STATIC_EXPORT %}{% snapshot_url snapshot 'index.jsonl' %}{% else %}{% snapshot_url snapshot item.path %}/?files=1{% endif %}" data-no-preview="1" target="_blank" rel="noopener">📁 {{item.name}}</a>
{% else %}
<a href="{% snapshot_url snapshot item.path %}" data-no-preview="1" target="_blank" rel="noopener">📄 {{item.name}}</a>
{% endif %}
{% endfor %}
</div>
</div>
</div>
{% endif %}
</div>
</div>
</header>
{% if can_delete_outputs %}{% include "includes/output_delete_controls.html" %}{% endif %}
<script>
const snapshotBaseUrl = "{% snapshot_base_url snapshot %}";
const snapshotFilesUrl = "{% if STATIC_EXPORT %}{% snapshot_url snapshot 'index.jsonl' %}{% else %}{% snapshot_base_url snapshot %}/?files=1{% endif %}";
function tryCenterImageFrame(frame) {
try {
const doc = frame.contentDocument || frame.contentWindow.document
if (!doc || !doc.body || !doc.images || doc.images.length !== 1) {
return
}
const img = doc.images[0]
doc.documentElement.style.height = '100%'
doc.body.style.height = '100%'
doc.documentElement.style.width = '100%'
doc.body.style.width = '100%'
doc.body.style.minWidth = '100%'
doc.body.style.margin = '0'
doc.body.style.display = 'flex'
doc.body.style.flexDirection = 'column'
doc.body.style.alignItems = 'center'
doc.body.style.justifyContent = 'flex-start'
doc.body.style.overflowX = 'auto'
doc.body.style.background = '#fff'
img.style.maxWidth = '100%'
img.style.width = 'auto'
img.style.height = 'auto'
img.style.maxHeight = 'none'
img.style.display = 'block'
img.style.margin = '0 auto'
} catch (err) {}
}
function resetPreviewFrameSize(frame) {
const wrapper = document.getElementById('main-frame-wrapper')
if (!wrapper || !frame) return
wrapper.style.height = ''
wrapper.style.overflow = ''
frame.style.height = ''
}
function fitPreviewFrameToContent(frame) {
const wrapper = document.getElementById('main-frame-wrapper')
if (!wrapper || !frame || frame.src.includes('.pdf')) return
try {
const doc = frame.contentDocument || frame.contentWindow.document
if (!doc || !doc.documentElement) return
const candidates = [
doc.documentElement,
doc.body,
...doc.querySelectorAll('main, [role="main"], #doc, .ui-content, .markdown-body'),
].filter(Boolean)
const contentHeight = Math.ceil(Math.max(...candidates.map((element) => element.scrollHeight || 0)))
const minimumHeight = Math.max(window.innerHeight - wrapper.getBoundingClientRect().top, 320)
if (!Number.isFinite(contentHeight) || contentHeight <= minimumHeight + 4) {
resetPreviewFrameSize(frame)
return
}
wrapper.style.height = `${contentHeight}px`
wrapper.style.overflow = 'visible'
frame.style.height = `${contentHeight}px`
} catch (err) {
resetPreviewFrameSize(frame)
}
}
function attachPreviewFrameHandlers(frame) {
resetPreviewFrameSize(frame)
if (frame.src.endsWith('.pdf')) {
frame.removeAttribute('sandbox')
frame.src = frame.src + '#toolbar=0'
}
frame.onload = function() {
if (this.src.includes('.pdf')) {
this.removeAttribute('sandbox')
this.src = this.src.split('?autoplay=')[0] + '#toolbar=0'
}
tryCenterImageFrame(this)
try {
// doesnt work if frame origin rules prevent accessing its DOM via JS
this.contentWindow.scrollTo(0, 0);
} catch(err) {}
requestAnimationFrame(() => {
fitPreviewFrameToContent(this)
try {
if (this._archiveboxResizeObserver) this._archiveboxResizeObserver.disconnect()
this._archiveboxResizeObserver = new ResizeObserver(() => fitPreviewFrameToContent(this))
this._archiveboxResizeObserver.observe(this.contentDocument.documentElement)
} catch (err) {}
})
}
}
function setPreviewResourcePriority(root, highPriority=false) {
for (const iframe of root.querySelectorAll('iframe')) {
iframe.loading = highPriority ? 'eager' : 'lazy'
iframe.setAttribute('fetchpriority', highPriority ? 'high' : 'low')
attachPreviewFrameHandlers(iframe)
}
for (const img of root.querySelectorAll('img')) {
img.loading = highPriority ? 'eager' : 'lazy'
img.decoding = highPriority ? 'sync' : 'async'
img.setAttribute('fetchpriority', highPriority ? 'high' : 'low')
}
for (const video of root.querySelectorAll('video')) {
video.loading = highPriority ? 'eager' : 'lazy'
video.setAttribute('preload', highPriority ? 'metadata' : 'none')
}
}
function prioritizeCardThumbnail(card, highPriority=false) {
if (!card) {
return
}
setPreviewResourcePriority(card, highPriority)
}
function prepareThumbnailPriorities() {
const cards = [...document.querySelectorAll('.thumb-card')]
cards.forEach((card) => prioritizeCardThumbnail(card, card.classList.contains('selected-card')))
}
// un-sandbox iframes showing pdfs (required to display pdf viewer)
document.querySelectorAll('iframe').forEach((frame) => attachPreviewFrameHandlers(frame))
function getPreviewHashValueFromHref(href, normalize=false) {
if (href == './') {
return 'all'
}
if (!href) {
return ''
}
try {
const url = new URL(href, window.location.href)
let path = url.pathname || ''
if (snapshotBaseUrl) {
const baseUrl = new URL(snapshotBaseUrl, window.location.href)
const basePath = (baseUrl.pathname || '').replace(/\/+$/, '')
if (url.origin === baseUrl.origin && basePath && path.startsWith(`${basePath}/`)) {
path = path.slice(basePath.length + 1)
} else {
path = path.replace(/^\/+/, '')
}
} else {
path = path.replace(/^\/+/, '')
}
const searchParams = new URLSearchParams(url.search || '')
searchParams.delete('preview')
const search = searchParams.toString()
const suffix = `${search ? `?${search}` : ''}${url.hash || ''}`
const value = `${path}${suffix}`.replace(/^\/+/, '')
return normalize ? value.toLowerCase() : value
} catch (err) {
const value = href.replace(/^https?:\/\/[^/]+\/?/, '').replace(/^\/+/, '')
return normalize ? value.toLowerCase() : value
}
}
function getPreviewHashValue(link) {
return getPreviewHashValueFromHref((link && link.getAttribute('href')) || '', true)
}
function resolvePreviewUrl(raw) {
if (!raw) return ''
if (raw.startsWith('http://') || raw.startsWith('https://')) return raw
if (raw.startsWith('//')) return window.location.protocol + raw
if (!snapshotBaseUrl) return raw
return snapshotBaseUrl + (raw.startsWith('/') ? raw : `/${raw}`)
}
function isSnapshotRootPreview(urlValue) {
if (!urlValue || !snapshotBaseUrl) {
return false
}
try {
const target = new URL(urlValue, window.location.href)
const base = new URL(snapshotBaseUrl, window.location.href)
if (target.origin !== base.origin) {
return false
}
const basePath = (base.pathname || '').replace(/\/+$/, '')
const targetPath = (target.pathname || '').replace(/\/+$/, '')
return (
target.search !== "{% if STATIC_EXPORT %}{% else %}?files=1{% endif %}"
&& (
targetPath === basePath
|| targetPath === `${basePath}/index.html`
|| targetPath === `${basePath}/index.json`
)
)
} catch (err) {
return false
}
}
function createMainFrame(previousFrame) {
const frame = document.createElement('iframe')
frame.id = 'main-frame'
frame.name = 'preview'
frame.className = 'full-page-iframe'
frame.sandbox = "allow-same-origin allow-top-navigation-by-user-activation allow-scripts allow-forms"
frame.loading = 'eager'
frame.setAttribute('fetchpriority', 'high')
attachPreviewFrameHandlers(frame)
return frame
}
function ensureMainFrame(forceReplace=false) {
let frame = document.getElementById('main-frame')
const wrapper = document.getElementById('main-frame-wrapper')
if (wrapper && wrapper.querySelector('#snapshot-empty-state')) {
return null
}
if (!frame || forceReplace) {
const previousFrame = frame
frame = createMainFrame(previousFrame)
if (previousFrame && previousFrame.parentNode) {
try {
previousFrame.src = 'about:blank'
} catch (err) {}
previousFrame.parentNode.replaceChild(frame, previousFrame)
} else if (wrapper) {
wrapper.innerHTML = ''
wrapper.appendChild(frame)
wrapper.classList.remove('full-page-wrapper')
}
}
const pluginWrapper = document.getElementById('plugin-full-wrapper')
if (pluginWrapper) {
pluginWrapper.classList.add('preview-hidden')
}
frame.classList.remove('preview-hidden')
return frame
}
function activateCardPreview(card, link, updateHash=true) {
if (!card) {
return false
}
prioritizeCardThumbnail(card, true)
const previewUrl = card.dataset.previewUrl
const rawTarget = previewUrl || (link ? link.getAttribute('href') : '') || ''
const target = resolvePreviewUrl(rawTarget)
if (!target || target.endsWith('#')) {
return false
}
document.querySelectorAll('.selected-card').forEach((selected) => selected.classList.remove('selected-card'))
card.closest('.thumb-card').classList.add('selected-card')
const nextSrc = isSnapshotRootPreview(target) ? snapshotFilesUrl : target
const existingFrame = document.getElementById('main-frame')
let currentSrc = ''
try {
currentSrc = existingFrame ? new URL(existingFrame.getAttribute('src') || existingFrame.src || '', window.location.href).href : ''
} catch (err) {}
let nextSrcAbs = nextSrc
try {
nextSrcAbs = new URL(nextSrc, window.location.href).href
} catch (err) {}
const iframe_elem = ensureMainFrame(currentSrc !== nextSrcAbs)
if (target.endsWith('.pdf')) {
iframe_elem.removeAttribute('sandbox')
} else {
iframe_elem.sandbox = "allow-same-origin allow-top-navigation-by-user-activation allow-scripts allow-forms"
}
if (rawTarget && updateHash) {
window.location.hash = getPreviewHashValueFromHref(rawTarget)
}
if (currentSrc !== nextSrcAbs) {
iframe_elem.src = nextSrc
}
return false
}
function handleCardClick(card, event) {
if (!event || event.defaultPrevented) {
return false
}
const targetEl = event.target.nodeType === Node.ELEMENT_NODE ? event.target : event.target.parentElement
if (targetEl && targetEl.closest('[data-no-preview]')) {
return true
}
const link = (targetEl && targetEl.closest('a[target=preview]')) || card.querySelector('a[target=preview]') || card.querySelector('a')
event.preventDefault()
if (typeof event.stopPropagation === 'function') {
event.stopPropagation()
}
if (typeof event.stopImmediatePropagation === 'function') {
event.stopImmediatePropagation()
}
return activateCardPreview(card, link)
}
function findPreviewLinkForHash(hashValue) {
if (!hashValue) {
return null
}
const previewLinks = [...document.querySelectorAll('a[target=preview]')]
const pathMatch = previewLinks.find((link) => getPreviewHashValue(link) == hashValue)
if (pathMatch) {
return pathMatch
}
const pluginCard = [...document.querySelectorAll('.thumb-card[data-plugin-name]')].find(
(card) => (card.dataset.pluginName || '').toLowerCase() == hashValue,
)
return pluginCard && pluginCard.querySelector('a[target=preview]')
}
function selectInitialPreview() {
const selectedPreviewHash = window.location.hash ? decodeURIComponent(window.location.hash.slice(1)).toLowerCase() : ''
const matchingLink = findPreviewLinkForHash(selectedPreviewHash)
if (matchingLink) {
return activateCardPreview(matchingLink.closest('.thumb-card'), matchingLink, false)
}
const selectedCard = document.querySelector('.thumb-card.selected-card') || document.querySelector('.thumb-card')
const selectedLink = selectedCard && (selectedCard.querySelector('a[target=preview]') || selectedCard.querySelector('a'))
if (selectedCard && selectedLink) {
return activateCardPreview(selectedCard, selectedLink, false)
}
const frame = ensureMainFrame(false)
const defaultSrc = frame && frame.dataset.defaultSrc
if (frame && defaultSrc && (!frame.src || frame.src === 'about:blank')) {
frame.src = defaultSrc
}
return false
}
for (const card of [...document.querySelectorAll('.thumb-card')]) {
card.addEventListener('click', function(event) {
if (event.target && event.target.closest('a')) {
return
}
handleCardClick(card, event)
})
for (const link of card.querySelectorAll('a')) {
link.addEventListener('click', function(event) {
handleCardClick(card, event)
})
}
}
function hideSnapshotHeader() {
console.log('Collapsing Snapshot header...')
document.querySelectorAll('.header-toggle').forEach((toggle) => { toggle.textContent = '▸' })
document.querySelectorAll('.header-bottom').forEach((header) => { header.hidden = true })
try {
localStorage.setItem("archivebox-snapshot-header-visible", "false")
} catch (e) {
console.log('Could not use localStorage to persist header collapse state', e)
}
}
function showSnapshotHeader() {
console.log('Expanding Snapshot header...')
document.querySelectorAll('.header-toggle').forEach((toggle) => { toggle.textContent = '▾' })
document.querySelectorAll('.header-bottom').forEach((header) => { header.hidden = false })
try {
localStorage.setItem("archivebox-snapshot-header-visible", "true")
} catch (e) {
console.log('Could not use localStorage to persist header collapse state', e)
}
}
function loadSnapshotHeaderState() {
// collapse snapshot header if user has previously hidden it
let snapshotHeaderIsExpanded = 'false'
try {
snapshotHeaderIsExpanded = localStorage.getItem("archivebox-snapshot-header-visible") || 'false'
} catch (e) {
console.log('Could not use localStorage to get header collapse state', e)
}
if (snapshotHeaderIsExpanded === 'false') {
hideSnapshotHeader()
}
}
function handleSnapshotHeaderToggle(event) {
event.preventDefault()
if ([...document.querySelectorAll('.header-toggle')].some((toggle) => toggle.textContent.includes('▾'))) {
hideSnapshotHeader()
} else {
showSnapshotHeader()
}
return true
}
// Hide or show the header once when its title row or collapse icon is clicked.
document.querySelectorAll('.header-toggle-trigger').forEach((trigger) => trigger.addEventListener('click', handleSnapshotHeaderToggle))
// check URL for hash e.g. #git and load relevant preview
selectInitialPreview()
prepareThumbnailPriorities()
loadSnapshotHeaderState()
// hide all preview iframes on small screens
// if (window.innerWidth < 1091) {
// jQuery('.card a[target=preview]').attr('target', '_self')
// }
</script>
</body>
</html>