maverick-wave 5.28.0 → 5.29.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.
Files changed (49) hide show
  1. package/.claude/skills/mw-maverick-wave/SKILL.md +4 -2
  2. package/.claude/skills/mw-maverick-wave/references/components.md +212 -0
  3. package/.claude/skills/mw-maverick-wave/references/javascript.md +6 -1
  4. package/.claude/skills/mw-maverick-wave/references/theming.md +53 -51
  5. package/CHANGELOG.md +10 -0
  6. package/CLAUDE.md +4 -4
  7. package/README.md +2 -2
  8. package/index.html +65 -0
  9. package/maverick-wave.min.css +2 -2
  10. package/maverick-wave.min.js +1 -1
  11. package/package.json +2 -2
  12. package/scripts/verify.js +2 -1
  13. package/src/assets/images/photo-balloon.svg +35 -0
  14. package/src/assets/images/photo-forest-path.svg +31 -0
  15. package/src/assets/images/photo-lighthouse.svg +42 -0
  16. package/src/assets/images/photo-palm.svg +27 -0
  17. package/src/assets/images/photo-peak.svg +27 -0
  18. package/src/assets/images/photo-tower.svg +34 -0
  19. package/src/assets/images/photo-waterfall.svg +32 -0
  20. package/src/assets/images/story-aurora.svg +36 -0
  21. package/src/assets/images/story-beach.svg +34 -0
  22. package/src/assets/images/story-stars.svg +35 -0
  23. package/src/assets/media/story-sunrise.jpg +0 -0
  24. package/src/assets/media/story-sunrise.mp4 +0 -0
  25. package/src/js/main.js +403 -0
  26. package/src/partials/avatars-container.html +50 -0
  27. package/src/partials/calendar-container.html +36 -38
  28. package/src/partials/feed-container.html +506 -0
  29. package/src/partials/item-lists-container.html +80 -130
  30. package/src/partials/kanban-container.html +17 -294
  31. package/src/partials/mosaic-container.html +173 -0
  32. package/src/partials/panels-container.html +113 -162
  33. package/src/partials/portrait-gallery-container.html +175 -0
  34. package/src/partials/stories-container.html +182 -0
  35. package/src/partials/tables-container.html +21 -40
  36. package/src/scss/abstracts/_mixins.scss +2 -0
  37. package/src/scss/abstracts/_variables.scss +3 -0
  38. package/src/scss/components/_avatars.scss +52 -0
  39. package/src/scss/components/_cards.scss +7 -3
  40. package/src/scss/components/_feed.scss +203 -0
  41. package/src/scss/components/_index.scss +6 -0
  42. package/src/scss/components/_lightbox.scss +178 -0
  43. package/src/scss/components/_mosaic.scss +83 -0
  44. package/src/scss/components/_overlay-btn.scss +47 -0
  45. package/src/scss/components/_portrait-gallery.scss +180 -0
  46. package/src/scss/components/_stories.scss +443 -0
  47. package/src/scss/components/_tiles.scss +4 -1
  48. package/src/scss/utilities/_fit.scss +29 -0
  49. package/src/scss/utilities/_index.scss +1 -0
package/src/js/main.js CHANGED
@@ -45,6 +45,7 @@
45
45
  initSmoothScrolling();
46
46
  initScrollSpy();
47
47
  initModals();
48
+ initLightbox();
48
49
  initDropdowns();
49
50
  initTagRemovals();
50
51
  initCopyButtons();
@@ -58,6 +59,9 @@
58
59
  const scope = root || document;
59
60
 
60
61
  initGalleries(scope);
62
+ initPortraitGalleries(scope);
63
+ initMosaics(scope);
64
+ initStoryReels(scope);
61
65
  initAccordions(scope);
62
66
  initProgressBars(scope);
63
67
  initReveals(scope);
@@ -406,6 +410,405 @@
406
410
  goToSlide(0);
407
411
  }
408
412
 
