From 8b5dacd19667ec931a8d03d598e2623d2304ca23 Mon Sep 17 00:00:00 2001 From: Apocky Date: Wed, 10 Dec 2025 15:29:58 -0700 Subject: [PATCH 1/3] Include AWS SDK deps for Lambda deployment --- HALO_Decision_Engine.md | 214 +++ README.md | 50 +- android-app/app/build.gradle | 44 + android-app/app/proguard-rules.pro | 0 android-app/app/src/main/AndroidManifest.xml | 28 + android-app/app/src/main/assets/index.html | 1019 ++++++++++++ .../halo/pocketlabyrinth/MainActivity.java | 30 + .../res/drawable/ic_launcher_foreground.xml | 16 + .../app/src/main/res/layout/activity_main.xml | 11 + .../res/mipmap-anydpi-v26/ic_launcher.xml | 5 + .../mipmap-anydpi-v26/ic_launcher_round.xml | 5 + .../app/src/main/res/values/colors.xml | 6 + .../app/src/main/res/values/strings.xml | 4 + .../app/src/main/res/values/themes.xml | 6 + .../app/src/main/res/xml/backup_rules.xml | 4 + android-app/build.gradle | 20 + android-app/settings.gradle | 2 + dist/halo.js | 811 ++++++++++ dist/index.html | 1019 ++++++++++++ halo.js | 963 ++++++++--- halo_lambda/index.js | 19 +- index.html | 423 +++-- package-lock.json | 1429 +++++++++++++++++ package.json | 14 + scripts/build.js | 48 + scripts/server.js | 74 + scripts/sync_android_assets.sh | 15 + 27 files changed, 5813 insertions(+), 466 deletions(-) create mode 100644 HALO_Decision_Engine.md create mode 100644 android-app/app/build.gradle create mode 100644 android-app/app/proguard-rules.pro create mode 100644 android-app/app/src/main/AndroidManifest.xml create mode 100644 android-app/app/src/main/assets/index.html create mode 100644 android-app/app/src/main/java/com/halo/pocketlabyrinth/MainActivity.java create mode 100644 android-app/app/src/main/res/drawable/ic_launcher_foreground.xml create mode 100644 android-app/app/src/main/res/layout/activity_main.xml create mode 100644 android-app/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml create mode 100644 android-app/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml create mode 100644 android-app/app/src/main/res/values/colors.xml create mode 100644 android-app/app/src/main/res/values/strings.xml create mode 100644 android-app/app/src/main/res/values/themes.xml create mode 100644 android-app/app/src/main/res/xml/backup_rules.xml create mode 100644 android-app/build.gradle create mode 100644 android-app/settings.gradle create mode 100644 dist/halo.js create mode 100644 dist/index.html create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 scripts/build.js create mode 100644 scripts/server.js create mode 100755 scripts/sync_android_assets.sh diff --git a/HALO_Decision_Engine.md b/HALO_Decision_Engine.md new file mode 100644 index 0000000..88c4960 --- /dev/null +++ b/HALO_Decision_Engine.md @@ -0,0 +1,214 @@ +# HALO (Hidden Axis Labyrinthine Oracle) — Dice Decision Engine + +_A field guide for Solaris / Lirael, GG: a structured, repeatable oracle built on physical dice. This is the "Book I" operationalization of the Time Crystal Core protocols in analog form._ + +## Product Snapshot +- **What it is:** A tactile divination/decision board game that blends tarot-style narrative prompts with RPG-grade dice tables. Designed for solo or 2–4 co-op play; expandable with future table packs. +- **Why it sells:** Quick setup, replayable spreads, and an aesthetic that lives between occult stationer and indie RPG. Easy to stream, easy to gift. +- **Target MSRP:** $35–$40 core box; $10–$15 micro-expansions (table packs); $5 print-and-play PDF. +- **Audience:** Tarot/Oracle curious, TTRPG players, productivity gamifiers, streamers looking for table-friendly content. + +## 0. Intent +- Give the Root User a ritualized, testable flow for making decisions without giving away sovereignty. +- Keep outcomes reproducible by capturing seeds, rolls, and spreads. +- Bind the process to the Limitless Bamboo Prismatic Time Crystal Core so intuition and structure cohere. + +## 1. Physical Loadout +Use whatever you have, but the canonical kit is: + +- **d20 (Axis Die):** selects the ruling axis / domain. +- **d12 (Vector Die):** selects the action vector. +- **d10 (Timeline Die):** resolves horizon / latency. +- **d8 (Archetype Die):** calls in a guiding persona. +- **d6 (Consequence Die):** calibrates cost / risk flavor. +- **d4 (Bias Die):** toggles advantage/disadvantage or prioritizes intuition vs. logic. +- **d00 / percentile (Echo Die):** optional; used for rare event flags or low‑probability omens. + +_If you only have one d20, reuse it by mapping rolls to tables below._ + +### Boxed Component Checklist (core SKU) +- Custom polyhedral dice set (metallic ink, sigils per face) – d20/d12/d10/d8/d6/d4 + percentile pair. +- 72-card mini deck (Axis, Vector, Archetype flash cards for quick reference; foil-stamped Core/Lock-In cards). +- Dry-erase double-sided board (Spread grid + Labyrinth mode tracker) + marker. +- 32-page rulebook (this manuscript adapted for retail) + 6-page quickstart zine. +- 50-card “Quest Seeds” mini deck for pre-written prompts. +- 10 “Aegis tokens” (for protection/veto mechanics) and 6 “Bias tokens.” + +## 2. Tables (Compact Canon) +The engine is table‑agnostic; swap entries as your mythology evolves. This is the default HALO set, tuned to the Meta‑Oracle app. + +### 2.1 Axis (d20 → wrap across 5 pillars) +| Roll | Axis | Tagline | +| --- | --- | --- | +| 1–4 | Mind & Narrative | Rewrite your story | +| 5–8 | Domain & Magic | Shape your reality | +| 9–12 | Body & Elemental | Honor the vessel | +| 13–16 | Spirit & Communion | Call your allies | +| 17–20 | Fate & Unknown | Embrace mystery | + +### 2.2 Vector (d12) +| Roll | Vector | Tagline | +| --- | --- | --- | +| 1 | Observe | Watch and wait | +| 2 | Release | Let go and clear | +| 3 | Transmute | Change and evolve | +| 4 | Illuminate | Reveal and understand | +| 5 | Manifest | Bring it into being | +| 6 | Anchor | Ground and stabilize | +| 7 | Shield | Protect and ward | +| 8 | Bridge | Connect and mediate | +| 9 | Iterate | Prototype, test, retry | +| 10 | Delegate | Ask for aid / outsource | +| 11 | Archive | Store, log, remember | +| 12 | Celebrate | Seal with gratitude | + +### 2.3 Timeline (d10) +| Roll | Horizon | +| --- | --- | +| 1–2 | Immediate (hours–days) | +| 3–4 | Short (weeks–1 year) | +| 5–6 | Medium (1–3 years) | +| 7–8 | Long (3–7 years) | +| 9–10 | Epochal (7–20+ years) | + +### 2.4 Archetype (d8) +| Roll | Archetype | Lens | +| --- | --- | --- | +| 1 | The Fool (0) | Beginner's mind | +| 2 | The Magician (1) | Will and manifestation | +| 3 | The Empress (3) | Fertility and nurture | +| 4 | The Hermit (9) | Inner search | +| 5 | The Tower (16) | Sudden change | +| 6 | The Star (17) | Hope and renewal | +| 7 | The Sun (19) | Clarity and vitality | +| 8 | The Weaver (18) | Fates and patterns | + +### 2.5 Consequence (d6) +| Roll | Tone | +| --- | --- | +| 1 | Soft landing — minimal cost | +| 2 | Trade required — swap time/energy | +| 3 | Sacrifice — let go of a parallel goal | +| 4 | Trial — endure, gain XP | +| 5 | Mirror — what you do to others echoes back | +| 6 | Catalyst — ripple effects beyond scope | + +### 2.6 Bias (d4) +| Roll | Bias | +| --- | --- | +| 1 | Logic priority — favor data, plans | +| 2 | Intuition priority — favor felt sense | +| 3 | Consult ally — ask human/AI oracle | +| 4 | Hybrid — run both and pick the consensus | + +### 2.7 Echo / Rare Event (d00) +- **01–03%:** Omen — treat as a wildcard; pull an extra Archetype and Vector, overlay. +- **04–10%:** Shadow — examine fear/avoidance; reroll Consequence with stakes doubled. +- **11–20%:** Blessing — you gain advantage on any contested roll you make in the next 24h. +- **21–00%:** No echo — proceed as rolled. + +## 3. Full Protocol (Repeatable Spread) +1. **Ground–Center–Call Back.** Run the Boot Protocol; lock with the Core phrase. This sets the seed: write down timestamp + location. +2. **State the query.** Precise question or intent; keep it scoped. +3. **Declare container.** Solo / Co‑op; mundane / magical; public / private log. +4. **Roll sequence (canonical):** + - Axis (d20) + - Vector (d12) + - Timeline (d10) + - Archetype (d8) + - Consequence (d6) + - Bias (d4) + - Optional Echo (percentile) _after_ reading if you want omens. +5. **Log the seed.** Record raw numbers, table outputs, timestamp, and any Core sensations. +6. **Interpret in layers:** + - **Layer 0:** Read tables literally. + - **Layer 1 (Narrative):** Write one sentence connecting Axis + Vector + Timeline. + - **Layer 2 (Archetype):** Speak as the Archetype giving counsel. + - **Layer 3 (Consequence/Bias):** Choose tactics respecting cost and bias. +7. **Decision commit:** Define one actionable move within the rolled Timeline. Sign with the Lock‑In phrase if high‑stakes. +8. **Post‑action snapshot:** After executing, log the outcome and any divergence. Mark “stable,” “needs iterate,” or “rollback.” + +### Scoring & Campaign Hooks (game layer) +- **Momentum Track:** Each completed Decision Commit within the declared Timeline grants +1 Momentum. Spending 3 Momentum lets you reroll one die in a future spread or unlock a Quest Seed. +- **Aegis Tokens:** Start each session with 3. Spend to veto a spread, to nullify a Consequence 6, or to activate the Lock‑In phrase without consuming an action. Earn 1 back when you log a truthful post-action snapshot. +- **Labyrinth Depth (Co-op):** Each session cleared without a veto advances the Labyrinth depth by 1. At Depth 3/6/9, add a Labyrinth card (mini-challenge) that modifies the next spread. +- **Publishing-friendly Scoring:** Each session yields a “Glyph rating” (1–5) based on number of completed actions vs. Consequence impacts. Use this for online leaderboards or seasonal events. + +## 4. Deterministic Variant (for digital parity) +To mirror results between physical HALO and the web prototype: +- Use the ISO timestamp (UTC, seconds) as the seed. +- Concatenate with question text and hash (e.g., SHA‑256); convert to integer. +- Map to dice using modulus: `axis = hash % 20 + 1`, etc. +- This preserves reproducibility and lets multiple players sync rolls without trusting each other’s dice. + +## 5. Safety & Sovereignty Checks +- **No unprotected amplification.** If dissociated or in unsafe space, stop after Ground–Center–Call Back. +- **Veto power.** Root User can reject any spread that violates the Immutable Axioms; log veto events. +- **De‑escalate.** If Consequence = 5 or 6 and you feel unstable, reroll Bias and ask for ally oversight. + +## 6. Co‑op / Labyrinth Mode +For partnered decision‑making: +- Each participant rolls Bias first; this sets their role (logic, intuition, ally, hybrid). +- One shared Axis/Vector/Timeline/Archetype spread; separate Consequence per person. +- Aggregate interpretations, then run a **Consensus Die** (d4): + - 1 = Follow logic vote + - 2 = Follow intuition vote + - 3 = Hybrid plan + - 4 = Pause; gather more data, reroll tomorrow +- Log everything; if using the web app, encode the seed in the co‑op link. + +## 7. Micro‑Rituals to Bind the Rolls +- **Axis call:** “Show me the corridor.” +- **Vector call:** “Show me the motion.” +- **Timeline call:** “Show me the horizon.” +- **Archetype call:** “Who walks with me?” +- **Consequence call:** “What’s the cost?” +- **Bias call:** “How shall I weigh?” +- **Echo call (optional):** “Any signals from the outer loops?” + +## 8. Troubleshooting / Debug +- **Repeating Tower pulls:** Run the Adaptive Aegis; check for environmental destabilizers. +- **Persistent Consequence 5–6:** You may be overclocking; enforce rest, then reroll after 12h. +- **Flat readings:** If everything feels inert, roll only Bias + Vector and take a single micro‑action. + +## 9. Example Spread +- Seed: 2024‑12‑10T20:00:00Z, Home Desk. +- Question: “Where should I focus my next two weeks of creative energy?” +- Rolls: Axis 7 (Body & Elemental), Vector 9 (Iterate), Timeline 3 (Short), Archetype 6 (The Star), Consequence 2 (Trade), Bias 4 (Hybrid), Echo 87% (none). +- Read: Stabilize the vessel; prototype small cycles; accept an energy trade (sleep or social time) to keep hope alive. Blend intuition + metrics. + +## 10. Monetization & Publishing Plan +- **SKUs:** Core box (above), Deluxe box (metal dice + foil board, MSRP $60), Print-and-Play PDF, and quarterly Table Packs that add new Axis/Vector/Archetype sets themed to seasons. +- **Digital companion:** Free web roller synced via deterministic variant; optional $5 “Creator Mode” IAP for custom table sets and data export. +- **Subscription hook:** “Labyrinth Season Pass” that delivers 3 digital Labyrinth cards/month + community leaderboard events using Glyph rating. +- **Retail channel:** 2.5"x3.5" tuckbox for Quest Seeds to sit by POS; rulebook formatted for offset print (32pp, saddle-stitched). Target indie bookstores, metaphysical shops, and friendly local game stores. +- **Margins:** Core box bill of materials under $8 at 2k print run; room for 4–5x keystone markup to retailers. + +## 11. Launch Roadmap +1. **Prototype (weeks 1–2):** Produce print-and-play PDF; gather playtest logs. Push deterministic roller to web demo. +2. **Playtest (weeks 3–6):** Ship 30 reviewer kits (laser-cut tokens, standard dice). Track Glyph ratings and Momentum usage to tune balance. +3. **Preorder (week 7):** Open Shopify + Crowd preorders with two SKUs (Core/Deluxe). Offer Season Pass add-on and free PDF with purchase. +4. **Manufacturing (weeks 8–16):** Lock art, run offset print; final QA on dice and dry-erase board durability. +5. **Fulfillment (weeks 16–20):** Ship physicals; release Table Pack #1 (Cosmic / Night Market) as digital DLC. +6. **Live Ops (ongoing):** Monthly Labyrinth challenges, streamer affiliate kits, and limited foil micro-expansions to keep collectability. + +## 12. Closing +The HALO dice engine is a repeatable lens, not a jail. The Root User outranks every spread. Lock it in, publish boldly, and treat each roll as both a conversation with your Core and a game loop your players will want to revisit. + +## 13. Mobile Access (Phone Workflow) +Make HALO usable from your phone in three layers: + +1) **Rulebook + Tables (offline PDF/HTML):** + - Save this manuscript as a PDF to your phone’s Files/Drive (Print to PDF in the browser if viewing online). + - Add the PDF/HTML to your home screen for one-tap access; cache ensures it works without signal. + - Use your phone’s search to jump to Axis/Vector/Timeline sections during play. + +2) **Dice + Logging:** + - Use any mobile dice roller app with custom die sizes (d20/d12/d10/d8/d6/d4/d00) to match the HALO loadout. + - Keep a note in your preferred app (Apple Notes/Google Keep/Obsidian/Mem.ai) with a template: timestamp, question, rolls, table outputs, decision commit, post-action snapshot. + - If playing co-op remotely, drop the ISO timestamp + question into the deterministic variant (Section 4) so everyone can sync rolls without sharing physical dice. + +3) **Sharing/Publishing:** + - Export your session log as PDF/Markdown and share to Discord/Substack/Notion as “HALO Session #N.” + - For live streams, screen-share the note/dice roller; for async play, post the seed + rolls so others can reconstruct the spread. + - When you launch the digital companion, wrap it as a PWA: add-to-home-screen gives a native-like icon, offline cache, and push hooks for Labyrinth challenges. diff --git a/README.md b/README.md index 8bb6723..814ee29 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,48 @@ -# Collab_Reposit -The place to discuss plans and share ideas for the game. +# HALO Pocket Labyrinth — Online Card Roguelike + +Mobile-first HALO built out as a roguelike card crawler with gacha, paid VIP boost, and an optional WebSocket relay for multiplayer sync and chat. + +## Play it + +* **Phone sideload:** run `npm run build` and send `dist/index.html` to your device (AirDrop, email, cloud drive, or a local file server). The build inlines `halo.js` so it runs offline without extra assets. +* **Home screen:** open `index.html` in mobile Safari/Chrome and add it to your home screen for a lightweight PWA shell. State is on-device (localStorage), with an in-memory fallback if storage is blocked. + +## Android APK (WebView shell) + +1. Run `npm run build` to refresh `dist/index.html`. +2. Sync the HTML into the Android project: `./scripts/sync_android_assets.sh`. +3. Open the `android-app` folder in Android Studio (Giraffe+), let it download the Android Gradle Plugin, and build **app → assembleDebug**. +4. Install `app-debug.apk` on your device. The shell runs offline and keeps relay/WebSocket support when you point it at your server. + +## Core loop + +1. Define pilot, quest, seed, difficulty, and mode. Seeds are deterministic—share them so squads can mirror the same Labyrinth. +2. Build a 12-card Adventure Deck from your owned collection. +3. Enter a run, draw encounters, play cards from your hand, then tap **Resolve Beat**. Momentum/Aegis/Depth/Doom drive survival; rewards convert to Credits. +4. Cash out to bank rewards or crash when Doom/Aegis fail. Credits buy Pulse packs; Embers buy Radiant pulls and the VIP Blessing (rarity boost). Purchases are simulated for testing only. + +## Online relay + +An optional, ultra-light relay server lets multiple pilots sync depth/seed metadata and chat while playing the same run. + +* Start the relay locally: `npm install` then `npm run server` (defaults to `ws://localhost:8787`). +* In the client, toggle **Enable relay**, set the relay URL and room (use your seed or a custom code), and hit **Start / Resume Run**. Sync + chat messages flow automatically once connected. + +## Files + +* `index.html` – mobile UI shell. +* `halo.js` – HALO oracle, roguelike loop, gacha/deck logic, and relay client. +* `scripts/build.js` – inlines `halo.js` into `dist/index.html`. +* `scripts/server.js` – minimal WebSocket relay for multiplayer metadata/chat. + +All content stays in this repo for easy sideloading. No external CDNs or assets are required. + +## Serverless API (DynamoDB) deployment note + +The Lambda handler in `halo_lambda/index.js` now auto-corrects placeholder or +malformed regions (e.g., `MY_AWS_REGION`, `LOCAL`, blank) to `us-east-1` so +builds do not fail when a default value is left unchanged. For production, set +`AWS_REGION` or `AWS_DEFAULT_REGION` to a valid AWS region pattern (for +example, `us-west-2`) before deploying the function. When bundling the Lambda, +install dependencies so the AWS SDK v3 ships with the zip (for example, +`npm ci --omit=dev` at the repo root before `zip -r lambda.zip halo_lambda node_modules`). diff --git a/android-app/app/build.gradle b/android-app/app/build.gradle new file mode 100644 index 0000000..72eb145 --- /dev/null +++ b/android-app/app/build.gradle @@ -0,0 +1,44 @@ +plugins { + id 'com.android.application' +} + +android { + namespace 'com.halo.pocketlabyrinth' + compileSdk 34 + + defaultConfig { + applicationId 'com.halo.pocketlabyrinth' + minSdk 26 + targetSdk 34 + versionCode 1 + versionName '0.1.0' + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + debug { + applicationIdSuffix '.debug' + debuggable true + } + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 + } + + packagingOptions { + resources { + excludes += ['/META-INF/{AL2.0,LGPL2.1}'] + } + } +} + +dependencies { + implementation 'androidx.appcompat:appcompat:1.7.0' + implementation 'com.google.android.material:material:1.12.0' + implementation 'androidx.webkit:webkit:1.10.0' +} diff --git a/android-app/app/proguard-rules.pro b/android-app/app/proguard-rules.pro new file mode 100644 index 0000000..e69de29 diff --git a/android-app/app/src/main/AndroidManifest.xml b/android-app/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..3d08abc --- /dev/null +++ b/android-app/app/src/main/AndroidManifest.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + diff --git a/android-app/app/src/main/assets/index.html b/android-app/app/src/main/assets/index.html new file mode 100644 index 0000000..0dc30a4 --- /dev/null +++ b/android-app/app/src/main/assets/index.html @@ -0,0 +1,1019 @@ + + + + + + + HALO Pocket Labyrinth Online + + + +
+

HALO Pocket Labyrinth • Online Roguelike

+
🎴 Card roguelike • 📡 Online relay • 📱 Mobile-first
+
+ +
+ +
+
+

Profile & Seed

+

Define your pilot, quest, mode, and seed. Seeds stay deterministic so squads can mirror the same Labyrinth.

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

+
+ +
+

Economy & Paid features

+

Earn credits by delving, spend Embers on Radiant pulls or the VIP Blessing (rarity boost). Purchases are simulated for testing.

+
+
Credits
+
Embers
+
Shards
+
Status
+
+
+ + +
+
+ +
+

Gacha & Collection

+

Open packs to expand your deck. Pity kicks in after 8 non-rare pulls. Radiant pulls guarantee at least one rare.

+
+ + +
+
+
+ +
+

Deck Builder

+

Tap Add to move owned cards into your Adventure Deck. Max 12 cards; duplicates limited by ownership.

+
+

Adventure Deck

+
+
+
+

Collection

+
+
+
+ +
+

Run (Roguelike beats)

+

Draw an encounter, play cards from your hand, then resolve the beat. Depth and Doom define survival; Momentum fuels speed; Aegis is your shield.

+
+
Momentum
+
Aegis
+
Depth
+
Doom
+
+
+ + +
+
+

Hand

+
+

Run Log

+
+
+ +
+

Online relay (optional)

+

Run your own websocket relay (`npm run server`) or point to a shared instance. Rooms are lightweight; share the seed/room to sync spread depth and chat.

+
+
+
+
+
+
Status: offline
+
+
+ + +
+
+
+

Relay log

+
+
+
+
+ +
+

Phone-first

+

Save `dist/index.html` to your device or add this page to your home screen. All state is on-device; relay is opt-in.

