first commit

This commit is contained in:
NicholaiVogel 2025-10-02 16:01:12 -06:00
commit a26c102fb5
131 changed files with 5044 additions and 0 deletions

BIN
.vs/BiohazardWeb/v16/.suo Normal file

Binary file not shown.

View File

@ -0,0 +1,9 @@
{
"ExpandedNodes": [
"",
"\\css",
"\\js"
],
"SelectedNode": "\\index.html",
"PreviewInSolutionExplorer": false
}

BIN
.vs/slnx.sqlite Normal file

Binary file not shown.

214
admin.html Normal file
View File

@ -0,0 +1,214 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Admin — Biohazard VFX</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="stylesheet" href="css/styles.css">
<style>
#admin.content-block {
max-width: 700px;
margin: 0 auto;
padding: 12vh var(--gutter-x) 8vh;
font-family: "SpaceMono", monospace;
color: var(--acid);
text-shadow: 0 0 2px var(--acid);
border-bottom: 1px solid var(--graphite);
background: none;
min-height: 60vh;
}
.admin-title {
font-family: "ArchiveCond", sans-serif;
font-size: 2rem;
text-transform: uppercase;
letter-spacing: 0.04em;
color: var(--hot);
margin-bottom: 1.2rem;
margin-top: 0;
text-align: center;
text-shadow: 0 0 2px var(--acid);
}
.admin-section {
margin-top: 2.5rem;
background: var(--graphite);
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.18);
padding: 2rem 1.5rem 1.2rem;
color: var(--acid);
font-family: "SpaceMono", monospace;
max-width: 600px;
margin-left: auto;
margin-right: auto;
}
.admin-section h2 {
font-family: "ArchiveCond", sans-serif;
font-size: 1.1rem;
text-transform: uppercase;
letter-spacing: 0.04em;
color: var(--hot);
margin-bottom: 0.7rem;
margin-top: 0;
text-align: left;
}
.admin-section p {
font-size: 1rem;
color: var(--acid);
opacity: 0.88;
margin: 0;
line-height: 1.5;
padding-left: 0.2em;
}
.admin-warning {
color: #ffb6b6;
background: #3d1a1a;
border: 1px solid #c66;
border-radius: 6px;
padding: 1em;
margin-bottom: 2em;
text-align: center;
font-size: 1.1em;
}
.admin-cms {
background: var(--graphite);
border-radius: 10px;
box-shadow: 0 4px 18px rgba(0,0,0,0.28);
padding: 2.5rem 2rem 2rem;
max-width: 400px;
width: 100%;
margin: 2rem auto;
border: 1px solid #222;
}
.admin-cms h2 {
color: var(--acid);
font-size: 1.3rem;
text-align: center;
margin-bottom: 1.5rem;
letter-spacing: 0.04em;
text-transform: uppercase;
font-family: 'ArchiveCond', sans-serif;
}
.admin-cms label {
font-size: 0.9rem;
color: var(--acid);
margin-top: 1rem;
display: block;
}
.admin-cms input,
.admin-cms select {
width: 100%;
padding: 0.7em;
margin-top: 0.3rem;
border: 1px solid var(--graphite);
border-radius: 5px;
background: var(--dark);
color: var(--acid);
font-family: "SpaceMono", monospace;
font-size: 1rem;
}
.admin-cms button {
width: 100%;
padding: 0.7em;
background: #00ffe7;
color: #181818;
border: none;
border-radius: 5px;
font-size: 1.1em;
font-family: inherit;
font-weight: bold;
cursor: pointer;
box-shadow: 0 2px 8px #00ffe733;
transition: background 0.2s, color 0.2s;
margin-top: 1.5rem;
}
.admin-cms .feedback {
text-align: center;
font-size: 1.5em;
margin-top: 1em;
min-height: 2em;
letter-spacing: 0.1em;
}
</style>
</head>
<body>
<div id="nav-placeholder"></div>
<main id="admin" class="content-block">
<form class="admin-cms" id="projectForm" autocomplete="off">
<h2>Add New Project</h2>
<label for="title">Title*</label>
<input type="text" id="title" name="title" required maxlength="80" autocomplete="off">
<label for="size">Size</label>
<select id="size" name="size">
<option value="small" selected>Small</option>
<option value="big">Big</option>
<option value="wide">Wide</option>
<option value="tall">Tall</option>
<option value="banner">Banner</option>
</select>
<label for="embed">Embed URL*</label>
<input type="url" id="embed" name="embed" required placeholder="https://player.vimeo.com/..." autocomplete="off">
<label for="thumb">Thumbnail*</label>
<input type="file" id="thumb" name="thumb" accept=".jpg,.jpeg,.png,.webp,.gif" required>
<label for="info">Project Info</label>
<textarea id="info" name="info" rows="3" style="width:100%;padding:0.7em;margin-top:0.3rem;border:1px solid var(--graphite);border-radius:5px;background:var(--dark);color:var(--acid);font-family:'SpaceMono',monospace;font-size:1rem;"></textarea>
<label for="credits">Credits</label>
<textarea id="credits" name="credits" rows="3" style="width:100%;padding:0.7em;margin-top:0.3rem;border:1px solid var(--graphite);border-radius:5px;background:var(--dark);color:var(--acid);font-family:'SpaceMono',monospace;font-size:1rem;"></textarea>
<button type="submit">Add Project</button>
<div class="feedback" id="feedback"></div>
</form>
</main>
<div id="socials-placeholder"></div>
<footer>
<p>© 2025 Biohazard VFX. All Rights Reserved.</p>
</footer>
<script>
// Inject nav fragment
fetch('fragments/nav.html')
.then(r => r.text())
.then(html => {
document.getElementById('nav-placeholder').outerHTML = html;
});
// Inject socials fragment
fetch('fragments/socials.html')
.then(r => r.text())
.then(html => {
document.getElementById('socials-placeholder').outerHTML = html;
});
// Micro-CMS form logic
const API_URL = '/api/projects';
const API_KEY = 'YOUR_API_SECRET_HERE'; // <-- Set this to your API_SECRET
const form = document.getElementById('projectForm');
const feedback = document.getElementById('feedback');
form.addEventListener('submit', async (e) => {
e.preventDefault();
feedback.textContent = '';
feedback.className = 'feedback';
const fd = new FormData(form);
try {
const res = await fetch(API_URL, {
method: 'POST',
headers: { 'x-api-key': API_KEY },
body: fd
});
const data = await res.json();
if (data.ok) {
feedback.textContent = '✅ Project added!';
feedback.classList.add('ok');
form.reset();
} else {
feedback.textContent = '❌ ' + (data.error || 'Error');
feedback.classList.add('err');
}
} catch (err) {
feedback.textContent = '❌ Network error';
feedback.classList.add('err');
}
});
</script>
</body>
</html>

132
admin/api.js Normal file
View File

@ -0,0 +1,132 @@
/**
* Biohazard VFX Micro-CMS API
*
* Installation:
* npm i express multer dotenv
*
* Usage:
* PORT=4000 node admin/api.js
* # or use PM2 for process management
*
* Reverse Proxy:
* Proxy /api/* to http://localhost:4000 via Nginx Proxy Manager.
*
* Security:
* Protect /admin/* and /api/* with basic-auth or Cloudflare Zero-Trust.
*
* SMB Share:
* Ensure new files are world-readable (0644) for Nginx.
*/
const express = require('express');
const multer = require('multer');
const fs = require('fs');
const fsp = require('fs/promises');
const path = require('path');
const dotenv = require('dotenv');
dotenv.config();
const API_SECRET = process.env.API_SECRET;
const PORT = process.env.PORT || 4000;
const PROJECTS_JSON = path.resolve(__dirname, '../projects/projects.json');
const PROJECTS_DIR = path.resolve(__dirname, '../projects');
const TMP_DIR = path.resolve(__dirname, 'tmp');
if (!API_SECRET) {
console.error('Missing API_SECRET in environment.');
process.exit(1);
}
if (!fs.existsSync(TMP_DIR)) fs.mkdirSync(TMP_DIR, { recursive: true });
const app = express();
app.use(express.json());
const upload = multer({ dest: TMP_DIR });
function slugify(str) {
return str
.toLowerCase()
.replace(/[^a-z0-9]+/g, '_')
.replace(/^_+|_+$/g, '')
.slice(0, 30);
}
function safeExt(filename) {
const ext = path.extname(filename).toLowerCase();
return ext.match(/\.(jpg|jpeg|png|webp|gif)$/) ? ext : '.jpg';
}
function requireApiKey(req, res, next) {
if (req.headers['x-api-key'] !== API_SECRET) {
return res.status(401).json({ ok: false, error: 'Unauthorized' });
}
next();
}
app.get('/api/projects', async (req, res) => {
try {
const data = await fsp.readFile(PROJECTS_JSON, 'utf8');
res.type('json').send(data);
} catch (e) {
res.status(500).json({ ok: false, error: 'Cannot read manifest.' });
}
});
app.post('/api/projects', requireApiKey, upload.single('thumb'), async (req, res) => {
try {
const { title, size = 'small', embed, credits = '', info = '' } = req.body;
const file = req.file;
if (!title || !embed || !file) {
if (file) fs.unlinkSync(file.path);
return res.status(400).json({ ok: false, error: 'Missing required fields.' });
}
// Load manifest
let manifest = [];
try {
manifest = JSON.parse(await fsp.readFile(PROJECTS_JSON, 'utf8'));
} catch (e) {}
const index = String(manifest.length + 1).padStart(2, '0');
const slug = slugify(title);
const folderName = `${index}_${slug}`;
const folderPath = path.join(PROJECTS_DIR, folderName);
await fsp.mkdir(folderPath, { recursive: true });
// Move thumbnail
const ext = safeExt(file.originalname);
const thumbName = `thumbnail${ext}`;
const thumbDest = path.join(folderPath, thumbName);
await fsp.rename(file.path, thumbDest);
await fsp.chmod(thumbDest, 0o644);
// Write info.txt and credits.txt with provided content
await fsp.writeFile(path.join(folderPath, 'info.txt'), info, { mode: 0o644 });
await fsp.writeFile(path.join(folderPath, 'credits.txt'), credits, { mode: 0o644 });
// Build new project object
const newObj = {
id: folderName,
title,
thumbnail: `projects/${folderName}/${thumbName}`,
size,
embed,
credits: `projects/${folderName}/credits.txt`,
info: `projects/${folderName}/info.txt`
};
manifest.push(newObj);
await fsp.writeFile(PROJECTS_JSON, JSON.stringify(manifest, null, 2), { mode: 0o644 });
res.json({ ok: true });
} catch (err) {
if (req.file && fs.existsSync(req.file.path)) fs.unlinkSync(req.file.path);
res.status(500).json({ ok: false, error: 'Server error.' });
}
});
app.listen(PORT, () => {
console.log(`Biohazard VFX micro-CMS running on port ${PORT}`);
});

