From 9169084d57a90d44e4a541ba064feae504805437 Mon Sep 17 00:00:00 2001 From: Kaden Napper Date: Mon, 27 Jul 2026 10:55:15 +1000 Subject: [PATCH] Improve accessibility and resolve website audit findings --- AGENTS.md | 70 +++++++++++++++++++++++++++++----------- README.md | 62 +++++++++++++++++++++++++++++------ css/hudra-legal.css | 6 +++- css/hudra.css | 21 ++++++++++-- css/napper.css | 2 ++ hudra/index.html | 18 +++++------ hudra/privacy/index.html | 8 ++--- hudra/support/index.html | 8 ++--- hudra/terms/index.html | 6 ++-- index.html | 9 +++--- js/hudra.js | 30 +++++++++++++---- js/napper.js | 30 +++++++++++++---- 12 files changed, 201 insertions(+), 69 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 23be489..078d6de 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2,15 +2,7 @@ ## Project purpose -This repository contains the public website for the Napper application ecosystem. - -The site includes: - -- the main `napper.au` landing site; -- the Hudra product site; -- the Lildra product site; -- the Pendra product site; -- shared legal, privacy, support and ecosystem content. +This repository contains the public website for the Napper application ecosystem. It includes the main `napper.au` landing site, the Hudra product site, and shared legal, privacy, support and ecosystem content. Lildra and Pendra product pages are planned but do not yet exist. ## Source of truth @@ -22,6 +14,56 @@ When an important architectural, deployment, naming or development decision is m 2. update this file when the guidance affects AI contributors; 3. avoid relying on undocumented conversational context. +## Product distinction + +- Hudra is primarily a presentation and slide-viewing system that turns complete QLearn lesson pages into a classroom presentation experience. Do not describe Hudra as a general-purpose editor. +- Lildra is the product focused on removing the surrounding QLearn interface and showing lesson-page content cleanly. +- Pendra is planned as a simpler path for bringing completed presentation slides into QLearn. + +Keep product wording within these boundaries and do not reuse Lildra's clutter-removal positioning as Hudra's primary message. + +## Current architecture + +The site is a dependency-free static site using HTML, CSS and small browser JavaScript files. Do not introduce a framework, package manager, build system or templating engine without an explicit architectural decision. + +```text +/ +├── index.html +├── css/ +│ ├── napper.css +│ ├── hudra.css +│ └── hudra-legal.css +├── js/ +│ ├── napper.js +│ └── hudra.js +├── img/ +└── hudra/ + ├── index.html + ├── support/index.html + ├── privacy/index.html + └── terms/index.html +``` + +The Hudra header, footer and foundational styling are currently repeated. Establish shared-component and shared-style conventions before duplicating Hudra's structure for Lildra or Pendra. Do not introduce those abstractions incidentally during an unrelated focused change. + +## Deployment assumptions + +- The repository is `napper/site` and is deployed to `napper.au`. +- A push to `main` triggers automatic deployment. +- Gitea sends a signed webhook to the CloudPanel server. +- `dploy` creates a release and switches the site's `current` symlink. +- CloudPanel serves `/home/napper/htdocs/napper.au/current`. +- The production server supports directory URLs such as `/hudra/privacy`, resolving them to the relevant `index.html`. +- Pages and assets use root-relative paths and assume deployment at the domain root. + +Do not expose webhook secrets, credentials or other server-specific private data. + +## Canonical external locations + +- QLearn: `https://qlearn.eq.edu.au` +- Gitea organisation: `https://git.napper.au/napper` +- Hudra releases: `https://git.napper.au/napper/hudra/releases` + ## Working principles - Keep the site simple and maintainable. @@ -30,8 +72,6 @@ When an important architectural, deployment, naming or development decision is m - Preserve accessibility, responsive design and fast page loads. - Reuse shared styles and components where practical. - Keep each application page focused on what the application does. -- Do not describe Hudra as a general-purpose editor. -- Treat Hudra primarily as a presentation and slide-viewing system for QLearn. - Do not expose secrets, credentials or server-specific private data. - Keep deployment configuration documented and reproducible. @@ -41,10 +81,4 @@ When an important architectural, deployment, naming or development decision is m 2. Make focused changes. 3. Test locally where possible. 4. Update documentation when behaviour or structure changes. -5. Commit using a clear, descriptive message. - -## Project structure - -The final structure will be documented after the existing website is imported and audited. - -Do not invent a new architecture before inspecting the existing site. \ No newline at end of file +5. Commit using a clear, descriptive message when a commit is requested. diff --git a/README.md b/README.md index 68aff46..ac1fc7d 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,63 @@ # Napper Website -Source code and deployment configuration for the Napper website and application ecosystem. +Static source for the Napper application ecosystem website, deployed at from the `napper/site` Gitea repository. -## Websites +## Current products -- -- -- -- +- **Hudra** is available at . It turns complete QLearn lesson pages into a presentation or slide-style classroom teaching experience. +- **Lildra** is in development. It is focused on removing the surrounding QLearn interface and showing lesson-page content cleanly. A `/lildra` page has not yet been added. +- **Pendra** is planned after Lildra. A `/pendra` page has not yet been added. + +## Site structure + +The site uses static HTML, CSS and JavaScript without a framework, package manager, build system or templating engine. + +```text +/ +├── index.html Napper ecosystem homepage +├── css/ +│ ├── napper.css Napper homepage styles +│ ├── hudra.css Hudra product and shared Hudra styles +│ └── hudra-legal.css Hudra support and legal-page styles +├── js/ +│ ├── napper.js Napper homepage behaviour +│ └── hudra.js Hudra page behaviour +├── img/ Hudra image assets +└── hudra/ + ├── index.html Hudra product page + ├── support/index.html Hudra support + ├── privacy/index.html Hudra privacy policy + └── terms/index.html Hudra terms of use +``` + +The HTML uses root-relative asset and navigation paths. Opening pages directly with `file://` or serving the repository beneath a URL subpath will not reproduce production routing. Local previews should use a static server configured with this repository as its web root and with directory-index handling enabled. + +## URLs and external services + +- Production site: +- Canonical QLearn URL: +- Canonical Gitea organisation: +- Hudra releases: + +Production supports clean directory URLs. For example, `/hudra/privacy` serves `hudra/privacy/index.html` without requiring `index.html` in the public URL. + +## Deployment + +Deployment occurs automatically after a push to `main`: + +1. Gitea sends a signed webhook to the CloudPanel server. +2. `dploy` creates a release from the repository state. +3. `dploy` switches the site's `current` symlink to the new release. +4. CloudPanel serves `/home/napper/htdocs/napper.au/current` as the document root for `napper.au`. + +The production document-root and directory-index arrangement are therefore part of the site's routing assumptions. ## Repository philosophy -The repository is the source of truth, not the conversation. +The repository is the source of truth, not individual conversations. Important architectural, development and deployment decisions must be recorded here or in `AGENTS.md` so that future contributors inherit the correct context. -Important architectural, development and deployment decisions must be recorded in the repository so that future contributors and AI coding sessions inherit the correct project context. +## Known asset work -## Current status +The Napper ecosystem homepage deliberately has no favicon until a dedicated neutral Napper favicon is created. Hudra product assets must not be used as Napper ecosystem branding. -The repository has been created and is being prepared for the import of the existing `napper.au` website. diff --git a/css/hudra-legal.css b/css/hudra-legal.css index f867c4c..dd6b7f8 100644 --- a/css/hudra-legal.css +++ b/css/hudra-legal.css @@ -23,7 +23,7 @@ flex-wrap: wrap; gap: 10px 22px; margin-top: 26px; - color: #71809d; + color: #64728d; font-size: .86rem; } .legal-page { padding: 72px 0 105px; } @@ -102,6 +102,10 @@ box-shadow: 0 12px 34px rgba(19, 37, 81, .06); } .contact-card:hover { border-color: #b7c8fa; transform: translateY(-2px); } +.legal-nav a:focus-visible, .contact-card:focus-visible, .code-note a:focus-visible { + outline: 3px solid #6f96ff; + outline-offset: 3px; +} .contact-card strong { display: block; color: var(--blue-950); font-size: 1.05rem; } .contact-card span { display: block; margin-top: 6px; color: var(--muted); font-size: .9rem; } .code-note { diff --git a/css/hudra.css b/css/hudra.css index fe78f30..bb619fd 100644 --- a/css/hudra.css +++ b/css/hudra.css @@ -71,7 +71,7 @@ button, input { font: inherit; } .button-primary { color: #fff; background: linear-gradient(135deg, var(--blue-600), var(--blue-800)); box-shadow: 0 14px 28px rgba(11, 69, 216, .27); } .button-secondary { color: var(--blue-900); border: 1px solid #cbd8fb; background: #fff; } .button-light { color: var(--blue-900); background: #fff; box-shadow: 0 14px 28px rgba(0,0,0,.12); } -.microcopy { margin: 17px 0 0; color: #7a8498; font-size: .86rem; } +.microcopy { margin: 17px 0 0; color: #626d83; font-size: .86rem; } .hero-visual { position: relative; min-height: 535px; display: flex; align-items: center; } .browser-window { position: relative; z-index: 2; width: 100%; overflow: hidden; border: 1px solid rgba(154, 174, 228, .6); border-radius: 24px; background: #fff; box-shadow: var(--shadow); transform: rotate(1.2deg); } @@ -198,7 +198,7 @@ button, input { font: inherit; } @media (max-width: 760px) { .container { width: min(calc(100% - 28px), var(--container)); } - .site-nav { position: absolute; left: 14px; right: 14px; top: 68px; display: none; padding: 16px; border: 1px solid var(--line); border-radius: 16px; background: #fff; box-shadow: 0 20px 45px rgba(8,35,108,.14); } + .site-nav { position: absolute; z-index: 60; left: 14px; right: 14px; top: 68px; display: none; padding: 16px; border: 1px solid var(--line); border-radius: 16px; background: #fff; box-shadow: 0 20px 45px rgba(8,35,108,.14); } .site-nav.open { display: grid; } .menu-button { display: block; } .hero { padding: 58px 0 66px; } @@ -255,8 +255,9 @@ button, input { font: inherit; } .timer-card span { color: #65769b; font-size: .65rem; font-weight: 750; } .timer-card strong { color: var(--blue-800); font-size: 1.2rem; font-variant-numeric: tabular-nums; } .control-row { display: grid; grid-template-columns: 34px 1fr 34px; align-items: center; gap: 7px; padding: 8px; } -.control-row button { height: 30px; color: #fff; border: 0; border-radius: 8px; background: var(--blue-700); font-weight: 900; } +.control-row .control-button { display: grid; place-items: center; height: 30px; color: #fff; border-radius: 8px; background: var(--blue-700); font-weight: 900; } .control-row span { color: #65769b; font-size: .7rem; font-weight: 800; text-align: center; } +.control-row .control-button { color: #fff; } .presentation-mini-ui span { display: flex; justify-content: space-between; gap: 18px; } .presentation-mini-ui b { color: var(--blue-700); } @@ -268,3 +269,17 @@ button, input { font: inherit; } .control-row { grid-column: 1 / -1; } .slide-screen { padding: 16px; } } + +.section[id], .cta-section[id] { scroll-margin-top: 96px; } + +.site-nav a:focus-visible, +.menu-button:focus-visible, +.button:focus-visible, +.text-link:focus-visible, +.product-card a:focus-visible, +.faq-list summary:focus-visible, +.footer-brand a:focus-visible, +.footer-links a:focus-visible { + outline: 3px solid #6f96ff; + outline-offset: 4px; +} diff --git a/css/napper.css b/css/napper.css index fe3f9c7..9a61ccd 100644 --- a/css/napper.css +++ b/css/napper.css @@ -3,3 +3,5 @@ @media(max-width:980px){.hero-grid,.approach-grid{grid-template-columns:1fr}.hero-copy{max-width:800px}.hero-visual{max-width:760px}.product-grid{grid-template-columns:1fr 1fr}.product-card-featured{grid-column:span 2}.trust-grid{grid-template-columns:repeat(2,1fr)}.trust-grid>div:nth-child(2){border-right:0}.trust-grid>div:nth-child(-n+2){border-bottom:1px solid var(--line)}} @media(max-width:760px){.container{width:min(calc(100% - 28px),var(--container))}.site-nav{position:absolute;left:14px;right:14px;top:68px;display:none;padding:16px;border:1px solid var(--line);border-radius:16px;background:#fff;box-shadow:0 20px 45px rgba(8,35,108,.14)}.site-nav.open{display:grid}.menu-button{display:block}.hero{padding:64px 0}.hero h1{font-size:clamp(3rem,15vw,4.8rem)}.hero-visual,.product-stack{min-height:620px;height:620px}.stack-card{width:94%;transform:none}.stack-hudra{right:0}.stack-lildra{left:0;top:210px}.stack-pendra{right:0;top:405px}.trust-grid,.product-grid{grid-template-columns:1fr}.trust-grid>div{border-right:0;border-bottom:1px solid var(--line)}.product-card-featured{grid-column:auto}.product-card{min-height:auto}.section{padding:78px 0}.cta-card{align-items:flex-start;flex-direction:column;padding:38px 28px}.footer-grid{grid-template-columns:1fr}.footer-bottom{flex-direction:column}.card-actions{align-items:flex-start;flex-direction:column}} @media(prefers-reduced-motion:reduce){*,*::before,*::after{scroll-behavior:auto!important;transition:none!important}} +.microcopy{color:#65728c}.section[id],.cta-section[id]{scroll-margin-top:96px}.site-nav a:focus-visible,.menu-button:focus-visible,.button:focus-visible,.text-link:focus-visible,.product-card a:focus-visible,.footer-brand a:focus-visible,.footer-links a:focus-visible{outline:3px solid #6f96ff;outline-offset:4px} +@media(max-width:760px){.site-nav{z-index:60}} diff --git a/hudra/index.html b/hudra/index.html index 38ede23..ede8f09 100644 --- a/hudra/index.html +++ b/hudra/index.html @@ -34,7 +34,7 @@ Privacy Help FAQ - Download Hudra + Download Hudra @@ -53,7 +53,7 @@

