@submitium/badges 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -129,6 +129,7 @@ The format (`ribbon`: one gliding line in the footer bar; `window`: a small glid
129
129
  - **Checked.** The wall must match a strict contract (`parseWall`): https links that point to the directory's own domain, https images, a closed list of `rel` values, plain text with a length limit, and a wall that hasn't expired (a week at most). Anything else and the whole wall is refused, not repaired.
130
130
  - **Plain elements only.** The wall becomes links, images, a few spans and one scoped `<style>`, all written by this package from numbers (no CSS comes from the API). React, Vue, Svelte and Astro write each element themselves and escape every value.
131
131
  - **In your HTML.** The badges are in the HTML your server sends, which is what directory checkers read. No client JavaScript: motion is pure CSS. It pauses on hover and keyboard focus, and stands still under `prefers-reduced-motion`.
132
+ - **Visible from day one.** Before your first order the wall has no badge yet: it shows one gliding line, "Directory badges will appear here as this product gets listed.", where the badges will be. Badges replace it as directories are ordered.
132
133
  - **Never breaks your page.** If the API is slow, down or answers anything unexpected, the last good wall is kept, or nothing renders.
133
134
  - **Fair to directories.** Each badge links to its directory with the directory's official `rel`, the image loads from the directory's own URL, and no badge is hidden or duplicated. Badges a directory wants in view at all times never move.
134
135
  - **Attribution.** One discreet "Badge wall by Submitium" link, dofollow by default; you can make it nofollow in the dashboard.
package/index.d.ts CHANGED
@@ -34,8 +34,14 @@ type WallNode = {
34
34
  };
35
35
  export declare const ATTRIBUTION_URL = "https://submitium.com/?ref=badge-wall";
36
36
  /**
37
- * The wall as a tree. An empty wall (nothing ordered yet) is its root alone: invisible, and still
38
- * found by our check, which looks for the project's key on it.
37
+ * Before the first order the wall has no badge yet: one line says where they will be, gliding
38
+ * where the badges will glide, so the site owner sees the place it takes (founder's review,
39
+ * 2026-10-21). Visitors read it too: plain, true for anyone.
40
+ */
41
+ export declare const EMPTY_TEXT = "Directory badges will appear here as this product gets listed.";
42
+ /**
43
+ * The wall as a tree. An empty wall (nothing ordered yet) holds one line where the badges will be
44
+ * (EMPTY_TEXT); our check finds it all the same, by the project's key on its root.
39
45
  */
40
46
  export declare function layoutWall(wall: Wall): WallNode;
41
47
  //#endregion
package/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { a as verifySignedWall, c as REL_TOKENS, d as WALL_VERSION, f as parseWall, i as isSignedWall, l as THEMES, n as fetchWall, o as ATTRIBUTIONS, r as SUBMITIUM_PUBLIC_KEYS, s as LIMITS, t as DEFAULT_API_URL, u as VARIANTS } from "./client-x4bD7iXW.js";
2
- import { a as attrsOf, i as TAGS, n as layoutWall, o as cssOf, r as ATTRS, s as styleText, t as ATTRIBUTION_URL } from "./layout-DvNYYz0J.js";
2
+ import { a as TAGS, c as styleText, i as ATTRS, n as EMPTY_TEXT, o as attrsOf, r as layoutWall, s as cssOf, t as ATTRIBUTION_URL } from "./layout-DOI7sqk6.js";
3
3
  //#region src/html.ts
4
4
  const VOID = /* @__PURE__ */ new Set([
5
5
  "img",
@@ -45,4 +45,4 @@ function write(node, depth) {
45
45
  return `<${node.tag}${attrs}>${inner}${lines ? end : ""}</${node.tag}>`;
46
46
  }
47
47
  //#endregion
48
- export { ATTRIBUTIONS, ATTRIBUTION_URL, ATTRS, DEFAULT_API_URL, LIMITS, REL_TOKENS, SUBMITIUM_PUBLIC_KEYS, TAGS, THEMES, VARIANTS, WALL_VERSION, attrsOf, cssOf, fetchWall, isSignedWall, layoutWall, parseWall, styleText, verifySignedWall, wallToHtml };
48
+ export { ATTRIBUTIONS, ATTRIBUTION_URL, ATTRS, DEFAULT_API_URL, EMPTY_TEXT, LIMITS, REL_TOKENS, SUBMITIUM_PUBLIC_KEYS, TAGS, THEMES, VARIANTS, WALL_VERSION, attrsOf, cssOf, fetchWall, isSignedWall, layoutWall, parseWall, styleText, verifySignedWall, wallToHtml };
@@ -178,8 +178,15 @@ function windowFormat({ movers, badge }) {
178
178
  moving: zone("sbm-w", movers.map((m, i) => item(loop.delays[i], badge(m))))
179
179
  };
180
180
  }
181
+ /**
182
+ * Before the first order the wall has no badge yet: one line says where they will be, gliding
183
+ * where the badges will glide, so the site owner sees the place it takes (founder's review,
184
+ * 2026-10-21). Visitors read it too: plain, true for anyone.
185
+ */
186
+ const EMPTY_TEXT = "Directory badges will appear here as this product gets listed.";
187
+ const CSS_EMPTY = `.sbm-ph{position:relative;flex:1 1 260px;width:260px;min-width:0;max-width:520px;height:${H}px;overflow:hidden;${mask(40)}}.sbm-col .sbm-ph{flex:none;width:320px;max-width:100%}.sbm-pt{display:flex;width:max-content;height:100%;align-items:center;animation:sbm-pe 18s linear infinite}.sbm-pt span{padding-right:48px;white-space:nowrap;opacity:.7}@keyframes sbm-pe{from{transform:translateX(0)}to{transform:translateX(-50%)}}.sbm:hover .sbm-pt,.sbm:focus-within .sbm-pt{animation-play-state:paused}@media (prefers-reduced-motion:reduce){.sbm-ph{width:auto;height:auto;${NO_MASK}}.sbm-pt{width:auto;animation:none}.sbm-pt span{white-space:normal;padding:0}.sbm-pt span+span{display:none}}`;
181
188
  const LAYOUT_CSS = {
182
- ribbon: ".sbm-ribbon{container-type:inline-size}.sbm-bar{display:flex;align-items:center;flex-wrap:wrap;gap:10px 16px;font-size:12px}@container (max-width:520px){.sbm-lb{display:none}.sbm-w{flex-basis:100%}}",
189
+ ribbon: ".sbm-ribbon{container-type:inline-size;flex:1 1 260px;width:100%;min-width:0}.sbm-bar{display:flex;align-items:center;flex-wrap:wrap;gap:10px 16px;font-size:12px}@container (max-width:520px){.sbm-lb{display:none}.sbm-w{flex-basis:100%}}",
183
190
  window: ".sbm-col{display:flex;flex-direction:column;align-items:flex-start;gap:10px;font-size:12px}"
184
191
  };
185
192
  const MARK_PATHS = [["#4432F4", "M20 1l-1 1 0 2 6 27 1 1 2 0 1 0 6-12 12-2 1-2-1-1-11-6-14-8-2 0zM39 22l-2 0-7 11-2 1-2 0-2-1-3-11-14 8-1 1 2 2 20 10 3 1 2 0 23-11 2-2-1-1-15-9-3 1z"], ["#CAC9FD", "M11 37l-5 3 0 1 1 0 25 13 2-1 18-9 5-2 1-2-1-1-5-2-19 9-1 1-21-10zM10 47l-4 1 0 2 1 1 25 12 2 0 9-5 13-7 2-1 0-2-2 0-4-2-17 9-3 1-20-10-2 1z"]];
@@ -250,8 +257,8 @@ function scoped(css, scope, root) {
250
257
  return out;
251
258
  }
252
259
  /**
253
- * The wall as a tree. An empty wall (nothing ordered yet) is its root alone: invisible, and still
254
- * found by our check, which looks for the project's key on it.
260
+ * The wall as a tree. An empty wall (nothing ordered yet) holds one line where the badges will be
261
+ * (EMPTY_TEXT); our check finds it all the same, by the project's key on its root.
255
262
  */
256
263
  function layoutWall(wall) {
257
264
  const { variant, theme, attribution } = wall.settings;
@@ -266,7 +273,18 @@ function layoutWall(wall) {
266
273
  "data-submitium": wall.key,
267
274
  "data-submitium-variant": variant
268
275
  }, children);
269
- if (wall.badges.length === 0) return root([]);
276
+ const by = attribution === "none" ? null : attributionNode(attribution);
277
+ const some = (...nodes) => nodes.filter((n) => n !== null);
278
+ const styleOf = (rules) => {
279
+ scope = `sbm-x${hash(rules)}`;
280
+ return el("style", {}, [scoped(rules, `.${scope}`, new Set(classes))]);
281
+ };
282
+ if (wall.badges.length === 0) {
283
+ const css = styleOf(CSS_BASE + (by ? CSS_BY : "") + LAYOUT_CSS[variant] + CSS_EMPTY);
284
+ const line = el("div", { class: "sbm-ph" }, [el("div", { class: "sbm-pt" }, [el("span", {}, [EMPTY_TEXT]), el("span", { "aria-hidden": "true" }, [EMPTY_TEXT])])]);
285
+ if (variant === "window") return root([css, el("div", { class: "sbm-col" }, some(line, by))]);
286
+ return root([css, el("div", { class: "sbm-bar" }, some(line, by ? el("span", { class: "sbm-ctl" }, [by]) : null))]);
287
+ }
270
288
  const candidates = wall.badges.filter((b) => !b.still).map((b) => ({
271
289
  badge: b,
272
290
  width: widthOf(b)
@@ -282,16 +300,12 @@ function layoutWall(wall) {
282
300
  class: "sbm-fix",
283
301
  "data-submitium-zone": "fixed"
284
302
  }, still.map((b) => el("span", { class: "sbm-i" }, [badgeNode(b, theme, false)]))) : null;
285
- const by = attribution === "none" ? null : attributionNode(attribution);
286
303
  const ctl = by ? el("span", { class: "sbm-ctl" }, [by]) : null;
287
304
  const zoneNode = built.moving;
288
305
  const label = el("span", { class: "sbm-lb" }, ["Featured on"]);
289
- const rules = CSS_BASE + (by ? CSS_BY : "") + LAYOUT_CSS[variant] + built.css;
290
- scope = `sbm-x${hash(rules)}`;
291
- const css = el("style", {}, [scoped(rules, `.${scope}`, new Set(classes))]);
292
- const some = (...nodes) => nodes.filter((n) => n !== null);
306
+ const css = styleOf(CSS_BASE + (by ? CSS_BY : "") + LAYOUT_CSS[variant] + built.css);
293
307
  if (variant === "window") return root([css, el("div", { class: "sbm-col" }, some(zoneNode, fixed, by))]);
294
308
  return zoneNode ? root(some(css, el("div", { class: "sbm-bar" }, some(label, zoneNode, ctl)), fixed)) : root([css, el("div", { class: "sbm-bar" }, some(label, fixed, ctl))]);
295
309
  }
296
310
  //#endregion
297
- export { attrsOf as a, TAGS as i, layoutWall as n, cssOf as o, ATTRS as r, styleText as s, ATTRIBUTION_URL as t };
311
+ export { TAGS as a, styleText as c, ATTRS as i, EMPTY_TEXT as n, attrsOf as o, layoutWall as r, cssOf as s, ATTRIBUTION_URL as t };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@submitium/badges",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "The Submitium badge wall: a signed, checked wall rendered as plain links and images. React, Vue, Svelte, Astro.",
5
5
  "license": "MIT",
6
6
  "author": "Submitium <contact@submitium.com>",
package/react.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { n as fetchWall } from "./client-x4bD7iXW.js";
2
- import { a as attrsOf, i as TAGS, n as layoutWall, o as cssOf } from "./layout-DvNYYz0J.js";
2
+ import { a as TAGS, o as attrsOf, r as layoutWall, s as cssOf } from "./layout-DOI7sqk6.js";
3
3
  import { createElement } from "react";
4
4
  //#region src/react.tsx
5
5
  /** React's names for the attributes React spells differently. */
package/vue.js CHANGED
@@ -1,4 +1,4 @@
1
- import { a as attrsOf, i as TAGS, n as layoutWall, o as cssOf } from "./layout-DvNYYz0J.js";
1
+ import { a as TAGS, o as attrsOf, r as layoutWall, s as cssOf } from "./layout-DOI7sqk6.js";
2
2
  import { defineComponent, h } from "vue";
3
3
  //#region src/vue.ts
4
4
  function toVue(node) {