138
contact.html Normal file
View File

@ -0,0 +1,138 @@
<!DOCTYPE html><html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Connect</title>
<link rel="stylesheet" href="css/styles.css">
</head>
<body>
<!-- =======================================================
NAV
======================================================= -->
<header class="nav">
<a href="index.html" class="logo">BIOHAZARD VFX</a>
<nav>
<a href="index.html#about">About</a>
<!-- <a href="index.html#case-studies">Case Studies</a> -->
<a href="index.html#projects">Work</a>
<a href="contact.html">Connect</a>
<a href="faq.html">FAQ</a>
<a href="index.html#team">Crew</a>
</nav>
</header>
<script>
(function(){
const overlay=document.createElement('div');
overlay.id='loader-overlay';
overlay.innerHTML='<div class="spinner"></div>';
document.body.appendChild(overlay);
window.addEventListener('load',()=>{overlay.classList.add('hide');setTimeout(()=>overlay.remove(),700);});
})();
</script>
<!-- Inline styles moved to css/styles.css -->
<!-- CONTACT SECTION -->
<section id="contact" class="section">
<h2>CONNECT</h2>
<form id="contactForm" action="https://api.web3forms.com/submit" method="POST" autocomplete="on">
<input type="hidden" name="access_key" value="cf89ee88-fb13-4091-ac7c-96249aa34eb0">
<input type="hidden" name="subject" value="New Contact Form Submission from Biohazard VFX">
<input type="hidden" name="from_name" id="from_name">
<input type="hidden" name="formatted_message" id="formatted_message">
<!-- Honeypot field for spam protection -->
<input type="text" name="website" class="honeypot" tabindex="-1" autocomplete="off">
<div class="form-row">
<div class="form-col">
<label for="first_name">First Name</label>
<input type="text" id="first_name" name="first_name" placeholder="First Name" required aria-required="true">
</div>
<div class="form-col">
<label for="last_name">Last Name</label>
<input type="text" id="last_name" name="last_name" placeholder="Last Name" required aria-required="true">
</div>
</div>
<label for="email">Email</label>
<input type="email" id="email" name="email" placeholder="Your Email" required aria-required="true">
<label for="user_subject">Subject</label>
<input type="text" id="user_subject" name="user_subject" placeholder="Subject" required aria-required="true">
<label for="message">Message</label>
<textarea id="message" name="message" rows="6" placeholder="Yap yap yap... spit your truth, we're all ears." required aria-required="true"></textarea>
<button type="submit">Send Message</button>
<div id="formMessage" class="form-message"></div>
<div class="contact-info">
<p>We usually reply within 24 hours.<br>Email: <a href="mailto:contact@biohazardvfx.com">contact@biohazardvfx.com</a></p>
</div>
</form>
</section>
<div id="socials-placeholder"></div>
<script>
// Inject nav fragment
fetch('fragments/nav.html')
.then(r => r.text())
.then(html => {
document.getElementById('nav-placeholder').outerHTML = html;
});
// Inject socials fragment
fetch('fragments/socials.html')
.then(r => r.text())
.then(html => {
document.getElementById('socials-placeholder').outerHTML = html;
});
// Custom feedback for form submission
const form = document.getElementById('contactForm');
const msg = document.getElementById('formMessage');
form.addEventListener('submit', function(e) {
msg.style.display = 'none';
msg.className = 'form-message';
});
form.addEventListener('submit', function(e) {
e.preventDefault();
// Compose clean message for Web3Forms
const first = form.first_name.value.trim();
const last = form.last_name.value.trim();
const email = form.email.value.trim();
const subject = form.user_subject.value.trim();
const message = form.message.value.trim();
form.from_name.value = `${first} ${last}`;
form.formatted_message.value =
`Name: ${first} ${last}\nEmail: ${email}\nSubject: ${subject}\nMessage:\n${message}`;
const formData = new FormData(form);
fetch(form.action, {
method: 'POST',
body: formData
})
.then(r => r.json())
.then(data => {
if(data.success) {
msg.textContent = 'Thank you! Your message has been sent.';
msg.classList.add('success');
msg.style.display = 'block';
form.reset();
} else {
msg.textContent = 'There was an error sending your message. Please try again.';
msg.classList.add('error');
msg.style.display = 'block';
}
})
.catch(() => {
msg.textContent = 'There was an error sending your message. Please try again.';
msg.classList.add('error');
msg.style.display = 'block';
});
});
</script>
<!-- =======================================================
COPYRIGHT
(static content, copyright notice)
======================================================= -->
<footer>
<p>© 2025 Biohazard VFX. All Rights Reserved.</p>
</footer>
<script src="js/ui.js" defer></script>
</body>
</html>

156
crew.html Normal file
View File

@ -0,0 +1,156 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Crew — Biohazard VFX</title>
<link rel="stylesheet" href="css/styles.css">
<style>
#team.content-block {
max-width: 2000px;
margin-left: auto;
margin-right: auto;
padding: 12vh var(--gutter-x) 8vh;
font-family: "SpaceMono", monospace;
color: var(--acid);
text-shadow: 0 0 2px var(--acid);
border-bottom: 1px solid var(--graphite);
background: none;
}
.team-container {
display: flex;
flex-wrap: wrap;
gap: 2.5rem;
justify-content: center;
margin-top: 3.5rem;
}
.portrait {
background: var(--graphite);
border-radius: 10px;
box-shadow: 0 4px 14px rgba(0,0,0,0.38);
padding: 2.2rem 2rem 1.5rem;
font-size: 1.08rem;
border: 1px solid rgba(80,80,80,0.18);
color: var(--acid);
text-align: center;
width: 270px;
transition: box-shadow .25s, background .25s, transform .2s;
text-decoration: none;
display: flex;
flex-direction: column;
align-items: center;
}
.portrait:hover {
box-shadow: 0 10px 28px rgba(0,0,0,0.5);
background: var(--slate);
transform: translateY(-4px) scale(1.03);
}
.portrait img {
width: 170px;
height: 170px;
object-fit: cover;
border-radius: 18px;
margin-bottom: 1.2rem;
/* Remove border for a cleaner look */
box-shadow: 0 2px 12px rgba(0,0,0,0.18);
border: none;
background: #181818;
}
.portrait h3 {
font-family: "ArchiveCond", sans-serif;
font-size: 1.2rem;
text-transform: uppercase;
letter-spacing: 0.04em;
color: var(--hot);
margin-bottom: 0.5rem;
margin-top: 0;
}
.portrait p {
font-family: "SpaceMono", monospace;
font-size: 1rem;
color: var(--acid);
opacity: 0.88;
margin: 0;
line-height: 1.5;
}
@media (max-width: 900px) {
.team-container {
gap: 1.2rem;
}
.portrait {
width: 100%;
max-width: 350px;
margin: 0 auto;
}
}
@media (max-width: 600px) {
#team.content-block {
padding: 7vh var(--gutter-x) 3vh;
}
.team-container {
flex-direction: column;
gap: 1.1rem;
margin-top: 2rem;
}
.portrait {
padding: 1.2rem 0.7rem 0.7rem;
font-size: 1em;
}
.portrait h3 {
font-size: 1em;
}
}
</style>
</head>
<body>
<script src="js/main.js" defer></script>
<div id="nav-placeholder"></div>
</section>
<section id="team" class="content-block">
<h2>Meet the Crew</h2>
<div class="team-container">
<a class="portrait" href="https://www.instagram.com/nicholai.exe/" target="_blank" aria-label="Nicholai Vogel Instagram">
<img src="images/nicholai.jpg" alt="Nicholai Vogel">
<h3>Nicholai Vogel</h3>
<p>Founder & CEO<br>VFX & CG Supervisor<br><span style="opacity:.7;font-size:.95em">"I just work here."</span></p>
</a>
<a class="portrait" href="https://www.instagram.com/davaneh/" target="_blank" aria-label="Davane Instagram">
<img src="images/davane.jpg" alt="Davane">
<h3>DAVANÉ</h3>
<p>Co-Founder & Executive Producer<br>VFX Supervisor<br><span style="opacity:.7;font-size:.95em">"The Executive"</span></p>
</a>
<a class="portrait" href="https://www.instagram.com/nuke_fx/" target="_blank" aria-label="Parth Gupta Instagram">
<img src="images/parth.jpg" alt="Parth Gupta">
<h3>Parth Gupta</h3>
<p>Co-Founder & Executive Producer<br>Comp Supervisor<br><span style="opacity:.7;font-size:.95em">"Hates Matchmove"</span></p>
</a>
</div>
<section id="crew" class="content-block">
<div class="crew-content">
<!-- Content dynamically injected -->
</div>
</section>
<div id="socials-placeholder"></div>
<footer>
<p>© 2025 Biohazard VFX. All Rights Reserved.</p>
</footer>
<script>
// Inject nav fragment
fetch('fragments/nav.html')
.then(r => r.text())
.then(html => {
document.getElementById('nav-placeholder').outerHTML = html;
});
// Inject socials fragment
fetch('fragments/socials.html')
.then(r => r.text())
.then(html => {
document.getElementById('socials-placeholder').outerHTML = html;
});
</script>
</body>
</html>