- + Download Hudra @@ -67,7 +67,7 @@
-
qlearn.edu.au.au
+
https://qlearn.eq.edu.au
Hudra
@@ -101,9 +101,9 @@ 08:35
@@ -273,10 +273,10 @@
Present directly from your lesson
-

QLearn content, without the QLearn clutter.

+

Turn a complete QLearn lesson into a slide-style teaching experience.

Turn the lesson you already made into a polished classroom presentation.

- Download Hudra + Download Hudra
@@ -297,7 +297,7 @@ Hudra Features How it works - Downloads + Downloads
Support @@ -315,4 +315,4 @@ - \ No newline at end of file + diff --git a/hudra/privacy/index.html b/hudra/privacy/index.html index bf0417f..e114197 100644 --- a/hudra/privacy/index.html +++ b/hudra/privacy/index.html @@ -9,15 +9,15 @@ - +
-
+
diff --git a/hudra/support/index.html b/hudra/support/index.html index 23ba07a..3f8b148 100644 --- a/hudra/support/index.html +++ b/hudra/support/index.html @@ -16,7 +16,7 @@
@@ -31,9 +31,9 @@
-
+
diff --git a/hudra/terms/index.html b/hudra/terms/index.html index 90dc624..94dc9b9 100644 --- a/hudra/terms/index.html +++ b/hudra/terms/index.html @@ -8,11 +8,11 @@ - +
-
+
diff --git a/index.html b/index.html index 109f422..8a23fc7 100644 --- a/index.html +++ b/index.html @@ -6,7 +6,6 @@ Napper — Focused tools for better lessons - @@ -27,7 +26,7 @@ @@ -96,7 +95,7 @@

