summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--data/blogs.json17
-rw-r--r--data/blogs/0.md23
-rw-r--r--data/blogs/1.md33
-rw-r--r--data/blogs/altaf-devlog-0.md5
-rw-r--r--data/blogs/ass.md6
-rw-r--r--data/events/alarm_id.md0
-rw-r--r--data/events/aseanrd_id.md0
-rw-r--r--data/events/foundersday.md0
-rw-r--r--data/events/noai.md0
-rw-r--r--data/events/noi.md0
-rw-r--r--data/events/nytc.md0
-rw-r--r--data/events/temasekeng.md0
-rw-r--r--data/events/tsb_id.md0
-rw-r--r--data/projects/simpliCity.md6
-rw-r--r--data/videos.json22
-rw-r--r--data/videos/eid2026.md9
-rw-r--r--data/videos/minds.md7
-rw-r--r--data/videos/paperverse.md7
-rw-r--r--data/videos/s3via.md9
-rw-r--r--data/videos/stringsoffreedom.md9
-rw-r--r--main.py20
-rw-r--r--pages.py84
-rw-r--r--templates/blogpost.html10
-rw-r--r--templates/video.html76
-rw-r--r--www/209.html2
-rw-r--r--www/404.html2
-rw-r--r--www/about/index.html1
-rw-r--r--www/blog/index.html3
-rw-r--r--www/index.html1
-rw-r--r--www/projects/index.html25
-rw-r--r--www/scripts/blog.js6
-rw-r--r--www/scripts/captcha.js2
-rw-r--r--www/scripts/constants.js2
-rw-r--r--www/scripts/video.js91
-rw-r--r--www/scripts/videoprojects.js4
-rw-r--r--www/style.css133
36 files changed, 557 insertions, 58 deletions
diff --git a/data/blogs.json b/data/blogs.json
index 5dc9302..d842e70 100644
--- a/data/blogs.json
+++ b/data/blogs.json
@@ -9,7 +9,7 @@
"tags": ["simpliCity", "Devlog"],
"thumbnail": "/assets/images/blog/beta0.2_cover.png",
"banner": "/assets/images/blog/beta0.2_banner.png",
- "path": "/data/posts/0.md"
+ "path": "/data/blogs/0.md"
},
{
"id": 1,
@@ -20,7 +20,7 @@
"tags": ["simpliCity", "Devlog"],
"thumbnail": "/assets/images/blog/33_cover.png",
"banner": "/assets/images/blog/33_banner.png",
- "path": "/data/posts/1.md"
+ "path": "/data/blogs/1.md"
},
{
"id": 2,
@@ -31,7 +31,18 @@
"tags": ["School"],
"thumbnail": "/assets/images/blog/ass.png",
"banner": "/assets/images/blog/ass.png",
- "path": "/data/posts/ass.md"
+ "path": "/data/blogs/ass.md"
+ },
+ {
+ "id": 3,
+ "title": "altaf-devlog[0]: Hello, world! And I'm sorry.",
+ "description": "No description provided.",
+ "date": "May 2026",
+ "author": "altaf-creator",
+ "tags": ["simpliCity", "Devlog", "Personal"],
+ "thumbnail": "",
+ "banner": "",
+ "path": "/data/blogs/altaf-devlog-0.md"
}
]
}
diff --git a/data/blogs/0.md b/data/blogs/0.md
index 566724b..91d9afc 100644
--- a/data/blogs/0.md
+++ b/data/blogs/0.md
@@ -1,14 +1,18 @@
--+-+-+
++=+=+=
+
# Introduction
Hello, friends! Today, I'm excited to announce that simpliCity beta0.2: The Visual Update is OUT NOW! 🥳
This update contains a lot of content features, bugfixes, and improvements from the last update.
-+-+-+-
-+-+-+
+
++=+=+=
+
## Notable Additions
Starting from NEW buildings, there is now;
+
* Factories
* A soup restaurant
* Restoran Padang
@@ -22,6 +26,7 @@ Starting from NEW buildings, there is now;
* Fences
And then the new game ambience;
+
* Better ambient lightning
* Better skybox coloring
* Clouds!
@@ -29,15 +34,18 @@ And then the new game ambience;
* Window lights
Last but certainly not least, big changes to UI!
+
* a COMPLETE UI overhaul
* Loading screen
* UI Sounds
* Hotbar organization
-+-+-+-
-
-+-+-+
+
++=+=+=
+
## Changes & Bugfixes
+
* Citizen arrival and depart rate modifications
* Performance optimizations
* Small improvements
@@ -45,9 +53,11 @@ Last but certainly not least, big changes to UI!
* Useful tooltips
* Better tooltip positioning
* Fixed credit overlapping text
-+-+-+-
-+-+-+
+
++=+=+=
+
# Closing
And that's (probably) the end of the beta0.2 update. You can already update the game on steam right now! I wanted to say thank you everyone for all of your support, suggestions, and help on building simpliCity.
@@ -57,4 +67,5 @@ This is Altaf.
Stay safe, and see you in the next update on [The Altaf (dev)Blog](/blog/)!
<span style="color: rgba(0, 0, 0, 0.5);">Steam News Link: [https://steamcommunity.com/games/2381230/announcements/detail/3680055105769610135](https://steamcommunity.com/games/2381230/announcements/detail/3680055105769610135)</span>
-+-+-+-
+
+-+-+-+
diff --git a/data/blogs/1.md b/data/blogs/1.md
index 41b8c1f..a8e00c7 100644
--- a/data/blogs/1.md
+++ b/data/blogs/1.md
@@ -1,19 +1,12 @@
--+-+-+
++=+=+=
+
# Opening
Hello, friends! Welcome back to the 5th.. or 6th.. (later editor note: it's 5th) weekly devlog! I want to apologize for not posting weekly devlogs for the past few weeks. In this devlog, I would like to tell you about my new projects and a stupid accident that I have did / occurred.
-+-+-+-
-+-+-+
-# New Projects
-Usually after I published a new update for my games, I often take a break for a few days or weeks. I also sometimes like to start a new side-project. And in this week, I have started some side-projects. Some of them are;
-
-1. This website update.
-2. A new game. I have some different ideas for my new game, so I am currently deciding which game should I make for this new project.
-Now you may wonder, why did I create a new game? Well it was because...
-+-+-+-
++=+=+=
--+-+-+
# New Projects
Usually after I published a new update for my games, I often take a break for a few days or weeks. I also sometimes like to start a new side-project. And in this week, I have started some side-projects. Some of them are;
@@ -21,10 +14,13 @@ Usually after I published a new update for my games, I often take a break for a
2. A new game. I have some different ideas for my new game, so I am currently deciding which game should I make for this new project.
Now you may wonder, why did I create a new game? Well it was because...
-+-+-+-
-+-+-+
+
++=+=+=
+
# The (dumb?) Accident
+
Long story short, I have accidentally teleported simpliCity project folder to a higher dimension that no human could ever even begin to comperhend, aka. deleted all the project files.
The long story is...
@@ -63,26 +59,33 @@ So, what does that mean? Well it can mean that I can recover my project, but it
1. Finish it up and polish it. And it's done! It will not be an exact copy, but all of the new features should still be recovered. And probably will be improved.
But this needs time. For now, I have decided to stop simpliCity development, and start a new projects. Why? My gamedev journey is still long, and I cannot stick to the same projects for months or years. I decided that this is the time for me to move on, and learn new things.
-+-+-+-
-+-+-+
+
++=+=+=
+
# What does it mean for simpliCity?
This means that simpliCity development will be on hold for quite some time until I recovered the project. This also means that I will make a new game! Yay! Finally!
-+-+-+-
-+-+-+
+
++=+=+=
+
# What can we learn
1. Back up all of your projects and files. Do NOT have only 1 copy of your project. Have it on a second drive, and also online services like Google Drive and OneDrive. Do this so if something happens to the original, you still have a copy of it.
1. Back up regularly. If you have a back-up but it's old, that is still the same (but better) than having no back-ups at all. Back up your project every week or at least month.
1. Be careful in things that you don't know. Especially file management and Git. Git is hard. And annoying.
1. It's time to learn new things and move on. It is ok to make a big project and spend years on it, but you should also try and making many side-projects and improve over time.
-+-+-+-
-+-+-+
+
++=+=+=
+
# Closing
We have reached the end of this devlog. becase I feel I want to say sorry to everyone for dissapointing you because of this mistake. I also want to say please learn from this mistake. Better be safe than sorry! Also thank you, thank you everyone for staying with me in this journey.
And that's it for this week. Thank you so much everyone for reading.
This is Altaf.
Stay safe, and see you next week on [The Altaf (dev)Blog](/blog/)!
-+-+-+-
+
+-+-+-+
diff --git a/data/blogs/altaf-devlog-0.md b/data/blogs/altaf-devlog-0.md
new file mode 100644
index 0000000..b614e65
--- /dev/null
+++ b/data/blogs/altaf-devlog-0.md
@@ -0,0 +1,5 @@
++=+=+=
+
+# Work in Progress.
+
+-+-+-+
diff --git a/data/blogs/ass.md b/data/blogs/ass.md
index 3266795..456b4ee 100644
--- a/data/blogs/ass.md
+++ b/data/blogs/ass.md
@@ -1,4 +1,5 @@
--+-+-+
++=+=+=
+
# Final Zoom Meeting Untuk ASS
Ditulis oleh Athaalaa Altaf Hafidz, 9B.
@@ -120,4 +121,5 @@ Terima kasih juga, pembaca!
Dengan Tulus Hati,
Altaf
-+-+-+-
+
+-+-+-+
diff --git a/data/events/alarm_id.md b/data/events/alarm_id.md
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/data/events/alarm_id.md
diff --git a/data/events/aseanrd_id.md b/data/events/aseanrd_id.md
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/data/events/aseanrd_id.md
diff --git a/data/events/foundersday.md b/data/events/foundersday.md
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/data/events/foundersday.md
diff --git a/data/events/noai.md b/data/events/noai.md
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/data/events/noai.md
diff --git a/data/events/noi.md b/data/events/noi.md
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/data/events/noi.md
diff --git a/data/events/nytc.md b/data/events/nytc.md
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/data/events/nytc.md
diff --git a/data/events/temasekeng.md b/data/events/temasekeng.md
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/data/events/temasekeng.md
diff --git a/data/events/tsb_id.md b/data/events/tsb_id.md
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/data/events/tsb_id.md
diff --git a/data/projects/simpliCity.md b/data/projects/simpliCity.md
index 43dfa02..ad5d1af 100644
--- a/data/projects/simpliCity.md
+++ b/data/projects/simpliCity.md
@@ -88,3 +88,9 @@ Have any feedback? Found some bugs? Or do you want to connect with others about
}}}
-+-+-+
+
++=+=+=
+
+
+
+-+-+-+
diff --git a/data/videos.json b/data/videos.json
new file mode 100644
index 0000000..7940394
--- /dev/null
+++ b/data/videos.json
@@ -0,0 +1,22 @@
+{
+ "stringsoffreedom": {
+ "path": "/assets/videos/stringsoffreedom.mp4",
+ "content_path": "/data/videos/stringsoffreedom.md"
+ },
+ "minds": {
+ "path": "/assets/videos/minds.mp4",
+ "content_path": "/data/videos/minds.md"
+ },
+ "paperverse": {
+ "path": "/assets/videos/paperverse.mp4",
+ "content_path": "/data/videos/paperverse.md"
+ },
+ "eid2026": {
+ "path": "/assets/videos/eid2026.mp4",
+ "content_path": "/data/videos/eid2026.md"
+ },
+ "s3via": {
+ "path": "/assets/videos/VIA.mp4",
+ "content_path": "/data/videos/s3via.md"
+ }
+}
diff --git a/data/videos/eid2026.md b/data/videos/eid2026.md
new file mode 100644
index 0000000..84324e7
--- /dev/null
+++ b/data/videos/eid2026.md
@@ -0,0 +1,9 @@
+---
+title: "A message for this Eid"
+description: "A message for this Eid"
+date: "24 March 2026"
+default_copyright: false
+license: "<i class=\"fa-brands fa-creative-commons\"></i><i class=\"fa-brands fa-creative-commons-by\"></i> CC-BY 4.0"
+license_url: "https://creativecommons.org/licenses/by/4.0/"
+downloadable: true
+---
diff --git a/data/videos/minds.md b/data/videos/minds.md
new file mode 100644
index 0000000..78cc911
--- /dev/null
+++ b/data/videos/minds.md
@@ -0,0 +1,7 @@
+---
+title: "MINDS Charity Car Wash Promotional"
+description: "Volunteer work with MINDS to promote their Charity Car Wash in 2025."
+date: "October 2025"
+default_copyright: true
+downloadable: false
+---
diff --git a/data/videos/paperverse.md b/data/videos/paperverse.md
new file mode 100644
index 0000000..66806c3
--- /dev/null
+++ b/data/videos/paperverse.md
@@ -0,0 +1,7 @@
+---
+title: "Paperverse Hub"
+description: "Paperverse Hub, a videography competition entry."
+date: "March 2026"
+default_copyright: true
+downloadable: false
+---
diff --git a/data/videos/s3via.md b/data/videos/s3via.md
new file mode 100644
index 0000000..a24652c
--- /dev/null
+++ b/data/videos/s3via.md
@@ -0,0 +1,9 @@
+---
+title: "Secondary 3 VIA Project"
+description: "Secondary 3 Class VIA Project"
+date: "July 2025"
+default_copyright: false
+license: "<i class=\"fa-brands fa-creative-commons\"></i><i class=\"fa-brands fa-creative-commons-by\"></i> CC-BY 4.0"
+license_url: "https://creativecommons.org/licenses/by/4.0/"
+downloadable: true
+---
diff --git a/data/videos/stringsoffreedom.md b/data/videos/stringsoffreedom.md
new file mode 100644
index 0000000..53e3e4d
--- /dev/null
+++ b/data/videos/stringsoffreedom.md
@@ -0,0 +1,9 @@
+---
+title: "Strings of Freedom"
+description: "Top Winning Submission for Yellow Ribbon Arts Competition 2025."
+date: "May 2025"
+default_copyright: true
+license: "<i class=\"fa-brands fa-creative-commons\"></i><i class=\"fa-brands fa-creative-commons-by\"></i> CC-BY 4.0"
+license_url: "https://creativecommons.org/licenses/by/4.0/"
+downloadable: false
+---
diff --git a/main.py b/main.py
index 9af3a54..f8e56df 100644
--- a/main.py
+++ b/main.py
@@ -17,6 +17,7 @@ TURNSTILE_SECRET = getenv("CLOUDFLARE_TURNSTILE_SECRET").encode("utf-8")
origins = [
"http://localhost",
+ "http://localhost:9091",
"https://altafcreator.com",
"https://backend.altafcreator.com"
]
@@ -97,4 +98,23 @@ def blogpost_redirect(blogpost: str):
status_code=301
)
+
+@app.get("/video/{video_name}/")
+def video_page(video_name: str):
+ status, html = pages.render_video(video_name)
+
+ if status == 200:
+ return fastapi.responses.Response(content=html, media_type="text/html", status_code=200)
+ else:
+ raise fastapi.HTTPException(status_code=status, detail="Blogpost doesn't exist.")
+
+
+@app.get("/video/{video_name}", include_in_schema=False)
+def video_page_redirect(video_name: str):
+ return fastapi.responses.RedirectResponse(
+ url=f"/video/{video_name}/",
+ status_code=301
+ )
+
+
app.mount("/", fastapi.staticfiles.StaticFiles(directory="./www/", html=True), name="static")
diff --git a/pages.py b/pages.py
index f114be7..9f37762 100644
--- a/pages.py
+++ b/pages.py
@@ -11,14 +11,18 @@ PATH_HTML_BLOGPOST_TEMPLATE = "./templates/blogpost.html"
PATH_BLOGPOST_INDEX = "./data/blogs.json"
PATH_HTML_EVENT_TEMPLATE = "./templates/eventdetails.html"
PATH_EVENT_INDEX = "./data/events.json"
+PATH_HTML_VIDEO_TEMPLATE = "./templates/video.html"
+PATH_VIDEO_INDEX = "./data/videos.json"
HTML_PROJECT_TEMPLATE = """"""
HTML_BLOGPOST_TEMPLATE = """"""
HTML_EVENT_TEMPLATE = """"""
+HTML_VIDEO_TEMPLATE = """"""
PROJECT_INDEX = {}
BLOGPOST_INDEX = {}
EVENT_INDEX = {}
+VIDEO_INDEX = {}
with open(PATH_HTML_PROJECT_TEMPLATE) as f:
@@ -27,6 +31,8 @@ with open(PATH_HTML_BLOGPOST_TEMPLATE) as f:
HTML_BLOGPOST_TEMPLATE = f.read()
with open(PATH_HTML_EVENT_TEMPLATE) as f:
HTML_EVENT_TEMPLATE = f.read()
+with open(PATH_HTML_VIDEO_TEMPLATE) as f:
+ HTML_VIDEO_TEMPLATE = f.read()
with open(PATH_PROJECT_INDEX) as f:
PROJECT_INDEX = json.load(f)
@@ -34,6 +40,8 @@ with open(PATH_BLOGPOST_INDEX) as f:
BLOGPOST_INDEX = json.load(f)
with open(PATH_EVENT_INDEX) as f:
EVENT_INDEX = json.load(f)
+with open(PATH_VIDEO_INDEX) as f:
+ VIDEO_INDEX = json.load(f)
def render_project_details(project_name: str) -> tuple:
@@ -49,7 +57,7 @@ def render_project_details(project_name: str) -> tuple:
metadata, rendered_content = markdown_renderer.md_to_html(source)
print(metadata, rendered_content)
- html = html.replace("{[{content}]}", f"{rendered_content}")
+ html = html.replace("{[{content}]}", rendered_content)
if "logo" in metadata:
html = html.replace("{[{img-src-icon-bottom-bar}]}", metadata["logo"])
@@ -89,9 +97,79 @@ def render_project_details(project_name: str) -> tuple:
return (200, str(soup))
-def render_blogpost(blogpost_id: int):
- return (209, "")
+def render_blogpost(blogpost_id: int) -> tuple:
+ html = HTML_BLOGPOST_TEMPLATE
+ source = ""
+
+ if blogpost_id >= len(BLOGPOST_INDEX["posts"]):
+ return (404, "")
+
+ metadata = BLOGPOST_INDEX["posts"][blogpost_id]
+
+ with open("." + metadata["path"], "r") as f:
+ source = f.read()
+
+ _, rendered_content = markdown_renderer.md_to_html(source)
+ print(metadata, rendered_content)
+
+ html = html.replace("{[{content}]}", rendered_content)
+
+ soup = BeautifulSoup(html, "html.parser")
+ soup.title.string = soup.title.string.replace("{[{title}]}", metadata["title"])
+
+ soup.select_one("#banner")["src"] = metadata["banner"]
+ soup.select_one("#title").string = metadata["title"]
+ soup.select_one("#description").string = metadata["description"]
+ soup.select_one("#date").string = metadata["date"]
+ soup.select_one("#author").string = metadata["author"]
+
+ for tag in metadata["tags"]:
+ new_tag = soup.new_tag("span")
+ new_tag["class"] = "chip"
+ new_tag.string = tag
+ soup.select_one("#tag-container").append(new_tag)
+
+ return (200, str(soup))
def blog_list() -> dict:
return BLOGPOST_INDEX
+
+
+def render_video(video_name: str) -> tuple:
+ html = HTML_VIDEO_TEMPLATE
+ source = ""
+
+ if video_name not in VIDEO_INDEX:
+ return (404, "")
+
+ with open("." + VIDEO_INDEX[video_name]["content_path"], "r") as f:
+ source = f.read()
+
+ metadata, rendered_content = markdown_renderer.md_to_html(source)
+ metadata["path"] = VIDEO_INDEX[video_name]["path"]
+ print(metadata, rendered_content)
+
+ html = html.replace("{[{content}]}", rendered_content)
+
+ html = html.replace("{[{video-title}]}", metadata["title"])
+ html = html.replace("{[{description}]}", metadata["description"])
+ html = html.replace("{[{date}]}", metadata["date"])
+
+ soup = BeautifulSoup(html, "html.parser")
+ soup.title.string = soup.title.string.replace("{[{title}]}", metadata["title"])
+
+ soup.select_one("#video")["src"] = metadata["path"]
+
+ if metadata["default_copyright"]:
+ soup.select_one("#video-license-parent").string = "©️ All rights reserved"
+ else:
+ soup.select_one("#video-license-info")["href"] = metadata["license_url"]
+ soup.select_one("#video-license-info").insert(0, BeautifulSoup(metadata["license"], "html.parser"))
+
+ if metadata["downloadable"] and not metadata["default_copyright"]:
+ soup.select_one("#video-download-btn").href = metadata["path"]
+ else:
+ soup.select_one("#video-download-btn").decompose()
+
+ return (200, str(soup))
diff --git a/templates/blogpost.html b/templates/blogpost.html
index f5b1ff1..16f908e 100644
--- a/templates/blogpost.html
+++ b/templates/blogpost.html
@@ -7,7 +7,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="/style.css">
<link rel="icon" type="image/x-icon" href="/assets/images/favicon.ico">
- <title>Blog / Loading... • altaf-creator</title>
+ <title>{[{title}]} • altaf-creator</title>
<link rel="stylesheet" href="/packages/fontawesome-free-7.2.0-web/css/all.css">
<script src="/packages/markdown-it-14.1.0/dist/markdown-it.js" type="module"></script>
<style>
@@ -18,8 +18,7 @@
</head>
-<body onload="blog(getUrlVars()['post'])">
- <script defer src="/scripts/blog.js"></script>
+<body onload="addNodes()">
<script defer src="/scripts/scroll.js"></script>
<script defer src="/scripts/onload.js"></script>
<div class="floating-nav-container">
@@ -55,10 +54,7 @@
</div>
</div>
</section>
- <div id="content">
- <h1>Hello, world!</h1>
- <p>i'm exhausted.</p>
- </div>
+ {[{content}]}
</body>
</html>
diff --git a/templates/video.html b/templates/video.html
new file mode 100644
index 0000000..5f52065
--- /dev/null
+++ b/templates/video.html
@@ -0,0 +1,76 @@
+<!DOCTYPE html>
+<html lang="en">
+
+<head>
+ <meta charset="UTF-8">
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <link rel="stylesheet" href="/style.css">
+ <link rel="icon" type="image/x-icon" href="/assets/images/favicon.ico">
+ <link rel="icon" type="image/x-icon" href="/assets/images/favicon.ico">
+ <title>{[{title}]} • altaf-creator</title>
+ <link rel="stylesheet" href="/packages/fontawesome-free-7.2.0-web/css/all.css">
+</head>
+
+<body onload="addNodes()">
+ <script defer src="/scripts/onload.js"></script>
+ <script defer src="/scripts/scroll.js"></script>
+ <script defer src="/scripts/video.js"></script>
+ <div class="floating-nav-container">
+ <div class="floating-nav">
+ <a href="/" class="mobile"><img src="/assets/images/hero/logo.png" alt="" class="sidebar-img mobile" style="margin-top: 0; margin-left: -20px;"></a>
+ <a href="/about/" class="link">About Me</a>
+ <a href="/projects/" class="link">Projects</a>
+ <a href="/blog/" class="link">Library</a>
+ </div>
+ </div>
+ <div class="sidebar" id="sidebar">
+ <a href="/">
+ <img src="/assets/images/hero/logo.png" alt="" class="sidebar-img">
+ </a>
+ <span>altaf-creator</span>
+ </div>
+ <div class="sidebar-progress-container" id="progressContainer">
+ </div>
+ <section class="normal-section video-section">
+ <div class="shadow-filter">
+ <video class="video" id="video">
+ </video>
+ <div class="shadow-filter">
+ <div class="video-controls">
+ <button id="video-controls-play"><i class="fa-solid fa-play"></i></button>
+ <span><span id="video-controls-currenttime">00:00</span><span id="video-controls-totaltime" class="desktop half-opacity-text">/00:00</span>
+ </span>
+ <input type="range" id="video-controls-progress" min="0" max="1" value="0" step="any"></progress>
+ <button id="video-controls-mutetoggle"><i class="fa-solid fa-volume"></i></button>
+ <button id="video-controls-maximise"><i class="fa-solid fa-maximize"></i></button>
+ </div>
+ </div>
+ </div>
+ </section>
+ <section class="normal-section">
+ <div class="center-grid">
+ <div class="div-sizing">
+ <div class="flex-container flex-container-normal flex-container-dynamicwrap gap">
+ <div class="flex">
+ <h1>{[{video-title}]}</h1>
+ <p class="half-opacity-text"><i class="fa-solid fa-calendar"></i> {[{date}]}</p>
+ <p>{[{description}]}</p>
+ </div>
+ <div class="flex-container flex-container-column flex-end">
+ <div class="flex-container flex-container-normal flex-container-dynamicwrap gap flex-end flex-end-dynamic">
+ <a class="button button-inline" href="#" id="video-download-btn"><i class="fa-solid fa-download"></i> Download</a>
+ <a class="button button-inline" href="#"><i class="fa-solid fa-share-nodes" id="video-share-btn"></i></a>
+ </div>
+ <p style="text-align: end;" class="half-opacity-text" id="video-license-parent">Licensed under<br>
+ <a class="link" href="#" id="video-license-info"><a>
+ </p>
+ </div>
+ </div>
+ </div>
+ </div>
+ </section>
+ {[{content}]}
+</body>
+
+</html>
diff --git a/www/209.html b/www/209.html
index 59fc22f..0644f4e 100644
--- a/www/209.html
+++ b/www/209.html
@@ -21,7 +21,7 @@
<a href="/" class="mobile"><img src="/assets/images/hero/logo.png" alt="" class="sidebar-img mobile" style="margin-top: 0; margin-left: -20px;"></a>
<a href="/about/" class="link">About Me</a>
<a href="/projects/" class="link">Projects</a>
- <a href="/blog/" class="link">(dev)Blog</a>
+ <a href="/blog/" class="link">Library</a>
</div>
</div>
<div class="sidebar" id="sidebar">
diff --git a/www/404.html b/www/404.html
index f1bf9fb..deb8591 100644
--- a/www/404.html
+++ b/www/404.html
@@ -21,7 +21,7 @@
<a href="/" class="mobile"><img src="/assets/images/hero/logo.png" alt="" class="sidebar-img mobile" style="margin-top: 0; margin-left: -20px;"></a>
<a href="/about/" class="link">About Me</a>
<a href="/projects/" class="link">Projects</a>
- <a href="/blog/" class="link">(dev)Blog</a>
+ <a href="/blog/" class="link">Library</a>
</div>
</div>
<div class="sidebar" id="sidebar">
diff --git a/www/about/index.html b/www/about/index.html
index 9fd4ef1..e5aacf6 100644
--- a/www/about/index.html
+++ b/www/about/index.html
@@ -19,6 +19,7 @@
</head>
<body onload="addNodes();">
+ <script defer src="/scripts/constants.js"></script>
<script defer src="/scripts/scroll.js"></script>
<script defer src="/scripts/onload.js"></script>
<script defer src="/scripts/captcha.js"></script>
diff --git a/www/blog/index.html b/www/blog/index.html
index 64ef45e..ac05222 100644
--- a/www/blog/index.html
+++ b/www/blog/index.html
@@ -12,6 +12,7 @@
</head>
<body onload="library();">
+ <script defer src="/scripts/constants.js"></script>
<script defer src="/scripts/onload.js"></script>
<script defer src="/scripts/scroll.js"></script>
<script defer src="/scripts/blog.js"></script>
@@ -30,7 +31,7 @@
<span>altaf-creator</span>
</div>
<div class="sidebar-progress-container" id="progressContainer"></div>
- <section style="border-top: none;">
+ <section style="border-top: none;" class="top-section-margin">
<div class="center-grid">
<div class="div-sizing">
<div class="flex-container flex-container-column flex-center-content" id="blog-container">
diff --git a/www/index.html b/www/index.html
index 88c51d7..bbcfae2 100644
--- a/www/index.html
+++ b/www/index.html
@@ -21,6 +21,7 @@
</head>
<body onload="index();">
+ <script defer src="/scripts/constants.js"></script>
<script defer src="/scripts/onload.js"></script>
<script defer src="/scripts/scroll.js"></script>
<script defer src="/scripts/blog.js"></script>
diff --git a/www/projects/index.html b/www/projects/index.html
index 301b8e5..2f788f4 100644
--- a/www/projects/index.html
+++ b/www/projects/index.html
@@ -12,6 +12,7 @@
</head>
<body onload="addNodes();">
+ <script defer src="/scripts/constants.js"></script>
<script defer src="/scripts/onload.js"></script>
<script defer src="/scripts/scroll.js"></script>
<script defer src="/scripts/videoprojects.js"></script>
@@ -312,7 +313,7 @@
<span class="heading">Videography<span class="desktop"> Projects</span></span>
<div class="flex-container flex-container-normal flex-container-dynamicwrap gap wide" style="margin-top: 1rem;">
<div class="flex video-higlight-card shadow-filter">
- <button onclick="loadVideo('/assets/videos/stringsoffreedom.mp4', 'Strings of Freedom');">⏵</button>
+ <button onclick="loadVideo('/assets/videos/stringsoffreedom.mp4', 'Strings of Freedom', 'stringsoffreedom');">⏵</button>
<img src="/assets/images/video-hero/stringsoffreedom.png" alt="">
<div class="img">
<h1>Strings of Freedom</h1>
@@ -321,7 +322,7 @@
</div>
</div>
<div class="flex video-higlight-card shadow-filter">
- <button onclick="loadVideo('/assets/videos/minds.mp4', 'MINDS Charity Car Wash Promotional Video');">⏵</button>
+ <button onclick="loadVideo('/assets/videos/minds.mp4', 'MINDS Charity Car Wash Promotional Video', 'minds');">⏵</button>
<img src="/assets/images/video-hero/minds.png" alt="">
<div class="img">
<h1>MINDS Charity Car Wash Promotional</h1>
@@ -333,50 +334,50 @@
<div class="flex video-gallery-card narrow">
<div>
<img src="/assets/images/video-hero/stringsoffreedom.png" alt="">
- <button onclick="loadVideo('/assets/videos/stringsoffreedom.mp4', 'Strings of Freedom');">⏵</button>
+ <button onclick="loadVideo('/assets/videos/stringsoffreedom.mp4', 'Strings of Freedom', 'stringsoffreedom');">⏵</button>
</div>
<div class="video-gallery-card-text video-gallery-card-text-accent flex-container flex-container-column flex-start gap">
- <a href="" class="link">Strings of Freedom</a>
+ <a href="/video/stringsoffreedom/" class="link">Strings of Freedom</a>
<span>(2025) Top Winning Submission for Yellow Ribbon Arts Competition 2025.</span>
</div>
</div>
<div class="flex video-gallery-card narrow">
<div>
<img src="/assets/images/video-hero/minds.png" alt="">
- <button onclick="loadVideo('/assets/videos/minds.mp4', 'MINDS Charity Car Wash Promotional Video');">⏵</button>
+ <button onclick="loadVideo('/assets/videos/minds.mp4', 'MINDS Charity Car Wash Promotional Video', 'minds');">⏵</button>
</div>
<div class="video-gallery-card-text video-gallery-card-text-accent flex-container flex-container-column flex-start gap">
- <a href="" class="link">MINDS Charity Car Wash Promotional</a>
+ <a href="/video/minds/" class="link">MINDS Charity Car Wash Promotional</a>
<span>(2025) Volunteer work with MINDS to promote their Charity Car Wash in 2025.</span>
</div>
</div>
<div class="flex video-gallery-card">
<div>
<img src="/assets/images/video-hero/paperverse.png" alt="">
- <button onclick="loadVideo('/assets/videos/paperverse.mp4', 'Paperverse Hub, a videography competition entry');">⏵</button>
+ <button onclick="loadVideo('/assets/videos/paperverse.mp4', 'Paperverse Hub, a videography competition entry', 'paperverse');">⏵</button>
</div>
<div class="video-gallery-card-text flex-container flex-container-normal flex-container-dynamicwrap flex-spacebetween flex-center-content">
- <a href="" class="link">Paperverse Hub</a>
+ <a href="/video/paperverse/" class="link">Paperverse Hub</a>
<span>2026</span>
</div>
</div>
<div class="flex video-gallery-card">
<div>
<img src="/assets/images/video-hero/eid2026.png" alt="">
- <button onclick="loadVideo('/assets/videos/eid2026.mp4', 'A message for this Eid');">⏵</button>
+ <button onclick="loadVideo('/assets/videos/eid2026.mp4', 'A message for this Eid', 'eid2026');">⏵</button>
</div>
<div class="video-gallery-card-text flex-container flex-container-normal flex-container-dynamicwrap flex-spacebetween flex-center-content gap">
- <a href="" class="link">A message for this Eid</a>
+ <a href="/video/eid2026/" class="link">A message for this Eid</a>
<span>2026</span>
</div>
</div>
<div class="flex video-gallery-card">
<div>
<img src="/assets/images/video-hero/via3rp.png" alt="">
- <button onclick="loadVideo('/assets/videos/VIA.mp4', 'Secondary 3 Class VIA Project');">⏵</button>
+ <button onclick="loadVideo('/assets/videos/VIA.mp4', 'Secondary 3 Class VIA Project', 's3via');">⏵</button>
</div>
<div class="video-gallery-card-text flex-container flex-container-normal flex-container-dynamicwrap flex-spacebetween flex-center-content gap">
- <a href="" class="link">Secondary 3 VIA Project</a>
+ <a href="/video/s3via/" class="link">Secondary 3 VIA Project</a>
<span>2025</span>
</div>
</div>
diff --git a/www/scripts/blog.js b/www/scripts/blog.js
index 0e8fa2f..5c5ad67 100644
--- a/www/scripts/blog.js
+++ b/www/scripts/blog.js
@@ -1,10 +1,8 @@
var evaluatedTags = "";
const clamp = (val, min, max) => Math.min(Math.max(val, min), max)
-const api_url = "https://altafcreator.com"
-
function postList(n = 0) {
- fetch(`${api_url}/api/blogs/`, {
+ fetch(`${API_URL}/blogs/`, {
method: "POST",
headers: {
"Content-Type": "application/json",
@@ -47,7 +45,7 @@ function postCard(post) {
<div class="flex-container-normal chip-container">
${evaluatedTags}
</div>
- <a href="/blog/post.html?post=${id}" class="link"><h2>${title}</h2></a>
+ <a href="/blog/${id}" class="link"><h2>${title}</h2></a>
<p>${date}</p>
</div>
</div>
diff --git a/www/scripts/captcha.js b/www/scripts/captcha.js
index 3ef5334..9ddc513 100644
--- a/www/scripts/captcha.js
+++ b/www/scripts/captcha.js
@@ -1,7 +1,7 @@
const emailUl = document.getElementById("email-list")
async function emailTurnstileSuccess(token) {
- const response = await fetch("https://altafcreator.com/api/email/", {
+ const response = await fetch(`${API_URL}/email/`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
diff --git a/www/scripts/constants.js b/www/scripts/constants.js
new file mode 100644
index 0000000..b2b7894
--- /dev/null
+++ b/www/scripts/constants.js
@@ -0,0 +1,2 @@
+//const API_URL = "https://altafcreator.com/api"
+const API_URL = "http://localhost:9091/api"
diff --git a/www/scripts/video.js b/www/scripts/video.js
new file mode 100644
index 0000000..9f32e96
--- /dev/null
+++ b/www/scripts/video.js
@@ -0,0 +1,91 @@
+const video = document.getElementById("video");
+const videoControlsProgress = document.getElementById("video-controls-progress");
+const videoControlsPlay = document.getElementById("video-controls-play");
+const videoControlsVolume = document.getElementById("video-controls-mutetoggle");
+const videoControlsMaximise = document.getElementById("video-controls-maximise");
+const videoControlsTimeCurrent = document.getElementById("video-controls-currenttime");
+const videoControlsTimeTotal = document.getElementById("video-controls-totaltime");
+
+let videoPlaying = false;
+let audioEnabled = true;
+let videoMaximised = false;
+
+function toggleVideo() {
+ if (videoPlaying) {
+ videoPlaying = false;
+ videoControlsPlay.innerHTML = `<i class="fa-solid fa-play"></i>`;
+ video.pause();
+ } else {
+ videoPlaying = true;
+ videoControlsPlay.innerHTML = `<i class="fa-solid fa-pause"></i>`;
+ video.play();
+ }
+}
+
+function videoUpdate() {
+ videoControlsProgress.value = video.currentTime / video.duration;
+ videoControlsTimeCurrent.innerHTML = secondsToFormatted(video.currentTime);
+ updateSlider();
+}
+
+function updateSlider() {
+ const value = (videoControlsProgress.value - videoControlsProgress.min) / (videoControlsProgress.max - videoControlsProgress.min) * 100;
+ videoControlsProgress.style.background = `linear-gradient(to right, var(--accent1) 0%, var(--accent2) ${value}%, #ddd ${value}%, #ddd 100%)`;
+}
+
+function seekVideo() {
+ updateSlider();
+ const time = videoControlsProgress.value * video.duration;
+ video.currentTime = time;
+}
+
+function toggleAudio() {
+ if (audioEnabled) {
+ audioEnabled = false;
+ videoControlsVolume.innerHTML = `<i class="fa-solid fa-volume-xmark"></i>`;
+ video.muted = true;
+ } else {
+ audioEnabled = true;
+ videoControlsVolume.innerHTML = `<i class="fa-solid fa-volume"></i>`;
+ video.muted = false;
+ }
+}
+
+function toggleMaximisation() {
+ if (videoMaximised) {
+ videoMaximised = false;
+ videoControlsMaximise.innerHTML = `<i class="fa-solid fa-maximize"></i>`;
+ video.exitFullscreen();
+ video.controls = false;
+ } else {
+ videoMaximised = true;
+ videoControlsMaximise.innerHTML = `<i class="fa-solid fa-minimize"></i>`;
+ video.requestFullscreen();
+ video.controls = true;
+ }
+}
+
+function secondsToFormatted(seconds) {
+ seconds = Math.round(seconds);
+ let hours = seconds / 3600;
+ let hoursMod = seconds % 3600; // in seconds
+ let minutes = hoursMod / 60;
+ let secs = hoursMod % 60;
+
+ if (hours >= 1) {
+ return `${Math.floor(hours)}:${Math.floor(minutes).toString().padStart(2, "0")}:${Math.floor(secs).toString().padStart(2, "0")}`;
+ } else {
+ return `${Math.floor(minutes).toString().padStart(2, "0")}:${Math.floor(secs).toString().padStart(2, "0")}`
+ }
+}
+
+videoControlsProgress.value = 0;
+updateSlider();
+video.addEventListener("loadeddata", function () {
+ videoControlsTimeTotal.innerHTML = `/${secondsToFormatted(video.duration)}`
+});
+videoControlsProgress.addEventListener("input", seekVideo);
+videoControlsPlay.addEventListener("click", toggleVideo);
+videoControlsVolume.addEventListener("click", toggleAudio);
+videoControlsMaximise.addEventListener("click", toggleMaximisation);
+video.addEventListener("timeupdate", videoUpdate);
diff --git a/www/scripts/videoprojects.js b/www/scripts/videoprojects.js
index 1879474..019403f 100644
--- a/www/scripts/videoprojects.js
+++ b/www/scripts/videoprojects.js
@@ -1,6 +1,7 @@
const playerContainer = document.getElementById("full-video-player");
const playerVideo = document.getElementById("full-video-player-video");
const playerCaption = document.getElementById("full-video-player-caption");
+const playerButton = document.getElementById("full-video-player-button")
function closeFullPlayer() {
playerContainer.style.display = "none";
@@ -8,8 +9,9 @@ function closeFullPlayer() {
playerVideo.currentTime = 0;
}
-function loadVideo(url, caption) {
+function loadVideo(url, caption, videoPath) {
playerContainer.style.display = "flex";
playerVideo.src = url;
playerCaption.innerHTML = caption;
+ playerButton.href = `/video/${videoPath}/`
}
diff --git a/www/style.css b/www/style.css
index 1134f0e..8bd39b9 100644
--- a/www/style.css
+++ b/www/style.css
@@ -210,6 +210,12 @@ li {
min-height: 0;
}
+.video-section {
+ border-top: none;
+ padding-top: 90px;
+ padding-bottom: 50px;
+}
+
.heading {
font-size: 2.5rem;
font-weight: 600;
@@ -470,6 +476,11 @@ li {
align-items: start;
}
+.flex-end {
+ align-items: end;
+ justify-content: end;
+}
+
.flex-right {
display: flex;
flex-direction: row;
@@ -1021,6 +1032,117 @@ li {
/*clip-path: polygon(0% 0%, 0% 0%, 0% 0%, 100% 0%, 100% 0%, 100% 0%, 100% calc(100% - 7px), calc(100% - 7px) calc(100% - 7px), calc(100% - 7px) 100%, 0% 100%, 0% 100%, 0% 100%);*/
}
+.video {
+ width: 100%;
+ clip-path: polygon(0 20.00px, 20px 20px, 20.00px 0, calc(100% - 20.00px) 0, calc(100% - 20px) 20px, 100% 20.00px, 100% calc(100% - 20.00px), calc(100% - 20px) calc(100% - 20px), calc(100% - 20.00px) 100%, 20.00px 100%, 20px calc(100% - 20px), 0 calc(100% - 20.00px));
+ background-color: var(--accent);
+ transition: clip-path .5s;
+}
+
+/*.video:hover {
+ clip-path: polygon(
+ 0 0, 0 0, 0 0,
+ 100% 0, 100% 0, 100% 0,
+ 100% 100%, 100% 100%, 100% 100%,
+ 0 100%, 0 100%, 0 100%
+ );
+}*/
+
+.video:hover ~ div>.video-controls {
+ opacity: 1;
+ transition-delay: 0s;
+}
+
+.video-controls:hover {
+ opacity: 1;
+ transition-delay: 0s;
+}
+
+.video-controls {
+ background-color: #fff;
+ height: 50px;
+ clip-path: polygon(0 10.00px, 10px 10px, 10.00px 0, calc(100% - 10.00px) 0, calc(100% - 10px) 10px, 100% 10.00px, 100% calc(100% - 10.00px), calc(100% - 10px) calc(100% - 10px), calc(100% - 10.00px) 100%, 10.00px 100%, 10px calc(100% - 10px), 0 calc(100% - 10.00px));
+ position: absolute;
+ bottom: 25px;
+ left: 20px;
+ right: 20px;
+ display: flex;
+ align-items: center;
+ padding: 15px;
+ box-sizing: border-box;
+ gap: 10px;
+ opacity: 0;
+ transition: opacity .2s;
+ transition-delay: 1s;
+}
+
+.video-controls > button {
+ background: none;
+ border: none;
+ height: 1.5rem;
+ width: 1.5rem;
+ cursor: pointer;
+}
+
+.video-controls > button:hover {
+ color: var(--accent);
+}
+
+.video-controls > button:active {
+ opacity: .6;
+}
+
+.video-controls > input[type="range"] {
+ -webkit-appearance: none;
+ appearance: none;
+ background: linear-gradient(to right, var(--accent1) 0%, var(--accent2) 50%, #ddd 50%, #ddd 100%);
+ height: 5px;
+ cursor: pointer;
+}
+
+.video-controls > input[type="range"]::-moz-range-thumb {
+ appearance: none;
+ background-color: black;
+ border: none;
+ border-radius: 0;
+ transform: rotate(45deg);
+ transition: transform .2s;
+ height: 13px;
+ width: 13px;
+}
+
+.video-controls > input[type="range"]::-webkit-slider-thumb {
+ -webkit-appearance: none;
+ appearance: none;
+ background-color: black;
+ border: none;
+ border-radius: 0;
+ transform: rotate(45deg);
+ transition: transform .2s;
+ height: 13px;
+ width: 13px;
+}
+
+.video-controls > input[type="range"]::-moz-range-thumb:hover {
+ transform: rotate(0deg);
+}
+
+.video-controls > input[type="range"]::-webkit-slider-thumb:hover {
+ transform: rotate(0deg);
+}
+
+.video-controls > input[type="range"]::-moz-range-thumb:active {
+ transform: rotate(0deg);
+}
+
+.video-controls > input[type="range"]::-webkit-slider-thumb:active {
+ transform: rotate(0deg);
+}
+
+#video-controls-progress {
+ width: 100%;
+}
+
.compatibility-icon {
display: none !important;
}
@@ -1232,10 +1354,14 @@ li {
}
section {
- padding-inline: 10px;
+ padding-inline: 15px;
margin-left: 0;
}
+ .video-section {
+ padding-bottom: 15px !important;
+ }
+
.gallery-card {
flex: 1 0 300px;
}
@@ -1287,6 +1413,11 @@ li {
padding-top: 40px;
}
+ .flex-end-dynamic {
+ align-items: start;
+ justify-content: start;
+ }
+
#full-video-player {
padding: 20px;
}