55
css/home.css Normal file
View File

@ -0,0 +1,55 @@
/* ───────── css/home.css ───────── */
:root {
/* 1 = 100% (default). Adjust as needed. */
--font-scale: 1.0;
}
html {
font-size: calc(16px * var(--font-scale));
}
/* Page-specific layout overrides for index.html */
.content-block, .section {
max-width: 1100px;
margin: 0 auto 4.5rem;
padding: 8vh var(--gutter-x) 6vh;
background: none;
border-radius: 0.7em;
box-sizing: border-box;
}
main.grid {
max-width: 2000px;
margin-left: auto;
margin-right: auto;
gap: 22px;
}
@media (max-width: 900px) {
.content-block, .section {
padding: 5vh 16px 3vh;
max-width: 98vw;
}
main.grid { max-width: 98vw; gap: 14px; }
}
@media (max-width: 600px) {
.content-block, .section {
padding: 3vh 6px 2vh;
max-width: 100vw;
}
main.grid { max-width: 100vw; gap: 8px; }
}
#projects.grid { margin-bottom: 6rem; }
#about.content-block,
#team.content-block,
#crew.content-block {
margin-top: 4.5rem;
margin-bottom: 1rem;
max-width: 2000px;
margin-left: auto;
margin-right: auto;
}
#pipeline.section {
margin-bottom: 1rem;
max-width: 2000px;
margin-left: auto;
margin-right: auto;
}
footer { margin-top: 1.5rem; padding-bottom: 1.5rem; }

934
css/styles.css Normal file
View File