Hudra turns existing lesson pages into a clean slide experience with presenter controls, previews, timers and dual-screen support.

@@ -158,7 +157,7 @@ @@ -166,4 +165,4 @@ - \ No newline at end of file + diff --git a/js/hudra.js b/js/hudra.js index 950ea34..1586930 100644 --- a/js/hudra.js +++ b/js/hudra.js @@ -2,17 +2,35 @@ const menuButton = document.querySelector('.menu-button'); const siteNav = document.querySelector('.site-nav'); if (menuButton && siteNav) { - menuButton.addEventListener('click', () => { - const isOpen = siteNav.classList.toggle('open'); + const menuLabel = menuButton.querySelector('.sr-only'); + const desktopViewport = window.matchMedia('(min-width: 761px)'); + + const setMenuState = (isOpen) => { + siteNav.classList.toggle('open', isOpen); menuButton.setAttribute('aria-expanded', String(isOpen)); + if (menuLabel) menuLabel.textContent = isOpen ? 'Close navigation' : 'Open navigation'; + }; + + menuButton.addEventListener('click', () => { + setMenuState(!siteNav.classList.contains('open')); }); siteNav.querySelectorAll('a').forEach((link) => { - link.addEventListener('click', () => { - siteNav.classList.remove('open'); - menuButton.setAttribute('aria-expanded', 'false'); - }); + link.addEventListener('click', () => setMenuState(false)); }); + + document.addEventListener('keydown', (event) => { + if (event.key === 'Escape' && siteNav.classList.contains('open')) { + setMenuState(false); + menuButton.focus(); + } + }); + + desktopViewport.addEventListener('change', (event) => { + if (event.matches) setMenuState(false); + }); + + setMenuState(false); } const year = document.getElementById('year'); diff --git a/js/napper.js b/js/napper.js index a867b36..f9f79ca 100644 --- a/js/napper.js +++ b/js/napper.js @@ -2,17 +2,35 @@ const menuButton = document.querySelector('.menu-button'); const navigation = document.querySelector('#site-nav'); if (menuButton && navigation) { - menuButton.addEventListener('click', () => { - const isOpen = navigation.classList.toggle('open'); + const menuLabel = menuButton.querySelector('.sr-only'); + const desktopViewport = window.matchMedia('(min-width: 761px)'); + + const setMenuState = (isOpen) => { + navigation.classList.toggle('open', isOpen); menuButton.setAttribute('aria-expanded', String(isOpen)); + if (menuLabel) menuLabel.textContent = isOpen ? 'Close navigation' : 'Open navigation'; + }; + + menuButton.addEventListener('click', () => { + setMenuState(!navigation.classList.contains('open')); }); navigation.querySelectorAll('a').forEach((link) => { - link.addEventListener('click', () => { - navigation.classList.remove('open'); - menuButton.setAttribute('aria-expanded', 'false'); - }); + link.addEventListener('click', () => setMenuState(false)); }); + + document.addEventListener('keydown', (event) => { + if (event.key === 'Escape' && navigation.classList.contains('open')) { + setMenuState(false); + menuButton.focus(); + } + }); + + desktopViewport.addEventListener('change', (event) => { + if (event.matches) setMenuState(false); + }); + + setMenuState(false); } const year = document.querySelector('#year');