PageSourceSearch

https://erdingtonelim.org/wp-content/plugins/gatherova/js/dist/eventbanner.js?ver=1790807369

js erdingtonelim.org collected 2026-10-02 02:05:07 UTC 9,286 bytes, 223 lines download raw bytes

1// Draws the auto-generated default event banner (used when an event has no header image).
2// Shared by the single event template, event cards, the view-event popup, and card listings.
3// Deliberately plain: solid/gradient colour + title + date, no decoration — never touches
4// the real uploaded-header-image path (that's always a plain <img>/background-image, untouched).
5
6function gwp_eventbanner_shadehex(hex, percent) {
7  hex = (hex || '#2196F3').replace('#', '');
8  if (hex.length === 3) hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
9  var num = parseInt(hex, 16);
10  if (isNaN(num)) return '#2196F3';
11  var r = Math.min(255, Math.max(0, (num >> 16) + Math.round(255 * percent)));
12  var g = Math.min(255, Math.max(0, ((num >> 8) & 0xff) + Math.round(255 * percent)));
13  var b = Math.min(255, Math.max(0, (num & 0xff) + Math.round(255 * percent)));
14  return '#' + (0x1000000 + r * 0x10000 + g * 0x100 + b).toString(16).slice(1);
15}
16
17function gwp_eventbanner_wraplines(ctx, text, maxWidth) {
18  var words = (text || '').split(' ');
19  var lines = [];
20  var line = '';
21  for (var i = 0; i < words.length; i++) {
22    var test = line ? line + ' ' + words[i] : words[i];
23    if (ctx.measureText(test).width > maxWidth && line) {
24      lines.push(line);
25      line = words[i];
26    } else {
27      line = test;
28    }
29  }
30  if (line) lines.push(line);
31  return lines;
32}
33
34/**
35 * Draws the banner into `canvas` at its actual on-screen size (read from the DOM) and at
36 * full device pixel density. The element's CSS always owns the visible box (width/height,
37 * position) — this never sets inline width/height styles, so it can't fight the page's own
38 * layout. If the element isn't laid out yet (e.g. inside a popup that's still display:none),
39 * it waits a couple of frames and retries, rather than guessing a size and locking it in.
40 */
41function gwp_renderEventBanner(canvas, opts) {
42  if (!canvas || typeof canvas.getContext !== 'function') return;
43  opts = opts || {};
44  var w = Math.round(opts.width || canvas.clientWidth || 0);
45  var h = Math.round(opts.height || canvas.clientHeight || 0);
46
47  if (!w || !h) {
48    var retries = (opts._retries || 0) + 1;
49    if (retries > 6) { w = w || 400; h = h || 180; } else {
50      var nextOpts = Object.assign({}, opts, { _retries: retries });
51      requestAnimationFrame(function () {
52        requestAnimationFrame(function () { gwp_renderEventBanner(canvas, nextOpts); });
53      });
54      return;
55    }
56  }
57
58  // Remember how this canvas was last drawn so a resize/orientation-change redraw
59  // (below) can reproduce it at the new box size — popups and cards get viewed on
60  // everything from phones to desktops, and a fixed-size draw would go stale.
61  canvas._gwpBannerOpts = opts;
62
63  var dpr = window.devicePixelRatio || 1;
64  canvas.width = Math.round(w * dpr);
65  canvas.height = Math.round(h * dpr);
66
67  var accent = opts.accentColor || '#2196F3';
68  var ctx = canvas.getContext('2d');
69  ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
70
71  // Always left-to-right, never corner-to-corner — a diagonal gradient's angle changes with
72  // the box's aspect ratio, which looks inconsistent across a masonry grid where every card
73  // is a different shape. Horizontal reads the same regardless of width/height.
74  var grad = ctx.createLinearGradient(0, 0, w, 0);
75  grad.addColorStop(0, gwp_eventbanner_shadehex(accent, 0.08));
76  grad.addColorStop(1, gwp_eventbanner_shadehex(accent, -0.14));
77  ctx.fillStyle = grad;
78  ctx.fillRect(0, 0, w, h);
79
80  var align = opts.align === 'left' ? 'left' : 'center';
81  var padX = opts.padX != null ? opts.padX : Math.max(14, w * 0.06);
82  var textX = align === 'left' ? padX : w / 2;
83  // Normally scales with the box (h * 0.17) so a single event page's big banner gets big
84  // text — but pass opts.titleSize for a fixed size instead, e.g. a masonry gallery where
85  // every card is a different height and matching font sizes reads more consistently than
86  // each card getting its own proportional size.
87  var titleSize = opts.titleSize || Math.round(h * 0.17);
88  var minTitleSize = opts.minTitleSize || Math.round(h * 0.09);
89  var maxLines = 2;
90  var lines = [];
91
92  ctx.textBaseline = 'alphabetic';
93  ctx.textAlign = align;
94  while (titleSize >= minTitleSize) {
95    ctx.font = '800 ' + titleSize + 'px Arial, Helvetica, sans-serif';
96    lines = gwp_eventbanner_wraplines(ctx, opts.title || '', w - padX * 2);
97    if (lines.length <= maxLines) break;
98    titleSize -= 1;
99  }
100  lines = lines.slice(0, maxLines);
101
102  var subtitleText = [opts.dateText, opts.timeText].filter(Boolean).join('   •   ');
103  var subtitleSize = Math.max(11, Math.round(titleSize * 0.42));
104  var lineHeight = Math.round(titleSize * 1.22);
105  var gapBetween = Math.round(subtitleSize * 0.9);
106  var chipPadY = Math.round(subtitleSize * 0.4);
107  var chipH = subtitleText ? (subtitleSize + chipPadY * 2) : 0;
108
109  // Strict top-down stacking — title lines, then a fixed gap, then the date chip.
110  // Never computed as an overlapping "centered around a midpoint" guess.
111  var totalH = lines.length * lineHeight + (subtitleText ? gapBetween + chipH : 0);
112  var top = Math.max(padX * 0.6, (h - totalH) / 2);
113
114  ctx.fillStyle = '#ffffff';
115  ctx.shadowColor = 'rgba(0,0,0,0.3)';
116  ctx.shadowBlur = Math.max(1, titleSize * 0.05);
117  for (var i = 0; i < lines.length; i++) {
118    ctx.fillText(lines[i], textX, top + lineHeight * i + titleSize * 0.9);
119  }
120  ctx.shadowBlur = 0;
121
122  if (subtitleText) {
123    var chipTop = top + lines.length * lineHeight + gapBetween;
124    ctx.font = '700 ' + subtitleSize + 'px Arial, Helvetica, sans-serif';
125    var textW = ctx.measureText(subtitleText).width;
126    var chipPadX = Math.round(subtitleSize * 0.55);
127    var chipW = Math.min(textW + chipPadX * 2, w - padX * 2);
128    var chipX = align === 'left' ? padX : (w - chipW) / 2;
129
130    ctx.fillStyle = 'rgba(0,0,0,0.28)';
131    ctx.beginPath();
132    if (typeof ctx.roundRect === 'function') {
133      ctx.roundRect(chipX, chipTop, chipW, chipH, chipH / 2);
134    } else {
135      ctx.rect(chipX, chipTop, chipW, chipH);
136    }
137    ctx.fill();
138
139    ctx.fillStyle = '#ffffff';
140    var subtitleX = align === 'left' ? chipX + chipPadX : w / 2;
141    ctx.fillText(subtitleText, subtitleX, chipTop + chipH / 2 + subtitleSize * 0.35);
142  }
143
144  if (opts.logoUrl) {
145    var img = new Image();
146    img.crossOrigin = 'anonymous';
147    img.onload = function () {
148      var logoH = h * 0.16;
149      var logoW = logoH * (img.width / img.height);
150      ctx.drawImage(img, padX, h - logoH - h * 0.08, logoW, logoH);
151    };
152    img.src = opts.logoUrl;
153  }
154}
155
156function gwp_downloadEventBannerPNG(canvas, filename) {
157  if (!canvas) return;
158  var a = document.createElement('a');
159  a.href = canvas.toDataURL('image/png');
160  a.download = filename || 'event-banner.png';
161  document.body.appendChild(a);
162  a.click();
163  document.body.removeChild(a);
164}
165
166function gwp_resolveEventAccentColor(explicitColor, groupNames, groupColorMap, fallback) {
167  if (explicitColor) return explicitColor;
168  if (groupNames && groupNames.length && groupColorMap) {
169    for (var i = 0; i < groupNames.length; i++) {
170      if (groupColorMap[groupNames[i]]) return groupColorMap[groupNames[i]];
171    }
172  }
173  return fallback || '#2196F3';
174}
175
176/**
177 * Finds every not-yet-rendered banner canvas matching `selector` and draws it — but waits
178 * for the whole page (styles, fonts, everything) to finish loading first, plus a couple of
179 * animation frames after that. Measuring a canvas's box too early (e.g. from an inline
180 * <script> that runs while the stylesheet is still being applied, or before a masonry-style
181 * grid has settled) locks in the wrong resolution — the box later renders at its real size,
182 * but the bitmap drawn into it is still the old (usually smaller/differently-shaped) one,
183 * which is what a stretched, blurry banner actually is.
184 */
185function gwp_initEventBannerCanvases(selector, extraOpts) {
186  function run() {
187    var list = document.querySelectorAll(selector + ':not([data-rendered])');
188    for (var i = 0; i < list.length; i++) {
189      var c = list[i];
190      c.setAttribute('data-rendered', '1');
191      var opts = {
192        title: c.getAttribute('data-title') || '',
193        dateText: c.getAttribute('data-date') || '',
194        accentColor: c.getAttribute('data-accent') || '#2196F3'
195      };
196      if (extraOpts) { for (var k in extraOpts) { opts[k] = extraOpts[k]; } }
197      gwp_renderEventBanner(c, opts);
198    }
199  }
200  function runAfterLayoutSettles() {
201    requestAnimationFrame(function () { requestAnimationFrame(run); });
202  }
203  if (document.readyState === 'complete') {
204    runAfterLayoutSettles();
205  } else {
206    window.addEventListener('load', runAfterLayoutSettles);
207  }
208}
209
210// Redraw every already-rendered banner on resize/orientation-change so a popup or card
211// left open across a rotation or window resize doesn't show a stale, wrongly-sized banner.
212(function () {
213  var _gwpBannerResizeTimer;
214  window.addEventListener('resize', function () {
215    clearTimeout(_gwpBannerResizeTimer);
216    _gwpBannerResizeTimer = setTimeout(function () {
217      var canvases = document.querySelectorAll('canvas');
218      for (var i = 0; i < canvases.length; i++) {
219        if (canvases[i]._gwpBannerOpts) gwp_renderEventBanner(canvases[i], canvases[i]._gwpBannerOpts);
220      }
221    }, 150);
222  });
223})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.