@ -0,0 +1,934 @@
/* ───────── css/styles.css ───────── */
:root{
--gutter-x: 24px; /* (adjusts margins) */
--vh:1vh;
/* Safety net for JS-less browsers */
--header-h: 64px;
font-family:'Roboto Condensed',sans-serif;
color-scheme:dark;
--acid:#ffffff;
--slate:#000000;
--graphite:#0e0e0e;
--hot:#ffffff;
--ghost:#777;
/* ====== THEME ====== */
--accent:#dbdbdb; /* neon-cyan accent */
--glass:rgba(14,14,14,.55);/* translucent card surface */
--blur:12px; /* backdrop blur strength */
}
*{box-sizing:border-box;margin:0;padding:0}
@media (max-width: 767px){
:root{ --gutter-x: 12px; } /* tighter on phones */
}
@font-face{
font-family:"BiohazardHead";
src:url("../fonts/BebasNeue-Regular.woff2") format("woff2"),
url("../fonts/BebasNeue-Regular.woff") format("woff");
font-weight:400; font-style:normal;
}
@font-face{
font-family:"Machina";
src:url("../fonts/Bebas_Neue/BebasNeue-Regular.ttf") format("opentype");
font-weight:700;
}
@font-face{
font-family:"ArchiveCond";
src:url("..//fonts/JetBrainsMono-2.304/fonts/webfonts/JetBrainsMono-Bold.woff2") format("woff2");
font-weight:500;
}
@font-face{
font-family:"SpaceMono";
src:url("../fonts/Space_Mono/SpaceMono-Regular.ttf") format("opentype");
font-weight:400;
}
body{
background:var(--slate);color:var(--acid);
padding-top: calc(var(--header-h) + 2vh);
}
/* Add horizontal padding only to main content blocks, not header */
.content-block, .section, main.grid {
padding-left: var(--gutter-x);
padding-right: var(--gutter-x);
}
a{text-decoration:none;color:inherit}
img{max-width:100%;display:block}
/* Fix 100 vh on mobile */
@media(max-width:767px){
body,html{height:calc(var(--vh)*100)}
}
header.nav{
position:fixed;top:0;left:0;width:100vw;/* Ensure full viewport width */
z-index:100;
display:flex;flex-direction:column;align-items:center;
background:linear-gradient(to bottom, rgba(11,11,12,0.6) 0%, rgba(11,11,12,0) 100%);
text-shadow:0 0 4px var(--acid);
mix-blend-mode:normal;
font-size:clamp(14px,1.1vw,20px);
margin:0;
box-sizing:border-box;
padding: 8px 0;
}
header.nav nav{
display:flex; gap:24px;
font-family:'ArchiveCond',sans-serif;
}
header.nav .logo{
font-family:"Machina",sans-serif;
font-weight:700; /* bold blocky */
font-size:clamp(40.5px,5.6vw,72px); /* 1.125× larger */
color:var(--acid);
transform:scale(.95);
transition:transform .3s, filter .1s;
letter-spacing:.07em;
text-transform:uppercase;
margin-bottom:2px;
position:relative; /* for underline */
}
header.nav .logo:hover{
transform:scale(1);
}
/* Fancy underline animation for logo */
header.nav .logo::after{
content:"";
position:absolute;left:0;right:0;bottom:0;
height:3px;
background:var(--accent);
transform:scaleX(0);
transform-origin:center center;
transition:transform .25s;
}
header.nav .logo:hover::after{
transform:scaleX(1);
}
header.social{
display:flex; gap:24px;
font-family:'ArchiveCond',sans-serif;
text-shadow:0 0 2px var(--acid);
top:0; left:0; width:100%;
z-index:100; padding:8px 0 0;
display:flex; flex-direction:column; /* stack logo + nav */
align-items:center; /* center horizontally */
mix-blend-mode:normal;
font-size:clamp(14px,1.1vw,20px);
}
footer{
text-align:center;
padding:3rem 1rem;
font-size:.95rem;
opacity:.7;
font-family:"SpaceMono",monospace;
}
#contact {
font-family:"SpaceMono",monospace;
max-width:1300px;
margin:0 auto;
padding:9vh var(--gutter-x) 4vh;
line-height:1.4;
color:var(--acid);
text-shadow:0 0 2px var(--acid);
border-bottom:1px solid var(--graphite);
}
#contact form{
font-family:"SpaceMono",monospace;
max-width:520px;
margin:0 auto;
display:flex;flex-direction:column;gap:1.5rem;
color:var(--acid);
background:var(--glass);
border:1px solid rgba(255,255,255,.06);
box-shadow:0 4px 18px rgba(0,0,0,.4);
backdrop-filter:blur(var(--blur));
-webkit-backdrop-filter:blur(var(--blur));
padding:2rem 1.5rem 1.5rem;
border-radius:10px;
}
#contact label{
font-family:"ArchiveCond",sans-serif;
margin-bottom:.4rem;
font-weight:500;
font-size:1rem;
text-align:left;
color:var(--accent);
letter-spacing:.04em;
text-transform:uppercase;
}
#contact input,
#contact textarea{
font-family:"SpaceMono",monospace;
padding:.75rem 1rem;
margin-bottom:.5rem;
border:1px solid rgba(255,255,255,.12);
background:rgba(0,0,0,.35);
color:var(--acid);
border-radius:4px;
font-size:1rem;
transition:border-color .2s, box-shadow .2s;
resize:vertical;
}
#contact input:focus,
#contact textarea:focus{
border-color:var(--accent);
box-shadow:0 0 6px var(--accent);
outline:none;
}
#contact input[type="submit"]{
font-family:"ArchiveCond",sans-serif;
cursor:pointer;
background:var(--accent);
color:var(--slate);
font-weight:700;
text-transform:uppercase;
letter-spacing:.07em;
border:none;
border-radius:4px;
padding:.85rem 1.5rem;
transition:background .2s,color .2s,box-shadow .2s;
margin-top:.5rem;
}
#contact input[type="submit"]:hover{
background:var(--acid);
color:var(--graphite);
box-shadow:0 2px 12px rgba(0,0,0,.25);
}
#contact button[type="submit"]{
font-family:"ArchiveCond",sans-serif;
cursor:pointer;
background:var(--accent);
color:var(--slate);
font-weight:700;
text-transform:uppercase;
letter-spacing:.07em;
border:none;
border-radius:4px;
padding:.85rem 1.5rem;
transition:background .2s,color .2s,box-shadow .2s;
margin-top:.5rem;
}
#contact button[type="submit"]:hover{
background:var(--acid);
color:var(--graphite);
box-shadow:0 2px 12px rgba(0,0,0,.25);
}
header.nav a{
transition:opacity .3s
}
header.nav a:hover{
opacity:.8
}
/* underline kicker */
header.nav nav a::after{
content:"";
display:block;
height:2px;
background:var(--accent);
transform:scaleX(0);transition:transform .25s;
}
header.nav nav a:hover::after{transform:scaleX(1);}
@media(max-width:767px){
header.nav{top:8px;justify-content:space-between;padding:0 var(--gutter);font-size:12px}
header.nav .logo{margin:0;font-weight:bold}
header.nav nav{gap:16px}
}
/* GRID */
.grid{
--cols:4;
display:grid;
grid-template-columns:repeat(var(--cols),1fr);
gap:24px;
padding-top: calc(var(--header-h)*.5 + 2vh);
counter-reset: item; /* Reset Project counter */
}
@media(max-width:1024px){.grid{--cols:3}}
@media(max-width:767px){.grid{--cols:2;gap:12px;padding:calc(var(--vh)*5) var(--gutter)}}
.item:not(.hidden) h2::before{ /* only real cards */
counter-increment:item;
}
.item{
position:relative;
counter-increment:item;
overflow:hidden;cursor:pointer;
opacity:0;transform:translateY(40px);
transition:opacity .6s,transform .6s;
}
.item.loaded{opacity:1;transform:none}
/* Item Spans */
.item[data-span="banner"] { grid-column: span 3; }
.item[data-span="portrait-xl"]{ grid-row: span 3; }
.item[data-span="wide"] { grid-column: span 2; }
.item[data-span="tall"] { grid-row: span 2; }
.item[data-span="big"] { grid-column: span 2; grid-row: span 2; }
@media(max-width:767px){
.item[data-span="wide"],
.item[data-span="tall"],
.item[data-span="big"]{ grid-column: span 2; grid-row: span 1; }
}
small,code,.mono{font-family:"SpaceMono",monospace}
.item h2{
font-family:"ArchiveCond",sans-serif;
letter-spacing:.04em;
font-size:clamp(12px,.9vw,18px);
text-transform:uppercase;
margin-bottom:8px;
line-height:1.1;
display:inline-block; /* so the counter sits inline */
position:relative;
transition:opacity .5s; /* Only opacity transitions */
}
.item h2::before{
counter-increment:item;
content: counter(item,decimal-leading-zero) " ";
font-family:'Roboto Mono',monospace;
margin-right:.75em;
}
/* Underline kicker for item h2, styled like nav */
.item h2::after {
content: "";
display: block;
height: 2px;
background: var(--accent);
transform: scaleX(0);
transition: transform .25s;
margin-top: 3px;
}
.item:hover h2::after {
transform: scaleX(1);
}
.thumb{
background-size:cover;background-position:center;
padding-bottom:62.5%;
transition:opacity .5s,transform .5s
}
.item:hover .thumb {
opacity:.8;
transform:scale(1.04);
}
.item:hover h2{
opacity:.8;
}
.content-block{
max-width:800px;
margin:0 auto;
padding:20vh var(--gutter);
font-family:'Roboto Mono',monospace;
line-height:1.5;
}
#about{
font-family:"ArchiveCond",sans-serif;
max-width:1300px;
margin:0 auto;
padding:4vh var(--gutter-x) 1vh;
line-height:1.2;
color:var(--acid);
text-shadow:0 0 2px var(--acid);
border-bottom:1px solid var(--graphite);
}
#crew{
font-family:"ArchiveCond",sans-serif;
max-width:1300px;
margin:0 auto;
padding:4vh var(--gutter-x) 4vh;
line-height:1.2;
color:var(--acid);
text-shadow:0 0 2px var(--acid);
border-bottom:1px solid var(--graphite);
}
/* PIPELINE SECTION */
.section h2{
font-family:"ArchiveCond",sans-serif;
font-size:clamp(1.1rem,1.5vw + .5rem,2rem);
text-transform:uppercase;
letter-spacing:.05em;
color:var(--accent);
margin:0 0 2.2rem;
position:relative;
}
.section h2::after{
content:"";
position:absolute;left:0;bottom:-6px;
width:40px;height:2px;
background:var(--accent);
}
#pipeline{
font-family:"ArchiveCond",sans-serif;
padding:4vh var(--gutter-x) 1vh;
line-height:1.2;
}
.pipeline-steps{
gap:var(--gutter-x);
}
.pipeline-step{
border-radius:10px;
}
.pipeline-step {
background: var(--glass);
color: var(--acid);
border-radius: 8px;
box-shadow: 0 4px 18px rgba(0,0,0,.4);
padding: 2rem 1.5rem 1.5rem;
min-width: 220px;
max-width: 320px;
flex: 1 1 220px;
display: flex;
flex-direction: column;
align-items: center;
opacity: 0.97;
transition: transform .25s, box-shadow .25s, opacity .25s;
text-align: center;
}
.pipeline-step:hover {
transform: translateY(-4px) scale(1.028);
box-shadow: 0 10px 32px rgba(0,0,0,.55);
opacity: 1;
}
.pipeline-step h3 {
font-family: "ArchiveCond", sans-serif;
font-size: 1.05rem;
text-transform: uppercase;
letter-spacing: 0.04em;
margin-bottom: 0.5rem;
margin-top: 0;
color: var(--hot);
text-shadow: 0 0 2px var(--acid);
font-weight: 500;
}
.pipeline-step p,
.pipeline-step small {
font-family: "SpaceMono", monospace;
font-size: 0.92rem;
color: var(--acid);
opacity: 0.85;
margin: 0;
line-height: 1.4;
}
@media (max-width: 1024px) {
.pipeline-steps {
gap: 1.2rem;
}
.pipeline-step {
padding: 1.2rem 0.7rem 1rem;
min-width: 160px;
max-width: 240px;
}
}
@media (max-width: 767px) {
#pipeline {
padding: 6vh var(--gutter-x) 2vh;
}
.pipeline-steps {
gap: 0.7rem;
flex-direction: column;
align-items: center;
}
.pipeline-step {
min-width: 0;
max-width: 320px;
width: 100%;
margin: 0 auto;
padding: 1rem 0.5rem 1rem;
}
}
/* TEAM SECTION */
#team {
font-family:"ArchiveCond",sans-serif;
max-width: 1300px;
margin: 0 auto;
padding: 4vh var(--gutter-x) 2.5vh; /* ↓ tighter bottom gap */
line-height: 1.2;
color: var(--acid);
text-shadow: 0 0 2px var(--acid);
border-bottom: 1px solid var(--graphite);
}
.team-container {
display: flex;
gap: var(--gutter-x);
margin: 3rem 0; /* slightly more spacing between heading and founder images */
padding: 0;
justify-content: center;
flex-wrap: wrap;
}
.portrait {
width: clamp(200px, 22vw, 300px);
padding: 0 0 1.25rem;
overflow: hidden; /* allow image to bleed */
background:var(--glass);
color: var(--acid);
border-radius: 10px;
/* glass-card behaviour */
/* duplicate here to avoid extra class */
border:1px solid rgba(255,255,255,.06);
box-shadow:0 4px 18px rgba(0,0,0,.35);
backdrop-filter:blur(var(--blur));
-webkit-backdrop-filter:blur(var(--blur));
transition:transform .25s, box-shadow .25s, opacity .25s;
text-decoration: none;
display: flex;
flex-direction: column;
align-items: center;
opacity: 0.96;
}
.portrait:hover {
transform:translateY(-4px) scale(1.03);
box-shadow:0 10px 32px rgba(0,0,0,.55);
opacity: 1;
}
.portrait img {
width: 100%;
height: auto;
border-radius: 0;
object-fit: cover;
margin: 0;
box-shadow: 0 2px 8px rgba(0,0,0,0.18);
background: var(--slate);
display: block;
}
.portrait h3 {
font-family: "ArchiveCond", sans-serif;
font-size: 1.3rem;
text-transform: uppercase;
letter-spacing: 0.04em;
margin-bottom: 0.25rem;
margin-top: 0;
color: var(--hot);
text-shadow: 0 0 2px var(--acid);
font-weight: 500;
text-align: center;
}
.portrait p {
font-family:"ArchiveCond",sans-serif;
font-size: 1rem;
color: var(--acid);
opacity: 0.85;
text-align: center;
margin: 0;
line-height: 1.4;
}
@media (max-width: 1024px) {
.team-container {
gap: 1.5rem;
}
.portrait {
width: 180px;
padding: 1rem 0.5rem 1rem;
}
.portrait img {
width: 120px;
height: 120px;
}
}
@media (max-width: 767px) {
#team {
padding: 6vh var(--gutter-x) 2vh;
}
.team-container {
gap: 1rem;
}
.portrait {
width: 100%;
max-width: 320px;
margin: 0 auto;
padding: 1rem 0.5rem 1rem;
}
.portrait img {
width: 100px;
height: 100px;
}
}
header.social social a {
position: relative;
display: inline-block;
transition: opacity .3s;
}
header.social social a::after {
content: "";
display: block;
height: 2px;
background: var(--acid);
transform: scaleX(0);
transition: transform 0.25s cubic-bezier(.4,0,.2,1);
margin-top: 2px;
}
header.social social a:hover::after {
transform: scaleX(1);
}
/* -------- GLOBAL GLASS CARD -------- */
.glass-card{
background:var(--glass);
border:1px solid rgba(255,255,255,.06);
box-shadow:0 4px 18px rgba(0,0,0,.4);
backdrop-filter:blur(var(--blur));
-webkit-backdrop-filter:blur(var(--blur));
transition:transform .25s, box-shadow .25s, opacity .25s;
}
.glass-card:hover{
transform:translateY(-4px) scale(1.025);
box-shadow:0 10px 32px rgba(0,0,0,.55);
}
/* -------- SECTION HEADING -------- */
.content-block h2{
font-family:"ArchiveCond",sans-serif;
font-size:clamp(1.1rem,1.5vw + .5rem,2rem);
text-transform:uppercase;
letter-spacing:.05em;
color:var(--accent);
margin:0 0 2.2rem;
position:relative;
}
.content-block h2::after{
content:"";
position:absolute;left:0;bottom:-6px;
width:40px;height:2px;
background:var(--accent);
}
/* ===== NAV accent underline ===== */
header.nav nav a::after{
background:var(--accent);
}
/* ===== GRID card underline ===== */
.item h2::after {
background: var(--accent);
}
/* ===== PORTRAITS ===== */
.portrait {
width: clamp(200px, 22vw, 300px);
padding: 0 0 1.25rem;
overflow: hidden; /* allow image to bleed */
background:var(--glass);
color: var(--acid);
border-radius: 10px;
/* glass-card behaviour */
/* duplicate here to avoid extra class */
border:1px solid rgba(255,255,255,.06);
box-shadow:0 4px 18px rgba(0,0,0,.35);
backdrop-filter:blur(var(--blur));
-webkit-backdrop-filter:blur(var(--blur));
transition:transform .25s, box-shadow .25s, opacity .25s;
text-decoration: none;
display: flex;
flex-direction: column;
align-items: center;
opacity: 0.96;
}
.portrait:hover {
transform:translateY(-4px) scale(1.03);
box-shadow:0 10px 32px rgba(0,0,0,.55);
opacity: 1;
}
.portrait img {
width: 100%;
height: auto;
border-radius: 0;
object-fit: cover;
margin: 0;
box-shadow: 0 2px 8px rgba(0,0,0,0.18);
background: var(--slate);
display: block;
}
.portrait h3 {
font-family: "ArchiveCond", sans-serif;
font-size: 1.3rem;
text-transform: uppercase;
letter-spacing: 0.04em;
margin-bottom: 0.25rem;
margin-top: 0;
color: var(--hot);
text-shadow: 0 0 2px var(--acid);
font-weight: 500;
text-align: center;
}
.portrait p {
font-family:"ArchiveCond",sans-serif;
font-size: 1rem;
color: var(--acid);
opacity: 0.85;
text-align: center;
margin: 0;
line-height: 1.4;
}
@media (max-width: 1024px) {
.team-container {
gap: 1.5rem;
}
.portrait {
width: 180px;
padding: 1rem 0.5rem 1rem;
}
.portrait img {
width: 120px;
height: 120px;
}
}
@media (max-width: 767px) {
#team {
padding: 6vh var(--gutter-x) 2vh;
}
.team-container {
gap: 1rem;
}
.portrait {
width: 100%;
max-width: 320px;
margin: 0 auto;
padding: 1rem 0.5rem 1rem;
}
.portrait img {
width: 100px;
height: 100px;
}
}
header.social social a {
position: relative;
display: inline-block;
transition: opacity .3s;
}
header.social social a::after {
content: "";
display: block;
height: 2px;
background: var(--acid);
transform: scaleX(0);
transition: transform 0.25s cubic-bezier(.4,0,.2,1);
margin-top: 2px;
}
header.social social a:hover::after {
transform: scaleX(1);
}
/* ===== PIPELINE, FAQ, PROJECT GLASS ===== */
.faq-list details,
.project-section{
background:var(--glass);
border:1px solid rgba(255,255,255,.06);
box-shadow:0 4px 18px rgba(0,0,0,.38);
backdrop-filter:blur(var(--blur));
-webkit-backdrop-filter:blur(var(--blur));
}
/* Contact info */
.contact-info{
margin:2em 0 4vh;text-align:center;font-size:1rem;opacity:.7;
font-family:"SpaceMono",monospace;
}
.contact-info a{color:var(--accent);}
/* === BACK TO TOP BUTTON === */
.back-to-top {
position: fixed;
bottom: 2rem;
right: 2rem;
width: 50px;
height: 50px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
z-index: 1000;
opacity: 0;
pointer-events: none;
transform: translateY(20px);
transition: opacity .3s, transform .3s, background .3s;
/* Glass card style */
background: var(--glass);
border: 1px solid rgba(255,255,255,.06);
backdrop-filter: blur(var(--blur));
-webkit-backdrop-filter: blur(var(--blur));
}
.back-to-top.visible {
opacity: .8;
pointer-events: auto;
transform: translateY(0);
}
.back-to-top:hover {
opacity: 1;
background: var(--accent);
}
.back-to-top svg {
width: 24px;
height: 24px;
stroke: var(--accent);
transition: stroke .3s;
}
.back-to-top:hover svg {
stroke: var(--slate);
}
/* === PAGE LOADER === */
#loader-overlay{
position:fixed;inset:0;
background:rgba(0,0,0,.85);
backdrop-filter:blur(6px);
display:flex;align-items:center;justify-content:center;
z-index:9999;
transition:opacity .6s;
}
#loader-overlay.hide{opacity:0;pointer-events:none;}
.spinner{
width:72px;height:72px;
border:6px solid rgba(255,255,255,.15);
border-top-color:var(--accent);
border-radius:50%;
animation:spin 1s linear infinite;
}
@keyframes spin{to{transform:rotate(360deg);}}
/* ===== HERO PARALLAX REFINEMENT ===== */
.hero{
position:relative;
height:100vh;
margin-top:-15vh; /* Remove negative margin to bring video to top */
overflow:hidden;
z-index:1;
}
/* Video as moveable box that behaves as background */
.hero video{
position:absolute;
top:0;
left:0;
width:100%;
height:120vh;
object-fit:cover;
z-index:-2;
pointer-events:none;
transform:translateY(-100vh);
transition:transform 0.1s ease-out;
}
/* Gradient overlay - separate from video, controlled by scroll */
.hero::after{
content:"";
position:absolute;
top:0;
left:0;
width:100%;
height:200vh; /* further extend to mask bottom edge even during video movement */
pointer-events:none;
background:linear-gradient(to bottom,rgba(0,0,0,0) 0%,rgba(0,0,0,0.5) 50%,rgba(0,0,0,1) 100%);
z-index:-1;
opacity:var(--gradient-opacity,100%);
transition:opacity 0.3s;
}
/* Dynamic about section padding */
#about.content-block {
margin-top: 15vh; /* Start with large padding */
max-width: 2000px;
margin-left: auto;
margin-right: auto;
transition: margin-top 0.5s ease-out;
}
/* When about reaches top, reduce padding */
#about.content-block.reduced-padding {
margin-top: -50rem;
}
/* Projects grid spacing */
#projects.grid {
margin-bottom: 4.5rem;
transition: margin-top 0.5s ease-out;
}
/* Content that scrolls over the video */
.content-wrapper {
position: relative;
z-index: 2;
background: transparent;
}
/* Remove black background from content sections */
.content-block, .section, main.grid {
background: transparent;
position: relative;
z-index: 2;
}
/* ===== CONTACT FORM REFINEMENTS ===== */
.form-message{
margin-top:1em;
padding:1em;
border-radius:6px;
font-family:"SpaceMono",monospace;
font-size:1.1em;
text-align:center;
display:none; /* initially hidden */
}
.form-message.success{background:#1a3d1a;color:#b6ffb6;border:1px solid #3c6;}
.form-message.error {background:#3d1a1a;color:#ffb6b6;border:1px solid #c66;}
.honeypot{display:none;}
/* Form layout helpers */
.form-row{display:flex;gap:1em;flex-wrap:wrap;}
.form-col{flex:1 1 120px;min-width:120px;}
@media(max-width:600px){
#contact{padding:4vh 8px 2vh;font-size:1rem;}
#contact form label,
#contact form input,
#contact form textarea{font-size:1em;}
}

259
directory-layout.txt Normal file
View File

@ -0,0 +1,259 @@
BiohazardWeb/
├─ index.html
├─ css
│ ├─ styles.css
├─ fonts/
├─ images/
├─ js
│ ├─ main.js
├─ projects/
│ ├─ 01_PROJ/
│ │ ├─ Videos/
│ │ ├─ thumbnail.jpg
│ │ ├─ info.txt
│ │ ├─ credits.txt
├─ reel/
│ ├─ Reel_July_2025.mp4
├─ written/
│ ├─ about_us.txt
A few notes:
1. The blurring on the header looks weird, maybe add a black gradient to it so things fade as it scrolls past them?
1.1 the inversion of the text looks weird
2. when I hover over the projects I'd like if the text on them transformed with them
2.2 the counter is offset, there are only two projects on the page currently and its starting at 02 and skipping 03 and going straight to 04 for the second One
3. I'd like if the about section had more room on the left and right
3.1 the old about is still at the bottom of the page and needs to be removed
4. I'd like to add a case studies section (added to nav as well)
4.1 I had a case studies section at one point, you can get an idea of it from this:
HTML
/*==================================================
CASE STUDIES
==================================================*/
#case-studies {
text-align: center;
padding-bottom: 1rem;
}
.case-studies-container {
display: flex;
gap: 2.5rem;
justify-content: center;
flex-wrap: wrap;
}
.case-study {
flex: 0 0 404px;
background: var(--box-bg);
border: none; /* Remove border for minimalism */
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
padding: 1rem;
border-radius: 8px;
transition: transform 0.3s ease, box-shadow 0.3s ease;
aspect-ratio: 404 / 316;
}
.case-study:hover {
transform: translateY(-8px) scale(1.02); /* Slight zoom */
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.4);
}
<!-- CASE STUDIES -->
<section id="case-studies" class="section">
<h2>CASE STUDIES</h2>
<div class="case-studies-container">
<div class="case-study">
<iframe src="https://www.behance.net/embed/project/211902289?ilo0=1" height="316" width="404" allowfullscreen lazyload frameborder="0" allow="clipboard-write" refererPolicy="strict-origin-when-cross-origin"></iframe>
</div>
<div class="case-study">
<iframe src="https://www.behance.net/embed/project/207065443?ilo0=1" height="316" width="404" allowfullscreen lazyload frameborder="0" allow="clipboard-write" refererPolicy="strict-origin-when-cross-origin"></iframe>
</div>
<div class="case-study">
<iframe src="https://www.behance.net/embed/project/214528309?ilo0=1" height="316" width="404" allowfullscreen lazyload frameborder="0" allow="clipboard-write" refererPolicy="strict-origin-when-cross-origin"></iframe>
</div>
</div>
</section>
5. I think it would also be nice to have a section dedicated to the founders, I had a previous version on an older version of the sight,
it would look good right above contact, heres an example:
/*==================================================
THE CREW
==================================================*/
#team {
max-width: 1200px;
margin: 0 auto 12rem;
text-align: center;
}
.team-container {
display: flex;
gap: 2rem;
justify-content: space-evenly;
align-items: center;
}
#team .team-container a.portrait {
display: block;
width: 350px;
height: 400px;
text-decoration: none;
color: inherit;
background: var(--box-bg);
padding: 1rem;
border-radius: 8px;
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
#team .team-container a.portrait:hover {
transform: translateY(-5px);
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
}
#team .team-container a.portrait img {
width: 200px;
height: 200px;
object-fit: cover;
border-radius: 8px;
margin-bottom: 1rem;
border: 3px solid var(--accent);
transition: border-color 0.3s ease;
}
#team .team-container a.portrait:hover img {
border-color: var(--hover-accent); /* Change border on hover */
}
#team .team-container a.portrait h3 {
font-size: 1.6rem;
font-weight: 700;
margin-bottom: 0.75rem;
color: var(--accent);
}
#team .team-container a.portrait p {
font-size: 1rem;
font-style: italic;
opacity: 0.85;
}
<!-- THE CREW -->
<section id="team" class="section">
<h2>THE CREW</h2>
<div class="team-container">
<a class="portrait" href="https://www.instagram.com/nicholai.exe/" target="_blank">
<img src="assets/nicholai.jpg" alt="Nicholai Vogel" />
<h3>Nicholai Vogel</h3>
<p>Founder & CEO<br>VFX Supervisor & Lead Generalist (9 yrs)</p>
</a>
<a class="portrait" href="https://www.instagram.com/nuke_fx/" target="_blank">
<img src="assets/parth.jpg" alt="Parth Gupta" />
<h3>Parth Gupta</h3>
<p>Head of Production<br>Lead Compositor & Coordinator<br><em>(Partner, 3 yrs)</em></p>
</a>
<a class="portrait" href="https://www.instagram.com/davaneh/" target="_blank">
<img src="assets/davane.jpg" alt="Davane" />
<h3>Davane</h3>
<p>Executive Producer<br>Compositor<br><em>(Partner, 1 yr)</em></p>
</a>
</div>
</section>
6. I think it would also be good to revamp the contact section, right now its too barebones, something like this would be cool but maybe as its own separate page actually, I don't want this one to get too long.
HTML
/*==================================================
CONTACT SECTION
==================================================*/
#contact { text-align: center; }
#contact form {
max-width: 700px;
margin: 0 auto;
display: flex;
flex-direction: column;
}
#contact label {
margin-bottom: 0.75rem;
font-weight: 700;
font-size: 1.1rem;
text-align: left;
}
#contact input,
#contact textarea {
padding: 1rem;
margin-bottom: 1.75rem;
border: none;
background: var(--contact-bg);
color: #FFFFFF;
border-radius: 4px;
font-size: 1rem;
transition: background 0.3s ease, box-shadow 0.3s ease;
}
#contact input:focus,
#contact textarea:focus {
background: var(--contact-focus-bg);
box-shadow: 0 0 5px var(--accent); /* Glow effect */
outline: none;
}
#contact input[type="submit"] {
cursor: pointer;
background: var(--accent);
font-weight: 700;
text-transform: uppercase;
letter-spacing: 1px;
transition: background 0.3s ease;
}
#contact input[type="submit"]:hover {
background: var(--contact-hover-bg);
}
.social-bar {
margin-top: 2.5rem;
font-size: 1rem;
}
.social-bar a {
color: #FFFFFF;
text-decoration: none;
margin: 0 15px;
font-weight: 700;
transition: color 0.3s ease;
}
.social-bar a:hover {
color: var(--accent);
}
<!-- CONTACT SECTION -->
<section id="contact" class="section">
<h2>CONNECT</h2>
<form>
<label for="name">Name</label>
<input type="text" id="name" placeholder="Your Name" />
<label for="email">Email</label>
<input type="email" id="email" placeholder="Your Email" />
<label for="message">Message</label>
<textarea id="message" rows="6" placeholder="Yap yap yap... spit your truth, we're all ears."></textarea>
<input type="submit" value="Send Message" />
</form>
<div class="social-bar">
<a href="https://www.instagram.com/biohazardvfx/" target="_blank">Instagram</a>
<a href="https://vimeo.com/" target="_blank">Vimeo</a>
<a href="https://youtube.com/" target="_blank">YouTube</a>
<a href="https://www.behance.net/" target="_blank">Behance</a>
</div>
</section>
7. Lastly, I think we should add a nice little footer.
HTML:
/*==================================================
FOOTER
==================================================*/
footer {
text-align: center;
padding: 3rem 1rem;
font-size: 0.95rem;
opacity: 0.7;
} /*==================================================
FOOTER
==================================================*/
footer {
text-align: center;
padding: 3rem 1rem;
font-size: 0.95rem;
opacity: 0.7;
}
<!-- FOOTER -->
<footer>
<p>© 2025 Biohazard VFX. All Rights Reserved.</p>
</footer>
</div>