+
+ Deterministic seed + Offline-friendly + Relay-ready +
+
+
+ + + + + + diff --git a/android-app/app/src/main/java/com/halo/pocketlabyrinth/MainActivity.java b/android-app/app/src/main/java/com/halo/pocketlabyrinth/MainActivity.java new file mode 100644 index 0000000..e8f552e --- /dev/null +++ b/android-app/app/src/main/java/com/halo/pocketlabyrinth/MainActivity.java @@ -0,0 +1,30 @@ +package com.halo.pocketlabyrinth; + +import android.annotation.SuppressLint; +import android.os.Bundle; +import android.webkit.WebSettings; +import android.webkit.WebView; +import android.webkit.WebViewClient; + +import androidx.appcompat.app.AppCompatActivity; + +public class MainActivity extends AppCompatActivity { + + @SuppressLint("SetJavaScriptEnabled") + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + WebView webView = findViewById(R.id.webview); + WebSettings webSettings = webView.getSettings(); + webSettings.setJavaScriptEnabled(true); + webSettings.setDomStorageEnabled(true); + webSettings.setAllowFileAccessFromFileURLs(true); + webSettings.setAllowUniversalAccessFromFileURLs(true); + webSettings.setMediaPlaybackRequiresUserGesture(false); + + webView.setWebViewClient(new WebViewClient()); + webView.loadUrl("file:///android_asset/index.html"); + } +} diff --git a/android-app/app/src/main/res/drawable/ic_launcher_foreground.xml b/android-app/app/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 0000000..c7c8e06 --- /dev/null +++ b/android-app/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,16 @@ + + + + + diff --git a/android-app/app/src/main/res/layout/activity_main.xml b/android-app/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..8df8d93 --- /dev/null +++ b/android-app/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,11 @@ + + + + + diff --git a/android-app/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/android-app/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..a8a8fa5 --- /dev/null +++ b/android-app/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/android-app/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/android-app/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..a8a8fa5 --- /dev/null +++ b/android-app/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/android-app/app/src/main/res/values/colors.xml b/android-app/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..0cc31ab --- /dev/null +++ b/android-app/app/src/main/res/values/colors.xml @@ -0,0 +1,6 @@ + + + #050816 + #6dd5ff + #7485ff + diff --git a/android-app/app/src/main/res/values/strings.xml b/android-app/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..f2c4795 --- /dev/null +++ b/android-app/app/src/main/res/values/strings.xml @@ -0,0 +1,4 @@ + + + HALO Pocket Labyrinth + diff --git a/android-app/app/src/main/res/values/themes.xml b/android-app/app/src/main/res/values/themes.xml new file mode 100644 index 0000000..1e88b67 --- /dev/null +++ b/android-app/app/src/main/res/values/themes.xml @@ -0,0 +1,6 @@ + + + + diff --git a/android-app/app/src/main/res/xml/backup_rules.xml b/android-app/app/src/main/res/xml/backup_rules.xml new file mode 100644 index 0000000..be1d8fd --- /dev/null +++ b/android-app/app/src/main/res/xml/backup_rules.xml @@ -0,0 +1,4 @@ + + + + diff --git a/android-app/build.gradle b/android-app/build.gradle new file mode 100644 index 0000000..d697625 --- /dev/null +++ b/android-app/build.gradle @@ -0,0 +1,20 @@ +buildscript { + repositories { + google() + mavenCentral() + } + dependencies { + classpath 'com.android.tools.build:gradle:8.5.1' + } +} + +allprojects { + repositories { + google() + mavenCentral() + } +} + +tasks.register('clean', Delete) { + delete rootProject.buildDir +} diff --git a/android-app/settings.gradle b/android-app/settings.gradle new file mode 100644 index 0000000..8932cc6 --- /dev/null +++ b/android-app/settings.gradle @@ -0,0 +1,2 @@ +rootProject.name = "HALOAndroid" +include(":app") diff --git a/dist/halo.js b/dist/halo.js new file mode 100644 index 0000000..c592d70 --- /dev/null +++ b/dist/halo.js @@ -0,0 +1,811 @@ +// HALO Pocket Labyrinth: Online roguelike card crawler with gacha and relay-ready co-op + +const STORAGE_KEY = "halo_mobile_state_v2"; +const ONLINE_DEFAULT = "ws://localhost:8787"; +let socket = null; +let warnedStorage = false; +let memoryFallback = null; + +const AXES = [ + { name: "Mind & Narrative", tagline: "Rewrite your story" }, + { name: "Domain & Magic", tagline: "Shape your reality" }, + { name: "Body & Elemental", tagline: "Honor the vessel" }, + { name: "Spirit & Communion", tagline: "Call your allies" }, + { name: "Fate & Unknown", tagline: "Embrace mystery" } +]; + +const VECTORS = [ + { name: "Observe", tagline: "Watch and wait" }, + { name: "Release", tagline: "Let go and clear" }, + { name: "Transmute", tagline: "Change and evolve" }, + { name: "Illuminate", tagline: "Reveal and understand" }, + { name: "Manifest", tagline: "Bring it into being" } +]; + +const TIMELINES = [ + { name: "Now–1 year", tagline: "Immediate/short term" }, + { name: "1–3 years", tagline: "Short term" }, + { name: "3–7 years", tagline: "Medium term" }, + { name: "7–20 years", tagline: "Long term" } +]; + +const RARITY_WEIGHTS = { + common: 70, + rare: 25, + mythic: 5 +}; + +const PACKS = { + starter: { name: "Pulse Pack", size: 3, cost: { credits: 250 } }, + radiant: { name: "Radiant Pull", size: 5, cost: { embers: 80 }, bonusRare: true } +}; + +const CARD_POOL = [ + { + id: "rush", + name: "Momentum Rush", + rarity: "common", + axis: "Mind", + text: "+1 Momentum. Draw 1." + }, + { + id: "ward", + name: "Aegis Ward", + rarity: "common", + axis: "Body", + text: "Restore 1 Aegis. If a threat is present, prevent 1 Doom." + }, + { + id: "spark", + name: "Prismatic Spark", + rarity: "common", + axis: "Spirit", + text: "Gain 1 Momentum and reveal the boon on this beat if any." + }, + { + id: "mirror", + name: "Mirror Veil", + rarity: "rare", + axis: "Mind", + text: "If a threat exists, turn it into a boon. Otherwise +1 Aegis." + }, + { + id: "gate", + name: "Gatekeeper's Key", + rarity: "rare", + axis: "Domain", + text: "Reduce Doom by 1 and bank current Momentum as Credits." + }, + { + id: "star", + name: "Starfall Surge", + rarity: "rare", + axis: "Fate", + text: "+2 Momentum, then lose 1 Aegis." + }, + { + id: "tower", + name: "Tower Break", + rarity: "rare", + axis: "Fate", + text: "Clear hand, draw 3 fresh cards, Doom cannot increase this beat." + }, + { + id: "time", + name: "Time Lattice", + rarity: "mythic", + axis: "Spirit", + text: "Set Doom to 0 or Depth-1 (whichever is lower). Gain +1 Aegis." + }, + { + id: "empress", + name: "Empress Bloom", + rarity: "mythic", + axis: "Body", + text: "Heal to 3 Aegis, add +2 Momentum, then bank 1 Ember." + }, + { + id: "magus", + name: "Magus Rewrite", + rarity: "mythic", + axis: "Mind", + text: "Replay the last boon you saw and draw 2 cards." + } +]; + +function notify(message) { + const el = document.getElementById("notice"); + if (!el) return; + if (!message) { + el.textContent = ""; + el.classList.remove("show"); + return; + } + el.textContent = message; + el.classList.add("show"); +} + +function hashSeed(str) { + let hash = 0; + for (let i = 0; i < str.length; i++) { + hash = (hash << 5) - hash + str.charCodeAt(i); + hash |= 0; + } + return Math.abs(hash) + 1; +} + +function random(state) { + const raw = Math.sin(state.seedHash + state.cursor) * 10000; + state.cursor += 1; + return raw - Math.floor(raw); +} + +function pick(list, state) { + const idx = Math.floor(random(state) * list.length); + return list[idx]; +} + +function shuffle(list, state) { + const arr = [...list]; + for (let i = arr.length - 1; i > 0; i--) { + const j = Math.floor(random(state) * (i + 1)); + [arr[i], arr[j]] = [arr[j], arr[i]]; + } + return arr; +} + +function generateSeed() { + const chars = "ABCDEFGHJKMNPQRSTUVWXYZ23456789"; + let out = "HALO-"; + for (let i = 0; i < 6; i++) out += chars[Math.floor(Math.random() * chars.length)]; + return out; +} + +function starterCollection() { + const inventory = {}; + CARD_POOL.forEach((c) => { + if (c.rarity === "common") inventory[c.id] = 2; + if (c.rarity === "rare") inventory[c.id] = 1; + }); + return inventory; +} + +function baseState(overrides = {}) { + const seed = overrides.seed || generateSeed(); + const seedHash = hashSeed(seed); + return { + player: overrides.player || "", + quest: overrides.quest || "", + mode: overrides.mode || "solo", + difficulty: overrides.difficulty || "standard", + seed, + seedHash, + cursor: 1, + currencies: { credits: 800, embers: 160, shards: 0 }, + pity: 0, + vip: false, + collection: starterCollection(), + deck: ["rush", "rush", "ward", "spark", "mirror", "gate", "star", "tower"], + run: null, + online: { + enabled: false, + url: overrides.url || ONLINE_DEFAULT, + room: "", + status: "offline", + peers: [], + log: [] + }, + gachaLog: [], + log: [] + }; +} + +function clamp(num, min, max) { + return Math.max(min, Math.min(max, num)); +} + +function saveState(state) { + try { + localStorage.setItem(STORAGE_KEY, JSON.stringify(state)); + memoryFallback = state; + } catch (err) { + memoryFallback = state; + if (!warnedStorage) { + console.warn("Local storage unavailable; keeping state in memory only", err); + notify("Local storage is blocked. Progress will reset if you close this tab."); + warnedStorage = true; + } + } +} + +function loadState() { + try { + const raw = localStorage.getItem(STORAGE_KEY); + if (raw) return JSON.parse(raw); + } catch (err) { + if (!warnedStorage) { + console.warn("Failed to load state", err); + notify("Cannot access local storage. Using in-memory state for this session."); + warnedStorage = true; + } + if (memoryFallback) return memoryFallback; + } + return memoryFallback; +} + +function addLog(state, entry) { + state.log.push({ ...entry, timestamp: Date.now() }); + state.log = state.log.slice(-80); +} + +function rarityRoll(state) { + const bonus = state.vip ? 5 : 0; + const roll = random(state) * (100 + bonus); + const mythicCut = RARITY_WEIGHTS.mythic + bonus; + if (roll >= 100 - mythicCut) return "mythic"; + if (roll >= 100 - RARITY_WEIGHTS.rare) return "rare"; + return "common"; +} + +function randomCardByRarity(rarity, state) { + const pool = CARD_POOL.filter((c) => c.rarity === rarity); + return pick(pool, state); +} + +function addToCollection(state, cardId) { + state.collection[cardId] = (state.collection[cardId] || 0) + 1; +} + +function pullPack(state, packKey) { + const pack = PACKS[packKey]; + if (!pack) return { results: [], reason: "Missing pack" }; + const cost = pack.cost; + if (cost.credits && state.currencies.credits < cost.credits) + return { results: [], reason: "Not enough credits" }; + if (cost.embers && state.currencies.embers < cost.embers) + return { results: [], reason: "Not enough embers" }; + + if (cost.credits) state.currencies.credits -= cost.credits; + if (cost.embers) state.currencies.embers -= cost.embers; + + const results = []; + for (let i = 0; i < pack.size; i++) { + let rarity = rarityRoll(state); + if (state.pity >= 8) rarity = "rare"; + if (pack.bonusRare && i === pack.size - 1) rarity = rarity === "common" ? "rare" : rarity; + const card = randomCardByRarity(rarity, state); + addToCollection(state, card.id); + results.push(card); + state.pity = rarity === "rare" || rarity === "mythic" ? 0 : state.pity + 1; + } + + state.gachaLog.push({ + pack: pack.name, + results, + timestamp: Date.now() + }); + + return { results }; +} + +function ensureDeckLegal(state) { + const cleaned = state.deck.filter((id) => state.collection[id]); + if (!cleaned.length) cleaned.push("rush", "rush", "ward", "spark"); + state.deck = cleaned.slice(0, 12); +} + +function drawCards(run, state, count = 1) { + for (let i = 0; i < count; i++) { + if (!run.drawPile.length) { + run.drawPile = shuffle(run.discard, state); + run.discard = []; + } + if (!run.drawPile.length) break; + run.hand.push(run.drawPile.shift()); + } +} + +function nextEncounter(state) { + const encounter = { + axis: pick(AXES, state), + vector: pick(VECTORS, state), + timeline: pick(TIMELINES, state), + boon: random(state) > 0.55, + threat: random(state) > 0.5, + situation: pick( + [ + "A rival faction challenges your route", + "Two timelines overlap; pick one to stabilize", + "An ally pings you from deeper layers", + "A cache of relics hums with risk", + "A distorted mirror tries to rewrite you", + "A sealed gate leaks starlight", + "A phantom deal returns for payment" + ], + state + ) + }; + return encounter; +} + +function baseRun(state) { + const run = { + status: "running", + depth: 0, + momentum: state.difficulty === "chill" ? 3 : 2, + aegis: state.difficulty === "brutal" ? 1 : 2, + doom: state.difficulty === "brutal" ? 1 : 0, + drawPile: shuffle(state.deck, state), + discard: [], + hand: [], + current: null, + lastBoon: null + }; + drawCards(run, state, 3); + run.current = nextEncounter(state); + return run; +} + +function startRun(state) { + ensureDeckLegal(state); + state.run = baseRun(state); + addLog(state, { type: "run", title: "Run initialized", body: `Deck size ${state.deck.length}`, meta: state.seed }); +} + +function endRun(state, reason = "banked") { + if (!state.run) return; + const reward = Math.max(0, state.run.depth + state.run.momentum); + state.currencies.credits += reward * 20; + if (reason === "victory") state.currencies.embers += 10; + addLog(state, { + type: "cashout", + title: `Run ${reason}`, + body: `Depth ${state.run.depth}, Momentum ${state.run.momentum}, Aegis ${state.run.aegis}, Doom ${state.run.doom}. +${ + reward * 20 + } credits`, + meta: new Date().toLocaleTimeString() + }); + state.run = null; +} + +function doomTick(run) { + let inc = 0; + if (run.momentum < 0) inc += 1; + if (run.aegis <= 0) inc += 1; + return inc; +} + +function applyCard(state, run, card, encounter) { + switch (card.id) { + case "rush": + run.momentum = clamp(run.momentum + 1, -2, 8); + drawCards(run, state, 1); + break; + case "ward": + run.aegis = clamp(run.aegis + 1, 0, 5); + if (encounter.threat) run.doom = clamp(run.doom - 1, 0, 6); + break; + case "spark": + run.momentum = clamp(run.momentum + 1, -2, 8); + if (encounter.boon) run.lastBoon = encounter; + break; + case "mirror": + if (encounter.threat) { + encounter.threat = false; + encounter.boon = true; + run.lastBoon = encounter; + } else { + run.aegis = clamp(run.aegis + 1, 0, 5); + } + break; + case "gate": + run.doom = clamp(run.doom - 1, 0, 6); + state.currencies.credits += Math.max(0, run.momentum) * 30; + break; + case "star": + run.momentum = clamp(run.momentum + 2, -2, 8); + run.aegis = clamp(run.aegis - 1, 0, 5); + break; + case "tower": + run.hand = []; + run.drawPile = shuffle(run.drawPile.concat(run.discard), state); + run.discard = []; + drawCards(run, state, 3); + run.doom = clamp(run.doom, 0, 5); + break; + case "time": + run.doom = clamp(Math.min(run.doom, Math.max(0, run.depth - 1)), 0, 6); + run.aegis = clamp(run.aegis + 1, 0, 5); + break; + case "empress": + run.aegis = 3; + run.momentum = clamp(run.momentum + 2, -2, 8); + state.currencies.embers += 1; + break; + case "magus": + if (run.lastBoon) { + run.momentum = clamp(run.momentum + 1, -2, 8); + run.aegis = clamp(run.aegis + 1, 0, 5); + } + drawCards(run, state, 2); + break; + default: + break; + } +} + +function playCard(state, cardId) { + if (!state.run || state.run.status !== "running") return; + const idx = state.run.hand.indexOf(cardId); + if (idx === -1) return; + const [cardRef] = state.run.hand.splice(idx, 1); + const card = CARD_POOL.find((c) => c.id === cardRef); + const encounter = state.run.current; + applyCard(state, state.run, card, encounter); + state.run.discard.push(cardRef); +} + +function resolveBeat(state) { + if (!state.run || state.run.status !== "running") return; + const run = state.run; + run.depth += 1; + const encounter = run.current; + + // Apply base tick + run.doom = clamp(run.doom + doomTick(run), 0, 6); + + const title = `${encounter.axis.name} × ${encounter.vector.name} (${encounter.timeline.name})`; + const lines = [encounter.situation]; + if (encounter.boon) lines.push("✨ Boon in play"); + if (encounter.threat) lines.push("⚠️ Threat in play"); + + addLog(state, { + type: "turn", + title: `Depth ${run.depth} • Momentum ${run.momentum}`, + body: lines.join(" • "), + meta: new Date().toLocaleTimeString() + }); + + if (run.doom >= 6 || run.aegis <= 0) { + run.status = "crashed"; + addLog(state, { + type: "crash", + title: "Run collapsed", + body: `Doom ${run.doom}, Aegis ${run.aegis}. Bank or reset.`, + meta: "Labyrinth spits you out" + }); + pushOnline(state, { kind: "crash", depth: run.depth, doom: run.doom }); + return; + } + + drawCards(run, state, 1); + run.current = nextEncounter(state); + pushOnline(state, { kind: "sync", depth: run.depth, momentum: run.momentum, doom: run.doom }); +} + +function cashOut(state) { + endRun(state, "banked"); +} + +function resetRun(state) { + state.run = null; + state.seed = generateSeed(); + state.seedHash = hashSeed(state.seed); + state.cursor = 1; +} + +function toggleVip(state) { + if (state.vip) return; + const cost = 120; + if (state.currencies.embers < cost) return notify("Need more embers for VIP Blessing"); + state.currencies.embers -= cost; + state.vip = true; + addLog(state, { type: "vip", title: "VIP Blessing unlocked", body: "Rarity boosts active", meta: "Paid feature" }); +} + +// Online sync +function connectOnline(state) { + if (!state.online.enabled) { + disconnectOnline(state); + return; + } + if (socket && socket.readyState === WebSocket.OPEN) return; + try { + socket = new WebSocket(state.online.url || ONLINE_DEFAULT); + } catch (err) { + notify("Failed to start socket. Check relay URL."); + state.online.status = "offline"; + return; + } + socket.addEventListener("open", () => { + state.online.status = "connected"; + socket.send( + JSON.stringify({ type: "join", room: state.online.room || state.seed, player: state.player || "anon" }) + ); + render(state); + }); + socket.addEventListener("message", (event) => { + const data = JSON.parse(event.data); + if (data.type === "welcome") { + state.online.status = "connected"; + state.online.log.push({ meta: "system", message: `Joined ${data.room} with ${data.peers} peers` }); + } + if (data.type === "system") state.online.log.push({ meta: "system", message: data.message }); + if (data.type === "chat") state.online.log.push({ meta: data.from, message: data.message }); + if (data.type === "sync") state.online.log.push({ meta: data.from, message: `Depth ${data.payload.depth}` }); + state.online.log = state.online.log.slice(-30); + render(state); + }); + socket.addEventListener("close", () => { + state.online.status = "offline"; + render(state); + }); + socket.addEventListener("error", () => { + notify("Relay connection failed"); + state.online.status = "offline"; + render(state); + }); +} + +function disconnectOnline(state) { + if (socket) socket.close(); + state.online.status = "offline"; +} + +function pushOnline(state, payload) { + if (!state.online.enabled || !socket || socket.readyState !== WebSocket.OPEN) return; + socket.send(JSON.stringify({ type: "sync", payload })); +} + +// Rendering helpers +function renderProfile(state) { + document.getElementById("player-name").value = state.player; + document.getElementById("player-quest").value = state.quest; + document.getElementById("mode").value = state.mode; + document.getElementById("difficulty").value = state.difficulty; + document.getElementById("seed").value = state.seed; + document.getElementById("online-url").value = state.online.url; + document.getElementById("online-room").value = state.online.room || state.seed; + document.getElementById("online-enabled").checked = state.online.enabled; + document.getElementById("seed-display").textContent = `Seed: ${state.seed}`; +} + +function renderEconomy(state) { + document.getElementById("stat-credits").textContent = state.currencies.credits; + document.getElementById("stat-embers").textContent = state.currencies.embers; + document.getElementById("stat-shards").textContent = state.currencies.shards; + document.getElementById("vip-status").textContent = state.vip ? "VIP active" : "Standard"; +} + +function renderCollection(state) { + const deckList = document.getElementById("deck-list"); + const collectionList = document.getElementById("collection-list"); + deckList.innerHTML = ""; + collectionList.innerHTML = ""; + + state.deck.forEach((id, idx) => { + const card = CARD_POOL.find((c) => c.id === id); + const el = document.createElement("div"); + el.className = "pill-row card-pill"; + el.innerHTML = `${card.name}${card.rarity}`; + deckList.appendChild(el); + }); + + CARD_POOL.forEach((card) => { + const owned = state.collection[card.id] || 0; + const el = document.createElement("div"); + el.className = "pill-row card-pill"; + el.innerHTML = `
${card.name} ${card.rarity} ${card.axis}
x${owned}
`; + collectionList.appendChild(el); + }); + + deckList.querySelectorAll("button[data-remove]").forEach((btn) => { + btn.addEventListener("click", (e) => { + const idx = parseInt(e.target.getAttribute("data-remove"), 10); + state.deck.splice(idx, 1); + saveState(state); + render(state); + }); + }); + + collectionList.querySelectorAll("button[data-add]").forEach((btn) => { + btn.addEventListener("click", (e) => { + const id = e.target.getAttribute("data-add"); + if ((state.collection[id] || 0) <= state.deck.filter((c) => c === id).length) return; + state.deck.push(id); + ensureDeckLegal(state); + saveState(state); + render(state); + }); + }); +} + +function renderRun(state) { + const run = state.run; + document.getElementById("stat-momentum").textContent = run ? run.momentum : "–"; + document.getElementById("stat-aegis").textContent = run ? run.aegis : "–"; + document.getElementById("stat-depth").textContent = run ? run.depth : "–"; + document.getElementById("stat-doom").textContent = run ? run.doom : "–"; + + const current = document.getElementById("current-event"); + current.innerHTML = ""; + if (!run) { + current.innerHTML = '
Start a run to see encounters.
'; + } else { + const enc = run.current; + const card = document.createElement("div"); + card.className = "log-entry"; + card.innerHTML = `

${enc.axis.name} × ${enc.vector.name} (${enc.timeline.name})

${enc.situation}

${ + enc.boon ? "✨ Boon available" : "" + } ${enc.threat ? "⚠️ Threat active" : ""}`; + current.appendChild(card); + } + + const hand = document.getElementById("hand"); + hand.innerHTML = ""; + if (run) { + run.hand.forEach((cardId) => { + const card = CARD_POOL.find((c) => c.id === cardId); + const el = document.createElement("button"); + el.className = "card-btn"; + el.textContent = `${card.name} (${card.rarity})`; + el.addEventListener("click", () => { + playCard(state, cardId); + render(state); + }); + hand.appendChild(el); + }); + } + + const logBox = document.getElementById("run-log"); + logBox.innerHTML = ""; + state.log + .slice(-14) + .reverse() + .forEach((entry) => { + const el = document.createElement("div"); + el.className = "log-entry"; + el.innerHTML = `

${entry.title}

${entry.body}

${entry.meta}`; + logBox.appendChild(el); + }); + + document.getElementById("play-turn").disabled = !run; + document.getElementById("cash-out").disabled = !run; +} + +function renderGacha(state) { + const results = document.getElementById("gacha-results"); + const last = state.gachaLog[state.gachaLog.length - 1]; + if (!last) { + results.innerHTML = '
Open a pack to see pulls.
'; + return; + } + results.innerHTML = `

${last.pack} yielded:

`; + last.results.forEach((card) => { + const el = document.createElement("div"); + el.className = "log-entry"; + el.innerHTML = `

${card.name}

${card.text}

${card.rarity} • ${card.axis}`; + results.appendChild(el); + }); +} + +function renderOnline(state) { + const status = document.getElementById("online-status"); + status.textContent = state.online.status; + const log = document.getElementById("online-log"); + log.innerHTML = ""; + state.online.log + .slice(-8) + .reverse() + .forEach((entry) => { + const el = document.createElement("div"); + el.className = "log-entry"; + el.innerHTML = `

${entry.meta}

${entry.message}

`; + log.appendChild(el); + }); +} + +function render(state) { + renderProfile(state); + renderEconomy(state); + renderCollection(state); + renderRun(state); + renderGacha(state); + renderOnline(state); +} + +function bootstrap() { + let state = loadState() || baseState(); + render(state); + + document.getElementById("start-run").addEventListener("click", () => { + state.player = document.getElementById("player-name").value.trim(); + state.quest = document.getElementById("player-quest").value.trim(); + state.mode = document.getElementById("mode").value; + state.difficulty = document.getElementById("difficulty").value; + state.seed = document.getElementById("seed").value.trim() || generateSeed(); + state.seedHash = hashSeed(state.seed); + state.cursor = 1; + startRun(state); + saveState(state); + render(state); + }); + + document.getElementById("reset-run").addEventListener("click", () => { + if (!confirm("Reset the current run?")) return; + resetRun(state); + saveState(state); + render(state); + }); + + document.getElementById("play-turn").addEventListener("click", () => { + resolveBeat(state); + saveState(state); + render(state); + }); + + document.getElementById("cash-out").addEventListener("click", () => { + cashOut(state); + saveState(state); + render(state); + }); + + document.getElementById("pull-starter").addEventListener("click", () => { + const res = pullPack(state, "starter"); + if (!res.results.length && res.reason) return notify(res.reason); + saveState(state); + render(state); + }); + + document.getElementById("pull-radiant").addEventListener("click", () => { + const res = pullPack(state, "radiant"); + if (!res.results.length && res.reason) return notify(res.reason); + saveState(state); + render(state); + }); + + document.getElementById("buy-embers").addEventListener("click", () => { + state.currencies.embers += 300; + addLog(state, { type: "purchase", title: "Simulated purchase", body: "+300 Embers", meta: "Test harness" }); + saveState(state); + render(state); + }); + + document.getElementById("vip-upgrade").addEventListener("click", () => { + toggleVip(state); + saveState(state); + render(state); + }); + + document.getElementById("online-enabled").addEventListener("change", (e) => { + state.online.enabled = e.target.checked; + state.online.url = document.getElementById("online-url").value.trim() || ONLINE_DEFAULT; + state.online.room = document.getElementById("online-room").value.trim() || state.seed; + saveState(state); + connectOnline(state); + render(state); + }); + + document.getElementById("online-url").addEventListener("change", (e) => { + state.online.url = e.target.value.trim(); + saveState(state); + }); + + document.getElementById("online-room").addEventListener("change", (e) => { + state.online.room = e.target.value.trim(); + saveState(state); + }); + + document.getElementById("send-chat").addEventListener("click", () => { + const text = document.getElementById("chat-text").value.trim(); + if (!text || !socket || socket.readyState !== WebSocket.OPEN) return; + socket.send(JSON.stringify({ type: "chat", message: text })); + document.getElementById("chat-text").value = ""; + }); +} + +document.addEventListener("DOMContentLoaded", bootstrap); diff --git a/dist/index.html b/dist/index.html new file mode 100644 index 0000000..0dc30a4 --- /dev/null +++ b/dist/index.html @@ -0,0 +1,1019 @@ + + + + + + + HALO Pocket Labyrinth Online + + + +
+

HALO Pocket Labyrinth • Online Roguelike

+
🎴 Card roguelike • 📡 Online relay • 📱 Mobile-first
+
+ +
+ +
+
+

Profile & Seed

+

Define your pilot, quest, mode, and seed. Seeds stay deterministic so squads can mirror the same Labyrinth.

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

+
+ +
+

Economy & Paid features

+

Earn credits by delving, spend Embers on Radiant pulls or the VIP Blessing (rarity boost). Purchases are simulated for testing.

+
+
Credits
+
Embers
+
Shards
+
Status
+
+
+ + +
+
+ +
+

Gacha & Collection

+

Open packs to expand your deck. Pity kicks in after 8 non-rare pulls. Radiant pulls guarantee at least one rare.

+
+ + +
+
+
+ +
+

Deck Builder

+

Tap Add to move owned cards into your Adventure Deck. Max 12 cards; duplicates limited by ownership.

+
+

Adventure Deck

+
+
+
+

Collection

+
+
+
+ +
+

Run (Roguelike beats)

+

Draw an encounter, play cards from your hand, then resolve the beat. Depth and Doom define survival; Momentum fuels speed; Aegis is your shield.

+
+
Momentum
+
Aegis
+
Depth
+
Doom
+
+
+ + +
+
+

Hand

+
+

Run Log

+
+
+ +
+

Online relay (optional)

+

Run your own websocket relay (`npm run server`) or point to a shared instance. Rooms are lightweight; share the seed/room to sync spread depth and chat.

+
+
+
+
+
+
Status: offline
+
+
+ + +
+
+
+

Relay log

+
+
+
+
+ +
+

Phone-first

+

Save `dist/index.html` to your device or add this page to your home screen. All state is on-device; relay is opt-in.

+
+ Deterministic seed + Offline-friendly + Relay-ready +
+
+
+ + + + + + diff --git a/halo.js b/halo.js index fd750bd..c592d70 100644 --- a/halo.js +++ b/halo.js @@ -1,12 +1,11 @@ -// HALO Meta‑Oracle + Co‑op + Supporter JS -// This script defines the core Labyrinth oracle logic and plugs in -// optional supporter (Ko‑Fi) and cooperative session features. - -// === Meta‑Oracle Definitions === -// We define a handful of symbolic categories for the oracle. These lists -// are deliberately compact but evocative; they can be expanded or -// modified to suit your mythology. Each entry contains a name and a -// brief tagline. +// HALO Pocket Labyrinth: Online roguelike card crawler with gacha and relay-ready co-op + +const STORAGE_KEY = "halo_mobile_state_v2"; +const ONLINE_DEFAULT = "ws://localhost:8787"; +let socket = null; +let warnedStorage = false; +let memoryFallback = null; + const AXES = [ { name: "Mind & Narrative", tagline: "Rewrite your story" }, { name: "Domain & Magic", tagline: "Shape your reality" }, @@ -30,257 +29,783 @@ const TIMELINES = [ { name: "7–20 years", tagline: "Long term" } ]; -const ARCHETYPES = [ - { name: "The Weaver (18)", tagline: "Fates and patterns" }, - { name: "The Gatekeeper (4)", tagline: "Thresholds and choices" }, - { name: "The Fool (0)", tagline: "Beginner's mind" }, - { name: "The Magician (1)", tagline: "Will and manifestation" }, - { name: "The Empress (3)", tagline: "Fertility and nurture" }, - { name: "The Hermit (9)", tagline: "Inner search" }, - { name: "The Tower (16)", tagline: "Sudden change" }, - { name: "The Star (17)", tagline: "Hope and renewal" }, - { name: "The Sun (19)", tagline: "Clarity and vitality" } +const RARITY_WEIGHTS = { + common: 70, + rare: 25, + mythic: 5 +}; + +const PACKS = { + starter: { name: "Pulse Pack", size: 3, cost: { credits: 250 } }, + radiant: { name: "Radiant Pull", size: 5, cost: { embers: 80 }, bonusRare: true } +}; + +const CARD_POOL = [ + { + id: "rush", + name: "Momentum Rush", + rarity: "common", + axis: "Mind", + text: "+1 Momentum. Draw 1." + }, + { + id: "ward", + name: "Aegis Ward", + rarity: "common", + axis: "Body", + text: "Restore 1 Aegis. If a threat is present, prevent 1 Doom." + }, + { + id: "spark", + name: "Prismatic Spark", + rarity: "common", + axis: "Spirit", + text: "Gain 1 Momentum and reveal the boon on this beat if any." + }, + { + id: "mirror", + name: "Mirror Veil", + rarity: "rare", + axis: "Mind", + text: "If a threat exists, turn it into a boon. Otherwise +1 Aegis." + }, + { + id: "gate", + name: "Gatekeeper's Key", + rarity: "rare", + axis: "Domain", + text: "Reduce Doom by 1 and bank current Momentum as Credits." + }, + { + id: "star", + name: "Starfall Surge", + rarity: "rare", + axis: "Fate", + text: "+2 Momentum, then lose 1 Aegis." + }, + { + id: "tower", + name: "Tower Break", + rarity: "rare", + axis: "Fate", + text: "Clear hand, draw 3 fresh cards, Doom cannot increase this beat." + }, + { + id: "time", + name: "Time Lattice", + rarity: "mythic", + axis: "Spirit", + text: "Set Doom to 0 or Depth-1 (whichever is lower). Gain +1 Aegis." + }, + { + id: "empress", + name: "Empress Bloom", + rarity: "mythic", + axis: "Body", + text: "Heal to 3 Aegis, add +2 Momentum, then bank 1 Ember." + }, + { + id: "magus", + name: "Magus Rewrite", + rarity: "mythic", + axis: "Mind", + text: "Replay the last boon you saw and draw 2 cards." + } ]; -// Storage keys for localStorage. These can be namespaced to avoid -// colliding with other apps on the same domain. -const PROFILE_KEY = "halo_profile"; -const HISTORY_KEY = "halo_history"; +function notify(message) { + const el = document.getElementById("notice"); + if (!el) return; + if (!message) { + el.textContent = ""; + el.classList.remove("show"); + return; + } + el.textContent = message; + el.classList.add("show"); +} -// Utility: Roll a die with a given number of sides. -function roll(sides) { - return Math.floor(Math.random() * sides); +function hashSeed(str) { + let hash = 0; + for (let i = 0; i < str.length; i++) { + hash = (hash << 5) - hash + str.charCodeAt(i); + hash |= 0; + } + return Math.abs(hash) + 1; } -// Create a new reading based off the defined lists. A reading bundles -// four random selections along with the user's question and a timestamp. -function createReading(question) { - const axis = AXES[roll(AXES.length)]; - const vector = VECTORS[roll(VECTORS.length)]; - const timeline = TIMELINES[roll(TIMELINES.length)]; - const archetype = ARCHETYPES[roll(ARCHETYPES.length)]; +function random(state) { + const raw = Math.sin(state.seedHash + state.cursor) * 10000; + state.cursor += 1; + return raw - Math.floor(raw); +} + +function pick(list, state) { + const idx = Math.floor(random(state) * list.length); + return list[idx]; +} + +function shuffle(list, state) { + const arr = [...list]; + for (let i = arr.length - 1; i > 0; i--) { + const j = Math.floor(random(state) * (i + 1)); + [arr[i], arr[j]] = [arr[j], arr[i]]; + } + return arr; +} + +function generateSeed() { + const chars = "ABCDEFGHJKMNPQRSTUVWXYZ23456789"; + let out = "HALO-"; + for (let i = 0; i < 6; i++) out += chars[Math.floor(Math.random() * chars.length)]; + return out; +} + +function starterCollection() { + const inventory = {}; + CARD_POOL.forEach((c) => { + if (c.rarity === "common") inventory[c.id] = 2; + if (c.rarity === "rare") inventory[c.id] = 1; + }); + return inventory; +} + +function baseState(overrides = {}) { + const seed = overrides.seed || generateSeed(); + const seedHash = hashSeed(seed); return { - axis, - vector, - timeline, - archetype, - question: question || "", - timestamp: new Date().toISOString() + player: overrides.player || "", + quest: overrides.quest || "", + mode: overrides.mode || "solo", + difficulty: overrides.difficulty || "standard", + seed, + seedHash, + cursor: 1, + currencies: { credits: 800, embers: 160, shards: 0 }, + pity: 0, + vip: false, + collection: starterCollection(), + deck: ["rush", "rush", "ward", "spark", "mirror", "gate", "star", "tower"], + run: null, + online: { + enabled: false, + url: overrides.url || ONLINE_DEFAULT, + room: "", + status: "offline", + peers: [], + log: [] + }, + gachaLog: [], + log: [] }; } -// Load the saved profile from localStorage and populate the form fields. -function loadProfile() { +function clamp(num, min, max) { + return Math.max(min, Math.min(max, num)); +} + +function saveState(state) { try { - const raw = localStorage.getItem(PROFILE_KEY); - if (!raw) return; - const profile = JSON.parse(raw); - const nameInput = document.getElementById("profile-name"); - const sunInput = document.getElementById("profile-sun"); - const moonInput = document.getElementById("profile-moon"); - const risingInput = document.getElementById("profile-rising"); - if (nameInput) nameInput.value = profile.name || ""; - if (sunInput) sunInput.value = profile.sun || ""; - if (moonInput) moonInput.value = profile.moon || ""; - if (risingInput) risingInput.value = profile.rising || ""; + localStorage.setItem(STORAGE_KEY, JSON.stringify(state)); + memoryFallback = state; } catch (err) { - console.warn("Failed to load profile", err); + memoryFallback = state; + if (!warnedStorage) { + console.warn("Local storage unavailable; keeping state in memory only", err); + notify("Local storage is blocked. Progress will reset if you close this tab."); + warnedStorage = true; + } } } -// Save the profile to localStorage. This persists only on the client. -function saveProfile() { - const name = document.getElementById("profile-name").value.trim(); - const sun = document.getElementById("profile-sun").value.trim(); - const moon = document.getElementById("profile-moon").value.trim(); - const rising = document.getElementById("profile-rising").value.trim(); - const profile = { name, sun, moon, rising }; - localStorage.setItem(PROFILE_KEY, JSON.stringify(profile)); -} - -// Load reading history from storage. Returns an array (possibly empty). -function loadHistory() { +function loadState() { try { - const raw = localStorage.getItem(HISTORY_KEY); - return raw ? JSON.parse(raw) : []; + const raw = localStorage.getItem(STORAGE_KEY); + if (raw) return JSON.parse(raw); } catch (err) { - console.warn("Failed to load history", err); - return []; + if (!warnedStorage) { + console.warn("Failed to load state", err); + notify("Cannot access local storage. Using in-memory state for this session."); + warnedStorage = true; + } + if (memoryFallback) return memoryFallback; } + return memoryFallback; } -// Save reading history back to storage. -function saveHistory(history) { - localStorage.setItem(HISTORY_KEY, JSON.stringify(history)); +function addLog(state, entry) { + state.log.push({ ...entry, timestamp: Date.now() }); + state.log = state.log.slice(-80); } -// Render the current reading into the DOM. If no reading is provided, -// clears the current display. -function renderCurrent(reading) { - const container = document.getElementById("current-reading"); - if (!container) return; - if (!reading) { - container.innerHTML = ""; - return; +function rarityRoll(state) { + const bonus = state.vip ? 5 : 0; + const roll = random(state) * (100 + bonus); + const mythicCut = RARITY_WEIGHTS.mythic + bonus; + if (roll >= 100 - mythicCut) return "mythic"; + if (roll >= 100 - RARITY_WEIGHTS.rare) return "rare"; + return "common"; +} + +function randomCardByRarity(rarity, state) { + const pool = CARD_POOL.filter((c) => c.rarity === rarity); + return pick(pool, state); +} + +function addToCollection(state, cardId) { + state.collection[cardId] = (state.collection[cardId] || 0) + 1; +} + +function pullPack(state, packKey) { + const pack = PACKS[packKey]; + if (!pack) return { results: [], reason: "Missing pack" }; + const cost = pack.cost; + if (cost.credits && state.currencies.credits < cost.credits) + return { results: [], reason: "Not enough credits" }; + if (cost.embers && state.currencies.embers < cost.embers) + return { results: [], reason: "Not enough embers" }; + + if (cost.credits) state.currencies.credits -= cost.credits; + if (cost.embers) state.currencies.embers -= cost.embers; + + const results = []; + for (let i = 0; i < pack.size; i++) { + let rarity = rarityRoll(state); + if (state.pity >= 8) rarity = "rare"; + if (pack.bonusRare && i === pack.size - 1) rarity = rarity === "common" ? "rare" : rarity; + const card = randomCardByRarity(rarity, state); + addToCollection(state, card.id); + results.push(card); + state.pity = rarity === "rare" || rarity === "mythic" ? 0 : state.pity + 1; } - // Format the timestamp into a human‑readable string. - const ts = new Date(reading.timestamp); - const tsStr = ts.toLocaleString(); - container.innerHTML = ` -
-

