{#
This file is part of EC-CUBE
Copyright(c) EC-CUBE CO.,LTD. All Rights Reserved.
http://www.ec-cube.co.jp/
For the full copyright and license information, please view the LICENSE
file that was distributed with this source code.
#}
{% extends 'default_frame.twig' %}
{% set body_class = 'product_page' %}
{% block title %}
{{ Product.name }}|{{ Product.ProductCategories|first.Category.name ?? '' }}|BlackCherry
{% endblock %}
{% set keywords_sentence = '無修正DVD通販のBlackCherry。人気女優出演作を匿名配送・即日発送!' %}
{% set desc = keywords_sentence ~ ' ' ~
Product.name ~
(Product.Maker.name ? '(' ~ Product.Maker.name ~ ')' : '')
%}
{% block description %}
<meta name="description" content="{{ desc|striptags|slice(0, 120) }}" >
{% endblock %}
{#
{% set page_description_raw = desc %}
{% block description %}
<meta name="description" content="{{ Product.description_list|striptags|slice(0, 120) }}{% if Product.search_word %} {{ Product.search_word }}{% endif %}">
{% if Product.search_word %}
<meta name="keywords" content="{{ Product.search_word }}">
{% endif %}
{% endblock %}
#}
{% block meta %}
<link rel="canonical" href="{{ url('product_detail', { id : Product.id }) }}">
{% endblock %}
{% block stylesheet %}
<style>
.slick-slider {
margin-bottom: 30px;
}
.slick-dots {
position: absolute;
bottom: -45px;
display: block;
width: 100%;
padding: 0;
list-style: none;
text-align: center;
}
.slick-dots li {
position: relative;
display: inline-block;
width: 20px;
height: 20px;
margin: 0 5px;
padding: 0;
cursor: pointer;
}
.slick-dots li button {
font-size: 0;
line-height: 0;
display: block;
width: 20px;
height: 20px;
padding: 5px;
cursor: pointer;
color: transparent;
border: 0;
outline: none;
background: transparent;
}
.slick-dots li button:hover,
.slick-dots li button:focus {
outline: none;
}
.slick-dots li button:hover:before,
.slick-dots li button:focus:before {
opacity: 1;
}
.slick-dots li button:before {
content: " ";
line-height: 20px;
position: absolute;
top: 0;
left: 0;
width: 12px;
height: 12px;
text-align: center;
opacity: .25;
background-color: black;
border-radius: 50%;
}
.slick-dots li.slick-active button:before {
opacity: .75;
background-color: black;
}
.slick-dots li button.thumbnail img {
width: 0;
height: 0;
}
</style>
{% endblock %}
{% block javascript %}
<script>
eccube.classCategories = {{ class_categories_as_json(Product)|raw }};
// 規格2に選択肢を割り当てる。
function fnSetClassCategories(form, classcat_id2_selected) {
var $form = $(form);
var product_id = $form.find('input[name=product_id]').val();
var $sele1 = $form.find('select[name=classcategory_id1]');
var $sele2 = $form.find('select[name=classcategory_id2]');
eccube.setClassCategories($form, product_id, $sele1, $sele2, classcat_id2_selected);
}
{% if form.classcategory_id2 is defined %}
fnSetClassCategories(
$('#form1'), {{ form.classcategory_id2.vars.value|json_encode|raw }}
);
{% elseif form.classcategory_id1 is defined %}
eccube.checkStock($('#form1'), {{ Product.id }}, {{ form.classcategory_id1.vars.value|json_encode|raw }}, null);
{% endif %}
</script>
<script>
$(function() {
// bfcache無効化
$(window).bind('pageshow', function(event) {
if (event.originalEvent.persisted) {
location.reload(true);
}
});
// Core Web Vital の Cumulative Layout Shift(CLS)対策のため
// img タグに width, height が付与されている.
// 630px 未満の画面サイズでは縦横比が壊れるための対策
// see https://github.com/EC-CUBE/ec-cube/pull/5023
$('.ec-grid2__cell').hide();
var removeSize = function () {
$('.slide-item').height('');
$('.slide-item img')
.removeAttr('width')
.removeAttr('height')
.removeAttr('style');
};
var slickInitial = function(slick) {
$('.ec-grid2__cell').fadeIn(1500);
var baseHeight = $(slick.target).height();
var baseWidth = $(slick.target).width();
var rate = baseWidth / baseHeight;
$('.slide-item').height(baseHeight * rate); // 余白を削除する
// transform を使用することでCLSの影響を受けないようにする
$('.slide-item img')
.css(
{
'transform-origin': 'top left',
'transform': 'scaleY(' + rate + ')',
'transition': 'transform .1s'
}
);
// 正しいサイズに近くなったら属性を解除する
setTimeout(removeSize, 500);
};
$('.item_visual').on('init', slickInitial);
// リサイズ時は CLS の影響を受けないため属性を解除する
$(window).resize(removeSize);
$('.item_visual').slick({
dots: false,
arrows: false,
responsive: [{
breakpoint: 768,
settings: {
dots: true
}
}]
});
$('.slideThumb').on('click', function() {
var index = $(this).attr('data-index');
$('.item_visual').slick('slickGoTo', index, false);
})
});
</script>
<script>
$(function() {
// お気に入り追加
$('.add_favorite').on('click', function(e) {
var data = $(this).data();
if (confirm(data.product_name + '\r\n\r\n' + 'こちらの商品をお気に入りに登録しますか?') ) {
} else {
return false;
}
});
// カート追加
$('.add-cart').on('click', function(event) {
{% if form.classcategory_id1 is defined %}
// 規格1フォームの必須チェック
if ($('#classcategory_id1').val() == '__unselected' || $('#classcategory_id1').val() == '') {
$('#classcategory_id1')[0].setCustomValidity('{{ '項目が選択されていません'|trans }}');
return true;
} else {
$('#classcategory_id1')[0].setCustomValidity('');
}
{% endif %}
{% if form.classcategory_id2 is defined %}
// 規格2フォームの必須チェック
if ($('#classcategory_id2').val() == '__unselected' || $('#classcategory_id2').val() == '') {
$('#classcategory_id2')[0].setCustomValidity('{{ '項目が選択されていません'|trans }}');
return true;
} else {
$('#classcategory_id2')[0].setCustomValidity('');
}
{% endif %}
// 個数フォームのチェック
if ($('#quantity').val() < 1) {
$('#quantity')[0].setCustomValidity('{{ '1以上で入力してください。'|trans }}');
return true;
} else {
$('#quantity')[0].setCustomValidity('');
}
event.preventDefault();
$form = $('#form1');
$.ajax({
url: $form.attr('action'),
type: $form.attr('method'),
data: $form.serialize(),
dataType: 'json',
beforeSend: function(xhr, settings) {
// Buttonを無効にする
$('.add-cart').prop('disabled', true);
}
}).done(function(data) {
// レスポンス内のメッセージをalertで表示
$.each(data.messages, function() {
$('#ec-modal-header').text(this);
});
$('.ec-modal').show()
// カートブロックを更新する
$.ajax({
url: "{{ url('block_cart') }}",
type: 'GET',
dataType: 'html'
}).done(function(html) {
const $html = $('<div>').html(html);
// ① ヘッダー用のカート部分を更新
const $headerCart = $html.find('.ec-headerRole__cart');
if ($headerCart.length) {
$('.ec-headerRole__cart').replaceWith($headerCart);
}
// ② サイドカート(ec-cartNaviWrap)も更新
const $sideCart = $html.find('.ec-cartNaviWrap');
if ($sideCart.length) {
$('.ec-cartNaviWrap').replaceWith($sideCart);
}
});
}).fail(function(data) {
alert('{{ 'カートへの追加に失敗しました。'|trans }}');
}).always(function(data) {
// Buttonを有効にする
$('.add-cart').prop('disabled', false);
});
});
});
$('.ec-modal-wrap').on('click', function(e) {
// モーダル内の処理は外側にバブリングさせない
e.stopPropagation();
});
$('.ec-modal-overlay, .ec-modal, .ec-modal-close, .ec-inlineBtn--cancel').on('click', function() {
$('.ec-modal').hide()
});
</script>
<script type="application/ld+json">
{
"@context": "https://schema.org/",
"@type": "Product",
"name": "{{ Product.name }}",
"image": [
{% for img in Product.ProductImage %}
"{{ app.request.schemeAndHttpHost }}{{ asset(img, 'save_image') }}"{% if not loop.last %},{% endif %}
{% else %}
"{{ app.request.schemeAndHttpHost }}{{ asset(''|no_image_product, 'save_image') }}"
{% endfor %}
],
"description": "{{ Product.description_list | default(Product.description_detail) | replace({'\n': '', '\r': ''}) | slice(0,300) }}",
{% if Product.code_min %}
"sku": "{{ Product.code_min }}",
{% endif %}
"offers": {
"@type": "Offer",
"url": "{{ url('product_detail', {'id': Product.id}) }}",
"priceCurrency": "{{ eccube_config.currency }}",
"price": {{ Product.getPrice02IncTaxMin ? Product.getPrice02IncTaxMin : 0}},
"availability": "{{ Product.stock_find ? "InStock" : "OutOfStock" }}"
}
}
</script>
<script>
$(function () {
var $modal = $('.bc-zoom-modal');
var $stage = $modal.find('.bc-zoom-stage');
var $img = $modal.find('.bc-zoom-img');
var $close = $modal.find('.bc-zoom-close');
// 要素の存在確認
if (!$modal.length || !$stage.length || !$img.length) {
console.error('Zoom modal elements not found');
return;
}
// サムネ(スライダー内を含む)クリックで開く
$(document).on('click', '.item_visual img.bc-zoom-thumb', function () {
var src = $(this).attr('data-zoom-src') || $(this).attr('src');
$img.attr('src', src);
// 画像読み込み後に初期拡大率を設定
$img.on('load', function() {
setInitialScale();
$img.off('load'); // 一度だけ実行
});
// 既に読み込み済みの場合
if ($img[0].complete && $img[0].naturalWidth > 0) {
setInitialScale();
}
resetTransform();
$('body').addClass('bc-no-scroll');
$modal.addClass('is-open').attr('aria-hidden', 'false');
});
// 閉じる(×/ESC/背景タップ)
function closeModal() {
$modal.removeClass('is-open').attr('aria-hidden', 'true');
$('body').removeClass('bc-no-scroll');
}
$close.on('click', closeModal);
$modal.on('click', function (e) { if (e.target === this) closeModal(); });
$(document).on('keydown', function (e) { if (e.key === 'Escape') closeModal(); });
// 変換状態
var scale = 1, minScale = 0.1, maxScale = 5; // minScaleを0.1に設定(SP版で0.5倍以下も可能に)
var tx = 0, ty = 0; // タップ拡大時の位置調整用
var isPC = window.innerWidth >= 768;
var initialScale = 1;
var pinch = { active:false, d0:0, cx:0, cy:0 };
// 2点間の距離を計算する関数
function dist2(touches) {
if (!touches || touches.length < 2) return 0;
try {
var dx = touches[0].clientX - touches[1].clientX;
var dy = touches[0].clientY - touches[1].clientY;
return Math.hypot(dx, dy);
} catch (e) {
return 0;
}
}
function applyTransform() {
$img.css('transform', 'translate(calc(-50% + ' + tx + 'px), calc(-50% + ' + ty + 'px)) scale(' + scale + ')');
}
function resetTransform() {
scale = initialScale;
tx = 0;
ty = 0;
applyTransform();
}
// 初期拡大率を設定(PC: 画面内に収まるように、SP: 0.5倍)
function setInitialScale() {
if (!$img[0] || $img[0].naturalWidth === 0) {
initialScale = 1;
scale = 1;
return;
}
var stageRect = $stage[0].getBoundingClientRect();
var stageWidth = stageRect.width;
var stageHeight = stageRect.height;
var imgNaturalWidth = $img[0].naturalWidth;
var imgNaturalHeight = $img[0].naturalHeight;
// デバイス判定を再計算
isPC = window.innerWidth >= 768;
if (isPC) {
// PC版: 画面内に完全に収まるようにする(幅・高さの両方を考慮)
var scaleByWidth = (stageWidth * 0.9) / imgNaturalWidth; // 90%で余裕を持たせる
var scaleByHeight = (stageHeight * 0.9) / imgNaturalHeight;
initialScale = Math.min(scaleByWidth, scaleByHeight);
// 最小・最大拡大率の制限
initialScale = Math.min(maxScale, Math.max(0.1, initialScale)); // PC版もminScaleを0.1に
} else {
// SP版: 0.5倍
initialScale = 0.5;
}
scale = initialScale;
tx = 0;
ty = 0;
applyTransform();
}
// SP版: ドラッグ移動用の変数
var dragging = false;
var dragStartX = 0;
var dragStartY = 0;
var dragStartTx = 0;
var dragStartTy = 0;
var rafId = null;
// タップで拡大(SP/タッチデバイス用)
var lastTapTime = 0;
var lastTapX = 0;
var lastTapY = 0;
var tapThreshold = 300; // ダブルタップ判定の時間(ms)
var tapDistanceThreshold = 50; // ダブルタップ判定の距離(px)
var isTap = true; // タップ判定用フラグ
var dragDistance = 0; // ドラッグ距離
// SP版: タッチ開始(ドラッグ/タップ/ピンチズーム)
// ネイティブイベントを使用(jQueryのイベントオブジェクトの問題を回避)
function handleTouchStart(e) {
try {
// 要素の存在確認
if (!$modal.length || !$stage.length || !$stage[0]) return;
// モーダルが開いていない場合は無視
if (!$modal.hasClass('is-open')) return;
// デバイス判定を再計算
isPC = window.innerWidth >= 768;
// タッチイベントを取得
var touches = e.touches || [];
if (!touches || touches.length === 0) return;
// PC版の場合は無効(ピンチズームはSP版のみ)
if (isPC && touches.length === 2) return;
// 2点タッチ時はピンチズーム
if (touches.length === 2) {
dragging = false; // ドラッグを無効化
isTap = false;
var d = dist2(touches);
if (d === 0 || d === undefined || !isFinite(d)) return; // 無効な値はスキップ
// ピンチ開始(常に初期化・リセット)
pinch.active = true;
pinch.d0 = d; // 基準距離を設定
var rect = $stage[0].getBoundingClientRect();
pinch.cx = (touches[0].clientX + touches[1].clientX)/2 - rect.left;
pinch.cy = (touches[0].clientY + touches[1].clientY)/2 - rect.top;
e.preventDefault();
e.stopPropagation();
return;
}
// PC版の場合は無効
if (isPC) return;
// シングルタッチのみ処理
if (touches.length !== 1) return;
var touch = touches[0];
var rect = $stage[0].getBoundingClientRect();
// 座標を統一(相対座標)
var touchX = touch.clientX - rect.left;
var touchY = touch.clientY - rect.top;
// 拡大時(initialScaleより大きい場合)は常にドラッグ可能
if (scale > initialScale) {
dragging = true;
isTap = true; // ドラッグ開始時は一旦trueに(距離判定でfalseになる)
dragDistance = 0;
dragStartX = touch.clientX;
dragStartY = touch.clientY;
dragStartTx = tx;
dragStartTy = ty;
// タップ開始位置を記録(相対座標、ダブルタップ判定用)
lastTapX = touchX;
lastTapY = touchY;
e.preventDefault();
e.stopPropagation();
} else {
// 縮小時はタップのみ
isTap = true;
dragging = false;
// タップ開始位置を記録(相対座標)
lastTapX = touchX;
lastTapY = touchY;
}
} catch (err) {
console.error('Error in handleTouchStart:', err);
}
}
// SP版: タッチ終了(ドラッグ/タップ/ピンチズーム)
function handleTouchEnd(e) {
try {
// 要素の存在確認
if (!$modal.length || !$stage.length || !$stage[0]) return;
// モーダルが開いていない場合は無視
if (!$modal.hasClass('is-open')) return;
// デバイス判定を再計算
isPC = window.innerWidth >= 768;
// タッチイベントを取得
var touches = e.touches || [];
var changedTouches = e.changedTouches || [];
// ピンチズーム終了(2点タッチが1点以下になった場合)
if (!touches || touches.length < 2) {
pinch.active = false;
pinch.d0 = 0; // リセット
dragging = false; // ドラッグもリセット
}
// requestAnimationFrameのクリーンアップ
if (rafId) {
cancelAnimationFrame(rafId);
rafId = null;
}
// PC版の場合は無効
if (isPC) return;
// シングルタッチのみ処理
if (!changedTouches || changedTouches.length !== 1) {
dragging = false;
return;
}
var touch = changedTouches[0];
var now = Date.now();
var rect = $stage[0].getBoundingClientRect();
var tapX = touch.clientX - rect.left;
var tapY = touch.clientY - rect.top;
// ドラッグ終了処理
var wasDragging = dragging;
if (dragging) {
dragging = false;
// ドラッグ距離が小さかった場合(10px以下)はタップとして扱う
if (dragDistance <= 10) {
// 拡大時はダブルタップでリセット
if (scale > initialScale) {
var isDoubleTap = (now - lastTapTime < tapThreshold) &&
(Math.abs(tapX - lastTapX) < tapDistanceThreshold) &&
(Math.abs(tapY - lastTapY) < tapDistanceThreshold);
if (isDoubleTap) {
// ダブルタップでリセット
e.preventDefault();
resetTransform();
lastTapTime = 0;
dragDistance = 0;
return;
}
}
// タップとして処理
isTap = true;
} else {
// ドラッグした場合はタップ処理をスキップ
isTap = false;
dragDistance = 0;
return;
}
}
// ピンチズーム中はタップ処理を無効化
if (pinch.active) {
return;
}
// ドラッグしていない場合のタップ処理(ダブルタップリセットのみ)
if (!wasDragging) {
// ダブルタップ判定(拡大時のみリセット)
if (scale > initialScale) {
var isDoubleTap = (now - lastTapTime < tapThreshold) &&
(Math.abs(tapX - lastTapX) < tapDistanceThreshold) &&
(Math.abs(tapY - lastTapY) < tapDistanceThreshold);
if (isDoubleTap) {
// ダブルタップでリセット
e.preventDefault();
resetTransform();
lastTapTime = 0;
return;
}
}
// シングルタップでの拡大機能は削除
}
// タップ情報を記録(次のタップとの比較用)
lastTapTime = now;
lastTapX = tapX;
lastTapY = tapY;
dragDistance = 0;
} catch (err) {
console.error('Error in handleTouchEnd:', err);
}
}
// タッチ移動処理(ピンチズーム/ドラッグ)
function handleTouchMove(e) {
try {
// 要素の存在確認
if (!$modal.length || !$stage.length || !$stage[0]) return;
// モーダルが開いていない場合は無視
if (!$modal.hasClass('is-open')) return;
// デバイス判定を再計算
isPC = window.innerWidth >= 768;
// PC版の場合は無効
if (isPC) return;
// タッチイベントを取得
var touches = e.touches || [];
if (!touches || touches.length === 0) return;
// ピンチズーム処理(2点タッチ時)
if (touches.length === 2) {
e.preventDefault();
e.stopPropagation();
dragging = false; // ドラッグを無効化
var d1 = dist2(touches);
if (d1 === 0 || d1 === undefined || !isFinite(d1)) return; // 無効な値はスキップ
// ピンチが開始されていない、または基準距離が未設定の場合は初期化
if (!pinch.active || !pinch.d0 || pinch.d0 === 0) {
pinch.active = true;
pinch.d0 = d1; // 基準距離を設定
var rect = $stage[0].getBoundingClientRect();
pinch.cx = (touches[0].clientX + touches[1].clientX)/2 - rect.left;
pinch.cy = (touches[0].clientY + touches[1].clientY)/2 - rect.top;
// 初回は基準を設定するだけで、計算は次回のtouchmoveで実行
return;
}
// ピンチ距離の変化から拡大率を計算(基準距離pinch.d0に対する現在の距離d1の比率)
var scaleChange = d1 / pinch.d0;
if (!isFinite(scaleChange) || scaleChange <= 0) {
// 無効な値の場合はスキップ
return;
}
// 現在の拡大率を基準に新しい拡大率を計算
var oldScale = scale;
var newScale = oldScale * scaleChange;
// 拡大率の制限
newScale = Math.min(maxScale, Math.max(minScale, newScale));
// ピンチ中心を基準に拡大(ピンチ中心がズームの中心になるように)
var rect = $stage[0].getBoundingClientRect();
var stageCenterX = rect.width / 2;
var stageCenterY = rect.height / 2;
// ピンチ中心から画像中心への相対位置を計算
var offsetX = pinch.cx - stageCenterX - tx;
var offsetY = pinch.cy - stageCenterY - ty;
// 拡大率の変化比
var scaleRatio = newScale / oldScale;
if (scaleRatio > 0 && isFinite(scaleRatio)) {
// ピンチ中心を基準に位置を調整
tx -= offsetX * (scaleRatio - 1);
ty -= offsetY * (scaleRatio - 1);
}
scale = newScale;
applyTransform();
// 基準距離を更新(次回の計算用に現在の距離を基準にする - 累積方式)
pinch.d0 = d1;
return;
}
// ドラッグ処理(1点タッチ時、拡大時のみ)
if (dragging && touches.length === 1 && !pinch.active) {
// 拡大時のみドラッグ可能(initialScaleより大きい場合)
if (scale > initialScale) {
e.preventDefault();
e.stopPropagation();
var touch = touches[0];
var dx = touch.clientX - dragStartX;
var dy = touch.clientY - dragStartY;
// ドラッグ距離を計算
dragDistance = Math.sqrt(dx * dx + dy * dy);
// ドラッグ距離が大きい場合はタップ扱いしない
if (dragDistance > 10) {
isTap = false;
}
// 移動量を適用
tx = dragStartTx + dx;
ty = dragStartTy + dy;
// requestAnimationFrameでスムーズに更新(カクつきを防止)
if (rafId) cancelAnimationFrame(rafId);
rafId = requestAnimationFrame(function() {
applyTransform();
});
}
return;
}
} catch (err) {
console.error('Error in handleTouchMove:', err);
}
}
// ネイティブイベントリスナーを登録
if ($stage.length && $stage[0]) {
$stage[0].addEventListener('touchstart', handleTouchStart, { passive: false });
$stage[0].addEventListener('touchmove', handleTouchMove, { passive: false });
$stage[0].addEventListener('touchend', handleTouchEnd, { passive: false });
$stage[0].addEventListener('touchcancel', handleTouchEnd, { passive: false });
}
// ホイールでズーム(PC用、マウス位置を中心に拡大)
if ($stage.length && $stage[0]) {
$stage[0].addEventListener('wheel', function (e) {
try {
// 要素の存在確認
if (!$stage.length || !$stage[0]) return;
e.preventDefault();
var delta = e.deltaY;
var factor = (delta > 0) ? -0.1 : 0.1;
var oldScale = scale;
var newScale = Math.min(maxScale, Math.max(minScale, scale + factor));
// マウス位置を中心に拡大
var rect = $stage[0].getBoundingClientRect();
var mouseX = e.clientX - rect.left;
var mouseY = e.clientY - rect.top;
var stageCenterX = rect.width / 2;
var stageCenterY = rect.height / 2;
var offsetX = mouseX - stageCenterX - tx;
var offsetY = mouseY - stageCenterY - ty;
var scaleRatio = newScale / oldScale;
tx -= offsetX * (scaleRatio - 1);
ty -= offsetY * (scaleRatio - 1);
scale = newScale;
applyTransform();
} catch (err) {
console.error('Error in wheel handler:', err);
}
}, { passive: false });
}
// リサイズ時に初期拡大率を再計算
$(window).on('resize', function() {
isPC = window.innerWidth >= 768;
if ($modal.hasClass('is-open')) {
setInitialScale();
}
});
});
</script>
{% endblock %}
{% block main %}
{# 商品拡大 #}
<div class="bc-zoom-modal" aria-hidden="true">
<button type="button" class="bc-zoom-close" aria-label="閉じる">×</button>
<div class="bc-zoom-stage">
<img class="bc-zoom-img" src="" alt="{{ Product.name }}">
</div>
</div>
<div class="ec-productRole">
<div class="ec-grid2">
<div class="ec-grid2__cell">
<div class="ec-sliderItemRole">
{#
<!--▼商品画像▼-->
<div class="item_visual">
{% for ProductImage in Product.ProductImage %}
<div class="slide-item"><img src="{{ asset(ProductImage, 'save_image') }}"
alt="{{ loop.first ? Product.name : '' }}"
width="550"
height="550"
{% if loop.index == 1 %}loading="eager" fetchpriority="high"{% else %}loading="lazy"{% endif %}
sizes="(max-width: 768px) 100vw, 50vw" /></div>
{% else %}
<div class="slide-item"><img src="{{ asset(''|no_image_product, 'save_image') }}" alt="{{ loop.first ? Product.name : '' }}" width="550" height="550"></div>
{% endfor %}
</div>
<!--▲商品画像▲-->
#}
<!--▼商品画像▼-->
<div class="item_visual">
{% for ProductImage in Product.ProductImage %}
<div class="slide-item">
<img
src="{{ asset(ProductImage, 'save_image') }}"
data-zoom-src="{{ asset(ProductImage, 'save_image') }}"
class="bc-zoom-thumb"
alt="{{ loop.first ? Product.name : '' }}"
width="550"
height="550"
{% if loop.index == 1 %}loading="eager" fetchpriority="high"{% else %}loading="lazy"{% endif %}
sizes="(max-width: 768px) 100vw, 50vw" />
</div>
{% else %}
<div class="slide-item">
<img
src="{{ asset(''|no_image_product, 'save_image') }}"
data-zoom-src="{{ asset(''|no_image_product, 'save_image') }}"
class="bc-zoom-thumb"
alt="{{ loop.first ? Product.name : '' }}"
width="550" height="550" />
</div>
{% endfor %}
</div>
<!--▲商品画像▲-->
</div>
</div>
<!--▼商品紹介テーブル▼-->
<div class="ec-grid2__cell">
<table class="detail-table">
<!--★商品名★-->
<tr class="detail-table__title">
<th>商品名</th>
<td>
<p>{{ Product.name }}</p>
</td>
</tr>
<!--★キャスト★-->
<tr class="detail-table__item">
<th>出演女優</th>
{% if Product.actress is not empty %}
<td>
{% set actresses = Product.actress|split(',') %}
{% for actress in actresses %}
{% set actress = actress|trim %}
<a href="{{ url('product_list') }}?date=&maker_id=&keyword={{ actress|url_encode }}">{{ actress }}</a>{% if not loop.last %}, {% endif %}
{% endfor %}
</td>
{% endif %}
</tr>
<!--★発売日★-->
<tr class="detail-table__item">
<th>入荷日</th>
<td>
{% for productCategory in Product.ProductCategories %}
{% set parent_category = productCategory.Category %}
{% if parent_category is not empty %}
{% set category_name = parent_category.name %}
{# 8桁なら YY年MM月DD日 に変換 #}
<a href="{{ url('product_list') }}?date={{ category_name[:4] ~ '-' ~ category_name[4:2] ~ '-' ~ category_name[6:2] }}&maker_id=&keyword=">
{{ category_name[:4] ~ '年' ~ category_name[4:2] ~ '月' ~ category_name[6:2] ~ '日' }}
</a>
{% endif %}
{% endfor %}
</td>
</tr>
<!--★収録時間★-->
<tr class="detail-table__item">
<th>収録時間</th>
<td>{{ Product.recording_time }}</td>
</tr>
<!--★メーカー★-->
{% if Product.Maker is not empty %}
<tr class="detail-table__item">
<th>メーカー</th>
<td>
<a href="{{ url('product_list') }}?date=&maker_id={{ Product.Maker.id }}&keyword=">{{ Product.Maker.name }}</a>
</td>
</tr>
{% endif %}
<!--★詳細メインコメント★-->
<tr class="detail-table__desc">
<th>商品コメント</th>
<td>
<p>{{ Product.description_detail|raw|nl2br }}</p>
<br>
<p>※当作品の出演者は全て18歳以上です。</p>
</td>
</tr>
</table>
<!--▲商品紹介テーブル▲-->
<br>
<!--▼買い物かごエリア▼-->
<form action="{{ url('product_add_cart', {id:Product.id}) }}" method="post" id="form1" name="form1">
<table class="detail-class">
<tr class="detail-class_plan">
{% if form.classcategory_id1 is defined %}
<th>{{ form_label(form.classcategory_id1) }}</th>
<td>
<div class="ec-select">
{{ form_widget(form.classcategory_id1 ) }}
{{ form_errors(form.classcategory_id1) }}
</div>
</td>
{% if form.classcategory_id2 is defined %}
<th>{{ form_label(form.classcategory_id2) }}</th>
<td>
<div class="ec-select">
{{ form_row(form.classcategory_id2) }}
{{ form_errors(form.classcategory_id2) }}
</div>
</td>
{% endif %}
{% endif %}
</tr>
<tr class="detail-class_price">
<th>価格(税込)</th>
<td>
<div class="ec-productRole__price">
<div class="ec-price">
<span class="ec-price__price price02-default">{{ Product.getPrice02IncTaxMin|price }}</span>
<span class="ec-price__tax">{{ '円'|trans }}</span>
</div>
</div>
</td>
</tr>
</table>
<div class="ec-numberInput">
{{ form_widget(form.quantity, { data: '1', type: 'hidden'} ) }}
</div>
{{ form_rest(form) }}
</form>
<!-- Button -->
<div class="cart-btn">
{% if BaseInfo.option_favorite_product %}
<form action="{{ url('product_add_favorite', {id:Product.id}) }}" method="post">
<div class="ec-productRole__btn">
{% if favorite == false %}
<input type="image" id="favorite" class="ec-blockBtn--cancel add_favorite" src="{{ asset('assets/img/new/img_favorite-add.png') }}" alt="お気に入りに追加" data-product_name="{{ Product.name}}" />
{% else %}
<input type="image" class="ec-blockBtn--cancel" disabled="disabled" src="{{ asset('assets/img/new/img_favorite-added.png') }}" alt="お気に入りに追加済" />
{% endif %}
</div>
</form>
{% endif %}
<div class="ec-productRole__btn">
<input type="image" src="{{ asset('assets/img/new/img_cart-add.png') }}" alt="カートに入れる" class="ec-blockBtn--action add-cart" data-cartid="{{ Product.id }}" form="productForm{{ Product.id }}" />
</div>
</div>
<!-- モーダルウィンドウ -->
<div class="ec-modal">
<div class="ec-modal-overlay">
<div class="ec-modal-wrap">
<span class="ec-modal-close"><span class="ec-icon"><img src="{{ asset('assets/icon/cross-dark.svg') }}" alt=""/></span></span>
<div id="ec-modal-header" class="text-center">{{ 'カートに追加しました。'|trans }}</div>
<div class="ec-modal-box">
<div class="ec-role">
<span class="ec-inlineBtn--cancel">{{ 'お買い物を続ける'|trans }}</span>
<a href="{{ url('cart') }}" class="ec-inlineBtn--action">{{ 'カートへ進む'|trans }}</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{# 関連商品(同じメーカー) #}
{% if RelatedProducts is defined and RelatedProducts|length > 0 %}
<style>
.related-products-section {
margin-top: 60px;
padding: 40px 20px;
background-color: #f8f8f8;
}
.related-products-section h2 {
text-align: center;
margin-bottom: 30px;
font-size: 1.5rem;
font-weight: bold;
background-color: #c60d69;
color: white;
padding: 15px 20px;
border-radius: 10px;
width: 100%;
box-sizing: border-box;
}
@media only screen and (max-width: 768px) {
.related-products-section h2 {
font-size: 1.2rem;
padding: 12px 15px;
}
}
.related-products-grid {
display: grid;
gap: 16px;
grid-template-columns: repeat(2, 1fr);
}
@media only screen and (min-width: 1200px) {
.related-products-grid {
grid-template-columns: repeat(4, 1fr);
gap: 20px;
}
}
.related-product-item {
background: #fc7cb3;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
transition: transform 0.2s;
}
.related-product-item:hover {
transform: translateY(-4px);
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
}
.related-product-image {
width: 100%;
overflow: hidden;
background: #f0f0f0;
}
.related-product-image img {
width: 100%;
height: 100%;
object-fit: cover;
}
.related-product-info {
padding: 12px;
}
.related-product-date {
font-size: 0.75rem;
color: #666;
margin-bottom: 8px;
}
.related-product-title {
font-size: 0.75rem;
line-height: 1.3;
margin-bottom: 8px;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
min-height: calc(1.3em * 2);
}
.related-product-desc {
font-size: 0.7rem;
color: white;
line-height: 1.3;
margin-bottom: 8px;
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
}
.related-product-link {
display: block;
text-align: center;
padding: 8px;
background-color: #28a745;
color: white;
text-decoration: none;
font-size: 0.8rem;
border-radius: 4px;
transition: background-color 0.2s;
white-space: nowrap;
}
.related-product-link:hover {
background-color: #218838;
color: white;
}
@media only screen and (max-width: 768px) {
.related-product-link {
font-size: 0.7rem;
padding: 6px;
}
}
</style>
<div class="related-products-section">
<h2>関連商品({{ Product.Maker is not empty ? Product.Maker.name : '同じメーカー' }})</h2>
<div class="related-products-grid">
{% for P in RelatedProducts %}
<div class="related-product-item">
<a href="{{ url('product_detail', {'id': P.id}) }}">
<div class="related-product-image">
<img src="{{ asset(P.list_image|no_image_product, 'save_image') }}"
alt="{{ P.name }}"
width="300"
height="300"
loading="lazy"
sizes="(max-width: 768px) 33vw, (max-width: 1200px) 20vw, 15vw" />
</div>
</a>
<div class="related-product-info">
<div class="related-product-desc">
{{ P.description_detail ? (P.description_detail | striptags | slice(0, 50) ~ '…') : '' }}
</div>
<a class="related-product-link" href="{{ url('product_detail', {'id': P.id}) }}">商品詳細へ</a>
</div>
</div>
{% endfor %}
</div>
</div>
{% endif %}
{# よく見られる商品 #}
{% if PopularProducts is defined and PopularProducts|length > 0 %}
<div class="related-products-section">
<h2>関連商品(よく見られる商品)</h2>
<div class="related-products-grid">
{% for P in PopularProducts %}
<div class="related-product-item">
<a href="{{ url('product_detail', {'id': P.id}) }}">
<div class="related-product-image">
<img src="{{ asset(P.list_image|no_image_product, 'save_image') }}"
alt="{{ P.name }}"
width="300"
height="300"
loading="lazy"
sizes="(max-width: 768px) 33vw, (max-width: 1200px) 20vw, 15vw" />
</div>
</a>
<div class="related-product-info">
<div class="related-product-desc">
{{ P.description_detail ? (P.description_detail | striptags | slice(0, 50) ~ '…') : '' }}
</div>
<a class="related-product-link" href="{{ url('product_detail', {'id': P.id}) }}">商品詳細へ</a>
</div>
</div>
{% endfor %}
</div>
</div>
{% endif %}
{% endblock %}