From 47c700f8c764c70e8adb09d639b39a1613ffab16 Mon Sep 17 00:00:00 2001 From: MartinBozhilov-coh Date: Thu, 2 Apr 2026 19:05:09 +0300 Subject: [PATCH 01/14] Add most of phase 1 & 2 articles and drafts --- src/content/docs/Polishing/accessibility.mdx | 6 ++ .../recommended-teck-stack-overview.mdx | 98 +++++++++++++++++++ .../workflow-planning.mdx | 7 ++ .../laying-out-the-screen.mdx | 2 +- .../mocking-data.mdx | 6 ++ 5 files changed, 118 insertions(+), 1 deletion(-) create mode 100644 src/content/docs/Polishing/accessibility.mdx create mode 100644 src/content/docs/phase-2-planning-and-setup/Recommended Tech Stack/recommended-teck-stack-overview.mdx create mode 100644 src/content/docs/phase-2-planning-and-setup/workflow-planning.mdx create mode 100644 src/content/docs/phase-4-logic-and-interactions/mocking-data.mdx 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-2-planning-and-setup/Recommended Tech Stack/recommended-teck-stack-overview.mdx b/src/content/docs/phase-2-planning-and-setup/Recommended Tech Stack/recommended-teck-stack-overview.mdx new file mode 100644 index 0000000..8e647b2 --- /dev/null +++ b/src/content/docs/phase-2-planning-and-setup/Recommended Tech Stack/recommended-teck-stack-overview.mdx @@ -0,0 +1,98 @@ +--- +title: "Recommended Tech Stack Overview" +description: "Discover the core technologies behind Gameface UI development. Learn why we recommend SolidJS, Vite, SCSS, and TypeScript for maximum game UI performance." +sidebar: + order: 1 +--- +import Summary from '../../../../components/Summary.astro'; +import Highlight from '../../../../components/Highlight.astro'; +import Link from '../../../../components/Link.astro'; +import { Icon } from '@astrojs/starlight/components'; + + + The choice of technology stack is a critical decision at the outset of any project, as it can influence multiple factors including performance, development time, and scalability. + + While traditional web developers often default to frameworks like React or Angular, game UI requires a different approach. Heavy runtimes, Virtual DOM diffing, and CSS-in-JS libraries can steal precious milliseconds from the game engine. + + To achieve the perfect balance between modern developer experience and raw performance, our officially recommended tech stack is + + SolidJS, + Vite, + SCSS (with BEM), + and TypeScript. + + +## Why Not Vanilla JavaScript? + +Choosing between using vanilla JavaScript (plain JavaScript) and a JavaScript framework is an important decision when starting your project. Vanilla JavaScript gives you complete control over the code, which can be an advantage for fine-tuning performance and behavior. + +However, as your project grows, building complex features from scratch can be time-consuming, potentially extending project timelines. Managing and maintaining a large codebase can become challenging without the organization and structure provided by a framework. Frameworks offer tools and features that streamline development and speed up the creation of complex applications. They also provide predefined architecture and patterns that help maintain a well-organized codebase. + +To get the organization of a framework without sacrificing the speed of Vanilla JS, we rely on SolidJS. + +## The Framework: SolidJS + +Our research and testing have revealed that SolidJS currently stands out as the optimal choice for achieving both high performance and scalability. + +If you are coming from traditional web development, SolidJS has a very similar syntax to React. However, under the hood, it operates entirely differently to maximize efficiency: + +* **No Virtual DOM:** Unlike React, SolidJS does not use a virtual DOM. Instead, it compiles templates to real DOM operations, leading to better performance and more predictable behavior. +* **Fine-Grained Reactivity:** SolidJS is engineered for high performance. It employs fine-grained reactivity, updating only the necessary parts of the DOM, resulting in faster rendering and lower memory usage compared to frameworks like React. +* **Vanilla-Like Speed:** Because it skips the Virtual DOM overhead, SolidJS achieves a level of performance that is incredibly close to writing pure vanilla JavaScript. + +:::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](). +::: + +## The Bundler: Vite + +A bundler is a tool or software application that is used to combine multiple source files into a single, optimized bundle. For Gameface, our build tool of choice is Vite. + +Vite is a modern build tool and development server designed for web development. We chose it over older tools like Webpack for several key reasons: +* **Native ESM:** Vite natively supports ES modules without bundling during development, resulting in quicker build times. +* **Instant Updates:** Vite offers HMR by default, making it easy for developers to see changes in real-time during development. +* **Plugin System**: Vite provides a flexible plugin system, enabling customization and extensibility. + +## Styling: SCSS, BEM, and Modularity + +In a game engine, complex (hierarchical) selectors are expensive to match. To maintain high frame rates, your CSS classes need to be as "flat" as possible. + +We tackle this using a combination of SCSS and the BEM (Block Element Modifier) methodology: + +* **The BEM Methodology:** The BEM methodology is recommended for writing HTML classes, as it promotes style isolation and helps reduce CSS conflicts. Additionally, it can help prevent performance issues that may arise from writing too many complex CSS selectors in Gameface. +* **SCSS & Flat Selectors:** To write clearer and more concise selectors, consider using a CSS preprocessor with the BEM methodology. SASS is a recommended option that has a syntax similar to traditional CSS. It also offers highly useful features like variables and functions, and has a large community with many resources. + +:::note[Alternative Styling Approaches] +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 SolidJS [CSS Modules](https://docs.solidjs.com/guides/styling-components/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; + } + } +} +``` + +We will deep-dive into the nuances of BEM, CSS Modules, and writing high-performance styles later in [Phase 3: Layout, Assets & Styling](#). +::: + +## The Safety Net: TypeScript + +Finally, we highly recommend writing your UI logic in TypeScript. + +TypeScript definitions and documentation provide strong typing and enhanced tooling for your JavaScript code within the Gameface environment. +Because your UI will be communicating heavily with a C++ backend via data-binding, this ensures you catch data-shape errors in your IDE before your UI ever touches the game engine. + +## Next Steps + +Now that you understand the "Why" behind our tech stack, it is time for the "How". + +In the next article, we will walk through setting up your development environment and initializing your first Vite + SolidJS project specifically configured for the Gameface engine. \ No newline at end of file diff --git a/src/content/docs/phase-2-planning-and-setup/workflow-planning.mdx b/src/content/docs/phase-2-planning-and-setup/workflow-planning.mdx new file mode 100644 index 0000000..fbf3773 --- /dev/null +++ b/src/content/docs/phase-2-planning-and-setup/workflow-planning.mdx @@ -0,0 +1,7 @@ +--- +title: Workflow planning +description: Setting up your development environment and tools for Gameface UI development. This section covers the +sidebar: + order: 4 +draft: true +--- \ No newline at end of file diff --git a/src/content/docs/phase-3-layout-assets-and-styling/laying-out-the-screen.mdx b/src/content/docs/phase-3-layout-assets-and-styling/laying-out-the-screen.mdx index 8057b4c..77e10e2 100644 --- a/src/content/docs/phase-3-layout-assets-and-styling/laying-out-the-screen.mdx +++ b/src/content/docs/phase-3-layout-assets-and-styling/laying-out-the-screen.mdx @@ -33,4 +33,4 @@ Provide a concise code example of a layout that takes advantage of both layout s - anchoring something to UI element - anchoring something to 3D object - centering on screen -- centering within control \ No newline at end of file +- centering within control diff --git a/src/content/docs/phase-4-logic-and-interactions/mocking-data.mdx b/src/content/docs/phase-4-logic-and-interactions/mocking-data.mdx new file mode 100644 index 0000000..9041659 --- /dev/null +++ b/src/content/docs/phase-4-logic-and-interactions/mocking-data.mdx @@ -0,0 +1,6 @@ +--- +title: Mocking Data +description: Learn how to mock real game data to test your UI independently. This section covers best practices for simulating dynamic data, managing local versus game state, and creating realistic test scenarios to ensure your UI is robust and ready for integration. +sidebar: + order: 1 +--- \ No newline at end of file From a0e5b8908450340e512d1fe14ff579393e73c242 Mon Sep 17 00:00:00 2001 From: MartinBozhilov-coh Date: Fri, 3 Apr 2026 17:52:27 +0300 Subject: [PATCH 02/14] Complete structure for phase 2 with drafts --- .../docs/phase-2-planning-and-setup/workflow-planning.mdx | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 src/content/docs/phase-2-planning-and-setup/workflow-planning.mdx diff --git a/src/content/docs/phase-2-planning-and-setup/workflow-planning.mdx b/src/content/docs/phase-2-planning-and-setup/workflow-planning.mdx deleted file mode 100644 index fbf3773..0000000 --- a/src/content/docs/phase-2-planning-and-setup/workflow-planning.mdx +++ /dev/null @@ -1,7 +0,0 @@ ---- -title: Workflow planning -description: Setting up your development environment and tools for Gameface UI development. This section covers the -sidebar: - order: 4 -draft: true ---- \ No newline at end of file From 2362e6e5cf65cbf36e41918d3a2e700987970f39 Mon Sep 17 00:00:00 2001 From: MartinBozhilov-coh Date: Fri, 24 Apr 2026 11:33:18 +0300 Subject: [PATCH 03/14] Add first four articles of phase 3 --- .../building-responsive-game-ui.mdx | 165 ++++++++- ...dling-aspect-ratios-with-media-queries.mdx | 337 +++++++++++++++++- .../writing-maintainable-css.mdx | 272 ++++++++++---- .../laying-out-the-screen.mdx | 220 +++++++++++- 4 files changed, 896 insertions(+), 98 deletions(-) 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..21cd9e8 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,6 +1,6 @@ --- 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 @@ -9,6 +9,13 @@ 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 +30,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 +224,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 `