maverick-wave 4.14.0 → 4.16.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/.claude/skills/maverick-wave/SKILL.md +4 -1
- package/.claude/skills/maverick-wave/references/components.md +162 -0
- package/CHANGELOG.md +13 -0
- package/README.md +1 -1
- package/index.html +68 -0
- package/maverick-wave.min.css +2 -2
- package/package.json +2 -2
- package/src/partials/pricing-container.html +523 -0
- package/src/scss/base/_reset.scss +9 -0
- package/src/scss/components/_index.scss +1 -0
- package/src/scss/components/_pricing.scss +231 -0
- package/src/scss/components/_segmented.scss +12 -0
|
@@ -202,7 +202,10 @@ Everything below is documented in `references/components.md` unless marked other
|
|
|
202
202
|
**Containers** `mw-card` (+ `simple`, `lg`, `xl`, `stack`, badge, ribbon,
|
|
203
203
|
feature frame) ·
|
|
204
204
|
`mw-panel` · `mw-tile` · `mw-accordion` · `mw-tabs` · `mw-modal` ·
|
|
205
|
-
`mw-item-list` family
|
|
205
|
+
`mw-item-list` family · `mw-offer` (+ `-head`, `-name`)
|
|
206
|
+
|
|
207
|
+
**Pricing** `mw-price` (+ `-amount`, `-fraction`, `-currency`, `-original`,
|
|
208
|
+
`-period`, `-word`, `-note`, `sm`/`lg`, `inline`, `center`, `plain`)
|
|
206
209
|
|
|
207
210
|
**Data & status** `mw-table` (+ `subtle`, `sticky-head`, `cards`, `compact`,
|
|
208
211
|
`hover`, responsive wrappers) · `mw-kanban` (+ `plain`, `compact`) ·
|
|
@@ -299,6 +299,168 @@ tech-stack grids:
|
|
|
299
299
|
</div>
|
|
300
300
|
```
|
|
301
301
|
|
|
302
|
+
## Pricing
|
|
303
|
+
|
|
304
|
+
Two pieces that are almost always needed together: `mw-price`, the figure on its
|
|
305
|
+
own, and `mw-offer`, the card it is sold from.
|
|
306
|
+
|
|
307
|
+
### Price
|
|
308
|
+
|
|
309
|
+
One baseline row - what it cost before, what it costs now, the unit, a discount
|
|
310
|
+
tag:
|
|
311
|
+
|
|
312
|
+
```html
|
|
313
|
+
<p class="mw-price">
|
|
314
|
+
<span class="mw-price-original">
|
|
315
|
+
<span class="mw-sr-only">Regular price:</span>159 Euro
|
|
316
|
+
</span>
|
|
317
|
+
<span class="mw-sr-only">Now:</span>
|
|
318
|
+
<span class="mw-price-amount"
|
|
319
|
+
>129<span class="mw-price-fraction">,90</span></span
|
|
320
|
+
>
|
|
321
|
+
<span class="mw-price-currency">Euro</span>
|
|
322
|
+
<span class="mw-price-period">/ month</span>
|
|
323
|
+
<span class="mw-tag mw-tag-secondary">-20%</span>
|
|
324
|
+
</p>
|
|
325
|
+
<p class="mw-price-note">Billed yearly, cancel any time.</p>
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
- Everything in the row is sized in `em` off `--mw-price-size`, so
|
|
329
|
+
`mw-price-sm` (1.5rem) and `mw-price-lg` (3rem) move the whole row at once -
|
|
330
|
+
cents, unit and struck original included. Set the token yourself for any other
|
|
331
|
+
size.
|
|
332
|
+
- `mw-price-original` is muted **and** struck, and it takes its own line
|
|
333
|
+
**above** the new price. Muted alone reads as a footnote; the line is what
|
|
334
|
+
says this is no longer the price. The rule is drawn explicitly, because the
|
|
335
|
+
browser default hairline disappears at this size against a muted tone.
|
|
336
|
+
- Stacked is the default because inline the row runs long - "199 Euro 159 Euro
|
|
337
|
+
-20 %" - and the first thing to wrap away when the width gives out is the
|
|
338
|
+
discount tag, the one part that was there to catch the eye. `mw-price-inline`
|
|
339
|
+
puts it back on one line for a wide card or a table row.
|
|
340
|
+
- `mw-price-fraction` goes **inside** the amount and sits one step down on the
|
|
341
|
+
baseline, not raised to the cap height - raised cents are a discounter's idiom
|
|
342
|
+
and make a service price read cheaper than it is.
|
|
343
|
+
- `mw-price-currency` and `mw-price-period` stop shrinking at 0.8rem, and
|
|
344
|
+
`mw-price-original` at 0.9rem. Below `mw-price-sm` the `em` chain would put
|
|
345
|
+
them under 12px, and that is the size most offer cards run at.
|
|
346
|
+
- `mw-price-word` for "On request" or "Free" - bold, so it holds the same slot in
|
|
347
|
+
a row of cards, but set well below a figure. Its line box is scaled back up to
|
|
348
|
+
the amount's, so a word and a number in neighbouring cards land on one line.
|
|
349
|
+
- `mw-price-note` is a **sibling** of the row, not a child - inside it, it would
|
|
350
|
+
inherit the price size and join the baseline.
|
|
351
|
+
- A `mw-tag` or `mw-badge` placed directly in the row is centred against the
|
|
352
|
+
digits rather than hung off their baseline.
|
|
353
|
+
|
|
354
|
+
| Class | What it does |
|
|
355
|
+
| ----------------- | ---------------------------------------------- |
|
|
356
|
+
| `mw-price-sm/lg` | Retunes `--mw-price-size`, everything follows |
|
|
357
|
+
| `mw-price-inline` | Old price in front of the new one, on one line |
|
|
358
|
+
| `mw-price-center` | Centres the row |
|
|
359
|
+
| `mw-price-plain` | Body colour instead of the brand |
|
|
360
|
+
|
|
361
|
+
A struck-through price is a visual convention a screen reader does not pass on -
|
|
362
|
+
`<s>` is announced by almost none of them. Wherever an old and a new price stand
|
|
363
|
+
next to each other, name them with `mw-sr-only`, as above. Without it the reader
|
|
364
|
+
hears two prices and no way to tell which one is charged.
|
|
365
|
+
|
|
366
|
+
### Offer card
|
|
367
|
+
|
|
368
|
+
`mw-offer` goes on a `mw-card` and does the one thing the card cannot: it pushes
|
|
369
|
+
the price to the bottom of the body, so the prices in a row line up even though
|
|
370
|
+
the feature lists have different lengths.
|
|
371
|
+
|
|
372
|
+
```html
|
|
373
|
+
<article class="mw-card mw-offer">
|
|
374
|
+
<div class="mw-card-body">
|
|
375
|
+
<h3 class="mw-card-title">Studio</h3>
|
|
376
|
+
<hr class="mw-card-title-divider" />
|
|
377
|
+
<p class="mw-card-text">Description.</p>
|
|
378
|
+
<ul class="mw-list mw-list-check">
|
|
379
|
+
<li>10 projects</li>
|
|
380
|
+
</ul>
|
|
381
|
+
<p class="mw-price mw-price-sm">…</p>
|
|
382
|
+
<p class="mw-price-note">Per month, billed yearly.</p>
|
|
383
|
+
<div class="mw-card-footer">
|
|
384
|
+
<button class="mw-btn mw-btn-primary">Choose</button>
|
|
385
|
+
</div>
|
|
386
|
+
</div>
|
|
387
|
+
</article>
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
- Bottom-aligned, note included: a card that carries a `mw-price-note` and one
|
|
391
|
+
that does not will **not** have their prices on the same line. Give every card
|
|
392
|
+
in a row a note, or none of them.
|
|
393
|
+
- To lift one plan out of the row, wrap it in `mw-card-feature` - the frame is
|
|
394
|
+
the card's, not the offer's.
|
|
395
|
+
- A price dropped into a `mw-card-footer` instead needs no modifier: it lines up
|
|
396
|
+
with the button beside it on its own, and `mw-price-sm` keeps it from
|
|
397
|
+
outweighing the action. That is the layout for a dated event, where the price
|
|
398
|
+
is a detail rather than the offer.
|
|
399
|
+
|
|
400
|
+
**Head band** - name and price in a solid bar at the top, for when the price is
|
|
401
|
+
the thing being compared and the feature list is only its justification:
|
|
402
|
+
|
|
403
|
+
```html
|
|
404
|
+
<article class="mw-card mw-offer mw-card-addon-secondary">
|
|
405
|
+
<div class="mw-offer-head">
|
|
406
|
+
<p class="mw-offer-name">Studio</p>
|
|
407
|
+
<p class="mw-price mw-price-sm">…</p>
|
|
408
|
+
</div>
|
|
409
|
+
<div class="mw-card-body">…</div>
|
|
410
|
+
</article>
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
The band reads `mw-card-addon-*` for its colour - the same set the card badge and
|
|
414
|
+
the ribbon use, primary without one. The price inherits the band's ink, and the
|
|
415
|
+
card drops its top corner arc, which the band already owns.
|
|
416
|
+
|
|
417
|
+
### Billing cycle
|
|
418
|
+
|
|
419
|
+
A subscription sold on several terms, where the longer one is cheaper. The
|
|
420
|
+
switch is a `mw-segmented` **above** the grid, not one per card - three cards
|
|
421
|
+
with three switches are three states the visitor has to keep in their head.
|
|
422
|
+
|
|
423
|
+
```html
|
|
424
|
+
<div
|
|
425
|
+
class="mw-segmented mw-segmented-auto"
|
|
426
|
+
role="group"
|
|
427
|
+
aria-label="Billing cycle"
|
|
428
|
+
>
|
|
429
|
+
<button type="button" class="mw-segmented-item mw-active" aria-pressed="true">
|
|
430
|
+
Monthly
|
|
431
|
+
</button>
|
|
432
|
+
<button type="button" class="mw-segmented-item" aria-pressed="false">
|
|
433
|
+
6 months <span class="mw-tag mw-tag-secondary">-10%</span>
|
|
434
|
+
</button>
|
|
435
|
+
<button type="button" class="mw-segmented-item" aria-pressed="false">
|
|
436
|
+
Yearly <span class="mw-tag mw-tag-secondary">-20%</span>
|
|
437
|
+
</button>
|
|
438
|
+
</div>
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
- The headline figure stays **per month** on every term. "288 Euro" next to
|
|
442
|
+
"30 Euro" is not a comparison a reader can make in their head; the amount that
|
|
443
|
+
actually leaves the account belongs in the `mw-price-note` below
|
|
444
|
+
("288 Euro charged once for the year, cancel monthly").
|
|
445
|
+
- The full monthly rate goes in `mw-price-original`, the saving in a `mw-tag`
|
|
446
|
+
next to it. Both carry `hidden` on the monthly term - the framework resets
|
|
447
|
+
`[hidden]` to `display: none !important`, so it beats a component's own
|
|
448
|
+
`display` and an `el.hidden = true` or an Angular `[hidden]` binding works on
|
|
449
|
+
a `mw-tag` too.
|
|
450
|
+
- Wrap the price in `aria-live="polite"` and put `aria-pressed` on the switch
|
|
451
|
+
items. The figure changes without the page moving, which a screen reader
|
|
452
|
+
otherwise never hears.
|
|
453
|
+
- Drop trailing zero cents: "24 Euro", not "24,00 Euro". `mw-price-amount` sets
|
|
454
|
+
tabular figures, so the row does not jump when the term changes.
|
|
455
|
+
- A tag inside the **active** item drops its colour and outlines itself in the
|
|
456
|
+
item's ink - the colour variants mix their background from the page, and an
|
|
457
|
+
orange pill on the blue fill comes out purple. Inactive items keep the colour,
|
|
458
|
+
which is what makes a visitor click them.
|
|
459
|
+
|
|
460
|
+
The switching itself is the application's job - the framework ships the look,
|
|
461
|
+
not the arithmetic. The showcase wires it in a few lines at the bottom of
|
|
462
|
+
`index.html`.
|
|
463
|
+
|
|
302
464
|
## Panels
|
|
303
465
|
|
|
304
466
|
A bordered box with a header rule - the "titled section" of an application.
|
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,19 @@ Patch releases are only for test purposes - here I only document major and minor
|
|
|
6
6
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
7
7
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
8
8
|
|
|
9
|
+
## [4.16.0] - 2026-08-29
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- `mw-price-original` now takes its own line above the new price by default
|
|
14
|
+
- `mw-price-stacked` is gone, `mw-price-inline` takes its place
|
|
15
|
+
|
|
16
|
+
## [4.15.0] - 2026-08-29
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
- `mw-price` - the price figure: struck-through original, discounted amount, cents, unit, period and note, ...
|
|
21
|
+
|
|
9
22
|
## [4.14.0] - 2026-08-27
|
|
10
23
|
|
|
11
24
|
### Added
|
package/README.md
CHANGED
|
@@ -17,7 +17,7 @@ The result is a framework that balances utility with simplicity, offering develo
|
|
|
17
17
|
## Features
|
|
18
18
|
|
|
19
19
|
- Responsive Grid System
|
|
20
|
-
- 30+ UI Components: Buttons, Cards, Panels, Tabs, Accordions, Modals, Tiles, Alerts, Spinners, Progress Bars, Avatars, Tags, Badges, Dropdown, Ratings, Stepper, Skeleton Loader, Empty State, Divider, and more
|
|
20
|
+
- 30+ UI Components: Buttons, Cards, Panels, Tabs, Accordions, Modals, Tiles, Alerts, Spinners, Progress Bars, Avatars, Tags, Badges, Dropdown, Ratings, Stepper, Skeleton Loader, Empty State, Price, Offer Cards, Divider, and more
|
|
21
21
|
- Form Elements: Input, Select, Textarea, Checkbox, Radio, Toggle, Input Group, with `mw-field` wrapper pattern for Angular Reactive Forms
|
|
22
22
|
- Utility Classes for spacing, flex, display, typography, text overflow, elevation and aspect ratio
|
|
23
23
|
- A five-step elevation ramp and a motion scale, so every shadow and every transition in the framework comes from one place
|
package/index.html
CHANGED
|
@@ -280,6 +280,7 @@
|
|
|
280
280
|
<a href="#code" class="mw-section-btn">Code</a>
|
|
281
281
|
<a href="#skeleton" class="mw-section-btn">Skeleton</a>
|
|
282
282
|
<a href="#empty-state" class="mw-section-btn">Empty State</a>
|
|
283
|
+
<a href="#pricing" class="mw-section-btn">Pricing</a>
|
|
283
284
|
</nav>
|
|
284
285
|
|
|
285
286
|
<!-- Buttons container -->
|
|
@@ -348,6 +349,11 @@
|
|
|
348
349
|
<div id="empty-state">
|
|
349
350
|
@@include('./src/partials/empty-state-container.html')
|
|
350
351
|
</div>
|
|
352
|
+
|
|
353
|
+
<!-- Pricing container -->
|
|
354
|
+
<div id="pricing">
|
|
355
|
+
@@include('./src/partials/pricing-container.html')
|
|
356
|
+
</div>
|
|
351
357
|
</div>
|
|
352
358
|
</section>
|
|
353
359
|
|
|
@@ -497,6 +503,68 @@
|
|
|
497
503
|
}
|
|
498
504
|
});
|
|
499
505
|
|
|
506
|
+
// just for the showcase: one segmented switch above the offer grid drives
|
|
507
|
+
// every card in it. The framework ships the look, not the arithmetic - a
|
|
508
|
+
// real application has its own prices and its own state, so this stays
|
|
509
|
+
// here rather than in maverick-wave.js.
|
|
510
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
511
|
+
const cycleSwitch = document.getElementById('cycle-switch');
|
|
512
|
+
const offers = document.getElementById('cycle-offers');
|
|
513
|
+
if (!cycleSwitch || !offers) return;
|
|
514
|
+
|
|
515
|
+
// Trailing zero cents are noise on a price - "24 Euro", not "24,00
|
|
516
|
+
// Euro". The tabular figures keep the row from jumping anyway.
|
|
517
|
+
const money = (n) => {
|
|
518
|
+
const fixed = n.toFixed(2);
|
|
519
|
+
return fixed.endsWith('.00')
|
|
520
|
+
? fixed.slice(0, -3)
|
|
521
|
+
: fixed.replace('.', ',');
|
|
522
|
+
};
|
|
523
|
+
|
|
524
|
+
const apply = (button) => {
|
|
525
|
+
const months = Number(button.dataset.cycle);
|
|
526
|
+
const off = Number(button.dataset.cycleOff || 0);
|
|
527
|
+
|
|
528
|
+
cycleSwitch.querySelectorAll('.mw-segmented-item').forEach((item) => {
|
|
529
|
+
const on = item === button;
|
|
530
|
+
item.classList.toggle('mw-active', on);
|
|
531
|
+
item.setAttribute('aria-pressed', String(on));
|
|
532
|
+
});
|
|
533
|
+
|
|
534
|
+
offers.querySelectorAll('.mw-offer').forEach((card) => {
|
|
535
|
+
const rate = Number(card.dataset.rate);
|
|
536
|
+
const perMonth = rate * (1 - off / 100);
|
|
537
|
+
const [whole, cents] = money(perMonth).split(',');
|
|
538
|
+
|
|
539
|
+
card.querySelector('[data-price-amount]').innerHTML = cents
|
|
540
|
+
? whole + '<span class="mw-price-fraction">,' + cents + '</span>'
|
|
541
|
+
: whole;
|
|
542
|
+
|
|
543
|
+
const original = card.querySelector('.mw-price-original');
|
|
544
|
+
original.hidden = off === 0;
|
|
545
|
+
original.querySelector('[data-price-rate]').textContent =
|
|
546
|
+
money(rate);
|
|
547
|
+
|
|
548
|
+
const tag = card.querySelector('[data-price-off]');
|
|
549
|
+
tag.hidden = off === 0;
|
|
550
|
+
tag.textContent = '-' + off + '%';
|
|
551
|
+
|
|
552
|
+
card.querySelector('[data-price-billing]').textContent =
|
|
553
|
+
off === 0
|
|
554
|
+
? button.dataset.cycleLabel + '.'
|
|
555
|
+
: money(perMonth * months) +
|
|
556
|
+
' Euro ' +
|
|
557
|
+
button.dataset.cycleLabel +
|
|
558
|
+
'.';
|
|
559
|
+
});
|
|
560
|
+
};
|
|
561
|
+
|
|
562
|
+
cycleSwitch.querySelectorAll('.mw-segmented-item').forEach((item) => {
|
|
563
|
+
item.addEventListener('click', () => apply(item));
|
|
564
|
+
});
|
|
565
|
+
apply(cycleSwitch.querySelector('.mw-active'));
|
|
566
|
+
});
|
|
567
|
+
|
|
500
568
|
// for the modals
|
|
501
569
|
function openModal(modalId) {
|
|
502
570
|
document.getElementById(modalId).classList.add('mw-modal-open');
|