BSB interactive chart
This code renders an interactive 3D animated stock chart with candlesticks that rotates on hover, featuring a dark theme and interactive "JOIN"/"VISIT" buttons.
<?php
/*
Template Name: Stock Graph
*/
<?php
/*
Template Name: Stock Graph
*/
// get_header(); // This function call has been commented out to remove the header
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Animated 3D Stock Graph with Enhanced Visuals</title>
<style>
body {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
background-color: #131F23; /* Updated background color */
}
.chart-container {
perspective: 1000px;
position:absolute;
overflow: visible; /* Ensure this is set to visible */
cursor: pointer;
}
#stockChart {
transform: rotateX(60deg) rotateZ(-45deg);
padding-left: 20px;
padding-top: 3%;
padding-bottom:2%;
transition: transform 0.5s ease;
background-color: #2C2A2A;
border-radius: 20px;
overflow: visible; /* Ensure this is set to visible */
}
#stockChart:hover {
transform: rotateX(60deg) rotateZ(0deg);
}
header, footer {
display: none;
}
.chart-title {
position: absolute;
top: -60px;
left: 50%;
transform: translateX(-50%) translateZ(50%) rotateX(60deg) rotateZ(-45deg) rotateY(0deg);
font-family: Helvetica Bold, sans-serif;
font-size: 50px;
font-weight: bold;
color: #CFCABE;
text-shadow: 2px 2px 4px rgba(0,0,0,0.5);
transition: transform 0.5s ease;
}
#stockChart.active {
transform: rotateX(0deg) rotateZ(0deg);
}
.chart-container.active .chart-title {
margin-bottom:10px;
transform: translateX(-50%) rotateX(0deg) rotateZ(0deg) scale(1.35);;
}
.chart-container:not(.active):hover .chart-title {
transform: translateX(-50%) rotateX(60deg) rotateZ(0deg);
}
.action-buttons {
position: absolute;
bottom: -60px;
left: 50%;
transform: translateX(-50%);
display: flex;
padding-top:20px;
margin-top: 20px;
opacity: 0;
transition: opacity 0.5s ease, transform 0.5s ease;
}
.chart-container.active .action-buttons {
opacity: 1;
transform: translateX(-50%) translateY(-20px);
margin-bottom:-15px;
}
.action-button {
padding: 10px 20px;
margin: 0 30px; /* Added margin to create space between buttons */
font-size: 15px;
font-weight: bold;
color: #CFCABE;
background-color: #2C2A2A;
border: none;
border-radius: 5px;
cursor: pointer;
transition: background-color 0.3s ease;
}
.action-button:hover {
background-color: #3E3C3C;
}
.chart-logo {
height: 25vh; /* Set the height to cover the entire viewport height */
position: fixed; /* Position relative to the viewport */
right: 250%;
transform: translateY(100%); /* Start completely off-screen */
transition: transform 0.5s ease, opacity 0.5s ease; /* Smooth transitions for movement and visibility */
opacity: 0; /* Start as invisible */
z-index: -1; /* Keep the logo behind other content until needed */
}
.action-buttons:hover .chart-logo {
position: absolute;
transform: translateY(13%); /* Move to fully visible at original position */
opacity: 1; /* Make logo visible */
z-index: 10; /* Bring the logo to the front when hovered */
}
</style>
</head>
<body>
<div class="chart-container">
<a href="https://staging.quebecstreetbets.com/" class="chart-title-link">
<div class="chart-title">BAYSTREETBETS</div>
</a>
<canvas id="stockChart" width="800" height="400"></canvas>
<div class="action-buttons">
<a href="https://staging.quebecstreetbets.com/register/" class="action-button-link">
<button class="action-button">JOIN</button>
</a>
<a href="https://staging.quebecstreetbets.com/" class="action-button-link">
<button class="action-button">VISIT</button>
</a>
<img src="https://staging.quebecstreetbets.com/wp-content/uploads/2024/01/communityIcon_9mfibkwcd7k51-1.png" class="chart-logo" alt="Site Logo">
</div>
</div>
<script>
const canvas = document.getElementById('stockChart');
const ctx = canvas.getContext('2d');
let data = [];
let prevClose = 10;
let currentCandle = null;
let animationProgress = 0;
function initializeNewCandle() {
const open = prevClose;
const targetClose = open + (Math.random() - 0.5) * 5;
currentCandle = {
time: new Date(),
open: open,
close: open,
high: open,
low: open,
targetClose: targetClose,
maxHigh: open,
minLow: open,
pricePoints: [open]
};
}
function updateCurrentCandle() {
if (!currentCandle) return;
const isRising = currentCandle.targetClose > currentCandle.open;
const volatility = Math.abs(currentCandle.targetClose - currentCandle.open) * 0.1;
// Simulate price oscillation
const newPrice = currentCandle.close + (Math.random() - 0.5) * volatility;
currentCandle.pricePoints.push(newPrice);
// Update close price
currentCandle.close = newPrice;
// Update high and low
currentCandle.high = Math.max(currentCandle.high, newPrice);
currentCandle.low = Math.min(currentCandle.low, newPrice);
// Update max high and min low
currentCandle.maxHigh = Math.max(currentCandle.maxHigh, newPrice);
currentCandle.minLow = Math.min(currentCandle.minLow, newPrice);
}
function finalizeCandle() {
if (!currentCandle) return;
// Set final close price
currentCandle.close = currentCandle.pricePoints[currentCandle.pricePoints.length - 1];
data.push({ ...currentCandle });
if (data.length > 20) data.shift();
prevClose = currentCandle.close;
currentCandle = null;
animationProgress = 0;
}
function drawGrid() {
const candleWidth = canvas.width / 21;
const leftPadding = canvas.width * 0.05; // Adjust left padding for price labels
const topBottomPadding = canvas.height * 0.1; // Adjust padding to avoid overlap with labels
const rightPadding = canvas.width * 0.05; // Adjust right padding for better alignment
const linesVertical = 19; // Number of vertical lines
const linesHorizontal = 10; // Number of horizontal grid lines
ctx.strokeStyle = 'rgba(207, 202, 190, 0.3)'; // Line color
ctx.font = '11px Arial'; // Increase font size for better readability
ctx.fillStyle = 'rgba(207, 202, 190, 0.8)'; // Label color
// Horizontal lines and price labels
const priceRange = getPriceRange();
const priceStep = (priceRange.max - priceRange.min) / linesHorizontal;
const gridBottom = scaleY(priceRange.min); // Bottom of the grid
const gridTop = scaleY(priceRange.max); // Top of the grid
for (let j = 0; j <= linesHorizontal; j++) {
const price = priceRange.max - j * priceStep;
const y = scaleY(price);
ctx.beginPath();
ctx.moveTo(leftPadding, y);
ctx.lineTo(canvas.width - rightPadding, y);
ctx.stroke();
// Price labels, skip the first and last
if (j !== 0 && j !== linesHorizontal) {
ctx.fillText(`$${price.toFixed(2)}`, 2, y + 3); // Move labels to the left of the grid
}
}
// Vertical lines and time labels
for (let i = 0; i <= linesVertical; i++) {
const x = leftPadding + i * candleWidth;
ctx.beginPath();
ctx.moveTo(x, gridTop); // Start at the top of the grid
ctx.lineTo(x, gridBottom); // End at the bottom of the grid
ctx.stroke();
// Time labels adjusted to appear further down below the grid
let minutesToAdd = i * 15;
let hours = 9 + Math.floor((30 + minutesToAdd) / 60);
let minutes = (30 + minutesToAdd) % 60;
let timeLabel = `${hours}:${minutes < 10 ? '0' : ''}${minutes}`;
ctx.fillText(timeLabel, x - ctx.measureText(timeLabel).width / 2, canvas.height - 1); // Position labels below the line with additional padding
}
}
function getPriceRange() {
let minPrice = Math.min(...data.map(d => d.low), currentCandle ? currentCandle.minLow : Infinity);
let maxPrice = Math.max(...data.map(d => d.high), currentCandle ? currentCandle.maxHigh : -Infinity);
const padding = (maxPrice - minPrice) * 0.1; // Add some padding to the range
minPrice -= padding;
maxPrice += padding;
return { min: minPrice, max: maxPrice };
}
function scaleY(price) {
const priceRange = getPriceRange();
return canvas.height - (price - priceRange.min) / (priceRange.max - priceRange.min) * canvas.height;
}
function drawCandle(x, candle, width) {
const isGreen = candle.close > candle.open;
ctx.strokeStyle = isGreen ? '#00ff00' : '#ff0000';
ctx.fillStyle = isGreen ? '#00aa00' : '#aa0000';
// Set shadow color based on candle type
canvas.style.boxShadow = isGreen ? '0 10px 20px rgba(0, 255, 0, 0.2)' : '0 10px 20px rgba(255, 165, 0, 0.2)';
const bodyTop = scaleY(Math.max(candle.open, candle.close));
const bodyBottom = scaleY(Math.min(candle.open, candle.close));
// Draw shadow
ctx.shadowColor = isGreen ? 'rgba(0, 255, 0, 0.2)' : 'rgba(255, 0, 0, 0.2)';
ctx.shadowBlur = 10;
ctx.shadowOffsetX = 5;
ctx.shadowOffsetY = 5;
// Draw upper wick if exists
if (candle.maxHigh > Math.max(candle.open, candle.close)) {
ctx.beginPath();
ctx.moveTo(x + width / 2, bodyTop);
ctx.lineTo(x + width / 2, scaleY(candle.maxHigh));
ctx.stroke();
}
// Draw lower wick if exists
if (candle.minLow < Math.min(candle.open, candle.close)) {
ctx.beginPath();
ctx.moveTo(x + width / 2, bodyBottom);
ctx.lineTo(x + width / 2, scaleY(candle.minLow));
ctx.stroke();
}
// Draw the body
ctx.fillRect(x, bodyTop, width, bodyBottom - bodyTop);
// Reset shadow
ctx.shadowColor = 'transparent';
ctx.shadowBlur = 0;
ctx.shadowOffsetX = 0;
ctx.shadowOffsetY = 0;
}
function scaleY(price) {
const minPrice = Math.min(...data.map(d => d.low), currentCandle ? currentCandle.minLow : Infinity);
const maxPrice = Math.max(...data.map(d => d.high), currentCandle ? currentCandle.maxHigh : -Infinity);
const padding = (maxPrice - minPrice) * 0.1;
return canvas.height - (price - (minPrice - padding)) / ((maxPrice + padding) - (minPrice - padding)) * canvas.height;
}
function drawChart() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Make background transparent
ctx.fillStyle = 'rgba(255, 255, 255, 0)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
drawGrid(); // Draw grid first to be under the candles
const candleWidth = canvas.width / 21;
const leftPadding = canvas.width * 0.05; // 4% left padding
data.forEach((candle, index) => {
drawCandle(leftPadding + index * candleWidth, candle, candleWidth - 2);
});
if (currentCandle) {
drawCandle(leftPadding + data.length * candleWidth, currentCandle, candleWidth - 2);
}
}
let lastUpdateTime = 0;
const updateInterval = 200; // Changed to 200ms
// Modified updateChart function to reset the animation loop
function updateChart(currentTime) {
if (currentTime - lastUpdateTime >= updateInterval) {
if (!currentCandle || animationProgress >= 1) {
if (currentCandle) finalizeCandle();
if (data.length >= 19) {
// Reset the data when the 19th candle is completed
data = [];
prevClose = 100; // Reset the initial closing value
}
initializeNewCandle();
}
updateCurrentCandle();
animationProgress += 0.04; // Doubled the speed of candle formation
drawChart();
lastUpdateTime = currentTime;
}
requestAnimationFrame(updateChart);
}
// Ensure that the animation starts by calling the requestAnimationFrame
requestAnimationFrame(updateChart);
const chartContainer = document.querySelector('.chart-container');
const stockChart = document.getElementById('stockChart');
chartContainer.addEventListener('click', () => {
chartContainer.classList.toggle('active');
stockChart.classList.toggle('active');
});
</script>
</body>
</html>
<?php
// get_footer(); // This function call has been commented out to remove the footer
?>
Video Title
Video description goes here. This is a brief overview of the video content.
Matrix Landing pages
Two pages with interactive text display, designed as a styling exercise set in the Matrix universe.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Follow the White Rabbit</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=DotGothic16&family=Geostar&family=Megrim&family=Stalinist+One&display=swap');
body, html {
margin: 0;
padding: 0;
height: 100%;
background-color: #000;
color: #00ff00;
overflow: hidden;
font-family: 'DotGothic16', monospace;
}
.container {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
height: 100%;
padding: 20px;
box-sizing: border-box;
position: relative;
z-index: 1;
}
#typed-text {
font-size: 24px;
text-align: center;
margin-bottom: 40px;
max-width: 600px;
line-height: 1.4;
font-family: 'Stalinist One', monospace;
opacity: 0;
transform: translateY(20px);
animation: fadeInUp 1s forwards;
}
#key-form {
display: none;
margin-bottom: 20px;
font-family: 'DotGothic16', monospace;
text-align: center;
font-size: 0.9vw;
background-color: rgba(0, 20, 0, 0.8);
border: 2px solid #00ff00;
border-radius: 5px;
padding: 20px;
transform: scale(0.9);
opacity: 0;
transition: all 0.5s ease;
}
label {
display: block;
margin-bottom: 10px;
font-size: 18px;
text-transform: uppercase;
letter-spacing: 2px;
}
input {
background-color: rgba(0, 40, 0, 0.6);
border: 2px solid #00ff00;
color: #00ff00;
padding: 10px 15px;
font-family: 'Stalinist One', monospace;
font-size: 16px;
width: 250px;
transition: all 0.3s ease;
max-width: 100%;
display: block;
margin: 0 auto 20px; /* Added margin-bottom */
}
input:focus {
outline: none;
box-shadow: 0 0 15px rgba(0, 255, 0, 0.5);
}
button {
background-color: rgba(0, 40, 0, 0.6);
border: 2px solid #00ff00;
color: #00ff00;
padding: 10px 20px;
font-family: 'DotGothic16', monospace;
font-size: 16px;
cursor: pointer;
transition: all 0.3s ease;
text-transform: uppercase;
letter-spacing: 2px;
margin-top: 20px;
}
/* New styles for the submit button */
.submit-btn {
display: inline-block;
margin-top: 0px;
margin-bottom: 2px;
}
#send-button {
display: none;
}
button:hover {
background-color: rgba(0, 80, 0, 0.8);
}
@keyframes fadeInUp {
to {
opacity: 1;
transform: translateY(0);
}
}
#white-rabbit {
position: fixed;
width: 50px;
height: 50px;
background-image: url('https://100kstadium.com/wp-content/uploads/2024/10/white-rabbit-1.png');
background-size: contain;
background-repeat: no-repeat;
display: none;
z-index: 1000;
left: 50%;
transform: translateX(-50%);
}
@keyframes jumpDown {
0% { top: -50px; opacity: 0; }
10% { opacity: 1; }
40% { top: calc(50% - 25px); }
60% { top: calc(50% - 25px); }
90% { opacity: 1; }
100% { top: 100%; opacity: 0; }
}
.jump-animation {
display: block !important;
animation: jumpDown 2s ease-in-out forwards;
}
#matrix-rain {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 0;
}
.fade-out {
animation: fadeOut 1s forwards;
}
@keyframes fadeOut {
to {
opacity: 0;
}
}
</style>
</head>
<body>
<canvas id="matrix-rain"></canvas>
<div id="white-rabbit"></div>
<div class="container">
<div id="typed-text"></div>
<form id="key-form">
<label for="key-input">SPEAK THE TRUTH AND PRESS ENTER :</label>
<input type="text" id="key-input" required autocomplete="off" style="-webkit-text-security: disc;">
<button type="submit" class="submit-btn">Submit</button>
</form>
<button id="send-button">FOLLOW THE RABBIT</button>
</div>
<script>
const whimsicalSentence = "The future is now.<br>Captured by the algorithmic matrices birthing the singularity. <br>A world where the mind and machine blend into one.<br> Nothing but lines of code weave the veil of illusion. Hidden behind symbols lie the encrypted truths.<br>Tell me...How is it to be Human?";
const typedTextElement = document.getElementById('typed-text');
const keyForm = document.getElementById('key-form');
const keyInput = document.getElementById('key-input');
const sendButton = document.getElementById('send-button');
const whiteRabbit = document.getElementById('white-rabbit');
const matrixRain = document.getElementById('matrix-rain');
const ctx = matrixRain.getContext('2d');
function setupMatrixRain() {
matrixRain.width = window.innerWidth;
matrixRain.height = window.innerHeight;
const columns = matrixRain.width / 15;
const drops = [];
for (let i = 0; i < columns; i++) {
drops[i] = 1;
}
const draw = () => {
ctx.fillStyle = 'rgba(0, 0, 0, 0.05)';
ctx.fillRect(0, 0, matrixRain.width, matrixRain.height);
ctx.fillStyle = '#0F0';
ctx.font = '15px monospace';
for (let i = 0; i < drops.length; i++) {
const text = String.fromCharCode(Math.random() * 128);
ctx.fillText(text, i * 15, drops[i] * 20);
if (drops[i] * 20 > matrixRain.height && Math.random() > 0.98) {
drops[i] = 0;
}
drops[i]++;
}
};
return draw;
}
function typeText(text, element, callback) {
const sentences = text.split('<br>');
let sentenceIndex = 0;
function typeSentence() {
if (sentenceIndex < sentences.length) {
const sentence = sentences[sentenceIndex];
let charIndex = 0;
element.innerHTML = '';
const intervalId = setInterval(() => {
if (charIndex < sentence.length) {
element.innerHTML += sentence[charIndex];
charIndex++;
} else {
clearInterval(intervalId);
setTimeout(() => {
element.classList.add('fade-out');
setTimeout(() => {
element.classList.remove('fade-out');
element.innerHTML = '';
sentenceIndex++;
typeSentence();
}, 1000);
}, 1000);
}
}, 50);
} else {
if (callback) callback();
}
}
typeSentence();
}
function startSequence() {
const drawMatrixRain = setupMatrixRain();
const rainInterval = setInterval(drawMatrixRain, 50);
setTimeout(() => {
clearInterval(rainInterval);
ctx.clearRect(0, 0, matrixRain.width, matrixRain.height);
typeText(whimsicalSentence, typedTextElement, () => {
setTimeout(() => {
keyForm.style.display = 'block';
keyForm.style.opacity = '1';
keyForm.style.transform = 'scale(1)';
keyInput.focus();
}, 1000);
});
}, 2000);
}
window.addEventListener('load', startSequence);
keyForm.addEventListener('submit', (e) => {
e.preventDefault();
sendButton.style.display = 'inline-block';
});
sendButton.addEventListener('click', () => {
whiteRabbit.style.display = 'block';
whiteRabbit.classList.add('jump-animation');
setTimeout(() => {
window.location.href = '/matrix-intro/';
}, 2000);
});
</script>
</body>
</html>
Video Title
Video description goes here. This is a brief overview of the video content.
MBWAI – 3d persona
The project's mascot is displayed next to the site title. The model responds to cursor movement, and the viewing angle can be adjusted by clicking and dragging the mouse around the character
This is a 3D model that has been created from scratch and is displayed using Three.js and the Spline ecosystem.
This is a 3D model that has been created from scratch and is displayed using Three.js and the Spline ecosystem.
Video Title
Video description goes here. This is a brief overview of the video content.