diff --git a/app/licenses/Cinzel-OFL.txt b/app/licenses/Cinzel-OFL.txt new file mode 100644 index 0000000..afdfd14 --- /dev/null +++ b/app/licenses/Cinzel-OFL.txt @@ -0,0 +1,93 @@ +Copyright 2020 The Cinzel Project Authors (https://github.com/NDISCOVER/Cinzel) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/app/src/main/java/com/runicgateway/app/MainActivity.kt b/app/src/main/java/com/runicgateway/app/MainActivity.kt index 74153d4..4aaf7ae 100644 --- a/app/src/main/java/com/runicgateway/app/MainActivity.kt +++ b/app/src/main/java/com/runicgateway/app/MainActivity.kt @@ -3,8 +3,10 @@ */ package com.runicgateway.app +import android.graphics.Color import android.os.Bundle import androidx.activity.ComponentActivity +import androidx.activity.SystemBarStyle import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge import androidx.compose.foundation.layout.fillMaxSize @@ -35,7 +37,10 @@ import dagger.hilt.android.AndroidEntryPoint class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - enableEdgeToEdge() + // Dark-only app (M5): force light system-bar icons over the transparent bars so + // they stay legible on the deep blue-black surfaces regardless of system theme. + val barStyle = SystemBarStyle.dark(Color.TRANSPARENT) + enableEdgeToEdge(statusBarStyle = barStyle, navigationBarStyle = barStyle) setContent { val appViewModel: AppViewModel = hiltViewModel() val state by appViewModel.state.collectAsStateWithLifecycle() diff --git a/app/src/main/java/com/runicgateway/app/ui/RunicApp.kt b/app/src/main/java/com/runicgateway/app/ui/RunicApp.kt index be27d97..3856f96 100644 --- a/app/src/main/java/com/runicgateway/app/ui/RunicApp.kt +++ b/app/src/main/java/com/runicgateway/app/ui/RunicApp.kt @@ -22,6 +22,7 @@ import androidx.compose.material3.NavigationDrawerItemDefaults import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.rememberDrawerState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -29,7 +30,9 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.LifecycleResumeEffect import androidx.lifecycle.compose.collectAsStateWithLifecycle @@ -111,11 +114,17 @@ fun RunicApp( drawerState = drawerState, gesturesEnabled = isTopLevel, drawerContent = { - ModalDrawerSheet { + ModalDrawerSheet(drawerContainerColor = MaterialTheme.colorScheme.surfaceVariant) { + val drawerItemColors = NavigationDrawerItemDefaults.colors( + selectedContainerColor = MaterialTheme.colorScheme.secondaryContainer, + selectedTextColor = MaterialTheme.colorScheme.onSecondaryContainer, + unselectedTextColor = MaterialTheme.colorScheme.onSurface, + ) Spacer(Modifier.height(12.dp)) Text( text = brand?.name?.takeIf { it.isNotBlank() } ?: stringResource(R.string.app_name), style = MaterialTheme.typography.titleLarge, + color = MaterialTheme.colorScheme.onSurface, modifier = Modifier.padding(horizontal = 24.dp, vertical = 12.dp), ) HorizontalDivider() @@ -128,6 +137,7 @@ fun RunicApp( scope.launch { drawerState.close() } navController.navigateTopLevel(entry.route) }, + colors = drawerItemColors, modifier = Modifier.padding(NavigationDrawerItemDefaults.ItemPadding), ) } @@ -152,6 +162,7 @@ fun RunicApp( navController.navigate(Routes.LOGIN) } }, + colors = drawerItemColors, modifier = Modifier.padding(NavigationDrawerItemDefaults.ItemPadding), ) NavigationDrawerItem( @@ -161,6 +172,7 @@ fun RunicApp( scope.launch { drawerState.close() } onChangeServer() }, + colors = drawerItemColors, modifier = Modifier.padding(NavigationDrawerItemDefaults.ItemPadding), ) } @@ -170,10 +182,19 @@ fun RunicApp( modifier = modifier, topBar = { TopAppBar( + colors = TopAppBarDefaults.topAppBarColors( + containerColor = MaterialTheme.colorScheme.surfaceVariant, + titleContentColor = MaterialTheme.colorScheme.onSurface, + navigationIconContentColor = MaterialTheme.colorScheme.onSurface, + actionIconContentColor = MaterialTheme.colorScheme.onSurface, + ), title = { Text( - brand?.name?.takeIf { it.isNotBlank() } - ?: stringResource(R.string.app_name), + text = (brand?.name?.takeIf { it.isNotBlank() } + ?: stringResource(R.string.app_name)).uppercase(), + style = MaterialTheme.typography.titleSmall.copy(letterSpacing = 1.2.sp), + maxLines = 1, + overflow = TextOverflow.Ellipsis, ) }, navigationIcon = { diff --git a/app/src/main/java/com/runicgateway/app/ui/auth/AccountScreen.kt b/app/src/main/java/com/runicgateway/app/ui/auth/AccountScreen.kt index ca5f859..3af9f29 100644 --- a/app/src/main/java/com/runicgateway/app/ui/auth/AccountScreen.kt +++ b/app/src/main/java/com/runicgateway/app/ui/auth/AccountScreen.kt @@ -50,6 +50,8 @@ import com.runicgateway.app.ui.auth.AccountViewModel.Feedback import com.runicgateway.app.ui.auth.AccountViewModel.Section import com.runicgateway.app.ui.components.ErrorView import com.runicgateway.app.ui.components.LoadingView +import com.runicgateway.app.ui.components.PillTone +import com.runicgateway.app.ui.components.StatusPill /** * The signed-in account surface (PLAN.md §5, §6.3): identity + role, self-service @@ -101,11 +103,10 @@ private fun IdentityCard(username: String, roleLabel: String) { Card(Modifier.fillMaxWidth()) { Column(Modifier.padding(20.dp)) { Text(text = username, style = MaterialTheme.typography.titleLarge) - Text( + StatusPill( text = roleLabel, - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant, - modifier = Modifier.padding(top = 4.dp), + tone = PillTone.Info, + modifier = Modifier.padding(top = 8.dp), ) } } @@ -221,10 +222,9 @@ private fun TwoFactorSection( ) { var code by rememberSaveable { mutableStateOf("") } SectionCard(R.string.account_totp_title) { - Text( - stringResource(if (account.totp_enabled) R.string.account_totp_on else R.string.account_totp_off), - style = MaterialTheme.typography.bodyMedium, - color = if (account.totp_enabled) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onSurfaceVariant, + StatusPill( + text = stringResource(if (account.totp_enabled) R.string.account_totp_on else R.string.account_totp_off), + tone = if (account.totp_enabled) PillTone.Success else PillTone.Neutral, modifier = Modifier.padding(top = 8.dp), ) diff --git a/app/src/main/java/com/runicgateway/app/ui/components/ThemeComponents.kt b/app/src/main/java/com/runicgateway/app/ui/components/ThemeComponents.kt new file mode 100644 index 0000000..15ccc19 --- /dev/null +++ b/app/src/main/java/com/runicgateway/app/ui/components/ThemeComponents.kt @@ -0,0 +1,148 @@ +/* + * SPDX-License-Identifier: GPL-3.0-or-later + */ +package com.runicgateway.app.ui.components + +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import com.runicgateway.app.ui.theme.ShardCardBottom +import com.runicgateway.app.ui.theme.ShardCardTop +import com.runicgateway.app.ui.theme.ShardDanger +import com.runicgateway.app.ui.theme.ShardDangerBg +import com.runicgateway.app.ui.theme.ShardElevated +import com.runicgateway.app.ui.theme.ShardFaint +import com.runicgateway.app.ui.theme.ShardOutline +import com.runicgateway.app.ui.theme.ShardPillBg +import com.runicgateway.app.ui.theme.ShardPillFg +import com.runicgateway.app.ui.theme.ShardSuccess +import com.runicgateway.app.ui.theme.ShardSuccessBg +import com.runicgateway.app.ui.theme.ShardSuccessDot +import com.runicgateway.app.ui.theme.ShardWarning +import com.runicgateway.app.ui.theme.ShardWarningBg + +/** + * Shared visual building blocks for the M5 shard-website design pass + * (docs/android/PLAN.md §M5): the recurring pill, section-label, feature-card, + * and stat-bar motifs the mockup repeats across screens. Pure presentation — + * no state, no data dependencies — so any screen can adopt them. + */ + +/** Semantic tone for a [StatusPill] / [OnlineDot]. */ +enum class PillTone { Success, Warning, Danger, Neutral, Info } + +private data class PillColors(val fg: Color, val bg: Color) + +private fun toneColors(tone: PillTone): PillColors = when (tone) { + PillTone.Success -> PillColors(ShardSuccess, ShardSuccessBg) + PillTone.Warning -> PillColors(ShardWarning, ShardWarningBg) + PillTone.Danger -> PillColors(ShardDanger, ShardDangerBg) + PillTone.Neutral, PillTone.Info -> PillColors(ShardPillFg, ShardPillBg) +} + +/** + * A small uppercase status chip — "Live", "Up", "Enabled", "IDOC", a role — with a + * rounded filled background tinted by [tone]. Mirrors the mockup's pill badges. + */ +@Composable +fun StatusPill(text: String, tone: PillTone, modifier: Modifier = Modifier) { + val c = toneColors(tone) + Surface(color = c.bg, shape = CircleShape, modifier = modifier) { + Text( + text = text.uppercase(), + style = MaterialTheme.typography.labelSmall, + color = c.fg, + modifier = Modifier.padding(horizontal = 10.dp, vertical = 4.dp), + ) + } +} + +/** The small colored status dot (green when live), matched to a [PillTone]. */ +@Composable +fun OnlineDot(tone: PillTone, modifier: Modifier = Modifier) { + val color = when (tone) { + PillTone.Success -> ShardSuccessDot + PillTone.Warning -> ShardWarning + PillTone.Danger -> ShardDanger + PillTone.Neutral, PillTone.Info -> ShardFaint + } + Box(modifier.size(8.dp).clip(CircleShape).background(color)) +} + +/** + * The muted, uppercase, letter-spaced section header the design uses above grouped + * content ("Attributes", "Vitals", "Password", "Linked accounts"). + */ +@Composable +fun SectionLabel(text: String, modifier: Modifier = Modifier) { + Text( + text = text.uppercase(), + style = MaterialTheme.typography.labelSmall, + color = ShardFaint, + modifier = modifier, + ) +} + +/** + * The elevated "feature" card: a vertical blue gradient with a hairline outline and + * soft shadow, used for the home status card, the shard-online banner, and the + * vendor card. [content] is laid out in a padded [Column]. + */ +@Composable +fun FeatureCard( + modifier: Modifier = Modifier, + contentPadding: Int = 18, + content: @Composable ColumnScope.() -> Unit, +) { + Box( + modifier = modifier + .fillMaxWidth() + .clip(RoundedCornerShape(12.dp)) + .background(Brush.verticalGradient(listOf(ShardCardTop, ShardCardBottom))) + .border(1.dp, ShardOutline, RoundedCornerShape(12.dp)), + ) { + Column(Modifier.padding(contentPadding.dp), content = content) + } +} + +/** + * A slim rounded meter (vitals / skills). [fraction] is clamped to 0..1; the fill is + * the slate accent over a bordered dark track. + */ +@Composable +fun StatBar(fraction: Float, modifier: Modifier = Modifier) { + val pct = fraction.coerceIn(0f, 1f) + Box( + modifier = modifier + .fillMaxWidth() + .height(6.dp) + .clip(RoundedCornerShape(3.dp)) + .background(ShardElevated) + .border(1.dp, ShardOutline, RoundedCornerShape(3.dp)), + ) { + Box( + Modifier + .fillMaxWidth(pct) + .height(6.dp) + .clip(RoundedCornerShape(3.dp)) + .background(MaterialTheme.colorScheme.secondary), + ) + } +} diff --git a/app/src/main/java/com/runicgateway/app/ui/home/HomeScreen.kt b/app/src/main/java/com/runicgateway/app/ui/home/HomeScreen.kt index b70e055..d841497 100644 --- a/app/src/main/java/com/runicgateway/app/ui/home/HomeScreen.kt +++ b/app/src/main/java/com/runicgateway/app/ui/home/HomeScreen.kt @@ -4,19 +4,19 @@ package com.runicgateway.app.ui.home import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll -import androidx.compose.material3.Card -import androidx.compose.material3.CardDefaults import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp @@ -27,7 +27,10 @@ import com.runicgateway.app.data.api.dto.BrandDto import com.runicgateway.app.data.api.dto.StatusDto import com.runicgateway.app.ui.UiState import com.runicgateway.app.ui.components.ErrorView +import com.runicgateway.app.ui.components.FeatureCard import com.runicgateway.app.ui.components.LoadingView +import com.runicgateway.app.ui.components.OnlineDot +import com.runicgateway.app.ui.components.PillTone /** * Home / status (PLAN.md §6.1): the shard's name + tagline from branding, and a @@ -83,40 +86,37 @@ private fun HomeContent(brand: BrandDto?, status: StatusDto, modifier: Modifier @Composable private fun StatusCard(status: StatusDto) { val online = !status.isMaintenance - val containerColor = - if (online) MaterialTheme.colorScheme.secondaryContainer - else MaterialTheme.colorScheme.errorContainer - Card( - modifier = Modifier.fillMaxWidth(), - colors = CardDefaults.cardColors(containerColor = containerColor), - ) { - Column(Modifier.padding(16.dp)) { + FeatureCard { + Row(verticalAlignment = Alignment.CenterVertically) { + OnlineDot(if (online) PillTone.Success else PillTone.Danger) + Spacer(Modifier.width(10.dp)) Text( text = stringResource( if (online) R.string.home_status_live else R.string.home_status_maintenance, ), - style = MaterialTheme.typography.titleLarge, + style = MaterialTheme.typography.titleMedium, + color = MaterialTheme.colorScheme.onSurface, + ) + } + if (status.isMaintenance && status.statusMessage.isNotBlank()) { + Text( + text = status.statusMessage, + style = MaterialTheme.typography.bodyMedium, + modifier = Modifier.padding(top = 6.dp), + ) + } + if (status.version.server.isNotBlank()) { + Text( + text = stringResource( + R.string.home_server_version, + status.version.server, + status.version.api, + ), + style = MaterialTheme.typography.labelMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier.padding(top = 10.dp), ) - if (status.isMaintenance && status.statusMessage.isNotBlank()) { - Text( - text = status.statusMessage, - style = MaterialTheme.typography.bodyMedium, - modifier = Modifier.padding(top = 6.dp), - ) - } - if (status.version.server.isNotBlank()) { - Text( - text = stringResource( - R.string.home_server_version, - status.version.server, - status.version.api, - ), - style = MaterialTheme.typography.labelMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant, - modifier = Modifier.padding(top = 10.dp), - ) - } } } } diff --git a/app/src/main/java/com/runicgateway/app/ui/player/CharacterSheetScreen.kt b/app/src/main/java/com/runicgateway/app/ui/player/CharacterSheetScreen.kt index 35fa13d..22998fc 100644 --- a/app/src/main/java/com/runicgateway/app/ui/player/CharacterSheetScreen.kt +++ b/app/src/main/java/com/runicgateway/app/ui/player/CharacterSheetScreen.kt @@ -35,6 +35,8 @@ import com.runicgateway.app.data.api.dto.TitlesDto import com.runicgateway.app.ui.UiState import com.runicgateway.app.ui.components.ErrorView import com.runicgateway.app.ui.components.LoadingView +import com.runicgateway.app.ui.components.SectionLabel +import com.runicgateway.app.ui.components.StatBar import kotlinx.serialization.json.jsonPrimitive /** @@ -140,10 +142,13 @@ private fun AttributesBlock(stats: CharStatsDto) { Stat(stringResource(R.string.player_char_dex), stats.dex) Stat(stringResource(R.string.player_char_int), stats.int) } - Column(Modifier.padding(top = 12.dp)) { - Vital(stringResource(R.string.player_char_hits), stats.hits, stats.hitsMax) - Vital(stringResource(R.string.player_char_mana), stats.mana, stats.manaMax) - Vital(stringResource(R.string.player_char_stam), stats.stam, stats.stamMax) + Column(Modifier.padding(top = 16.dp)) { + SectionLabel(stringResource(R.string.player_char_vitals)) + Column(Modifier.padding(top = 8.dp)) { + Vital(stringResource(R.string.player_char_hits), stats.hits, stats.hitsMax) + Vital(stringResource(R.string.player_char_mana), stats.mana, stats.manaMax) + Vital(stringResource(R.string.player_char_stam), stats.stam, stats.stamMax) + } } } } @@ -158,9 +163,13 @@ private fun Stat(label: String, value: Int?) { @Composable private fun Vital(label: String, cur: Int?, max: Int?) { - Row(Modifier.fillMaxWidth().padding(vertical = 2.dp), horizontalArrangement = Arrangement.SpaceBetween) { - Text(label, style = MaterialTheme.typography.bodyMedium) - Text("${cur ?: "—"} / ${max ?: "—"}", style = MaterialTheme.typography.bodyMedium) + Column(Modifier.padding(vertical = 5.dp)) { + Row(Modifier.fillMaxWidth().padding(bottom = 4.dp), horizontalArrangement = Arrangement.SpaceBetween) { + Text(label, style = MaterialTheme.typography.labelMedium, color = MaterialTheme.colorScheme.onSurfaceVariant) + Text("${cur ?: "—"} / ${max ?: "—"}", style = MaterialTheme.typography.labelMedium, color = MaterialTheme.colorScheme.onSurfaceVariant) + } + val fraction = if (max != null && max > 0 && cur != null) cur.toFloat() / max else 0f + StatBar(fraction) } } @@ -194,9 +203,13 @@ private fun SkillsBlock(skills: List) { if (shown.isEmpty()) return SheetCard(R.string.player_char_skills) { shown.forEach { skill -> - Row(Modifier.fillMaxWidth().padding(vertical = 3.dp), horizontalArrangement = Arrangement.SpaceBetween) { - Text(skill.n ?: "—", style = MaterialTheme.typography.bodyMedium) - Text(formatSkill(skill.value ?: 0.0), style = MaterialTheme.typography.bodyMedium, fontWeight = FontWeight.Medium) + val value = skill.value ?: 0.0 + Column(Modifier.padding(vertical = 5.dp)) { + Row(Modifier.fillMaxWidth().padding(bottom = 4.dp), horizontalArrangement = Arrangement.SpaceBetween) { + Text(skill.n ?: "—", style = MaterialTheme.typography.labelMedium, color = MaterialTheme.colorScheme.onSurfaceVariant) + Text(formatSkill(value), style = MaterialTheme.typography.labelMedium, color = MaterialTheme.colorScheme.onSurfaceVariant, fontWeight = FontWeight.Medium) + } + StatBar((value / SKILL_CAP).toFloat()) } } } @@ -243,6 +256,9 @@ private fun SheetCard(titleRes: Int, content: @Composable () -> Unit) { // ── Pure helpers (unit-tested) ─────────────────────────────────────────────── +/** ServUO's default skill cap; used to scale the skill meter fill (0..1). */ +private const val SKILL_CAP = 120.0 + /** Format a skill value: drop the ".0" on whole numbers, else one decimal. */ internal fun formatSkill(value: Double): String = if (value % 1.0 == 0.0) value.toInt().toString() else "%.1f".format(value) diff --git a/app/src/main/java/com/runicgateway/app/ui/player/CharactersScreen.kt b/app/src/main/java/com/runicgateway/app/ui/player/CharactersScreen.kt index 875dd9f..41b85a8 100644 --- a/app/src/main/java/com/runicgateway/app/ui/player/CharactersScreen.kt +++ b/app/src/main/java/com/runicgateway/app/ui/player/CharactersScreen.kt @@ -38,6 +38,8 @@ import com.runicgateway.app.ui.ErrorKind import com.runicgateway.app.ui.UiState import com.runicgateway.app.ui.components.ErrorView import com.runicgateway.app.ui.components.LoadingView +import com.runicgateway.app.ui.components.PillTone +import com.runicgateway.app.ui.components.StatusPill /** * The player's linked game accounts + character rosters (PLAN.md §6.3). Tapping a @@ -212,14 +214,18 @@ private fun CharRow(char: RosterCharDto, onOpenChar: (String) -> Unit) { Modifier.fillMaxWidth().padding(14.dp), verticalAlignment = Alignment.CenterVertically, ) { - Column(Modifier.weight(1f)) { - Text(char.name ?: stringResource(R.string.player_char_unknown), style = MaterialTheme.typography.bodyLarge) - Text( - stringResource(if (char.online) R.string.player_char_online else R.string.player_char_offline), - style = MaterialTheme.typography.labelSmall, - color = if (char.online) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onSurfaceVariant, - ) - } + Text( + char.name ?: stringResource(R.string.player_char_unknown), + style = MaterialTheme.typography.bodyLarge, + modifier = Modifier.weight(1f), + ) + StatusPill( + text = stringResource( + if (char.online) R.string.player_char_online else R.string.player_char_offline, + ), + tone = if (char.online) PillTone.Success else PillTone.Neutral, + modifier = Modifier.padding(end = 8.dp), + ) Text("›", style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.onSurfaceVariant) } } diff --git a/app/src/main/java/com/runicgateway/app/ui/shard/ChampsScreen.kt b/app/src/main/java/com/runicgateway/app/ui/shard/ChampsScreen.kt index ebcf149..7560337 100644 --- a/app/src/main/java/com/runicgateway/app/ui/shard/ChampsScreen.kt +++ b/app/src/main/java/com/runicgateway/app/ui/shard/ChampsScreen.kt @@ -20,6 +20,8 @@ import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.runicgateway.app.R import com.runicgateway.app.data.api.dto.ChampDto +import com.runicgateway.app.ui.components.PillTone +import com.runicgateway.app.ui.components.StatusPill /** The champion-spawn board (PLAN.md §6.2), live via SSE deltas. */ @Composable @@ -53,11 +55,7 @@ private fun ChampCard(champ: ChampDto) { modifier = Modifier.weight(1f), ) champ.status?.let { - Text( - text = it.replaceFirstChar { c -> c.uppercase() }, - style = MaterialTheme.typography.labelMedium, - color = MaterialTheme.colorScheme.primary, - ) + StatusPill(text = it, tone = champStatusTone(it)) } } val detail = champDetail(champ) @@ -82,6 +80,13 @@ private fun ChampCard(champ: ChampDto) { } } +/** Map a champ status to a pill tone: live spawns read green, cooldowns amber. */ +private fun champStatusTone(status: String): PillTone = when (status.lowercase()) { + "active", "up", "spawned" -> PillTone.Success + "cooldown", "restarting", "advancing" -> PillTone.Warning + else -> PillTone.Neutral +} + /** A short category-specific status line, mirroring the website's champ detail. */ @Composable private fun champDetail(champ: ChampDto): String = when (champ.category) { diff --git a/app/src/main/java/com/runicgateway/app/ui/shard/HousesScreen.kt b/app/src/main/java/com/runicgateway/app/ui/shard/HousesScreen.kt index 6b1f40f..0d6fe96 100644 --- a/app/src/main/java/com/runicgateway/app/ui/shard/HousesScreen.kt +++ b/app/src/main/java/com/runicgateway/app/ui/shard/HousesScreen.kt @@ -20,6 +20,8 @@ import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.runicgateway.app.R import com.runicgateway.app.data.api.dto.HouseDto +import com.runicgateway.app.ui.components.PillTone +import com.runicgateway.app.ui.components.StatusPill /** The public "falling houses" (IDOC) board (PLAN.md §6.2), live via `house.decay`. */ @Composable @@ -54,10 +56,9 @@ private fun HouseCard(house: HouseDto) { overflow = TextOverflow.Ellipsis, modifier = Modifier.weight(1f), ) - Text( + StatusPill( text = stringResource(R.string.houses_idoc_badge), - style = MaterialTheme.typography.labelMedium, - color = MaterialTheme.colorScheme.error, + tone = PillTone.Danger, ) } val where = listOfNotNull( diff --git a/app/src/main/java/com/runicgateway/app/ui/shard/ShardComponents.kt b/app/src/main/java/com/runicgateway/app/ui/shard/ShardComponents.kt index 81b2cdb..41e4971 100644 --- a/app/src/main/java/com/runicgateway/app/ui/shard/ShardComponents.kt +++ b/app/src/main/java/com/runicgateway/app/ui/shard/ShardComponents.kt @@ -27,6 +27,8 @@ import com.runicgateway.app.ui.UiState import com.runicgateway.app.ui.components.EmptyView import com.runicgateway.app.ui.components.ErrorView import com.runicgateway.app.ui.components.LoadingView +import com.runicgateway.app.ui.theme.ShardSuccess +import com.runicgateway.app.ui.theme.ShardSuccessDot /** * A small "Live / Offline" indicator for the shard screens (PLAN.md §6.2): a dot + @@ -34,22 +36,19 @@ import com.runicgateway.app.ui.components.LoadingView */ @Composable fun LiveChip(connected: Boolean, modifier: Modifier = Modifier) { - val color = if (connected) { - MaterialTheme.colorScheme.primary - } else { - MaterialTheme.colorScheme.onSurfaceVariant - } + val dotColor = if (connected) ShardSuccessDot else MaterialTheme.colorScheme.onSurfaceVariant + val textColor = if (connected) ShardSuccess else MaterialTheme.colorScheme.onSurfaceVariant Row(modifier = modifier, verticalAlignment = Alignment.CenterVertically) { androidx.compose.foundation.layout.Box( Modifier .size(8.dp) .clip(CircleShape) - .background(color), + .background(dotColor), ) Text( text = stringResource(if (connected) R.string.shard_live else R.string.shard_offline), style = MaterialTheme.typography.labelMedium, - color = color, + color = textColor, modifier = Modifier.padding(start = 6.dp), ) } diff --git a/app/src/main/java/com/runicgateway/app/ui/shard/ShardScreen.kt b/app/src/main/java/com/runicgateway/app/ui/shard/ShardScreen.kt index 3274665..e889f34 100644 --- a/app/src/main/java/com/runicgateway/app/ui/shard/ShardScreen.kt +++ b/app/src/main/java/com/runicgateway/app/ui/shard/ShardScreen.kt @@ -30,7 +30,11 @@ import com.runicgateway.app.data.api.dto.OnlineStaffDto import com.runicgateway.app.data.api.dto.ShardStatusDto import com.runicgateway.app.ui.UiState import com.runicgateway.app.ui.components.ErrorView +import com.runicgateway.app.ui.components.FeatureCard import com.runicgateway.app.ui.components.LoadingView +import com.runicgateway.app.ui.components.PillTone +import com.runicgateway.app.ui.components.SectionLabel +import com.runicgateway.app.ui.components.StatusPill /** Board destinations reachable from the hub. */ enum class ShardBoard { CHAMPS, GUILDS, GOVERNORS, HOUSES } @@ -114,29 +118,42 @@ private fun HubContent( @Composable private fun StatusCard(status: ShardStatusDto, presenceCount: Int?) { - Card(Modifier.fillMaxWidth()) { - Column(Modifier.padding(16.dp)) { - Text( - text = stringResource( - if (status.isOnline) R.string.shard_status_online else R.string.shard_status_offline, - ), - style = MaterialTheme.typography.titleMedium, - color = if (status.isOnline) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onSurfaceVariant, - ) - val online = presenceCount ?: status.onlineCount - Text( - text = stringResource(R.string.shard_online_count, online), - style = MaterialTheme.typography.bodyMedium, - modifier = Modifier.padding(top = 6.dp), - ) - status.economy?.gold?.let { gold -> + FeatureCard { + Row( + Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + ) { + Column(Modifier.weight(1f)) { Text( - text = stringResource(R.string.shard_economy_gold, "%,.0f".format(gold)), - style = MaterialTheme.typography.bodySmall, - color = MaterialTheme.colorScheme.onSurfaceVariant, - modifier = Modifier.padding(top = 2.dp), + text = stringResource( + if (status.isOnline) R.string.shard_status_online else R.string.shard_status_offline, + ), + style = MaterialTheme.typography.titleMedium, + color = MaterialTheme.colorScheme.onSurface, ) + val online = presenceCount ?: status.onlineCount + Text( + text = stringResource(R.string.shard_online_count, online), + style = MaterialTheme.typography.labelMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier.padding(top = 6.dp), + ) + status.economy?.gold?.let { gold -> + Text( + text = stringResource(R.string.shard_economy_gold, "%,.0f".format(gold)), + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier.padding(top = 2.dp), + ) + } } + StatusPill( + text = stringResource( + if (status.isOnline) R.string.shard_live else R.string.shard_offline, + ), + tone = if (status.isOnline) PillTone.Success else PillTone.Neutral, + ) } } } @@ -168,11 +185,7 @@ private fun BoardsCard(onOpenBoard: (ShardBoard) -> Unit) { @Composable private fun SectionHeader(text: String) { - Text( - text = text, - style = MaterialTheme.typography.titleSmall, - modifier = Modifier.padding(top = 4.dp), - ) + SectionLabel(text = text, modifier = Modifier.padding(top = 4.dp)) } @Composable diff --git a/app/src/main/java/com/runicgateway/app/ui/theme/Color.kt b/app/src/main/java/com/runicgateway/app/ui/theme/Color.kt index fdb8a88..e748fdf 100644 --- a/app/src/main/java/com/runicgateway/app/ui/theme/Color.kt +++ b/app/src/main/java/com/runicgateway/app/ui/theme/Color.kt @@ -5,12 +5,44 @@ package com.runicgateway.app.ui.theme import androidx.compose.ui.graphics.Color -// Placeholder palette for the M0 skeleton. The M5 design pass replaces this and -// derives Material 3 colors from each shard's per-install branding (PLAN.md §3, §5). -val Purple80 = Color(0xFFD0BCFF) -val PurpleGrey80 = Color(0xFFCCC2DC) -val Pink80 = Color(0xFFEFB8C8) +// ───────────────────────────────────────────────────────────────────────────── +// Shard-website palette (M5 design pass, docs/android/PLAN.md §M5). Deep blue-black +// surfaces, a slate-blue accent, parchment serif text, and a light CTA fill — +// mirrored from the "Runic Gateway Screens" design. Dark-only by design. +// ───────────────────────────────────────────────────────────────────────────── -val Purple40 = Color(0xFF6650A4) -val PurpleGrey40 = Color(0xFF625B71) -val Pink40 = Color(0xFF7D5260) +// Surfaces +val ShardPage = Color(0xFF0B0F14) // app/page background +val ShardSurface = Color(0xFF0E1318) // screen background +val ShardElevated = Color(0xFF11161D) // top bar, inputs, drawer, list tracks +val ShardCardTop = Color(0xFF192231) // feature-card gradient (top) +val ShardCardBottom = Color(0xFF141A21) // feature-card gradient (bottom) + +// Lines +val ShardOutline = Color(0xFF2A3544) // borders / input outlines +val ShardDivider = Color(0xFF1D2733) // hairline row dividers + +// Text +val ShardHeading = Color(0xFFEEF3F8) // brightest headings +val ShardHeadingDim = Color(0xFFE6EDF6) // heading on surface +val ShardBody = Color(0xFFC4CDD8) // body copy +val ShardMuted = Color(0xFFAEB8C4) // secondary text +val ShardFaint = Color(0xFF6F7D8E) // meta / faint labels + +// Accent + CTA +val ShardAccent = Color(0xFF7F99BD) // links, secondary highlights +val ShardCta = Color(0xFFCDD9E8) // filled button surface +val ShardOnCta = Color(0xFF0B0F14) // text on the filled button + +// Neutral / info pill +val ShardPillBg = Color(0xFF13243C) +val ShardPillFg = Color(0xFFCDD9E8) + +// Semantic (status pills + dots) +val ShardSuccess = Color(0xFF7FD0A4) +val ShardSuccessBg = Color(0x295FB98A) // rgba(95,185,138,0.16) +val ShardSuccessDot = Color(0xFF5FB98A) +val ShardWarning = Color(0xFFE6C26A) +val ShardWarningBg = Color(0x2EE6C26A) // rgba(230,194,106,0.18) +val ShardDanger = Color(0xFFD98B84) +val ShardDangerBg = Color(0x29D98B84) // rgba(217,139,132,0.16) diff --git a/app/src/main/java/com/runicgateway/app/ui/theme/Font.kt b/app/src/main/java/com/runicgateway/app/ui/theme/Font.kt new file mode 100644 index 0000000..f3f783d --- /dev/null +++ b/app/src/main/java/com/runicgateway/app/ui/theme/Font.kt @@ -0,0 +1,40 @@ +/* + * SPDX-License-Identifier: GPL-3.0-or-later + */ +package com.runicgateway.app.ui.theme + +import androidx.compose.ui.text.ExperimentalTextApi +import androidx.compose.ui.text.font.Font +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontVariation +import androidx.compose.ui.text.font.FontWeight +import com.runicgateway.app.R + +/** + * Type families for the M5 shard-website design pass (docs/android/PLAN.md §M5). + * + * - [Cinzel] — the engraved serif display face used for headings, screen titles, + * and the top-bar title. Shipped as a single weight-axis **variable** font + * (`res/font/cinzel_variable.ttf`, SIL OFL — see `app/licenses/Cinzel-OFL.txt`); + * the 500/600/700 instances the design uses are pinned via [FontVariation] + * (supported on API 26+, and our minSdk is 29). + * - [AppSerif] — the parchment body face. Android's platform serif is Noto Serif, + * which reads as the design's Georgia body copy without bundling another binary. + * - [AppSans] — the label/meta/button face (the design's "Helvetica Neue" runs). + */ +@OptIn(ExperimentalTextApi::class) +private fun cinzel(weight: FontWeight) = + Font( + R.font.cinzel_variable, + weight = weight, + variationSettings = FontVariation.Settings(FontVariation.weight(weight.weight)), + ) + +val Cinzel = FontFamily( + cinzel(FontWeight.Medium), // 500 + cinzel(FontWeight.SemiBold), // 600 + cinzel(FontWeight.Bold), // 700 +) + +val AppSerif = FontFamily.Serif +val AppSans = FontFamily.SansSerif diff --git a/app/src/main/java/com/runicgateway/app/ui/theme/Theme.kt b/app/src/main/java/com/runicgateway/app/ui/theme/Theme.kt index 99f87fe..8ce1d0f 100644 --- a/app/src/main/java/com/runicgateway/app/ui/theme/Theme.kt +++ b/app/src/main/java/com/runicgateway/app/ui/theme/Theme.kt @@ -3,54 +3,78 @@ */ package com.runicgateway.app.ui.theme -import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Shapes import androidx.compose.material3.darkColorScheme -import androidx.compose.material3.lightColorScheme import androidx.compose.runtime.Composable import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp -private val DarkColorScheme = darkColorScheme( - primary = Purple80, - secondary = PurpleGrey80, - tertiary = Pink80, +/** + * The shard-website color scheme (M5 design pass). The app is **dark-only** — the + * design is a single deep blue-black theme, so there is no light variant and the + * system light/dark setting is intentionally ignored. Material roles are mapped + * onto the palette in [ui/theme/Color.kt] so the ~20 token-based screens take on + * the theme without per-screen color work. + */ +private val ShardColorScheme = darkColorScheme( + primary = ShardCta, // filled CTA buttons + onPrimary = ShardOnCta, + secondary = ShardAccent, // links / secondary highlights + onSecondary = ShardOnCta, + tertiary = ShardAccent, + onTertiary = ShardOnCta, + background = ShardPage, + onBackground = ShardBody, + surface = ShardSurface, + onSurface = ShardBody, + surfaceVariant = ShardElevated, + onSurfaceVariant = ShardMuted, + surfaceContainer = ShardElevated, + surfaceContainerHigh = ShardElevated, + surfaceContainerLow = ShardSurface, + outline = ShardOutline, + outlineVariant = ShardDivider, + secondaryContainer = ShardPillBg, // neutral chips / selected drawer item + onSecondaryContainer = ShardPillFg, + error = ShardDanger, + onError = ShardOnCta, + errorContainer = ShardDangerBg, + onErrorContainer = ShardDanger, ) -private val LightColorScheme = lightColorScheme( - primary = Purple40, - secondary = PurpleGrey40, - tertiary = Pink40, +/** 8dp inputs/chips, 12dp cards, 16dp large surfaces — matching the mockup radii. */ +private val ShardShapes = Shapes( + extraSmall = RoundedCornerShape(8.dp), + small = RoundedCornerShape(8.dp), + medium = RoundedCornerShape(12.dp), + large = RoundedCornerShape(16.dp), + extraLarge = RoundedCornerShape(24.dp), ) /** - * App theme for the functional pass. The color scheme is seeded from the - * per-shard brand accent (PLAN.md §3: the app themes itself from the site's - * branding) when one is available, so the app takes on each shard's color; it - * falls back to a neutral placeholder scheme before connect or when a site - * publishes no accent. - * - * This is deliberately a minimal seeding — a single-color override on the - * default Material 3 schemes. The M5 design pass replaces it with a full, - * designed color system (§2.1, §5); nothing here is meant to be the final look. + * App theme. The color scheme is the fixed shard-website dark palette; when a shard + * publishes a brand accent (PLAN.md §3), it seeds the [MaterialTheme]'s primary and + * secondary roles so buttons and highlights carry that shard's color while the rest + * of the deep blue-black system stays intact. With no accent, the slate default is + * used. */ @Composable fun RunicGatewayTheme( accent: Color? = null, - darkTheme: Boolean = isSystemInDarkTheme(), content: @Composable () -> Unit, ) { - val base = if (darkTheme) DarkColorScheme else LightColorScheme val colorScheme = if (accent != null) { - // Brand consistency across a shard wins over device dynamic color: seed - // the primary role from the accent so buttons/highlights carry the brand. - base.copy(primary = accent, secondary = accent) + ShardColorScheme.copy(primary = accent, secondary = accent, tertiary = accent) } else { - base + ShardColorScheme } MaterialTheme( colorScheme = colorScheme, typography = Typography, + shapes = ShardShapes, content = content, ) } diff --git a/app/src/main/java/com/runicgateway/app/ui/theme/Type.kt b/app/src/main/java/com/runicgateway/app/ui/theme/Type.kt index d980457..8df3e97 100644 --- a/app/src/main/java/com/runicgateway/app/ui/theme/Type.kt +++ b/app/src/main/java/com/runicgateway/app/ui/theme/Type.kt @@ -5,17 +5,77 @@ package com.runicgateway.app.ui.theme import androidx.compose.material3.Typography import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.sp -// Default Material 3 type scale for the skeleton; refined in the M5 design pass. +/** + * The M5 type scale. Three families carry the design (see [ui/theme/Font.kt]): + * [Cinzel] for the engraved display/headline/title roles, [AppSerif] (Noto Serif) + * for parchment body copy, and [AppSans] for the letter-spaced label/meta/button + * roles. Sizes and tracking mirror the "Runic Gateway Screens" mockup. + */ val Typography = Typography( + // Display / headline / title — Cinzel engraved serif + displayLarge = TextStyle( + fontFamily = Cinzel, fontWeight = FontWeight.SemiBold, + fontSize = 40.sp, lineHeight = 46.sp, letterSpacing = 0.4.sp, + ), + displayMedium = TextStyle( + fontFamily = Cinzel, fontWeight = FontWeight.SemiBold, + fontSize = 32.sp, lineHeight = 40.sp, letterSpacing = 0.3.sp, + ), + displaySmall = TextStyle( + fontFamily = Cinzel, fontWeight = FontWeight.SemiBold, + fontSize = 28.sp, lineHeight = 36.sp, letterSpacing = 0.2.sp, + ), + headlineLarge = TextStyle( + fontFamily = Cinzel, fontWeight = FontWeight.SemiBold, + fontSize = 26.sp, lineHeight = 34.sp, letterSpacing = 0.2.sp, + ), + headlineMedium = TextStyle( + fontFamily = Cinzel, fontWeight = FontWeight.SemiBold, + fontSize = 24.sp, lineHeight = 32.sp, letterSpacing = 0.2.sp, + ), + headlineSmall = TextStyle( + fontFamily = Cinzel, fontWeight = FontWeight.SemiBold, + fontSize = 22.sp, lineHeight = 28.sp, letterSpacing = 0.2.sp, + ), + titleLarge = TextStyle( + fontFamily = Cinzel, fontWeight = FontWeight.SemiBold, + fontSize = 20.sp, lineHeight = 26.sp, letterSpacing = 0.2.sp, + ), + titleMedium = TextStyle( + fontFamily = Cinzel, fontWeight = FontWeight.SemiBold, + fontSize = 17.sp, lineHeight = 24.sp, letterSpacing = 0.15.sp, + ), + titleSmall = TextStyle( + fontFamily = Cinzel, fontWeight = FontWeight.SemiBold, + fontSize = 15.sp, lineHeight = 22.sp, letterSpacing = 0.1.sp, + ), + // Body — parchment serif bodyLarge = TextStyle( - fontFamily = FontFamily.Default, - fontWeight = FontWeight.Normal, - fontSize = 16.sp, - lineHeight = 24.sp, - letterSpacing = 0.5.sp, + fontFamily = AppSerif, fontWeight = FontWeight.Normal, + fontSize = 16.sp, lineHeight = 26.sp, letterSpacing = 0.15.sp, + ), + bodyMedium = TextStyle( + fontFamily = AppSerif, fontWeight = FontWeight.Normal, + fontSize = 15.sp, lineHeight = 24.sp, letterSpacing = 0.15.sp, + ), + bodySmall = TextStyle( + fontFamily = AppSerif, fontWeight = FontWeight.Normal, + fontSize = 13.sp, lineHeight = 20.sp, letterSpacing = 0.2.sp, + ), + // Labels / meta / buttons — sans, letter-spaced + labelLarge = TextStyle( + fontFamily = AppSans, fontWeight = FontWeight.Bold, + fontSize = 14.sp, lineHeight = 18.sp, letterSpacing = 0.45.sp, + ), + labelMedium = TextStyle( + fontFamily = AppSans, fontWeight = FontWeight.Medium, + fontSize = 12.sp, lineHeight = 16.sp, letterSpacing = 0.4.sp, + ), + labelSmall = TextStyle( + fontFamily = AppSans, fontWeight = FontWeight.Medium, + fontSize = 11.sp, lineHeight = 15.sp, letterSpacing = 0.5.sp, ), ) diff --git a/app/src/main/res/font/cinzel_variable.ttf b/app/src/main/res/font/cinzel_variable.ttf new file mode 100644 index 0000000..d218a0b Binary files /dev/null and b/app/src/main/res/font/cinzel_variable.ttf differ diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 1047d14..efa49f0 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -2,4 +2,7 @@ #1B1033 + + #0B0F14 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 61d5834..f5e95ff 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -143,6 +143,7 @@ Governor of %1$s Guildmaster, %1$s Attributes + Vitals STR DEX INT diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index ed9031d..7653b61 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -6,5 +6,11 @@ background / status-bar behavior for launch. --> -