<!DOCTYPE html>
<html lang="en" data-theme="light">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<title>{{.Name}}</title>
{{/* Open Graph / Twitter Card metadata for link previews. See
     public_viewer.html for the gating rationale. */}}
{{if .Description}}<meta name="description" content="{{.Description}}">{{end}}
<meta property="og:type" content="website">
<meta property="og:title" content="{{.Name}}">
{{if .Description}}<meta property="og:description" content="{{.Description}}">{{end}}
{{if .ShareURL}}<meta property="og:url" content="{{.ShareURL}}">{{end}}
{{if .BrandName}}<meta property="og:site_name" content="{{.BrandName}}">{{end}}
{{if .OGImageURL}}<meta property="og:image" content="{{.OGImageURL}}">{{end}}
<meta name="twitter:card" content="{{if .OGImageURL}}summary_large_image{{else}}summary{{end}}">
<meta name="twitter:title" content="{{.Name}}">
{{if .Description}}<meta name="twitter:description" content="{{.Description}}">{{end}}
{{if .OGImageURL}}<meta name="twitter:image" content="{{.OGImageURL}}">{{end}}
<style>{{.ContentViewerCSS}}</style>
<style>
/* Header palette + body palette aliased to the same physical colors so the
   header CSS lifted from public_viewer.html (asset viewer) and the body
   rules (.collection-desc/.section-desc/.item-card) read consistent values.
   Names mirror public_viewer.html's variables for easier diffing. */
:root, [data-theme="light"] {
  --bg: #f5f5f5;
  --bg-surface: #fff;
  --text: #1a1a1a;
  --text-muted: #6b7280;
  --page-border: #e5e5e5;
  --badge-bg: #e5e7eb;
  --badge-text: #374151;
  --toggle-hover: #f3f4f6;
  --accent: #2563eb;
  /* Aliases for legacy body rules that still reference the old names. */
  --fg: var(--text);
  --muted: var(--text-muted);
  --border: var(--page-border);
  --card-bg: var(--bg-surface);
}
[data-theme="dark"] {
  --bg: #111113;
  --bg-surface: #1a1a1e;
  --text: #e4e4e7;
  --text-muted: #71717a;
  --page-border: #27272a;
  --badge-bg: #27272a;
  --badge-text: #a1a1aa;
  --toggle-hover: #27272a;
  --accent: #60a5fa;
  --fg: var(--text);
  --muted: var(--text-muted);
  --border: var(--page-border);
  --card-bg: var(--bg-surface);
}
*{margin:0;padding:0;box-sizing:border-box}
html, body { height: 100%; }
body {
  font-family: system-ui, -apple-system, sans-serif;
  background: var(--bg);
  color: var(--text);
  display: flex;
  flex-direction: column;
  min-height: 100vh;
}
/* Header — ported verbatim from public_viewer.html so the collection
   viewer and asset viewer share an identical header on mobile and
   desktop. Keep these rules in sync with public_viewer.html. */
