@aw-webflow/pricing_page_js 1.0.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.
@@ -0,0 +1,22 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(npm install *)",
5
+ "Bash(curl -sL https://www.atomicwork.com/pricing-new -o /tmp/pricing.html)",
6
+ "Read(//tmp/**)",
7
+ "Read(//private/tmp/**)",
8
+ "Bash(python3 -)",
9
+ "mcp__chrome-devtools__new_page",
10
+ "mcp__chrome-devtools__list_pages",
11
+ "Bash(node --check script.js)",
12
+ "Bash(curl -sL https://atomicwork.webflow.io/pricing-new -o staging.html)",
13
+ "Bash(gh auth *)",
14
+ "Bash(gh repo *)",
15
+ "Bash(git add *)",
16
+ "Bash(git commit -q -m ' *)",
17
+ "Bash(git push *)",
18
+ "Bash(git tag *)",
19
+ "Bash(gh release create v1.0.0 --title v1.0.0 --notes ' *)"
20
+ ]
21
+ }
22
+ }
package/CLAUDE.md ADDED
@@ -0,0 +1,15 @@
1
+ # CLAUDE.md
2
+
3
+ ## Project Overview
4
+
5
+ Custom JavaScript for a Webflow page. The code lives in `script.js` and is published to npm as the scoped package `@aw-webflow/pricing_page_js`. It is consumed in Webflow via the jsDelivr CDN, which serves the published npm package directly.
6
+
7
+ ## Coding Conventions
8
+
9
+ - All code must use `var` and ES5 syntax for maximum browser compatibility. Do not use `let`, `const`, arrow functions, template literals, or other ES6+ features in `script.js`.
10
+
11
+ ## Deployment
12
+
13
+ 1. Push changes to GitHub.
14
+ 2. Publish to npm: `npm publish --access public` (bump the version in `package.json` first).
15
+ 3. jsDelivr automatically serves the published package from the npm registry.
package/README.md ADDED
@@ -0,0 +1,27 @@
1
+ # @aw-webflow/pricing_page_js
2
+
3
+ Custom JavaScript for a Webflow page.
4
+
5
+ ## Usage via jsDelivr CDN
6
+
7
+ Add the following script tag to your Webflow page (before `</body>`):
8
+
9
+ ```html
10
+ <script src="https://cdn.jsdelivr.net/npm/@aw-webflow/pricing_page_js@1.0.0/script.min.js"></script>
11
+ ```
12
+
13
+ ## Deployment Workflow
14
+
15
+ 1. Push your changes to GitHub.
16
+ 2. Bump the version in `package.json`.
17
+ 3. Publish to npm: `npm publish --access public`.
18
+ 4. Update the version in the Webflow `<script>` tag to point to the new release.
19
+
20
+ ## Local Development
21
+
22
+ ```bash
23
+ npm install
24
+ npm start
25
+ ```
26
+
27
+ `npm start` runs `parcel script.js` for local bundling and development.
package/package.json ADDED
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "@aw-webflow/pricing_page_js",
3
+ "version": "1.0.0",
4
+ "description": "",
5
+ "scripts": {
6
+ "start": "parcel script.js",
7
+ "test": "echo \"Error: no test specified\" && exit 1"
8
+ },
9
+ "license": "ISC",
10
+ "devDependencies": {
11
+ "parcel": "^2.16.4"
12
+ }
13
+ }
package/script.js ADDED
@@ -0,0 +1,218 @@
1
+ /* Pricing page - "Compare plans" accordion
2
+ *
3
+ * The collapsible table divs each carry id="pricing-table" (yes, the same id
4
+ * repeats on every table). They sit as siblings, each one immediately after a
5
+ * .table-section-header:
6
+ *
7
+ * <div class="table-section-header"> ... <img class="table-dropdown-icon"></div>
8
+ * <div id="pricing-table" class="fs-table-*_instance"> ...table... </div> <- collapsible
9
+ *
10
+ * So we select every [id="pricing-table"] element and treat its previous
11
+ * sibling header as the toggle. Clicking the header slides the table open/shut.
12
+ *
13
+ * Behaviour: first category open on load, rest collapsed. Independent
14
+ * toggling (multiple categories can be open at once).
15
+ *
16
+ * Tables may be injected at runtime by Finsweet, so we also watch for new
17
+ * ones (observer + polling).
18
+ *
19
+ * ES5 only (var / no arrow functions) for browser compatibility.
20
+ */
21
+ (function () {
22
+ 'use strict';
23
+
24
+ var INSTANCE_SELECTOR = '[id="pricing-table"]'; // duplicate id, select them all
25
+ var HEADER_SELECTOR = '.table-section-header';
26
+ var ICON_SELECTOR = '.table-dropdown-icon';
27
+ var INIT_FLAG = 'awAccordionBound';
28
+ var SLIDE_CLASS = 'aw-slide';
29
+ var DURATION = 350; // ms, keep in sync with the injected CSS transition
30
+
31
+ var boundInstances = []; // collapsible table elements we control
32
+
33
+ // ---- one-time CSS injection ------------------------------------------
34
+ function injectStyles() {
35
+ if (document.getElementById('aw-accordion-styles')) return;
36
+ var css =
37
+ '.table-section-header{cursor:pointer;}' +
38
+ ICON_SELECTOR + '{transition:transform .35s ease;}' +
39
+ '.table-section-header.is-open ' + ICON_SELECTOR + '{transform:rotate(180deg);}' +
40
+ // transition added after first paint to avoid a load flash
41
+ '.' + SLIDE_CLASS + '{transition:height .35s ease;overflow:hidden;}';
42
+ var style = document.createElement('style');
43
+ style.id = 'aw-accordion-styles';
44
+ style.type = 'text/css';
45
+ style.appendChild(document.createTextNode(css));
46
+ document.head.appendChild(style);
47
+ }
48
+
49
+ // ---- helpers ----------------------------------------------------------
50
+ function matches(el, selector) {
51
+ if (!el || el.nodeType !== 1) return false;
52
+ var fn =
53
+ el.matches ||
54
+ el.webkitMatchesSelector ||
55
+ el.msMatchesSelector ||
56
+ el.mozMatchesSelector;
57
+ return fn ? fn.call(el, selector) : false;
58
+ }
59
+
60
+ // The header that controls an instance = its previous sibling header.
61
+ function findHeader(instance) {
62
+ var node = instance.previousElementSibling;
63
+ while (node) {
64
+ if (matches(node, HEADER_SELECTOR)) return node;
65
+ if (matches(node, INSTANCE_SELECTOR)) return null; // hit another table
66
+ node = node.previousElementSibling;
67
+ }
68
+ return null;
69
+ }
70
+
71
+ function getIcon(header) {
72
+ return header.querySelector(ICON_SELECTOR);
73
+ }
74
+
75
+ function expand(instance) {
76
+ instance.style.height = instance.scrollHeight + 'px';
77
+ // after the slide finishes, release to auto so it stays responsive
78
+ window.setTimeout(function () {
79
+ if (instance.getAttribute('data-aw-open') === '1') {
80
+ instance.style.height = 'auto';
81
+ }
82
+ }, DURATION);
83
+ }
84
+
85
+ function collapse(instance) {
86
+ // from auto -> fixed px, force reflow, then to 0 so it animates
87
+ instance.style.height = instance.scrollHeight + 'px';
88
+ instance.offsetHeight; // reflow
89
+ instance.style.height = '0px';
90
+ }
91
+
92
+ function setOpen(header, instance, open) {
93
+ instance.setAttribute('data-aw-open', open ? '1' : '0');
94
+ header.setAttribute('aria-expanded', open ? 'true' : 'false');
95
+ if (open) {
96
+ if (header.className.indexOf('is-open') === -1) header.className += ' is-open';
97
+ expand(instance);
98
+ } else {
99
+ header.className = header.className.replace(/\s*\bis-open\b/g, '');
100
+ collapse(instance);
101
+ }
102
+ }
103
+
104
+ function toggle(header, instance) {
105
+ setOpen(header, instance, instance.getAttribute('data-aw-open') !== '1');
106
+ }
107
+
108
+ // ---- wire up one instance + its header -------------------------------
109
+ function bindInstance(instance, startOpen) {
110
+ if (instance[INIT_FLAG]) return false;
111
+ var header = findHeader(instance);
112
+ if (!header) return false; // no controlling header (e.g. overview table)
113
+
114
+ instance[INIT_FLAG] = true;
115
+ boundInstances.push(instance);
116
+
117
+ // accessibility
118
+ if (!header.getAttribute('role')) header.setAttribute('role', 'button');
119
+ if (!header.hasAttribute('tabindex')) header.setAttribute('tabindex', '0');
120
+
121
+ // initial state WITHOUT animation (slide class added after first paint)
122
+ instance.style.overflow = 'hidden';
123
+ if (startOpen) {
124
+ instance.setAttribute('data-aw-open', '1');
125
+ header.setAttribute('aria-expanded', 'true');
126
+ if (header.className.indexOf('is-open') === -1) header.className += ' is-open';
127
+ instance.style.height = 'auto';
128
+ } else {
129
+ instance.setAttribute('data-aw-open', '0');
130
+ header.setAttribute('aria-expanded', 'false');
131
+ instance.style.height = '0px';
132
+ }
133
+
134
+ header.addEventListener('click', function () {
135
+ toggle(header, instance);
136
+ });
137
+ header.addEventListener('keydown', function (e) {
138
+ var key = e.key || e.keyCode;
139
+ if (key === 'Enter' || key === ' ' || key === 13 || key === 32) {
140
+ e.preventDefault();
141
+ toggle(header, instance);
142
+ }
143
+ });
144
+
145
+ return true;
146
+ }
147
+
148
+ // ---- (re)scan for instances ------------------------------------------
149
+ var slideEnabled = false;
150
+ function enableSlide() {
151
+ if (slideEnabled) return;
152
+ for (var i = 0; i < boundInstances.length; i++) {
153
+ var el = boundInstances[i];
154
+ if (el.className.indexOf(SLIDE_CLASS) === -1) el.className += ' ' + SLIDE_CLASS;
155
+ }
156
+ slideEnabled = true;
157
+ }
158
+
159
+ function scan() {
160
+ var instances = document.querySelectorAll(INSTANCE_SELECTOR);
161
+ var boundAny = false;
162
+ var firstUnbound = true;
163
+ for (var i = 0; i < instances.length; i++) {
164
+ var inst = instances[i];
165
+ var wasBound = !!inst[INIT_FLAG];
166
+ // only the FIRST togglable table starts open, the rest collapsed
167
+ var ok = bindInstance(inst, firstUnbound && !wasBound);
168
+ if (ok) boundAny = true;
169
+ if (ok || wasBound) firstUnbound = false;
170
+ }
171
+ if (boundAny) {
172
+ // turn transitions on after the initial paint so load is flash-free
173
+ window.requestAnimationFrame
174
+ ? window.requestAnimationFrame(function () {
175
+ window.requestAnimationFrame(enableSlide);
176
+ })
177
+ : window.setTimeout(enableSlide, 60);
178
+ }
179
+ return boundAny;
180
+ }
181
+
182
+ // ---- bootstrap --------------------------------------------------------
183
+ function bootstrap() {
184
+ injectStyles();
185
+ scan();
186
+
187
+ var first = document.querySelector(INSTANCE_SELECTOR);
188
+ var root = (first && first.parentNode) || document.body;
189
+ if (window.MutationObserver) {
190
+ var observer = new MutationObserver(function () {
191
+ scan();
192
+ });
193
+ observer.observe(root, { childList: true, subtree: true });
194
+ }
195
+
196
+ // polling fallback (stops once every instance is paired)
197
+ var tries = 0;
198
+ var poll = window.setInterval(function () {
199
+ tries++;
200
+ scan();
201
+ var instances = document.querySelectorAll(INSTANCE_SELECTOR);
202
+ var allBound = instances.length > 0;
203
+ for (var i = 0; i < instances.length; i++) {
204
+ if (!instances[i][INIT_FLAG] && findHeader(instances[i])) {
205
+ allBound = false;
206
+ break;
207
+ }
208
+ }
209
+ if (allBound || tries > 40) window.clearInterval(poll); // ~20s max
210
+ }, 500);
211
+ }
212
+
213
+ if (document.readyState === 'loading') {
214
+ document.addEventListener('DOMContentLoaded', bootstrap);
215
+ } else {
216
+ bootstrap();
217
+ }
218
+ })();