Website Redesign 7
This commit is contained in:
+194
-55
@@ -1,55 +1,194 @@
|
||||
<svelte:head>
|
||||
<script type="module" src="https://unpkg.com/@google/model-viewer/dist/model-viewer.min.js"></script>
|
||||
</svelte:head>
|
||||
|
||||
<script lang="ts">
|
||||
// @ts-nocheck
|
||||
import { page } from '$app/stores';
|
||||
import { onMount } from "svelte";
|
||||
|
||||
import { getModalStore } from '@skeletonlabs/skeleton';
|
||||
|
||||
const modalStore = getModalStore();
|
||||
|
||||
let container, row, modelViewer, modelViewerTitle, modelViewerBtn;
|
||||
|
||||
// const modal: ModalSettings = {
|
||||
// type: 'alert',
|
||||
// // Data
|
||||
// title: 'Example Alert',
|
||||
// body: 'This is an example modal.',
|
||||
// image: 'https://i.imgur.com/WOgTG96.gif',
|
||||
// };
|
||||
// modalStore.trigger(modal);
|
||||
|
||||
onMount(() => {
|
||||
modelViewerTitle = document.querySelector("#model-viewer-bts-title");
|
||||
|
||||
for(let i = 0; i < $page.data.files.length; i++) {
|
||||
|
||||
console.log($page.data.files[i]);
|
||||
}
|
||||
});
|
||||
|
||||
function download() {
|
||||
modelViewerBtn.disabled = true;
|
||||
modelViewerBtn.innerHTML = "Downloading...";
|
||||
modelViewer.exportScene("glb").then((blob) => {
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
a.download = modelViewerTitle.innerText + ".glb";
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
window.URL.revokeObjectURL(url);
|
||||
modelViewerBtn.disabled = false;
|
||||
modelViewerBtn.innerHTML = "Download";
|
||||
});
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
<section class="h-full mx-auto justify-center p-10">
|
||||
|
||||
</section>
|
||||
<script lang="ts">
|
||||
import TerminalTUI from '$lib/components/TerminalTUI.svelte';
|
||||
import type { TerminalLine } from '$lib/components/tui/types';
|
||||
import ModelViewer from '$lib/components/ModelViewer.svelte';
|
||||
import { user } from '$lib/config';
|
||||
import { getPageSpeedMultiplier } from '$lib';
|
||||
import { themeColors } from '$lib/stores/theme';
|
||||
import Icon from '@iconify/svelte';
|
||||
|
||||
const speed = getPageSpeedMultiplier('models');
|
||||
|
||||
// Available GLB files in static/models/
|
||||
const glbModels = [
|
||||
{ name: 'Bake Potato', path: '/models/BakePotato.glb' },
|
||||
{ name: 'Soda Can', path: '/models/Soda.glb' }
|
||||
];
|
||||
|
||||
let selectedModel = $state(glbModels[0]);
|
||||
let showViewer = $state(false);
|
||||
|
||||
function selectModel(model: typeof glbModels[0]) {
|
||||
selectedModel = model;
|
||||
showViewer = true;
|
||||
}
|
||||
|
||||
// Build the terminal lines for the models page
|
||||
const lines: TerminalLine[] = [
|
||||
{ type: 'command', content: 'ls ~/3d-models/' },
|
||||
{ type: 'blank', content: '' },
|
||||
|
||||
{ type: 'header', content: '(&primary,bold)3D Models(&)' },
|
||||
{ type: 'output', content: `(&muted)A collection of 3D models created for games, visualization, and fun.(&)` },
|
||||
{ type: 'blank', content: '' },
|
||||
|
||||
{ type: 'divider', content: 'VIEWER' },
|
||||
{ type: 'blank', content: '' },
|
||||
|
||||
// Interactive model buttons
|
||||
...glbModels.map(model => ({
|
||||
type: 'button' as const,
|
||||
content: model.name,
|
||||
icon: 'mdi:cube-scan',
|
||||
style: 'primary' as const,
|
||||
action: () => selectModel(model)
|
||||
})),
|
||||
|
||||
{ type: 'blank', content: '' },
|
||||
{ type: 'success', content: `(&success)${glbModels.length} models available(&)` }
|
||||
];
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>3D Models | {user.name}</title>
|
||||
<meta name="description" content="3D Models portfolio - Characters, Environments, Props and more" />
|
||||
</svelte:head>
|
||||
|
||||
<div class="models-page">
|
||||
<div
|
||||
class="models-layout"
|
||||
class:viewer-open={showViewer}
|
||||
style="--theme-bg: {$themeColors.background};"
|
||||
>
|
||||
<!-- Terminal Section -->
|
||||
<div class="terminal-section">
|
||||
<TerminalTUI {lines} title="~/3d-models" interactive={true} {speed} />
|
||||
</div>
|
||||
|
||||
<!-- 3D Viewer Section -->
|
||||
{#if showViewer}
|
||||
<div class="viewer-section">
|
||||
<div class="viewer-tabs">
|
||||
{#each glbModels as model}
|
||||
<button
|
||||
class="viewer-tab"
|
||||
class:active={selectedModel.path === model.path}
|
||||
onclick={() => selectModel(model)}
|
||||
>
|
||||
<Icon icon="mdi:cube-outline" width="14" />
|
||||
{model.name}
|
||||
</button>
|
||||
{/each}
|
||||
<button class="viewer-tab close" onclick={() => showViewer = false}>
|
||||
<Icon icon="mdi:close" width="14" />
|
||||
</button>
|
||||
</div>
|
||||
<ModelViewer
|
||||
modelPath={selectedModel.path}
|
||||
modelName={selectedModel.name}
|
||||
class="model-viewer-container"
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.models-page {
|
||||
min-height: calc(100vh - 60px);
|
||||
padding: 2rem 1rem;
|
||||
}
|
||||
|
||||
.models-layout {
|
||||
display: flex;
|
||||
gap: 1.5rem;
|
||||
max-width: 1600px;
|
||||
margin: 0 auto;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.terminal-section {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.viewer-section {
|
||||
width: 500px;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0;
|
||||
animation: slideIn 0.3s ease;
|
||||
}
|
||||
|
||||
@keyframes slideIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateX(20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
|
||||
.viewer-tabs {
|
||||
display: flex;
|
||||
background: var(--theme-bg);
|
||||
border-radius: 8px 8px 0 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.viewer-tab {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.35rem;
|
||||
padding: 0.5rem 1rem;
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: inherit;
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-size: 0.8rem;
|
||||
cursor: pointer;
|
||||
opacity: 0.6;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.viewer-tab:hover {
|
||||
opacity: 0.9;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
.viewer-tab.active {
|
||||
opacity: 1;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.viewer-tab.close {
|
||||
margin-left: auto;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.viewer-tab.close:hover {
|
||||
opacity: 1;
|
||||
color: #f38ba8;
|
||||
}
|
||||
|
||||
:global(.model-viewer-container) {
|
||||
flex: 1;
|
||||
min-height: 400px;
|
||||
border-radius: 0 0 8px 8px;
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
.models-layout {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.viewer-section {
|
||||
width: 100%;
|
||||
order: -1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user