158
faq.html Normal file
View File

@ -0,0 +1,158 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>FAQ — Biohazard VFX</title>
<link rel="stylesheet" href="css/styles.css">
<style>
#faq.content-block {
max-width: 2000px;
margin: 0 auto;
padding: 12vh var(--gutter-x) 8vh;
font-family: "SpaceMono", monospace;
color: var(--acid);
text-shadow: 0 0 2px var(--acid);
border-bottom: 1px solid var(--graphite);
background: none;
}
.faq-list {
display: flex;
flex-direction: column;
gap: 2.2rem;
margin-top: 3.5rem;
}
.faq-list details {
background: var(--graphite);
border-radius: 8px;
box-shadow: 0 4px 14px rgba(0,0,0,0.38);
padding: 1.5rem 1.5rem 1.2rem;
font-size: 1.08rem;
transition: box-shadow .25s, background .25s;
border: 1px solid rgba(80,80,80,0.18);
position: relative;
color: var(--acid);
}
.faq-list details[open] {
box-shadow: 0 10px 28px rgba(0,0,0,0.5);
background: var(--slate);
}
.faq-list summary {
font-family: "ArchiveCond", sans-serif;
font-size: 1.1rem;
text-transform: uppercase;
letter-spacing: 0.04em;
color: var(--hot);
cursor: pointer;
outline: none;
margin-bottom: 0.7rem;
transition: color .2s;
padding: 0.2em 0;
}
.faq-list details[open] summary {
color: var(--acid);
}
.faq-list p {
font-family: "SpaceMono", monospace;
font-size: 1rem;
color: var(--acid);
opacity: 0.88;
margin: 0;
line-height: 1.5;
padding-left: 0.2em;
}
.faq-list details[open] > .faq-anim {
max-height: 500px;
opacity: 1;
transition: max-height 0.5s cubic-bezier(.4,0,.2,1), opacity 0.4s;
}
.faq-anim {
overflow: hidden;
max-height: 0;
opacity: 0;
transition: max-height 0.5s cubic-bezier(.4,0,.2,1), opacity 0.4s;
will-change: max-height, opacity;
}
@media (max-width: 767px) {
#faq.content-block {
padding: 7vh var(--gutter-x) 3vh;
}
.faq-list {
gap: 1.1rem;
margin-top: 2rem;
}
.faq-list details {
padding: 1rem 0.7rem 0.7rem;
font-size: 1em;
}
.faq-list summary {
font-size: 1em;
}
}
</style>
</head>
<body>
<script>
(function(){
const overlay=document.createElement('div');
overlay.id='loader-overlay';
overlay.innerHTML='<div class="spinner"></div>';
document.body.appendChild(overlay);
window.addEventListener('load',()=>{overlay.classList.add('hide');setTimeout(()=>overlay.remove(),700);});
})();
</script>
<div id="nav-placeholder"></div>
<section id="faq" class="content-block">
<h2>Frequently Asked Questions</h2>
<div class="faq-list" id="faq-list"></div>
</section>
<div id="socials-placeholder"></div>
<footer>
<p>© 2025 Biohazard VFX. All Rights Reserved.</p>
</footer>
<script src="js/ui.js" defer></script>
<script>
// Inject nav fragment
fetch('fragments/nav.html')
.then(r => r.text())
.then(html => {
document.getElementById('nav-placeholder').outerHTML = html;
});
// Inject socials fragment
fetch('fragments/socials.html')
.then(r => r.text())
.then(html => {
document.getElementById('socials-placeholder').outerHTML = html;
});
// Load FAQ from JSON
fetch('written/faq.json')
.then(r => r.json())
.then(faqs => {
const list = document.getElementById('faq-list');
list.innerHTML = faqs.map(faq => `
<details>
<summary>${faq.question}</summary>
<div class="faq-anim"><p>${faq.answer}</p></div>
</details>
`).join('');
// Only one FAQ open at a time, and animate dropdowns smoothly
document.querySelectorAll('.faq-list details').forEach((d) => {
d.addEventListener('toggle', function(e) {
if (d.open) {
document.querySelectorAll('.faq-list details').forEach((other) => {
if (other !== d && other.open) other.removeAttribute('open');
});
setTimeout(() => {
const newRect = d.getBoundingClientRect();
const isFullyVisible = newRect.top >= 0 && newRect.bottom <= window.innerHeight;
if (!isFullyVisible) {
d.scrollIntoView({behavior: 'smooth', block: 'start'});
}
}, 520);
}
});
});
});
</script>
</body>
</html>

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