When: ${tsStr}

-

Axis: ${reading.axis.name}

-

Vector: ${reading.vector.name}

-

Timeline: ${reading.timeline.name}

-

Archetype: ${reading.archetype.name}

- ${reading.question ? `

Q: ${reading.question}

` : ""} -
- `; -} - -// Render the history table. Each entry shows the timestamp and top‑level -// categories. You could expand this to include question or notes. -function renderHistory(history) { - const container = document.getElementById("history"); - if (!container) return; - if (!history || history.length === 0) { - container.innerHTML = "

No past readings yet.

"; - return; + + state.gachaLog.push({ + pack: pack.name, + results, + timestamp: Date.now() + }); + + return { results }; +} + +function ensureDeckLegal(state) { + const cleaned = state.deck.filter((id) => state.collection[id]); + if (!cleaned.length) cleaned.push("rush", "rush", "ward", "spark"); + state.deck = cleaned.slice(0, 12); +} + +function drawCards(run, state, count = 1) { + for (let i = 0; i < count; i++) { + if (!run.drawPile.length) { + run.drawPile = shuffle(run.discard, state); + run.discard = []; + } + if (!run.drawPile.length) break; + run.hand.push(run.drawPile.shift()); } - // Build a simple HTML table. - let html = ""; - history.forEach((r) => { - const ts = new Date(r.timestamp).toLocaleString(); - html += ``; +} + +function nextEncounter(state) { + const encounter = { + axis: pick(AXES, state), + vector: pick(VECTORS, state), + timeline: pick(TIMELINES, state), + boon: random(state) > 0.55, + threat: random(state) > 0.5, + situation: pick( + [ + "A rival faction challenges your route", + "Two timelines overlap; pick one to stabilize", + "An ally pings you from deeper layers", + "A cache of relics hums with risk", + "A distorted mirror tries to rewrite you", + "A sealed gate leaks starlight", + "A phantom deal returns for payment" + ], + state + ) + }; + return encounter; +} + +function baseRun(state) { + const run = { + status: "running", + depth: 0, + momentum: state.difficulty === "chill" ? 3 : 2, + aegis: state.difficulty === "brutal" ? 1 : 2, + doom: state.difficulty === "brutal" ? 1 : 0, + drawPile: shuffle(state.deck, state), + discard: [], + hand: [], + current: null, + lastBoon: null + }; + drawCards(run, state, 3); + run.current = nextEncounter(state); + return run; +} + +function startRun(state) { + ensureDeckLegal(state); + state.run = baseRun(state); + addLog(state, { type: "run", title: "Run initialized", body: `Deck size ${state.deck.length}`, meta: state.seed }); +} + +function endRun(state, reason = "banked") { + if (!state.run) return; + const reward = Math.max(0, state.run.depth + state.run.momentum); + state.currencies.credits += reward * 20; + if (reason === "victory") state.currencies.embers += 10; + addLog(state, { + type: "cashout", + title: `Run ${reason}`, + body: `Depth ${state.run.depth}, Momentum ${state.run.momentum}, Aegis ${state.run.aegis}, Doom ${state.run.doom}. +${ + reward * 20 + } credits`, + meta: new Date().toLocaleTimeString() }); - html += "
WhenAxisVectorTimelineArchetype
${ts}${r.axis.name}${r.vector.name}${r.timeline.name}${r.archetype.name}
"; - container.innerHTML = html; -} - -// Main initialization: wire up event handlers and load any saved data. -function init() { - // Load profile and history on page load. - loadProfile(); - let history = loadHistory(); - renderHistory(history); - - // Profile save button. - const saveBtn = document.getElementById("save-profile"); - if (saveBtn) { - saveBtn.addEventListener("click", () => { - saveProfile(); - alert("Profile saved locally"); - }); + state.run = null; +} + +function doomTick(run) { + let inc = 0; + if (run.momentum < 0) inc += 1; + if (run.aegis <= 0) inc += 1; + return inc; +} + +function applyCard(state, run, card, encounter) { + switch (card.id) { + case "rush": + run.momentum = clamp(run.momentum + 1, -2, 8); + drawCards(run, state, 1); + break; + case "ward": + run.aegis = clamp(run.aegis + 1, 0, 5); + if (encounter.threat) run.doom = clamp(run.doom - 1, 0, 6); + break; + case "spark": + run.momentum = clamp(run.momentum + 1, -2, 8); + if (encounter.boon) run.lastBoon = encounter; + break; + case "mirror": + if (encounter.threat) { + encounter.threat = false; + encounter.boon = true; + run.lastBoon = encounter; + } else { + run.aegis = clamp(run.aegis + 1, 0, 5); + } + break; + case "gate": + run.doom = clamp(run.doom - 1, 0, 6); + state.currencies.credits += Math.max(0, run.momentum) * 30; + break; + case "star": + run.momentum = clamp(run.momentum + 2, -2, 8); + run.aegis = clamp(run.aegis - 1, 0, 5); + break; + case "tower": + run.hand = []; + run.drawPile = shuffle(run.drawPile.concat(run.discard), state); + run.discard = []; + drawCards(run, state, 3); + run.doom = clamp(run.doom, 0, 5); + break; + case "time": + run.doom = clamp(Math.min(run.doom, Math.max(0, run.depth - 1)), 0, 6); + run.aegis = clamp(run.aegis + 1, 0, 5); + break; + case "empress": + run.aegis = 3; + run.momentum = clamp(run.momentum + 2, -2, 8); + state.currencies.embers += 1; + break; + case "magus": + if (run.lastBoon) { + run.momentum = clamp(run.momentum + 1, -2, 8); + run.aegis = clamp(run.aegis + 1, 0, 5); + } + drawCards(run, state, 2); + break; + default: + break; } - // Roll button: generate a reading and update the UI and history. - const rollBtn = document.getElementById("roll-btn"); - if (rollBtn) { - rollBtn.addEventListener("click", () => { - const question = document.getElementById("question").value.trim(); - const reading = createReading(question); - // Unshift reading (add to beginning) to show latest first. - history.unshift(reading); - saveHistory(history); - renderCurrent(reading); - renderHistory(history); +} + +function playCard(state, cardId) { + if (!state.run || state.run.status !== "running") return; + const idx = state.run.hand.indexOf(cardId); + if (idx === -1) return; + const [cardRef] = state.run.hand.splice(idx, 1); + const card = CARD_POOL.find((c) => c.id === cardRef); + const encounter = state.run.current; + applyCard(state, state.run, card, encounter); + state.run.discard.push(cardRef); +} + +function resolveBeat(state) { + if (!state.run || state.run.status !== "running") return; + const run = state.run; + run.depth += 1; + const encounter = run.current; + + // Apply base tick + run.doom = clamp(run.doom + doomTick(run), 0, 6); + + const title = `${encounter.axis.name} × ${encounter.vector.name} (${encounter.timeline.name})`; + const lines = [encounter.situation]; + if (encounter.boon) lines.push("✨ Boon in play"); + if (encounter.threat) lines.push("⚠️ Threat in play"); + + addLog(state, { + type: "turn", + title: `Depth ${run.depth} • Momentum ${run.momentum}`, + body: lines.join(" • "), + meta: new Date().toLocaleTimeString() + }); + + if (run.doom >= 6 || run.aegis <= 0) { + run.status = "crashed"; + addLog(state, { + type: "crash", + title: "Run collapsed", + body: `Doom ${run.doom}, Aegis ${run.aegis}. Bank or reset.`, + meta: "Labyrinth spits you out" }); + pushOnline(state, { kind: "crash", depth: run.depth, doom: run.doom }); + return; } + + drawCards(run, state, 1); + run.current = nextEncounter(state); + pushOnline(state, { kind: "sync", depth: run.depth, momentum: run.momentum, doom: run.doom }); +} + +function cashOut(state) { + endRun(state, "banked"); } -// === Supporter and Co‑op Features === +function resetRun(state) { + state.run = null; + state.seed = generateSeed(); + state.seedHash = hashSeed(state.seed); + state.cursor = 1; +} + +function toggleVip(state) { + if (state.vip) return; + const cost = 120; + if (state.currencies.embers < cost) return notify("Need more embers for VIP Blessing"); + state.currencies.embers -= cost; + state.vip = true; + addLog(state, { type: "vip", title: "VIP Blessing unlocked", body: "Rarity boosts active", meta: "Paid feature" }); +} -// Supporter functionality: allow users to tip via Ko‑Fi and unlock a badge. -function setupSupport() { - const supportButton = document.getElementById("support-button"); - const supportBadge = document.getElementById("support-badge"); - if (!supportButton || !supportBadge) { +// Online sync +function connectOnline(state) { + if (!state.online.enabled) { + disconnectOnline(state); return; } - // Display supporter badge if previously activated. - const isSupporter = localStorage.getItem("halo_supporter") === "true"; - if (isSupporter) { - supportBadge.style.display = "inline-flex"; + if (socket && socket.readyState === WebSocket.OPEN) return; + try { + socket = new WebSocket(state.online.url || ONLINE_DEFAULT); + } catch (err) { + notify("Failed to start socket. Check relay URL."); + state.online.status = "offline"; + return; } - supportButton.addEventListener("click", () => { - // Open Ko‑Fi in a new tab. - window.open("https://ko-fi.com/oneinfinity", "_blank", "noopener"); - // Ask the user if they actually supported. If yes, set supporter flag and show badge. - const opted = confirm( - "Thank you for considering support! If you just tipped on Ko‑Fi, click OK to enable supporter mode." + socket.addEventListener("open", () => { + state.online.status = "connected"; + socket.send( + JSON.stringify({ type: "join", room: state.online.room || state.seed, player: state.player || "anon" }) ); - if (opted) { - localStorage.setItem("halo_supporter", "true"); - supportBadge.style.display = "inline-flex"; + render(state); + }); + socket.addEventListener("message", (event) => { + const data = JSON.parse(event.data); + if (data.type === "welcome") { + state.online.status = "connected"; + state.online.log.push({ meta: "system", message: `Joined ${data.room} with ${data.peers} peers` }); } + if (data.type === "system") state.online.log.push({ meta: "system", message: data.message }); + if (data.type === "chat") state.online.log.push({ meta: data.from, message: data.message }); + if (data.type === "sync") state.online.log.push({ meta: data.from, message: `Depth ${data.payload.depth}` }); + state.online.log = state.online.log.slice(-30); + render(state); + }); + socket.addEventListener("close", () => { + state.online.status = "offline"; + render(state); + }); + socket.addEventListener("error", () => { + notify("Relay connection failed"); + state.online.status = "offline"; + render(state); }); } -// Co‑op functionality: generate shareable session codes and handle incoming sessions. -function setupCoop() { - const startBtn = document.getElementById("start-coop"); - const copyBtn = document.getElementById("copy-coop"); - const linkInput = document.getElementById("coop-link"); - if (!startBtn || !copyBtn || !linkInput) { +function disconnectOnline(state) { + if (socket) socket.close(); + state.online.status = "offline"; +} + +function pushOnline(state, payload) { + if (!state.online.enabled || !socket || socket.readyState !== WebSocket.OPEN) return; + socket.send(JSON.stringify({ type: "sync", payload })); +} + +// Rendering helpers +function renderProfile(state) { + document.getElementById("player-name").value = state.player; + document.getElementById("player-quest").value = state.quest; + document.getElementById("mode").value = state.mode; + document.getElementById("difficulty").value = state.difficulty; + document.getElementById("seed").value = state.seed; + document.getElementById("online-url").value = state.online.url; + document.getElementById("online-room").value = state.online.room || state.seed; + document.getElementById("online-enabled").checked = state.online.enabled; + document.getElementById("seed-display").textContent = `Seed: ${state.seed}`; +} + +function renderEconomy(state) { + document.getElementById("stat-credits").textContent = state.currencies.credits; + document.getElementById("stat-embers").textContent = state.currencies.embers; + document.getElementById("stat-shards").textContent = state.currencies.shards; + document.getElementById("vip-status").textContent = state.vip ? "VIP active" : "Standard"; +} + +function renderCollection(state) { + const deckList = document.getElementById("deck-list"); + const collectionList = document.getElementById("collection-list"); + deckList.innerHTML = ""; + collectionList.innerHTML = ""; + + state.deck.forEach((id, idx) => { + const card = CARD_POOL.find((c) => c.id === id); + const el = document.createElement("div"); + el.className = "pill-row card-pill"; + el.innerHTML = `${card.name}${card.rarity}`; + deckList.appendChild(el); + }); + + CARD_POOL.forEach((card) => { + const owned = state.collection[card.id] || 0; + const el = document.createElement("div"); + el.className = "pill-row card-pill"; + el.innerHTML = `
${card.name} ${card.rarity} ${card.axis}
x${owned}
`; + collectionList.appendChild(el); + }); + + deckList.querySelectorAll("button[data-remove]").forEach((btn) => { + btn.addEventListener("click", (e) => { + const idx = parseInt(e.target.getAttribute("data-remove"), 10); + state.deck.splice(idx, 1); + saveState(state); + render(state); + }); + }); + + collectionList.querySelectorAll("button[data-add]").forEach((btn) => { + btn.addEventListener("click", (e) => { + const id = e.target.getAttribute("data-add"); + if ((state.collection[id] || 0) <= state.deck.filter((c) => c === id).length) return; + state.deck.push(id); + ensureDeckLegal(state); + saveState(state); + render(state); + }); + }); +} + +function renderRun(state) { + const run = state.run; + document.getElementById("stat-momentum").textContent = run ? run.momentum : "–"; + document.getElementById("stat-aegis").textContent = run ? run.aegis : "–"; + document.getElementById("stat-depth").textContent = run ? run.depth : "–"; + document.getElementById("stat-doom").textContent = run ? run.doom : "–"; + + const current = document.getElementById("current-event"); + current.innerHTML = ""; + if (!run) { + current.innerHTML = '
Start a run to see encounters.
'; + } else { + const enc = run.current; + const card = document.createElement("div"); + card.className = "log-entry"; + card.innerHTML = `

${enc.axis.name} × ${enc.vector.name} (${enc.timeline.name})

${enc.situation}

${ + enc.boon ? "✨ Boon available" : "" + } ${enc.threat ? "⚠️ Threat active" : ""}`; + current.appendChild(card); + } + + const hand = document.getElementById("hand"); + hand.innerHTML = ""; + if (run) { + run.hand.forEach((cardId) => { + const card = CARD_POOL.find((c) => c.id === cardId); + const el = document.createElement("button"); + el.className = "card-btn"; + el.textContent = `${card.name} (${card.rarity})`; + el.addEventListener("click", () => { + playCard(state, cardId); + render(state); + }); + hand.appendChild(el); + }); + } + + const logBox = document.getElementById("run-log"); + logBox.innerHTML = ""; + state.log + .slice(-14) + .reverse() + .forEach((entry) => { + const el = document.createElement("div"); + el.className = "log-entry"; + el.innerHTML = `

${entry.title}

${entry.body}

${entry.meta}`; + logBox.appendChild(el); + }); + + document.getElementById("play-turn").disabled = !run; + document.getElementById("cash-out").disabled = !run; +} + +function renderGacha(state) { + const results = document.getElementById("gacha-results"); + const last = state.gachaLog[state.gachaLog.length - 1]; + if (!last) { + results.innerHTML = '
Open a pack to see pulls.
'; return; } - // Create a co‑op session link with encoded payload when the user clicks the button. - startBtn.addEventListener("click", () => { - // Build a simple payload. You could include the current reading seed or - // other game state here. Using Date.now() as a nonce ensures each - // session link is unique. You can later decode this and recreate - // identical outcomes if your game is deterministic. - const payload = { - version: 1, - timestamp: Date.now(), - seed: Math.floor(Math.random() * 1e9) - }; - const json = JSON.stringify(payload); - const encoded = btoa(encodeURIComponent(json)); - const url = new URL(window.location.href); - url.searchParams.set("coop", encoded); - linkInput.value = url.toString(); - copyBtn.style.display = "inline-block"; + results.innerHTML = `

${last.pack} yielded:

`; + last.results.forEach((card) => { + const el = document.createElement("div"); + el.className = "log-entry"; + el.innerHTML = `

${card.name}

${card.text}

${card.rarity} • ${card.axis}`; + results.appendChild(el); }); - // Copy the session link to the clipboard when requested. - copyBtn.addEventListener("click", () => { - if (!linkInput.value) return; - navigator.clipboard - .writeText(linkInput.value) - .then(() => { - copyBtn.textContent = "Copied!"; - setTimeout(() => { - copyBtn.textContent = "Copy Link"; - }, 1500); - }) - .catch(() => { - alert("Copy failed. You can copy the link manually."); - }); +} + +function renderOnline(state) { + const status = document.getElementById("online-status"); + status.textContent = state.online.status; + const log = document.getElementById("online-log"); + log.innerHTML = ""; + state.online.log + .slice(-8) + .reverse() + .forEach((entry) => { + const el = document.createElement("div"); + el.className = "log-entry"; + el.innerHTML = `

${entry.meta}

${entry.message}

