diff --git a/package.json b/package.json index 48a4f76..5de5e51 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "dependencies": { "@astrojs/starlight": "0.37.6", "astro": "5.17.1", - "coherent-docs-theme": "^1.0.7", + "coherent-docs-theme": "^1.0.8", "gray-matter": "^4.0.3", "sharp": "^0.34.2", "starlight-auto-sidebar": "^0.1.4" diff --git a/src/assets/phase-3/animations/animation-js-control-play-from-to.webm b/src/assets/phase-3/animations/animation-js-control-play-from-to.webm new file mode 100644 index 0000000..2aaa53c Binary files /dev/null and b/src/assets/phase-3/animations/animation-js-control-play-from-to.webm differ diff --git a/src/assets/phase-3/animations/animation-js-control.webm b/src/assets/phase-3/animations/animation-js-control.webm new file mode 100644 index 0000000..21cf7e9 Binary files /dev/null and b/src/assets/phase-3/animations/animation-js-control.webm differ diff --git a/src/assets/phase-3/animations/complex-spritesheets-animation-1.webm b/src/assets/phase-3/animations/complex-spritesheets-animation-1.webm new file mode 100644 index 0000000..071cb6c Binary files /dev/null and b/src/assets/phase-3/animations/complex-spritesheets-animation-1.webm differ diff --git a/src/assets/phase-3/animations/complex-spritesheets-animation-2.webm b/src/assets/phase-3/animations/complex-spritesheets-animation-2.webm new file mode 100644 index 0000000..8af21ef Binary files /dev/null and b/src/assets/phase-3/animations/complex-spritesheets-animation-2.webm differ diff --git a/src/assets/phase-3/animations/complex-spritesheets-animation-3.webm b/src/assets/phase-3/animations/complex-spritesheets-animation-3.webm new file mode 100644 index 0000000..bac1723 Binary files /dev/null and b/src/assets/phase-3/animations/complex-spritesheets-animation-3.webm differ diff --git a/src/assets/phase-3/animations/complex-spritesheets-animation-4.webm b/src/assets/phase-3/animations/complex-spritesheets-animation-4.webm new file mode 100644 index 0000000..f99eb94 Binary files /dev/null and b/src/assets/phase-3/animations/complex-spritesheets-animation-4.webm differ diff --git a/src/assets/phase-3/animations/complex-spritesheets-animation-5.webm b/src/assets/phase-3/animations/complex-spritesheets-animation-5.webm new file mode 100644 index 0000000..1f9690a Binary files /dev/null and b/src/assets/phase-3/animations/complex-spritesheets-animation-5.webm differ diff --git a/src/assets/phase-3/animations/complex-spritesheets-animation-variants.webm b/src/assets/phase-3/animations/complex-spritesheets-animation-variants.webm new file mode 100644 index 0000000..b9cac84 Binary files /dev/null and b/src/assets/phase-3/animations/complex-spritesheets-animation-variants.webm differ diff --git a/src/assets/phase-3/animations/complex-spritesheets-animation.webm b/src/assets/phase-3/animations/complex-spritesheets-animation.webm new file mode 100644 index 0000000..6ef3ccd Binary files /dev/null and b/src/assets/phase-3/animations/complex-spritesheets-animation.webm differ diff --git a/src/assets/phase-3/animations/css-keyframes-entrance.webm b/src/assets/phase-3/animations/css-keyframes-entrance.webm new file mode 100644 index 0000000..2c7a780 Binary files /dev/null and b/src/assets/phase-3/animations/css-keyframes-entrance.webm differ diff --git a/src/assets/phase-3/animations/css-keyframes-glow.webm b/src/assets/phase-3/animations/css-keyframes-glow.webm new file mode 100644 index 0000000..4b627fb Binary files /dev/null and b/src/assets/phase-3/animations/css-keyframes-glow.webm differ diff --git a/src/assets/phase-3/animations/css-transitions-example.webm b/src/assets/phase-3/animations/css-transitions-example.webm new file mode 100644 index 0000000..ea0a279 Binary files /dev/null and b/src/assets/phase-3/animations/css-transitions-example.webm differ diff --git a/src/assets/phase-3/animations/fire-effect-strip.png b/src/assets/phase-3/animations/fire-effect-strip.png new file mode 100644 index 0000000..7c3b24d Binary files /dev/null and b/src/assets/phase-3/animations/fire-effect-strip.png differ diff --git a/src/assets/phase-3/animations/spiral-effect-strip.png b/src/assets/phase-3/animations/spiral-effect-strip.png new file mode 100644 index 0000000..faebad5 Binary files /dev/null and b/src/assets/phase-3/animations/spiral-effect-strip.png differ diff --git a/src/assets/phase-3/animations/spritesheet-flame-variants.webm b/src/assets/phase-3/animations/spritesheet-flame-variants.webm new file mode 100644 index 0000000..98e1294 Binary files /dev/null and b/src/assets/phase-3/animations/spritesheet-flame-variants.webm differ diff --git a/src/assets/phase-3/animations/spritesheet-flame.webm b/src/assets/phase-3/animations/spritesheet-flame.webm new file mode 100644 index 0000000..2002ab5 Binary files /dev/null and b/src/assets/phase-3/animations/spritesheet-flame.webm differ diff --git a/src/assets/phase-3/animations/starting-style-modal.webm b/src/assets/phase-3/animations/starting-style-modal.webm new file mode 100644 index 0000000..255d073 Binary files /dev/null and b/src/assets/phase-3/animations/starting-style-modal.webm differ diff --git a/src/assets/phase-3/animations/svg-animation-blink-glow.webm b/src/assets/phase-3/animations/svg-animation-blink-glow.webm new file mode 100644 index 0000000..4bc5c6d Binary files /dev/null and b/src/assets/phase-3/animations/svg-animation-blink-glow.webm differ diff --git a/src/assets/phase-3/animations/svg-animation-locked-glow.webm b/src/assets/phase-3/animations/svg-animation-locked-glow.webm new file mode 100644 index 0000000..8725a5f Binary files /dev/null and b/src/assets/phase-3/animations/svg-animation-locked-glow.webm differ diff --git a/src/assets/phase-3/animations/svg-animation-shimmer.webm b/src/assets/phase-3/animations/svg-animation-shimmer.webm new file mode 100644 index 0000000..3c8dc5d Binary files /dev/null and b/src/assets/phase-3/animations/svg-animation-shimmer.webm differ diff --git a/src/assets/phase-3/graphics-and-shapes/9-slice-example.webm b/src/assets/phase-3/graphics-and-shapes/9-slice-example.webm new file mode 100644 index 0000000..28475d6 Binary files /dev/null and b/src/assets/phase-3/graphics-and-shapes/9-slice-example.webm differ diff --git a/src/assets/phase-3/graphics-and-shapes/border-image-generator.webm b/src/assets/phase-3/graphics-and-shapes/border-image-generator.webm new file mode 100644 index 0000000..3fb9f67 Binary files /dev/null and b/src/assets/phase-3/graphics-and-shapes/border-image-generator.webm differ diff --git a/src/assets/phase-3/graphics-and-shapes/clip-path-radial-menu-disabled.png b/src/assets/phase-3/graphics-and-shapes/clip-path-radial-menu-disabled.png new file mode 100644 index 0000000..e17f8d3 Binary files /dev/null and b/src/assets/phase-3/graphics-and-shapes/clip-path-radial-menu-disabled.png differ diff --git a/src/assets/phase-3/graphics-and-shapes/clip-path-radial-menu.png b/src/assets/phase-3/graphics-and-shapes/clip-path-radial-menu.png new file mode 100644 index 0000000..35464e0 Binary files /dev/null and b/src/assets/phase-3/graphics-and-shapes/clip-path-radial-menu.png differ diff --git a/src/assets/phase-3/graphics-and-shapes/health-bar.png b/src/assets/phase-3/graphics-and-shapes/health-bar.png new file mode 100644 index 0000000..4206ffc Binary files /dev/null and b/src/assets/phase-3/graphics-and-shapes/health-bar.png differ diff --git a/src/assets/phase-3/graphics-and-shapes/hexagon-skill-node.png b/src/assets/phase-3/graphics-and-shapes/hexagon-skill-node.png new file mode 100644 index 0000000..6b34287 Binary files /dev/null and b/src/assets/phase-3/graphics-and-shapes/hexagon-skill-node.png differ diff --git a/src/assets/phase-3/graphics-and-shapes/inline-svg-button-group.png b/src/assets/phase-3/graphics-and-shapes/inline-svg-button-group.png new file mode 100644 index 0000000..7577f28 Binary files /dev/null and b/src/assets/phase-3/graphics-and-shapes/inline-svg-button-group.png differ diff --git a/src/assets/phase-3/graphics-and-shapes/mask-pattern-complete.png b/src/assets/phase-3/graphics-and-shapes/mask-pattern-complete.png new file mode 100644 index 0000000..a1bdbc3 Binary files /dev/null and b/src/assets/phase-3/graphics-and-shapes/mask-pattern-complete.png differ diff --git a/src/assets/phase-3/graphics-and-shapes/mask-pattern-tiled.png b/src/assets/phase-3/graphics-and-shapes/mask-pattern-tiled.png new file mode 100644 index 0000000..b8af017 Binary files /dev/null and b/src/assets/phase-3/graphics-and-shapes/mask-pattern-tiled.png differ diff --git a/src/assets/phase-3/graphics-and-shapes/mask-pattern.png b/src/assets/phase-3/graphics-and-shapes/mask-pattern.png new file mode 100644 index 0000000..ae2c92b Binary files /dev/null and b/src/assets/phase-3/graphics-and-shapes/mask-pattern.png differ diff --git a/src/assets/phase-3/graphics-and-shapes/minimap-base.png b/src/assets/phase-3/graphics-and-shapes/minimap-base.png new file mode 100644 index 0000000..8af1b9f Binary files /dev/null and b/src/assets/phase-3/graphics-and-shapes/minimap-base.png differ diff --git a/src/assets/phase-3/graphics-and-shapes/minimap-fade.png b/src/assets/phase-3/graphics-and-shapes/minimap-fade.png new file mode 100644 index 0000000..75ef39e Binary files /dev/null and b/src/assets/phase-3/graphics-and-shapes/minimap-fade.png differ diff --git a/src/assets/phase-3/graphics-and-shapes/progress-bar.webm b/src/assets/phase-3/graphics-and-shapes/progress-bar.webm new file mode 100644 index 0000000..b4fdf66 Binary files /dev/null and b/src/assets/phase-3/graphics-and-shapes/progress-bar.webm differ diff --git a/src/assets/phase-3/graphics-and-shapes/progress-circle.webm b/src/assets/phase-3/graphics-and-shapes/progress-circle.webm new file mode 100644 index 0000000..44d71cb Binary files /dev/null and b/src/assets/phase-3/graphics-and-shapes/progress-circle.webm differ diff --git a/src/assets/phase-3/graphics-and-shapes/skill-tree-node.webm b/src/assets/phase-3/graphics-and-shapes/skill-tree-node.webm new file mode 100644 index 0000000..796c058 Binary files /dev/null and b/src/assets/phase-3/graphics-and-shapes/skill-tree-node.webm differ diff --git a/src/assets/phase-3/graphics-and-shapes/svg-rarity-variations.webm b/src/assets/phase-3/graphics-and-shapes/svg-rarity-variations.webm new file mode 100644 index 0000000..c96ea6e Binary files /dev/null and b/src/assets/phase-3/graphics-and-shapes/svg-rarity-variations.webm differ diff --git a/src/assets/phase-3/graphics-and-shapes/svg-stroke-dashing-progress.webm b/src/assets/phase-3/graphics-and-shapes/svg-stroke-dashing-progress.webm new file mode 100644 index 0000000..5dfc948 Binary files /dev/null and b/src/assets/phase-3/graphics-and-shapes/svg-stroke-dashing-progress.webm differ diff --git a/src/assets/phase-3/graphics-and-shapes/viewbox-minimap-panning.webm b/src/assets/phase-3/graphics-and-shapes/viewbox-minimap-panning.webm new file mode 100644 index 0000000..3a7a11b Binary files /dev/null and b/src/assets/phase-3/graphics-and-shapes/viewbox-minimap-panning.webm differ diff --git a/src/assets/phase-3/text-and-fonts/3d-text.png b/src/assets/phase-3/text-and-fonts/3d-text.png new file mode 100644 index 0000000..234cf6c Binary files /dev/null and b/src/assets/phase-3/text-and-fonts/3d-text.png differ diff --git a/src/assets/phase-3/text-and-fonts/emoji-example.png b/src/assets/phase-3/text-and-fonts/emoji-example.png new file mode 100644 index 0000000..cb11d8f Binary files /dev/null and b/src/assets/phase-3/text-and-fonts/emoji-example.png differ diff --git a/src/assets/phase-3/text-and-fonts/localization-fit-fixed-padding.png b/src/assets/phase-3/text-and-fonts/localization-fit-fixed-padding.png new file mode 100644 index 0000000..8ff3b4f Binary files /dev/null and b/src/assets/phase-3/text-and-fonts/localization-fit-fixed-padding.png differ diff --git a/src/assets/phase-3/text-and-fonts/localization-fit-fixed.png b/src/assets/phase-3/text-and-fonts/localization-fit-fixed.png new file mode 100644 index 0000000..fa22bdb Binary files /dev/null and b/src/assets/phase-3/text-and-fonts/localization-fit-fixed.png differ diff --git a/src/assets/phase-3/text-and-fonts/localization-fit-grow.png b/src/assets/phase-3/text-and-fonts/localization-fit-grow.png new file mode 100644 index 0000000..576239b Binary files /dev/null and b/src/assets/phase-3/text-and-fonts/localization-fit-grow.png differ diff --git a/src/assets/phase-3/text-and-fonts/localization-fit-max-size.png b/src/assets/phase-3/text-and-fonts/localization-fit-max-size.png new file mode 100644 index 0000000..b100aaf Binary files /dev/null and b/src/assets/phase-3/text-and-fonts/localization-fit-max-size.png differ diff --git a/src/assets/phase-3/text-and-fonts/localization-fit.png b/src/assets/phase-3/text-and-fonts/localization-fit.png new file mode 100644 index 0000000..a7fd508 Binary files /dev/null and b/src/assets/phase-3/text-and-fonts/localization-fit.png differ diff --git a/src/assets/phase-3/text-and-fonts/neon-text.png b/src/assets/phase-3/text-and-fonts/neon-text.png new file mode 100644 index 0000000..6f3cf8f Binary files /dev/null and b/src/assets/phase-3/text-and-fonts/neon-text.png differ diff --git a/src/assets/phase-3/text-and-fonts/text-outline.png b/src/assets/phase-3/text-and-fonts/text-outline.png new file mode 100644 index 0000000..6ed46b5 Binary files /dev/null and b/src/assets/phase-3/text-and-fonts/text-outline.png differ diff --git a/src/assets/phase-3/text-and-fonts/vertical-align-correct.png b/src/assets/phase-3/text-and-fonts/vertical-align-correct.png new file mode 100644 index 0000000..0e0e067 Binary files /dev/null and b/src/assets/phase-3/text-and-fonts/vertical-align-correct.png differ diff --git a/src/assets/phase-3/text-and-fonts/vertical-align-wrong.png b/src/assets/phase-3/text-and-fonts/vertical-align-wrong.png new file mode 100644 index 0000000..2cd7aff Binary files /dev/null and b/src/assets/phase-3/text-and-fonts/vertical-align-wrong.png differ diff --git a/src/assets/phase-4/data-binding/custom-data-bind.webm b/src/assets/phase-4/data-binding/custom-data-bind.webm new file mode 100644 index 0000000..007a63d Binary files /dev/null and b/src/assets/phase-4/data-binding/custom-data-bind.webm differ diff --git a/src/assets/phase-4/data-binding/data-bind-class-toggle-video.webm b/src/assets/phase-4/data-binding/data-bind-class-toggle-video.webm new file mode 100644 index 0000000..f2a3436 Binary files /dev/null and b/src/assets/phase-4/data-binding/data-bind-class-toggle-video.webm differ diff --git a/src/assets/phase-4/data-binding/dynamic-nameplate.webm b/src/assets/phase-4/data-binding/dynamic-nameplate.webm new file mode 100644 index 0000000..9d297de Binary files /dev/null and b/src/assets/phase-4/data-binding/dynamic-nameplate.webm differ diff --git a/src/assets/phase-4/data-binding/party-roster-full.webm b/src/assets/phase-4/data-binding/party-roster-full.webm new file mode 100644 index 0000000..0351766 Binary files /dev/null and b/src/assets/phase-4/data-binding/party-roster-full.webm differ diff --git a/src/assets/phase-4/data-binding/virtual-list.webm b/src/assets/phase-4/data-binding/virtual-list.webm new file mode 100644 index 0000000..e0f9886 Binary files /dev/null and b/src/assets/phase-4/data-binding/virtual-list.webm differ diff --git a/src/assets/phase-4/interactions/Complex-ui-areas.png b/src/assets/phase-4/interactions/Complex-ui-areas.png new file mode 100644 index 0000000..2bdb95d Binary files /dev/null and b/src/assets/phase-4/interactions/Complex-ui-areas.png differ diff --git a/src/assets/phase-4/interactions/custom-cursor.webm b/src/assets/phase-4/interactions/custom-cursor.webm new file mode 100644 index 0000000..9cd2d96 Binary files /dev/null and b/src/assets/phase-4/interactions/custom-cursor.webm differ diff --git a/src/assets/phase-4/interactions/drag-and-drop-tetris-grid.webm b/src/assets/phase-4/interactions/drag-and-drop-tetris-grid.webm new file mode 100644 index 0000000..96b8eef Binary files /dev/null and b/src/assets/phase-4/interactions/drag-and-drop-tetris-grid.webm differ diff --git a/src/assets/phase-4/interactions/switching-areas.webm b/src/assets/phase-4/interactions/switching-areas.webm new file mode 100644 index 0000000..91d219a Binary files /dev/null and b/src/assets/phase-4/interactions/switching-areas.webm differ diff --git a/src/assets/phase-4/interactions/text-selection.webm b/src/assets/phase-4/interactions/text-selection.webm new file mode 100644 index 0000000..723f2e0 Binary files /dev/null and b/src/assets/phase-4/interactions/text-selection.webm differ diff --git a/src/content/docs/Polishing/accessibility.mdx b/src/content/docs/Polishing/accessibility.mdx new file mode 100644 index 0000000..5d2cd15 --- /dev/null +++ b/src/content/docs/Polishing/accessibility.mdx @@ -0,0 +1,6 @@ +--- +title: Accessibility +description: Learn how to make your game UI accessible to all players using Gameface's built-in accessibility features. This section covers best practices for implementing screen reader support, keyboard navigation, and localization to ensure your UI is inclusive and reaches a wider audience. +sidebar: + order: 1 +--- \ No newline at end of file diff --git a/src/content/docs/phase-1-getting-started/introduction.mdx b/src/content/docs/phase-1-getting-started/introduction.mdx index dcbed02..31c4a91 100644 --- a/src/content/docs/phase-1-getting-started/introduction.mdx +++ b/src/content/docs/phase-1-getting-started/introduction.mdx @@ -31,9 +31,9 @@ Here's a brief overview of what we'll cover in each phase: Configuring your development environment, establishing your recommended tech stack (like SolidJS and Vite), and understanding how data flows between the game and the UI. This phase covers core concepts like "push vs. fetch", how to organize your views, and bridging the gap between design and development - using tools like the [Figma Exporter](https://www.figma.com/community/plugin/1595556380268929590/coherent-gameface-exporter). + using tools like the Figma Exporter. - We also bridge to prototyping by introducing [Gameface UI](https://gameface-ui.coherent-labs.com/)—our component library that makes prototyping fast and easy. + We also bridge to prototyping by introducing Gameface UI—our component library that makes prototyping fast and easy. 3. #### **Phase 3: Layout, Assets & Styling** The fun part! We cover how to translate designs into clean, scalable game UIs. @@ -49,7 +49,7 @@ Here's a brief overview of what we'll cover in each phase: We cover how to mock real game data to test your UI independently, manage local versus game state using data-binding, - and handle complex user inputs like gamepad, touch support, and spatial navigation using the [Interaction Manager](https://frontend-tools.coherent-labs.com/interaction-manager/getting-started/). + and handle complex user inputs like gamepad, touch support, and spatial navigation using the Interaction Manager. 5. #### **Phase 5: Polishing & Debugging** The final 10%. We cover how to polish your UI to make it feel smooth, accessible, bug-free, and most importantly, performant. @@ -66,7 +66,7 @@ To keep this guide lean and focused on UI creation, we have intentionally exclud * Deep dives into the Gameface API. * Instructions on how to compile or initialize the Gameface library within your game. -*If you are a backend programmer looking for engine integration, please refer to the [official Gameface technical documentation](https://docs.coherent-labs.com/cpp-gameface/).* +*If you are a backend programmer looking for engine integration, please refer to the official Gameface technical documentation.* ## Your Content Creation Reference @@ -99,5 +99,5 @@ We will get our hands dirty with a quick demo showcasing how to run, test, and u ### Need more convincing? -- Read more about what [Gameface is](https://docs.coherent-labs.com/cpp-gameface/what_is_gfp/overview/) - for a technical overview. -- Check out the main differences between Gameface and a traditional browser environment in our [Differences to traditional browsers](https://docs.coherent-labs.com/cpp-gameface/what_is_gfp/htmlfeaturesupport/) documentation. \ No newline at end of file +- Read more about what Gameface is - for a technical overview. +- Check out the main differences between Gameface and a traditional browser environment in our Differences to traditional browsers documentation. \ No newline at end of file diff --git a/src/content/docs/phase-2-planning-and-setup/Alternative Frameworks/_meta.yml b/src/content/docs/phase-2-planning-and-setup/Alternative Frameworks/_meta.yml deleted file mode 100644 index 52a4460..0000000 --- a/src/content/docs/phase-2-planning-and-setup/Alternative Frameworks/_meta.yml +++ /dev/null @@ -1,2 +0,0 @@ -order: 3 -collapsed: true diff --git a/src/content/docs/phase-2-planning-and-setup/Alternative Frameworks/preact-setup.mdx b/src/content/docs/phase-2-planning-and-setup/Alternative Frameworks/preact-setup.mdx deleted file mode 100644 index 20876f6..0000000 --- a/src/content/docs/phase-2-planning-and-setup/Alternative Frameworks/preact-setup.mdx +++ /dev/null @@ -1,13 +0,0 @@ ---- -title: Setting up a Preact Project -description: Learn how to scaffold a modern Vite + Preact project and configure it to run perfectly within Gameface. -sidebar: - order: 2 -draft: true ---- - -- Degit or install with CLI the vite react gameface template - -- Go over the structure and how to run and build the project - -- Cover some important caveats about the framework in Gameface diff --git a/src/content/docs/phase-2-planning-and-setup/Alternative Frameworks/react-setup.mdx b/src/content/docs/phase-2-planning-and-setup/Alternative Frameworks/react-setup.mdx deleted file mode 100644 index b6834cb..0000000 --- a/src/content/docs/phase-2-planning-and-setup/Alternative Frameworks/react-setup.mdx +++ /dev/null @@ -1,14 +0,0 @@ ---- -title: Setting up a React Project -description: Learn how to scaffold a modern Vite + React project and configure it to run perfectly within Gameface. -sidebar: - order: 1 -draft: true ---- - -- Degit or install with CLI the vite react gameface template - -- Go over the structure and how to run and build the project - -- Cover some important caveats about the framework in Gameface - diff --git a/src/content/docs/phase-2-planning-and-setup/Alternative Frameworks/svelte-setup.mdx b/src/content/docs/phase-2-planning-and-setup/Alternative Frameworks/svelte-setup.mdx deleted file mode 100644 index f88b9fe..0000000 --- a/src/content/docs/phase-2-planning-and-setup/Alternative Frameworks/svelte-setup.mdx +++ /dev/null @@ -1,13 +0,0 @@ ---- -title: Setting up a Svelte5 Project -description: Learn how to scaffold a modern Vite + Svelte5 project and configure it to run perfectly within Gameface. -sidebar: - order: 3 -draft: true ---- - -- Degit or install with CLI the vite react gameface template - -- Go over the structure and how to run and build the project - -- Cover some important caveats about the framework in Gameface diff --git a/src/content/docs/phase-2-planning-and-setup/Alternative Frameworks/vue-setup.mdx b/src/content/docs/phase-2-planning-and-setup/Alternative Frameworks/vue-setup.mdx deleted file mode 100644 index fbadd3d..0000000 --- a/src/content/docs/phase-2-planning-and-setup/Alternative Frameworks/vue-setup.mdx +++ /dev/null @@ -1,13 +0,0 @@ ---- -title: Setting up a Vue Project -description: Learn how to scaffold a modern Vite + Vue project and configure it to run perfectly within Gameface. -sidebar: - order: 4 -draft: true ---- - -- Degit or install with CLI the vite react gameface template - -- Go over the structure and how to run and build the project - -- Cover some important caveats about the framework in Gameface diff --git a/src/content/docs/phase-2-planning-and-setup/Recommended Tech Stack/recommended-tech-stack-overview.mdx b/src/content/docs/phase-2-planning-and-setup/Recommended Tech Stack/recommended-tech-stack-overview.mdx index 25b31eb..3659667 100644 --- a/src/content/docs/phase-2-planning-and-setup/Recommended Tech Stack/recommended-tech-stack-overview.mdx +++ b/src/content/docs/phase-2-planning-and-setup/Recommended Tech Stack/recommended-tech-stack-overview.mdx @@ -42,7 +42,7 @@ If you are coming from traditional web development, SolidJS has a very similar s :::note[Alternative Frameworks] While Gameface does support other frameworks like React, Vue, and Preact, they rely on Virtual DOMs or heavier runtimes that can impact game performance. You can find setup guides for these in the -[Alternative Frameworks section](). +[Alternative Frameworks section](/phase-2-planning-and-setup/alternative-frameworks/). ::: ## The Bundler: Vite @@ -67,7 +67,7 @@ We tackle this using a combination of SCSSCSS Modules and SCSS's nesting capabilities to keep your styles scoped and organized while still compiling down to engine-friendly flat selectors. ```scss .dropdown { diff --git a/src/content/docs/phase-2-planning-and-setup/Recommended Tech Stack/setting-up-the-gameface-stack.mdx b/src/content/docs/phase-2-planning-and-setup/Recommended Tech Stack/setting-up-the-gameface-stack.mdx index 9b8f69a..cec6e10 100644 --- a/src/content/docs/phase-2-planning-and-setup/Recommended Tech Stack/setting-up-the-gameface-stack.mdx +++ b/src/content/docs/phase-2-planning-and-setup/Recommended Tech Stack/setting-up-the-gameface-stack.mdx @@ -69,7 +69,7 @@ cd my-game-ui npm install ``` -Once the installation is complete, you can see how to [preview the project in gameface](#connect-to-the-gameface-player) in the next section. +Once the installation is complete, you can see how to preview the project in Gameface in the next section. ## Connect to the Gameface Player @@ -84,7 +84,7 @@ Regardless of which option you chose above, the steps to start your local server npm run dev ``` - This will start a local web server, usually at `http://localhost:3000`. + This will start a local web server, usually at `http://localhost:3000/`. 2. #### Launch the Player Instead of viewing your UI in a standard web browser like Chrome, you should view it directly in the Gameface Player to ensure 100% engine accuracy. @@ -110,7 +110,7 @@ Regardless of which option you chose above, the steps to start your local server :::caution[Gameface UI URL] Gameface UI comes with 2 pre built in samples. - If you went with the Gameface UI template, make sure to change the url to `http://localhost:3000/menu` or `/hud` depending on which preset sample you wish to preview. + If you went with the Gameface UI template, make sure to change the url to `http://localhost:3000/menu/` or `http://localhost:3000/hud/` depending on which preset sample you wish to preview. Otherwise you will see a blank page. ::: @@ -120,8 +120,8 @@ Regardless of which option you chose above, the steps to start your local server Your local environment is now running smoothly inside the Gameface Player! Where you go next depends on which starter project you chose: -* **If you chose Option B (The Blank Starter):** Move on to [Workflow Planning](../../workflow-planning). -We will discuss setting up your IDE, version control, and how to translate your mindset from UMG/Unity to web & Gameface technologies. - * **If you chose Option A (The Gameface UI Library):** Jump right into [Prototyping and Developing (Gameface UI)](../../prototyping-and-developing). -We will explore the Gameface UI's file structure and show you how to rapidly prototype a view using the pre-made components. \ No newline at end of file +We will explore the Gameface UI's file structure and show you how to rapidly prototype a view using the pre-made components. + +* **If you chose Option B (The Blank Starter):** Move on to [Workflow Planning](/phase-2-planning-and-setup/workflow-planning/workflow-planning/). +We will discuss setting up your IDE, version control, and how to translate your mindset from UMG/Unity to web & Gameface technologies. \ No newline at end of file diff --git a/src/content/docs/phase-2-planning-and-setup/alternative-frameworks.mdx b/src/content/docs/phase-2-planning-and-setup/alternative-frameworks.mdx new file mode 100644 index 0000000..f1cab13 --- /dev/null +++ b/src/content/docs/phase-2-planning-and-setup/alternative-frameworks.mdx @@ -0,0 +1,29 @@ +--- +title: Alternative Frameworks +description: Learn about alternative frameworks that you can use to build your Gameface projects. +sidebar: + order: 3 +--- + +:::note[TO DO] +Use tabs to distinguish between the different frameworks. + +Either: +- write a single article and use tabs to distinguish between the different frameworks or +- write a separate article for each framework and wrap it in a tab component. + +Frameworks to cover: +- React, +- Preact, +- Vue, +- Svelte +::: + +## Topics + +- Degit or install with CLI the vite react gameface template + +- Go over the structure and how to run and build the project + +- Cover some important caveats about the framework in Gameface + diff --git a/src/content/docs/phase-2-planning-and-setup/development-environment-and-tools.mdx b/src/content/docs/phase-2-planning-and-setup/development-environment-and-tools.mdx index 7117d4c..7916d3d 100644 --- a/src/content/docs/phase-2-planning-and-setup/development-environment-and-tools.mdx +++ b/src/content/docs/phase-2-planning-and-setup/development-environment-and-tools.mdx @@ -9,6 +9,10 @@ draft: true import Summary from 'coherent-docs-theme/components/Summary.astro'; import Highlight from 'coherent-docs-theme/components/Highlight.astro'; +:::note[TO DO] +- Complete article whenever all the tools and the public player are live. +::: + To set you up for success with Gameface, we provide a suite of specialized tools designed to streamline your workflow. diff --git a/src/content/docs/phase-2-planning-and-setup/prototyping-and-developing.mdx b/src/content/docs/phase-2-planning-and-setup/prototyping-and-developing.mdx index aa2d2fa..39746b4 100644 --- a/src/content/docs/phase-2-planning-and-setup/prototyping-and-developing.mdx +++ b/src/content/docs/phase-2-planning-and-setup/prototyping-and-developing.mdx @@ -12,6 +12,10 @@ import { FileTree, Steps, Code } from '@astrojs/starlight/components'; import menuPreviewVideo from '../../../assets/phase-2/prototyping-and-developing/gameface-ui-menu-preview.webm' import hmrPreview from '../../../assets/phase-2/prototyping-and-developing/HMR-preview.webm' +:::note[TO DO] +- add a valid link to the The Vite Gameface style transformer when it is live. +::: + Gameface UI is an official boilerplate powered by Vite and SolidJS, pre-configured with a comprehensive library of game-ready components. @@ -317,4 +321,4 @@ We have also integrated some of our own tools and libraries directly into the bo This is just scratching the surface of what Gameface UI can do. We will be exploring more of its features and capabilities in the upcoming articles, including how to prototype quickly and create custom components. -If you are ready to finalize your setup, move on to the [Workflow Planning](../workflow-planning/) article, where we will discuss version control and IDE configuration. \ No newline at end of file +If you are ready to finalize your setup, move on to the [Workflow Planning](/phase-2-planning-and-setup/workflow-planning/workflow-planning/) article, where we will discuss version control and IDE configuration. \ No newline at end of file diff --git a/src/content/docs/phase-3-layout-assets-and-styling/Animations/animation-libraries.mdx b/src/content/docs/phase-3-layout-assets-and-styling/Animations/animation-libraries.mdx new file mode 100644 index 0000000..0d5785a --- /dev/null +++ b/src/content/docs/phase-3-layout-assets-and-styling/Animations/animation-libraries.mdx @@ -0,0 +1,371 @@ +--- +title: "Animation Libraries in Gameface" +description: + "Learn when to use tested animation libraries in Gameface, how to set them up, and which engine-specific limits to keep in mind before adding a + JavaScript animation library to your UI." +sidebar: + order: 5 +tableOfContents: + maxHeadingLevel: 2 +--- + +import Summary from "coherent-docs-theme/components/Summary.astro"; +import Highlight from "coherent-docs-theme/components/Highlight.astro"; +import Link from "coherent-docs-theme/components/Link.astro"; +import { Tabs, TabItem } from "@astrojs/starlight/components"; + +:::note[TO DO] + +Should we extensively test all of the listed libraries? +I tested if they run without errors but haven't done any more extensive testing of different library methods and such. + +::: + + + CSS transitions, CSS keyframes, SVG animations, WAAPI control, and sprite sheets should remain your default animation tools in Gameface. Animation + libraries are useful when the UI needs designer-authored motion, complex timeline sequencing, or + library-level callbacks that would be difficult to maintain with CSS alone. + + +## Use CSS First + +In browser UI development, it is common to install an animation library as soon as a screen needs motion. In a game UI, that habit needs more +discipline. JavaScript animation libraries calculate values on the JavaScript thread, usually on every animation frame. That work competes with input +handling, state updates, data-binding updates, and any other UI logic running in the same view. + +Gameface already gives you several native animation tools: + +1. **CSS transitions** for simple state changes like hover, focus, selected, disabled, or expanded. +2. **CSS keyframes** for looping or multi-step effects that do not need runtime sequencing. +3. **WAAPI control** for pausing, reversing, seeking, or replaying CSS-authored animations from JavaScript. +4. **Sprite sheets** for pre-rendered VFX where the final visual should come from an offline art tool. + +Reach for an animation library only when the project needs behavior that those tools do not express cleanly. Common examples include a long +end-of-match results sequence, an onboarding flow with multiple timed callouts, or an After Effects animation exported by the motion design team. + +:::note[The Practical Boundary] + +Libraries can organize complex motion, but they do not bypass Gameface's supported HTML, CSS, SVG, and DOM feature set. If a library tries to animate +an unsupported property or relies on an unsupported browser API, that part of the animation still will not work. + +::: + +## Choosing a Library + +The libraries below are documented as tested options that can run out of the box in Gameface, within the feature boundaries of the engine. Treat them +as separate tools rather than interchangeable ways to move elements on screen. + +- Use Lottie when the source of truth is an After Effects composition exported as JSON. It is best for designer-authored vector + motion that should play back as an asset. +- Use GSAP when you need a maintained JavaScript timeline with sequencing, easing, callbacks, and runtime control across many + DOM elements. +- Use AnimeJS when you need a lighter JavaScript animation library for DOM element tweening, simple sequencing, or staggered + effects. +- Use Framer Motion when the UI is built in React and you want React-level animation primitives for components, variants, and + transitions. +- Fallback to CSS or WAAPI when the animation is a simple UI state transition, an idle loop, a loading pulse, or a basic panel + entrance. + +Other animation libraries may also work, but they have not been explicitly tested in the same way. If a library almost works and only depends on a +small missing DOM method, a narrow polyfill can be reasonable. If it depends on a larger unsupported rendering feature, choose a different approach +instead. + +The tabs below show a minimal setup for each tested library and the Gameface-specific rules that matter most when using it. + + + + Lottie usually refers to two related pieces: a JSON animation exported from After Effects, often through the Bodymovin plugin, and the + JavaScript library that renders that JSON at runtime. Gameface officially supports Lottie Light, which renders through + SVG and does not support expressions or effects. + + Use Lottie when the animation should be owned by motion design rather than recreated by hand in CSS. Level-up bursts, reward reveals, stylized + loaders, and branded transition moments are good candidates. + + ### Installation & Setup + + Install it through your package manager for project with bundlers (like Vite or Webpack): + + ```bash title="Terminal" + npm i lottie-web-light + ``` + + For a direct script setup, download the Lottie Light build and serve it with the rest of your UI assets: + + ```html title="lottie-page.html" + + + ``` + + ### Basic Usage + + Start with a concrete container. `Bodymovin` usually generates an inline SVG with `width="100%"` and `height="100%"`, so the parent element **must + have an explicit size:** + + ```html title="lottie-container.html" +
+ ``` + + ```css title="lottie-container.css" + .level-up-lottie { + width: 32vh; + height: 32vh; + } + ``` + + Then load the animation with the SVG renderer. The example below uses the global `bodymovin` object from `lottie_light.js`: + + ```js title="level-up-lottie.js" ins="renderer: \"svg\"" ins="animation.setSubframe(false);" + const container = document.getElementById("level-up-lottie"); + + const animation = bodymovin.loadAnimation({ + container, + path: "./assets/animations/level-up.json", + renderer: "svg", + loop: false, + autoplay: true, + }); + + // Snap playback to exported frames instead of interpolating subframes. + animation.setSubframe(false); + ``` + + :::tip[When Lottie Fits] + + Lottie is a good fit when the animation is mostly vector-based and the exported JSON is the asset you want to ship. If the composition depends + on many embedded raster images, a sprite sheet or video may be easier to budget and test. + + ::: + + :::caution[Lottie-Specific Limits] + + Use the SVG renderer and prefer the Lottie Light build. The Lottie web player does not work out of the box in Gameface, and `dotLottie` is not + currently supported. SVG filters, `mix-blend-mode` inside SVG, WebP images inside the Lottie asset, multiline text, and dynamic After Effects + text boxes are not supported by the documented Gameface Lottie path. + + ::: +
+ + + GSAP is useful when the UI needs a timeline rather than a single animation. A post-match screen, for example, might fade in the panel, count up + rewards, stagger player rows, reveal buttons, and fire callbacks at specific points in the sequence. + + ### Installation & Setup + + For project with bundlers (like Vite or Webpack): + ```bash title="Terminal" + npm i gsap + ``` + + For a direct script setup, download the production build and serve it with the rest of your UI assets: + ```html title="match-results-page.html" + + + ``` + + ### Basic Usage + + The markup can stay ordinary. The library controls the elements through selectors: + + ```html title="match-results.html" +
+

Victory

+
+
+
+ +
+ ``` + + A single tween is enough for a simple property change: + + ```js title="match-results.js" + gsap.to(".match-results__xp-fill", { + width: "100%", + duration: 1.5, + ease: "power2.out", + }); + ``` + + Timelines are where GSAP becomes more useful. They let you describe the order of a sequence in one place: + + ```js title="match-results-timeline.js" ins="gsap.timeline" + const resultsTimeline = gsap.timeline({ paused: true }); + + resultsTimeline + .from(".match-results", { + opacity: 0, + y: 24, + duration: 0.35, + ease: "power2.out", + }) + .to(".match-results__xp-fill", { + width: "100%", + duration: 1.2, + ease: "power2.out", + }) + .from(".match-results__continue", { + opacity: 0, + y: 12, + duration: 0.25, + }); + + resultsTimeline.play(); + ``` + + :::note[The Tradeoffs] + + GSAP gives you strong sequencing, callbacks, easing presets, and runtime control. The cost is that the animated values are computed in + JavaScript, so it should be reserved for screen-level choreography rather than hover states, small idle loops, or basic fades that CSS can run + natively. + + ::: +
+ + + AnimeJS is a smaller JavaScript animation library for DOM element tweening, sequencing, and staggered effects. It is a reasonable choice when a + screen needs a little more orchestration than CSS provides, but the project does not need the full GSAP timeline feature set. + + ### Installation & Setup + + For project with bundlers (like Vite or Webpack): + ```bash title="Terminal" + npm i animejs + ``` + + For a direct script setup, download the browser build and serve it with the rest of your UI assets: + + ```html title="loadout-page.html" + + + ``` + + ### Basic Usage + + A common use case is a menu where items enter in order: + + ```html title="loadout-menu.html" + + ``` + + The configuration object defines the target elements, final values, timing, easing, and stagger: + + ```js title="loadout-menu.js" ins="anime.stagger(80)" ins="translateX: [\"-4vh\", \"0vh\"]" + anime({ + targets: ".loadout-menu__item", + opacity: [0, 1], + translateX: ["-4vh", "0vh"], + delay: anime.stagger(80), + duration: 450, + easing: "easeOutQuad", + }); + ``` + + Always include explicit units for dimensional values. For example, use `top: "100px"` rather than `top: 100`, and use `"4vh"` rather than `4` + when the target property expects a length. + + :::caution[SVG Animation Boundary] + + AnimeJS can animate regular DOM elements in Gameface, but some AnimeJS SVG animation features are not supported or are not part of the tested + path. Before relying on SVG path motion, SVG attribute tweening, or shape animation through AnimeJS, test that exact use case in Gameface + Player. For production SVG animation, CSS keyframes, CSS transitions, WAAPI control, or direct attribute updates are usually safer. + + ::: + + :::note[The Tradeoffs] + + AnimeJS keeps simple staggered sequences compact, but it still runs animation work through JavaScript. Use it for bounded UI sequences, not for + permanent idle effects or basic state changes that CSS can express directly. + + ::: + + + + Framer Motion is a React animation library. Use it only when the UI screen is already built in React and the team wants component-level + animation APIs such as variants, transitions, layout-aware component states, or declarative entrance and exit behavior. + + ### Installation & Setup + + Install it in the React project that builds your Gameface view: + + ```bash title="Terminal" + npm install framer-motion + ``` + + ### Basic Usage + + The basic React pattern is to import `motion` and replace the element you want to animate with a motion-enabled element: + + ```jsx title="RewardPanel.jsx" ins="motion." + import { motion } from "framer-motion"; + + export function RewardPanel() { + return ( + +

Reward Unlocked

+ +
+ ); + } + ``` + + :::note[The Tradeoffs] + + Framer Motion is useful when animation state should live with React component state. It is not a reason to move a non-React screen to React, and + it should not replace simple CSS transitions or CSS keyframes for routine visual feedback. + + ::: +
+ +
+ +## Gameface Constraints + +Each library is limited by the same rendering and DOM capabilities as the rest of your Gameface UI. A library can provide timing, sequencing, easing, +and callbacks, but it cannot make an unsupported CSS property, SVG feature, asset format, or browser API behave as if it were supported. + +For GSAP, AnimeJS, and Framer Motion, the practical rule is simple: animate properties that Gameface already supports. +Transforms, opacity, and supported dimensional properties are safer choices than library demos copied directly from browser-focused examples. + +For Lottie, test the exported animation early in the Gameface Player. After Effects compositions can contain features that are valid in the Lottie +ecosystem but outside the supported Gameface path, such as SVG filters, SVG blend modes, dynamic text, or unsupported embedded image formats. + +For libraries not listed above, start with the smallest representative prototype. Test the exact property, SVG feature, DOM method, callback, or +module format that your screen depends on. If the gap is small and local, such as a missing DOM convenience method, a focused polyfill may be enough. +If the library depends on unsupported rendering behavior, a polyfill will not make that feature render correctly. + +:::note[Event Timing] + +Gameface supports the `animationend` event for CSS animations. If all you need is to run code after a CSS animation finishes, you may not need a +library. Use a library when you need timeline callbacks at specific points during a longer sequence, not only at the end. + +::: + +:::tip[Keep the Library Layer Small] + +The best use of an animation library in Gameface is orchestration. Let the library decide when a bounded sequence starts, waits, staggers, or calls +back into your UI code. Keep routine visual states in CSS so the engine can handle them without JavaScript per-frame work. + +::: + +## Learn More + +- Lottie Web documentation +- Lottie Light download page +- GSAP documentation +- AnimeJS documentation +- Framer Motion documentation + +## Next Steps + +This closes the Phase 3 animation track. Continue to [Mocking Data](/phase-4-logic-and-interactions/mocking-data/) to start wiring styled UI screens +to changing data and interaction logic. diff --git a/src/content/docs/phase-3-layout-assets-and-styling/Animations/animejs.mdx b/src/content/docs/phase-3-layout-assets-and-styling/Animations/animejs.mdx deleted file mode 100644 index 60e0544..0000000 --- a/src/content/docs/phase-3-layout-assets-and-styling/Animations/animejs.mdx +++ /dev/null @@ -1,26 +0,0 @@ ---- -title: "AnimeJS: Setup, Usage, and Gotchas" -description: Coming soon! -sidebar: - order: 6 -draft: true ---- - -AnimeJS is a lightweight, highly flexible JavaScript animation library. It serves as an excellent alternative to GSAP for teams looking to sequence UI animations with a smaller bundle size. - -## Topics - -* **Why Choose AnimeJS:** * **Concept:** Compare AnimeJS briefly to GSAP. Note that AnimeJS is often preferred for its smaller footprint and excellent built-in support for SVG path animations and staggering. -* **Installation & Setup:** * **How-to:** Show how to import the AnimeJS library into a Gameface project. -* **Basic Usage Example:** * **How-to:** Provide a minimal setup script demonstrating how to animate a property using the `anime({ ... })` configuration object. -```js - anime({ - targets: '.menu-item', - translateX: 250, - delay: anime.stagger(100), // Staggering is AnimeJS's superpower - duration: 800, - easing: 'easeInOutQuad' - }); -``` - -* **Gameface Performance Gotchas:** * **Gotcha:** Just like GSAP, AnimeJS runs on the JavaScript thread. Warn developers to avoid using it for continuous, never-ending animations or basic state toggles, and to always prefer CSS transitions for those tasks to maintain optimal FPS. \ No newline at end of file diff --git a/src/content/docs/phase-3-layout-assets-and-styling/Animations/gsap.mdx b/src/content/docs/phase-3-layout-assets-and-styling/Animations/gsap.mdx deleted file mode 100644 index 3933464..0000000 --- a/src/content/docs/phase-3-layout-assets-and-styling/Animations/gsap.mdx +++ /dev/null @@ -1,25 +0,0 @@ ---- -title: "GSAP: Setup, Usage, and Gotchas" -description: Coming soon! -sidebar: - order: 5 -draft: true ---- - -GSAP is the industry heavyweight for complex, sequenced JavaScript animations. While incredibly powerful, it must be used carefully in a game engine to avoid performance drops. - -## Topics - -* **When to use GSAP:** * **Concept:** Explain that GSAP excels at complex timeline sequencing (e.g., an end-of-match screen where 15 different UI elements fade, slide, and bounce in a strict choreographed order). -* **Installation & Setup:** * **How-to:** Briefly show how to import GSAP into the project via NPM or a standard script tag. -* **Basic Usage Example:** * **How-to:** Provide a minimal example of a `gsap.to()` tween and how to chain animations together using a GSAP Timeline (`gsap.timeline()`). - -```js - gsap.to(".xp-bar-fill", { - width: "100%", - duration: 1.5, - ease: "power2.out" - }); -``` - -* **The Performance Gotcha:** * **Gotcha:** Reiterate the golden rule: GSAP relies heavily on the JavaScript thread to calculate values every single frame. Warn developers *never* to use GSAP for simple hover effects, infinite looping idle animations, or basic UI fades, as this steals CPU cycles from the game engine. Reserve GSAP strictly for complex choreographies. \ No newline at end of file diff --git a/src/content/docs/phase-3-layout-assets-and-styling/Animations/lottie.mdx b/src/content/docs/phase-3-layout-assets-and-styling/Animations/lottie.mdx deleted file mode 100644 index 7a5e75f..0000000 --- a/src/content/docs/phase-3-layout-assets-and-styling/Animations/lottie.mdx +++ /dev/null @@ -1,27 +0,0 @@ ---- -title: "Lottie Animations: Setup, Usage, and Gotchas" -description: Coming soon! -sidebar: - order: 4 -draft: true ---- - -Gameface natively supports Lottie, allowing you to export complex, high-fidelity vector animations directly from Adobe After Effects into your game UI. Here is how to install and optimize the Lottie player. - -## Topics - -* **What is Lottie:** * **Concept:** Explain that Lottie uses a JSON file exported from After Effects (via the Bodymovin plugin) to render complex animations. It is the industry standard for UI motion graphics. -* **Installation & Setup:** * **How-to:** Show developers how to include the `lottie-web` player library in their Gameface project (either via NPM in their Vite stack or as a direct ` + + + +
Embedded HTML in SVG
+
+ + + + + + +``` + +Before relying on a less common SVG element or attribute, check the official Gameface SVG support table. This is especially important when importing SVGs exported from design tools, because those files can include metadata, masks, clip paths, text nodes, grouped effects, or unsupported paint features that are not visible at first glance. + +:::tip[Asset Export Checklist] +Before committing SVG assets, open the exported file and remove unnecessary metadata, hidden layers, editor-specific attributes, and unused groups. Cleaner SVGs are easier to inspect, cheaper to parse, and less likely to depend on unsupported SVG features. +::: + +## Recommended Workflow + +Instead of treating this as a single checklist, make the decision in three short passes: asset type, loading mode, then compatibility validation. + +### Pass 1: Identify the Asset Type + +Start by asking what visual problem the asset solves: + +* If it is high-detail artwork (portraits, painted panels, item art), use a raster format. +* If it is geometric UI structure (icons, borders, separators, symbols), use SVG. + +### Pass 2: Pick the Loading Mode + +Once the asset is SVG, decide how it enters the DOM: + +* Use external loading (`` or CSS `background-image`) for static visuals. +* Use inline SVG only when you must target internal nodes (for example per-path color states, stroke progression, or viewBox-driven effects). + +### Pass 3: Validate Engine Compatibility + +Before integrating exported SVGs from design tools: + +* Open the SVG and remove editor metadata, hidden groups, and unused nodes. +* Check for unsupported features (for example ``, SMIL tags, scripting blocks). +* Cross-check uncommon elements and attributes against the official support table. + +## Next Steps + +With the basic image format and loading strategy in place, continue to [SVG UI Tricks: ViewBox, Strokes, and Fills](/phase-3-layout-assets-and-styling/graphics--shapes/svg-ui-tricks/) to see when inline SVGs become useful for dynamic UI states, minimaps, and vector progress indicators. \ No newline at end of file diff --git a/src/content/docs/phase-3-layout-assets-and-styling/Graphics & Shapes/svg-ui-tricks.mdx b/src/content/docs/phase-3-layout-assets-and-styling/Graphics & Shapes/svg-ui-tricks.mdx index 520b920..2f65794 100644 --- a/src/content/docs/phase-3-layout-assets-and-styling/Graphics & Shapes/svg-ui-tricks.mdx +++ b/src/content/docs/phase-3-layout-assets-and-styling/Graphics & Shapes/svg-ui-tricks.mdx @@ -1,19 +1,564 @@ --- title: "SVG UI Tricks: ViewBox, Strokes, and Fills" -description: Coming soon! +description: "Build dynamic inline SVG UI patterns with fill and stroke variants, viewBox camera control, and stroke-dash progress effects." sidebar: order: 2 -draft: true --- -Inline SVGs are incredibly powerful for interactive game UIs. By manipulating standard SVG properties with CSS and JavaScript, you can create dynamic minimaps, animated skill trees, and endless asset variations. +import Summary from "coherent-docs-theme/components/Summary.astro"; +import Highlight from "coherent-docs-theme/components/Highlight.astro"; +import rarityVariationsVideo from "../../../../assets/phase-3/graphics-and-shapes/svg-rarity-variations.webm"; +import minimapPanningVideo from "../../../../assets/phase-3/graphics-and-shapes/viewbox-minimap-panning.webm"; +import circularProgressVideo from "../../../../assets/phase-3/graphics-and-shapes/progress-circle.webm"; +import progressBarVideo from "../../../../assets/phase-3/graphics-and-shapes/progress-bar.webm"; +import skillTreeVideo from "../../../../assets/phase-3/graphics-and-shapes/skill-tree-node.webm"; -## Topics + + This article focuses on three practical inline SVG techniques used in game UI: dynamic visual variants through fill and stroke, camera-like minimap control through viewBox, and progress/connection effects through stroke dashing. -* **Generating Variations with Fills & Strokes:** * **How-to:** Show how keeping an SVG inline allows developers to use CSS to dynamically change the `fill` and `stroke` properties. Provide a quick example of a single sword icon changing from a gray "disabled" state to a glowing gold "legendary" state just by toggling a CSS class, saving texture memory. -* **Minimaps & Panning with `viewBox`:** * **Concept:** Explain how the `` attribute defines the visible coordinate system. - * **How-to:** Demonstrate how to manipulate the `viewBox` values via JavaScript to zoom in, zoom out, or pan across a massive vector map without physically scaling or moving the DOM element itself. -* **Progress Bars & Connecting Lines (Stroke Dashing):** * **How-to:** Showcase one of the best vector UI tricks: using `stroke-dasharray` and `stroke-dashoffset`. Provide a brief snippet showing how to use these CSS properties to animate a connecting line between two nodes on a skill tree, or how to create a perfect circular health/progress bar that fills up smoothly. + The key principle is to keep SVGs inline only when you need internal node control. For static artwork, keep using external SVGs as covered in the previous article. -## Notes -We can directly use the playground examples as showcases if applicable. \ No newline at end of file + + +## Why Inline SVG Matters for Stateful UI + +When the same icon or shape appears in multiple gameplay states, exporting a new image for each state quickly creates asset duplication. Inline SVG +lets you keep one geometry definition and apply different visual states with classes, variables, and data attributes. + +This is valuable for: + +- Rarity tiers, locked/unlocked states, cooldown overlays, and quest-state highlights. +- Reusable widgets, such as minimaps and radial meters, where geometry remains constant but appearance changes each frame. +- UI systems that need rapid iteration without re-exporting large icon sets. + +:::note[The Tradeoff] + +Inline SVG gives precise control over internal paths, but every inline node is still part of the DOM tree. Use this pattern for stateful graphics, not +for every decorative icon in large repeated lists. + +::: + +## Generating Variations with Fills and Strokes + +A typical use case for inline SVG is handling item rarity. For example, in a game's UI, you may need the same weapon icon to appear in several +different states depending on gameplay needs: + +- neutral (common) +- colored (rare) +- colored and emphasized (legendary) +- disabled (locked) + +Exporting separate images for each variation is possible, but leads to more files to manage and slows down future updates. + +Inline SVG gives you a more maintainable option: keep the geometry in the markup, then let CSS classes control the visual +state. The important part is that each meaningful SVG shape has a class. + +In this example, the blade, guard, and spark can all be styled independently. + +```html title="rarity-icon.html" ins="weapon-icon__blade" ins="weapon-icon__guard" ins="weapon-icon__spark" + +``` + +The SVG itself does not know what rarity it represents. It only exposes named pieces of geometry. The base `.weapon-button` class defines the default +variables, and the SVG paths read from those variables. + +```css title="rarity-icon.css" +.weapon-button { + --blade-fill: #8b8b8b; /* The main blade color (default: neutral weapon) */ + --blade-stroke: transparent; /* Optional outline for blade (invisible in base state) */ + --guard-fill: #373330; /* The color of the sword guard */ + --spark-fill: transparent; /* Spark highlight (revealed for legendary, hidden by default) */ + --spark-opacity: 0; /* Spark visibility */ +} + +.weapon-icon { + width: 58px; + height: 58px; +} + +.weapon-icon__blade { + fill: var(--blade-fill); + stroke: var(--blade-stroke); + stroke-width: 1.5; + transition: + fill 140ms ease, + stroke 140ms ease; +} + +.weapon-icon__guard { + fill: var(--guard-fill); + transition: fill 140ms ease; +} + +.weapon-icon__spark { + fill: var(--spark-fill); + opacity: var(--spark-opacity); + transition: + fill 140ms ease, + opacity 140ms ease; +} +``` + +:::note[Why It Works] + +CSS variables set on the button are passed down to the SVG inside it. This means each SVG path can use those variables for its colors and styles. So, +you can change the whole icon's look just by updating the class or variables on the button, without repeating code inside the SVG. + +::: + +Once the base styles exist, rarity variations only need to apply classes that override variables: + +In this example, there are three modifier states beyond the base style: + +- The rare state changes the blade color and adds a stroke. +- The legendary state keeps the stronger blade treatment and reveals the spark path. +- The disabled state desaturates the icon by changing the blade and guard values. + +```css title="rarity-variations.css" +.weapon-button--rare { + /* Only change blade and add a stroke to simulate shine */ + --blade-fill: #f2a34e; + --blade-stroke: #ffd89d; +} + +.weapon-button--legendary { + /* Keep the stronger blade treatment and reveal the spark path */ + --blade-fill: #4ef2d9; + --blade-stroke: #c8fff6; + --spark-fill: #c8fff6; + --spark-opacity: 1; +} + +.weapon-button--disabled { + /* Desaturate the icon by changing the blade and guard values */ + --blade-fill: #3f3f3f; + --guard-fill: #2a2a2a; +} +``` + +Applying a variation is then just a class change on the base element. The SVG paths update automatically because their `fill`, `stroke`, and `opacity` +are connected to the variables. + +An example change of the state could look like this: + +```js title="rarity-toggle.js" +const button = document.getElementById("preview-button"); + +function applyRarity(rarityClass) { + button.classList.remove("weapon-button--rare", "weapon-button--legendary", "weapon-button--disabled"); + + if (rarityClass) { + button.classList.add(rarityClass); + } +} + +applyRarity("weapon-button--legendary"); +applyRarity(""); /* Return to the base/common state */ +``` + +:::tip[The Performance Win] + +A single SVG structure with class-driven variables scales cleanly across many states. You avoid creating separate icon files for each visual variant +while keeping the state system easy to audit and extend. + +::: + +The entire example looks like this: + + + +## Minimap Panning with `viewBox` + +Another useful inline SVG pattern is using `viewBox` as a camera for maps and minimaps. Instead of moving every map path individually, the SVG keeps +the same UI size and you change which part of its internal coordinate system is visible. + +The important distinction is that the SVG has two different sizes: + +- The **CSS size**, which controls how large the map appears in your UI. +- The **`viewBox` size**, which controls which internal SVG coordinates are currently visible. + +The `viewBox` attribute is made of four numbers: + +```html +viewBox="x y width height" +``` + +- `x` and `y` define the top-left position of the visible window inside the SVG map. +- `width` and `height` define how much of the SVG map is visible. +- Increasing `x` or `y` pans across the map. +- Reducing `width` and `height` zooms in because the same screen area now shows a smaller part of the SVG coordinate space. + +### Defining the Map Coordinate Space + +Set up an SVG with a `viewBox` so we can define the coordinate space and the initial visible area. This gives us a baseline before we pan and zoom by +changing the `viewBox` values: + +```html title="minimap.html" ins="viewBox" +
+ + + +
+``` + +From here, all map movement and zooming comes from updating `viewBox`. + +The `viewBox="0 0 400 400"` value means: + +- Start viewing the SVG from coordinate `0, 0`. +- Show `400` units horizontally. +- Show `400` units vertically. + +:::note[Match the authored coordinate space] + +`viewBox` values should match the coordinate space your SVG was authored in. If your paths were drawn in a `0..400` by `0..400` space, use +`viewBox="0 0 400 400"` as the baseline. + +This also sets camera bounds. With a `400x400` authored map, the camera's max top-left position is computed from `MAP_WIDTH - visibleWidth` and +`MAP_HEIGHT - visibleHeight`, where `MAP_WIDTH` and `MAP_HEIGHT` are both `400`. + +Aspect ratio still matters, but ratio alone is not enough. The coordinate range must align with how the SVG content is authored. + +::: + +```css title="minimap.css" +.map-frame { + width: 56vh; + height: 56vh; + border: 0.12vh solid #2a3b49; + border-radius: 0.8vh; + overflow: hidden; + background: #0f151c; +} + +.world-map { + width: 100%; + height: 100%; +} +``` + +Here the map is sized with `vh` so it scales with the game viewport. You could use `px`, `vh`, `vmax`, or a layout-specific variable. The `viewBox` +logic would stay the same because `viewBox` does not use CSS units. It uses the SVG's internal coordinate system. + +In other words, `width: 56vh` and `height: 56vh` only define the final UI box on screen. The internal map is still `400x400` SVG units, and every +point, road, marker, and region is interpreted inside that coordinate space. When the UI box grows or shrinks, the SVG content scales +proportionally to fit that box. + +### Building the `viewBox` Camera + +Treat the `viewBox` as a rectangular camera window moving over the SVG map: + +- `panX` controls the camera's left edge. +- `panY` controls the camera's top edge. +- `visibleWidth` controls how much of the map is visible horizontally. +- `visibleHeight` controls how much of the map is visible vertically. + +Start by storing the full map size and the current camera state: + +```js title="minimap.js" +const map = document.getElementById("world-map"); + +const MAP_WIDTH = 400; +const MAP_HEIGHT = 400; + +let zoom = 1; +let panX = 0; +let panY = 0; +``` + +The zoom value does not scale the SVG element directly. Instead, it changes how large the visible window is. At `zoom = 1`, the visible window is the +full `400x400` map. At `zoom = 2`, the visible window becomes `200x200`, so the same UI area displays a smaller portion of the map and appears zoomed +in. + +Notice that this calculation never reads the CSS size of `.map-frame`. Whether the map is rendered as `56vh`, or any other responsive size, the camera +math still operates in `400x400` SVG coordinates. + +```js title="minimap-camera-size.js" +const visibleWidth = MAP_WIDTH / zoom; +const visibleHeight = MAP_HEIGHT / zoom; +``` + +After calculating the camera size, clamp its position (bounds check). Without this clamp, the camera could move past the edge of the SVG and reveal +empty space. + +```js title="minimap-camera-clamp.js" +const x = Math.max(0, Math.min(panX, MAP_WIDTH - visibleWidth)); +const y = Math.max(0, Math.min(panY, MAP_HEIGHT - visibleHeight)); +``` + +Finally, write the four camera values back into the SVG: + +```js title="minimap-apply-viewbox.js" +map.setAttribute("viewBox", `${x} ${y} ${visibleWidth} ${visibleHeight}`); +``` + +Combined into a function, the camera update looks like this: + +```js title="minimap-camera.js" +function applyViewBox() { + const visibleWidth = MAP_WIDTH / zoom; + const visibleHeight = MAP_HEIGHT / zoom; + + const x = Math.max(0, Math.min(panX, MAP_WIDTH - visibleWidth)); + const y = Math.max(0, Math.min(panY, MAP_HEIGHT - visibleHeight)); + + map.setAttribute("viewBox", `${x} ${y} ${visibleWidth} ${visibleHeight}`); +} + +applyViewBox(); +``` + +### Moving the camera with mouse input + +A common pattern is to use mouse input to move the camera. This is done by tracking the mouse position and converting it into SVG coordinates. + +For mouse-based map navigation, connect dragging to `panX` / `panY` and the mouse wheel to `zoom`. + +```js title="mouse-map-controls.js" +let isDragging = false; +let lastMouseX = 0; +let lastMouseY = 0; + +map.addEventListener("mousedown", (event) => { + isDragging = true; + lastMouseX = event.clientX; + lastMouseY = event.clientY; +}); + +map.addEventListener("mousemove", (event) => { + if (!isDragging) return; + + const visibleWidth = MAP_WIDTH / zoom; + const visibleHeight = MAP_HEIGHT / zoom; + const deltaX = event.clientX - lastMouseX; + const deltaY = event.clientY - lastMouseY; + + // Convert screen movement into SVG coordinate movement. + panX -= deltaX * (visibleWidth / map.clientWidth); + panY -= deltaY * (visibleHeight / map.clientHeight); + + lastMouseX = event.clientX; + lastMouseY = event.clientY; + + applyViewBox(); +}); + +map.addEventListener("mouseup", () => { + isDragging = false; +}); + +map.addEventListener("wheel", (event) => { + event.preventDefault(); + + zoom += event.deltaY < 0 ? 0.2 : -0.2; + zoom = Math.max(1, Math.min(zoom, 8)); + + applyViewBox(); +}); +``` + +The end result looks like this: + + + +:::note[Why It Works] + +The drag calculation is the bridge between the two coordinate systems. `deltaX` and `deltaY` come from the mouse in screen pixels, while `panX` and +`panY` are SVG coordinates. Dividing the visible SVG window by the rendered element size converts one screen pixel into the correct amount of SVG map +movement. This is why the same interaction continues to work regardless of the map's UI size. + +You are not scaling or translating the DOM node itself. You are changing the visible coordinate window inside the SVG. The map can remain a fixed-size +UI component while the internal vector coordinate system handles the camera movement. + +This also avoids maintaining multiple raster map resolutions for different zoom levels. The SVG paths remain vector data, so the map stays +resolution-independent compared to texture-based minimap images. + +::: + +::::caution[Complex SVG Cost] + +Changing `viewBox` on a complex SVG can still trigger redraw work. This is usually appropriate for controlled interactions, such as opening a map +screen or dragging a minimap, but you should avoid unnecessary **per-frame** updates on very large SVG maps. If the SVG contains many moving paths, +labels, and decorative layers, profile the interaction in the target UI. + +:::: + +## Progress Bars and Connections with Stroke Dashing + +Some UI elements need to show partial progress along a fixed shape. A loading bar can fill from left to right, a circular meter can reveal around its +rim, or a skill-tree connector can light up as the next node unlocks. Exporting separate images for each step is not practical, because the geometry +is the same and only the visible portion changes. + +Before explaining the progress logic, let's overview the SVG properties involved: + +- `d` defines the shape of the path element. For a straight progress bar, this can be a simple move-and-line command such as + `M 24 40 L 276 40`. +- `pathLength` defines a normalized length for the path. In this example, `pathLength="100"` lets the dash values behave like percentages instead of + using the path's authored coordinate length. +- `stroke` defines the visible color of the path outline. In this pattern, the track usually uses a dim color and the fill path uses the active color. +- `stroke-width` defines how thick the outlined path appears. +- stroke-dasharray defines the dash pattern used to draw the stroke. If you set it to the same value as the path length, the + path becomes one long dash. +- stroke-dashoffset shifts that dash along the path. This is the value you change to hide or reveal the colored stroke. + +These properties work together by turning one path into a controllable reveal. The foreground path is still fully present in the SVG, but the dash +offset hides part of its stroke. At `progress = 0`, the offset equals the full path length and the colored stroke is hidden. At `progress = 1`, the +offset is `0` and the full stroke is visible. + +:::tip[Explicit PathLength] + +Without `pathLength`, the full length of this straight path would come from its coordinates. In the example below, `276 - 24` gives a length of `252`, +which is correct but reads like a magic number in code. Setting `pathLength="100"` gives the path a predictable progress scale. The full dash becomes +`100`, then `stroke-dashoffset: 100` hides it, `stroke-dashoffset: 50` reveals half of it, and `stroke-dashoffset: 0` reveals all of it. + +::: + +### Creating a progress bar + +Start with two paths using the same `d` value. The first path is the dim track. The second path is the colored fill that will be revealed by changing +its dash offset: + +```html title="stroke-progress.html" ins="d="M 24 40 L 276 40"" ins="pathLength="100"" ins="stroke-dasharray="100"" ins="stroke-dashoffset="100"" + + + + + +``` + +The CSS keeps both shapes visually aligned. The foreground path uses the same geometry as the track, but its visible length is controlled by the dash +properties: + +```css title="stroke-progress.css" ins="stroke-dasharray" ins="stroke-dashoffset" +.energy-meter { + width: 300px; + height: 80px; +} + +.meter__track, +.meter__fill { + fill: none; + stroke-width: 10; + stroke-linecap: round; +} + +.meter__track { + stroke: rgba(255, 255, 255, 0.12); +} + +.meter__fill { + stroke: #60c8ff; + stroke-dasharray: 100; /* Match the normalized path length. */ + stroke-dashoffset: 100; /* Start fully hidden. */ +} +``` + +Once the SVG structure exists, the update code only needs to convert a normalized progress value into a dash offset. This is also where the stroke can +switch from an idle color to an active color: + +```js title="stroke-progress.js" ins="pathLength * (1 - progress)" ins="stroke-dashoffset" ins="stroke" +const fillPath = document.getElementById("energy-fill"); + +const pathLength = 100; +const activeColor = "#60c8ff"; +const idleColor = "rgba(255, 255, 255, 0.12)"; + +function setProgress(value) { + const progress = Math.max(0, Math.min(value, 1)); + const offset = pathLength * (1 - progress); + + fillPath.setAttribute("stroke-dashoffset", String(offset)); + fillPath.setAttribute("stroke", progress > 0 ? activeColor : idleColor); +} + +setProgress(0.65); +``` + + + +:::note[Why It Works] + +The core method is to set `stroke-dasharray` to the same value as `pathLength`, then change `stroke-dashoffset` to reveal or hide the stroke. + +`pathLength` does not resize or reshape the SVG path. It normalizes the measurement scale used by dash calculations, so the example can use `100` +instead of carrying the authored coordinate length through the CSS and JavaScript. + +::: + +### Creating a skill tree node connection + +The same idea scales to a skill tree without changing the rendering technique. Each connection can store a path definition and a length, or normalize +the connector with `pathLength="100"` if you want to keep the progress math percentage-based: + +```js title="skill-tree-edge.js" +const edge = { + d: "M 200 50 L 90 155", + length: 100, +}; +``` + +Render a dim ghost path underneath, then render a second path with the same `d` value on top. The game UI logic decides whether a node is locked, +unlocking, or complete, but the SVG layer still only needs the same two values: + +```js title="skill-tree-progress.js" +const dashOffset = edge.length * (1 - progress); + +edgePath.setAttribute("stroke-dasharray", String(edge.length)); +edgePath.setAttribute("stroke-dashoffset", String(dashOffset)); +edgePath.setAttribute("stroke", progress > 0 ? nodeColor : "rgba(255,255,255,0.07)"); +``` + +The end result can look like this: + + + +### Creating a circular progress bar + +This dash pattern is most useful when the shape is not a plain rectangle. A circular health meter, a curved cast bar, a route line on a map can all +use the same offset logic. The geometry can be straight, curved, or circular, while the progress value remains a single number. + +As a matter of fact, this is exactly how our `` component in Gameface UI works. + +```tsx title="Progress.Circle.tsx" + + {/* The outline path is the same as the fill path, but it is not filled. */} + + + +``` + + + +## Next Steps + +Now that you can control inline SVG geometry and styling at runtime, continue to +[Advanced UI Shapes: 9-Slice, Clipping, and Masking](/phase-3-layout-assets-and-styling/graphics--shapes/advanced-ui-shapes/) to combine scalable +borders, hard shape cuts, and alpha masks for more complex panel construction. diff --git a/src/content/docs/phase-3-layout-assets-and-styling/Styling & Scalability/building-responsive-game-ui.mdx b/src/content/docs/phase-3-layout-assets-and-styling/Styling & Scalability/building-responsive-game-ui.mdx index 932c17b..1ab4513 100644 --- a/src/content/docs/phase-3-layout-assets-and-styling/Styling & Scalability/building-responsive-game-ui.mdx +++ b/src/content/docs/phase-3-layout-assets-and-styling/Styling & Scalability/building-responsive-game-ui.mdx @@ -1,15 +1,164 @@ --- title: "Building Scalable & Responsive Game UIs" -description: Coming soon! +description: "Learn how to build responsive game UIs that scale flawlessly across multiple resolutions using viewport units, REMs, and dynamic scale factors." sidebar: order: 2 -draft: true --- -## Topics +import Summary from 'coherent-docs-theme/components/Summary.astro'; +import Highlight from 'coherent-docs-theme/components/Highlight.astro'; -- **The Problem with Hardcoded Pixels**: Discuss why using strict px values breaks down when a game is played across multiple resolutions (e.g., 1080p vs. 4k). -- **Viewport Units for Layouts**: Explain how to use vw and vh units so that layout containers, panels, and margins resize proportionally to the screen's dimensions. -- **Scaling Typography with EM/REM**: Cover the best practices for setting a base font size and using em or rem to ensure text scales consistently with the rest of the UI without relying on expensive JavaScript calculations. -- **Alternative Approaches**: Showcase alternative methods for scalable UI like the current example in our docs (dynamically calculating a scale factor and applying it to the root) and discuss the pros and cons of -each approach. \ No newline at end of file + + A scalable UI is a UI that can dynamically scale up or down to fit bigger or smaller screens. A well-designed responsive interface ensures that components are not distorted or malformed from the scaling, and allows adjustments to fit the screen even if the aspect ratio changes. + + This guide explores the mechanics of scalable layouts in Gameface. We will cover the pitfalls of hardcoded pixels, how to leverage viewport units and REM scaling, and alternative approaches like calculating and applying dynamic scale factors based on a reference resolution. + + +## The Problem with Hardcoded Pixels + +When building a UI, it is tempting to measure and position elements using strict pixel (`px`) values based on a single target screen dimension. +The screen size for which the UI was originally designed is known as the reference resolution. + +However, pixels are absolute units. If your reference resolution is 1080p (1920x1080), a hardcoded `300px` minimap will look perfectly sized. But when the game is rendered on a 4K screen (3840x2160), +the screen has exactly twice the number of pixels in both width and height. Because the minimap remains strictly `300px`, +it will appear physically half the size on the 4K screen, making it incredibly difficult for the player to read. + +:::danger[The 4K Pixel Trap] +If a UI element needs to retain a constant physical size despite the screen resolution , +or if it needs to increase or decrease its size proportionally based on the screen size, you must abandon absolute units in favor of relative sizing. +::: + +## Viewport Units for Layouts + +The most direct way to scale layout containers proportionally is by using **Viewport Units**: vw (viewport width) and vh (viewport height). + +* `1vw` is equal to 1% of the total screen width. +* `1vh` is equal to 1% of the total screen height. + +Because these units are intrinsically tied to the screen's actual dimensions, an element sized with `vw` or `vh` will always occupy the exact same relative physical space on the screen, +whether it is running on a 720p handheld or a 4K monitor. + +```css title="hud.css" +/* This container will always take up exactly 30% of the screen's width + and 100% of its height, regardless of the user's resolution. +*/ +.inventory-panel { + width: 30vw; + height: 100vh; + background: rgba(0, 0, 0, 0.8); +} + +/* Margins and spacing can also use viewport units to maintain + consistent relative spacing from the edge of the screen. +*/ +.minimap { + width: 15vw; + height: 15vw; /* Using vw for both ensures the map stays a perfect square */ + margin-top: 2vh; + margin-right: 2vw; +} +``` + +## Global Layout Scaling with REM Units + +While viewport units (`vw` and `vh`) are excellent for massive layout blocks, using them for every single padding, margin, border, and font size can become tedious and hard to maintain. + +Instead, the most robust way to build a scalable UI is to author your internal component dimensions using rem units. The `rem` unit is a size unit used in CSS that is tied directly to the root (`html`) element's font size. + +If you set the size of all UI elements using `rem` units, you can easily resize them globally by changing the font size of the HTML element. By scaling this single root value, the entire UI—text, buttons, margins, and containers—scales up or down in perfect unison. + +```css title="scalable-components.css" +/* If the root font-size is 10px, this button is 200px wide. + If the root font-size changes to 20px, this button dynamically grows to 400px wide. */ +.action-button { + width: 20rem; + height: 5rem; + padding: 1rem; + font-size: 1.5rem; +} +``` + +To make this actually responsive to the screen, we need a way to automatically adjust that root font size based on the player's resolution. +There are two main ways to achieve this: dynamically via JavaScript , or statically via CSS . + +### The Dynamic Scale Factor Method (JavaScript) + +The industry standard for game UI is to design the interface for a specific **reference resolution** (e.g., 1920x1080). + +A user interface is scaled using a scale factor, which is a numeric value that determines how the size of the UI element will be affected. Comparing the current resolution to the reference resolution gives the scale factor. + +When calculating the scale factor, the sides which are used determine the end result of the scaling. You can compare only the width, only the height, or both. For standard widescreen games, scaling based on the screen's **height** is often preferred to ensure vertical UI panels don't get cut off. + +Here is how you can use JavaScript to dynamically calculate this scale factor and apply it to the root element. + +```javascript title="scale-manager.js" +// Grab the root HTML element to apply our dynamic font-size +const rootElement = document.getElementsByTagName('html')[0]; + +function resize() { + // Get the current physical height of the screen + const windowHeight = window.innerHeight; + + // Compare the current height to our reference height of 1080 + // If the player is on a 2160p (4K) screen, the factor is 2.0 + const scaleFactorY = windowHeight / 1080; + + // Multiply our base CSS design size (e.g., 16px) by the scale factor + const fontSize = 16 * scaleFactorY; + + // Apply the newly calculated font size to the root element + rootElement.style.fontSize = `${fontSize}px`; +} + +// Run once on initialization to set the initial scale +resize(); + +// Update the scale dynamically whenever the window resizes +window.addEventListener('resize', resize); +``` + +:::note[The Tradeoffs] +* **Pros:** This approach gives you absolute precision. It also makes developer handoff incredibly easy: if your UI designer builds a screen in Figma at 1080p, you can translate a `24px` Figma value directly to `1.5rem` (assuming a 16px base) in your CSS without any complex mental math. +* **Cons:** It requires a JavaScript execution and a layout recalculation every time the window resizes. +::: + +### The CSS-Only Method (vh Math) + +If you want to avoid the JavaScript bottleneck entirely, you can manually calculate the Viewport Height (vh) equivalent of your base pixel size and apply it directly to your CSS. + +Assuming our reference wireframe was designed at 1920x1080, and we want our base `1rem` to equal `16px` at that resolution, we calculate the viewport height percentage: +`(16px / 1080px) * 100 = 1.4814vh`. + +Add this rule to the top of your `index.css`: + +```css title="index.css" +html { + font-size: 1.4814vh; +} +``` + +With this single rule applied, an element with `width: 2rem` will render at exactly 32px on a 1080p screen, but will dynamically scale up on a 1440p or 4K screen, maintaining its relative proportion to the height of the window. + +:::note[The Tradeoffs] +* **Pros:** Extremely fast and avoids JS execution overhead during window resizing. Excellent for rapid prototyping. +* **Cons:** Lacks the explicit control of a JavaScript scale manager and can behave unpredictably on extreme ultrawide monitors without additional constraints. +::: + +## Finding the Balance + +There is no "one-size-fits-all" solution when it comes to building a responsive game UI. All of the aforementioned approaches have their benefits depending on your UI's specific needs. + +Using viewport units (`vh` and `vw`) is a fantastic way to ensure that your layout containers and margins scale proportionally to the screen size. +However, relying solely on viewport units for every single UI element can become unwieldy and difficult to maintain, especially for typography and smaller components or when trying to match the design 1:1. + +If you are prototyping quickly and want an easily scalable and responsive layout, the **CSS-only `vh` method** is incredibly efficient. +However, it can be somewhat vulnerable for a final production build when confronted with extreme edge cases, like super-ultrawide displays. + +The **Dynamic Scale Factor via JavaScript** method is highly consistent and provides precise control over how your UI scales. +While it does come with a slight performance trade-off whenever the window gets resized, it is worth noting that in a compiled game environment, players rarely resize their windows mid-gameplay. + +Our general recommendation is to evaluate these trade-offs and find the balance that best fits your project's technical requirements and production timeline. + +## Next Steps + +With your layouts smoothly scaling up and down based on raw screen size, the next challenge is dealing with screens that change shape entirely. Proceed to the [Handling Aspect Ratios with Media Queries](../handling-aspect-ratios-with-media-queries/) article to learn how to keep ultrawide monitors and handheld screens from stretching or breaking your UI components. \ No newline at end of file diff --git a/src/content/docs/phase-3-layout-assets-and-styling/Styling & Scalability/handling-aspect-ratios-with-media-queries.mdx b/src/content/docs/phase-3-layout-assets-and-styling/Styling & Scalability/handling-aspect-ratios-with-media-queries.mdx index 4fca045..67c7594 100644 --- a/src/content/docs/phase-3-layout-assets-and-styling/Styling & Scalability/handling-aspect-ratios-with-media-queries.mdx +++ b/src/content/docs/phase-3-layout-assets-and-styling/Styling & Scalability/handling-aspect-ratios-with-media-queries.mdx @@ -1,19 +1,340 @@ --- title: "Handling Aspect Ratios with Media Queries" -description: Coming soon! +description: "Learn how to use CSS media queries to adapt your game UI for different screen shapes, from 16:9 TVs to 21:9 ultrawide monitors and 16:10 handhelds." sidebar: order: 3 -draft: true --- -## Topics +import Summary from 'coherent-docs-theme/components/Summary.astro'; +import Highlight from 'coherent-docs-theme/components/Highlight.astro'; -- **The Reality of Multiple Screens**: Address the variety of screens modern games run on, from standard 16:9 TVs to 21:9 Ultrawide PC monitors and 16:10 handhelds (like the Steam Deck). + + While scale factors and viewport units ensure your UI components remain the correct physical size, they do not solve the problem of screens fundamentally changing shape. Modern games must support a wide variety of aspect ratios seamlessly. -- **Using Media Queries for Aspect Ratios**: Detail how to apply different CSS rules based specifically on the screen's aspect ratio rather than just raw pixel width. + This guide explores how to use CSS Media Queries to adapt your layouts for different screen proportions. We will cover the differences between querying resolution versus aspect ratio, outline best practices for ultrawide safe zones, and explore Gameface-specific custom media features. + -- **Ultrawide HUD Best Practices**: Discuss common game UX patterns, such as using media queries to push HUD elements closer to the center of the screen on ultrawide monitors so the player doesn't have to physically turn their head to see the minimap or ammo count. +## The Reality of Multiple Screens -- **Resolution vs. Orientation Queries**: Briefly touch on when to query for specific resolutions versus using aspect-ratio or orientation queries to tailor the UI's appearance. +In traditional game development, UI was often built with the assumption that the player was using a standard 16:9 display (like a 1920x1080 PC monitor or a 4K television). Today, the hardware landscape is much more fragmented. -- **Practical Example**: Showcase a simple example of how to restyle a layout based on the aspect ratio (we can directly use the playground for this). \ No newline at end of file +A modern UI must cleanly adapt to: +* **16:9 (Standard):** Typical desktop monitors and console televisions. +* **21:9 / 32:9 (Ultrawide):** Enthusiast PC monitors that are significantly wider relative to their height. +* **16:10 (Handhelds):** Devices like the Steam Deck, which are slightly taller than standard 16:9 screens. +* **4:3 / 5:4 (Retro/Arcade):** Older monitors or specific arcade cabinet builds. + +If your UI relies entirely on absolute anchoring to the extreme edges of the screen (e.g., anchoring a minimap using `right: 0`), +those elements will physically pull much further apart on an ultrawide monitor and crush closer together on a 4:3 monitor. + +## Using Media Queries for Aspect Ratios + +To handle variations in screen shapes, Gameface supports a subset of the CSS3 Media Query standard. A media query acts as a conditional statement for your CSS, enabling or disabling specific style rules based on the current Width and Height of the Gameface View. + +While web developers often use media queries to check the raw pixel width of a browser window (`max-width: 768px`), game UI developers must focus on the **shape** of the screen. This is done using the `aspect-ratio`, `min-aspect-ratio`, and `max-aspect-ratio` features. + +Here is how you establish your baseline layout for standard 16:9 screens: + +```css title="aspect-ratios.css" +/* Default styles for standard 16:9 screens */ +.inventory-panel { + width: 40rem; +} +``` + +Now, to tailor the layout for an ultrawide monitor (like 21:9) so the inventory panel utilizes the extra horizontal space, you add a `min-aspect-ratio` query: + +```css title="aspect-ratios.css" +/* This rule activates only if the screen is wider than 16:9 (e.g., 21:9 Ultrawide) */ +@media (min-aspect-ratio: 21/9) { + .inventory-panel { + /* Expand the panel to utilize the extra horizontal space */ + width: 60rem; + } +} +``` + +Conversely, if the player is using a narrower screen (like a 4:3 retro monitor or arcade cabinet), you can shrink the panel so it doesn't overlap other critical UI elements: + +```css title="aspect-ratios.css" +/* This rule activates only if the screen is narrower than 16:9 (e.g., 4:3) */ +@media (max-aspect-ratio: 4/3) { + .inventory-panel { + /* Shrink the panel to fit the constrained space */ + width: 30rem; + } +} +``` + +:::danger[CSS Nesting Gotcha] +Gameface supports nesting `@keyframes` and other at-rules inside of a media query. However, the ordering of at-rules within nested media queries is currently strict. To ensure your styles override properly, **always put your nested media queries at the very end of your stylesheets**. + +**DON'T DO THIS:** +```css +/* ❌ Bad: Nested media is declared first */ +@media (min-width: 500px) { + @keyframes anim { 0% { background-color: blue; } } +} +/* This global rule will incorrectly override the one above! */ +@keyframes anim { 0% { background-color: red; } } +``` + +**DO THIS:** +```css +/* ✅ Good: Global rule is declared first */ +@keyframes anim { 0% { background-color: red; } } + +/* The nested media query is safely at the end of the file */ +@media (min-width: 500px) { + @keyframes anim { 0% { background-color: blue; } } +} +``` +::: + +## Ultrawide HUDs: Safe Zones vs. Media Queries + +One of the most common UX problems in gaming is the Ultrawide HUD Spread. + +If you anchor your minimap to the top-right corner, it sits perfectly in the player's peripheral vision on a standard 16:9 screen. +However, on a 32:9 super-ultrawide monitor, that minimap is pushed so far to the physical edge of the screen that the player has to physically turn their head to see it. + +To solve this, we can use two different architectural approaches, each with its own pros and cons: + +### Approach 1: The Safe Zone Wrapper + +A common web development instinct is to wrap the critical HUD elements in a centered container with a maximum width (e.g., `max-width: 100rem; margin: 0 auto;`). +By centering this wrapper, you create an invisible "Safe Zone" that keeps elements anchored to where you place them. + +An example implementation of this approach looks like this: + +```html title="hud-wrapper.html" + +
+
100 HP
+
+
+ +``` + +```css title="hud-wrapper.css" +.hud-safezone { + position: relative; + width: 100vw; + max-width: 100rem; /* Locks the UI to a specified width */ + height: 100vh; + margin: 0 auto; /* Centers the container horizontally, creating safe zones on either side on ultrawides */ +} + +.health-bar { + position: absolute; + bottom: 3rem; + left: 3rem; +} + +.minimap { + position: absolute; + top: 3rem; + right: 3rem; + width: 15rem; + height: 15rem; +} +``` + +:::note[The Tradeoffs] +This is structurally robust and highly performant because it avoids media query recalculations entirely. +It guarantees your HUD never stretches beyond a specific boundary, it's great for simple use cases. + +However... it acts as a global constraint. If a specific element within that wrapper needs to bleed out to the screen edge or be positioned differently, the wrapper restricts it. +Furthermore, you lose granular aspect ratio control. A wrapper cannot smoothly fade the opacity or scale down an element *only* when the screen hits a 21:9 ratio. + +**For that level of control, you need media queries.** +::: + +### Approach 2: Pure Aspect-Ratio Media Queries + +Gameface supports the `aspect-ratio`, `min-aspect-ratio`, and `max-aspect-ratio` media features. This allows for granular control over individual elements rather than the entire screen. + +Designers often want to push critical elements (Health, Minimap) inward to a safe zone on an ultrawide, while keeping non-critical elements (Chat Box, Damage Vignettes) glued to the true edges for maximum immersion. +Furthermore, media queries allow you to change other properties—like shrinking elements or lowering opacity—specifically for extreme screen shapes. + +Here, we leave the chat box on the left edge of the screen, but selectively push the minimap inward. + +```html title="hud-media.html" + +
System: Welcome...
+
+ +``` + +```css title="hud-media.css" +/* The Chat Box always stays glued to the extreme left edge for immersion */ +.chat-box { + position: absolute; + bottom: 2rem; + left: 2rem; +} + +/* 1. Default 16:9 Anchor for the Minimap */ +.minimap { + position: absolute; + top: 2rem; + right: 2rem; + width: 15rem; + height: 15rem; +} + +/* 2. Ultrawide (21:9): Push inward slightly so it doesn't spread out */ +@media (min-aspect-ratio: 21/9) { + .minimap { + right: 15vw; + transform: scale(0.9); /* Shrink slightly so it doesn't block the wider cinematic view */ + } +} + +/* 3. Super-Ultrawide (32:9): Push heavily inward into the player's safe zone */ +@media (min-aspect-ratio: 32/9) { + .minimap { + right: 25vw; + transform: scale(0.8); /* Shrink so it doesn't block the wider cinematic view */ + opacity: 0.7; /* Lower opacity to reduce peripheral distraction */ + } +} +``` + +:::note[The Tradeoffs] +Allows fine control over every individual element. But managing dozens of media queries for every single UI element can become verbose, hard to maintain and slightly less performant than a pure structural wrapper. +::: + +### Complex UIs use both! + +If your UI is complex enough to require both rigid centering and granular aspect ratio tweaks, the best practice is to combine both approaches. + +You create two separate layers: an **Edge Layer** (for immersive, full-screen elements) and a **Safe Zone Layer** (for critical HUD elements). + +This gives you the structural safety of Approach 1, but allows you to use `min-aspect-ratio` media queries to achieve granular UX tweaks when the player is on a massive screen. + +```html title="hud-hybrid.html" + +
+
System: Welcome...
+
+ +
+
+
+ +``` + +```css title="hud-hybrid.css" +/* --- STRUCTURAL LAYERS --- */ +.edge-layer { + position: absolute; + width: 100vw; + height: 100vh; +} + +.safe-zone-layer { + position: absolute; + width: 100vw; + max-width: 100rem; /* Locks the critical HUD to 16:9 */ + height: 100vh; + left: 50%; + transform: translateX(-50%); +} + +/* --- ELEMENT STYLING --- */ +.chat-box { + position: absolute; + bottom: 2rem; + left: 2rem; +} + +.minimap { + position: absolute; + top: 2rem; + right: 2rem; + width: 15rem; + height: 15rem; +} + +/* --- GRANULAR UX TWEAKS VIA MEDIA QUERIES --- */ +/* The minimap is already perfectly positioned by the safe-zone-layer. + We only use the media query to tweak its scale and opacity for super-ultrawide users! */ +@media (min-aspect-ratio: 32/9) { + .minimap { + transform: scale(0.8); /* Shrink so it doesn't block the wider cinematic view */ + opacity: 0.7; /* Lower opacity to reduce peripheral distraction */ + } +} +``` + +:::tip[The Best of Both Worlds] +In the Hybrid Approach, you never have to guess or calculate `vw` offsets to push elements around if they need to stick to the center. +The defined size of the `.safe-zone-layer` handles the positioning automatically. + +You only use Media Queries when you want to execute highly specific design changes based on the screen's shape, leaving your CSS clean, scalable, and easy to maintain. +::: + +## Resolution vs. Orientation Queries + +While `aspect-ratio` is your primary tool for responsive game UI, Gameface also supports specific size and orientation expressions: `min-width`, `max-width`, `min-height`, `max-height`, and `orientation`. + +You can chain multiple expressions together using the `and` operator to create highly specific targeting. + +* **`min-width` / `max-width`:** Use these when a specific UI component physically runs out of room to render its content correctly. For example, if your settings menu becomes illegible below 1280px wide regardless of the aspect ratio, use `@media (max-width: 1280px)` to switch the layout to a more compact design. +* **`orientation`:** Gameface supports the values `landscape` and `portrait`. If you are developing a mobile game or a companion app, the player might physically rotate their device, altering the width/height ratio. + +For example, you can seamlessly switch a mobile inventory layout from a horizontal grid to a vertically scrolling list when the device is held upright: + +```css title="mobile-inventory.css" +/* Default: Landscape (Horizontal Layout) */ +.inventory-grid { + display: flex; + flex-direction: row; +} + +/* Portrait (Vertical Layout) */ +@media (orientation: portrait) { + .inventory-grid { + flex-direction: column; + } +} +``` +## Beyond Aspect Ratios: Custom Media Features + +Although managing aspect ratios is the most common use case for media queries, Gameface includes a powerful capability that extends this system: Custom Media Features. + +Your game engine developers can expose custom game state variables directly to the styling engine. This allows frontend developers to write CSS that reacts instantly to changes in the game's settings without needing to write complex JavaScript event listeners. + +For example, backend engineers might expose the current game language or graphics settings as custom media features. Multiple values concerning one feature are mutually exclusive, meaning if `(language: en)` is enabled, `(language: de)` is automatically disabled. + +```css title="custom-features.css" +/* Change the font family dynamically based on the game's active language */ +@media (language: en) { + .dialogue-text { + font-family: 'Open Sans', sans-serif; + } +} + +@media (language: jp) { + .dialogue-text { + font-family: 'Noto Sans JP', sans-serif; + } +} + +/* Simplify expensive UI effects if the player sets Dynamic Range to standard */ +@media (dynamic-range: standard) { + .health-bar { + /* Fallback for lower-end devices */ + background-color: red; + } +} +``` + +:::tip[Performance Impact] +It is important to know that whenever the game engine toggles a custom media feature on or off, it triggers a full style recalculation of the page. While this is highly efficient for global state changes (like switching languages or graphics presets), it should not be used for high-frequency updates (like tracking player health). +::: + +## Next Steps + +Now that you have a firm grasp on architecting scalable and responsive layouts using pure CSS, it is time to look at how we safely modify those styles at runtime. In the next article, [Dynamic Styling in JavaScript](../dynamic-styling-in-javascript/), we will explore how to interact with your CSS using Gameface APIs without causing unnecessary performance bottlenecks. \ No newline at end of file diff --git a/src/content/docs/phase-3-layout-assets-and-styling/Styling & Scalability/writing-maintainable-css.mdx b/src/content/docs/phase-3-layout-assets-and-styling/Styling & Scalability/writing-maintainable-css.mdx index 5350a13..92a0ddd 100644 --- a/src/content/docs/phase-3-layout-assets-and-styling/Styling & Scalability/writing-maintainable-css.mdx +++ b/src/content/docs/phase-3-layout-assets-and-styling/Styling & Scalability/writing-maintainable-css.mdx @@ -1,14 +1,20 @@ --- title: "Writing Maintainable & High-Performance CSS" -description: Coming soon! +description: Learn how to structure your stylesheets for maximum performance in Gameface using flat selectors, the BEM methodology, and preprocessors like SASS. sidebar: order: 1 -draft: true --- import Summary from 'coherent-docs-theme/components/Summary.astro'; import Highlight from 'coherent-docs-theme/components/Highlight.astro'; +:::note[TO DO] +- Delete drafts +- Research the CSS-in-JS section and the suggested libraries. See if they work and how their performance compares. +- Recommend our styles to css plugin in the alternative approaches section. +::: + + ## Topics - **The Cost of Complex Selectors**: Briefly explain why deep, hierarchical CSS selectors are expensive to match in a game engine and can cause performance drops. @@ -23,121 +29,192 @@ Cover how to use variables for standardizing UI colors and typography, and mixin - **Alternative Approaches**: Briefly mention CSS modules or CSS-in-JS as modern alternatives for scoping styles, noting their pros and cons within the Gameface environment. - Examples here as well -import Link from '../../../../components/Link.astro'; - - Writing CSS for a game engine requires a different mindset than standard web development. Because UI rendering shares resources with the game loop, unoptimized styles can quickly lead to frame drops and performance bottlenecks. + Writing CSS for a game engine requires a different mindset than standard web development. + Because UI rendering shares resources with the game loop, unoptimized styles can quickly lead to frame drops and performance bottlenecks. - This guide covers how to optimize your CSS architecture for Gameface. We will explore the performance costs of complex selectors, how to use the BEM methodology and SASS to keep styles flat, and the pros and cons of modern alternatives like CSS Modules. + This guide covers how to optimize your CSS architecture for Gameface. + We will explore the performance costs of complex selectors, how to use the BEM methodology and SASS to keep styles flat, and the pros and cons + of modern alternatives like `CSS Modules`. -:::note[Author's Note] -Because no engine-specific documentation was provided for this section, the following architectural guidelines are based on standard frontend performance best practices, rigorously adapted to fit known Gameface UI execution and styling constraints. -::: - ## The Cost of Complex Selectors In web rendering engines, CSS selectors are evaluated **from right to left**. When the engine encounters a deeply nested selector, it must first find the target element (the rightmost selector) and then traverse all the way up the DOM tree to verify its parent lineage. Consider a hierarchical selector like this: -```css -/* The engine finds all 'a' tags being hovered, then checks if they are inside 'li', then 'ul', etc. */ -.main-menu .settings-panel ul li a:hover { - color: red; +```css title="expensive-selectors.css" +/* The engine finds all elements with the 'offline' class, checks if they are inside 'status', then 'info', etc. */ +.social-menu .player-list .player-card .info .status.offline { + color: gray; } ``` -To apply this style, the engine finds every anchor tag in the document when hovered, checks if it is inside a list item, checks if that list item is in an unordered list, checks if that list is inside `.settings-panel`, and finally checks if it is inside `.main-menu`. +To apply this style, the engine finds every element with the `offline` class, checks if it also has the `status` class, checks if it is inside `.info`, checks if that is inside `.player-card`, and so on. -In a game engine environment, style recalculations happen frequently. Forcing the engine to constantly parse deep, complex selector chains for hundreds of UI elements will spike the CPU usage and cause noticeable frame drops. To maximize performance, **CSS selectors should be as flat as possible.** +In a game engine environment, style recalculations happen frequently. +Forcing the engine to constantly parse deep, complex selector chains for hundreds of UI elements will artificially inflate the "Style Solve" phase of your frame. +To maximize performance and keep your CPU footprint minimal, CSS selectors should be as flat as possible. ## The BEM Methodology -To solve the problem of complex selectors while maintaining a clean codebase, we recommend using **BEM (Block, Element, Modifier)**. BEM is a naming convention that keeps your selectors incredibly flat—usually just a single class deep—while naturally preventing CSS conflicts through strict scoping. +To solve the problem of complex selectors while maintaining a clean, readable codebase, +we recommend using the **BEM (Block, Element, Modifier)** methodology. -* **Block:** The standalone entity that is meaningful on its own (e.g., `button`, `menu`). -* **Element:** A part of a block that has no standalone meaning and is semantically tied to its block (e.g., `menu__item`). -* **Modifier:** A flag on a block or element used to change appearance or behavior (e.g., `button--disabled`). +BEM is a naming convention that keeps your selectors incredibly flat , usually just a single class deep, while naturally preventing CSS conflicts through strict scoping. + +* **Block:** The standalone entity that is meaningful on its own (e.g., `player-card`, `inventory-slot`). +* **Element:** A part of a block that has no standalone meaning and is semantically tied to its block (e.g., `player-card__name`). +* **Modifier:** A flag on a block or element used to change appearance, state, or behavior (e.g., `player-card__status--offline`). ### Before vs. After BEM -Here is how a standard, deeply nested CSS structure compares to a flat BEM structure: +Let's look at a common game UI component—a player card in a social menu. First, here is the HTML structure we want to style: + +```html title="player-card.html" +
+ +
+ xX_Sniper_Xx + Offline +
+
+``` + +Now, let's see how a standard nested CSS structure compares to a flat BEM structure, and why the engine prefers the latter. **Before (Deeply Nested & Expensive):** -```css title="menu.css" +```css title="player-card.css" /* Requires multiple node traversals during style recalculation */ -.nav-menu { background: #333; } -.nav-menu ul { list-style: none; } -.nav-menu ul li { padding: 10px; } -.nav-menu ul li a { color: white; } -.nav-menu ul li a.active { font-weight: bold; } +.player-card { background: #222; } +.player-card .info { display: flex; } +.player-card .info .name { font-size: 1.2rem; } +.player-card .info .status { color: green; } +.player-card .info .status.offline { color: gray; } ``` **After (BEM - Flat & Performant):** -```css title="menu.css" -/* Engine immediately identifies the target node without traversing parents */ -.nav-menu { background: #333; } -.nav-menu__list { list-style: none; } -.nav-menu__item { padding: 10px; } -.nav-menu__link { color: white; } -.nav-menu__link--active { font-weight: bold; } +```css title="player-card.css" +/* The engine immediately identifies the target node without traversing parents */ +.player-card { background: #222; } +.player-card__info { display: flex; } +.player-card__name { font-size: 1.2rem; } +.player-card__status { color: green; } +.player-card__status--offline { color: gray; } ``` -By refactoring to BEM, the engine only needs to check for a single class name (`.nav-menu__link--active`) rather than traversing four levels of DOM nodes. This drastically reduces the time spent matching CSS rules. +:::tip[The Performance Win] +By refactoring to BEM, the engine only needs to check for a single exact class match (like `.player-card__status--offline`) rather than traversing four levels of DOM nodes to confirm a match. +This drastically reduces the time spent calculating styles during DOM updates. +::: -## Leveraging SASS +## Leveraging CSS Preprocessors (SASS) While BEM is fantastic for performance, writing out long class names manually can become tedious. This is where a CSS preprocessor like **SASS** becomes invaluable. -SASS allows you to use the parent selector (`&`) to generate BEM class names dynamically, keeping your stylesheets clean and organized. Furthermore, SASS provides **variables** to standardize your UI colors and typography, and **mixins** to reuse layout patterns across different views without bloating the compiled CSS. +SASS allows you to use the parent selector (`&`) to generate BEM class names dynamically, keeping your stylesheets clean and organized. +Furthermore, SASS provides variables to standardize your UI colors and typography, +and mixins to reuse layout patterns across different views without bloating your final compiled CSS. ### Refactoring BEM with SASS -Let's rewrite the previous BEM example using SASS to see how it improves the developer experience: +Let's rewrite the BEM example using SASS to see how it improves the developer experience while still delivering ultra-fast CSS to the engine: -```scss title="menu.scss" +```scss title="player-card.scss" // 1. Define Variables for UI consistency -$bg-color: #333; +$bg-color: #222; $text-color: white; -$spacing-md: 10px; +$color-online: green; +$color-offline: gray; -// 2. Create a Mixin for common layout patterns -@mixin flex-center { - display: flex; - align-items: center; - justify-content: center; -} - -// 3. Use the parent selector (&) to dynamically compile flat BEM classes -.nav-menu { +// 2. Use the parent selector (&) to dynamically compile flat BEM classes +.player-card { background: $bg-color; - // Compiles to .nav-menu__list - &__list { - list-style: none; - @include flex-center; + // Compiles to .player-card__info + &__info { + display: flex; + flex-direction: column; } - // Compiles to .nav-menu__item - &__item { - padding: $spacing-md; + // Compiles to .player-card__name + &__name { + font-size: 1.2rem; + color: $text-color; } - // Compiles to .nav-menu__link - &__link { - color: $text-color; + // Compiles to .player-card__status + &__status { + color: $color-online; - // Compiles to .nav-menu__link--active - &--active { - font-weight: bold; + // Compiles to .player-card__status--offline + &--offline { + color: $color-offline; } } } ``` -:::tip[SASS Compilation] -Gameface's Vite boilerplate automatically compiles SASS/SCSS into highly optimized, flat CSS at build time. You get all the developer experience benefits without passing any runtime parsing overhead to the game engine. +:::note[Pre-Compiled Efficiency] +Bundlers like `Vite` compile SASS/SCSS into highly optimized, flat CSS at build time. +You get all the developer experience benefits of nested authoring without passing any runtime parsing overhead to the game engine. +::: + +### Reusing Layout Patterns with Mixins + +One of the most powerful features of SASS for game UI development is the `@mixin`. Mixins allow you to define reusable chunks of CSS declarations and easily include them wherever needed across your project. + +In standard web development, developers often create generic utility classes (like `.flex-center` or `.absolute-fill`) and apply multiple classes to a single HTML element to build a layout. +While this works, it clutters your DOM and forces the engine to match multiple separate classes per element during the layout pass. + +With SASS mixins, you can keep your HTML clean and your BEM selectors completely flat. You define the layout logic once, and inject it directly into your specific component classes at build time. + +```html title="death-screen.html" +
+

YOU DIED

+
+``` + +```scss title="mixins.scss" +// Define a mixin for a common game UI pattern: a full-screen absolute anchor +@mixin absolute-fill { + position: absolute; + top: 0; + left: 0; + width: 100vw; + height: 100vh; +} + +// Define a mixin for perfectly centering flex content +@mixin flex-center { + display: flex; + justify-content: center; + align-items: center; +} +``` + +```scss title="death-screen.scss" +// Use the mixins inside your BEM block +.death-screen { + // Injects the absolute positioning logic + @include absolute-fill; + // Injects the flex centering logic + @include flex-center; + + background-color: rgba(0, 0, 0, 0.8); + + &__text { + color: red; + font-size: 5rem; + } +} +``` + +:::note[The Performance Trade-off] +When this SASS compiles, the properties inside the mixin are physically duplicated into every CSS class that includes it. +While this slightly increases the final `.css` file size, it guarantees that the Gameface engine only has to evaluate a single, flat selector. +In game UI, trading a tiny bit of memory for faster CPU style-matching is exactly what we want! ::: ## Alternative Approaches @@ -146,32 +223,72 @@ If you prefer modern component-based styling over global BEM classes, there are ### CSS Modules -CSS Modules automatically generate unique class names for your styles at build time (e.g., `.button` becomes `_button_1x89f_1`). +CSS Modules provide a way to write standard CSS that is locally scoped to a specific component. +When you use CSS modules, the build tool automatically generates unique class names for your styles (e.g., `.card` becomes `_card_1x89f_1`). + +This solves the problem of naming collisions without needing strict naming conventions like BEM. -* **Pros:** Excellent style isolation out of the box. Selectors remain completely flat, making them highly performant for Gameface. This is fully supported by the Vite boilerplate (just name your file `[name].module.css`). -* **Cons:** You lose some of the global pattern reusability that SASS mixins provide natively, requiring you to structure your components carefully. +* **Pros:** Excellent style isolation out of the box. Because the generated hashes are unique, the selectors remain completely flat, making them highly performant for Gameface's style matching. +This is fully supported by the `Vite` (just name your file `[name].module.css`). +* **Cons:** You lose some of the global pattern reusability that SASS mixins provide natively, requiring you to structure your React/Solid components carefully to share logic. -```tsx title="Button.tsx" +```tsx title="PlayerCard.tsx" // Importing the CSS Module maps the local class name to the hashed build name -import styles from './Button.module.css'; +import styles from './PlayerCard.module.css'; -const Button = () => { - // Renders as a flat selector: ; +const PlayerCard = () => { + // Renders as a flat selector:
+ return
+ Username +
; }; ``` +:::note[Our personal go-to] +In general, strict BEM can result in longer class names, which might affect code readability in your HTML. You can opt for a more relaxed version of BEM that allows for shorter class names and clean nesting. + +For example, instead of writing `.dropdown__option--active`, you could write `.dropdown-option--active` or even `.dropdown-option-active`. +You can leverage scoped CSS Modules and SCSS's nesting capabilities to keep your styles scoped and organized while still compiling down to engine-friendly flat selectors. + +```scss +.dropdown { + &-option { + /* Compiles to .dropdown-option */ + &--active { + /* Compiles to .dropdown-option--active */ + color: red; + } + } +} +``` +::: + ### CSS-in-JS -Libraries like Styled Components or Emotion allow you to write CSS directly inside your JavaScript files. +CSS-in-JS is a pattern where you write your CSS directly inside your JavaScript or TypeScript files, usually using template literals or objects. Libraries like Styled Components, Emotion, or JSS are popular examples. They allow you to tightly couple your styles to your component logic and easily pass JavaScript variables (like props or state) directly into your CSS. + +```tsx title="CSS-in-JS Example (Not Recommended)" +// Example of runtime CSS-in-JS (Styled Components) +const StatusText = styled.span` + color: ${props => props.isOffline ? 'gray' : 'green'}; + font-size: 1.2rem; +`; +``` -* **Pros:** Complete encapsulation and dynamic styling based on component state. -* **Cons:** Standard CSS-in-JS libraries add significant runtime overhead because they parse and inject CSS via JavaScript on the fly. Since JavaScript execution shares resources heavily in Gameface, this can cause severe layout thrashing and frame drops during gameplay. +* **Pros:** Complete encapsulation and highly dynamic styling based on component state. +* **Cons:** Most standard CSS-in-JS libraries add significant **runtime overhead**. They work by parsing your JavaScript strings into CSS rules, generating classes, and injecting `