Files
core2026/docs/frontend-css-architecture.md
T

12 KiB

Game frontend CSS architecture

The game frontend preserves the rendered contract of ref/sam; CSS reuse is not a reason to normalize a page's width, height, typography, texture, or interaction states. When reuse and the reference geometry conflict, the reference geometry wins.

Layers

app/game-frontend/src/assets/main.css is the single global entry point. It loads the following layers:

  1. styles/tokens.css: exact shared font, color, and /image/game texture values. These are value aliases only and must resolve to the same computed value as the ref page.
  2. styles/game-shell.css: the flexible shell shared by the main dashboard, public dashboard, and chief center. Only declarations proven identical across those screens belong here.
  3. styles/ref-shell.css: fixed ref geometry, including the 1000px desktop / 500px mobile family used by the battle center. Its namespace stays separate from the flexible shell so a generic responsive rule cannot override it.
  4. Scoped SFC styles: page-specific grids, fixed table dimensions, selectors, and state styling. These remain closest to the DOM contract they implement.

styles/legacy-controls.css is the shared control layer between tokens and the two shell layers. It owns only control geometry and state rules that are proven identical in the Ref Bootstrap/Lumen family. A page still owns control width, grid placement, and any visual family that is not Bootstrap/Lumen.

The Ref-style directory pages share a second, deliberately compact control family through LegacySortControls.vue. Its .legacy-sort-* rules own the explicit dark select/option palette, the raised submit button, and the focus/active states for sortable table headers. A page supplies only the available sort keys and placement. NPC·암행부·세력도시는 Ref의 고정 방향을 유지합니다. 장수 일람은 사용자 조작 계약에 따라 로드한 snapshot 위에서 내림차순 → 오름차순 → 해제를 순환하고, 여러 열의 방향과 우선순위를 scoped SFC indicator로 표시합니다. 장수 일람의 성격·특기·부상 설명은 많은 행에서 eager popup instance를 만들지 않는 DirectoryTooltip.vue scoped CSS가 소유하며, mobile에서는 viewport 가장자리 8px 안의 고정 설명판으로 전환합니다. 게임 내 tooltip trigger는 hover/focus 동작과 cursor: help를 유지하되, tooltip이 있는 모든 텍스트에 점선 밑줄을 반복하지 않습니다. keyboard focus-visible outline은 별도의 접근성 상태로 유지합니다.

Button composition

Choose the Ref visual family before choosing a semantic color. Buttons from different historical families are not made identical merely because they have the same label.

Ref family Core composition Use
Bootstrap/Lumen primary .legacy-button.legacy-button--primary commit, purchase, submit, or another affirmative mutation
Bootstrap/Lumen secondary .legacy-button.legacy-button--secondary reset, cancel, neutral toggle, or load-more
Bootstrap/Lumen danger .legacy-button.legacy-button--danger destructive action only when Ref uses variant="danger"
Bootstrap/Lumen info .legacy-button.legacy-button--info informational or edit action only when Ref uses variant="info"
btn-sammo-base2 navigation .legacy-button.legacy-button--navigation page back/close and paired reload controls
Bootstrap/Lumen dark .legacy-button.legacy-button--dark dark navigation or utility control when Ref uses btn-dark
dynamic Lumen color .legacy-button.legacy-button--lumen nation or scenario color supplied through the shared face/edge/text custom properties
page-specific/native control feature-namespaced scoped class only when Ref computed geometry or interaction differs from the Bootstrap/Lumen family

The base class supplies accessible link/button normalization and the historical base1 fallback used by already measured screens. The Lumen family selector owns the 0 1px 4px raised edge and the shared hover/active movement. Its semantic modifiers only assign --legacy-button-bg, --legacy-button-border, and --legacy-button-color; custom nation colors use the explicit .legacy-button--lumen structure class and assign those same properties. New Bootstrap/Lumen controls must add an explicit family or semantic modifier; do not infer a mutation role from a label such as 구입 in page CSS. A disabled control keeps its semantic color and uses the shared opacity/cursor state. Hover and active use the Ref Lumen bottom-border movement rather than an unrelated brightness filter.

The shared family is opt-in at each rendered control; defining the primitive does not connect an existing .main-menu-link, .game-shell__action, or feature button automatically. MainNavigationLink.vue exposes lumenVariant="navigation|lumen" so top-level global and nation links can opt in while flat popup menu items stay outside the raised family. The main page's global menu, nation menu, desktop synchronization/reload/lobby controls, and reserved-turn pull/push/expand row all use the same primitive. When adding a new main-page control, inventory every desktop/mobile render site instead of validating one representative button.

The primitive does not set a fixed min-height: Ref's 35.5px default height is the result of line-height, padding, and the 4px edge, so it naturally becomes 34.5px/33.5px while the 1px/2px top margin keeps the bottom coordinate fixed. A fixed row opts into .legacy-button--fixed-height and supplies only --legacy-button-height; the shared layer derives the hover/active heights so every owner keeps the same bottom-coordinate contract.

