213 lines
13 KiB
Markdown
213 lines
13 KiB
Markdown
# 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` | `normal × 12 / 14` (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.
|
||
|
||
|
||
### Relative small tier and exception variables (2026-09-15)
|
||
|
||
`small` is `calc(var(--sammo-font-size-normal) * 12 / 14)`. The reference is the
|
||
14px normal tier, not the immediate parent. An 80% declaration on nested `small`
|
||
elements would shrink repeatedly and would yield 12.8px inside a 16px parent;
|
||
the token keeps UI metadata at 12px regardless of nesting.
|
||
|
||
The retained exceptions are also named in `tokens.css`: `nation-card`,
|
||
`chief-compact-{base,row,header,name}`, `hall-{name,secondary}`,
|
||
`personnel-mobile-{name,lock}`, `bracket-bet-button`, and `war-{name,crew}`
|
||
(all with the `--sammo-font-size-` prefix). Their values preserve the previous
|
||
rendering, including rem/em/% semantics. Zero-sized hidden markers and arbitrary
|
||
user HTML remain content contracts. The betting page and bracket now reference
|
||
the shared tier variables directly instead of the redundant `--bet-font-*` aliases.
|
||
The historical deferral reasons above describe the original 12px proposal.
|