mikuru 1.0.38 → 1.0.39

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 (75) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/components/MikuruAlertDialog.mikuru +183 -0
  3. package/components/MikuruAvatar.mikuru +60 -0
  4. package/components/MikuruAvatarGroup.mikuru +66 -0
  5. package/components/MikuruBadge.mikuru +62 -0
  6. package/components/MikuruBreadcrumb.mikuru +86 -0
  7. package/components/MikuruCalendar.mikuru +142 -0
  8. package/components/MikuruChip.mikuru +64 -0
  9. package/components/MikuruCodeBlock.mikuru +20 -13
  10. package/components/MikuruCodeView.mikuru +21 -0
  11. package/components/MikuruColorPicker.mikuru +63 -0
  12. package/components/MikuruCommandPalette.mikuru +197 -0
  13. package/components/MikuruContextMenu.mikuru +137 -0
  14. package/components/MikuruDataList.mikuru +61 -0
  15. package/components/MikuruDatePicker.mikuru +293 -0
  16. package/components/MikuruDrawer.mikuru +115 -0
  17. package/components/MikuruEmptyState.mikuru +72 -0
  18. package/components/MikuruFileUpload.mikuru +161 -0
  19. package/components/MikuruKbd.mikuru +28 -0
  20. package/components/MikuruMarkdownEditor.mikuru +561 -0
  21. package/components/MikuruPagination.mikuru +109 -0
  22. package/components/MikuruPopover.mikuru +152 -0
  23. package/components/MikuruRadioGroup.mikuru +111 -0
  24. package/components/MikuruRangeSlider.mikuru +96 -0
  25. package/components/MikuruRating.mikuru +72 -0
  26. package/components/MikuruSearchInput.mikuru +97 -0
  27. package/components/MikuruSegmentedControl.mikuru +70 -0
  28. package/components/MikuruSkeleton.mikuru +74 -0
  29. package/components/MikuruSlider.mikuru +77 -0
  30. package/components/MikuruStatCard.mikuru +63 -0
  31. package/components/MikuruStepper.mikuru +123 -0
  32. package/components/MikuruSwitch.mikuru +104 -0
  33. package/components/MikuruTable.mikuru +242 -0
  34. package/components/MikuruTagInput.mikuru +127 -0
  35. package/components/MikuruTimePicker.mikuru +61 -0
  36. package/components/MikuruTimeline.mikuru +93 -0
  37. package/components/MikuruTreeView.mikuru +72 -0
  38. package/components/MikuruWysiwygEditor.mikuru +259 -0
  39. package/package.json +289 -1
  40. package/types/components/MikuruAlertDialog.d.ts +16 -0
  41. package/types/components/MikuruAvatar.d.ts +12 -0
  42. package/types/components/MikuruAvatarGroup.d.ts +19 -0
  43. package/types/components/MikuruBadge.d.ts +11 -0
  44. package/types/components/MikuruBreadcrumb.d.ts +16 -0
  45. package/types/components/MikuruCalendar.d.ts +11 -0
  46. package/types/components/MikuruChip.d.ts +12 -0
  47. package/types/components/MikuruCodeView.d.ts +11 -0
  48. package/types/components/MikuruColorPicker.d.ts +11 -0
  49. package/types/components/MikuruCommandPalette.d.ts +20 -0
  50. package/types/components/MikuruContextMenu.d.ts +18 -0
  51. package/types/components/MikuruDataList.d.ts +17 -0
  52. package/types/components/MikuruDatePicker.d.ts +12 -0
  53. package/types/components/MikuruDrawer.d.ts +14 -0
  54. package/types/components/MikuruEmptyState.d.ts +12 -0
  55. package/types/components/MikuruFileUpload.d.ts +14 -0
  56. package/types/components/MikuruKbd.d.ts +9 -0
  57. package/types/components/MikuruMarkdownEditor.d.ts +15 -0
  58. package/types/components/MikuruPagination.d.ts +12 -0
  59. package/types/components/MikuruPopover.d.ts +13 -0
  60. package/types/components/MikuruRadioGroup.d.ts +21 -0
  61. package/types/components/MikuruRangeSlider.d.ts +15 -0
  62. package/types/components/MikuruRating.d.ts +13 -0
  63. package/types/components/MikuruSearchInput.d.ts +12 -0
  64. package/types/components/MikuruSegmentedControl.d.ts +18 -0
  65. package/types/components/MikuruSkeleton.d.ts +13 -0
  66. package/types/components/MikuruSlider.d.ts +15 -0
  67. package/types/components/MikuruStatCard.d.ts +12 -0
  68. package/types/components/MikuruStepper.d.ts +19 -0
  69. package/types/components/MikuruSwitch.d.ts +12 -0
  70. package/types/components/MikuruTable.d.ts +27 -0
  71. package/types/components/MikuruTagInput.d.ts +13 -0
  72. package/types/components/MikuruTimePicker.d.ts +12 -0
  73. package/types/components/MikuruTimeline.d.ts +17 -0
  74. package/types/components/MikuruTreeView.d.ts +17 -0
  75. package/types/components/MikuruWysiwygEditor.d.ts +12 -0