93
fonts/Bebas_Neue/OFL.txt Normal file
View File

@ -0,0 +1,93 @@
Copyright © 2010 by Dharma Type.
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
https://openfontlicense.org
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,93 @@
Copyright 2022 The Instrument Serif Project Authors (https://github.com/Instrument/instrument-serif)
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
https://openfontlicense.org
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

Binary file not shown.

View File

@ -0,0 +1,10 @@
# This is the official list of project authors for copyright purposes.
# This file is distinct from the CONTRIBUTORS.txt file.
# See the latter for an explanation.
#
# Names should be added to this file as:
# Name or Organization <email address>
JetBrains <>
Philipp Nurullin <philipp.nurullin@jetbrains.com>
Konstantin Bulenkov <kb@jetbrains.com>

View File

@ -0,0 +1,93 @@
Copyright 2020 The JetBrains Mono Project Authors (https://github.com/JetBrains/JetBrainsMono)
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
https://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

BIN
fonts/Space_Mono.zip Normal file

Binary file not shown.

93
fonts/Space_Mono/OFL.txt Normal file
View File

@ -0,0 +1,93 @@
Copyright 2016 The Space Mono Project Authors (https://github.com/googlefonts/spacemono)
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
https://openfontlicense.org
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
fonts/Stretch-Sans-Font.zip Normal file

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 145 KiB

Binary file not shown.

Binary file not shown.

BIN
fonts/Vamos-Font.zip Normal file

Binary file not shown.

BIN
fonts/Vamos-Font/vamos.otf Normal file

Binary file not shown.

11
fragments/nav.html Normal file
View File

@ -0,0 +1,11 @@
<header class="nav">
<a href="index.html#top" class="logo">BIOHAZARD VFX</a>
<nav>
<a href="index.html#about" aria-label="About section">About</a>
<!-- <a href="index.html#case-studies">Case Studies</a> -->
<a href="index.html#projects" aria-label="Projects section">Work</a>
<a href="contact.html" aria-label="Contact page">Connect</a>
<a href="faq.html" aria-label="FAQ page">FAQ</a>
<a href="index.html#team" aria-label="Team section">Crew</a>
</nav>
</header>

7
fragments/socials.html Normal file
View File

@ -0,0 +1,7 @@
<header class="social">
<social>
<a href="https://www.instagram.com/biohazardvfx/">Instagram</a>
<a href="https://youtube.com/">YouTube</a>
<a href="https://www.behance.net/">Behance</a>
</social>
</header>

BIN
images/Splash.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 MiB

BIN
images/davane.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

BIN
images/favicon-16x16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 976 B

BIN
images/favicon-32x32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 574 KiB

BIN
images/nicholai.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 MiB

BIN
images/nicholai.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 MiB

BIN
images/parth.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 351 KiB

312
index.html Normal file
View File

@ -0,0 +1,312 @@
<!DOCTYPE html>
<html lang="en" data-current="home">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Biohazard VFX | VFX Studio &amp; Post-Production</title>
<!-- Favicons -->
<link rel="icon" type="image/png" sizes="32x32" href="/images/favicon-32x32.png" />
<link rel="icon" type="image/png" sizes="16x16" href="/images/favicon-16x16.png" />
<!-- SEO Meta -->
<meta name="description" content="Biohazard VFX is a global visual-effects studio delivering world-class VFX supervision, 3D animation, and end-to-end post-production." />
<!-- canonical -->
<link rel="canonical" href="https://biohazardvfx.com/" />
<!-- Preload critical fonts -->
<link rel="preload" href="fonts/BebasNeue-Regular.woff2" as="font" type="font/woff2" crossorigin />
<link rel="preload" href="fonts/Space_Mono/SpaceMono-Regular.ttf" as="font" type="font/ttf" crossorigin />
<meta name="robots" content="index, follow" />
<!-- Open Graph / Facebook -->
<meta property="og:type" content="website" />
<meta property="og:title" content="Biohazard VFX — Visual Effects Studio" />
<meta property="og:description" content="Global visual-effects & post-production studio delivering world-class VFX supervision, 3D animation, and finishing." />
<meta property="og:image" content="/images/Splash.jpg" />
<meta property="og:url" content="https://biohazardvfx.com/" />
<!-- Twitter -->
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="Biohazard VFX — Visual Effects Studio" />
<meta name="twitter:description" content="Global visual-effects & post-production studio delivering world-class VFX supervision, 3D animation, and finishing." />
<meta name="twitter:image" content="/images/Splash.jpg" />
<!-- Structured Data -->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Organization",
"name": "Biohazard VFX",
"url": "https://biohazardvfx.com/",
"logo": "https://biohazardvfx.com/images/favicon-32x32.png",
"contactPoint": [{
"@type": "ContactPoint",
"email": "contact@biohazardvfx.com",
"contactType": "customer service"
}],
"sameAs": [
"https://www.instagram.com/nicholai.exe/",
"https://www.instagram.com/davaneh/",
"https://www.instagram.com/nuke_fx/"
]
}
</script>
<!-- Main stylesheet -->
<link rel="stylesheet" href="css/styles.css" />
<link rel="stylesheet" href="css/home.css" />
<!-- GLOBAL FONT-SIZE CONTROLS ---------------------->
<style id="global-font-scale">
/*
Adjust --font-scale below to make type bigger or smaller everywhere.
1 = 100% (default). 1.1 = +10%. 0.9 = 10%, etc.
*/
:root {
--font-scale: 1.0;
}
html { font-size: calc(16px * var(--font-scale)); }
</style>
<!-- END FONT-SIZE CONTROLS -->
<style>
.content-block, .section {
max-width: 1100px;
margin: 0 auto 4.5rem auto;
padding: 8vh var(--gutter-x) 6vh;
background: none;
border-radius: 0.7em;
box-sizing: border-box;
}
main.grid {
max-width: 2000px;
margin-left: auto;
margin-right: auto;
gap: 22px;
}
@media (max-width: 900px) {
.content-block, .section {
padding: 5vh 16px 3vh;
max-width: 98vw;
}
main.grid {
max-width: 98vw;
gap: 14px;
}
}
@media (max-width: 600px) {
.content-block, .section {
padding: 3vh 6px 2vh;
max-width: 100vw;
}
main.grid {
max-width: 100vw;
gap: 8px;
}
}
#projects.grid {
margin-bottom: 4.5rem;
}
#about.content-block {
margin-top: 4.5rem;
max-width: 2000px;
margin-left: auto;
margin-right: auto;
}
#team.content-block {
margin-top: 4.5rem;
max-width: 2000px;
margin-left: auto;
margin-right: auto;
}
#crew.content-block {
margin-top: 4.5rem;
max-width: 2000px;
margin-left: auto;
margin-right: auto;
}
#pipeline.section {
margin-bottom: 4.5rem;
max-width: 2000px;
margin-left: auto;
margin-right: auto;
}
footer {
margin-top: 2.5rem;
padding-bottom: 1.5rem;
}
</style>
</head>
<body>
<!-- =======================================================
NAV
======================================================= -->
<div id="nav-placeholder"></div>
<!-- HERO SECTION -->
<section id="hero" class="hero">
<video src="videos/reel.mp4" autoplay muted loop playsinline preload="metadata"></video>
</section>
<!-- =======================================================
ABOUT
======================================================= -->
<section id="about" class="content-block" data-speed="0.15">
<div class="about-content">
<!-- Content dynamically injected -->
</div>
</section>
<!-- =======================================================
PROJECT GRID (populated dynamically from /projects)
======================================================= -->
<main id="projects" class="grid" data-speed="0.1"></main>
<!-- =======================================================
TEAM SECTION (portraits)
======================================================= -->
<section id="team" class="content-block" data-speed="0.12">
<h2>Meet The Founders</h2>
<div class="team-container">
<a class="portrait" href="https://www.instagram.com/nicholai.exe/" target="_blank" aria-label="Nicholai Vogel Instagram">
<img src="images/nicholai.jpg" alt="Nicholai Vogel">
<h3>Nicholai Vogel</h3>
<p>Founder & CEO<br>VFX & CG Supervisor<br><span style="opacity:.7;font-size:.95em">"I just work here."</span></p>
</a>
<a class="portrait" href="https://www.instagram.com/davaneh/" target="_blank" aria-label="Davane Instagram">
<img src="images/davane.jpg" alt="Davane">
<h3>DAVANÉ</h3>
<p>Co-Founder & Executive Producer<br>VFX Supervisor<br><span style="opacity:.7;font-size:.95em">"The Executive"</span></p>
</a>
<a class="portrait" href="https://www.instagram.com/nuke_fx/" target="_blank" aria-label="Parth Gupta Instagram">
<img src="images/parth.jpg" alt="Parth Gupta">
<h3>Parth Gupta</h3>
<p>Co-Founder & Executive Producer<br>Comp Lead<br><span style="opacity:.7;font-size:.95em">"Hates Matchmove"</span></p>
</a>
</div>
</section>
<!-- =======================================================
CREW COPY SECTION (text injected from written/crew.txt)
======================================================= -->
<section id="crew" class="content-block" data-speed="0.14">
<div class="crew-content"><!-- Content dynamically injected --></div>
</section>
<!-- =======================================================
Pipeline Section
=======================================================
<section id="pipeline" class="section">
<h2>PIPELINE</h2>
<div class="pipeline-content" id="pipeline-content">
</div>
</section> -->
<div class="contact-info" style="margin-top:2em;text-align:center;font-size:1em;opacity:.7;">
<p>We usually reply within 24 hours.<br>Email: <a href="mailto:contact@biohazardvfx.com">contact@biohazardvfx.com</a></p>
</div>
<!-- Main JS bundle (handles lazy-loading + data fetch) -->
<script src="js/main.js" defer></script>
<script src="js/common.js" defer></script>
<script src="js/ui.js" defer></script>
<script src="js/home.js" defer></script>
<!-- =======================================================
Social Links
======================================================= -->
<div id="socials-placeholder"></div>
<!-- =======================================================
COPYRIGHT
(static content, copyright notice)
======================================================= -->
<footer>
<p>© 2025 Biohazard VFX. All Rights Reserved.</p>
</footer>
<script>
// Inject nav fragment
fetch('fragments/nav.html')
.then(r => r.text())
.then(html => {
document.getElementById('nav-placeholder').outerHTML = html;
});
// Inject socials fragment
fetch('fragments/socials.html')
.then(r => r.text())
.then(html => {
document.getElementById('socials-placeholder').outerHTML = html;
});
// Inject pipeline blurb
fetch('written/pipeline.txt')
.then(r => r.text())
.then(txt => {
document.getElementById('pipeline-content').innerHTML = `<p>${txt}</p>`;
});
// 2. Smooth scroll navigation for anchor links
document.addEventListener('DOMContentLoaded', function() {
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
anchor.addEventListener('click', function(e) {
const targetId = this.getAttribute('href').slice(1);
const target = document.getElementById(targetId);
if(target) {
e.preventDefault();
target.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
});
});
});
// 10. Lazy loading for all images and iframes
document.addEventListener('DOMContentLoaded', function() {
document.querySelectorAll('img, iframe').forEach(el => {
if(!el.hasAttribute('loading')) el.setAttribute('loading', 'lazy');
});
});
// 9. Accessibility: Add ARIA labels to nav links
document.querySelectorAll('header.nav nav a').forEach(link => {
if(!link.hasAttribute('aria-label')) {
link.setAttribute('aria-label', link.textContent.trim());
}
});
// 11. Parallax video effect
document.addEventListener('DOMContentLoaded',()=>{
const hero=document.getElementById('hero');
const about=document.getElementById('about');
const video=hero?.querySelector('video');
if(!hero||!about||!video) return;
const update=()=>{
const scrollY=window.scrollY;
const viewportHeight=window.innerHeight;
const aboutRect=about.getBoundingClientRect();
const aboutTop=aboutRect.top;
// Gradually fade IN gradient as we scroll: 0 when at top, 1 once About approaches hero top
const gradientOpacity = Math.max(0, Math.min(1, (viewportHeight - aboutTop) / (viewportHeight * .0125)));
hero.style.setProperty('--gradient-opacity', gradientOpacity);
// Allow the About block to slide upward and sit over the video. We let the margin become
// slightly negative for an overlapping effect once the user scrolls further.
const aboutPadding=15-(scrollY*0.03); // rem units can go negative for overlap
about.style.marginTop=aboutPadding+'rem';
// Move video box smoothly - no position switching
const videoOffset=Math.max(0,scrollY-viewportHeight/2);
video.style.transform=`translateY(${videoOffset}px)`;
};
update();
window.addEventListener('scroll',update,{passive:true});
window.addEventListener('resize',update,{passive:true});
});
</script>
</body>
</html>

