diff --git a/assets/scss/_browser.scss b/assets/scss/_browser.scss index b0347d5..0bd6997 100644 --- a/assets/scss/_browser.scss +++ b/assets/scss/_browser.scss @@ -1,4 +1,3 @@ - .browser { position: relative; --browser-blue: #00B39F; @@ -8,19 +7,11 @@ --browser-glow: rgba(0, 179, 159, 0.75); } -.browser-mockup { - position: relative; - max-width: 980px; - width: 100%; - margin: 48px auto 0; - container-type: inline-size; - display: flex; - flex-direction: column; -} .browser-shell { position: relative; - width: 100%; + max-width: 980px; + margin: 48px auto 0; border-radius: 22px; overflow: hidden; background: #111214; @@ -28,11 +19,9 @@ box-shadow: 0 40px 90px rgba(0, 0, 0, 0.6), inset 0 1px 0 rgba(255, 255, 255, 0.04); - aspect-ratio: 16 / 10; - display: flex; - flex-direction: column; } + .browser-shell::after { content: ""; position: absolute; @@ -42,38 +31,45 @@ pointer-events: none; } + .browser-header { display: flex; align-items: center; gap: 18px; - padding: 1.8cqi 2.2cqi; /* scale padding proportionally */ + padding: 18px 22px; background: #1a1b1d; border-bottom: 1px solid rgba(100, 100, 100, 0.06); } + .browser-dots { display: flex; - gap: 0.8cqi; + gap: 8px; } + .browser-dots span { - width: unquote("min(10px, 1.2cqi)"); - height: unquote("min(10px, 1.2cqi)"); + width: 10px; + height: 10px; border-radius: 50%; } + .dot-red { background: #ff5f57; } + .dot-yellow { background: #febc2e; } + .dot-green { background: #28c840; } + .browser-pill { flex: 1; justify-self: start; @@ -84,28 +80,21 @@ color: #cbd2d9; background: rgba(200, 200, 200, 0.08); margin: 0 auto; - font-size: clamp(0.6rem, 1.2cqi, 0.9rem); /* Scale font size slightly */ + font-size: .9rem; } + .browser-body { - flex: 1; position: relative; - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; + min-height: 440px; + padding: 64px 32px; + display: grid; + place-items: center; + gap: 34px; background: linear-gradient(180deg, #000202 0%, #001211 65%, #00211e 100%); overflow: hidden; } -.browser-body-inner { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - gap: 34px; - transform-origin: center center; -} .browser-body::before { content: ""; @@ -118,14 +107,190 @@ .browser-scene { position: relative; + width: 100%; + max-width: 800px; + margin: 0 auto; + background: transparent; + + canvas { + width: 100%; + height: auto; + display: block; + transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1); + transform-style: preserve-3d; + filter: brightness(1) contrast(1); + cursor: pointer; + + // Hover effect: Scale and rotate + &:hover { + transform: scale(1.05) rotateY(2deg); + filter: brightness(1.15) contrast(1.1); + } + + // Active/Pulse effect on click + &:active { + animation: canvasPulse 0.6s ease; + } + } +} + +// Canvas pulse animation for click effect +@keyframes canvasPulse { + 0%, 100% { + transform: scale(1) rotateY(0deg); + } + 50% { + transform: scale(1.08) rotateY(3deg); + } +} + + +/* ============================================ + DASHBOARD IMAGE STYLES (NEW) + ============================================ */ + + +.dashboard-container { + perspective: 1200px; + width: 100%; + max-width: 560px; + height: auto; + position: relative; +} + + +.dashboard-content { + position: relative; + width: 100%; + border-radius: 12px; + overflow: hidden; + background: rgba(0, 0, 0, 0.3); + border: 1px solid rgba(0, 211, 169, 0.3); + box-shadow: 0 8px 32px rgba(0, 179, 159, 0.15), + inset 0 1px 0 rgba(255, 255, 255, 0.05); + backdrop-filter: blur(4px); + animation: dashboardFloat 4s ease-in-out infinite, + dashboardGlow 6s ease-in-out infinite; + transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1); + + &:hover { + border-color: rgba(0, 211, 169, 0.6); + box-shadow: 0 12px 40px rgba(0, 179, 159, 0.25), + inset 0 1px 0 rgba(255, 255, 255, 0.08); + } +} + + +.dashboard-image { + width: 100%; + height: auto; + display: block; + border-radius: 10px; + transition: all 0.4s cubic-bezier(0.34, 1.56, 0.64, 1); + filter: drop-shadow(0 0 20px rgba(0, 179, 159, 0.25)); + will-change: transform, filter; + object-fit: cover; + cursor: pointer; + + &:hover { + filter: drop-shadow(0 0 30px rgba(0, 179, 159, 0.5)) brightness(1.1) contrast(1.05); + transform: scale(1.02) translateY(-2px); + } + + &:active { + animation: imagePulse 0.6s ease-out; + } +} + + +/* ============================================ + KEYFRAME ANIMATIONS FOR DASHBOARD + ============================================ */ + + +@keyframes dashboardFloat { + 0%, 100% { + transform: translateY(0px) rotateX(0deg); + } + 50% { + transform: translateY(-15px) rotateX(1deg); + } +} + + +@keyframes dashboardGlow { + 0%, 100% { + box-shadow: 0 8px 32px rgba(0, 179, 159, 0.15), + inset 0 1px 0 rgba(255, 255, 255, 0.05); + } + 50% { + box-shadow: 0 12px 40px rgba(0, 179, 159, 0.25), + inset 0 1px 0 rgba(255, 255, 255, 0.08); + } +} + + +@keyframes imagePulse { + 0% { + transform: scale(1); + filter: drop-shadow(0 0 20px rgba(0, 211, 169, 0.5)); + } + 50% { + transform: scale(1.03); + filter: drop-shadow(0 0 40px rgba(0, 211, 169, 0.8)); + } + 100% { + transform: scale(1); + filter: drop-shadow(0 0 20px rgba(0, 211, 169, 0.25)); + } +} + + +/* ============================================ + LOGO FALLBACK STYLES + ============================================ */ + + +.logo-fallback-container { + perspective: 1000px; width: 280px; - height: 240px; - display: grid; - place-items: center; - perspective: 900px; - z-index: 4; + height: 280px; } + +.logo-3d-container { + perspective: 1000px; + width: 280px; + height: 280px; +} + + +.logo-3d { + width: 100%; + height: 100%; + position: relative; + transform-style: preserve-3d; + animation: rotateLogoY 8s ease-in-out infinite; + + img { + width: 100%; + height: 100%; + object-fit: contain; + filter: drop-shadow(0 0 30px rgba(0, 179, 159, 0.2)); + } +} + + +.logo-float { + animation: floatLogo 6s ease-in-out infinite; +} + + +/* ============================================ + 3D CUBE STYLES (OLD - KEPT FOR REFERENCE) + ============================================ */ + + .cube { position: relative; width: 140px; @@ -134,6 +299,7 @@ animation: cubeSpin 16s ease-in-out infinite, cubeFloat 6s ease-in-out infinite; } + .face { position: absolute; width: 140px; @@ -145,30 +311,36 @@ opacity: 0.95; } + .face-front { transform: translateZ(40px); } + .face-right { transform: rotateY(90deg) translateZ(40px); background: linear-gradient(145deg, #006661 0%, #003B37 100%); } + .face-left { transform: rotateY(-90deg) translateZ(40px); background: linear-gradient(145deg, #006661 0%, #003B37 100%); } + .face-top { transform: rotateX(90deg) translateZ(40px); background: linear-gradient(145deg, var(--browser-blue-soft) 0%, #007763 100%); } + .face-bottom { transform: rotateX(-90deg) translateZ(40px); background: linear-gradient(145deg, #003B37 0%, #002722 100%); } + .cube-shadow { position: absolute; width: 180px; @@ -179,6 +351,12 @@ animation: shadowPulse 6s ease-in-out infinite; } + +/* ============================================ + CTA BUTTON STYLES & ANIMATIONS + ============================================ */ + + .shine::before, .shine::after { content: ""; @@ -200,17 +378,20 @@ pointer-events: none; } + .shine.shine::before { animation: trail 14s infinite linear; opacity: 0.35; } + .shine.shine::after { animation: trail-offset 14s infinite linear; offset-distance: 50%; opacity: 0.5; } + .browser-cta { position: relative; z-index: 2; @@ -231,6 +412,7 @@ transition: transform 0.4s cubic-bezier(0.16, 1, 0.3, 1), box-shadow 0.4s cubic-bezier(0.16, 1, 0.3, 1), border-color 0.3s ease; + cursor: pointer; &:hover { transform: translateY(-3px); @@ -245,6 +427,7 @@ } } + .browser-cta::after { content: ""; position: absolute; @@ -253,20 +436,21 @@ padding: 1px; background: linear-gradient(120deg, rgba(0, 211, 169, 0.6), rgba(0, 102, 97, 0.15)); -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0); - mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0); -webkit-mask-composite: xor; mask-composite: exclude; pointer-events: none; } + .cta-hexagon { width: 18px; - height: 18px; /* (width * tan(60deg)) to maintain aspect ratio */ + height: 18px; background: radial-gradient(circle, rgba(158, 255, 236, 0.9), rgba(0, 211, 169, 0.55)); box-shadow: 0 0 12px rgba(0, 211, 169, 0.6); clip-path: polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%); } + .cta-spark { width: 18px; height: 18px; @@ -277,6 +461,7 @@ box-shadow: 0 0 12px rgba(0, 211, 169, 0.6); } + .cta-arrow { width: 26px; height: 26px; @@ -286,47 +471,252 @@ display: grid; place-items: center; position: relative; + transition: all 0.3s ease; + + &::before { + content: ""; + width: 7px; + height: 7px; + border-top: 2px solid rgba(158, 255, 236, 0.9); + border-right: 2px solid rgba(158, 255, 236, 0.9); + transform: rotate(45deg); + transition: all 0.3s ease; + } } -.cta-arrow::before { - content: ""; - width: 7px; - height: 7px; - border-top: 2px solid rgba(158, 255, 236, 0.9); - border-right: 2px solid rgba(158, 255, 236, 0.9); - transform: rotate(45deg); +.browser-cta:hover .cta-arrow { + border-color: rgba(0, 211, 169, 0.6); + background: rgba(0, 211, 169, 0.1); + + &::before { + transform: rotate(45deg) translateX(2px) translateY(-2px); + } } + +/* ============================================ + BROWSER STAND STYLES + ============================================ */ + .browser-stand { - width: 22.45cqi; - height: 13.06cqi; + width: 220px; + height: 128px; margin: 0 auto; - border-radius: 0 0 0px 00px; + border-radius: 0 0 0 0; background: linear-gradient(180deg, #1d251d, #0b120b); - box-shadow: 0 3cqi 6cqi rgba(0, 0, 0, 0.6); + box-shadow: 0 30px 60px rgba(0, 0, 0, 0.6); position: relative; } .browser-stand::before { content: ""; position: absolute; - width: 100%; - height: 18.75%; - top: 84.375%; + width: 220px; + height: 24px; + top: 108px; left: 50%; transform: translateX(-50%); border-radius: 4px; - // background: linear-gradient(180deg, #0d100d, #050805); - // box-shadow: inset 0 0 10px rgba(250, 250, 250, 0.45); - background: linear-gradient(to right,rgba(170, 204, 172, 0.082),color-mix(in srgb,#000 88%,#fff),rgba(170, 204, 172, 0.082)); + background: linear-gradient(to right, rgba(170, 204, 172, 0.082), color-mix(in srgb, #000 88%, #fff), rgba(170, 204, 172, 0.082)); } -.browser-shadow { - background: radial-gradient(circle at 50% 100%, rgba(0, 179, 159, 0.05), transparent 50%); - position: relative; - top: -12.24cqi; - height: 13.26cqi; + +/* ============================================ + BROWSER CTA CONTAINER + ============================================ */ + +.browser-cta-container { + display: flex; + justify-content: center; + align-items: center; width: 100%; - pointer-events: none; - z-index: -1; -} \ No newline at end of file + z-index: 10; +} + + +/* ============================================ + RESPONSIVE ADJUSTMENTS + ============================================ */ + +@media (max-width: 768px) { + .browser-shell { + max-width: 95vw; + margin: 24px auto 0; + } + + .browser-body { + min-height: 360px; + padding: 48px 24px; + gap: 24px; + } + + .browser-scene { + max-width: 100%; + } + + canvas { + max-width: 100%; + } + + .browser-pill { + max-width: 280px; + font-size: 0.85rem; + padding: 3px 12px; + } + + .browser-cta { + padding: 12px 20px; + font-size: 0.9rem; + gap: 12px; + } + + .browser-stand { + width: 180px; + height: 100px; + } + + .browser-stand::before { + width: 180px; + top: 84px; + } +} + +@media (max-width: 480px) { + .browser-shell { + max-width: 100%; + border-radius: 12px; + margin: 16px auto 0; + } + + .browser-body { + min-height: 300px; + padding: 32px 16px; + gap: 16px; + } + + .browser-header { + padding: 12px 16px; + gap: 12px; + } + + .browser-pill { + max-width: 200px; + font-size: 0.8rem; + padding: 2px 10px; + } + + .browser-dots { + gap: 6px; + } + + .browser-dots span { + width: 8px; + height: 8px; + } + + .browser-cta { + padding: 10px 16px; + font-size: 0.85rem; + gap: 10px; + border-radius: 999px; + } + + .cta-hexagon { + width: 14px; + height: 14px; + } + + .cta-arrow { + width: 22px; + height: 22px; + } + + .cta-arrow::before { + width: 6px; + height: 6px; + border-top: 1.5px solid rgba(158, 255, 236, 0.9); + border-right: 1.5px solid rgba(158, 255, 236, 0.9); + } + + .browser-stand { + width: 140px; + height: 80px; + } + + .browser-stand::before { + width: 140px; + height: 18px; + top: 66px; + } +} + + +/* ============================================ + UTILITY ANIMATIONS (Referenced in code) + ============================================ */ + +@keyframes trail { + 0% { + offset-distance: 0%; + } + 100% { + offset-distance: 100%; + } +} + +@keyframes trail-offset { + 0% { + offset-distance: 0%; + } + 100% { + offset-distance: 100%; + } +} + +@keyframes cubeSpin { + 0% { + transform: rotateX(0deg) rotateY(0deg); + } + 100% { + transform: rotateX(360deg) rotateY(360deg); + } +} + +@keyframes cubeFloat { + 0%, 100% { + transform: translateY(0px); + } + 50% { + transform: translateY(-20px); + } +} + +@keyframes shadowPulse { + 0%, 100% { + opacity: 0.65; + transform: scaleX(1); + } + 50% { + opacity: 0.9; + transform: scaleX(1.1); + } +} + +@keyframes rotateLogoY { + 0% { + transform: rotateY(0deg); + } + 100% { + transform: rotateY(360deg); + } +} + +@keyframes floatLogo { + 0%, 100% { + transform: translateY(0px); + } + 50% { + transform: translateY(-20px); + } +} + diff --git a/layouts/partials/cube-scene.html b/layouts/partials/cube-scene.html new file mode 100644 index 0000000..ef30e02 --- /dev/null +++ b/layouts/partials/cube-scene.html @@ -0,0 +1,256 @@ +
+ +
+
+ + \ No newline at end of file diff --git a/layouts/partials/head.html b/layouts/partials/head.html index 3a34741..5649ec0 100644 --- a/layouts/partials/head.html +++ b/layouts/partials/head.html @@ -1,36 +1,64 @@ - - - - - - - - - - - {{ if .IsHome }}{{ .Site.Title }}{{ else }}{{ .Title }} | {{ .Site.Title }}{{ end }} - - - - - - - - - - - - - - {{- with site.Params.canonicalBaseURL }} - - {{- end }} - {{- if eq (getenv "HUGO_PREVIEW") "true" }} - - {{- end }} - - - - - {{ partial "head-css.html" . }} + + + + + + + + + + + + + +{{ if .IsHome }}{{ .Site.Title }}{{ else }}{{ .Title }} | {{ .Site.Title }}{{ end }} + + + + + + + + + + + + + + + + + + +{{ partial "head-css.html" . }} + + + + + + + + + + + \ No newline at end of file diff --git a/layouts/partials/section/browser.html b/layouts/partials/section/browser.html index 5fa41a2..35d1895 100644 --- a/layouts/partials/section/browser.html +++ b/layouts/partials/section/browser.html @@ -1,54 +1,39 @@ -
-
-

See Kanvas in Action

+
+

See Kanvas in Action

+
+ +
+
+
+
+
+
+
+
https://kanvas.new/extension/meshmap
-
-
-
-
- - - -
-
https://kanvas.new
-
-
-
-
- -
- -
-
-
- - - See Kanvas in action - - + +
+
+
+
+ Production + Collaboration
+ + {{ partial "cube-scene.html" (dict "Ordinal" 1) }} +
-
-
+ +
- -
\ No newline at end of file + + +
+
\ No newline at end of file diff --git a/static/js/cube-scene.js b/static/js/cube-scene.js new file mode 100644 index 0000000..1f3a6c3 --- /dev/null +++ b/static/js/cube-scene.js @@ -0,0 +1,158 @@ +const THREE = window.THREE; + +const TECH_LOGOS = window.TECH_LOGOS || []; +const SIMPLE_ICONS = window.SIMPLE_ICONS || []; +const CENTER_ICON_SVG = window.CENTER_ICON_SVG || ""; + +async function createIconTexture(svgString, isCenter = false) { + + const size = isCenter ? 256 : 128; + + const canvas = document.createElement("canvas"); + canvas.width = size; + canvas.height = size; + + const ctx = canvas.getContext("2d"); + + ctx.fillStyle = "rgba(6,12,12,0.9)"; + ctx.fillRect(0, 0, size, size); + + ctx.strokeStyle = "#5FCDB8"; + ctx.lineWidth = 4; + ctx.strokeRect(2, 2, size - 4, size - 4); + + return new Promise((resolve) => { + + if (!svgString) { + const texture = new THREE.CanvasTexture(canvas); + resolve(texture); + return; + } + + const img = new Image(); + + const blob = new Blob([svgString], { type: "image/svg+xml" }); + const url = URL.createObjectURL(blob); + + img.onload = () => { + + const padding = isCenter ? 40 : 20; + const drawSize = size - padding * 2; + + ctx.drawImage(img, padding, padding, drawSize, drawSize); + + const texture = new THREE.CanvasTexture(canvas); + texture.colorSpace = THREE.SRGBColorSpace; + + URL.revokeObjectURL(url); + + resolve(texture); + }; + + img.src = url; + + }); +} + +async function initCubeScene(containerId) { + + console.log("🚀 Initializing cube scene:", containerId); + + const container = document.getElementById(containerId); + if (!container) { + console.error("Container not found:", containerId); + return; + } + + const canvas = container.querySelector("canvas"); + + const scene = new THREE.Scene(); + + const width = container.clientWidth || 800; + const height = 600; + + const camera = new THREE.PerspectiveCamera(60, width / height, 0.1, 1000); + camera.position.set(0, 2, 7); + + const renderer = new THREE.WebGLRenderer({ + canvas, + alpha: true, + antialias: true + }); + + renderer.setSize(width, height); + renderer.setPixelRatio(window.devicePixelRatio); + + console.log("Logos loaded:", TECH_LOGOS.length); + + // CENTER CUBE + + const centerTex = await createIconTexture(CENTER_ICON_SVG, true); + + const centerGeo = new THREE.BoxGeometry(1.6, 1.6, 1.6); + const centerMat = new THREE.MeshBasicMaterial({ + map: centerTex, + transparent: true + }); + + const centerCube = new THREE.Mesh(centerGeo, centerMat); + scene.add(centerCube); + + // ORBITING CUBES + + const orbiters = []; + + for (let i = 0; i < 12; i++) { + + const logoIndex = TECH_LOGOS.length ? i % TECH_LOGOS.length : 0; + const tex = await createIconTexture(TECH_LOGOS[logoIndex]); + + const geo = new THREE.BoxGeometry(0.7, 0.7, 0.7); + + const mat = new THREE.MeshBasicMaterial({ + map: tex, + transparent: true + }); + + const cube = new THREE.Mesh(geo, mat); + + scene.add(cube); + + orbiters.push({ + cube, + angle: (i / 12) * Math.PI * 2, + radius: 3 + (i % 3) * 0.5, + speed: 0.3 + }); + } + + const clock = new THREE.Clock(); + + function animate() { + + const t = clock.getElapsedTime(); + + centerCube.rotation.y = t * 0.3; + + orbiters.forEach((o) => { + + const a = o.angle + t * o.speed; + + o.cube.position.x = Math.cos(a) * o.radius; + o.cube.position.z = Math.sin(a) * o.radius; + o.cube.position.y = Math.sin(t) * 0.5; + + o.cube.rotation.x += 0.01; + o.cube.rotation.y += 0.01; + }); + + renderer.render(scene, camera); + + requestAnimationFrame(animate); + } + + animate(); +} + +// expose globally +window.initCubeScene = initCubeScene; \ No newline at end of file diff --git a/static/js/tech-logos.js b/static/js/tech-logos.js new file mode 100644 index 0000000..ed1b28f --- /dev/null +++ b/static/js/tech-logos.js @@ -0,0 +1,267 @@ +const KEPPEL_LIGHT = '#5FCDB8'; +const KEPPEL_PRIMARY = '#3AB09E'; +const KEPPEL_GLOW = '#4ECBB8'; +const BACKGROUND_COLOR = '#060c0c'; + +const KEPPEL_FILL = 'rgba(58, 176, 158, 0.3)'; +const KEPPEL_FILL_LIGHT = 'rgba(95, 205, 184, 0.25)'; + +// Center cube icon - Server/Data center + const CENTER_ICON_SVG = ` + + + + +`; + +// 28 unique tech-themed SVG logos in Keppel colors with fills +const TECH_LOGOS = [ + // 1. AWS-style cloud + ` + + `, + + // 2. Kubernetes wheel + ` + + + + + + + + + + + `, + + // 3. Docker whale + ` + + + + + + `, + + // 4. Terraform style + ` + + + + `, + + // 5. Lambda symbol + ` + + + + `, + + // 6. Database cylinder + ` + + + + + `, + + // 7. Shield security + ` + + + `, + + // 8. Network nodes + ` + + + + + + + `, + + // 9. API brackets + ` + + + + `, + + // 10. Gear settings + ` + + + + `, + + // 11. Server rack + ` + + + + + + + `, + + // 12. Git branch + ` + + + + + + `, + + // 13. Terminal/CLI + ` + + + + `, + + // 14. Code/script + ` + + + + `, + + // 15. Lock/security + ` + + + + + `, + + // 16. Microservices/hexagon + ` + + + `, + + // 17. Pipeline/flow + ` + + + + + + + `, + + // 18. Monitoring/chart + ` + + + `, + + // 19. Ansible/automation + ` + + + + + `, + + // 20. Container/box + ` + + + + + `, + + // 21. Prometheus/fire + ` + + + `, + + // 22. Grafana/dashboard + ` + + + + + `, + + // 23. Redis/keyvalue + ` + + + + + + `, + + // 24. Jenkins/butler + ` + + + + + + `, + + // 25. Vault/safe + ` + + + + + `, + + // 26. Elasticsearch/search + ` + + + + + + `, + + // 27. Kafka/streaming + ` + + + + + + + + `, + + // 28. Nginx/server + ` + + + + `, +]; + +// Simple icons for opposite faces +const SIMPLE_ICONS = [ + ``, + ``, + ``, + ``, + ``, + ``, + ``, + ``, + ``, + ``, + ``, + ``, +]; + +// expose variables globally for non-module scripts +if (typeof window !== 'undefined') { + window.TECH_LOGOS = TECH_LOGOS; + window.SIMPLE_ICONS = SIMPLE_ICONS; + window.CENTER_ICON_SVG = CENTER_ICON_SVG; +} \ No newline at end of file