@@ -0,0 +1,152 @@
1
+ <template>
2
+ <span class="mikuru-popover" ref="rootEl" @keydown="handleKeydown">
3
+ <button
4
+ class="popover-trigger"
5
+ type="button"
6
+ :aria-expanded="isOpen ? 'true' : 'false'"
7
+ aria-haspopup="dialog"
8
+ @click="toggle"
9
+ >
10
+ {{ label }}
11
+ </button>
12
+
13
+ <span
14
+ m-if="isOpen"
15
+ class="popover-panel"
16
+ role="dialog"
17
+ :aria-label="title || label"
18
+ :data-side="side"
19
+ >
20
+ <strong m-if="title">{{ title }}</strong>
21
+ <slot>{{ body }}</slot>
22
+ </span>
23
+ </span>
24
+ </template>
25
+
26
+ <script>
27
+ import { onMounted, onUnmounted, ref } from "mikuru";
28
+
29
+ const {
30
+ label = "Open popover",
31
+ title = "",
32
+ body = "",
33
+ side = "bottom",
34
+ closeOnOutside = true
35
+ } = defineProps({
36
+ label: String,
37
+ title: String,
38
+ body: String,
39
+ side: String,
40
+ closeOnOutside: Boolean
41
+ });
42
+
43
+ const emit = defineEmits(["open", "close"]);
44
+ const rootEl = ref(null);
45
+ const isOpen = ref(false);
46
+
47
+ onMounted(() => {
48
+ document.addEventListener("pointerdown", handleDocumentPointer);
49
+ });
50
+
51
+ onUnmounted(() => {
52
+ document.removeEventListener("pointerdown", handleDocumentPointer);
53
+ });
54
+
55
+ function open() {
56
+ if (isOpen.value) return;
57
+ isOpen.value = true;
58
+ emit("open");
59
+ }
60
+
61
+ function close() {
62
+ if (!isOpen.value) return;
63
+ isOpen.value = false;
64
+ emit("close");
65
+ }
66
+
67
+ function toggle() {
68
+ if (isOpen.value) {
69
+ close();
70
+ return;
71
+ }
72
+ open();
73
+ }
74
+
75
+ function handleDocumentPointer(event) {
76
+ if (!closeOnOutside.value || !isOpen.value) return;
77
+ const root = rootEl.value;
78
+ if (!root || root.contains(event.target)) return;
79
+ close();
80
+ }
81
+
82
+ function handleKeydown(event) {
83
+ if (event.key === "Escape") {
84
+ close();
85
+ }
86
+ }
87
+ </script>
88
+
89
+ <style scoped>
90
+ .mikuru-popover {
91
+ position: relative;
92
+ display: inline-block;
93
+ }
94
+
95
+ .popover-trigger {
96
+ border: 1px solid #cbd5e1;
97
+ border-radius: 8px;
98
+ padding: 9px 12px;
99
+ color: #111827;
100
+ background: #ffffff;
101
+ font: inherit;
102
+ cursor: pointer;
103
+ }
104
+
105
+ .popover-trigger:hover,
106
+ .popover-trigger:focus-visible {
107
+ border-color: #2563eb;
108
+ outline: 3px solid rgb(37 99 235 / 16%);
109
+ }
110
+
111
+ .popover-panel {
112
+ position: absolute;
113
+ z-index: 45;
114
+ display: grid;
115
+ gap: 8px;
116
+ width: max-content;
117
+ max-width: min(280px, 82vw);
118
+ border: 1px solid #e5e7eb;
119
+ border-radius: 8px;
120
+ padding: 12px;
121
+ color: #334155;
122
+ background: #ffffff;
123
+ box-shadow: 0 18px 48px rgb(15 23 42 / 16%);
124
+ }
125
+
126
+ .popover-panel,
127
+ .popover-panel[data-side="bottom"] {
128
+ top: calc(100% + 8px);
129
+ left: 0;
130
+ }
131
+
132
+ .popover-panel[data-side="top"] {
133
+ top: auto;
134
+ bottom: calc(100% + 8px);
135
+ left: 0;
136
+ }
137
+
138
+ .popover-panel[data-side="right"] {
139
+ top: 0;
140
+ left: calc(100% + 8px);
141
+ }
142
+
143
+ .popover-panel[data-side="left"] {
144
+ top: 0;
145
+ left: auto;
146
+ right: calc(100% + 8px);
147
+ }
148
+
149
+ .popover-panel strong {
150
+ color: #111827;
151
+ }
152
+ </style>
@@ -0,0 +1,111 @@
1
+ <template>
2
+ <fieldset class="mikuru-radio-group">
3
+ <legend>{{ label }}</legend>
4
+ <label
5
+ m-for="option in options"
6
+ :key="option.value"
7
+ class="radio-option"
8
+ :data-checked="isChecked(option) ? 'true' : 'false'"
9
+ :data-disabled="option.disabled || disabled ? 'true' : 'false'"
10
+ >
11
+ <input
12
+ type="radio"
13
+ :name="groupName"
14
+ :value="option.value"
15
+ :checked="isChecked(option)"
16
+ :disabled="option.disabled || disabled"
17
+ @change="selectOption(option)"
18
+ />
19
+ <span>
20
+ <strong>{{ option.label }}</strong>
21
+ <small m-if="option.description">{{ option.description }}</small>
22
+ </span>
23
+ </label>
24
+ <small m-if="help" class="group-help">{{ help }}</small>
25
+ </fieldset>
26
+ </template>
27
+
28
+ <script>
29
+ const {
30
+ label = "Options",
31
+ modelValue = "",
32
+ options = [],
33
+ name = "",
34
+ help = "",
35
+ disabled = false
36
+ } = defineProps({
37
+ label: String,
38
+ modelValue: String,
39
+ options: Array,
40
+ name: String,
41
+ help: String,
42
+ disabled: Boolean
43
+ });
44
+
45
+ const emit = defineEmits(["update:modelValue", "change"]);
46
+ const groupName = name.value || `mikuru-radio-${Math.random().toString(36).slice(2)}`;
47
+
48
+ function isChecked(option) {
49
+ return option.value === modelValue.value;
50
+ }
51
+
52
+ function selectOption(option) {
53
+ if (disabled.value || option.disabled) return;
54
+ emit("update:modelValue", option.value);
55
+ emit("change", option.value, option);
56
+ }
57
+ </script>
58
+
59
+ <style scoped>
60
+ .mikuru-radio-group {
61
+ display: grid;
62
+ gap: 8px;
63
+ margin: 0;
64
+ border: 0;
65
+ padding: 0;
66
+ color: #111827;
67
+ font: inherit;
68
+ }
69
+
70
+ legend {
71
+ margin-bottom: 2px;
72
+ padding: 0;
73
+ font-weight: 650;
74
+ }
75
+
76
+ .radio-option {
77
+ display: grid;
78
+ grid-template-columns: auto 1fr;
79
+ gap: 10px;
80
+ align-items: start;
81
+ border: 1px solid #cbd5e1;
82
+ border-radius: 8px;
83
+ padding: 10px;
84
+ background: #ffffff;
85
+ cursor: pointer;
86
+ }
87
+
88
+ .radio-option[data-checked="true"] {
89
+ border-color: #2563eb;
90
+ background: #eff6ff;
91
+ }
92
+
93
+ .radio-option[data-disabled="true"] {
94
+ opacity: 0.55;
95
+ cursor: not-allowed;
96
+ }
97
+
98
+ input {
99
+ margin-top: 3px;
100
+ accent-color: #2563eb;
101
+ }
102
+
103
+ .radio-option span {
104
+ display: grid;
105
+ gap: 2px;
106
+ }
107
+
108
+ small {
109
+ color: #64748b;
110
+ }
111
+ </style>
@@ -0,0 +1,96 @@
1
+ <template>
2
+ <section class="mikuru-range-slider">
3
+ <header>
4
+ <span>{{ label }}</span>
5
+ <output>{{ minValue }} - {{ maxValue }}</output>
6
+ </header>
7
+ <div class="range-controls">
8
+ <input type="range" :min="min" :max="max" :step="step" :value="minValue" @input="updateMin($event)" />
9
+ <input type="range" :min="min" :max="max" :step="step" :value="maxValue" @input="updateMax($event)" />
10
+ </div>
11
+ <small m-if="help">{{ help }}</small>
12
+ </section>
13
+ </template>
14
+
15
+ <script>
16
+ import { ref, watch } from "mikuru";
17
+
18
+ const {
19
+ label = "Range",
20
+ minValue: initialMinValue = 0,
21
+ maxValue: initialMaxValue = 100,
22
+ min = 0,
23
+ max = 100,
24
+ step = 1,
25
+ help = ""
26
+ } = defineProps({
27
+ label: String,
28
+ minValue: Number,
29
+ maxValue: Number,
30
+ min: Number,
31
+ max: Number,
32
+ step: Number,
33
+ help: String
34
+ });
35
+
36
+ const emit = defineEmits(["change", "update:minValue", "update:maxValue"]);
37
+ const minValue = ref(initialMinValue.value);
38
+ const maxValue = ref(initialMaxValue.value);
39
+
40
+ watch([initialMinValue, initialMaxValue], () => {
41
+ minValue.value = initialMinValue.value;
42
+ maxValue.value = initialMaxValue.value;
43
+ });
44
+
45
+ function updateMin(event) {
46
+ const nextValue = Math.min(Number(event.target.value), maxValue.value);
47
+ minValue.value = nextValue;
48
+ emitValues();
49
+ }
50
+
51
+ function updateMax(event) {
52
+ const nextValue = Math.max(Number(event.target.value), minValue.value);
53
+ maxValue.value = nextValue;
54
+ emitValues();
55
+ }
56
+
57
+ function emitValues() {
58
+ emit("update:minValue", minValue.value);
59
+ emit("update:maxValue", maxValue.value);
60
+ emit("change", { min: minValue.value, max: maxValue.value });
61
+ }
62
+ </script>
63
+
64
+ <style scoped>
65
+ .mikuru-range-slider {
66
+ display: grid;
67
+ gap: 8px;
68
+ color: #111827;
69
+ font: inherit;
70
+ }
71
+
72
+ header {
73
+ display: flex;
74
+ justify-content: space-between;
75
+ gap: 12px;
76
+ font-weight: 650;
77
+ }
78
+
79
+ output {
80
+ color: #2563eb;
81
+ }
82
+
83
+ .range-controls {
84
+ display: grid;
85
+ gap: 4px;
86
+ }
87
+
88
+ input {
89
+ width: 100%;
90
+ accent-color: #2563eb;
91
+ }
92
+
93
+ small {
94
+ color: #64748b;
95
+ }
96
+ </style>
@@ -0,0 +1,72 @@
1
+ <template>
2
+ <div class="mikuru-rating" role="radiogroup" :aria-label="label">
3
+ <button
4
+ m-for="item in ratingItems"
5
+ :key="item"
6
+ type="button"
7
+ role="radio"
8
+ :aria-checked="item === modelValue ? 'true' : 'false'"
9
+ :data-active="item <= modelValue ? 'true' : 'false'"
10
+ :disabled="disabled"
11
+ @click="setRating(item)"
12
+ >{{ symbol }}</button>
13
+ </div>
14
+ </template>
15
+
16
+ <script>
17
+ import { computed } from "mikuru";
18
+
19
+ const {
20
+ label = "Rating",
21
+ modelValue = 0,
22
+ max = 5,
23
+ symbol = "★",
24
+ disabled = false
25
+ } = defineProps({
26
+ label: String,
27
+ modelValue: Number,
28
+ max: Number,
29
+ symbol: String,
30
+ disabled: Boolean
31
+ });
32
+
33
+ const emit = defineEmits(["update:modelValue", "change"]);
34
+ const ratingItems = computed(() => Array.from({ length: Math.max(1, max.value) }, (_, index) => index + 1));
35
+
36
+ function setRating(value) {
37
+ if (disabled.value) return;
38
+ emit("update:modelValue", value);
39
+ emit("change", value);
40
+ }
41
+ </script>
42
+
43
+ <style scoped>
44
+ .mikuru-rating {
45
+ display: inline-flex;
46
+ gap: 2px;
47
+ }
48
+
49
+ button {
50
+ border: 0;
51
+ padding: 2px;
52
+ color: #cbd5e1;
53
+ background: transparent;
54
+ font: inherit;
55
+ font-size: 1.35rem;
56
+ line-height: 1;
57
+ cursor: pointer;
58
+ }
59
+
60
+ button[data-active="true"] {
61
+ color: #f59e0b;
62
+ }
63
+
64
+ button:focus-visible {
65
+ border-radius: 6px;
66
+ outline: 3px solid rgb(37 99 235 / 18%);
67
+ }
68
+
69
+ button:disabled {
70
+ cursor: not-allowed;
71
+ }
72
+ </style>
@@ -0,0 +1,97 @@
1
+ <template>
2
+ <form class="mikuru-search-input" role="search" @submit="submitSearch">
3
+ <label :for="inputId">{{ label }}</label>
4
+ <div class="search-control">
5
+ <span aria-hidden="true">⌕</span>
6
+ <input
7
+ :id="inputId"
8
+ type="search"
9
+ :value="modelValue"
10
+ :placeholder="placeholder"
11
+ :disabled="disabled"
12
+ @input="updateValue($event)"
13
+ />
14
+ <button m-if="modelValue" type="button" @click="clear">Clear</button>
15
+ </div>
16
+ </form>
17
+ </template>
18
+
19
+ <script>
20
+ const {
21
+ label = "Search",
22
+ modelValue = "",
23
+ placeholder = "Search...",
24
+ disabled = false
25
+ } = defineProps({
26
+ label: String,
27
+ modelValue: String,
28
+ placeholder: String,
29
+ disabled: Boolean
30
+ });
31
+
32
+ const emit = defineEmits(["update:modelValue", "input", "submit", "clear"]);
33
+ const inputId = `mikuru-search-${Math.random().toString(36).slice(2)}`;
34
+
35
+ function updateValue(event) {
36
+ emit("update:modelValue", event.target.value);
37
+ emit("input", event.target.value);
38
+ }
39
+
40
+ function submitSearch(event) {
41
+ event.preventDefault();
42
+ emit("submit", modelValue.value);
43
+ }
44
+
45
+ function clear() {
46
+ emit("update:modelValue", "");
47
+ emit("input", "");
48
+ emit("clear");
49
+ }
50
+ </script>
51
+
52
+ <style scoped>
53
+ .mikuru-search-input {
54
+ display: grid;
55
+ gap: 6px;
56
+ color: #111827;
57
+ font: inherit;
58
+ }
59
+
60
+ label {
61
+ font-weight: 650;
62
+ }
63
+
64
+ .search-control {
65
+ display: grid;
66
+ grid-template-columns: auto 1fr auto;
67
+ align-items: center;
68
+ gap: 8px;
69
+ border: 1px solid #cbd5e1;
70
+ border-radius: 8px;
71
+ padding: 8px 10px;
72
+ background: #ffffff;
73
+ }
74
+
75
+ .search-control:focus-within {
76
+ border-color: #2563eb;
77
+ outline: 3px solid rgb(37 99 235 / 18%);
78
+ }
79
+
80
+ input {
81
+ min-width: 0;
82
+ border: 0;
83
+ padding: 0;
84
+ color: #111827;
85
+ background: transparent;
86
+ font: inherit;
87
+ outline: none;
88
+ }
89
+
90
+ button {
91
+ border: 0;
92
+ color: #2563eb;
93
+ background: transparent;
94
+ font: inherit;
95
+ cursor: pointer;
96
+ }
97
+ </style>
@@ -0,0 +1,70 @@
1
+ <template>
2
+ <div class="mikuru-segmented-control" role="radiogroup" :aria-label="label">
3
+ <button
4
+ m-for="option in options"
5
+ :key="option.value"
6
+ type="button"
7
+ role="radio"
8
+ :aria-checked="isSelected(option) ? 'true' : 'false'"
9
+ :disabled="option.disabled || disabled"
10
+ @click="selectOption(option)"
11
+ >{{ option.label }}</button>
12
+ </div>
13
+ </template>
14
+
15
+ <script>
16
+ const {
17
+ label = "Segmented control",
18
+ modelValue = "",
19
+ options = [],
20
+ disabled = false
21
+ } = defineProps({
22
+ label: String,
23
+ modelValue: String,
24
+ options: Array,
25
+ disabled: Boolean
26
+ });
27
+
28
+ const emit = defineEmits(["update:modelValue", "change"]);
29
+
30
+ function isSelected(option) {
31
+ return option.value === modelValue.value;
32
+ }
33
+
34
+ function selectOption(option) {
35
+ if (disabled.value || option.disabled) return;
36
+ emit("update:modelValue", option.value);
37
+ emit("change", option.value, option);
38
+ }
39
+ </script>
40
+
41
+ <style scoped>
42
+ .mikuru-segmented-control {
43
+ display: inline-flex;
44
+ width: fit-content;
45
+ border: 1px solid #cbd5e1;
46
+ border-radius: 8px;
47
+ padding: 3px;
48
+ background: #f8fafc;
49
+ }
50
+
51
+ button {
52
+ border: 0;
53
+ border-radius: 6px;
54
+ padding: 7px 11px;
55
+ color: #334155;
56
+ background: transparent;
57
+ font: inherit;
58
+ cursor: pointer;
59
+ }
60
+
61
+ button[aria-checked="true"] {
62
+ color: #ffffff;
63
+ background: #2563eb;
64
+ }
65
+
66
+ button:disabled {
67
+ color: #94a3b8;
68
+ cursor: not-allowed;
69
+ }
70
+ </style>
@@ -0,0 +1,74 @@
1
+ <template>
2
+ <div class="mikuru-skeleton" :data-animated="animated ? 'true' : 'false'" :data-shape="shape" aria-hidden="true">
3
+ <span
4
+ m-for="line in skeletonLines"
5
+ :key="line.key"
6
+ :style="line.style"
7
+ ></span>
8
+ </div>
9
+ </template>
10
+
11
+ <script>
12
+ import { computed } from "mikuru";
13
+
14
+ const {
15
+ lines = 3,
16
+ width = "100%",
17
+ height = "12px",
18
+ shape = "text",
19
+ animated = true
20
+ } = defineProps({
21
+ lines: Number,
22
+ width: String,
23
+ height: String,
24
+ shape: String,
25
+ animated: Boolean
26
+ });
27
+
28
+ const skeletonLines = computed(() => {
29
+ const count = Math.max(1, Number(lines.value || 1));
30
+ return Array.from({ length: count }, (_, index) => ({
31
+ key: index + 1,
32
+ style: `width: ${index === count - 1 && count > 1 ? "72%" : width.value}; height: ${height.value};`
33
+ }));
34
+ });
35
+ </script>
36
+
37
+ <style scoped>
38
+ .mikuru-skeleton {
39
+ display: grid;
40
+ gap: 8px;
41
+ }
42
+
43
+ span {
44
+ display: block;
45
+ border-radius: 999px;
46
+ background: #e2e8f0;
47
+ }
48
+
49
+ .mikuru-skeleton[data-shape="card"] span {
50
+ border-radius: 8px;
51
+ }
52
+
53
+ .mikuru-skeleton[data-shape="circle"] span {
54
+ width: var(--skeleton-size, 44px);
55
+ height: var(--skeleton-size, 44px);
56
+ border-radius: 999px;
57
+ }
58
+
59
+ .mikuru-skeleton[data-animated="true"] span {
60
+ background: linear-gradient(90deg, #e2e8f0 0%, #f8fafc 44%, #e2e8f0 88%);
61
+ background-size: 220% 100%;
62
+ animation: skeleton-pulse 1.2s ease-in-out infinite;
63
+ }
64
+
65
+ @keyframes skeleton-pulse {
66
+ from {
67
+ background-position: 100% 0;
68
+ }
69
+
70
+ to {
71
+ background-position: -100% 0;
72
+ }
73
+ }
74
+ </style>