413
+ const ICON_PATHS = {
414
+ close: 'M6 6l12 12M18 6L6 18',
415
+ prev: 'M15 5l-7 7 7 7',
416
+ next: 'M9 5l7 7-7 7',
417
+ };
418
+
419
+ function iconButton(className, label, name) {
420
+ const button = document.createElement('button');
421
+ button.type = 'button';
422
+ button.className = className;
423
+ button.setAttribute('aria-label', label);
424
+ button.innerHTML =
425
+ '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="' +
426
+ ICON_PATHS[name] +
427
+ '"/></svg>';
428
+ return button;
429
+ }
430
+
431
+ // The natural size once loaded, the width and height attributes before
432
+ function mediaRatio(media) {
433
+ const width =
434
+ media.naturalWidth || media.videoWidth || +media.getAttribute('width');
435
+ const height =
436
+ media.naturalHeight || media.videoHeight || +media.getAttribute('height');
437
+ return width && height ? width / height : 0;
438
+ }
439
+
440
+ // Read from the scroll position, so swipe, trackpad and arrows agree
441
+ function stripControls(track, step, prev, next, onIndex) {
442
+ function update() {
443
+ const end = track.scrollWidth - track.clientWidth - 2;
444
+ const last = track.children.length - 1;
445
+ onIndex(
446
+ track.scrollLeft >= end ? last : Math.round(track.scrollLeft / step())
447
+ );
448
+ prev.disabled = track.scrollLeft <= 2;
449
+ next.disabled = track.scrollLeft >= end;
450
+ }
451
+
452
+ prev.addEventListener('click', () => track.scrollBy({ left: -step() }));
453
+ next.addEventListener('click', () => track.scrollBy({ left: step() }));
454
+ track.addEventListener('scroll', update, { passive: true });
455
+ return update;
456
+ }
457
+
458
+ // Past 1.4x off the frame's shape, a crop would cut away most of the photo
459
+ function fitMedia(frame, media) {
460
+ if (frame.classList.contains('mw-fit-cover')) return;
461
+ const video = media.tagName === 'VIDEO';
462
+ const forced = frame.classList.contains('mw-fit-contain');
463
+
464
+ function apply() {
465
+ if (!forced) {
466
+ const ratio = mediaRatio(media);
467
+ if (!ratio || !frame.clientHeight) return;
468
+
469
+ const off = ratio / (frame.clientWidth / frame.clientHeight);
470
+ frame.classList.toggle('mw-fit-contain', Math.max(off, 1 / off) >= 1.4);
471
+ }
472
+
473
+ const src = video ? media.poster : media.currentSrc || media.src;
474
+ if (
475
+ frame.classList.contains('mw-fit-contain') &&
476
+ src &&
477
+ !frame.querySelector(':scope > .mw-fit-backdrop')
478
+ ) {
479
+ const backdrop = document.createElement('img');
480
+ backdrop.className = 'mw-fit-backdrop';
481
+ backdrop.alt = '';
482
+ backdrop.src = src;
483
+ frame.prepend(backdrop);
484
+ }
485
+ }
486
+
487
+ // Watched, not measured once: a frame in a closed tab has no shape yet
488
+ new ResizeObserver(apply).observe(frame);
489
+ media.addEventListener(video ? 'loadedmetadata' : 'load', apply);
490
+ }
491
+
492
+ // ===== Portrait Gallery =====
493
+ function initPortraitGalleries(root) {
494
+ root.querySelectorAll('.mw-portrait-gallery').forEach((gallery) => {
495
+ if (fresh(gallery)) initPortraitGallery(gallery);
496
+ });
497
+ }
498
+
499
+ function initPortraitGallery(gallery) {
500
+ const track = gallery.querySelector('.mw-portrait-gallery-track');
501
+ if (!track) return;
502
+ const slides = Array.from(track.children);
503
+
504
+ slides.forEach((slide) => {
505
+ const media = slide.querySelector(':scope > img, :scope > video');
506
+ if (media) fitMedia(slide, media);
507
+ });
508
+
509
+ if (slides.length < 2) return;
510
+
511
+ const prev = iconButton(
512
+ 'mw-overlay-btn mw-portrait-gallery-prev',
513
+ 'Previous image',
514
+ 'prev'
515
+ );
516
+ const next = iconButton(
517
+ 'mw-overlay-btn mw-portrait-gallery-next',
518
+ 'Next image',
519
+ 'next'
520
+ );
521
+ const dots = document.createElement('div');
522
+ dots.className = 'mw-portrait-gallery-dots';
523
+
524
+ // Measured on every use: the slide width follows the container query
525
+ const step = () => slides[1].offsetLeft - slides[0].offsetLeft;
526
+
527
+ slides.forEach((slide, i) => {
528
+ const dot = document.createElement('button');
529
+ dot.type = 'button';
530
+ dot.className = 'mw-portrait-gallery-dot';
531
+ dot.setAttribute('aria-label', 'Go to image ' + (i + 1));
532
+ dot.addEventListener('click', () => track.scrollTo({ left: i * step() }));
533
+ dots.appendChild(dot);
534
+ });
535
+
536
+ gallery.append(prev, next, dots);
537
+ stripControls(track, step, prev, next, (index) => {
538
+ Array.from(dots.children).forEach((dot, i) => {
539
+ dot.classList.toggle('mw-active', i === index);
540
+ });
541
+ })();
542
+ }
543
+
544
+ // ===== Mosaic =====
545
+ function initMosaics(root) {
546
+ root.querySelectorAll('.mw-mosaic-item').forEach((item) => {
547
+ if (!fresh(item)) return;
548
+ const img = item.querySelector('img');
549
+ const shaped = ['mw-mosaic-wide', 'mw-mosaic-tall', 'mw-mosaic-big'].some(
550
+ (name) => item.classList.contains(name)
551
+ );
552
+ if (!img || shaped) return;
553
+
554
+ function shape() {
555
+ const ratio = mediaRatio(img);
556
+ if (ratio >= 1.3) item.classList.add('mw-mosaic-wide');
557
+ else if (ratio && ratio <= 0.8) item.classList.add('mw-mosaic-tall');
558
+ return ratio > 0;
559
+ }
560
+
561
+ if (!shape()) img.addEventListener('load', shape, { once: true });
562
+ });
563
+ }
564
+
565
+ // ===== Lightbox =====
566
+ function initLightbox() {
567
+ let lightbox = null;
568
+
569
+ document.addEventListener('click', (e) => {
570
+ const link = e.target.closest('a[href]');
571
+ const group = link && link.closest('.mw-mosaic, [data-mw-lightbox]');
572
+ if (!group || !link.querySelector('img') || e.defaultPrevented) return;
573
+ // A click meant to open the photo elsewhere stays a link
574
+ if (e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) {
575
+ return;
576
+ }
577
+ e.preventDefault();
578
+
579
+ // In reading order, which a dense grid does not keep to the source one
580
+ const links = Array.from(group.querySelectorAll('a[href]'))
581
+ .filter((a) => a.querySelector('img'))
582
+ .map((a) => ({ a, box: a.getBoundingClientRect() }))
583
+ .sort(
584
+ (x, y) => Math.round(x.box.top - y.box.top) || x.box.left - y.box.left
585
+ )
586
+ .map(({ a }) => a);
587
+ lightbox = lightbox || createLightbox();
588
+ lightbox.open(links, links.indexOf(link));
589
+ });
590
+ }
591
+
592
+ function createLightbox() {
593
+ const dialog = document.createElement('dialog');
594
+ dialog.className = 'mw-lightbox';
595
+ dialog.setAttribute('aria-label', 'Image viewer');
596
+
597
+ const bar = document.createElement('div');
598
+ bar.className = 'mw-lightbox-bar';
599
+ const prev = iconButton(
600
+ 'mw-overlay-btn mw-lightbox-prev',
601
+ 'Previous image',
602
+ 'prev'
603
+ );
604
+ const next = iconButton(
605
+ 'mw-overlay-btn mw-lightbox-next',
606
+ 'Next image',
607
+ 'next'
608
+ );
609
+ const close = iconButton(
610
+ 'mw-overlay-btn mw-lightbox-close',
611
+ 'Close',
612
+ 'close'
613
+ );
614
+ close.autofocus = true;
615
+ const count = document.createElement('span');
616
+ count.className = 'mw-lightbox-count';
617
+ count.setAttribute('aria-live', 'polite');
618
+ const track = document.createElement('div');
619
+ track.className = 'mw-lightbox-track';
620
+
621
+ bar.append(prev, count, next, close);
622
+ dialog.append(bar, track);
623
+ document.body.appendChild(dialog);
624
+
625
+ let links = [];
626
+ let current = -1;
627
+
628
+ function load(i) {
629
+ const img = track.children[i] && track.children[i].querySelector('img');
630
+ if (img && !img.src) img.src = img.dataset.src;
631
+ }
632
+
633
+ const update = stripControls(
634
+ track,
635
+ () => track.clientWidth,
636
+ prev,
637
+ next,
638
+ (index) => {
639
+ if (index === current) return;
640
+ current = index;
641
+ count.textContent = index + 1 + ' / ' + links.length;
642
+ [index - 1, index, index + 1].forEach(load);
643
+ }
644
+ );
645
+
646
+ close.addEventListener('click', () => dialog.close());
647
+
648
+ // The stage around the photo is the backdrop here
649
+ track.addEventListener('click', (e) => {
650
+ if (e.target.classList.contains('mw-lightbox-slide')) dialog.close();
651
+ });
652
+
653
+ dialog.addEventListener('keydown', (e) => {
654
+ if (e.key === 'ArrowLeft') prev.click();
655
+ else if (e.key === 'ArrowRight') next.click();
656
+ });
657
+
658
+ // Back on the tile of the photo the reader stopped at, not the one opened
659
+ dialog.addEventListener('close', () => {
660
+ // The event arrives a task late; a viewer opened again by then stays
661
+ if (dialog.open) return;
662
+ if (links[current]) links[current].focus();
663
+ });
664
+
665
+ return {
666
+ open(list, index) {
667
+ links = list;
668
+ current = -1;
669
+ track.replaceChildren(...list.map(lightboxSlide));
670
+ dialog.showModal();
671
+ track.scrollTo({
672
+ left: index * track.clientWidth,
673
+ behavior: 'instant',
674
+ });
675
+ update();
676
+ },
677
+ };
678
+ }
679
+
680
+ function lightboxSlide(link) {
681
+ const slide = document.createElement('figure');
682
+ slide.className = 'mw-lightbox-slide';
683
+ const img = document.createElement('img');
684
+ img.dataset.src = link.href;
685
+ img.alt = link.querySelector('img').alt;
686
+ img.decoding = 'async';
687
+ slide.appendChild(img);
688
+
689
+ const figure = link.closest('figure');
690
+ const caption = figure && figure.querySelector('figcaption');
691
+ if (caption) {
692
+ const text = document.createElement('figcaption');
693
+ caption.childNodes.forEach((node) => text.append(node.cloneNode(true)));
694
+ slide.appendChild(text);
695
+ }
696
+ return slide;
697
+ }
698
+
699
+ // ===== Story Reels =====
700
+ // CSS plays the story; this adds tap steps, video on its turn, close at end
701
+ function initStoryReels(root) {
702
+ root.querySelectorAll('.mw-story-reel').forEach((reel) => {
703
+ if (fresh(reel)) initStoryReel(reel);
704
+ });
705
+ }
706
+
707
+ function initStoryReel(reel) {
708
+ const frames = Array.from(
709
+ reel.querySelectorAll(':scope > img, :scope > video')
710
+ );
711
+ const back = iconButton('mw-story-reel-prev', 'Previous', 'prev');
712
+ const forward = iconButton('mw-story-reel-next', 'Next', 'next');
713
+ const pause = reel.querySelector('.mw-story-reel-pause input');
714
+ const close = reel.querySelector('[popovertargetaction="hide"]');
715
+ // The popover focuses it on open, so the arrow keys reach the reel at once
716
+ if (close) close.autofocus = true;
717
+ reel.append(back, forward);
718
+
719
+ const clocks = () =>
720
+ reel
721
+ .getAnimations({ subtree: true })
722
+ .filter((animation) => animation instanceof CSSAnimation);
723
+
724
+ // The fill runs through the whole story, so it tells the current frame
725
+ function current() {
726
+ const fill = clocks().find(
727
+ (animation) => animation.animationName === 'mw-story-reel-fill'
728
+ );
729
+ if (!fill) return null;
730
+ // Frames past the ones the stylesheet times are hidden and do not count
731
+ const count = frames.filter(
732
+ (frame) => getComputedStyle(frame).display !== 'none'
733
+ ).length;
734
+ const length = fill.effect.getTiming().duration / count;
735
+ return { count, length, index: Math.floor(fill.currentTime / length) };
736
+ }
737
+
738
+ function play(frame) {
739
+ frames.forEach((other) => {
740
+ if (other.tagName === 'VIDEO' && other !== frame) other.pause();
741
+ });
742
+ if (!frame || frame.tagName !== 'VIDEO') return;
743
+ frame.currentTime = 0;
744
+ if (!pause || !pause.checked) frame.play().catch(() => {});
745
+ }
746
+
747
+ function step(delta) {
748
+ const now = current();
749
+ if (!now) return;
750
+ const target = Math.max(now.index + delta, 0);
751
+ if (target >= now.count) {
752
+ reel.hidePopover();
753
+ return;
754
+ }
755
+ // Past the 1ms reveal by a margin, or rounding lands just short of it
756
+ const time = target * now.length + 20;
757
+ clocks().forEach((animation) => (animation.currentTime = time));
758
+ play(frames[target]);
759
+ }
760
+
761
+ // The video on screen keeps to the story's clock: held or paused, it stops
762
+ function syncVideo(held) {
763
+ const now = reel.matches(':popover-open') && current();
764
+ const frame = now && frames[now.index];
765
+ if (!frame || frame.tagName !== 'VIDEO') return;
766
+ if (held || (pause && pause.checked)) frame.pause();
767
+ else frame.play().catch(() => {});
768
+ }
769
+
770
+ // A hold pauses the story in CSS; letting go must not also step it
771
+ let pressed = 0;
772
+ reel.addEventListener('pointerdown', (e) => {
773
+ pressed = e.timeStamp;
774
+ syncVideo(true);
775
+ });
776
+ ['pointerup', 'pointercancel'].forEach((type) => {
777
+ reel.addEventListener(type, () => syncVideo(false));
778
+ });
779
+
780
+ function onTap(delta) {
781
+ return (e) => {
782
+ if (e.detail && e.timeStamp - pressed > 250) return;
783
+ step(delta);
784
+ };
785
+ }
786
+
787
+ back.addEventListener('click', onTap(-1));
788
+ forward.addEventListener('click', onTap(1));
789
+
790
+ // A seek backwards ends the reveals it skips too; only the current frame plays
791
+ reel.addEventListener('animationend', (e) => {
792
+ if (e.animationName === 'mw-story-reel-fill') {
793
+ reel.hidePopover();
794
+ } else if (e.animationName === 'mw-story-reel-frame') {
795
+ const now = current();
796
+ if (now && frames[now.index] === e.target) play(e.target);
797
+ }
798
+ });
799
+
800
+ reel.addEventListener('toggle', (e) => {
801
+ play(e.newState === 'open' ? frames[0] : null);
802
+ });
803
+
804
+ if (pause) pause.addEventListener('change', () => syncVideo(false));
805
+
806
+ reel.addEventListener('keydown', (e) => {
807
+ if (e.key === 'ArrowLeft') step(-1);
808
+ else if (e.key === 'ArrowRight') step(1);
809
+ });
810
+ }
811
+
409
812
  // ===== Theme Toggle =====
