diff --git a/.env b/.env index 8f443531d..a93779ad9 100644 --- a/.env +++ b/.env @@ -1,4 +1,12 @@ -# VITE_ASSET_PATH=https://M3-org.github.io/loot-assets -# access remote (may have CORS issues) or download from https://github.com/M3-org/loot-assets and load locally -#VITE_ASSET_PATH=./ +# Grudge Studio CharacterStudio Configuration +# Asset path — local manifest.json or remote VITE_ASSET_PATH=./ + +# Grudge Studio Backend (Railway) +VITE_GRUDGE_API=https://api.grudge-studio.com + +# Grudge SSO Gateway +VITE_AUTH_GATEWAY=https://id.grudge-studio.com + +# R2 CDN for 3D models, textures, icons +VITE_ASSET_CDN=https://assets.grudge-studio.com diff --git a/.env.example b/.env.example index c8149e04f..45c6c20f0 100644 --- a/.env.example +++ b/.env.example @@ -1,4 +1,32 @@ +# ── GCS — Grudge Character Studio :: Environment Variables ─────────────────── +# Copy to .env.local for local development. Never commit .env.local. + +# ── Asset path (required) ───────────────────────────────────────────────────── +# Path to the VRM/manifest assets directory. +# Leave empty to use the default public/ bundle, or point at a CDN. VITE_ASSET_PATH= + +# ── Grudge Studio API (The-ENGINE backend) ─────────────────────────────────── +# REST API for user accounts, characters, inventory, GBUX balance. +# Production: https://api.grudge-studio.com +# Local dev: http://localhost:5000 +VITE_GRUDGE_API=https://api.grudge-studio.com + +# ── Auth gateway (Grudge SSO) ───────────────────────────────────────────────── +# SSO login endpoint. Redirects back with ?sso_token=... after login. +# Production: https://id.grudge-studio.com +# Local dev: http://localhost:5000 (The-ENGINE handles /login) +VITE_AUTH_GATEWAY=https://id.grudge-studio.com + +# ── Asset CDN ───────────────────────────────────────────────────────────────── +# Optional CDN for 3D assets, textures, and manifests. +VITE_ASSET_CDN=https://assets.grudge-studio.com + +# ── OpenSea API key (optional — for NFT trait verification) ─────────────────── VITE_OPENSEA_KEY= + +# ── Helius API key (optional — for Solana RPC) ─────────────────────────────── VITE_HELIUS_KEY= -VITE_VALIDATION_SERVER_URL=http://localhost:3000 \ No newline at end of file + +# ── Validation server (optional — for VRM export validation) ───────────────── +VITE_VALIDATION_SERVER_URL=http://localhost:3000 diff --git a/.github/workflows/webpack.yml b/.github/workflows/webpack.yml new file mode 100644 index 000000000..9626ff6d3 --- /dev/null +++ b/.github/workflows/webpack.yml @@ -0,0 +1,28 @@ +name: NodeJS with Webpack + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +jobs: + build: + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [18.x, 20.x, 22.x] + + steps: + - uses: actions/checkout@v4 + + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + + - name: Build + run: | + npm install + npx webpack diff --git a/index.html b/index.html index 15d9183b2..3105ad9a0 100644 --- a/index.html +++ b/index.html @@ -2,16 +2,36 @@ - + - Character Studio + + GCS — Grudge Character Studio · HYDRA + + + + + + + + + + + + + + + + + + + + -
-
+
diff --git a/package.json b/package.json index 38d77283c..67facc2bf 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,9 @@ { - "name": "@m3-org/characterstudio", - "version": "0.5.0", - "homepage": "https://m3-org.github.io/CharacterStudio", + "name": "@grudge-studio/gcs", + "version": "1.0.0", + "description": "GCS — Grudge Character Studio. HYDRA-powered 3D VRM character creator for the Grudge Studio ecosystem.", + "homepage": "https://character.grudge-studio.com", "scripts": { - "predeploy": "npm run build", - "deploy": "gh-pages -d build", "dev": "vite --host", "build": "vite build", "serve": "vite preview", diff --git a/public/manifest.json b/public/manifest.json index 482bf5a39..18f8d77cb 100644 --- a/public/manifest.json +++ b/public/manifest.json @@ -1,89 +1,69 @@ { - "characters":[ + "characters": [ { - "name": "Anata", - "description": "Female", - "portrait": "./assets/portraitImages/female.jpg", - "manifest":"https://m3-org.github.io/loot-assets/anata/female/manifest.json", - "format": "vrm" + "name": "Human", + "description": "Balanced fighter, versatile in all roles.", + "portrait": "https://assets.grudge-studio.com/portraits/human.jpg", + "manifest": "https://assets.grudge-studio.com/manifests/human.json", + "format": "glb", + "raceId": "human", + "faction": "crusade" }, { - "name": "Anata", - "description": "Male", - "portrait": "./assets/portraitImages/male.jpg", - "manifest":"./loot-assets/anata/male/manifest.json", - "format": "vrm" + "name": "Barbarian", + "description": "Brutal berserker, raw power and fury.", + "portrait": "https://assets.grudge-studio.com/portraits/barbarian.jpg", + "manifest": "https://assets.grudge-studio.com/manifests/barbarian.json", + "format": "glb", + "raceId": "barbarian", + "faction": "crusade" }, { - "name": "ON1FORCE", - "description": "Demon", - "portrait": "./assets/portraitImages/0n1.jpg", - "manifest":"https://m3-org.github.io/loot-assets/0N1/manifest.json", - "format": "vrm" + "name": "Elf", + "description": "Agile marksman, master of precision.", + "portrait": "https://assets.grudge-studio.com/portraits/elf.jpg", + "manifest": "https://assets.grudge-studio.com/manifests/elf.json", + "format": "glb", + "raceId": "elf", + "faction": "fabled" }, { - "name": "TUBBY CAT", - "description": "Cute", - "portrait": "./assets/portraitImages/tubby.jpg", - "manifest":"https://m3-org.github.io/loot-assets/tubbycats/manifest.json", - "format": "vrm" - } - ], - "loras":[ - { - "name": "SDXL Lora 728", - "description": "Poses and camera positions to create Lora for SDXL 726 x 726", - "manifest":"./lora-assets/manifest.json", - "icon": "|" - } - ], - "sprites":[ - { - "name": "Multiple Animations", - "description": "Multiple definitions for different animations converted to sprites.", - "manifest":"./sprite-atlas-assets/manifest.json", - "icon": "|" - } - - ], - "thumbnails":[ - { - "name": "Clothing Thumbnail", - "description": "Create thumbnails for Clothing", - "manifest":"./thumbnail-assets/manifest.json", - "icon": "|" + "name": "Dwarf", + "description": "Stout defender, unbreakable resolve.", + "portrait": "https://assets.grudge-studio.com/portraits/dwarf.jpg", + "manifest": "https://assets.grudge-studio.com/manifests/dwarf.json", + "format": "glb", + "raceId": "dwarf", + "faction": "crusade" }, { - "name": "Clothing Thumbnail2", - "description": "Create thumbnails for Clothing", - "manifest":"./thumbnail-assets/manifest.json", - "icon": "|" - } - ], - "defaultAnimations":[ - { - "name": "T-Pose", - "description": "T-Pose", - "location":"./animations/T-Pose.fbx", - "icon": "|" - }, - { - "name": "Dancing", - "description": "Basic Dance Animation", - "location":"./animations/Dancing.fbx", - "icon": "|" + "name": "Orc", + "description": "Savage warrior, strength through rage.", + "portrait": "https://assets.grudge-studio.com/portraits/orc.jpg", + "manifest": "https://assets.grudge-studio.com/manifests/orc.json", + "format": "glb", + "raceId": "orc", + "faction": "legion" }, { - "name": "Walking", - "description": "Basic Walk Animation", - "location":"./animations/Walking.fbx", - "icon": "|" - }, - { - "name": "Waving", - "description": "Basic Waving Animation", - "location":"./animations/Waving.fbx", - "icon": "|" + "name": "Undead", + "description": "Risen skeleton, relentless and fearless.", + "portrait": "https://assets.grudge-studio.com/portraits/undead.jpg", + "manifest": "https://assets.grudge-studio.com/manifests/undead.json", + "format": "glb", + "raceId": "undead", + "faction": "legion" } + ], + "classes": [ + { "id": "warrior", "name": "Warrior", "icon": "⚔️", "defaultWeapon": "unarmed" }, + { "id": "mage", "name": "Mage", "icon": "🔮", "defaultWeapon": "unarmed" }, + { "id": "ranger", "name": "Ranger", "icon": "🏹", "defaultWeapon": "unarmed" }, + { "id": "worg", "name": "Worge", "icon": "🐺", "defaultWeapon": "unarmed" } + ], + "defaultAnimations": [ + { "name": "Idle", "description": "Standing idle", "location": "https://assets.grudge-studio.com/models/animations/sword-shield/sword and shield idle.glb" }, + { "name": "Walk", "description": "Walking forward", "location": "https://assets.grudge-studio.com/models/animations/sword-shield/sword and shield run.glb" }, + { "name": "Attack", "description": "Basic attack", "location": "https://assets.grudge-studio.com/models/animations/sword-shield/sword and shield attack.glb" } ] } diff --git a/public/style.css b/public/style.css index 6e07fca3b..9626ea986 100644 --- a/public/style.css +++ b/public/style.css @@ -1,51 +1,65 @@ -/* New UI: TT Squares Condensed Font */ -@font-face { - font-family: "TTSC-Black-Italic"; - src: url("./fonts/tt-squares/TT Squares Condensed Black Italic.otf"); -} -@font-face { - font-family: "TTSC-Black"; - src: url("./fonts/tt-squares/TT Squares Condensed Black.otf"); -} -@font-face { - font-family: "TTSC-Bold-Italic"; - src: url("./fonts/tt-squares/TT Squares Condensed Bold Italic.otf"); -} -@font-face { - font-family: "TTSC-Bold"; - src: url("./fonts/tt-squares/TT Squares Condensed Bold.otf"); -} -@font-face { - font-family: "TTSC-Light-Italic"; - src: url("./fonts/tt-squares/TT Squares Condensed Light Italic.otf"); -} -@font-face { - font-family: "TTSC-Light"; - src: url("./fonts/tt-squares/TT Squares Condensed Light.otf"); -} -@font-face { - font-family: "TTSC-Regular-Italic"; - src: url("./fonts/tt-squares/TT Squares Condensed Regular Italic.otf"); -} -@font-face { - font-family: "TTSC-Regular"; - src: url("./fonts/tt-squares/TT Squares Condensed Regular.otf"); -} -@font-face { - font-family: "TTSC-Thin-Italic"; - src: url("./fonts/tt-squares/TT Squares Condensed Thin italic.otf"); -} -@font-face { - font-family: "TTSC-Thin"; - src: url("./fonts/tt-squares/TT Squares Condensed Thin.otf"); +/* ── GCS — Grudge Character Studio :: Global Styles ────────────────────────── + Brand system: Hydra visual identity (hydralauncher.gg) + Primary teal: #16b195 | Secondary blue: #3e62c0 + Dark bg: #0d0d0d | Surface: #121212 / #1a1a1a + Display font: Space Grotesk | Body: Inter + TT Squares Condensed + ────────────────────────────────────────────────────────────────────────── */ + +:root { + /* Hydra brand colors (from hydralauncher/hydra globals.scss) */ + --hydra-teal: #16b195; + --hydra-blue: #3e62c0; + --hydra-gradient: linear-gradient(270deg, #16b195 50%, #3e62c0 100%); + --hydra-gradient-v: linear-gradient(180deg, #16b195 0%, #3e62c0 100%); + --hydra-platinum: #30edc5; + + /* Grudge Studio accent (established brand) */ + --grudge-green: #4ade80; + --grudge-green-s: rgba(74, 222, 128, 0.12); + + /* Backgrounds */ + --bg: #0d0d0d; + --bg-surface: #121212; + --bg-raised: #1a1a1a; + --bg-panel: rgba(18, 18, 18, 0.92); + + /* Borders */ + --border: rgba(255, 255, 255, 0.08); + --border-mid: rgba(255, 255, 255, 0.12); + + /* Text */ + --text: #f0f1f7; + --text-body: #d0d1d7; + --text-muted: #7a7b8a; + + /* Feedback */ + --success: #1c9749; + --danger: #e11d48; + --warning: #ffc107; + + /* Brand bar height */ + --brand-bar-h: 40px; } -html, -body { +/* ── TT Squares Condensed (local) ── */ +@font-face { font-family: "TTSC-Black-Italic"; src: url("./fonts/tt-squares/TT Squares Condensed Black Italic.otf"); } +@font-face { font-family: "TTSC-Black"; src: url("./fonts/tt-squares/TT Squares Condensed Black.otf"); } +@font-face { font-family: "TTSC-Bold-Italic"; src: url("./fonts/tt-squares/TT Squares Condensed Bold Italic.otf"); } +@font-face { font-family: "TTSC-Bold"; src: url("./fonts/tt-squares/TT Squares Condensed Bold.otf"); } +@font-face { font-family: "TTSC-Light-Italic"; src: url("./fonts/tt-squares/TT Squares Condensed Light Italic.otf"); } +@font-face { font-family: "TTSC-Light"; src: url("./fonts/tt-squares/TT Squares Condensed Light.otf"); } +@font-face { font-family: "TTSC-Regular-Italic"; src: url("./fonts/tt-squares/TT Squares Condensed Regular Italic.otf"); } +@font-face { font-family: "TTSC-Regular"; src: url("./fonts/tt-squares/TT Squares Condensed Regular.otf"); } +@font-face { font-family: "TTSC-Thin-Italic"; src: url("./fonts/tt-squares/TT Squares Condensed Thin italic.otf"); } +@font-face { font-family: "TTSC-Thin"; src: url("./fonts/tt-squares/TT Squares Condensed Thin.otf"); } + +html, body { margin: 0; padding: 0; height: 100%; - font-family: "TTSC-Regular"; + font-family: 'Inter', "TTSC-Regular", system-ui, sans-serif; + background: var(--bg); + color: var(--text); } #root { @@ -53,32 +67,20 @@ body { overflow: hidden; } -::-webkit-scrollbar { - width: 0.1em; -} -::-webkit-scrollbar-track { - box-shadow: transparent; -} +/* ── Hydra-branded scrollbar ── */ +::-webkit-scrollbar { width: 3px; height: 3px; } +::-webkit-scrollbar-track { background: transparent; } ::-webkit-scrollbar-thumb { - background: rgb(0,149,100); -background: -moz-linear-gradient(180deg, rgba(0,149,100,0) 0%, rgba(8,234,160,0.6) 50%, rgba(0,149,100,0) 100%); -background: -webkit-linear-gradient(180deg, rgba(0,149,100,0) 0%, rgba(8,234,160,0.6) 50%, rgba(0,149,100,0) 100%); -background: linear-gradient(180deg, rgba(0,149,100,0) 0%, rgba(8,234,160,0.6) 50%, rgba(0,149,100,0) 100%); -filter: progid:DXImageTransform.Microsoft.gradient(startColorstr="#009564",endColorstr="#009564",GradientType=1); + background: var(--hydra-gradient-v); + border-radius: 2px; } +::-webkit-scrollbar-thumb:hover { background: var(--hydra-teal); } -.horizontalScroll ::-webkit-scrollbar { - height: 0.1em; -} -.horizontalScroll ::-webkit-scrollbar-track { - box-shadow: transparent; -} +.horizontalScroll ::-webkit-scrollbar { height: 3px; } +.horizontalScroll ::-webkit-scrollbar-track { background: transparent; } .horizontalScroll ::-webkit-scrollbar-thumb { - background: rgb(0,149,100); -background: -moz-linear-gradient(180deg, rgba(0,149,100,0) 0%, rgba(8,234,160,0.6) 50%, rgba(0,149,100,0) 100%); -background: -webkit-linear-gradient(180deg, rgba(0,149,100,0) 0%, rgba(8,234,160,0.6) 50%, rgba(0,149,100,0) 100%); -background: linear-gradient(180deg, rgba(0,149,100,0) 0%, rgba(8,234,160,0.6) 50%, rgba(0,149,100,0) 100%); -filter: progid:DXImageTransform.Microsoft.gradient(startColorstr="#009564",endColorstr="#009564",GradientType=1); + background: linear-gradient(90deg, #16b195 50%, #3e62c0 100%); + border-radius: 2px; } .root { @@ -95,63 +97,177 @@ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr="#009564",endCo -ms-user-select: none; user-select: none; outline: none; - -webkit-tap-highlight-color: rgba(255, 255, 255, 0); /* mobile webkit */ + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); } +/* ── Loading indicator ── */ .loadingIndicator { display: none; pointer-events: none; position: absolute; - left: 0; - right: 0; - top: 0; - bottom: 0; - width: 80px; - height: 80px; + left: 0; right: 0; top: 0; bottom: 0; + width: 80px; height: 80px; margin: auto; border-radius: 10px; - filter: drop-shadow(1px 1px 2px black); -} -.loadingIndicator.active { - display: block; + filter: drop-shadow(1px 1px 4px rgba(22, 177, 149, 0.4)); } +.loadingIndicator.active { display: block; } .loadingIndicator > img { position: absolute; - left: 0; - right: 0; - top: 0; - bottom: 0; - width: 65%; - height: 65%; + left: 0; right: 0; top: 0; bottom: 0; + width: 65%; height: 65%; margin: auto; } -.rotate{ - animation: rotate 2.2s linear infinite; -} +@keyframes rotate { to { transform: rotate(360deg); } } +.rotate { animation: rotate 2.2s linear infinite; } +/* ── Section titles (existing component class hooks) ── */ .sectionTitle { position: absolute; top: 34px; left: 32px; - color: white; + color: var(--text); text-transform: uppercase; - text-shadow: 1px 1px 2px black; + text-shadow: 0 1px 4px rgba(0,0,0,0.8); font-size: 16px; + font-family: 'Space Grotesk', "TTSC-Bold", sans-serif; + font-weight: 700; + letter-spacing: 0.06em; } .generalTitle { position: absolute; top: 16px; left: 32px; - color: white; - opacity: 0.7; - font-size: 12px; + color: var(--text-muted); + opacity: 0.8; + font-size: 11px; padding: 0; - text-shadow: 1px 1px 2px black; + text-shadow: 0 1px 3px rgba(0,0,0,0.8); text-transform: uppercase; + font-family: 'Space Grotesk', sans-serif; + font-weight: 600; + letter-spacing: 0.1em; +} + +/* ── GCS Brand Bar ── */ +.gcs-brand-bar { + position: fixed; + top: 0; left: 0; right: 0; + height: var(--brand-bar-h); + z-index: 1000; + display: flex; + align-items: center; + justify-content: space-between; + padding: 0 16px; + background: linear-gradient(90deg, + rgba(13,13,13,0.96) 0%, + rgba(13,13,13,0.82) 60%, + rgba(13,13,13,0.0) 100% + ); + backdrop-filter: blur(8px); + -webkit-backdrop-filter: blur(8px); + border-bottom: 1px solid rgba(22, 177, 149, 0.12); + pointer-events: none; /* only interactive children get pointer-events */ +} + +.gcs-brand-bar::before { + content: ''; + position: absolute; + bottom: 0; left: 0; right: 0; + height: 1px; + background: var(--hydra-gradient); + opacity: 0.35; +} + +.gcs-brand-mark { + display: flex; + align-items: center; + gap: 8px; + pointer-events: auto; + text-decoration: none; +} + +.gcs-wordmark { + font-family: 'Space Grotesk', sans-serif; + font-weight: 800; + font-size: 0.85rem; + letter-spacing: 0.22em; + text-transform: uppercase; + background: var(--hydra-gradient); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +.gcs-studio-name { + font-family: 'Space Grotesk', sans-serif; + font-size: 0.6rem; + font-weight: 500; + letter-spacing: 0.08em; + color: var(--text-muted); + text-transform: uppercase; +} + +.gcs-divider { + width: 1px; + height: 14px; + background: var(--border-mid); + flex-shrink: 0; +} + +.gcs-account-area { + display: flex; + align-items: center; + gap: 8px; + pointer-events: auto; +} + +.gcs-grudge-id { + font-family: 'Space Grotesk', monospace; + font-size: 0.65rem; + font-weight: 600; + color: var(--hydra-teal); + letter-spacing: 0.04em; + opacity: 0.9; +} + +.gcs-btn { + font-family: 'Space Grotesk', sans-serif; + font-size: 0.65rem; + font-weight: 700; + letter-spacing: 0.06em; + text-transform: uppercase; + padding: 4px 12px; + border-radius: 6px; + border: none; + cursor: pointer; + transition: all 0.1s; +} + +.gcs-btn-login { + background: rgba(22, 177, 149, 0.12); + border: 1px solid rgba(22, 177, 149, 0.3); + color: var(--hydra-teal); +} +.gcs-btn-login:hover { + background: rgba(22, 177, 149, 0.22); + border-color: var(--hydra-teal); +} + +.gcs-btn-logout { + background: transparent; + border: 1px solid var(--border); + color: var(--text-muted); +} +.gcs-btn-logout:hover { + border-color: var(--border-mid); + color: var(--text); } -@keyframes rotate{ - to{ transform: rotate(360deg); } +/* ── Canvas offset when brand bar shown ── */ +/* Shift entire React root down by brand bar height */ +#root[data-brandbar="true"] { + padding-top: var(--brand-bar-h); } diff --git a/src/App.jsx b/src/App.jsx index 673ff5a20..7efbac4b5 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -9,6 +9,7 @@ import { EffectManager } from "./library/effectManager" import MessageWindow from "./components/MessageWindow" import Background from "./components/Background" +import BrandBar from "./components/BrandBar" import Appearance from "./pages/Appearance" import BatchDownload from "./pages/BatchDownload" @@ -206,8 +207,8 @@ export default function App() { return ( - -
Character Studio
+ + {/* */}