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
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ jobs:
persist-credentials: false
- name: Validate public-readiness contracts
run: bash tests/scripts/public-readiness-contract-tests.sh
- name: Validate website localization contracts
run: bash tests/scripts/website-localization-contract-tests.sh
- name: Validate asset provenance hashes
run: bash tests/scripts/asset-provenance-contract-tests.sh
- name: Validate bundled song manifests
Expand Down
462 changes: 83 additions & 379 deletions README.md

Large diffs are not rendered by default.

86 changes: 86 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# HitTheKit documentation

This directory is the canonical technical-documentation hub for HitTheKit.
Documentation is versioned with the code so architectural and behavioral
changes can be reviewed in the same pull request that introduces them.

The GitHub Wiki is intentionally not used. Keeping one source of truth avoids
stale copies, preserves history, supports local reading and makes broken links
testable in CI.

## Start here

- [Root README](../README.md) — project overview and source setup
- [Contributing](../CONTRIBUTING.md) — contribution workflow and current limits
- [Support](../SUPPORT.md) — supported environments and diagnostic information
- [Roadmap](../ROADMAP.md) — current priorities and public milestones
- [Security](../SECURITY.md) — private vulnerability reporting

## Architecture

- [Unity integration](architecture/unity-integration.md)
- [Gameplay session model](architecture/gameplay-session-model.md)
- [Player progress persistence](architecture/player-progress-persistence.md)
- [Electronic-drum profiles](architecture/electronic-drum-profiles.md)
- [Device Setup boundaries](architecture/device-setup-boundaries.md)
- [Capture-to-profile pipeline](architecture/capture-to-profile-pipeline.md)
- [macOS CoreMIDI runtime](architecture/macos-coremidi-runtime.md)

## Product and interaction design

- [Beginner learning path](design/beginner-learning-path.md)
- [Gameplay environments](design/gameplay-environments.md)
- [Gameplay highway themes](design/gameplay-highway-themes.md)
- [Main-menu flow](design/main-menu-stage-command.md)
- [Main-menu 3D stage](design/main-menu-3d-stage.md)
- [Device Setup UI](design/device-setup-ui.md)
- [Kit configuration flow](design/kit-configuration-flow.md)

## Development and testing

- [DSP song clock](development/dsp-song-clock.md)
- [Chart timeline](development/chart-timeline.md)
- [Demo-song vertical slice](development/demo-song-vertical-slice.md)
- [Keyboard hit matching](development/keyboard-hit-matching.md)
- [Pad visuals](development/pad-visuals.md)
- [Song library](development/song-library.md)
- [Kit-mapping wizard](development/kit-mapping-wizard.md)
- [Device Setup simulation](development/device-setup-simulation.md)
- [Portable MIDI capture](development/portable-midi-capture.md)
- [CoreMIDI plug-in build](development/coremidi-plugin-build.md)
- [CoreMIDI hardware smoke test](development/coremidi-hardware-smoke.md)
- [URP migration](development/urp-migration.md)

## Packaging and release

- [Public release checklist](release/PUBLIC_RELEASE_CHECKLIST.md)
- [Publication runbook](release/PUBLICATION_RUNBOOK.md)
- [Release process](release/RELEASE_PROCESS.md)
- [0.5.0 release-notes draft](release/0.5.0-release-notes-draft.md)
- [macOS playtest package](development/macos-playtest-package.md)
- [macOS signing and notarization](development/macos-signing-notarization.md)
- [Windows playtest package](development/windows-playtest-package.md)

## Rights and governance

- [Asset provenance](legal/ASSET_PROVENANCE.md)
- [Dual-licensing readiness report](legal/dual-licensing-readiness-report.md)
- [Dual-licensing decision record](governance/dual-licensing-decision.md)
- [Commercial-license draft](legal/COMMERCIAL-LICENSE-DRAFT.md)
- [Project governance](../GOVERNANCE.md)
- [License](../LICENSE)
- [Licensing overview](../LICENSING.md)
- [Third-party notices](../THIRD_PARTY_NOTICES.md)
- [Trademarks](../TRADEMARKS.md)

