diff --git a/README.md b/README.md index 156d528..62c6d61 100644 --- a/README.md +++ b/README.md @@ -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! diff --git a/features/browser-apis.html b/features/browser-apis.html new file mode 100644 index 0000000..54fb8a5 --- /dev/null +++ b/features/browser-apis.html @@ -0,0 +1,230 @@ + + + + + + Browser API Demos - Getting Interactive with GitHub Pages + + + +
+
+

Getting Interactive with GitHub Pages

+

Frontend magic for GitHub Pages - zero backend, pure fun!

+
+
+ + + +
+
+
+

Browser API Demos

+

+ 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. +

+

Here are a few examples of what you can do.

+
+ +
+ + +
+

1. Geolocation API

+

+ Get the user's geographical location. This requires user permission. +

+ +

+

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.';
+    }
+});
+
+ +
+ + +
+

2. Notifications API

+

+ Display desktop notifications to the user. This also requires + permission. +

+ +

+

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." });
+            }
+        });
+    }
+});
+
+ +
+ + +
+

3. Web Speech API (Synthesis)

+

Convert text to speech right in the browser.

+ + +

+

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.');
+    }
+});
+
+
+
+ + + + + + + + + diff --git a/features/clipboard.html b/features/clipboard.html new file mode 100644 index 0000000..a87679e --- /dev/null +++ b/features/clipboard.html @@ -0,0 +1,180 @@ + + + + + + Clipboard Interaction - Getting Interactive with GitHub Pages + + + + +
+
+

Getting Interactive with GitHub Pages

+

Frontend magic for GitHub Pages - zero backend, pure fun!

+
+
+ + + +
+
+
+

Clipboard Interaction with the Clipboard API

+

+ A "Copy to Clipboard" button is a small but incredibly useful + feature. The modern way to implement this is with the asynchronous + navigator.clipboard API. It's more secure and powerful + than the old document.execCommand('copy') method. +

+

+ The API is promise-based, making it easy to work with and provide + feedback to the user upon success or failure. Note that this API + requires a secure context (like HTTPS), which is automatically + provided by GitHub Pages. +

+
+ +
+

Live Demo

+ + +
+
+ +
+

The Code

+

+ The JavaScript is straightforward: get the text, call the API, and + handle the promise. +

+ +

HTML

+
<textarea id="copy-text">Some text to copy.</textarea>
+<button id="copy-btn" class="btn">Copy Text</button>
+<div id="copy-feedback"></div>
+ +

JavaScript

+
const copyText = document.getElementById('copy-text');
+const copyBtn = document.getElementById('copy-btn');
+const copyFeedback = document.getElementById('copy-feedback');
+
+copyBtn.addEventListener('click', () => {
+    const textToCopy = copyText.value;
+
+    if (!navigator.clipboard) {
+        copyFeedback.textContent = 'Clipboard API not available.';
+        return;
+    }
+
+    navigator.clipboard.writeText(textToCopy).then(() => {
+        copyFeedback.textContent = 'Copied to clipboard!';
+        copyFeedback.style.color = 'green';
+
+        const originalText = copyBtn.textContent;
+        copyBtn.textContent = 'Copied!';
+
+        setTimeout(() => {
+            copyFeedback.textContent = '';
+            copyBtn.textContent = originalText;
+        }, 2000);
+    }).catch(err => {
+        copyFeedback.textContent = `Failed to copy: ${err}`;
+        copyFeedback.style.color = 'red';
+    });
+});
+
+
+
+
+ + + + + + + + + diff --git a/features/drag-drop.html b/features/drag-drop.html new file mode 100644 index 0000000..52cc8b6 --- /dev/null +++ b/features/drag-drop.html @@ -0,0 +1,222 @@ + + + + + + Drag and Drop UI - Getting Interactive with GitHub Pages + + + + +
+
+

Getting Interactive with GitHub Pages

+

Frontend magic for GitHub Pages - zero backend, pure fun!

+
+
+ + + +
+
+
+

Drag and Drop UI

+

+ The HTML5 Drag and Drop API provides a native way to create user + interfaces where elements can be moved and reordered. It's a + powerful tool for building interactive applications like Kanban + boards or reorderable lists. +

+

