maverick-wave 5.27.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.
- package/.claude/skills/mw-maverick-wave/SKILL.md +4 -2
- package/.claude/skills/mw-maverick-wave/references/components.md +212 -0
- package/.claude/skills/mw-maverick-wave/references/javascript.md +6 -1
- package/.claude/skills/mw-maverick-wave/references/theming.md +53 -51
- package/CHANGELOG.md +17 -0
- package/CLAUDE.md +4 -4
- package/README.md +2 -2
- package/index.html +65 -0
- package/maverick-wave.min.css +4 -4
- package/maverick-wave.min.js +1 -1
- package/package.json +2 -2
- package/scripts/verify.js +2 -1
- package/src/assets/images/photo-balloon.svg +35 -0
- package/src/assets/images/photo-forest-path.svg +31 -0
- package/src/assets/images/photo-lighthouse.svg +42 -0
- package/src/assets/images/photo-palm.svg +27 -0
- package/src/assets/images/photo-peak.svg +27 -0
- package/src/assets/images/photo-tower.svg +34 -0
- package/src/assets/images/photo-waterfall.svg +32 -0
- package/src/assets/images/story-aurora.svg +36 -0
- package/src/assets/images/story-beach.svg +34 -0
- package/src/assets/images/story-stars.svg +35 -0
- package/src/assets/media/story-sunrise.jpg +0 -0
- package/src/assets/media/story-sunrise.mp4 +0 -0
- package/src/js/main.js +411 -8
- package/src/partials/avatars-container.html +50 -0
- package/src/partials/calendar-container.html +36 -38
- package/src/partials/feed-container.html +506 -0
- package/src/partials/item-lists-container.html +80 -130
- package/src/partials/kanban-container.html +17 -294
- package/src/partials/mosaic-container.html +173 -0
- package/src/partials/panels-container.html +113 -162
- package/src/partials/portrait-gallery-container.html +175 -0
- package/src/partials/stories-container.html +182 -0
- package/src/partials/tables-container.html +21 -40
- package/src/scss/abstracts/_mixins.scss +2 -0
- package/src/scss/abstracts/_variables.scss +9 -0
- package/src/scss/components/_avatars.scss +52 -0
- package/src/scss/components/_cards.scss +7 -3
- package/src/scss/components/_feed.scss +203 -0
- package/src/scss/components/_hero-badge.scss +48 -0
- package/src/scss/components/_index.scss +8 -0
- package/src/scss/components/_leader-row.scss +24 -18
- package/src/scss/components/_lightbox.scss +178 -0
- package/src/scss/components/_mosaic.scss +83 -0
- package/src/scss/components/_overlay-btn.scss +47 -0
- package/src/scss/components/_portrait-gallery.scss +180 -0
- package/src/scss/components/_stories.scss +443 -0
- package/src/scss/components/_tiles.scss +4 -1
- package/src/scss/components/_tooltip.scss +193 -0
- package/src/scss/layout/_footer.scss +46 -84
- package/src/scss/layout/_grid.scss +48 -45
- package/src/scss/layout/_header-reveal.scss +1 -1
- package/src/scss/layout/_header.scss +49 -66
- package/src/scss/layout/_home.scss +5 -2
- package/src/scss/layout/_main.scss +100 -337
- package/src/scss/layout/_parallax.scss +1 -1
- package/src/scss/layout/_section.scss +24 -17
- package/src/scss/utilities/_fit.scss +29 -0
- 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');
|
|
@@ -586,10 +989,10 @@
|
|
|
586
989
|
const announces = menuBtn.tagName === 'BUTTON';
|
|
587
990
|
|
|
588
991
|
function setOpen(open) {
|
|
589
|
-
menuBtn.classList.toggle('
|
|
590
|
-
navbar.classList.toggle('
|
|
992
|
+
menuBtn.classList.toggle('mw-active', open);
|
|
993
|
+
navbar.classList.toggle('mw-active', open);
|
|
591
994
|
// The scrim and the rules that pin the bar and the burger in place hang
|
|
592
|
-
// off this one class rather than off `:has(.mw-navbar.
|
|
995
|
+
// off this one class rather than off `:has(.mw-navbar.mw-active)` - a selector
|
|
593
996
|
// that has to re-match the whole document from a class change several
|
|
594
997
|
// levels down, which is where WebKit's invalidation has let it slip.
|
|
595
998
|
document.body.classList.toggle('mw-nav-open', open);
|
|
@@ -599,7 +1002,7 @@
|
|
|
599
1002
|
function toggleMenu(e) {
|
|
600
1003
|
e.preventDefault();
|
|
601
1004
|
e.stopPropagation();
|
|
602
|
-
setOpen(!navbar.classList.contains('
|
|
1005
|
+
setOpen(!navbar.classList.contains('mw-active'));
|
|
603
1006
|
}
|
|
604
1007
|
|
|
605
1008
|
// One listener, not two. The button carries touch-action: manipulation, so
|
|
@@ -610,7 +1013,7 @@
|
|
|
610
1013
|
|
|
611
1014
|
// The scrim is a body pseudo-element, so its taps land on the document
|
|
612
1015
|
document.addEventListener('click', function (e) {
|
|
613
|
-
if (!navbar.classList.contains('
|
|
1016
|
+
if (!navbar.classList.contains('mw-active')) return;
|
|
614
1017
|
if (navbar.contains(e.target) || menuBtn.contains(e.target)) return;
|
|
615
1018
|
setOpen(false);
|
|
616
1019
|
});
|
|
@@ -618,7 +1021,7 @@
|
|
|
618
1021
|
// Or Escape strands the keyboard user in a panel that is no longer there
|
|
619
1022
|
document.addEventListener('keydown', function (e) {
|
|
620
1023
|
if (e.key !== 'Escape') return;
|
|
621
|
-
if (!navbar.classList.contains('
|
|
1024
|
+
if (!navbar.classList.contains('mw-active')) return;
|
|
622
1025
|
setOpen(false);
|
|
623
1026
|
menuBtn.focus();
|
|
624
1027
|
});
|
|
@@ -891,8 +1294,8 @@
|
|
|
891
1294
|
const menuBtn = document.querySelector('.mw-menu-btn');
|
|
892
1295
|
const nav = document.querySelector('.mw-navbar');
|
|
893
1296
|
if (menuBtn && nav) {
|
|
894
|
-
menuBtn.classList.remove('
|
|
895
|
-
nav.classList.remove('
|
|
1297
|
+
menuBtn.classList.remove('mw-active');
|
|
1298
|
+
nav.classList.remove('mw-active');
|
|
896
1299
|
document.body.classList.remove('mw-nav-open');
|
|
897
1300
|
if (menuBtn.tagName === 'BUTTON') {
|
|
898
1301
|
menuBtn.setAttribute('aria-expanded', 'false');
|
|
@@ -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
|
|
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
|
|
117
|
+
class="mw-calendar"
|
|
120
118
|
id="calendar-week"
|
|
121
119
|
data-calendar="week"
|
|
122
120
|
data-calendar-locale="en-GB"
|