feat: enhance designer canvas with multi-view support and model viewer integration
Some checks failed
Deploy Production / deploy (push) Failing after 1m11s
Some checks failed
Deploy Production / deploy (push) Failing after 1m11s
- Added canvasId prop to DesignerCanvas for identifying multiple canvases. - Implemented active view selection (front, top, left, right) in designer page. - Updated DesignerCanvas to maintain aspect ratio and dimensions based on view. - Integrated @google/model-viewer for 3D model rendering on the index page. - Refactored useSlipmatDesigner to manage multiple canvases and their states. - Added LAMESA.glb model file for 3D representation. - Updated package.json and package-lock.json to include @google/model-viewer dependency.
This commit is contained in:
@@ -4,12 +4,14 @@ import { onBeforeUnmount, onMounted, ref, watch } from "vue";
|
||||
import type { Canvas as FabricCanvas } from "fabric";
|
||||
|
||||
const props = defineProps<{
|
||||
canvasId?: string;
|
||||
size: number;
|
||||
registerCanvas: (payload: {
|
||||
canvas: FabricCanvas;
|
||||
fabric: typeof import("fabric");
|
||||
canvasId?: string;
|
||||
}) => void;
|
||||
unregisterCanvas: () => void;
|
||||
unregisterCanvas: (canvasId?: string) => void;
|
||||
backgroundColor: string;
|
||||
}>();
|
||||
|
||||
@@ -30,28 +32,28 @@ const syncCanvasDomStyles = () => {
|
||||
if (!element) {
|
||||
return;
|
||||
}
|
||||
element.classList.add("rounded-full");
|
||||
element.style.borderRadius = "9999px";
|
||||
element.classList.add("rounded-lg");
|
||||
element.style.borderRadius = "8px";
|
||||
element.style.backgroundColor = "transparent";
|
||||
});
|
||||
};
|
||||
|
||||
const updateCssDimensions = (dimension?: number) => {
|
||||
const updateCssDimensions = (containerWidth?: number) => {
|
||||
if (!fabricCanvas) {
|
||||
return;
|
||||
}
|
||||
const targetSize =
|
||||
dimension ??
|
||||
const targetWidth =
|
||||
containerWidth ??
|
||||
containerElement.value?.clientWidth ??
|
||||
containerElement.value?.clientHeight ??
|
||||
null;
|
||||
if (!targetSize) {
|
||||
if (!targetWidth) {
|
||||
return;
|
||||
}
|
||||
const targetHeight = Math.round(targetWidth * 0.67); // 3:2 aspect ratio
|
||||
fabricCanvas.setDimensions(
|
||||
{
|
||||
width: targetSize,
|
||||
height: targetSize,
|
||||
width: targetWidth,
|
||||
height: targetHeight,
|
||||
},
|
||||
{ cssOnly: true }
|
||||
);
|
||||
@@ -70,8 +72,8 @@ const observeContainer = () => {
|
||||
if (!entry) {
|
||||
return;
|
||||
}
|
||||
const dimension = Math.min(entry.contentRect.width, entry.contentRect.height);
|
||||
updateCssDimensions(dimension);
|
||||
const containerWidth = entry.contentRect.width;
|
||||
updateCssDimensions(containerWidth);
|
||||
});
|
||||
resizeObserver.observe(containerElement.value);
|
||||
updateCssDimensions();
|
||||
@@ -91,9 +93,14 @@ const setupCanvas = async () => {
|
||||
preserveObjectStacking: true,
|
||||
});
|
||||
|
||||
fabricCanvas.setDimensions({ width: props.size, height: props.size });
|
||||
const canvasHeight = Math.round(props.size * 0.67); // 3:2 aspect ratio
|
||||
fabricCanvas.setDimensions({ width: props.size, height: canvasHeight });
|
||||
|
||||
props.registerCanvas({ canvas: fabricCanvas, fabric: fabricModule });
|
||||
props.registerCanvas({
|
||||
canvas: fabricCanvas,
|
||||
fabric: fabricModule,
|
||||
canvasId: props.canvasId
|
||||
});
|
||||
observeContainer();
|
||||
syncCanvasDomStyles();
|
||||
isReady.value = true;
|
||||
@@ -104,7 +111,7 @@ onMounted(() => {
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
props.unregisterCanvas();
|
||||
props.unregisterCanvas(props.canvasId);
|
||||
resizeObserver?.disconnect();
|
||||
resizeObserver = null;
|
||||
fabricCanvas = null;
|
||||
@@ -118,7 +125,8 @@ watch(
|
||||
if (!isReady.value || next === prev || !fabricCanvas) {
|
||||
return;
|
||||
}
|
||||
fabricCanvas.setDimensions({ width: next, height: next });
|
||||
const canvasHeight = Math.round(next * 0.67); // 3:2 aspect ratio
|
||||
fabricCanvas.setDimensions({ width: next, height: canvasHeight });
|
||||
updateCssDimensions();
|
||||
fabricCanvas.renderAll();
|
||||
}
|
||||
@@ -127,20 +135,20 @@ watch(
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="relative mx-auto flex max-w-[min(720px,100%)] items-center justify-center overflow-hidden rounded-3xl border border-slate-700/60 bg-slate-900/80 p-6 shadow-xl shadow-slate-950/40"
|
||||
class="relative mx-auto flex max-w-[min(900px,100%)] items-center justify-center overflow-hidden rounded-3xl border border-slate-700/60 bg-slate-900/80 p-6 shadow-xl shadow-slate-950/40"
|
||||
>
|
||||
<div class="relative aspect-square w-full">
|
||||
<div class="relative aspect-[3/2] w-full">
|
||||
<div class="absolute inset-4 sm:inset-5 md:inset-6 lg:inset-8">
|
||||
<div ref="containerElement" class="relative h-full w-full">
|
||||
<canvas
|
||||
ref="canvasElement"
|
||||
class="absolute inset-0 h-full w-full rounded-full"
|
||||
class="absolute inset-0 h-full w-full rounded-lg"
|
||||
:width="size"
|
||||
:height="size"
|
||||
/>
|
||||
<div
|
||||
v-if="!isReady"
|
||||
class="absolute inset-0 grid place-items-center rounded-full bg-slate-900/70"
|
||||
class="absolute inset-0 grid place-items-center rounded-lg bg-slate-900/70"
|
||||
>
|
||||
<span class="text-sm font-medium text-slate-400">Loading canvas…</span>
|
||||
</div>
|
||||
|
||||
@@ -18,6 +18,7 @@ const {
|
||||
previewUrl,
|
||||
registerCanvas,
|
||||
unregisterCanvas,
|
||||
setActiveCanvas,
|
||||
addTextbox,
|
||||
addShape,
|
||||
addImageFromFile,
|
||||
@@ -41,6 +42,14 @@ const {
|
||||
resetZoom,
|
||||
} = useSlipmatDesigner();
|
||||
|
||||
// Active view selector for multi-canvas design
|
||||
const activeView = ref<'front' | 'top' | 'left' | 'right'>('front');
|
||||
|
||||
const setActiveView = (view: 'front' | 'top' | 'left' | 'right') => {
|
||||
activeView.value = view;
|
||||
setActiveCanvas(view);
|
||||
};
|
||||
|
||||
const DESIGN_PRICE_USD = 39.99;
|
||||
|
||||
const { user, backendUser, initAuth, isLoading } = useAuth();
|
||||
@@ -376,13 +385,13 @@ const handleCheckout = async () => {
|
||||
<section class="mt-10 flex flex-col gap-8 lg:grid lg:grid-cols-[320px_minmax(0,1fr)] lg:gap-6">
|
||||
<!-- Left Sidebar - Template Picker and Preview (together on desktop, separate on mobile) -->
|
||||
<div class="contents lg:block lg:space-y-6">
|
||||
<div class="order-1">
|
||||
<!-- <div class="order-1">
|
||||
<TemplatePicker
|
||||
:templates="templates"
|
||||
:selected-template-id="selectedTemplate.id"
|
||||
@select="handleTemplateSelect"
|
||||
/>
|
||||
</div>
|
||||
</div> -->
|
||||
|
||||
<div class="order-3">
|
||||
<DesignerPreview
|
||||
@@ -396,6 +405,23 @@ const handleCheckout = async () => {
|
||||
|
||||
<!-- Designer Canvas - Second on mobile, right column on desktop -->
|
||||
<div class="order-2 flex flex-col gap-6 lg:order-0">
|
||||
<!-- View Selector Tabs -->
|
||||
<div class="flex gap-2 rounded-lg border border-slate-200 bg-slate-50 p-2">
|
||||
<button
|
||||
v-for="view in ['front', 'top', 'left', 'right']"
|
||||
:key="view"
|
||||
@click="setActiveView(view as 'front' | 'top' | 'left' | 'right')"
|
||||
:class="[
|
||||
'flex-1 rounded-md px-4 py-2 text-sm font-semibold capitalize transition-all',
|
||||
activeView === view
|
||||
? 'bg-white text-slate-900 shadow-sm'
|
||||
: 'text-slate-600 hover:text-slate-900'
|
||||
]"
|
||||
>
|
||||
{{ view }} View
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="rounded-3xl border border-slate-200 bg-white shadow-xl"
|
||||
>
|
||||
@@ -420,17 +446,55 @@ const handleCheckout = async () => {
|
||||
:on-zoom-out="zoomOut"
|
||||
:on-zoom-reset="resetZoom"
|
||||
/>
|
||||
<div class="p-6">
|
||||
<DesignerCanvas
|
||||
:size="displaySize"
|
||||
:background-color="selectedTemplate.backgroundColor"
|
||||
:register-canvas="registerCanvas"
|
||||
:unregister-canvas="unregisterCanvas"
|
||||
/>
|
||||
<div class="p-6 space-y-4">
|
||||
<!-- Canvas for each view -->
|
||||
<div v-show="activeView === 'front'" class="canvas-container">
|
||||
<h3 class="mb-2 text-sm font-semibold text-slate-700">Front View</h3>
|
||||
<DesignerCanvas
|
||||
canvas-id="front"
|
||||
:size="displaySize"
|
||||
:background-color="selectedTemplate.backgroundColor"
|
||||
:register-canvas="registerCanvas"
|
||||
:unregister-canvas="unregisterCanvas"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-show="activeView === 'top'" class="canvas-container">
|
||||
<h3 class="mb-2 text-sm font-semibold text-slate-700">Top View</h3>
|
||||
<DesignerCanvas
|
||||
canvas-id="top"
|
||||
:size="displaySize"
|
||||
:background-color="selectedTemplate.backgroundColor"
|
||||
:register-canvas="registerCanvas"
|
||||
:unregister-canvas="unregisterCanvas"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-show="activeView === 'left'" class="canvas-container">
|
||||
<h3 class="mb-2 text-sm font-semibold text-slate-700">Left View</h3>
|
||||
<DesignerCanvas
|
||||
canvas-id="left"
|
||||
:size="displaySize"
|
||||
:background-color="selectedTemplate.backgroundColor"
|
||||
:register-canvas="registerCanvas"
|
||||
:unregister-canvas="unregisterCanvas"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-show="activeView === 'right'" class="canvas-container">
|
||||
<h3 class="mb-2 text-sm font-semibold text-slate-700">Right View</h3>
|
||||
<DesignerCanvas
|
||||
canvas-id="right"
|
||||
:size="displaySize"
|
||||
:background-color="selectedTemplate.backgroundColor"
|
||||
:register-canvas="registerCanvas"
|
||||
:unregister-canvas="unregisterCanvas"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<p class="mt-4 text-sm text-slate-600">
|
||||
Safe zone and bleed guides update automatically when you switch
|
||||
templates. Use the toolbar to layer text, shapes, and imagery
|
||||
inside the design area.
|
||||
Design each view of your table jersey separately. Switch between views using the tabs above.
|
||||
Safe zone and bleed guides update automatically when you switch templates.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,58 +1,115 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted } from "vue";
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const startDesigning = () => {
|
||||
router.push('/designer');
|
||||
router.push("/designer");
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
// Import model-viewer on client-side only
|
||||
if (process.client) {
|
||||
import("@google/model-viewer");
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="relative min-h-screen overflow-hidden bg-white">
|
||||
<!-- Subtle Background Pattern -->
|
||||
<div class="absolute inset-0 opacity-5">
|
||||
<div class="absolute inset-0" style="background-image: radial-gradient(circle at 1px 1px, rgb(156 163 175) 1px, transparent 0); background-size: 40px 40px;"></div>
|
||||
<div
|
||||
class="absolute inset-0"
|
||||
style="
|
||||
background-image: radial-gradient(
|
||||
circle at 1px 1px,
|
||||
rgb(156 163 175) 1px,
|
||||
transparent 0
|
||||
);
|
||||
background-size: 40px 40px;
|
||||
"
|
||||
></div>
|
||||
</div>
|
||||
|
||||
<!-- Main Content Grid -->
|
||||
<div class="relative z-20 flex min-h-screen flex-col px-4 py-12 md:px-8 lg:px-16">
|
||||
<div
|
||||
class="relative z-20 flex min-h-screen flex-col px-4 py-12 md:px-8 lg:px-16"
|
||||
>
|
||||
<!-- Top Section - Split Layout -->
|
||||
<div class="flex flex-1 flex-col items-center justify-between gap-8 lg:flex-row lg:gap-16">
|
||||
<div
|
||||
class="flex flex-1 flex-col items-center justify-between gap-8 lg:flex-row lg:gap-16"
|
||||
>
|
||||
<!-- Left Side - Title & Description -->
|
||||
<div class="w-full space-y-8 text-center lg:w-1/2 lg:text-left">
|
||||
<h1 class="text-6xl font-bold tracking-tight text-slate-900 sm:text-7xl md:text-8xl">
|
||||
<h1
|
||||
class="text-6xl font-bold tracking-tight text-slate-900 sm:text-7xl md:text-8xl"
|
||||
>
|
||||
TableJerseys
|
||||
</h1>
|
||||
<div class="space-y-4">
|
||||
<p class="text-2xl font-semibold text-slate-700 sm:text-3xl md:text-4xl">
|
||||
<p
|
||||
class="text-2xl font-semibold text-slate-700 sm:text-3xl md:text-4xl"
|
||||
>
|
||||
Design custom jerseys for your table
|
||||
</p>
|
||||
|
||||
<!-- Simple preview for mobile view -->
|
||||
<div class="relative mx-auto my-8 block lg:hidden">
|
||||
<div class="relative mx-auto h-[280px] w-[280px] sm:h-80 sm:w-80">
|
||||
<div class="flex h-full w-full items-center justify-center rounded-2xl border-2 border-slate-200 bg-gradient-to-br from-slate-50 to-slate-100 shadow-lg overflow-hidden">
|
||||
<div
|
||||
class="flex h-full w-full items-center justify-center rounded-2xl border-2 border-slate-200 bg-gradient-to-br from-slate-50 to-slate-100 shadow-lg overflow-hidden"
|
||||
>
|
||||
<!-- Rotating Table with Fitted Cover -->
|
||||
<div class="animate-spin-slow relative" style="transform-style: preserve-3d;">
|
||||
<div
|
||||
class="animate-spin-slow relative"
|
||||
style="transform-style: preserve-3d"
|
||||
>
|
||||
<div class="relative">
|
||||
<!-- Table with fitted cover -->
|
||||
<div class="relative">
|
||||
<!-- Table Top -->
|
||||
<div class="relative h-24 w-40 rounded-t-sm bg-gradient-to-br from-slate-800 via-slate-700 to-slate-900 shadow-xl">
|
||||
<div
|
||||
class="relative h-24 w-40 rounded-t-sm bg-gradient-to-br from-slate-800 via-slate-700 to-slate-900 shadow-xl"
|
||||
>
|
||||
<!-- Jersey design on top of cover -->
|
||||
<div class="absolute inset-4 flex items-center justify-center rounded-lg bg-gradient-to-br from-blue-400 via-white to-red-400 shadow-lg border-2 border-slate-300">
|
||||
<div
|
||||
class="absolute inset-4 flex items-center justify-center rounded-lg bg-gradient-to-br from-blue-400 via-white to-red-400 shadow-lg border-2 border-slate-300"
|
||||
>
|
||||
<div class="text-center">
|
||||
<div class="text-2xl font-bold text-slate-800">23</div>
|
||||
<div class="text-[7px] font-semibold text-slate-600 tracking-wider">CUSTOM</div>
|
||||
<div class="text-2xl font-bold text-slate-800">
|
||||
23
|
||||
</div>
|
||||
<div
|
||||
class="text-[7px] font-semibold text-slate-600 tracking-wider"
|
||||
>
|
||||
CUSTOM
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Fitted Cover Skirt (draping down) -->
|
||||
<div class="absolute top-24 left-0 right-0 h-16 bg-gradient-to-b from-slate-800 via-slate-900 to-slate-950 rounded-b-sm shadow-2xl">
|
||||
<div
|
||||
class="absolute top-24 left-0 right-0 h-16 bg-gradient-to-b from-slate-800 via-slate-900 to-slate-950 rounded-b-sm shadow-2xl"
|
||||
>
|
||||
<!-- Fabric folds/creases -->
|
||||
<div class="absolute inset-0 opacity-30" style="background: repeating-linear-gradient(90deg, transparent, transparent 8px, rgba(0,0,0,0.3) 8px, rgba(0,0,0,0.3) 9px);"></div>
|
||||
<div
|
||||
class="absolute inset-0 opacity-30"
|
||||
style="
|
||||
background: repeating-linear-gradient(
|
||||
90deg,
|
||||
transparent,
|
||||
transparent 8px,
|
||||
rgba(0, 0, 0, 0.3) 8px,
|
||||
rgba(0, 0, 0, 0.3) 9px
|
||||
);
|
||||
"
|
||||
></div>
|
||||
<!-- Shadow at bottom -->
|
||||
<div class="absolute bottom-0 left-0 right-0 h-2 bg-black/40"></div>
|
||||
<div
|
||||
class="absolute bottom-0 left-0 right-0 h-2 bg-black/40"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -75,69 +132,67 @@ const startDesigning = () => {
|
||||
>
|
||||
<span class="relative z-10 flex items-center gap-3">
|
||||
Start Designing
|
||||
<svg class="h-6 w-6 transition-transform group-hover:translate-x-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 7l5 5m0 0l-5 5m5-5H6" />
|
||||
<svg
|
||||
class="h-6 w-6 transition-transform group-hover:translate-x-1"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M13 7l5 5m0 0l-5 5m5-5H6"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</NuxtLink>
|
||||
</div>
|
||||
|
||||
<!-- Stats Section - Now on Left Side -->
|
||||
<div class="mx-auto grid max-w-4xl gap-8 pt-12 grid-cols-3 sm:grid-cols-3 lg:mx-0">
|
||||
<div
|
||||
class="mx-auto grid max-w-4xl gap-8 pt-12 grid-cols-3 sm:grid-cols-3 lg:mx-0"
|
||||
>
|
||||
<div class="space-y-2 text-center lg:text-left">
|
||||
<div class="text-3xl font-bold text-slate-900 md:text-4xl">Custom</div>
|
||||
<div class="text-3xl font-bold text-slate-900 md:text-4xl">
|
||||
Custom
|
||||
</div>
|
||||
<div class="text-sm text-slate-600">Any Size</div>
|
||||
</div>
|
||||
<div class="space-y-2 text-center lg:text-left">
|
||||
<div class="text-3xl font-bold text-slate-900 md:text-4xl">300 DPI</div>
|
||||
<div class="text-3xl font-bold text-slate-900 md:text-4xl">
|
||||
300 DPI
|
||||
</div>
|
||||
<div class="text-sm text-slate-600">Print Quality</div>
|
||||
</div>
|
||||
<div class="space-y-2 text-center lg:text-left">
|
||||
<div class="text-3xl font-bold text-slate-900 md:text-4xl">$39.99</div>
|
||||
<div class="text-3xl font-bold text-slate-900 md:text-4xl">
|
||||
$39.99
|
||||
</div>
|
||||
<div class="text-sm text-slate-600">Per Design</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Right Side - Simple Image -->
|
||||
<!-- Right Side - 3D Model -->
|
||||
<div class="relative hidden w-full lg:block lg:w-1/2">
|
||||
<div class="relative mx-auto max-w-4xl">
|
||||
<div class="relative mx-auto h-[300px] w-[300px] sm:h-[600px] sm:w-[600px]">
|
||||
<div class="flex h-full w-full items-center justify-center rounded-2xl border-2 border-slate-200 bg-gradient-to-br from-slate-50 to-slate-100 shadow-xl overflow-hidden">
|
||||
<!-- Rotating Table with Fitted Cover -->
|
||||
<div class=" relative" style="transform-style: preserve-3d;">
|
||||
<div class="relative">
|
||||
<!-- Table with fitted cover -->
|
||||
<div class="relative">
|
||||
<!-- Table Top with jersey on cover -->
|
||||
<div class="relative h-48 w-96 sm:h-64 sm:w-[500px] rounded-t-lg bg-gradient-to-br from-slate-800 via-slate-700 to-slate-900 shadow-2xl">
|
||||
<!-- Jersey design on top of cover -->
|
||||
<div class="absolute inset-8 sm:inset-12 flex items-center justify-center rounded-xl bg-gradient-to-br from-blue-400 via-white to-red-400 shadow-2xl border-4 border-slate-300">
|
||||
<div class="absolute inset-4 sm:inset-6 flex items-center justify-center rounded-lg bg-gradient-to-br from-blue-100 via-white to-red-100 border-2 border-slate-200">
|
||||
<div class="text-center">
|
||||
<div class="text-5xl sm:text-7xl font-bold text-slate-800">23</div>
|
||||
<div class="text-sm sm:text-xl font-semibold text-slate-600 tracking-wider">CUSTOM</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Subtle highlights on cover top -->
|
||||
<div class="absolute top-4 left-8 h-6 w-12 rounded-full bg-white/10 blur-lg"></div>
|
||||
</div>
|
||||
|
||||
<!-- Fitted Cover Skirt (draping down like trade show table) -->
|
||||
<div class="absolute top-48 sm:top-64 left-0 right-0 h-32 sm:h-40 bg-gradient-to-b from-slate-800 via-slate-900 to-slate-950 rounded-b-lg shadow-2xl">
|
||||
<!-- Fabric folds/pleats -->
|
||||
<div class="absolute inset-0 opacity-40" style="background: repeating-linear-gradient(90deg, transparent, transparent 16px, rgba(0,0,0,0.4) 16px, rgba(0,0,0,0.4) 18px);"></div>
|
||||
<!-- More realistic vertical folds -->
|
||||
<div class="absolute inset-0 opacity-20" style="background: repeating-linear-gradient(90deg, rgba(255,255,255,0.05) 0px, transparent 2px, transparent 14px, rgba(0,0,0,0.3) 16px);"></div>
|
||||
<!-- Shadow at bottom edge -->
|
||||
<div class="absolute bottom-0 left-0 right-0 h-4 bg-black/50 rounded-b-lg"></div>
|
||||
<!-- Ground shadow -->
|
||||
<div class="absolute -bottom-2 left-4 right-4 h-2 bg-black/30 blur-sm rounded-full"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="relative mx-auto h-[300px] w-[300px] sm:h-[600px] sm:w-[600px]"
|
||||
>
|
||||
<div
|
||||
class="flex h-full w-full items-center justify-center rounded-2xl border-2 border-slate-300 bg-linear-to-br from-slate-700 to-slate-900 shadow-xl overflow-hidden"
|
||||
>
|
||||
<model-viewer
|
||||
src="/LAMESA.glb"
|
||||
alt="Table with Jersey"
|
||||
auto-rotate
|
||||
rotation-per-second="15deg"
|
||||
camera-controls
|
||||
shadow-intensity="1"
|
||||
class="w-full h-full"
|
||||
style="--poster-color: transparent"
|
||||
></model-viewer>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user