feat(rpubs): polish publish flow with teal accents, SVG icons, and card layouts
Visual overhaul of the rpubs editor and publish panel to match rpubs.online/press design language: circular step indicators with checkmarks, SVG stroke icons on all buttons, card-based publish panel with teal accent colors, pricing tier cards, numbered DIY guide steps, group buy progress bar, and format info chips. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
0067369af1
commit
2ab620fcc5
|
|
@ -64,6 +64,14 @@ These ideas matter because they challenge the assumption that only private owner
|
|||
|
||||
const SYNC_DEBOUNCE_MS = 800;
|
||||
|
||||
// ── SVG Icons ──
|
||||
const SVG_CHECK = `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>`;
|
||||
const SVG_ARROW_RIGHT = `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="5" y1="12" x2="19" y2="12"/><polyline points="12 5 19 12 12 19"/></svg>`;
|
||||
const SVG_ARROW_LEFT = `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="19" y1="12" x2="5" y2="12"/><polyline points="12 19 5 12 12 5"/></svg>`;
|
||||
const SVG_EXPAND = `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 3 21 3 21 9"/><polyline points="9 21 3 21 3 15"/><line x1="21" y1="3" x2="14" y2="10"/><line x1="3" y1="21" x2="10" y2="14"/></svg>`;
|
||||
const SVG_DOWNLOAD = `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>`;
|
||||
const SVG_SPINNER = `<span class="btn-spinner"></span>`;
|
||||
|
||||
export class FolkPubsEditor extends HTMLElement {
|
||||
private _formats: BookFormat[] = [];
|
||||
private _spaceSlug = "personal";
|
||||
|
|
@ -451,18 +459,18 @@ export class FolkPubsEditor extends HTMLElement {
|
|||
<!-- Step indicator -->
|
||||
<div class="step-indicator">
|
||||
<div class="steps">
|
||||
<button class="step ${this._view === 'write' ? 'active' : ''} ${this._pdfUrl ? 'completed' : ''}" data-step="write">
|
||||
<span class="step-num">${this._pdfUrl ? '✓' : '1'}</span>
|
||||
<button class="step ${this._view === 'write' ? 'active' : ''} ${this._pdfUrl && this._view !== 'write' ? 'done' : ''}" data-step="write">
|
||||
<span class="step-circle">${this._pdfUrl && this._view !== 'write' ? SVG_CHECK : '1'}</span>
|
||||
<span class="step-label">Create</span>
|
||||
</button>
|
||||
<div class="step-line ${this._view !== 'write' ? 'filled' : ''}"></div>
|
||||
<button class="step ${this._view === 'preview' ? 'active' : ''} ${this._view === 'publish' ? 'completed' : ''}" data-step="preview" ${!this._pdfUrl ? 'disabled' : ''}>
|
||||
<span class="step-num">${this._view === 'publish' ? '✓' : '2'}</span>
|
||||
<button class="step ${this._view === 'preview' ? 'active' : ''} ${this._view === 'publish' ? 'done' : ''}" data-step="preview" ${!this._pdfUrl ? 'disabled' : ''}>
|
||||
<span class="step-circle">${this._view === 'publish' ? SVG_CHECK : '2'}</span>
|
||||
<span class="step-label">Preview</span>
|
||||
</button>
|
||||
<div class="step-line ${this._view === 'publish' ? 'filled' : ''}"></div>
|
||||
<button class="step ${this._view === 'publish' ? 'active' : ''}" data-step="publish" ${!this._pdfUrl ? 'disabled' : ''}>
|
||||
<span class="step-num">3</span>
|
||||
<span class="step-circle">3</span>
|
||||
<span class="step-label">Publish</span>
|
||||
</button>
|
||||
</div>
|
||||
|
|
@ -499,7 +507,9 @@ export class FolkPubsEditor extends HTMLElement {
|
|||
</div>
|
||||
${this._error ? `<div class="inline-error">${this.escapeHtml(this._error)}</div>` : ''}
|
||||
<button class="btn-generate" ${this._loading ? "disabled" : ""}>
|
||||
${this._loading ? "Generating..." : "Generate Preview \u2192"}
|
||||
${this._loading
|
||||
? `${SVG_SPINNER} Generating...`
|
||||
: `Generate Preview ${SVG_ARROW_RIGHT}`}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -507,19 +517,22 @@ export class FolkPubsEditor extends HTMLElement {
|
|||
}
|
||||
|
||||
private renderPreviewStep(): string {
|
||||
const currentFormat = this._formats.find(f => f.id === this._selectedFormat);
|
||||
const dims = currentFormat ? `${currentFormat.widthMm}\u00D7${currentFormat.heightMm}mm` : '';
|
||||
return `
|
||||
<div class="preview-step">
|
||||
<div class="preview-actions">
|
||||
<button class="action-btn" data-action="back-to-write">\u2190 Edit</button>
|
||||
<button class="action-btn" data-action="fullscreen-toggle">Fullscreen</button>
|
||||
<a class="action-btn" href="${this._pdfUrl}" download>Download PDF</a>
|
||||
<button class="action-btn" data-action="back-to-write">${SVG_ARROW_LEFT} Edit</button>
|
||||
<button class="action-btn" data-action="fullscreen-toggle">${SVG_EXPAND} Fullscreen</button>
|
||||
<a class="action-btn" href="${this._pdfUrl}" download>${SVG_DOWNLOAD} Download</a>
|
||||
${currentFormat ? `<span class="format-chip">${this.escapeHtml(currentFormat.name)} \u00B7 ${dims}</span>` : ''}
|
||||
</div>
|
||||
<div class="preview-flipbook">
|
||||
<folk-pubs-flipbook pdf-url="${this._pdfUrl}"></folk-pubs-flipbook>
|
||||
</div>
|
||||
<div class="preview-bottom-bar">
|
||||
<span class="preview-info">${this._pdfInfo || ''}</span>
|
||||
<button class="btn-publish-next">Publish \u2192</button>
|
||||
<button class="btn-publish-next">Publish ${SVG_ARROW_RIGHT}</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
|
@ -942,7 +955,7 @@ export class FolkPubsEditor extends HTMLElement {
|
|||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0.5rem 1rem;
|
||||
padding: 0.625rem 1rem;
|
||||
border-bottom: 1px solid var(--rs-border-subtle);
|
||||
background: var(--rs-bg-surface);
|
||||
}
|
||||
|
|
@ -956,39 +969,56 @@ export class FolkPubsEditor extends HTMLElement {
|
|||
.step {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.375rem;
|
||||
padding: 0.35rem 0.75rem;
|
||||
border: 1px solid var(--rs-border);
|
||||
border-radius: 2rem;
|
||||
gap: 0.5rem;
|
||||
padding: 0.25rem 0.5rem;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
background: transparent;
|
||||
color: var(--rs-text-muted);
|
||||
font-size: 0.78rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s;
|
||||
}
|
||||
.step:disabled { cursor: not-allowed; opacity: 0.4; }
|
||||
.step:not(:disabled):hover { border-color: var(--rs-primary); color: var(--rs-text-primary); }
|
||||
.step.active {
|
||||
background: var(--rs-primary);
|
||||
border-color: var(--rs-primary);
|
||||
.step:disabled { cursor: not-allowed; opacity: 0.35; }
|
||||
.step:not(:disabled):hover { color: var(--rs-text-primary); }
|
||||
.step:not(:disabled):hover .step-circle { border-color: var(--rs-accent, #14b8a6); }
|
||||
.step.active .step-circle {
|
||||
background: var(--rs-accent, #14b8a6);
|
||||
border-color: var(--rs-accent, #14b8a6);
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
}
|
||||
.step.completed:not(.active) {
|
||||
border-color: var(--rs-success, #22c55e);
|
||||
color: var(--rs-success, #22c55e);
|
||||
.step.active { color: var(--rs-text-primary); font-weight: 600; }
|
||||
.step.done .step-circle {
|
||||
background: var(--rs-accent, #14b8a6);
|
||||
border-color: var(--rs-accent, #14b8a6);
|
||||
color: #fff;
|
||||
}
|
||||
.step.done { color: var(--rs-accent, #14b8a6); }
|
||||
|
||||
.step-num { font-weight: 700; font-size: 0.75rem; }
|
||||
.step-circle {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 50%;
|
||||
border: 2px solid var(--rs-border, #444);
|
||||
background: transparent;
|
||||
color: var(--rs-text-muted);
|
||||
font-size: 0.75rem;
|
||||
font-weight: 700;
|
||||
transition: all 0.15s;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.step-line {
|
||||
width: 24px;
|
||||
width: 32px;
|
||||
height: 2px;
|
||||
background: var(--rs-border);
|
||||
margin: 0 0.25rem;
|
||||
margin: 0 0.125rem;
|
||||
transition: background 0.15s;
|
||||
}
|
||||
.step-line.filled { background: var(--rs-primary); }
|
||||
.step-line.filled { background: var(--rs-accent, #14b8a6); }
|
||||
|
||||
.step-info {
|
||||
font-size: 0.75rem;
|
||||
|
|
@ -1052,8 +1082,8 @@ export class FolkPubsEditor extends HTMLElement {
|
|||
.format-badge {
|
||||
padding: 0.15rem 0.5rem;
|
||||
border-radius: 1rem;
|
||||
background: rgba(59, 130, 246, 0.1);
|
||||
color: var(--rs-primary);
|
||||
background: rgba(20, 184, 166, 0.1);
|
||||
color: var(--rs-accent, #14b8a6);
|
||||
font-size: 0.65rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
|
@ -1067,17 +1097,30 @@ export class FolkPubsEditor extends HTMLElement {
|
|||
padding: 0.5rem 1.25rem;
|
||||
border: none;
|
||||
border-radius: 0.5rem;
|
||||
background: var(--rs-primary);
|
||||
background: var(--rs-accent, #14b8a6);
|
||||
color: #fff;
|
||||
font-size: 0.85rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: background 0.15s;
|
||||
white-space: nowrap;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.375rem;
|
||||
}
|
||||
.btn-generate:hover { background: var(--rs-primary-hover); }
|
||||
.btn-generate:hover { background: var(--rs-accent-hover, #0d9488); }
|
||||
.btn-generate:disabled { opacity: 0.5; cursor: not-allowed; }
|
||||
|
||||
.btn-spinner {
|
||||
display: inline-block;
|
||||
width: 14px; height: 14px;
|
||||
border: 2px solid rgba(255,255,255,0.3);
|
||||
border-top-color: #fff;
|
||||
border-radius: 50%;
|
||||
animation: btn-spin 0.6s linear infinite;
|
||||
}
|
||||
@keyframes btn-spin { to { transform: rotate(360deg); } }
|
||||
|
||||
/* ── Preview step ── */
|
||||
|
||||
.preview-step {
|
||||
|
|
@ -1105,8 +1148,21 @@ export class FolkPubsEditor extends HTMLElement {
|
|||
text-decoration: none;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.375rem;
|
||||
}
|
||||
.action-btn:hover { border-color: var(--rs-accent, #14b8a6); color: var(--rs-text-primary); }
|
||||
.action-btn:hover svg { color: var(--rs-accent, #14b8a6); }
|
||||
|
||||
.format-chip {
|
||||
margin-left: auto;
|
||||
padding: 0.25rem 0.625rem;
|
||||
border-radius: 1rem;
|
||||
background: rgba(20, 184, 166, 0.1);
|
||||
color: var(--rs-accent, #14b8a6);
|
||||
font-size: 0.7rem;
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.action-btn:hover { border-color: var(--rs-primary); color: var(--rs-text-primary); }
|
||||
|
||||
.preview-flipbook {
|
||||
flex: 1;
|
||||
|
|
@ -1140,14 +1196,17 @@ export class FolkPubsEditor extends HTMLElement {
|
|||
padding: 0.5rem 1.25rem;
|
||||
border: none;
|
||||
border-radius: 0.5rem;
|
||||
background: var(--rs-primary);
|
||||
background: var(--rs-accent, #14b8a6);
|
||||
color: #fff;
|
||||
font-size: 0.85rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: background 0.15s;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.375rem;
|
||||
}
|
||||
.btn-publish-next:hover { background: var(--rs-primary-hover); }
|
||||
.btn-publish-next:hover { background: var(--rs-accent-hover, #0d9488); }
|
||||
|
||||
/* ── Publish step ── */
|
||||
|
||||
|
|
|
|||
|
|
@ -169,9 +169,19 @@ export class FolkPubsFlipbook extends HTMLElement {
|
|||
const container = this.shadowRoot.querySelector(".flipbook-container") as HTMLElement;
|
||||
if (!container) return;
|
||||
|
||||
try {
|
||||
await this.loadStPageFlip();
|
||||
} catch (e) {
|
||||
console.warn('[folk-pubs-flipbook] StPageFlip failed to load, using fallback:', e);
|
||||
this.renderFallback();
|
||||
return;
|
||||
}
|
||||
const PageFlip = (window as any).St?.PageFlip;
|
||||
if (!PageFlip) return;
|
||||
if (!PageFlip) {
|
||||
console.warn('[folk-pubs-flipbook] StPageFlip not available, using fallback');
|
||||
this.renderFallback();
|
||||
return;
|
||||
}
|
||||
|
||||
this._flipBook = new PageFlip(container, {
|
||||
width: Math.round(pageW),
|
||||
|
|
@ -241,9 +251,26 @@ export class FolkPubsFlipbook extends HTMLElement {
|
|||
});
|
||||
}
|
||||
|
||||
private renderFallback() {
|
||||
if (!this.shadowRoot) return;
|
||||
this.shadowRoot.innerHTML = `
|
||||
${this.getStyles()}
|
||||
<div class="reader">
|
||||
<div class="fallback-pages">
|
||||
${this._pageImages.map((src, i) => `<img src="${src}" alt="Page ${i + 1}" />`).join('')}
|
||||
</div>
|
||||
<div class="page-info">${this._numPages} pages</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private getStyles(): string {
|
||||
return `<style>
|
||||
:host { display: block; }
|
||||
:host {
|
||||
display: block;
|
||||
position: relative;
|
||||
z-index: 0; /* stacking context — keeps StPageFlip elements above backgrounds */
|
||||
}
|
||||
|
||||
.loading {
|
||||
display: flex; flex-direction: column; align-items: center;
|
||||
|
|
@ -252,21 +279,32 @@ export class FolkPubsFlipbook extends HTMLElement {
|
|||
.loading-spinner {
|
||||
width: 32px; height: 32px;
|
||||
border: 3px solid var(--rs-border-strong, #444);
|
||||
border-top-color: #60a5fa; border-radius: 50%;
|
||||
border-top-color: var(--rs-accent, #14b8a6); border-radius: 50%;
|
||||
animation: spin 0.8s linear infinite;
|
||||
}
|
||||
@keyframes spin { to { transform: rotate(360deg); } }
|
||||
.loading-status { color: var(--rs-text-secondary, #aaa); font-size: 0.8rem; }
|
||||
.loading-bar { width: 160px; height: 3px; background: var(--rs-bg-surface, #333); border-radius: 2px; overflow: hidden; }
|
||||
.loading-fill { height: 100%; background: #60a5fa; transition: width 0.3s; border-radius: 2px; }
|
||||
.loading-fill { height: 100%; background: var(--rs-accent, #14b8a6); transition: width 0.3s; border-radius: 2px; }
|
||||
|
||||
.error { padding: 1rem; color: #f87171; font-size: 0.8rem; text-align: center; }
|
||||
|
||||
.reader { display: flex; flex-direction: column; align-items: center; gap: 0.5rem; }
|
||||
.flipbook-row { display: flex; align-items: center; gap: 0.375rem; }
|
||||
.flipbook-container {
|
||||
position: relative;
|
||||
overflow: hidden; border-radius: 3px;
|
||||
box-shadow: 0 4px 20px rgba(0,0,0,0.35);
|
||||
background: #fff;
|
||||
}
|
||||
/* Fallback scroll view when StPageFlip fails to load */
|
||||
.fallback-pages {
|
||||
display: flex; flex-direction: column; align-items: center; gap: 1rem;
|
||||
padding: 1rem; max-height: 500px; overflow-y: auto;
|
||||
}
|
||||
.fallback-pages img {
|
||||
max-width: 100%; border-radius: 2px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.2);
|
||||
}
|
||||
.nav-btn {
|
||||
width: 32px; height: 60px;
|
||||
|
|
|
|||
|
|
@ -30,6 +30,24 @@ export class FolkPubsPublishPanel extends HTMLElement {
|
|||
private _orderStatus: string | null = null;
|
||||
private _batchStatus: any = null;
|
||||
|
||||
private static readonly ICONS = {
|
||||
download: `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>`,
|
||||
copy: `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>`,
|
||||
mail: `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="4" width="20" height="16" rx="2"/><path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7"/></svg>`,
|
||||
book: `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"/></svg>`,
|
||||
scissors: `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="6" cy="6" r="3"/><circle cx="6" cy="18" r="3"/><line x1="20" y1="4" x2="8.12" y2="15.88"/><line x1="14.47" y1="14.48" x2="20" y2="20"/><line x1="8.12" y1="8.12" x2="12" y2="12"/></svg>`,
|
||||
printer: `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 6 2 18 2 18 9"/><path d="M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"/><rect x="6" y="14" width="12" height="8"/></svg>`,
|
||||
location: `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"/><circle cx="12" cy="10" r="3"/></svg>`,
|
||||
users: `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg>`,
|
||||
check: `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>`,
|
||||
send: `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><line x1="22" y1="2" x2="11" y2="13"/><polygon points="22 2 15 22 11 13 2 9 22 2"/></svg>`,
|
||||
share: `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="18" cy="5" r="3"/><circle cx="6" cy="12" r="3"/><circle cx="18" cy="19" r="3"/><line x1="8.59" y1="13.51" x2="15.42" y2="17.49"/><line x1="15.41" y1="6.51" x2="8.59" y2="10.49"/></svg>`,
|
||||
info: `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="12" y1="16" x2="12" y2="12"/><line x1="12" y1="8" x2="12.01" y2="8"/></svg>`,
|
||||
phone: `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72c.127.96.361 1.903.7 2.81a2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45c.907.339 1.85.573 2.81.7A2 2 0 0 1 22 16.92z"/></svg>`,
|
||||
globe: `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/></svg>`,
|
||||
verified: `<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg>`,
|
||||
};
|
||||
|
||||
static get observedAttributes() {
|
||||
return ["pdf-url", "format-id", "format-name", "page-count", "space-slug"];
|
||||
}
|
||||
|
|
@ -59,9 +77,18 @@ export class FolkPubsPublishPanel extends HTMLElement {
|
|||
${this.getStyles()}
|
||||
<div class="panel">
|
||||
<div class="tabs">
|
||||
<button class="tab ${this._activeTab === 'share' ? 'active' : ''}" data-tab="share">Share</button>
|
||||
<button class="tab ${this._activeTab === 'diy' ? 'active' : ''}" data-tab="diy">DIY Print</button>
|
||||
<button class="tab ${this._activeTab === 'order' ? 'active' : ''}" data-tab="order">Order</button>
|
||||
<button class="tab ${this._activeTab === 'share' ? 'active' : ''}" data-tab="share">
|
||||
${FolkPubsPublishPanel.ICONS.share}
|
||||
<span>Share</span>
|
||||
</button>
|
||||
<button class="tab ${this._activeTab === 'diy' ? 'active' : ''}" data-tab="diy">
|
||||
${FolkPubsPublishPanel.ICONS.scissors}
|
||||
<span>DIY Print</span>
|
||||
</button>
|
||||
<button class="tab ${this._activeTab === 'order' ? 'active' : ''}" data-tab="order">
|
||||
${FolkPubsPublishPanel.ICONS.printer}
|
||||
<span>Order</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="tab-content">
|
||||
${this._activeTab === 'share' ? this.renderShareTab() : ''}
|
||||
|
|
@ -76,28 +103,64 @@ export class FolkPubsPublishPanel extends HTMLElement {
|
|||
private renderShareTab(): string {
|
||||
return `
|
||||
<div class="section">
|
||||
<a class="action-btn primary" href="${this._pdfUrl}" download>Download PDF</a>
|
||||
<button class="action-btn" data-action="copy-link">Copy Flipbook Link</button>
|
||||
<div class="info-card">
|
||||
${FolkPubsPublishPanel.ICONS.book}
|
||||
<div class="info-card-text">
|
||||
<span class="info-card-title">${this.esc(this._formatName)}</span>
|
||||
<span class="info-card-detail">${this._pageCount} pages</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a class="action-btn primary" href="${this._pdfUrl}" download>
|
||||
${FolkPubsPublishPanel.ICONS.download}
|
||||
<span>Download PDF</span>
|
||||
</a>
|
||||
|
||||
<button class="action-btn secondary" data-action="copy-link">
|
||||
${FolkPubsPublishPanel.ICONS.copy}
|
||||
<span>Copy Flipbook Link</span>
|
||||
</button>
|
||||
|
||||
<div class="divider-row">
|
||||
<span class="divider-line"></span>
|
||||
<span class="divider-text">or send by email</span>
|
||||
<span class="divider-line"></span>
|
||||
</div>
|
||||
|
||||
<div class="email-row">
|
||||
<input type="email" class="email-input" placeholder="Email address" />
|
||||
<button class="action-btn small" data-action="email-pdf" ${this._emailSending ? 'disabled' : ''}>
|
||||
${this._emailSending ? 'Sending...' : 'Send'}
|
||||
<div class="email-input-wrap">
|
||||
${FolkPubsPublishPanel.ICONS.mail}
|
||||
<input type="email" class="email-input" placeholder="recipient@email.com" />
|
||||
</div>
|
||||
<button class="action-btn accent send-btn" data-action="email-pdf" ${this._emailSending ? 'disabled' : ''}>
|
||||
${this._emailSending
|
||||
? '<span class="spinner"></span>'
|
||||
: FolkPubsPublishPanel.ICONS.send}
|
||||
<span>${this._emailSending ? 'Sending...' : 'Send'}</span>
|
||||
</button>
|
||||
</div>
|
||||
${this._emailSent ? '<div class="msg success">PDF sent!</div>' : ''}
|
||||
${this._emailSent ? `<div class="msg success">${FolkPubsPublishPanel.ICONS.check} PDF sent!</div>` : ''}
|
||||
${this._emailError ? `<div class="msg error">${this.esc(this._emailError)}</div>` : ''}
|
||||
</div>
|
||||
<div class="meta">
|
||||
${this._formatName} · ${this._pageCount} pages
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private renderDiyTab(): string {
|
||||
return `
|
||||
<div class="section">
|
||||
<div class="section-header">
|
||||
${FolkPubsPublishPanel.ICONS.scissors}
|
||||
<div>
|
||||
<div class="section-title">DIY Printing Guide</div>
|
||||
<div class="section-subtitle">Print, fold & bind at home</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="action-btn primary" data-action="download-imposition" ${this._impositionLoading ? 'disabled' : ''}>
|
||||
${this._impositionLoading ? 'Generating...' : 'Download Imposition PDF'}
|
||||
${this._impositionLoading
|
||||
? '<span class="spinner"></span>'
|
||||
: FolkPubsPublishPanel.ICONS.download}
|
||||
<span>${this._impositionLoading ? 'Generating...' : 'Download Imposition PDF'}</span>
|
||||
</button>
|
||||
<p class="hint">Pre-arranged pages for double-sided printing & folding.</p>
|
||||
<div class="guide-placeholder" data-guide-target></div>
|
||||
|
|
@ -112,8 +175,30 @@ export class FolkPubsPublishPanel extends HTMLElement {
|
|||
|
||||
return `
|
||||
<div class="section">
|
||||
<div class="pricing-tiers">
|
||||
<div class="tier-card">
|
||||
<div class="tier-qty">25+</div>
|
||||
<div class="tier-binding">Saddle stitch</div>
|
||||
<div class="tier-price">~$1.20/ea</div>
|
||||
</div>
|
||||
<div class="tier-card popular">
|
||||
<div class="tier-badge">popular</div>
|
||||
<div class="tier-qty">50+</div>
|
||||
<div class="tier-binding">Perfect bind</div>
|
||||
<div class="tier-price">~$0.85/ea</div>
|
||||
</div>
|
||||
<div class="tier-card">
|
||||
<div class="tier-qty">100+</div>
|
||||
<div class="tier-binding">Perfect bind</div>
|
||||
<div class="tier-price">~$0.60/ea</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="action-btn primary" data-action="find-printers" ${this._printersLoading ? 'disabled' : ''}>
|
||||
${this._printersLoading ? 'Searching...' : 'Find Nearby Printers'}
|
||||
${this._printersLoading
|
||||
? '<span class="spinner"></span>'
|
||||
: FolkPubsPublishPanel.ICONS.location}
|
||||
<span>${this._printersLoading ? 'Searching...' : 'Find Nearby Printers'}</span>
|
||||
</button>
|
||||
${this._printersError ? `<div class="msg error">${this.esc(this._printersError)}</div>` : ''}
|
||||
${this._printers.length > 0 ? this.renderPrinterList() : ''}
|
||||
|
|
@ -126,10 +211,13 @@ export class FolkPubsPublishPanel extends HTMLElement {
|
|||
<div class="printer-list">
|
||||
${this._printers.map((p) => `
|
||||
<button class="printer-card" data-provider-id="${this.esc(p.id)}">
|
||||
<div class="printer-name">${this.esc(p.name)}</div>
|
||||
<div class="printer-header">
|
||||
<span class="printer-name">${this.esc(p.name)}</span>
|
||||
<span class="printer-distance">${p.distance_km} km</span>
|
||||
</div>
|
||||
<div class="printer-meta">
|
||||
${this.esc(p.city)} · ${p.distance_km} km
|
||||
${p.source === 'curated' ? '<span class="badge">curated</span>' : ''}
|
||||
${this.esc(p.city)}
|
||||
${p.source === 'curated' ? `<span class="verified-badge">${FolkPubsPublishPanel.ICONS.verified} verified</span>` : ''}
|
||||
</div>
|
||||
${p.tags?.length ? `<div class="printer-tags">${p.tags.map((t: string) => `<span class="tag">${this.esc(t)}</span>`).join('')}</div>` : ''}
|
||||
${p.capabilities?.length ? `<div class="printer-caps">${p.capabilities.join(', ')}</div>` : ''}
|
||||
|
|
@ -145,18 +233,57 @@ export class FolkPubsPublishPanel extends HTMLElement {
|
|||
<div class="section">
|
||||
<button class="back-btn" data-action="back-to-list">← Back to results</button>
|
||||
<div class="provider-detail">
|
||||
<div class="provider-card-header">
|
||||
${FolkPubsPublishPanel.ICONS.printer}
|
||||
<h4>${this.esc(p.name)}</h4>
|
||||
<div class="provider-info">
|
||||
${p.address ? `<div>${this.esc(p.address)}</div>` : ''}
|
||||
${p.website ? `<div><a href="${this.esc(p.website)}" target="_blank" rel="noopener">${this.esc(p.website)}</a></div>` : ''}
|
||||
${p.email ? `<div>${this.esc(p.email)}</div>` : ''}
|
||||
${p.phone ? `<div>${this.esc(p.phone)}</div>` : ''}
|
||||
${p.description ? `<div class="provider-desc">${this.esc(p.description)}</div>` : ''}
|
||||
</div>
|
||||
<button class="action-btn primary" data-action="place-order">Place Order</button>
|
||||
<button class="action-btn" data-action="join-batch">Join Group Buy</button>
|
||||
${this._orderStatus ? `<div class="msg success">${this.esc(this._orderStatus)}</div>` : ''}
|
||||
${this._batchStatus ? `<div class="msg info">Batch: ${this._batchStatus.action} · ${this._batchStatus.participants || '?'} participants</div>` : ''}
|
||||
<div class="provider-info-grid">
|
||||
${p.address ? `<div class="provider-info-row">${FolkPubsPublishPanel.ICONS.location} <span>${this.esc(p.address)}</span></div>` : ''}
|
||||
${p.phone ? `<div class="provider-info-row">${FolkPubsPublishPanel.ICONS.phone} <span>${this.esc(p.phone)}</span></div>` : ''}
|
||||
${p.website ? `<div class="provider-info-row">${FolkPubsPublishPanel.ICONS.globe} <a href="${this.esc(p.website)}" target="_blank" rel="noopener">${this.esc(p.website)}</a></div>` : ''}
|
||||
${p.email ? `<div class="provider-info-row">${FolkPubsPublishPanel.ICONS.mail} <span>${this.esc(p.email)}</span></div>` : ''}
|
||||
</div>
|
||||
${p.description ? `<div class="provider-desc">${this.esc(p.description)}</div>` : ''}
|
||||
|
||||
<button class="action-btn primary" data-action="place-order">
|
||||
${FolkPubsPublishPanel.ICONS.printer}
|
||||
<span>Place Order</span>
|
||||
</button>
|
||||
<button class="action-btn secondary" data-action="join-batch">
|
||||
${FolkPubsPublishPanel.ICONS.users}
|
||||
<span>Join Group Buy</span>
|
||||
</button>
|
||||
|
||||
${this._orderStatus ? `<div class="msg success">${FolkPubsPublishPanel.ICONS.check} ${this.esc(this._orderStatus)}</div>` : ''}
|
||||
${this._batchStatus ? this.renderBatchProgress() : ''}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private renderBatchProgress(): string {
|
||||
const b = this._batchStatus;
|
||||
if (b.action === 'error') return `<div class="msg error">${this.esc(b.error)}</div>`;
|
||||
|
||||
const participants = b.participants || 1;
|
||||
const threshold = 25;
|
||||
const pct = Math.min(Math.round((participants / threshold) * 100), 100);
|
||||
|
||||
return `
|
||||
<div class="batch-card">
|
||||
<div class="batch-header">
|
||||
${FolkPubsPublishPanel.ICONS.users}
|
||||
<span>Group Buy Progress</span>
|
||||
</div>
|
||||
<div class="batch-bar-wrap">
|
||||
<div class="batch-bar">
|
||||
<div class="batch-bar-fill" style="width:${pct}%"></div>
|
||||
</div>
|
||||
<div class="batch-bar-label">${participants} / ${threshold} participants</div>
|
||||
</div>
|
||||
<div class="batch-tiers">
|
||||
<span class="${participants >= 25 ? 'tier-unlocked' : 'tier-locked'}">25+ unlocked</span>
|
||||
<span class="${participants >= 50 ? 'tier-unlocked' : 'tier-locked'}">50+ ${participants >= 50 ? 'unlocked' : 'locked'}</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
|
@ -178,8 +305,12 @@ export class FolkPubsPublishPanel extends HTMLElement {
|
|||
const url = `${window.location.origin}${window.location.pathname}`;
|
||||
navigator.clipboard.writeText(url).then(() => {
|
||||
const btn = this.shadowRoot!.querySelector('[data-action="copy-link"]')!;
|
||||
btn.textContent = "Copied!";
|
||||
setTimeout(() => { btn.textContent = "Copy Flipbook Link"; }, 2000);
|
||||
const span = btn.querySelector('span');
|
||||
if (span) span.textContent = "Copied!";
|
||||
btn.innerHTML = `${FolkPubsPublishPanel.ICONS.check}<span>Copied!</span>`;
|
||||
setTimeout(() => {
|
||||
btn.innerHTML = `${FolkPubsPublishPanel.ICONS.copy}<span>Copy Flipbook Link</span>`;
|
||||
}, 2000);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -315,22 +446,61 @@ export class FolkPubsPublishPanel extends HTMLElement {
|
|||
|
||||
target.innerHTML = `
|
||||
<div class="guide">
|
||||
<h4>${guide.formatName} — DIY Guide</h4>
|
||||
<div class="guide-stat">Sheets needed: ${sheets} (${guide.parentSheet})</div>
|
||||
<div class="guide-stat">Binding: ${binding}</div>
|
||||
<div class="guide-stat">Paper: ${guide.paperRecommendation}</div>
|
||||
<div class="guide-card">
|
||||
<div class="guide-stats">
|
||||
<div class="guide-stat-col">
|
||||
<span class="guide-stat-value">${sheets}</span>
|
||||
<span class="guide-stat-label">Sheets</span>
|
||||
</div>
|
||||
<div class="guide-stat-col">
|
||||
<span class="guide-stat-value">${guide.parentSheet}</span>
|
||||
<span class="guide-stat-label">Paper</span>
|
||||
</div>
|
||||
<div class="guide-stat-col">
|
||||
<span class="guide-stat-value">${binding}</span>
|
||||
<span class="guide-stat-label">Binding</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h5>Tools</h5>
|
||||
<ul>${guide.tools.map((t: string) => `<li>${t}</li>`).join('')}</ul>
|
||||
<div class="guide-info-banner">
|
||||
${FolkPubsPublishPanel.ICONS.info}
|
||||
<span>${guide.paperRecommendation}</span>
|
||||
</div>
|
||||
|
||||
<h5>Folding</h5>
|
||||
<ol>${guide.foldInstructions.map((s: string) => `<li>${s}</li>`).join('')}</ol>
|
||||
<div class="guide-section">
|
||||
<div class="guide-section-title">Tools needed</div>
|
||||
<ul class="guide-list">${guide.tools.map((t: string) => `<li>${t}</li>`).join('')}</ul>
|
||||
</div>
|
||||
|
||||
<h5>Binding</h5>
|
||||
<ol>${guide.bindingInstructions.filter((s: string) => s).map((s: string) => `<li>${s.replace(/^\s+/, '')}</li>`).join('')}</ol>
|
||||
<div class="guide-section">
|
||||
<div class="guide-section-title">Folding</div>
|
||||
<div class="guide-numbered-steps">
|
||||
${guide.foldInstructions.map((s: string, i: number) => `
|
||||
<div class="guide-step">
|
||||
<span class="guide-step-num">${i + 1}</span>
|
||||
<span>${s}</span>
|
||||
</div>
|
||||
`).join('')}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h5>Tips</h5>
|
||||
<ul>${guide.tips.map((t: string) => `<li>${t}</li>`).join('')}</ul>
|
||||
<div class="guide-section">
|
||||
<div class="guide-section-title">Binding</div>
|
||||
<div class="guide-numbered-steps">
|
||||
${guide.bindingInstructions.filter((s: string) => s).map((s: string, i: number) => `
|
||||
<div class="guide-step">
|
||||
<span class="guide-step-num">${i + 1}</span>
|
||||
<span>${s.replace(/^\s+/, '')}</span>
|
||||
</div>
|
||||
`).join('')}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="guide-section">
|
||||
<div class="guide-section-title">Tips</div>
|
||||
<ul class="guide-tips">${guide.tips.map((t: string) => `<li>\u2605 ${t}</li>`).join('')}</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
|
@ -446,104 +616,378 @@ export class FolkPubsPublishPanel extends HTMLElement {
|
|||
private getStyles(): string {
|
||||
return `<style>
|
||||
:host { display: block; max-width: 32rem; margin: 0 auto; }
|
||||
.panel { display: flex; flex-direction: column; gap: 0.5rem; }
|
||||
|
||||
.panel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: var(--rs-bg-surface, #1e1e2e);
|
||||
border: 1px solid var(--rs-border, #333);
|
||||
border-radius: 0.75rem;
|
||||
box-shadow: var(--rs-shadow-md, 0 4px 16px rgba(0,0,0,0.25));
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* ── Tabs ── */
|
||||
|
||||
.tabs {
|
||||
display: flex; gap: 0; border-bottom: 1px solid var(--rs-border-subtle, #333);
|
||||
display: flex;
|
||||
gap: 0;
|
||||
border-bottom: 1px solid var(--rs-border-subtle, #333);
|
||||
background: var(--rs-bg-page, #181825);
|
||||
}
|
||||
.tab {
|
||||
flex: 1; padding: 0.4rem 0.5rem;
|
||||
border: none; border-bottom: 2px solid transparent;
|
||||
flex: 1;
|
||||
padding: 0.625rem 0.5rem;
|
||||
border: none;
|
||||
border-bottom: 2px solid transparent;
|
||||
background: transparent;
|
||||
color: var(--rs-text-secondary, #aaa);
|
||||
font-size: 0.75rem; font-weight: 500; cursor: pointer;
|
||||
font-size: 0.78rem;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.375rem;
|
||||
}
|
||||
.tab:hover { color: var(--rs-text-primary, #eee); }
|
||||
.tab.active {
|
||||
color: var(--rs-primary, #3b82f6);
|
||||
border-bottom-color: var(--rs-primary, #3b82f6);
|
||||
color: var(--rs-accent, #14b8a6);
|
||||
border-bottom-color: var(--rs-accent, #14b8a6);
|
||||
}
|
||||
.tab svg { opacity: 0.7; }
|
||||
.tab.active svg { opacity: 1; }
|
||||
|
||||
.tab-content { padding: 0.5rem 0; }
|
||||
.section { display: flex; flex-direction: column; gap: 0.5rem; }
|
||||
/* ── Tab Content ── */
|
||||
|
||||
.tab-content { padding: 1rem; }
|
||||
.section { display: flex; flex-direction: column; gap: 0.75rem; }
|
||||
|
||||
/* ── Info Card ── */
|
||||
|
||||
.info-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.625rem;
|
||||
padding: 0.625rem 0.75rem;
|
||||
background: var(--rs-card-bg, #232336);
|
||||
border: 1px solid var(--rs-card-border, #2a2a3e);
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
.info-card svg { color: var(--rs-accent, #14b8a6); flex-shrink: 0; }
|
||||
.info-card-text { display: flex; flex-direction: column; gap: 0.125rem; }
|
||||
.info-card-title { font-size: 0.8rem; font-weight: 600; color: var(--rs-text-primary, #eee); }
|
||||
.info-card-detail { font-size: 0.7rem; color: var(--rs-text-muted, #666); }
|
||||
|
||||
/* ── Section Header ── */
|
||||
|
||||
.section-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.625rem;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
.section-header svg { color: var(--rs-accent, #14b8a6); flex-shrink: 0; }
|
||||
.section-title { font-size: 0.85rem; font-weight: 600; color: var(--rs-text-primary, #eee); }
|
||||
.section-subtitle { font-size: 0.7rem; color: var(--rs-text-muted, #666); }
|
||||
|
||||
/* ── Action Buttons ── */
|
||||
|
||||
.action-btn {
|
||||
display: block; width: 100%; text-align: center;
|
||||
padding: 0.5rem; border-radius: 0.375rem;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.375rem;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
padding: 0.5rem 0.75rem;
|
||||
border-radius: 0.5rem;
|
||||
border: 1px solid var(--rs-border, #444);
|
||||
background: var(--rs-bg-surface, #2a2a2a);
|
||||
color: var(--rs-text-primary, #eee);
|
||||
font-size: 0.8rem; cursor: pointer;
|
||||
font-size: 0.8rem;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
transition: all 0.15s;
|
||||
}
|
||||
.action-btn:hover { border-color: var(--rs-primary, #3b82f6); }
|
||||
.action-btn:hover { border-color: var(--rs-accent, #14b8a6); }
|
||||
.action-btn:disabled { opacity: 0.5; cursor: not-allowed; }
|
||||
.action-btn svg { flex-shrink: 0; }
|
||||
|
||||
.action-btn.primary {
|
||||
background: var(--rs-primary, #3b82f6);
|
||||
border-color: var(--rs-primary, #3b82f6);
|
||||
color: #fff; font-weight: 600;
|
||||
background: var(--rs-accent, #14b8a6);
|
||||
border-color: var(--rs-accent, #14b8a6);
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
}
|
||||
.action-btn.primary:hover { opacity: 0.9; }
|
||||
.action-btn.small { width: auto; flex-shrink: 0; padding: 0.4rem 0.75rem; }
|
||||
.action-btn.primary:hover { background: var(--rs-accent-hover, #0d9488); }
|
||||
|
||||
.action-btn.secondary {
|
||||
background: transparent;
|
||||
border: 1px solid var(--rs-border, #444);
|
||||
}
|
||||
.action-btn.secondary:hover { border-color: var(--rs-accent, #14b8a6); color: var(--rs-accent, #14b8a6); }
|
||||
|
||||
.action-btn.accent {
|
||||
background: var(--rs-accent, #14b8a6);
|
||||
border-color: var(--rs-accent, #14b8a6);
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
}
|
||||
.action-btn.accent:hover { background: var(--rs-accent-hover, #0d9488); }
|
||||
|
||||
.send-btn { width: auto; flex-shrink: 0; padding: 0.5rem 0.875rem; }
|
||||
|
||||
/* ── Spinner ── */
|
||||
|
||||
.spinner {
|
||||
display: inline-block;
|
||||
width: 14px; height: 14px;
|
||||
border: 2px solid rgba(255,255,255,0.3);
|
||||
border-top-color: #fff;
|
||||
border-radius: 50%;
|
||||
animation: spin 0.6s linear infinite;
|
||||
}
|
||||
@keyframes spin { to { transform: rotate(360deg); } }
|
||||
|
||||
/* ── Divider ── */
|
||||
|
||||
.divider-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.625rem;
|
||||
margin: 0.25rem 0;
|
||||
}
|
||||
.divider-line { flex: 1; height: 1px; background: var(--rs-border-subtle, #333); }
|
||||
.divider-text { font-size: 0.7rem; color: var(--rs-text-muted, #666); white-space: nowrap; }
|
||||
|
||||
/* ── Email ── */
|
||||
|
||||
.email-row { display: flex; gap: 0.375rem; }
|
||||
.email-input {
|
||||
flex: 1; padding: 0.4rem 0.5rem;
|
||||
.email-input-wrap {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.375rem;
|
||||
padding: 0 0.5rem;
|
||||
border: 1px solid var(--rs-input-border, #444);
|
||||
border-radius: 0.375rem;
|
||||
border-radius: 0.5rem;
|
||||
background: var(--rs-input-bg, #1a1a2e);
|
||||
transition: border-color 0.15s;
|
||||
}
|
||||
.email-input-wrap:focus-within { border-color: var(--rs-accent, #14b8a6); }
|
||||
.email-input-wrap svg { color: var(--rs-text-muted, #666); flex-shrink: 0; }
|
||||
.email-input {
|
||||
flex: 1;
|
||||
padding: 0.5rem 0;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: var(--rs-input-text, #eee);
|
||||
font-size: 0.8rem;
|
||||
outline: none;
|
||||
}
|
||||
.email-input:focus { outline: none; border-color: var(--rs-primary, #3b82f6); }
|
||||
.email-input::placeholder { color: var(--rs-text-muted, #666); }
|
||||
|
||||
.msg { font-size: 0.75rem; padding: 0.375rem 0.5rem; border-radius: 0.25rem; }
|
||||
.msg.success { background: rgba(34, 197, 94, 0.15); color: var(--rs-success, #22c55e); }
|
||||
.msg.error { background: rgba(248, 113, 113, 0.1); color: #f87171; }
|
||||
.msg.info { background: rgba(59, 130, 246, 0.1); color: #60a5fa; }
|
||||
/* ── Messages ── */
|
||||
|
||||
.msg {
|
||||
font-size: 0.75rem;
|
||||
padding: 0.5rem 0.625rem;
|
||||
border-radius: 0.5rem;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.375rem;
|
||||
}
|
||||
.msg.success {
|
||||
background: rgba(20, 184, 166, 0.1);
|
||||
border: 1px solid rgba(20, 184, 166, 0.2);
|
||||
color: var(--rs-accent, #14b8a6);
|
||||
}
|
||||
.msg.error {
|
||||
background: rgba(248, 113, 113, 0.1);
|
||||
border: 1px solid rgba(248, 113, 113, 0.2);
|
||||
color: #f87171;
|
||||
}
|
||||
.msg.info {
|
||||
background: rgba(20, 184, 166, 0.08);
|
||||
border: 1px solid rgba(20, 184, 166, 0.15);
|
||||
color: var(--rs-accent, #14b8a6);
|
||||
}
|
||||
|
||||
.hint { font-size: 0.7rem; color: var(--rs-text-muted, #666); margin: 0; }
|
||||
.meta { font-size: 0.7rem; color: var(--rs-text-secondary, #aaa); text-align: center; }
|
||||
|
||||
/* DIY guide */
|
||||
.guide h4 { margin: 0.5rem 0 0.25rem; font-size: 0.85rem; color: var(--rs-text-primary, #eee); }
|
||||
.guide h5 { margin: 0.75rem 0 0.25rem; font-size: 0.75rem; color: var(--rs-text-secondary, #aaa); text-transform: uppercase; letter-spacing: 0.05em; }
|
||||
.guide ul, .guide ol { margin: 0; padding-left: 1.25rem; font-size: 0.75rem; color: var(--rs-text-primary, #ddd); line-height: 1.5; }
|
||||
.guide li { margin-bottom: 0.25rem; }
|
||||
.guide-stat { font-size: 0.75rem; color: var(--rs-text-secondary, #aaa); }
|
||||
/* ── DIY Guide ── */
|
||||
|
||||
.guide-card {
|
||||
background: var(--rs-card-bg, #232336);
|
||||
border: 1px solid var(--rs-card-border, #2a2a3e);
|
||||
border-radius: 0.5rem;
|
||||
padding: 0.875rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.guide-stats {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
gap: 0.5rem;
|
||||
text-align: center;
|
||||
}
|
||||
.guide-stat-col { display: flex; flex-direction: column; gap: 0.125rem; }
|
||||
.guide-stat-value { font-size: 0.85rem; font-weight: 700; color: var(--rs-accent, #14b8a6); }
|
||||
.guide-stat-label { font-size: 0.65rem; color: var(--rs-text-muted, #666); text-transform: uppercase; letter-spacing: 0.04em; }
|
||||
|
||||
.guide-info-banner {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 0.5rem;
|
||||
padding: 0.5rem 0.625rem;
|
||||
background: rgba(20, 184, 166, 0.08);
|
||||
border: 1px solid rgba(20, 184, 166, 0.15);
|
||||
border-radius: 0.375rem;
|
||||
font-size: 0.72rem;
|
||||
color: var(--rs-text-secondary, #aaa);
|
||||
line-height: 1.4;
|
||||
}
|
||||
.guide-info-banner svg { color: var(--rs-accent, #14b8a6); flex-shrink: 0; margin-top: 0.05rem; }
|
||||
|
||||
.guide-section { display: flex; flex-direction: column; gap: 0.375rem; }
|
||||
.guide-section-title {
|
||||
font-size: 0.72rem;
|
||||
font-weight: 600;
|
||||
color: var(--rs-text-secondary, #aaa);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
|
||||
.guide-list {
|
||||
margin: 0; padding-left: 1.25rem;
|
||||
font-size: 0.75rem; color: var(--rs-text-primary, #ddd); line-height: 1.5;
|
||||
}
|
||||
.guide-list li { margin-bottom: 0.2rem; }
|
||||
|
||||
.guide-numbered-steps { display: flex; flex-direction: column; gap: 0.375rem; }
|
||||
.guide-step {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 0.5rem;
|
||||
font-size: 0.75rem;
|
||||
color: var(--rs-text-primary, #ddd);
|
||||
line-height: 1.4;
|
||||
}
|
||||
.guide-step-num {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 20px; height: 20px;
|
||||
border-radius: 50%;
|
||||
background: var(--rs-accent, #14b8a6);
|
||||
color: #fff;
|
||||
font-size: 0.65rem;
|
||||
font-weight: 700;
|
||||
flex-shrink: 0;
|
||||
margin-top: 0.05rem;
|
||||
}
|
||||
|
||||
.guide-tips {
|
||||
margin: 0; padding: 0; list-style: none;
|
||||
font-size: 0.75rem; color: var(--rs-text-primary, #ddd); line-height: 1.5;
|
||||
}
|
||||
.guide-tips li { margin-bottom: 0.2rem; }
|
||||
|
||||
/* ── Pricing Tiers ── */
|
||||
|
||||
.pricing-tiers {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
.tier-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 0.125rem;
|
||||
padding: 0.625rem 0.5rem;
|
||||
border: 1px solid var(--rs-card-border, #2a2a3e);
|
||||
border-radius: 0.5rem;
|
||||
background: var(--rs-card-bg, #232336);
|
||||
text-align: center;
|
||||
position: relative;
|
||||
}
|
||||
.tier-card.popular {
|
||||
border-color: var(--rs-accent, #14b8a6);
|
||||
background: rgba(20, 184, 166, 0.06);
|
||||
}
|
||||
.tier-badge {
|
||||
position: absolute;
|
||||
top: -8px;
|
||||
font-size: 0.55rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
padding: 0.1rem 0.5rem;
|
||||
border-radius: 1rem;
|
||||
background: var(--rs-accent, #14b8a6);
|
||||
color: #fff;
|
||||
}
|
||||
.tier-qty { font-size: 0.9rem; font-weight: 700; color: var(--rs-accent, #14b8a6); }
|
||||
.tier-binding { font-size: 0.65rem; color: var(--rs-text-muted, #666); }
|
||||
.tier-price { font-size: 0.75rem; font-weight: 600; color: var(--rs-text-primary, #eee); }
|
||||
|
||||
/* ── Printer List ── */
|
||||
|
||||
/* Printer list */
|
||||
.printer-list { display: flex; flex-direction: column; gap: 0.375rem; max-height: 300px; overflow-y: auto; }
|
||||
.printer-card {
|
||||
text-align: left; padding: 0.5rem;
|
||||
border: 1px solid var(--rs-border, #444);
|
||||
border-radius: 0.375rem;
|
||||
background: var(--rs-bg-surface, #2a2a2a);
|
||||
text-align: left;
|
||||
padding: 0.625rem 0.75rem;
|
||||
border: 1px solid var(--rs-card-border, #2a2a3e);
|
||||
border-radius: 0.5rem;
|
||||
background: var(--rs-card-bg, #232336);
|
||||
color: var(--rs-text-primary, #eee);
|
||||
cursor: pointer; transition: border-color 0.15s;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s;
|
||||
}
|
||||
.printer-card:hover {
|
||||
border-color: var(--rs-accent, #14b8a6);
|
||||
background: rgba(20, 184, 166, 0.04);
|
||||
}
|
||||
.printer-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.printer-card:hover { border-color: var(--rs-primary, #3b82f6); }
|
||||
.printer-name { font-size: 0.8rem; font-weight: 600; }
|
||||
.printer-meta { font-size: 0.7rem; color: var(--rs-text-secondary, #aaa); }
|
||||
.printer-tags { display: flex; gap: 0.25rem; flex-wrap: wrap; margin-top: 0.25rem; }
|
||||
.printer-distance { font-size: 0.7rem; color: var(--rs-text-muted, #666); }
|
||||
.printer-meta {
|
||||
font-size: 0.7rem;
|
||||
color: var(--rs-text-secondary, #aaa);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.375rem;
|
||||
margin-top: 0.125rem;
|
||||
}
|
||||
.verified-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.2rem;
|
||||
font-size: 0.6rem;
|
||||
color: var(--rs-accent, #14b8a6);
|
||||
}
|
||||
.printer-tags { display: flex; gap: 0.25rem; flex-wrap: wrap; margin-top: 0.375rem; }
|
||||
.tag {
|
||||
font-size: 0.6rem; padding: 0.1rem 0.375rem;
|
||||
font-size: 0.6rem;
|
||||
padding: 0.15rem 0.5rem;
|
||||
border-radius: 1rem;
|
||||
background: rgba(34, 197, 94, 0.15);
|
||||
color: var(--rs-success, #22c55e);
|
||||
background: rgba(20, 184, 166, 0.1);
|
||||
color: var(--rs-accent, #14b8a6);
|
||||
}
|
||||
.badge {
|
||||
font-size: 0.6rem; padding: 0.1rem 0.375rem;
|
||||
border-radius: 1rem;
|
||||
background: rgba(59, 130, 246, 0.15);
|
||||
color: #60a5fa;
|
||||
}
|
||||
.printer-caps { font-size: 0.65rem; color: var(--rs-text-muted, #666); margin-top: 0.2rem; }
|
||||
.printer-caps { font-size: 0.65rem; color: var(--rs-text-muted, #666); margin-top: 0.25rem; }
|
||||
|
||||
/* ── Provider Detail ── */
|
||||
|
||||
/* Provider detail */
|
||||
.back-btn {
|
||||
background: none; border: none;
|
||||
color: var(--rs-text-secondary, #aaa);
|
||||
|
|
@ -551,10 +995,91 @@ export class FolkPubsPublishPanel extends HTMLElement {
|
|||
padding: 0; text-align: left;
|
||||
}
|
||||
.back-btn:hover { color: var(--rs-text-primary, #eee); }
|
||||
.provider-detail h4 { margin: 0.5rem 0 0.375rem; font-size: 0.9rem; }
|
||||
.provider-info { font-size: 0.75rem; color: var(--rs-text-secondary, #aaa); display: flex; flex-direction: column; gap: 0.2rem; margin-bottom: 0.5rem; }
|
||||
.provider-info a { color: var(--rs-primary, #3b82f6); }
|
||||
.provider-desc { font-size: 0.7rem; color: var(--rs-text-muted, #666); margin-top: 0.25rem; }
|
||||
|
||||
.provider-detail {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.625rem;
|
||||
}
|
||||
.provider-card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
.provider-card-header svg { color: var(--rs-accent, #14b8a6); }
|
||||
.provider-detail h4 { margin: 0; font-size: 0.9rem; }
|
||||
|
||||
.provider-info-grid {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.375rem;
|
||||
padding: 0.625rem 0.75rem;
|
||||
background: var(--rs-card-bg, #232336);
|
||||
border: 1px solid var(--rs-card-border, #2a2a3e);
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
.provider-info-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
font-size: 0.75rem;
|
||||
color: var(--rs-text-secondary, #aaa);
|
||||
}
|
||||
.provider-info-row svg { color: var(--rs-text-muted, #666); flex-shrink: 0; }
|
||||
.provider-info-row a { color: var(--rs-accent, #14b8a6); text-decoration: none; }
|
||||
.provider-info-row a:hover { text-decoration: underline; }
|
||||
|
||||
.provider-desc { font-size: 0.72rem; color: var(--rs-text-muted, #666); line-height: 1.4; }
|
||||
|
||||
/* ── Batch / Group Buy ── */
|
||||
|
||||
.batch-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
padding: 0.75rem;
|
||||
background: var(--rs-card-bg, #232336);
|
||||
border: 1px solid var(--rs-card-border, #2a2a3e);
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
.batch-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.375rem;
|
||||
font-size: 0.78rem;
|
||||
font-weight: 600;
|
||||
color: var(--rs-text-primary, #eee);
|
||||
}
|
||||
.batch-header svg { color: var(--rs-accent, #14b8a6); }
|
||||
.batch-bar-wrap { display: flex; flex-direction: column; gap: 0.25rem; }
|
||||
.batch-bar {
|
||||
height: 6px;
|
||||
border-radius: 3px;
|
||||
background: var(--rs-border-subtle, #333);
|
||||
overflow: hidden;
|
||||
}
|
||||
.batch-bar-fill {
|
||||
height: 100%;
|
||||
border-radius: 3px;
|
||||
background: linear-gradient(90deg, var(--rs-accent, #14b8a6), var(--rs-accent-hover, #0d9488));
|
||||
transition: width 0.4s ease;
|
||||
}
|
||||
.batch-bar-label { font-size: 0.68rem; color: var(--rs-text-muted, #666); }
|
||||
.batch-tiers {
|
||||
display: flex;
|
||||
gap: 0.75rem;
|
||||
font-size: 0.68rem;
|
||||
}
|
||||
.tier-unlocked { color: var(--rs-accent, #14b8a6); font-weight: 500; }
|
||||
.tier-locked { color: var(--rs-text-muted, #666); }
|
||||
|
||||
/* ── Responsive ── */
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.tab-content { padding: 0.75rem; }
|
||||
.pricing-tiers { grid-template-columns: 1fr; }
|
||||
.guide-stats { grid-template-columns: 1fr; }
|
||||
}
|
||||
</style>`;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue