diff --git a/app.js b/app.js index bfaab09..9bd0adb 100644 --- a/app.js +++ b/app.js @@ -1,5 +1,6 @@ // Same repo: index.json and preset files are served from the same origin as the page var INDEX_JSON_URL = './index.json'; +var X2D_PROGRESS_JSON_URL = './x2d-progress.json'; // Use relative URL so fetch is same-origin (no CORS). Works on GitHub Pages and local. var RAW_BASE = ''; var THEME_STORAGE_KEY = 'polymaker-preset-theme'; @@ -31,6 +32,182 @@ function applyTheme(theme) { } } +function formatDisplayDate(dateString) { + if (!dateString) return ''; + + var parts = String(dateString).split('-'); + var year = parts[0]; + var month = parseInt(parts[1], 10); + var day = parseInt(parts[2], 10); + var monthNames = [ + 'January', 'February', 'March', 'April', 'May', 'June', + 'July', 'August', 'September', 'October', 'November', 'December' + ]; + + if (!year || isNaN(month) || isNaN(day) || month < 1 || month > 12) { + return dateString; + } + + return monthNames[month - 1] + ' ' + day + ', ' + year; +} + +function parseLocalDate(dateString) { + if (!dateString) return null; + + var parts = String(dateString).split('-'); + var year = parseInt(parts[0], 10); + var month = parseInt(parts[1], 10); + var day = parseInt(parts[2], 10); + + if (isNaN(year) || isNaN(month) || isNaN(day)) { + return null; + } + + return new Date(year, month - 1, day); +} + +function getTimelineLabel(dateString) { + var targetDate = parseLocalDate(dateString); + var now = new Date(); + var today = new Date(now.getFullYear(), now.getMonth(), now.getDate()); + var daysLeft; + + if (!targetDate) { + return 'Date not set'; + } + + daysLeft = Math.round((targetDate.getTime() - today.getTime()) / 86400000); + + if (daysLeft > 1) { + return daysLeft + ' days left'; + } + if (daysLeft === 1) { + return '1 day left'; + } + if (daysLeft === 0) { + return 'Due today'; + } + if (daysLeft === -1) { + return '1 day overdue'; + } + return Math.abs(daysLeft) + ' days overdue'; +} + +function buildX2DChecklist(items) { + var html = ''; + var i; + var item; + var itemClass; + var statusText; + + for (i = 0; i < items.length; i++) { + item = items[i] || {}; + itemClass = item.completed ? ' is-complete' : ' is-pending'; + statusText = item.completed ? 'Done' : 'Pending'; + + html += '
  • '; + html += '' + escapeHtml(item.productName || 'Unknown product') + ''; + html += '' + statusText + ''; + html += '
  • '; + } + + return html; +} + +function renderX2DProgress(progressData) { + var titleEl = document.getElementById('x2d-progress-title'); + var metaEl = document.getElementById('x2d-progress-meta'); + var percentEl = document.getElementById('x2d-progress-percentage'); + var barEl = document.getElementById('x2d-progress-bar'); + var fillEl = document.getElementById('x2d-progress-fill'); + var scopeEl = document.getElementById('x2d-progress-scope'); + var completeEl = document.getElementById('x2d-progress-complete'); + var remainingEl = document.getElementById('x2d-progress-remaining'); + var deadlineEl = document.getElementById('x2d-progress-deadline'); + var timelineEl = document.getElementById('x2d-progress-timeline'); + var listEl = document.getElementById('x2d-progress-list'); + var items = progressData && progressData.items ? progressData.items : []; + var completedCount = 0; + var totalCount = items.length; + var remainingCount; + var percent; + var title; + var goal; + var formattedDeadline; + var timelineLabel; + var i; + + if (!titleEl || !metaEl || !percentEl || !barEl || !fillEl || !scopeEl || !completeEl || !remainingEl || !deadlineEl || !timelineEl || !listEl) { + return; + } + + for (i = 0; i < totalCount; i++) { + if (items[i] && items[i].completed === true) { + completedCount += 1; + } + } + + remainingCount = Math.max(totalCount - completedCount, 0); + percent = totalCount ? Math.round((completedCount / totalCount) * 100) : 0; + title = progressData && progressData.title ? progressData.title : 'Bambu X2D Preset Adaptation Progress'; + goal = progressData && progressData.goal ? progressData.goal : 'We are planning to finish X2D presets for all active products'; + formattedDeadline = formatDisplayDate(progressData && progressData.targetDate); + timelineLabel = getTimelineLabel(progressData && progressData.targetDate); + + titleEl.textContent = title; + metaEl.textContent = goal + (formattedDeadline ? ' before ' + formattedDeadline + '.' : '.'); + percentEl.textContent = percent + '%'; + fillEl.style.width = percent + '%'; + barEl.setAttribute('aria-valuenow', String(percent)); + scopeEl.textContent = 'View product checklist (' + totalCount + ')'; + completeEl.textContent = completedCount + ' / ' + totalCount; + remainingEl.textContent = String(remainingCount); + deadlineEl.textContent = formattedDeadline ? formattedDeadline : 'Date not set'; + timelineEl.textContent = timelineLabel; + listEl.innerHTML = buildX2DChecklist(items); +} + +function renderX2DProgressError(message) { + var metaEl = document.getElementById('x2d-progress-meta'); + var percentEl = document.getElementById('x2d-progress-percentage'); + var barEl = document.getElementById('x2d-progress-bar'); + var fillEl = document.getElementById('x2d-progress-fill'); + var scopeEl = document.getElementById('x2d-progress-scope'); + var completeEl = document.getElementById('x2d-progress-complete'); + var remainingEl = document.getElementById('x2d-progress-remaining'); + var deadlineEl = document.getElementById('x2d-progress-deadline'); + var timelineEl = document.getElementById('x2d-progress-timeline'); + var listEl = document.getElementById('x2d-progress-list'); + + if (metaEl) metaEl.textContent = message; + if (percentEl) percentEl.textContent = '0%'; + if (fillEl) fillEl.style.width = '0%'; + if (barEl) barEl.setAttribute('aria-valuenow', '0'); + if (scopeEl) scopeEl.textContent = 'View product checklist (0)'; + if (completeEl) completeEl.textContent = '0 / 0'; + if (remainingEl) remainingEl.textContent = '0'; + if (deadlineEl) deadlineEl.textContent = 'Date not set'; + if (timelineEl) timelineEl.textContent = 'Unavailable'; + if (listEl) listEl.innerHTML = ''; +} + +function initX2DProgress() { + fetch(X2D_PROGRESS_JSON_URL) + .then(function (r) { + if (!r.ok) { + throw new Error('Network response was not ok: ' + r.statusText); + } + return r.json(); + }) + .then(function (data) { + renderX2DProgress(data); + }) + .catch(function (err) { + console.warn('Failed to load X2D progress:', err); + renderX2DProgressError('Unable to load adaptation progress right now.'); + }); +} + function initTheme() { var initial = 'dark'; try { @@ -137,6 +314,7 @@ function init() { } initTheme(); + initX2DProgress(); // ============================================ // .bbsflmt Bundle Helper Functions diff --git a/index.html b/index.html index 572a94d..334e759 100644 --- a/index.html +++ b/index.html @@ -57,6 +57,45 @@

    Select your slicer to view and download Polymaker print profiles and filament presets

    +
    +
    +
    +

    Adaptation Progress

    +

    Bambu X2D Preset Adaptation Progress

    +

    Loading adaptation progress...

    +
    +
    + 0% + Complete +
    +
    +
    +
    +
    +
    +
    + Completed + 0 / 0 +
    +
    + Remaining + 0 +
    +
    + Target Date + May 10, 2026 +
    +
    + Timeline + 0 days left +
    +
    +
    + View product checklist (0) +
      +
      +
      +
      diff --git a/style.css b/style.css index 8cefaa7..6b86f83 100644 --- a/style.css +++ b/style.css @@ -179,6 +179,243 @@ body.theme-wiki .preset-text { margin: 0; } +.adaptation-card { + padding: 0.9rem 1rem 1rem; + margin-bottom: 0.55rem; + border-color: rgba(0, 204, 204, 0.28); + background: + linear-gradient(135deg, rgba(0, 204, 204, 0.14), rgba(0, 163, 163, 0.06)), + var(--bg-card); + overflow: hidden; +} + +.adaptation-header { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 1rem; + margin-bottom: 0.75rem; +} + +.adaptation-copy { + min-width: 0; +} + +.adaptation-kicker { + margin: 0 0 0.25rem; + font-size: 0.72rem; + font-weight: 700; + letter-spacing: 0.12em; + text-transform: uppercase; + color: var(--accent-light); +} + +.adaptation-title { + margin: 0; + font-size: clamp(1rem, 2vw, 1.3rem); + line-height: 1.1; + color: #ffffff; +} + +.adaptation-summary { + margin: 0.4rem 0 0; + max-width: 58rem; + font-size: 0.88rem; + color: rgba(228, 228, 231, 0.88); +} + +.adaptation-score { + display: inline-flex; + flex-direction: column; + align-items: flex-end; + justify-content: center; + min-width: 90px; + padding: 0.65rem 0.8rem; + border-radius: 12px; + background: rgba(255, 255, 255, 0.07); + border: 1px solid rgba(255, 255, 255, 0.12); +} + +.adaptation-score-value { + font-size: 1.5rem; + line-height: 1; + font-weight: 700; + color: #ffffff; +} + +.adaptation-score-label { + margin-top: 0.15rem; + font-size: 0.72rem; + color: var(--text-muted); + text-transform: uppercase; + letter-spacing: 0.08em; +} + +.adaptation-progress-track { + position: relative; + width: 100%; + height: 12px; + border-radius: 999px; + background: rgba(255, 255, 255, 0.08); + overflow: hidden; + border: 1px solid rgba(255, 255, 255, 0.08); +} + +.adaptation-progress-fill { + height: 100%; + width: 0; + border-radius: 999px; + background: linear-gradient(90deg, #00a3a3 0%, #00cccc 55%, #7ff7f2 100%); + box-shadow: 0 0 22px rgba(0, 204, 204, 0.35); +} + +.adaptation-metrics { + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: 0.6rem; + margin-top: 0.8rem; +} + +.adaptation-metric { + padding: 0.7rem 0.8rem; + border-radius: 12px; + background: rgba(255, 255, 255, 0.04); + border: 1px solid rgba(255, 255, 255, 0.08); +} + +.adaptation-metric-label { + display: block; + margin-bottom: 0.15rem; + font-size: 0.72rem; + color: var(--text-muted); + text-transform: uppercase; + letter-spacing: 0.08em; +} + +.adaptation-metric-value { + display: block; + font-size: 0.95rem; + color: var(--text); +} + +.adaptation-details { + margin-top: 0.8rem; + border-top: 1px solid rgba(255, 255, 255, 0.08); + padding-top: 0.75rem; +} + +.adaptation-details summary { + cursor: pointer; + font-size: 0.82rem; + font-weight: 600; + color: var(--accent-light); + list-style: none; +} + +.adaptation-details summary::-webkit-details-marker { + display: none; +} + +.adaptation-details summary::after { + content: '+'; + margin-left: 0.45rem; + color: var(--text-muted); +} + +.adaptation-details[open] summary::after { + content: '-'; +} + +.adaptation-items { + list-style: none; + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 0.55rem; + margin: 0.8rem 0 0; + padding: 0; +} + +.adaptation-item { + display: flex; + align-items: center; + justify-content: space-between; + gap: 0.75rem; + padding: 0.65rem 0.75rem; + border-radius: 10px; + background: rgba(255, 255, 255, 0.04); + border: 1px solid rgba(255, 255, 255, 0.08); +} + +.adaptation-item.is-complete { + background: rgba(0, 204, 204, 0.08); + border-color: rgba(0, 204, 204, 0.18); +} + +.adaptation-item-name { + min-width: 0; + font-size: 0.8rem; + color: var(--text); +} + +.adaptation-item-status { + flex-shrink: 0; + padding: 0.2rem 0.5rem; + border-radius: 999px; + background: rgba(255, 255, 255, 0.08); + font-size: 0.7rem; + font-weight: 600; + color: var(--text-muted); +} + +.adaptation-item.is-complete .adaptation-item-status { + background: rgba(0, 204, 204, 0.18); + color: #b8fffb; +} + +body.theme-wiki .adaptation-card { + border-color: rgba(0, 163, 163, 0.2); + background: + linear-gradient(135deg, rgba(0, 204, 204, 0.1), rgba(255, 255, 255, 0.94)), + var(--bg-card); +} + +body.theme-wiki .adaptation-title { + color: var(--text); +} + +body.theme-wiki .adaptation-summary { + color: rgba(17, 24, 39, 0.72); +} + +body.theme-wiki .adaptation-score, +body.theme-wiki .adaptation-metric, +body.theme-wiki .adaptation-item { + background: rgba(255, 255, 255, 0.84); + border-color: rgba(148, 163, 184, 0.18); +} + +body.theme-wiki .adaptation-score-value { + color: var(--text); +} + +body.theme-wiki .adaptation-progress-track { + background: rgba(148, 163, 184, 0.18); + border-color: rgba(148, 163, 184, 0.12); +} + +body.theme-wiki .adaptation-details { + border-top-color: rgba(148, 163, 184, 0.18); +} + +body.theme-wiki .adaptation-item.is-complete { + background: rgba(0, 204, 204, 0.1); + border-color: rgba(0, 204, 204, 0.22); +} + +body.theme-wiki .adaptation-item.is-complete .adaptation-item-status { + color: #066565; +} + /* Help icon button */ .help-icon-btn { position: relative; @@ -1211,6 +1448,28 @@ body.theme-wiki .btn-download-bundle:disabled { } @media (max-width: 640px) { + .adaptation-card { + padding: 0.75rem 0.8rem 0.85rem; + } + + .adaptation-header { + flex-direction: column; + align-items: stretch; + gap: 0.7rem; + } + + .adaptation-score { + align-items: flex-start; + } + + .adaptation-metrics { + grid-template-columns: 1fr 1fr; + } + + .adaptation-items { + grid-template-columns: 1fr; + } + .list-header { flex-direction: column; align-items: flex-start; diff --git a/x2d-progress.json b/x2d-progress.json new file mode 100644 index 0000000..1c5b1df --- /dev/null +++ b/x2d-progress.json @@ -0,0 +1,262 @@ +{ + "title": "Bambu X2D Preset Adaptation Progress", + "goal": "We are planning to finish X2D presets for all active products", + "targetDate": "2026-05-10", + "items": [ + { + "series": "Polymaker", + "productName": "PolyCast", + "completed": false + }, + { + "series": "Polymaker", + "productName": "PolyDissolve S1", + "completed": false + }, + { + "series": "Polymaker", + "productName": "PolyFlex TPU90", + "completed": false + }, + { + "series": "Polymaker", + "productName": "PolyFlex TPU95", + "completed": false + }, + { + "series": "Polymaker", + "productName": "PolyFlex TPU95-HF", + "completed": false + }, + { + "series": "Polymaker", + "productName": "Polymaker ABS", + "completed": false + }, + { + "series": "Polymaker", + "productName": "Polymaker ASA", + "completed": false + }, + { + "series": "Polymaker", + "productName": "Polymaker CosPLA", + "completed": false + }, + { + "series": "Polymaker", + "productName": "PolyLite LW-PLA", + "completed": false + }, + { + "series": "Polymaker", + "productName": "PolyLite PC", + "completed": false + }, + { + "series": "Polymaker", + "productName": "PolyMax PC", + "completed": false + }, + { + "series": "Polymaker", + "productName": "PolyLite PETG", + "completed": false + }, + { + "series": "Polymaker", + "productName": "PolyLite PLA", + "completed": false + }, + { + "series": "Polymaker", + "productName": "PolyLite PLA-CF", + "completed": false + }, + { + "series": "Polymaker", + "productName": "PolyLite PLA Pro", + "completed": false + }, + { + "series": "Polymaker", + "productName": "Polymaker HT-PLA", + "completed": false + }, + { + "series": "Polymaker", + "productName": "Polymaker HT-PLA-GF", + "completed": false + }, + { + "series": "Polymaker", + "productName": "Polymaker PETG", + "completed": false + }, + { + "series": "Polymaker", + "productName": "Polymaker PLA Pro", + "completed": false + }, + { + "series": "Polymaker", + "productName": "PolyMax PETG", + "completed": false + }, + { + "series": "Polymaker", + "productName": "PolyMax PLA", + "completed": false + }, + { + "series": "Polymaker", + "productName": "PolySmooth", + "completed": false + }, + { + "series": "Polymaker", + "productName": "PolySupport for PLA", + "completed": false + }, + { + "series": "Polymaker", + "productName": "PolySupport for PA12", + "completed": false + }, + { + "series": "Panchroma", + "productName": "Panchroma PLA Celestial", + "completed": false + }, + { + "series": "Panchroma", + "productName": "Panchroma PLA Galaxy", + "completed": false + }, + { + "series": "Panchroma", + "productName": "Panchroma PLA Glow", + "completed": false + }, + { + "series": "Panchroma", + "productName": "Panchroma PLA Luminous", + "completed": false + }, + { + "series": "Panchroma", + "productName": "Panchroma PLA Metallic", + "completed": false + }, + { + "series": "Panchroma", + "productName": "Panchroma PLA Neon", + "completed": false + }, + { + "series": "Panchroma", + "productName": "Panchroma PLA Starlight", + "completed": false + }, + { + "series": "Panchroma", + "productName": "Panchroma PLA Translucent", + "completed": false + }, + { + "series": "Panchroma", + "productName": "Panchroma PLA UV Shift", + "completed": false + }, + { + "series": "Panchroma", + "productName": "Panchroma CoPE", + "completed": false + }, + { + "series": "Panchroma", + "productName": "Panchroma PLA Basic", + "completed": false + }, + { + "series": "Panchroma", + "productName": "Panchroma PLA Silk", + "completed": false + }, + { + "series": "Panchroma", + "productName": "Panchroma PLA Matte", + "completed": false + }, + { + "series": "Panchroma", + "productName": "Panchroma PLA Marble", + "completed": false + }, + { + "series": "Panchroma", + "productName": "Panchroma PLA Satin", + "completed": false + }, + { + "series": "Fiberon", + "productName": "Fiberon PETG-ESD", + "completed": false + }, + { + "series": "Fiberon", + "productName": "Fiberon PETG-rCF08", + "completed": false + }, + { + "series": "Fiberon", + "productName": "Fiberon ASA-CF08", + "completed": false + }, + { + "series": "Fiberon", + "productName": "Fiberon PET-CF17", + "completed": false + }, + { + "series": "Fiberon", + "productName": "Fiberon PET-GF15", + "completed": false + }, + { + "series": "Fiberon", + "productName": "Fiberon PA12-CF10", + "completed": false + }, + { + "series": "Fiberon", + "productName": "Fiberon PA6-CF20", + "completed": false + }, + { + "series": "Fiberon", + "productName": "Fiberon PA6-GF25", + "completed": false + }, + { + "series": "Fiberon", + "productName": "Fiberon PA612-CF15", + "completed": false + }, + { + "series": "Fiberon", + "productName": "Fiberon PA612-ESD", + "completed": false + }, + { + "series": "Fiberon", + "productName": "Fiberon PPS-CF10", + "completed": false + }, + { + "series": "Fiberon", + "productName": "Fiberon PPS-GF20", + "completed": false + } + ] +}