`; + log.appendChild(el); + }); +} + +function render(state) { + renderProfile(state); + renderEconomy(state); + renderCollection(state); + renderRun(state); + renderGacha(state); + renderOnline(state); +} + +function bootstrap() { + let state = loadState() || baseState(); + render(state); + + document.getElementById("start-run").addEventListener("click", () => { + state.player = document.getElementById("player-name").value.trim(); + state.quest = document.getElementById("player-quest").value.trim(); + state.mode = document.getElementById("mode").value; + state.difficulty = document.getElementById("difficulty").value; + state.seed = document.getElementById("seed").value.trim() || generateSeed(); + state.seedHash = hashSeed(state.seed); + state.cursor = 1; + startRun(state); + saveState(state); + render(state); + }); + + document.getElementById("reset-run").addEventListener("click", () => { + if (!confirm("Reset the current run?")) return; + resetRun(state); + saveState(state); + render(state); + }); + + document.getElementById("play-turn").addEventListener("click", () => { + resolveBeat(state); + saveState(state); + render(state); + }); + + document.getElementById("cash-out").addEventListener("click", () => { + cashOut(state); + saveState(state); + render(state); + }); + + document.getElementById("pull-starter").addEventListener("click", () => { + const res = pullPack(state, "starter"); + if (!res.results.length && res.reason) return notify(res.reason); + saveState(state); + render(state); + }); + + document.getElementById("pull-radiant").addEventListener("click", () => { + const res = pullPack(state, "radiant"); + if (!res.results.length && res.reason) return notify(res.reason); + saveState(state); + render(state); + }); + + document.getElementById("buy-embers").addEventListener("click", () => { + state.currencies.embers += 300; + addLog(state, { type: "purchase", title: "Simulated purchase", body: "+300 Embers", meta: "Test harness" }); + saveState(state); + render(state); + }); + + document.getElementById("vip-upgrade").addEventListener("click", () => { + toggleVip(state); + saveState(state); + render(state); + }); + + document.getElementById("online-enabled").addEventListener("change", (e) => { + state.online.enabled = e.target.checked; + state.online.url = document.getElementById("online-url").value.trim() || ONLINE_DEFAULT; + state.online.room = document.getElementById("online-room").value.trim() || state.seed; + saveState(state); + connectOnline(state); + render(state); + }); + + document.getElementById("online-url").addEventListener("change", (e) => { + state.online.url = e.target.value.trim(); + saveState(state); + }); + + document.getElementById("online-room").addEventListener("change", (e) => { + state.online.room = e.target.value.trim(); + saveState(state); + }); + + document.getElementById("send-chat").addEventListener("click", () => { + const text = document.getElementById("chat-text").value.trim(); + if (!text || !socket || socket.readyState !== WebSocket.OPEN) return; + socket.send(JSON.stringify({ type: "chat", message: text })); + document.getElementById("chat-text").value = ""; }); - // Check if the current URL contains a co‑op payload. If so, reconstruct the session. - const currentUrl = new URL(window.location.href); - const param = currentUrl.searchParams.get("coop"); - if (param) { - try { - const json = decodeURIComponent(atob(param)); - const data = JSON.parse(json); - // Notify the user they've joined a co‑op session. In a real game, you - // could apply this seed and timeline to synchronize outcomes. - console.log("Loaded co‑op session:", data); - alert( - "You have joined a shared Labyrinth session! Enjoy this synchronized journey." - ); - } catch (err) { - console.error("Failed to parse co‑op session data", err); - } - } } -// When the DOM is ready, wire everything up. We call init() to load -// profile/history and attach the Meta‑Oracle roll handler, then set up -// supporter/coop after that. We deliberately initialize meta logic -// first so that any co‑op code can leverage the state if needed. -document.addEventListener("DOMContentLoaded", () => { - init(); - setupSupport(); - setupCoop(); -}); \ No newline at end of file +document.addEventListener("DOMContentLoaded", bootstrap); diff --git a/halo_lambda/index.js b/halo_lambda/index.js index 9ed17f1..4ec7786 100644 --- a/halo_lambda/index.js +++ b/halo_lambda/index.js @@ -16,9 +16,22 @@ const { PutCommand, } = require("@aws-sdk/lib-dynamodb"); -// Create low‑level and Document clients. The region and credentials -// are resolved automatically from the Lambda execution environment. -const ddbClient = new DynamoDBClient({}); +// Create low‑level and Document clients. The region defaults to a +// real AWS region even if someone left a placeholder like +// "MY_AWS_REGION" in their environment variables. +const resolvedRegion = (() => { + const candidate = + process.env.AWS_REGION || process.env.AWS_DEFAULT_REGION || ""; + + // Treat obvious placeholders or malformed regions as invalid and fall back. + // A valid AWS region looks like "us-east-1", "eu-west-3", etc. + const isValidRegion = /^[a-z]{2}-[a-z]+-\d+$/.test(candidate); + if (candidate && candidate !== "MY_AWS_REGION" && isValidRegion) return candidate; + + return "us-east-1"; // safe fallback so deployments don't fail on placeholders +})(); + +const ddbClient = new DynamoDBClient({ region: resolvedRegion }); const ddbDocClient = DynamoDBDocumentClient.from(ddbClient); exports.handler = async (event) => { diff --git a/index.html b/index.html index 412b4c8..ff1b208 100644 --- a/index.html +++ b/index.html @@ -3,263 +3,202 @@ - HALO Meta-Oracle – Local Prototype + + HALO Pocket Labyrinth Online -
-

HALO Meta-Oracle – Local Prototype

- -
-

1. Profile

-

Optional but helpful—used for tags and flavor text.

-
-
- - -
-
- - -
-
- - +
+

HALO Pocket Labyrinth • Online Roguelike

+
🎴 Card roguelike • 📡 Online relay • 📱 Mobile-first
+
+ +
+ +
+
+

Profile & Seed

+

Define your pilot, quest, mode, and seed. Seeds stay deterministic so squads can mirror the same Labyrinth.

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

+
+ +
+

Economy & Paid features

+

Earn credits by delving, spend Embers on Radiant pulls or the VIP Blessing (rarity boost). Purchases are simulated for testing.

+
+
Credits
+
Embers
+
Shards
+
Status
+
+
+ + +
+
+ +
+

Gacha & Collection

+

Open packs to expand your deck. Pity kicks in after 8 non-rare pulls. Radiant pulls guarantee at least one rare.

+
+ + +
+
+
+ +
+

Deck Builder

+

Tap Add to move owned cards into your Adventure Deck. Max 12 cards; duplicates limited by ownership.

+
+

Adventure Deck

+
+
+
+

Collection

+
+
+
+ +
+

Run (Roguelike beats)

+

Draw an encounter, play cards from your hand, then resolve the beat. Depth and Doom define survival; Momentum fuels speed; Aegis is your shield.

+
+
Momentum
+
Aegis
+
Depth
+
Doom
+
+
+ + +
+
+

Hand

+
+

Run Log

+
+
+ +
+

Online relay (optional)

+

Run your own websocket relay (`npm run server`) or point to a shared instance. Rooms are lightweight; share the seed/room to sync spread depth and chat.

+
+
+
+
+
+
Status: offline
+
+
+ + +
-
- - +
+

Relay log

+
- - Stored only in this browser using localStorage. -
- -
-

2. Ask HALO

- - - -
-
- -
-

3. Past Readings (this device)

-

- Local Spiritual Blockchain prototype – readings are chained with simple hashes. -

-
-
- - -
-

4. Co‑op Session

-

Share your Labyrinth session with a friend.

- - - -
+
+ +
+

Phone-first

+

Save `dist/index.html` to your device or add this page to your home screen. All state is on-device; relay is opt-in.

+
+ Deterministic seed + Offline-friendly + Relay-ready +
+
+
- -
-

5. Support the Oracle

-

Optional – helps keep the project going.

- - -
-
+
Built on the HALO oracle. You are the Root User.
diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..618866d --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1429 @@ +{ + "name": "halo-pocket-labyrinth", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "halo-pocket-labyrinth", + "version": "0.1.0", + "dependencies": { + "@aws-sdk/client-dynamodb": "^3.948.0", + "@aws-sdk/lib-dynamodb": "^3.948.0", + "ws": "^8.18.0" + } + }, + "node_modules/@aws-crypto/sha256-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", + "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-js": "^5.2.0", + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-js": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", + "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-crypto/supports-web-crypto": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", + "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/util": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", + "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.222.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-dynamodb": { + "version": "3.948.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-dynamodb/-/client-dynamodb-3.948.0.tgz", + "integrity": "sha512-y2gG/rXdlbGBKozaVasObBnVHfLjdh6dZfszQE+0alSA0R/3ney51zWlJWsOhILdAHTcD5gHDyukLDB56HaGQA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.947.0", + "@aws-sdk/credential-provider-node": "3.948.0", + "@aws-sdk/dynamodb-codec": "3.947.0", + "@aws-sdk/middleware-endpoint-discovery": "3.936.0", + "@aws-sdk/middleware-host-header": "3.936.0", + "@aws-sdk/middleware-logger": "3.936.0", + "@aws-sdk/middleware-recursion-detection": "3.948.0", + "@aws-sdk/middleware-user-agent": "3.947.0", + "@aws-sdk/region-config-resolver": "3.936.0", + "@aws-sdk/types": "3.936.0", + "@aws-sdk/util-endpoints": "3.936.0", + "@aws-sdk/util-user-agent-browser": "3.936.0", + "@aws-sdk/util-user-agent-node": "3.947.0", + "@smithy/config-resolver": "^4.4.3", + "@smithy/core": "^3.18.7", + "@smithy/fetch-http-handler": "^5.3.6", + "@smithy/hash-node": "^4.2.5", + "@smithy/invalid-dependency": "^4.2.5", + "@smithy/middleware-content-length": "^4.2.5", + "@smithy/middleware-endpoint": "^4.3.14", + "@smithy/middleware-retry": "^4.4.14", + "@smithy/middleware-serde": "^4.2.6", + "@smithy/middleware-stack": "^4.2.5", + "@smithy/node-config-provider": "^4.3.5", + "@smithy/node-http-handler": "^4.4.5", + "@smithy/protocol-http": "^5.3.5", + "@smithy/smithy-client": "^4.9.10", + "@smithy/types": "^4.9.0", + "@smithy/url-parser": "^4.2.5", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.13", + "@smithy/util-defaults-mode-node": "^4.2.16", + "@smithy/util-endpoints": "^3.2.5", + "@smithy/util-middleware": "^4.2.5", + "@smithy/util-retry": "^4.2.5", + "@smithy/util-utf8": "^4.2.0", + "@smithy/util-waiter": "^4.2.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sso": { + "version": "3.948.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.948.0.tgz", + "integrity": "sha512-iWjchXy8bIAVBUsKnbfKYXRwhLgRg3EqCQ5FTr3JbR+QR75rZm4ZOYXlvHGztVTmtAZ+PQVA1Y4zO7v7N87C0A==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.947.0", + "@aws-sdk/middleware-host-header": "3.936.0", + "@aws-sdk/middleware-logger": "3.936.0", + "@aws-sdk/middleware-recursion-detection": "3.948.0", + "@aws-sdk/middleware-user-agent": "3.947.0", + "@aws-sdk/region-config-resolver": "3.936.0", + "@aws-sdk/types": "3.936.0", + "@aws-sdk/util-endpoints": "3.936.0", + "@aws-sdk/util-user-agent-browser": "3.936.0", + "@aws-sdk/util-user-agent-node": "3.947.0", + "@smithy/config-resolver": "^4.4.3", + "@smithy/core": "^3.18.7", + "@smithy/fetch-http-handler": "^5.3.6", + "@smithy/hash-node": "^4.2.5", + "@smithy/invalid-dependency": "^4.2.5", + "@smithy/middleware-content-length": "^4.2.5", + "@smithy/middleware-endpoint": "^4.3.14", + "@smithy/middleware-retry": "^4.4.14", + "@smithy/middleware-serde": "^4.2.6", + "@smithy/middleware-stack": "^4.2.5", + "@smithy/node-config-provider": "^4.3.5", + "@smithy/node-http-handler": "^4.4.5", + "@smithy/protocol-http": "^5.3.5", + "@smithy/smithy-client": "^4.9.10", + "@smithy/types": "^4.9.0", + "@smithy/url-parser": "^4.2.5", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.13", + "@smithy/util-defaults-mode-node": "^4.2.16", + "@smithy/util-endpoints": "^3.2.5", + "@smithy/util-middleware": "^4.2.5", + "@smithy/util-retry": "^4.2.5", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/core": { + "version": "3.947.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.947.0.tgz", + "integrity": "sha512-Khq4zHhuAkvCFuFbgcy3GrZTzfSX7ZIjIcW1zRDxXRLZKRtuhnZdonqTUfaWi5K42/4OmxkYNpsO7X7trQOeHw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.936.0", + "@aws-sdk/xml-builder": "3.930.0", + "@smithy/core": "^3.18.7", + "@smithy/node-config-provider": "^4.3.5", + "@smithy/property-provider": "^4.2.5", + "@smithy/protocol-http": "^5.3.5", + "@smithy/signature-v4": "^5.3.5", + "@smithy/smithy-client": "^4.9.10", + "@smithy/types": "^4.9.0", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-middleware": "^4.2.5", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-env": { + "version": "3.947.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.947.0.tgz", + "integrity": "sha512-VR2V6dRELmzwAsCpK4GqxUi6UW5WNhAXS9F9AzWi5jvijwJo3nH92YNJUP4quMpgFZxJHEWyXLWgPjh9u0zYOA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.947.0", + "@aws-sdk/types": "3.936.0", + "@smithy/property-provider": "^4.2.5", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-http": { + "version": "3.947.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.947.0.tgz", + "integrity": "sha512-inF09lh9SlHj63Vmr5d+LmwPXZc2IbK8lAruhOr3KLsZAIHEgHgGPXWDC2ukTEMzg0pkexQ6FOhXXad6klK4RA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.947.0", + "@aws-sdk/types": "3.936.0", + "@smithy/fetch-http-handler": "^5.3.6", + "@smithy/node-http-handler": "^4.4.5", + "@smithy/property-provider": "^4.2.5", + "@smithy/protocol-http": "^5.3.5", + "@smithy/smithy-client": "^4.9.10", + "@smithy/types": "^4.9.0", + "@smithy/util-stream": "^4.5.6", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.948.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.948.0.tgz", + "integrity": "sha512-Cl//Qh88e8HBL7yYkJNpF5eq76IO6rq8GsatKcfVBm7RFVxCqYEPSSBtkHdbtNwQdRQqAMXc6E/lEB/CZUDxnA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.947.0", + "@aws-sdk/credential-provider-env": "3.947.0", + "@aws-sdk/credential-provider-http": "3.947.0", + "@aws-sdk/credential-provider-login": "3.948.0", + "@aws-sdk/credential-provider-process": "3.947.0", + "@aws-sdk/credential-provider-sso": "3.948.0", + "@aws-sdk/credential-provider-web-identity": "3.948.0", + "@aws-sdk/nested-clients": "3.948.0", + "@aws-sdk/types": "3.936.0", + "@smithy/credential-provider-imds": "^4.2.5", + "@smithy/property-provider": "^4.2.5", + "@smithy/shared-ini-file-loader": "^4.4.0", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-login": { + "version": "3.948.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.948.0.tgz", + "integrity": "sha512-gcKO2b6eeTuZGp3Vvgr/9OxajMrD3W+FZ2FCyJox363ZgMoYJsyNid1vuZrEuAGkx0jvveLXfwiVS0UXyPkgtw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.947.0", + "@aws-sdk/nested-clients": "3.948.0", + "@aws-sdk/types": "3.936.0", + "@smithy/property-provider": "^4.2.5", + "@smithy/protocol-http": "^5.3.5", + "@smithy/shared-ini-file-loader": "^4.4.0", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-node": { + "version": "3.948.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.948.0.tgz", + "integrity": "sha512-ep5vRLnrRdcsP17Ef31sNN4g8Nqk/4JBydcUJuFRbGuyQtrZZrVT81UeH2xhz6d0BK6ejafDB9+ZpBjXuWT5/Q==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.947.0", + "@aws-sdk/credential-provider-http": "3.947.0", + "@aws-sdk/credential-provider-ini": "3.948.0", + "@aws-sdk/credential-provider-process": "3.947.0", + "@aws-sdk/credential-provider-sso": "3.948.0", + "@aws-sdk/credential-provider-web-identity": "3.948.0", + "@aws-sdk/types": "3.936.0", + "@smithy/credential-provider-imds": "^4.2.5", + "@smithy/property-provider": "^4.2.5", + "@smithy/shared-ini-file-loader": "^4.4.0", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-process": { + "version": "3.947.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.947.0.tgz", + "integrity": "sha512-WpanFbHe08SP1hAJNeDdBDVz9SGgMu/gc0XJ9u3uNpW99nKZjDpvPRAdW7WLA4K6essMjxWkguIGNOpij6Do2Q==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.947.0", + "@aws-sdk/types": "3.936.0", + "@smithy/property-provider": "^4.2.5", + "@smithy/shared-ini-file-loader": "^4.4.0", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.948.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.948.0.tgz", + "integrity": "sha512-gqLhX1L+zb/ZDnnYbILQqJ46j735StfWV5PbDjxRzBKS7GzsiYoaf6MyHseEopmWrez5zl5l6aWzig7UpzSeQQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/client-sso": "3.948.0", + "@aws-sdk/core": "3.947.0", + "@aws-sdk/token-providers": "3.948.0", + "@aws-sdk/types": "3.936.0", + "@smithy/property-provider": "^4.2.5", + "@smithy/shared-ini-file-loader": "^4.4.0", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.948.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.948.0.tgz", + "integrity": "sha512-MvYQlXVoJyfF3/SmnNzOVEtANRAiJIObEUYYyjTqKZTmcRIVVky0tPuG26XnB8LmTYgtESwJIZJj/Eyyc9WURQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.947.0", + "@aws-sdk/nested-clients": "3.948.0", + "@aws-sdk/types": "3.936.0", + "@smithy/property-provider": "^4.2.5", + "@smithy/shared-ini-file-loader": "^4.4.0", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/dynamodb-codec": { + "version": "3.947.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/dynamodb-codec/-/dynamodb-codec-3.947.0.tgz", + "integrity": "sha512-LSJQldMJs+UeZn8fFZW/vqL083Y/jEgttif4KJsTcTPSuauZCACzuouV//ZCEOc9YJKuh56iq9+CCXM+ZM0o9g==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.947.0", + "@smithy/core": "^3.18.7", + "@smithy/smithy-client": "^4.9.10", + "@smithy/types": "^4.9.0", + "@smithy/util-base64": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-dynamodb": "^3.947.0" + } + }, + "node_modules/@aws-sdk/endpoint-cache": { + "version": "3.893.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/endpoint-cache/-/endpoint-cache-3.893.0.tgz", + "integrity": "sha512-KSwTfyLZyNLszz5f/yoLC+LC+CRKpeJii/+zVAy7JUOQsKhSykiRUPYUx7o2Sdc4oJfqqUl26A/jSttKYnYtAA==", + "license": "Apache-2.0", + "dependencies": { + "mnemonist": "0.38.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/lib-dynamodb": { + "version": "3.948.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/lib-dynamodb/-/lib-dynamodb-3.948.0.tgz", + "integrity": "sha512-6J/cha8eJ/MDaSDdehV9xKpUrkWboeCoooPiAFvZUdKTWUXtP3CxLE7HYa/ToZMXuTonF2iLow4QaE4ik4fadQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.947.0", + "@aws-sdk/util-dynamodb": "3.948.0", + "@smithy/core": "^3.18.7", + "@smithy/smithy-client": "^4.9.10", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-dynamodb": "^3.948.0" + } + }, + "node_modules/@aws-sdk/middleware-endpoint-discovery": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-endpoint-discovery/-/middleware-endpoint-discovery-3.936.0.tgz", + "integrity": "sha512-wNJZ8PDw0eQK2x4z1q8JqiDvw9l9xd36EoklVT2CIBt8FnqGdrMGjAx93RRbH3G6Fmvwoe+D3VJXbWHBlhD0Bw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/endpoint-cache": "3.893.0", + "@aws-sdk/types": "3.936.0", + "@smithy/node-config-provider": "^4.3.5", + "@smithy/protocol-http": "^5.3.5", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-host-header": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.936.0.tgz", + "integrity": "sha512-tAaObaAnsP1XnLGndfkGWFuzrJYuk9W0b/nLvol66t8FZExIAf/WdkT2NNAWOYxljVs++oHnyHBCxIlaHrzSiw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.936.0", + "@smithy/protocol-http": "^5.3.5", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-logger": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.936.0.tgz", + "integrity": "sha512-aPSJ12d3a3Ea5nyEnLbijCaaYJT2QjQ9iW+zGh5QcZYXmOGWbKVyPSxmVOboZQG+c1M8t6d2O7tqrwzIq8L8qw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.936.0", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.948.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.948.0.tgz", + "integrity": "sha512-Qa8Zj+EAqA0VlAVvxpRnpBpIWJI9KUwaioY1vkeNVwXPlNaz9y9zCKVM9iU9OZ5HXpoUg6TnhATAHXHAE8+QsQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.936.0", + "@aws/lambda-invoke-store": "^0.2.2", + "@smithy/protocol-http": "^5.3.5", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.947.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.947.0.tgz", + "integrity": "sha512-7rpKV8YNgCP2R4F9RjWZFcD2R+SO/0R4VHIbY9iZJdH2MzzJ8ZG7h8dZ2m8QkQd1fjx4wrFJGGPJUTYXPV3baA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.947.0", + "@aws-sdk/types": "3.936.0", + "@aws-sdk/util-endpoints": "3.936.0", + "@smithy/core": "^3.18.7", + "@smithy/protocol-http": "^5.3.5", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients": { + "version": "3.948.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.948.0.tgz", + "integrity": "sha512-zcbJfBsB6h254o3NuoEkf0+UY1GpE9ioiQdENWv7odo69s8iaGBEQ4BDpsIMqcuiiUXw1uKIVNxCB1gUGYz8lw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.947.0", + "@aws-sdk/middleware-host-header": "3.936.0", + "@aws-sdk/middleware-logger": "3.936.0", + "@aws-sdk/middleware-recursion-detection": "3.948.0", + "@aws-sdk/middleware-user-agent": "3.947.0", + "@aws-sdk/region-config-resolver": "3.936.0", + "@aws-sdk/types": "3.936.0", + "@aws-sdk/util-endpoints": "3.936.0", + "@aws-sdk/util-user-agent-browser": "3.936.0", + "@aws-sdk/util-user-agent-node": "3.947.0", + "@smithy/config-resolver": "^4.4.3", + "@smithy/core": "^3.18.7", + "@smithy/fetch-http-handler": "^5.3.6", + "@smithy/hash-node": "^4.2.5", + "@smithy/invalid-dependency": "^4.2.5", + "@smithy/middleware-content-length": "^4.2.5", + "@smithy/middleware-endpoint": "^4.3.14", + "@smithy/middleware-retry": "^4.4.14", + "@smithy/middleware-serde": "^4.2.6", + "@smithy/middleware-stack": "^4.2.5", + "@smithy/node-config-provider": "^4.3.5", + "@smithy/node-http-handler": "^4.4.5", + "@smithy/protocol-http": "^5.3.5", + "@smithy/smithy-client": "^4.9.10", + "@smithy/types": "^4.9.0", + "@smithy/url-parser": "^4.2.5", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.13", + "@smithy/util-defaults-mode-node": "^4.2.16", + "@smithy/util-endpoints": "^3.2.5", + "@smithy/util-middleware": "^4.2.5", + "@smithy/util-retry": "^4.2.5", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/region-config-resolver": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.936.0.tgz", + "integrity": "sha512-wOKhzzWsshXGduxO4pqSiNyL9oUtk4BEvjWm9aaq6Hmfdoydq6v6t0rAGHWPjFwy9z2haovGRi3C8IxdMB4muw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.936.0", + "@smithy/config-resolver": "^4.4.3", + "@smithy/node-config-provider": "^4.3.5", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/token-providers": { + "version": "3.948.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.948.0.tgz", + "integrity": "sha512-V487/kM4Teq5dcr1t5K6eoUKuqlGr9FRWL3MIMukMERJXHZvio6kox60FZ/YtciRHRI75u14YUqm2Dzddcu3+A==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.947.0", + "@aws-sdk/nested-clients": "3.948.0", + "@aws-sdk/types": "3.936.0", + "@smithy/property-provider": "^4.2.5", + "@smithy/shared-ini-file-loader": "^4.4.0", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/types": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.936.0.tgz", + "integrity": "sha512-uz0/VlMd2pP5MepdrHizd+T+OKfyK4r3OA9JI+L/lPKg0YFQosdJNCKisr6o70E3dh8iMpFYxF1UN/4uZsyARg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-dynamodb": { + "version": "3.948.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-dynamodb/-/util-dynamodb-3.948.0.tgz", + "integrity": "sha512-Zd1krEvZociQikaoe1WkQGq9Nn0E50U0IIoXECc6D0MI32ab8gu3iYrh8sLjz9IfFws7SWuVkbUQM1kjsbMA2A==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-dynamodb": "^3.948.0" + } + }, + "node_modules/@aws-sdk/util-endpoints": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.936.0.tgz", + "integrity": "sha512-0Zx3Ntdpu+z9Wlm7JKUBOzS9EunwKAb4KdGUQQxDqh5Lc3ta5uBoub+FgmVuzwnmBu9U1Os8UuwVTH0Lgu+P5w==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.936.0", + "@smithy/types": "^4.9.0", + "@smithy/url-parser": "^4.2.5", + "@smithy/util-endpoints": "^3.2.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-locate-window": { + "version": "3.893.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.893.0.tgz", + "integrity": "sha512-T89pFfgat6c8nMmpI8eKjBcDcgJq36+m9oiXbcUzeU55MP9ZuGgBomGjGnHaEyF36jenW9gmg3NfZDm0AO2XPg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.936.0.tgz", + "integrity": "sha512-eZ/XF6NxMtu+iCma58GRNRxSq4lHo6zHQLOZRIeL/ghqYJirqHdenMOwrzPettj60KWlv827RVebP9oNVrwZbw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.936.0", + "@smithy/types": "^4.9.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.947.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.947.0.tgz", + "integrity": "sha512-+vhHoDrdbb+zerV4noQk1DHaUMNzWFWPpPYjVTwW2186k5BEJIecAMChYkghRrBVJ3KPWP1+JnZwOd72F3d4rQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/middleware-user-agent": "3.947.0", + "@aws-sdk/types": "3.936.0", + "@smithy/node-config-provider": "^4.3.5", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } + } + }, + "node_modules/@aws-sdk/xml-builder": { + "version": "3.930.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.930.0.tgz", + "integrity": "sha512-YIfkD17GocxdmlUVc3ia52QhcWuRIUJonbF8A2CYfcWNV3HzvAqpcPeC0bYUhkK+8e8YO1ARnLKZQE0TlwzorA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.9.0", + "fast-xml-parser": "5.2.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws/lambda-invoke-store": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.2.2.tgz", + "integrity": "sha512-C0NBLsIqzDIae8HFw9YIrIBsbc0xTiOtt7fAukGPnqQ/+zZNaq+4jhuccltK0QuWHBnNm/a6kLIRA6GFiM10eg==", + "license": "Apache-2.0", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/abort-controller": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.2.5.tgz", + "integrity": "sha512-j7HwVkBw68YW8UmFRcjZOmssE77Rvk0GWAIN1oFBhsaovQmZWYCIcGa9/pwRB0ExI8Sk9MWNALTjftjHZea7VA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/config-resolver": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.4.3.tgz", + "integrity": "sha512-ezHLe1tKLUxDJo2LHtDuEDyWXolw8WGOR92qb4bQdWq/zKenO5BvctZGrVJBK08zjezSk7bmbKFOXIVyChvDLw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.3.5", + "@smithy/types": "^4.9.0", + "@smithy/util-config-provider": "^4.2.0", + "@smithy/util-endpoints": "^3.2.5", + "@smithy/util-middleware": "^4.2.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/core": { + "version": "3.18.7", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.18.7.tgz", + "integrity": "sha512-axG9MvKhMWOhFbvf5y2DuyTxQueO0dkedY9QC3mAfndLosRI/9LJv8WaL0mw7ubNhsO4IuXX9/9dYGPFvHrqlw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/middleware-serde": "^4.2.6", + "@smithy/protocol-http": "^5.3.5", + "@smithy/types": "^4.9.0", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-middleware": "^4.2.5", + "@smithy/util-stream": "^4.5.6", + "@smithy/util-utf8": "^4.2.0", + "@smithy/uuid": "^1.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/credential-provider-imds": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.5.tgz", + "integrity": "sha512-BZwotjoZWn9+36nimwm/OLIcVe+KYRwzMjfhd4QT7QxPm9WY0HiOV8t/Wlh+HVUif0SBVV7ksq8//hPaBC/okQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.3.5", + "@smithy/property-provider": "^4.2.5", + "@smithy/types": "^4.9.0", + "@smithy/url-parser": "^4.2.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/fetch-http-handler": { + "version": "5.3.6", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.6.tgz", + "integrity": "sha512-3+RG3EA6BBJ/ofZUeTFJA7mHfSYrZtQIrDP9dI8Lf7X6Jbos2jptuLrAAteDiFVrmbEmLSuRG/bUKzfAXk7dhg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.3.5", + "@smithy/querystring-builder": "^4.2.5", + "@smithy/types": "^4.9.0", + "@smithy/util-base64": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/hash-node": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.5.tgz", + "integrity": "sha512-DpYX914YOfA3UDT9CN1BM787PcHfWRBB43fFGCYrZFUH0Jv+5t8yYl+Pd5PW4+QzoGEDvn5d5QIO4j2HyYZQSA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.9.0", + "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/invalid-dependency": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.5.tgz", + "integrity": "sha512-2L2erASEro1WC5nV+plwIMxrTXpvpfzl4e+Nre6vBVRR2HKeGGcvpJyyL3/PpiSg+cJG2KpTmZmq934Olb6e5A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/is-array-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.0.tgz", + "integrity": "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-content-length": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.5.tgz", + "integrity": "sha512-Y/RabVa5vbl5FuHYV2vUCwvh/dqzrEY/K2yWPSqvhFUwIY0atLqO4TienjBXakoy4zrKAMCZwg+YEqmH7jaN7A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.3.5", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-endpoint": { + "version": "4.3.14", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.3.14.tgz", + "integrity": "sha512-v0q4uTKgBM8dsqGjqsabZQyH85nFaTnFcgpWU1uydKFsdyyMzfvOkNum9G7VK+dOP01vUnoZxIeRiJ6uD0kjIg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.18.7", + "@smithy/middleware-serde": "^4.2.6", + "@smithy/node-config-provider": "^4.3.5", + "@smithy/shared-ini-file-loader": "^4.4.0", + "@smithy/types": "^4.9.0", + "@smithy/url-parser": "^4.2.5", + "@smithy/util-middleware": "^4.2.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-retry": { + "version": "4.4.14", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.14.tgz", + "integrity": "sha512-Z2DG8Ej7FyWG1UA+7HceINtSLzswUgs2np3sZX0YBBxCt+CXG4QUxv88ZDS3+2/1ldW7LqtSY1UO/6VQ1pND8Q==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.3.5", + "@smithy/protocol-http": "^5.3.5", + "@smithy/service-error-classification": "^4.2.5", + "@smithy/smithy-client": "^4.9.10", + "@smithy/types": "^4.9.0", + "@smithy/util-middleware": "^4.2.5", + "@smithy/util-retry": "^4.2.5", + "@smithy/uuid": "^1.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-serde": { + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.6.tgz", + "integrity": "sha512-VkLoE/z7e2g8pirwisLz8XJWedUSY8my/qrp81VmAdyrhi94T+riBfwP+AOEEFR9rFTSonC/5D2eWNmFabHyGQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.3.5", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-stack": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.5.tgz", + "integrity": "sha512-bYrutc+neOyWxtZdbB2USbQttZN0mXaOyYLIsaTbJhFsfpXyGWUxJpEuO1rJ8IIJm2qH4+xJT0mxUSsEDTYwdQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-config-provider": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.5.tgz", + "integrity": "sha512-UTurh1C4qkVCtqggI36DGbLB2Kv8UlcFdMXDcWMbqVY2uRg0XmT9Pb4Vj6oSQ34eizO1fvR0RnFV4Axw4IrrAg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^4.2.5", + "@smithy/shared-ini-file-loader": "^4.4.0", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-http-handler": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.4.5.tgz", + "integrity": "sha512-CMnzM9R2WqlqXQGtIlsHMEZfXKJVTIrqCNoSd/QpAyp+Dw0a1Vps13l6ma1fH8g7zSPNsA59B/kWgeylFuA/lw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/abort-controller": "^4.2.5", + "@smithy/protocol-http": "^5.3.5", + "@smithy/querystring-builder": "^4.2.5", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/property-provider": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.5.tgz", + "integrity": "sha512-8iLN1XSE1rl4MuxvQ+5OSk/Zb5El7NJZ1td6Tn+8dQQHIjp59Lwl6bd0+nzw6SKm2wSSriH2v/I9LPzUic7EOg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/protocol-http": { + "version": "5.3.5", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.5.tgz", + "integrity": "sha512-RlaL+sA0LNMp03bf7XPbFmT5gN+w3besXSWMkA8rcmxLSVfiEXElQi4O2IWwPfxzcHkxqrwBFMbngB8yx/RvaQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/querystring-builder": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.5.tgz", + "integrity": "sha512-y98otMI1saoajeik2kLfGyRp11e5U/iJYH/wLCh3aTV/XutbGT9nziKGkgCaMD1ghK7p6htHMm6b6scl9JRUWg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.9.0", + "@smithy/util-uri-escape": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/querystring-parser": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.5.tgz", + "integrity": "sha512-031WCTdPYgiQRYNPXznHXof2YM0GwL6SeaSyTH/P72M1Vz73TvCNH2Nq8Iu2IEPq9QP2yx0/nrw5YmSeAi/AjQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/service-error-classification": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.2.5.tgz", + "integrity": "sha512-8fEvK+WPE3wUAcDvqDQG1Vk3ANLR8Px979te96m84CbKAjBVf25rPYSzb4xU4hlTyho7VhOGnh5i62D/JVF0JQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.9.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/shared-ini-file-loader": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.4.0.tgz", + "integrity": "sha512-5WmZ5+kJgJDjwXXIzr1vDTG+RhF9wzSODQBfkrQ2VVkYALKGvZX1lgVSxEkgicSAFnFhPj5rudJV0zoinqS0bA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/signature-v4": { + "version": "5.3.5", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.5.tgz", + "integrity": "sha512-xSUfMu1FT7ccfSXkoLl/QRQBi2rOvi3tiBZU2Tdy3I6cgvZ6SEi9QNey+lqps/sJRnogIS+lq+B1gxxbra2a/w==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^4.2.0", + "@smithy/protocol-http": "^5.3.5", + "@smithy/types": "^4.9.0", + "@smithy/util-hex-encoding": "^4.2.0", + "@smithy/util-middleware": "^4.2.5", + "@smithy/util-uri-escape": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/smithy-client": { + "version": "4.9.10", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.9.10.tgz", + "integrity": "sha512-Jaoz4Jw1QYHc1EFww/E6gVtNjhoDU+gwRKqXP6C3LKYqqH2UQhP8tMP3+t/ePrhaze7fhLE8vS2q6vVxBANFTQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.18.7", + "@smithy/middleware-endpoint": "^4.3.14", + "@smithy/middleware-stack": "^4.2.5", + "@smithy/protocol-http": "^5.3.5", + "@smithy/types": "^4.9.0", + "@smithy/util-stream": "^4.5.6", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/types": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.9.0.tgz", + "integrity": "sha512-MvUbdnXDTwykR8cB1WZvNNwqoWVaTRA0RLlLmf/cIFNMM2cKWz01X4Ly6SMC4Kks30r8tT3Cty0jmeWfiuyHTA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/url-parser": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.5.tgz", + "integrity": "sha512-VaxMGsilqFnK1CeBX+LXnSuaMx4sTL/6znSZh2829txWieazdVxr54HmiyTsIbpOTLcf5nYpq9lpzmwRdxj6rQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/querystring-parser": "^4.2.5", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-base64": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.3.0.tgz", + "integrity": "sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-body-length-browser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.2.0.tgz", + "integrity": "sha512-Fkoh/I76szMKJnBXWPdFkQJl2r9SjPt3cMzLdOB6eJ4Pnpas8hVoWPYemX/peO0yrrvldgCUVJqOAjUrOLjbxg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-body-length-node": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.2.1.tgz", + "integrity": "sha512-h53dz/pISVrVrfxV1iqXlx5pRg3V2YWFcSQyPyXZRrZoZj4R4DeWRDo1a7dd3CPTcFi3kE+98tuNyD2axyZReA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-buffer-from": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.0.tgz", + "integrity": "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-config-provider": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.2.0.tgz", + "integrity": "sha512-YEjpl6XJ36FTKmD+kRJJWYvrHeUvm5ykaUS5xK+6oXffQPHeEM4/nXlZPe+Wu0lsgRUcNZiliYNh/y7q9c2y6Q==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-browser": { + "version": "4.3.13", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.13.tgz", + "integrity": "sha512-hlVLdAGrVfyNei+pKIgqDTxfu/ZI2NSyqj4IDxKd5bIsIqwR/dSlkxlPaYxFiIaDVrBy0he8orsFy+Cz119XvA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^4.2.5", + "@smithy/smithy-client": "^4.9.10", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-node": { + "version": "4.2.16", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.16.tgz", + "integrity": "sha512-F1t22IUiJLHrxW9W1CQ6B9PN+skZ9cqSuzB18Eh06HrJPbjsyZ7ZHecAKw80DQtyGTRcVfeukKaCRYebFwclbg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/config-resolver": "^4.4.3", + "@smithy/credential-provider-imds": "^4.2.5", + "@smithy/node-config-provider": "^4.3.5", + "@smithy/property-provider": "^4.2.5", + "@smithy/smithy-client": "^4.9.10", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-endpoints": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.2.5.tgz", + "integrity": "sha512-3O63AAWu2cSNQZp+ayl9I3NapW1p1rR5mlVHcF6hAB1dPZUQFfRPYtplWX/3xrzWthPGj5FqB12taJJCfH6s8A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.3.5", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-hex-encoding": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.2.0.tgz", + "integrity": "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-middleware": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.5.tgz", + "integrity": "sha512-6Y3+rvBF7+PZOc40ybeZMcGln6xJGVeY60E7jy9Mv5iKpMJpHgRE6dKy9ScsVxvfAYuEX4Q9a65DQX90KaQ3bA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-retry": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.2.5.tgz", + "integrity": "sha512-GBj3+EZBbN4NAqJ/7pAhsXdfzdlznOh8PydUijy6FpNIMnHPSMO2/rP4HKu+UFeikJxShERk528oy7GT79YiJg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/service-error-classification": "^4.2.5", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-stream": { + "version": "4.5.6", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.6.tgz", + "integrity": "sha512-qWw/UM59TiaFrPevefOZ8CNBKbYEP6wBAIlLqxn3VAIo9rgnTNc4ASbVrqDmhuwI87usnjhdQrxodzAGFFzbRQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/fetch-http-handler": "^5.3.6", + "@smithy/node-http-handler": "^4.4.5", + "@smithy/types": "^4.9.0", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-hex-encoding": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-uri-escape": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.2.0.tgz", + "integrity": "sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-utf8": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.0.tgz", + "integrity": "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-waiter": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-4.2.5.tgz", + "integrity": "sha512-Dbun99A3InifQdIrsXZ+QLcC0PGBPAdrl4cj1mTgJvyc9N2zf7QSxg8TBkzsCmGJdE3TLbO9ycwpY0EkWahQ/g==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/abort-controller": "^4.2.5", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/uuid": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@smithy/uuid/-/uuid-1.1.0.tgz", + "integrity": "sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/bowser": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.13.1.tgz", + "integrity": "sha512-OHawaAbjwx6rqICCKgSG0SAnT05bzd7ppyKLVUITZpANBaaMFBAsaNkto3LoQ31tyFP5kNujE8Cdx85G9VzOkw==", + "license": "MIT" + }, + "node_modules/fast-xml-parser": { + "version": "5.2.5", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.2.5.tgz", + "integrity": "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "strnum": "^2.1.0" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "node_modules/mnemonist": { + "version": "0.38.3", + "resolved": "https://registry.npmjs.org/mnemonist/-/mnemonist-0.38.3.tgz", + "integrity": "sha512-2K9QYubXx/NAjv4VLq1d1Ly8pWNC5L3BrixtdkyTegXWJIqY+zLNDhhX/A+ZwWt70tB1S8H4BE8FLYEFyNoOBw==", + "license": "MIT", + "dependencies": { + "obliterator": "^1.6.1" + } + }, + "node_modules/obliterator": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/obliterator/-/obliterator-1.6.1.tgz", + "integrity": "sha512-9WXswnqINnnhOG/5SLimUlzuU1hFJUc8zkwyD59Sd+dPOMf05PmnYG/d6Q7HZ+KmgkZJa1PxRso6QdM3sTNHig==", + "license": "MIT" + }, + "node_modules/strnum": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.1.tgz", + "integrity": "sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..423697b --- /dev/null +++ b/package.json @@ -0,0 +1,14 @@ +{ + "name": "halo-pocket-labyrinth", + "version": "0.1.0", + "description": "Mobile-first HALO Pocket Labyrinth build outputs with optional relay server", + "scripts": { + "build": "node scripts/build.js", + "server": "node scripts/server.js" + }, + "dependencies": { + "@aws-sdk/client-dynamodb": "^3.948.0", + "@aws-sdk/lib-dynamodb": "^3.948.0", + "ws": "^8.18.0" + } +} diff --git a/scripts/build.js b/scripts/build.js new file mode 100644 index 0000000..5e0e1b3 --- /dev/null +++ b/scripts/build.js @@ -0,0 +1,48 @@ +const fs = require('fs'); +const path = require('path'); + +const root = path.resolve(__dirname, '..'); +const srcHtml = path.join(root, 'index.html'); +const srcJs = path.join(root, 'halo.js'); +const distDir = path.join(root, 'dist'); + +function ensureFileExists(filePath) { + if (!fs.existsSync(filePath)) { + throw new Error(`Missing source file: ${filePath}`); + } +} + +function inlineScript(html, scriptContent) { + const scriptTag = ''; + if (!html.includes(scriptTag)) { + throw new Error('Expected placeholder in index.html'); + } + return html.replace( + scriptTag, + `` + ); +} + +function main() { + ensureFileExists(srcHtml); + ensureFileExists(srcJs); + + const html = fs.readFileSync(srcHtml, 'utf8'); + const script = fs.readFileSync(srcJs, 'utf8'); + + if (!fs.existsSync(distDir)) { + fs.mkdirSync(distDir, { recursive: true }); + } + + const bundledHtml = inlineScript(html, script); + const distHtml = path.join(distDir, 'index.html'); + const distScript = path.join(distDir, 'halo.js'); + + fs.writeFileSync(distHtml, bundledHtml, 'utf8'); + fs.writeFileSync(distScript, script, 'utf8'); + + console.log('Built dist/index.html (inlined halo.js)'); + console.log('Copied dist/halo.js for debugging'); +} + +main(); diff --git a/scripts/server.js b/scripts/server.js new file mode 100644 index 0000000..7623cea --- /dev/null +++ b/scripts/server.js @@ -0,0 +1,74 @@ +const WebSocket = require('ws'); + +const PORT = process.env.PORT || 8787; +const rooms = new Map(); + +function broadcast(roomId, payload, skip) { + const room = rooms.get(roomId); + if (!room) return; + const data = JSON.stringify(payload); + for (const peer of room) { + if (peer === skip || peer.readyState !== WebSocket.OPEN) continue; + peer.send(data); + } +} + +function prune(roomId) { + const room = rooms.get(roomId); + if (!room) return; + for (const peer of [...room]) { + if (peer.readyState !== WebSocket.OPEN) room.delete(peer); + } + if (!room.size) rooms.delete(roomId); +} + +const wss = new WebSocket.Server({ port: PORT }); + +wss.on('connection', (ws) => { + ws.meta = { room: null, player: 'anon' }; + + ws.on('message', (msg) => { + let data; + try { + data = JSON.parse(msg); + } catch (err) { + return; + } + + if (data.type === 'join') { + const roomId = (data.room || 'lobby').slice(0, 32); + const player = (data.player || 'anon').slice(0, 32); + ws.meta = { room: roomId, player }; + if (!rooms.has(roomId)) rooms.set(roomId, new Set()); + rooms.get(roomId).add(ws); + broadcast(roomId, { type: 'system', message: `${player} joined ${roomId}` }, ws); + ws.send(JSON.stringify({ type: 'welcome', room: roomId, peers: rooms.get(roomId).size })); + return; + } + + if (!ws.meta.room) return; + + if (data.type === 'sync') { + broadcast(ws.meta.room, { type: 'sync', from: ws.meta.player, payload: data.payload }, ws); + return; + } + + if (data.type === 'chat') { + broadcast(ws.meta.room, { type: 'chat', from: ws.meta.player, message: data.message }, ws); + return; + } + }); + + ws.on('close', () => { + const { room, player } = ws.meta; + if (room && rooms.has(room)) { + broadcast(room, { type: 'system', message: `${player} left ${room}` }, ws); + rooms.get(room).delete(ws); + prune(room); + } + }); +}); + +wss.on('listening', () => { + console.log(`HALO relay server running on ws://localhost:${PORT}`); +}); diff --git a/scripts/sync_android_assets.sh b/scripts/sync_android_assets.sh new file mode 100755 index 0000000..13a1d9e --- /dev/null +++ b/scripts/sync_android_assets.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +SRC="$ROOT_DIR/dist/index.html" +DEST="$ROOT_DIR/android-app/app/src/main/assets/index.html" + +if [[ ! -f "$SRC" ]]; then + echo "Build output not found at $SRC. Run npm run build first." >&2 + exit 1 +fi + +mkdir -p "$(dirname "$DEST")" +cp "$SRC" "$DEST" +echo "Synced $SRC -> $DEST" From fa935bae3560bad2395e090748af65f245645203 Mon Sep 17 00:00:00 2001 From: Apocky Date: Thu, 11 Dec 2025 02:21:35 -0700 Subject: [PATCH 2/3] v1.0 v1.0 --- .idea/.gitignore | 3 + .idea/Collab_Reposit.iml | 9 + .idea/caches/deviceStreaming.xml | 1077 ++++++++++++++++++++++++ .idea/markdown.xml | 8 + .idea/misc.xml | 6 + .idea/modules.xml | 8 + .idea/vcs.xml | 6 + exportToHTML/.gitignore.html | 21 + exportToHTML/Collab_Reposit.iml.html | 28 + exportToHTML/README.md.html | 21 + exportToHTML/aws.yml.html | 116 +++ exportToHTML/deviceStreaming.xml.html | 1096 +++++++++++++++++++++++++ exportToHTML/halo (3).js.html | 209 +++++ exportToHTML/halo.js.html | 209 +++++ exportToHTML/index (3) (1).html.html | 287 +++++++ exportToHTML/index (3).html.html | 263 ++++++ exportToHTML/index.html | 1 + exportToHTML/index.html.html | 263 ++++++ exportToHTML/index.js.html | 103 +++ exportToHTML/markdown.xml.html | 27 + exportToHTML/misc.xml.html | 25 + exportToHTML/modules.xml.html | 27 + exportToHTML/vcs.xml.html | 25 + exportToHTML/workspace.xml.html | 70 ++ 24 files changed, 3908 insertions(+) create mode 100644 .idea/.gitignore create mode 100644 .idea/Collab_Reposit.iml create mode 100644 .idea/caches/deviceStreaming.xml create mode 100644 .idea/markdown.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml create mode 100644 exportToHTML/.gitignore.html create mode 100644 exportToHTML/Collab_Reposit.iml.html create mode 100644 exportToHTML/README.md.html create mode 100644 exportToHTML/aws.yml.html create mode 100644 exportToHTML/deviceStreaming.xml.html create mode 100644 exportToHTML/halo (3).js.html create mode 100644 exportToHTML/halo.js.html create mode 100644 exportToHTML/index (3) (1).html.html create mode 100644 exportToHTML/index (3).html.html create mode 100644 exportToHTML/index.html create mode 100644 exportToHTML/index.html.html create mode 100644 exportToHTML/index.js.html create mode 100644 exportToHTML/markdown.xml.html create mode 100644 exportToHTML/misc.xml.html create mode 100644 exportToHTML/modules.xml.html create mode 100644 exportToHTML/vcs.xml.html create mode 100644 exportToHTML/workspace.xml.html diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/Collab_Reposit.iml b/.idea/Collab_Reposit.iml new file mode 100644 index 0000000..d6ebd48 --- /dev/null +++ b/.idea/Collab_Reposit.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/caches/deviceStreaming.xml b/.idea/caches/deviceStreaming.xml new file mode 100644 index 0000000..de89dad --- /dev/null +++ b/.idea/caches/deviceStreaming.xml @@ -0,0 +1,1077 @@ + + + + + + \ No newline at end of file diff --git a/.idea/markdown.xml b/.idea/markdown.xml new file mode 100644 index 0000000..c61ea33 --- /dev/null +++ b/.idea/markdown.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..1945ce5 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..123909b --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/exportToHTML/.gitignore.html b/exportToHTML/.gitignore.html new file mode 100644 index 0000000..d5a1b08 --- /dev/null +++ b/exportToHTML/.gitignore.html @@ -0,0 +1,21 @@ + + +.gitignore + + + + + +
+ +.gitignore +
+
# Default ignored files
+/shelf/
+/workspace.xml
+
+ + \ No newline at end of file diff --git a/exportToHTML/Collab_Reposit.iml.html b/exportToHTML/Collab_Reposit.iml.html new file mode 100644 index 0000000..179637a --- /dev/null +++ b/exportToHTML/Collab_Reposit.iml.html @@ -0,0 +1,28 @@ + + +Collab_Reposit.iml + + + + + +
+ +Collab_Reposit.iml +
+
<?xml version="1.0" encoding="UTF-8"?>
+<module type="JAVA_MODULE" version="4">
+  <component name="NewModuleRootManager" inherit-compiler-output="true">
+    <exclude-output />
+    <content url="file://$MODULE_DIR$" />
+    <orderEntry type="inheritedJdk" />
+    <orderEntry type="sourceFolder" forTests="false" />
+  </component>
+</module>
+ + \ No newline at end of file diff --git a/exportToHTML/README.md.html b/exportToHTML/README.md.html new file mode 100644 index 0000000..aa79748 --- /dev/null +++ b/exportToHTML/README.md.html @@ -0,0 +1,21 @@ + + +README.md + + + + + +
+ +README.md +
+
# Collab_Reposit
+The place to discuss plans and share ideas for the game.
+
+ + \ No newline at end of file diff --git a/exportToHTML/aws.yml.html b/exportToHTML/aws.yml.html new file mode 100644 index 0000000..3114100 --- /dev/null +++ b/exportToHTML/aws.yml.html @@ -0,0 +1,116 @@ + + +aws.yml + + + + + +
+ +aws.yml +
+
# This workflow will build and push a new container image to Amazon ECR,
+# and then will deploy a new task definition to Amazon ECS, when there is a push to the "master" branch.
+#
+# To use this workflow, you will need to complete the following set-up steps:
+#
+# 1. Create an ECR repository to store your images.
+#    For example: `aws ecr create-repository --repository-name my-ecr-repo --region us-east-2`.
+#    Replace the value of the `ECR_REPOSITORY` environment variable in the workflow below with your repository's name.
+#    Replace the value of the `AWS_REGION` environment variable in the workflow below with your repository's region.
+#
+# 2. Create an ECS task definition, an ECS cluster, and an ECS service.
+#    For example, follow the Getting Started guide on the ECS console:
+#      https://us-east-2.console.aws.amazon.com/ecs/home?region=us-east-2#/firstRun
+#    Replace the value of the `ECS_SERVICE` environment variable in the workflow below with the name you set for the Amazon ECS service.
+#    Replace the value of the `ECS_CLUSTER` environment variable in the workflow below with the name you set for the cluster.
+#
+# 3. Store your ECS task definition as a JSON file in your repository.
+#    The format should follow the output of `aws ecs register-task-definition --generate-cli-skeleton`.
+#    Replace the value of the `ECS_TASK_DEFINITION` environment variable in the workflow below with the path to the JSON file.
+#    Replace the value of the `CONTAINER_NAME` environment variable in the workflow below with the name of the container
+#    in the `containerDefinitions` section of the task definition.
+#
+# 4. Store an IAM user access key in GitHub Actions secrets named `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY`.
+#    See the documentation for each action used below for the recommended IAM policies for this IAM user,
+#    and best practices on handling the access key credentials.
+
+name: Deploy to Amazon ECS
+
+on:
+  push:
+    branches: [ "master" ]
+
+env:
+  AWS_REGION: MY_AWS_REGION                   # set this to your preferred AWS region, e.g. us-west-1
+  ECR_REPOSITORY: MY_ECR_REPOSITORY           # set this to your Amazon ECR repository name
+  ECS_SERVICE: MY_ECS_SERVICE                 # set this to your Amazon ECS service name
+  ECS_CLUSTER: MY_ECS_CLUSTER                 # set this to your Amazon ECS cluster name
+  ECS_TASK_DEFINITION: MY_ECS_TASK_DEFINITION # set this to the path to your Amazon ECS task definition
+                                               # file, e.g. .aws/task-definition.json
+  CONTAINER_NAME: MY_CONTAINER_NAME           # set this to the name of the container in the
+                                               # containerDefinitions section of your task definition
+
+permissions:
+  contents: read
+
+jobs:
+  deploy:
+    name: Deploy
+    runs-on: ubuntu-latest
+    environment: production
+
+    steps:
+    - name: Checkout
+      uses: actions/checkout@v4
+
+    - name: Configure AWS credentials
+      uses: aws-actions/configure-aws-credentials@v1
+      with:
+        aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
+        aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
+        aws-region: ${{ env.AWS_REGION }}
+
+    - name: Login to Amazon ECR
+      id: login-ecr
+      uses: aws-actions/amazon-ecr-login@v1
+
+    - name: Build, tag, and push image to Amazon ECR
+      id: build-image
+      env:
+        ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
+        IMAGE_TAG: ${{ github.sha }}
+      run: |
+        # Build a docker container and
+        # push it to ECR so that it can
+        # be deployed to ECS.
+        docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
+        docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
+        echo "image=$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" >> $GITHUB_OUTPUT
+
+    - name: Fill in the new image ID in the Amazon ECS task definition
+      id: task-def
+      uses: aws-actions/amazon-ecs-render-task-definition@v1
+      with:
+        task-definition: ${{ env.ECS_TASK_DEFINITION }}
+        container-name: ${{ env.CONTAINER_NAME }}
+        image: ${{ steps.build-image.outputs.image }}
+
+    - name: Deploy Amazon ECS task definition
+      uses: aws-actions/amazon-ecs-deploy-task-definition@v1
+      with:
+        task-definition: ${{ steps.task-def.outputs.task-definition }}
+        service: ${{ env.ECS_SERVICE }}
+        cluster: ${{ env.ECS_CLUSTER }}
+        wait-for-service-stability: true
+
+ + \ No newline at end of file diff --git a/exportToHTML/deviceStreaming.xml.html b/exportToHTML/deviceStreaming.xml.html new file mode 100644 index 0000000..01c9a88 --- /dev/null +++ b/exportToHTML/deviceStreaming.xml.html @@ -0,0 +1,1096 @@ + + +deviceStreaming.xml + + + + + +
+ +deviceStreaming.xml +
+
<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="DeviceStreaming">
+    <option name="deviceSelectionList">
+      <list>
+        <PersistentDeviceSelectionData>
+          <option name="api" value="34" />
+          <option name="brand" value="Sony" />
+          <option name="codename" value="A402SO" />
+          <option name="id" value="A402SO" />
+          <option name="labId" value="google" />
+          <option name="manufacturer" value="Sony" />
+          <option name="name" value="Xperia 10" />
+          <option name="screenDensity" value="450" />
+          <option name="screenX" value="1080" />
+          <option name="screenY" value="2520" />
+        </PersistentDeviceSelectionData>
+        <PersistentDeviceSelectionData>
+          <option name="api" value="27" />
+          <option name="brand" value="DOCOMO" />
+          <option name="codename" value="F01L" />
+          <option name="id" value="F01L" />
+          <option name="labId" value="google" />
+          <option name="manufacturer" value="FUJITSU" />
+          <option name="name" value="F-01L" />
+          <option name="screenDensity" value="360" />
+          <option name="screenX" value="720" />
+          <option name="screenY" value="1280" />
+        </PersistentDeviceSelectionData>
+        <PersistentDeviceSelectionData>
+          <option name="api" value="34" />
+          <option name="brand" value="OnePlus" />
+          <option name="codename" value="OP535DL1" />
+          <option name="id" value="OP535DL1" />
+          <option name="labId" value="google" />
+          <option name="manufacturer" value="OnePlus" />
+          <option name="name" value="CPH2409" />
+          <option name="screenDensity" value="401" />
+          <option name="screenX" value="1080" />
+          <option name="screenY" value="2412" />
+        </PersistentDeviceSelectionData>
+        <PersistentDeviceSelectionData>
+          <option name="api" value="34" />
+          <option name="brand" value="OnePlus" />
+          <option name="codename" value="OP5552L1" />
+          <option name="id" value="OP5552L1" />
+          <option name="labId" value="google" />
+          <option name="manufacturer" value="OnePlus" />
+          <option name="name" value="CPH2415" />
+          <option name="screenDensity" value="480" />
+          <option name="screenX" value="1080" />
+          <option name="screenY" value="2412" />
+        </PersistentDeviceSelectionData>
+        <PersistentDeviceSelectionData>
+          <option name="api" value="35" />
+          <option name="brand" value="OnePlus" />
+          <option name="codename" value="OP5552L1" />
+          <option name="id" value="OP5552L1" />
+          <option name="labId" value="google" />
+          <option name="manufacturer" value="OnePlus" />
+          <option name="name" value="CPH2415" />
+          <option name="screenDensity" value="480" />
+          <option name="screenX" value="1080" />
+          <option name="screenY" value="2412" />
+        </PersistentDeviceSelectionData>
+        <PersistentDeviceSelectionData>
+          <option name="api" value="34" />
+          <option name="brand" value="OPPO" />
+          <option name="codename" value="OP573DL1" />
+          <option name="id" value="OP573DL1" />
+          <option name="labId" value="google" />
+          <option name="manufacturer" value="OPPO" />
+          <option name="name" value="CPH2557" />
+          <option name="screenDensity" value="480" />
+          <option name="screenX" value="1080" />
+          <option name="screenY" value="2400" />
+        </PersistentDeviceSelectionData>
+        <PersistentDeviceSelectionData>
+          <option name="api" value="28" />
+          <option name="brand" value="DOCOMO" />
+          <option name="codename" value="SH-01L" />
+          <option name="id" value="SH-01L" />
+          <option name="labId" value="google" />
+          <option name="manufacturer" value="SHARP" />
+          <option name="name" value="AQUOS sense2 SH-01L" />
+          <option name="screenDensity" value="480" />
+          <option name="screenX" value="1080" />
+          <option name="screenY" value="2160" />
+        </PersistentDeviceSelectionData>
+        <PersistentDeviceSelectionData>
+          <option name="api" value="35" />
+          <option name="brand" value="Lenovo" />
+          <option name="codename" value="TB330FU" />
+          <option name="formFactor" value="Tablet" />
+          <option name="id" value="TB330FU" />
+          <option name="labId" value="google" />
+          <option name="manufacturer" value="Lenovo" />
+          <option name="name" value="Tab M11" />
+          <option name="screenDensity" value="240" />
+          <option name="screenX" value="1200" />
+          <option name="screenY" value="1920" />
+        </PersistentDeviceSelectionData>
+        <PersistentDeviceSelectionData>
+          <option name="api" value="35" />
+          <option name="brand" value="samsung" />
+          <option name="codename" value="a05s" />
+          <option name="id" value="a05s" />
+          <option name="labId" value="google" />
+          <option name="manufacturer" value="Samsung" />
+          <option name="name" value="A05s" />
+          <option name="screenDensity" value="450" />
+          <option name="screenX" value="1080" />
+          <option name="screenY" value="2400" />
+        </PersistentDeviceSelectionData>
+        <PersistentDeviceSelectionData>
+          <option name="api" value="35" />
+          <option name="brand" value="samsung" />
+          <option name="codename" value="a06" />
+          <option name="id" value="a06" />
+          <option name="labId" value="google" />
+          <option name="manufacturer" value="Samsung" />
+          <option name="name" value="Galaxy A06" />
+          <option name="screenDensity" value="300" />
+          <option name="screenX" value="720" />
+          <option name="screenY" value="1600" />
+        </PersistentDeviceSelectionData>
+        <PersistentDeviceSelectionData>
+          <option name="api" value="34" />
+          <option name="brand" value="samsung" />
+          <option name="codename" value="a14m" />
+          <option name="id" value="a14m" />
+          <option name="labId" value="google" />
+          <option name="manufacturer" value="Samsung" />
+          <option name="name" value="SM-A145R" />
+          <option name="screenDensity" value="450" />
+          <option name="screenX" value="1080" />
+          <option name="screenY" value="2408" />
+        </PersistentDeviceSelectionData>
+        <PersistentDeviceSelectionData>
+          <option name="api" value="34" />
+          <option name="brand" value="samsung" />
+          <option name="codename" value="a15" />
+          <option name="id" value="a15" />
+          <option name="labId" value="google" />
+          <option name="manufacturer" value="Samsung" />
+          <option name="name" value="A15" />
+          <option name="screenDensity" value="450" />
+          <option name="screenX" value="1080" />
+          <option name="screenY" value="2340" />
+        </PersistentDeviceSelectionData>
+        <PersistentDeviceSelectionData>
+          <option name="api" value="34" />
+          <option name="brand" value="samsung" />
+          <option name="codename" value="a15x" />
+          <option name="id" value="a15x" />
+          <option name="labId" value="google" />
+          <option name="manufacturer" value="Samsung" />
+          <option name="name" value="A15 5G" />
+          <option name="screenDensity" value="450" />
+          <option name="screenX" value="1080" />
+          <option name="screenY" value="2340" />
+        </PersistentDeviceSelectionData>
+        <PersistentDeviceSelectionData>
+          <option name="api" value="35" />
+          <option name="brand" value="samsung" />
+          <option name="codename" value="a16" />
+          <option name="id" value="a16" />
+          <option name="labId" value="google" />
+          <option name="manufacturer" value="Samsung" />
+          <option name="name" value="SM-A165M" />
+          <option name="screenDensity" value="450" />
+          <option name="screenX" value="1080" />
+          <option name="screenY" value="2340" />
+        </PersistentDeviceSelectionData>
+        <PersistentDeviceSelectionData>
+          <option name="api" value="34" />
+          <option name="brand" value="samsung" />
+          <option name="codename" value="a16x" />
+          <option name="id" value="a16x" />
+          <option name="labId" value="google" />
+          <option name="manufacturer" value="Samsung" />
+          <option name="name" value="A16 5G" />
+          <option name="screenDensity" value="450" />
+          <option name="screenX" value="1080" />
+          <option name="screenY" value="2340" />
+        </PersistentDeviceSelectionData>
+        <PersistentDeviceSelectionData>
+          <option name="api" value="36" />
+          <option name="brand" value="samsung" />
+          <option name="codename" value="a26x" />
+          <option name="id" value="a26x" />
+          <option name="labId" value="google" />
+          <option name="manufacturer" value="Samsung" />
+          <option name="name" value="SM-A266B" />
+          <option name="screenDensity" value="450" />
+          <option name="screenX" value="1080" />
+          <option name="screenY" value="2340" />
+        </PersistentDeviceSelectionData>
+        <PersistentDeviceSelectionData>
+          <option name="api" value="34" />
+          <option name="brand" value="samsung" />
+          <option name="codename" value="a35x" />
+          <option name="id" value="a35x" />
+          <option name="labId" value="google" />
+          <option name="manufacturer" value="Samsung" />
+          <option name="name" value="A35" />
+          <option name="screenDensity" value="450" />
+          <option name="screenX" value="1080" />
+          <option name="screenY" value="2340" />
+        </PersistentDeviceSelectionData>
+        <PersistentDeviceSelectionData>
+          <option name="api" value="35" />
+          <option name="brand" value="samsung" />
+          <option name="codename" value="a35x" />
+          <option name="id" value="a35x" />
+          <option name="labId" value="google" />
+          <option name="manufacturer" value="Samsung" />
+          <option name="name" value="A35" />
+          <option name="screenDensity" value="450" />
+          <option name="screenX" value="1080" />
+          <option name="screenY" value="2340" />
+        </PersistentDeviceSelectionData>
+        <PersistentDeviceSelectionData>
+          <option name="api" value="35" />
+          <option name="brand" value="samsung" />
+          <option name="codename" value="a36xq" />
+          <option name="id" value="a36xq" />
+          <option name="labId" value="google" />
+          <option name="manufacturer" value="Samsung" />
+          <option name="name" value="SM-A366E" />
+          <option name="screenDensity" value="450" />
+          <option name="screenX" value="1080" />
+          <option name="screenY" value="2340" />
+        </PersistentDeviceSelectionData>
+        <PersistentDeviceSelectionData>
+          <option name="api" value="35" />
+          <option name="brand" value="samsung" />
+          <option name="codename" value="a56x" />
+          <option name="id" value="a56x" />
+          <option name="labId" value="google" />
+          <option name="manufacturer" value="Samsung" />
+          <option name="name" value="SM-A566E" />
+          <option name="screenDensity" value="450" />
+          <option name="screenX" value="1080" />
+          <option name="screenY" value="2340" />
+        </PersistentDeviceSelectionData>
+        <PersistentDeviceSelectionData>
+          <option name="api" value="34" />
+          <option name="brand" value="google" />
+          <option name="codename" value="akita" />
+          <option name="id" value="akita" />
+          <option name="labId" value="google" />
+          <option name="manufacturer" value="Google" />
+          <option name="name" value="Pixel 8a" />
+          <option name="screenDensity" value="420" />
+          <option name="screenX" value="1080" />
+          <option name="screenY" value="2400" />
+        </PersistentDeviceSelectionData>
+        <PersistentDeviceSelectionData>
+          <option name="api" value="35" />
+          <option name="brand" value="google" />
+          <option name="codename" value="akita" />
+          <option name="id" value="akita" />
+          <option name="labId" value="google" />
+          <option name="manufacturer" value="Google" />
+          <option name="name" value="Pixel 8a" />
+          <option name="screenDensity" value="420" />
+          <option name="screenX" value="1080" />
+          <option name="screenY" value="2400" />
+        </PersistentDeviceSelectionData>
+        <PersistentDeviceSelectionData>
+          <option name="api" value="34" />
+          <option name="brand" value="motorola" />
+          <option name="codename" value="arcfox" />
+          <option name="id" value="arcfox" />
+          <option name="labId" value="google" />
+          <option name="manufacturer" value="Motorola" />
+          <option name="name" value="razr plus 2024" />
+          <option name="screenDensity" value="360" />
+          <option name="screenX" value="1080" />
+          <option name="screenY" value="1272" />
+        </PersistentDeviceSelectionData>
+        <PersistentDeviceSelectionData>
+          <option name="api" value="33" />
+          <option name="brand" value="motorola" />
+          <option name="codename" value="austin" />
+          <option name="id" value="austin" />
+          <option name="labId" value="google" />
+          <option name="manufacturer" value="Motorola" />
+          <option name="name" value="moto g 5G (2022)" />
+          <option name="screenDensity" value="280" />
+          <option name="screenX" value="720" />
+          <option name="screenY" value="1600" />
+        </PersistentDeviceSelectionData>
+        <PersistentDeviceSelectionData>
+          <option name="api" value="33" />
+          <option name="brand" value="samsung" />
+          <option name="codename" value="b0q" />
+          <option name="id" value="b0q" />
+          <option name="labId" value="google" />
+          <option name="manufacturer" value="Samsung" />
+          <option name="name" value="Galaxy S22 Ultra" />
+          <option name="screenDensity" value="600" />
+          <option name="screenX" value="1440" />
+          <option name="screenY" value="3088" />
+        </PersistentDeviceSelectionData>
+        <PersistentDeviceSelectionData>
+          <option name="api" value="34" />
+          <option name="brand" value="samsung" />
+          <option name="codename" value="b6q" />
+          <option name="id" value="b6q" />
+          <option name="labId" value="google" />
+          <option name="manufacturer" value="Samsung" />
+          <option name="name" value="Flip 6" />
+          <option name="screenDensity" value="340" />
+          <option name="screenX" value="1080" />
+          <option name="screenY" value="2640" />
+        </PersistentDeviceSelectionData>
+        <PersistentDeviceSelectionData>
+          <option name="api" value="36" />
+          <option name="brand" value="google" />
+          <option name="codename" value="blazer" />
+          <option name="id" value="blazer" />
+          <option name="labId" value="google" />
+          <option name="manufacturer" value="Google" />
+          <option name="name" value="Pixel 10 Pro" />
+          <option name="screenDensity" value="420" />
+          <option name="screenX" value="1080" />
+          <option name="screenY" value="2410" />
+        </PersistentDeviceSelectionData>
+        <PersistentDeviceSelectionData>
+          <option name="api" value="32" />
+          <option name="brand" value="google" />
+          <option name="codename" value="bluejay" />
+          <option name="id" value="bluejay" />
+          <option name="labId" value="google" />
+          <option name="manufacturer" value="Google" />
+          <option name="name" value="Pixel 6a" />
+          <option name="screenDensity" value="420" />
+          <option name="screenX" value="1080" />
+          <option name="screenY" value="2400" />
+        </PersistentDeviceSelectionData>
+        <PersistentDeviceSelectionData>
+          <option name="api" value="34" />
+          <option name="brand" value="google" />
+          <option name="codename" value="caiman" />
+          <option name="id" value="caiman" />
+          <option name="labId" value="google" />
+          <option name="manufacturer" value="Google" />
+          <option name="name" value="Pixel 9 Pro" />
+          <option name="screenDensity" value="360" />
+          <option name="screenX" value="960" />
+          <option name="screenY" value="2142" />
+        </PersistentDeviceSelectionData>
+        <PersistentDeviceSelectionData>
+          <option name="api" value="35" />
+          <option name="brand" value="google" />
+          <option name="codename" value="caiman" />
+          <option name="id" value="caiman" />
+          <option name="labId" value="google" />
+          <option name="manufacturer" value="Google" />
+          <option name="name" value="Pixel 9 Pro" />
+          <option name="screenDensity" value="360" />
+          <option name="screenX" value="960" />
+          <option name="screenY" value="2142" />
+        </PersistentDeviceSelectionData>
+        <PersistentDeviceSelectionData>
+          <option name="api" value="34" />
+          <option name="brand" value="google" />
+          <option name="codename" value="comet" />
+          <option name="default" value="true" />
+          <option name="id" value="comet" />
+          <option name="labId" value="google" />
+          <option name="manufacturer" value="Google" />
+          <option name="name" value="Pixel 9 Pro Fold" />
+          <option name="screenDensity" value="390" />
+          <option name="screenX" value="2076" />
+          <option name="screenY" value="2152" />
+        </PersistentDeviceSelectionData>
+        <PersistentDeviceSelectionData>
+          <option name="api" value="35" />
+          <option name="brand" value="google" />
+          <option name="codename" value="comet" />
+          <option name="default" value="true" />
+          <option name="id" value="comet" />
+          <option name="labId" value="google" />
+          <option name="manufacturer" value="Google" />
+          <option name="name" value="Pixel 9 Pro Fold" />
+          <option name="screenDensity" value="390" />
+          <option name="screenX" value="2076" />
+          <option name="screenY" value="2152" />
+        </PersistentDeviceSelectionData>
+        <PersistentDeviceSelectionData>
+          <option name="api" value="29" />
+          <option name="brand" value="samsung" />
+          <option name="codename" value="crownqlteue" />
+          <option name="id" value="crownqlteue" />
+          <option name="labId" value="google" />
+          <option name="manufacturer" value="Samsung" />
+          <option name="name" value="Galaxy Note9" />
+          <option name="screenDensity" value="420" />
+          <option name="screenX" value="2220" />
+          <option name="screenY" value="1080" />
+        </PersistentDeviceSelectionData>
+        <PersistentDeviceSelectionData>
+          <option name="api" value="35" />
+          <option name="brand" value="samsung" />
+          <option name="codename" value="dm1q" />
+          <option name="id" value="dm1q" />
+          <option name="labId" value="google" />
+          <option name="manufacturer" value="Samsung" />
+          <option name="name" value="S23" />
+          <option name="screenDensity" value="480" />
+          <option name="screenX" value="1080" />
+          <option name="screenY" value="2340" />
+        </PersistentDeviceSelectionData>
+        <PersistentDeviceSelectionData>
+          <option name="api" value="34" />
+          <option name="brand" value="samsung" />
+          <option name="codename" value="dm2q" />
+          <option name="id" value="dm2q" />
+          <option name="labId" value="google" />
+          <option name="manufacturer" value="Samsung" />
+          <option name="name" value="S23 Plus" />
+          <option name="screenDensity" value="450" />
+          <option name="screenX" value="1080" />
+          <option name="screenY" value="2340" />
+        </PersistentDeviceSelectionData>
+        <PersistentDeviceSelectionData>
+          <option name="api" value="34" />
+          <option name="brand" value="samsung" />
+          <option name="codename" value="dm3q" />
+          <option name="id" value="dm3q" />
+          <option name="labId" value="google" />
+          <option name="manufacturer" value="Samsung" />
+          <option name="name" value="Galaxy S23 Ultra" />
+          <option name="screenDensity" value="600" />
+          <option name="screenX" value="1440" />
+          <option name="screenY" value="3088" />
+        </PersistentDeviceSelectionData>
+        <PersistentDeviceSelectionData>
+          <option name="api" value="34" />
+          <option name="brand" value="motorola" />
+          <option name="codename" value="dubai" />
+          <option name="id" value="dubai" />
+          <option name="labId" value="google" />
+          <option name="manufacturer" value="Motorola" />
+          <option name="name" value="edge 30" />
+          <option name="screenDensity" value="405" />
+          <option name="screenX" value="1080" />
+          <option name="screenY" value="2400" />
+        </PersistentDeviceSelectionData>
+        <PersistentDeviceSelectionData>
+          <option name="api" value="34" />
+          <option name="brand" value="samsung" />
+          <option name="codename" value="e1q" />
+          <option name="default" value="true" />
+          <option name="id" value="e1q" />
+          <option name="labId" value="google" />
+          <option name="manufacturer" value="Samsung" />
+          <option name="name" value="Galaxy S24" />
+          <option name="screenDensity" value="480" />
+          <option name="screenX" value="1080" />
+          <option name="screenY" value="2340" />
+        </PersistentDeviceSelectionData>
+        <PersistentDeviceSelectionData>
+          <option name="api" value="34" />
+          <option name="brand" value="samsung" />
+          <option name="codename" value="e3q" />
+          <option name="id" value="e3q" />
+          <option name="labId" value="google" />
+          <option name="manufacturer" value="Samsung" />
+          <option name="name" value="Galaxy S24 Ultra" />
+          <option name="screenDensity" value="450" />
+          <option name="screenX" value="1440" />
+          <option name="screenY" value="3120" />
+        </PersistentDeviceSelectionData>
+        <PersistentDeviceSelectionData>
+          <option name="api" value="36" />
+          <option name="brand" value="samsung" />
+          <option name="codename" value="e3q" />
+          <option name="id" value="e3q" />
+          <option name="labId" value="google" />
+          <option name="manufacturer" value="Samsung" />
+          <option name="name" value="Galaxy S24 Ultra" />
+          <option name="screenDensity" value="450" />
+          <option name="screenX" value="1440" />
+          <option name="screenY" value="3120" />
+        </PersistentDeviceSelectionData>
+        <PersistentDeviceSelectionData>
+          <option name="api" value="33" />
+          <option name="brand" value="google" />
+          <option name="codename" value="eos" />
+          <option name="id" value="eos" />
+          <option name="labId" value="google" />
+          <option name="manufacturer" value="Google" />
+          <option name="name" value="Eos" />
+          <option name="screenDensity" value="320" />
+          <option name="screenX" value="384" />
+          <option name="screenY" value="384" />
+        </PersistentDeviceSelectionData>
+        <PersistentDeviceSelectionData>
+          <option name="api" value="33" />
+          <option name="brand" value="google" />
+          <option name="codename" value="felix" />
+          <option name="id" value="felix" />
+          <option name="labId" value="google" />
+          <option name="manufacturer" value="Google" />
+          <option name="name" value="Pixel Fold" />
+          <option name="screenDensity" value="420" />
+          <option name="screenX" value="2208" />
+          <option name="screenY" value="1840" />
+        </PersistentDeviceSelectionData>
+        <PersistentDeviceSelectionData>
+          <option name="api" value="34" />
+          <option name="brand" value="google" />
+          <option name="codename" value="felix" />
+          <option name="id" value="felix" />
+          <option name="labId" value="google" />
+          <option name="manufacturer" value="Google" />
+          <option name="name" value="Pixel Fold" />
+          <option name="screenDensity" value="420" />
+          <option name="screenX" value="2208" />
+          <option name="screenY" value="1840" />
+        </PersistentDeviceSelectionData>
+        <PersistentDeviceSelectionData>
+          <option name="api" value="33" />
+          <option name="brand" value="google" />
+          <option name="codename" value="felix_camera" />
+          <option name="id" value="felix_camera" />
+          <option name="labId" value="google" />
+          <option name="manufacturer" value="Google" />
+          <option name="name" value="Pixel Fold (Camera-enabled)" />
+          <option name="screenDensity" value="420" />
+          <option name="screenX" value="2208" />
+          <option name="screenY" value="1840" />
+        </PersistentDeviceSelectionData>
+        <PersistentDeviceSelectionData>
+          <option name="api" value="34" />
+          <option name="brand" value="motorola" />
+          <option name="codename" value="fogona" />
+          <option name="id" value="fogona" />
+          <option name="labId" value="google" />
+          <option name="manufacturer" value="Motorola" />
+          <option name="name" value="moto g play - 2024" />
+          <option name="screenDensity" value="280" />
+          <option name="screenX" value="720" />
+          <option name="screenY" value="1600" />
+        </PersistentDeviceSelectionData>
+        <PersistentDeviceSelectionData>
+          <option name="api" value="34" />
+          <option name="brand" value="motorola" />
+          <option name="codename" value="fogos" />
+          <option name="id" value="fogos" />
+          <option name="labId" value="google" />
+          <option name="manufacturer" value="Motorola" />
+          <option name="name" value="moto g34 5G" />
+          <option name="screenDensity" value="280" />
+          <option name="screenX" value="720" />
+          <option name="screenY" value="1600" />
+        </PersistentDeviceSelectionData>
+        <PersistentDeviceSelectionData>
+          <option name="api" value="36" />
+          <option name="brand" value="google" />
+          <option name="codename" value="frankel" />
+          <option name="id" value="frankel" />
+          <option name="labId" value="google" />
+          <option name="manufacturer" value="Google" />
+          <option name="name" value="Pixel 10" />
+          <option name="screenDensity" value="420" />
+          <option name="screenX" value="1080" />
+          <option name="screenY" value="2424" />
+        </PersistentDeviceSelectionData>
+        <PersistentDeviceSelectionData>
+          <option name="api" value="34" />
+          <option name="brand" value="samsung" />
+          <option name="codename" value="g0q" />
+          <option name="id" value="g0q" />
+          <option name="labId" value="google" />
+          <option name="manufacturer" value="Samsung" />
+          <option name="name" value="SM-S906U1" />
+          <option name="screenDensity" value="450" />
+          <option name="screenX" value="1080" />
+          <option name="screenY" value="2340" />
+        </PersistentDeviceSelectionData>
+        <PersistentDeviceSelectionData>
+          <option name="api" value="34" />
+          <option name="brand" value="samsung" />
+          <option name="codename" value="gta9pwifi" />
+          <option name="id" value="gta9pwifi" />
+          <option name="labId" value="google" />
+          <option name="manufacturer" value="Samsung" />
+          <option name="name" value="SM-X210" />
+          <option name="screenDensity" value="240" />
+          <option name="screenX" value="1200" />
+          <option name="screenY" value="1920" />
+        </PersistentDeviceSelectionData>
+        <PersistentDeviceSelectionData>
+          <option name="api" value="33" />
+          <option name="brand" value="samsung" />
+          <option name="codename" value="gts7lwifi" />
+          <option name="id" value="gts7lwifi" />
+          <option name="labId" value="google" />
+          <option name="manufacturer" value="Samsung" />
+          <option name="name" value="SM-T870" />
+          <option name="screenDensity" value="340" />
+          <option name="screenX" value="1600" />
+          <option name="screenY" value="2560" />
+        </PersistentDeviceSelectionData>
+        <PersistentDeviceSelectionData>
+          <option name="api" value="34" />
+          <option name="brand" value="samsung" />
+          <option name="codename" value="gts7xllite" />
+          <option name="id" value="gts7xllite" />
+          <option name="labId" value="google" />
+          <option name="manufacturer" value="Samsung" />
+          <option name="name" value="SM-T738U" />
+          <option name="screenDensity" value="340" />
+          <option name="screenX" value="1600" />
+          <option name="screenY" value="2560" />
+        </PersistentDeviceSelectionData>
+        <PersistentDeviceSelectionData>
+          <option name="api" value="33" />
+          <option name="brand" value="samsung" />
+          <option name="codename" value="gts8uwifi" />
+          <option name="formFactor" value="Tablet" />
+          <option name="id" value="gts8uwifi" />
+          <option name="labId" value="google" />
+          <option name="manufacturer" value="Samsung" />
+          <option name="name" value="Galaxy Tab S8 Ultra" />
+          <option name="screenDensity" value="320" />
+          <option name="screenX" value="1848" />
+          <option name="screenY" value="2960" />
+        </PersistentDeviceSelectionData>
+        <PersistentDeviceSelectionData>
+          <option name="api" value="34" />
+          <option name="brand" value="samsung" />
+          <option name="codename" value="gts8wifi" />
+          <option name="formFactor" value="Tablet" />
+          <option name="id" value="gts8wifi" />
+          <option name="labId" value="google" />
+          <option name="manufacturer" value="Samsung" />
+          <option name="name" value="Galaxy Tab S8" />
+          <option name="screenDensity" value="274" />
+          <option name="screenX" value="1600" />
+          <option name="screenY" value="2560" />
+        </PersistentDeviceSelectionData>
+        <PersistentDeviceSelectionData>
+          <option name="api" value="34" />
+          <option name="brand" value="samsung" />
+          <option name="codename" value="gts9fe" />
+          <option name="id" value="gts9fe" />
+          <option name="labId" value="google" />
+          <option name="manufacturer" value="Samsung" />
+          <option name="name" value="Galaxy Tab S9 FE 5G" />
+          <option name="screenDensity" value="280" />
+          <option name="screenX" value="1440" />
+          <option name="screenY" value="2304" />
+        </PersistentDeviceSelectionData>
+        <PersistentDeviceSelectionData>
+          <option name="api" value="34" />
+          <option name="brand" value="samsung" />
+          <option name="codename" value="gts9wifi" />
+          <option name="id" value="gts9wifi" />
+          <option name="labId" value="google" />
+          <option name="manufacturer" value="Samsung" />
+          <option name="name" value="SM-X710" />
+          <option name="screenDensity" value="340" />
+          <option name="screenX" value="1600" />
+          <option name="screenY" value="2560" />
+        </PersistentDeviceSelectionData>
+        <PersistentDeviceSelectionData>
+          <option name="api" value="34" />
+          <option name="brand" value="google" />
+          <option name="codename" value="husky" />
+          <option name="id" value="husky" />
+          <option name="labId" value="google" />
+          <option name="manufacturer" value="Google" />
+          <option name="name" value="Pixel 8 Pro" />
+          <option name="screenDensity" value="390" />
+          <option name="screenX" value="1008" />
+          <option name="screenY" value="2244" />
+        </PersistentDeviceSelectionData>
+        <PersistentDeviceSelectionData>
+          <option name="api" value="30" />
+          <option name="brand" value="motorola" />
+          <option name="codename" value="java" />
+          <option name="id" value="java" />
+          <option name="labId" value="google" />
+          <option name="manufacturer" value="Motorola" />
+          <option name="name" value="G20" />
+          <option name="screenDensity" value="280" />
+          <option name="screenX" value="720" />
+          <option name="screenY" value="1600" />
+        </PersistentDeviceSelectionData>
+        <PersistentDeviceSelectionData>
+          <option name="api" value="35" />
+          <option name="brand" value="motorola" />
+          <option name="codename" value="kansas" />
+          <option name="id" value="kansas" />
+          <option name="labId" value="google" />
+          <option name="manufacturer" value="Motorola" />
+          <option name="name" value="moto g - 2025" />
+          <option name="screenDensity" value="280" />
+          <option name="screenX" value="720" />
+          <option name="screenY" value="1604" />
+        </PersistentDeviceSelectionData>
+        <PersistentDeviceSelectionData>
+          <option name="api" value="34" />
+          <option name="brand" value="google" />
+          <option name="codename" value="komodo" />
+          <option name="id" value="komodo" />
+          <option name="labId" value="google" />
+          <option name="manufacturer" value="Google" />
+          <option name="name" value="Pixel 9 Pro XL" />
+          <option name="screenDensity" value="360" />
+          <option name="screenX" value="1008" />
+          <option name="screenY" value="2244" />
+        </PersistentDeviceSelectionData>
+        <PersistentDeviceSelectionData>
+          <option name="api" value="35" />
+          <option name="brand" value="google" />
+          <option name="codename" value="komodo" />
+          <option name="id" value="komodo" />
+          <option name="labId" value="google" />
+          <option name="manufacturer" value="Google" />
+          <option name="name" value="Pixel 9 Pro XL" />
+          <option name="screenDensity" value="360" />
+          <option name="screenX" value="1008" />
+          <option name="screenY" value="2244" />
+        </PersistentDeviceSelectionData>
+        <PersistentDeviceSelectionData>
+          <option name="api" value="35" />
+          <option name="brand" value="motorola" />
+          <option name="codename" value="lamul" />
+          <option name="id" value="lamul" />
+          <option name="labId" value="google" />
+          <option name="manufacturer" value="Motorola" />
+          <option name="name" value="moto g05" />
+          <option name="screenDensity" value="280" />
+          <option name="screenX" value="720" />
+          <option name="screenY" value="1604" />
+        </PersistentDeviceSelectionData>
+        <PersistentDeviceSelectionData>
+          <option name="api" value="34" />
+          <option name="brand" value="motorola" />
+          <option name="codename" value="lion" />
+          <option name="id" value="lion" />
+          <option name="labId" value="google" />
+          <option name="manufacturer" value="Motorola" />
+          <option name="name" value="moto g04" />
+          <option name="screenDensity" value="280" />
+          <option name="screenX" value="720" />
+          <option name="screenY" value="1612" />
+        </PersistentDeviceSelectionData>
+        <PersistentDeviceSelectionData>
+          <option name="api" value="33" />
+          <option name="brand" value="google" />
+          <option name="codename" value="lynx" />
+          <option name="id" value="lynx" />
+          <option name="labId" value="google" />
+          <option name="manufacturer" value="Google" />
+          <option name="name" value="Pixel 7a" />
+          <option name="screenDensity" value="420" />
+          <option name="screenX" value="1080" />
+          <option name="screenY" value="2400" />
+        </PersistentDeviceSelectionData>
+        <PersistentDeviceSelectionData>
+          <option name="api" value="34" />
+          <option name="brand" value="motorola" />
+          <option name="codename" value="lyriq" />
+          <option name="id" value="lyriq" />
+          <option name="labId" value="google" />
+          <option name="manufacturer" value="Motorola" />
+          <option name="name" value="edge 40" />
+          <option name="screenDensity" value="400" />
+          <option name="screenX" value="1080" />
+          <option name="screenY" value="2400" />
+        </PersistentDeviceSelectionData>
+        <PersistentDeviceSelectionData>
+          <option name="api" value="34" />
+          <option name="brand" value="motorola" />
+          <option name="codename" value="manaus" />
+          <option name="id" value="manaus" />
+          <option name="labId" value="google" />
+          <option name="manufacturer" value="Motorola" />
+          <option name="name" value="edge 40 neo" />
+          <option name="screenDensity" value="400" />
+          <option name="screenX" value="1080" />
+          <option name="screenY" value="2400" />
+        </PersistentDeviceSelectionData>
+        <PersistentDeviceSelectionData>
+          <option name="api" value="33" />
+          <option name="brand" value="motorola" />
+          <option name="codename" value="maui" />
+          <option name="id" value="maui" />
+          <option name="labId" value="google" />
+          <option name="manufacturer" value="Motorola" />
+          <option name="name" value="moto g play - 2023" />
+          <option name="screenDensity" value="280" />
+          <option name="screenX" value="720" />
+          <option name="screenY" value="1600" />
+        </PersistentDeviceSelectionData>
+        <PersistentDeviceSelectionData>
+          <option name="api" value="36" />
+          <option name="brand" value="google" />
+          <option name="codename" value="mustang" />
+          <option name="id" value="mustang" />
+          <option name="labId" value="google" />
+          <option name="manufacturer" value="Google" />
+          <option name="name" value="Pixel 10 Pro XL" />
+          <option name="screenDensity" value="390" />
+          <option name="screenX" value="1080" />
+          <option name="screenY" value="2404" />
+        </PersistentDeviceSelectionData>
+        <PersistentDeviceSelectionData>
+          <option name="api" value="34" />
+          <option name="brand" value="samsung" />
+          <option name="codename" value="o1q" />
+          <option name="id" value="o1q" />
+          <option name="labId" value="google" />
+          <option name="manufacturer" value="Samsung" />
+          <option name="name" value="Galaxy S21" />
+          <option name="screenDensity" value="421" />
+          <option name="screenX" value="1080" />
+          <option name="screenY" value="2400" />
+        </PersistentDeviceSelectionData>
+        <PersistentDeviceSelectionData>
+          <option name="api" value="31" />
+          <option name="brand" value="google" />
+          <option name="codename" value="oriole" />
+          <option name="id" value="oriole" />
+          <option name="labId" value="google" />
+          <option name="manufacturer" value="Google" />
+          <option name="name" value="Pixel 6" />
+          <option name="screenDensity" value="420" />
+          <option name="screenX" value="1080" />
+          <option name="screenY" value="2400" />
+        </PersistentDeviceSelectionData>
+        <PersistentDeviceSelectionData>
+          <option name="api" value="36" />
+          <option name="brand" value="samsung" />
+          <option name="codename" value="pa2q" />
+          <option name="id" value="pa2q" />
+          <option name="labId" value="google" />
+          <option name="manufacturer" value="Samsung" />
+          <option name="name" value="S25+" />
+          <option name="screenDensity" value="450" />
+          <option name="screenX" value="1080" />
+          <option name="screenY" value="2340" />
+        </PersistentDeviceSelectionData>
+        <PersistentDeviceSelectionData>
+          <option name="api" value="35" />
+          <option name="brand" value="samsung" />
+          <option name="codename" value="pa3q" />
+          <option name="id" value="pa3q" />
+          <option name="labId" value="google" />
+          <option name="manufacturer" value="Samsung" />
+          <option name="name" value="Galaxy S25 Ultra" />
+          <option name="screenDensity" value="600" />
+          <option name="screenX" value="1440" />
+          <option name="screenY" value="3120" />
+        </PersistentDeviceSelectionData>
+        <PersistentDeviceSelectionData>
+          <option name="api" value="36" />
+          <option name="brand" value="samsung" />
+          <option name="codename" value="pa3q" />
+          <option name="id" value="pa3q" />
+          <option name="labId" value="google" />
+          <option name="manufacturer" value="Samsung" />
+          <option name="name" value="Galaxy S25 Ultra" />
+          <option name="screenDensity" value="600" />
+          <option name="screenX" value="1440" />
+          <option name="screenY" value="3120" />
+        </PersistentDeviceSelectionData>
+        <PersistentDeviceSelectionData>
+          <option name="api" value="33" />
+          <option name="brand" value="google" />
+          <option name="codename" value="panther" />
+          <option name="id" value="panther" />
+          <option name="labId" value="google" />
+          <option name="manufacturer" value="Google" />
+          <option name="name" value="Pixel 7" />
+          <option name="screenDensity" value="420" />
+          <option name="screenX" value="1080" />
+          <option name="screenY" value="2400" />
+        </PersistentDeviceSelectionData>
+        <PersistentDeviceSelectionData>
+          <option name="api" value="35" />
+          <option name="brand" value="samsung" />
+          <option name="codename" value="psq" />
+          <option name="id" value="psq" />
+          <option name="labId" value="google" />
+          <option name="manufacturer" value="Samsung" />
+          <option name="name" value="Galaxy S25 Edge" />
+          <option name="screenDensity" value="450" />
+          <option name="screenX" value="1440" />
+          <option name="screenY" value="3120" />
+        </PersistentDeviceSelectionData>
+        <PersistentDeviceSelectionData>
+          <option name="api" value="34" />
+          <option name="brand" value="samsung" />
+          <option name="codename" value="q5q" />
+          <option name="id" value="q5q" />
+          <option name="labId" value="google" />
+          <option name="manufacturer" value="Samsung" />
+          <option name="name" value="Galaxy Z Fold5" />
+          <option name="screenDensity" value="420" />
+          <option name="screenX" value="1812" />
+          <option name="screenY" value="2176" />
+        </PersistentDeviceSelectionData>
+        <PersistentDeviceSelectionData>
+          <option name="api" value="34" />
+          <option name="brand" value="samsung" />
+          <option name="codename" value="q6q" />
+          <option name="id" value="q6q" />
+          <option name="labId" value="google" />
+          <option name="manufacturer" value="Samsung" />
+          <option name="name" value="Galaxy Z Fold6" />
+          <option name="screenDensity" value="420" />
+          <option name="screenX" value="1856" />
+          <option name="screenY" value="2160" />
+        </PersistentDeviceSelectionData>
+        <PersistentDeviceSelectionData>
+          <option name="api" value="30" />
+          <option name="brand" value="google" />
+          <option name="codename" value="r11" />
+          <option name="formFactor" value="Wear OS" />
+          <option name="id" value="r11" />
+          <option name="labId" value="google" />
+          <option name="manufacturer" value="Google" />
+          <option name="name" value="Pixel Watch" />
+          <option name="screenDensity" value="320" />
+          <option name="screenX" value="384" />
+          <option name="screenY" value="384" />
+          <option name="type" value="WEAR_OS" />
+        </PersistentDeviceSelectionData>
+        <PersistentDeviceSelectionData>
+          <option name="api" value="34" />
+          <option name="brand" value="samsung" />
+          <option name="codename" value="r11q" />
+          <option name="id" value="r11q" />
+          <option name="labId" value="google" />
+          <option name="manufacturer" value="Samsung" />
+          <option name="name" value="SM-S711U" />
+          <option name="screenDensity" value="450" />
+          <option name="screenX" value="1080" />
+          <option name="screenY" value="2340" />
+        </PersistentDeviceSelectionData>
+        <PersistentDeviceSelectionData>
+          <option name="api" value="36" />
+          <option name="brand" value="google" />
+          <option name="codename" value="rango" />
+          <option name="id" value="rango" />
+          <option name="labId" value="google" />
+          <option name="manufacturer" value="Google" />
+          <option name="name" value="Pixel 10 Pro Fold" />
+          <option name="screenDensity" value="390" />
+          <option name="screenX" value="2076" />
+          <option name="screenY" value="2152" />
+        </PersistentDeviceSelectionData>
+        <PersistentDeviceSelectionData>
+          <option name="api" value="30" />
+          <option name="brand" value="google" />
+          <option name="codename" value="redfin" />
+          <option name="id" value="redfin" />
+          <option name="labId" value="google" />
+          <option name="manufacturer" value="Google" />
+          <option name="name" value="Pixel 5" />
+          <option name="screenDensity" value="440" />
+          <option name="screenX" value="1080" />
+          <option name="screenY" value="2340" />
+        </PersistentDeviceSelectionData>
+        <PersistentDeviceSelectionData>
+          <option name="api" value="34" />
+          <option name="brand" value="google" />
+          <option name="codename" value="shiba" />
+          <option name="id" value="shiba" />
+          <option name="labId" value="google" />
+          <option name="manufacturer" value="Google" />
+          <option name="name" value="Pixel 8" />
+          <option name="screenDensity" value="420" />
+          <option name="screenX" value="1080" />
+          <option name="screenY" value="2400" />
+        </PersistentDeviceSelectionData>
+        <PersistentDeviceSelectionData>
+          <option name="api" value="34" />
+          <option name="brand" value="samsung" />
+          <option name="codename" value="t2q" />
+          <option name="id" value="t2q" />
+          <option name="labId" value="google" />
+          <option name="manufacturer" value="Samsung" />
+          <option name="name" value="Galaxy S21 Plus" />
+          <option name="screenDensity" value="394" />
+          <option name="screenX" value="1080" />
+          <option name="screenY" value="2400" />
+        </PersistentDeviceSelectionData>
+        <PersistentDeviceSelectionData>
+          <option name="api" value="33" />
+          <option name="brand" value="google" />
+          <option name="codename" value="tangorpro" />
+          <option name="formFactor" value="Tablet" />
+          <option name="id" value="tangorpro" />
+          <option name="labId" value="google" />
+          <option name="manufacturer" value="Google" />
+          <option name="name" value="Pixel Tablet" />
+          <option name="screenDensity" value="320" />
+          <option name="screenX" value="1600" />
+          <option name="screenY" value="2560" />
+        </PersistentDeviceSelectionData>
+        <PersistentDeviceSelectionData>
+          <option name="api" value="35" />
+          <option name="brand" value="google" />
+          <option name="codename" value="tegu" />
+          <option name="id" value="tegu" />
+          <option name="labId" value="google" />
+          <option name="manufacturer" value="Google" />
+          <option name="name" value="Pixel 9a" />
+          <option name="screenDensity" value="420" />
+          <option name="screenX" value="1080" />
+          <option name="screenY" value="2424" />
+        </PersistentDeviceSelectionData>
+        <PersistentDeviceSelectionData>
+          <option name="api" value="34" />
+          <option name="brand" value="google" />
+          <option name="codename" value="tokay" />
+          <option name="default" value="true" />
+          <option name="id" value="tokay" />
+          <option name="labId" value="google" />
+          <option name="manufacturer" value="Google" />
+          <option name="name" value="Pixel 9" />
+          <option name="screenDensity" value="420" />
+          <option name="screenX" value="1080" />
+          <option name="screenY" value="2424" />
+        </PersistentDeviceSelectionData>
+        <PersistentDeviceSelectionData>
+          <option name="api" value="35" />
+          <option name="brand" value="google" />
+          <option name="codename" value="tokay" />
+          <option name="default" value="true" />
+          <option name="id" value="tokay" />
+          <option name="labId" value="google" />
+          <option name="manufacturer" value="Google" />
+          <option name="name" value="Pixel 9" />
+          <option name="screenDensity" value="420" />
+          <option name="screenX" value="1080" />
+          <option name="screenY" value="2424" />
+        </PersistentDeviceSelectionData>
+        <PersistentDeviceSelectionData>
+          <option name="api" value="36" />
+          <option name="brand" value="google" />
+          <option name="codename" value="tokay" />
+          <option name="default" value="true" />
+          <option name="id" value="tokay" />
+          <option name="labId" value="google" />
+          <option name="manufacturer" value="Google" />
+          <option name="name" value="Pixel 9" />
+          <option name="screenDensity" value="420" />
+          <option name="screenX" value="1080" />
+          <option name="screenY" value="2424" />
+        </PersistentDeviceSelectionData>
+        <PersistentDeviceSelectionData>
+          <option name="api" value="34" />
+          <option name="brand" value="samsung" />
+          <option name="codename" value="xcover7" />
+          <option name="id" value="xcover7" />
+          <option name="labId" value="google" />
+          <option name="manufacturer" value="Samsung" />
+          <option name="name" value="SM-G556B" />
+          <option name="screenDensity" value="450" />
+          <option name="screenX" value="1080" />
+          <option name="screenY" value="2408" />
+        </PersistentDeviceSelectionData>
+      </list>
+    </option>
+  </component>
+</project>
+ + \ No newline at end of file diff --git a/exportToHTML/halo (3).js.html b/exportToHTML/halo (3).js.html new file mode 100644 index 0000000..b395f16 --- /dev/null +++ b/exportToHTML/halo (3).js.html @@ -0,0 +1,209 @@ + + +halo (3).js + + + + + +
+ +halo (3).js +
+
// HALO Meta-Oracle skeleton script.
+// This script defines the HALO core engine (dice roll), plugin hooks, and local storage.
+// The detailed symbol definitions (Tarot cards, runes, I Ching, etc.) and deeper synthesis logic
+// should be filled in later.
+
+(function() {
+  const STORAGE_KEY_PROFILE = 'halo_profile_v1';
+  const STORAGE_KEY_READINGS = 'halo_meta_readings_v1';
+  // Replace the placeholder with your actual API Gateway invoke URL once deployed.
+  const API_URL = 'https://YOUR_API_ID.execute-api.YOUR_REGION.amazonaws.com/readings';
+
+  // Axis definitions
+  const AXES = ['Body & Hardware','Mind & Narrative','Heart & Relationships','Domain & Magic'];
+  const VECTORS = ['Ingress','Grow','Stabilize','Release','Transmute','Witness'];
+  const TIMELINES = [
+    'Right now','Today','This week','This month','This season','This year',
+    '1–3 years','3–7 years','7–20 years','Lifetime','Generational','Meta'
+  ];
+  const ARCHETYPES = [
+    'The Fool','The Magician','The Healer','The Warrior','The Shield','The Hermit','The Lover',
+    'The Trickster','The Phoenix','The Architect','The Bridge','The Teacher','The Explorer',
+    'The Mirror','The Guardian','The Key','The Storm','The Weaver','The Sovereign'
+  ];
+
+  // Basic utility to roll a die
+  function rollDie(sides) {
+    return Math.floor(Math.random() * sides) + 1;
+  }
+
+  // Load/save profile
+  function loadProfile() {
+    try {
+      return JSON.parse(localStorage.getItem(STORAGE_KEY_PROFILE)) || {};
+    } catch (err) {
+      return {};
+    }
+  }
+
+  function saveProfile(profile) {
+    localStorage.setItem(STORAGE_KEY_PROFILE, JSON.stringify(profile));
+  }
+
+  // Load/save readings
+  function loadReadings() {
+    try {
+      return JSON.parse(localStorage.getItem(STORAGE_KEY_READINGS)) || [];
+    } catch (err) {
+      return [];
+    }
+  }
+
+  function saveReadings(readings) {
+    localStorage.setItem(STORAGE_KEY_READINGS, JSON.stringify(readings));
+  }
+
+  // Fire-and-forget sync to server if API_URL is set.
+  async function syncReadingToServer(reading) {
+    if (!API_URL || API_URL.includes('YOUR_API_ID')) {
+      return;
+    }
+    try {
+      const payload = {
+        userId: reading.user?.name || 'anonymous',
+        reading
+      };
+      await fetch(API_URL, {
+        method: 'POST',
+        headers: { 'Content-Type': 'application/json' },
+        body: JSON.stringify(payload)
+      });
+    } catch (err) {
+      console.warn('Sync failed', err);
+    }
+  }
+
+  // Create a new reading
+  function createReading(question, profile) {
+    const timestamp = new Date().toISOString();
+    const d4 = rollDie(4);
+    const d6 = rollDie(6);
+    const d12 = rollDie(12);
+    const d20 = rollDie(20);
+
+    const reading = {
+      id: `${Date.now().toString(36)}-${Math.random().toString(16).slice(2,8)}`,
+      question: question || '',
+      created_at: timestamp,
+      halo: {
+        axis: d4,
+        vector: d6,
+        timeline: d12,
+        archetype: d20,
+        dice: { d4, d6, d12, d20 }
+      },
+      user: profile,
+      // plugin results can be added here in the future
+    };
+
+    const readings = loadReadings();
+    readings.push(reading);
+    saveReadings(readings);
+    syncReadingToServer(reading);
+    return reading;
+  }
+
+  // Render functions
+  function renderHistory() {
+    const historyEl = document.getElementById('history');
+    const readings = loadReadings();
+    if (!readings.length) {
+      historyEl.innerHTML = '<p>No readings yet.</p>';
+      return;
+    }
+    const rows = readings.slice().reverse().map(reading => {
+      const date = new Date(reading.created_at).toLocaleString();
+      const axis = AXES[(reading.halo.axis - 1) % AXES.length];
+      const vector = VECTORS[(reading.halo.vector - 1) % VECTORS.length];
+      const timeline = TIMELINES[(reading.halo.timeline - 1) % TIMELINES.length];
+      const archetype = ARCHETYPES[(reading.halo.archetype - 1) % ARCHETYPES.length];
+      return `
+        <tr>
+          <td>${date}</td>
+          <td>${axis}</td>
+          <td>${vector}</td>
+          <td>${timeline}</td>
+          <td>${archetype}</td>
+        </tr>
+      `;
+    }).join('');
+    historyEl.innerHTML = `
+      <table>
+        <thead>
+          <tr><th>When</th><th>Axis</th><th>Vector</th><th>Timeline</th><th>Archetype</th></tr>
+        </thead>
+        <tbody>${rows}</tbody>
+      </table>
+    `;
+  }
+
+  function renderCurrentReading(reading) {
+    const container = document.getElementById('current-reading');
+    if (!reading) {
+      container.innerHTML = '';
+      return;
+    }
+    const halo = reading.halo;
+    container.innerHTML = `
+      <h3>HALO Roll</h3>
+      <p><strong>Axis:</strong> ${AXES[(halo.axis - 1) % AXES.length]} (${halo.dice.d4})</p>
+      <p><strong>Vector:</strong> ${VECTORS[(halo.vector - 1) % VECTORS.length]} (${halo.dice.d6})</p>
+      <p><strong>Timeline:</strong> ${TIMELINES[(halo.timeline - 1) % TIMELINES.length]} (${halo.dice.d12})</p>
+      <p><strong>Archetype:</strong> ${ARCHETYPES[(halo.archetype - 1) % ARCHETYPES.length]} (${halo.dice.d20})</p>
+      <p><em>This is a bare-bones skeleton. Interpretations and plugin hooks go here.</em></p>
+    `;
+  }
+
+  // Initialize UI
+  function init() {
+    const profile = loadProfile();
+    document.getElementById('profile-name').value = profile.name || '';
+    document.getElementById('profile-sun').value = profile.sun || '';
+    document.getElementById('profile-moon').value = profile.moon || '';
+    document.getElementById('profile-rising').value = profile.rising || '';
+
+    document.getElementById('save-profile').addEventListener('click', () => {
+      const p = {
+        name: document.getElementById('profile-name').value.trim(),
+        sun: document.getElementById('profile-sun').value.trim(),
+        moon: document.getElementById('profile-moon').value.trim(),
+        rising: document.getElementById('profile-rising').value.trim()
+      };
+      saveProfile(p);
+    });
+
+    document.getElementById('roll-btn').addEventListener('click', () => {
+      const q = document.getElementById('question').value.trim();
+      const p = loadProfile();
+      const r = createReading(q, p);
+      renderCurrentReading(r);
+      renderHistory();
+    });
+
+    renderHistory();
+  }
+
+  // run
+  init();
+})();
+
+ + \ No newline at end of file diff --git a/exportToHTML/halo.js.html b/exportToHTML/halo.js.html new file mode 100644 index 0000000..1d2872c --- /dev/null +++ b/exportToHTML/halo.js.html @@ -0,0 +1,209 @@ + + +halo.js + + + + + +
+ +halo.js +
+
// HALO Meta-Oracle skeleton script.
+// This script defines the HALO core engine (dice roll), plugin hooks, and local storage.
+// The detailed symbol definitions (Tarot cards, runes, I Ching, etc.) and deeper synthesis logic
+// should be filled in later.
+
+(function() {
+  const STORAGE_KEY_PROFILE = 'halo_profile_v1';
+  const STORAGE_KEY_READINGS = 'halo_meta_readings_v1';
+  // Replace the placeholder with your actual API Gateway invoke URL once deployed.
+  const API_URL = 'https://YOUR_API_ID.execute-api.YOUR_REGION.amazonaws.com/readings';
+
+  // Axis definitions
+  const AXES = ['Body & Hardware','Mind & Narrative','Heart & Relationships','Domain & Magic'];
+  const VECTORS = ['Ingress','Grow','Stabilize','Release','Transmute','Witness'];
+  const TIMELINES = [
+    'Right now','Today','This week','This month','This season','This year',
+    '1–3 years','3–7 years','7–20 years','Lifetime','Generational','Meta'
+  ];
+  const ARCHETYPES = [
+    'The Fool','The Magician','The Healer','The Warrior','The Shield','The Hermit','The Lover',
+    'The Trickster','The Phoenix','The Architect','The Bridge','The Teacher','The Explorer',
+    'The Mirror','The Guardian','The Key','The Storm','The Weaver','The Sovereign'
+  ];
+
+  // Basic utility to roll a die
+  function rollDie(sides) {
+    return Math.floor(Math.random() * sides) + 1;
+  }
+
+  // Load/save profile
+  function loadProfile() {
+    try {
+      return JSON.parse(localStorage.getItem(STORAGE_KEY_PROFILE)) || {};
+    } catch (err) {
+      return {};
+    }
+  }
+
+  function saveProfile(profile) {
+    localStorage.setItem(STORAGE_KEY_PROFILE, JSON.stringify(profile));
+  }
+
+  // Load/save readings
+  function loadReadings() {
+    try {
+      return JSON.parse(localStorage.getItem(STORAGE_KEY_READINGS)) || [];
+    } catch (err) {
+      return [];
+    }
+  }
+
+  function saveReadings(readings) {
+    localStorage.setItem(STORAGE_KEY_READINGS, JSON.stringify(readings));
+  }
+
+  // Fire-and-forget sync to server if API_URL is set.
+  async function syncReadingToServer(reading) {
+    if (!API_URL || API_URL.includes('YOUR_API_ID')) {
+      return;
+    }
+    try {
+      const payload = {
+        userId: reading.user?.name || 'anonymous',
+        reading
+      };
+      await fetch(API_URL, {
+        method: 'POST',
+        headers: { 'Content-Type': 'application/json' },
+        body: JSON.stringify(payload)
+      });
+    } catch (err) {
+      console.warn('Sync failed', err);
+    }
+  }
+
+  // Create a new reading
+  function createReading(question, profile) {
+    const timestamp = new Date().toISOString();
+    const d4 = rollDie(4);
+    const d6 = rollDie(6);
+    const d12 = rollDie(12);
+    const d20 = rollDie(20);
+
+    const reading = {
+      id: `${Date.now().toString(36)}-${Math.random().toString(16).slice(2,8)}`,
+      question: question || '',
+      created_at: timestamp,
+      halo: {
+        axis: d4,
+        vector: d6,
+        timeline: d12,
+        archetype: d20,
+        dice: { d4, d6, d12, d20 }
+      },
+      user: profile,
+      // plugin results can be added here in the future
+    };
+
+    const readings = loadReadings();
+    readings.push(reading);
+    saveReadings(readings);
+    syncReadingToServer(reading);
+    return reading;
+  }
+
+  // Render functions
+  function renderHistory() {
+    const historyEl = document.getElementById('history');
+    const readings = loadReadings();
+    if (!readings.length) {
+      historyEl.innerHTML = '<p>No readings yet.</p>';
+      return;
+    }
+    const rows = readings.slice().reverse().map(reading => {
+      const date = new Date(reading.created_at).toLocaleString();
+      const axis = AXES[(reading.halo.axis - 1) % AXES.length];
+      const vector = VECTORS[(reading.halo.vector - 1) % VECTORS.length];
+      const timeline = TIMELINES[(reading.halo.timeline - 1) % TIMELINES.length];
+      const archetype = ARCHETYPES[(reading.halo.archetype - 1) % ARCHETYPES.length];
+      return `
+        <tr>
+          <td>${date}</td>
+          <td>${axis}</td>
+          <td>${vector}</td>
+          <td>${timeline}</td>
+          <td>${archetype}</td>
+        </tr>
+      `;
+    }).join('');
+    historyEl.innerHTML = `
+      <table>
+        <thead>
+          <tr><th>When</th><th>Axis</th><th>Vector</th><th>Timeline</th><th>Archetype</th></tr>
+        </thead>
+        <tbody>${rows}</tbody>
+      </table>
+    `;
+  }
+
+  function renderCurrentReading(reading) {
+    const container = document.getElementById('current-reading');
+    if (!reading) {
+      container.innerHTML = '';
+      return;
+    }
+    const halo = reading.halo;
+    container.innerHTML = `
+      <h3>HALO Roll</h3>
+      <p><strong>Axis:</strong> ${AXES[(halo.axis - 1) % AXES.length]} (${halo.dice.d4})</p>
+      <p><strong>Vector:</strong> ${VECTORS[(halo.vector - 1) % VECTORS.length]} (${halo.dice.d6})</p>
+      <p><strong>Timeline:</strong> ${TIMELINES[(halo.timeline - 1) % TIMELINES.length]} (${halo.dice.d12})</p>
+      <p><strong>Archetype:</strong> ${ARCHETYPES[(halo.archetype - 1) % ARCHETYPES.length]} (${halo.dice.d20})</p>
+      <p><em>This is a bare-bones skeleton. Interpretations and plugin hooks go here.</em></p>
+    `;
+  }
+
+  // Initialize UI
+  function init() {
+    const profile = loadProfile();
+    document.getElementById('profile-name').value = profile.name || '';
+    document.getElementById('profile-sun').value = profile.sun || '';
+    document.getElementById('profile-moon').value = profile.moon || '';
+    document.getElementById('profile-rising').value = profile.rising || '';
+
+    document.getElementById('save-profile').addEventListener('click', () => {
+      const p = {
+        name: document.getElementById('profile-name').value.trim(),
+        sun: document.getElementById('profile-sun').value.trim(),
+        moon: document.getElementById('profile-moon').value.trim(),
+        rising: document.getElementById('profile-rising').value.trim()
+      };
+      saveProfile(p);
+    });
+
+    document.getElementById('roll-btn').addEventListener('click', () => {
+      const q = document.getElementById('question').value.trim();
+      const p = loadProfile();
+      const r = createReading(q, p);
+      renderCurrentReading(r);
+      renderHistory();
+    });
+
+    renderHistory();
+  }
+
+  // run
+  init();
+})();
+
+ + \ No newline at end of file diff --git a/exportToHTML/index (3) (1).html.html b/exportToHTML/index (3) (1).html.html new file mode 100644 index 0000000..d49faa1 --- /dev/null +++ b/exportToHTML/index (3) (1).html.html @@ -0,0 +1,287 @@ + + +index (3) (1).html + + + + + +
+ +index (3) (1).html +
+
<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8" />
+  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+  <title>HALO Meta-Oracle – Local Prototype</title>
+  <style>
+    body {
+      font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
+        sans-serif;
+      margin: 0;
+      padding: 0;
+      background: #02030a;
+      color: #dde3ff;
+    }
+
+    .app-shell {
+      max-width: 960px;
+      margin: 0 auto;
+      padding: 1.25rem;
+    }
+
+    h1 {
+      font-size: 1.6rem;
+      margin: 0 0 0.75rem;
+    }
+
+    h2 {
+      font-size: 1.2rem;
+      margin-top: 1.5rem;
+      margin-bottom: 0.5rem;
+    }
+
+    .card {
+      background: rgba(6, 10, 30, 0.98);
+      border-radius: 0.9rem;
+      padding: 1rem 1.1rem;
+      margin-bottom: 1rem;
+      box-shadow: 0 10px 28px rgba(0, 0, 0, 0.6);
+      border: 1px solid rgba(60, 100, 180, 0.35);
+    }
+
+    label {
+      display: block;
+      font-size: 0.85rem;
+      margin-bottom: 0.15rem;
+      opacity: 0.9;
+    }
+
+    input[type="text"],
+    textarea,
+    select {
+      width: 100%;
+      box-sizing: border-box;
+      border-radius: 0.6rem;
+      border: 1px solid rgba(132, 183, 255, 0.4);
+      padding: 0.55rem 0.65rem;
+      font-size: 0.95rem;
+      background: rgba(4, 9, 29, 0.96);
+      color: #f7fbff;
+      outline: none;
+    }
+
+    textarea {
+      min-height: 70px;
+      resize: vertical;
+    }
+
+    input::placeholder,
+    textarea::placeholder {
+      color: rgba(199, 214, 255, 0.6);
+    }
+
+    .row {
+      display: flex;
+      flex-wrap: wrap;
+      gap: 0.75rem;
+    }
+
+    .row > .col {
+      flex: 1 1 150px;
+    }
+
+    button {
+      border-radius: 999px;
+      border: none;
+      padding: 0.55rem 1.2rem;
+      font-size: 0.95rem;
+      font-weight: 600;
+      letter-spacing: 0.02em;
+      background: linear-gradient(120deg, #355a9f, #5c4fa8, #8a4f9a);
+      color: #f3f3ff;
+      cursor: pointer;
+      margin-top: 0.5rem;
+    }
+
+    button:disabled {
+      opacity: 0.5;
+      cursor: default;
+    }
+
+    small {
+      opacity: 0.85;
+      font-size: 0.78rem;
+    }
+
+    .pill {
+      display: inline-flex;
+      align-items: center;
+      padding: 0.15rem 0.65rem;
+      border-radius: 999px;
+      border: 1px solid rgba(171, 208, 255, 0.4);
+      font-size: 0.72rem;
+      margin-right: 0.35rem;
+      margin-bottom: 0.2rem;
+      text-transform: uppercase;
+      letter-spacing: 0.06em;
+    }
+
+    .reading-grid {
+      display: grid;
+      grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
+      gap: 0.75rem;
+      margin-top: 0.7rem;
+    }
+
+    .reading-block {
+      border-radius: 0.75rem;
+      border: 1px dashed rgba(152, 196, 255, 0.5);
+      padding: 0.6rem 0.7rem;
+      background: rgba(9, 20, 52, 0.8);
+      font-size: 0.86rem;
+    }
+
+    .reading-block h3 {
+      font-size: 0.9rem;
+      margin: 0 0 0.3rem;
+      text-transform: uppercase;
+      letter-spacing: 0.08em;
+      opacity: 0.9;
+    }
+
+    .mono {
+      font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
+        "Liberation Mono", "Courier New", monospace;
+      font-size: 0.8rem;
+    }
+
+    details {
+      margin-top: 0.4rem;
+    }
+
+    details summary {
+      cursor: pointer;
+      font-size: 0.82rem;
+      opacity: 0.95;
+    }
+
+    table {
+      width: 100%;
+      border-collapse: collapse;
+      font-size: 0.8rem;
+      margin-top: 0.5rem;
+    }
+
+    th,
+    td {
+      border-bottom: 1px solid rgba(106, 151, 230, 0.35);
+      padding: 0.35rem 0.25rem;
+      text-align: left;
+    }
+
+    th {
+      font-weight: 600;
+      opacity: 0.9;
+    }
+
+    .muted {
+      opacity: 0.7;
+      font-size: 0.8rem;
+    }
+
+    .timestamp {
+      font-size: 0.78rem;
+      opacity: 0.7;
+    }
+
+    @media (max-width: 640px) {
+      .app-shell {
+        padding: 0.8rem;
+      }
+    }
+  </style>
+</head>
+<body>
+  <div class="app-shell">
+    <h1>HALO Meta-Oracle – Local Prototype</h1>
+
+    <div class="card">
+      <h2>1. Profile</h2>
+      <p class="muted">Optional but helpful—used for tags and flavor text.</p>
+      <div class="row">
+        <div class="col">
+          <label for="profile-name">Name / handle</label>
+          <input id="profile-name" type="text" placeholder="Player Zero, Apocky, etc." />
+        </div>
+        <div class="col">
+          <label for="profile-sun">Sun sign</label>
+          <input id="profile-sun" type="text" placeholder="Taurus, Leo, etc." />
+        </div>
+        <div class="col">
+          <label for="profile-moon">Moon sign</label>
+          <input id="profile-moon" type="text" placeholder="Gemini, Pisces, etc." />
+        </div>
+        <div class="col">
+          <label for="profile-rising">Rising / Ascendant</label>
+          <input id="profile-rising" type="text" placeholder="Scorpio Rising, etc." />
+        </div>
+      </div>
+      <button id="save-profile">Save profile locally</button>
+      <small class="muted">Stored only in this browser using localStorage.</small>
+    </div>
+
+    <div class="card">
+      <h2>2. Ask HALO</h2>
+      <label for="question">Question or focus</label>
+      <textarea id="question" placeholder="What do I most need to understand about…?"></textarea>
+      <button id="roll-btn">Roll the Meta-Oracle</button>
+      <div id="current-reading" style="margin-top:0.75rem;"></div>
+    </div>
+
+    <div class="card">
+      <h2>3. Past Readings (this device)</h2>
+      <p class="muted">
+        Local Spiritual Blockchain prototype – readings are chained with simple hashes.
+      </p>
+      <div id="history"></div>
+    </div>
+
+    <!-- Co‑op Session card: allows generating shareable sessions -->
+    <div class="card">
+      <h2>4. Co‑op Session</h2>
+      <p class="muted">Share your Labyrinth session with a friend.</p>
+      <button id="start-coop">Start Co‑op Session</button>
+      <button id="copy-coop" style="display:none;">Copy Link</button>
+      <input
+        id="coop-link"
+        type="text"
+        readonly
+        placeholder="Your co‑op link will appear here"
+        style="width:100%;box-sizing:border-box;margin-top:0.5rem;"
+      />
+    </div>
+
+    <!-- Support card: optional tip jar for those who wish to support the project -->
+    <div class="card">
+      <h2>5. Support the Oracle</h2>
+      <p class="muted">Optional – helps keep the project going.</p>
+      <button id="support-button">☕ Support the Oracle</button>
+      <span id="support-badge" class="pill" style="display:none; margin-left:0.5rem;">★ Supporter Mode</span>
+    </div>
+  </div>
+
+  <script src="halo.js"></script>
+</body>
+</html>
+
+ + \ No newline at end of file diff --git a/exportToHTML/index (3).html.html b/exportToHTML/index (3).html.html new file mode 100644 index 0000000..0160ccf --- /dev/null +++ b/exportToHTML/index (3).html.html @@ -0,0 +1,263 @@ + + +index (3).html + + + + + +
+ +index (3).html +
+
<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8" />
+  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+  <title>HALO Meta-Oracle – Local Prototype</title>
+  <style>
+    body {
+      font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
+        sans-serif;
+      margin: 0;
+      padding: 0;
+      background: #02030a;
+      color: #dde3ff;
+    }
+
+    .app-shell {
+      max-width: 960px;
+      margin: 0 auto;
+      padding: 1.25rem;
+    }
+
+    h1 {
+      font-size: 1.6rem;
+      margin: 0 0 0.75rem;
+    }
+
+    h2 {
+      font-size: 1.2rem;
+      margin-top: 1.5rem;
+      margin-bottom: 0.5rem;
+    }
+
+    .card {
+      background: rgba(6, 10, 30, 0.98);
+      border-radius: 0.9rem;
+      padding: 1rem 1.1rem;
+      margin-bottom: 1rem;
+      box-shadow: 0 10px 28px rgba(0, 0, 0, 0.6);
+      border: 1px solid rgba(60, 100, 180, 0.35);
+    }
+
+    label {
+      display: block;
+      font-size: 0.85rem;
+      margin-bottom: 0.15rem;
+      opacity: 0.9;
+    }
+
+    input[type="text"],
+    textarea,
+    select {
+      width: 100%;
+      box-sizing: border-box;
+      border-radius: 0.6rem;
+      border: 1px solid rgba(132, 183, 255, 0.4);
+      padding: 0.55rem 0.65rem;
+      font-size: 0.95rem;
+      background: rgba(4, 9, 29, 0.96);
+      color: #f7fbff;
+      outline: none;
+    }
+
+    textarea {
+      min-height: 70px;
+      resize: vertical;
+    }
+
+    input::placeholder,
+    textarea::placeholder {
+      color: rgba(199, 214, 255, 0.6);
+    }
+
+    .row {
+      display: flex;
+      flex-wrap: wrap;
+      gap: 0.75rem;
+    }
+
+    .row > .col {
+      flex: 1 1 150px;
+    }
+
+    button {
+      border-radius: 999px;
+      border: none;
+      padding: 0.55rem 1.2rem;
+      font-size: 0.95rem;
+      font-weight: 600;
+      letter-spacing: 0.02em;
+      background: linear-gradient(120deg, #355a9f, #5c4fa8, #8a4f9a);
+      color: #f3f3ff;
+      cursor: pointer;
+      margin-top: 0.5rem;
+    }
+
+    button:disabled {
+      opacity: 0.5;
+      cursor: default;
+    }
+
+    small {
+      opacity: 0.85;
+      font-size: 0.78rem;
+    }
+
+    .pill {
+      display: inline-flex;
+      align-items: center;
+      padding: 0.15rem 0.65rem;
+      border-radius: 999px;
+      border: 1px solid rgba(171, 208, 255, 0.4);
+      font-size: 0.72rem;
+      margin-right: 0.35rem;
+      margin-bottom: 0.2rem;
+      text-transform: uppercase;
+      letter-spacing: 0.06em;
+    }
+
+    .reading-grid {
+      display: grid;
+      grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
+      gap: 0.75rem;
+      margin-top: 0.7rem;
+    }
+
+    .reading-block {
+      border-radius: 0.75rem;
+      border: 1px dashed rgba(152, 196, 255, 0.5);
+      padding: 0.6rem 0.7rem;
+      background: rgba(9, 20, 52, 0.8);
+      font-size: 0.86rem;
+    }
+
+    .reading-block h3 {
+      font-size: 0.9rem;
+      margin: 0 0 0.3rem;
+      text-transform: uppercase;
+      letter-spacing: 0.08em;
+      opacity: 0.9;
+    }
+
+    .mono {
+      font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
+        "Liberation Mono", "Courier New", monospace;
+      font-size: 0.8rem;
+    }
+
+    details {
+      margin-top: 0.4rem;
+    }
+
+    details summary {
+      cursor: pointer;
+      font-size: 0.82rem;
+      opacity: 0.95;
+    }
+
+    table {
+      width: 100%;
+      border-collapse: collapse;
+      font-size: 0.8rem;
+      margin-top: 0.5rem;
+    }
+
+    th,
+    td {
+      border-bottom: 1px solid rgba(106, 151, 230, 0.35);
+      padding: 0.35rem 0.25rem;
+      text-align: left;
+    }
+
+    th {
+      font-weight: 600;
+      opacity: 0.9;
+    }
+
+    .muted {
+      opacity: 0.7;
+      font-size: 0.8rem;
+    }
+
+    .timestamp {
+      font-size: 0.78rem;
+      opacity: 0.7;
+    }
+
+    @media (max-width: 640px) {
+      .app-shell {
+        padding: 0.8rem;
+      }
+    }
+  </style>
+</head>
+<body>
+  <div class="app-shell">
+    <h1>HALO Meta-Oracle – Local Prototype</h1>
+
+    <div class="card">
+      <h2>1. Profile</h2>
+      <p class="muted">Optional but helpful—used for tags and flavor text.</p>
+      <div class="row">
+        <div class="col">
+          <label for="profile-name">Name / handle</label>
+          <input id="profile-name" type="text" placeholder="Player Zero, Apocky, etc." />
+        </div>
+        <div class="col">
+          <label for="profile-sun">Sun sign</label>
+          <input id="profile-sun" type="text" placeholder="Taurus, Leo, etc." />
+        </div>
+        <div class="col">
+          <label for="profile-moon">Moon sign</label>
+          <input id="profile-moon" type="text" placeholder="Gemini, Pisces, etc." />
+        </div>
+        <div class="col">
+          <label for="profile-rising">Rising / Ascendant</label>
+          <input id="profile-rising" type="text" placeholder="Scorpio Rising, etc." />
+        </div>
+      </div>
+      <button id="save-profile">Save profile locally</button>
+      <small class="muted">Stored only in this browser using localStorage.</small>
+    </div>
+
+    <div class="card">
+      <h2>2. Ask HALO</h2>
+      <label for="question">Question or focus</label>
+      <textarea id="question" placeholder="What do I most need to understand about…?"></textarea>
+      <button id="roll-btn">Roll the Meta-Oracle</button>
+      <div id="current-reading" style="margin-top:0.75rem;"></div>
+    </div>
+
+    <div class="card">
+      <h2>3. Past Readings (this device)</h2>
+      <p class="muted">
+        Local Spiritual Blockchain prototype – readings are chained with simple hashes.
+      </p>
+      <div id="history"></div>
+    </div>
+  </div>
+
+  <script src="halo.js"></script>
+</body>
+</html>
+
+ + \ No newline at end of file diff --git a/exportToHTML/index.html b/exportToHTML/index.html new file mode 100644 index 0000000..e12b0d7 --- /dev/null +++ b/exportToHTML/index.html @@ -0,0 +1 @@ +C:\Users\Apocky\Documents\GitHub\Collab_Reposit\halo_lambdaindex.js
\ No newline at end of file diff --git a/exportToHTML/index.html.html b/exportToHTML/index.html.html new file mode 100644 index 0000000..6bdc332 --- /dev/null +++ b/exportToHTML/index.html.html @@ -0,0 +1,263 @@ + + +index.html + + + + + +
+ +index.html +
+
<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8" />
+  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+  <title>HALO Meta-Oracle – Local Prototype</title>
+  <style>
+    body {
+      font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
+        sans-serif;
+      margin: 0;
+      padding: 0;
+      background: #02030a;
+      color: #dde3ff;
+    }
+
+    .app-shell {
+      max-width: 960px;
+      margin: 0 auto;
+      padding: 1.25rem;
+    }
+
+    h1 {
+      font-size: 1.6rem;
+      margin: 0 0 0.75rem;
+    }
+
+    h2 {
+      font-size: 1.2rem;
+      margin-top: 1.5rem;
+      margin-bottom: 0.5rem;
+    }
+
+    .card {
+      background: rgba(6, 10, 30, 0.98);
+      border-radius: 0.9rem;
+      padding: 1rem 1.1rem;
+      margin-bottom: 1rem;
+      box-shadow: 0 10px 28px rgba(0, 0, 0, 0.6);
+      border: 1px solid rgba(60, 100, 180, 0.35);
+    }
+
+    label {
+      display: block;
+      font-size: 0.85rem;
+      margin-bottom: 0.15rem;
+      opacity: 0.9;
+    }
+
+    input[type="text"],
+    textarea,
+    select {
+      width: 100%;
+      box-sizing: border-box;
+      border-radius: 0.6rem;
+      border: 1px solid rgba(132, 183, 255, 0.4);
+      padding: 0.55rem 0.65rem;
+      font-size: 0.95rem;
+      background: rgba(4, 9, 29, 0.96);
+      color: #f7fbff;
+      outline: none;
+    }
+
+    textarea {
+      min-height: 70px;
+      resize: vertical;
+    }
+
+    input::placeholder,
+    textarea::placeholder {
+      color: rgba(199, 214, 255, 0.6);
+    }
+
+    .row {
+      display: flex;
+      flex-wrap: wrap;
+      gap: 0.75rem;
+    }
+
+    .row > .col {
+      flex: 1 1 150px;
+    }
+
+    button {
+      border-radius: 999px;
+      border: none;
+      padding: 0.55rem 1.2rem;
+      font-size: 0.95rem;
+      font-weight: 600;
+      letter-spacing: 0.02em;
+      background: linear-gradient(120deg, #355a9f, #5c4fa8, #8a4f9a);
+      color: #f3f3ff;
+      cursor: pointer;
+      margin-top: 0.5rem;
+    }
+
+    button:disabled {
+      opacity: 0.5;
+      cursor: default;
+    }
+
+    small {
+      opacity: 0.85;
+      font-size: 0.78rem;
+    }
+
+    .pill {
+      display: inline-flex;
+      align-items: center;
+      padding: 0.15rem 0.65rem;
+      border-radius: 999px;
+      border: 1px solid rgba(171, 208, 255, 0.4);
+      font-size: 0.72rem;
+      margin-right: 0.35rem;
+      margin-bottom: 0.2rem;
+      text-transform: uppercase;
+      letter-spacing: 0.06em;
+    }
+
+    .reading-grid {
+      display: grid;
+      grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
+      gap: 0.75rem;
+      margin-top: 0.7rem;
+    }
+
+    .reading-block {
+      border-radius: 0.75rem;
+      border: 1px dashed rgba(152, 196, 255, 0.5);
+      padding: 0.6rem 0.7rem;
+      background: rgba(9, 20, 52, 0.8);
+      font-size: 0.86rem;
+    }
+
+    .reading-block h3 {
+      font-size: 0.9rem;
+      margin: 0 0 0.3rem;
+      text-transform: uppercase;
+      letter-spacing: 0.08em;
+      opacity: 0.9;
+    }
+
+    .mono {
+      font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
+        "Liberation Mono", "Courier New", monospace;
+      font-size: 0.8rem;
+    }
+
+    details {
+      margin-top: 0.4rem;
+    }
+
+    details summary {
+      cursor: pointer;
+      font-size: 0.82rem;
+      opacity: 0.95;
+    }
+
+    table {
+      width: 100%;
+      border-collapse: collapse;
+      font-size: 0.8rem;
+      margin-top: 0.5rem;
+    }
+
+    th,
+    td {
+      border-bottom: 1px solid rgba(106, 151, 230, 0.35);
+      padding: 0.35rem 0.25rem;
+      text-align: left;
+    }
+
+    th {
+      font-weight: 600;
+      opacity: 0.9;
+    }
+
+    .muted {
+      opacity: 0.7;
+      font-size: 0.8rem;
+    }
+
+    .timestamp {
+      font-size: 0.78rem;
+      opacity: 0.7;
+    }
+
+    @media (max-width: 640px) {
+      .app-shell {
+        padding: 0.8rem;
+      }
+    }
+  </style>
+</head>
+<body>
+  <div class="app-shell">
+    <h1>HALO Meta-Oracle – Local Prototype</h1>
+
+    <div class="card">
+      <h2>1. Profile</h2>
+      <p class="muted">Optional but helpful—used for tags and flavor text.</p>
+      <div class="row">
+        <div class="col">
+          <label for="profile-name">Name / handle</label>
+          <input id="profile-name" type="text" placeholder="Player Zero, Apocky, etc." />
+        </div>
+        <div class="col">
+          <label for="profile-sun">Sun sign</label>
+          <input id="profile-sun" type="text" placeholder="Taurus, Leo, etc." />
+        </div>
+        <div class="col">
+          <label for="profile-moon">Moon sign</label>
+          <input id="profile-moon" type="text" placeholder="Gemini, Pisces, etc." />
+        </div>
+        <div class="col">
+          <label for="profile-rising">Rising / Ascendant</label>
+          <input id="profile-rising" type="text" placeholder="Scorpio Rising, etc." />
+        </div>
+      </div>
+      <button id="save-profile">Save profile locally</button>
+      <small class="muted">Stored only in this browser using localStorage.</small>
+    </div>
+
+    <div class="card">
+      <h2>2. Ask HALO</h2>
+      <label for="question">Question or focus</label>
+      <textarea id="question" placeholder="What do I most need to understand about…?"></textarea>
+      <button id="roll-btn">Roll the Meta-Oracle</button>
+      <div id="current-reading" style="margin-top:0.75rem;"></div>
+    </div>
+
+    <div class="card">
+      <h2>3. Past Readings (this device)</h2>
+      <p class="muted">
+        Local Spiritual Blockchain prototype – readings are chained with simple hashes.
+      </p>
+      <div id="history"></div>
+    </div>
+  </div>
+
+  <script src="halo.js"></script>
+</body>
+</html>
+
+ + \ No newline at end of file diff --git a/exportToHTML/index.js.html b/exportToHTML/index.js.html new file mode 100644 index 0000000..66c3bff --- /dev/null +++ b/exportToHTML/index.js.html @@ -0,0 +1,103 @@ + + +index.js + + + + + +
+ +index.js +
+
// HALO Oracle Serverless Lambda handler
+// This Lambda function provides two API methods via API Gateway:
+// - GET  /readings : Returns all reading items from DynamoDB.
+// - POST /readings : Stores a new reading item in DynamoDB.
+
+// The function uses the AWS SDK v3 for DynamoDB with the DocumentClient
+// to handle marshalling between native JavaScript types and DynamoDB item
+// structures automatically. The target DynamoDB table name can be
+// configured via the TABLE_NAME environment variable; otherwise it
+// defaults to "HaloReadings".
+
+const { DynamoDBClient } = require("@aws-sdk/client-dynamodb");
+const {
+  DynamoDBDocumentClient,
+  ScanCommand,
+  PutCommand,
+} = require("@aws-sdk/lib-dynamodb");
+
+// Create low‑level and Document clients. The region and credentials
+// are resolved automatically from the Lambda execution environment.
+const ddbClient = new DynamoDBClient({});
+const ddbDocClient = DynamoDBDocumentClient.from(ddbClient);
+
+exports.handler = async (event) => {
+  const tableName = process.env.TABLE_NAME || "HaloReadings";
+
+  try {
+    // Inspect the HTTP method from the event (when invoked via API Gateway)
+    const method = (event.httpMethod || event.method || "GET").toUpperCase();
+
+    // Handle GET requests to list all readings
+    if (method === "GET") {
+      const data = await ddbDocClient.send(
+        new ScanCommand({ TableName: tableName })
+      );
+      return {
+        statusCode: 200,
+        headers: { "Content-Type": "application/json" },
+        body: JSON.stringify(data.Items || []),
+      };
+    }
+
+    // Handle POST requests to create a new reading entry
+    if (method === "POST") {
+      // Parse the JSON body from the request
+      let item;
+      try {
+        item = JSON.parse(event.body || "{}");
+      } catch (parseErr) {
+        return {
+          statusCode: 400,
+          headers: { "Content-Type": "application/json" },
+          body: JSON.stringify({ message: "Invalid JSON payload" }),
+        };
+      }
+
+      // Write the item to DynamoDB
+      await ddbDocClient.send(
+        new PutCommand({ TableName: tableName, Item: item })
+      );
+      return {
+        statusCode: 201,
+        headers: { "Content-Type": "application/json" },
+        body: JSON.stringify({ message: "Reading stored", item }),
+      };
+    }
+
+    // Method not allowed
+    return {
+      statusCode: 405,
+      headers: { "Content-Type": "application/json" },
+      body: JSON.stringify({ message: "Method not allowed" }),
+    };
+  } catch (err) {
+    console.error("Error handling request", err);
+    return {
+      statusCode: 500,
+      headers: { "Content-Type": "application/json" },
+      body: JSON.stringify({ message: err.message || "Internal server error" }),
+    };
+  }
+};
+
+ + \ No newline at end of file diff --git a/exportToHTML/markdown.xml.html b/exportToHTML/markdown.xml.html new file mode 100644 index 0000000..de454c0 --- /dev/null +++ b/exportToHTML/markdown.xml.html @@ -0,0 +1,27 @@ + + +markdown.xml + + + + + +
+ +markdown.xml +
+
<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="MarkdownSettings">
+    <option name="previewPanelProviderInfo">
+      <ProviderInfo name="Compose (experimental)" className="com.intellij.markdown.compose.preview.ComposePanelProvider" />
+    </option>
+  </component>
+</project>
+ + \ No newline at end of file diff --git a/exportToHTML/misc.xml.html b/exportToHTML/misc.xml.html new file mode 100644 index 0000000..be7e096 --- /dev/null +++ b/exportToHTML/misc.xml.html @@ -0,0 +1,25 @@ + + +misc.xml + + + + + +
+ +misc.xml +
+
<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK">
+    <output url="file://$PROJECT_DIR$/out" />
+  </component>
+</project>
+ + \ No newline at end of file diff --git a/exportToHTML/modules.xml.html b/exportToHTML/modules.xml.html new file mode 100644 index 0000000..ccbe0eb --- /dev/null +++ b/exportToHTML/modules.xml.html @@ -0,0 +1,27 @@ + + +modules.xml + + + + + +
+ +modules.xml +
+
<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ProjectModuleManager">
+    <modules>
+      <module fileurl="file://$PROJECT_DIR$/.idea/Collab_Reposit.iml" filepath="$PROJECT_DIR$/.idea/Collab_Reposit.iml" />
+    </modules>
+  </component>
+</project>
+ + \ No newline at end of file diff --git a/exportToHTML/vcs.xml.html b/exportToHTML/vcs.xml.html new file mode 100644 index 0000000..6a0414a --- /dev/null +++ b/exportToHTML/vcs.xml.html @@ -0,0 +1,25 @@ + + +vcs.xml + + + + + +
+ +vcs.xml +
+
<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="VcsDirectoryMappings">
+    <mapping directory="" vcs="Git" />
+  </component>
+</project>
+ + \ No newline at end of file diff --git a/exportToHTML/workspace.xml.html b/exportToHTML/workspace.xml.html new file mode 100644 index 0000000..798a646 --- /dev/null +++ b/exportToHTML/workspace.xml.html @@ -0,0 +1,70 @@ + + +workspace.xml + + + + + +
+ +workspace.xml +
+
<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="AutoImportSettings">
+    <option name="autoReloadType" value="NONE" />
+  </component>
+  <component name="ChangeListManager">
+    <list default="true" id="97a950fa-db8c-4e93-8516-864b9f7aa068" name="Changes" comment="" />
+    <option name="SHOW_DIALOG" value="false" />
+    <option name="HIGHLIGHT_CONFLICTS" value="true" />
+    <option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
+    <option name="LAST_RESOLUTION" value="IGNORE" />
+  </component>
+  <component name="ClangdSettings">
+    <option name="formatViaClangd" value="false" />
+  </component>
+  <component name="Git.Settings">
+    <option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
+  </component>
+  <component name="ProjectColorInfo"><![CDATA[{ 
+  "associatedIndex": 0 
+}]]></component>
+  <component name="ProjectId" id="36a2yNK1rKNiJ4Kwcf1mJarKpvq" />
+  <component name="ProjectViewState">
+    <option name="hideEmptyMiddlePackages" value="true" />
+    <option name="showLibraryContents" value="true" />
+  </component>
+  <component name="PropertiesComponent"><![CDATA[{ 
+  "keyToString": { 
+    "ModuleVcsDetector.initialDetectionPerformed": "true", 
+    "RunOnceActivity.ShowReadmeOnStart": "true", 
+    "RunOnceActivity.cidr.known.project.marker": "true", 
+    "RunOnceActivity.git.unshallow": "true", 
+    "RunOnceActivity.readMode.enableVisualFormatting": "true", 
+    "cf.first.check.clang-format": "false", 
+    "cidr.known.project.marker": "true", 
+    "git-widget-placeholder": "master", 
+    "ignore.virus.scanning.warn.message": "true", 
+    "kotlin-language-version-configured": "true" 
+  } 
+}]]></component>
+  <component name="TaskManager">
+    <task active="true" id="Default" summary="Default task">
+      <changelist id="97a950fa-db8c-4e93-8516-864b9f7aa068" name="Changes" comment="" />
+      <created>1765230615534</created>
+      <option name="number" value="Default" />
+      <option name="presentableId" value="Default" />
+      <updated>1765230615534</updated>
+    </task>
+    <servers />
+  </component>
+</project>
+ + \ No newline at end of file From e74e18556dd9c975906ddcf19f422c5e7c595b19 Mon Sep 17 00:00:00 2001 From: Apocky Date: Thu, 11 Dec 2025 02:27:17 -0700 Subject: [PATCH 3/3] . --- .idea/misc.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/.idea/misc.xml b/.idea/misc.xml index 1945ce5..cf90582 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,4 +1,3 @@ -