Footer Columns
A site footer: the logo and a line, up to five columns of links under mono titles, a bottom row with the year, legal pages and Back to top; under a link, a line comes in from the left.
View codeA shop footer: four promises with icons, a newsletter field whose button rolls to Subscribed, columns that fold on phones, a country and currency menu, the payment methods.
With your AI
Say it to your AI. It gets the exact code below, not its best guess, and knows where it goes.
Add the Kodza Footer Shop to my page.
claude mcp add -t http kodza https://kodza.dev/api/mcp
<footer class="kodza-footer-shop">
<div class="kodza-footer-shop__inner">
<ul class="kodza-footer-shop__promises" aria-label="Our promises">
<li>
<a class="kodza-footer-shop__promise" href="/returns/">
<svg class="kodza-footer-shop__icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M9 14L4 9l5-5" /><path d="M4 9h10.5a5.5 5.5 0 0 1 0 11H11" /></svg>
<span class="kodza-footer-shop__claim">Free returns, 30 days</span>
<span class="kodza-footer-shop__small">Changed your mind? The label's on us.</span>
</a>
</li>
<li>
<a class="kodza-footer-shop__promise" href="/shipping/">
<svg class="kodza-footer-shop__icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M3.5 7.5L12 3l8.5 4.5v9L12 21l-8.5-4.5z" /><path d="M3.5 7.5L12 12l8.5-4.5" /><path d="M12 12v9" /><path d="M7.8 5.3l8.4 4.5" /></svg>
<span class="kodza-footer-shop__claim">Ships in 48 hours</span>
<span class="kodza-footer-shop__small">Tracked, from our workshop in Marseille.</span>
</a>
</li>
<li>
<a class="kodza-footer-shop__promise" href="/payment/">
<svg class="kodza-footer-shop__icon" viewBox="0 0 24 24" aria-hidden="true"><rect x="3" y="5" width="18" height="14" rx="2" /><path d="M3 10h18" /><path d="M7 15h3" /></svg>
<span class="kodza-footer-shop__claim">Pay how you like</span>
<span class="kodza-footer-shop__small">Card, Apple Pay or PayPal. Never stored here.</span>
</a>
</li>
<li>
<a class="kodza-footer-shop__promise" href="/contact/">
<svg class="kodza-footer-shop__icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M12 4a8 8 0 0 1 0 16c-1.3 0-2.6-.3-3.7-.9L4 20l.9-4.3A8 8 0 0 1 12 4z" /><path d="M8.5 12h.01M12 12h.01M15.5 12h.01" /></svg>
<span class="kodza-footer-shop__claim">A human answers</span>
<span class="kodza-footer-shop__small">Weekdays, 9 to 6. No bot, no ticket number.</span>
</a>
</li>
</ul>
<div class="kodza-footer-shop__main">
<div class="kodza-footer-shop__brand">
<a class="kodza-footer-shop__logo" href="/" aria-label="Galet, home"><svg viewBox="0 0 24 24" width="22" height="22" aria-hidden="true"><path d="M3 12.5C3 7 7.5 3 13 3s8 3.4 8 7.6C21 15.6 16.6 18 11.4 18 6.4 18 3 16.2 3 12.5z" /></svg><span>galet</span></a>
<p class="kodza-footer-shop__pitch">Pieces for the table, made to outlast the trend.</p>
<form class="kodza-footer-shop__form" action="/newsletter/" method="post">
<div class="kodza-footer-shop__box">
<label class="kodza-footer-shop__field">
<input class="kodza-footer-shop__input" type="email" name="email" placeholder=" " autocomplete="email" required data-error="That does not look like an email.">
<span class="kodza-footer-shop__label">Email</span>
</label>
<button class="kodza-footer-shop__send" type="submit" data-sending="Sending" data-sent="Subscribed"><span class="kodza-footer-shop__say"><span>Subscribe</span></span></button>
</div>
<p class="kodza-footer-shop__note">
<span class="kodza-footer-shop__hint">One letter a month. New pieces, no countdowns.</span>
<span class="kodza-footer-shop__error" aria-live="polite"></span>
<span class="kodza-footer-shop__done" role="status" data-failed="Couldn't reach the list. Try again in a minute.">Done. Confirm it from your inbox.</span>
</p>
</form>
</div>
<nav class="kodza-footer-shop__nav" aria-label="Footer">
<div class="kodza-footer-shop__col">
<p class="kodza-footer-shop__title">Shop</p>
<ul class="kodza-footer-shop__links">
<li><a class="kodza-footer-shop__link" href="/new/">New in</a></li>
<li><a class="kodza-footer-shop__link" href="/kitchen/">Kitchen</a></li>
<li><a class="kodza-footer-shop__link" href="/table/">Table</a></li>
<li><a class="kodza-footer-shop__link" href="/bath/">Bath</a></li>
<li><a class="kodza-footer-shop__link" href="/linen/">Linen</a></li>
<li><a class="kodza-footer-shop__link" href="/gift-cards/">Gift cards</a></li>
</ul>
</div>
<div class="kodza-footer-shop__col">
<p class="kodza-footer-shop__title">Help</p>
<ul class="kodza-footer-shop__links">
<li><a class="kodza-footer-shop__link" href="/shipping/">Shipping</a></li>
<li><a class="kodza-footer-shop__link" href="/returns/">Returns</a></li>
<li><a class="kodza-footer-shop__link" href="/track/">Track an order</a></li>
<li><a class="kodza-footer-shop__link" href="/care/">Care guide</a></li>
<li><a class="kodza-footer-shop__link" href="/contact/">Contact</a></li>
</ul>
</div>
<div class="kodza-footer-shop__col">
<p class="kodza-footer-shop__title">Galet</p>
<ul class="kodza-footer-shop__links">
<li><a class="kodza-footer-shop__link" href="/story/">Our story</a></li>
<li><a class="kodza-footer-shop__link" href="/makers/">Makers</a></li>
<li><a class="kodza-footer-shop__link" href="/journal/">Journal</a></li>
<li><a class="kodza-footer-shop__link" href="/stockists/">Stockists</a></li>
<li><a class="kodza-footer-shop__link" href="/trade/">Trade</a></li>
</ul>
</div>
</nav>
</div>
<div class="kodza-footer-shop__base">
<div class="kodza-footer-shop__legal">
<div class="kodza-footer-shop__region">
<button class="kodza-footer-shop__picker" type="button" aria-expanded="false">
<svg class="kodza-footer-shop__globe" viewBox="0 0 16 16" aria-hidden="true"><circle cx="8" cy="8" r="6.25" /><path d="M1.75 8h12.5" /><path d="M8 1.75c-2.1 2-2.1 10.5 0 12.5M8 1.75c2.1 2 2.1 10.5 0 12.5" /></svg>
<span class="kodza-footer-shop__sr">Country and currency: </span>
<span class="kodza-footer-shop__value">France · EUR €</span>
<svg class="kodza-footer-shop__chevron" viewBox="0 0 16 16" aria-hidden="true"><path d="M4 10l4-4 4 4" /></svg>
</button>
<ul class="kodza-footer-shop__places" aria-label="Country and currency">
<li><a class="kodza-footer-shop__place" href="/en-fr/" hreflang="en-FR" aria-current="true"><span class="kodza-footer-shop__country">France</span><span class="kodza-footer-shop__currency">EUR €</span></a></li>
<li><a class="kodza-footer-shop__place" href="/en-be/" hreflang="en-BE"><span class="kodza-footer-shop__country">Belgium</span><span class="kodza-footer-shop__currency">EUR €</span></a></li>
<li><a class="kodza-footer-shop__place" href="/en-de/" hreflang="en-DE"><span class="kodza-footer-shop__country">Germany</span><span class="kodza-footer-shop__currency">EUR €</span></a></li>
<li><a class="kodza-footer-shop__place" href="/en-gb/" hreflang="en-GB"><span class="kodza-footer-shop__country">United Kingdom</span><span class="kodza-footer-shop__currency">GBP £</span></a></li>
<li><a class="kodza-footer-shop__place" href="/en-us/" hreflang="en-US"><span class="kodza-footer-shop__country">United States</span><span class="kodza-footer-shop__currency">USD $</span></a></li>
<li><a class="kodza-footer-shop__place" href="/en-jp/" hreflang="en-JP"><span class="kodza-footer-shop__country">Japan</span><span class="kodza-footer-shop__currency">JPY ¥</span></a></li>
</ul>
</div>
<p class="kodza-footer-shop__copy">© <span class="kodza-footer-shop__year">2026</span> Galet</p>
<ul class="kodza-footer-shop__row">
<li><a class="kodza-footer-shop__link" href="/terms/">Terms</a></li>
<li><a class="kodza-footer-shop__link" href="/privacy/">Privacy</a></li>
<li><a class="kodza-footer-shop__link" href="/cookies/">Cookies</a></li>
</ul>
</div>
<ul class="kodza-footer-shop__pay" aria-label="Payment methods">
<li>Visa</li>
<li>Mastercard</li>
<li>Amex</li>
<li>Apple Pay</li>
<li>PayPal</li>
</ul>
</div>
</div>
</footer>
/* Kodza License: free to use, client work included. Not to resell or redistribute, ported or not: https://kodza.dev/docs/#print-license */
/* Kodza — Footer Shop
The footer of an online shop, where the last doubts get answered: a row of promises (returns,
shipping, payment, a human), the letter in a text field whose button rolls Subscribe, Sending,
Subscribed, the columns, then a country and currency menu that unrolls upwards and the payment
methods in mono. On phones the columns fold into accordions; without the script they stay open,
two by two. Each promise has its icon beside the claim; kodza-footer-shop--big-icons on the root
makes it big, above the claim.
Everything is tuned with the --kz-footer-shop-* variables. */
.kodza-footer-shop {
--kz-footer-shop-bg: #ffffff;
--kz-footer-shop-fg: #0a0a0a;
--kz-footer-shop-muted: rgba(10, 10, 10, 0.62);
--kz-footer-shop-line: rgba(10, 10, 10, 0.1);
--kz-footer-shop-edge: rgba(10, 10, 10, 0.16); /* the field and the country button */
--kz-footer-shop-edge-hover: rgba(10, 10, 10, 0.32);
--kz-footer-shop-soft: rgba(10, 10, 10, 0.05); /* the highlight in the menu, a done button */
--kz-footer-shop-raised: #ffffff; /* the field and the menu */
--kz-footer-shop-shadow: 0 18px 40px -16px rgba(10, 10, 10, 0.3);
--kz-footer-shop-width: 1200px; /* the content, centred */
--kz-footer-shop-pad: 24px;
--kz-footer-shop-mono: "Geist Mono", ui-monospace, "SF Mono", Menlo, monospace;
--kz-footer-shop-ease: cubic-bezier(0.22, 1, 0.36, 1);
display: block;
box-sizing: border-box;
width: 100%;
margin: 0;
padding: 0;
border-top: 1px solid var(--kz-footer-shop-line);
background: var(--kz-footer-shop-bg);
color: var(--kz-footer-shop-fg);
font-size: 15px;
line-height: 1.5;
text-align: left;
container: kodza-footer-shop / inline-size;
}
/* Centred by its margins (no margin: auto, which some browsers read late) */
.kodza-footer-shop__inner {
box-sizing: border-box;
margin: 0 max(0px, calc((100% - var(--kz-footer-shop-width)) / 2));
padding-top: clamp(48px, 5vw, 64px);
padding-bottom: 28px;
padding-left: max(var(--kz-footer-shop-pad), env(safe-area-inset-left, 0px));
padding-right: max(var(--kz-footer-shop-pad), env(safe-area-inset-right, 0px));
}
.kodza-footer-shop__promises,
.kodza-footer-shop__links,
.kodza-footer-shop__row,
.kodza-footer-shop__places,
.kodza-footer-shop__pay {
margin: 0;
padding: 0;
list-style: none;
}
.kodza-footer-shop__promises li,
.kodza-footer-shop__links li,
.kodza-footer-shop__row li,
.kodza-footer-shop__places li {
margin: 0;
padding: 0;
}
/* The promises: each one links to its page */
.kodza-footer-shop__promises {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 32px clamp(24px, 3vw, 40px);
margin-bottom: clamp(48px, 6vw, 72px);
padding-bottom: clamp(40px, 5vw, 56px);
border-bottom: 1px solid var(--kz-footer-shop-line);
}
.kodza-footer-shop__promises li {
min-width: 0;
}
.kodza-footer-shop__promise {
display: grid;
grid-template-columns: 20px minmax(0, 1fr);
justify-items: start;
gap: 4px 10px;
border-radius: 6px;
color: inherit;
text-decoration: none;
}
/* The icon sits beside the claim, on its first line; the small print goes under the claim */
.kodza-footer-shop__icon {
display: block;
width: 20px;
height: 20px;
margin: 0.5px 0 0;
fill: none;
stroke: currentColor;
stroke-width: 1.5;
stroke-linecap: round;
stroke-linejoin: round;
}
/* Big icons: the icon stands above the claim, the claim and its small print get the whole width */
.kodza-footer-shop--big-icons .kodza-footer-shop__promise {
grid-template-columns: minmax(0, 1fr);
}
.kodza-footer-shop--big-icons .kodza-footer-shop__icon {
width: 44px;
height: 44px;
margin: 0 0 14px;
stroke-width: 1.1;
}
.kodza-footer-shop--big-icons .kodza-footer-shop__small {
grid-column: auto;
}
.kodza-footer-shop__claim {
font-size: 15px;
font-weight: 500;
line-height: 1.4;
letter-spacing: -0.01em;
}
.kodza-footer-shop__small {
grid-column: 2;
font-size: 13px;
line-height: 1.5;
color: var(--kz-footer-shop-muted);
text-wrap: pretty;
}
.kodza-footer-shop__main {
display: grid;
grid-template-columns: minmax(0, 1.2fr) minmax(0, 2fr);
gap: 48px clamp(32px, 5vw, 80px);
}
.kodza-footer-shop__brand {
min-width: 0;
}
/* Pulled up so the name sits on the line of the column titles */
.kodza-footer-shop__logo {
display: inline-flex;
align-items: center;
gap: 8px;
height: 40px;
margin: -11px 0 0;
border-radius: 6px;
color: inherit;
font-size: 18px;
font-weight: 600;
line-height: 1;
letter-spacing: -0.04em;
text-decoration: none;
}
.kodza-footer-shop__logo svg {
display: block;
flex: none;
fill: currentColor;
}
.kodza-footer-shop__pitch {
max-width: 24em;
margin: 10px 0 0;
font-size: 15px;
line-height: 1.55;
color: var(--kz-footer-shop-muted);
text-wrap: pretty;
}
/* The letter: the box of Text Field, with the button inside it */
.kodza-footer-shop__form {
max-width: 440px;
margin: 28px 0 0;
padding: 0;
}
.kodza-footer-shop__box {
position: relative;
display: flex;
align-items: center;
gap: 8px;
box-sizing: border-box;
height: 56px;
margin: 0;
padding: 0 8px 0 0;
border-radius: 12px;
background: var(--kz-footer-shop-raised);
box-shadow: inset 0 0 0 1px var(--kz-footer-shop-edge);
transition: box-shadow 0.25s ease;
}
@media (hover: hover) {
.kodza-footer-shop__box:hover {
box-shadow: inset 0 0 0 1px var(--kz-footer-shop-edge-hover);
}
}
.kodza-footer-shop__box:has(.kodza-footer-shop__input:focus) {
box-shadow: inset 0 0 0 1.5px var(--kz-footer-shop-fg), 0 0 0 4px var(--kz-footer-shop-soft);
}
.kodza-footer-shop__box:has(.kodza-footer-shop__input[aria-invalid="true"]) {
box-shadow: inset 0 0 0 1.5px var(--kz-footer-shop-fg);
}
.kodza-footer-shop__box:has(.kodza-footer-shop__input[aria-invalid="true"]:focus) {
box-shadow: inset 0 0 0 1.5px var(--kz-footer-shop-fg), 0 0 0 4px var(--kz-footer-shop-soft);
}
.kodza-footer-shop__field {
position: relative;
display: flex;
flex: 1;
align-items: flex-end;
align-self: stretch;
box-sizing: border-box;
min-width: 0;
margin: 0;
padding: 0 0 9px 16px;
cursor: text;
}
.kodza-footer-shop__input {
flex: 1;
width: 100%;
min-width: 0;
height: 22px;
margin: 0;
padding: 0;
border: 0;
border-radius: 0;
background: none;
box-shadow: none;
color: inherit;
font: inherit;
font-size: 15px;
line-height: 22px;
outline: none;
}
/* The label: inside the box at rest, folded up above the text once focused or filled */
.kodza-footer-shop__label {
position: absolute;
top: 50%;
left: 16px;
color: var(--kz-footer-shop-muted);
font-size: 15px;
line-height: 1;
white-space: nowrap;
pointer-events: none;
transform: translateY(-50%);
transform-origin: left top;
transition: transform 0.35s var(--kz-footer-shop-ease);
}
.kodza-footer-shop__input:focus ~ .kodza-footer-shop__label,
.kodza-footer-shop__input:not(:placeholder-shown) ~ .kodza-footer-shop__label {
transform: translateY(-18px) scale(0.8);
}
/* The button: its word rolls (the old one goes up, the new one comes from below) and its width
follows */
.kodza-footer-shop__send {
display: inline-flex;
flex: none;
align-items: center;
justify-content: center;
box-sizing: border-box;
height: 40px;
margin: 0;
padding: 0 18px;
border: 0;
border-radius: 999px;
background: var(--kz-footer-shop-fg);
color: var(--kz-footer-shop-bg);
font: inherit;
font-size: 14px;
font-weight: 500;
line-height: 1;
cursor: pointer;
transition: background-color 0.35s ease, color 0.35s ease, opacity 0.2s ease;
}
@media (hover: hover) {
.kodza-footer-shop__send:hover {
opacity: 0.86;
}
}
.kodza-footer-shop__say {
display: inline-grid;
overflow: hidden;
line-height: 20px;
white-space: nowrap;
}
.kodza-footer-shop__say > span {
display: inline-flex;
grid-area: 1 / 1;
justify-self: start;
align-items: center;
gap: 6px;
}
.kodza-footer-shop__tick {
display: block;
flex: none;
width: 14px;
height: 14px;
fill: none;
stroke: currentColor;
stroke-width: 1.8;
stroke-linecap: round;
stroke-linejoin: round;
stroke-dasharray: 1;
stroke-dashoffset: 1;
animation: kodza-footer-shop-tick 0.4s var(--kz-footer-shop-ease) 0.35s forwards;
}
@keyframes kodza-footer-shop-tick {
to {
stroke-dashoffset: 0;
}
}
.kodza-footer-shop__form.is-sending .kodza-footer-shop__send {
cursor: progress;
}
.kodza-footer-shop__form.is-done .kodza-footer-shop__send {
background: var(--kz-footer-shop-soft);
color: var(--kz-footer-shop-fg);
opacity: 1;
cursor: default;
}
/* The line under the box: the hint, an error or Done, one in place of the other */
.kodza-footer-shop__note {
display: grid;
margin: 10px 0 0;
padding: 0 2px;
overflow: hidden;
font-size: 13px;
line-height: 1.4;
color: var(--kz-footer-shop-muted);
}
.kodza-footer-shop__hint,
.kodza-footer-shop__error,
.kodza-footer-shop__done {
grid-area: 1 / 1;
transition: opacity 0.25s ease, transform 0.4s var(--kz-footer-shop-ease);
}
.kodza-footer-shop__error,
.kodza-footer-shop__done {
color: var(--kz-footer-shop-fg);
font-weight: 500;
opacity: 0;
transform: translateY(100%);
}
.kodza-footer-shop__error {
display: flex;
align-items: flex-start;
gap: 6px;
}
.kodza-footer-shop__error::before {
content: "";
flex: none;
width: 14px;
height: 14px;
margin-top: 2px;
background: currentColor;
-webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Ccircle cx='8' cy='8' r='6.5' fill='none' stroke='black' stroke-width='1.5'/%3E%3Cpath d='M8 4.5v4.2M8 10.9v.1' stroke='black' stroke-width='1.6' stroke-linecap='round'/%3E%3C/svg%3E") center / contain no-repeat;
mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Ccircle cx='8' cy='8' r='6.5' fill='none' stroke='black' stroke-width='1.5'/%3E%3Cpath d='M8 4.5v4.2M8 10.9v.1' stroke='black' stroke-width='1.6' stroke-linecap='round'/%3E%3C/svg%3E") center / contain no-repeat;
}
.kodza-footer-shop__form.is-error .kodza-footer-shop__hint,
.kodza-footer-shop__form.is-done .kodza-footer-shop__hint {
opacity: 0;
transform: translateY(-100%);
}
.kodza-footer-shop__form.is-error .kodza-footer-shop__error,
.kodza-footer-shop__form.is-done .kodza-footer-shop__done {
opacity: 1;
transform: none;
}
/* The columns */
.kodza-footer-shop__nav {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(120px, 100%), 1fr));
gap: 40px 24px;
min-width: 0;
}
.kodza-footer-shop__col {
min-width: 0;
}
.kodza-footer-shop__title,
.kodza-footer-shop__copy {
margin: 0;
font-family: var(--kz-footer-shop-mono);
font-size: 12px;
font-weight: 400;
letter-spacing: 0.02em;
line-height: 1.5;
color: var(--kz-footer-shop-muted);
}
.kodza-footer-shop__title {
margin-bottom: 16px;
}
/* Made by the script, for phones: the title becomes the button of its accordion */
.kodza-footer-shop__fold {
display: none;
align-items: center;
justify-content: space-between;
gap: 16px;
box-sizing: border-box;
width: 100%;
height: 52px;
margin: 0;
padding: 0;
border: 0;
border-radius: 4px;
background: none;
color: inherit;
font: inherit;
font-size: 15px;
font-weight: 500;
line-height: 1;
text-align: left;
cursor: pointer;
}
.kodza-footer-shop__plus {
position: relative;
flex: none;
width: 12px;
height: 12px;
}
.kodza-footer-shop__plus::before,
.kodza-footer-shop__plus::after {
content: "";
position: absolute;
top: 5.25px;
left: 0;
width: 12px;
height: 1.5px;
border-radius: 1px;
background: currentColor;
transition: transform 0.4s var(--kz-footer-shop-ease);
}
.kodza-footer-shop__plus::after {
transform: rotate(90deg);
}
.kodza-footer-shop__col.is-open .kodza-footer-shop__plus::after {
transform: rotate(0deg);
}
.kodza-footer-shop__links {
display: grid;
justify-items: start;
gap: 10px;
}
/* Links: the line grows from the left, and shrinks away to the right */
.kodza-footer-shop__link,
.kodza-footer-shop__claim {
background: linear-gradient(currentColor, currentColor) no-repeat 100% 100% / 0 1px;
transition: background-size 0.4s var(--kz-footer-shop-ease), color 0.2s ease;
}
.kodza-footer-shop__link {
color: inherit;
font-size: 14px;
line-height: 1.5;
text-decoration: none;
}
@media (hover: hover) {
.kodza-footer-shop__link:hover,
.kodza-footer-shop__promise:hover .kodza-footer-shop__claim {
background-position: 0 100%;
background-size: 100% 1px;
}
}
/* The bottom row */
.kodza-footer-shop__base {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
gap: 16px 32px;
margin-top: clamp(56px, 7vw, 96px);
padding-top: 20px;
border-top: 1px solid var(--kz-footer-shop-line);
}
.kodza-footer-shop__legal {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 12px 24px;
}
.kodza-footer-shop__row {
display: flex;
flex-wrap: wrap;
gap: 4px 20px;
}
.kodza-footer-shop__row .kodza-footer-shop__link {
font-size: 13px;
color: var(--kz-footer-shop-muted);
}
@media (hover: hover) {
.kodza-footer-shop__row .kodza-footer-shop__link:hover {
color: var(--kz-footer-shop-fg);
}
}
/* Country and currency: a button, and a list of links that unrolls upwards (downwards when there
is no room above) */
.kodza-footer-shop__region {
position: relative;
z-index: 0;
display: inline-flex;
transition: z-index 0s linear 0.35s;
}
.kodza-footer-shop__region.is-open {
z-index: 20;
transition-delay: 0s;
}
.kodza-footer-shop__picker {
display: inline-flex;
align-items: center;
gap: 8px;
box-sizing: border-box;
height: 36px;
margin: 0;
padding: 0 10px 0 12px;
border: 0;
border-radius: 999px;
background: none;
box-shadow: inset 0 0 0 1px var(--kz-footer-shop-edge);
color: inherit;
font: inherit;
font-size: 13px;
font-weight: 500;
line-height: 1;
cursor: pointer;
transition: box-shadow 0.2s ease;
}
.kodza-footer-shop__region.is-open .kodza-footer-shop__picker {
box-shadow: inset 0 0 0 1px var(--kz-footer-shop-edge-hover);
}
@media (hover: hover) {
.kodza-footer-shop__picker:hover {
box-shadow: inset 0 0 0 1px var(--kz-footer-shop-edge-hover);
}
}
.kodza-footer-shop__globe,
.kodza-footer-shop__chevron {
display: block;
flex: none;
fill: none;
stroke: currentColor;
stroke-linecap: round;
stroke-linejoin: round;
color: var(--kz-footer-shop-muted);
}
.kodza-footer-shop__globe {
width: 16px;
height: 16px;
stroke-width: 1.25;
}
.kodza-footer-shop__chevron {
width: 14px;
height: 14px;
stroke-width: 1.5;
transition: transform 0.35s var(--kz-footer-shop-ease);
}
.kodza-footer-shop__region.is-open .kodza-footer-shop__chevron {
transform: rotate(180deg);
}
.kodza-footer-shop__sr {
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
}
.kodza-footer-shop__value {
display: inline-grid;
overflow: hidden;
line-height: 20px;
white-space: nowrap;
}
.kodza-footer-shop__value > span {
grid-area: 1 / 1;
justify-self: start;
}
.kodza-footer-shop__places {
position: absolute;
bottom: calc(100% + 8px);
left: 0;
z-index: 20;
display: flex;
flex-direction: column;
box-sizing: border-box;
min-width: max(100%, 240px);
padding: 6px;
border-radius: 14px;
background: var(--kz-footer-shop-raised);
box-shadow: 0 0 0 1px var(--kz-footer-shop-line), var(--kz-footer-shop-shadow);
visibility: hidden;
opacity: 0;
clip-path: inset(100% 0 0 0 round 14px);
transform: translateY(4px);
transition: clip-path 0.35s var(--kz-footer-shop-ease), transform 0.35s var(--kz-footer-shop-ease), opacity 0.2s ease, visibility 0s linear 0.35s;
}
.kodza-footer-shop__region.is-open .kodza-footer-shop__places {
visibility: visible;
opacity: 1;
clip-path: inset(-60px -40px -40px -40px round 14px);
transform: none;
transition-delay: 0s;
}
.kodza-footer-shop__region.is-below .kodza-footer-shop__places {
top: calc(100% + 8px);
bottom: auto;
clip-path: inset(0 0 100% 0 round 14px);
transform: translateY(-4px);
}
.kodza-footer-shop__region.is-below.is-open .kodza-footer-shop__places {
clip-path: inset(-40px -40px -60px -40px round 14px);
transform: none;
}
.kodza-footer-shop__region.is-end .kodza-footer-shop__places {
right: 0;
left: auto;
}
/* The highlight that glides from one country to the next */
.kodza-footer-shop__places .kodza-footer-shop__glide {
position: absolute;
top: 0;
left: 6px;
right: 6px;
height: var(--kz-footer-shop-hl-h, 36px);
border-radius: 8px;
background: var(--kz-footer-shop-soft);
opacity: var(--kz-footer-shop-hl-on, 0);
transform: translateY(var(--kz-footer-shop-hl-y, 0px));
pointer-events: none;
}
.kodza-footer-shop__region.is-open .kodza-footer-shop__glide {
transition: transform 0.22s ease, opacity 0.15s ease;
}
.kodza-footer-shop__place {
position: relative;
display: flex;
align-items: center;
justify-content: space-between;
gap: 24px;
box-sizing: border-box;
height: 36px;
padding: 0 10px 0 32px;
border-radius: 8px;
color: inherit;
font-size: 14px;
font-weight: 500;
line-height: 1;
text-decoration: none;
white-space: nowrap;
}
.kodza-footer-shop__place:focus {
outline: none;
}
.kodza-footer-shop__currency {
font-family: var(--kz-footer-shop-mono);
font-size: 12px;
font-weight: 400;
color: var(--kz-footer-shop-muted);
}
.kodza-footer-shop__place[aria-current="true"]::before {
content: "";
position: absolute;
left: 10px;
width: 14px;
height: 14px;
background: currentColor;
-webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M3 8.5l3.2 3L13 4.5' fill='none' stroke='black' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / contain no-repeat;
mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M3 8.5l3.2 3L13 4.5' fill='none' stroke='black' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / contain no-repeat;
}
/* The payment methods, in mono, a dot between two */
.kodza-footer-shop__pay {
display: flex;
flex-wrap: wrap;
gap: 4px 0;
font-family: var(--kz-footer-shop-mono);
font-size: 12px;
letter-spacing: 0.02em;
line-height: 1.5;
color: var(--kz-footer-shop-muted);
}
.kodza-footer-shop__pay li {
display: inline-flex;
align-items: center;
margin: 0;
padding: 0;
}
.kodza-footer-shop__pay li + li::before {
content: "";
flex: none;
width: 3px;
height: 3px;
margin: 0 10px;
border-radius: 50%;
background: currentColor;
}
.kodza-footer-shop__promise:focus-visible,
.kodza-footer-shop__logo:focus-visible,
.kodza-footer-shop__link:focus-visible,
.kodza-footer-shop__send:focus-visible,
.kodza-footer-shop__picker:focus-visible,
.kodza-footer-shop__fold:focus-visible {
outline: 2px solid var(--kz-footer-shop-fg);
outline-offset: 3px;
}
.kodza-footer-shop__link:focus-visible {
border-radius: 2px;
}
@media (prefers-reduced-motion: reduce) {
.kodza-footer-shop__box,
.kodza-footer-shop__label,
.kodza-footer-shop__send,
.kodza-footer-shop__hint,
.kodza-footer-shop__error,
.kodza-footer-shop__done,
.kodza-footer-shop__plus::before,
.kodza-footer-shop__plus::after,
.kodza-footer-shop__link,
.kodza-footer-shop__claim,
.kodza-footer-shop__picker,
.kodza-footer-shop__chevron,
.kodza-footer-shop__places,
.kodza-footer-shop__region.is-open .kodza-footer-shop__glide {
transition: none;
}
.kodza-footer-shop__tick {
animation: none;
stroke-dashoffset: 0;
}
}
/* The brand goes on top of the columns */
@container kodza-footer-shop (max-width: 960px) {
.kodza-footer-shop__main {
grid-template-columns: minmax(0, 1fr);
}
}
@container kodza-footer-shop (max-width: 760px) {
.kodza-footer-shop__promises {
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 28px 16px;
}
}
/* Two narrow columns: a big icon gets a little smaller */
@container kodza-footer-shop (max-width: 520px) {
.kodza-footer-shop--big-icons .kodza-footer-shop__icon {
width: 36px;
height: 36px;
margin: 0 0 10px;
}
}
/* Phones: the columns fold into accordions (two by two without the script), the bottom row
stacks */
@container kodza-footer-shop (max-width: 600px) {
.kodza-footer-shop__nav {
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 36px 16px;
}
.kodza-footer-shop.is-ready .kodza-footer-shop__nav {
grid-template-columns: minmax(0, 1fr);
gap: 0;
border-top: 1px solid var(--kz-footer-shop-line);
}
.kodza-footer-shop.is-ready .kodza-footer-shop__col {
border-bottom: 1px solid var(--kz-footer-shop-line);
}
.kodza-footer-shop.is-ready .kodza-footer-shop__title {
display: none;
}
.kodza-footer-shop.is-ready .kodza-footer-shop__fold {
display: flex;
}
.kodza-footer-shop.is-ready .kodza-footer-shop__links {
gap: 0;
}
.kodza-footer-shop.is-ready .kodza-footer-shop__links li {
padding: 6px 0;
}
.kodza-footer-shop.is-ready .kodza-footer-shop__links li:last-child {
padding-bottom: 18px;
}
.kodza-footer-shop.is-ready .kodza-footer-shop__links .kodza-footer-shop__link {
font-size: 15px;
}
.kodza-footer-shop.is-ready .kodza-footer-shop__col:not(.is-open) .kodza-footer-shop__links {
height: 0;
overflow: clip;
visibility: hidden;
}
.kodza-footer-shop.is-ready .kodza-footer-shop__col.is-moving .kodza-footer-shop__links {
overflow: clip;
}
.kodza-footer-shop__base {
flex-direction: column;
align-items: flex-start;
}
}
/* kodza-guard 08dbe65b77f5 — generated by Kodza, do not edit.
Theme guard: keeps a site's own CSS (WordPress themes, Bootstrap, CSS resets…) off this
component. :not(#_) is always true and weighs as much as an id, so these lines win over
theme rules made of classes and tags. Customise the component through its --kz-* variables. */
/* What the browser gives, where the component sets it only in some states: at the lowest weight, so its own rules below put it back */
:where(.kodza-footer-shop__promise):not(#_),
:where(.kodza-footer-shop__logo):not(#_),
:where(.kodza-footer-shop__send):not(#_),
:where(.kodza-footer-shop__fold):not(#_),
:where(.kodza-footer-shop__picker):not(#_) {
outline: revert;
outline-offset: revert;
}
:where(.kodza-footer-shop__nav):not(#_) {
border-top-width: revert;
border-top-style: revert;
border-top-color: revert;
}
:where(.kodza-footer-shop__link):not(#_) {
border-radius: revert;
outline: revert;
outline-offset: revert;
}
:where(.kodza-footer-shop__glide):not(#_) {
transition: revert;
}
:where(.kodza-footer-shop__place):not(#_) {
outline: revert;
}
:where(.kodza-footer-shop__title):not(#_):where(:not([hidden])) {
display: revert;
}
:where(.kodza-footer-shop__title):not(#_):where([hidden]) {
display: none;
}
/* What the browser gives, on everything the component leaves to it */
.kodza-footer-shop:not(#_) {
border-radius: revert;
outline: revert;
text-decoration: revert;
transition: revert;
list-style: revert;
position: revert;
border-right-width: revert;
border-bottom-width: revert;
border-left-width: revert;
border-right-style: revert;
border-bottom-style: revert;
border-left-style: revert;
border-right-color: revert;
border-bottom-color: revert;
border-left-color: revert;
box-shadow: revert;
outline-offset: revert;
appearance: revert;
max-height: revert;
font-family: revert;
font-weight: revert;
font-style: revert;
letter-spacing: revert;
text-transform: revert;
text-underline-offset: revert;
text-indent: revert;
text-shadow: revert;
vertical-align: revert;
}
.kodza-footer-shop__promises:not(#_) {
border-radius: revert;
outline: revert;
text-decoration: revert;
transition: revert;
box-sizing: revert;
float: revert;
position: revert;
border-top-width: revert;
border-right-width: revert;
border-left-width: revert;
border-top-style: revert;
border-right-style: revert;
border-left-style: revert;
border-top-color: revert;
border-right-color: revert;
border-left-color: revert;
background-color: revert;
background-image: revert;
box-shadow: revert;
outline-offset: revert;
appearance: revert;
max-width: revert;
max-height: revert;
color: revert;
font-family: revert;
font-size: revert;
font-weight: revert;
font-style: revert;
line-height: revert;
letter-spacing: revert;
text-transform: revert;
text-underline-offset: revert;
text-align: revert;
text-indent: revert;
text-shadow: revert;
vertical-align: revert;
}
.kodza-footer-shop__promise:not(#_) {
border: revert;
margin: revert;
padding: revert;
transition: revert;
list-style: revert;
box-sizing: revert;
float: revert;
position: revert;
background-color: revert;
background-image: revert;
box-shadow: revert;
appearance: revert;
max-width: revert;
max-height: revert;
font-family: revert;
font-size: revert;
font-weight: revert;
font-style: revert;
line-height: revert;
letter-spacing: revert;
text-transform: revert;
text-underline-offset: revert;
text-align: revert;
text-indent: revert;
text-shadow: revert;
vertical-align: revert;
}
.kodza-footer-shop__icon:not(#_) {
border: revert;
padding: revert;
border-radius: revert;
outline: revert;
text-decoration: revert;
transition: revert;
list-style: revert;
box-sizing: revert;
float: revert;
position: revert;
background-color: revert;
background-image: revert;
box-shadow: revert;
outline-offset: revert;
appearance: revert;
max-width: revert;
max-height: revert;
color: revert;
font-family: revert;
font-size: revert;
font-weight: revert;
font-style: revert;
line-height: revert;
letter-spacing: revert;
text-transform: revert;
text-underline-offset: revert;
text-align: revert;
text-indent: revert;
text-shadow: revert;
vertical-align: revert;
}
.kodza-footer-shop__claim:not(#_),
.kodza-footer-shop__small:not(#_),
.kodza-footer-shop__main:not(#_),
.kodza-footer-shop__brand:not(#_),
.kodza-footer-shop__form:not(#_),
.kodza-footer-shop__label:not(#_),
.kodza-footer-shop__say:not(#_),
.kodza-footer-shop__hint:not(#_),
.kodza-footer-shop__error:not(#_),
.kodza-footer-shop__done:not(#_),
.kodza-footer-shop__col:not(#_),
.kodza-footer-shop__plus:not(#_),
.kodza-footer-shop__base:not(#_),
.kodza-footer-shop__legal:not(#_),
.kodza-footer-shop__region:not(#_),
.kodza-footer-shop__sr:not(#_),
.kodza-footer-shop__value:not(#_),
.kodza-footer-shop__country:not(#_),
.kodza-footer-shop__currency:not(#_),
.kodza-footer-shop__year:not(#_),
.kodza-footer-shop__logo span:where(:not([class*="kodza-"])):not(#_),
.kodza-footer-shop__say span:where(:not([class*="kodza-"])):not(#_),
.kodza-footer-shop__value span:where(:not([class*="kodza-"])):not(#_) {
box-sizing: revert;
}
.kodza-footer-shop__logo:not(#_) {
border: revert;
padding: revert;
transition: revert;
list-style: revert;
box-sizing: revert;
float: revert;
position: revert;
background-color: revert;
background-image: revert;
box-shadow: revert;
appearance: revert;
max-width: revert;
max-height: revert;
font-family: revert;
font-style: revert;
text-transform: revert;
text-underline-offset: revert;
text-align: revert;
text-indent: revert;
text-shadow: revert;
vertical-align: revert;
}
.kodza-footer-shop__pitch:not(#_) {
border: revert;
padding: revert;
border-radius: revert;
outline: revert;
text-decoration: revert;
transition: revert;
list-style: revert;
box-sizing: revert;
float: revert;
position: revert;
background-color: revert;
background-image: revert;
box-shadow: revert;
outline-offset: revert;
appearance: revert;
max-height: revert;
font-family: revert;
font-weight: revert;
font-style: revert;
letter-spacing: revert;
text-transform: revert;
text-underline-offset: revert;
text-align: revert;
text-indent: revert;
text-shadow: revert;
vertical-align: revert;
}
.kodza-footer-shop__field:not(#_) {
border: revert;
border-radius: revert;
outline: revert;
text-decoration: revert;
transition: revert;
list-style: revert;
float: revert;
background-color: revert;
background-image: revert;
box-shadow: revert;
outline-offset: revert;
appearance: revert;
max-width: revert;
max-height: revert;
color: revert;
font-family: revert;
font-size: revert;
font-weight: revert;
font-style: revert;
line-height: revert;
letter-spacing: revert;
text-transform: revert;
text-underline-offset: revert;
text-align: revert;
text-indent: revert;
text-shadow: revert;
vertical-align: revert;
}
.kodza-footer-shop__input:not(#_) {
text-decoration: revert;
transition: revert;
list-style: revert;
box-sizing: revert;
float: revert;
position: revert;
outline-offset: revert;
appearance: revert;
max-width: revert;
max-height: revert;
letter-spacing: revert;
text-transform: revert;
text-underline-offset: revert;
text-align: revert;
text-indent: revert;
text-shadow: revert;
vertical-align: revert;
white-space: revert;
min-height: revert;
}
.kodza-footer-shop__send:not(#_) {
text-decoration: revert;
list-style: revert;
float: revert;
position: revert;
box-shadow: revert;
appearance: revert;
max-width: revert;
max-height: revert;
letter-spacing: revert;
text-transform: revert;
text-underline-offset: revert;
text-align: revert;
text-indent: revert;
text-shadow: revert;
vertical-align: revert;
white-space: revert;
min-height: revert;
}
.kodza-footer-shop__note:not(#_) {
border: revert;
border-radius: revert;
outline: revert;
text-decoration: revert;
transition: revert;
list-style: revert;
box-sizing: revert;
float: revert;
position: revert;
background-color: revert;
background-image: revert;
box-shadow: revert;
outline-offset: revert;
appearance: revert;
max-width: revert;
max-height: revert;
font-family: revert;
font-weight: revert;
font-style: revert;
letter-spacing: revert;
text-transform: revert;
text-underline-offset: revert;
text-align: revert;
text-indent: revert;
text-shadow: revert;
vertical-align: revert;
}
.kodza-footer-shop__nav:not(#_) {
margin: revert;
padding: revert;
border-radius: revert;
outline: revert;
text-decoration: revert;
transition: revert;
list-style: revert;
box-sizing: revert;
float: revert;
position: revert;
border-right-width: revert;
border-bottom-width: revert;
border-left-width: revert;
border-right-style: revert;
border-bottom-style: revert;
border-left-style: revert;
border-right-color: revert;
border-bottom-color: revert;
border-left-color: revert;
background-color: revert;
background-image: revert;
box-shadow: revert;
outline-offset: revert;
appearance: revert;
max-width: revert;
max-height: revert;
color: revert;
font-family: revert;
font-size: revert;
font-weight: revert;
font-style: revert;
line-height: revert;
letter-spacing: revert;
text-transform: revert;
text-underline-offset: revert;
text-align: revert;
text-indent: revert;
text-shadow: revert;
vertical-align: revert;
}
.kodza-footer-shop__title:not(#_),
.kodza-footer-shop__copy:not(#_) {
border: revert;
padding: revert;
border-radius: revert;
outline: revert;
text-decoration: revert;
transition: revert;
list-style: revert;
box-sizing: revert;
float: revert;
position: revert;
background-color: revert;
background-image: revert;
box-shadow: revert;
outline-offset: revert;
appearance: revert;
max-width: revert;
max-height: revert;
font-style: revert;
text-transform: revert;
text-underline-offset: revert;
text-align: revert;
text-indent: revert;
text-shadow: revert;
vertical-align: revert;
}
.kodza-footer-shop__fold:not(#_) {
text-decoration: revert;
transition: revert;
list-style: revert;
float: revert;
position: revert;
box-shadow: revert;
appearance: revert;
max-width: revert;
max-height: revert;
letter-spacing: revert;
text-transform: revert;
text-underline-offset: revert;
text-indent: revert;
text-shadow: revert;
vertical-align: revert;
white-space: revert;
min-height: revert;
}
.kodza-footer-shop__links:not(#_),
.kodza-footer-shop__row:not(#_) {
border: revert;
border-radius: revert;
outline: revert;
text-decoration: revert;
transition: revert;
box-sizing: revert;
float: revert;
position: revert;
background-color: revert;
background-image: revert;
box-shadow: revert;
outline-offset: revert;
appearance: revert;
max-width: revert;
max-height: revert;
color: revert;
font-family: revert;
font-size: revert;
font-weight: revert;
font-style: revert;
line-height: revert;
letter-spacing: revert;
text-transform: revert;
text-underline-offset: revert;
text-align: revert;
text-indent: revert;
text-shadow: revert;
vertical-align: revert;
}
.kodza-footer-shop__link:not(#_) {
border: revert;
margin: revert;
padding: revert;
list-style: revert;
box-sizing: revert;
float: revert;
position: revert;
box-shadow: revert;
appearance: revert;
max-width: revert;
max-height: revert;
font-family: revert;
font-weight: revert;
font-style: revert;
letter-spacing: revert;
text-transform: revert;
text-underline-offset: revert;
text-align: revert;
text-indent: revert;
text-shadow: revert;
vertical-align: revert;
}
.kodza-footer-shop__picker:not(#_) {
text-decoration: revert;
list-style: revert;
float: revert;
position: revert;
appearance: revert;
max-width: revert;
max-height: revert;
letter-spacing: revert;
text-transform: revert;
text-underline-offset: revert;
text-align: revert;
text-indent: revert;
text-shadow: revert;
vertical-align: revert;
white-space: revert;
min-height: revert;
}
.kodza-footer-shop__globe:not(#_) {
border: revert;
margin: revert;
padding: revert;
border-radius: revert;
outline: revert;
text-decoration: revert;
transition: revert;
list-style: revert;
box-sizing: revert;
float: revert;
position: revert;
background-color: revert;
background-image: revert;
box-shadow: revert;
outline-offset: revert;
appearance: revert;
max-width: revert;
max-height: revert;
font-family: revert;
font-size: revert;
font-weight: revert;
font-style: revert;
line-height: revert;
letter-spacing: revert;
text-transform: revert;
text-underline-offset: revert;
text-align: revert;
text-indent: revert;
text-shadow: revert;
vertical-align: revert;
}
.kodza-footer-shop__chevron:not(#_) {
border: revert;
margin: revert;
padding: revert;
border-radius: revert;
outline: revert;
text-decoration: revert;
list-style: revert;
box-sizing: revert;
float: revert;
position: revert;
background-color: revert;
background-image: revert;
box-shadow: revert;
outline-offset: revert;
appearance: revert;
max-width: revert;
max-height: revert;
font-family: revert;
font-size: revert;
font-weight: revert;
font-style: revert;
line-height: revert;
letter-spacing: revert;
text-transform: revert;
text-underline-offset: revert;
text-align: revert;
text-indent: revert;
text-shadow: revert;
vertical-align: revert;
}
.kodza-footer-shop__places:not(#_) {
border: revert;
outline: revert;
text-decoration: revert;
float: revert;
outline-offset: revert;
appearance: revert;
max-width: revert;
max-height: revert;
color: revert;
font-family: revert;
font-size: revert;
font-weight: revert;
font-style: revert;
line-height: revert;
letter-spacing: revert;
text-transform: revert;
text-underline-offset: revert;
text-align: revert;
text-indent: revert;
text-shadow: revert;
vertical-align: revert;
}
.kodza-footer-shop__glide:not(#_) {
border: revert;
outline: revert;
text-decoration: revert;
list-style: revert;
box-sizing: revert;
float: revert;
box-shadow: revert;
outline-offset: revert;
appearance: revert;
max-width: revert;
max-height: revert;
color: revert;
font-family: revert;
font-size: revert;
font-weight: revert;
font-style: revert;
line-height: revert;
letter-spacing: revert;
text-transform: revert;
text-underline-offset: revert;
text-align: revert;
text-indent: revert;
text-shadow: revert;
vertical-align: revert;
}
.kodza-footer-shop__place:not(#_) {
border: revert;
margin: revert;
transition: revert;
list-style: revert;
float: revert;
background-color: revert;
background-image: revert;
box-shadow: revert;
outline-offset: revert;
appearance: revert;
max-width: revert;
max-height: revert;
font-family: revert;
font-style: revert;
letter-spacing: revert;
text-transform: revert;
text-underline-offset: revert;
text-align: revert;
text-indent: revert;
text-shadow: revert;
vertical-align: revert;
}
.kodza-footer-shop__pay:not(#_) {
border: revert;
border-radius: revert;
outline: revert;
text-decoration: revert;
transition: revert;
box-sizing: revert;
float: revert;
position: revert;
background-color: revert;
background-image: revert;
box-shadow: revert;
outline-offset: revert;
appearance: revert;
max-width: revert;
max-height: revert;
font-weight: revert;
font-style: revert;
text-transform: revert;
text-underline-offset: revert;
text-align: revert;
text-indent: revert;
text-shadow: revert;
vertical-align: revert;
}
.kodza-footer-shop__promises li:where(:not([class*="kodza-"]):not(.kodza-footer-shop__promises [class*="kodza-"] *)):not(#_),
.kodza-footer-shop__links li:where(:not([class*="kodza-"]):not(.kodza-footer-shop__links [class*="kodza-"] *)):not(#_),
.kodza-footer-shop__places li:where(:not([class*="kodza-"]):not(.kodza-footer-shop__places [class*="kodza-"] *)):not(#_),
.kodza-footer-shop__row li:where(:not([class*="kodza-"]):not(.kodza-footer-shop__row [class*="kodza-"] *)):not(#_),
.kodza-footer-shop__pay li:where(:not([class*="kodza-"]):not(.kodza-footer-shop__pay [class*="kodza-"] *)):not(#_) {
border: revert;
border-radius: revert;
outline: revert;
text-decoration: revert;
transition: revert;
list-style: revert;
box-sizing: revert;
float: revert;
position: revert;
background-color: revert;
background-image: revert;
box-shadow: revert;
outline-offset: revert;
appearance: revert;
max-width: revert;
max-height: revert;
color: revert;
font-family: revert;
font-size: revert;
font-weight: revert;
font-style: revert;
line-height: revert;
letter-spacing: revert;
text-transform: revert;
text-underline-offset: revert;
text-align: revert;
text-indent: revert;
text-shadow: revert;
vertical-align: revert;
}
.kodza-footer-shop__logo svg:where(:not([class*="kodza-"])):not(#_) {
border: revert;
margin: revert;
padding: revert;
border-radius: revert;
outline: revert;
text-decoration: revert;
transition: revert;
list-style: revert;
box-sizing: revert;
float: revert;
position: revert;
background-color: revert;
background-image: revert;
box-shadow: revert;
outline-offset: revert;
appearance: revert;
max-width: revert;
max-height: revert;
color: revert;
font-family: revert;
font-size: revert;
font-weight: revert;
font-style: revert;
line-height: revert;
letter-spacing: revert;
text-transform: revert;
text-underline-offset: revert;
text-align: revert;
text-indent: revert;
text-shadow: revert;
vertical-align: revert;
}
.kodza-footer-shop__pitch:not(#_):where(:not([hidden])),
.kodza-footer-shop__input:not(#_):where(:not([hidden])),
.kodza-footer-shop__link:not(#_):where(:not([hidden])),
.kodza-footer-shop__glide:not(#_):where(:not([hidden])),
.kodza-footer-shop__copy:not(#_):where(:not([hidden])),
.kodza-footer-shop__promises li:where(:not([class*="kodza-"]):not(.kodza-footer-shop__promises [class*="kodza-"] *)):not(#_):where(:not([hidden])),
.kodza-footer-shop__links li:where(:not([class*="kodza-"]):not(.kodza-footer-shop__links [class*="kodza-"] *)):not(#_):where(:not([hidden])),
.kodza-footer-shop__places li:where(:not([class*="kodza-"]):not(.kodza-footer-shop__places [class*="kodza-"] *)):not(#_):where(:not([hidden])),
.kodza-footer-shop__row li:where(:not([class*="kodza-"]):not(.kodza-footer-shop__row [class*="kodza-"] *)):not(#_):where(:not([hidden])) {
display: revert;
}
.kodza-footer-shop__pitch:not(#_):where([hidden]),
.kodza-footer-shop__input:not(#_):where([hidden]),
.kodza-footer-shop__link:not(#_):where([hidden]),
.kodza-footer-shop__glide:not(#_):where([hidden]),
.kodza-footer-shop__copy:not(#_):where([hidden]),
.kodza-footer-shop__promises li:where(:not([class*="kodza-"]):not(.kodza-footer-shop__promises [class*="kodza-"] *)):not(#_):where([hidden]),
.kodza-footer-shop__links li:where(:not([class*="kodza-"]):not(.kodza-footer-shop__links [class*="kodza-"] *)):not(#_):where([hidden]),
.kodza-footer-shop__places li:where(:not([class*="kodza-"]):not(.kodza-footer-shop__places [class*="kodza-"] *)):not(#_):where([hidden]),
.kodza-footer-shop__row li:where(:not([class*="kodza-"]):not(.kodza-footer-shop__row [class*="kodza-"] *)):not(#_):where([hidden]) {
display: none;
}
.kodza-footer-shop__error:not(#_)::before,
.kodza-footer-shop__plus:not(#_)::before,
.kodza-footer-shop__plus:not(#_)::after,
.kodza-footer-shop__place:not(#_)::before {
box-sizing: revert;
}
/* The component's own rules, one step stronger: real buttons and fields, what it sets on tags themes style (links, lists, headings, images…), and the root width (a theme may narrow it, never widen it) */
.kodza-footer-shop:not(#_) {
display: block;
box-sizing: border-box;
padding: 0;
border-top: 1px solid var(--kz-footer-shop-line);
background: var(--kz-footer-shop-bg);
color: var(--kz-footer-shop-fg);
font-size: 15px;
line-height: 1.5;
text-align: left;
}
.kodza-footer-shop__promises:not(#_),
.kodza-footer-shop__links:not(#_),
.kodza-footer-shop__row:not(#_),
.kodza-footer-shop__places:not(#_),
.kodza-footer-shop__pay:not(#_) {
margin: 0;
padding: 0;
list-style: none;
}
.kodza-footer-shop__promises li:not(#_),
.kodza-footer-shop__links li:not(#_),
.kodza-footer-shop__row li:not(#_),
.kodza-footer-shop__places li:not(#_) {
margin: 0;
padding: 0;
}
.kodza-footer-shop__promises:not(#_) {
display: grid;
margin-bottom: clamp(48px, 6vw, 72px);
padding-bottom: clamp(40px, 5vw, 56px);
border-bottom: 1px solid var(--kz-footer-shop-line);
}
.kodza-footer-shop__promise:not(#_) {
display: grid;
border-radius: 6px;
color: inherit;
text-decoration: none;
}
.kodza-footer-shop__icon:not(#_) {
display: block;
margin: 0.5px 0 0;
}
.kodza-footer-shop--big-icons .kodza-footer-shop__icon:not(#_) {
margin: 0 0 14px;
}
.kodza-footer-shop__logo:not(#_) {
display: inline-flex;
margin: -11px 0 0;
border-radius: 6px;
color: inherit;
font-size: 18px;
font-weight: 600;
line-height: 1;
letter-spacing: -0.04em;
text-decoration: none;
}
.kodza-footer-shop__logo svg:not(#_) {
display: block;
}
.kodza-footer-shop__pitch:not(#_) {
max-width: 24em;
margin: 10px 0 0;
font-size: 15px;
line-height: 1.55;
color: var(--kz-footer-shop-muted);
}
.kodza-footer-shop__field:not(#_) {
position: relative;
display: flex;
box-sizing: border-box;
margin: 0;
padding: 0 0 9px 16px;
}
.kodza-footer-shop__input:not(#_) {
flex: 1;
width: 100%;
min-width: 0;
height: 22px;
margin: 0;
padding: 0;
border: 0;
border-radius: 0;
background: none;
box-shadow: none;
color: inherit;
font: inherit;
font-size: 15px;
line-height: 22px;
outline: none;
}
.kodza-footer-shop__send:not(#_) {
display: inline-flex;
flex: none;
align-items: center;
justify-content: center;
box-sizing: border-box;
height: 40px;
margin: 0;
padding: 0 18px;
border: 0;
border-radius: 999px;
background: var(--kz-footer-shop-fg);
color: var(--kz-footer-shop-bg);
font: inherit;
font-size: 14px;
font-weight: 500;
line-height: 1;
cursor: pointer;
transition: background-color 0.35s ease, color 0.35s ease, opacity 0.2s ease;
}
@media (hover: hover) {
.kodza-footer-shop__send:hover:not(#_) {
opacity: 0.86;
}
}
.kodza-footer-shop__form.is-sending .kodza-footer-shop__send:not(#_) {
cursor: progress;
}
.kodza-footer-shop__form.is-done .kodza-footer-shop__send:not(#_) {
background: var(--kz-footer-shop-soft);
color: var(--kz-footer-shop-fg);
opacity: 1;
cursor: default;
}
.kodza-footer-shop__note:not(#_) {
display: grid;
margin: 10px 0 0;
padding: 0 2px;
font-size: 13px;
line-height: 1.4;
color: var(--kz-footer-shop-muted);
}
.kodza-footer-shop__nav:not(#_) {
display: grid;
}
.kodza-footer-shop__title:not(#_),
.kodza-footer-shop__copy:not(#_) {
margin: 0;
font-family: var(--kz-footer-shop-mono);
font-size: 12px;
font-weight: 400;
letter-spacing: 0.02em;
line-height: 1.5;
color: var(--kz-footer-shop-muted);
}
.kodza-footer-shop__title:not(#_) {
margin-bottom: 16px;
}
.kodza-footer-shop__fold:not(#_) {
display: none;
align-items: center;
justify-content: space-between;
gap: 16px;
box-sizing: border-box;
width: 100%;
height: 52px;
margin: 0;
padding: 0;
border: 0;
border-radius: 4px;
background: none;
color: inherit;
font: inherit;
font-size: 15px;
font-weight: 500;
line-height: 1;
text-align: left;
cursor: pointer;
}
.kodza-footer-shop__links:not(#_) {
display: grid;
}
.kodza-footer-shop__link:not(#_) {
background: linear-gradient(currentColor, currentColor) no-repeat 100% 100% / 0 1px;
transition: background-size 0.4s var(--kz-footer-shop-ease), color 0.2s ease;
}
.kodza-footer-shop__link:not(#_) {
color: inherit;
font-size: 14px;
line-height: 1.5;
text-decoration: none;
}
@media (hover: hover) {
.kodza-footer-shop__link:hover:not(#_) {
background-position: 0 100%;
background-size: 100% 1px;
}
}
.kodza-footer-shop__row:not(#_) {
display: flex;
}
.kodza-footer-shop__row .kodza-footer-shop__link:not(#_) {
font-size: 13px;
color: var(--kz-footer-shop-muted);
}
@media (hover: hover) {
.kodza-footer-shop__row .kodza-footer-shop__link:hover:not(#_) {
color: var(--kz-footer-shop-fg);
}
}
.kodza-footer-shop__picker:not(#_) {
display: inline-flex;
align-items: center;
gap: 8px;
box-sizing: border-box;
height: 36px;
margin: 0;
padding: 0 10px 0 12px;
border: 0;
border-radius: 999px;
background: none;
box-shadow: inset 0 0 0 1px var(--kz-footer-shop-edge);
color: inherit;
font: inherit;
font-size: 13px;
font-weight: 500;
line-height: 1;
cursor: pointer;
transition: box-shadow 0.2s ease;
}
.kodza-footer-shop__region.is-open .kodza-footer-shop__picker:not(#_) {
box-shadow: inset 0 0 0 1px var(--kz-footer-shop-edge-hover);
}
@media (hover: hover) {
.kodza-footer-shop__picker:hover:not(#_) {
box-shadow: inset 0 0 0 1px var(--kz-footer-shop-edge-hover);
}
}
.kodza-footer-shop__globe:not(#_),
.kodza-footer-shop__chevron:not(#_) {
display: block;
color: var(--kz-footer-shop-muted);
}
.kodza-footer-shop__chevron:not(#_) {
transition: transform 0.35s var(--kz-footer-shop-ease);
}
.kodza-footer-shop__places:not(#_) {
position: absolute;
display: flex;
box-sizing: border-box;
padding: 6px;
border-radius: 14px;
background: var(--kz-footer-shop-raised);
box-shadow: 0 0 0 1px var(--kz-footer-shop-line), var(--kz-footer-shop-shadow);
transition: clip-path 0.35s var(--kz-footer-shop-ease), transform 0.35s var(--kz-footer-shop-ease), opacity 0.2s ease, visibility 0s linear 0.35s;
}
.kodza-footer-shop__region.is-open .kodza-footer-shop__places:not(#_) {
transition-delay: 0s;
}
.kodza-footer-shop__places .kodza-footer-shop__glide:not(#_) {
position: absolute;
border-radius: 8px;
background: var(--kz-footer-shop-soft);
}
.kodza-footer-shop__region.is-open .kodza-footer-shop__glide:not(#_) {
transition: transform 0.22s ease, opacity 0.15s ease;
}
.kodza-footer-shop__place:not(#_) {
position: relative;
display: flex;
box-sizing: border-box;
padding: 0 10px 0 32px;
border-radius: 8px;
color: inherit;
font-size: 14px;
font-weight: 500;
line-height: 1;
text-decoration: none;
}
.kodza-footer-shop__place:focus:not(#_) {
outline: none;
}
.kodza-footer-shop__pay:not(#_) {
display: flex;
font-family: var(--kz-footer-shop-mono);
font-size: 12px;
letter-spacing: 0.02em;
line-height: 1.5;
color: var(--kz-footer-shop-muted);
}
.kodza-footer-shop__pay li:not(#_) {
display: inline-flex;
margin: 0;
padding: 0;
}
.kodza-footer-shop__promise:focus-visible:not(#_),
.kodza-footer-shop__logo:focus-visible:not(#_),
.kodza-footer-shop__link:focus-visible:not(#_) {
outline: 2px solid var(--kz-footer-shop-fg);
outline-offset: 3px;
}
.kodza-footer-shop__send:focus-visible:not(#_),
.kodza-footer-shop__picker:focus-visible:not(#_),
.kodza-footer-shop__fold:focus-visible:not(#_) {
outline: 2px solid var(--kz-footer-shop-fg);
outline-offset: 3px;
}
.kodza-footer-shop__link:focus-visible:not(#_) {
border-radius: 2px;
}
@media (prefers-reduced-motion: reduce) {
.kodza-footer-shop__send:not(#_),
.kodza-footer-shop__picker:not(#_) {
transition: none;
}
}
@media (prefers-reduced-motion: reduce) {
.kodza-footer-shop__link:not(#_),
.kodza-footer-shop__chevron:not(#_),
.kodza-footer-shop__places:not(#_),
.kodza-footer-shop__region.is-open .kodza-footer-shop__glide:not(#_) {
transition: none;
}
}
@container kodza-footer-shop (max-width: 520px) {
.kodza-footer-shop--big-icons .kodza-footer-shop__icon:not(#_) {
margin: 0 0 10px;
}
}
@container kodza-footer-shop (max-width: 600px) {
.kodza-footer-shop.is-ready .kodza-footer-shop__nav:not(#_) {
border-top: 1px solid var(--kz-footer-shop-line);
}
}
@container kodza-footer-shop (max-width: 600px) {
.kodza-footer-shop.is-ready .kodza-footer-shop__title:not(#_) {
display: none;
}
}
@container kodza-footer-shop (max-width: 600px) {
.kodza-footer-shop.is-ready .kodza-footer-shop__fold:not(#_) {
display: flex;
}
}
@container kodza-footer-shop (max-width: 600px) {
.kodza-footer-shop.is-ready .kodza-footer-shop__links li:not(#_) {
padding: 6px 0;
}
}
@container kodza-footer-shop (max-width: 600px) {
.kodza-footer-shop.is-ready .kodza-footer-shop__links li:last-child:not(#_) {
padding-bottom: 18px;
}
}
@container kodza-footer-shop (max-width: 600px) {
.kodza-footer-shop.is-ready .kodza-footer-shop__links .kodza-footer-shop__link:not(#_) {
font-size: 15px;
}
}
// Kodza License: free to use, client work included. Not to resell or redistribute, ported or not: https://kodza.dev/docs/#print-license
// Kodza — Footer Shop
// Writes the year of the ©, sends the letter without leaving the page (the button rolls
// Subscribe, Sending, Subscribed), opens the country menu (click, Enter, Space, arrows, Home,
// End, first letter; Escape, Tab or a click outside close it) and, on phones, folds the columns
// into accordions. The form fires "kodza:subscribe" with { form, email, respondWith }: call
// respondWith(promise) to send it yourself, otherwise it posts to its action. Picking a country
// fires "kodza:select" with { item, value } and follows its link.
(() => {
const SELECTOR = '.kodza-footer-shop';
const reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
const EASE = 'cubic-bezier(0.22, 1, 0.36, 1)';
const FOLD = 'cubic-bezier(0.65, 0, 0.35, 1)';
let uid = 0;
// A word rolls into its box: the old one leaves upwards, the new one rises from below, and the
// box eases to its new width instead of jumping
const roll = (box, text, { delay = 0, tick = false } = {}) => {
const prev = box.lastElementChild;
if (!prev || prev.textContent === text) return;
const from = box.offsetWidth;
const span = document.createElement('span');
span.textContent = text;
if (tick) {
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svg.setAttribute('viewBox', '0 0 16 16');
svg.setAttribute('aria-hidden', 'true');
svg.setAttribute('class', 'kodza-footer-shop__tick');
svg.innerHTML = '<path d="M3 8.5l3.2 3L13 4.5" pathLength="1" />';
span.prepend(svg);
}
box.append(span);
if (reduceMotion) return prev.remove();
const to = span.offsetWidth;
const timing = { duration: 450, delay, easing: EASE };
prev.setAttribute('aria-hidden', 'true');
box.animate([{ width: `${from}px` }, { width: `${to}px` }], { ...timing, fill: 'backwards' });
prev.animate([{ transform: 'none' }, { transform: 'translateY(-110%)' }], { ...timing, fill: 'forwards' }).finished.then(() => prev.remove(), () => {});
span.animate([{ transform: 'translateY(110%)' }, { transform: 'none' }], { ...timing, fill: 'backwards' });
};
const wrap = (box) => box && box.replaceChildren(Object.assign(document.createElement('span'), { textContent: box.textContent.trim() }));
// The letter: judged like Text Field (when the visitor leaves a filled field, then live while
// they fix it), sent in place, then the field stays filled and the button says it worked
function letter(form, signal) {
const input = form.querySelector('.kodza-footer-shop__input');
const send = form.querySelector('.kodza-footer-shop__send');
const say = form.querySelector('.kodza-footer-shop__say');
const hint = form.querySelector('.kodza-footer-shop__hint');
const error = form.querySelector('.kodza-footer-shop__error');
const done = form.querySelector('.kodza-footer-shop__done');
if (!input || !send || !say) return;
const prefix = `kodza-footer-shop-${++uid}`;
const described = [];
if (hint) described.push((hint.id = `${prefix}-hint`));
if (error) described.push((error.id = `${prefix}-error`));
if (described.length) input.setAttribute('aria-describedby', described.join(' '));
const idle = say.textContent.trim();
const thanks = done?.textContent.trim() || '';
if (done) done.textContent = ''; // written when it worked, so the status is read out then
wrap(say);
form.noValidate = true; // the message under the box says what is wrong, not the browser's bubble
const fail = (text) => {
if (error) error.textContent = text;
form.classList.toggle('is-error', Boolean(text));
};
const check = () => {
const bad = !input.validity.valid;
input.setAttribute('aria-invalid', String(bad));
fail(bad ? input.dataset.error || input.validationMessage : '');
return !bad;
};
input.addEventListener('blur', () => input.value.trim() && check(), { signal });
input.addEventListener('input', () => input.getAttribute('aria-invalid') === 'true' && check(), { signal });
form.addEventListener('submit', (event) => {
event.preventDefault();
if (send.getAttribute('aria-disabled') === 'true') return;
if (!check()) return input.focus();
let reply = null;
form.dispatchEvent(new CustomEvent('kodza:subscribe', {
bubbles: true,
detail: { form, email: input.value.trim(), respondWith: (promise) => { reply = promise; } },
}));
const sent = reply
? Promise.resolve(reply)
: fetch(form.action, { method: 'POST', body: new FormData(form), mode: 'no-cors' }).then((res) => {
if (res.type !== 'opaque' && !res.ok) throw new Error(`HTTP ${res.status}`);
});
send.setAttribute('aria-disabled', 'true');
input.readOnly = true;
form.classList.add('is-sending');
roll(say, send.dataset.sending || 'Sending');
// Long enough to read the word, however fast the list answers
const wait = new Promise((resolve) => setTimeout(resolve, reduceMotion ? 0 : 700));
Promise.all([sent, wait]).then(() => {
if (signal.aborted) return;
form.classList.remove('is-sending');
form.classList.add('is-done');
roll(say, send.dataset.sent || 'Subscribed', { tick: true });
if (done) done.textContent = thanks;
}, () => {
if (signal.aborted) return;
form.classList.remove('is-sending');
send.removeAttribute('aria-disabled');
input.readOnly = false;
roll(say, idle);
fail(done?.dataset.failed || 'Something went wrong. Try again.');
});
}, { signal });
}
// Country and currency: a button and a list of links, unrolled upwards (downwards when the
// page leaves no room above), with the highlight of Dropdown Menu gliding from one to the next
function region(root, signal) {
const trigger = root.querySelector('.kodza-footer-shop__picker');
const list = root.querySelector('.kodza-footer-shop__places');
if (!trigger || !list) return;
const items = [...list.querySelectorAll('.kodza-footer-shop__place')];
const value = trigger.querySelector('.kodza-footer-shop__value');
const glide = document.createElement('li');
glide.className = 'kodza-footer-shop__glide';
glide.setAttribute('aria-hidden', 'true');
list.prepend(glide);
list.id = `kodza-footer-shop-${++uid}-places`;
trigger.setAttribute('aria-controls', list.id);
items.forEach((item) => (item.tabIndex = -1));
wrap(value);
const isOpen = () => root.classList.contains('is-open');
const label = (item) => {
const country = item.querySelector('.kodza-footer-shop__country')?.textContent.trim() || item.textContent.trim();
const currency = item.querySelector('.kodza-footer-shop__currency')?.textContent.trim();
return currency ? `${country} · ${currency}` : country;
};
const light = (item) => {
if (!item) return list.style.setProperty('--kz-footer-shop-hl-on', 0);
list.style.setProperty('--kz-footer-shop-hl-y', `${item.offsetTop}px`);
list.style.setProperty('--kz-footer-shop-hl-h', `${item.offsetHeight}px`);
list.style.setProperty('--kz-footer-shop-hl-on', 1);
};
const focusItem = (item) => {
if (!item) return;
item.focus({ preventScroll: true });
light(item);
};
const open = (which = 'none') => {
if (!isOpen()) {
const box = trigger.getBoundingClientRect();
const height = list.offsetHeight + 8;
const below = box.top - height < 8 && box.bottom + height < innerHeight - 8;
// A closed list that changes side gets there at once, so it always unrolls from the button
if (below !== root.classList.contains('is-below')) {
list.style.transition = 'none';
root.classList.toggle('is-below', below);
void list.offsetWidth;
list.style.transition = '';
}
root.classList.toggle('is-end', box.left + list.offsetWidth > innerWidth - 8);
root.classList.add('is-open');
trigger.setAttribute('aria-expanded', 'true');
}
const current = items.find((item) => item.getAttribute('aria-current') === 'true');
if (which === 'first') focusItem(current || items[0]);
else if (which === 'last') focusItem(items[items.length - 1]);
else light(null);
};
const close = (refocus = false) => {
if (!isOpen()) return;
root.classList.remove('is-open');
trigger.setAttribute('aria-expanded', 'false');
light(null);
if (refocus) trigger.focus();
};
const pick = (item) => {
items.forEach((other) => (other === item ? other.setAttribute('aria-current', 'true') : other.removeAttribute('aria-current')));
if (value) roll(value, label(item), { delay: 280 });
root.dispatchEvent(new CustomEvent('kodza:select', { detail: { item, value: label(item) }, bubbles: true }));
light(item);
setTimeout(() => !signal.aborted && close(true), reduceMotion ? 0 : 320);
};
trigger.addEventListener('click', () => (isOpen() ? close() : open()), { signal });
trigger.addEventListener('keydown', (event) => {
if (['ArrowDown', 'Enter', ' '].includes(event.key)) {
event.preventDefault();
open('first');
} else if (event.key === 'ArrowUp') {
event.preventDefault();
open('last');
}
}, { signal });
items.forEach((item) => {
item.addEventListener('click', () => pick(item), { signal });
item.addEventListener('pointermove', () => document.activeElement !== item && focusItem(item), { signal });
});
list.addEventListener('pointerleave', () => {
if (list.contains(document.activeElement)) list.focus({ preventScroll: true });
light(null);
}, { signal });
list.addEventListener('keydown', (event) => {
const index = items.indexOf(document.activeElement);
const moves = { ArrowDown: index + 1, ArrowUp: index - 1, Home: 0, End: items.length - 1 };
if (event.key in moves) {
event.preventDefault();
focusItem(items[(moves[event.key] + items.length) % items.length]);
} else if (event.key === 'Tab') {
close();
} else if (event.key.length === 1 && /\S/.test(event.key)) {
// Type a letter: jump to the next country that starts with it
const letter = event.key.toLowerCase();
const order = [...items.slice(index + 1), ...items.slice(0, index + 1)];
focusItem(order.find((item) => item.textContent.trim().toLowerCase().startsWith(letter)));
}
}, { signal });
document.addEventListener('pointerdown', (event) => {
if (isOpen() && !root.contains(event.target)) close();
}, { signal });
// Escape closes it wherever the focus is: opened with a click, it stays on the button
// (Chrome) or on the page (Safari), not in the list
document.addEventListener('keydown', (event) => {
if (event.key !== 'Escape' || !isOpen()) return;
event.preventDefault();
close(true);
}, { signal });
// and the focus leaving it (Tab from the button) closes it too
root.addEventListener('focusout', (event) => {
if (isOpen() && event.relatedTarget && !root.contains(event.relatedTarget)) close();
}, { signal });
list.tabIndex = -1;
}
// Phones: each column title becomes the button of its accordion (CSS shows it under 600px of
// footer width). The list opens to its height, its links rising one after the other
function columns(nav, signal) {
nav.querySelectorAll('.kodza-footer-shop__col').forEach((col) => {
const title = col.querySelector('.kodza-footer-shop__title');
const list = col.querySelector('.kodza-footer-shop__links');
if (!title || !list) return;
list.id = `kodza-footer-shop-${++uid}-links`;
const button = document.createElement('button');
button.type = 'button';
button.className = 'kodza-footer-shop__fold';
button.setAttribute('aria-expanded', 'false');
button.setAttribute('aria-controls', list.id);
const plus = document.createElement('span');
plus.className = 'kodza-footer-shop__plus';
plus.setAttribute('aria-hidden', 'true');
button.append(title.textContent.trim(), plus);
title.after(button);
button.addEventListener('click', () => {
const opening = !col.classList.contains('is-open') || button.getAttribute('aria-expanded') === 'false';
const from = list.offsetHeight;
list.getAnimations().forEach((animation) => animation.cancel());
button.setAttribute('aria-expanded', String(opening));
if (opening) {
col.classList.add('is-open');
if (reduceMotion) return col.classList.remove('is-moving');
col.classList.add('is-moving');
list.animate([{ height: `${from}px` }, { height: `${list.scrollHeight}px` }], { duration: 520, easing: EASE }).onfinish = () => col.classList.remove('is-moving');
if (from === 0) [...list.children].forEach((li, i) => li.animate([
{ opacity: 0, transform: 'translateY(12px)' }, { opacity: 1, transform: 'none' },
], { duration: 560, delay: 60 + i * 45, easing: EASE, fill: 'backwards' }));
} else {
if (reduceMotion) return col.classList.remove('is-open', 'is-moving');
col.classList.add('is-moving');
list.animate([{ height: `${from}px`, opacity: 1 }, { height: '0px', opacity: 0.4 }], { duration: 420, easing: FOLD }).onfinish = () => col.classList.remove('is-open', 'is-moving');
}
}, { signal });
});
}
// One instance, with its own state: two copies on a page never interfere. Everything it
// starts is tied to `signal`, so kodza:destroy stops it all.
function mount(root, signal) {
root.querySelectorAll('.kodza-footer-shop__year').forEach((year) => {
year.textContent = String(new Date().getFullYear());
});
root.querySelectorAll('.kodza-footer-shop__form').forEach((form) => letter(form, signal));
root.querySelectorAll('.kodza-footer-shop__region').forEach((place) => region(place, signal));
root.querySelectorAll('.kodza-footer-shop__nav').forEach((nav) => columns(nav, signal));
}
// Kodza lifecycle, the same in every component: `kodza:init` starts the instances inside
// event.detail.root (the whole page by default), `kodza:destroy` stops them and puts their
// markup back. For themes and apps that swap content without a page load. Each running root
// keeps its markup as written under KODZA, shared by every Kodza script on the page: a Kodza
// component inside another one is saved as written, not half-started, and stopped with it.
const KODZA = Symbol.for('kodza');
const instances = new WeakMap();
const find = (scope = document) => [scope, ...scope.querySelectorAll(SELECTOR)].filter((el) => el.matches?.(SELECTOR));
const attrsOf = (el) => [...el.attributes].map((a) => [a.name, a.value]);
const put = (el, saved) => {
[...el.attributes].forEach((a) => el.removeAttribute(a.name));
saved.attrs.forEach(([name, value]) => el.setAttribute(name, value));
el.innerHTML = saved.html;
};
// The markup inside a root as written: Kodza components already running in it give back their own
const written = (root) => {
const all = [...root.querySelectorAll('*')];
if (!all.some((el) => el[KODZA])) return root.innerHTML;
const copy = root.cloneNode(true);
const twins = copy.querySelectorAll('*');
let last = null;
all.forEach((el, i) => {
if (!el[KODZA] || last?.contains(el)) return;
last = el;
put(twins[i], el[KODZA]);
});
return copy.innerHTML;
};
const init = (scope) => find(scope).forEach((root) => {
if (root.classList.contains('is-ready')) return; // already running, maybe from another copy of this script
const controller = new AbortController();
const saved = { html: written(root), attrs: attrsOf(root), stop: () => destroy(root) };
instances.set(root, { controller, saved });
root[KODZA] = saved;
mount(root, controller.signal);
root.classList.add('is-ready');
});
const destroy = (scope) => find(scope).forEach((root) => {
const instance = instances.get(root);
if (!instance) return;
instances.delete(root);
// The Kodza components running inside it stop first: once its markup is put back, their own
// script could not find them any more
root.querySelectorAll('*').forEach((el) => el[KODZA]?.stop());
instance.controller.abort();
delete root[KODZA];
put(root, instance.saved);
});
document.addEventListener('kodza:init', (event) => init(event.detail?.root));
document.addEventListener('kodza:destroy', (event) => destroy(event.detail?.root));
init();
// Loaded in <head>, the script runs before the markup exists: once more when it is parsed
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', () => init(), { once: true });
})();
'use client';
// Kodza — Footer Shop, for React. From https://kodza.dev/components/footer-shop/
// Kodza License: free to use, client work included. Not to resell or redistribute, ported or not: https://kodza.dev/docs/#print-license
// Next to this file: footer-shop.css and footer-shop.js, the component's CSS and JS from Kodza, as they are.
// Yours to edit: the markup is plain JSX below.
// The script changes this markup once React has rendered it (it adds layers, splits text, moves
// nodes), so React must not patch it afterwards. When a prop changes, the key gives React a fresh
// element: kodza:destroy stops the script on the old one, kodza:init starts it on the new one.
// Built from your data (tabs, rows, options)? Put what the markup is made of in the key, as plain
// values (ids, text, numbers): a change starts the script again on fresh markup. What the visitor
// changes (the open tab, the sort, what they type) is the script's: keep it out of the key, follow
// it with onClick or onInput on the root, and change it from your code as a visitor would (click
// the tab; set the field's value, then dispatch a bubbling input event).
import { useRef, useEffect, type ComponentPropsWithoutRef, type ComponentRef, type CSSProperties } from 'react';
import './footer-shop.css';
type Variant = "big-icons";
export type FooterShopProps = Omit<ComponentPropsWithoutRef<'footer'>, "children" | "variant" | "vars" | "onSubscribe" | "onSelect"> & {
/** Modifier class(es): .kodza-footer-shop--big-icons */
variant?: Variant | Variant[];
/** Settings, as CSS variables on the root */
vars?: {
/** default: #ffffff */
"--kz-footer-shop-bg"?: string | number;
/** default: #0a0a0a */
"--kz-footer-shop-fg"?: string | number;
/** default: rgba(10, 10, 10, 0.62) */
"--kz-footer-shop-muted"?: string | number;
/** default: rgba(10, 10, 10, 0.1) */
"--kz-footer-shop-line"?: string | number;
/** the field and the country button — default: rgba(10, 10, 10, 0.16) */
"--kz-footer-shop-edge"?: string | number;
/** default: rgba(10, 10, 10, 0.32) */
"--kz-footer-shop-edge-hover"?: string | number;
/** the highlight in the menu, a done button — default: rgba(10, 10, 10, 0.05) */
"--kz-footer-shop-soft"?: string | number;
/** the field and the menu — default: #ffffff */
"--kz-footer-shop-raised"?: string | number;
/** default: 0 18px 40px -16px rgba(10, 10, 10, 0.3) */
"--kz-footer-shop-shadow"?: string | number;
/** the content, centred — default: 1200px */
"--kz-footer-shop-width"?: string | number;
/** default: 24px */
"--kz-footer-shop-pad"?: string | number;
/** default: "Geist Mono", ui-monospace, "SF Mono", Menlo, monospace */
"--kz-footer-shop-mono"?: string | number;
/** default: cubic-bezier(0.22, 1, 0.36, 1) */
"--kz-footer-shop-ease"?: string | number;
};
/** "kodza:subscribe", fired by the script (bubbles from the root) */
onSubscribe?: (event: CustomEvent) => void;
/** "kodza:select", fired by the script (bubbles from the root) */
onSelect?: (event: CustomEvent) => void;
className?: string;
};
export function FooterShop({ variant, vars, onSubscribe, onSelect, className, style, ...rest }: FooterShopProps) {
const ref = useRef<ComponentRef<'footer'>>(null);
const classes = ["kodza-footer-shop", ...([] as string[]).concat(variant ?? []).map((v) => `kodza-footer-shop--${v}`), className].filter(Boolean).join(' ');
// Everything the markup is made of: when it changes, React gets a fresh element (see the top)
const key = JSON.stringify([classes, vars, style, rest]);
// Starts the script on this element once React has rendered it, stops it when it goes
useEffect(() => {
const root = ref.current;
let live = true;
// @ts-ignore: a plain script, no types to find
import('./footer-shop.js').then(() => {
if (live) document.dispatchEvent(new CustomEvent('kodza:init', { detail: { root } }));
});
return () => {
live = false;
document.dispatchEvent(new CustomEvent('kodza:destroy', { detail: { root } }));
};
}, [key]);
useEffect(() => {
const root = ref.current;
if (!root || !onSubscribe) return;
const listener = (event: Event) => onSubscribe(event as CustomEvent);
root.addEventListener("kodza:subscribe", listener);
return () => root.removeEventListener("kodza:subscribe", listener);
}, [onSubscribe, key]);
useEffect(() => {
const root = ref.current;
if (!root || !onSelect) return;
const listener = (event: Event) => onSelect(event as CustomEvent);
root.addEventListener("kodza:select", listener);
return () => root.removeEventListener("kodza:select", listener);
}, [onSelect, key]);
return (
<footer key={key} {...rest} className={classes} style={{ ...vars, ...style } as CSSProperties} ref={ref}>
<div className="kodza-footer-shop__inner">
<ul className="kodza-footer-shop__promises" aria-label="Our promises">
<li>
<a className="kodza-footer-shop__promise" href="/returns/">
<svg className="kodza-footer-shop__icon" viewBox="0 0 24 24" aria-hidden="true">
<path d="M9 14L4 9l5-5" />
<path d="M4 9h10.5a5.5 5.5 0 0 1 0 11H11" />
</svg>
{' '}
<span className="kodza-footer-shop__claim">Free returns, 30 days</span>
{' '}
<span className="kodza-footer-shop__small">Changed your mind? The label's on us.</span>
</a>
</li>
<li>
<a className="kodza-footer-shop__promise" href="/shipping/">
<svg className="kodza-footer-shop__icon" viewBox="0 0 24 24" aria-hidden="true">
<path d="M3.5 7.5L12 3l8.5 4.5v9L12 21l-8.5-4.5z" />
<path d="M3.5 7.5L12 12l8.5-4.5" />
<path d="M12 12v9" />
<path d="M7.8 5.3l8.4 4.5" />
</svg>
{' '}
<span className="kodza-footer-shop__claim">Ships in 48 hours</span>
{' '}
<span className="kodza-footer-shop__small">Tracked, from our workshop in Marseille.</span>
</a>
</li>
<li>
<a className="kodza-footer-shop__promise" href="/payment/">
<svg className="kodza-footer-shop__icon" viewBox="0 0 24 24" aria-hidden="true">
<rect x="3" y="5" width="18" height="14" rx="2" />
<path d="M3 10h18" />
<path d="M7 15h3" />
</svg>
{' '}
<span className="kodza-footer-shop__claim">Pay how you like</span>
{' '}
<span className="kodza-footer-shop__small">Card, Apple Pay or PayPal. Never stored here.</span>
</a>
</li>
<li>
<a className="kodza-footer-shop__promise" href="/contact/">
<svg className="kodza-footer-shop__icon" viewBox="0 0 24 24" aria-hidden="true">
<path d="M12 4a8 8 0 0 1 0 16c-1.3 0-2.6-.3-3.7-.9L4 20l.9-4.3A8 8 0 0 1 12 4z" />
<path d="M8.5 12h.01M12 12h.01M15.5 12h.01" />
</svg>
{' '}
<span className="kodza-footer-shop__claim">A human answers</span>
{' '}
<span className="kodza-footer-shop__small">Weekdays, 9 to 6. No bot, no ticket number.</span>
</a>
</li>
</ul>
<div className="kodza-footer-shop__main">
<div className="kodza-footer-shop__brand">
<a className="kodza-footer-shop__logo" href="/" aria-label="Galet, home">
<svg viewBox="0 0 24 24" width="22" height="22" aria-hidden="true">
<path d="M3 12.5C3 7 7.5 3 13 3s8 3.4 8 7.6C21 15.6 16.6 18 11.4 18 6.4 18 3 16.2 3 12.5z" />
</svg>
<span>galet</span>
</a>
<p className="kodza-footer-shop__pitch">Pieces for the table, made to outlast the trend.</p>
<form className="kodza-footer-shop__form" action="/newsletter/" method="post">
<div className="kodza-footer-shop__box">
<label className="kodza-footer-shop__field">
<input className="kodza-footer-shop__input" type="email" name="email" placeholder=" " autoComplete="email" required data-error="That does not look like an email." />
{' '}
<span className="kodza-footer-shop__label">Email</span>
</label>
{' '}
<button className="kodza-footer-shop__send" type="submit" data-sending="Sending" data-sent="Subscribed">
<span className="kodza-footer-shop__say">
<span>Subscribe</span>
</span>
</button>
</div>
<p className="kodza-footer-shop__note">
<span className="kodza-footer-shop__hint">One letter a month. New pieces, no countdowns.</span>
{' '}
<span className="kodza-footer-shop__error" aria-live="polite" />
{' '}
<span className="kodza-footer-shop__done" role="status" data-failed="Couldn't reach the list. Try again in a minute.">Done. Confirm it from your inbox.</span>
</p>
</form>
</div>
<nav className="kodza-footer-shop__nav" aria-label="Footer">
<div className="kodza-footer-shop__col">
<p className="kodza-footer-shop__title">Shop</p>
<ul className="kodza-footer-shop__links">
<li>
<a className="kodza-footer-shop__link" href="/new/">New in</a>
</li>
<li>
<a className="kodza-footer-shop__link" href="/kitchen/">Kitchen</a>
</li>
<li>
<a className="kodza-footer-shop__link" href="/table/">Table</a>
</li>
<li>
<a className="kodza-footer-shop__link" href="/bath/">Bath</a>
</li>
<li>
<a className="kodza-footer-shop__link" href="/linen/">Linen</a>
</li>
<li>
<a className="kodza-footer-shop__link" href="/gift-cards/">Gift cards</a>
</li>
</ul>
</div>
<div className="kodza-footer-shop__col">
<p className="kodza-footer-shop__title">Help</p>
<ul className="kodza-footer-shop__links">
<li>
<a className="kodza-footer-shop__link" href="/shipping/">Shipping</a>
</li>
<li>
<a className="kodza-footer-shop__link" href="/returns/">Returns</a>
</li>
<li>
<a className="kodza-footer-shop__link" href="/track/">Track an order</a>
</li>
<li>
<a className="kodza-footer-shop__link" href="/care/">Care guide</a>
</li>
<li>
<a className="kodza-footer-shop__link" href="/contact/">Contact</a>
</li>
</ul>
</div>
<div className="kodza-footer-shop__col">
<p className="kodza-footer-shop__title">Galet</p>
<ul className="kodza-footer-shop__links">
<li>
<a className="kodza-footer-shop__link" href="/story/">Our story</a>
</li>
<li>
<a className="kodza-footer-shop__link" href="/makers/">Makers</a>
</li>
<li>
<a className="kodza-footer-shop__link" href="/journal/">Journal</a>
</li>
<li>
<a className="kodza-footer-shop__link" href="/stockists/">Stockists</a>
</li>
<li>
<a className="kodza-footer-shop__link" href="/trade/">Trade</a>
</li>
</ul>
</div>
</nav>
</div>
<div className="kodza-footer-shop__base">
<div className="kodza-footer-shop__legal">
<div className="kodza-footer-shop__region">
<button className="kodza-footer-shop__picker" type="button" aria-expanded="false">
<svg className="kodza-footer-shop__globe" viewBox="0 0 16 16" aria-hidden="true">
<circle cx="8" cy="8" r="6.25" />
<path d="M1.75 8h12.5" />
<path d="M8 1.75c-2.1 2-2.1 10.5 0 12.5M8 1.75c2.1 2 2.1 10.5 0 12.5" />
</svg>
{' '}
<span className="kodza-footer-shop__sr">Country and currency: </span>
{' '}
<span className="kodza-footer-shop__value">France · EUR €</span>
{' '}
<svg className="kodza-footer-shop__chevron" viewBox="0 0 16 16" aria-hidden="true">
<path d="M4 10l4-4 4 4" />
</svg>
</button>
<ul className="kodza-footer-shop__places" aria-label="Country and currency">
<li>
<a className="kodza-footer-shop__place" href="/en-fr/" hrefLang="en-FR" aria-current="true">
<span className="kodza-footer-shop__country">France</span>
<span className="kodza-footer-shop__currency">EUR €</span>
</a>
</li>
<li>
<a className="kodza-footer-shop__place" href="/en-be/" hrefLang="en-BE">
<span className="kodza-footer-shop__country">Belgium</span>
<span className="kodza-footer-shop__currency">EUR €</span>
</a>
</li>
<li>
<a className="kodza-footer-shop__place" href="/en-de/" hrefLang="en-DE">
<span className="kodza-footer-shop__country">Germany</span>
<span className="kodza-footer-shop__currency">EUR €</span>
</a>
</li>
<li>
<a className="kodza-footer-shop__place" href="/en-gb/" hrefLang="en-GB">
<span className="kodza-footer-shop__country">United Kingdom</span>
<span className="kodza-footer-shop__currency">GBP £</span>
</a>
</li>
<li>
<a className="kodza-footer-shop__place" href="/en-us/" hrefLang="en-US">
<span className="kodza-footer-shop__country">United States</span>
<span className="kodza-footer-shop__currency">USD $</span>
</a>
</li>
<li>
<a className="kodza-footer-shop__place" href="/en-jp/" hrefLang="en-JP">
<span className="kodza-footer-shop__country">Japan</span>
<span className="kodza-footer-shop__currency">JPY ¥</span>
</a>
</li>
</ul>
</div>
<p className="kodza-footer-shop__copy">© <span className="kodza-footer-shop__year">2026</span> Galet</p>
<ul className="kodza-footer-shop__row">
<li>
<a className="kodza-footer-shop__link" href="/terms/">Terms</a>
</li>
<li>
<a className="kodza-footer-shop__link" href="/privacy/">Privacy</a>
</li>
<li>
<a className="kodza-footer-shop__link" href="/cookies/">Cookies</a>
</li>
</ul>
</div>
<ul className="kodza-footer-shop__pay" aria-label="Payment methods">
<li>Visa</li>
<li>Mastercard</li>
<li>Amex</li>
<li>Apple Pay</li>
<li>PayPal</li>
</ul>
</div>
</div>
</footer>
);
}
Checked
Robots poked at it before you did. Every component goes through the same checks, and again each time it changes.