13 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:
styles/tokens.css: exact shared font, color, and/image/gametexture values. These are value aliases only and must resolve to the same computed value as the ref page.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.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.- 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-gridmust 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:
- Compare every same-named selector's declarations and semantic role.
- Confirm the affected pages use the same layout family.
- Record desktop and mobile
getBoundingClientRect()andgetComputedStyle()values before the move. - Move only identical declarations; keep exceptions in the owning SFC.
- 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_plateremains0.75em, and.crew_plateremains90%. Legacy inline0.9emconversions 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 >= 9branch 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
smallrules 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.