410
813
  function initThemeToggle() {
411
814
  const themeToggle = document.querySelector('.mw-theme-toggle');
@@ -3,6 +3,10 @@
3
3
  A picture or a pair of initials, in five sizes. The colour variants sit on the
4
4
  border, so they still read over a photo; on an initials avatar they fill the
5
5
  circle as well. Inside a group the overlap follows the avatar size.
6
+ <code>mw-avatar-landscape</code> (3:2) and
7
+ <code>mw-avatar-portrait</code> (9:16, 2:3 on <code>-lg</code>) are for a
8
+ photo that is not square - meant for the two large sizes. A landscape avatar
9
+ is as tall as the round one.
6
10
  </p>
7
11
 
8
12
  <div class="mw-grid-2">
@@ -177,3 +181,49 @@
177
181
  </div>
178
182
  </div>
179
183
  </div>
184
+
185
+ <div class="mw-grid-2 mw-mt-6">
186
+ <div>
187
+ <h3 class="mw-mb-4">Landscape</h3>
188
+ <div class="mw-card mw-card-simple">
189
+ <div class="mw-grid-stack-center">
190
+ <div class="mw-avatar mw-avatar-lg mw-avatar-landscape">
191
+ <img
192
+ src="https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?auto=format&fit=crop&crop=faces&w=720&h=480&q=80"
193
+ alt="Avatar"
194
+ loading="lazy"
195
+ />
196
+ </div>
197
+ <div class="mw-avatar mw-avatar-xl mw-avatar-landscape">
198
+ <img
199
+ src="https://images.unsplash.com/photo-1494790108377-be9c29b29330?auto=format&fit=crop&crop=faces&w=720&h=480&q=80"
200
+ alt="Avatar"
201
+ loading="lazy"
202
+ />
203
+ </div>
204
+ </div>
205
+ </div>
206
+ </div>
207
+
208
+ <div>
209
+ <h3 class="mw-mb-4">Portrait</h3>
210
+ <div class="mw-card mw-card-simple">
211
+ <div class="mw-d-flex mw-flex-wrap mw-justify-center mw-items-end">
212
+ <div class="mw-avatar mw-avatar-lg mw-avatar-portrait">
213
+ <img
214
+ src="https://images.unsplash.com/photo-1500648767791-00dcc994a43e?auto=format&fit=crop&crop=faces&w=480&h=720&q=80"
215
+ alt="Avatar"
216
+ loading="lazy"
217
+ />
218
+ </div>
219
+ <div class="mw-avatar mw-avatar-xl mw-avatar-portrait">
220
+ <img
221
+ src="https://images.unsplash.com/photo-1438761681033-6461ffad8d80?auto=format&fit=crop&crop=faces&w=450&h=800&q=80"
222
+ alt="Avatar"
223
+ loading="lazy"
224
+ />
225
+ </div>
226
+ </div>
227
+ </div>
228
+ </div>
229
+ </div>
@@ -26,10 +26,26 @@
26
26
  </div>
27
27
 
28
28
  <div class="mw-grid-2 calendar-demos mw-my-6">
29
- <div>
29
+ <div class="calendar-demo">
30
30
  <h4 class="mw-mb-2">Month</h4>
31
+ <div class="mw-segmented mw-mb-4" data-modifier-demo="calendar-month">
32
+ <button
33
+ type="button"
34
+ class="mw-segmented-item mw-active"
35
+ data-modifier=""
36
+ >
37
+ Default
38
+ </button>
39
+ <button
40
+ type="button"
41
+ class="mw-segmented-item"
42
+ data-modifier="mw-calendar-compact"
43
+ >
44
+ Compact
45
+ </button>
46
+ </div>
31
47
  <div
32
- class="mw-calendar calendar-demo"
48
+ class="mw-calendar"
33
49
  id="calendar-month"
34
50
  data-calendar="month"
35
51
  data-calendar-locale="en-GB"
@@ -79,44 +95,26 @@
79
95
  </div>
80
96
  </div>
81
97
 
82
- <div>
83
- <h4 class="mw-mb-2">Compact Month</h4>
84
- <div
85
- class="mw-calendar mw-calendar-compact calendar-demo"
86
- id="calendar-compact"
87
- data-calendar="month"
88
- data-calendar-locale="en-GB"
89
- >
90
- <div class="mw-calendar-header">
91
- <button
92
- type="button"
93
- class="mw-btn mw-btn-outline mw-btn-sm mw-calendar-nav"
94
- data-calendar-nav="-1"
95
- aria-label="Previous month"
96
- >
97
- <i class="fas fa-chevron-left"></i>
98
- </button>
99
-
100
- <div class="mw-calendar-title" aria-live="polite"></div>
101
-
102
- <button
103
- type="button"
104
- class="mw-btn mw-btn-outline mw-btn-sm mw-calendar-nav"
105
- data-calendar-nav="1"
106
- aria-label="Next month"
107
- >
108
- <i class="fas fa-chevron-right"></i>
109
- </button>
110
- </div>
111
-
112
- <div class="mw-calendar-grid"></div>
113
- </div>
114
- </div>
115
-
116
- <div>
98
+ <div class="calendar-demo">
117
99
  <h4 class="mw-mb-2">Week</h4>
100
+ <div class="mw-segmented mw-mb-4" data-modifier-demo="calendar-week">
101
+ <button
102
+ type="button"
103
+ class="mw-segmented-item mw-active"
104
+ data-modifier=""
105
+ >
106
+ Default
107
+ </button>
108
+ <button
109
+ type="button"
110
+ class="mw-segmented-item"
111
+ data-modifier="mw-calendar-compact"
112
+ >
113
+ Compact
114
+ </button>
115
+ </div>
118
116
  <div
119
- class="mw-calendar calendar-demo"
117
+ class="mw-calendar"
120
118
  id="calendar-week"
121
119
  data-calendar="week"
122
120
  data-calendar-locale="en-GB"