openvibers/openvibe.quest Commits
Quest: quest cards with a badge medal, steps as a checklist, reward chips; site groups with counts; a shorter hero; no API paths in the copy
Changes
@@@ -48,12 +48,32 @@ button.sc-btn.sc-primary { border: 0; }p.lede { font-size: 1.05rem; color: var(--text-secondary, #96a7c2); max-width: 780px; }.site-group { margin: 1.2rem 0; }.site-group small { color: var(--text-muted, #7386a3); font-weight: 400; }--.quest-list { list-style: none; margin: 0; padding: 0; display: grid; gap: .7rem; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); }--.quest { border: 1px solid var(--border, #1f2d47); border-radius: 10px; padding: .7rem .9rem; background: var(--bg-card, #131c2e); }--.quest-title { font-weight: 650; }--.quest-why { margin: .25rem 0; color: var(--text-secondary, #96a7c2); font-size: .93rem; }--.quest-steps, .quest-reward { margin: .3rem 0; font-size: .87rem; }--.quest-steps .step { display: inline-block; margin-right: .6rem; }++.site-group { margin: 22px 0 0; }++.site-group h3 { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 10px; margin: 0 0 10px; font-size: 1.1rem; }++.site-group h3 a { color: var(--text-primary, #e6edf7); text-decoration: none; }++.site-group h3 a:hover { color: var(--accent-light, #60a5fa); }++.site-group h3 small { flex-basis: 100%; font-weight: 400; font-size: .86rem; color: var(--text-muted, #7386a3); }++.site-count { font-size: .72rem; font-weight: 700; padding: 1px 8px; border-radius: 999px; border: 1px solid var(--border-light, #2c3d5c); color: var(--text-muted, #7386a3); }++.quest-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 12px; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); }++.quest { display: grid; grid-template-columns: 56px minmax(0, 1fr); gap: 14px; padding: 16px; border-radius: 16px; border: 1px solid var(--border, #1f2d47); background: var(--bg-card, #131c2e); transition: border-color .15s, transform .15s; }++.quest:hover { border-color: color-mix(in srgb, var(--accent, #3b82f6) 55%, var(--border, #1f2d47)); transform: translateY(-1px); }++.medal { width: 56px; height: 56px; border-radius: 50%; display: grid; place-items: center; font-weight: 800; font-size: 1rem; letter-spacing: .02em; color: hsl(var(--h) 85% 82%); background: radial-gradient(circle at 35% 30%, hsl(var(--h) 55% 34%), hsl(var(--h) 50% 18%) 70%); box-shadow: inset 0 0 0 3px hsl(var(--h) 55% 42% / .55), 0 6px 16px -8px hsl(var(--h) 70% 40% / .8); }++.medal.earned { box-shadow: inset 0 0 0 3px hsl(var(--h) 80% 62%), 0 0 0 3px hsl(var(--h) 80% 62% / .25), 0 8px 20px -8px hsl(var(--h) 80% 50%); }++.quest-main { min-width: 0; }++.quest-title { font-weight: 700; font-size: 1.02rem; color: var(--text-primary, #e6edf7); text-decoration: none; }++.quest-title:hover { color: var(--accent-light, #60a5fa); text-decoration: underline; text-underline-offset: 3px; }++.quest-why { margin: 4px 0 8px; color: var(--text-secondary, #96a7c2); font-size: .92rem; line-height: 1.45; }++.quest-steps { list-style: none; margin: 0 0 10px; padding: 0; display: grid; gap: 4px; font-size: .9rem; }++.quest-steps li { position: relative; padding-left: 22px; color: var(--text-primary, #e6edf7); }++.quest-steps li::before { content: ''; position: absolute; left: 0; top: .28em; width: 13px; height: 13px; border-radius: 4px; border: 1.5px solid var(--border-light, #2c3d5c); }++.step-count { font-size: .78rem; font-weight: 700; color: var(--text-muted, #7386a3); }++.quest-reward { margin: 0; }++.reward { display: flex; flex-wrap: wrap; gap: 6px; }++.reward-badge, .reward-coins { font-size: .78rem; padding: 3px 10px; border-radius: 999px; border: 1px solid var(--border-light, #2c3d5c); color: var(--text-secondary, #96a7c2); }++.reward-badge strong { color: var(--text-primary, #e6edf7); }++.reward-coins { color: var(--warning, #f59e0b); border-color: color-mix(in srgb, var(--warning, #f59e0b) 40%, transparent); }++.reward-coins.soon { color: var(--text-muted, #7386a3); border-style: dashed; border-color: var(--border-light, #2c3d5c); }++@media (prefers-reduced-motion: reduce) { .quest { transition: none; } }.steps { margin: .4rem 0; }.done { color: var(--success, #22c55e); font-weight: 700; }.progress { display: grid; grid-template-columns: minmax(8rem, 1fr) 2fr auto; gap: .5rem; align-items: center; margin: .3rem 0; font-size: .9rem; }@@@ -52,8 +52,8 @@ function createPageRoutes(ctx) {r.get('/', (req, res) => {const hero = showcase.hero({eyebrow: `${SITE_NAME} · ${TAGLINE}`,-- title: SITE_NAME,-- accent: TAGLINE,++ title: 'Quests across OpenVibe.',++ accent: 'Badges for what you really do.',lede: `${SITE_NAME} is the network's shared quest log: one place that knows what you have really done on every OpenVibe site, and pays you for it in badges and OpenCoins. Quest runs no activities of its own — it listens to what the sites already report, and nothing else.`,actions: signedIn(req)? [{ label: 'Your quest log', href: '/me', primary: true }, { label: 'How it works', href: '/how-it-works' }]@@@ -70,7 +70,7 @@ ${raw(showcase.features({lede: 'One quest log across every OpenVibe site, built on what the sites already tell the network.',items: [{ icon: 'ov:page', title: 'One log, every site', text: 'A stream on OpenVibe.Live, a thread on OpenVibe.Community, a message in OpenVibe.Chat: the same log, grouped by the site the event came from.' },-- { icon: 'ov:account', title: 'Rewards are badges', text: 'A quest pays a text badge on your profile (GET /api/v1/profiles/:subject/badges) and, when OpenCoins are on, OpenCoins.' },++ { icon: 'ov:account', title: 'Rewards are badges', text: 'A quest pays a badge that shows on your profile across the network and, once they are switched on, OpenCoins.' },{ icon: 'ov:tools', title: 'OpenCoins, not Vibes', text: 'OpenCoins are loyalty points credited through OpenVibe.Network\'s wallet. They are never money, they are not Vibes, and Vibes are never touched here.' },{ icon: 'ov:games', title: 'Made by communities next', text: 'Quests a streamer or a community writes themselves need moderation, which this release does not have yet. The tables and the API already key everything by quest id.' },],@@@ -14,18 +14,36 @@ const { html } = require('./html');*/function reward(quest, { coinsEnabled = false } = {}) {const badge = quest.reward.badge.name;-- if (quest.reward.coins <= 0) return html`<span class="reward">Badge: <strong>${badge}</strong></span>`;-- if (!coinsEnabled) return html`<span class="reward">Badge: <strong>${badge}</strong> · <span class="muted">OpenCoins rewards start soon</span></span>`;-- return html`<span class="reward">Badge: <strong>${badge}</strong> · <strong>${quest.reward.coins}</strong> OpenCoins</span>`;++ const badgeChip = html`<span class="reward-badge">Badge: <strong>${badge}</strong></span>`;++ if (quest.reward.coins <= 0) return html`<span class="reward">${badgeChip}</span>`;++ if (!coinsEnabled) return html`<span class="reward">${badgeChip}<span class="reward-coins soon">OpenCoins rewards start soon</span></span>`;++ return html`<span class="reward">${badgeChip}<span class="reward-coins"><strong>${quest.reward.coins}</strong> OpenCoins</span></span>`;}--/** One quest in a list: what it is, why, what it pays, and the events it counts. */++/** A badge's colour, fixed by its quest id, so a badge looks the same wherever it is shown. */++function hueOf(id) {++ let h = 0;++ for (const ch of String(id)) h = (h * 33 + ch.codePointAt(0)) % 360;++ return h;++}++++/** The badge as a medal: its initials on a ring of its colour (decorative; the name is always written beside it). */++function medal(quest, { earned = false } = {}) {++ const words = String(quest.reward.badge.name || quest.title).split(/\s+/).filter(Boolean);++ const initials = (words.length > 1 ? words[0][0] + words[1][0] : (words[0] || '?').slice(0, 2)).toUpperCase();++ return html`<span class="medal${earned ? ' earned' : ''}" style="--h:${hueOf(quest.id)}" aria-hidden="true">${initials}</span>`;++}++++/** One quest in a list: the badge it pays, what it is, why, the steps as a checklist, and the reward. */function card(quest, { coinsEnabled = false } = {}) {return html`<li class="quest">++ ${medal(quest)}++ <div class="quest-main"><a class="quest-title" href="/quests/${quest.id}">${quest.title}</a><p class="quest-why">${quest.why}</p>-- <p class="quest-steps">${quest.steps.map((s) => html`<span class="step">${s.label}${s.count > 1 ? html` ×${s.count}` : ''} <code>${s.event_type}</code></span>`)}</p>++ <ul class="quest-steps">${quest.steps.map((s) => html`<li>${s.label}${s.count > 1 ? html` <span class="step-count">×${s.count}</span>` : ''}</li>`)}</ul><p class="quest-reward">${reward(quest, { coinsEnabled })}</p>++ </div></li>`;}@@@ -34,7 +52,7 @@ function list(groups = catalog.grouped(), { coinsEnabled = false } = {}) {// The heading id is the domain with its dots replaced: an id with a dot is legal but awkward to target.const anchor = (site) => `site-${String(site).replace(/[^a-z0-9-]/gi, '-')}`;return html`${groups.map((g) => html`<section class="site-group" aria-labelledby="${anchor(g.site)}">-- <h3 id="${anchor(g.site)}"><a href="https://${g.site}">${g.name}</a>${g.what ? html` <small>${g.what}</small>` : ''}</h3>++ <h3 id="${anchor(g.site)}"><a href="https://${g.site}">${String(g.name).replace(/^OpenVibe\./, '')}</a><span class="site-count">${g.quests.length} quest${g.quests.length === 1 ? '' : 's'}</span>${g.what ? html` <small>${g.what}</small>` : ''}</h3><ul class="quest-list">${g.quests.map((q) => card(q, { coinsEnabled }))}</ul></section>`)}`;}@@@ -58,4 +76,4 @@ ${dailyCap} OpenCoins per person per day and ${globalDailyCap} for everyone togenever money — and never Vibes.</p>`;}--module.exports = { reward, card, list, progress, coinsNote };++module.exports = { medal, hueOf, reward, card, list, progress, coinsNote };@@@ -22,8 +22,8 @@ const BUDGETS = {jsRawKB: 264, // 239.8jsBrotliKB: 62.5, // 56.5cssFiles: 2, // 2 (app.css + the cached /shared/showcase.css)-- cssRawKB: 16.5, // 15.0-- cssBrotliKB: 4.1, // 3.7++ cssRawKB: 21.0, // 19.1 with the quest medals and cards++ cssBrotliKB: 5.1, // 4.6externalFiles: 0, // 0};
@@ -48,12 +48,32 @@ button.sc-btn.sc-primary { border: 0; }
@@ -48,12 +48,32 @@ button.sc-btn.sc-primary { border: 0; }
p.lede { font-size: 1.05rem; color: var(--text-secondary, #96a7c2); max-width: 780px; }
p.lede { font-size: 1.05rem; color: var(--text-secondary, #96a7c2); max-width: 780px; }
.site-group { margin: 1.2rem 0; }
.site-group { margin: 1.2rem 0; }
.site-group small { color: var(--text-muted, #7386a3); font-weight: 400; }
.site-group small { color: var(--text-muted, #7386a3); font-weight: 400; }
-.quest-list { list-style: none; margin: 0; padding: 0; display: grid; gap: .7rem; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); }
-.quest { border: 1px solid var(--border, #1f2d47); border-radius: 10px; padding: .7rem .9rem; background: var(--bg-card, #131c2e); }
-.quest-title { font-weight: 650; }
-.quest-why { margin: .25rem 0; color: var(--text-secondary, #96a7c2); font-size: .93rem; }
-.quest-steps, .quest-reward { margin: .3rem 0; font-size: .87rem; }
-.quest-steps .step { display: inline-block; margin-right: .6rem; }
+.site-group { margin: 22px 0 0; }
+.site-group h3 { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 10px; margin: 0 0 10px; font-size: 1.1rem; }
+.site-group h3 a { color: var(--text-primary, #e6edf7); text-decoration: none; }
+.site-group h3 a:hover { color: var(--accent-light, #60a5fa); }
+.site-group h3 small { flex-basis: 100%; font-weight: 400; font-size: .86rem; color: var(--text-muted, #7386a3); }
+.site-count { font-size: .72rem; font-weight: 700; padding: 1px 8px; border-radius: 999px; border: 1px solid var(--border-light, #2c3d5c); color: var(--text-muted, #7386a3); }
+.quest-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 12px; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); }
+.quest { display: grid; grid-template-columns: 56px minmax(0, 1fr); gap: 14px; padding: 16px; border-radius: 16px; border: 1px solid var(--border, #1f2d47); background: var(--bg-card, #131c2e); transition: border-color .15s, transform .15s; }
+.quest:hover { border-color: color-mix(in srgb, var(--accent, #3b82f6) 55%, var(--border, #1f2d47)); transform: translateY(-1px); }
+.medal { width: 56px; height: 56px; border-radius: 50%; display: grid; place-items: center; font-weight: 800; font-size: 1rem; letter-spacing: .02em; color: hsl(var(--h) 85% 82%); background: radial-gradient(circle at 35% 30%, hsl(var(--h) 55% 34%), hsl(var(--h) 50% 18%) 70%); box-shadow: inset 0 0 0 3px hsl(var(--h) 55% 42% / .55), 0 6px 16px -8px hsl(var(--h) 70% 40% / .8); }
+.medal.earned { box-shadow: inset 0 0 0 3px hsl(var(--h) 80% 62%), 0 0 0 3px hsl(var(--h) 80% 62% / .25), 0 8px 20px -8px hsl(var(--h) 80% 50%); }
+.quest-main { min-width: 0; }
+.quest-title { font-weight: 700; font-size: 1.02rem; color: var(--text-primary, #e6edf7); text-decoration: none; }
+.quest-title:hover { color: var(--accent-light, #60a5fa); text-decoration: underline; text-underline-offset: 3px; }
+.quest-why { margin: 4px 0 8px; color: var(--text-secondary, #96a7c2); font-size: .92rem; line-height: 1.45; }
+.quest-steps { list-style: none; margin: 0 0 10px; padding: 0; display: grid; gap: 4px; font-size: .9rem; }
+.quest-steps li { position: relative; padding-left: 22px; color: var(--text-primary, #e6edf7); }
+.quest-steps li::before { content: ''; position: absolute; left: 0; top: .28em; width: 13px; height: 13px; border-radius: 4px; border: 1.5px solid var(--border-light, #2c3d5c); }
+.step-count { font-size: .78rem; font-weight: 700; color: var(--text-muted, #7386a3); }
+.quest-reward { margin: 0; }
+.reward { display: flex; flex-wrap: wrap; gap: 6px; }
+.reward-badge, .reward-coins { font-size: .78rem; padding: 3px 10px; border-radius: 999px; border: 1px solid var(--border-light, #2c3d5c); color: var(--text-secondary, #96a7c2); }
+.reward-badge strong { color: var(--text-primary, #e6edf7); }
+.reward-coins { color: var(--warning, #f59e0b); border-color: color-mix(in srgb, var(--warning, #f59e0b) 40%, transparent); }
+.reward-coins.soon { color: var(--text-muted, #7386a3); border-style: dashed; border-color: var(--border-light, #2c3d5c); }
+@media (prefers-reduced-motion: reduce) { .quest { transition: none; } }
.steps { margin: .4rem 0; }
.steps { margin: .4rem 0; }
.done { color: var(--success, #22c55e); font-weight: 700; }
.done { color: var(--success, #22c55e); font-weight: 700; }
.progress { display: grid; grid-template-columns: minmax(8rem, 1fr) 2fr auto; gap: .5rem; align-items: center; margin: .3rem 0; font-size: .9rem; }
.progress { display: grid; grid-template-columns: minmax(8rem, 1fr) 2fr auto; gap: .5rem; align-items: center; margin: .3rem 0; font-size: .9rem; }
@@ -52,8 +52,8 @@ function createPageRoutes(ctx) {
@@ -52,8 +52,8 @@ function createPageRoutes(ctx) {
r.get('/', (req, res) => {
r.get('/', (req, res) => {
const hero = showcase.hero({
const hero = showcase.hero({
eyebrow: `${SITE_NAME} · ${TAGLINE}`,
eyebrow: `${SITE_NAME} · ${TAGLINE}`,
- title: SITE_NAME,
- accent: TAGLINE,
+ title: 'Quests across OpenVibe.',
+ accent: 'Badges for what you really do.',
lede: `${SITE_NAME} is the network's shared quest log: one place that knows what you have really done on every OpenVibe site, and pays you for it in badges and OpenCoins. Quest runs no activities of its own — it listens to what the sites already report, and nothing else.`,
lede: `${SITE_NAME} is the network's shared quest log: one place that knows what you have really done on every OpenVibe site, and pays you for it in badges and OpenCoins. Quest runs no activities of its own — it listens to what the sites already report, and nothing else.`,
actions: signedIn(req)
actions: signedIn(req)
? [{ label: 'Your quest log', href: '/me', primary: true }, { label: 'How it works', href: '/how-it-works' }]
? [{ label: 'Your quest log', href: '/me', primary: true }, { label: 'How it works', href: '/how-it-works' }]
@@ -70,7 +70,7 @@ ${raw(showcase.features({
@@ -70,7 +70,7 @@ ${raw(showcase.features({
lede: 'One quest log across every OpenVibe site, built on what the sites already tell the network.',
lede: 'One quest log across every OpenVibe site, built on what the sites already tell the network.',
items: [
items: [
{ icon: 'ov:page', title: 'One log, every site', text: 'A stream on OpenVibe.Live, a thread on OpenVibe.Community, a message in OpenVibe.Chat: the same log, grouped by the site the event came from.' },
{ icon: 'ov:page', title: 'One log, every site', text: 'A stream on OpenVibe.Live, a thread on OpenVibe.Community, a message in OpenVibe.Chat: the same log, grouped by the site the event came from.' },
- { icon: 'ov:account', title: 'Rewards are badges', text: 'A quest pays a text badge on your profile (GET /api/v1/profiles/:subject/badges) and, when OpenCoins are on, OpenCoins.' },
+ { icon: 'ov:account', title: 'Rewards are badges', text: 'A quest pays a badge that shows on your profile across the network and, once they are switched on, OpenCoins.' },
{ icon: 'ov:tools', title: 'OpenCoins, not Vibes', text: 'OpenCoins are loyalty points credited through OpenVibe.Network\'s wallet. They are never money, they are not Vibes, and Vibes are never touched here.' },
{ icon: 'ov:tools', title: 'OpenCoins, not Vibes', text: 'OpenCoins are loyalty points credited through OpenVibe.Network\'s wallet. They are never money, they are not Vibes, and Vibes are never touched here.' },
{ icon: 'ov:games', title: 'Made by communities next', text: 'Quests a streamer or a community writes themselves need moderation, which this release does not have yet. The tables and the API already key everything by quest id.' },
{ icon: 'ov:games', title: 'Made by communities next', text: 'Quests a streamer or a community writes themselves need moderation, which this release does not have yet. The tables and the API already key everything by quest id.' },
],
],
@@ -14,18 +14,36 @@ const { html } = require('./html');
@@ -14,18 +14,36 @@ const { html } = require('./html');
*/
*/
function reward(quest, { coinsEnabled = false } = {}) {
function reward(quest, { coinsEnabled = false } = {}) {
const badge = quest.reward.badge.name;
const badge = quest.reward.badge.name;
- if (quest.reward.coins <= 0) return html`<span class="reward">Badge: <strong>${badge}</strong></span>`;
- if (!coinsEnabled) return html`<span class="reward">Badge: <strong>${badge}</strong> · <span class="muted">OpenCoins rewards start soon</span></span>`;
- return html`<span class="reward">Badge: <strong>${badge}</strong> · <strong>${quest.reward.coins}</strong> OpenCoins</span>`;
+ const badgeChip = html`<span class="reward-badge">Badge: <strong>${badge}</strong></span>`;
+ if (quest.reward.coins <= 0) return html`<span class="reward">${badgeChip}</span>`;
+ if (!coinsEnabled) return html`<span class="reward">${badgeChip}<span class="reward-coins soon">OpenCoins rewards start soon</span></span>`;
+ return html`<span class="reward">${badgeChip}<span class="reward-coins"><strong>${quest.reward.coins}</strong> OpenCoins</span></span>`;
}
}
-/** One quest in a list: what it is, why, what it pays, and the events it counts. */
+/** A badge's colour, fixed by its quest id, so a badge looks the same wherever it is shown. */
+function hueOf(id) {
+ let h = 0;
+ for (const ch of String(id)) h = (h * 33 + ch.codePointAt(0)) % 360;
+ return h;
+}
+
+/** The badge as a medal: its initials on a ring of its colour (decorative; the name is always written beside it). */
+function medal(quest, { earned = false } = {}) {
+ const words = String(quest.reward.badge.name || quest.title).split(/\s+/).filter(Boolean);
+ const initials = (words.length > 1 ? words[0][0] + words[1][0] : (words[0] || '?').slice(0, 2)).toUpperCase();
+ return html`<span class="medal${earned ? ' earned' : ''}" style="--h:${hueOf(quest.id)}" aria-hidden="true">${initials}</span>`;
+}
+
+/** One quest in a list: the badge it pays, what it is, why, the steps as a checklist, and the reward. */
function card(quest, { coinsEnabled = false } = {}) {
function card(quest, { coinsEnabled = false } = {}) {
return html`<li class="quest">
return html`<li class="quest">
+ ${medal(quest)}
+ <div class="quest-main">
<a class="quest-title" href="/quests/${quest.id}">${quest.title}</a>
<a class="quest-title" href="/quests/${quest.id}">${quest.title}</a>
<p class="quest-why">${quest.why}</p>
<p class="quest-why">${quest.why}</p>
- <p class="quest-steps">${quest.steps.map((s) => html`<span class="step">${s.label}${s.count > 1 ? html` ×${s.count}` : ''} <code>${s.event_type}</code></span>`)}</p>
+ <ul class="quest-steps">${quest.steps.map((s) => html`<li>${s.label}${s.count > 1 ? html` <span class="step-count">×${s.count}</span>` : ''}</li>`)}</ul>
<p class="quest-reward">${reward(quest, { coinsEnabled })}</p>
<p class="quest-reward">${reward(quest, { coinsEnabled })}</p>
+ </div>
</li>`;
</li>`;
}
}
@@ -34,7 +52,7 @@ function list(groups = catalog.grouped(), { coinsEnabled = false } = {}) {
@@ -34,7 +52,7 @@ function list(groups = catalog.grouped(), { coinsEnabled = false } = {}) {
// The heading id is the domain with its dots replaced: an id with a dot is legal but awkward to target.
// The heading id is the domain with its dots replaced: an id with a dot is legal but awkward to target.
const anchor = (site) => `site-${String(site).replace(/[^a-z0-9-]/gi, '-')}`;
const anchor = (site) => `site-${String(site).replace(/[^a-z0-9-]/gi, '-')}`;
return html`${groups.map((g) => html`<section class="site-group" aria-labelledby="${anchor(g.site)}">
return html`${groups.map((g) => html`<section class="site-group" aria-labelledby="${anchor(g.site)}">
- <h3 id="${anchor(g.site)}"><a href="https://${g.site}">${g.name}</a>${g.what ? html` <small>${g.what}</small>` : ''}</h3>
+ <h3 id="${anchor(g.site)}"><a href="https://${g.site}">${String(g.name).replace(/^OpenVibe\./, '')}</a><span class="site-count">${g.quests.length} quest${g.quests.length === 1 ? '' : 's'}</span>${g.what ? html` <small>${g.what}</small>` : ''}</h3>
<ul class="quest-list">${g.quests.map((q) => card(q, { coinsEnabled }))}</ul>
<ul class="quest-list">${g.quests.map((q) => card(q, { coinsEnabled }))}</ul>
</section>`)}`;
</section>`)}`;
}
}
@@ -58,4 +76,4 @@ ${dailyCap} OpenCoins per person per day and ${globalDailyCap} for everyone toge
@@ -58,4 +76,4 @@ ${dailyCap} OpenCoins per person per day and ${globalDailyCap} for everyone toge
never money — and never Vibes.</p>`;
never money — and never Vibes.</p>`;
}
}
-module.exports = { reward, card, list, progress, coinsNote };
+module.exports = { medal, hueOf, reward, card, list, progress, coinsNote };
@@ -22,8 +22,8 @@ const BUDGETS = {
@@ -22,8 +22,8 @@ const BUDGETS = {
jsRawKB: 264, // 239.8
jsRawKB: 264, // 239.8
jsBrotliKB: 62.5, // 56.5
jsBrotliKB: 62.5, // 56.5
cssFiles: 2, // 2 (app.css + the cached /shared/showcase.css)
cssFiles: 2, // 2 (app.css + the cached /shared/showcase.css)
- cssRawKB: 16.5, // 15.0
- cssBrotliKB: 4.1, // 3.7
+ cssRawKB: 21.0, // 19.1 with the quest medals and cards
+ cssBrotliKB: 5.1, // 4.6
externalFiles: 0, // 0
externalFiles: 0, // 0
};
};