Compare commits
No commits in common. "0c8cc5000ca415df2112f6e9dfcaed62582a610e" and "84aa4cac17679637ea3b0eb536ac62ce3c1fa04e" have entirely different histories.
0c8cc5000c
...
84aa4cac17
39 changed files with 707 additions and 2445 deletions
|
|
@ -6,8 +6,7 @@
|
||||||
"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,9 +51,4 @@ 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/*
|
|
||||||
|
|
@ -1,68 +0,0 @@
|
||||||
.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;
|
|
||||||
}
|
|
||||||
|
|
@ -1,254 +0,0 @@
|
||||||
/* 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,20 +43,14 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.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;
|
||||||
|
|
@ -81,39 +75,4 @@
|
||||||
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,18 +524,13 @@ 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;
|
||||||
|
|
@ -558,35 +553,6 @@ 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;
|
||||||
|
|
@ -1053,6 +1019,13 @@ 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);
|
||||||
|
|
@ -1062,17 +1035,11 @@ 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;
|
||||||
}
|
}
|
||||||
@media screen and (max-width: 720px) {
|
.store__nav a::before {
|
||||||
.store__nav a {
|
content: "← ";
|
||||||
padding-top: 0;
|
|
||||||
font-size: var(--fs-small);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.section__product .details ul {
|
.section__product .details ul {
|
||||||
|
|
@ -1081,7 +1048,122 @@ 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;
|
||||||
|
|
@ -1120,7 +1202,7 @@ body.is-fullscreen {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@media screen and (min-width: 720px) {
|
@media screen and (min-width: 720px) {
|
||||||
.section__product .product-content {
|
.section__product {
|
||||||
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);
|
||||||
|
|
@ -1139,123 +1221,10 @@ 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;
|
||||||
|
|
@ -1289,296 +1258,8 @@ body.is-fullscreen {
|
||||||
margin-top: calc(var(--spacing) * 4);
|
margin-top: calc(var(--spacing) * 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
.product-purchase {
|
.snipcart-modal__container {
|
||||||
margin-top: 2rem;
|
z-index: 1000;
|
||||||
}
|
|
||||||
|
|
||||||
.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,7 +22,6 @@
|
||||||
@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 "components/shopify-buy-button.scss";
|
@import "template/shop/snipcart";
|
||||||
@import "components/shopify-cart-drawer.scss";
|
|
||||||
|
|
||||||
@import "template/subscription-newsletter/layout";
|
@import "template/subscription-newsletter/layout";
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,12 @@
|
||||||
|
.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;
|
||||||
|
|
@ -5,6 +14,10 @@
|
||||||
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);
|
||||||
|
|
@ -14,35 +27,174 @@
|
||||||
a {
|
a {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
|
||||||
&::before {
|
|
||||||
content: "← ";
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media #{$small} {
|
a::before {
|
||||||
a {
|
content: "← ";
|
||||||
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 {
|
||||||
padding-bottom: 0.2em;
|
position: relative;
|
||||||
|
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media #{$small} {
|
// Swiper navigation arrows
|
||||||
|
.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;
|
||||||
|
|
@ -55,7 +207,6 @@
|
||||||
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);
|
||||||
|
|
@ -75,25 +226,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 {
|
.swiper-button-next{ display: none; }
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@media #{$small-up} {
|
@media #{$small-up} {
|
||||||
.product-content {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 1fr;
|
.section__product{
|
||||||
gap: calc(var(--padding-body) * 2);
|
display: grid;
|
||||||
margin-bottom: calc(var(--spacing) * 3);
|
grid-template-columns: 1fr 1fr;
|
||||||
}
|
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);
|
||||||
|
|
@ -104,140 +255,17 @@
|
||||||
border-top: var(--border-light);
|
border-top: var(--border-light);
|
||||||
}
|
}
|
||||||
|
|
||||||
.col-left {
|
.col-left{
|
||||||
min-height: 100%;
|
min-height: 100%;
|
||||||
padding-bottom: 40px;
|
padding-bottom: 40px; //dots
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
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,
|
.product-gallery .swiper-slide figure{
|
||||||
.swiper-button-next {
|
width: calc(100% - 60px);
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,275 +0,0 @@
|
||||||
/**
|
|
||||||
* 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();
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
|
|
@ -1,63 +0,0 @@
|
||||||
(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);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
})();
|
|
||||||
|
|
@ -1,188 +0,0 @@
|
||||||
(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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
|
|
@ -1,47 +0,0 @@
|
||||||
(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>';
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
|
|
@ -1,343 +0,0 @@
|
||||||
/**
|
|
||||||
* 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;
|
|
||||||
|
|
@ -1,164 +0,0 @@
|
||||||
# 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
|
|
||||||
|
|
@ -1,66 +0,0 @@
|
||||||
/**
|
|
||||||
* 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);
|
|
||||||
|
|
||||||
})();
|
|
||||||
|
|
@ -1,94 +0,0 @@
|
||||||
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));
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
Title: Éclairages : 12 entretiens et analyses sur les violences d'État
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
Uuid: gzshayl6xoefrnsz
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
Title: Éclairages : 12 entretiens et analyses sur les violences d’État
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
Shopifyhandle: eclairages-12-entretiens-et-analyses-sur-les-violences-d-etat
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
Uuid: gzshayl6xoefrnsz
|
|
||||||
52
content/1_tshirt-index-01/product.en.txt
Normal file
52
content/1_tshirt-index-01/product.en.txt
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
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
|
||||||
56
content/1_tshirt-index-01/product.fr.txt
Normal file
56
content/1_tshirt-index-01/product.fr.txt
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
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
|
||||||
BIN
content/1_tshirt-index-01/test.png
Normal file
BIN
content/1_tshirt-index-01/test.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 262 KiB |
9
content/1_tshirt-index-01/test.png.fr.txt
Normal file
9
content/1_tshirt-index-01/test.png.fr.txt
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
Sort: 1
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
Uuid: elxkhcta8dkjhr60
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
Template: image
|
||||||
BIN
content/1_tshirt-index-01/tshirt-01.png
Normal file
BIN
content/1_tshirt-index-01/tshirt-01.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.4 MiB |
9
content/1_tshirt-index-01/tshirt-01.png.fr.txt
Normal file
9
content/1_tshirt-index-01/tshirt-01.png.fr.txt
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
Sort: 2
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
Uuid: deupkqq83jvloz0r
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
Template: image
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
Title: T-shirt Index
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
Uuid: qq27mjjpethsvnwp
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
Title: T-shirt Index
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
Shopifyhandle: t-shirt-index-01
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
Uuid: qq27mjjpethsvnwp
|
|
||||||
|
|
@ -1,21 +1,134 @@
|
||||||
title: Product
|
title:
|
||||||
|
en: Product
|
||||||
|
fr: Produit
|
||||||
icon: cart
|
icon: cart
|
||||||
|
|
||||||
columns:
|
tabs:
|
||||||
- width: 1/1
|
content:
|
||||||
fields:
|
label:
|
||||||
info:
|
en: Content
|
||||||
type: info
|
fr: Contenu
|
||||||
text:
|
columns:
|
||||||
en: "Product data (title, description, images, price) is managed in Shopify Admin. This Kirby page only serves for routing."
|
- width: 2/3
|
||||||
fr: "Les données produit (titre, description, images, prix) sont gérées dans Shopify Admin. Cette page Kirby sert uniquement au routing."
|
sections:
|
||||||
|
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
|
||||||
|
|
||||||
shopifyHandle:
|
- width: 1/3
|
||||||
label:
|
sections:
|
||||||
en: Shopify Handle
|
images:
|
||||||
fr: Shopify Handle
|
type: files
|
||||||
type: text
|
headline:
|
||||||
help:
|
en: Product Images
|
||||||
en: "Product handle from Shopify (e.g. tshirt-index-01). If empty, uses the page slug."
|
fr: Images du produit
|
||||||
fr: "Handle du produit Shopify (ex: tshirt-index-01). Si vide, utilise le slug de la page Kirby."
|
template: image
|
||||||
placeholder: tshirt-index-01
|
layout: cards
|
||||||
|
|
|
||||||
|
|
@ -37,8 +37,6 @@ 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',
|
||||||
|
|
@ -146,6 +144,5 @@ return [
|
||||||
return Response::json(['status' => 'success'], 200);
|
return Response::json(['status' => 'success'], 200);
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
*/
|
|
||||||
]
|
]
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -13,23 +13,7 @@ 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,23 +13,7 @@ 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',
|
||||||
|
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
<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>
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
<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>
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
<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,5 +1,3 @@
|
||||||
<?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">
|
||||||
|
|
@ -12,17 +10,10 @@
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
<script src="<?= url('assets/js/onload.js') ?>"></script>
|
<script src="<?= url('assets/js/onload.js') ?>"></script>
|
||||||
<script src="<?= url('assets/js/shopify-cart.js') ?>"></script>
|
<?php if(isset($scripts) && is_array($scripts)): ?>
|
||||||
|
<?php foreach($scripts as $script): ?>
|
||||||
<?php if ($scripts ?? null): ?>
|
<script src="<?= url($script) ?>"></script>
|
||||||
<?php if (in_array('product', $scripts)): ?>
|
<?php endforeach ?>
|
||||||
<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,7 +12,6 @@
|
||||||
|
|
||||||
<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">
|
||||||
|
|
@ -47,9 +46,5 @@
|
||||||
</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,24 +1,35 @@
|
||||||
<?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">
|
||||||
data-products-loader
|
<?php foreach($site->children()->listed() as $product): ?>
|
||||||
data-language="<?= strtoupper($kirby->language()->code()) ?>">
|
<article class="store__product">
|
||||||
|
<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>
|
||||||
|
|
||||||
<div class="products-loading">
|
<p class="p__baseline-big">
|
||||||
<p><?= t('loading') ?></p>
|
<?= t('supportText', 'Pour nous soutenir, vous pouvez aussi') ?>
|
||||||
</div>
|
<a href="https://soutenir.index.ngo" class="link-don"><?= t('makeDonation', 'faire un don') ?></a>
|
||||||
|
</p>
|
||||||
</section>
|
</main>
|
||||||
|
|
||||||
<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,47 +1,114 @@
|
||||||
<?php
|
<?php snippet('header', ['title' => $page->title(), 'template' => 'shop']) ?>
|
||||||
$shopifyHandle = $page->shopifyHandle()->or($page->slug());
|
|
||||||
|
|
||||||
snippet('header', ['title' => $page->title(), 'template' => 'shop']);
|
<main>
|
||||||
?>
|
<nav class="store__nav">
|
||||||
|
<a href="<?= $site->homePage()->url() ?>"><?= t('backToShop', 'Retour à la boutique') ?></a>
|
||||||
|
</nav>
|
||||||
|
|
||||||
<main>
|
<section class="section__product">
|
||||||
<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" data-product-title></h2>
|
<h2 class="p__baseline-big"><?= $page->title()->html() ?></h2>
|
||||||
<p class="p__baseline-big" data-product-price></p>
|
<p class="p__baseline-big"><?= $page->price() ?>€</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="details" data-product-details></div>
|
<div class="details">
|
||||||
|
<?php if($page->details()->isNotEmpty()): ?>
|
||||||
|
<?= $page->details()->kt() ?>
|
||||||
|
<?php endif ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
<?php snippet('buy-button') ?>
|
<?php if($page->hasOptions()->toBool() && $page->optionValues()->isNotEmpty()): ?>
|
||||||
|
<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" data-product-images></div>
|
<div class="swiper-wrapper">
|
||||||
|
<?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>
|
||||||
</div>
|
</section>
|
||||||
|
</main>
|
||||||
|
|
||||||
<div class="product-error" style="display: none;">
|
<?php snippet('footer', ['scripts' => ['assets/js/product-size.js', 'assets/js/snipcart.js', 'assets/js/product-gallery.js']]) ?>
|
||||||
<p><?= t('productNotFound') ?></p>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<?php snippet('footer', ['scripts' => ['product']]) ?>
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue