astro-theme-popular 0.9.0 → 0.10.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "astro-theme-popular",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
4
4
  "description": "Popular: a warm, community-first Astro theme for meetups, clubs and fan communities. Ships as an Astro integration.",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/scripts/toc.js CHANGED
@@ -13,8 +13,19 @@
13
13
  var el = document.getElementById(id);
14
14
  if (el) { map[id] = a; targets.push(el); }
15
15
  });
16
+ /* The sidebar has its own scrollbar when it outgrows the viewport, so
17
+ follow the reader: keep the highlighted entry in view. Done by hand
18
+ rather than with scrollIntoView, which would also scroll the page. */
19
+ function reveal(a) {
20
+ if (!a || toc.scrollHeight <= toc.clientHeight) return;
21
+ var box = toc.getBoundingClientRect();
22
+ var item = a.getBoundingClientRect();
23
+ if (item.top < box.top) toc.scrollTop -= box.top - item.top;
24
+ else if (item.bottom > box.bottom) toc.scrollTop += item.bottom - box.bottom;
25
+ }
16
26
  function setActive(id) {
17
27
  links.forEach(function (a) { a.classList.toggle("is-active", a === map[id]); });
28
+ reveal(map[id]);
18
29
  }
19
30
  var obs = new IntersectionObserver(function (entries) {
20
31
  entries.forEach(function (e) { if (e.isIntersecting) setActive(e.target.id); });
@@ -0,0 +1,58 @@
1
+ ---
2
+ /** PreviewBar: fixed banner rendered ONLY when the config sets
3
+ * `SITE.previewMode` (a generator's throwaway build, shown in an iframe). The
4
+ * config written into an adopter's own repo never sets it, so the banner is
5
+ * structurally incapable of reaching a real site.
6
+ *
7
+ * Informational only, no controls: a preview is served with
8
+ * `sandbox="allow-scripts"` and no allow-same-origin, so a button here could
9
+ * not drive the surrounding page. Actions belong in the chrome outside the
10
+ * iframe. Unlike DemoBar this renders in dev too: someone running the same
11
+ * config locally is previewing as well. Twin of Hugo's preview-bar.html.
12
+ *
13
+ * previewMode accepts `true` or `{ note: 'Expires in 60 minutes.' }`, and
14
+ * POPULAR_PREVIEW_NOTE in the environment does the same for a build whose
15
+ * config someone else owns.
16
+ */
17
+ import * as cfg from 'popular:config';
18
+ const { SITE, STRINGS } = cfg;
19
+ /* A generator owns the build but not the adopter's config, so the banner can
20
+ also be switched on from the environment: the counterpart of Hugo's
21
+ HUGO_PARAMS_PREVIEWMODE_NOTE. Read at build time, never shipped to the
22
+ client, and absent from any build that did not ask for it. */
23
+ const fromEnv = process.env.POPULAR_PREVIEW_NOTE;
24
+ const preview = (SITE as Record<string, any>).previewMode
25
+ ?? (fromEnv ? { note: fromEnv } : null);
26
+ const note = typeof preview === 'object' && preview ? preview.note : undefined;
27
+ /* The demo bar occupies the same corner, and a previewed demo has both on, so
28
+ sit above it rather than on top of it. Matches DemoBar's own rule: it does
29
+ not render in dev. */
30
+ const stacked = !import.meta.env.DEV && !!((cfg as Record<string, any>).DEMO_BAR ?? null);
31
+ ---
32
+
33
+ {preview && (
34
+ <>
35
+ <style>
36
+ .g-previewbar { position: fixed; bottom: 14px; left: 50%; transform: translateX(-50%); z-index: 90;
37
+ display: flex; align-items: center; gap: 8px; padding: 8px 16px; max-width: calc(100vw - 28px);
38
+ background: color-mix(in srgb, var(--ink-900) 92%, transparent); color: var(--ink-200);
39
+ border-radius: var(--radius-pill); box-shadow: var(--shadow-lg); font-size: var(--text-xs);
40
+ font-family: var(--font-sans); backdrop-filter: blur(8px); }
41
+ .g-previewbar__label { color: var(--ink-400); font-weight: var(--weight-semibold);
42
+ letter-spacing: var(--tracking-wide); text-transform: uppercase; font-size: 0.65rem; }
43
+ .g-previewbar__note { color: var(--ink-400); }
44
+ @media (max-width: 640px) { .g-previewbar__label { display: none; } }
45
+ /* Reserve space so the fixed bar never covers the footer credit. */
46
+ .g-footer__bottom { padding-bottom: 72px; }
47
+ /* Both bars on the same site: stack, and reserve for both. */
48
+ .g-previewbar--stacked { bottom: 62px; }
49
+ </style>
50
+ {stacked && <style>.g-footer__bottom {'{'} padding-bottom: 120px; {'}'}</style>}
51
+ <div class={`g-previewbar${stacked ? ' g-previewbar--stacked' : ''}`} role="status">
52
+ <i class="fa-solid fa-eye" aria-hidden="true"></i>
53
+ <span class="g-previewbar__label">{STRINGS.previewLabel}</span>
54
+ <span>{STRINGS.previewBanner}</span>
55
+ {note && <span class="g-previewbar__note">{note}</span>}
56
+ </div>
57
+ </>
58
+ )}
@@ -10,6 +10,7 @@ import '../styles/components.css';
10
10
  import Header from 'popular:component/Header';
11
11
  import Footer from 'popular:component/Footer';
12
12
  import DemoBar from '../components/DemoBar.astro';
13
+ import PreviewBar from '../components/PreviewBar.astro';
13
14
  import Notice from '../components/Notice.astro';
14
15
  import { SITE, BRAND, STRINGS } from 'popular:config';
15
16
  import { withBase, absoluteUrl } from '../lib/url';
@@ -111,5 +112,6 @@ const brandCss = `html:root{${vars.join(';')}}`;
111
112
  </main>
112
113
  <Footer />
113
114
  <DemoBar />
115
+ <PreviewBar />
114
116
  </body>
115
117
  </html>
@@ -245,7 +245,13 @@ pre:hover .g-copy, .g-copy:focus-visible { opacity: 1; }
245
245
 
246
246
  /* ---------- Docs (handbook / runbooks) ---------- */
247
247
  .g-doc { display: grid; grid-template-columns: 260px 1fr; gap: var(--space-5); align-items: start; padding-block: var(--space-5) var(--space-6); }
248
- .g-doc-toc { position: sticky; top: 92px; display: flex; flex-direction: column; gap: 0.15rem; }
248
+ /* The sidebar outgrows the viewport on a long page (every h2/h3 of the
249
+ current page nests under its entry). A sticky element taller than the
250
+ viewport cannot be scrolled to its end, so the entries below the fold,
251
+ including the links to the other docs pages, were unreachable without
252
+ scrolling through the whole article first. Cap it to the space under
253
+ the header and give it its own scrollbar. */
254
+ .g-doc-toc { position: sticky; top: 92px; max-height: calc(100vh - 92px - var(--space-4)); overflow-y: auto; overscroll-behavior: contain; scrollbar-width: thin; display: flex; flex-direction: column; gap: 0.15rem; }
249
255
  .g-doc-toc__label { font-family: var(--font-display); font-size: var(--text-xs); font-weight: var(--weight-bold); letter-spacing: var(--tracking-wider); text-transform: uppercase; color: var(--text-muted); margin-bottom: 0.6rem; }
250
256
  .g-doc-toc a { display: block; padding: 0.4rem 0.7rem; border-radius: var(--radius-md); font-size: var(--text-sm); line-height: 1.3; color: var(--text-body); border-left: 3px solid transparent; }
251
257
  .g-doc-toc a:hover { color: var(--color-primary); }
@@ -306,7 +312,7 @@ pre:hover .g-copy, .g-copy:focus-visible { opacity: 1; }
306
312
  /* Narrow screens: the sidebar becomes a pill row of sibling doc pages.
307
313
  The column labels and per-heading anchors only make sense in a sticky
308
314
  sidebar; the headings they point to sit directly below anyway. */
309
- .g-doc-toc { position: static; flex-direction: row; flex-wrap: wrap; gap: 0.4rem; border-bottom: var(--border-width) solid var(--border-subtle); padding-bottom: 0.8rem; margin-bottom: 1rem; }
315
+ .g-doc-toc { position: static; max-height: none; overflow: visible; flex-direction: row; flex-wrap: wrap; gap: 0.4rem; border-bottom: var(--border-width) solid var(--border-subtle); padding-bottom: 0.8rem; margin-bottom: 1rem; }
310
316
  .g-doc-toc__label { display: none; }
311
317
  .g-doc-toc a[href^="#"] { display: none; }
312
318
  .g-doc-toc a { border: var(--border-width) solid var(--border-subtle); border-radius: var(--radius-pill); padding: 0.3rem 0.8rem; }