---
title: Карточка товара (в листинге)
section: catalog
theme: mebelidomanet
framework: bootstrap
redesign_status: done
last_updated: 2026-07-11
files:
  php_inline: []
  php_partials:
    - views/category/_item.php
    - views/category/_product.php
    - views/product/_favorite-icon.php
    - views/product/_star-ratings-info.php
  scss:
    - src/client/product-card/index.scss
  js: []
  widgets: []
  shared:
    - src/favorite/
    - src/btn-to-cart/
deps_on:
  - shared/favorite-module
  - product/product-reviews
themes_with_analog:
  - mebelmall
  - dommebeli
  - mebelmart
  - freshshop
  - mebelmag
  - mebelbazar
  - hormebel
  - cartzilla
  - mebelmarket
  - stolkomebeli
---

# Карточка товара

## Назначение

Плитка одного товара в листинге. Используется на странице категории, в каруселях главной (`new`/`top`/`special`), в страницах поиска и «Избранное», в блоках «Похожие» / «Связанные» / «Коллекция» на странице товара.

В проекте **два partial-файла** карточки:

- `views/category/_item.php` — упрощённая версия (мини-обзор товара по ID).
- `views/category/_product.php` — полная карточка с лейблами, рейтингом, размерами, наличием, ценой и кнопкой «Купить». Используется везде, где видно полную плитку.

## Где живёт

- **HTML:** `views/category/_product.php` — основной файл (95 строк); `views/category/_item.php` — облегчённый вариант.
- **SCSS:** `src/client/product-card/index.scss` — стили `.product-card`, `.product-card__name`, `.product-card__price`, `.product-card__now_price`, `.product-card__buy`, `.product-card__btn`, `.product-card__favorite-delete`, `.product-cont`, `.photo`.
- **JS:** прямого нет. Рейтинг — статика (SVG звёзды). Избранное / в корзину — через [shared/favorite-module](../shared/favorite-module.md) (атрибут `data-id` на `.product-card__favorite-delete`).

## Структура одной карточки (`_product.php`, редизайн)

Обёртка `.product-card` (`d-flex flex-column h-100`) с двумя зонами: медиа + тело.

1. **Медиа** `.product-card__media` (position-relative, тёплый фон `$md-bg`):
   - **Лейблы** `.product-cont` (absolute, top-left, flex-column) — `.product-label--{hit,new,sale}` пилюли с inline-SVG (stroke, currentColor) + текст: Хит (акцент, flame), Новинка (зелёный, sparkle), Sale (красный, tag). Активность берётся из `Product::getLabels()` (ключ `label`: `recommend`→Хит, `new`, `sale` при `price < oldPrice`). Рендер вынесен в общий partial `views/product/_labels.php` (карта `$labelMeta`), тот же partial используется на странице товара (`wrapClass => 'product-page__labels'`). Стили пилюль `.product-label` — верхнеуровневые в `product-card/index.scss` (шарятся). Раньше были растровые PNG-звёзды (`/images/label*.png`).
   - **Фото** `.photo` — ссылка на товар; `img` с `aspect-ratio: 1/1`, `object-fit: contain`, zoom `scale(1.05)` на hover карточки.
   - **«В наличии»** (опц., `$model->inStock`) — пилюля `.product-card__stock` (absolute bottom-left, зелёная точка + текст).
2. **Тело** `.product-card__body` (flex-column, gap):
   - **Star ratings** (опц., `$domain->showProductReview == 1`) — `.star-ratings-info_product_card`, здесь переопределён на `position: static` (в общем `star-ratings-info.scss` он absolute).
   - **Название** `.product-card__name` — `Html::a(...)`, слева, 2 строки (line-clamp), hover → `$md-accent`.
   - **Размеры** (опц., `$showDimentions && $model->type == 'import'`) — `.product-card__dims`, компактно «Ш · В · Г мм».
   - **Цена** `.product-card__price` / `.product-card__now_price` — serif (Fraunces), `number_format($model->price, 0, ' ', ' ')`, прижата вниз (`margin-top: auto`).
   - **Кнопка «Купить»** `.product-card__btn` — soft-фон `$md-bg` → hover `$md-accent` (белый текст), со стрелкой-SVG, ссылка на страницу товара.
