Files
f1den 29d10ce4cf Тема магазина по макету Figma
Каталог, карточка товара, корзина, оформление заказа и блог на WooCommerce.

Стили разложены по слоям: токены из Figma, база, компоненты, страницы,
брейкпоинты. Цвета, типографика, радиусы и отступы взяты из Variables и
текстовых стилей макета, ниже по слоям хардкода нет.

Иконки — Phosphor, вставляются инлайном как SVG, символами ничего не рисуется.
Стили WooCommerce отключены, вёрстка своя, !important не используется.
2026-07-30 22:38:49 +03:00

577 lines
13 KiB
CSS
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* Components.
*
* Every control here is built against the `Компоненты` matrices in Figma
* (DESKTOP + MOBILE, light theme) and uses tokens only. States follow the
* design's own vocabulary: default / hover / pressed / focus / error /
* disabled.
*/
/* ---------------------------------------------------------------- icon --- */
.icon {
display: inline-block;
width: var(--icon-size, 24px);
height: var(--icon-size, 24px);
flex: 0 0 auto;
color: inherit;
fill: currentColor;
vertical-align: middle;
}
.icon-20 {
--icon-size: 20px;
}
.icon-16 {
--icon-size: 16px;
}
/* Icons that read as "back" are the forward vector mirrored, never a
second asset. */
.icon-flip {
transform: scaleX(-1);
}
/* -------------------------------------------------------------- button --- */
.button {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
padding: var(--pad-h-btn-m) var(--pad-w-text-m);
border: 1px solid transparent;
border-radius: var(--br-btn-m);
color: var(--text-main-contrast);
background: var(--bg-button);
font: var(--control-m);
text-align: center;
cursor: pointer;
transition:
color var(--motion-fast),
background-color var(--motion-fast),
border-color var(--motion-fast);
}
.button:hover {
background: var(--bg-button-hover);
}
.button:active {
background: var(--bg-button-pressed);
}
.button:disabled,
.button[aria-disabled="true"] {
color: var(--text-disable);
background: var(--bg-control-disable);
cursor: default;
}
/* Large button — the hero / checkout size. */
.button-l {
padding: var(--pad-h-btn-l) var(--pad-w-text-l);
border-radius: var(--br-btn-l);
}
/* An icon sitting next to the label tightens the padding on its side. */
.button:has(> .icon:first-child) {
padding-left: var(--pad-w-icon-m);
}
.button:has(> .icon:last-child) {
padding-right: var(--pad-w-icon-m);
}
.button-l:has(> .icon:first-child) {
padding-left: var(--pad-w-icon-l);
}
.button-l:has(> .icon:last-child) {
padding-right: var(--pad-w-icon-l);
}
/* Light (secondary) button. */
.button-light {
color: var(--text-main);
background: var(--bg-button-light);
}
.button-light:hover {
background: var(--bg-button-light-hover);
}
.button-light:active {
background: var(--bg-button-light-pressed);
}
/* Outline button — light fill with a visible edge. */
.button-outline {
color: var(--text-main);
border-color: var(--border-control);
background: var(--bg-main);
}
.button-outline:hover {
border-color: var(--border-control-hover);
background: var(--bg-button-light-hover);
}
.button-outline:active {
border-color: var(--border-control-pressed);
background: var(--bg-button-light-pressed);
}
/* Square icon-only control (cart, carousel arrows, favourite). */
.icon-button {
display: inline-grid;
width: 56px;
height: 56px;
padding: 0;
place-items: center;
border: 1px solid var(--border-control);
border-radius: var(--br-btn-m);
color: var(--text-main);
background: var(--bg-main);
cursor: pointer;
transition:
border-color var(--motion-fast),
background-color var(--motion-fast);
}
.icon-button:hover {
border-color: var(--border-control-hover);
background: var(--bg-button-light-hover);
}
.icon-button:active {
border-color: var(--border-control-pressed);
background: var(--bg-button-light-pressed);
}
.icon-button:disabled {
border-color: var(--border-control-disable);
color: var(--text-disable);
background: var(--bg-main);
cursor: default;
}
/* ---------------------------------------------------------- text link --- */
.text-link {
display: inline-flex;
align-items: center;
gap: 8px;
color: var(--text-link);
font: var(--control-m);
transition: color var(--motion-fast);
}
.text-link:hover {
color: var(--text-link-hover);
}
/* -------------------------------------------------- checkbox and radio --- */
/*
* Measured off the `radio-btn-icon` and `checkbox-icon` components: 20×20 box,
* 1px edge, 4px radius (`br-checkbox`), an 8px dot and a ~12px tick.
*
* The edge stays `border-checkbox` in every state, including checked — only
* the mark inside turns on. Turning the edge black as well is what made the
* radio read as a bullseye.
*
* Disabled fills with `bg-control-disable`, drops the edge, and dims the mark.
*/
.control-check {
display: inline-flex;
align-items: center;
gap: 12px;
font: var(--control-m);
cursor: pointer;
}
.control-check input[type="checkbox"],
.control-check input[type="radio"],
input[type="checkbox"],
input[type="radio"] {
position: relative;
width: 20px;
height: 20px;
margin: 0;
flex: 0 0 20px;
border: 1px solid var(--border-checkbox);
/* The mark is drawn in currentColor, so pin it here instead of inheriting
whatever colour the surrounding label happens to use. */
color: var(--text-main);
background: var(--bg-main);
cursor: pointer;
appearance: none;
transition:
border-color var(--motion-fast),
background-color var(--motion-fast);
}
input[type="checkbox"] {
border-radius: var(--br-checkbox);
}
input[type="radio"] {
border-radius: 50%;
}
input[type="checkbox"]:hover,
input[type="radio"]:hover {
border-color: var(--border-checkbox-hover);
}
/* The tick is the Phosphor `check-bold` vector, drawn as a mask so the mark
inherits `currentColor` and stays a vector at every zoom level. */
input[type="checkbox"]:checked::after {
position: absolute;
inset: 0;
background: currentColor;
content: "";
-webkit-mask: var(--icon-check) center / 12px 12px no-repeat;
mask: var(--icon-check) center / 12px 12px no-repeat;
}
input[type="radio"]:checked::after {
position: absolute;
top: 50%;
left: 50%;
width: 8px;
height: 8px;
border-radius: 50%;
background: currentColor;
content: "";
transform: translate(-50%, -50%);
}
input[type="checkbox"]:disabled,
input[type="radio"]:disabled {
border-color: transparent;
color: var(--text-disable);
background: var(--bg-control-disable);
cursor: default;
}
input[type="checkbox"]:focus-visible,
input[type="radio"]:focus-visible {
outline: 2px solid var(--border-input-focus);
outline-offset: 2px;
}
.control-check:has(input:disabled) {
color: var(--text-disable);
cursor: default;
}
/* ---------------------------------------------------------- text field --- */
/*
* Figma text field: 72px tall on desktop / 64px on mobile, 16px radius,
* the label sits inside the field above the value.
*/
.field {
display: flex;
flex-direction: column;
gap: 4px;
}
.field-control {
position: relative;
display: flex;
min-height: var(--h-input-l);
padding: 12px 20px;
align-items: center;
flex-direction: column;
justify-content: center;
gap: 2px;
border: 1px solid var(--border-input);
border-radius: var(--br-control-l);
background: var(--bg-main);
transition: border-color var(--motion-fast);
}
.field-control:hover {
border-color: var(--border-input-hover);
}
.field-control:focus-within {
border-color: var(--border-input-focus);
box-shadow: inset 0 0 0 1px var(--border-input-focus);
}
.field-label {
width: 100%;
color: var(--text-caption);
font: var(--caption);
}
.field-control input,
.field-control textarea,
.field-input {
width: 100%;
padding: 0;
border: 0;
outline: 0;
color: var(--text-main);
background: transparent;
font: var(--control-m);
}
.field-control input::placeholder,
.field-control textarea::placeholder {
color: var(--text-caption);
}
.field-control textarea {
min-height: calc(var(--h-text-area-l) - 48px);
resize: vertical;
}
.field-error-text,
.field-error {
color: var(--text-negative);
font: var(--caption);
}
/* The slot only takes space once it carries a message. */
.field-error:empty {
display: none;
}
.field.is-error .field-control {
border-color: var(--border-input-error);
box-shadow: inset 0 0 0 1px var(--border-input-error);
}
.field.is-disabled .field-control,
.field-control:has(input:disabled),
.field-control:has(textarea:disabled) {
border-color: transparent;
background: var(--bg-control-disable);
}
.field.is-disabled .field-label,
.field-control:has(input:disabled) .field-label,
.field-control:has(input:disabled) input {
color: var(--text-disable);
}
/* Compact single-line input used inside filters and the cart. */
.input-compact {
min-height: 48px;
padding: 12px 16px;
border: 1px solid var(--border-input);
border-radius: var(--br-control-m);
outline: 0;
color: var(--text-main);
background: var(--bg-main);
font: var(--control-m);
transition: border-color var(--motion-fast);
}
.input-compact:hover {
border-color: var(--border-input-hover);
}
.input-compact:focus {
border-color: var(--border-input-focus);
}
/* --------------------------------------------------------------- select --- */
.custom-select {
position: relative;
}
.custom-select-trigger {
display: flex;
width: 100%;
align-items: center;
justify-content: space-between;
gap: 8px;
padding: 0;
border: 0;
color: var(--text-main);
background: transparent;
font: var(--control-m);
cursor: pointer;
transition: color var(--motion-fast);
}
.custom-select-trigger .icon {
--icon-size: 20px;
transition: transform var(--motion-fast);
}
.custom-select.is-open .custom-select-trigger .icon {
transform: rotate(180deg);
}
.custom-select-menu {
position: absolute;
z-index: 20;
top: calc(100% + 8px);
right: 0;
min-width: 100%;
padding: 8px 0;
border: 1px solid var(--border-main);
border-radius: var(--br-control-m);
background: var(--bg-select-unit);
box-shadow: var(--shadow-m);
}
.custom-select-option {
display: block;
width: 100%;
padding: 10px 16px;
border: 0;
color: var(--text-main);
background: transparent;
font: var(--control-m);
text-align: left;
white-space: nowrap;
cursor: pointer;
transition: background-color var(--motion-fast);
}
.custom-select-option:hover {
background: var(--bg-select-unit-hover);
}
.custom-select-option[aria-selected="true"] {
background: var(--bg-select-unit-selected);
}
/* ----------------------------------------------------- quantity control --- */
.quantity-control {
display: grid;
min-height: 52px;
grid-template-columns: 40px minmax(0, 1fr) 40px;
align-items: center;
border-radius: var(--br-control-m);
background: var(--bg-secondary);
}
.quantity-control button {
display: grid;
height: 100%;
padding: 0;
place-items: center;
border: 0;
border-radius: inherit;
color: var(--text-main);
background: transparent;
cursor: pointer;
transition: background-color var(--motion-fast);
}
.quantity-control button .icon {
--icon-size: 20px;
}
.quantity-control button:hover {
background: var(--bg-button-light-hover);
}
.quantity-control button:active {
background: var(--bg-button-light-pressed);
}
.quantity-control button:disabled {
color: var(--text-disable);
cursor: default;
}
.quantity-control button:disabled:hover,
.quantity-control button:disabled:active {
background: transparent;
}
.quantity-control button:focus-visible {
z-index: 1;
outline: 2px solid var(--border-input-focus);
outline-offset: -2px;
}
.quantity-control input {
width: 100%;
padding: 0;
border: 0;
outline: 0;
background: transparent;
font: var(--control-m);
text-align: center;
appearance: textfield;
}
.quantity-control input::-webkit-outer-spin-button,
.quantity-control input::-webkit-inner-spin-button {
margin: 0;
appearance: none;
}
/* ------------------------------------------------------------- pill/tag --- */
.badge {
display: inline-block;
padding: 4px 8px;
border-radius: var(--br-checkbox);
color: var(--text-main-contrast);
background: var(--bg-main-contrast);
font: var(--upper-s);
letter-spacing: var(--ls-upper);
text-transform: uppercase;
}
/* ----------------------------------------------------------- focus ring --- */
.button:focus-visible,
.icon-button:focus-visible,
.text-link:focus-visible,
.custom-select-trigger:focus-visible,
.custom-select-option:focus-visible {
outline: 2px solid var(--border-input-focus);
outline-offset: 2px;
}
/* ------------------------------------------------------------ carousel --- */
/*
* Applied by script once a carousel is wired up. Without JS the container
* stays the grid it was authored as, showing every item.
*/
.is-carousel {
overflow: hidden;
}
.carousel-track {
--carousel-gap: 24px;
display: flex;
flex-wrap: nowrap;
gap: var(--carousel-gap);
will-change: transform;
transition: transform var(--motion-slow-duration) var(--motion-slow);
}
.carousel-track > * {
flex: 0 0
calc(
(100% - (var(--carousel-per-view) - 1) * var(--carousel-gap)) /
var(--carousel-per-view)
);
min-width: 0;
}
/* While the track is being re-ordered behind the scenes. */
.carousel-track.is-settling {
transition: none;
}