Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 38 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,42 @@
[![Run Tests](https://github.com/attogram/base/actions/workflows/ci.yml/badge.svg)](https://github.com/attogram/base/actions/workflows/ci.yml)
[![Release](https://img.shields.io/github/v/release/attogram/base?style=flat)](https://github.com/attogram/base/releases)
[![GitHub stars](https://img.shields.io/github/stars/attogram/base?style=flat)](https://github.com/attogram/base/stargazers)
[![GitHub watchers](https://img.shields.io/github/watchers/attogram/base?style=flat)](https://github.com/attogram/base/watchers)
[![Forks](https://img.shields.io/github/forks/attogram/base?style=flat)](https://github.com/attogram/base/forks)
[![Issues](https://img.shields.io/github/issues/attogram/base?style=flat)](https://github.com/attogram/base/issues)
[![GitHub commit activity](https://img.shields.io/github/commit-activity/t/attogram/base?style=flat)](https://github.com/attogram/base/commits/main/)
[![License](https://img.shields.io/github/license/attogram/base?style=flat)](./LICENSE)
# Getting Interactive with GitHub Pages

Welcome to [base](./docs/base.md).
[![Run Tests](https://github.com/attogram/static-magic/actions/workflows/ci.yml/badge.svg)](https://github.com/attogram/static-magic/actions/workflows/ci.yml)
[![GitHub stars](https://img.shields.io/github/stars/attogram/static-magic?style=flat)](https://github.com/attogram/static-magic/stargazers)
[![GitHub watchers](https://img.shields.io/github/watchers/attogram/static-magic?style=flat)](https://github.com/attogram/static-magic/watchers)
[![Forks](https://img.shields.io/github/forks/attogram/static-magic?style=flat)](https://github.com/attogram/static-magic/forks)
[![Issues](https://img.shields.io/github/issues/attogram/static-magic?style=flat)](https://github.com/attogram/static-magic/issues)
[![License](https://img.shields.io/github/license/attogram/static-magic?style=flat)](./LICENSE)

Your starting point for new GitHub projects.
Frontend magic for GitHub Pages - zero backend, pure fun!

- [Documentation](./docs/README.md)
This website is a collection of interactive examples and games that you can run directly on GitHub Pages. No backend, no build tools, just plain HTML, CSS, and JavaScript.

- Repo: [https://github.com/attogram/base](https://github.com/attogram/base)
- **Website**: https://attogram.github.io/static-magic/
- **Repo**: https://github.com/attogram/static-magic

## Features

This site demonstrates how to create rich interactive experiences on a static site, including:

- Forms without servers
- Live JavaScript runner
- Theme switcher (light/dark mode)
- Persistent notes app
- Filterable lists and search
- Markdown to HTML converter
- JSON viewer/formatter
- Interactive diagrams with Mermaid.js
- And much more!

## Games

Check out the `/games` directory for a collection of simple browser games, including:

- Clicker Game
- Tic Tac Toe
- Memory Match
- And more!

## For Developers

This project is a fork of [attogram/base](https://github.com/attogram/base) and serves as a living example of what you can do with GitHub Pages. Feel free to fork this repository and build your own interactive static sites!
230 changes: 230 additions & 0 deletions features/browser-apis.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Browser API Demos - Getting Interactive with GitHub Pages</title>
<link rel="stylesheet" href="../style.css" />
</head>
<body>
<header>
<div class="container">
<h1>Getting Interactive with GitHub Pages</h1>
<p>Frontend magic for GitHub Pages - zero backend, pure fun!</p>
</div>
</header>

<nav>
<div class="container">
<ul>
<li><a href="../index.html">Home</a></li>
<li><a href="forms.html">Forms</a></li>
<li><a href="live-js-runner.html">JS Runner</a></li>
<li><a href="theme-switcher.html">Theme Switcher</a></li>
<li><a href="notes-app.html">Notes App</a></li>
<li><a href="filter-search.html">Filter & Search</a></li>
<li><a href="markdown-converter.html">Markdown Converter</a></li>
<li><a href="json-viewer.html">JSON Viewer</a></li>
<li><a href="mermaid-diagrams.html">Mermaid Diagrams</a></li>
<li><a href="clipboard.html">Clipboard API</a></li>
<li><a href="drag-drop.html">Drag & Drop</a></li>
<li><a href="spa-routing.html">SPA Routing</a></li>
<li><a href="page-state.html">Page State</a></li>
<li><a href="browser-apis.html">Browser APIs</a></li>
<li><a href="../tools/tech-tools.html">Tech Tools</a></li>
<li><a href="../games/index.html">Games</a></li>
<li><a href="../guides/best-practices.html">Best Practices</a></li>
<li><a href="../guides/resources.html">Resources</a></li>
<li><a href="../guides/markdown-usage.html">Markdown Usage</a></li>
</ul>
</div>
</nav>

<main>
<div class="container">
<section id="api-intro">
<h2>Browser API Demos</h2>
<p>
Modern browsers come packed with powerful JavaScript APIs that can
give your static site capabilities that once required a native
application. These often require user permission for security and
privacy reasons.
</p>
<p>Here are a few examples of what you can do.</p>
</section>

<hr />

<!-- Geolocation API Demo -->
<section id="geolocation-demo">
<h3>1. Geolocation API</h3>
<p>
Get the user's geographical location. This requires user permission.
</p>
<button id="geo-btn" class="btn">Get My Location</button>
<p id="geo-output"></p>
<h4>Code</h4>
<pre><code>const geoBtn = document.getElementById('geo-btn');
const geoOutput = document.getElementById('geo-output');

geoBtn.addEventListener('click', () => {
if ('geolocation' in navigator) {
navigator.geolocation.getCurrentPosition((position) => {
const lat = position.coords.latitude;
const lon = position.coords.longitude;
geoOutput.textContent = `Latitude: ${lat}, Longitude: ${lon}`;
}, (error) => {
geoOutput.textContent = `Error: ${error.message}`;
});
} else {
geoOutput.textContent = 'Geolocation is not available.';
}
});</code></pre>
</section>

<hr />

<!-- Notifications API Demo -->
<section id="notifications-demo">
<h3>2. Notifications API</h3>
<p>
Display desktop notifications to the user. This also requires
permission.
</p>
<button id="notify-btn" class="btn">Show Notification</button>
<p id="notify-output"></p>
<h4>Code</h4>
<pre><code>const notifyBtn = document.getElementById('notify-btn');

notifyBtn.addEventListener('click', () => {
if (!("Notification" in window)) {
alert("This browser does not support desktop notification");
} else if (Notification.permission === "granted") {
new Notification("Hi there!", { body: "This is a notification." });
} else if (Notification.permission !== "denied") {
Notification.requestPermission().then((permission) => {
if (permission === "granted") {
new Notification("Hi there!", { body: "This is a notification." });
}
});
}
});</code></pre>
</section>

<hr />

<!-- Speech Synthesis API Demo -->
<section id="speech-demo">
<h3>3. Web Speech API (Synthesis)</h3>
<p>Convert text to speech right in the browser.</p>
<input type="text" id="speech-text" value="Hello, world!" />
<button id="speak-btn" class="btn">Speak</button>
<p id="speech-output"></p>
<h4>Code</h4>
<pre><code>const speakBtn = document.getElementById('speak-btn');
const speechText = document.getElementById('speech-text');

speakBtn.addEventListener('click', () => {
if ('speechSynthesis' in window) {
const utterance = new SpeechSynthesisUtterance(speechText.value);
window.speechSynthesis.speak(utterance);
} else {
alert('Speech synthesis is not available.');
}
});</code></pre>
</section>
</div>
</main>

<footer>
<div class="container">
<p>
Created with ❤️ and vanilla JS. Fork this project on
<a href="https://github.com/attogram/static-magic" target="_blank"
>GitHub</a
>.
</p>
</div>
</footer>

<button id="theme-switcher" class="btn">Dark Mode</button>

<script src="../script.js"></script>
<script>
// Geolocation
const geoBtn = document.getElementById("geo-btn");
const geoOutput = document.getElementById("geo-output");
geoBtn.addEventListener("click", () => {
geoOutput.textContent = "Getting location...";
if ("geolocation" in navigator) {
navigator.geolocation.getCurrentPosition(
(position) => {
const lat = position.coords.latitude.toFixed(4);
const lon = position.coords.longitude.toFixed(4);
geoOutput.textContent = `Latitude: ${lat}, Longitude: ${lon}`;
},
(error) => {
geoOutput.textContent = `Error: ${error.message}`;
}
);
} else {
geoOutput.textContent =
"Geolocation is not available in this browser.";
}
});

// Notifications
const notifyBtn = document.getElementById("notify-btn");
const notifyOutput = document.getElementById("notify-output");
notifyBtn.addEventListener("click", () => {
if (!("Notification" in window)) {
notifyOutput.textContent =
"This browser does not support desktop notification.";
return;
}
if (Notification.permission === "granted") {
new Notification("Hi there!", {
body: "This is a notification from the demo.",
});
notifyOutput.textContent = "Notification sent!";
} else if (Notification.permission !== "denied") {
Notification.requestPermission().then((permission) => {
if (permission === "granted") {
new Notification("Hi there!", {
body: "This is a notification from the demo.",
});
notifyOutput.textContent =
"Permission granted! Notification sent.";
} else {
notifyOutput.textContent = "Permission denied.";
}
});
} else {
notifyOutput.textContent = "Notification permission has been denied.";
}
});

// Speech Synthesis
const speakBtn = document.getElementById("speak-btn");
const speechText = document.getElementById("speech-text");
const speechOutput = document.getElementById("speech-output");
speakBtn.addEventListener("click", () => {
if ("speechSynthesis" in window) {
if (speechText.value.trim() !== "") {
const utterance = new SpeechSynthesisUtterance(speechText.value);
window.speechSynthesis.speak(utterance);
speechOutput.textContent = `Speaking...`;
utterance.onend = () => {
speechOutput.textContent = "Finished speaking.";
};
} else {
speechOutput.textContent = "Please enter some text to speak.";
}
} else {
speechOutput.textContent =
"Speech synthesis is not available in this browser.";
}
});
</script>
</body>
</html>
Loading