.header {
  box-sizing: border-box;
  background: var(--bg-surface);
  border-bottom: 1px solid var(--page-border);
  padding: 12px 24px;
  display: flex;
  align-items: center;
  gap: 12px;
  flex-shrink: 0;
}
.brand {
  display: flex;
  align-items: center;
  gap: 8px;
  color: var(--text-muted);
  flex-shrink: 0;
}
.brand a {
  display: flex;
  align-items: center;
  gap: 8px;
  color: inherit;
  text-decoration: none;
}
.brand a:hover { opacity: 0.8; }
.brand-logo {
  width: 24px;
  height: 24px;
  display: flex;
  align-items: center;
}
.brand-logo svg {
  /* display:block + max-width:100% defends against operator-supplied SVGs
     whose root element carries width="100%"/height="100%" attributes that
     would otherwise force the logo to overflow on narrow viewports. */
  width: 24px;
  height: 24px;
  display: block;
  max-width: 100%;
}
.brand-implementor .brand-logo { width: auto; height: 28px; }
.brand-implementor .brand-logo svg { width: auto; height: 28px; max-width: 100%; }
.brand-name {
  font-size: 13px;
  font-weight: 500;
  color: var(--text-muted);
}
.separator {
  width: 1px;
  height: 20px;
  background: var(--page-border);
  flex-shrink: 0;
}
.header-center {
  display: flex;
  align-items: center;
  gap: 12px;
  flex: 1;
  min-width: 0;
}
.header-center h1 {
  font-size: 16px;
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.theme-toggle {
  background: none;
  border: 1px solid var(--page-border);
  border-radius: 6px;
  padding: 4px 6px;
  cursor: pointer;
  color: var(--text-muted);
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  transition: background 0.15s;
}
.theme-toggle:hover { background: var(--toggle-hover); }
.theme-toggle svg { width: 16px; height: 16px; }
.notice {
  box-sizing: border-box;
  font-size: 11px;
  color: var(--text-muted);
  text-align: center;
  padding: 4px 24px;
  border-bottom: 1px solid var(--page-border);
  background: var(--bg-surface);
  flex-shrink: 0;
  line-height: 1.4;
}
main{max-width:960px;margin:0 auto;padding:32px 24px;width:100%}
.collection-desc{margin-bottom:32px}
.collection-desc,.section-desc{font-size:14px;line-height:1.7;color:var(--fg)}
.collection-desc h1,.section-desc h1{font-size:1.25em;font-weight:700;margin:16px 0 8px}
.collection-desc h2,.section-desc h2{font-size:1.15em;font-weight:600;margin:14px 0 6px}
.collection-desc h3,.section-desc h3{font-size:1.05em;font-weight:600;margin:12px 0 4px}
.collection-desc p,.section-desc p{margin:0 0 10px}
.collection-desc ul,.collection-desc ol,.section-desc ul,.section-desc ol{margin:4px 0 10px;padding-left:24px}
.collection-desc li,.section-desc li{margin:2px 0}
.collection-desc pre,.section-desc pre{background:var(--card-bg);border:1px solid var(--border);border-radius:6px;padding:12px;overflow-x:auto;font-size:13px;margin:8px 0}
.collection-desc code,.section-desc code{background:var(--card-bg);padding:2px 5px;border-radius:3px;font-size:0.9em}
.collection-desc pre code,.section-desc pre code{background:none;padding:0}
.collection-desc a,.section-desc a{color:var(--accent);text-decoration:underline}
.collection-desc blockquote,.section-desc blockquote{border-left:3px solid var(--border);padding-left:12px;margin:8px 0;color:var(--muted)}
.collection-desc table,.section-desc table{border-collapse:collapse;margin:8px 0;width:100%}
.collection-desc th,.collection-desc td,.section-desc th,.section-desc td{border:1px solid var(--border);padding:6px 10px;text-align:left;font-size:13px}
.collection-desc th,.section-desc th{background:var(--card-bg);font-weight:600}
.collection-desc strong,.section-desc strong{font-weight:600}
.collection-desc em,.section-desc em{font-style:italic}
.collection-desc hr,.section-desc hr{border:none;border-top:1px solid var(--border);margin:16px 0}
.collection-desc img,.section-desc img{max-width:100%;border-radius:4px}
.section{margin-bottom:32px}
.section-title{font-size:18px;font-weight:600;margin-bottom:4px}
.section-desc{margin-bottom:12px}
.items-grid{display:grid;gap:12px;margin-top:1.14286em}
.items-grid.thumb-large{grid-template-columns:repeat(auto-fill,minmax(220px,1fr))}
.items-grid.thumb-medium{grid-template-columns:repeat(auto-fill,minmax(180px,1fr))}
.items-grid.thumb-small{grid-template-columns:repeat(auto-fill,minmax(160px,1fr))}
.items-grid.thumb-none{grid-template-columns:repeat(auto-fill,minmax(250px,1fr))}
.item-card{border:1px solid var(--border);border-radius:8px;background:var(--card-bg);cursor:pointer;overflow:hidden;transition:border-color .15s}
.item-card:hover{border-color:var(--accent)}
.item-thumb{width:100%;background:var(--border);display:flex;align-items:center;justify-content:center;color:var(--muted);font-size:24px}
.thumb-large .item-thumb{aspect-ratio:4/3}
.thumb-medium .item-thumb{aspect-ratio:3/2;max-height:120px}
.thumb-small .item-thumb{aspect-ratio:2/1;max-height:80px}
.thumb-none .item-thumb{display:none}
.item-thumb img{width:100%;height:100%;object-fit:cover;object-position:top}
.item-info{padding:10px 12px}
.item-name{font-size:13px;font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.item-type{font-size:11px;color:var(--muted);margin-top:2px}
.item-desc{font-size:11px;color:var(--muted);margin-top:2px;overflow:hidden;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical}
/* Modal overlay */
.modal-overlay{position:fixed;inset:0;background:rgba(0,0,0,.6);z-index:100;display:none;align-items:center;justify-content:center}
.modal-overlay.open{display:flex}
.modal-content{background:var(--bg);border-radius:12px;width:95vw;height:92vh;display:flex;flex-direction:column;overflow:hidden}
.modal-header{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;border-bottom:1px solid var(--border)}
.modal-header h2{font-size:15px;font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.modal-close{background:none;border:none;font-size:20px;cursor:pointer;color:var(--fg);padding:4px 8px;border-radius:4px}
.modal-close:hover{background:var(--border)}
.modal-body{flex:1;overflow:auto;position:relative}
.modal-body iframe{width:100%;height:100%;border:none}
</style>
<script>
/* Apply the saved theme before <body> paints to avoid a flash-of-light
   on first render in dark mode. Mirrors the early-execution block in
   public_viewer.html. */
(function(){
  var saved = null;
  try { saved = localStorage.getItem("mdp-theme"); } catch(e) {}
  var theme = saved === "dark" || saved === "light"
    ? saved
    : (window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light");
  document.documentElement.setAttribute("data-theme", theme);
  document.documentElement.classList.toggle("dark", theme === "dark");
})();
</script>
</head>
<body>
<div class="header">
  {{if or .ImplementorName .ImplementorLogoSVG}}
  <div class="brand brand-implementor">
    {{if .ImplementorURL}}<a href="{{.ImplementorURL}}" target="_blank" rel="noopener noreferrer">{{end}}
    {{if .ImplementorLogoSVG}}<div class="brand-logo">{{.ImplementorLogoSVG}}</div>{{end}}
    {{if .ImplementorName}}<span class="brand-name">{{.ImplementorName}}</span>{{end}}
    {{if .ImplementorURL}}</a>{{end}}
  </div>
  <div class="separator"></div>
  {{end}}
  <div class="header-center">
    <h1>{{.Name}}</h1>
  </div>
  <button class="theme-toggle" id="theme-toggle" aria-label="Toggle dark mode" title="Toggle dark mode">
    <svg id="icon-sun" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="5"/><line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/><line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/></svg>
    <svg id="icon-moon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>
  </button>
  {{/* Platform brand block. The Go handler always populates BrandName
       (defaults to "MCP Data Platform") and BrandLogoSVG (defaults to
       the bundled logo SVG) in pkg/portal/public.go, so in practice these
       are never empty — but we guard anyway for symmetry with the
       implementor block and to remain robust to future handler changes
       that might allow opting out. {{.BrandLogoSVG}} is rendered as
       template.HTML by the handler (see public.go: template.HTML(brandLogo)),
       so the inline SVG passes through unescaped. */}}
  {{if or .BrandName .BrandLogoSVG}}
  <div class="separator"></div>
  <div class="brand brand-platform">
    {{if .BrandURL}}<a href="{{.BrandURL}}" target="_blank" rel="noopener noreferrer">{{end}}
    {{if .BrandLogoSVG}}<div class="brand-logo">{{.BrandLogoSVG}}</div>{{end}}
    {{if .BrandName}}<span class="brand-name">{{.BrandName}}</span>{{end}}
    {{if .BrandURL}}</a>{{end}}
  </div>
  {{end}}
</div>
{{if or .NoticeText (and .ExpiresAtISO (not .HideExpiration))}}
<div class="notice" id="notice">
  {{if and .ExpiresAtISO (not .HideExpiration)}}<span id="expires">This page expires <span id="expiry-rel"></span></span>{{end}}
  {{if and .ExpiresAtISO (not .HideExpiration) .NoticeText}}<span class="notice-sep"> &middot; </span>{{end}}
  {{if .NoticeText}}<span>{{.NoticeText}}</span>{{end}}
</div>
{{end}}

<main id="main"></main>

<div class="modal-overlay" id="modal">
  <div class="modal-content">
    <div class="modal-header">
      <h2 id="modal-title"></h2>
      <button class="modal-close" onclick="closeModal()" title="Close">&times;</button>
    </div>
    <div class="modal-body" id="modal-body"></div>
  </div>
</div>

<script type="application/json" id="collection-data">{{.CollectionJSON}}</script>
<script>{{.ContentViewerJS}}</script>
<script>
(function(){
  // Theme toggle — mirrors public_viewer.html's single sun/moon button.
  // Persists under the same `mdp-theme` key the asset viewer uses, so
  // navigating between a single-asset share and a collection share keeps
  // the user's choice. Treat any persisted "system" value (from prior
  // builds that exposed Light/Dark/System) as a request to follow the
  // OS preference at render time, then upgrade to the resolved theme.
  var html = document.documentElement;
  var btn = document.getElementById('theme-toggle');
  var sunIcon = document.getElementById('icon-sun');
  var moonIcon = document.getElementById('icon-moon');

  function applyTheme(t){
    if(t !== 'dark' && t !== 'light'){
      t = window.matchMedia('(prefers-color-scheme:dark)').matches ? 'dark' : 'light';
    }
    html.setAttribute('data-theme', t);
    html.classList.toggle('dark', t === 'dark');
    if(sunIcon) sunIcon.style.display = t === 'dark' ? 'block' : 'none';
    if(moonIcon) moonIcon.style.display = t === 'dark' ? 'none' : 'block';
  }

  var saved = null;
  try { saved = localStorage.getItem('mdp-theme'); } catch(e) {}
  applyTheme(saved || (window.matchMedia('(prefers-color-scheme:dark)').matches ? 'dark' : 'light'));

  if(btn){
    btn.addEventListener('click', function(){
      var next = html.getAttribute('data-theme') === 'dark' ? 'light' : 'dark';
      applyTheme(next);
      try { localStorage.setItem('mdp-theme', next); } catch(e) {}
    });
  }

  // Expiration text rendered in the .notice band (parity with asset viewer):
  // "This page expires in N day(s)/hour(s)/minute(s)". Computed once on load
  // — short-share TTLs are typically days, so a per-minute interval would
  // be needless work; the asset viewer also computes once.
  var expiryEl = document.getElementById('expiry-rel');
  var expiresISO = '{{.ExpiresAtISO}}';
  if(expiryEl && expiresISO){
    var diff = new Date(expiresISO) - new Date();
    if(diff > 0){
      var mins = Math.floor(diff / 60000);
      var hrs = Math.floor(mins / 60);
      var days = Math.floor(hrs / 24);
      if(days > 0) expiryEl.textContent = 'in ' + days + ' day' + (days !== 1 ? 's' : '');
      else if(hrs > 0) expiryEl.textContent = 'in ' + hrs + ' hour' + (hrs !== 1 ? 's' : '');
      else expiryEl.textContent = 'in ' + Math.max(1, mins) + ' minute' + (mins !== 1 ? 's' : '');
    } else {
      expiryEl.textContent = 'soon';
    }
  }

  var data = JSON.parse(document.getElementById('collection-data').textContent);
  var main = document.getElementById('main');
  var token = '{{.Token}}';
  var thumbSize = data.thumbnailSize || 'large';

  // The chrome <header> now carries the page title (<h1>{{.Name}}</h1>),
  // so the body only renders the description (if any) above the section
  // grid. This avoids visually duplicating the title. Note: the
  // markdown-rendered description can still emit its own h1/h2/h3 if
  // the operator's content uses them — the document outline isn't
  // strictly capped at one h1, but at least the platform-emitted
  // chrome doesn't double the title.
  if(data.description){
    var descEl = document.createElement('div');
    descEl.className = 'collection-desc';
    main.appendChild(descEl);
    if(window.renderMarkdown) window.renderMarkdown(descEl, data.description);
  }

  (data.sections || []).forEach(function(sec){
    var secDiv = document.createElement('div');
    secDiv.className = 'section';
    if(sec.title) secDiv.innerHTML += '<h2 class="section-title">' + esc(sec.title) + '</h2>';
    if(sec.description){
      var secDescEl = document.createElement('div');
      secDescEl.className = 'section-desc';
      secDiv.appendChild(secDescEl);
      if(window.renderMarkdown) window.renderMarkdown(secDescEl, sec.description);
    }
    var grid = document.createElement('div');
    grid.className = 'items-grid thumb-' + thumbSize;
    (sec.items || []).forEach(function(item){
      var thumb = item.hasThumbnail
        ? '<img src="/portal/view/' + token + '/items/' + item.assetId + '/thumbnail" alt="" onerror="this.parentNode.innerHTML=\'&#x1f4c4;\'">'
        : '&#x1f4c4;';
      grid.innerHTML += '<div class="item-card" onclick="openItem(\'' + item.assetId + '\',\'' + esc(item.name) + '\')">' +
        '<div class="item-thumb">' + thumb + '</div>' +
        '<div class="item-info"><div class="item-name">' + esc(item.name) + '</div>' +
        (item.description ? '<div class="item-desc">' + esc(item.description) + '</div>' : '') +
        '<div class="item-type">' + esc(item.contentType) + '</div></div></div>';
    });
    secDiv.appendChild(grid);
    main.appendChild(secDiv);
  });

  // Modal + history state
  var modal = document.getElementById('modal');
  var modalIsOpen = false;

  window.openItem = function(assetId, name){
    document.getElementById('modal-title').textContent = name;
    var body = document.getElementById('modal-body');
    body.innerHTML = '<div style="display:flex;align-items:center;justify-content:center;height:100%;color:var(--muted)">Loading...</div>';
    modal.className = 'modal-overlay open';
    document.body.style.overflow = 'hidden';
    if (!modalIsOpen) {
      history.pushState({modal:true}, '');
      modalIsOpen = true;
    }

    // Load the full asset viewer (same page as single-asset public shares) in an iframe.
    var iframe = document.createElement('iframe');
    iframe.style.cssText = 'width:100%;height:100%;border:none';
    iframe.src = '/portal/view/' + token + '/items/' + assetId + '/view?embedded=1';
    body.innerHTML = '';
    body.appendChild(iframe);
  };

  window.closeModal = function(){
    if (!modalIsOpen) return;
    modal.className = 'modal-overlay';
    document.body.style.overflow = '';
    document.getElementById('modal-body').innerHTML = '';
    modalIsOpen = false;
  };

  // Browser back button closes the modal
  window.addEventListener('popstate', function(e){
    if (modalIsOpen) {
      closeModal();
    }
  });

  document.addEventListener('keydown', function(e){
    if(e.key === 'Escape' && modalIsOpen) {
      history.back(); // triggers popstate which calls closeModal
    }
  });

  function esc(s){
    var d = document.createElement('div');
    d.appendChild(document.createTextNode(s || ''));
    return d.innerHTML;
  }
})();
</script>
</body>
</html>