## Community and launch material

- [Launch kit](launch-kit/README.md)
- [Community post draft](launch-kit/community-post.md)
- [LinkedIn post draft](launch-kit/linkedin-post.md)
- [GitHub Discussions](https://github.com/Codewriter90x/HitTheKit/discussions)
- [Issue templates](https://github.com/Codewriter90x/HitTheKit/issues/new/choose)

If a guide is missing or inaccurate, open a documentation issue or propose a
small pull request. Avoid duplicating the same instructions in the root README,
the website and this hub; link to the canonical guide instead.
1 change: 1 addition & 0 deletions tests/scripts/public-readiness-contract-tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ for path in \
NOTICE \
PRIVACY.md \
README.md \
docs/README.md \
ROADMAP.md \
SECURITY.md \
SUPPORT.md \
Expand Down
63 changes: 63 additions & 0 deletions tests/scripts/website-localization-contract-tests.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
#!/bin/sh
set -eu

REPOSITORY_ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/../.." && pwd)
cd "$REPOSITORY_ROOT"

fail() {
echo "website localization contract: $*" >&2
exit 1
}

ITALIAN_PAGE=website/index.html
ENGLISH_PAGE=website/en/index.html
SCRIPT=website/assets/js/site.js
SITEMAP=website/sitemap.xml

for path in "$ITALIAN_PAGE" "$ENGLISH_PAGE" "$SCRIPT" "$SITEMAP"; do
[ -s "$path" ] || fail "required file is missing or empty: $path"
done

grep -Fq '<html lang="it" data-locale="it">' "$ITALIAN_PAGE" ||
fail "Italian page must declare the Italian locale"
grep -Fq '<html lang="en" data-locale="en">' "$ENGLISH_PAGE" ||
fail "English page must declare the English locale"

for page in "$ITALIAN_PAGE" "$ENGLISH_PAGE"; do
grep -Fq 'hreflang="it" href="https://codewriter90x.github.io/HitTheKit/"' "$page" ||
fail "$page is missing the Italian alternate"
grep -Fq 'hreflang="en" href="https://codewriter90x.github.io/HitTheKit/en/"' "$page" ||
fail "$page is missing the English alternate"
grep -Fq 'hreflang="x-default" href="https://codewriter90x.github.io/HitTheKit/en/"' "$page" ||
fail "$page is missing the global default alternate"
done

grep -Fq 'rel="canonical" href="https://codewriter90x.github.io/HitTheKit/"' "$ITALIAN_PAGE" ||
fail "Italian canonical URL is incorrect"
grep -Fq 'rel="canonical" href="https://codewriter90x.github.io/HitTheKit/en/"' "$ENGLISH_PAGE" ||
fail "English canonical URL is incorrect"

grep -Fq 'src="../assets/js/site.js"' "$ENGLISH_PAGE" ||
fail "English page does not load the shared script from its locale directory"
grep -Fq 'href="../assets/css/styles.css"' "$ENGLISH_PAGE" ||
fail "English page does not load the shared stylesheet from its locale directory"

grep -Fq 'getBrowserLanguage' "$SCRIPT" ||
fail "browser-language detection is missing"
grep -Fq 'window.location.replace(routeForLanguage("en"))' "$SCRIPT" ||
fail "the first-visit English route is missing"
grep -Fq 'saveLanguage(language)' "$SCRIPT" ||
fail "the explicit language preference is not persisted"

ITALIAN_SITEMAP_COUNT=$(grep -Fc '<loc>https://codewriter90x.github.io/HitTheKit/</loc>' "$SITEMAP")
ENGLISH_SITEMAP_COUNT=$(grep -Fc '<loc>https://codewriter90x.github.io/HitTheKit/en/</loc>' "$SITEMAP")
[ "$ITALIAN_SITEMAP_COUNT" -eq 1 ] || fail "sitemap must contain the Italian URL exactly once"
[ "$ENGLISH_SITEMAP_COUNT" -eq 1 ] || fail "sitemap must contain the English URL exactly once"

README_LINES=$(wc -l < README.md | tr -d '[:space:]')
[ "$README_LINES" -le 220 ] ||
fail "root README should remain a concise entry point (found $README_LINES lines)"
grep -Fq '[documentation hub](docs/README.md)' README.md ||
fail "root README must link to the canonical documentation hub"

echo "WEBSITE_LOCALIZATION_CONTRACTS_OK locales=it,en readme_lines=$README_LINES"
14 changes: 11 additions & 3 deletions website/README.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,23 @@
# HitTheKit website

Static, dependency-free public website for HitTheKit. The production entry point
is `index.html`; styles and behavior live under `assets/`.
Static, dependency-free public website for HitTheKit. The Italian production
entry point is `index.html`; the English entry point is `en/index.html`. Styles
and behavior are shared under `assets/`.

Run a local preview from the repository root:

```sh
python3 -m http.server 4173 --directory website
```

Then open `http://127.0.0.1:4173/`.
Then open `http://127.0.0.1:4173/` or
`http://127.0.0.1:4173/en/`.

The root remains the canonical Italian URL. On a first visit, browsers whose
preferred language is not Italian are redirected to the stable English route.
The language control remembers an explicit choice. Both pages publish
canonical and reciprocal `hreflang` metadata, and both URLs are present in the
sitemap.

Before publishing under a custom domain, update the canonical URL, Open Graph
image URL, `robots.txt`, and `sitemap.xml`.
Expand Down
32 changes: 28 additions & 4 deletions website/assets/js/site.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,27 @@ function getSavedLanguage() {
const value = localStorage.getItem("htk-lang");
if (value === "en" || value === "it") return value;
} catch {
// Fall through to the browser preference when storage is unavailable.
// A private or locked-down browser may make storage unavailable.
}

return null;
}

function getBrowserLanguage() {
const browserLanguage = navigator.languages?.[0] || navigator.language || "";
return browserLanguage.toLowerCase().startsWith("it") ? "it" : "en";
}

function saveLanguage(language) {
try { localStorage.setItem("htk-lang", language); } catch { /* storage can be unavailable */ }
}

function routeForLanguage(language) {
const suffix = `${window.location.search}${window.location.hash}`;
if (language === "en") return `en/${suffix}`;
return `../${suffix}`;
}

function applyLanguage(language) {
const dictionary = copy[language];
document.documentElement.lang = language;
Expand All @@ -61,7 +75,6 @@ function applyLanguage(language) {
? "HitTheKit è il rhythm game open source per batteria elettronica: timing deterministico, lezioni progressive e supporto CoreMIDI su macOS."
: "HitTheKit is the open-source rhythm game for electronic drums: deterministic timing, progressive lessons, and CoreMIDI support on macOS.";
}
try { localStorage.setItem("htk-lang", language); } catch { /* storage can be unavailable */ }
}

function setMenu(open) {
Expand All @@ -75,7 +88,9 @@ function setMenu(open) {
}

languageButton.addEventListener("click", () => {
applyLanguage(document.documentElement.lang === "it" ? "en" : "it");
const language = document.documentElement.lang === "it" ? "en" : "it";
saveLanguage(language);
window.location.assign(routeForLanguage(language));
});

menuButton.addEventListener("click", () => {
Expand Down Expand Up @@ -131,4 +146,13 @@ if ("IntersectionObserver" in window) {
pageSections.forEach((section) => sectionObserver.observe(section));
}

applyLanguage(getSavedLanguage());
const pageLanguage = document.documentElement.dataset.locale === "en" ? "en" : "it";
const preferredLanguage = getSavedLanguage() || getBrowserLanguage();

// The root is the canonical Italian entry point. A first-time non-Italian
// visitor is sent to the stable English route; direct /en/ visits remain stable.
if (pageLanguage === "it" && preferredLanguage === "en") {
window.location.replace(routeForLanguage("en"));
} else {
applyLanguage(pageLanguage);
}
Loading
Loading