Only layout belongs in the SFC: width, grid column, the fixed height variable, margins required by the page, and breakpoint-specific placement. Color base variables may be supplied by the owner for dynamic nation/scenario colors, but border construction, font weight, hover/focus/active, and disabled presentation belong in legacy-controls.css when the Ref family is shared. Generic .btn, button, or .primary rules must not be promoted globally.

Class naming

  • .game-shell, .game-shell__header, .game-shell__actions: flexible application shell.
  • .ref-shell, .ref-shell__topbar, .ref-shell__control: measured legacy shell and controls.
  • .game-feedback--error, .ref-feedback--error: feedback scoped to its visual family.
  • Feature-specific classes stay namespaced by their feature or component. Generic names such as .title, .error, .ghost, .stack, and .layout-grid must not be promoted from a scoped SFC merely because the same spelling appears elsewhere.

Feature hooks stay inside their owning component. Shared presentation uses one of the explicit shell namespaces.

Consolidation rule

Before moving declarations out of an SFC:

  1. Compare every same-named selector's declarations and semantic role.
  2. Confirm the affected pages use the same layout family.
  3. Record desktop and mobile getBoundingClientRect() and getComputedStyle() values before the move.
  4. Move only identical declarations; keep exceptions in the owning SFC.
  5. Re-run Chromium geometry plus hover, focus, active, and disabled states.

The main page and chief center are the flexible-shell references. The battle center is the fixed ref-shell reference. If another page has a measured ref contract that differs from both, preserve that page's local contract rather than forcing it into either family.

Asset boundary

The CSS variables contain /image/game/* URLs but do not import or copy image files. Caddy continues to own /image/*; Vite must not rewrite the image tree as application assets.

Game typography tiers

The game UI uses four starting sizes from assets/styles/tokens.css:

Token suffix (--sammo-font-size-) CSS size Role
small 12px Compact controls, metadata, secondary labels
normal 14px Body, tables, ordinary controls
emphasis 16px Emphasized names and section labels
title 24px Page and major section headings

This is an intentional Core UX policy adopted on 2026-09-15. It is a starting policy for verified UI, not a claim that every rendered glyph has one of four computed sizes. Existing geometry and media queries remain owned by each page. A breakpoint can choose another tier (for example, the personnel nation heading uses 16px on its narrow layout and 24px on its wide layout).

Use the tokens for new UI. Do not round arbitrary content or all remaining relative sizes automatically. Before moving an existing label to a tier, compare actual Chromium screenshots and text geometry at 500px and 1000px, with the same font, data, DPR and zoom. Also check the real mobile viewport modes: a 390px-wide device scales a 500px layout to approximately 78% and a 1000px layout to 39%; these do not change the CSS token values. A page with a fixed 1000px minimum width can still render at about 39% in 500px mode because Chromium fits the overflowing content. Record visualViewport.scale for each route; do not infer every route's scale from the selected mode alone.

Preserved exceptions

  • Battle log .small_war_log .name_plate remains 0.75em, and .crew_plate remains 90%. Legacy inline 0.9em conversions and zero-sized hidden markers remain unchanged. At a 14px parent these are 10.5px and 12.6px respectively; nested content must be calculated from its actual parent.
  • NPC possession names retain their existing length rule: 4 and 7 characters render at 16px, and the existing length >= 9 branch renders at 12px. Do not remove the smaller branch or enlarge long names to match adjacent labels.
  • Player HTML, editor size choices, and personal CSS remain content/user settings. Scoped small rules normalize UI metadata without rewriting injected HTML.

Deferred conversions

Area Preserved size Reason
Chief overview compact rows and turn indices 0.55rem (8.8px) 12px text overlaps the existing 11.25px rows
Chief compact header/name 0.65rem / 0.6rem Preserve the same dense card contract
Narrow personnel chief name / lock label 15px / 10px Enlarging them reduces visible maximum-length names
Best generals / hall name and nation cells 11px; inner small 95% 12px worsens overflow in the fixed name cells
General selection and main nation basic card Existing local/inherited sizes Maximum-width names already overflow; conversion is deferred

User-created general names and founded/renamed nation names are limited by legacy width 18 (CJK counts 2, ASCII counts 1): test both 9 CJK characters and 18 wide ASCII characters. Selection-pool and scenario names have a different source contract and are not proven bounded by that user-input guard.

Existing ellipsis, horizontal scrolling, and pre-existing maximum-name overflow are not fixed by these tokens. The deferred areas need a separate layout decision before their fonts are enlarged. The typographyPolicy.spec.ts fixture tests protect the safe main labels, personnel breakpoints, dense chief rows, NPC length branches, and battle log ratios. They use mocked read responses and do not constitute live game or public deployment verification.