This commit is contained in:
Philippe Torrel
2026-10-09 10:54:32 +02:00
parent 19dc8aed74
commit df640e0af2
42 changed files with 528 additions and 39 deletions
+1
View File
@@ -22,6 +22,7 @@
- [HTMLReference.io](https://htmlreference.io/)
- [BEM](https://getbem.com/) - Block Element Modifier
- <https://devtoys.pro/de/blog/keycode-reference-guide>
## CSS Tricks
- [CSSReference.io](https://cssreference.io/)
@@ -0,0 +1,22 @@
# Übung 18: Cuteycat
Der Katzenfreundeverein Cuteycat hat einen Auftrag für dich. Der Verein möchte auf seiner Website einen Wettbewerb ausrichten. Die Seite zeigt eine Reihe von Katzen, aus denen ein Besucher jeweils die drei niedlichsten auswählen soll.
1. Sobald ein Besucher auf eine der Katzen klickt, soll diese auch in der rechten Box (Cutest Cats) erscheinen. Sie darf aber aus der Liste der Katzen (Our Cats) nicht verschwinden!
2. Achte darauf, dass sich die gleiche Katze nicht zweimal auswählen lässt.
3. Es darf nicht mehr als drei niedlichste Katzen geben.
4. Benutzer sollen eine Katze in der Gewinnerbox (Cutest Cats) anklicken können, um sie wieder herauszunehmen.
**Hinweise**
Versuche die Übung ohne .innerHTML zu lösen!
Unter der URL <http://loremflickr.com/150/150/cute,kitten/all> kannst du weitere zufällige Katzenbilder generieren.
Die Seite <http://fantasynamegenerators.com/cat-names.php> produziert zufällige Katzennamen.
Verwende `.cloneNode(true)` auf den `li`-Elementen, um das Entfernen der Katzen aus Our Cats zu verhindern.
Registriere den Event-Handler auf den `li`-Elementen. Falls der Benutzer aber nicht direkt das `li` anklickt, sondern nur Bild oder Text (Name der Katze), so zeigt event.target auf das angeklickte Element. Mittels event.currentTarget kannst du aber immer noch auf das `li` zugreifen. `currentTarget` enthält immer das Element, auf dem der Handler tatsächlich registriert wurde.
@@ -0,0 +1,76 @@
@charset "UTF-8";
/* Color Theme Swatches in Hex */
/* Color Theme Swatches in RGBA */
*,
html {
box-sizing: border-box;
}
html,
body {
height: 100%;
background-color: #f7eedf;
}
.m-cutey-cat h2 {
color: #7aa67a;
}
.m-cutey-cat .box-cat-selection {
position: fixed;
}
.m-cutey-cat ul.list {
margin: 0;
padding: 0;
list-style: none;
font-family: Georgia, "Times New Roman", Times, serif;
font-size: 2rem;
}
.m-cutey-cat ul.list li {
min-width: 400px;
background-color: #feaea5;
margin: 0 0 10px;
user-select: none;
border-radius: 16px;
overflow: hidden;
box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.1);
transition: all 0.4s cubic-bezier(0.52, 0.02, 0.43, 1.39);
}
.m-cutey-cat ul.list li:not(.selected):hover {
background-color: #fd8173;
box-shadow: 0 0 0 rgba(0, 0, 0, 0.1);
transform: scale(1.1);
}
.m-cutey-cat ul.list li:not(.selected):hover img {
border-width: 0;
}
.m-cutey-cat ul.list li img {
width: 120px;
border: 10px solid rgba(255, 255, 255, 0.6);
transition: all 0.4s;
}
.m-cutey-cat ul.list li span {
color: white;
padding: 0 1rem;
letter-spacing: 2px;
}
.m-cutey-cat ul.list.list-cat-vote li.selected {
background-color: #ffdbd7;
opacity: 0.5;
}
.m-cutey-cat ul.list.list-cat-selection li {
background-color: #b7cfb7;
}
.m-cutey-cat ul.list.list-cat-selection li:hover {
background-color: #98bb98;
}
.m-cutey-cat ul.list.list-cat-selection li:hover::after {
position: absolute;
top: 30px;
right: 1.5rem;
content: "";
font-family: "Font Awesome 5 Free";
font-weight: 900;
color: white;
}
/*# sourceMappingURL=main.css.map */
@@ -0,0 +1 @@
{"version":3,"sourceRoot":"","sources":["../scss/main.scss"],"names":[],"mappings":";AAEA;AAOA;AAWA;AAAA;EAEE;;;AAGF;AAAA;EAEE;EACA,kBAzBa;;;AA+Bb;EACE;;AAGF;EACE;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA,kBA7CS;EA8CT;EACA;EACA;EACA;EACA;EACA;;AACA;EACE;EACA;EACA;;AAEA;EACE;;AAIJ;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;;AAMA;EACE;EACA;;AAMJ;EACE,kBAxFO;;AAyFP;EACE;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA","file":"main.css"}
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

@@ -0,0 +1,17 @@
const $ = (qs) => document.querySelector(qs);
const $$ = (qs) => Array.from(document.querySelectorAll(qs));
const $on = (el, ev, fn) => {
Array.isArray(el) ? el.forEach((ae) => $on(ae, ev, fn)) : el.addEventListener(ev, fn);
return el;
};
Node.prototype.on = function (name, fn) {
this.addEventListener(name, fn);
return this;
};
Array.prototype.on = Array.prototype.addEventListener = function (name, fn) {
this.forEach((elem) => elem.addEventListener(name, fn));
return this;
};
File diff suppressed because one or more lines are too long
@@ -0,0 +1,92 @@
'use strict';
(() => {
// === DOM & VARS =======
const DOM = {
voteCat: document.querySelector('.list-cat-vote'),
winnerCat: document.querySelector('.list-cat-selection'),
};
// === INIT =============
const init = () => {
DOM.voteCat.addEventListener('click', (e) => {
check(e, 'move');
});
DOM.winnerCat.addEventListener('click', (e) => {
check(e, 'remove');
});
};
// === EVENTHANDLER =====
const check = (e, type = '') => {
const { target } = e;
const li = target.closest('li');
if (type === '') {
return;
}
if (!li) {
return;
}
if (type === 'move') {
moveCat(li);
}
if (type === 'remove') {
remove(li);
}
};
// === XHR/FETCH ========
// === FUNCTIONS ========
const moveCat = (li) => {
const isDuplicate = checkDuplicate(li.querySelector('img').getAttribute('src'));
if (isDuplicate) {
return;
}
if (DOM.winnerCat.childElementCount > 2) {
remove(DOM.winnerCat.querySelector('li:first-child'));
}
DOM.winnerCat.appendChild(li.cloneNode(true));
};
const remove = (el) => {
el.remove();
};
const checkDuplicate = (imgSrc) => {
return !!DOM.winnerCat.querySelector(`img[src="${imgSrc}"]`);
};
init();
})();
// Übung 18: Cuteycat
// Der Katzenfreundeverein Cuteycat hat einen Auftrag für dich. Der Verein möchte auf seiner Website einen Wettbewerb ausrichten. Die Seite zeigt eine Reihe von Katzen, aus denen ein Besucher jeweils die drei niedlichsten auswählen soll.
// 1. Sobald ein Besucher auf eine der Katzen klickt, soll diese auch in der rechten Box (Cutest Cats) erscheinen. Sie darf aber aus der Liste der Katzen (Our Cats) nicht verschwinden!
// 2. Achte darauf, dass sich die gleiche Katze nicht zweimal auswählen lässt.
// 3. Es darf nicht mehr als drei niedlichste Katzen geben.
// 4. Benutzer sollen eine Katze in der Gewinnerbox (Cutest Cats) anklicken können, um sie wieder herauszunehmen.
// Hinweise
// Versuche die Übung ohne .innerHTML zu lösen!
// Unter der URL http://loremflickr.com/150/150/cute,kitten/all kannst du weitere zufällige Katzenbilder generieren.
// Die Seite http://fantasynamegenerators.com/cat-names.php produziert zufällige Katzennamen.
// Verwende .cloneNode(true) auf den li-Elementen, um das Entfernen der Katzen aus Our Cats zu verhindern.
// Registriere den Event-Handler auf den li-Elementen. Falls der Benutzer aber nicht direkt das li anklickt, sondern nur Bild oder Text (Name der Katze), so zeigt event.target auf das angeklickte Element. Mittels event.currentTarget kannst du aber immer noch auf das li zugreifen. currentTarget enthält immer das Element, auf dem der Handler tatsächlich registriert wurde.
@@ -0,0 +1,103 @@
// sass assets/scss/main.scss:assets/css/main.css --style=expanded --watch
/* Color Theme Swatches in Hex */
$color-theme1: #f7eedf;
$color-theme2: #b7cfb7;
$color-theme3: #ffcdb6;
$color-theme4: #feaea5;
$color-theme5: #ffc7a2;
$primary-color: #efefef;
// Allgemeine Formatierung =================================
*,
html {
box-sizing: border-box;
}
html,
body {
height: 100%;
background-color: $color-theme1;
}
// Module cutey cat ======================
.m-cutey-cat {
h2 {
color: darken($color-theme2, 20);
}
.box-cat-selection {
position: fixed;
}
ul.list {
margin: 0;
padding: 0;
list-style: none;
font-family: Georgia, 'Times New Roman', Times, serif;
font-size: 2rem;
li {
min-width: 400px;
background-color: $color-theme4;
margin: 0 0 10px;
user-select: none;
border-radius: 16px;
overflow: hidden;
box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.1);
transition: all 0.4s cubic-bezier(0.52, 0.02, 0.43, 1.39);
&:not(.selected):hover {
background-color: darken($color-theme4, 10);
box-shadow: 0 0 0 rgba(0, 0, 0, 0.1);
transform: scale(1.1);
img {
border-width: 0;
}
}
img {
width: 120px;
border: 10px solid rgba(white, 0.6);
transition: all 0.4s;
}
span {
color: white;
padding: 0 1rem;
letter-spacing: 2px;
}
}
&.list-cat-vote {
li {
&.selected {
background-color: lighten($color-theme4, 10);
opacity: 0.5;
}
}
}
&.list-cat-selection {
li {
background-color: $color-theme2;
&:hover {
background-color: darken($color-theme2, 10);
&::after {
position: absolute;
top: 30px;
right: 1.5rem;
content: '\f1f8';
font-family: 'Font Awesome 5 Free';
font-weight: 900;
color: white;
}
}
}
}
}
}
@@ -0,0 +1,59 @@
<!doctype html>
<html lang="de">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Cutey Cats</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/7.0.0/css/all.min.css" />
<link rel="stylesheet" href="assets/css/main.css" />
<script src="assets/js/main.js" defer></script>
</head>
<body>
<main>
<div class="container py-5">
<h1>Cutey Cats</h1>
</div>
<section class="section-cutey-cat">
<div class="m-cutey-cat">
<div class="container">
<div class="row">
<div class="col">
<div class="box-cat-vote">
<h2>Our Cats</h2>
<ul class="list list-cat-vote">
<li><img src="assets/img/kittens/1.jpg" alt="cat 1" /><span>Chance</span></li>
<li><img src="assets/img/kittens/2.jpg" alt="cat 2" /><span>MeowMix</span></li>
<li><img src="assets/img/kittens/3.jpg" alt="cat 3" /><span>Stripes</span></li>
<li><img src="assets/img/kittens/4.jpg" alt="cat 4" /><span>Schrodinger</span></li>
<li><img src="assets/img/kittens/5.jpg" alt="cat 5" /><span>Waffles</span></li>
<li><img src="assets/img/kittens/6.jpg" alt="cat 6" /><span>Tazmina</span></li>
<li><img src="assets/img/kittens/7.jpg" alt="cat 7" /><span>Wiggle</span></li>
<li><img src="assets/img/kittens/8.jpg" alt="cat 8" /><span>Gin</span></li>
<li><img src="assets/img/kittens/9.jpg" alt="cat 9" /><span>Paws</span></li>
<li><img src="assets/img/kittens/10.jpg" alt="cat 10" /><span>Marshmallow</span></li>
<li><img src="assets/img/kittens/11.jpg" alt="cat 11" /><span>Caesar</span></li>
<li><img src="assets/img/kittens/12.jpg" alt="cat 12" /><span>Trogdor</span></li>
<li><img src="assets/img/kittens/13.jpg" alt="cat 13" /><span>Sourpuss</span></li>
<li><img src="assets/img/kittens/14.jpg" alt="cat 14" /><span>Stitch</span></li>
<li><img src="assets/img/kittens/15.jpg" alt="cat 15" /><span>Sunshine</span></li>
</ul>
</div>
</div>
<div class="col">
<div class="box-cat-selection">
<h2>Cutest Cats</h2>
<ul class="list list-cat-selection"></ul>
</div>
</div>
</div>
</div>
</div>
</section>
</main>
</body>
</html>
@@ -2,16 +2,56 @@
(() => {
// === DOM & VARS =======
const DOM = {};
const module = document.querySelector('.m-cutey-cat') || console.warn('Module: "cutey-cat" not found');
const DOM = {
module,
catsVote: Array.from(module.querySelectorAll('.list-cat-vote li')),
cutestCatsList: module.querySelector('.list-cat-selection'),
};
// === INIT =============
const init = () => {};
const init = () => {
DOM.catsVote.forEach((catItem) => {
catItem.addEventListener('click', onClickVote);
});
};
// === EVENTHANDLER =====
const onClickVote = (e) => {
const listEl = e.currentTarget;
selectedCat(listEl);
};
const onClickRemove = (e) => {
const listItem = e.currentTarget;
listItem.remove();
};
// === XHR/FETCH ========
// === FUNCTIONS ========
const selectedCat = (listItem) => {
// Guard
if (DOM.cutestCatsList.children.length >= 3 || isInSelection(listItem)) {
return;
}
const li = listItem.cloneNode(true);
li.addEventListener('click', onClickRemove);
DOM.cutestCatsList.appendChild(li);
};
const isInSelection = (el) => {
const catImage = el.querySelector('img').getAttribute('src');
const alreadySelected = Array.from(DOM.cutestCatsList.children).some((catLi) => {
return catLi.querySelector('img').getAttribute('src') === catImage;
});
return alreadySelected;
};
init();
})();
+1 -1
View File
@@ -1,4 +1,4 @@
<!DOCTYPE html>
<!doctype html>
<html lang="de">
<head>
<meta charset="UTF-8" />
@@ -79,12 +79,12 @@
// removeChild() // IE11 support
list.removeChild($('ul.list li'));
// classList
// classList - DOMTokenList
list.classList.add('list-group');
list.classList.remove('andere-klasse');
list.classList.toggle('active');
// dataset & attribute
// dataset - DOMStringMap & attribute
list.dataset.id = 'list'; // <ul data-id="list">...</ul>
list.setAttribute('aria-label', 'Hauptliste');
list.removeAttribute('aria-label');
@@ -36,6 +36,8 @@
// document.querySelectorAll('css-selector')
console.log(Array.from(document.querySelectorAll('ul.list'))); //=> [<li>Listenpunkt 01</li>, ...]
// document.body.images
// =======================
// document.getElementById('ID');
@@ -2,4 +2,5 @@ const PRODUCTS = [
{ name: '3Doodler 3D Printing Pen', price: 29.99 },
{ name: 'Powerstation 5- E. Maximus Chargus', price: 44.95 },
{ name: '8-Bit Legendary Hero Heat-Change Mug', price: 6.99 },
{ name: '16-Bit Legendary Hero Heat-Change Mug', price: 12.99 },
];
@@ -1,5 +1,6 @@
[
{ "name": "3Doodler 3D Printing Pen", "price": 29.99 },
{ "name": "Powerstation 5- E. Maximus Chargus", "price": 44.95 },
{ "name": "8-Bit Legendary Hero Heat-Change Mug", "price": 6.99 }
{ "name": "8-Bit Legendary Hero Heat-Change Mug", "price": 6.99 },
{ "name": "16-Bit Legendary Hero Heat-Change Mug", "price": 12.99 }
]
@@ -25,6 +25,7 @@
<th class="th-actions">Actions</th>
</tr>
</thead>
<!-- products via js -->
<tbody></tbody>
<tfoot>
<tr>
@@ -154,6 +154,7 @@ const ProductManager = (el = null, options = {}) => {
return {
element: DOM.module,
init,
initProducts,
};
};
@@ -37,7 +37,7 @@
<hr />
<div class="box" id="source-box"></div>
<div class="target-box"></div>
<div class="target-box" ondra></div>
</div>
</main>
<script>
@@ -0,0 +1,27 @@
const CreateBtn = (options = {}) => {
const {
classNames = ['btn-primary'],
iconName = 'circle',
label,
clickHandler = (e) => {
console.log('no click action');
},
} = options;
const btn = document.createElement('button');
const icon = document.createElement('i');
const span = document.createElement('span');
btn.classList.add('btn', ...classNames);
icon.classList.add('fas', `fa-${iconName}`);
span.classList.add('visually-hidden');
span.textContent = label;
btn.addEventListener('click', clickHandler);
btn.appendChild(icon);
btn.appendChild(span);
return btn;
};
export default CreateBtn;
@@ -18,17 +18,28 @@ const ProductManager = (el = null, options = {}) => {
templateRow: module.querySelector('.template-row'),
};
console.log(DOM);
// console.log(DOM);
// === INIT =============
const init = () => {
// Funktionsaufrufe zu beginn der Anwendung
initProducts();
DOM.btnAdd.disabled = true;
// Event-Lauscher zu beginn der Anwendung
DOM.btnAdd.addEventListener('click', onClickAdd);
window.addEventListener('keyup', onKeyUp);
};
// === EVENTHANDLER =====
const onKeyUp = (e) => {
DOM.btnAdd.disabled = !(DOM.inputName.value !== '' && DOM.inputPrice.value !== '');
// if (DOM.inputName.value !== '' && DOM.inputPrice.value !== '') {
// DOM.btnAdd.disabled = false;
// } else {
// DOM.btnAdd.disabled = true;
// }
};
const onClickAdd = (e) => {
const product = {
name: DOM.inputName.value, //
@@ -36,6 +47,8 @@ const ProductManager = (el = null, options = {}) => {
};
addProduct(product);
DOM.inputName.value = DOM.inputPrice.value = '';
DOM.btnAdd.disabled = true;
disableNonFunctionalButtons();
};
@@ -51,14 +64,62 @@ const ProductManager = (el = null, options = {}) => {
const onClickMoveUp = (e) => {
const btn = e.currentTarget;
const currentRow = parents(btn, 'tr')[0];
DOM.tbody.insertBefore(currentRow, currentRow.previousElementSibling);
currentRow.previousElementSibling.before(currentRow);
// DOM.tbody.insertBefore(currentRow, currentRow.previousElementSibling);
disableNonFunctionalButtons();
};
const onClickMoveDown = (e) => {
const btn = e.currentTarget;
const currentRow = parents(btn, 'tr')[0];
DOM.tbody.insertBefore(currentRow, currentRow.nextElementSibling.nextElementSibling);
currentRow.nextElementSibling.after(currentRow);
// DOM.tbody.insertBefore(currentRow, currentRow.nextElementSibling.nextElementSibling);
disableNonFunctionalButtons();
};
const onMouseDownDrag = (e) => {
const btn = e.currentTarget;
const currentRow = parents(btn, 'tr')[0]; // btn.closest('tr'); // btn.parentNode.parentNode;
currentRow.draggable = true; // => <tr draggable="true" >...</tr>
};
const onMouseUpDrag = (e) => {
const btn = e.currentTarget;
const currentRow = parents(btn, 'tr')[0]; // btn.closest('tr'); // btn.parentNode.parentNode;
currentRow.draggable = false; // => <tr draggable="false" >...</tr>
};
const onDragStart = (e) => {
// console.log('DragEvent: ', e);
const currentRow = e.currentTarget;
const idx = [...currentRow.parentNode.children].indexOf(currentRow);
e.dataTransfer.setData('text/plain', String(idx));
// console.log('tr reference index: ', idx);
};
const onDragOver = (e) => {
e.preventDefault(); // WICHTIG: sonst funktioniet drop-EventHandler nicht
};
const onDrop = (e) => {
const currentRow = e.currentTarget; // <- Zielzeile, aud die gedroppt wird
const targetIdx = [...currentRow.parentNode.children].indexOf(currentRow);
const sourceIdx = Number(e.dataTransfer.getData('text/plain'));
console.log({ targetIdx, sourceIdx });
//const draggedRow = DOM.tbody.querySelector(`tr:nth-child(${sourceIdx + 1})`);
const draggedRow = DOM.tbody.children[sourceIdx];
// Guard wenn Element auf sich selbst losgelassen wurde, abbruch
if (sourceIdx === targetIdx) return;
// Verschiebe das Element im DOM
if (sourceIdx > targetIdx) {
currentRow.before(draggedRow);
} else {
currentRow.after(draggedRow);
}
disableNonFunctionalButtons();
};
@@ -99,7 +160,13 @@ const ProductManager = (el = null, options = {}) => {
const btnMoveUpEl = trEl.querySelector('.button-product-move-up');
const btnMoveDownEl = trEl.querySelector('.button-product-move-down');
// TODO: Optional Drag 'n Drop hinzufügen.
// Drag 'n Drop hinzufügen.
btnDragEl.addEventListener('mousedown', onMouseDownDrag);
btnDragEl.addEventListener('mouseup', onMouseUpDrag);
trEl.addEventListener('dragstart', onDragStart);
trEl.addEventListener('dragover', onDragOver);
trEl.addEventListener('drop', onDrop);
tdNameEl.textContent = name;
tdPriceEl.textContent = Number(price).toFixed(2);
@@ -110,32 +177,6 @@ const ProductManager = (el = null, options = {}) => {
DOM.tbody.appendChild(trEl);
};
// const createBtn = (options = {}) => {
// const {
// classNames = ['btn-primary'],
// iconName = 'circle',
// label,
// clickHandler = (e) => {
// console.log('no click action');
// },
// } = options;
// const btn = document.createElement('button');
// const icon = document.createElement('i');
// const span = document.createElement('span');
// btn.classList.add('btn', ...classNames);
// icon.classList.add('fas', `fa-${iconName}`);
// span.classList.add('visually-hidden');
// span.textContent = label;
// btn.addEventListener('click', clickHandler);
// btn.appendChild(icon);
// btn.appendChild(span);
// return btn;
// };
const disableNonFunctionalButtons = () => {
DOM.tbody.querySelectorAll('tr').forEach((tr) => {
tr.querySelector('.button-product-move-up').disabled = !tr.previousElementSibling;
@@ -1,5 +1,7 @@
[
{ "name": "3Doodler 3D Printing Pen", "price": 29.99 },
{ "name": "Powerstation 5- E. Maximus Chargus", "price": 44.95 },
{ "name": "8-Bit Legendary Hero Heat-Change Mug", "price": 6.99 }
{ "name": "8-Bit Legendary Hero Heat-Change Mug", "price": 6.99 },
{ "name": "16-Bit Legendary Hero Heat-Change Mug", "price": 12.99 },
{ "name": "32-Bit Legendary Hero Heat-Change Mug", "price": 14.99 }
]
@@ -1,5 +1,5 @@
{
"name": "product-manager-tpl-json",
"name": "product-manager-final",
"version": "1.0.0",
"description": "",
"main": "index.js",