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";
|
import type { Canvas as FabricCanvas } from "fabric";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
|
canvasId?: string;
|
||||||
size: number;
|
size: number;
|
||||||
registerCanvas: (payload: {
|
registerCanvas: (payload: {
|
||||||
canvas: FabricCanvas;
|
canvas: FabricCanvas;
|
||||||
fabric: typeof import("fabric");
|
fabric: typeof import("fabric");
|
||||||
|
canvasId?: string;
|
||||||
}) => void;
|
}) => void;
|
||||||
unregisterCanvas: () => void;
|
unregisterCanvas: (canvasId?: string) => void;
|
||||||
backgroundColor: string;
|
backgroundColor: string;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
@@ -30,28 +32,28 @@ const syncCanvasDomStyles = () => {
|
|||||||
if (!element) {
|
if (!element) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
element.classList.add("rounded-full");
|
element.classList.add("rounded-lg");
|
||||||
element.style.borderRadius = "9999px";
|
element.style.borderRadius = "8px";
|
||||||
element.style.backgroundColor = "transparent";
|
element.style.backgroundColor = "transparent";
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateCssDimensions = (dimension?: number) => {
|
const updateCssDimensions = (containerWidth?: number) => {
|
||||||
if (!fabricCanvas) {
|
if (!fabricCanvas) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const targetSize =
|
const targetWidth =
|
||||||
dimension ??
|
containerWidth ??
|
||||||
containerElement.value?.clientWidth ??
|
containerElement.value?.clientWidth ??
|
||||||
containerElement.value?.clientHeight ??
|
|
||||||
null;
|
null;
|
||||||
if (!targetSize) {
|
if (!targetWidth) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const targetHeight = Math.round(targetWidth * 0.67); // 3:2 aspect ratio
|
||||||
fabricCanvas.setDimensions(
|
fabricCanvas.setDimensions(
|
||||||
{
|
{
|
||||||
width: targetSize,
|
width: targetWidth,
|
||||||
height: targetSize,
|
height: targetHeight,
|
||||||
},
|
},
|
||||||
{ cssOnly: true }
|
{ cssOnly: true }
|
||||||
);
|
);
|
||||||
@@ -70,8 +72,8 @@ const observeContainer = () => {
|
|||||||
if (!entry) {
|
if (!entry) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const dimension = Math.min(entry.contentRect.width, entry.contentRect.height);
|
const containerWidth = entry.contentRect.width;
|
||||||
updateCssDimensions(dimension);
|
updateCssDimensions(containerWidth);
|
||||||
});
|
});
|
||||||
resizeObserver.observe(containerElement.value);
|
resizeObserver.observe(containerElement.value);
|
||||||
updateCssDimensions();
|
updateCssDimensions();
|
||||||
@@ -91,9 +93,14 @@ const setupCanvas = async () => {
|
|||||||
preserveObjectStacking: true,
|
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();
|
observeContainer();
|
||||||
syncCanvasDomStyles();
|
syncCanvasDomStyles();
|
||||||
isReady.value = true;
|
isReady.value = true;
|
||||||
@@ -104,7 +111,7 @@ onMounted(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
props.unregisterCanvas();
|
props.unregisterCanvas(props.canvasId);
|
||||||
resizeObserver?.disconnect();
|
resizeObserver?.disconnect();
|
||||||
resizeObserver = null;
|
resizeObserver = null;
|
||||||
fabricCanvas = null;
|
fabricCanvas = null;
|
||||||
@@ -118,7 +125,8 @@ watch(
|
|||||||
if (!isReady.value || next === prev || !fabricCanvas) {
|
if (!isReady.value || next === prev || !fabricCanvas) {
|
||||||
return;
|
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();
|
updateCssDimensions();
|
||||||
fabricCanvas.renderAll();
|
fabricCanvas.renderAll();
|
||||||
}
|
}
|
||||||
@@ -127,20 +135,20 @@ watch(
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div
|
<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 class="absolute inset-4 sm:inset-5 md:inset-6 lg:inset-8">
|
||||||
<div ref="containerElement" class="relative h-full w-full">
|
<div ref="containerElement" class="relative h-full w-full">
|
||||||
<canvas
|
<canvas
|
||||||
ref="canvasElement"
|
ref="canvasElement"
|
||||||
class="absolute inset-0 h-full w-full rounded-full"
|
class="absolute inset-0 h-full w-full rounded-lg"
|
||||||
:width="size"
|
:width="size"
|
||||||
:height="size"
|
:height="size"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
v-if="!isReady"
|
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>
|
<span class="text-sm font-medium text-slate-400">Loading canvas…</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ const {
|
|||||||
previewUrl,
|
previewUrl,
|
||||||
registerCanvas,
|
registerCanvas,
|
||||||
unregisterCanvas,
|
unregisterCanvas,
|
||||||
|
setActiveCanvas,
|
||||||
addTextbox,
|
addTextbox,
|
||||||
addShape,
|
addShape,
|
||||||
addImageFromFile,
|
addImageFromFile,
|
||||||
@@ -41,6 +42,14 @@ const {
|
|||||||
resetZoom,
|
resetZoom,
|
||||||
} = useSlipmatDesigner();
|
} = 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 DESIGN_PRICE_USD = 39.99;
|
||||||
|
|
||||||
const { user, backendUser, initAuth, isLoading } = useAuth();
|
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">
|
<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) -->
|
<!-- Left Sidebar - Template Picker and Preview (together on desktop, separate on mobile) -->
|
||||||
<div class="contents lg:block lg:space-y-6">
|
<div class="contents lg:block lg:space-y-6">
|
||||||
<div class="order-1">
|
<!-- <div class="order-1">
|
||||||
<TemplatePicker
|
<TemplatePicker
|
||||||
:templates="templates"
|
:templates="templates"
|
||||||
:selected-template-id="selectedTemplate.id"
|
:selected-template-id="selectedTemplate.id"
|
||||||
@select="handleTemplateSelect"
|
@select="handleTemplateSelect"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div> -->
|
||||||
|
|
||||||
<div class="order-3">
|
<div class="order-3">
|
||||||
<DesignerPreview
|
<DesignerPreview
|
||||||
@@ -396,6 +405,23 @@ const handleCheckout = async () => {
|
|||||||
|
|
||||||
<!-- Designer Canvas - Second on mobile, right column on desktop -->
|
<!-- Designer Canvas - Second on mobile, right column on desktop -->
|
||||||
<div class="order-2 flex flex-col gap-6 lg:order-0">
|
<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
|
<div
|
||||||
class="rounded-3xl border border-slate-200 bg-white shadow-xl"
|
class="rounded-3xl border border-slate-200 bg-white shadow-xl"
|
||||||
>
|
>
|
||||||
@@ -420,17 +446,55 @@ const handleCheckout = async () => {
|
|||||||
:on-zoom-out="zoomOut"
|
:on-zoom-out="zoomOut"
|
||||||
:on-zoom-reset="resetZoom"
|
:on-zoom-reset="resetZoom"
|
||||||
/>
|
/>
|
||||||
<div class="p-6">
|
<div class="p-6 space-y-4">
|
||||||
<DesignerCanvas
|
<!-- Canvas for each view -->
|
||||||
:size="displaySize"
|
<div v-show="activeView === 'front'" class="canvas-container">
|
||||||
:background-color="selectedTemplate.backgroundColor"
|
<h3 class="mb-2 text-sm font-semibold text-slate-700">Front View</h3>
|
||||||
:register-canvas="registerCanvas"
|
<DesignerCanvas
|
||||||
:unregister-canvas="unregisterCanvas"
|
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">
|
<p class="mt-4 text-sm text-slate-600">
|
||||||
Safe zone and bleed guides update automatically when you switch
|
Design each view of your table jersey separately. Switch between views using the tabs above.
|
||||||
templates. Use the toolbar to layer text, shapes, and imagery
|
Safe zone and bleed guides update automatically when you switch templates.
|
||||||
inside the design area.
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,58 +1,115 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { onMounted } from "vue";
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const startDesigning = () => {
|
const startDesigning = () => {
|
||||||
router.push('/designer');
|
router.push("/designer");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
// Import model-viewer on client-side only
|
||||||
|
if (process.client) {
|
||||||
|
import("@google/model-viewer");
|
||||||
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="relative min-h-screen overflow-hidden bg-white">
|
<div class="relative min-h-screen overflow-hidden bg-white">
|
||||||
<!-- Subtle Background Pattern -->
|
<!-- Subtle Background Pattern -->
|
||||||
<div class="absolute inset-0 opacity-5">
|
<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>
|
</div>
|
||||||
|
|
||||||
<!-- Main Content Grid -->
|
<!-- 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 -->
|
<!-- 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 -->
|
<!-- Left Side - Title & Description -->
|
||||||
<div class="w-full space-y-8 text-center lg:w-1/2 lg:text-left">
|
<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
|
TableJerseys
|
||||||
</h1>
|
</h1>
|
||||||
<div class="space-y-4">
|
<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
|
Design custom jerseys for your table
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<!-- Simple preview for mobile view -->
|
<!-- Simple preview for mobile view -->
|
||||||
<div class="relative mx-auto my-8 block lg:hidden">
|
<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="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 -->
|
<!-- 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">
|
<div class="relative">
|
||||||
<!-- Table with fitted cover -->
|
<!-- Table with fitted cover -->
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<!-- Table Top -->
|
<!-- 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 -->
|
<!-- 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-center">
|
||||||
<div class="text-2xl font-bold text-slate-800">23</div>
|
<div class="text-2xl font-bold text-slate-800">
|
||||||
<div class="text-[7px] font-semibold text-slate-600 tracking-wider">CUSTOM</div>
|
23
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="text-[7px] font-semibold text-slate-600 tracking-wider"
|
||||||
|
>
|
||||||
|
CUSTOM
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Fitted Cover Skirt (draping down) -->
|
<!-- 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 -->
|
<!-- 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 -->
|
<!-- 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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -75,69 +132,67 @@ const startDesigning = () => {
|
|||||||
>
|
>
|
||||||
<span class="relative z-10 flex items-center gap-3">
|
<span class="relative z-10 flex items-center gap-3">
|
||||||
Start Designing
|
Start Designing
|
||||||
<svg class="h-6 w-6 transition-transform group-hover:translate-x-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 7l5 5m0 0l-5 5m5-5H6" />
|
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>
|
</svg>
|
||||||
</span>
|
</span>
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Stats Section - Now on Left Side -->
|
<!-- 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="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 class="text-sm text-slate-600">Any Size</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="space-y-2 text-center lg:text-left">
|
<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 class="text-sm text-slate-600">Print Quality</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="space-y-2 text-center lg:text-left">
|
<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 class="text-sm text-slate-600">Per Design</div>
|
||||||
</div>
|
</div>
|
||||||
</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 hidden w-full lg:block lg:w-1/2">
|
||||||
<div class="relative mx-auto max-w-4xl">
|
<div class="relative mx-auto max-w-4xl">
|
||||||
<div class="relative mx-auto h-[300px] w-[300px] sm:h-[600px] sm:w-[600px]">
|
<div
|
||||||
<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">
|
class="relative mx-auto h-[300px] w-[300px] sm:h-[600px] sm:w-[600px]"
|
||||||
<!-- Rotating Table with Fitted Cover -->
|
>
|
||||||
<div class=" relative" style="transform-style: preserve-3d;">
|
<div
|
||||||
<div class="relative">
|
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"
|
||||||
<!-- Table with fitted cover -->
|
>
|
||||||
<div class="relative">
|
<model-viewer
|
||||||
<!-- Table Top with jersey on cover -->
|
src="/LAMESA.glb"
|
||||||
<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">
|
alt="Table with Jersey"
|
||||||
<!-- Jersey design on top of cover -->
|
auto-rotate
|
||||||
<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">
|
rotation-per-second="15deg"
|
||||||
<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">
|
camera-controls
|
||||||
<div class="text-center">
|
shadow-intensity="1"
|
||||||
<div class="text-5xl sm:text-7xl font-bold text-slate-800">23</div>
|
class="w-full h-full"
|
||||||
<div class="text-sm sm:text-xl font-semibold text-slate-600 tracking-wider">CUSTOM</div>
|
style="--poster-color: transparent"
|
||||||
</div>
|
></model-viewer>
|
||||||
</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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -86,9 +86,11 @@ export const useSlipmatDesigner = () => {
|
|||||||
const selectedTemplate = ref<SlipmatTemplate>(FALLBACK_TEMPLATE);
|
const selectedTemplate = ref<SlipmatTemplate>(FALLBACK_TEMPLATE);
|
||||||
|
|
||||||
const fabricApi = shallowRef<FabricModule | null>(null);
|
const fabricApi = shallowRef<FabricModule | null>(null);
|
||||||
const canvas = shallowRef<FabricCanvas | null>(null);
|
const canvases = shallowRef<Record<string, FabricCanvas>>({});
|
||||||
const backgroundCircle = shallowRef<FabricCircle | null>(null);
|
const activeCanvasId = ref<string>('front');
|
||||||
const safeZoneCircle = shallowRef<FabricCircle | null>(null);
|
const canvas = computed(() => canvases.value[activeCanvasId.value] || null);
|
||||||
|
const backgroundCircle = shallowRef<Record<string, FabricCircle | null>>({});
|
||||||
|
const safeZoneCircle = shallowRef<Record<string, FabricCircle | null>>({});
|
||||||
|
|
||||||
const previewUrl = ref<string | null>(null);
|
const previewUrl = ref<string | null>(null);
|
||||||
const previewBlob = shallowRef<Blob | null>(null);
|
const previewBlob = shallowRef<Blob | null>(null);
|
||||||
@@ -135,20 +137,32 @@ export const useSlipmatDesigner = () => {
|
|||||||
if (!currentCanvas) {
|
if (!currentCanvas) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (backgroundCircle.value) {
|
const bgCircle = backgroundCircle.value[activeCanvasId.value];
|
||||||
backgroundCircle.value.set({ radius: currentCanvas.getWidth() / 2 });
|
const safeCircle = safeZoneCircle.value[activeCanvasId.value];
|
||||||
|
|
||||||
|
if (bgCircle) {
|
||||||
|
const bgRect = bgCircle as any;
|
||||||
|
if (bgRect.set) {
|
||||||
|
bgRect.set({
|
||||||
|
width: currentCanvas.getWidth(),
|
||||||
|
height: currentCanvas.getHeight()
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (backgroundCircle.value) {
|
if (bgCircle) {
|
||||||
currentCanvas.sendObjectToBack(backgroundCircle.value);
|
currentCanvas.sendObjectToBack(bgCircle);
|
||||||
}
|
}
|
||||||
if (safeZoneCircle.value) {
|
if (safeCircle) {
|
||||||
currentCanvas.bringObjectToFront(safeZoneCircle.value);
|
currentCanvas.bringObjectToFront(safeCircle);
|
||||||
}
|
}
|
||||||
currentCanvas.requestRenderAll();
|
currentCanvas.requestRenderAll();
|
||||||
};
|
};
|
||||||
|
|
||||||
const isStaticObject = (object: FabricObject | null) =>
|
const isStaticObject = (object: FabricObject | null) => {
|
||||||
object === backgroundCircle.value || object === safeZoneCircle.value;
|
const bgCircle = backgroundCircle.value[activeCanvasId.value];
|
||||||
|
const safeCircle = safeZoneCircle.value[activeCanvasId.value];
|
||||||
|
return object === bgCircle || object === safeCircle;
|
||||||
|
};
|
||||||
|
|
||||||
const getSelectedObjects = (): FabricObject[] => {
|
const getSelectedObjects = (): FabricObject[] => {
|
||||||
const currentCanvas = canvas.value;
|
const currentCanvas = canvas.value;
|
||||||
@@ -222,30 +236,35 @@ export const useSlipmatDesigner = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const size = currentCanvas.getWidth();
|
const width = currentCanvas.getWidth();
|
||||||
|
const height = currentCanvas.getHeight();
|
||||||
const { backgroundColor, safeZoneInches = 0, diameterInches } =
|
const { backgroundColor, safeZoneInches = 0, diameterInches } =
|
||||||
selectedTemplate.value;
|
selectedTemplate.value;
|
||||||
|
|
||||||
const bgCircle =
|
const canvasId = activeCanvasId.value;
|
||||||
backgroundCircle.value ??
|
const bgRect =
|
||||||
new fabric.Circle({
|
backgroundCircle.value[canvasId] as any ??
|
||||||
left: size / 2,
|
new fabric.Rect({
|
||||||
top: size / 2,
|
left: 0,
|
||||||
radius: size / 2,
|
top: 0,
|
||||||
originX: "center",
|
width: width,
|
||||||
originY: "center",
|
height: height,
|
||||||
|
originX: "left",
|
||||||
|
originY: "top",
|
||||||
selectable: false,
|
selectable: false,
|
||||||
evented: false,
|
evented: false,
|
||||||
|
rx: 8,
|
||||||
|
ry: 8,
|
||||||
});
|
});
|
||||||
|
|
||||||
bgCircle.set({ fill: backgroundColor });
|
bgRect.set({ fill: backgroundColor });
|
||||||
bgCircle.set({ radius: size / 2 });
|
bgRect.set({ width: width, height: height });
|
||||||
|
|
||||||
if (!backgroundCircle.value) {
|
if (!backgroundCircle.value[canvasId]) {
|
||||||
backgroundCircle.value = bgCircle;
|
backgroundCircle.value[canvasId] = bgRect as any;
|
||||||
currentCanvas.add(bgCircle);
|
currentCanvas.add(bgRect);
|
||||||
}
|
}
|
||||||
currentCanvas.sendObjectToBack(bgCircle);
|
currentCanvas.sendObjectToBack(bgRect);
|
||||||
currentCanvas.requestRenderAll();
|
currentCanvas.requestRenderAll();
|
||||||
|
|
||||||
const safeRatio = Math.max(
|
const safeRatio = Math.max(
|
||||||
@@ -253,16 +272,17 @@ export const useSlipmatDesigner = () => {
|
|||||||
(diameterInches - safeZoneInches * 2) / diameterInches
|
(diameterInches - safeZoneInches * 2) / diameterInches
|
||||||
);
|
);
|
||||||
|
|
||||||
const safeCircleRadius = (size / 2) * safeRatio;
|
const safeMargin = ((1 - safeRatio) * Math.min(width, height)) / 2;
|
||||||
|
|
||||||
const safeCircle =
|
const safeRect =
|
||||||
safeZoneCircle.value ??
|
safeZoneCircle.value[canvasId] as any ??
|
||||||
new fabric.Circle({
|
new fabric.Rect({
|
||||||
left: size / 2,
|
left: safeMargin,
|
||||||
top: size / 2,
|
top: safeMargin,
|
||||||
radius: safeCircleRadius,
|
width: width - safeMargin * 2,
|
||||||
originX: "center",
|
height: height - safeMargin * 2,
|
||||||
originY: "center",
|
originX: "left",
|
||||||
|
originY: "top",
|
||||||
fill: "rgba(0,0,0,0)",
|
fill: "rgba(0,0,0,0)",
|
||||||
stroke: "#4b5563",
|
stroke: "#4b5563",
|
||||||
strokeDashArray: [8, 8],
|
strokeDashArray: [8, 8],
|
||||||
@@ -270,36 +290,52 @@ export const useSlipmatDesigner = () => {
|
|||||||
selectable: false,
|
selectable: false,
|
||||||
evented: false,
|
evented: false,
|
||||||
hoverCursor: "default",
|
hoverCursor: "default",
|
||||||
|
rx: 8,
|
||||||
|
ry: 8,
|
||||||
});
|
});
|
||||||
|
|
||||||
safeCircle.set({ radius: safeCircleRadius });
|
safeRect.set({
|
||||||
|
left: safeMargin,
|
||||||
|
top: safeMargin,
|
||||||
|
width: width - safeMargin * 2,
|
||||||
|
height: height - safeMargin * 2
|
||||||
|
});
|
||||||
|
|
||||||
if (!safeZoneCircle.value) {
|
if (!safeZoneCircle.value[canvasId]) {
|
||||||
safeZoneCircle.value = safeCircle;
|
safeZoneCircle.value[canvasId] = safeRect as any;
|
||||||
currentCanvas.add(safeCircle);
|
currentCanvas.add(safeRect);
|
||||||
}
|
}
|
||||||
|
|
||||||
maintainStaticLayerOrder();
|
maintainStaticLayerOrder();
|
||||||
currentCanvas.requestRenderAll();
|
currentCanvas.requestRenderAll();
|
||||||
|
|
||||||
currentCanvas.clipPath = new fabric.Circle({
|
currentCanvas.clipPath = new fabric.Rect({
|
||||||
left: size / 2,
|
left: 0,
|
||||||
top: size / 2,
|
top: 0,
|
||||||
radius: size / 2,
|
width: width,
|
||||||
originX: "center",
|
height: height,
|
||||||
originY: "center",
|
originX: "left",
|
||||||
|
originY: "top",
|
||||||
absolutePositioned: true,
|
absolutePositioned: true,
|
||||||
|
rx: 8,
|
||||||
|
ry: 8,
|
||||||
});
|
});
|
||||||
|
|
||||||
currentCanvas.renderAll();
|
currentCanvas.renderAll();
|
||||||
schedulePreviewRefresh();
|
schedulePreviewRefresh();
|
||||||
};
|
};
|
||||||
|
|
||||||
const registerCanvas = ({ canvas: instance, fabric }: CanvasReadyPayload) => {
|
const registerCanvas = ({ canvas: instance, fabric, canvasId = 'front' }: CanvasReadyPayload & { canvasId?: string }) => {
|
||||||
fabricApi.value = fabric;
|
fabricApi.value = fabric;
|
||||||
canvas.value = instance;
|
canvases.value[canvasId] = instance;
|
||||||
|
|
||||||
|
const previousActiveId = activeCanvasId.value;
|
||||||
|
// Temporarily set this canvas as active to initialize it
|
||||||
|
activeCanvasId.value = canvasId;
|
||||||
|
|
||||||
instance.setDimensions({ width: DISPLAY_SIZE, height: DISPLAY_SIZE });
|
const canvasWidth = DISPLAY_SIZE;
|
||||||
|
const canvasHeight = Math.round(DISPLAY_SIZE * 0.67); // 3:2 aspect ratio for table
|
||||||
|
instance.setDimensions({ width: canvasWidth, height: canvasHeight });
|
||||||
instance.preserveObjectStacking = true;
|
instance.preserveObjectStacking = true;
|
||||||
|
|
||||||
const refreshEvents = [
|
const refreshEvents = [
|
||||||
@@ -352,18 +388,41 @@ export const useSlipmatDesigner = () => {
|
|||||||
updateSelectedStyleState();
|
updateSelectedStyleState();
|
||||||
setZoom(zoomLevel.value);
|
setZoom(zoomLevel.value);
|
||||||
schedulePreviewRefresh();
|
schedulePreviewRefresh();
|
||||||
|
|
||||||
|
// Restore the active canvas to 'front' if this wasn't the front canvas
|
||||||
|
if (canvasId !== 'front' && previousActiveId) {
|
||||||
|
activeCanvasId.value = previousActiveId;
|
||||||
|
} else if (canvasId === 'front') {
|
||||||
|
// Keep 'front' as active
|
||||||
|
activeCanvasId.value = 'front';
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const unregisterCanvas = () => {
|
const unregisterCanvas = (canvasId?: string) => {
|
||||||
canvas.value?.dispose();
|
if (canvasId && canvases.value[canvasId]) {
|
||||||
canvas.value = null;
|
canvases.value[canvasId]?.dispose();
|
||||||
fabricApi.value = null;
|
delete canvases.value[canvasId];
|
||||||
backgroundCircle.value = null;
|
delete backgroundCircle.value[canvasId];
|
||||||
safeZoneCircle.value = null;
|
delete safeZoneCircle.value[canvasId];
|
||||||
activeFillColor.value = null;
|
} else {
|
||||||
activeStrokeColor.value = null;
|
// Unregister all
|
||||||
hasStyleableSelection.value = false;
|
Object.values(canvases.value).forEach(c => c?.dispose());
|
||||||
zoomLevel.value = 1;
|
canvases.value = {};
|
||||||
|
backgroundCircle.value = {};
|
||||||
|
safeZoneCircle.value = {};
|
||||||
|
fabricApi.value = null;
|
||||||
|
activeFillColor.value = null;
|
||||||
|
activeStrokeColor.value = null;
|
||||||
|
hasStyleableSelection.value = false;
|
||||||
|
zoomLevel.value = 1;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const setActiveCanvas = (canvasId: string) => {
|
||||||
|
if (canvases.value[canvasId]) {
|
||||||
|
activeCanvasId.value = canvasId;
|
||||||
|
updateSelectedStyleState();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const centerPoint = () => {
|
const centerPoint = () => {
|
||||||
@@ -795,6 +854,7 @@ export const useSlipmatDesigner = () => {
|
|||||||
maxZoom: MAX_ZOOM,
|
maxZoom: MAX_ZOOM,
|
||||||
registerCanvas,
|
registerCanvas,
|
||||||
unregisterCanvas,
|
unregisterCanvas,
|
||||||
|
setActiveCanvas,
|
||||||
addTextbox,
|
addTextbox,
|
||||||
addShape,
|
addShape,
|
||||||
addImageFromFile,
|
addImageFromFile,
|
||||||
|
|||||||
119
package-lock.json
generated
119
package-lock.json
generated
@@ -7,6 +7,7 @@
|
|||||||
"name": "tablejerseys-web",
|
"name": "tablejerseys-web",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@google/model-viewer": "^4.1.0",
|
||||||
"@nuxtjs/color-mode": "^3.5.2",
|
"@nuxtjs/color-mode": "^3.5.2",
|
||||||
"@tailwindcss/vite": "^4.1.16",
|
"@tailwindcss/vite": "^4.1.16",
|
||||||
"fabric": "^6.0.2",
|
"fabric": "^6.0.2",
|
||||||
@@ -1513,6 +1514,22 @@
|
|||||||
"integrity": "sha512-+uGNN7rkfn41HLO0vekTFhTxk61eKa8mTpRGLO0QSqlQdKvIoGAvLp3ppdVIWbTGYJWM6Kp0iN+PjMIOcnVqTw==",
|
"integrity": "sha512-+uGNN7rkfn41HLO0vekTFhTxk61eKa8mTpRGLO0QSqlQdKvIoGAvLp3ppdVIWbTGYJWM6Kp0iN+PjMIOcnVqTw==",
|
||||||
"license": "Apache-2.0"
|
"license": "Apache-2.0"
|
||||||
},
|
},
|
||||||
|
"node_modules/@google/model-viewer": {
|
||||||
|
"version": "4.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@google/model-viewer/-/model-viewer-4.1.0.tgz",
|
||||||
|
"integrity": "sha512-7WB/jS6wfBfRl/tWhsUUvDMKFE1KlKME97coDLlZQfvJD0nCwjhES1lJ+k7wnmf7T3zMvCfn9mIjM/mgZapuig==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@monogrid/gainmap-js": "^3.1.0",
|
||||||
|
"lit": "^3.2.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"three": "^0.172.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@grpc/grpc-js": {
|
"node_modules/@grpc/grpc-js": {
|
||||||
"version": "1.9.15",
|
"version": "1.9.15",
|
||||||
"resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.9.15.tgz",
|
"resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.9.15.tgz",
|
||||||
@@ -1699,6 +1716,21 @@
|
|||||||
"integrity": "sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==",
|
"integrity": "sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@lit-labs/ssr-dom-shim": {
|
||||||
|
"version": "1.5.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.5.1.tgz",
|
||||||
|
"integrity": "sha512-Aou5UdlSpr5whQe8AA/bZG0jMj96CoJIWbGfZ91qieWu5AWUMKw8VR/pAkQkJYvBNhmCcWnZlyyk5oze8JIqYA==",
|
||||||
|
"license": "BSD-3-Clause"
|
||||||
|
},
|
||||||
|
"node_modules/@lit/reactive-element": {
|
||||||
|
"version": "2.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-2.1.2.tgz",
|
||||||
|
"integrity": "sha512-pbCDiVMnne1lYUIaYNN5wrwQXDtHaYtg7YEFPeW+hws6U47WeFvISGUWekPGKWOP1ygrs0ef0o1VJMk1exos5A==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"@lit-labs/ssr-dom-shim": "^1.5.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@mapbox/node-pre-gyp": {
|
"node_modules/@mapbox/node-pre-gyp": {
|
||||||
"version": "1.0.11",
|
"version": "1.0.11",
|
||||||
"resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz",
|
"resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz",
|
||||||
@@ -1720,6 +1752,18 @@
|
|||||||
"node-pre-gyp": "bin/node-pre-gyp"
|
"node-pre-gyp": "bin/node-pre-gyp"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@monogrid/gainmap-js": {
|
||||||
|
"version": "3.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@monogrid/gainmap-js/-/gainmap-js-3.4.0.tgz",
|
||||||
|
"integrity": "sha512-2Z0FATFHaoYJ8b+Y4y4Hgfn3FRFwuU5zRrk+9dFWp4uGAdHGqVEdP7HP+gLA3X469KXHmfupJaUbKo1b/aDKIg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"promise-worker-transferable": "^1.0.4"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"three": ">= 0.159.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@napi-rs/wasm-runtime": {
|
"node_modules/@napi-rs/wasm-runtime": {
|
||||||
"version": "1.0.7",
|
"version": "1.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.0.7.tgz",
|
||||||
@@ -4183,6 +4227,12 @@
|
|||||||
"integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==",
|
"integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/trusted-types": {
|
||||||
|
"version": "2.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
|
||||||
|
"integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@unhead/vue": {
|
"node_modules/@unhead/vue": {
|
||||||
"version": "2.0.19",
|
"version": "2.0.19",
|
||||||
"resolved": "https://registry.npmjs.org/@unhead/vue/-/vue-2.0.19.tgz",
|
"resolved": "https://registry.npmjs.org/@unhead/vue/-/vue-2.0.19.tgz",
|
||||||
@@ -7294,6 +7344,12 @@
|
|||||||
"integrity": "sha512-3MOLanc3sb3LNGWQl1RlQlNWURE5g32aUphrDyFeCsxBTk08iE3VNe4CwsUZ0Qs1X+EfX0+r29Sxdpza4B+yRA==",
|
"integrity": "sha512-3MOLanc3sb3LNGWQl1RlQlNWURE5g32aUphrDyFeCsxBTk08iE3VNe4CwsUZ0Qs1X+EfX0+r29Sxdpza4B+yRA==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/immediate": {
|
||||||
|
"version": "3.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
|
||||||
|
"integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/impound": {
|
"node_modules/impound": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/impound/-/impound-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/impound/-/impound-1.0.0.tgz",
|
||||||
@@ -7495,6 +7551,12 @@
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
|
"node_modules/is-promise": {
|
||||||
|
"version": "2.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz",
|
||||||
|
"integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/is-reference": {
|
"node_modules/is-reference": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz",
|
||||||
@@ -7758,6 +7820,15 @@
|
|||||||
"safe-buffer": "~5.1.0"
|
"safe-buffer": "~5.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/lie": {
|
||||||
|
"version": "3.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz",
|
||||||
|
"integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"immediate": "~3.0.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/lightningcss": {
|
"node_modules/lightningcss": {
|
||||||
"version": "1.30.2",
|
"version": "1.30.2",
|
||||||
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz",
|
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz",
|
||||||
@@ -8055,6 +8126,37 @@
|
|||||||
"integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==",
|
"integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/lit": {
|
||||||
|
"version": "3.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/lit/-/lit-3.3.2.tgz",
|
||||||
|
"integrity": "sha512-NF9zbsP79l4ao2SNrH3NkfmFgN/hBYSQo90saIVI1o5GpjAdCPVstVzO1MrLOakHoEhYkrtRjPK6Ob521aoYWQ==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"@lit/reactive-element": "^2.1.0",
|
||||||
|
"lit-element": "^4.2.0",
|
||||||
|
"lit-html": "^3.3.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/lit-element": {
|
||||||
|
"version": "4.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/lit-element/-/lit-element-4.2.2.tgz",
|
||||||
|
"integrity": "sha512-aFKhNToWxoyhkNDmWZwEva2SlQia+jfG0fjIWV//YeTaWrVnOxD89dPKfigCUspXFmjzOEUQpOkejH5Ly6sG0w==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"@lit-labs/ssr-dom-shim": "^1.5.0",
|
||||||
|
"@lit/reactive-element": "^2.1.0",
|
||||||
|
"lit-html": "^3.3.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/lit-html": {
|
||||||
|
"version": "3.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/lit-html/-/lit-html-3.3.2.tgz",
|
||||||
|
"integrity": "sha512-Qy9hU88zcmaxBXcc10ZpdK7cOLXvXpRoBxERdtqV9QOrfpMZZ6pSYP91LhpPtap3sFMUiL7Tw2RImbe0Al2/kw==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/trusted-types": "^2.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/local-pkg": {
|
"node_modules/local-pkg": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-1.1.2.tgz",
|
||||||
@@ -9816,6 +9918,16 @@
|
|||||||
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
|
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/promise-worker-transferable": {
|
||||||
|
"version": "1.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/promise-worker-transferable/-/promise-worker-transferable-1.0.4.tgz",
|
||||||
|
"integrity": "sha512-bN+0ehEnrXfxV2ZQvU2PetO0n4gqBD4ulq3MI1WOPLgr7/Mg9yRQkX5+0v1vagr74ZTsl7XtzlaYDo2EuCeYJw==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"is-promise": "^2.1.0",
|
||||||
|
"lie": "^3.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/prompts": {
|
"node_modules/prompts": {
|
||||||
"version": "2.4.2",
|
"version": "2.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz",
|
||||||
@@ -11057,6 +11169,13 @@
|
|||||||
"b4a": "^1.6.4"
|
"b4a": "^1.6.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/three": {
|
||||||
|
"version": "0.172.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/three/-/three-0.172.0.tgz",
|
||||||
|
"integrity": "sha512-6HMgMlzU97MsV7D/tY8Va38b83kz8YJX+BefKjspMNAv0Vx6dxMogHOrnRl/sbMIs3BPUKijPqDqJ/+UwJbIow==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true
|
||||||
|
},
|
||||||
"node_modules/tiny-invariant": {
|
"node_modules/tiny-invariant": {
|
||||||
"version": "1.3.3",
|
"version": "1.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz",
|
||||||
|
|||||||
@@ -10,7 +10,8 @@
|
|||||||
"postinstall": "nuxt prepare"
|
"postinstall": "nuxt prepare"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nuxtjs/color-mode": "^3.5.2",
|
"@google/model-viewer": "^4.1.0",
|
||||||
|
"@nuxtjs/color-mode": "^3.5.2",
|
||||||
"@tailwindcss/vite": "^4.1.16",
|
"@tailwindcss/vite": "^4.1.16",
|
||||||
"fabric": "^6.0.2",
|
"fabric": "^6.0.2",
|
||||||
"firebase": "^12.5.0",
|
"firebase": "^12.5.0",
|
||||||
|
|||||||
BIN
public/LAMESA.glb
Normal file
BIN
public/LAMESA.glb
Normal file
Binary file not shown.
Reference in New Issue
Block a user