Files
Additional/resources/views/product-detail.blade.php
T
twotalesanimation 6de01c13c5 New Initial Commit
2025-12-09 12:04:54 +02:00

734 lines
26 KiB
PHP
Raw 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.
@extends('layouts.app')
@section('title', $product->name . ' - Premium Custom Designs')
@section('styles')
<style>
.breadcrumb {
margin-bottom: 2rem;
font-size: 0.9rem;
}
.breadcrumb a {
color: var(--accent-primary);
text-decoration: none;
margin: 0 0.5rem;
}
.product-section {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 3rem;
margin-bottom: 4rem;
}
.product-image {
width: 100%;
height: 500px;
object-fit: cover;
border-radius: 8px;
cursor: pointer;
}
.lightbox {
display: none;
position: fixed;
z-index: 1000;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.9);
}
.lightbox.active {
display: flex;
align-items: center;
justify-content: center;
}
.lightbox-content {
position: relative;
max-width: 90%;
max-height: 90vh;
}
.lightbox-image {
width: 100%;
height: auto;
max-height: 85vh;
object-fit: contain;
}
.lightbox-close {
position: absolute;
top: 20px;
right: 35px;
color: white;
font-size: 40px;
font-weight: bold;
cursor: pointer;
transition: color 0.2s;
}
.lightbox-close:hover {
color: var(--accent-primary);
}
.lightbox-nav {
position: absolute;
top: 50%;
transform: translateY(-50%);
color: white;
font-size: 36px;
cursor: pointer;
user-select: none;
padding: 10px 15px;
transition: color 0.2s;
background-color: rgba(0, 0, 0, 0.5);
border-radius: 4px;
}
.lightbox-nav:hover {
color: var(--accent-primary);
background-color: rgba(0, 0, 0, 0.8);
}
.lightbox-prev {
left: 20px;
}
.lightbox-next {
right: 20px;
}
.lightbox-counter {
position: absolute;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
color: white;
font-size: 14px;
background-color: rgba(0, 0, 0, 0.5);
padding: 8px 12px;
border-radius: 4px;
}
.product-info h1 {
font-family: 'Abril Fatface', cursive;
font-size: 2.5rem;
color: var(--primary-color);
margin-bottom: 1rem;
}
.category-tag {
display: inline-block;
background-color: var(--accent-pink);
color: white;
padding: 0.4rem 1rem;
border-radius: 20px;
font-size: 0.85rem;
font-weight: 900;
margin-bottom: 1rem;
}
.price {
font-size: 2rem;
color: var(--primary-color);
font-weight: 600;
margin: 1rem 0;
}
.stock-status {
font-size: 0.9rem;
margin-bottom: 1.5rem;
}
.stock-status.in-stock {
color: var(--primary-color);
}
.stock-status.low-stock {
color: var(--accent-primary);
}
.stock-status.out-of-stock {
color: var(--accent-primary);
}
.description {
color: #666;
line-height: 1.8;
margin-bottom: 2rem;
font-size: 1.05rem;
}
.quantity-selector {
display: flex;
align-items: center;
gap: 1rem;
margin-bottom: 2rem;
}
.quantity-selector label {
font-weight: 600;
}
.quantity-selector input {
width: 80px;
padding: 0.5rem;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 1rem;
}
.btn-group {
display: flex;
gap: 1rem;
}
.related-products {
margin-top: 4rem;
padding-top: 3rem;
border-top: 1px solid #ddd;
}
.related-products h2 {
font-family: 'Abril Fatface', cursive;
font-size: 2rem;
color: var(--primary-color);
margin-bottom: 2rem;
}
.alert {
padding: 1rem;
border-radius: 4px;
margin-bottom: 2rem;
}
.alert-success {
background-color: #d4e8d4;
color: var(--primary-color);
border: 1px solid #a8d4a8;
}
.calculator-modal {
display: none;
position: fixed;
z-index: 2000;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.6);
}
.calculator-modal.active {
display: flex;
align-items: center;
justify-content: center;
}
.calculator-content {
background: white;
padding: 2rem;
border-radius: 12px;
max-width: 500px;
width: 90%;
position: relative;
}
.calculator-close {
position: absolute;
top: 15px;
right: 20px;
font-size: 28px;
font-weight: bold;
color: #666;
cursor: pointer;
transition: color 0.2s;
}
.calculator-close:hover {
color: var(--accent-primary);
}
.wall-schematic {
background: var(--section-light-bg);
padding: 2rem;
border-radius: 8px;
margin: 1.5rem 0;
text-align: center;
}
.wall-diagram {
width: 100%;
max-width: 300px;
height: 150px;
border: 3px solid var(--primary-color);
position: relative;
margin: 1rem auto;
background: white;
}
.wall-label {
position: absolute;
font-size: 0.8rem;
font-weight: 600;
color: var(--primary-color);
}
.wall-label.width {
bottom: -25px;
left: 50%;
transform: translateX(-50%);
}
.wall-label.height {
right: -60px;
top: 50%;
transform: translateY(-50%);
}
.calc-input-group {
margin-bottom: 1.5rem;
}
.calc-input-group label {
display: block;
font-weight: 600;
margin-bottom: 0.5rem;
color: var(--primary-color);
}
.calc-input-group input {
width: 100%;
padding: 0.75rem;
border: 2px solid #ddd;
border-radius: 6px;
font-size: 1rem;
}
.calc-result {
background: var(--accent-light);
padding: 1.5rem;
border-radius: 8px;
margin-top: 1.5rem;
text-align: center;
}
.calc-result h3 {
margin: 0 0 0.5rem 0;
color: white;
font-size: 1.2rem;
}
.calc-result .result-value {
font-size: 2.5rem;
font-weight: bold;
color: white;
font-family: 'Abril Fatface', cursive;
}
.calc-result small {
display: block;
margin-top: 0.5rem;
color: rgba(255, 255, 255, 0.9);
}
@media (max-width: 768px) {
.product-section {
grid-template-columns: 1fr;
gap: 2rem;
}
.product-image {
height: 300px;
}
.product-info h1 {
font-size: 1.8rem;
}
.calculator-content {
padding: 1.5rem;
}
}
</style>
@endsection
@section('content')
<main class="container" style="padding-top: 20px;">
<!-- Breadcrumb -->
<!-- <div class="breadcrumb">
<a href="{{ route('home') }}">Home</a> /
<a href="{{ route('wallpapers') }}">Products</a> /
<span>{{ $product->name }}</span>
</div> -->
@if (session('success'))
<div class="alert alert-success">
{{ session('success') }}
</div>
@endif
<!-- Product Section -->
<section class="product-section">
<div>
@if($product->images->count() > 0)
<img id="mainImage" src="{{ asset('storage/' . $product->images->first()->image_path) }}" alt="{{ $product->name }}" class="product-image">
@if($product->images->count() > 1)
<div style="display: flex; gap: 0.5rem; margin-top: 1rem; overflow-x: auto;">
@foreach($product->images as $image)
<img
src="{{ asset('storage/' . $image->image_path) }}"
alt="{{ $product->name }}"
class="product-thumbnail"
onclick="document.getElementById('mainImage').src = '{{ asset('storage/' . $image->image_path) }}'"
style="width: 80px; height: 80px; object-fit: cover; border-radius: 4px; cursor: pointer; border: 2px solid transparent; transition: border-color 0.2s; flex-shrink: 0;"
onmouseover="this.style.borderColor = 'var(--accent-primary)'"
onmouseout="this.style.borderColor = 'transparent'"
>
@endforeach
</div>
@endif
@else
<img id="mainImage" src="{{ $product->image }}" alt="{{ $product->name }}" class="product-image">
@endif
</div>
<div class="product-info">
<h1>{{ $product->name }}</h1>
<span class="category-tag">{{ $product->category->name }}</span>
<!-- <div class="stock-status @if($product->stock > 10) in-stock @elseif($product->stock > 0) low-stock @else out-of-stock @endif">
@if($product->stock > 0)
<strong>{{ $product->stock }} in stock</strong>
@else
<strong>Out of stock</strong>
@endif
</div> -->
<p class="description">{{ $product->description }}</p>
@if($product->type === 'wallpaper')
<div style="background: var(--section-light-bg); padding: 1rem; border-radius: 4px; margin-bottom: 1.5rem; font-size: 0.9rem;">
<p><strong>Wallpaper Info:</strong> Sold per meter (10m rolls). Pattern is 1m wide x 2.7m high. For a wall height of 2.7m, you need 3m of wallpaper.</p>
</div>
@elseif($product->type === 'mural')
<div style="background: var(--section-light-bg); padding: 1rem; border-radius: 4px; margin-bottom: 1.5rem; font-size: 0.9rem;">
<p><strong>Mural Info:</strong> Sold per (square meter). Single image, non-repeating pattern. Perfect for accent walls.</p>
</div>
@endif
@if($product->stock > 0)
<form action="{{ route('cart-add', $product) }}" method="POST">
@csrf
<!-- Print Stock Selection -->
<div class="quantity-selector" style="flex-direction: column; align-items: flex-start; gap: 0.5rem; margin-bottom: 2rem;">
<label for="print_stock_id" style="font-weight: 600;">Select Print Stock:</label>
<select id="print_stock_id" name="print_stock_id" required style="width: 100%; padding: 0.75rem; border: 1px solid #ddd; border-radius: 4px; font-size: 1rem;">
@foreach($product->printStocks as $stock)
<option value="{{ $stock->id }}"
data-cost="{{ $product->type === 'wallpaper' ? $stock->cost_per_meter : $stock->cost_per_m2 }}"
data-width="{{ $stock->width ?? 1 }}">
{{ $stock->name }} ({{ $stock->width ?? 1 }}m wide) - R{{ $product->type === 'wallpaper' ? number_format($stock->cost_per_meter, 2) : number_format($stock->cost_per_m2, 2) }}/{{ $product->type === 'wallpaper' ? 'm' : 'm²' }}
</option>
@endforeach
</select>
<small style="color: #666;">Base cost per {{ $product->type === 'wallpaper' ? 'meter' : 'm²' }} of print stock</small>
</div>
@if($product->type === 'wallpaper')
<!-- Calculator Button -->
<div style="margin-bottom: 1.5rem;">
<button type="button" class="btn btn--secondary" onclick="openCalculator()" style="width: 100%;">
📐 Calculate Required Length
</button>
</div>
<div class="quantity-selector" style="flex-direction: column; align-items: flex-start; gap: 0.5rem;">
<label for="length">Length Required (meters):</label>
<input type="number" id="length" name="length" min="1" step="0.5" value="3" required style="width: 150px;">
<small style="color: #666;">Recommended: Add 0.5m for pattern matching and trimming</small>
</div>
@elseif($product->type === 'mural')
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; margin-bottom: 2rem;">
<div>
<label for="width" style="display: block; font-weight: 600; margin-bottom: 0.5rem;">Width (meters):</label>
<input type="number" id="width" name="width" min="0.5" step="0.1" value="2" required style="width: 100%; padding: 0.5rem; border: 1px solid #ddd; border-radius: 4px;">
</div>
<div>
<label for="height" style="display: block; font-weight: 600; margin-bottom: 0.5rem;">Height (meters):</label>
<input type="number" id="height" name="height" min="0.5" step="0.1" value="2.7" required style="width: 100%; padding: 0.5rem; border: 1px solid #ddd; border-radius: 4px;">
</div>
</div>
@endif
<!-- Total Price Display -->
<div style="background: var(--accent-light); padding: 1.5rem; border-radius: 20px; margin-bottom: 2rem;">
<p style="margin: 0 0 0.5rem 0; color: black;">Estimated Total:</p>
<div style="font-family: Abril fatface;font-size: 3rem; font-weight: 400; color: white;">R<span id="totalPrice">{{ number_format($product->price, 2) }}</span></div>
<small style="color: #fff; display: block; margin-top: 0.5rem;" id="priceBreakdown"></small>
</div>
<div class="btn-group" style="gap: 1rem;">
<button type="submit" class="btn" style="flex: 1;">Add to Cart</button>
<a href="{{ route('cart') }}" class="btn btn--secondary" style="flex: 1; text-align: center;">View Cart</a>
</div>
</form>
<script>
const productType = 'wallpaper' === 'wallpaper' ? 'wallpaper' : 'mural';
const basePrice = {{ $product->price }};
const isWallpaper = {{ $product->type === 'wallpaper' ? 'true' : 'false' }};
function updatePrice() {
const stockSelect = document.getElementById('print_stock_id');
const selectedOption = stockSelect.options[stockSelect.selectedIndex];
const stockCost = parseFloat(selectedOption.dataset.cost) || 0;
let totalCost = stockCost;
let breakdown = '';
if (isWallpaper) {
const length = parseFloat(document.getElementById('length').value) || 0;
totalCost = stockCost * length;
breakdown = `Stock: R${stockCost.toFixed(2)}/m × ${length}m`;
} else {
const width = parseFloat(document.getElementById('width').value) || 0;
const height = parseFloat(document.getElementById('height').value) || 0;
const m2 = width * height;
totalCost = stockCost * m2;
breakdown = `Stock: R${stockCost.toFixed(2)}/m² × ${m2.toFixed(2)}m²`;
}
document.getElementById('totalPrice').textContent = totalCost.toFixed(2);
document.getElementById('priceBreakdown').textContent = breakdown;
}
document.getElementById('print_stock_id').addEventListener('change', updatePrice);
if (isWallpaper) {
document.getElementById('length').addEventListener('input', updatePrice);
} else {
document.getElementById('width').addEventListener('input', updatePrice);
document.getElementById('height').addEventListener('input', updatePrice);
}
updatePrice();
</script>
@else
<button class="btn" disabled style="background-color: #ccc; cursor: not-allowed;">Out of Stock</button>
@endif
</div>
</section>
<!-- Calculator Modal -->
<div id="calculatorModal" class="calculator-modal">
<div class="calculator-content">
<span class="calculator-close" onclick="closeCalculator()">&times;</span>
<h2 style="color: var(--primary-color); margin-bottom: 1rem; font-family: 'Abril Fatface', cursive;">Wallpaper Calculator</h2>
<div class="wall-schematic">
<p style="margin-bottom: 1rem; font-size: 0.9rem; color: #666;">Enter your wall dimensions:</p>
<div class="wall-diagram">
<span class="wall-label width">Width</span>
<span class="wall-label height">Height</span>
</div>
</div>
<div class="calc-input-group">
<label for="calc-wall-width">Wall Width (meters):</label>
<input type="number" id="calc-wall-width" min="0.1" step="0.1" value="4" oninput="calculateWallpaper()">
</div>
<div class="calc-input-group">
<label for="calc-wall-height">Wall Height (meters):</label>
<input type="number" id="calc-wall-height" min="0.1" step="0.1" value="2.7" oninput="calculateWallpaper()">
</div>
<div class="calc-input-group">
<label for="calc-wallpaper-width">Wallpaper Width (meters):</label>
<div style="padding: 0.75rem; background: var(--section-light-bg); border-radius: 6px; font-size: 1rem; font-weight: 600; color: var(--primary-color);">
<span id="calc-wallpaper-width">1</span>m
</div>
<small style="color: #666; display: block; margin-top: 0.25rem;">Width from selected print stock</small>
</div>
<div class="calc-result" id="calcResult" style="display: none;">
<h3>Required Length:</h3>
<div class="result-value"><span id="calcResultValue">0</span>m</div>
<small id="calcBreakdown"></small>
</div>
<button class="btn" onclick="useCalculatedLength()" style="width: 100%; margin-top: 1rem;">
Use This Length
</button>
</div>
</div>
<!-- Lightbox Modal -->
<div id="lightbox" class="lightbox">
<div class="lightbox-content">
<span class="lightbox-close" onclick="closeLightbox()">&times;</span>
<span class="lightbox-nav lightbox-prev" onclick="prevImage()">&lsaquo;</span>
<img id="lightboxImage" class="lightbox-image" style="border-radius: 20px;" src="" alt="">
<span class="lightbox-nav lightbox-next" onclick="nextImage()">&rsaquo;</span>
<div class="lightbox-counter">
<span id="imageCounter">1</span> / <span id="totalImages">1</span>
</div>
</div>
</div>
<script>
let currentImageIndex = 0;
let allImages = [];
function initLightboxImages() {
@if($product->images->count() > 0)
allImages = [
@foreach($product->images as $image)
'{{ asset('storage/' . $image->image_path) }}',
@endforeach
];
@else
allImages = ['{{ $product->image }}'];
@endif
document.getElementById('totalImages').textContent = allImages.length;
}
function openLightbox(imageSrc) {
currentImageIndex = allImages.indexOf(imageSrc);
if (currentImageIndex === -1) {
currentImageIndex = 0;
}
updateLightboxImage();
document.getElementById('lightbox').classList.add('active');
document.body.style.overflow = 'hidden';
}
function closeLightbox() {
document.getElementById('lightbox').classList.remove('active');
document.body.style.overflow = 'auto';
}
function nextImage() {
currentImageIndex = (currentImageIndex + 1) % allImages.length;
updateLightboxImage();
}
function prevImage() {
currentImageIndex = (currentImageIndex - 1 + allImages.length) % allImages.length;
updateLightboxImage();
}
function updateLightboxImage() {
document.getElementById('lightboxImage').src = allImages[currentImageIndex];
document.getElementById('imageCounter').textContent = currentImageIndex + 1;
}
// Close lightbox when clicking outside the image
document.getElementById('lightbox').addEventListener('click', function(event) {
if (event.target === this) {
closeLightbox();
}
});
// Close lightbox with Escape key
document.addEventListener('keydown', function(event) {
if (event.key === 'Escape') {
closeLightbox();
}
// Navigate with arrow keys
if (document.getElementById('lightbox').classList.contains('active')) {
if (event.key === 'ArrowRight') {
nextImage();
}
if (event.key === 'ArrowLeft') {
prevImage();
}
}
});
// Add click handler to main image
document.getElementById('mainImage').addEventListener('click', function() {
openLightbox(this.src);
});
// Initialize lightbox images on page load
initLightboxImages();
// Calculator functions
function openCalculator() {
// Update calculator width from selected print stock
const stockSelect = document.getElementById('print_stock_id');
const selectedOption = stockSelect.options[stockSelect.selectedIndex];
const stockWidth = parseFloat(selectedOption.dataset.width) || 1;
document.getElementById('calc-wallpaper-width').textContent = stockWidth;
document.getElementById('calculatorModal').classList.add('active');
document.body.style.overflow = 'hidden';
calculateWallpaper();
}
function closeCalculator() {
document.getElementById('calculatorModal').classList.remove('active');
document.body.style.overflow = 'auto';
}
function calculateWallpaper() {
const wallWidth = parseFloat(document.getElementById('calc-wall-width').value) || 0;
const wallHeight = parseFloat(document.getElementById('calc-wall-height').value) || 0;
const wallpaperWidth = parseFloat(document.getElementById('calc-wallpaper-width').textContent) || 1;
if (wallWidth > 0 && wallHeight > 0 && wallpaperWidth > 0) {
// Calculate number of strips needed
const stripsNeeded = Math.ceil(wallWidth / wallpaperWidth);
// Calculate total length needed (strips × wall height)
const totalLength = stripsNeeded * wallHeight;
// Add 10% for pattern matching and waste
const withWaste = totalLength * 1.1;
const finalLength = Math.ceil(withWaste * 2) / 2; // Round up to nearest 0.5m
// Display results
document.getElementById('calcResultValue').textContent = finalLength.toFixed(1);
document.getElementById('calcBreakdown').innerHTML =
`${stripsNeeded} strips × ${wallHeight}m height = ${totalLength.toFixed(1)}m<br>` +
`+ 10% waste allowance = ${finalLength.toFixed(1)}m total`;
document.getElementById('calcResult').style.display = 'block';
}
}
function useCalculatedLength() {
const calculatedLength = parseFloat(document.getElementById('calcResultValue').textContent);
document.getElementById('length').value = calculatedLength;
closeCalculator();
updatePrice();
}
// Close calculator when clicking outside
document.getElementById('calculatorModal').addEventListener('click', function(event) {
if (event.target === this) {
closeCalculator();
}
});
</script>
<!-- Related Products -->
@if($product->category->products->count() > 1)
<section class="related-products" style="margin-bottom: 20px;">
<h2>More from {{ $product->category->name }}</h2>
<div class="grid grid--3">
@foreach($product->category->products->where('id', '!=', $product->id)->take(3) as $relatedProduct)
@include('components.product-card', ['product' => $relatedProduct])
@endforeach
</div>
</section>
@endif
</main>
@endsection