41
js/common.js Normal file
View File

@ -0,0 +1,41 @@
/* ───────── js/common.js ───────── */
(() => {
function injectFragment(id, url) {
const el = document.getElementById(id);
if (!el) return;
fetch(url)
.then(r => r.text())
.then(html => { el.outerHTML = html; })
.catch(console.error);
}
document.addEventListener('DOMContentLoaded', () => {
// Inject shared fragments if placeholders present
injectFragment('nav-placeholder', 'fragments/nav.html');
injectFragment('socials-placeholder', 'fragments/socials.html');
// Smooth scroll for same-page anchors
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
anchor.addEventListener('click', e => {
const targetId = anchor.getAttribute('href').slice(1);
const target = document.getElementById(targetId);
if (target) {
e.preventDefault();
target.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
});
});
// Lazy-load images & iframes
document.querySelectorAll('img, iframe').forEach(el => {
if (!el.hasAttribute('loading')) el.setAttribute('loading', 'lazy');
});
// Ensure nav links have aria-labels
document.querySelectorAll('header.nav nav a').forEach(link => {
if (!link.hasAttribute('aria-label')) {
link.setAttribute('aria-label', link.textContent.trim());
}
});
});
})();

37
js/home.js Normal file
View File

@ -0,0 +1,37 @@
/* ───────── js/home.js ───────── */
document.addEventListener('DOMContentLoaded', () => {
// Hero parallax & gradient effect
const hero = document.getElementById('hero');
const about = document.getElementById('about');
const video = hero?.querySelector('video');
if (hero && about && video) {
const update = () => {
const scrollY = window.scrollY;
const viewportHeight = window.innerHeight;
const aboutTop = about.getBoundingClientRect().top;
const gradientOpacity = Math.max(0, Math.min(1, (viewportHeight - aboutTop) / (viewportHeight * 0.0125)));
hero.style.setProperty('--gradient-opacity', gradientOpacity.toString());
const aboutPadding = 15 - scrollY * 0.03;
about.style.marginTop = `${aboutPadding}rem`;
const videoOffset = Math.max(0, scrollY - viewportHeight / 2);
video.style.transform = `translateY(${videoOffset}px)`;
};
update();
window.addEventListener('scroll', update, { passive: true });
window.addEventListener('resize', update, { passive: true });
}
// Pipeline blurb injection (optional, safe-fail)
const pipeTarget = document.getElementById('pipeline-content');
if (pipeTarget) {
fetch('written/pipeline.txt')
.then(r => r.text())
.then(txt => { pipeTarget.innerHTML = `<p>${txt}</p>`; })
.catch(console.error);
}
});

