Compare commits
6 commits
84aa4cac17
...
0c8cc5000c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0c8cc5000c | ||
|
|
ad699f0365 | ||
|
|
957cf79e45 | ||
|
|
b3940bba08 | ||
|
|
28501fec7c | ||
|
|
c08662caf8 |
39 changed files with 2448 additions and 710 deletions
|
|
@ -6,7 +6,8 @@
|
||||||
"Bash(git commit:*)",
|
"Bash(git commit:*)",
|
||||||
"Bash(find:*)",
|
"Bash(find:*)",
|
||||||
"Bash(curl:*)",
|
"Bash(curl:*)",
|
||||||
"WebFetch(domain:snipcart.com)"
|
"WebFetch(domain:snipcart.com)",
|
||||||
|
"Bash(grep:*)"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
7
.gitignore
vendored
7
.gitignore
vendored
|
|
@ -51,4 +51,9 @@ Icon
|
||||||
|
|
||||||
# Guide d'intégration (contient des informations sensibles)
|
# Guide d'intégration (contient des informations sensibles)
|
||||||
# ---------------
|
# ---------------
|
||||||
GUIDE-INTEGRATION-MONDIAL-RELAY.md
|
GUIDE-INTEGRATION-MONDIAL-RELAY.md
|
||||||
|
|
||||||
|
# Claude settings
|
||||||
|
# ---------------
|
||||||
|
.claude
|
||||||
|
/.claude/*
|
||||||
68
assets/css/components/_shopify-buy-button.scss
Normal file
68
assets/css/components/_shopify-buy-button.scss
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
.product-purchase {
|
||||||
|
margin-top: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-stock-info {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stock-status {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
font-weight: 600;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stock-status.in-stock {
|
||||||
|
color: #00cc00;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stock-status.low-stock {
|
||||||
|
color: #ff9900;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stock-status.out-of-stock {
|
||||||
|
color: #ff3333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-add-to-cart {
|
||||||
|
font-family: "Open Sans", sans-serif;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 1rem;
|
||||||
|
color: #000000;
|
||||||
|
background-color: #00ff00;
|
||||||
|
border: none;
|
||||||
|
border-radius: 40px;
|
||||||
|
padding: 12px 34px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.3s ease;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-add-to-cart:hover:not(:disabled) {
|
||||||
|
background-color: #00e600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-add-to-cart:focus {
|
||||||
|
outline: 2px solid #00e600;
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-add-to-cart:disabled {
|
||||||
|
opacity: 0.6;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-add-to-cart.success {
|
||||||
|
background-color: #00cc00;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-add-to-cart.error {
|
||||||
|
background-color: #ff3333;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-add-to-cart.out-of-stock {
|
||||||
|
background-color: #cccccc;
|
||||||
|
color: #666666;
|
||||||
|
}
|
||||||
254
assets/css/components/_shopify-cart-drawer.scss
Normal file
254
assets/css/components/_shopify-cart-drawer.scss
Normal file
|
|
@ -0,0 +1,254 @@
|
||||||
|
/* Cart Drawer Styles */
|
||||||
|
.cart-drawer {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 9999;
|
||||||
|
pointer-events: none;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.3s ease;
|
||||||
|
color: #000;
|
||||||
|
|
||||||
|
&.is-open {
|
||||||
|
pointer-events: auto;
|
||||||
|
opacity: 1;
|
||||||
|
|
||||||
|
.cart-drawer__panel {
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__overlay {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__panel {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 420px;
|
||||||
|
background-color: #ffffff;
|
||||||
|
box-shadow: -2px 0 8px rgba(0, 0, 0, 0.15);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
transform: translateX(100%);
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 1.5rem;
|
||||||
|
border-bottom: 1px solid #e0e0e0;
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__close {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0.5rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: opacity 0.2s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__content {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 1.5rem;
|
||||||
|
|
||||||
|
&.is-loading {
|
||||||
|
opacity: 0.5;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__empty {
|
||||||
|
text-align: center;
|
||||||
|
padding: 3rem 1rem;
|
||||||
|
color: #666;
|
||||||
|
|
||||||
|
&.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__items {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
|
||||||
|
&.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__footer {
|
||||||
|
border-top: 1px solid #e0e0e0;
|
||||||
|
padding: 1.5rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__total {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 1.125rem;
|
||||||
|
font-weight: bold;
|
||||||
|
|
||||||
|
&-label {
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-amount {
|
||||||
|
color: #000;
|
||||||
|
font-size: 1.25rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__checkout-btn {
|
||||||
|
width: 100%;
|
||||||
|
font-family: "Open Sans", sans-serif;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 1rem;
|
||||||
|
color: #000000;
|
||||||
|
background-color: #00ff00;
|
||||||
|
border: none;
|
||||||
|
border-radius: 40px;
|
||||||
|
padding: 14px 34px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.3s ease;
|
||||||
|
|
||||||
|
&:hover:not(:disabled) {
|
||||||
|
background-color: #00e600;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
opacity: 0.6;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cart Item
|
||||||
|
.cart-item {
|
||||||
|
display: flex;
|
||||||
|
gap: 1rem;
|
||||||
|
padding: 1rem;
|
||||||
|
border: 1px solid #e0e0e0;
|
||||||
|
border-radius: 8px;
|
||||||
|
|
||||||
|
&__image {
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
object-fit: cover;
|
||||||
|
border-radius: 4px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__details {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__title {
|
||||||
|
font-weight: 600;
|
||||||
|
margin: 0;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__variant {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: #666;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__price {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__quantity {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
margin-top: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__qty-btn {
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
border: 1px solid #000;
|
||||||
|
background: #fff;
|
||||||
|
color: #000;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: bold;
|
||||||
|
transition: all 0.2s;
|
||||||
|
|
||||||
|
&:hover:not(:disabled) {
|
||||||
|
background-color: #000;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
opacity: 0.4;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__qty-value {
|
||||||
|
min-width: 30px;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__remove {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: #ff3333;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
text-decoration: underline;
|
||||||
|
align-self: flex-start;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: #cc0000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -43,14 +43,20 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.header-left,
|
.header-left {
|
||||||
.header-right {
|
|
||||||
width: 90px;
|
width: 90px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.header-right {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
.header-center {
|
.header-center {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
@ -75,4 +81,39 @@
|
||||||
color: var(--color-txt);
|
color: var(--color-txt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.header-cart-btn {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--color-txt);
|
||||||
|
padding: 0;
|
||||||
|
line-height: 1;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.25rem;
|
||||||
|
transition: opacity 0.2s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-cart-count {
|
||||||
|
font-weight: normal;
|
||||||
|
|
||||||
|
&:empty {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not(:empty)::before {
|
||||||
|
content: '(';
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not(:empty)::after {
|
||||||
|
content: ')';
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -524,13 +524,18 @@ main {
|
||||||
#site-header.is-shrinked .site-title {
|
#site-header.is-shrinked .site-title {
|
||||||
width: 80px !important;
|
width: 80px !important;
|
||||||
}
|
}
|
||||||
#site-header .header-left,
|
#site-header .header-left {
|
||||||
#site-header .header-right {
|
|
||||||
width: 90px;
|
width: 90px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
}
|
}
|
||||||
|
#site-header .header-right {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
#site-header .header-center {
|
#site-header .header-center {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
@ -553,6 +558,35 @@ main {
|
||||||
#site-header #toggle-lang li.is-selected {
|
#site-header #toggle-lang li.is-selected {
|
||||||
color: var(--color-txt);
|
color: var(--color-txt);
|
||||||
}
|
}
|
||||||
|
#site-header .header-cart-btn {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--color-txt);
|
||||||
|
padding: 0;
|
||||||
|
line-height: 1;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.25rem;
|
||||||
|
transition: opacity 0.2s;
|
||||||
|
}
|
||||||
|
#site-header .header-cart-btn:hover {
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
#site-header .header-cart-count {
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
#site-header .header-cart-count:empty {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
#site-header .header-cart-count:not(:empty)::before {
|
||||||
|
content: "(";
|
||||||
|
}
|
||||||
|
#site-header .header-cart-count:not(:empty)::after {
|
||||||
|
content: ")";
|
||||||
|
}
|
||||||
|
|
||||||
#site-footer {
|
#site-footer {
|
||||||
background-color: black;
|
background-color: black;
|
||||||
|
|
@ -1019,13 +1053,6 @@ body.is-fullscreen {
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.section__product,
|
|
||||||
.store__nav {
|
|
||||||
max-width: 1200px;
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.store__nav {
|
.store__nav {
|
||||||
padding-top: calc(var(--spacing) * 1);
|
padding-top: calc(var(--spacing) * 1);
|
||||||
padding-bottom: calc(var(--spacing) * 0.5);
|
padding-bottom: calc(var(--spacing) * 0.5);
|
||||||
|
|
@ -1035,11 +1062,17 @@ body.is-fullscreen {
|
||||||
.store__nav a {
|
.store__nav a {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
.store__nav a::before {
|
||||||
|
content: "← ";
|
||||||
|
}
|
||||||
.store__nav a:hover {
|
.store__nav a:hover {
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
.store__nav a::before {
|
@media screen and (max-width: 720px) {
|
||||||
content: "← ";
|
.store__nav a {
|
||||||
|
padding-top: 0;
|
||||||
|
font-size: var(--fs-small);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.section__product .details ul {
|
.section__product .details ul {
|
||||||
|
|
@ -1048,122 +1081,7 @@ body.is-fullscreen {
|
||||||
.section__product .details ul li {
|
.section__product .details ul li {
|
||||||
padding-bottom: 0.2em;
|
padding-bottom: 0.2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.product-options__list {
|
|
||||||
list-style: none;
|
|
||||||
display: flex;
|
|
||||||
gap: 2ch;
|
|
||||||
}
|
|
||||||
.product-options__list li {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
.product-options__list li input[type=radio] {
|
|
||||||
position: fixed;
|
|
||||||
opacity: 0;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
.product-options__list li label {
|
|
||||||
font-family: var(--title);
|
|
||||||
font-size: var(--fs-normal);
|
|
||||||
height: 4ch;
|
|
||||||
width: 4ch;
|
|
||||||
border-radius: 50%;
|
|
||||||
border: var(--border);
|
|
||||||
border-color: transparent;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
padding-top: 0px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.product-options__list li input[type=radio]:checked + label {
|
|
||||||
border-color: var(--color-txt);
|
|
||||||
}
|
|
||||||
.product-options__list li input[type=radio]:not(:checked) + label:hover {
|
|
||||||
border-color: var(--grey-600);
|
|
||||||
background-color: var(--grey-800);
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-gallery {
|
|
||||||
position: relative;
|
|
||||||
aspect-ratio: 4/3;
|
|
||||||
}
|
|
||||||
.product-gallery .swiper-slide {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
.product-gallery .swiper-slide figure {
|
|
||||||
aspect-ratio: 4/3;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
.product-gallery .swiper-slide figure img {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
-o-object-fit: contain;
|
|
||||||
object-fit: contain;
|
|
||||||
}
|
|
||||||
.product-gallery .swiper-button-prev,
|
|
||||||
.product-gallery .swiper-button-next {
|
|
||||||
color: var(--color-txt);
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
}
|
|
||||||
.product-gallery .swiper-button-prev:after,
|
|
||||||
.product-gallery .swiper-button-next:after {
|
|
||||||
font-size: 20px;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
.product-gallery .swiper-button-prev:hover,
|
|
||||||
.product-gallery .swiper-button-next:hover {
|
|
||||||
opacity: 0.7;
|
|
||||||
}
|
|
||||||
.product-gallery .swiper-pagination {
|
|
||||||
position: relative;
|
|
||||||
margin-top: calc(var(--spacing) * 0.5);
|
|
||||||
bottom: 0;
|
|
||||||
}
|
|
||||||
.product-gallery .swiper-pagination .swiper-pagination-bullet {
|
|
||||||
width: 8px;
|
|
||||||
height: 8px;
|
|
||||||
background: var(--grey-600);
|
|
||||||
opacity: 0.5;
|
|
||||||
transition: opacity 0.3s;
|
|
||||||
}
|
|
||||||
.product-gallery .swiper-pagination .swiper-pagination-bullet:hover {
|
|
||||||
opacity: 0.7;
|
|
||||||
}
|
|
||||||
.product-gallery .swiper-pagination .swiper-pagination-bullet-active {
|
|
||||||
background: var(--color-txt);
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero {
|
|
||||||
margin-bottom: calc(var(--spacing) * 1);
|
|
||||||
padding: calc(var(--spacing) * 0.5) 0;
|
|
||||||
border-top: var(--border-light);
|
|
||||||
border-bottom: var(--border-light);
|
|
||||||
}
|
|
||||||
.hero .p__baseline-big {
|
|
||||||
margin: 0;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.add-to-cart {
|
|
||||||
margin: 0;
|
|
||||||
border-bottom: var(--border-light);
|
|
||||||
padding: calc(var(--spacing) * 0.5) 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-options {
|
|
||||||
border-bottom: var(--border-light);
|
|
||||||
padding: calc(var(--spacing) * 0.25) 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (max-width: 720px) {
|
@media screen and (max-width: 720px) {
|
||||||
.store__nav a {
|
|
||||||
padding-top: 0;
|
|
||||||
font-size: var(--fs-small);
|
|
||||||
}
|
|
||||||
.section__product {
|
.section__product {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
@ -1202,7 +1120,7 @@ body.is-fullscreen {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@media screen and (min-width: 720px) {
|
@media screen and (min-width: 720px) {
|
||||||
.section__product {
|
.section__product .product-content {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr 1fr;
|
grid-template-columns: 1fr 1fr;
|
||||||
gap: calc(var(--padding-body) * 2);
|
gap: calc(var(--padding-body) * 2);
|
||||||
|
|
@ -1221,10 +1139,123 @@ body.is-fullscreen {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-options {
|
||||||
|
border-bottom: var(--border-light);
|
||||||
|
padding: calc(var(--spacing) * 0.25) 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-options__list {
|
||||||
|
list-style: none;
|
||||||
|
display: flex;
|
||||||
|
gap: 2ch;
|
||||||
|
}
|
||||||
|
.product-options__list li {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.product-options__list li input[type=radio] {
|
||||||
|
position: fixed;
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.product-options__list li input[type=radio]:checked + label {
|
||||||
|
border-color: var(--color-txt);
|
||||||
|
}
|
||||||
|
.product-options__list li input[type=radio]:not(:checked) + label:hover {
|
||||||
|
border-color: var(--grey-600);
|
||||||
|
background-color: var(--grey-800);
|
||||||
|
}
|
||||||
|
.product-options__list li label {
|
||||||
|
font-family: var(--title);
|
||||||
|
font-size: var(--fs-normal);
|
||||||
|
height: 4ch;
|
||||||
|
width: 4ch;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: var(--border);
|
||||||
|
border-color: transparent;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding-top: 0px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-gallery {
|
||||||
|
position: relative;
|
||||||
|
aspect-ratio: 4/3;
|
||||||
|
}
|
||||||
|
.product-gallery .swiper-slide {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.product-gallery .swiper-slide figure {
|
||||||
|
aspect-ratio: 4/3;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.product-gallery .swiper-slide figure img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
-o-object-fit: contain;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
@media screen and (min-width: 720px) {
|
||||||
.product-gallery .swiper-slide figure {
|
.product-gallery .swiper-slide figure {
|
||||||
width: calc(100% - 60px);
|
width: calc(100% - 60px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.product-gallery .swiper-button-prev,
|
||||||
|
.product-gallery .swiper-button-next {
|
||||||
|
color: var(--color-txt);
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
.product-gallery .swiper-button-prev:after,
|
||||||
|
.product-gallery .swiper-button-next:after {
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.product-gallery .swiper-button-prev:hover,
|
||||||
|
.product-gallery .swiper-button-next:hover {
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
.product-gallery .swiper-pagination {
|
||||||
|
position: relative;
|
||||||
|
margin-top: calc(var(--spacing) * 0.5);
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
.product-gallery .swiper-pagination .swiper-pagination-bullet {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
background: var(--grey-600);
|
||||||
|
opacity: 0.5;
|
||||||
|
transition: opacity 0.3s;
|
||||||
|
}
|
||||||
|
.product-gallery .swiper-pagination .swiper-pagination-bullet:hover {
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
.product-gallery .swiper-pagination .swiper-pagination-bullet.swiper-pagination-bullet-active {
|
||||||
|
background: var(--color-txt);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero {
|
||||||
|
margin-bottom: calc(var(--spacing) * 1);
|
||||||
|
padding: calc(var(--spacing) * 0.5) 0;
|
||||||
|
border-top: var(--border-light);
|
||||||
|
border-bottom: var(--border-light);
|
||||||
|
}
|
||||||
|
.hero .p__baseline-big {
|
||||||
|
margin: 0;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-to-cart {
|
||||||
|
margin: 0;
|
||||||
|
border-bottom: var(--border-light);
|
||||||
|
padding: calc(var(--spacing) * 0.5) 0;
|
||||||
|
}
|
||||||
|
|
||||||
[data-template=thanks] .thanks-page {
|
[data-template=thanks] .thanks-page {
|
||||||
min-height: 60vh;
|
min-height: 60vh;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
@ -1258,8 +1289,296 @@ body.is-fullscreen {
|
||||||
margin-top: calc(var(--spacing) * 4);
|
margin-top: calc(var(--spacing) * 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
.snipcart-modal__container {
|
.product-purchase {
|
||||||
z-index: 1000;
|
margin-top: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-stock-info {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stock-status {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
font-weight: 600;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stock-status.in-stock {
|
||||||
|
color: #00cc00;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stock-status.low-stock {
|
||||||
|
color: #ff9900;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stock-status.out-of-stock {
|
||||||
|
color: #ff3333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-add-to-cart {
|
||||||
|
font-family: "Open Sans", sans-serif;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 1rem;
|
||||||
|
color: #000000;
|
||||||
|
background-color: #00ff00;
|
||||||
|
border: none;
|
||||||
|
border-radius: 40px;
|
||||||
|
padding: 12px 34px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.3s ease;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-add-to-cart:hover:not(:disabled) {
|
||||||
|
background-color: #00e600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-add-to-cart:focus {
|
||||||
|
outline: 2px solid #00e600;
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-add-to-cart:disabled {
|
||||||
|
opacity: 0.6;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-add-to-cart.success {
|
||||||
|
background-color: #00cc00;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-add-to-cart.error {
|
||||||
|
background-color: #ff3333;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-add-to-cart.out-of-stock {
|
||||||
|
background-color: #cccccc;
|
||||||
|
color: #666666;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Cart Drawer Styles */
|
||||||
|
.cart-drawer {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 9999;
|
||||||
|
pointer-events: none;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.3s ease;
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
.cart-drawer.is-open {
|
||||||
|
pointer-events: auto;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
.cart-drawer.is-open .cart-drawer__panel {
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
.cart-drawer__overlay {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.cart-drawer__panel {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 420px;
|
||||||
|
background-color: #ffffff;
|
||||||
|
box-shadow: -2px 0 8px rgba(0, 0, 0, 0.15);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
transform: translateX(100%);
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
}
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.cart-drawer__panel {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.cart-drawer__header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 1.5rem;
|
||||||
|
border-bottom: 1px solid #e0e0e0;
|
||||||
|
}
|
||||||
|
.cart-drawer__header h3 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.cart-drawer__close {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0.5rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: opacity 0.2s;
|
||||||
|
}
|
||||||
|
.cart-drawer__close:hover {
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
.cart-drawer__content {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 1.5rem;
|
||||||
|
}
|
||||||
|
.cart-drawer__content.is-loading {
|
||||||
|
opacity: 0.5;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.cart-drawer__empty {
|
||||||
|
text-align: center;
|
||||||
|
padding: 3rem 1rem;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
.cart-drawer__empty.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.cart-drawer__items {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
.cart-drawer__items.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.cart-drawer__footer {
|
||||||
|
border-top: 1px solid #e0e0e0;
|
||||||
|
padding: 1.5rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
.cart-drawer__total {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 1.125rem;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.cart-drawer__total-label {
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
.cart-drawer__total-amount {
|
||||||
|
color: #000;
|
||||||
|
font-size: 1.25rem;
|
||||||
|
}
|
||||||
|
.cart-drawer__checkout-btn {
|
||||||
|
width: 100%;
|
||||||
|
font-family: "Open Sans", sans-serif;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 1rem;
|
||||||
|
color: #000000;
|
||||||
|
background-color: #00ff00;
|
||||||
|
border: none;
|
||||||
|
border-radius: 40px;
|
||||||
|
padding: 14px 34px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.3s ease;
|
||||||
|
}
|
||||||
|
.cart-drawer__checkout-btn:hover:not(:disabled) {
|
||||||
|
background-color: #00e600;
|
||||||
|
}
|
||||||
|
.cart-drawer__checkout-btn:disabled {
|
||||||
|
opacity: 0.6;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cart-item {
|
||||||
|
display: flex;
|
||||||
|
gap: 1rem;
|
||||||
|
padding: 1rem;
|
||||||
|
border: 1px solid #e0e0e0;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
.cart-item__image {
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
-o-object-fit: cover;
|
||||||
|
object-fit: cover;
|
||||||
|
border-radius: 4px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
.cart-item__details {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
.cart-item__title {
|
||||||
|
font-weight: 600;
|
||||||
|
margin: 0;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
.cart-item__variant {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: #666;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.cart-item__price {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
.cart-item__quantity {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
margin-top: auto;
|
||||||
|
}
|
||||||
|
.cart-item__qty-btn {
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
border: 1px solid #000;
|
||||||
|
background: #fff;
|
||||||
|
color: #000;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: bold;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
.cart-item__qty-btn:hover:not(:disabled) {
|
||||||
|
background-color: #000;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.cart-item__qty-btn:disabled {
|
||||||
|
opacity: 0.4;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
.cart-item__qty-value {
|
||||||
|
min-width: 30px;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
.cart-item__remove {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: #ff3333;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
text-decoration: underline;
|
||||||
|
align-self: flex-start;
|
||||||
|
}
|
||||||
|
.cart-item__remove:hover {
|
||||||
|
color: #cc0000;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-template=subscription-newsletter] main {
|
[data-template=subscription-newsletter] main {
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -22,6 +22,7 @@
|
||||||
@import "template/shop/layout";
|
@import "template/shop/layout";
|
||||||
@import "template/shop/section--product";
|
@import "template/shop/section--product";
|
||||||
@import "template/shop/thanks";
|
@import "template/shop/thanks";
|
||||||
@import "template/shop/snipcart";
|
@import "components/shopify-buy-button.scss";
|
||||||
|
@import "components/shopify-cart-drawer.scss";
|
||||||
|
|
||||||
@import "template/subscription-newsletter/layout";
|
@import "template/subscription-newsletter/layout";
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,3 @@
|
||||||
.section__product,
|
|
||||||
.store__nav{
|
|
||||||
max-width: 1200px;
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.section__product,
|
.section__product,
|
||||||
.store__nav {
|
.store__nav {
|
||||||
max-width: 1200px;
|
max-width: 1200px;
|
||||||
|
|
@ -14,10 +5,6 @@
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.store__nav {
|
.store__nav {
|
||||||
padding-top: calc(var(--spacing) * 1);
|
padding-top: calc(var(--spacing) * 1);
|
||||||
padding-bottom: calc(var(--spacing) * 0.5);
|
padding-bottom: calc(var(--spacing) * 0.5);
|
||||||
|
|
@ -27,174 +14,35 @@
|
||||||
a {
|
a {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: "← ";
|
||||||
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
a::before {
|
@media #{$small} {
|
||||||
content: "← ";
|
a {
|
||||||
}
|
padding-top: 0;
|
||||||
}
|
font-size: var(--fs-small);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.section__product .details {
|
|
||||||
// margin-bottom: calc(var(--spacing) * 2);
|
|
||||||
|
|
||||||
ul{
|
|
||||||
margin-left: 2ch;
|
|
||||||
li{
|
|
||||||
padding-bottom: 0.2em;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.section__product {
|
||||||
|
.details {
|
||||||
|
ul {
|
||||||
|
margin-left: 2ch;
|
||||||
|
|
||||||
.product-options__list {
|
|
||||||
list-style: none;
|
|
||||||
display: flex;
|
|
||||||
gap: 2ch;
|
|
||||||
|
|
||||||
li {
|
li {
|
||||||
position: relative;
|
padding-bottom: 0.2em;
|
||||||
|
|
||||||
input[type="radio"] {
|
|
||||||
position: fixed;
|
|
||||||
opacity: 0;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
label {
|
|
||||||
font-family: var(--title);
|
|
||||||
font-size: var(--fs-normal);
|
|
||||||
height: 4ch;
|
|
||||||
width: 4ch;
|
|
||||||
border-radius: 50%;
|
|
||||||
border: var(--border);
|
|
||||||
border-color: transparent;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
padding-top: 0px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type="radio"]:checked + label {
|
|
||||||
border-color: var(--color-txt);
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type="radio"]:not(:checked) + label:hover {
|
|
||||||
border-color: var(--grey-600);
|
|
||||||
background-color: var(--grey-800);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.product-gallery {
|
|
||||||
position: relative;
|
|
||||||
aspect-ratio: 4 / 3;
|
|
||||||
|
|
||||||
.swiper-slide {
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
figure {
|
|
||||||
|
|
||||||
aspect-ratio: 4 / 3;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
|
|
||||||
img {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
object-fit: contain;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Swiper navigation arrows
|
@media #{$small} {
|
||||||
.swiper-button-prev,
|
|
||||||
.swiper-button-next {
|
|
||||||
color: var(--color-txt);
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
|
|
||||||
&:after {
|
|
||||||
font-size: 20px;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
opacity: 0.7;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Swiper pagination dots
|
|
||||||
.swiper-pagination {
|
|
||||||
position: relative;
|
|
||||||
margin-top: calc(var(--spacing) * 0.5);
|
|
||||||
bottom: 0;
|
|
||||||
|
|
||||||
.swiper-pagination-bullet {
|
|
||||||
width: 8px;
|
|
||||||
height: 8px;
|
|
||||||
background: var(--grey-600);
|
|
||||||
opacity: 0.5;
|
|
||||||
transition: opacity 0.3s;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
opacity: 0.7;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.swiper-pagination-bullet-active {
|
|
||||||
background: var(--color-txt);
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.hero {
|
|
||||||
margin-bottom: calc(var(--spacing) * 1);
|
|
||||||
padding: calc(var(--spacing) * 0.5) 0;
|
|
||||||
border-top: var(--border-light);
|
|
||||||
border-bottom: var(--border-light);
|
|
||||||
|
|
||||||
.p__baseline-big {
|
|
||||||
margin: 0;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.add-to-cart {
|
|
||||||
margin: 0;
|
|
||||||
border-bottom: var(--border-light);
|
|
||||||
padding: calc(var(--spacing) * 0.5) 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-options {
|
|
||||||
border-bottom: var(--border-light);
|
|
||||||
padding: calc(var(--spacing) * 0.25) 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@media #{$small} {
|
|
||||||
.store__nav a {
|
|
||||||
padding-top: 0;
|
|
||||||
font-size: var(--fs-small);
|
|
||||||
}
|
|
||||||
|
|
||||||
.section__product {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
margin-bottom: 10vh;
|
margin-bottom: 10vh;
|
||||||
|
|
@ -207,6 +55,7 @@
|
||||||
margin-top: calc(var(--spacing) * 0.5);
|
margin-top: calc(var(--spacing) * 0.5);
|
||||||
order: 1;
|
order: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
figure {
|
figure {
|
||||||
order: 2;
|
order: 2;
|
||||||
margin-bottom: calc(var(--spacing) * 1);
|
margin-bottom: calc(var(--spacing) * 1);
|
||||||
|
|
@ -226,25 +75,25 @@
|
||||||
order: 5;
|
order: 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.product-gallery{
|
.product-gallery {
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
position: relative;
|
position: relative;
|
||||||
left: calc(var(--padding-body)*-1);
|
left: calc(var(--padding-body) * -1);
|
||||||
|
|
||||||
.swiper-button-prev,
|
.swiper-button-prev,
|
||||||
.swiper-button-next{ display: none; }
|
.swiper-button-next {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@media #{$small-up} {
|
@media #{$small-up} {
|
||||||
|
.product-content {
|
||||||
|
display: grid;
|
||||||
.section__product{
|
grid-template-columns: 1fr 1fr;
|
||||||
display: grid;
|
gap: calc(var(--padding-body) * 2);
|
||||||
grid-template-columns: 1fr 1fr;
|
margin-bottom: calc(var(--spacing) * 3);
|
||||||
gap: calc(var(--padding-body)*2);
|
}
|
||||||
margin-bottom: calc(var(--spacing)*3);
|
|
||||||
|
|
||||||
.details {
|
.details {
|
||||||
margin-bottom: calc(var(--spacing) * 2);
|
margin-bottom: calc(var(--spacing) * 2);
|
||||||
|
|
@ -255,17 +104,140 @@
|
||||||
border-top: var(--border-light);
|
border-top: var(--border-light);
|
||||||
}
|
}
|
||||||
|
|
||||||
.col-left{
|
.col-left {
|
||||||
min-height: 100%;
|
min-height: 100%;
|
||||||
padding-bottom: 40px; //dots
|
padding-bottom: 40px;
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-gallery .swiper-slide figure{
|
|
||||||
width: calc(100% - 60px);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.product-options {
|
||||||
|
border-bottom: var(--border-light);
|
||||||
|
padding: calc(var(--spacing) * 0.25) 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-options__list {
|
||||||
|
list-style: none;
|
||||||
|
display: flex;
|
||||||
|
gap: 2ch;
|
||||||
|
|
||||||
|
li {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
input[type="radio"] {
|
||||||
|
position: fixed;
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
|
||||||
|
&:checked + label {
|
||||||
|
border-color: var(--color-txt);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not(:checked) + label:hover {
|
||||||
|
border-color: var(--grey-600);
|
||||||
|
background-color: var(--grey-800);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
font-family: var(--title);
|
||||||
|
font-size: var(--fs-normal);
|
||||||
|
height: 4ch;
|
||||||
|
width: 4ch;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: var(--border);
|
||||||
|
border-color: transparent;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding-top: 0px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-gallery {
|
||||||
|
position: relative;
|
||||||
|
aspect-ratio: 4 / 3;
|
||||||
|
|
||||||
|
.swiper-slide {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
figure {
|
||||||
|
aspect-ratio: 4 / 3;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media #{$small-up} {
|
||||||
|
figure {
|
||||||
|
width: calc(100% - 60px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.swiper-button-prev,
|
||||||
|
.swiper-button-next {
|
||||||
|
color: var(--color-txt);
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
|
||||||
|
&:after {
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.swiper-pagination {
|
||||||
|
position: relative;
|
||||||
|
margin-top: calc(var(--spacing) * 0.5);
|
||||||
|
bottom: 0;
|
||||||
|
|
||||||
|
.swiper-pagination-bullet {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
background: var(--grey-600);
|
||||||
|
opacity: 0.5;
|
||||||
|
transition: opacity 0.3s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.swiper-pagination-bullet-active {
|
||||||
|
background: var(--color-txt);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero {
|
||||||
|
margin-bottom: calc(var(--spacing) * 1);
|
||||||
|
padding: calc(var(--spacing) * 0.5) 0;
|
||||||
|
border-top: var(--border-light);
|
||||||
|
border-bottom: var(--border-light);
|
||||||
|
|
||||||
|
.p__baseline-big {
|
||||||
|
margin: 0;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-to-cart {
|
||||||
|
margin: 0;
|
||||||
|
border-bottom: var(--border-light);
|
||||||
|
padding: calc(var(--spacing) * 0.5) 0;
|
||||||
|
}
|
||||||
|
|
|
||||||
275
assets/js/cart-drawer.js
Normal file
275
assets/js/cart-drawer.js
Normal file
|
|
@ -0,0 +1,275 @@
|
||||||
|
/**
|
||||||
|
* Cart Drawer Component
|
||||||
|
* Manages the cart sidebar with add/remove/update functionality
|
||||||
|
*/
|
||||||
|
(function() {
|
||||||
|
const drawer = document.getElementById('cart-drawer');
|
||||||
|
const emptyState = document.querySelector('[data-cart-empty]');
|
||||||
|
const itemsContainer = document.querySelector('[data-cart-items]');
|
||||||
|
const checkoutBtn = document.querySelector('[data-cart-checkout]');
|
||||||
|
const closeButtons = document.querySelectorAll('[data-cart-close]');
|
||||||
|
const totalDisplay = document.querySelector('[data-cart-total]');
|
||||||
|
const headerCartBtn = document.querySelector('[data-cart-open]');
|
||||||
|
const headerCartCount = document.querySelector('[data-cart-count]');
|
||||||
|
|
||||||
|
// Get translated text
|
||||||
|
const removeText = drawer.dataset.textRemove || 'Remove';
|
||||||
|
|
||||||
|
let currentCart = null;
|
||||||
|
let cartInstance = null;
|
||||||
|
|
||||||
|
// Wait for ShopifyCart to be available
|
||||||
|
function initCartDrawer() {
|
||||||
|
if (typeof ShopifyCart === 'undefined') {
|
||||||
|
setTimeout(initCartDrawer, 100);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
cartInstance = new ShopifyCart({
|
||||||
|
domain: 'nv7cqv-bu.myshopify.com',
|
||||||
|
storefrontAccessToken: 'dec3d35a2554384d149c72927d1cfd1b'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Initialize event listeners
|
||||||
|
setupEventListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupEventListeners() {
|
||||||
|
// Close drawer
|
||||||
|
closeButtons.forEach(btn => {
|
||||||
|
btn.addEventListener('click', closeDrawer);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Open drawer from header button
|
||||||
|
if (headerCartBtn) {
|
||||||
|
headerCartBtn.addEventListener('click', openDrawer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checkout button
|
||||||
|
checkoutBtn.addEventListener('click', () => {
|
||||||
|
if (currentCart?.checkoutUrl) {
|
||||||
|
window.location.href = currentCart.checkoutUrl;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Listen for custom cart update events
|
||||||
|
document.addEventListener('cart:updated', (e) => {
|
||||||
|
currentCart = e.detail.cart;
|
||||||
|
renderCart();
|
||||||
|
openDrawer();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function openDrawer() {
|
||||||
|
drawer.classList.add('is-open');
|
||||||
|
document.body.style.overflow = 'hidden';
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeDrawer() {
|
||||||
|
drawer.classList.remove('is-open');
|
||||||
|
document.body.style.overflow = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function calculateTotal() {
|
||||||
|
if (!currentCart || !currentCart.lines) return 0;
|
||||||
|
|
||||||
|
return currentCart.lines.edges.reduce((total, edge) => {
|
||||||
|
const item = edge.node;
|
||||||
|
const price = parseFloat(item.merchandise.price.amount);
|
||||||
|
const quantity = item.quantity;
|
||||||
|
return total + (price * quantity);
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatPrice(amount, currency = 'EUR') {
|
||||||
|
return new Intl.NumberFormat('fr-FR', {
|
||||||
|
style: 'currency',
|
||||||
|
currency: currency
|
||||||
|
}).format(amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateCartCount() {
|
||||||
|
if (!currentCart || !currentCart.lines || currentCart.lines.edges.length === 0) {
|
||||||
|
if (headerCartCount) {
|
||||||
|
headerCartCount.textContent = '';
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate total quantity
|
||||||
|
const totalQty = currentCart.lines.edges.reduce((sum, edge) => {
|
||||||
|
return sum + edge.node.quantity;
|
||||||
|
}, 0);
|
||||||
|
|
||||||
|
if (headerCartCount) {
|
||||||
|
headerCartCount.textContent = totalQty > 0 ? totalQty : '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderCart() {
|
||||||
|
if (!currentCart || !currentCart.lines || currentCart.lines.edges.length === 0) {
|
||||||
|
emptyState.classList.remove('hidden');
|
||||||
|
itemsContainer.classList.add('hidden');
|
||||||
|
checkoutBtn.disabled = true;
|
||||||
|
if (totalDisplay) {
|
||||||
|
totalDisplay.textContent = '0,00 €';
|
||||||
|
}
|
||||||
|
updateCartCount();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
emptyState.classList.add('hidden');
|
||||||
|
itemsContainer.classList.remove('hidden');
|
||||||
|
checkoutBtn.disabled = false;
|
||||||
|
|
||||||
|
// Calculate and display total
|
||||||
|
const total = calculateTotal();
|
||||||
|
const currency = currentCart.lines.edges[0]?.node.merchandise.price.currencyCode || 'EUR';
|
||||||
|
if (totalDisplay) {
|
||||||
|
totalDisplay.textContent = formatPrice(total, currency);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update header cart count
|
||||||
|
updateCartCount();
|
||||||
|
|
||||||
|
// Render cart items
|
||||||
|
itemsContainer.innerHTML = currentCart.lines.edges.map(edge => {
|
||||||
|
const item = edge.node;
|
||||||
|
const merchandise = item.merchandise;
|
||||||
|
|
||||||
|
return `
|
||||||
|
<div class="cart-item" data-line-id="${item.id}">
|
||||||
|
<div class="cart-item__details">
|
||||||
|
<h4 class="cart-item__title">${merchandise.product.title}</h4>
|
||||||
|
${merchandise.title !== 'Default Title' ? `<p class="cart-item__variant">${merchandise.title}</p>` : ''}
|
||||||
|
<p class="cart-item__price">${formatPrice(parseFloat(merchandise.price.amount), merchandise.price.currencyCode)}</p>
|
||||||
|
|
||||||
|
<div class="cart-item__quantity">
|
||||||
|
<button class="cart-item__qty-btn" data-action="decrease" data-line-id="${item.id}">−</button>
|
||||||
|
<span class="cart-item__qty-value">${item.quantity}</span>
|
||||||
|
<button class="cart-item__qty-btn" data-action="increase" data-line-id="${item.id}">+</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button class="cart-item__remove" data-action="remove" data-line-id="${item.id}">
|
||||||
|
${removeText}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}).join('');
|
||||||
|
|
||||||
|
// Attach event listeners to quantity buttons
|
||||||
|
attachQuantityListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
function attachQuantityListeners() {
|
||||||
|
const buttons = itemsContainer.querySelectorAll('[data-action]');
|
||||||
|
|
||||||
|
buttons.forEach(btn => {
|
||||||
|
btn.addEventListener('click', async (e) => {
|
||||||
|
const action = e.target.dataset.action;
|
||||||
|
const lineId = e.target.dataset.lineId;
|
||||||
|
|
||||||
|
await handleQuantityChange(action, lineId);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleQuantityChange(action, lineId) {
|
||||||
|
if (!cartInstance || !currentCart) return;
|
||||||
|
|
||||||
|
// Find the line item
|
||||||
|
const line = currentCart.lines.edges.find(edge => edge.node.id === lineId);
|
||||||
|
if (!line) return;
|
||||||
|
|
||||||
|
const currentQty = line.node.quantity;
|
||||||
|
let newQty = currentQty;
|
||||||
|
|
||||||
|
if (action === 'increase') {
|
||||||
|
newQty = currentQty + 1;
|
||||||
|
} else if (action === 'decrease') {
|
||||||
|
newQty = Math.max(0, currentQty - 1);
|
||||||
|
} else if (action === 'remove') {
|
||||||
|
newQty = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update cart via API
|
||||||
|
try {
|
||||||
|
itemsContainer.classList.add('is-loading');
|
||||||
|
|
||||||
|
const query = `
|
||||||
|
mutation cartLinesUpdate($cartId: ID!, $lines: [CartLineUpdateInput!]!) {
|
||||||
|
cartLinesUpdate(cartId: $cartId, lines: $lines) {
|
||||||
|
cart {
|
||||||
|
id
|
||||||
|
checkoutUrl
|
||||||
|
lines(first: 10) {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
id
|
||||||
|
quantity
|
||||||
|
merchandise {
|
||||||
|
... on ProductVariant {
|
||||||
|
id
|
||||||
|
title
|
||||||
|
price {
|
||||||
|
amount
|
||||||
|
currencyCode
|
||||||
|
}
|
||||||
|
product {
|
||||||
|
title
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
userErrors {
|
||||||
|
field
|
||||||
|
message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const data = await cartInstance.query(query, {
|
||||||
|
cartId: currentCart.id,
|
||||||
|
lines: [{
|
||||||
|
id: lineId,
|
||||||
|
quantity: newQty
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
|
||||||
|
if (data.cartLinesUpdate.userErrors.length > 0) {
|
||||||
|
throw new Error(data.cartLinesUpdate.userErrors[0].message);
|
||||||
|
}
|
||||||
|
|
||||||
|
currentCart = data.cartLinesUpdate.cart;
|
||||||
|
renderCart();
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error updating cart:', error);
|
||||||
|
alert('Erreur lors de la mise à jour du panier');
|
||||||
|
} finally {
|
||||||
|
itemsContainer.classList.remove('is-loading');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Public API
|
||||||
|
window.CartDrawer = {
|
||||||
|
open: openDrawer,
|
||||||
|
close: closeDrawer,
|
||||||
|
updateCart: (cart) => {
|
||||||
|
currentCart = cart;
|
||||||
|
renderCart();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Initialize when DOM is ready
|
||||||
|
if (document.readyState === 'loading') {
|
||||||
|
document.addEventListener('DOMContentLoaded', initCartDrawer);
|
||||||
|
} else {
|
||||||
|
initCartDrawer();
|
||||||
|
}
|
||||||
|
})();
|
||||||
63
assets/js/product-add-to-cart.js
Normal file
63
assets/js/product-add-to-cart.js
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
(function() {
|
||||||
|
const cart = new ShopifyCart({
|
||||||
|
domain: 'nv7cqv-bu.myshopify.com',
|
||||||
|
storefrontAccessToken: 'dec3d35a2554384d149c72927d1cfd1b'
|
||||||
|
});
|
||||||
|
|
||||||
|
const addToCartBtn = document.querySelector('[data-shopify-add-to-cart]');
|
||||||
|
|
||||||
|
if (!addToCartBtn) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const texts = {
|
||||||
|
add: addToCartBtn.dataset.textAdd || 'Add to cart',
|
||||||
|
adding: addToCartBtn.dataset.textAdding || 'Adding...',
|
||||||
|
added: addToCartBtn.dataset.textAdded || 'Added! ✓',
|
||||||
|
error: addToCartBtn.dataset.textError || 'Error - Try again'
|
||||||
|
};
|
||||||
|
|
||||||
|
addToCartBtn.addEventListener('click', async function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const variantId = this.dataset.variantId;
|
||||||
|
|
||||||
|
if (!variantId) {
|
||||||
|
console.error('No variant ID found');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
addToCartBtn.disabled = true;
|
||||||
|
const originalText = addToCartBtn.textContent;
|
||||||
|
addToCartBtn.textContent = texts.adding;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const cartResult = await cart.addToCart(variantId, 1);
|
||||||
|
|
||||||
|
addToCartBtn.textContent = texts.added;
|
||||||
|
addToCartBtn.classList.add('success');
|
||||||
|
|
||||||
|
document.dispatchEvent(new CustomEvent('cart:updated', {
|
||||||
|
detail: { cart: cartResult }
|
||||||
|
}));
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
addToCartBtn.disabled = false;
|
||||||
|
addToCartBtn.textContent = originalText;
|
||||||
|
addToCartBtn.classList.remove('success');
|
||||||
|
}, 1500);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error adding to cart:', error);
|
||||||
|
|
||||||
|
addToCartBtn.textContent = texts.error;
|
||||||
|
addToCartBtn.classList.add('error');
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
addToCartBtn.disabled = false;
|
||||||
|
addToCartBtn.textContent = originalText;
|
||||||
|
addToCartBtn.classList.remove('error');
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})();
|
||||||
188
assets/js/product-loader.js
Normal file
188
assets/js/product-loader.js
Normal file
|
|
@ -0,0 +1,188 @@
|
||||||
|
(async function () {
|
||||||
|
const container = document.querySelector("[data-product-loader]");
|
||||||
|
if (!container) return;
|
||||||
|
|
||||||
|
const handle = container.dataset.shopifyHandle;
|
||||||
|
const language = container.dataset.language || 'fr';
|
||||||
|
const isEnglish = language === 'en';
|
||||||
|
const loadingState = container.querySelector(".product-loading");
|
||||||
|
const contentState = container.querySelector(".product-content");
|
||||||
|
const errorState = container.querySelector(".product-error");
|
||||||
|
|
||||||
|
try {
|
||||||
|
const cart = new ShopifyCart({
|
||||||
|
domain: "nv7cqv-bu.myshopify.com",
|
||||||
|
storefrontAccessToken: "dec3d35a2554384d149c72927d1cfd1b",
|
||||||
|
});
|
||||||
|
|
||||||
|
const product = await cart.getProductByHandle(handle);
|
||||||
|
|
||||||
|
if (!product) {
|
||||||
|
throw new Error("Product not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
renderProduct(product, isEnglish);
|
||||||
|
|
||||||
|
loadingState.style.display = "none";
|
||||||
|
contentState.removeAttribute("style");
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
if (typeof Swiper !== "undefined" && product.images.edges.length > 0) {
|
||||||
|
new Swiper(".product-gallery", {
|
||||||
|
loop: product.images.edges.length > 1,
|
||||||
|
navigation: {
|
||||||
|
nextEl: ".swiper-button-next",
|
||||||
|
prevEl: ".swiper-button-prev",
|
||||||
|
},
|
||||||
|
pagination: {
|
||||||
|
el: ".swiper-pagination",
|
||||||
|
clickable: true,
|
||||||
|
},
|
||||||
|
keyboard: {
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error loading product:", error);
|
||||||
|
loadingState.style.display = "none";
|
||||||
|
errorState.style.display = "block";
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderProduct(product, isEnglish) {
|
||||||
|
renderTitle(product, isEnglish);
|
||||||
|
renderPrice(product);
|
||||||
|
renderDetails(product, isEnglish);
|
||||||
|
renderImages(product, isEnglish);
|
||||||
|
renderVariants(product);
|
||||||
|
setupAddToCart(product);
|
||||||
|
renderStock(product);
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderTitle(product, isEnglish) {
|
||||||
|
const titleEl = document.querySelector("[data-product-title]");
|
||||||
|
if (titleEl) {
|
||||||
|
const title = isEnglish && product.titleEn?.value
|
||||||
|
? product.titleEn.value
|
||||||
|
: product.title;
|
||||||
|
titleEl.textContent = title;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderPrice(product) {
|
||||||
|
const priceEl = document.querySelector("[data-product-price]");
|
||||||
|
if (priceEl) {
|
||||||
|
const price = parseFloat(product.priceRange.minVariantPrice.amount);
|
||||||
|
priceEl.textContent = price.toFixed(2) + "€";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderDetails(product, isEnglish) {
|
||||||
|
const detailsEl = document.querySelector("[data-product-details]");
|
||||||
|
if (detailsEl) {
|
||||||
|
const description = isEnglish && product.descriptionEn?.value
|
||||||
|
? product.descriptionEn.value
|
||||||
|
: product.descriptionHtml || "";
|
||||||
|
detailsEl.innerHTML = description;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderImages(product, isEnglish) {
|
||||||
|
const imagesContainer = document.querySelector("[data-product-images]");
|
||||||
|
|
||||||
|
if (imagesContainer && product.images.edges.length > 0) {
|
||||||
|
const productTitle = isEnglish && product.titleEn?.value
|
||||||
|
? product.titleEn.value
|
||||||
|
: product.title;
|
||||||
|
|
||||||
|
imagesContainer.innerHTML = product.images.edges
|
||||||
|
.map((edge) => {
|
||||||
|
const img = edge.node;
|
||||||
|
return `
|
||||||
|
<div class="swiper-slide">
|
||||||
|
<figure>
|
||||||
|
<img src="${img.url}"
|
||||||
|
alt="${img.altText || productTitle}"
|
||||||
|
loading="lazy" />
|
||||||
|
</figure>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
})
|
||||||
|
.join("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderVariants(product) {
|
||||||
|
if (product.variants.edges.length <= 1) return;
|
||||||
|
|
||||||
|
const variantsContainer = document.querySelector("[data-product-variants]");
|
||||||
|
const variantSelector = document.querySelector("[data-variant-selector]");
|
||||||
|
|
||||||
|
if (!variantsContainer || !variantSelector) return;
|
||||||
|
|
||||||
|
variantsContainer.style.display = "block";
|
||||||
|
|
||||||
|
variantSelector.innerHTML = product.variants.edges
|
||||||
|
.map((edge) => {
|
||||||
|
const variant = edge.node;
|
||||||
|
const variantId = variant.id.replace(
|
||||||
|
"gid://shopify/ProductVariant/",
|
||||||
|
""
|
||||||
|
);
|
||||||
|
const price = parseFloat(variant.price.amount).toFixed(2) + "€";
|
||||||
|
const availability = variant.availableForSale
|
||||||
|
? ""
|
||||||
|
: " (Rupture de stock)";
|
||||||
|
|
||||||
|
return `<option value="${variantId}" ${
|
||||||
|
!variant.availableForSale ? "disabled" : ""
|
||||||
|
}>
|
||||||
|
${variant.title} - ${price}${availability}
|
||||||
|
</option>`;
|
||||||
|
})
|
||||||
|
.join("");
|
||||||
|
|
||||||
|
variantSelector.addEventListener("change", (e) => {
|
||||||
|
const addToCartBtn = document.querySelector("[data-shopify-add-to-cart]");
|
||||||
|
if (addToCartBtn) {
|
||||||
|
addToCartBtn.dataset.variantId = e.target.value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupAddToCart(product) {
|
||||||
|
const addToCartBtn = document.querySelector("[data-shopify-add-to-cart]");
|
||||||
|
if (!addToCartBtn) return;
|
||||||
|
|
||||||
|
const productId = product.id.replace("gid://shopify/Product/", "");
|
||||||
|
addToCartBtn.dataset.productId = productId;
|
||||||
|
|
||||||
|
const firstAvailableVariant = product.variants.edges.find(
|
||||||
|
(e) => e.node.availableForSale
|
||||||
|
);
|
||||||
|
if (firstAvailableVariant) {
|
||||||
|
const variantId = firstAvailableVariant.node.id.replace(
|
||||||
|
"gid://shopify/ProductVariant/",
|
||||||
|
""
|
||||||
|
);
|
||||||
|
addToCartBtn.dataset.variantId = variantId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderStock(product) {
|
||||||
|
const stockEl = document.querySelector("[data-product-stock]");
|
||||||
|
if (!stockEl) return;
|
||||||
|
|
||||||
|
const addToCartBtn = document.querySelector("[data-shopify-add-to-cart]");
|
||||||
|
|
||||||
|
if (product.availableForSale) {
|
||||||
|
stockEl.textContent = addToCartBtn?.dataset.textInStock || "En stock";
|
||||||
|
stockEl.classList.add("in-stock");
|
||||||
|
} else {
|
||||||
|
stockEl.textContent =
|
||||||
|
addToCartBtn?.dataset.textOutOfStock || "Rupture de stock";
|
||||||
|
stockEl.classList.add("out-of-stock");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})();
|
||||||
47
assets/js/products-list-loader.js
Normal file
47
assets/js/products-list-loader.js
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
(async function() {
|
||||||
|
const container = document.querySelector('[data-products-loader]');
|
||||||
|
if (!container) return;
|
||||||
|
|
||||||
|
const language = (container.dataset.language || 'FR').toLowerCase();
|
||||||
|
const isEnglish = language === 'en';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const cart = new ShopifyCart({
|
||||||
|
domain: 'nv7cqv-bu.myshopify.com',
|
||||||
|
storefrontAccessToken: 'dec3d35a2554384d149c72927d1cfd1b'
|
||||||
|
});
|
||||||
|
|
||||||
|
const productsData = await cart.getAllProducts();
|
||||||
|
const products = productsData.edges;
|
||||||
|
|
||||||
|
const productsHtml = products.map(edge => {
|
||||||
|
const product = edge.node;
|
||||||
|
const image = product.images.edges[0]?.node;
|
||||||
|
const price = parseFloat(product.priceRange.minVariantPrice.amount).toFixed(2);
|
||||||
|
const slug = product.handle;
|
||||||
|
const productUrl = isEnglish ? `/en/${slug}` : `/${slug}`;
|
||||||
|
const productTitle = isEnglish && product.titleEn?.value
|
||||||
|
? product.titleEn.value
|
||||||
|
: product.title;
|
||||||
|
|
||||||
|
return `
|
||||||
|
<article class="store__product">
|
||||||
|
<figure>
|
||||||
|
${image ? `<img src="${image.url}" alt="${image.altText || productTitle}" loading="lazy" />` : ''}
|
||||||
|
</figure>
|
||||||
|
<p class="line-1">
|
||||||
|
<a href="${productUrl}">${productTitle}</a>
|
||||||
|
</p>
|
||||||
|
<p class="price">${price}€</p>
|
||||||
|
<a href="${productUrl}" class="link-block" aria-hidden="true"></a>
|
||||||
|
</article>
|
||||||
|
`;
|
||||||
|
}).join('');
|
||||||
|
|
||||||
|
container.innerHTML = productsHtml;
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading products:', error);
|
||||||
|
container.innerHTML = '<p>Erreur lors du chargement des produits</p>';
|
||||||
|
}
|
||||||
|
})();
|
||||||
343
assets/js/shopify-cart.js
Normal file
343
assets/js/shopify-cart.js
Normal file
|
|
@ -0,0 +1,343 @@
|
||||||
|
/**
|
||||||
|
* Shopify Storefront API Client
|
||||||
|
* Custom implementation using Cart API (2026-01)
|
||||||
|
*/
|
||||||
|
class ShopifyCart {
|
||||||
|
constructor(config) {
|
||||||
|
this.domain = config.domain;
|
||||||
|
this.storefrontAccessToken = config.storefrontAccessToken;
|
||||||
|
this.apiVersion = '2026-01';
|
||||||
|
this.endpoint = `https://${this.domain}/api/${this.apiVersion}/graphql.json`;
|
||||||
|
this.cartId = null;
|
||||||
|
this.cartItems = [];
|
||||||
|
|
||||||
|
// Load existing cart from localStorage
|
||||||
|
this.loadCart();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make GraphQL request to Shopify Storefront API
|
||||||
|
*/
|
||||||
|
async query(query, variables = {}) {
|
||||||
|
const response = await fetch(this.endpoint, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-Shopify-Storefront-Access-Token': this.storefrontAccessToken,
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ query, variables }),
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (result.errors) {
|
||||||
|
console.error('Shopify API Error:', result.errors);
|
||||||
|
throw new Error(result.errors[0].message);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get product information by ID
|
||||||
|
*/
|
||||||
|
async getProduct(productId) {
|
||||||
|
const query = `
|
||||||
|
query getProduct($id: ID!) {
|
||||||
|
product(id: $id) {
|
||||||
|
id
|
||||||
|
title
|
||||||
|
description
|
||||||
|
availableForSale
|
||||||
|
variants(first: 10) {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
id
|
||||||
|
title
|
||||||
|
price {
|
||||||
|
amount
|
||||||
|
currencyCode
|
||||||
|
}
|
||||||
|
availableForSale
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const data = await this.query(query, {
|
||||||
|
id: `gid://shopify/Product/${productId}`
|
||||||
|
});
|
||||||
|
|
||||||
|
return data.product;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get product by handle
|
||||||
|
* @param {string} handle - Product handle (slug)
|
||||||
|
*/
|
||||||
|
async getProductByHandle(handle) {
|
||||||
|
const query = `
|
||||||
|
query getProductByHandle($handle: String!) {
|
||||||
|
product(handle: $handle) {
|
||||||
|
id
|
||||||
|
handle
|
||||||
|
title
|
||||||
|
description
|
||||||
|
descriptionHtml
|
||||||
|
availableForSale
|
||||||
|
tags
|
||||||
|
titleEn: metafield(namespace: "custom", key: "title_en") {
|
||||||
|
value
|
||||||
|
}
|
||||||
|
descriptionEn: metafield(namespace: "custom", key: "description_en") {
|
||||||
|
value
|
||||||
|
}
|
||||||
|
priceRange {
|
||||||
|
minVariantPrice {
|
||||||
|
amount
|
||||||
|
currencyCode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
images(first: 10) {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
id
|
||||||
|
url
|
||||||
|
altText
|
||||||
|
width
|
||||||
|
height
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
variants(first: 20) {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
id
|
||||||
|
title
|
||||||
|
sku
|
||||||
|
availableForSale
|
||||||
|
price {
|
||||||
|
amount
|
||||||
|
currencyCode
|
||||||
|
}
|
||||||
|
selectedOptions {
|
||||||
|
name
|
||||||
|
value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const data = await this.query(query, { handle });
|
||||||
|
return data.product || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all products for listing page
|
||||||
|
* @param {number} first - Number of products to fetch
|
||||||
|
*/
|
||||||
|
async getAllProducts(first = 20) {
|
||||||
|
const query = `
|
||||||
|
query getAllProducts($first: Int!) {
|
||||||
|
products(first: $first, sortKey: TITLE) {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
id
|
||||||
|
handle
|
||||||
|
title
|
||||||
|
description
|
||||||
|
availableForSale
|
||||||
|
titleEn: metafield(namespace: "custom", key: "title_en") {
|
||||||
|
value
|
||||||
|
}
|
||||||
|
priceRange {
|
||||||
|
minVariantPrice {
|
||||||
|
amount
|
||||||
|
currencyCode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
images(first: 1) {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
id
|
||||||
|
url
|
||||||
|
altText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pageInfo {
|
||||||
|
hasNextPage
|
||||||
|
endCursor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const data = await this.query(query, { first });
|
||||||
|
return data.products;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new cart
|
||||||
|
*/
|
||||||
|
async createCart(lines = []) {
|
||||||
|
const query = `
|
||||||
|
mutation cartCreate($input: CartInput!) {
|
||||||
|
cartCreate(input: $input) {
|
||||||
|
cart {
|
||||||
|
id
|
||||||
|
checkoutUrl
|
||||||
|
lines(first: 10) {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
id
|
||||||
|
quantity
|
||||||
|
merchandise {
|
||||||
|
... on ProductVariant {
|
||||||
|
id
|
||||||
|
title
|
||||||
|
price {
|
||||||
|
amount
|
||||||
|
currencyCode
|
||||||
|
}
|
||||||
|
product {
|
||||||
|
title
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
userErrors {
|
||||||
|
field
|
||||||
|
message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const data = await this.query(query, {
|
||||||
|
input: { lines }
|
||||||
|
});
|
||||||
|
|
||||||
|
if (data.cartCreate.userErrors.length > 0) {
|
||||||
|
throw new Error(data.cartCreate.userErrors[0].message);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.cartId = data.cartCreate.cart.id;
|
||||||
|
this.saveCart();
|
||||||
|
|
||||||
|
return data.cartCreate.cart;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add item to cart
|
||||||
|
*/
|
||||||
|
async addToCart(variantId, quantity = 1) {
|
||||||
|
const lines = [{
|
||||||
|
merchandiseId: `gid://shopify/ProductVariant/${variantId}`,
|
||||||
|
quantity: quantity
|
||||||
|
}];
|
||||||
|
|
||||||
|
let cart;
|
||||||
|
|
||||||
|
if (this.cartId) {
|
||||||
|
// Add to existing cart
|
||||||
|
const query = `
|
||||||
|
mutation cartLinesAdd($cartId: ID!, $lines: [CartLineInput!]!) {
|
||||||
|
cartLinesAdd(cartId: $cartId, lines: $lines) {
|
||||||
|
cart {
|
||||||
|
id
|
||||||
|
checkoutUrl
|
||||||
|
lines(first: 10) {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
id
|
||||||
|
quantity
|
||||||
|
merchandise {
|
||||||
|
... on ProductVariant {
|
||||||
|
id
|
||||||
|
title
|
||||||
|
price {
|
||||||
|
amount
|
||||||
|
currencyCode
|
||||||
|
}
|
||||||
|
product {
|
||||||
|
title
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
userErrors {
|
||||||
|
field
|
||||||
|
message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const data = await this.query(query, {
|
||||||
|
cartId: this.cartId,
|
||||||
|
lines
|
||||||
|
});
|
||||||
|
|
||||||
|
if (data.cartLinesAdd.userErrors.length > 0) {
|
||||||
|
throw new Error(data.cartLinesAdd.userErrors[0].message);
|
||||||
|
}
|
||||||
|
|
||||||
|
cart = data.cartLinesAdd.cart;
|
||||||
|
} else {
|
||||||
|
// Create new cart
|
||||||
|
cart = await this.createCart(lines);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.cartItems = cart.lines.edges;
|
||||||
|
return cart;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get checkout URL to redirect user
|
||||||
|
*/
|
||||||
|
getCheckoutUrl(cart) {
|
||||||
|
return cart?.checkoutUrl || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save cart ID to localStorage
|
||||||
|
*/
|
||||||
|
saveCart() {
|
||||||
|
if (this.cartId) {
|
||||||
|
localStorage.setItem('shopify_cart_id', this.cartId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load cart ID from localStorage
|
||||||
|
*/
|
||||||
|
loadCart() {
|
||||||
|
this.cartId = localStorage.getItem('shopify_cart_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear cart
|
||||||
|
*/
|
||||||
|
clearCart() {
|
||||||
|
this.cartId = null;
|
||||||
|
this.cartItems = [];
|
||||||
|
localStorage.removeItem('shopify_cart_id');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Export for use in other scripts
|
||||||
|
window.ShopifyCart = ShopifyCart;
|
||||||
164
assets/snipcart-archive/README.md
Normal file
164
assets/snipcart-archive/README.md
Normal file
|
|
@ -0,0 +1,164 @@
|
||||||
|
# Archive Snipcart
|
||||||
|
|
||||||
|
Cette archive contient tous les fichiers et le code nécessaires pour réactiver l'intégration Snipcart si besoin.
|
||||||
|
|
||||||
|
## Date d'archivage
|
||||||
|
13 janvier 2026
|
||||||
|
|
||||||
|
## Raison de l'archivage
|
||||||
|
Remplacement par l'intégration Shopify Buy Button
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Fichiers archivés
|
||||||
|
|
||||||
|
### Fichiers JavaScript
|
||||||
|
- `snipcart.js` - Loader Snipcart avec configuration de la clé API
|
||||||
|
- `product-size.js` - Gestion des options produit (tailles, etc.) pour Snipcart
|
||||||
|
|
||||||
|
### Fichiers SCSS
|
||||||
|
- `_snipcart.scss` - Styles pour le modal Snipcart
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Comment restaurer l'intégration Snipcart
|
||||||
|
|
||||||
|
### 1. Restaurer les fichiers JavaScript
|
||||||
|
|
||||||
|
#### Fichier: `assets/js/snipcart.js`
|
||||||
|
Copier le fichier depuis l'archive:
|
||||||
|
```bash
|
||||||
|
cp assets/snipcart-archive/snipcart.js assets/js/
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Fichier: `assets/js/product-size.js`
|
||||||
|
Copier le fichier depuis l'archive:
|
||||||
|
```bash
|
||||||
|
cp assets/snipcart-archive/product-size.js assets/js/
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Restaurer les styles SCSS
|
||||||
|
|
||||||
|
#### Fichier: `assets/css/template/shop/_snipcart.scss`
|
||||||
|
Copier le fichier depuis l'archive:
|
||||||
|
```bash
|
||||||
|
cp assets/snipcart-archive/_snipcart.scss assets/css/template/shop/
|
||||||
|
```
|
||||||
|
|
||||||
|
Puis décommenter l'import dans `assets/css/style.scss`:
|
||||||
|
```scss
|
||||||
|
// Décommenter cette ligne:
|
||||||
|
@import 'template/shop/snipcart';
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Restaurer le template produit
|
||||||
|
|
||||||
|
#### Fichier: `site/templates/product.php`
|
||||||
|
|
||||||
|
1. **Restaurer le bouton "Ajouter au panier" avec les attributs Snipcart** (ligne ~40-78):
|
||||||
|
- Décommenter tous les attributs `data-item-*` du bouton
|
||||||
|
- Ajouter la classe `snipcart-add-item` au bouton
|
||||||
|
|
||||||
|
2. **Restaurer les scripts dans le footer** (ligne 114):
|
||||||
|
```php
|
||||||
|
<?php snippet('footer', ['scripts' => ['assets/js/product-size.js', 'assets/js/snipcart.js', 'assets/js/product-gallery.js']]) ?>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Restaurer les routes et webhooks
|
||||||
|
|
||||||
|
#### Fichier: `site/config/config.php`
|
||||||
|
|
||||||
|
Décommenter les routes Snipcart (lignes 39-147):
|
||||||
|
|
||||||
|
1. **Route de validation produit** (`validate.json`):
|
||||||
|
- Permet à Snipcart de valider les prix et stock
|
||||||
|
- Route: `(:any)/validate.json`
|
||||||
|
|
||||||
|
2. **Webhook Snipcart**:
|
||||||
|
- Gère les événements de commande (décrémente le stock)
|
||||||
|
- Route: `snipcart-webhook`
|
||||||
|
|
||||||
|
### 5. Configuration Snipcart
|
||||||
|
|
||||||
|
#### Clé API publique
|
||||||
|
La clé API publique Snipcart est dans `snipcart.js`:
|
||||||
|
```javascript
|
||||||
|
publicApiKey: 'NGU4ODQ3MjAtY2MzMC00MWEyLWI2YTMtNjBmNGYzMTBlOTZkNjM4OTY1NDY4OTE5MTQyMTI3'
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Configuration du webhook
|
||||||
|
Pour que le webhook fonctionne, il faut:
|
||||||
|
1. Configurer l'URL du webhook dans le dashboard Snipcart
|
||||||
|
2. URL: `https://votre-domaine.com/snipcart-webhook`
|
||||||
|
3. Événements à écouter: `order.completed`
|
||||||
|
|
||||||
|
### 6. Vérifications après restauration
|
||||||
|
|
||||||
|
- [ ] Les fichiers JS sont présents dans `assets/js/`
|
||||||
|
- [ ] Le fichier SCSS est présent et importé
|
||||||
|
- [ ] Les boutons "Ajouter au panier" ont la classe `snipcart-add-item`
|
||||||
|
- [ ] Les attributs `data-item-*` sont présents sur les boutons
|
||||||
|
- [ ] Les routes sont décommentées dans `config.php`
|
||||||
|
- [ ] Le CSS de Snipcart est compilé
|
||||||
|
- [ ] Le webhook est configuré dans le dashboard Snipcart
|
||||||
|
- [ ] Les traductions sont restaurées dans `site/languages/en.php` et `fr.php`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Fonctionnalités Snipcart implémentées
|
||||||
|
|
||||||
|
### Gestion des produits
|
||||||
|
- Affichage du prix
|
||||||
|
- Options de produit (tailles, couleurs, etc.)
|
||||||
|
- Validation des options obligatoires
|
||||||
|
- Images produit
|
||||||
|
|
||||||
|
### Gestion du panier
|
||||||
|
- Ajout au panier
|
||||||
|
- Gestion du stock
|
||||||
|
- Calcul des frais de port (basé sur poids/dimensions)
|
||||||
|
- Validation des prix côté serveur
|
||||||
|
|
||||||
|
### Gestion des commandes
|
||||||
|
- Webhook pour décrémenter le stock automatiquement
|
||||||
|
- Redirection vers page de remerciement après paiement
|
||||||
|
- Token de commande dans l'URL
|
||||||
|
|
||||||
|
### Multi-langue
|
||||||
|
- Support FR/EN
|
||||||
|
- Redirection post-paiement avec détection de la langue
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Dépendances externes
|
||||||
|
|
||||||
|
### CDN Snipcart
|
||||||
|
Snipcart est chargé depuis le CDN officiel:
|
||||||
|
- Version: 3.0
|
||||||
|
- JS: `https://cdn.snipcart.com/themes/v3.0/default/snipcart.js`
|
||||||
|
- CSS: `https://cdn.snipcart.com/themes/v3.0/default/snipcart.css`
|
||||||
|
|
||||||
|
### Stratégie de chargement
|
||||||
|
- `loadStrategy: 'on-user-interaction'`
|
||||||
|
- Chargement différé pour optimiser les performances
|
||||||
|
- Timeout: 2750ms
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Notes importantes
|
||||||
|
|
||||||
|
1. **Sécurité**: Le webhook devrait valider la signature Snipcart en production (voir commentaire dans `config.php`)
|
||||||
|
|
||||||
|
2. **Stock**: Le système décrémente automatiquement le stock via le webhook `order.completed`
|
||||||
|
|
||||||
|
3. **Validation**: Chaque produit expose une route `validate.json` pour que Snipcart puisse vérifier les prix
|
||||||
|
|
||||||
|
4. **Multi-langue**: La redirection post-paiement détecte automatiquement la langue depuis l'URL
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Support
|
||||||
|
|
||||||
|
Pour toute question sur Snipcart:
|
||||||
|
- Documentation: https://docs.snipcart.com/
|
||||||
|
- Support: https://snipcart.com/support
|
||||||
66
assets/snipcart-archive/product-size.js
Normal file
66
assets/snipcart-archive/product-size.js
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
/**
|
||||||
|
* Gestion de la sélection des options produit
|
||||||
|
* Met à jour les attributs Snipcart et gère les classes CSS
|
||||||
|
*/
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialise la gestion des options
|
||||||
|
*/
|
||||||
|
function initOptionSelector() {
|
||||||
|
const optionsContainer = document.querySelector('.product-options');
|
||||||
|
const addToCartButton = document.querySelector('.snipcart-add-item');
|
||||||
|
|
||||||
|
if (!addToCartButton) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Si pas d'options, le bouton est déjà actif
|
||||||
|
if (!optionsContainer) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const radios = optionsContainer.querySelectorAll('input[type="radio"]');
|
||||||
|
|
||||||
|
// Réinitialiser toutes les options (important pour le cache navigateur)
|
||||||
|
radios.forEach(radio => {
|
||||||
|
radio.checked = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Retirer la classe is-selected de tous les li
|
||||||
|
const allLi = optionsContainer.querySelectorAll('li');
|
||||||
|
allLi.forEach(li => li.classList.remove('is-selected'));
|
||||||
|
|
||||||
|
// S'assurer que le bouton est désactivé au départ
|
||||||
|
addToCartButton.setAttribute('disabled', 'disabled');
|
||||||
|
|
||||||
|
// Écouter les changements de sélection
|
||||||
|
radios.forEach(radio => {
|
||||||
|
radio.addEventListener('change', function() {
|
||||||
|
// Mettre à jour l'attribut Snipcart
|
||||||
|
addToCartButton.setAttribute('data-item-custom1-value', this.value);
|
||||||
|
|
||||||
|
// Activer le bouton
|
||||||
|
addToCartButton.removeAttribute('disabled');
|
||||||
|
|
||||||
|
// Changer le texte du bouton
|
||||||
|
const buttonText = addToCartButton.querySelector('.txt');
|
||||||
|
if (buttonText) {
|
||||||
|
buttonText.textContent = buttonText.getAttribute('data-default-text') || 'Ajouter au panier';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gérer la classe is-selected sur les li parents
|
||||||
|
allLi.forEach(li => li.classList.remove('is-selected'));
|
||||||
|
this.closest('li').classList.add('is-selected');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialisation au chargement de la page
|
||||||
|
*/
|
||||||
|
document.addEventListener('DOMContentLoaded', initOptionSelector);
|
||||||
|
|
||||||
|
})();
|
||||||
94
assets/snipcart-archive/snipcart.js
Normal file
94
assets/snipcart-archive/snipcart.js
Normal file
|
|
@ -0,0 +1,94 @@
|
||||||
|
window.SnipcartSettings = {
|
||||||
|
publicApiKey:
|
||||||
|
'NGU4ODQ3MjAtY2MzMC00MWEyLWI2YTMtNjBmNGYzMTBlOTZkNjM4OTY1NDY4OTE5MTQyMTI3',
|
||||||
|
loadStrategy: 'on-user-interaction',
|
||||||
|
};
|
||||||
|
|
||||||
|
// Redirection après paiement réussi
|
||||||
|
document.addEventListener('snipcart.ready', function() {
|
||||||
|
Snipcart.events.on('cart.confirmed', function(cartState) {
|
||||||
|
// Détecter la langue actuelle depuis l'URL
|
||||||
|
const currentPath = window.location.pathname;
|
||||||
|
const langMatch = currentPath.match(/^\/([a-z]{2})(\/|$)/);
|
||||||
|
const langPrefix = langMatch ? '/' + langMatch[1] : '';
|
||||||
|
|
||||||
|
window.location.href = langPrefix + '/thanks?order=' + cartState.token;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
(() => {
|
||||||
|
var c, d;
|
||||||
|
(d = (c = window.SnipcartSettings).version) != null || (c.version = '3.0');
|
||||||
|
var s, S;
|
||||||
|
(S = (s = window.SnipcartSettings).timeoutDuration) != null ||
|
||||||
|
(s.timeoutDuration = 2750);
|
||||||
|
var l, p;
|
||||||
|
(p = (l = window.SnipcartSettings).domain) != null ||
|
||||||
|
(l.domain = 'cdn.snipcart.com');
|
||||||
|
var w, u;
|
||||||
|
(u = (w = window.SnipcartSettings).protocol) != null ||
|
||||||
|
(w.protocol = 'https');
|
||||||
|
var f =
|
||||||
|
window.SnipcartSettings.version.includes('v3.0.0-ci') ||
|
||||||
|
(window.SnipcartSettings.version != '3.0' &&
|
||||||
|
window.SnipcartSettings.version.localeCompare('3.4.0', void 0, {
|
||||||
|
numeric: !0,
|
||||||
|
sensitivity: 'base',
|
||||||
|
}) === -1),
|
||||||
|
m = ['focus', 'mouseover', 'touchmove', 'scroll', 'keydown'];
|
||||||
|
window.LoadSnipcart = o;
|
||||||
|
document.readyState === 'loading'
|
||||||
|
? document.addEventListener('DOMContentLoaded', r)
|
||||||
|
: r();
|
||||||
|
function r() {
|
||||||
|
window.SnipcartSettings.loadStrategy
|
||||||
|
? window.SnipcartSettings.loadStrategy === 'on-user-interaction' &&
|
||||||
|
(m.forEach((t) => document.addEventListener(t, o)),
|
||||||
|
setTimeout(o, window.SnipcartSettings.timeoutDuration))
|
||||||
|
: o();
|
||||||
|
}
|
||||||
|
var a = !1;
|
||||||
|
function o() {
|
||||||
|
if (a) return;
|
||||||
|
a = !0;
|
||||||
|
let t = document.getElementsByTagName('head')[0],
|
||||||
|
e = document.querySelector('#snipcart'),
|
||||||
|
i = document.querySelector(
|
||||||
|
`src[src^="${window.SnipcartSettings.protocol}://${window.SnipcartSettings.domain}"][src$="snipcart.js"]`
|
||||||
|
),
|
||||||
|
n = document.querySelector(
|
||||||
|
`link[href^="${window.SnipcartSettings.protocol}://${window.SnipcartSettings.domain}"][href$="snipcart.css"]`
|
||||||
|
);
|
||||||
|
e ||
|
||||||
|
((e = document.createElement('div')),
|
||||||
|
(e.id = 'snipcart'),
|
||||||
|
e.setAttribute('hidden', 'true'),
|
||||||
|
document.body.appendChild(e)),
|
||||||
|
v(e),
|
||||||
|
i ||
|
||||||
|
((i = document.createElement('script')),
|
||||||
|
(i.src = `${window.SnipcartSettings.protocol}://${window.SnipcartSettings.domain}/themes/v${window.SnipcartSettings.version}/default/snipcart.js`),
|
||||||
|
(i.async = !0),
|
||||||
|
t.appendChild(i)),
|
||||||
|
n ||
|
||||||
|
((n = document.createElement('link')),
|
||||||
|
(n.rel = 'stylesheet'),
|
||||||
|
(n.type = 'text/css'),
|
||||||
|
(n.href = `${window.SnipcartSettings.protocol}://${window.SnipcartSettings.domain}/themes/v${window.SnipcartSettings.version}/default/snipcart.css`),
|
||||||
|
t.prepend(n)),
|
||||||
|
m.forEach((g) => document.removeEventListener(g, o));
|
||||||
|
}
|
||||||
|
function v(t) {
|
||||||
|
!f ||
|
||||||
|
((t.dataset.apiKey = window.SnipcartSettings.publicApiKey),
|
||||||
|
window.SnipcartSettings.addProductBehavior &&
|
||||||
|
(t.dataset.configAddProductBehavior =
|
||||||
|
window.SnipcartSettings.addProductBehavior),
|
||||||
|
window.SnipcartSettings.modalStyle &&
|
||||||
|
(t.dataset.configModalStyle = window.SnipcartSettings.modalStyle),
|
||||||
|
window.SnipcartSettings.currency &&
|
||||||
|
(t.dataset.currency = window.SnipcartSettings.currency),
|
||||||
|
window.SnipcartSettings.templatesUrl &&
|
||||||
|
(t.dataset.templatesUrl = window.SnipcartSettings.templatesUrl));
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
Title: Éclairages : 12 entretiens et analyses sur les violences d'État
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
Uuid: gzshayl6xoefrnsz
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
Title: Éclairages : 12 entretiens et analyses sur les violences d’État
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
Shopifyhandle: eclairages-12-entretiens-et-analyses-sur-les-violences-d-etat
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
Uuid: gzshayl6xoefrnsz
|
||||||
|
|
@ -1,52 +0,0 @@
|
||||||
Title: T-shirt Index 01
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
Price: 35
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
Description: <p>T-shirt de soutien à Index, 100% coton</p>
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
Details:
|
|
||||||
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"content": {
|
|
||||||
"text": "<p>100% cotton</p>"
|
|
||||||
},
|
|
||||||
"id": "detail1",
|
|
||||||
"isHidden": false,
|
|
||||||
"type": "text"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"content": {
|
|
||||||
"text": "<p>Lorem ipsum dolor sit amet</p>"
|
|
||||||
},
|
|
||||||
"id": "detail2",
|
|
||||||
"isHidden": false,
|
|
||||||
"type": "text"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
Options:
|
|
||||||
|
|
||||||
-
|
|
||||||
label: Size
|
|
||||||
values: XS, S, M, L, XL
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
Snipcartid: tshirt-01
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
Backgroundcolor: #ffffff
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
Template: product
|
|
||||||
|
|
@ -1,56 +0,0 @@
|
||||||
Title: T-shirt Index 01
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
Price: 35
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
Stock: 10
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
Description: T-shirt de soutien à Index, 100% coton
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
Details: <p>T-shirt en coton organique avec impression sérigraphique.<br>Marquage sur la face avant : logo « INDEX » de 10 cm de large.</p><ul><li><p>100 % coton biologique</p></li><li><p>Grammage : 180 g/m²</p></li><li><p>Jersey simple au toucher très doux</p></li><li><p>Excellente tenue dans le temps</p></li><li><p>Bande de propreté intérieure au col</p></li><li><p>Surpiqûres doubles en bas de manches et en bas de corps</p></li></ul><p>Envoi uniquement via Mondial Relay vers la France, la Belgique et la Suisse.</p>
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
Hasoptions: true
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
Optionlabel: Taille
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
Optionvalues: XS, S, M, L, XL
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
Options:
|
|
||||||
|
|
||||||
-
|
|
||||||
label: Taille
|
|
||||||
values: XS, S, M, L, XL
|
|
||||||
-
|
|
||||||
label: Couleur
|
|
||||||
values: Rouge, Vert
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
Snipcartid: tshirt-01
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
Backgroundcolor: #ffffff
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
Template: product
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
Uuid: udrrfizhayqixfoo
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 262 KiB |
|
|
@ -1,9 +0,0 @@
|
||||||
Sort: 1
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
Uuid: elxkhcta8dkjhr60
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
Template: image
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 2.4 MiB |
|
|
@ -1,9 +0,0 @@
|
||||||
Sort: 2
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
Uuid: deupkqq83jvloz0r
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
Template: image
|
|
||||||
5
content/2_t-shirt-index/product.en.txt
Normal file
5
content/2_t-shirt-index/product.en.txt
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
Title: T-shirt Index
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
Uuid: qq27mjjpethsvnwp
|
||||||
9
content/2_t-shirt-index/product.fr.txt
Normal file
9
content/2_t-shirt-index/product.fr.txt
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
Title: T-shirt Index
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
Shopifyhandle: t-shirt-index-01
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
Uuid: qq27mjjpethsvnwp
|
||||||
|
|
@ -1,134 +1,21 @@
|
||||||
title:
|
title: Product
|
||||||
en: Product
|
|
||||||
fr: Produit
|
|
||||||
icon: cart
|
icon: cart
|
||||||
|
|
||||||
tabs:
|
columns:
|
||||||
content:
|
- width: 1/1
|
||||||
label:
|
fields:
|
||||||
en: Content
|
info:
|
||||||
fr: Contenu
|
type: info
|
||||||
columns:
|
text:
|
||||||
- width: 2/3
|
en: "Product data (title, description, images, price) is managed in Shopify Admin. This Kirby page only serves for routing."
|
||||||
sections:
|
fr: "Les données produit (titre, description, images, prix) sont gérées dans Shopify Admin. Cette page Kirby sert uniquement au routing."
|
||||||
main:
|
|
||||||
type: fields
|
|
||||||
fields:
|
|
||||||
price:
|
|
||||||
label:
|
|
||||||
en: Price (€)
|
|
||||||
fr: Prix (€)
|
|
||||||
type: number
|
|
||||||
min: 0
|
|
||||||
step: 0.01
|
|
||||||
required: true
|
|
||||||
translate: false
|
|
||||||
width: 1/4
|
|
||||||
stock:
|
|
||||||
label: Stock
|
|
||||||
type: number
|
|
||||||
min: 0
|
|
||||||
default: 0
|
|
||||||
help:
|
|
||||||
en: Edit through french version
|
|
||||||
fr: Partagé entre les versions FR et EN
|
|
||||||
translate: false
|
|
||||||
width: 1/4
|
|
||||||
space:
|
|
||||||
type: gap
|
|
||||||
width: 2/4
|
|
||||||
weight:
|
|
||||||
label:
|
|
||||||
en: Weight (g)
|
|
||||||
fr: Poids (g)
|
|
||||||
type: number
|
|
||||||
min: 0
|
|
||||||
default: 0
|
|
||||||
help:
|
|
||||||
en: Weight in grams for shipping calculation
|
|
||||||
fr: Poids en grammes pour le calcul de la livraison
|
|
||||||
translate: false
|
|
||||||
width: 1/4
|
|
||||||
length:
|
|
||||||
label:
|
|
||||||
en: Length (cm)
|
|
||||||
fr: Longueur (cm)
|
|
||||||
type: number
|
|
||||||
min: 0
|
|
||||||
default: 0
|
|
||||||
help:
|
|
||||||
en: Package length in centimeters
|
|
||||||
fr: Longueur du colis en centimètres
|
|
||||||
translate: false
|
|
||||||
width: 1/4
|
|
||||||
width:
|
|
||||||
label:
|
|
||||||
en: Width (cm)
|
|
||||||
fr: Largeur (cm)
|
|
||||||
type: number
|
|
||||||
min: 0
|
|
||||||
default: 0
|
|
||||||
help:
|
|
||||||
en: Package width in centimeters
|
|
||||||
fr: Largeur du colis en centimètres
|
|
||||||
translate: false
|
|
||||||
width: 1/4
|
|
||||||
height:
|
|
||||||
label:
|
|
||||||
en: Height (cm)
|
|
||||||
fr: Hauteur (cm)
|
|
||||||
type: number
|
|
||||||
min: 0
|
|
||||||
default: 0
|
|
||||||
help:
|
|
||||||
en: Package height in centimeters
|
|
||||||
fr: Hauteur du colis en centimètres
|
|
||||||
translate: false
|
|
||||||
width: 1/4
|
|
||||||
description:
|
|
||||||
label: Description panier
|
|
||||||
type: writer
|
|
||||||
help: Visible dans le panier seulement.
|
|
||||||
details:
|
|
||||||
label:
|
|
||||||
en: Details
|
|
||||||
fr: Détails
|
|
||||||
type: writer
|
|
||||||
hasOptions:
|
|
||||||
label:
|
|
||||||
en: Options
|
|
||||||
fr: Options
|
|
||||||
type: toggle
|
|
||||||
default: false
|
|
||||||
translate: false
|
|
||||||
width: 1/7
|
|
||||||
optionLabel:
|
|
||||||
label:
|
|
||||||
en: Option label
|
|
||||||
fr: Libellé de l'option
|
|
||||||
type: text
|
|
||||||
width: 3/7
|
|
||||||
when:
|
|
||||||
hasOptions: true
|
|
||||||
optionValues:
|
|
||||||
label:
|
|
||||||
en: Option values
|
|
||||||
fr: Valeurs de l'option
|
|
||||||
type: tags
|
|
||||||
help:
|
|
||||||
en: "Comma-separated values (e.g.: XS, S, M, L, XL)"
|
|
||||||
fr: "Valeurs séparées par des virgules (ex: XS, S, M, L, XL)"
|
|
||||||
translate: false
|
|
||||||
when:
|
|
||||||
hasOptions: true
|
|
||||||
width: 3/7
|
|
||||||
|
|
||||||
- width: 1/3
|
shopifyHandle:
|
||||||
sections:
|
label:
|
||||||
images:
|
en: Shopify Handle
|
||||||
type: files
|
fr: Shopify Handle
|
||||||
headline:
|
type: text
|
||||||
en: Product Images
|
help:
|
||||||
fr: Images du produit
|
en: "Product handle from Shopify (e.g. tshirt-index-01). If empty, uses the page slug."
|
||||||
template: image
|
fr: "Handle du produit Shopify (ex: tshirt-index-01). Si vide, utilise le slug de la page Kirby."
|
||||||
layout: cards
|
placeholder: tshirt-index-01
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,8 @@ return [
|
||||||
],
|
],
|
||||||
|
|
||||||
'routes' => [
|
'routes' => [
|
||||||
|
// SNIPCART ROUTES - Désactivées, voir assets/snipcart-archive/README.md pour restauration
|
||||||
|
/*
|
||||||
[
|
[
|
||||||
'pattern' => '(:any)/validate.json',
|
'pattern' => '(:any)/validate.json',
|
||||||
'method' => 'GET',
|
'method' => 'GET',
|
||||||
|
|
@ -144,5 +146,6 @@ return [
|
||||||
return Response::json(['status' => 'success'], 200);
|
return Response::json(['status' => 'success'], 200);
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
*/
|
||||||
]
|
]
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,23 @@ return [
|
||||||
'backToShop' => 'Back to shop',
|
'backToShop' => 'Back to shop',
|
||||||
'supportText' => 'To support us, you can also',
|
'supportText' => 'To support us, you can also',
|
||||||
'makeDonation' => 'make a donation',
|
'makeDonation' => 'make a donation',
|
||||||
|
|
||||||
|
// Shop / Cart
|
||||||
'addToCart' => 'Add to cart',
|
'addToCart' => 'Add to cart',
|
||||||
|
'cart' => 'Cart',
|
||||||
|
'cartEmpty' => 'Your cart is empty',
|
||||||
|
'total' => 'Total',
|
||||||
|
'checkout' => 'Checkout',
|
||||||
|
'remove' => 'Remove',
|
||||||
|
'inStock' => 'In stock',
|
||||||
|
'outOfStock' => 'Out of stock',
|
||||||
|
'addingToCart' => 'Adding...',
|
||||||
|
'addedToCart' => 'Added! ✓',
|
||||||
|
'errorAddToCart' => 'Error - Try again',
|
||||||
|
'closeCart' => 'Close cart',
|
||||||
|
'loading' => 'Loading...',
|
||||||
|
'productNotFound' => 'Product not found',
|
||||||
|
'selectVariant' => 'Select',
|
||||||
|
|
||||||
// Blueprints - Home
|
// Blueprints - Home
|
||||||
'home.title' => 'Home',
|
'home.title' => 'Home',
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,23 @@ return [
|
||||||
'backToShop' => 'Retour à la boutique',
|
'backToShop' => 'Retour à la boutique',
|
||||||
'supportText' => 'Pour nous soutenir, vous pouvez aussi',
|
'supportText' => 'Pour nous soutenir, vous pouvez aussi',
|
||||||
'makeDonation' => 'faire un don',
|
'makeDonation' => 'faire un don',
|
||||||
|
|
||||||
|
// Shop / Cart
|
||||||
'addToCart' => 'Ajouter au panier',
|
'addToCart' => 'Ajouter au panier',
|
||||||
|
'cart' => 'Panier',
|
||||||
|
'cartEmpty' => 'Votre panier est vide',
|
||||||
|
'total' => 'Total',
|
||||||
|
'checkout' => 'Passer commande',
|
||||||
|
'remove' => 'Retirer',
|
||||||
|
'inStock' => 'En stock',
|
||||||
|
'outOfStock' => 'Rupture de stock',
|
||||||
|
'addingToCart' => 'Ajout en cours...',
|
||||||
|
'addedToCart' => 'Ajouté ! ✓',
|
||||||
|
'errorAddToCart' => 'Erreur - Réessayer',
|
||||||
|
'closeCart' => 'Fermer le panier',
|
||||||
|
'loading' => 'Chargement...',
|
||||||
|
'productNotFound' => 'Produit non trouvé',
|
||||||
|
'selectVariant' => 'Choisir',
|
||||||
|
|
||||||
// Blueprints - Home
|
// Blueprints - Home
|
||||||
'home.title' => 'Accueil',
|
'home.title' => 'Accueil',
|
||||||
|
|
|
||||||
20
site/snippets/buy-button--t-shirt.php
Normal file
20
site/snippets/buy-button--t-shirt.php
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
<div class="product-purchase">
|
||||||
|
<div class="product-stock-info">
|
||||||
|
<p data-product-stock class="stock-status"></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
class="btn-add-to-cart"
|
||||||
|
data-shopify-add-to-cart
|
||||||
|
data-product-id="15689076179317"
|
||||||
|
data-variant-id=""
|
||||||
|
data-text-add="<?= t('addToCart') ?>"
|
||||||
|
data-text-adding="<?= t('addingToCart') ?>"
|
||||||
|
data-text-added="<?= t('addedToCart') ?>"
|
||||||
|
data-text-error="<?= t('errorAddToCart') ?>"
|
||||||
|
data-text-out-of-stock="<?= t('outOfStock') ?>"
|
||||||
|
data-text-in-stock="<?= t('inStock') ?>"
|
||||||
|
>
|
||||||
|
<?= t('addToCart') ?>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
25
site/snippets/buy-button.php
Normal file
25
site/snippets/buy-button.php
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
<div class="product-purchase">
|
||||||
|
<div class="product-stock-info">
|
||||||
|
<p data-product-stock class="stock-status"></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="product-variants" data-product-variants style="display: none;">
|
||||||
|
<label for="variant-select"><?= t('selectVariant') ?></label>
|
||||||
|
<select id="variant-select" data-variant-selector></select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
class="btn-add-to-cart"
|
||||||
|
data-shopify-add-to-cart
|
||||||
|
data-product-id=""
|
||||||
|
data-variant-id=""
|
||||||
|
data-text-add="<?= t('addToCart') ?>"
|
||||||
|
data-text-adding="<?= t('addingToCart') ?>"
|
||||||
|
data-text-added="<?= t('addedToCart') ?>"
|
||||||
|
data-text-error="<?= t('errorAddToCart') ?>"
|
||||||
|
data-text-out-of-stock="<?= t('outOfStock') ?>"
|
||||||
|
data-text-in-stock="<?= t('inStock') ?>"
|
||||||
|
>
|
||||||
|
<?= t('addToCart') ?>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
32
site/snippets/cart-drawer.php
Normal file
32
site/snippets/cart-drawer.php
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
<div id="cart-drawer" class="cart-drawer" data-text-remove="<?= t('remove') ?>">
|
||||||
|
<div class="cart-drawer__overlay" data-cart-close></div>
|
||||||
|
<div class="cart-drawer__panel">
|
||||||
|
<div class="cart-drawer__header">
|
||||||
|
<h3><?= t('cart') ?></h3>
|
||||||
|
<button class="cart-drawer__close" data-cart-close aria-label="<?= t('closeCart') ?>">
|
||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
|
<line x1="18" y1="6" x2="6" y2="18"></line>
|
||||||
|
<line x1="6" y1="6" x2="18" y2="18"></line>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="cart-drawer__content">
|
||||||
|
<div class="cart-drawer__empty" data-cart-empty>
|
||||||
|
<p><?= t('cartEmpty') ?></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="cart-drawer__items" data-cart-items></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="cart-drawer__footer">
|
||||||
|
<div class="cart-drawer__total">
|
||||||
|
<span class="cart-drawer__total-label"><?= t('total') ?></span>
|
||||||
|
<span class="cart-drawer__total-amount" data-cart-total>0,00 €</span>
|
||||||
|
</div>
|
||||||
|
<button class="cart-drawer__checkout-btn" data-cart-checkout>
|
||||||
|
<?= t('checkout') ?>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
<?php snippet('cart-drawer') ?>
|
||||||
|
|
||||||
<footer id="site-footer">
|
<footer id="site-footer">
|
||||||
<div class="site-footer__container">
|
<div class="site-footer__container">
|
||||||
<div class="footer__mentions">
|
<div class="footer__mentions">
|
||||||
|
|
@ -10,10 +12,17 @@
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
<script src="<?= url('assets/js/onload.js') ?>"></script>
|
<script src="<?= url('assets/js/onload.js') ?>"></script>
|
||||||
<?php if(isset($scripts) && is_array($scripts)): ?>
|
<script src="<?= url('assets/js/shopify-cart.js') ?>"></script>
|
||||||
<?php foreach($scripts as $script): ?>
|
|
||||||
<script src="<?= url($script) ?>"></script>
|
<?php if ($scripts ?? null): ?>
|
||||||
<?php endforeach ?>
|
<?php if (in_array('product', $scripts)): ?>
|
||||||
|
<script src="<?= url('assets/js/product-loader.js') ?>"></script>
|
||||||
|
<script src="<?= url('assets/js/product-add-to-cart.js') ?>"></script>
|
||||||
|
<?php endif ?>
|
||||||
|
<?php else: ?>
|
||||||
|
<script src="<?= url('assets/js/products-list-loader.js') ?>"></script>
|
||||||
<?php endif ?>
|
<?php endif ?>
|
||||||
|
|
||||||
|
<script src="<?= url('assets/js/cart-drawer.js') ?>"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@
|
||||||
|
|
||||||
<link rel="stylesheet" type="text/css" href="<?= url('assets/fonts/stylesheet.css') ?>?version-cache-prevent<?= rand(0, 1000)?>" />
|
<link rel="stylesheet" type="text/css" href="<?= url('assets/fonts/stylesheet.css') ?>?version-cache-prevent<?= rand(0, 1000)?>" />
|
||||||
<link rel="stylesheet" type="text/css" href="<?= url('assets/css/style.css') ?>" />
|
<link rel="stylesheet" type="text/css" href="<?= url('assets/css/style.css') ?>" />
|
||||||
|
<link rel="stylesheet" type="text/css" href="<?= url('assets/css/cart-drawer.css') ?>" />
|
||||||
</head>
|
</head>
|
||||||
<body data-template="<?= $page->template() ?>">
|
<body data-template="<?= $page->template() ?>">
|
||||||
<header id="site-header">
|
<header id="site-header">
|
||||||
|
|
@ -46,5 +47,9 @@
|
||||||
</li>
|
</li>
|
||||||
<?php endforeach ?>
|
<?php endforeach ?>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
<button class="header-cart-btn" data-cart-open aria-label="<?= t('cart') ?>">
|
||||||
|
<?= t('cart') ?> <span class="header-cart-count" data-cart-count></span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
|
||||||
|
|
@ -1,35 +1,24 @@
|
||||||
<?php snippet('header', ['title' => $site->title(), 'template' => 'store']) ?>
|
<?php snippet('header', ['title' => $site->title(), 'template' => 'store']) ?>
|
||||||
|
|
||||||
<main>
|
<main>
|
||||||
<p class="p__baseline-big">
|
<p class="p__baseline-big">
|
||||||
<?= $page->baseline()->or('Bienvenue sur la boutique de soutien à Index') ?>
|
<?= $page->baseline()->or('Bienvenue sur la boutique de soutien à Index') ?>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<section id="store__container">
|
<section id="store__container"
|
||||||
<?php foreach($site->children()->listed() as $product): ?>
|
data-products-loader
|
||||||
<article class="store__product">
|
data-language="<?= strtoupper($kirby->language()->code()) ?>">
|
||||||
<figure>
|
|
||||||
<?php if($cover = $product->files()->sortBy('sort', 'asc')->first()): ?>
|
|
||||||
<?php snippet('picture', [
|
|
||||||
'file' => $cover,
|
|
||||||
'alt' => $product->title()->html(),
|
|
||||||
'preset' => 'product-card',
|
|
||||||
'size' => 25,
|
|
||||||
'lazy' => true
|
|
||||||
]) ?>
|
|
||||||
<?php endif ?>
|
|
||||||
</figure>
|
|
||||||
<p class="line-1"><a href="<?= $product->url() ?>"><?= $product->title()->html() ?></a></p>
|
|
||||||
<p class="price"><?= $product->price() ?>€</p>
|
|
||||||
<a href="<?= $product->url() ?>" class="link-block" aria-hidden="true"></a>
|
|
||||||
</article>
|
|
||||||
<?php endforeach ?>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<p class="p__baseline-big">
|
<div class="products-loading">
|
||||||
<?= t('supportText', 'Pour nous soutenir, vous pouvez aussi') ?>
|
<p><?= t('loading') ?></p>
|
||||||
<a href="https://soutenir.index.ngo" class="link-don"><?= t('makeDonation', 'faire un don') ?></a>
|
</div>
|
||||||
</p>
|
|
||||||
</main>
|
</section>
|
||||||
|
|
||||||
|
<p class="p__baseline-big">
|
||||||
|
<?= t('supportText') ?>
|
||||||
|
<a href="https://soutenir.index.ngo" class="link-don"><?= t('makeDonation') ?></a>
|
||||||
|
</p>
|
||||||
|
</main>
|
||||||
|
|
||||||
<?php snippet('footer') ?>
|
<?php snippet('footer') ?>
|
||||||
|
|
|
||||||
|
|
@ -1,114 +1,47 @@
|
||||||
<?php snippet('header', ['title' => $page->title(), 'template' => 'shop']) ?>
|
<?php
|
||||||
|
$shopifyHandle = $page->shopifyHandle()->or($page->slug());
|
||||||
|
|
||||||
<main>
|
snippet('header', ['title' => $page->title(), 'template' => 'shop']);
|
||||||
<nav class="store__nav">
|
?>
|
||||||
<a href="<?= $site->homePage()->url() ?>"><?= t('backToShop', 'Retour à la boutique') ?></a>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<section class="section__product">
|
<main>
|
||||||
|
<nav class="store__nav">
|
||||||
|
<a href="<?= $site->homePage()->url() ?>"><?= t('backToShop') ?></a>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<section class="section__product"
|
||||||
|
data-product-loader
|
||||||
|
data-shopify-handle="<?= $shopifyHandle ?>"
|
||||||
|
data-language="<?= $kirby->language()->code() ?>">
|
||||||
|
|
||||||
|
<div class="product-loading">
|
||||||
|
<p><?= t('loading') ?></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="product-content" style="display: none;">
|
||||||
<div class="col-left">
|
<div class="col-left">
|
||||||
<div class="hero">
|
<div class="hero">
|
||||||
<h2 class="p__baseline-big"><?= $page->title()->html() ?></h2>
|
<h2 class="p__baseline-big" data-product-title></h2>
|
||||||
<p class="p__baseline-big"><?= $page->price() ?>€</p>
|
<p class="p__baseline-big" data-product-price></p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="details">
|
<div class="details" data-product-details></div>
|
||||||
<?php if($page->details()->isNotEmpty()): ?>
|
|
||||||
<?= $page->details()->kt() ?>
|
|
||||||
<?php endif ?>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<?php if($page->hasOptions()->toBool() && $page->optionValues()->isNotEmpty()): ?>
|
<?php snippet('buy-button') ?>
|
||||||
<div class="product-options">
|
|
||||||
<ul class="product-options__list">
|
|
||||||
<?php
|
|
||||||
$values = $page->optionValues()->split(',');
|
|
||||||
$optionSlug = $page->optionLabel()->slug();
|
|
||||||
foreach($values as $index => $value):
|
|
||||||
$value = trim($value);
|
|
||||||
$uniqueId = $optionSlug . '-' . Str::slug(strtolower($value));
|
|
||||||
?>
|
|
||||||
<li>
|
|
||||||
<input type="radio" id="<?= $uniqueId ?>" name="<?= $optionSlug ?>" value="<?= $value ?>" />
|
|
||||||
<label for="<?= $uniqueId ?>"><?= $value ?></label>
|
|
||||||
</li>
|
|
||||||
<?php endforeach ?>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<?php endif ?>
|
|
||||||
|
|
||||||
<div class="add-to-cart">
|
|
||||||
<button
|
|
||||||
class="btn__default snipcart-add-item"
|
|
||||||
data-item-id="<?= $page->slug() ?>"
|
|
||||||
data-item-price="<?= $page->price() ?>"
|
|
||||||
data-item-description="<?= $page->description()->excerpt(100) ?>"
|
|
||||||
data-item-image="<?= $page->images()->first() ? $page->images()->first()->url() : '' ?>"
|
|
||||||
data-item-name="<?= $page->title()->html() ?>"
|
|
||||||
data-item-shippable="true"
|
|
||||||
data-item-weight="<?= $page->weight()->or(0) ?>"
|
|
||||||
data-item-length="<?= $page->length()->or(0) ?>"
|
|
||||||
data-item-width="<?= $page->width()->or(0) ?>"
|
|
||||||
data-item-height="<?= $page->height()->or(0) ?>"
|
|
||||||
<?php
|
|
||||||
if($page->hasOptions()->toBool() && $page->optionValues()->isNotEmpty()):
|
|
||||||
$values = $page->optionValues()->split(',');
|
|
||||||
$trimmedValues = array_map('trim', $values);
|
|
||||||
$snipcartOptions = implode('|', $trimmedValues);
|
|
||||||
?>
|
|
||||||
data-item-custom1-name="<?= $page->optionLabel()->html() ?>"
|
|
||||||
data-item-custom1-options="<?= $snipcartOptions ?>"
|
|
||||||
data-item-custom1-required="true"
|
|
||||||
disabled
|
|
||||||
<?php endif; ?>
|
|
||||||
>
|
|
||||||
<span class="icon">
|
|
||||||
<svg clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="2" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path d="m14.523 18.787s4.501-4.505 6.255-6.26c.146-.146.219-.338.219-.53s-.073-.383-.219-.530c-1.753-1.754-6.255-6.258-6.255-6.258-.144-.145-.334-.217-.524-.217-.193 0-.385.074-.532.221-.293.292-.295.766-.004 1.056l4.978 4.978h-14.692c-.414 0-.75.336-.75.75s.336.75.75.75h14.692l-4.979 4.979c-.289.289-.286.762.006 1.054.148.148.341.222.533.222.19 0 .378-.072.522-.215z" fill-rule="nonzero" />
|
|
||||||
</svg>
|
|
||||||
</span>
|
|
||||||
<div class="txt" data-default-text="<?= t('addToCart', 'Ajouter au panier') ?>">
|
|
||||||
<?php if($page->hasOptions()->toBool() && $page->optionValues()->isNotEmpty()): ?>
|
|
||||||
<?= t('chooseOption', 'Choisissez une option') ?>
|
|
||||||
<?php else: ?>
|
|
||||||
<?= t('addToCart', 'Ajouter au panier') ?>
|
|
||||||
<?php endif ?>
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="product-gallery swiper">
|
<div class="product-gallery swiper">
|
||||||
<div class="swiper-wrapper">
|
<div class="swiper-wrapper" data-product-images></div>
|
||||||
<?php
|
|
||||||
if ($page->hasFiles()):
|
|
||||||
foreach($page->files()->sortBy('sort', 'asc') as $image):
|
|
||||||
?>
|
|
||||||
<div class="swiper-slide">
|
|
||||||
<figure>
|
|
||||||
<?php snippet('picture', [
|
|
||||||
'file' => $image,
|
|
||||||
'alt' => $page->title()->html(),
|
|
||||||
'preset' => 'product-detail',
|
|
||||||
'size' => 50,
|
|
||||||
'lazy' => false
|
|
||||||
]) ?>
|
|
||||||
</figure>
|
|
||||||
</div>
|
|
||||||
<?php
|
|
||||||
endforeach;
|
|
||||||
endif;
|
|
||||||
?>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Navigation arrows -->
|
|
||||||
<div class="swiper-button-prev"></div>
|
<div class="swiper-button-prev"></div>
|
||||||
<div class="swiper-button-next"></div>
|
<div class="swiper-button-next"></div>
|
||||||
|
|
||||||
<!-- Pagination dots -->
|
|
||||||
<div class="swiper-pagination"></div>
|
<div class="swiper-pagination"></div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</div>
|
||||||
</main>
|
|
||||||
|
|
||||||
<?php snippet('footer', ['scripts' => ['assets/js/product-size.js', 'assets/js/snipcart.js', 'assets/js/product-gallery.js']]) ?>
|
<div class="product-error" style="display: none;">
|
||||||
|
<p><?= t('productNotFound') ?></p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<?php snippet('footer', ['scripts' => ['product']]) ?>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue