/* * 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; }