113
js/main.js Normal file
View File

@ -0,0 +1,113 @@
/* js/main.js
Dynamically builds the project grid + injects about/contact/crew copy.
-----------------------------------------------------------------*/
// Page loader overlay
(function(){
const overlay=document.createElement('div');
overlay.id='loader-overlay';
overlay.innerHTML='<div class="spinner"></div>';
document.addEventListener('DOMContentLoaded',()=>document.body.appendChild(overlay));
window.addEventListener('load',()=>{
overlay.classList.add('hide');
setTimeout(()=>overlay.remove(),700);
});
})();
// Mobile 100vh fix
function setVh(){document.documentElement.style.setProperty('--vh',window.innerHeight*0.01);}setVh();window.addEventListener('resize',setVh);
// Header height fix
function setHeaderOffset(){
const headerEl = document.querySelector('header.nav');
if(!headerEl) return; // Header not in DOM yet abort and wait
const h = headerEl.offsetHeight;
document.documentElement.style.setProperty('--header-h', `${h}px`);
}
setHeaderOffset();
// Run once more after a short delay to catch late-injected nav fragments
setTimeout(setHeaderOffset, 500);
window.addEventListener('resize', setHeaderOffset);
// Fadein intersection observer
const reveal = new IntersectionObserver((entries,o)=>{
entries.forEach(e=>{if(e.isIntersecting){e.target.classList.add('loaded');o.unobserve(e.target);}});
},{threshold:.3});
// Grab the grid once so every scope has it
const grid = document.getElementById('projects');
// Only run project-grid logic on pages that actually have the grid element
if(grid){
// Build card element
function card({id,title,thumbnail,size='small'}){
const a = document.createElement('a');
a.href = `project.html?id=${encodeURIComponent(id)}`;
a.className = 'item';
a.dataset.span = size; // <— MAGIC LINE
a.innerHTML = `
<h2>${title}</h2>
<div class="thumb" style="background-image:url('${thumbnail}')"></div>`;
reveal.observe(a);
return a;
}
// Load manifest
fetch('projects/projects.json')
.then(r=>{if(!r.ok)throw new Error('manifest missing');return r.json();})
.then(list=>{
if(!Array.isArray(list))throw new Error('manifest must be an array');
list.forEach(p=>grid.appendChild(card(p)));
})
.catch(err=>{
console.error('Manifest issue:',err.message);
grid.innerHTML=`<p style="grid-column:1/-1">No projects manifest found.</p>`;
});
}
// Inject About & Contact copy
['about','contact'].forEach(key=>{
const el=document.getElementById(key);
if(!el)return;
fetch(`written/${key}_us.txt`)
.then(r=>r.ok?r.text():Promise.reject())
.then(txt=>{
el.innerHTML=`<h2 style="text-transform:uppercase;margin-bottom:1em">${key}</h2><p>${txt.replace(/\n/g,'<br>')}</p>`;
})
.catch(()=>{});
});
// Inject Crew copy
['crew'].forEach(key=>{
const el=document.getElementById(key);
if(!el)return;
fetch(`written/${key}.txt`)
.then(r=>r.ok?r.text():Promise.reject())
.then(txt=>{
el.innerHTML=`<p>${txt.replace(/\n/g,'<br>')}</p>`;
})
.catch(()=>{});
});
// ───────── SIMPLE PARALLAX HANDLER ─────────
// Applies translateY based on scroll position to any element with a data-speed attribute.
document.addEventListener('DOMContentLoaded', () => {
const layers = Array.from(document.querySelectorAll('[data-speed]'));
if(!layers.length) return;
const update = () => {
const scrollY = window.scrollY;
layers.forEach(el => {
const speed = parseFloat(el.dataset.speed) || 0;
// Negative so layers move opposite to scroll for depth illusion
el.style.transform = `translateY(${ -scrollY * speed }px)`;
});
};
update();
window.addEventListener('scroll', update, { passive: true });
window.addEventListener('resize', update, { passive: true });
});

Some files were not shown because too many files have changed in this diff Show More