|
|
|
@ -189,6 +189,12 @@ class FolkGroupBuyPage extends HTMLElement {
|
|
|
|
const commonsRevenue = simRevenue * (this.commonsSharePct / 100);
|
|
|
|
const commonsRevenue = simRevenue * (this.commonsSharePct / 100);
|
|
|
|
const currentCommons = currentRevenue * (this.commonsSharePct / 100);
|
|
|
|
const currentCommons = currentRevenue * (this.commonsSharePct / 100);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Overall progress toward max tier (for the fill-up visual)
|
|
|
|
|
|
|
|
const overallPct = Math.min(100, Math.round((d.totalPledged / maxTierQty) * 100));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// People SVG icons for social proof
|
|
|
|
|
|
|
|
const peopleCount = d.pledges?.length || 0;
|
|
|
|
|
|
|
|
|
|
|
|
this.shadow.innerHTML = `
|
|
|
|
this.shadow.innerHTML = `
|
|
|
|
<style>${styles}</style>
|
|
|
|
<style>${styles}</style>
|
|
|
|
<div class="page">
|
|
|
|
<div class="page">
|
|
|
|
@ -196,18 +202,60 @@ class FolkGroupBuyPage extends HTMLElement {
|
|
|
|
${d.imageUrl ? `<img class="hero-img" src="${this.esc(d.imageUrl)}" alt="${this.esc(d.title)}" />` : ''}
|
|
|
|
${d.imageUrl ? `<img class="hero-img" src="${this.esc(d.imageUrl)}" alt="${this.esc(d.title)}" />` : ''}
|
|
|
|
<div class="hero-info">
|
|
|
|
<div class="hero-info">
|
|
|
|
<h1 class="hero-title">${this.esc(d.title)}</h1>
|
|
|
|
<h1 class="hero-title">${this.esc(d.title)}</h1>
|
|
|
|
${d.productType ? `<span class="tag tag-type">${this.esc(d.productType)}</span>` : ''}
|
|
|
|
<div class="hero-tags">
|
|
|
|
${d.description ? `<p class="hero-desc">${this.esc(d.description)}</p>` : ''}
|
|
|
|
${d.productType ? `<span class="tag tag-type">${this.esc(d.productType)}</span>` : ''}
|
|
|
|
<div class="hero-meta">
|
|
|
|
|
|
|
|
<span class="status status-${d.status.toLowerCase()}">${d.status}</span>
|
|
|
|
<span class="status status-${d.status.toLowerCase()}">${d.status}</span>
|
|
|
|
<span>${daysLeft} days left</span>
|
|
|
|
</div>
|
|
|
|
<span>${d.totalPledged} pledged</span>
|
|
|
|
${d.description ? `<p class="hero-desc">${this.esc(d.description)}</p>` : ''}
|
|
|
|
|
|
|
|
<div class="hero-stats">
|
|
|
|
|
|
|
|
<div class="hero-stat">
|
|
|
|
|
|
|
|
<div class="hero-stat-value">${d.totalPledged}</div>
|
|
|
|
|
|
|
|
<div class="hero-stat-label">pledged</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="hero-stat">
|
|
|
|
|
|
|
|
<div class="hero-stat-value">${peopleCount}</div>
|
|
|
|
|
|
|
|
<div class="hero-stat-label">backers</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="hero-stat">
|
|
|
|
|
|
|
|
<div class="hero-stat-value">${daysLeft}</div>
|
|
|
|
|
|
|
|
<div class="hero-stat-label">days left</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="hero-stat">
|
|
|
|
|
|
|
|
<div class="hero-stat-value">$${(d.currentTier?.per_unit || d.tiers[0]?.per_unit || 0).toFixed(2)}</div>
|
|
|
|
|
|
|
|
<div class="hero-stat-label">per unit</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="main-grid">
|
|
|
|
<div class="main-grid">
|
|
|
|
<div class="tiers-section">
|
|
|
|
<div class="tiers-section">
|
|
|
|
|
|
|
|
<!-- Fill-up progress visual -->
|
|
|
|
|
|
|
|
<div class="fill-visual">
|
|
|
|
|
|
|
|
<div class="fill-visual__container">
|
|
|
|
|
|
|
|
<div class="fill-visual__liquid" style="height:${overallPct}%">
|
|
|
|
|
|
|
|
<div class="fill-visual__wave"></div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="fill-visual__label">
|
|
|
|
|
|
|
|
<span class="fill-visual__qty">${d.totalPledged}</span>
|
|
|
|
|
|
|
|
<span class="fill-visual__of">/ ${maxTierQty}</span>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
${d.tiers.map((t: any) => {
|
|
|
|
|
|
|
|
const tierPct = Math.round((t.min_qty / maxTierQty) * 100);
|
|
|
|
|
|
|
|
return `<div class="fill-visual__marker" style="bottom:${tierPct}%" title="${t.min_qty}+ = $${t.per_unit.toFixed(2)}/ea">
|
|
|
|
|
|
|
|
<span class="fill-visual__marker-line"></span>
|
|
|
|
|
|
|
|
<span class="fill-visual__marker-label">${t.min_qty}+</span>
|
|
|
|
|
|
|
|
</div>`;
|
|
|
|
|
|
|
|
}).join('')}
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="fill-visual__people">
|
|
|
|
|
|
|
|
${Array.from({length: Math.min(peopleCount, 8)}, (_, i) =>
|
|
|
|
|
|
|
|
`<svg class="fill-visual__person" style="animation-delay:${i * 0.1}s" viewBox="0 0 24 24" fill="currentColor"><circle cx="12" cy="8" r="4"/><path d="M20 21a8 8 0 10-16 0"/></svg>`
|
|
|
|
|
|
|
|
).join('')}
|
|
|
|
|
|
|
|
${peopleCount > 8 ? `<span class="fill-visual__more">+${peopleCount - 8}</span>` : ''}
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<h3>Volume Pricing Tiers</h3>
|
|
|
|
<h3>Volume Pricing Tiers</h3>
|
|
|
|
<div class="tier-table">
|
|
|
|
<div class="tier-table">
|
|
|
|
${d.tiers.map((t: any, i: number) => {
|
|
|
|
${d.tiers.map((t: any, i: number) => {
|
|
|
|
@ -221,19 +269,21 @@ class FolkGroupBuyPage extends HTMLElement {
|
|
|
|
return `<div class="tier-row ${active ? 'tier-current' : ''} ${reached ? 'tier-reached' : ''} ${this.simQty > 0 && simActive ? 'tier-sim-active' : ''} ${this.simQty > 0 && simReached && !reached ? 'tier-sim-reached' : ''}">
|
|
|
|
return `<div class="tier-row ${active ? 'tier-current' : ''} ${reached ? 'tier-reached' : ''} ${this.simQty > 0 && simActive ? 'tier-sim-active' : ''} ${this.simQty > 0 && simReached && !reached ? 'tier-sim-reached' : ''}">
|
|
|
|
<span class="tier-qty">${t.min_qty}+</span>
|
|
|
|
<span class="tier-qty">${t.min_qty}+</span>
|
|
|
|
<span class="tier-price">$${t.per_unit.toFixed(2)}/ea</span>
|
|
|
|
<span class="tier-price">$${t.per_unit.toFixed(2)}/ea</span>
|
|
|
|
${savings > 0 ? `<span class="tier-savings">-${savings}%</span>` : `<span class="tier-savings"></span>`}
|
|
|
|
${savings > 0 ? `<span class="tier-savings">-${savings}%</span>` : `<span class="tier-savings tier-savings--base">base</span>`}
|
|
|
|
<span class="tier-commons" title="Commons revenue at this tier">$${tierCommons.toFixed(0)} commons</span>
|
|
|
|
<span class="tier-commons" title="Commons revenue at this tier">$${tierCommons.toFixed(0)} commons</span>
|
|
|
|
${reached ? `<span class="tier-check">✓</span>` : this.simQty > 0 && simReached ? `<span class="tier-check tier-check-sim">✓</span>` : ''}
|
|
|
|
${reached ? `<span class="tier-check">✓</span>` : this.simQty > 0 && simReached ? `<span class="tier-check tier-check-sim">✓</span>` : `<span class="tier-check"></span>`}
|
|
|
|
</div>`;
|
|
|
|
</div>`;
|
|
|
|
}).join('')}
|
|
|
|
}).join('')}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
${nextTier ? `
|
|
|
|
${nextTier ? `
|
|
|
|
<div class="progress-section">
|
|
|
|
<div class="progress-section">
|
|
|
|
<div class="progress-label">${remaining} more to unlock $${nextTier.per_unit.toFixed(2)}/ea</div>
|
|
|
|
<div class="progress-label">
|
|
|
|
|
|
|
|
<span>${remaining} more to unlock <strong>$${nextTier.per_unit.toFixed(2)}/ea</strong></span>
|
|
|
|
|
|
|
|
<span class="progress-meta">${d.totalPledged} / ${nextTierQty}</span>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
<div class="progress-bar"><div class="progress-fill" style="width:${progressPct}%"></div></div>
|
|
|
|
<div class="progress-bar"><div class="progress-fill" style="width:${progressPct}%"></div></div>
|
|
|
|
<div class="progress-meta">${d.totalPledged} / ${nextTierQty}</div>
|
|
|
|
</div>` : `<div class="progress-section"><div class="progress-label text-green">🎉 Best tier unlocked!</div></div>`}
|
|
|
|
</div>` : `<div class="progress-section"><div class="progress-label text-green">Best tier unlocked!</div></div>`}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<div class="sim-section">
|
|
|
|
<div class="sim-section">
|
|
|
|
<h3>What If...?</h3>
|
|
|
|
<h3>What If...?</h3>
|
|
|
|
@ -263,7 +313,7 @@ class FolkGroupBuyPage extends HTMLElement {
|
|
|
|
<span class="sim-value">${simNextTier.min_qty - simTotal} more for $${simNextTier.per_unit.toFixed(2)}/ea</span>
|
|
|
|
<span class="sim-value">${simNextTier.min_qty - simTotal} more for $${simNextTier.per_unit.toFixed(2)}/ea</span>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="progress-bar"><div class="progress-fill sim-fill" style="width:${simProgressPct}%"></div></div>
|
|
|
|
<div class="progress-bar"><div class="progress-fill sim-fill" style="width:${simProgressPct}%"></div></div>
|
|
|
|
` : `<div class="sim-row"><span class="text-green" style="font-weight:600">Best tier unlocked!</span></div>`}
|
|
|
|
` : `<div class="sim-row"><span class="text-green" style="font-weight:600">🎉 Best tier unlocked!</span></div>`}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
` : ''}
|
|
|
|
` : ''}
|
|
|
|
|
|
|
|
|
|
|
|
@ -297,19 +347,23 @@ class FolkGroupBuyPage extends HTMLElement {
|
|
|
|
<div class="pledges-list">
|
|
|
|
<div class="pledges-list">
|
|
|
|
${d.pledges.map((p: any) => `
|
|
|
|
${d.pledges.map((p: any) => `
|
|
|
|
<div class="pledge-row">
|
|
|
|
<div class="pledge-row">
|
|
|
|
|
|
|
|
<span class="pledge-avatar">${this.esc(p.displayName.charAt(0).toUpperCase())}</span>
|
|
|
|
<span class="pledge-name">${this.esc(p.displayName)}</span>
|
|
|
|
<span class="pledge-name">${this.esc(p.displayName)}</span>
|
|
|
|
<span class="pledge-qty">${p.quantity}</span>
|
|
|
|
<span class="pledge-qty">${p.quantity} unit${p.quantity > 1 ? 's' : ''}</span>
|
|
|
|
</div>`).join('')}
|
|
|
|
</div>`).join('')}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="pledge-panel">
|
|
|
|
<div class="pledge-panel">
|
|
|
|
<h3>Join this Group Buy</h3>
|
|
|
|
<div class="pledge-panel__header">
|
|
|
|
|
|
|
|
<h3>Join this Group Buy</h3>
|
|
|
|
|
|
|
|
<div class="pledge-panel__social">${peopleCount} backer${peopleCount !== 1 ? 's' : ''} so far</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
${this.pledged ? `
|
|
|
|
${this.pledged ? `
|
|
|
|
<div class="pledge-success">
|
|
|
|
<div class="pledge-success">
|
|
|
|
<div class="pledge-success-icon">✓</div>
|
|
|
|
<div class="pledge-success-icon">✓</div>
|
|
|
|
<p>Pledge submitted! Share this link to bring more people in.</p>
|
|
|
|
<p>Pledge submitted! Share this link to bring more people in.</p>
|
|
|
|
<button class="btn btn-primary" data-action="copy-link">Copy Share Link</button>
|
|
|
|
<button class="btn btn-primary btn-lg" data-action="copy-link">🔗 Copy Share Link</button>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
` : `
|
|
|
|
` : `
|
|
|
|
<div class="pledge-form">
|
|
|
|
<div class="pledge-form">
|
|
|
|
@ -317,14 +371,19 @@ class FolkGroupBuyPage extends HTMLElement {
|
|
|
|
<input class="input" type="text" data-field="pledge-name" placeholder="Anonymous" value="${this.esc(this.pledgeName)}" />
|
|
|
|
<input class="input" type="text" data-field="pledge-name" placeholder="Anonymous" value="${this.esc(this.pledgeName)}" />
|
|
|
|
<label>Quantity</label>
|
|
|
|
<label>Quantity</label>
|
|
|
|
<div class="qty-controls">
|
|
|
|
<div class="qty-controls">
|
|
|
|
<button class="btn btn-sm" data-action="qty-dec">-</button>
|
|
|
|
<button class="btn btn-sm" data-action="qty-dec">−</button>
|
|
|
|
<input class="qty-input" type="number" data-field="pledge-qty" value="${this.pledgeQty}" min="1" />
|
|
|
|
<input class="qty-input" type="number" data-field="pledge-qty" value="${this.pledgeQty}" min="1" />
|
|
|
|
<button class="btn btn-sm" data-action="qty-inc">+</button>
|
|
|
|
<button class="btn btn-sm" data-action="qty-inc">+</button>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
${d.currentTier ? `<div class="pledge-price">$${(d.currentTier.per_unit * this.pledgeQty).toFixed(2)} at current tier</div>` : ''}
|
|
|
|
${d.currentTier ? `
|
|
|
|
<button class="btn btn-primary btn-lg" data-action="submit-pledge" ${this.pledging ? 'disabled' : ''}>
|
|
|
|
<div class="pledge-price-box">
|
|
|
|
|
|
|
|
<div class="pledge-price-amount">$${(d.currentTier.per_unit * this.pledgeQty).toFixed(2)}</div>
|
|
|
|
|
|
|
|
<div class="pledge-price-label">at current tier ($${d.currentTier.per_unit.toFixed(2)}/ea)</div>
|
|
|
|
|
|
|
|
</div>` : ''}
|
|
|
|
|
|
|
|
<button class="btn btn-pledge btn-lg" data-action="submit-pledge" ${this.pledging ? 'disabled' : ''}>
|
|
|
|
${this.pledging ? 'Pledging...' : `Pledge ${this.pledgeQty} unit${this.pledgeQty > 1 ? 's' : ''}`}
|
|
|
|
${this.pledging ? 'Pledging...' : `Pledge ${this.pledgeQty} unit${this.pledgeQty > 1 ? 's' : ''}`}
|
|
|
|
</button>
|
|
|
|
</button>
|
|
|
|
|
|
|
|
<p class="pledge-disclaimer">No payment collected now. You'll be notified when the group buy closes.</p>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
`}
|
|
|
|
`}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
@ -377,75 +436,163 @@ class FolkGroupBuyPage extends HTMLElement {
|
|
|
|
|
|
|
|
|
|
|
|
private getStyles(): string {
|
|
|
|
private getStyles(): string {
|
|
|
|
return `
|
|
|
|
return `
|
|
|
|
:host { display: block; padding: 1.5rem; max-width: 900px; margin: 0 auto; }
|
|
|
|
:host { display: block; padding: 2rem 1.5rem; width: 100%; max-width: 960px; }
|
|
|
|
* { box-sizing: border-box; }
|
|
|
|
* { box-sizing: border-box; }
|
|
|
|
|
|
|
|
|
|
|
|
.loading { text-align: center; padding: 3rem; color: var(--rs-text-secondary); }
|
|
|
|
.loading { text-align: center; padding: 3rem; color: var(--rs-text-secondary); }
|
|
|
|
.error { background: rgba(239,68,68,0.1); border: 1px solid var(--rs-error); border-radius: 8px; padding: 1rem; color: #fca5a5; text-align: center; }
|
|
|
|
.error { background: rgba(239,68,68,0.1); border: 1px solid var(--rs-error); border-radius: 8px; padding: 1.5rem; color: #fca5a5; text-align: center; }
|
|
|
|
|
|
|
|
|
|
|
|
.hero { display: flex; gap: 1.5rem; margin-bottom: 2rem; }
|
|
|
|
.hero { display: flex; gap: 2rem; margin-bottom: 2.5rem; padding: 1.5rem; background: var(--rs-bg-surface); border: 1px solid var(--rs-border); border-radius: 16px; }
|
|
|
|
.hero-img { width: 200px; height: 200px; border-radius: 12px; object-fit: cover; flex-shrink: 0; }
|
|
|
|
.hero-img { width: 220px; height: 220px; border-radius: 12px; object-fit: cover; flex-shrink: 0; }
|
|
|
|
.hero-title { color: var(--rs-text-primary); font-size: 1.75rem; font-weight: 700; margin: 0 0 0.5rem; }
|
|
|
|
.hero-title { color: var(--rs-text-primary); font-size: 1.75rem; font-weight: 700; margin: 0 0 0.5rem; line-height: 1.2; }
|
|
|
|
.hero-desc { color: var(--rs-text-secondary); font-size: 0.9375rem; line-height: 1.5; margin: 0.5rem 0; }
|
|
|
|
.hero-tags { display: flex; gap: 0.5rem; align-items: center; margin-bottom: 0.5rem; }
|
|
|
|
.hero-meta { display: flex; gap: 1rem; align-items: center; color: var(--rs-text-muted); font-size: 0.8125rem; margin-top: 0.75rem; }
|
|
|
|
.hero-desc { color: var(--rs-text-secondary); font-size: 0.9375rem; line-height: 1.6; margin: 0.5rem 0 1rem; }
|
|
|
|
|
|
|
|
.hero-stats { display: flex; gap: 1.5rem; padding-top: 0.75rem; border-top: 1px solid var(--rs-border-subtle); }
|
|
|
|
|
|
|
|
.hero-stat { text-align: center; }
|
|
|
|
|
|
|
|
.hero-stat-value { color: var(--rs-text-primary); font-size: 1.25rem; font-weight: 700; }
|
|
|
|
|
|
|
|
.hero-stat-label { color: var(--rs-text-muted); font-size: 0.6875rem; text-transform: uppercase; letter-spacing: 0.05em; }
|
|
|
|
|
|
|
|
|
|
|
|
.tag { display: inline-block; padding: 0.125rem 0.5rem; border-radius: 4px; font-size: 0.6875rem; }
|
|
|
|
.tag { display: inline-block; padding: 0.1875rem 0.625rem; border-radius: 6px; font-size: 0.6875rem; font-weight: 500; }
|
|
|
|
.tag-type { background: rgba(99,102,241,0.1); color: var(--rs-primary-hover); }
|
|
|
|
.tag-type { background: rgba(99,102,241,0.1); color: var(--rs-primary-hover); }
|
|
|
|
|
|
|
|
|
|
|
|
.status { padding: 0.125rem 0.5rem; border-radius: 999px; font-size: 0.6875rem; font-weight: 500; }
|
|
|
|
.status { padding: 0.1875rem 0.625rem; border-radius: 999px; font-size: 0.6875rem; font-weight: 600; }
|
|
|
|
.status-open { background: rgba(34,197,94,0.15); color: #4ade80; }
|
|
|
|
.status-open { background: rgba(34,197,94,0.15); color: #4ade80; }
|
|
|
|
.status-locked { background: rgba(251,191,36,0.15); color: #fbbf24; }
|
|
|
|
.status-locked { background: rgba(251,191,36,0.15); color: #fbbf24; }
|
|
|
|
.status-ordered { background: rgba(99,102,241,0.15); color: #a5b4fc; }
|
|
|
|
.status-ordered { background: rgba(99,102,241,0.15); color: #a5b4fc; }
|
|
|
|
.status-cancelled { background: rgba(239,68,68,0.15); color: #f87171; }
|
|
|
|
.status-cancelled { background: rgba(239,68,68,0.15); color: #f87171; }
|
|
|
|
|
|
|
|
|
|
|
|
.main-grid { display: grid; grid-template-columns: 1fr 320px; gap: 2rem; }
|
|
|
|
.main-grid { display: grid; grid-template-columns: 1fr 340px; gap: 2rem; align-items: start; }
|
|
|
|
|
|
|
|
|
|
|
|
h3 { color: var(--rs-text-primary); font-size: 1rem; font-weight: 600; margin: 0 0 0.75rem; }
|
|
|
|
h3 { color: var(--rs-text-primary); font-size: 1.0625rem; font-weight: 600; margin: 0 0 0.75rem; }
|
|
|
|
|
|
|
|
|
|
|
|
.tier-table { background: var(--rs-bg-surface); border: 1px solid var(--rs-border); border-radius: 10px; overflow: hidden; margin-bottom: 1rem; }
|
|
|
|
/* Fill-up progress visual */
|
|
|
|
.tier-row { display: flex; align-items: center; padding: 0.625rem 1rem; border-bottom: 1px solid var(--rs-border-subtle); gap: 1rem; }
|
|
|
|
.fill-visual { display: flex; gap: 1.5rem; align-items: flex-end; margin-bottom: 2rem; }
|
|
|
|
|
|
|
|
.fill-visual__container {
|
|
|
|
|
|
|
|
position: relative; width: 80px; height: 180px;
|
|
|
|
|
|
|
|
border: 2px solid var(--rs-border); border-radius: 12px;
|
|
|
|
|
|
|
|
overflow: hidden; background: var(--rs-bg-surface);
|
|
|
|
|
|
|
|
flex-shrink: 0;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
.fill-visual__liquid {
|
|
|
|
|
|
|
|
position: absolute; bottom: 0; left: 0; right: 0;
|
|
|
|
|
|
|
|
background: linear-gradient(180deg, #f59e0b 0%, #22c55e 100%);
|
|
|
|
|
|
|
|
transition: height 0.6s cubic-bezier(0.34, 1.56, 0.64, 1);
|
|
|
|
|
|
|
|
border-radius: 0 0 10px 10px;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
.fill-visual__wave {
|
|
|
|
|
|
|
|
position: absolute; top: -4px; left: -10%; right: -10%; height: 10px;
|
|
|
|
|
|
|
|
background: radial-gradient(ellipse at 50% 100%, transparent 60%, currentColor 61%);
|
|
|
|
|
|
|
|
opacity: 0.2;
|
|
|
|
|
|
|
|
animation: wave 3s ease-in-out infinite;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
@keyframes wave { 0%,100% { transform: translateX(-5%); } 50% { transform: translateX(5%); } }
|
|
|
|
|
|
|
|
.fill-visual__label {
|
|
|
|
|
|
|
|
position: absolute; inset: 0; display: flex; flex-direction: column;
|
|
|
|
|
|
|
|
align-items: center; justify-content: center; z-index: 1;
|
|
|
|
|
|
|
|
text-shadow: 0 1px 3px rgba(0,0,0,0.3);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
.fill-visual__qty { color: #fff; font-size: 1.5rem; font-weight: 800; }
|
|
|
|
|
|
|
|
.fill-visual__of { color: rgba(255,255,255,0.7); font-size: 0.6875rem; }
|
|
|
|
|
|
|
|
.fill-visual__marker {
|
|
|
|
|
|
|
|
position: absolute; left: 0; right: 0; display: flex; align-items: center; z-index: 2;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
.fill-visual__marker-line {
|
|
|
|
|
|
|
|
flex: 1; height: 1px; background: rgba(255,255,255,0.3);
|
|
|
|
|
|
|
|
border-top: 1px dashed var(--rs-border);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
.fill-visual__marker-label {
|
|
|
|
|
|
|
|
position: absolute; right: -3rem; font-size: 0.625rem; color: var(--rs-text-muted);
|
|
|
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
.fill-visual__people { display: flex; flex-wrap: wrap; gap: 0.25rem; align-items: flex-end; }
|
|
|
|
|
|
|
|
.fill-visual__person {
|
|
|
|
|
|
|
|
width: 24px; height: 24px; color: var(--rs-text-muted); opacity: 0.6;
|
|
|
|
|
|
|
|
animation: personPop 0.3s ease-out both;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
.fill-visual__more {
|
|
|
|
|
|
|
|
font-size: 0.6875rem; color: var(--rs-text-muted); font-weight: 600;
|
|
|
|
|
|
|
|
padding: 0.125rem 0.375rem; background: var(--rs-bg-surface-raised);
|
|
|
|
|
|
|
|
border-radius: 999px;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
@keyframes personPop {
|
|
|
|
|
|
|
|
from { transform: scale(0); opacity: 0; }
|
|
|
|
|
|
|
|
to { transform: scale(1); opacity: 0.6; }
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.tier-table { background: var(--rs-bg-surface); border: 1px solid var(--rs-border); border-radius: 12px; overflow: hidden; margin-bottom: 1.25rem; }
|
|
|
|
|
|
|
|
.tier-row { display: flex; align-items: center; padding: 0.75rem 1rem; border-bottom: 1px solid var(--rs-border-subtle); gap: 1rem; transition: background 0.15s; }
|
|
|
|
.tier-row:last-child { border-bottom: none; }
|
|
|
|
.tier-row:last-child { border-bottom: none; }
|
|
|
|
.tier-current { background: rgba(99,102,241,0.1); border-left: 3px solid var(--rs-primary-hover); }
|
|
|
|
.tier-current { background: rgba(99,102,241,0.1); border-left: 3px solid var(--rs-primary-hover); }
|
|
|
|
.tier-reached { }
|
|
|
|
.tier-reached { }
|
|
|
|
.tier-qty { color: var(--rs-text-primary); font-weight: 600; min-width: 3rem; }
|
|
|
|
.tier-qty { color: var(--rs-text-primary); font-weight: 700; min-width: 3rem; font-size: 0.9375rem; }
|
|
|
|
.tier-price { color: var(--rs-text-primary); flex: 1; }
|
|
|
|
.tier-price { color: var(--rs-text-primary); flex: 1; font-size: 0.9375rem; }
|
|
|
|
.tier-savings { color: #4ade80; font-size: 0.8125rem; font-weight: 500; min-width: 3rem; }
|
|
|
|
.tier-savings { color: #4ade80; font-size: 0.8125rem; font-weight: 600; min-width: 3.5rem; }
|
|
|
|
.tier-check { color: #4ade80; font-size: 0.875rem; }
|
|
|
|
.tier-savings--base { color: var(--rs-text-muted); font-weight: 400; }
|
|
|
|
|
|
|
|
.tier-check { color: #4ade80; font-size: 0.9375rem; min-width: 1.25rem; text-align: center; }
|
|
|
|
|
|
|
|
|
|
|
|
.progress-section { margin-bottom: 1.5rem; }
|
|
|
|
.progress-section { margin-bottom: 1.5rem; }
|
|
|
|
.progress-label { color: var(--rs-text-primary); font-size: 0.875rem; margin-bottom: 0.5rem; }
|
|
|
|
.progress-label { color: var(--rs-text-primary); font-size: 0.875rem; margin-bottom: 0.5rem; display: flex; justify-content: space-between; align-items: center; }
|
|
|
|
.progress-bar { background: var(--rs-bg-surface-raised); border-radius: 999px; height: 10px; overflow: hidden; }
|
|
|
|
.progress-meta { color: var(--rs-text-muted); font-size: 0.75rem; }
|
|
|
|
.progress-fill { background: linear-gradient(90deg, var(--rs-primary), #8b5cf6); height: 100%; border-radius: 999px; transition: width 0.3s; }
|
|
|
|
.progress-bar { background: var(--rs-bg-surface-raised); border-radius: 999px; height: 12px; overflow: hidden; }
|
|
|
|
.progress-meta { color: var(--rs-text-muted); font-size: 0.75rem; margin-top: 0.25rem; text-align: right; }
|
|
|
|
.progress-fill { background: linear-gradient(90deg, #f59e0b, #22c55e); height: 100%; border-radius: 999px; transition: width 0.5s cubic-bezier(0.34, 1.56, 0.64, 1); }
|
|
|
|
.text-green { color: #4ade80; }
|
|
|
|
.text-green { color: #4ade80; }
|
|
|
|
|
|
|
|
|
|
|
|
.pledges-list { margin-bottom: 1rem; }
|
|
|
|
.pledges-list { margin-bottom: 1.5rem; }
|
|
|
|
.pledge-row { display: flex; justify-content: space-between; padding: 0.375rem 0; border-bottom: 1px solid var(--rs-border-subtle); }
|
|
|
|
.pledge-row { display: flex; align-items: center; gap: 0.75rem; padding: 0.5rem 0; border-bottom: 1px solid var(--rs-border-subtle); }
|
|
|
|
.pledge-name { color: var(--rs-text-primary); font-size: 0.875rem; }
|
|
|
|
.pledge-row:last-child { border-bottom: none; }
|
|
|
|
.pledge-qty { color: var(--rs-text-secondary); font-size: 0.875rem; font-weight: 600; }
|
|
|
|
.pledge-avatar {
|
|
|
|
|
|
|
|
width: 28px; height: 28px; border-radius: 999px; flex-shrink: 0;
|
|
|
|
|
|
|
|
background: linear-gradient(135deg, #6366f1, #a855f7);
|
|
|
|
|
|
|
|
color: #fff; font-size: 0.75rem; font-weight: 700;
|
|
|
|
|
|
|
|
display: flex; align-items: center; justify-content: center;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
.pledge-name { color: var(--rs-text-primary); font-size: 0.875rem; flex: 1; }
|
|
|
|
|
|
|
|
.pledge-qty { color: var(--rs-text-secondary); font-size: 0.8125rem; font-weight: 600; white-space: nowrap; }
|
|
|
|
|
|
|
|
|
|
|
|
.pledge-panel { background: var(--rs-bg-surface); border: 1px solid var(--rs-border); border-radius: 12px; padding: 1.25rem; height: fit-content; position: sticky; top: 1.5rem; }
|
|
|
|
.pledge-panel {
|
|
|
|
.pledge-form { display: flex; flex-direction: column; gap: 0.75rem; }
|
|
|
|
background: var(--rs-bg-surface); border: 1px solid var(--rs-border);
|
|
|
|
|
|
|
|
border-radius: 16px; padding: 1.5rem; height: fit-content;
|
|
|
|
|
|
|
|
position: sticky; top: 1.5rem;
|
|
|
|
|
|
|
|
box-shadow: 0 4px 24px rgba(0,0,0,0.1);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
.pledge-panel__header { margin-bottom: 1rem; }
|
|
|
|
|
|
|
|
.pledge-panel__header h3 { margin-bottom: 0.25rem; }
|
|
|
|
|
|
|
|
.pledge-panel__social { color: var(--rs-text-muted); font-size: 0.75rem; }
|
|
|
|
|
|
|
|
.pledge-form { display: flex; flex-direction: column; gap: 0.875rem; }
|
|
|
|
.pledge-form label { color: var(--rs-text-secondary); font-size: 0.8125rem; font-weight: 500; }
|
|
|
|
.pledge-form label { color: var(--rs-text-secondary); font-size: 0.8125rem; font-weight: 500; }
|
|
|
|
.input { padding: 0.5rem 0.75rem; border-radius: 8px; border: 1px solid var(--rs-input-border); background: var(--rs-input-bg); color: var(--rs-input-text); font-size: 0.875rem; }
|
|
|
|
.input { padding: 0.5rem 0.75rem; border-radius: 8px; border: 1px solid var(--rs-input-border); background: var(--rs-input-bg); color: var(--rs-input-text); font-size: 0.875rem; }
|
|
|
|
.input:focus { outline: none; border-color: var(--rs-primary); }
|
|
|
|
.input:focus { outline: none; border-color: var(--rs-primary); box-shadow: 0 0 0 2px rgba(99,102,241,0.15); }
|
|
|
|
|
|
|
|
|
|
|
|
.qty-controls { display: flex; align-items: center; gap: 0.5rem; }
|
|
|
|
.qty-controls { display: flex; align-items: center; gap: 0.5rem; }
|
|
|
|
.qty-input { width: 60px; text-align: center; padding: 0.375rem; border-radius: 8px; border: 1px solid var(--rs-input-border); background: var(--rs-input-bg); color: var(--rs-input-text); font-size: 0.875rem; }
|
|
|
|
.qty-input { width: 60px; text-align: center; padding: 0.375rem; border-radius: 8px; border: 1px solid var(--rs-input-border); background: var(--rs-input-bg); color: var(--rs-input-text); font-size: 0.875rem; }
|
|
|
|
|
|
|
|
|
|
|
|
.pledge-price { color: var(--rs-text-secondary); font-size: 0.875rem; }
|
|
|
|
.pledge-price-box {
|
|
|
|
|
|
|
|
background: rgba(99,102,241,0.05); border: 1px solid rgba(99,102,241,0.15);
|
|
|
|
|
|
|
|
border-radius: 10px; padding: 0.75rem; text-align: center;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
.pledge-price-amount { color: var(--rs-text-primary); font-size: 1.375rem; font-weight: 700; }
|
|
|
|
|
|
|
|
.pledge-price-label { color: var(--rs-text-muted); font-size: 0.75rem; margin-top: 0.125rem; }
|
|
|
|
|
|
|
|
|
|
|
|
.btn { padding: 0.5rem 1rem; border-radius: 8px; border: 1px solid var(--rs-border); background: var(--rs-bg-surface); color: var(--rs-text-primary); cursor: pointer; font-size: 0.875rem; }
|
|
|
|
.btn { padding: 0.5rem 1rem; border-radius: 8px; border: 1px solid var(--rs-border); background: var(--rs-bg-surface); color: var(--rs-text-primary); cursor: pointer; font-size: 0.875rem; transition: all 0.15s; }
|
|
|
|
.btn:hover { border-color: var(--rs-border-strong); }
|
|
|
|
.btn:hover { border-color: var(--rs-border-strong); }
|
|
|
|
.btn-primary { background: var(--rs-primary-hover); border-color: var(--rs-primary); color: #fff; }
|
|
|
|
.btn-primary { background: var(--rs-primary-hover); border-color: var(--rs-primary); color: #fff; }
|
|
|
|
.btn-primary:hover { background: #4338ca; }
|
|
|
|
.btn-primary:hover { background: #4338ca; }
|
|
|
|
.btn-primary:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
|
|
.btn-primary:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
|
|
|
|
|
|
.btn-pledge {
|
|
|
|
|
|
|
|
background: linear-gradient(135deg, #22c55e, #16a34a); border: none; color: #fff;
|
|
|
|
|
|
|
|
font-weight: 700; font-size: 1.0625rem; padding: 0.875rem; border-radius: 12px;
|
|
|
|
|
|
|
|
box-shadow: 0 2px 8px rgba(34,197,94,0.3);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
.btn-pledge:hover { background: linear-gradient(135deg, #16a34a, #15803d); box-shadow: 0 4px 12px rgba(34,197,94,0.4); }
|
|
|
|
|
|
|
|
.btn-pledge:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
|
|
.btn-sm { padding: 0.375rem 0.75rem; font-size: 0.8125rem; }
|
|
|
|
.btn-sm { padding: 0.375rem 0.75rem; font-size: 0.8125rem; }
|
|
|
|
.btn-lg { padding: 0.75rem; font-size: 1rem; width: 100%; }
|
|
|
|
.btn-lg { padding: 0.875rem; font-size: 1rem; width: 100%; }
|
|
|
|
|
|
|
|
|
|
|
|
.pledge-success { text-align: center; }
|
|
|
|
.pledge-disclaimer { color: var(--rs-text-muted); font-size: 0.6875rem; text-align: center; margin: 0; line-height: 1.4; }
|
|
|
|
.pledge-success-icon { font-size: 2rem; color: #4ade80; margin-bottom: 0.5rem; }
|
|
|
|
|
|
|
|
.pledge-success p { color: var(--rs-text-secondary); font-size: 0.875rem; margin: 0.5rem 0 1rem; }
|
|
|
|
.pledge-success { text-align: center; padding: 0.5rem 0; }
|
|
|
|
|
|
|
|
.pledge-success-icon { font-size: 2.5rem; color: #4ade80; margin-bottom: 0.75rem; }
|
|
|
|
|
|
|
|
.pledge-success p { color: var(--rs-text-secondary); font-size: 0.875rem; margin: 0.5rem 0 1.25rem; line-height: 1.5; }
|
|
|
|
|
|
|
|
|
|
|
|
/* Tier commons column */
|
|
|
|
/* Tier commons column */
|
|
|
|
.tier-commons { color: var(--rs-text-muted); font-size: 0.6875rem; min-width: 5rem; text-align: right; }
|
|
|
|
.tier-commons { color: var(--rs-text-muted); font-size: 0.6875rem; min-width: 5rem; text-align: right; }
|
|
|
|
@ -456,20 +603,20 @@ class FolkGroupBuyPage extends HTMLElement {
|
|
|
|
.tier-check-sim { color: #a855f7; opacity: 0.7; }
|
|
|
|
.tier-check-sim { color: #a855f7; opacity: 0.7; }
|
|
|
|
|
|
|
|
|
|
|
|
/* "What If" simulator */
|
|
|
|
/* "What If" simulator */
|
|
|
|
.sim-section { background: var(--rs-bg-surface); border: 1px solid var(--rs-border); border-radius: 12px; padding: 1.25rem; margin: 1.5rem 0; }
|
|
|
|
.sim-section { background: var(--rs-bg-surface); border: 1px solid var(--rs-border); border-radius: 16px; padding: 1.5rem; margin: 1.5rem 0; }
|
|
|
|
.sim-desc { color: var(--rs-text-secondary); font-size: 0.8125rem; margin: 0 0 1rem; line-height: 1.4; }
|
|
|
|
.sim-desc { color: var(--rs-text-secondary); font-size: 0.8125rem; margin: 0 0 1rem; line-height: 1.4; }
|
|
|
|
.sim-controls { display: flex; align-items: center; gap: 0.75rem; margin-bottom: 0.75rem; }
|
|
|
|
.sim-controls { display: flex; align-items: center; gap: 0.75rem; margin-bottom: 0.75rem; }
|
|
|
|
.sim-label { color: var(--rs-text-secondary); font-size: 0.8125rem; font-weight: 500; white-space: nowrap; }
|
|
|
|
.sim-label { color: var(--rs-text-secondary); font-size: 0.8125rem; font-weight: 500; white-space: nowrap; }
|
|
|
|
.sim-slider { flex: 1; accent-color: #a855f7; height: 6px; cursor: pointer; }
|
|
|
|
.sim-slider { flex: 1; accent-color: #a855f7; height: 6px; cursor: pointer; }
|
|
|
|
.sim-qty-input { width: 60px; }
|
|
|
|
.sim-qty-input { width: 60px; }
|
|
|
|
.sim-results { background: rgba(168,85,247,0.05); border: 1px solid rgba(168,85,247,0.2); border-radius: 8px; padding: 0.75rem; margin-bottom: 1rem; }
|
|
|
|
.sim-results { background: rgba(168,85,247,0.05); border: 1px solid rgba(168,85,247,0.2); border-radius: 10px; padding: 1rem; margin-bottom: 1rem; }
|
|
|
|
.sim-row { display: flex; justify-content: space-between; padding: 0.25rem 0; color: var(--rs-text-primary); font-size: 0.875rem; }
|
|
|
|
.sim-row { display: flex; justify-content: space-between; padding: 0.3125rem 0; color: var(--rs-text-primary); font-size: 0.875rem; }
|
|
|
|
.sim-value { text-align: right; }
|
|
|
|
.sim-value { text-align: right; }
|
|
|
|
.sim-better { color: #4ade80; font-weight: 600; font-size: 0.8125rem; }
|
|
|
|
.sim-better { color: #4ade80; font-weight: 600; font-size: 0.8125rem; }
|
|
|
|
.sim-fill { background: linear-gradient(90deg, #a855f7, #d946ef); }
|
|
|
|
.sim-fill { background: linear-gradient(90deg, #a855f7, #d946ef); }
|
|
|
|
|
|
|
|
|
|
|
|
/* Commons revenue */
|
|
|
|
/* Commons revenue */
|
|
|
|
.commons-section { border-top: 1px solid var(--rs-border-subtle); padding-top: 1rem; margin-top: 0.5rem; }
|
|
|
|
.commons-section { border-top: 1px solid var(--rs-border-subtle); padding-top: 1rem; margin-top: 0.75rem; }
|
|
|
|
.commons-section h4 { color: var(--rs-text-primary); font-size: 0.9375rem; font-weight: 600; margin: 0 0 0.75rem; }
|
|
|
|
.commons-section h4 { color: var(--rs-text-primary); font-size: 0.9375rem; font-weight: 600; margin: 0 0 0.75rem; }
|
|
|
|
.commons-controls { display: flex; align-items: center; gap: 0.75rem; margin-bottom: 0.75rem; }
|
|
|
|
.commons-controls { display: flex; align-items: center; gap: 0.75rem; margin-bottom: 0.75rem; }
|
|
|
|
.commons-slider { flex: 1; accent-color: #4ade80; height: 6px; cursor: pointer; }
|
|
|
|
.commons-slider { flex: 1; accent-color: #4ade80; height: 6px; cursor: pointer; }
|
|
|
|
@ -482,11 +629,15 @@ class FolkGroupBuyPage extends HTMLElement {
|
|
|
|
.commons-stat-arrow { color: var(--rs-text-muted); font-size: 1.25rem; }
|
|
|
|
.commons-stat-arrow { color: var(--rs-text-muted); font-size: 1.25rem; }
|
|
|
|
|
|
|
|
|
|
|
|
@media (max-width: 768px) {
|
|
|
|
@media (max-width: 768px) {
|
|
|
|
.hero { flex-direction: column; }
|
|
|
|
:host { padding: 1.25rem 1rem; }
|
|
|
|
|
|
|
|
.hero { flex-direction: column; padding: 1.25rem; }
|
|
|
|
.hero-img { width: 100%; height: auto; aspect-ratio: 1; }
|
|
|
|
.hero-img { width: 100%; height: auto; aspect-ratio: 1; }
|
|
|
|
|
|
|
|
.hero-stats { flex-wrap: wrap; gap: 1rem; }
|
|
|
|
.main-grid { grid-template-columns: 1fr; }
|
|
|
|
.main-grid { grid-template-columns: 1fr; }
|
|
|
|
.pledge-panel { position: static; }
|
|
|
|
.pledge-panel { position: static; }
|
|
|
|
.commons-stats { flex-wrap: wrap; gap: 0.5rem; }
|
|
|
|
.commons-stats { flex-wrap: wrap; gap: 0.5rem; }
|
|
|
|
|
|
|
|
.fill-visual { flex-direction: column; align-items: center; }
|
|
|
|
|
|
|
|
.fill-visual__marker-label { display: none; }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
`;
|
|
|
|
`;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|