+ The API is event-driven. Key events include + dragstart (when you start dragging), + dragover (when you're dragging over a valid drop + target), and drop (when you release the mouse). +

+
+ +
+

Live Demo

+

Drag the items between the lanes.

+
+
+
Item 1
+
Item 2
+
+
+
Item 3
+
+
+
+
+ +
+

The Code

+

+ The logic involves adding event listeners for the various + drag-and-drop events to both the draggable items and the drop + targets (lanes). +

+ +

HTML

+
<div class="drop-lanes">
+    <div class="lane" id="lane-1">
+        <div class="draggable" draggable="true" id="item-1">Item 1</div>
+    </div>
+    <div class="lane" id="lane-2"></div>
+</div>
+ +

CSS (for visual feedback)

+
.draggable.dragging {
+    opacity: 0.5;
+}
+.lane.drag-over {
+    background-color: rgba(0, 123, 255, 0.1);
+    border-style: solid;
+}
+ +

JavaScript

+
const draggables = document.querySelectorAll('.draggable');
+const lanes = document.querySelectorAll('.lane');
+
+draggables.forEach(draggable => {
+    draggable.addEventListener('dragstart', (e) => {
+        draggable.classList.add('dragging');
+        e.dataTransfer.setData('text/plain', draggable.id);
+    });
+
+    draggable.addEventListener('dragend', () => {
+        draggable.classList.remove('dragging');
+    });
+});
+
+lanes.forEach(lane => {
+    lane.addEventListener('dragover', e => {
+        e.preventDefault(); // Necessary to allow dropping
+        lane.classList.add('drag-over');
+    });
+
+    lane.addEventListener('dragleave', () => {
+        lane.classList.remove('drag-over');
+    });
+
+    lane.addEventListener('drop', e => {
+        e.preventDefault();
+        const id = e.dataTransfer.getData('text/plain');
+        const draggable = document.getElementById(id);
+        if (draggable) {
+            lane.appendChild(draggable);
+        }
+        lane.classList.remove('drag-over');
+    });
+});
+
+
+
+
+ + + + + + + + + diff --git a/features/filter-search.html b/features/filter-search.html new file mode 100644 index 0000000..99348f7 --- /dev/null +++ b/features/filter-search.html @@ -0,0 +1,170 @@ + + + + + + + Filterable Lists & Search - Getting Interactive with GitHub Pages + + + + + +
+
+

Getting Interactive with GitHub Pages

+

Frontend magic for GitHub Pages - zero backend, pure fun!

+
+
+ + + +
+
+
+

Filterable Lists & Client-Side Search

+

+ A fast, responsive search experience is crucial for good UX. For + many sites, you don't need a powerful server-side search engine. If + your dataset is reasonably small (like a list of products, articles, + or users), you can implement a near-instant search feature using + just JavaScript. +

+

+ The technique is simple: listen for keyboard input in a search box, + and then iterate through your list of items, hiding those that don't + match the search query. +

+
+ +
+

Live Demo

+ +
    +
  • Apple
  • +
  • Banana
  • +
  • Orange
  • +
  • Grape
  • +
  • Strawberry
  • +
  • Blueberry
  • +
  • Mango
  • +
  • Pineapple
  • +
+
+ +
+

The Code

+

+ You need an input field and a list of items in your HTML. The + JavaScript will handle the filtering logic. +

+ +

HTML

+
<input type="text" id="search-input" placeholder="Search for a fruit...">
+<ul id="item-list">
+    <li>Apple</li>
+    <li>Banana</li>
+    <li>Orange</li>
+    <li>Grape</li>
+    <li>Strawberry</li>
+    <li>Blueberry</li>
+    <li>Mango</li>
+    <li>Pineapple</li>
+</ul>
+ +

JavaScript

+
const searchInput = document.getElementById('search-input');
+const itemList = document.getElementById('item-list');
+const listItems = itemList.getElementsByTagName('li');
+
+searchInput.addEventListener('input', (event) => {
+    const query = event.target.value.toLowerCase().trim();
+
+    for (let i = 0; i < listItems.length; i++) {
+        const item = listItems[i];
+        const text = item.textContent.toLowerCase();
+
+        if (text.includes(query)) {
+            item.style.display = '';
+        } else {
+            item.style.display = 'none';
+        }
+    }
+});
+
+
+
+ + + + + + + + + diff --git a/features/forms.html b/features/forms.html new file mode 100644 index 0000000..f6a2009 --- /dev/null +++ b/features/forms.html @@ -0,0 +1,156 @@ + + + + + + Forms Without Servers - Getting Interactive with GitHub Pages + + + +
+
+

Getting Interactive with GitHub Pages

+

Frontend magic for GitHub Pages - zero backend, pure fun!

+
+
+ + + +
+
+
+

Forms Without Servers

+

+ One of the most common myths about static sites is that you can't + handle form submissions. While you can't have a traditional + server-side script to process the data, you can absolutely capture + user input and create interactive experiences entirely on the + client-side with JavaScript. +

+
+ +
+

Use Cases

+
    +
  • + Calculators: Price estimators, unit converters, + or fitness calculators that provide instant results. +
  • +
  • + Quizzes: Simple quizzes where the score is + calculated and displayed in the browser. +
  • +
  • + Interactive Checklists: Tools that let users + check off items and see a summary. +
  • +
  • + Contact Forms (with a twist): Forms that gather + user input and then open the user's default email client with a + pre-filled mailto: link. +
  • +
+
+ +
+

Live Demo

+
+ + + +
+
+
+ +
+

The Code

+

+ Here's the full code to make the demo above work. You can copy and + paste this into your own HTML file. +

+ +

HTML

+
<form id="greeting-form">
+    <label for="name-input">Enter your name:</label>
+    <input type="text" id="name-input" required>
+    <button type="submit" class="btn">Say Hello</button>
+</form>
+<div id="greeting-output"></div>
+ +

JavaScript

+
const form = document.getElementById('greeting-form');
+const nameInput = document.getElementById('name-input');
+const outputDiv = document.getElementById('greeting-output');
+
+form.addEventListener('submit', (event) => {
+    // Prevent the form from submitting the traditional way
+    event.preventDefault();
+
+    // Get the value from the input field
+    const name = nameInput.value;
+
+    // Display the greeting
+    outputDiv.textContent = `Hello, ${name}!`;
+
+    // Optional: Clear the input field
+    nameInput.value = '';
+});
+
+
+
+ + + + + + + + + diff --git a/features/json-viewer.html b/features/json-viewer.html new file mode 100644 index 0000000..b3d0fae --- /dev/null +++ b/features/json-viewer.html @@ -0,0 +1,228 @@ + + + + + + + JSON Viewer / Formatter - Getting Interactive with GitHub Pages + + + + + +
+
+

Getting Interactive with GitHub Pages

+

Frontend magic for GitHub Pages - zero backend, pure fun!

+
+
+ + + +
+
+
+

JSON Viewer and Formatter

+

+ Working with JSON is a daily task for many developers. A simple + in-browser tool to quickly format and inspect JSON can be very + handy. This can be built easily using JavaScript's built-in + JSON object. +

+

+ We'll use JSON.parse() to validate the input and + JSON.stringify() to pretty-print it. For a better + experience, we'll also add some basic syntax highlighting using + regular expressions. +

+
+ +
+

Live Demo

+ + +

+        
+ +
+

The Code

+

+ The core logic is small, with the syntax highlighting being the most + complex part. +

+ +

HTML

+
<textarea id="json-input" placeholder="Paste your JSON here..."></textarea>
+<button id="format-btn" class="btn">Format JSON</button>
+<pre id="json-output"></pre>
+ +

CSS (for Syntax Highlighting)

+
.json-key { color: #bb86fc; }
+.json-string { color: #03dac6; }
+.json-number { color: #cf6679; }
+.json-boolean { color: #ffab40; }
+.json-null { color: #b0bec5; }
+ +

JavaScript

+
const jsonInput = document.getElementById('json-input');
+const formatBtn = document.getElementById('format-btn');
+const jsonOutput = document.getElementById('json-output');
+
+function syntaxHighlight(json) {
+    json = json.replace(/&/g, '&').replace(/<\/g, '&lt;').replace(/>/g, '&gt;');
+    return json.replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g, function (match) {
+        let cls = 'json-number';
+        if (/^"/.test(match)) {
+            if (/:$/.test(match)) {
+                cls = 'json-key';
+            } else {
+                cls = 'json-string';
+            }
+        } else if (/true|false/.test(match)) {
+            cls = 'json-boolean';
+        } else if (/null/.test(match)) {
+            cls = 'json-null';
+        }
+        return '<span class="' + cls + '">' + match + '</span>';
+    });
+}
+
+formatBtn.addEventListener('click', () => {
+    try {
+        const jsonObj = JSON.parse(jsonInput.value);
+        const prettyJson = JSON.stringify(jsonObj, null, 4);
+        jsonOutput.innerHTML = syntaxHighlight(prettyJson);
+    } catch (error) {
+        jsonOutput.innerHTML = `<span style="color: red;">Invalid JSON: ${error.message}</span>`;
+    }
+});
+
+// Set some default JSON and format it on page load
+jsonInput.value = '{\n  "name": "Jules",\n  "isAgent": true,\n  "skills": ["HTML", "CSS", "JavaScript"],\n  "experience": null\n}';
+formatBtn.click(); // Trigger a click to format the default JSON
+
+
+
+
+ + + + + + + + + diff --git a/features/live-js-runner.html b/features/live-js-runner.html new file mode 100644 index 0000000..9fdf3df --- /dev/null +++ b/features/live-js-runner.html @@ -0,0 +1,189 @@ + + + + + + + Live JavaScript Runner - Getting Interactive with GitHub Pages + + + + + +
+
+

Getting Interactive with GitHub Pages

+

Frontend magic for GitHub Pages - zero backend, pure fun!

+
+
+ + + +
+
+
+

Live JavaScript Runner

+

+ You can build a simple JavaScript playground right in the browser. + This allows users to write and execute code on the fly, seeing the + results instantly. It's a powerful tool for learning and + experimentation. +

+

+ Security Note: Executing arbitrary code from users + can be risky. While we're using new Function() which is + safer than eval(), this approach should still be used + with caution, especially in a production environment with sensitive + user data. For a simple educational tool on a static site, the risk + is minimal. +

+
+ +
+

Live Demo

+ + +

Output:

+

+        
+ +
+

The Code

+

+ The magic is in capturing the console.log messages and + executing the user's code in a controlled way. +

+ +

HTML

+
<textarea id="js-code" placeholder="console.log('Hello, world!');"></textarea>
+<button id="run-btn" class="btn">Run</button>
+<h4>Output:</h4>
+<pre id="js-output"></pre>
+ +

JavaScript

+
const codeArea = document.getElementById('js-code');
+const runButton = document.getElementById('run-btn');
+const outputArea = document.getElementById('js-output');
+
+runButton.addEventListener('click', () => {
+    const userCode = codeArea.value;
+    let output = '';
+
+    // Temporarily override console.log to capture output
+    const oldLog = console.log;
+    console.log = (...args) => {
+        output += args.map(arg => JSON.stringify(arg, null, 2)).join(' ') + '\\n';
+    };
+
+    try {
+        // Use new Function() to execute the code
+        new Function(userCode)();
+        outputArea.style.color = 'var(--text-color)';
+    } catch (error) {
+        output = `Error: ${error.message}`;
+        outputArea.style.color = 'red';
+    } finally {
+        // Restore the original console.log
+        console.log = oldLog;
+        outputArea.textContent = output || '(No output)';
+    }
+});
+
+
+
+ + + + + + + + + diff --git a/features/markdown-converter.html b/features/markdown-converter.html new file mode 100644 index 0000000..421373b --- /dev/null +++ b/features/markdown-converter.html @@ -0,0 +1,195 @@ + + + + + + + Markdown to HTML Converter - Getting Interactive with GitHub Pages + + + + + +
+
+

Getting Interactive with GitHub Pages

+

Frontend magic for GitHub Pages - zero backend, pure fun!

+
+
+ + + +
+
+
+

Live Markdown to HTML Converter

+

+ Markdown is a popular way to write formatted text. With a + client-side JavaScript library, you can easily convert Markdown into + HTML in real-time, creating a live preview editor. +

+

+ For this demo, we're using a library called + Showdown.js, + included from a CDN (Content Delivery Network). This means we don't + need to host the library ourselves. +

+
+ +
+

Live Demo

+
+ +
+
+
+ +
+

The Code

+

+ You'll need to include the Showdown.js library from a CDN in your + HTML, then write a short script to tie it to your input and output + elements. +

+ +

HTML

+
<!-- Include Showdown.js from a CDN -->
+<script src="https://cdnjs.cloudflare.com/ajax/libs/showdown/2.1.0/showdown.min.js"></script>
+
+<div class="converter-container">
+    <textarea id="markdown-input"></textarea>
+    <div id="html-output"></div>
+</div>
+ +

JavaScript

+
const markdownInput = document.getElementById('markdown-input');
+const htmlOutput = document.getElementById('html-output');
+
+// Create a new Showdown converter with some options
+const converter = new showdown.Converter({
+    tables: true,
+    strikethrough: true,
+    tasklists: true,
+    simpleLineBreaks: true
+});
+
+// Function to update the output
+const updateOutput = () => {
+    const markdownText = markdownInput.value;
+    const html = converter.makeHtml(markdownText);
+    htmlOutput.innerHTML = html;
+};
+
+// Listen for input events on the textarea
+markdownInput.addEventListener('input', updateOutput);
+
+// Set some default text and do an initial conversion
+markdownInput.value = "# Hello, Markdown!\\n\\nType some **Markdown** here.\\n\\n- List item 1\\n- List item 2";
+updateOutput();
+
+
+
+
+ + + + + + + + + + + diff --git a/features/mermaid-diagrams.html b/features/mermaid-diagrams.html new file mode 100644 index 0000000..eb5c3b3 --- /dev/null +++ b/features/mermaid-diagrams.html @@ -0,0 +1,204 @@ + + + + + + + Interactive Diagrams with Mermaid.js - Getting Interactive with GitHub + Pages + + + + +
+
+

Getting Interactive with GitHub Pages

+

Frontend magic for GitHub Pages - zero backend, pure fun!

+
+
+ + + +
+
+
+

Interactive Diagrams with Mermaid.js

+

+ Adding diagrams to your documentation or blog posts can make complex + ideas much easier to understand. + Mermaid.js + is a fantastic library that lets you create beautiful diagrams and + charts from simple, text-based syntax, similar to Markdown. +

+

+ By including the library from a CDN, you can render flowcharts, + sequence diagrams, Gantt charts, and more, right in the browser. +

+
+ +
+

Live Demo

+ + +
+
+
+ +
+

The Code

+

+ Include the Mermaid.js script from a CDN, then use its API to render + your diagram definition. +

+ +

HTML

+
<!-- Include Mermaid.js from a CDN -->
+<script src="https://cdn.jsdelivr.net/npm/mermaid@9/dist/mermaid.min.js"></script>
+
+<textarea id="mermaid-input"></textarea>
+<button id="render-btn" class="btn">Render Diagram</button>
+<div id="mermaid-output"></div>
+<div id="mermaid-error"></div>
+ +

JavaScript

+
// Initialize Mermaid.js
+mermaid.initialize({ startOnLoad: false });
+
+const mermaidInput = document.getElementById('mermaid-input');
+const renderBtn = document.getElementById('render-btn');
+const mermaidOutput = document.getElementById('mermaid-output');
+const errorDiv = document.getElementById('mermaid-error');
+
+const renderDiagram = async () => {
+    errorDiv.textContent = '';
+    mermaidOutput.innerHTML = 'Rendering...';
+    const definition = mermaidInput.value;
+
+    try {
+        // A unique ID is needed for each render
+        const uniqueId = `mermaid-graph-${Date.now()}`;
+        const { svg, bindFunctions } = await mermaid.render(uniqueId, definition);
+        mermaidOutput.innerHTML = svg;
+        if (bindFunctions) {
+            bindFunctions(mermaidOutput);
+        }
+    } catch (error) {
+        mermaidOutput.innerHTML = '';
+        errorDiv.textContent = error;
+    }
+};
+
+renderBtn.addEventListener('click', renderDiagram);
+
+// Default diagram
+mermaidInput.value = `graph TD
+    A[Start] --> B{Is it interactive?};
+    B -->|Yes| C[Awesome!];
+    B -->|No| D[Add some JS!];
+    C --> E[End];
+    D --> E[End];`;
+
+renderDiagram();
+
+
+
+
+ + + + + + + + + + + diff --git a/features/notes-app.html b/features/notes-app.html new file mode 100644 index 0000000..adff5b6 --- /dev/null +++ b/features/notes-app.html @@ -0,0 +1,253 @@ + + + + + + Persistent Notes App - Getting Interactive with GitHub Pages + + + + +
+
+

Getting Interactive with GitHub Pages

+

Frontend magic for GitHub Pages - zero backend, pure fun!

+
+
+ + + +
+
+
+

Persistent Notes App with localStorage

+

+ Want to save user data without a server? The browser's + localStorage API is the perfect tool for the job. It + allows you to save key-value pairs that persist even after the + browser window is closed. +

+

+ This makes it ideal for creating simple applications like to-do + lists, settings panels, and, as we'll demonstrate here, a persistent + notes app. +

+
+ +
+

Live Demo

+ + +

Your Notes:

+
    +
    + +
    +

    The Code

    +

    + We need HTML for the interface and JavaScript to manage the notes. + The core logic involves reading from and writing to + localStorage. +

    + +

    HTML

    +
    <textarea id="note-input" placeholder="Write a new note..."></textarea>
    +<button id="add-note-btn" class="btn">Add Note</button>
    +<h3>Your Notes:</h3>
    +<ul id="notes-list"></ul>
    + +

    JavaScript

    +
    const noteInput = document.getElementById('note-input');
    +const addNoteBtn = document.getElementById('add-note-btn');
    +const notesList = document.getElementById('notes-list');
    +
    +// Load notes from localStorage
    +let notes = JSON.parse(localStorage.getItem('notes')) || [];
    +
    +// Function to render notes
    +const renderNotes = () => {
    +    notesList.innerHTML = '';
    +    // Render in reverse order to show newest first
    +    notes.slice().reverse().forEach((noteText, index) => {
    +        const li = document.createElement('li');
    +        // This is the original index in the 'notes' array
    +        const originalIndex = notes.length - 1 - index;
    +
    +        // Sanitize note text before displaying to prevent HTML injection
    +        const sanitizedText = noteText.replace(/&/g, '&').replace(/<\/g, '&lt;').replace(/>/g, '&gt;');
    +
    +        li.innerHTML = `
    +            <span>${sanitizedText}</span>
    +            <button class="delete-btn" data-index="${originalIndex}">Delete</button>
    +        `;
    +        notesList.appendChild(li);
    +    });
    +};
    +
    +// Function to save notes
    +const saveNotes = () => {
    +    localStorage.setItem('notes', JSON.stringify(notes));
    +};
    +
    +// Add note event
    +addNoteBtn.addEventListener('click', () => {
    +    const newNote = noteInput.value.trim();
    +    if (newNote) {
    +        notes.push(newNote);
    +        saveNotes();
    +        renderNotes();
    +        noteInput.value = '';
    +    }
    +});
    +
    +// Delete note event (using event delegation)
    +notesList.addEventListener('click', (event) => {
    +    if (event.target.classList.contains('delete-btn')) {
    +        const index = parseInt(event.target.getAttribute('data-index'), 10);
    +        notes.splice(index, 1);
    +        saveNotes();
    +        renderNotes();
    +    }
    +});
    +
    +// Initial render
    +renderNotes();
    +
    +
    +
    + + + + + + + + + diff --git a/features/page-state.html b/features/page-state.html new file mode 100644 index 0000000..40b24ef --- /dev/null +++ b/features/page-state.html @@ -0,0 +1,245 @@ + + + + + + Page State Memory - Getting Interactive with GitHub Pages + + + + +
    +
    +

    Getting Interactive with GitHub Pages

    +

    Frontend magic for GitHub Pages - zero backend, pure fun!

    +
    +
    + + + +
    +
    +
    +

    Remembering Page State

    +

    + A great user experience often means not forgetting what the user was + doing. Using the browser's storage APIs, you can easily save the + state of UI elements, so that if the user reloads the page or comes + back later, their settings and input are preserved. +

    +
      +
    • + localStorage: Stores data with no expiration date. It will persist until the + user manually clears their browser cache. +
    • +
    • + sessionStorage: Stores data for one session only. The data is lost when the + browser tab is closed. +
    • +
    +

    + For this demo, we'll use localStorage to make the state + persistent across visits. +

    +
    + +
    +

    Live Demo

    +

    + Change the values in the form below, then reload the page. Your + changes will be remembered. +

    +
    + + + + + + + +
    + +
    + +
    +

    The Code

    +

    + The logic involves listening for changes on the form inputs and + saving the values to localStorage. On page load, we + check for saved values and apply them. +

    + +

    HTML

    +
    <form id="state-form">
    +    <label for="username">Username:</label>
    +    <input type="text" id="username">
    +
    +    <label for="volume">Volume: <span id="volume-value">50</span></label>
    +    <input type="range" id="volume" min="0" max="100">
    +
    +    <label>
    +        <input type="checkbox" id="notifications">
    +        Enable notifications
    +    </label>
    +</form>
    +<button id="clear-state-btn" class="btn">Clear Saved State</button>
    + +

    JavaScript

    +
    const form = document.getElementById('state-form');
    +const usernameInput = document.getElementById('username');
    +const volumeSlider = document.getElementById('volume');
    +const volumeValue = document.getElementById('volume-value');
    +const notificationsCheckbox = document.getElementById('notifications');
    +const clearBtn = document.getElementById('clear-state-btn');
    +
    +const storageKey = 'formState';
    +
    +// Function to save state
    +function saveState() {
    +    const state = {
    +        username: usernameInput.value,
    +        volume: volumeSlider.value,
    +        notifications: notificationsCheckbox.checked
    +    };
    +    localStorage.setItem(storageKey, JSON.stringify(state));
    +}
    +
    +// Function to load state
    +function loadState() {
    +    const savedState = JSON.parse(localStorage.getItem(storageKey));
    +    if (savedState) {
    +        usernameInput.value = savedState.username || '';
    +        volumeSlider.value = savedState.volume || 50;
    +        notificationsCheckbox.checked = savedState.notifications || false;
    +    }
    +    volumeValue.textContent = volumeSlider.value;
    +}
    +
    +// Event Listeners
    +form.addEventListener('input', (e) => {
    +    if(e.target.id === 'volume') {
    +        volumeValue.textContent = e.target.value;
    +    }
    +    saveState();
    +});
    +
    +clearBtn.addEventListener('click', () => {
    +    localStorage.removeItem(storageKey);
    +    window.location.reload();
    +});
    +
    +// Load the state when the page loads
    +loadState();
    +
    +
    +
    +
    + + + + + + + + + diff --git a/features/spa-routing.html b/features/spa-routing.html new file mode 100644 index 0000000..68fa55c --- /dev/null +++ b/features/spa-routing.html @@ -0,0 +1,216 @@ + + + + + + + Client-Side Routing (SPA-style) - Getting Interactive with GitHub Pages + + + + + +
    +
    +

    Getting Interactive with GitHub Pages

    +

    Frontend magic for GitHub Pages - zero backend, pure fun!

    +
    +
    + + + +
    +
    +
    +

    Client-Side Routing (SPA-style)

    +

    + You can simulate the navigation of a Single-Page Application (SPA) + without a complex framework. The secret is using the URL's "hash" + (the part after the #). By listening for changes to the + hash, you can show and hide content dynamically, creating a fast, + app-like experience. +

    +

    + This technique is perfect for simple SPAs, tabbed interfaces, or + deep-linking to specific sections of a page. The browser's history + is even updated, so the back and forward buttons work as expected. +

    +
    + +
    +

    Live Demo

    + +
    +
    +

    Home View

    +

    + This is the home content. Notice the URL has changed to include + #home. +

    +
    +
    +

    About View

    +

    This is the about page content. The URL now ends in #about.

    +
    +
    +

    Contact View

    +

    This is the contact section. The URL hash is #contact.

    +
    +
    +
    + +
    +

    The Code

    +

    + The JavaScript listens for the hashchange event on the + window and a DOMContentLoaded event to handle the + initial page load. +

    + +

    HTML

    +
    <nav class="spa-nav">
    +    <a href="#home">Home</a>
    +    <a href="#about">About</a>
    +    <a href="#contact">Contact</a>
    +</nav>
    +
    +<div id="router-output">
    +    <div id="home" class="page-content">...</div>
    +    <div id="about" class="page-content">...</div>
    +    <div id="contact" class="page-content">...</div>
    +</div>
    + +

    JavaScript

    +
    document.addEventListener('DOMContentLoaded', () => {
    +    const contentPages = document.querySelectorAll('.page-content');
    +    const spaNavLinks = document.querySelectorAll('.spa-nav a');
    +    const defaultRoute = 'home';
    +
    +    function handleRouteChange() {
    +        const route = window.location.hash.substring(1) || defaultRoute;
    +
    +        contentPages.forEach(page => {
    +            page.classList.toggle('active', page.id === route);
    +        });
    +
    +        spaNavLinks.forEach(link => {
    +            link.classList.toggle('active', link.hash === `#${route}`);
    +        });
    +    }
    +
    +    window.addEventListener('hashchange', handleRouteChange);
    +
    +    // Set initial hash if none is present and handle initial load
    +    if (!window.location.hash) {
    +        window.location.hash = defaultRoute;
    +    } else {
    +        handleRouteChange();
    +    }
    +});
    +
    +
    +
    +
    + + + + + + + + + diff --git a/features/theme-switcher.html b/features/theme-switcher.html new file mode 100644 index 0000000..2621419 --- /dev/null +++ b/features/theme-switcher.html @@ -0,0 +1,171 @@ + + + + + + + Theme Switcher (Light/Dark) - Getting Interactive with GitHub Pages + + + + +
    +
    +

    Getting Interactive with GitHub Pages

    +

    Frontend magic for GitHub Pages - zero backend, pure fun!

    +
    +
    + + + +
    +
    +
    +

    Theme Switcher (Light/Dark Mode)

    +

    + A theme switcher is a fantastic feature that respects user + preferences and improves accessibility. It's surprisingly easy to + implement on a static site using CSS variables and a little bit of + JavaScript to handle localStorage. +

    +
    + +
    +

    Live Demo

    +

    + The live demo is right here on this page! Look for the "Dark Mode" / + "Light Mode" button, likely in the top-right corner. Click it to + toggle the theme. Your preference will be saved and applied + automatically on your next visit. +

    +
    + This is a sample container to demonstrate the theme change. +
    +
    + +
    +

    The Code

    +

    + The implementation involves three parts: CSS variables for the + themes, a JavaScript function to toggle the theme and save the + preference, and a button in your HTML. +

    + +

    1. The CSS

    +

    + In your main stylesheet (style.css), define two sets of + color variables. One for the default (light) theme, and one for the + dark theme, triggered by a class on the body element. +

    +
    /* Light Theme (Default) */
    +:root {
    +    --bg-color: #ffffff;
    +    --text-color: #333333;
    +    --header-bg: #f8f9fa;
    +    /* ... other light theme variables */
    +}
    +
    +/* Dark Theme */
    +body.dark-mode {
    +    --bg-color: #121212;
    +    --text-color: #e0e0e0;
    +    --header-bg: #1f1f1f;
    +    /* ... other dark theme variables */
    +}
    +
    +body {
    +    background-color: var(--bg-color);
    +    color: var(--text-color);
    +    transition: background-color 0.3s, color 0.3s;
    +}
    + +

    2. The HTML

    +

    + Place a button in your HTML file. This button will be used to + trigger the theme change. +

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

    3. The JavaScript

    +

    + This script checks for a saved theme in localStorage, + applies it on page load, and adds an event listener to the button to + toggle the theme. +

    +
    document.addEventListener('DOMContentLoaded', () => {
    +    const themeSwitcher = document.getElementById('theme-switcher');
    +    const currentTheme = localStorage.getItem('theme');
    +
    +    // Apply saved theme or OS preference
    +    if (currentTheme) {
    +        document.body.classList.toggle('dark-mode', currentTheme === 'dark');
    +        themeSwitcher.textContent = currentTheme === 'dark' ? 'Light Mode' : 'Dark Mode';
    +    } else if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
    +        document.body.classList.add('dark-mode');
    +        themeSwitcher.textContent = 'Light Mode';
    +    } else {
    +        themeSwitcher.textContent = 'Dark Mode';
    +    }
    +
    +    // Theme switcher button event listener
    +    themeSwitcher.addEventListener('click', () => {
    +        document.body.classList.toggle('dark-mode');
    +        let theme = 'light';
    +        if (document.body.classList.contains('dark-mode')) {
    +            theme = 'dark';
    +        }
    +        localStorage.setItem('theme', theme);
    +        themeSwitcher.textContent = theme === 'dark' ? 'Light Mode' : 'Dark Mode';
    +    });
    +});
    +
    +
    +
    + + + + + + + + diff --git a/games/clicker.html b/games/clicker.html new file mode 100644 index 0000000..b4f6c7a --- /dev/null +++ b/games/clicker.html @@ -0,0 +1,196 @@ + + + + + + Clicker Game - Games + + + + + +
    +
    +

    Clicker Game

    +
    +
    + +
    +
    +
    +

    Score: 0

    + +
    + +

    Points per click: 1

    +
    +
    +
    +

    How it Works

    +

    + This is a simple clicker game. Click the button to increase your + score. You can spend your score to buy upgrades, which increase the + number of points you get per click. Your score and upgrade level are + saved in your browser's localStorage, so you can close + the page and come back to it later! +

    +

    The Code

    +
    <!-- HTML -->
    +<div class="game-container">
    +    <p id="score">Score: 0</p>
    +    <button id="clicker-btn" class="btn">Click Me!</button>
    +    <div>
    +        <button id="upgrade-btn" class="btn">Upgrade Click (Cost: 10)</button>
    +        <p>Points per click: <span id="click-power">1</span></p>
    +    </div>
    +</div>
    +
    +<!-- JavaScript -->
    +const scoreDisplay = document.getElementById('score');
    +const clickerBtn = document.getElementById('clicker-btn');
    +const upgradeBtn = document.getElementById('upgrade-btn');
    +const clickPowerDisplay = document.getElementById('click-power');
    +
    +let score = 0;
    +let clickPower = 1;
    +let upgradeCost = 10;
    +
    +function loadGame() {
    +    const savedGame = JSON.parse(localStorage.getItem('clickerGame'));
    +    if (savedGame) {
    +        score = parseInt(savedGame.score, 10) || 0;
    +        clickPower = parseInt(savedGame.clickPower, 10) || 1;
    +        upgradeCost = parseInt(savedGame.upgradeCost, 10) || 10;
    +    }
    +    updateDisplay();
    +}
    +
    +function saveGame() {
    +    localStorage.setItem('clickerGame', JSON.stringify({ score, clickPower, upgradeCost }));
    +}
    +
    +function updateDisplay() {
    +    scoreDisplay.textContent = `Score: ${score}`;
    +    clickPowerDisplay.textContent = clickPower;
    +    upgradeBtn.textContent = `Upgrade Click (Cost: ${upgradeCost})`;
    +    upgradeBtn.disabled = score < upgradeCost;
    +}
    +
    +clickerBtn.addEventListener('click', () => {
    +    score += clickPower;
    +    updateDisplay();
    +    saveGame();
    +});
    +
    +upgradeBtn.addEventListener('click', () => {
    +    if (score >= upgradeCost) {
    +        score -= upgradeCost;
    +        clickPower++;
    +        upgradeCost = Math.ceil(upgradeCost * 1.5);
    +        updateDisplay();
    +        saveGame();
    +    }
    +});
    +
    +loadGame();
    +
    +
    +
    +
    + + + + + + diff --git a/games/drag-drop-puzzle.html b/games/drag-drop-puzzle.html new file mode 100644 index 0000000..88cfab5 --- /dev/null +++ b/games/drag-drop-puzzle.html @@ -0,0 +1,176 @@ + + + + + + Drag and Drop Puzzle - Games + + + + + +
    +
    +

    Drag and Drop Puzzle

    +
    +
    + +
    +
    +
    +
    +

    Puzzle Pieces

    +
    +
    +
    +

    Puzzle Board

    +
    +
    +
    +
    +

    How it Works

    +

    + An image is divided into a 3x3 grid. The pieces are created as divs + with the `background-image` property set to the source image and the + `background-position` adjusted to show the correct part of the + image. The pieces are then shuffled and placed in a "pool". The user + must drag each piece from the pool and drop it into the correct slot + on the puzzle board. The HTML5 Drag and Drop API is used to manage + moving the pieces. +

    +
    +
    +
    + + + + + + diff --git a/games/index.html b/games/index.html new file mode 100644 index 0000000..69ba787 --- /dev/null +++ b/games/index.html @@ -0,0 +1,127 @@ + + + + + + Games - Getting Interactive with GitHub Pages + + + + +
    +
    +

    Getting Interactive with GitHub Pages

    +

    Frontend magic for GitHub Pages - zero backend, pure fun!

    +
    +
    + + + +
    +
    +
    +

    Browser Games Showcase

    +

    + This section showcases simple browser games built with just HTML, + CSS, and vanilla JavaScript. Each game is self-contained and + includes an explanation of how it works and its full source code. +

    +
    + +
    + +
    +
    +
    + + + + + + + + diff --git a/games/maze.html b/games/maze.html new file mode 100644 index 0000000..bf859f5 --- /dev/null +++ b/games/maze.html @@ -0,0 +1,188 @@ + + + + + + Maze Navigation - Games + + + + + +
    +
    +

    Maze Navigation

    +
    +
    + +
    +
    +
    +

    Use the arrow keys to navigate from blue to green.

    +
    + +
    +
    +

    How it Works

    +

    + A simple maze is generated using a 2D array where 'W' represents a + wall and 'P' represents a path. This array is rendered as a grid + using CSS. The player's position is tracked with coordinates. An + event listener for the 'keydown' event checks for arrow key presses. + When a key is pressed, the game logic checks if the target cell is a + wall. If not, the player's position is updated, and the grid is + re-rendered. +

    +

    The Code

    +
    <!-- HTML -->
    +<div id="maze-container"></div>
    +<button id="restart-btn" class="btn">Reset Game</button>
    +
    +
    +
    +
    + + + + + + diff --git a/games/memory-match.html b/games/memory-match.html new file mode 100644 index 0000000..5d8185a --- /dev/null +++ b/games/memory-match.html @@ -0,0 +1,211 @@ + + + + + + Memory Match - Games + + + + + +
    +
    +

    Memory Match Game

    +
    +
    + +
    +
    +
    +

    Moves: 0

    +
    + +
    +
    +

    How it Works

    +

    + This is a classic card matching memory game. A deck of paired cards + is shuffled and laid out face down. The player flips two cards at a + time. If they match, they remain face up. If not, they are flipped + back over. The goal is to find all the pairs. +

    +

    The Code

    +
    <!-- HTML -->
    +<p id="game-status">Moves: 0</p>
    +<div id="memory-game-board"></div>
    +<button id="restart-btn" class="btn">New Game</button>
    +
    +<!-- CSS for card flip -->
    +#memory-game-board { perspective: 1000px; }
    +.card {
    +    transform-style: preserve-3d;
    +    transition: transform 0.6s;
    +}
    +.card.flipped, .card.matched { transform: rotateY(180deg); }
    +.card-face {
    +    position: absolute;
    +    width: 100%; height: 100%;
    +    backface-visibility: hidden;
    +}
    +.card-back { transform: rotateY(180deg); }
    +
    +<!-- JavaScript -->
    +const gameBoard = document.getElementById('memory-game-board');
    +// ... game logic ...
    +
    +
    +
    +
    + + + + + + diff --git a/games/number-guessing.html b/games/number-guessing.html new file mode 100644 index 0000000..a7651ef --- /dev/null +++ b/games/number-guessing.html @@ -0,0 +1,157 @@ + + + + + + Number Guessing Game - Games + + + + + +
    +
    +

    Number Guessing Game

    +
    +
    + +
    +
    +
    +

    I'm thinking of a number between 1 and 100.

    +
    + + +
    +

    + +
    +
    +

    How it Works

    +

    + The computer generates a random number between 1 and 100 using + Math.random(). The player submits their guess via a + form. The game then compares the guess to the secret number and + provides feedback ('Too high!', 'Too low!', or 'You got it!'). The + input form is disabled once the correct number is guessed, and a + 'Play Again' button appears. +

    +

    The Code

    +
    <!-- HTML -->
    +<p>I'm thinking of a number between 1 and 100.</p>
    +<form id="guess-form">
    +    <input type="number" id="guess-input" min="1" max="100" required>
    +    <button type="submit" id="guess-submit" class="btn">Guess</button>
    +</form>
    +<p id="feedback"></p>
    +<button id="restart-btn" class="btn" style="display: none;">Play Again</button>
    +
    +<!-- JavaScript -->
    +const guessInput = document.getElementById('guess-input');
    +// ... game logic ...
    +
    +
    +
    +
    + + + + + + diff --git a/games/rock-paper-scissors.html b/games/rock-paper-scissors.html new file mode 100644 index 0000000..c0d11cb --- /dev/null +++ b/games/rock-paper-scissors.html @@ -0,0 +1,137 @@ + + + + + + Rock, Paper, Scissors - Games + + + + + +
    +
    +

    Rock, Paper, Scissors

    +
    +
    + +
    +
    +
    +

    Choose your weapon!

    +
    + + + +
    +
    +

    Player: 0 - Computer: 0

    +
    +
    +

    How it Works

    +

    + The player clicks one of the three buttons. The computer's choice is + generated randomly from the three options. A function then compares + the two choices to determine a winner based on the classic rules: + Rock beats Scissors, Scissors beats Paper, and Paper beats Rock. The + scores are then updated. +

    +

    The Code

    +
    <!-- HTML -->
    +<div class="choices">
    +    <button class="btn" data-choice="Rock">✊</button>
    +    <button class="btn" data-choice="Paper">✋</button>
    +    <button class="btn" data-choice="Scissors">✌️</button>
    +</div>
    +<div id="result"></div>
    +<p id="score">Player: 0 - Computer: 0</p>
    +
    +<!-- JavaScript -->
    +const choiceButtons = document.querySelectorAll('.choices button');
    +// ... game logic ...
    +
    +
    +
    +
    + + + + + + diff --git a/games/script.js b/games/script.js new file mode 100644 index 0000000..5a7c6e9 --- /dev/null +++ b/games/script.js @@ -0,0 +1 @@ +// Scripts for the games section diff --git a/games/simon-says.html b/games/simon-says.html new file mode 100644 index 0000000..675b6f5 --- /dev/null +++ b/games/simon-says.html @@ -0,0 +1,175 @@ + + + + + + Simon Says - Games + + + + + +
    +
    +

    Simon Says

    +
    +
    + +
    +
    +
    +

    Press "Start" to play.

    +
    +
    +
    +
    +
    +
    + +
    +
    +

    How it Works

    +

    + This is a memory game. The computer will light up a sequence of + colored pads. You must repeat the sequence in the same order. Each + round, the sequence gets one step longer. The game ends if you make + a mistake. +

    +
    +
    +
    + + + + + + diff --git a/games/style.css b/games/style.css new file mode 100644 index 0000000..c7f9b71 --- /dev/null +++ b/games/style.css @@ -0,0 +1,44 @@ +/* Styles for the games section */ + +.game-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); + gap: 20px; +} + +.game-card { + background-color: var(--nav-bg); + border-radius: 5px; + padding: 20px; + text-decoration: none; + color: var(--text-color); + transition: + transform 0.2s, + box-shadow 0.2s; + display: block; +} + +.game-card:hover { + transform: translateY(-5px); + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); +} + +.game-thumbnail { + height: 150px; + background-color: var(--code-bg); + border-radius: 5px; + margin-bottom: 15px; + display: flex; + align-items: center; + justify-content: center; + font-style: italic; + color: var(--footer-text); +} + +.game-thumbnail::after { + content: "Thumbnail"; +} + +.game-card h3 { + margin-top: 0; +} diff --git a/games/tic-tac-toe.html b/games/tic-tac-toe.html new file mode 100644 index 0000000..e646372 --- /dev/null +++ b/games/tic-tac-toe.html @@ -0,0 +1,257 @@ + + + + + + Tic Tac Toe - Games + + + + + +
    +
    +

    Tic Tac Toe

    +
    +
    + +
    +
    +
    +
    Player X's turn
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    +

    How it Works

    +

    + This is a classic Tic Tac Toe game. The game state is managed in a + JavaScript array. After each move, the game checks for a win by + comparing the board state against a set of winning combinations. The + game ends when a player wins or all cells are filled, resulting in a + draw. +

    +

    The Code

    +
    <!-- HTML -->
    +<div id="game-status">Player X's turn</div>
    +<div id="tic-tac-toe-board">
    +    <div class="cell" data-index="0"></div>
    +    ... (9 cells) ...
    +</div>
    +<button id="restart-btn" class="btn">New Game</button>
    +
    +<!-- JavaScript -->
    +const statusDisplay = document.getElementById('game-status');
    +const restartBtn = document.getElementById('restart-btn');
    +const cells = document.querySelectorAll('.cell');
    +
    +let currentPlayer = 'X';
    +let gameState = ["", "", "", "", "", "", "", "", ""];
    +let gameActive = true;
    +
    +const winningConditions = [
    +    [0, 1, 2], [3, 4, 5], [6, 7, 8], // Rows
    +    [0, 3, 6], [1, 4, 7], [2, 5, 8], // Columns
    +    [0, 4, 8], [2, 4, 6]             // Diagonals
    +];
    +
    +function handleCellClick(e) {
    +    const clickedCell = e.target;
    +    const clickedCellIndex = parseInt(clickedCell.getAttribute('data-index'));
    +
    +    if (gameState[clickedCellIndex] !== "" || !gameActive) return;
    +
    +    gameState[clickedCellIndex] = currentPlayer;
    +    clickedCell.textContent = currentPlayer;
    +    handleResultValidation();
    +}
    +
    +function handleResultValidation() {
    +    let roundWon = false;
    +    for (let i = 0; i < winningConditions.length; i++) {
    +        const winCondition = winningConditions[i];
    +        let a = gameState[winCondition[0]];
    +        let b = gameState[winCondition[1]];
    +        let c = gameState[winCondition[2]];
    +        if (a === '' || b === '' || c === '') continue;
    +        if (a === b && b === c) {
    +            roundWon = true;
    +            break;
    +        }
    +    }
    +
    +    if (roundWon) {
    +        statusDisplay.textContent = `Player ${currentPlayer} has won!`;
    +        gameActive = false;
    +        return;
    +    }
    +
    +    if (!gameState.includes("")) {
    +        statusDisplay.textContent = "Game ended in a draw!";
    +        gameActive = false;
    +        return;
    +    }
    +
    +    currentPlayer = currentPlayer === "X" ? "O" : "X";
    +    statusDisplay.textContent = `Player ${currentPlayer}'s turn`;
    +}
    +
    +function handleRestartGame() {
    +    gameActive = true;
    +    currentPlayer = "X";
    +    gameState = ["", "", "", "", "", "", "", "", ""];
    +    statusDisplay.textContent = `Player ${currentPlayer}'s turn`;
    +    cells.forEach(cell => cell.textContent = "");
    +}
    +
    +cells.forEach(cell => cell.addEventListener('click', handleCellClick));
    +restartBtn.addEventListener('click', handleRestartGame);
    +
    +
    +
    +
    + + + + + + diff --git a/games/typing-speed.html b/games/typing-speed.html new file mode 100644 index 0000000..29e0672 --- /dev/null +++ b/games/typing-speed.html @@ -0,0 +1,182 @@ + + + + + + Typing Speed Test - Games + + + + + +
    +
    +

    Typing Speed Test

    +
    +
    + +
    +
    +
    +

    Type the text below as quickly and accurately as you can.

    +
    + +
    + +
    +
    +

    How it Works

    +

    + A random snippet of text is displayed. When the user starts typing + in the text area, a timer begins. On each input, the typed text is + compared to the sample text. When the typed text's length matches + the sample text's length, the test ends. The Words Per Minute (WPM) + is calculated based on the number of correctly typed characters and + the elapsed time. (WPM is conventionally calculated as + `(character_count / 5) / time_in_minutes`). +

    +
    +
    +
    + + + + + + diff --git a/games/wordle.html b/games/wordle.html new file mode 100644 index 0000000..4f6903e --- /dev/null +++ b/games/wordle.html @@ -0,0 +1,198 @@ + + + + + + Wordle Clone - Games + + + + + +
    +
    +

    Wordle Clone

    +
    +
    + +
    +
    +
    +

    Guess the 5-letter word in 6 tries.

    +
    +

    + +
    +
    +

    How it Works

    +

    + A 5-letter word is chosen at random. The player types their guess. + When a 5-letter guess is submitted (by pressing Enter), the letters + are colored based on their status: Green for correct letter in the + correct position, Yellow for correct letter in the wrong position, + and Gray for a letter not in the word at all. The game is managed by + listening for `keydown` events. +

    +
    +
    +
    + + + + + + diff --git a/guides/best-practices.html b/guides/best-practices.html new file mode 100644 index 0000000..5432a52 --- /dev/null +++ b/guides/best-practices.html @@ -0,0 +1,145 @@ + + + + + + Best Practices - Getting Interactive with GitHub Pages + + + +
    +
    +

    Best Practices for Static Interactivity

    +
    +
    + +
    +
    +
    +

    Progressive Enhancement

    +

    + Start with a solid HTML foundation that works without CSS or + JavaScript. Your content should be readable and your links should + function. Then, layer on CSS for styling and JavaScript for + interactivity. This ensures your site is usable by everyone, + regardless of their browser capabilities or network speed. +

    +
    +
    +

    Accessibility (a11y)

    +

    + An interactive site is only useful if everyone can interact with it. + Keep accessibility in mind: +

    +
      +
    • + Semantic HTML: Use tags like + <nav>, <main>, + <button>, and + <input> correctly. They provide built-in + accessibility features. +
    • +
    • + Keyboard Navigation: Ensure all interactive + elements (buttons, links, form fields) can be accessed and + operated using only the keyboard. +
    • +
    • + ARIA Roles: Use ARIA (Accessible Rich Internet + Applications) attributes when necessary to provide more context + for screen readers, especially for custom widgets. +
    • +
    • + Color Contrast: Make sure your text has + sufficient contrast against its background. +
    • +
    +
    +
    +

    Performance

    +

    + Client-side interactivity means the user's device is doing all the + work. Keep it fast: +

    +
      +
    • + Minimize JavaScript: Use vanilla JavaScript + whenever possible. Avoid large libraries or frameworks if you only + need a small part of their functionality. +
    • +
    • + Load Scripts Asynchronously: Use the + async or defer attributes on your + <script> tags to prevent them from blocking + page rendering. +
    • +
    • + Optimize Assets: Compress images and minify your + CSS and JavaScript files. +
    • +
    +
    +
    +

    Security

    +

    Even without a server, there are security considerations:

    +
      +
    • + Cross-Site Scripting (XSS): Be careful when + displaying user-generated content. Sanitize any input before + rendering it as HTML to prevent malicious scripts from being + injected. For example, instead of + element.innerHTML = userInput, use + element.textContent = userInput if you don't need to + render HTML. +
    • +
    • + Third-Party Scripts: Be cautious when including + scripts from CDNs or other sources. Ensure they are from a trusted + provider. +
    • +
    +
    +
    +
    + + + + + diff --git a/guides/markdown-usage.html b/guides/markdown-usage.html new file mode 100644 index 0000000..0f97bd2 --- /dev/null +++ b/guides/markdown-usage.html @@ -0,0 +1,106 @@ + + + + + + Markdown Usage - Getting Interactive with GitHub Pages + + + +
    +
    +

    Using Markdown for Content

    +
    +
    + +
    +
    +
    +

    What is Markdown?

    +

    + Markdown is a lightweight markup language with plain-text-formatting + syntax. Its main goal is to be as readable as possible, even in its + raw form. It's a simple way to write and format articles, + documentation, and messages. +

    +
    # This is a heading
    +
    +This is a paragraph with some **bold** and *italic* text.
    +
    +- This is a list item.
    +- So is this.
    +
    +
    +

    Why Use Markdown on a Static Site?

    +
      +
    • + Simplicity: It's much easier and faster to write + content in Markdown than in raw HTML. +
    • +
    • + Portability: Markdown files are just plain text, + so they can be opened and edited by any text editor. +
    • +
    • + GitHub Native: GitHub renders Markdown files + automatically, making it the standard for READMEs and + documentation in repositories. +
    • +
    +
    +
    +

    Live Conversion

    +

    + As you've seen on our + Markdown Converter + page, you can use a JavaScript library to convert Markdown to HTML + directly in the browser. This means you can write your content in + simple .md files and then use a script to fetch and + render them on your site, creating a very simple Content Management + System (CMS) without a backend. +

    +
    +
    +
    + + + + + diff --git a/guides/resources.html b/guides/resources.html new file mode 100644 index 0000000..d5a62e6 --- /dev/null +++ b/guides/resources.html @@ -0,0 +1,153 @@ + + + + + + Resources - Getting Interactive with GitHub Pages + + + +
    +
    +

    Resources

    +
    +
    + +
    +
    +
    +

    Browser API Documentation (MDN)

    + +
    +
    +

    Minimal Libraries

    + +
    +
    +

    Tools & Platforms

    + +
    +
    +
    + + + + + diff --git a/index.html b/index.html new file mode 100644 index 0000000..d5aeaa0 --- /dev/null +++ b/index.html @@ -0,0 +1,121 @@ + + + + + + Getting Interactive with GitHub Pages + + + +
    +
    +

    Getting Interactive with GitHub Pages

    +

    Frontend magic for GitHub Pages - zero backend, pure fun!

    +
    +
    + + + +
    +
    +
    +

    Welcome!

    +

    + This website is dedicated to showcasing the power of static + websites, particularly those hosted on GitHub Pages. Many developers + believe that a "static" site means a non-interactive, boring page. + We're here to prove that wrong! +

    +

    + With just HTML, CSS, and vanilla JavaScript, you can create rich, + interactive, and useful web applications without any backend servers + or complex build tools. This site is a living demonstration of that + philosophy. +

    +
    + +
    +

    What You'll Find Here

    +

    + We have a collection of pages, each dedicated to a specific + interactive feature. For each feature, you'll find: +

    +
      +
    • A clear explanation of the concept.
    • +
    • Practical use cases and ideas.
    • +
    • + A live, working demo that you can try right in your browser. +
    • +
    • + The complete, copy-paste-ready HTML, CSS, and JavaScript code. +
    • +
    +

    + Explore the navigation menu to see demos of serverless forms, a live + JavaScript editor, a persistent notes app, client-side search, and + much more. +

    +
    + +
    +

    Who Is This For?

    +
      +
    • + Developers: Looking for lightweight, + dependency-free solutions for common web tasks. +
    • +
    • + Educators & Students: Teaching or learning web + development concepts in a clear, accessible way. +
    • +
    • + Open Source Contributors: Who want to add cool + features to their project documentation or GitHub Pages sites. +
    • +
    +
    +
    +
    + + + + + + + + diff --git a/script.js b/script.js new file mode 100644 index 0000000..26ed9aa --- /dev/null +++ b/script.js @@ -0,0 +1,38 @@ +document.addEventListener("DOMContentLoaded", () => { + const themeSwitcher = document.getElementById("theme-switcher"); + const currentTheme = localStorage.getItem("theme"); + + // Apply saved theme or OS preference + if (currentTheme) { + document.body.classList.toggle("dark-mode", currentTheme === "dark"); + if (themeSwitcher) { + themeSwitcher.textContent = + currentTheme === "dark" ? "Light Mode" : "Dark Mode"; + } + } else if ( + window.matchMedia && + window.matchMedia("(prefers-color-scheme: dark)").matches + ) { + document.body.classList.add("dark-mode"); + if (themeSwitcher) { + themeSwitcher.textContent = "Light Mode"; + } + } else { + if (themeSwitcher) { + themeSwitcher.textContent = "Dark Mode"; + } + } + + // Theme switcher button event listener + if (themeSwitcher) { + themeSwitcher.addEventListener("click", () => { + document.body.classList.toggle("dark-mode"); + let theme = "light"; + if (document.body.classList.contains("dark-mode")) { + theme = "dark"; + } + localStorage.setItem("theme", theme); + themeSwitcher.textContent = theme === "dark" ? "Light Mode" : "Dark Mode"; + }); + } +}); diff --git a/style.css b/style.css new file mode 100644 index 0000000..1da2cff --- /dev/null +++ b/style.css @@ -0,0 +1,190 @@ +/* General Body Styles */ +body { + font-family: + -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", + Arial, sans-serif; + line-height: 1.6; + margin: 0; + padding: 0; + transition: + background-color 0.3s, + color 0.3s; +} + +/* Light Theme (Default) */ +:root { + --bg-color: #ffffff; + --text-color: #333333; + --header-bg: #f8f9fa; + --header-text: #333333; + --nav-bg: #e9ecef; + --nav-link: #007bff; + --nav-link-hover: #0056b3; + --footer-bg: #f8f9fa; + --footer-text: #6c757d; + --code-bg: #e9ecef; + --code-text: #333; + --border-color: #dee2e6; +} + +/* Dark Theme */ +body.dark-mode { + --bg-color: #121212; + --text-color: #e0e0e0; + --header-bg: #1f1f1f; + --header-text: #e0e0e0; + --nav-bg: #2c2c2c; + --nav-link: #bb86fc; + --nav-link-hover: #9e6ffc; + --footer-bg: #1f1f1f; + --footer-text: #8c8c8c; + --code-bg: #2c2c2c; + --code-text: #e0e0e0; + --border-color: #3a3a3a; +} + +body { + background-color: var(--bg-color); + color: var(--text-color); +} + +.container { + max-width: 960px; + margin: 0 auto; + padding: 20px; +} + +/* Header */ +header { + background-color: var(--header-bg); + color: var(--header-text); + padding: 1rem 0; + border-bottom: 1px solid var(--border-color); + text-align: center; +} + +header h1 { + margin: 0; + font-size: 2.5rem; +} + +/* Navigation */ +nav { + background-color: var(--nav-bg); + padding: 1rem; + margin-bottom: 20px; +} + +nav ul { + list-style: none; + margin: 0; + padding: 0; + display: flex; + flex-wrap: wrap; + justify-content: center; +} + +nav ul li { + margin: 5px 15px; +} + +nav ul li a { + text-decoration: none; + color: var(--nav-link); + font-weight: bold; + transition: color 0.2s; +} + +nav ul li a:hover { + color: var(--nav-link-hover); +} + +/* Main Content */ +main { + padding: 20px 0; +} + +section { + margin-bottom: 40px; +} + +h2 { + border-bottom: 2px solid var(--border-color); + padding-bottom: 10px; + margin-bottom: 20px; +} + +/* Footer */ +footer { + text-align: center; + padding: 20px; + margin-top: 40px; + background-color: var(--footer-bg); + color: var(--footer-text); + border-top: 1px solid var(--border-color); +} + +/* Code Blocks */ +pre { + background-color: var(--code-bg); + color: var(--code-text); + padding: 15px; + border-radius: 5px; + overflow-x: auto; + border: 1px solid var(--border-color); +} + +code { + font-family: + "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace; +} + +/* Buttons */ +.btn { + display: inline-block; + font-weight: 400; + color: #fff; + text-align: center; + vertical-align: middle; + cursor: pointer; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + background-color: #007bff; + border: 1px solid #007bff; + padding: 0.375rem 0.75rem; + font-size: 1rem; + line-height: 1.5; + border-radius: 0.25rem; + transition: + color 0.15s ease-in-out, + background-color 0.15s ease-in-out, + border-color 0.15s ease-in-out, + box-shadow 0.15s ease-in-out; +} + +.btn:hover { + background-color: #0069d9; + border-color: #0062cc; +} + +/* Theme switcher button */ +#theme-switcher { + position: fixed; + top: 20px; + right: 20px; + z-index: 1000; +} + +/* Responsive Design */ +@media (max-width: 768px) { + nav ul { + flex-direction: column; + align-items: center; + } + + header h1 { + font-size: 2rem; + } +} diff --git a/tools/tech-tools.html b/tools/tech-tools.html new file mode 100644 index 0000000..0b583fb --- /dev/null +++ b/tools/tech-tools.html @@ -0,0 +1,216 @@ + + + + + + Tech Tools - Getting Interactive with GitHub Pages + + + + +
    +
    +

    Getting Interactive with GitHub Pages

    +

    Frontend magic for GitHub Pages - zero backend, pure fun!

    +
    +
    + + + +
    +
    +
    +

    Client-Side Tech Tools

    +

    + Many simple developer utilities can be built to run entirely in the + browser. This is fast, secure, and works offline. Here are a few + examples of common tools recreated with vanilla JavaScript. +

    +
    + +
    + + + +
    + + +
    +

    Base64 Encoder/Decoder

    + + + + +
    + + +
    +

    URL Encoder/Decoder

    + + + + +
    + + +
    +

    Text Counter

    + +

    Characters: 0, Words: 0, Lines: 0

    +
    +
    +
    + + + + + + + + +