3. **Спрятанный** `.product-card__favorite-delete[data-id]` — точка монтирования JS «удалить из избранного» (на странице `page-favorite`), сохранена.

## CSS / классы

- Своя BEM-разметка `.product-card*` в `src/client/product-card/index.scss`, палитра из `_md-tokens.scss` (`$md-line`, `$md-bg`, `$md-ink`, `$md-accent`, `$md-ease`). Не Bootstrap-утилиты (кроме `d-flex flex-column h-100` на обёртке).
- **Hover карточки:** `translateY(-4px)` + мягкая тень, `border-color: transparent`, zoom фото.
- **Owl-фикс:** в конце файла — правило для `.owl-max-height .owl-item > .product-card` / `> .item` (`flex: 1 1 100%`). Ячейка owl — flex-контейнер (global.scss), без этого карточка схлопывается по ширине квадратного media.

## JS-зависимости

- Прямых нет.
- На странице «Избранное» `.product-card__favorite-delete[data-id]` обрабатывается JS из [shared/favorite-module](../shared/favorite-module.md).
- Карусельная обёртка вокруг карточек (на главной) — `owl.carousel`, см. соответствующие блоки [main-page/product-carousel-tabs](../main-page/product-carousel-tabs.md), [catalog/category-page](category-page.md).

## Зависит от других блоков

- [shared/favorite-module](../shared/favorite-module.md) — обработчик кнопки «убрать из избранного».
- [product/product-reviews](../product/product-reviews.md) — общий partial `_star-ratings-info.php` для рейтинга.

## Заметки

- Карточка передаётся с разными опциями через `render(['../category/_product', ...])`: `$showDimentions` (по умолчанию `true`), `$showOldPrice` (на текущий момент в коде не используется, но в сигнатуре остался), `$pageType` (`blank` по умолчанию, влияет на `getPublicTitle($pageType, $category)`), `$category` (для контекста заголовка).
- `getPublicTitle()` — модель `Product` собирает заголовок с учётом домена и контекста страницы (категория/избранное/корзина/...).
- `$model->publicImageThumb` — путь к ресайзнутой миниатюре, см. `docs/images.md`.
- `<noindex>` оборачивает кнопку «Купить» если `$domain->btnBuyNoindex` — SEO-флаг по доменам, не трогать.
- **Редизайн выполнен** (2026-07-11) в стиле ../mebel-landing на чистом BS4+SCSS: рамка-карточка со скруглением, тёплая медиа-зона с квадратным фото (contain + zoom на hover), лейблы top-left, название слева (line-clamp 2), serif-цена, soft-CTA `$md-bg`→`$md-accent`. Затрагивает все места рендера `_product` (каталог, главная-карусель, поиск, избранное, похожие/связанные/коллекция, factory, api).
- **Кнопки «в избранное» на карточке НЕТ — сознательно.** В проекте ДВЕ несвязанные системы избранного: (1) legacy PHP-session — `_favorite-icon.php` + `favorite-icon/helpers.js` (fetch `/favorite/add-to-favorite`, пишет в `$_SESSION['favorite']`, обновляет мёртвый селектор `.favorite-counter small`); (2) актуальная React/Redux+localStorage — `src/favorite/` (`.btnToFavorite[data-uid…]` → `favorite/add`, `localStorage.larekFavorite`), её читают хедер-счётчик `.favoriteCounter` и страница `page-favorite`. Первая пишет в сессию, но хедер/страница её не видят; вторая (`.btnToFavorite`) на каждый add дёргает блокирующий `window.confirm(...)` (middleware `src/store/index.ts`) и добавляет без дедупа — ок для одной кнопки на странице товара, но спам-конфирмы в листинге. Добавлять wishlist на карточку → сперва помирить две системы (отдельная задача).
