From 52e06da8fc1df55bb751e830f149a40f54ec2637 Mon Sep 17 00:00:00 2001 From: wtclaude Date: Mon, 20 Jul 2026 01:51:51 -0500 Subject: [PATCH] feat(ui): m5 shard-website design pass MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Restyle every Android screen with the shard-website theme from the "Runic Gateway Screens" design (docs/android/PLAN.md §M5): deep blue-black surfaces, a slate-blue accent, parchment serif body copy, and an engraved Cinzel serif display face. The app is now dark-only, matching the design. Theme layer (propagates to all token-based screens): - Color.kt: replace the placeholder purple palette with named shard tokens. - Theme.kt: one dark color scheme mapped onto the palette + 8/12/16dp shapes; drop the light branch; keep optional per-shard brand-accent seeding. - Type.kt: full type scale — Cinzel display/headline/title, serif body, letter-spaced sans labels/buttons. - Font.kt + res/font/cinzel_variable.ttf (SIL OFL, app/licenses/Cinzel-OFL.txt): the Cinzel display family, pinned to 500/600/700 via FontVariation. Shared components (ui/components/ThemeComponents.kt): StatusPill (semantic tones), OnlineDot, SectionLabel, FeatureCard (gradient), StatBar — adopted across Home, Account, Shard hub, Champs, Characters, Houses, and the character sheet (vitals/skills meters). Shell: dark top-bar + drawer styling; dark launch theme and light system-bar icons so the first frame matches (no white flash). Build (assembleDebug) and unit tests green. Co-Authored-By: Claude Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01NgyHnrNa8WwG3doxvxjuCr --- app/licenses/Cinzel-OFL.txt | 93 +++++++++++ .../java/com/runicgateway/app/MainActivity.kt | 7 +- .../java/com/runicgateway/app/ui/RunicApp.kt | 27 +++- .../runicgateway/app/ui/auth/AccountScreen.kt | 16 +- .../app/ui/components/ThemeComponents.kt | 148 ++++++++++++++++++ .../runicgateway/app/ui/home/HomeScreen.kt | 62 ++++---- .../app/ui/player/CharacterSheetScreen.kt | 36 +++-- .../app/ui/player/CharactersScreen.kt | 22 ++- .../runicgateway/app/ui/shard/ChampsScreen.kt | 15 +- .../runicgateway/app/ui/shard/HousesScreen.kt | 7 +- .../app/ui/shard/ShardComponents.kt | 13 +- .../runicgateway/app/ui/shard/ShardScreen.kt | 63 +++++--- .../com/runicgateway/app/ui/theme/Color.kt | 48 +++++- .../com/runicgateway/app/ui/theme/Font.kt | 40 +++++ .../com/runicgateway/app/ui/theme/Theme.kt | 74 ++++++--- .../com/runicgateway/app/ui/theme/Type.kt | 74 ++++++++- app/src/main/res/font/cinzel_variable.ttf | Bin 0 -> 125468 bytes app/src/main/res/values/colors.xml | 3 + app/src/main/res/values/strings.xml | 1 + app/src/main/res/values/themes.xml | 8 +- 20 files changed, 615 insertions(+), 142 deletions(-) create mode 100644 app/licenses/Cinzel-OFL.txt create mode 100644 app/src/main/java/com/runicgateway/app/ui/components/ThemeComponents.kt create mode 100644 app/src/main/java/com/runicgateway/app/ui/theme/Font.kt create mode 100644 app/src/main/res/font/cinzel_variable.ttf 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 0000000000000000000000000000000000000000..d218a0b9c8879fd5a708872cc0ef357e507b35ca GIT binary patch literal 125468 zcmcG12Vh&(+4ec-O1je3ww5)lX-QtPWy!L<;=S#}PU0laB7*=48H62XfHKM|&;n(Z z5%?&jlrq|~p@SJZ`G8V3Ev1F8EoBs1K=S{bdnMUfjPL({$kCPFyWaDj_q_WY6G{ki z;6p_w`xlQ4;TI+J9XpQNq5grvH5Wa!oRAk!BZNILG&!-@@%Z;dLSE@0J3?bV}ynhw0Z{7%i zG$@>n-{z>M>aXZq3m$jzmLm>c7aaW8?Sz=S@&3`R>vn8si)cTtzZcgB zx2@ZH$Tj!<1@}MtBlP#M?MEDa%(5lshY8t@_gR*2KkAU}8#Nc4h5PY5B7%~5G$E($ zJY@bFbH~4kAmCkuJblo$<^1?kHl>SspE3mp;kWddAR*u4=jn%xk5rH_`XNcNCucr7V0&ZsoNzY2kCG1d?|qvP zCmnMe>%lc&)GBa~f3KFnZ;8~>MY#WR(!q~tZD^H+?^+xi8}PX;jNf0>^tk3N zJZo-y2e7}9ES*G4m0AtH+i7a%KkO{P4nu1~%Mc^mj_Y|V-)~C^+lW@slgNIX2DJU6 zb|mnt#-->&`gK@hPGN+xSAgLn!TKQgR2)d9gL93$UWCvOm=H_xVzW{Lq`9kQ)BrLbb zaPBeS?ZwfH<9s}0K8f=q`5CD}uLH^7`{$fnG&3V)Ah-5zgEQ9%g~rwZ-_L16dS*R8&#z~F zI4;<$LH4RJE~?i5`y*)Fp@Mu@ZALpD&473D@BH~Y(GK17DB#`n+?ii-xz1k3@d31p z(XK<|aNe#g92d{r#=e8=&?sd89_?lPegN;fapoamWTtWpf(kdVv4lGIP^Djq4IKWY!ln_G|D6c;L%4 z=!hvZK7Hr?oUqvviow zrz`1UbTd7bev^6G6YLd1C1?eMP$gU{d{1~#_^JOa{|7;P&=d3r!@*cE8Egyo2agHf z77B-1*c>hkJHno@KU^Cg4zCR#65ahNo0-9TgCvQ5UQZ?Jq<$KsHME{K(^fi4C+R`- zaJmieD`$_h7htYw1WAaM-glb+{h%#a9`pr+^7~r%c%OBT_ie!YsQf-|nSIWGGu-B# z^IvW%*PW}+)heJjICaVEufAb?{ms|!dgH3sPklY{TI*|#guMF6Yw_0{ujyX>^Q$ky zW<6c~XHa(=E1?DEbD^8`kRj4bTcK|cA}h%%vYKorqvQ#)gB(MS zBPWyN$w}lCat8SZIhUMIE+F3_SCOm9HRLRE2l+9%3)=ZU@*~_nmRw2VA(cVRAp2PaYu?kT-Ic-|o zrTv!nRb7wn+qxTdKh!-SIi!#@C9RdVNIRs5q^G3k^j3X^{`&@I*kHKVm@#%6$BfI2 zHyiIXK4^Tx_}`|u=?v3_rYlT0nLaShm?g8r95N@&E#^M+0`oV_e=@&o{?sB`Y?c!( z&sbiyylwf=GGpzv&bLlm54PTF{jv3@)~Bt{Ti>w$t!%t(S=sus*KD1(qim`KP&&0+v@hYPjsK{e#sN}oaK4Z^Oom5PtLo{yWV@H_rJV%cpvaS z;eE#Yim$>~<@-^ERI#PvQGc(0zJJ=k!GEv+7ye%d`T`Sym4P1yb-|6nqk}txPX(U~ zz8?Ia;71`EGK5^AaA;lVp3pBszX`n*`b&5;d_(xQ@IB$5hkq5ZM7)t$q%krtG7?!I zc_{K&Wn<;)%F`?Ft;|Ie(e=?Aqff@FVu!_!jhz|$R%}=7@z{Uj+4u?ZTjKwz(p6=v z`l>cm9aD8_)sL#4ulidxs~)KSdi4*hAFqC<`i<(p*EH4~TXRm$l{F95yk7J7nonx= zwYJ*&+L787wVP{q)Sg*;aqV5T&(^+D`$}D^ZcE)S65hnYiF@nA_1o*Oseh{e<@$d% z=o^+d+}-eG!=D;HN(xC|vL$&&^4-SM8ZT|UJ>^ZMQrl8*r`M&orB6%0)nsovsOik6 z?=-!ZS(bUQd7^nu^N!}rn!nrpo8~vNnrub3IlDHyCHsx+kFu||BwD`PTHTsy?P(ov zUDkSV>$cVxTmRhp&$jWlv)V3c`%c^U+J4+Fw42+#?O$*IR{OQ>-)sL-`y=hU+n?)b z>FDpcVji70J#WLj?ek8a_eSS;yN>KSrR&_T%erpp`a#!yU5|DBuIrVqx4Zt;O}mZV z?(S%JWA~>$H}p>Se%80MU)_Jr!16)!pm#7nm>#@t@SVZ;2lGR^A^T8ZsAecL)IBsl zbo$V{L!S=!4^Ir=HT>|1Yh=^NZ%3O(yGO@Ir$;x8-a6JY);~5mwrXtS*fC?Lja@Ky z#n??_cZ@wa_T;#8JUsrj@ptF1pTBkfj`?TKe}4WO^Zz#g;{~G&mM*w)!N(J9!Z_ic zIBDYHiQN;=PyBJ>9}}NUY9_6dzRBg2cP8;nWRN-b|KzOuaGn_L7k$OO~9s z!#OF zbM4GXDt`g}Iif)VBr&mBL)K`>8cNZN^SfwW{@MHm>i$n2OsQTc>KRyD?7!J=Tv-M! z5rou*r4$hajnkP}>r13L=!LQpd_xh=D0Gsk1V6C=*D&>DxC9+7LB;Xz614bi z4YQm)`{d5jwXW_}oH8@>+23T!7@@y{(3PN!C}dr*`ii6w_au{AqjA`s&aBJCVzErD zIosSEcNK^gj>W?CSlYbgUv^#oqYi2YfiBBG**Z%&W()+X+uRymb*er<12(7Du+-Gx z)CJ<6a!x!I+q7m+niWVnqONuMV$`j1`^2)Eh^I<2L<6dTPX`)_1$qw58R0hQ`>#04 zLdxl|Ya-!Trm5K#=DozwXPRPPdi0;HY;3HICX-Q{$78d(-Cu?q1W#qKA|9{s$IM=j z+v|0EyrnUmDvefXvYY66FNGEC*RVDeKCXnVmW7=*1RG!pA=y0#idrL9nT6*?mnCWdVEJ3C_mq zK9k2PNii2YHrLO-k-M!;s<3$j0ovvF(}M!x+}GI!CWn;!&G~_O4fph#?Gq)CDd2wHChCkxhKQihz!-kN=5t-Ge4bj@^RCMe#Q@xNV2)95hI78t7nl+3NyMX zhVJ4cT_8y+#Yff#P$V3V#M4(EKDaIwO!+eNtD85C9zhRp8jjWWPgJ^En!=%;56N$%?nd4OB%|`qQj|{!Ahe}^0y}w-63kWptBF= zE*z1f9#-RtN|@~(Gw;KX*$r!uP|?I9^}w#V8GUfMBET}N%q|zu&$7o2o{7x^8+WZ- zapShpMS5EKo!Y)QU2)?PTW?$$uefk}<>JdWY`A=iWllI0 zGDdlmEMtt2mt4mDj?0(={3P&}3}*uD_lOK{Rd0pgTToLD4TBQ$0TR4<5Gnd-@KV?0eS+I+*xl{ajDp4Rqs*Dl>T zG<4dML|*`swNGH$O*fyMfqgKx`=xOUxohqZ~83)lav6K60Fb;Mk7@ zPVX{|gN6x5$<&siTplY>*XC95V)(TV%y6#L^zbHJpl}>MSw{B8pyeV~Okh4kGyJGb z;C*K>pUF+Jcwq)p{8EI#EbVvq$W!<&YR$h(7qs16ndz&s98{RM87b@@IMLZylYeN> zfsjdmFsAJYrtMT9kb7%aAk-1Nb?$`y_healgfe-;=624qet{AWnIoN?5;V6`=11OV znLaX<3C1FQ90J#P3RLZY##87caE+%x#WHBT0+jQp0@Z#t3*~xHfm(WJ-^S^#KwX+u z{0TEdobDy4XWa+zEHN7Nh0T}{^C7oKPxNekUn=I7d?vX5j2oJ$Z1i> zT-n2g4`k(B;gfBGf&hA9_LytIk>iK&TDfx9;o0e-3cJocu(5aQwBg~?rs@_B`)Jzo zsJV%j|ChfbPFLQzb?Z$l%L29w+a_wJE{8Z;OdWCWoe4>jsr){oH{7}ja)|`Z{RFpe zK>Z}gs|453b)+D{a6KPaemx>5$3hFrqy3&d+xZ-EV1^XOWFh=}!qr}=Fi(UzoG;IC z2%}lbThYI8xG}5|`3R-B5vb?{SrQbkg9XU1gTTUd?53XlVLkLu`KhMD#K4#sCyPTf zNevKN^csGY^#o*ue9ky2U#<+DJbXX!`}41tCWkyi>}dWw_x%2#1aTTfpV;fAewz{N zP-JfpqBKk3-D6Bcndm;fho;NgWYrRudk?Edwrfkha?It=&!ZB1E_X0nS)jn;rU?T@ zZy5s#s9~am3+ssqbR7Lxr1ySk!z_OPZL$>MJp5IOAWM4cl4)RsYz%Rl@OjARUs1w+ zUJrJcG|T>QlPA_3Z9QjH6mD!`oAB-!{^uEMo*$Lrp!}FD)i@kd?L{tU5VfU2S$@e- zroNQRuS!50Wcj5)#Y+m33eE@TbNR_kJ984^RvJW>2;gT!8x(sBP5V{)8yD+Y9F_$9 zlF#e8NAxr|d$mrzv!494cHa8ohqA#iO^^*72S<0KaO1%TlE`uCofVl#fxV zH=dc^nGeth`%H^Gb_Ph^An%7AA}9mv%qm+xm|>Z;t2EVi`%=BJP$nR7v4<&Enm{!p z%>kXEeX^F_Hv+jf!lw%;#Wm+O7#$wVELs5ru#4dXIGrxYF;F5|uWE$jMFN2q>~<;% zdY8SXcdWODF3*3LE*Etk7qj|x^3>Qb?G+A8@;-vHNzQ>xL%VU*DEhA;8H#eDpr0TS zWFx;htKQEX;pA2%pkfb{EP?##d(2@)uoYMX`u}UP+FxFhRLUT5NiOShu5(iyGd>qE zQxvP_QQ4{~;@4T!pkEe4_?b?Z6D$LUj>TiKFN5(d8n;IyshlA@D>th}ZP^!eVV*&& zx43FUc8#{8LaTG~9?c|S8E~5O9)afMJ${@+bM84Svbc5c*~y)*BK;v{i=wGO^GL=f z`)qOjMIso+us+I=64W;Nzq+@n#K8u-n{MVXL$A}i7+bN-hKc12_qd0N79`(2*9 zFk8(t?+cg0643&`3Pi+XkmMQ2JTC8&aOEQLXi+6+U3NQn#er?cmPU-mhU`Yv77?tDJc8|!hXYdn^1 z8F8N3>SVk7a=!~VIJ(M5q>$eNoo4asTG=J-xg+7iU$Tj9+@5P@2W`pi^oOL@R-M19V;Nn~D*eYTNAo@!hAqxOeZXlMX5rjJIuOE9S?^X`0v>bX=wSgT?!^THd%Doe z+;KfdztibA_AFqBZ_8bFT2)6?xmqiF;@L{}Vh>K>bWf@8Jes2PF@$JaHzJ6G7(WNN z(2Rb}dawv$ahKdy^kEBDG5x`2ZUb*tEa4^aR(CF$;-75y%A2^=t9<4LWwUom`Q%bt zev3@`t^(y>ktwghxrZaenHt$MZX+mg?&BzM5&5ES@L1tFGPbaLO5W`O@U@k6{{b;y zDS9)|`woMnw&wGC!9JLy_)8d`Rl?ALES9wd=k|btxeL+DRWjyp3wrj9jCtgXF~{_Y z(_1!Qw|x0^n-AxohtHulJ<^lA8IiRE)13X>nM)w)S)?Q%cLkpiD9k+wPRZNh*Et|} zYUD^#GjfH4$f7L;zMQf=plJ(-MT|Y-fwTO8>064?P-~n!<8jXNU%?r_#ntUG`(0FW zIi!Xro6CorYj6ldvkLwd*i^9s=yg{fYxo`iD6`! zF;l=9g`g{J?4+N${0zb!6DD4-;w%Do$rkw~9@B&yfpz+T_IA-Tedxl(NXna-pR`t) zz11~ldsRb4w!W|4B6y>f9%ife2ArPGW)tV7z7}75)m&~WICk6_We<892kMg3S+Uk0 zZ)vtT!*zf&An|Yt^!YDVw&LK9C4L%7EKl3z$3&X8s9$?ruHD}WZ z-2R%TCyBKlPVaP5fk> z?N*7R>WE}^px%~t{>0bqU%XLr+bzkAGd&(>!ca#=gY_<7Uq#J=M#?+4XotmP3x{)fkw%lQb|XZkqJF+vJdCGat}!;_hX za!=+1+@mHbWF%h0yq=Auao-#YkQ+eOw3X*nxDMq0f+F9GPCA$}v>({LnYCSg8(JFr zPF%S7lmUSoZcoE-WAtAB^*;U;!JpybZS&d=8=#Nn+ZP<$+jHy!`gn)aCrLhMNAbw9 zma&sLa|P!sWHEj&TTcp{TTdL0jOZ!cc2c0+c2c0?3Ed?q_e$kwFbjB-^S5#}w__Bz z12sR~j#1#;j!~ZC+REb^shN44P9>;k;O)}2-z-5Z4(TB(p2LSWRj@P6y#+B+nP>dJnjd&c=Hz4rl0Pj!adZCa0A_n6kztj|R!3>g;+ zZfq6oC%Nx$WiKqu{WX$yFK0Vx?(Xy0=4H7bRHn-hW@~z7R6`*dQJ(4Kn9AdeIygSQ zw3d%e0nW!)hQm63i(AJ9I3HmJE|NbIjPc&jLETard`l%We1kj^JTg>?=zUhs8X^p2 zMUf2!_?A&__<_$AgGNwBNaK>tolHoG+qinuh#;3TWY^G#Ci4R;by}Cv_$O1a z+48jOx83ymMV97(=|h9V`%i<>C2A)vnV^aF{ZIIpXPE*SYqw5xnhY=&2G|3+3rrO@ zNvd?whT8nSLn>E9lB(EwlC8odxGJUm&E>QgX9a#$p*Ppdz2UKMKCX(#iLvBT%9aL- zMcfor)e7zfSJNhUjrTUq{ZhH}mp@wjF0JbMVBSZSKhkM|*{h~Nvn7Agl&Lvr`pD>X z9j}-{opOYiJ&^xa1Vt9-^)N% zT^Y_tm%}l8yX3Kzp?qu=sMsNo>>kfRJ;Ro=f;F-UdPRx8gkU=nnzT1k*y5o~2%^g- zDzO)y;ig=HAVLu58ie)ddq?R_`NQeO_C&;}$!F=`LuZQmmOfKH3H32BpeT^tx?Goz zUG7(Da?AYwldp2ChntuhTBEF>G%)+QWw1%^-x0hYb1Z{RvQ(C#Maw|GmPdINsMgty z3XD$yy@nl%nOgLxi&YD=e3-?ojo46+k=;*e$|SW-Ja>oQSEhYL>!`DQCjPnW?|QR= z?#zEoJI2j*cI~gURv$YwcRdsH9SC6Xx|E86+#l&@<&m(I|C--_eW@-*#zqJO$D@`0 zoP0Y*QGt>ol+o?CmY~iORGode2xV`TpkmX@Md<8vnPo%ix$MQ#wXUjvbBfF?U{`=5 zJm$?K4Ddiu*|0xqq*25pQ$(YI<+(>iN}5}u&{~>{_N9!DQ|ug&C+T-mTGd*v(N#6p z`)O6Q%%dOId;At#eX@3BA5zH_eA(}RTdXQCl%i@1C$klY~y|_I;IX((NXZWctH0{RCFK&C0BMpbu0Pci$rENYVX!K z6GnPN56ft+Zc69Pyquq>qJPZ4!1CYgr)R4T27#WF+~Ll2T1TbwvZki8@`U8JHf^ft zZZkMdk`!<-Pwp1hn5QYpWwj$6X+dJRCg{z*#TxWBZNBnkf2R7fX36SiQ?1NxH9psB z#P@o)PLB!#csk68RT>SYd}`5`kn1=Q=iP4|$J{cGlGj<(pBEhO9qO$^9Y^C>U3Kq* z=!tYk?Lbvc|DtMdXC^$_vsJB1bqQkzPYAUIG~_Xiv1!TQk@7c1M0KLSs<9_#F`Fwo z8=k(H@1GI7DqJnjC zl{{~D7Ubt`GnhHDg>seP0yl9u?7lUR$k1kvRSBvZ{h)X)d$$DDE_k^>|HnapH{!3n zW@0v;6p6Pno(h$1Jzf)W0Edm9b@0Ut7F>Mrb8K);>X@_6I;LsEh^L1Q8_R<{D;O#_ zU{%iWIjdKnJ>tLQlv6J8%XECZM+!OV^W_21Q7v=9TrA)lmEu}o77rnWQjagjrH5AJ z|Ma;C<^Sa87ANP-$Axmx>r92VhyAn&WiOSWuC@Px2IkkQFn{;1*WhuJLL`Dh4{^17 zpb*hM^OL&vO=dpQP3N=x2d}?QP5JlfQ1*&&YF^B=T5oqWH9739darBVQGvnyFO17f zZkhT)w`m!;mUHeCn3g_F&x$}GpS>thkq%xgPsGLYq})AVcKC%lkEGANBD44(Tz;Ob zmwP62-q#gPu$=Rq0_A)sLz!UUe5XLUCQzViJ2XLoHe3@ZP|*TSP=Io7RG`{VOVHW3 zF-v#pZL)q)u63#Z&7Xi;4bZ&w1kV_+)c_9kH>HvIlC>K9(7t(p#J80^(MP&y@K3ZX z|BpZEZO*19r>%`s{%A*iJpZ6^+T;$&jPEv0*Mt9YP(}cJrBMHg{n1^m&An72eD1H# zAg>bR^5q%GmxMAO_9)Nbep!|m=qdFXj@e2MdSd;;k&sho>e?_kesp*DG2=DkLjg+N zPSZW6glqDMp@YA*XwjwYP@~szVf%O#qGs7?17&glSvEV&&K(sUSYIehB0djf90acQ zzsY3}sx_{ixs})5!jI&*S-56aH!E2=WK-p=oDH$6FN^Iewc8`k&CW%?6#5GdqGWZ| zgpmi*`YN=NlRf_B`y06rQ@T!^y^d|`&R^6`AIP7W8ZnfI=sY1@ZWzJW@H}WIe5H35 z;PS7z-wRV4)0+nZKIZ%b#$n~%Qo!X3NQ$U+VLXe! zpxnHWdym9uvG?4s=GL7O3bDE7+VqHkJ}^rTCzh^pdQfMPAXQQgf*&PI-k&}Yg>v5) zq_BnMadrKTp%P}iSu(nTajRm8I zNOg5tAk~@*(!TuP8a_Q&g>XZ^-dv`8GQYZ;anZu1j9Wi4qNn9y@D`UiNH@J%tVvOd zTH^UX(6}f8XmU%l=I#pCknr+6|;LzeTl%e*q5e1Z7bB8d_%E3KX)MLvSPsrrA3o-e?8)uqKbz1 zprCJUMeiev3KL$F^5rp1QI9$Q=bc{yy$^3Jzlommxrk|r7CGU=VAb%76c3^f5jnQj z1{xM4ro19WDKd0voG!?NFDOT8Zf5uE-Z5KLs6}~ZquCP|($wNKwT2{nXLcR6xbh#e z$cpivngP42%5C155!=izGdr{=pXRkFWqp$0Yc_kSu8F1EbI&1y^RG=zvn_WO{rHI7 zD_+0U)TE;jRwC*(-e-ShhjBS3SBw{6`oeVKjgP)b#Ia|(jCuo@}%ymrj$ z3MN8M>sWOyP3TxBs!-~x$E?m!9n_L_j2)KyXfreByE-Lai6M2e>XzK^=sPGu$$R7y zl*Z2DDbBw#rt*yZKIdCR^M54!vkIL16&wzK_Au^OM8NInR{_eoUCyZizEJM3PUiIh z>Y1C7*C!C4$^gx&Jt_7z4O&pXNJObYQ9|zAfKHc$&$d1LD#-+3iBKk}w)mWeM1lsK zW;$HOt5iyzD)m>UL++o+-l(V*y)h*C4(sFu;k_^QT#o-JJq#W3vTRGg&oHv+hg=uI zlc|V&pjI){)X8jnSD<`t@TApEO{-5D96XU9PaGWGF*vwmbcBDhLlaX+4-OtZl_(x_ zx1vIO$3Y=R99FA!0Hf|lrP}w?ApyFoksqOU)aZ*Xa9=56%)czokIZ_F^%d&r681NWUB;>o@5rnR3( zf#Y4dbDiw4V*iWB1JFSp3k54a7JA+@)ZlL1Z&e8Q1ioBVi0vy?g%m<^bc?yoS1byt zuD2a@;l#v+2YFMWP|7>JwX<{U2lJrN|0BNn8pXOg+MWJJpuV-zxKy$`5Er#KNgh`_ z0(0z8l^P`>x1c0s{DSrCE*#H&Pw%qO-`gCzfzzg^Pa9w(xm(n1u^bZc!YTEaLIM{R zT~k_KW_Y?36DY=L)R5Rb7OPa+7b`+En%RmF9xf_Xgv_e;l0vs}S4ggdyX%O-LtD~Q zho(bKZE3Sgm>ONL))>1P6g-?DIdWuL#vSN#YD5g9dVZ#LVJ&q_8fvYt)%%U}29c@f zfej@j$Kw@>+YHNt$B+KD2d;djhEn6PKWwv5IW|{ujhtL25Bs0juN0-F4VItm5;$(# z-m^MYKYw+DzdlosnveN?>(y#QH`Aj!*5&7j`&}sbPa?@_oYd%K^SR5%UJK-6Kkt7B1>rLo>C1+B@( zcp-*&258GTX6r%(o{yTX3#rF%4R}W>3po(qK<&CBcb?ubmhn{$ zC9DaHr`qm_RC@#2p*oAIBJ30FO%*}6ucgtXLQ#khkX_qT(SSEyspfSdVr7lqZ}HUyQDv!=hU|yoAm6wx zd4?=eALf#i=GGMBR%gi3SsBU%s~jbRHUpg?Avu(5Gemcw1G1>HxDw;dGm}bXh)byq zaUluMOG8{vB?Et;+7M9f9bZkrrK5(H_S6s+t>)b>WHyou8!P(SY=WpUsKUKPBlNhs9KzwDF~jti7>3ZoR6!%~RbQ*Ol>X zxs0dW*PknWbt`@$u$TKPLz(dOBjDleOtai47uKer2H%wyVK2)ai6bA^9LIo*$ylRY z2UF-R>R^;Ym{M=Cwm7{ZtKiZ@f%BppZb{+MXH&1)c^4 zl9MSApiH183Rvt<`fRjr?CwEhQqA-}PoT;mNd{lJ!$!3dyYqiTl_c*eMwRy;ql#TT z6iZ#PCkt@$bL@P^{XsK^yoqfBjSg53v-jFPd1Vq}PQ`4sQV$a;)Wh`l&~)x47Uhx1 z?e;`0|H25>!_PxOOt@S>@`1~%*G22?+u4%b_t_E@#P|j*jta^U@ z;c-g1vvJXtcKz>ArW*zI*V} zHCyY?I_BteAnm+%^TTG;anN&_&u+fWZ1X_|PUp{FxBc){^sM|z8&BA=k)E9?b4!xD zEK@vkTxFbO3GpjF7hv6AB-@4xoZFWi4*PP1+m}iYcz!^Eii6y~-18a8_8-Tki~{A> zrE;xf5w|XtC-D5S0(G_V{6N)AE2m5e>XH6jyjI{@S>@V_##d0M^C_r!4XDUHr#W>x zsG~rgj<{!?&Yo23C`0bsiI!<*mF*hkn`}L@X=pq11zNIxogn z!BwaP{eE8<+nJd=N^qVnli{e-xqs%TML4{)B3vZ*%)DBJ&prqK!(V3ZKxNCnAQw-O zN5TpVJR;Xg_CX&ibvkp(bg~C3(Zy&VB1SCV$%8XK>pMY+3GBwlw#)4K+Cd`)Aep z_c{d|B6|^5D@Do#yI-RD7i#iU@;d}v1CEy~CC-w2!RvF}a%L40)3#@Q&TO@pxNmvR z-qQZE`>Ov!13hnd{Udt2-tIK~&fse_<*!;|X{a##)!?)p$;Ew$i+i3jV9WCNMCg|M z_w#vd^|d5_xt<2?7{h>_2`(>i&pQedA7k+;XnvMFU%5QlaS^>&us|;o87R=T{ z&-iRrpkju{iuZZ~vmA0eWD}3^3_-_zVQmg7T4-9X?u?+g(PW`S zVtx~Sw)ygKV|&b$SIu+!^txCAn(R5?rL~n-%bDED7L^YMT zG?KB9d-q4BcdNK0QJ_51AVZm8exwMMb3+PLo9D6!_LvaG{ET6DGmI{|0y|OiCj+iB z&w<~>;jrZR4#MZ4##aqJk18In??f7+W3(f8DypO~Bsr`n>Xsz-(?fIj^SCqo74{zP zsK+zA2?=36q>yc{M+g9Ag4`fNNF&W{g4y=N6%^yzL^%#nng`hfwxGgR$hh}rxRei*ip+qq9pfG5gIW_>BP9d338 zgU)6@#}i1InUeEInN>1nom`o<%Vym4Cj^xu8; z)pzs8*Wu%7KDpwbgfg z0jt!IKP0kz#dyQ~zQIKx(2=$ZYjV0Xz3GtE69G#saG?}jz2&H_^$S-sIzA-t9`HQ9 z6Dz;4dy);tJdaex72gyR7t2Dp7svOLX^wOL)wpEf%?i|;sp;$&By0Hyds2uG)K&H-9ipp#Aki?@m%t86G~B(SxpgEWRgGu; znebTqYHu}WO%?dffPSlBrSg%cu!?JLts zj(j8C0rq$t*|ZQlbVDx7o{Hwrm!vW27oaJUHh3H&L9ftT*&pOpVW{==Gts2tUBuLc z-x9a+lEO4S>woIIj$V-xgwEB6PP>_Vcw(SIr5^p-BHe<=gOz%TZ8u+Q(WRF6j2zZi zDXAoCujyWv?O$y7sj$|-hwUdQDp&srneZyF5T;n|!KXYQJcWXYRWe9L_(P&57Ry4y zU!od1q{|-}NGIE|2T!1*HqdU@czTEWS}Ikl#&!LDo7yR@UUVvZRdg*s;o=2j$MiNY zNv1}tJs#|^WHi^!&$eyqqqP6@l?$%^+BC<2#~ptoSAQc;f{kcG=&P9SRAp`Ih@=nu zsb|_4s*DB=gIaGeQxH^p%n)oG-{_|h9~hg1b+j6aIFnTEi3bg z9C78W!4pvL9ddx<&2=MbgW26OFVHbG znhLcw*esF8k!;J7q%S>O=WVEt1dKM{^8TTzn%-!6GuFIr-o{g^7M~gw z?b9b-G`8^g?xrQF}1k6nF^JPvlq8VPR~5XVM5)b6IQ zlrsx^g4d(-J%gY*pocy|$~Hzmf=exuPwDOXU*ascg1@7jg*3)lh!>HW=$L%gkF!6K z&wi$yJxMvcA^%JCxr#QCM{pKwZ`+?Fay()(J4DG@?l!v37N^-nn?#3+ZZVj3YNoMT z@_)f;JjYL)=mee<Tj4@<4O6cBCU&<_@eEyq%1B@Ut2SlQ*=_WWI?5)HFJ$Rz-sqEY8?TlAP^iJ8yHIrKRe zr`)#N32PZgyfZQLUF`G9{cr*?fe;rgG|T|3fMZ$FAJ7`qp6YVuFB7#Iv)E(O8STDw zRXKu9;fSQQIFu(dBUYaBCqId2;z-ZNQF#NyYSquk_3UBz6L^PfPDVE#K}u0LT3PDv zF-vyCm_b#{#NPfkt<66NMafq?;QpU0_k(Mcm1ca8yEGPU+xEPl{aoH*z*gqH^Q}Dg zzk-eh`y*q^cMwqaq=KHnMu}*0={1bpXM-o9-T)te%6HH!@*Ra`kp1*MzyA{iW@IXX zdgvqUw`TW|s`y(tVTuU2^T}0pPGoR7T;D`{fl(Wf#N49`8 z$k2*>>6|QhWN9$>S$5ZW+O zlRoTPV^NnU%YCWFrp{>Z=%V4=J9Dr=<_N)(pDI{H1evUT=kjq19NY~}1%J_-^1Re=-ar_DYqz26(T zMj^D`)wR2XV7slkRvXTenVac{GkjkV;s~LL_rtuQ z!w>k9nQLebdqv*e2NoChh)F~Jigs=Z`I!dwsxA?={X85DSs%xyh3BcYdKww;VDR?Y> zT%(O=dqdeJEe4Lq4$!F*>*qy`YuX`NpyAxN`)^sN|uA^6$rG+R|T z170qj;e1gz6UL6_D`*?n$F`7548^f|xJHwIRzus6Jm8#IxB?2p4NhdFimagL(RXkM zmnW_)wjvhg&#E8ljz3PTmOp9bZuMh7Q$H?$^1C{4*R{pF5Ic79d-x~4_J`Wzj@PN> zPg?o7`e%=+cgvrA46t7Cb@{Hso0NAHp3ZxQ=aLT#SIE!%aPEVYnRFwq#ID+L8X*_t zmSHYM2+1u&&*E$W^$Ql931eRct{=iV`3^QtD+QBshYZIZcvlTuKrX}e&>A)kGPQi1 zVQ=$f+9A;-Xlz}SUblJky3X~Ts%QOe6*PHVQ#)~(ewUT;nH)~z+wK3`{Ld3j~!>VqmPU9QT?gI1sF74{8HW!Z!OG5DMXwX=z#4z~1$ZJN0P$2ZL<>vX zA+Be$mvX3J?#lK!NcZ>h>WEjT>&}FlgEr@*6^;#ylIwjctZr9@`WvG2D$5){4Y(3x zKORr|`>~%{VTpUN$?r@#e&X#6P8~+QKEB4iX)H!rxIN}g+U~082_)v%W0Ct5{p=i2 zSHL>a57_si*NSvSoehthxWq^)?1u1~?6!cn;HDzXhV9VB(At&_XU|VB?Xd?11MvpGN@`S;` z6IP|WM>aXV#+HN6yud%mV@xUht)fb-W0dy=;A@FsIN}qLu1>g{T&l{rA?^T)&5cWL zV}jkHK#5h>2M4;nb$x9?mD!?^e2o=q!CJqtqjALUl{DU_U`Lxp5G;Wzdu(+})zb0u zd?j6D%%V!>&-WqrmeXeQ2{UbqF@2N+Fi!JKOR zbtRFMwVWk8u(x)#(mES_nZaI`w4L;`S!bWis<mB#&-`(c=J?Qj*npZ=JX^A%8V7PwBtq=L19VL)X9FBjVfRwX+95l5&s z?W#ywz1m1)Aec>g!(qGkKqr2wbJ_KR%HvjRLPobX>{4sW@!f6z`GHPAeh10(^mp`7 zc$*{};eiKCtu%%{o`xMFb7oAlB%yUT1w1X;l+96TmJ$YETgcO#Nx6KLFEw-oXhl3g zowfdCXGKNaPbr*M9z8o2ef}8V;g4k%g?sWJCFI`y7h6Ps0Bs+~?lpvm>f~?$ z#580kf~~SSiCV5gltQLu3=+KHJfmV|SFGRR>Wu~J>+f?dYK*i5XfPYDTkP{Ks*5y- z@T0PE@tMIyBIr)W&92_xwuXW&P0?smwyG`M5sP(%TVv6}w|Qm!gs;jT$)e}>nQnTG z;6|Mcp@=FXvWLK4A=YQoz(M74K3Tdqr@(c5l5}c#+^@~x@i;W1pfv_1tzK_1*%%eL z6$(Gv=&22vD+4+eQ>jgmO_D)xLdnCN(*n`}qDj(wbS5fk?dDjMess&JJCB+wtLSNs z3sjBepcae9q~GVmv+_ayQ24(Od?m_vaGOx(Qg|OwLbJRYta_$e zW>`9E%TJVXd#Y{bAB?cy$!oG=Aik@Uaa)QDYYZFqxpBI{YW$a9ju^rmrqRrzF1HTz zGwsawd)1oMNKLxWX*M*BZW^FX8P_mp>HaOJNSf5aY`5s?Y!st$lb|-PKYx7n z1(Pau`=Ra0iE61zN(QY>gR#a9i<2tNk8U{lyPILt^19jcpw0A%;MJkhp&$K8IYpV# zVHzMu!2?v_qvn_p$0~TPTrylma~0x(GrHeP|IpEHGOeB`f40Wkv5i_>{+x2t4epMZ zKhsoEQ4^_jRWv0mPU!#_v)uki)(&f#jL96?0-{cILQH z_#t!fT?xqOySQ*v_`L)C&i^(7kKd3i_&tj&8(>88Z-{O}qADah!}w6Moy zU9YT}`u~&B3xPckoY7c{R!1GqYs~%&^u~VELKC>GdneVY| zLwrGvnDkyd7ul3Qroh_rqi_C;K9u`6)#mfeP~1wrDs*NUc9>uf`tv8`kE7jGPj99_ zk@no3sOzEb5hyK0dhu5YUkCTsAhIZD9$}v<`3UZAVE*DC-`;=+IhA5@>NE*33Xrzw z#Gmir0Vl3Nu!jxwNnxcZoaAtKb6J^G*;U&*wrIXHGf*4s8;aS6me$6)GWE{3g|)uS zDDBlt@xE29ZR%uXBIT^4X2nH-2t&gbjANZqE)33VLWKzqI?HxaUK`=kya_w+C zMbYh~m*(3LL8fHG%s9D3o@I#ZSR)$}2{81{k>F6!rcuS$9g{KWm&EWKsy%nhxr8_pan9{Z15 zkxs8TuD@_RLv(NG=-JrOv9YIiDqzwWr+ey0YimdA$2TGeJ@-cbfash%68u`*Nj?@gxPSDUT;@9SU= z)7S897?n|xKOB#pefI1({%v{6YSz|7X zMXlzVQ%>F+6FjH(iF~ zMBMIhm|da#tZt2m`ENA-EFOJ+1O1aNC@R=&f(^S{2*3N?tFPXD_svKvU7Ej?ZlPGy z$5-_!ceK(x`wOy2O5PdXCg0_Y2k|m6|78mqC^U;DsFi*!YQ0tdQ+1WqqR*=p_|P>i zZ@xiaQMpoU$KM3dQ}!24nZJzY*2l)XoVtosSCY<8ENX3CRL88r+?%oHx^k_%wlRc1 zn+xeH;V(M>4UrWk{u9CIOz`#ApoN^+lMQvmccCA6Y7HxTloGHkqC;u1NvWWc>BdXU z@ej*0Wl@LAWqT*yB*sx;oSj3@lLF~L@t@Yh^yQLZ5y}N@P1;sgZZV1$HIsBG{6eHw zC90@V#b~*aIbvCub=fR?sCX}b$oH6D&vxwCVbn9L#aM^4nhf2J>G?3iw>?+R0kTwsqsF zn*JRt)%n$j87OiQnAL(qts(*=oK!1$CY0deK|LsUG>Q8DAqf%CqPvS2LF2~;+ z+={WpSn=_~*kqdG@?R4w45zXyG6oh^Olf&*Cdh0t>JpQD>u3J=h?IP)%bsks%lXx8 z8X6FS0Ky=GmM-jF6;Gr1ozIqJa&utajW-Hbdqi@qatc~0pce(_TAM{sIdoyVT^Q0b zrdl50i2{dSWo3d6>Az)$$s5B#SD8;ESk(bHle{XmhCT4;qts+FIn`!n(+aAm1W6Pn z;T~pIS5uWBdM~rLHweioHXR6prB*nGLE891I?K%28A6&^xu>=8TuU=StMt z4IZ=E?=^yD@VAfuLHmTIm@)YKn*~=^UQ`3&-REoY;h&^Uha+iGSn5mq@XwBz1i$lU z_6K@_FwJo()Irk=G@Fjp93=^pa-lM=`Dmt(o{1ymQFQNW{d&fW^=Xy2+87C89bK{P zo_b?tYTCGOA^o23MARM8^T^GbjYr{?><#6Wm=oV(57KJ|sUQXC#2wOezTa)L4dOX%LBn2oLX77-H7HBUb z&I2pL&;BWLs`Jf|Yiikhb2dEYv-c_XF8zgk2bb5S@q*KrMJR!foa87sm;;dhRgNQc zj&h?pXex`vo$NhlU9A(Im%XYAfAkgKwQBwqSlr4Up*OKVVjl6VPUO~4K4E{1pf7Z0 zivAGu3{Gbh@p?#r!p>F@hjjbiE8Vfx`92HU8UFtD1N3}hp+Z@D0R2J})tO9nRVGtq zuc)v)d_G}e6$&0|LZKR~--j(@(17PIjM6oT@On9(opQ8Pw!R{Y*~Lp0cS#zv!(ffq zIgC!Lo=GZ;!(@%sx(p7Bfjwd>(>a}byT43t5uFY!BZ94mIBZC`lYJ)deC3p*Z``T_ z9ST#QBFD@V%mQSh%jV~$LKrCl+uL3|``6^C4;g3gkMeg93~d~1ikeTfCOgMhH`DfG zwylYs_J7!W5Adpvqi=lo+5={tH>4@G92oP|E3aC&-F~P*K9k&q2 zB_@t5iAx;fIF3WG$afV-4VcldU6WE*aK!Am6iUN@_6p(b+d_l-q{j zEUqgGlFrfk&CpVF6f58He1>2c@;3(k_7v795rh4Cp;{)m%~48?8nI`X7vOR>^B@@= zbx`Nrdni}K#qgul(grD4)2?`AaM%5r>pjC{`h+pXiL#0Gs9jVh$9Kir?lE+&eXr|o zd9Bi{_ANt4&;&Phqp@N&?J%^b9j=%ZiCv{p86`utiKIsjI@Y?Lfr^=z4LxfOXVI&s zbj#gp=sKbu*h%Qsvmrqni&D=A(VS};vG-bCH&!gWRV?ApP(Y_e^{1q!4!O>keX8iR z#+Q70=Xk@xxfHl_n^zs3SN9xtGjp4?T#Hrg*|dg``$10i+i#+bllXnxC8IBKFxB6_L`SEAlK=x-LFsj zkZ~C#^@#<=5KmF6^+quBs@F{~&J4dIDrZ#n!clTu!|J8Ot_m5PGk*3+Ez~(Y$K^^W z8f1;g&rc5tat$Bd3laS?+Lco|E3@m~yd^c`21&QeIp*SK*`A6;^)Odwk=>R!U_e21 zxHCE=XmG*cgn@RfRT>!H?=9`X@@ZbzS)jgM~iamdlYNv@J@3u zMM$<1pUR5 zc_XIHPnU7o>9NzRifp!mGMs?POn`zvTpyh=r7*85)IE0o$Pu%~42sSynO&e2O&gIE zG-6)S*vT28uF#aCjOtR-^LLSU$CQDoyb@0u6|lzvJMjc2<-CT3NjdkM#?44-Z#U=M zl4Qxd=$msF?%ObS?7FMwzb)s4?x9rf(CzR8$kk}K##qXs z*WD^E*Gbo(3XdBqjqpxrX`YCh*bgyM5capwDVfoz3EHu3bah3AHuTv38vjx=fOMLs zV>UsHS5#eg``>ZhJ8Q*~mVe|zl^*UPpLFqp%yFf8&ROe9(?Wxyr?kz_+gQOG0nH6W zq|Ai1{#^szi;<~03D%Mc2{hPii*6>3fYhQmZ(PQWRCbi{TU18F9r6#U+~dS<$PbnF zu%$*ncNPtM#J~2XVNdDIG&St`w59}T*h9zt#TUQ)rM!;y>kSXP#^}ArnnIyB4J|u{ zosSU8JW_gKPfO5tCtA?xXpLBb&a>f0t zhO6WFM6WBg3KfI%X`d^#IprE2?n{lZu3Y1uUxqzEt4L>vB@S{$k|t)bRspOT(%gXg ze7=!llzHO1C<=poW-;bPGVUyDL)z&s2B?zHWvwAW5jkV4=jB^V>K05*l^;2@Ue$+` zkl(ODe3* zP^<8^{9bR!!&(ik*Pvd^-52Owi&R_p4(DdAL9+rv8OPG}K7%;oPe|S~CSx~m4 zZgv4ipyE(ftP+nnqJRb#6I(L9R~_8I7d)4SB&O#Mu*Kq(MCmM6@h9)-nk%nSmq6oG zQ|72t@(q@nx)8b**Xeg&ky|k$CC2UwPAja+(KfR}u&&dllEW$5sxdQ1KzRXsciuwE zp&++Jty$jlwn+cG_elCX7xJLhVgqg>-X<=AR-%Br`22fZjPrEN&r~{s+NRs~lwLDx z%Y35FPqQPTg8Ozt2}|wIM}z8l=z^5fp*7iGixi!cz~X!aJcG#2232Rd}bo({x98;we=4 za4pF!7vd*(6+T?PX}TjkaT_XpoIEa;dGkZ=Dtw&0U%Bg(sK4=sPt?ZgbVD*mc53_H z@QK<^jO?m^dZ1UDn2kB{Vo@tRViPFh8{*s0D0ad6rzr2i5wn+~xBrMtoRhMX5P|wQ zB`@u5L_QCyFqAQ=uIilB5)&`df3>c1J%lk(Bi(-`#^Kyg4eYf}Xv|Q(R3qx-ylX>Y zvd~)xYT!H@S7f8vChHCEFTP1fF1GfNa#OjZZWUp7Jz&jtdMkk-PldG(CK=t9=Wu%K&`i58MC#?lV>l= zO{iJBt)lA}J+Ust5U!oV-wi;OQ-2qd9l{aN!yB zU^R2VJjt)bIPQw0Avj!9!0V~le?S{gusjL9yJypXPV)k2KIc+v&yQ(Zzh>pKMZb|`bxTJmB=kLApqEkwkIl+-Mx+kQiHc5=?(_j#Yv<;e^x)y6k}Q$5 z=0h#w%|2QLwm@C@9?YU-k)ge4L_DA7p+*6BADR-+v8gDXgMH{x6bf6`EBe6Jl@}6Q zra%YSQbNEq^!EMK5_n!{B&C1ub5|aCeh5BC-GYpHeX8y1UNK)ox+X4MJ5?7T&{+(U zy{18whDAh-1R!Zv%m}K6Vn#Zkh@A#$nMLP??ewJJnoF`L7bfLQA2EE!g3%+_Z=8@d zwPM29Q3FB?Crlg@9XT+>njD4WZWw-SQYIA-#fCj4tvWj!V+^+m8E&X?%r75Ww6r*9 z!Sa&gmBminaTGBqr#Lx$Ox5HGvC#u?gbp|M#SBe#CdUr194ZH~^aS(SD|pWE17nUf zQ_UHu4%q#cKz9yLb0iM0L6;BK@S#`UkhYNQo9+`Mov7uc+mZO8QD0f+qW!IN_0sc` zyWmjjYV?rjBYJ_<@{cINEbSSV>BrHnIMn)Rp3$BErJLB{jGZc2RKnRu-s+6TBc-9i z)nH<0X}N&rK<2XoGmb=Snk8GAaZ1X^>WUjK(;m#^->syDEZLrC3_!8*85)S}|kl0Bh`+ zvC#ag#sXvPJluI5Zt;n+g#$3(hI5FDoEH%N)@SxSa1P~C$(73GD5 zvoFq%czItvn^&IpAw3%`&%)<*O)^@6nQ>#jLW?~^B(94)12e~60k@0pyfx3s^QmmW z?)_BrIL}#yH|=^X;60*r_cL0pFaLy3L*~~f?{cMyUC!MZSrd|K_JjH0oFYfuqa37` zBs*V>#9r1CeI)XE1p7D=v65baIw{6t7_A3ly9w=7bEp|3_V7IH}bA5%&&C~|v6~zWHR%wOFA(qH+E}KT! zMlDd;==Q;T^=G}MJ8aAvOHn$%9XpxlShAVMRr7!8cY)-0&2#U^`&AOZ9IHFhNgF2J zIDc+AEs;fGUB55ue>Ni@NPIwU?_U;iAK)YU6zd{OHYht1dou@P*U3a+*8l!_#f-<< zi?MB&H_OnCBiJINL7S(-xZzm@>4WB;7LNqM*F$#UrP0onPpx%+Q{$==?KXLmxlIJvccW z`$;1cTw}Dwmd;I<~3H zZX2~sLN7QN`%H6;Jv4%IE6oi(5^H@Vzrtb--`ZXXGq!9);*d%DW+7Bj%QtjT;4m-j zX(MQvZ|(?d$yzM)bv~mO{p9GmQ3>g}=?PJDHJPi?B8^N-j5->bkPcmMxe3m^>Wt27 zqehL(k9E1D+}Y(rv<9{C*O@lSot}`8?w+Jg?`*_qHKsuO-lRm_`InYBiN+dL4jL1F zNi=|6AukdqVYm~=!0uRizKU0#EA@OTx7Xb+*YlXA;-P&ZD*Rx`3@2iL7474$!zxxj z>N#ItaLbaiME_MFQg0!+l_2cGUzNWZn&E@kZb*voZ8nn7)WZSKC z7yMxTgw~}ahn7uCRUGPv&>MRcvLNDga`{wmDQ*y%iMu+-pi{-tP}4t= zzPu%FVxoBodfv#W`X4S{{KNVIqcbx{514l`whPFh@$%bUKXXX&#V;O`*NnX@y{KYX z*yXgmI4*i%NpNa>H7zr0Pur}x1-(+U+m$}^&T8!N(n_MDUGuJAv*!AF+I^jsHtm<^ zB9})yz&$J<+ERF5#{_BzVuFU<#FM#YB$ox94N(u-5S6!sGH-# zwh=QPKli|RNE*-a;D*h|lr)~&4NKjXyF(miDSEYz{1CrGyJir7U_8{0Rrqjm%E%9e zr~F`)>Jc8=k&W;%xShthjSA_KbX~<02dx3iut%~)e-rKaS&Y#*!a}!wMD@2dYo8=` z9!S{YYtp4n-&?rgyA35qlb*M4*~%Lhc$>8LY;0#vtbD$|4V%X?!WGe;%JX30a3V$p z+&Q34#F;(|MzScX4_>!d{gJwH8J$oR^{+TyL&y9wu~QUVs%ZR%16^J1NTpjqw5N#= zO2{CQZbm}LthFVpClAGCWyv{XQd7%vY%XW^=!A^I1kKvCI6OUTSd2X=CBv9ADx@YwT=ok4+uecL)X|9#bV*N7y?&p(c!Zn z6j(lzzOH?9ea7S;-*x41YuL1n*W9%vqi$o%`k}5NV`k*fZXQ$GeEp&Y`xW&_imI9rG_MyXV@x?iDxU4@jXZYY}Qb%Wm zJAxPNo-!Lfkfx1W`>mR}yB3WMce%A8voBsW{gPFqr8Z*O&Z(6x(}G*GGdf&*EIPXD+nE=wTvU~n zS5vzt+fm%IvLt8P;-y2bPMS1e=!_8-hr`xffYS+KDaj$!w{af1oX@IqPNQ-vR~kXt z6`eNiO&ZemI+e%kYW>F@C?(x4cq@8Y+70NtWdeo5<_0QA9GX3Z9S%ALG7_i4XvCxe z(_3&lh460lkkmn;uEdzItf;{wlOtn~42b7@CN!Jfm06uPx-v5QH^~XvxMczvu(|9j zLx#>XM1bjA1du%hD}V#tg`hejs^*am56ev+KBD-icrSGj2Dw>vy7} z7UGr%3+4myn5&V79-eXI9(CkD&Vl1=#f7$*OvgfEZ}C zL(5!nLyLO2Xyt^8HKT`4^Gw`uNkzx(xjU;Y&{z`wd+emsAeNgvxoP~^byafuw2f2K zEbgnX?E3l4_VTj!nevH}SX>I86k7rsRk6omTt{^znohBfMQpta{TMdY{*I;I=Zp z0p>%?la`+Ym+|0vgiRJkIWlFb6+6T%4vBl;V=N#?wWSCf$LzAX>*@Lbj)r!Pu&mKj zYR8u@nUEdX^;S?=(4Xdp6~sCJ;ffjz< z9!VFzr|_=sdDaJ!GRbRFkv256`DUP$6co=mMwxfbMis_o;#QR*r54VaB_9&Fw9LC| zf{WsuuOiK5vkwhQ3mG=5az?h6Rz5aABjismr}Np^SLKe-;`!|}N|#sUgg!4J@ZG{#H3VZHp-Vu)=%q$peG`eB>i_;Qxoku3!4IUMRan5&4 z#u7zDk9KSMEI1_2;hYs-loI+q`N*uUDk=Y1arH&RCzlq*9=~r+c=4dnkAq_d-2Ke3 zxhuwHRTSq$zo8v=qvcxS(%qd^xIO1PUC-982zG0kB~^puxUN50<1h!#l8aR$8wpvxH-67tW` zl;ZH;$2~JKun3)3_MYdiw?hX;x!vh;GH-a-!^pgjF+}^W_9@m;S&B%i{gh~h?;nRT z1}9X&N|#z8R1CGoz<*?}HBAnxQA?P_JZdf|&fGbraptIuu*jiBRZGWZO|Kd|JgzJu zXZ++@!&rDotl_^c1q;`$8=AKsoQAu2>834>LtlO+d*kNj#Us&Q3dt^xkdR& z8hYds9l7Gtex^!m@Vn$nd?34e`CW3QU1G}B8A~IEd2Z4!XCnr>qupcOYKcJ!7h-e= zZ|hK7fYJ2glC4xMVuCSo!@?KMdNBI|dyKgpEpLOM=&FQd3+|#u5IH5Yyf8Ag=g6gP}dAU+cD46os6#@Z)B37io|i)XJ+ZgK$$EWUx^g#JRny$KEz>r*yd)31$}RTz z0f{+->`v$4>|8m_Wev+pwZsew3v%a-D;-#MQE^molp`dfXink4^0K0msA)^H^Jb5Z z4s}F^gcmKJJaEF;>>zhodWgq@ffC%~IfO9m~lxaj=^w+Gv->sLWBhnjQB* zz?K<-7^;dwDAW~#$! z8i@Yav?Q#yc%}VhP87%Jx{S{*NYVUR)6^ zm!Mt5e?f1MugDuPB>Mu~AfGzs3^&MEq?^fZGs2cYN8&<1k33iNIA z9<7~RrK8{a1g6aN=@S?<1o{MmNuR)oC4u?`D&|EEF!Twi99Rqda!|lIz@2aUerQZs z3yIfxX-t69aiuXqGc_i-g8VclAb#BcJ_oaNBmO9*k->ZYJ2crb&q7lES2OI{Lmc-# z6%}N6gm%4lA~wvi*AW)0eXF8tgZ$or7wnEa#|qpk?}!d3nnIhf>|*IEBtXFFEcr6G zP4f)5T`q#Vy61HdZdRpRGewl@FNnM{F}n`1WfjiLa)qT2Pe>S+9_Ac6x4D~LrVpBABdhNp+QvgS2+v&-~h ziz;nHD#s5|2-4h&@|o6ObB=+K~`Q2G<*%`^OVxeR&6ZWP?!{3qlY*RDqOp{XNy@R0Pu zq^V09#DS?*BlAnJtpi#|QpWbJb)@6k8Pi+) z)H*Wb=Di?STJvhb4Z!P2!$1@UcUteF6MuA5dS-ug$z)V}b;(>bPRm{xs7q#zSCGBDw6@8&HD}P5_?CHx3Q>D7g{Rq-9Qgwh@|TezYZlMT;63`$I3o^`flS_nQwxSP@ zH_SH3;gbxr9W$lDhS>or=19Zr#Fv$jrAVs_(cXe{5y~vFQy05o*j>ZymhP#eFpyx$ z5W{R0NpgZ=wuwa0rV3++d%IzF!TmdiIT$-|?lsIIGF8&L4aFHMiZwH>1QBZ?9YJtM zL8Ea_PMM|1w#+rmnh3UBY?v*+yy4P!r{&v*yG^()-!;tGjb!)IP?Tgsao)|5BYuHN7mAjk0YccO3scKQY#RI}KK zgKsT3VzN%G7i}U1BIA7ir{Id6^|&HD1))6Hrr!Z`18lPpx>?l2wwlA2iFUl#!>t8} z=u*)2Qao*lw-t>%56?P$Pa)c|Z8#TSHDgnH9u|8map}Wc#9o7C-zqT&ZvH6^4Y18Y zdg~BF1CGPAAb+{YB|YO1PYwQ6F!_I-gZTWv@b^pU=C=SLDeoTStcfvT#_s=B_ZVj) zCvEsQ;jb_=38qH;xA2n-pEZcHl}o&t)2M@si2=ec`KDmuo6;rR&xL6%Oj{633%*D( zLNwsL8uliHr#Mo835uQaox-@KuycXW>3G)w_Y~GY=IH@3WW%p}sYf7Z^jGz+au%4b znVVX%8Ts>YU91A6w70-cb({hpO1DhRW^)_bKuV7mK{%l{w-Mj8B2B`51K!kz)}bBL zAcpDZ`IUd2%|VP*BTZ;))Ltomsx=QvP=^%gpdsOKHCzd^lzXa?cI1q3=<`_!n@J<( z_^tuiFJ}zU;nCjH?A~Jf3T4LEw?DOdN(X&8|8N={D?2@ZS~}d)#Z%~ae+Odw36$Sh z@!|dAT4>A55V*)+tik?(OlYvNO9x1`PLej5r=?5$9{XE^u!Clp_!P6r5HVbaifkDs z{s2v;N5$vjPcmFaKnj;DBSoH!lF>3o#zIP&FGgS$K3*osL^Rz3@r?L0s_;2EKqf&d z|5I@gv(k6aj~B^-G8vm@i)D&<4O*oJi&5xX(`33Fg0;5i#a~d}FUSmBtD7ZCQ&@M~m1UXU65Hn?& zsFCHeLRQKu@lSMlvt_lMBqz%$VkAj2Ya=x4{XGrW~ zku^AV{iU2GE*A^sY+MvPSI)y7o4dqrtR!44=gS3hA!Zbdq5okqG)*i8@3j=W8%}{Y zIE4FLE|M$6Nx2fc2Y!pADXZjaaWlrnXz*mQ;QZn-!X{$2pM;$lAB%sB({hchlWTG7 z#x3A9C*a)dK#cVFVuZZ|qi=-xAFKl0jxpAa6~Ra}+T~&;SjCZ^ZwK2gNVML*kc$^ralYe(^`J?y_EdQ(P~8ghu!Py70#AV_N+(mOGI)geyv>*V+3_3{RJqr6Fe zU*0VD%OBvH%^%8JNRQ^dm zgL~DVmCwoNK^*S6WK*ygVC`a7zOPpj0Yj`k)`{)mDC_atu+>*Q6nE!vc+jwwdaDgHqVi|x~E zSGTv-*{5^dDuTjt>vTk5pQ*fN`gx5kvQ#y#3#nP(-n6c^rMpbMgKF=>mae?7i7BW8|+WAI~=c^ps=C^p7*4Y$eaCAXie3*4@@uGc;Li;wKr+u43dk25!?(iap6D=GwQoYN@sCRh=0UJ z=GzF%Ic0OOL5hY&A%HFJGQxuKL7S&xO&y!<Eg%y(SJ%9;#80hhOtR z0jp6ig(Fy|!7`0Mfn_=@(@o1PILv{=95Vx|(?kZ8!RyQ@a8d8gPWCC@S8INOW6D&v z22Js`its5uJh5l!6caCO4V&V{gz>Gf#KFesz#hTI5WyC^O31EC6U230SWIZnnz z%2KnU2LIVXH9m|um3c*tX_y^e(;Y`O0XPcsS}7(Uew@m@Vs>P}$j3PdHikcy^BQ0I zf@=J7JBz*Tv(-!0evn_42dPn0MZ%~nQdpj=oYW|4n0?p@nd{%RFs?Aj60Slh62qK3 z!ak2bbyW?_xATH>loCX3uax?tnwYba&ujpV!=3`&j z&&8>%Zf;x^THCm>uBElMX$^aXt^!j~*HE`sQC%2YTS%tG-fGazFAiGO-q27N;HC!R z!h%so7&Q<>aB0{_8}CtW7Llpw)_QVV(JQ&udN!+Z(XqC{aA|H=?TtPw$R9N_Y+YRo z1Z-ZVh{PH4Klc&kZDmD1Rg3(U!-dvg}Akx#khWKxAl?tE6=Vk6zFzkvVoUNX%9Nyiw zzNJom>SBVY!Z_QuG_$X(qpn4nNR$BGw~$kZ;ccO{SM!IxaT;dHq#0LY&PdYS5IhoL zA^~IZNX!aT0MqeE$Qd#K^B@_3j&nQ`v-vTA6YxmP(5nF_f5amp5BMwKD|jU28z*qgaXIGV5;M~cfE(pT zz*dR;U=E6$U{6DPFTMbH47*Sz=8CTZVs;4lG4|w0%mY6I?8JUZoFB#sVhd)0 zPQY+2956ey@s6^sg zBAnZw-TOF9PW;^wXouSMkVpwxZMmZ7<9xz$PiVG!oKJLrEJs3Dswcdw=VN&_v?;VH zA~+)0_A0@MVEc44F5lbvM3^%)+uop_h>_+K_8>ny!XC6=X+Pq9zI4QTpUDpO%yRU2 zoKHA5I_<{OBj1i)j(zHhDCzBS-0FDC@s{(b?}?~%eCm6=F`Fsaff>6q(Rgy5zNg%2 zJ`0@PpEb_C-seW=ecs2&Z$t^we>b8MIn0h|i70WM3fdQPKRrRW2Jt)Op^!&Ij)lA) z`fljEA+L9TLW2z~!?)X$9XkC?HAnRp`XHVi3?B?V8kQ9LZdg*c#|isn<7o>sA8);d z9S@(`%X4G+jo}Y_pI5`x(}nhtjk+&Ec^5^rplxQMy+XkdxBp<+MniTt0rJlRF%7cE z@sJGfLJzzbz2SPu`A*^<>NmyP;%N}l`{)Tj#LC)HP@fdfupIeWXbg9X=OHI{i@(S) z$cA5lzO+H&uaM!Ui(`=7W9)o)CL@);vIHlmx_T03h~%0ghx?#ITqlc2mH848ti5r!t(*a0bJf3~LzfVz`&#r3}Bt z@G^#%GrWS~w;5i^5aS0@xtifM46kK~TO8qjJ;NIqVowKLZesX-hBq_Z&k#3b!1or0 zKV*0-!`m6&!SGIocQHJ`@J9@P%%w!RTDPb z=1|N#U!~BkFH+1Xn+6;m>zGV#Z3P&Qbzy6ZC)>`0atPewIm9b%;T$3y^xKL;z=L5s z!_5r0Fx<*;8^aEU+Zpa)xRW5o)Uk)+6=AJMj1p>;w~k?)axwDJs(*l>y8g-zc5B^F zdDDN>x`RTra(~o8JcEvKP?8B;61Nezn{J%YMMCRj#_}oh*vhHbZy&$f}|vn}0-HI{5UuBSY=e8}~cuq~Lvb-YMEEsvoN zj&;09*w;VUvWol2t9BeD>~G}|*c*W~wk$zjZOH2|5v-ruF%jnt2u3rEVHnFWj$u5* z1cr$W2QW-xIFMm7!$Azw84h8X!7!6y7Q>+ohcO(^Fq>fx!(4{>3`a05U|7g-B*P+x z#SBL=9K&!5!>J6XGsLKXa?NB|Lr`-wRHdfbOZ4_~)FMF(Ln}iYLpwv9a)U2)kOR6H zx)}yB94(?Zmtgc2+aJZEve>>))MLClEH*G~VA#m8iD4_g+K1Llu$|#%hFchJWw?!D z2gB_QcQD+^@E#GpwOBlPaw?8B6`z3;uTK!Rfd0Oc%0!s z7`{cZVVx4C!%AiJrbJoG=2Z+=GhD;4j^SE{>lm(Q=wbK};Xxjw)aBn8zR2(;hA%Tj z-@N@%`5|FRe#G!&%7gqj!_y2uVfZOQEuKrNVO>&eds#~%h`Pd;+pZ>+VB4;yc_vym zXqlmfp_QSHp`D?Fp_8GDp_^e4!}*A5H?~j^T*wd`&R|~5a5atMn1f_(Zo{?#g4mG= zxQ^j^h8~d&mwJ)4?OLp36Kr7E$gqiFt9}S^wlQpHxS8P=hFckKW7xrPJHs6ecQSlI zgdvr`imo<&hP4b=F^Hk~=8#AcqY`;{E1H@rA9>(~)zwt@*)9L!(+jb%Bp~fFm zsK)Ezf2{RB_#bKS;IZvg<7W2RgVD;p*$V$tTQvBe*fIw_3i#001{!A?SD|M<(OAkO z#tDqU6dQUTi~-S&BPs3n$I-j!`oVTLjSGz_i0MdU0b)9c93B(1;Qlv;FEV_I;mZWM zmp|C{EPdDT27TA?IepjgF6Z!Q!*P`J)aI2)C82d5{6A=T5&kC|p64%bXxK`*X}BLg z2O8|~In=O=^L|MKaSzQ62Pp3i2e9WaT|d@VFIMYMwYNdzQi)nNvOlE{T0*F!n~M?ZXv>T8U-~C4cB)0hdziV5OIk{h<}MV) zlDX`7y>niTHV8_B=zdh*|Ng@I3SejCDRZ>GL&6JxD;BjvgN7=>!vOnKhO93Ffj z@6ay;CkWoG{%N@m=0xN-g7X^*sEJ+r2lY3KH}z9(`A8GoawBF?`pT9LqLsE()ZVU* zb5MJ2EghInB8B>mTz@C)+mwCd>qte{pW65;=VCdBStPg?Ha;ygQF|G3C{iiFyi#}` zR9|iSTxhk=kmO}meceOYKLZmA^Fe(Qz3Hc)qQA21r&=FGi+BjBQ}68kA=hXv=C6Jv z{P9;m^$pBftw&5Z^{!71w4cr9AKnvsw$K;SpUO{oP`{1Kh1mFG=2OF3AJkug-zzN- z@f#zp`qhWCKU)HEMNaxj;4@j3M}MU`88rEjex!b^`qqRSOHGr)IoX<>oX&~1VLrd^ zrutlR(LdOTnH=Ih(d_IV;@s{2xTmiXFUL=912|x_ug=Z*(dMkmTpw<_o$c-S4VR<; z;m;QGLirP3>F@gyy_hs2MAQA~ML;L&NA19D3#dD$3;ne3d;JTlOCP<_SNaiO-J>6W z2t7%z{#1ReexiP?e%f=C+5qz4Z6Eq+gb5srqmH)AK~@`^|RNGa~(FWo4eYAcEG;$FC+YrZo^dC2S z5(&qkqmlY=JoivM=reLL=EU}h=LWNX@K4*5(mmXTvLoMT?C14U#BZSNuh8E=PmGUn z^oi&jdj10NaRb60X+6T_K3JcnUqQnZz^+-|(%Ez0X>Hgl%G=E>&1=p@Dt@MAjo75$heLq7!fGx8s13<&?q z#%TRmZ~fCou1D3s5r5EhKk)_n(dJO!*B9QTqb%ll3BGD9xBYw3#t%`wB2Oi~rHM9I zV2s7Y(}tm+SVHZL%3zcO?Zo#b@O+Tk5Xy7J*JtY|j5LJLjlR(@@nAI}@JDxXsGo}8 zw4M8#(mGRHBzjUb1^h49AMuRl|Ggf?FO+ytk~95`aCnKdtP6 zuzq5o{rqkIya@RCvHCI4IQ{#;M_@{RWiaM*roVpdTzL<_xXeiW-h}rv_#n_2}Qe{-4Q% zo9&&)vs1o4fpVb#s4*F14&$kNj!4_6`GYhkS3d;4?eNA*eILfF+f-km?*}d3(e$W( z1^Q{42{`h9Uum98QePf{B^*g{{U_J*L^s8sTZ+A8=H^cuY-ue$)u(u$v z-O^aDpH}Uj$H0@ypZXHT_jGR){{0@AeRlu;C$K!eFoEu5Yfe^fzA@H6PR#!?cAv}7 zq~UYl1qm7uX;49^${pmD+}9mM=flpL!Pf>_%Vvsp2D1HsH%_ z4<_z>?F+IEv@P`m%^fk#_{MN=>p?$e82tm!GNirAwD){p$hKw1Zt`BH-S<}VQJ*}Q z=O1m&uj#vb_5lhn^ld zd`q=2$Cx9q+{F(s?w#a_J+DSd(6T-}c0v&+qFt@%S6vv+b&M z&Hluf3kuJFzx5iQZ|^2fqq|oY`1MpzTX$!*R@Bq=tGf8h{xH{n)PF9k-`h3}JbpOj z1dxUJ)0s&(lutid@)4qGYTw*X{Mb8Y2grlK_x2(W!tMgJO_Enpd-UNQ^2TnwosXaX zs%7u(d&r2r<15R9u%<@gNG`?lagymkPR?bECLjO)lfnTXMxRN1n8F8z^Zg)eH^vs_ zMt!7y5PhQmTHX;#gTDtJ{czk1--daGH_yIQF1!hKluv((nb+42Am8+7e8ADapFHJP zMiU<|8)H`i#bCA}#MK)<_G6BFp!qcl+5M;F1IkrD&~rVC<%K*y_HVP@V>y4jIp60P zqRsi08J}N&YC84%-Lqf$y6C>&S?U(B=X}0ffF8VicmMyCJ+L1+TdG*^8cTl)M{CIR zH@`t!r*-Q-X$4wP@sQcziN(+ME8CF^h5BYbqF{5Tl~r1 zb1X6AqIfO@uhn;cygrnlfN{d><29JF5OTo!;%@ywcYJt*`Wep5GwDYVl+&j4fe*;= zeLs_53+$(T{=J#}ao3xFf8D5$^YwG;tHAPNZ~wbyZ2BRLw^$!A!aZubLxvNIc(~tH zZvOZ%!}~((x1l}y@bM(`=00yWfsaL-rSyUtsOI7ddBO zSh7)g_0!+?95v0vN6<~`>gO9nO_!c;s`s|Up6*|#OMmhem_Fep(51W0hY$7%^<0Yn z3+DyEqx$hT-M{K-_w|8+e%K}3%MW||`kX8G61-nd#Gjfxs{S0>#L<=v{5PvTPVi$M z3w_BiRG;bX!vorlZ*HMKrP?5c$NKMB{jru8sL$rL@neYpIb#es5!eUt+VL+aFO+RR znoF+t<-wR6(%N#r3U`)3C}%KsL>v7<&%h>a=$|z0(x3D3Nt!zg9F8$Q_0z#q{~Q-)Iv~qI*h*cogg<7V6(q9)vkWk3Pja_hP=jd%1kM2hFAWZ2dI( z?eV3w6;IuF8vTs5AHht10JKhPFw`f~`-E>TOF!CoK6sBM?XyI9p0^qDE#|>K0G~*5 zFu$LFe@i>lef~%T=e*AR^U3n)PHX<-g|%nw8TRp`n5SF$e++n3|8Tr%zZ3FEHbb{O3hv?u{n;>ia1c&vAnE3tYSQ>{G4;>nEyz(=MMInf%itp6pEN_=gRQ#b@*3 z!ROYOiQ0G*YT?{}w075@uk<6>|3iOjS4Y&pKi^RpRaf1{?*4v!-r;)EeP2;gxya8f zUti(;-Ax=YJ`RHB4k}+Bcl-nPVkUfamu;AN=p}eBbD-Wro!Je0Sp4|xz_4n618D6z zfBG@_iKkj$qtHFu3GEm1;TWebKyT^?et&^7AKI6OUv1`@ea=$uG>gLSN&$ z{}*4~OndiwmWR$X_7!LUIVwi>`&zI{Y<0p)X&vN<>y?# z`oBHA{uKH#`tz9t@QUD}O+NZ;Wy6_;chTo|8v{_+Ebqnpi=O>|z*rnGE}MP5Ic8yB z>gn!hR(xZ({@mGecVYc~>#cpo3x4~w|Gp*Sn|hYt_qDIz@VQ-JQ?=*L3mjv=qr-gB zyB?*#3x35rXX_t7slT}~niKPrJNG;B|H6PY$1?dT{a9bJ8P7d|S?>>zL|@Q%p2PSb zq%k=#jx*SjJ&=ifT|dfC-#_AEO<$iJ$8`DXZ}c;%ynpjt@bT4??K1}bRQG*MYD`2Q zsm4+p<|!W;{DF=4uVX(k+zuK31^#Wp^SCb|^^Jat_QRp>XZi6UbRGcnLXx+gNlvWf zg}xX~lYWUIcc}I}>2*5+zK#B;)SSa~KlgXi?Iv;-hP4MImG`XeROZ7{o384sUQ5RwDh&- z@@r1OSAP9n|1;!7`>MUEbzh93eAc8t^AGg@I4g#Jug^K(g(#H&y8eZT@BjKZ!p_Xs z_M8WvTZz#JlI+XJJ7BGg<@YDCe}VpdKCt&Zuq@Er!-MhG*AE(VQDbj1uQ3|^sW~2< zEtimiCz(_BV2Ox%DD`hkzWN~2%pdEVDH{6qC@=l;GI!pvt{d5r;W;&fo0 zn)@L1&${m)LV5l6#_E2udUJg9<;}mX`&>;^&vkY3@w{mIU+A~LEbq*5*XKgcioF{B z`}y#Jbzd6mqcLvNemq(mHqLzaCno=WWBm#Lb%Zh3vMFv}ljA!$$c%Z|4dCDFaPA9l zwfE1&k&mC&PkD};`>fU49`${J^CaB=gK^9EQ|nG7WA?c#*%*CCe;+OA`vdzTibah> zzWBZ_6J*>NLyY}}f7|#l@Q|hco>QD=Rv+!%*mv$-6RW0pd^qV3J_5_+hmV6j*KgH5 zM^q=iGy=cBFgCQ6VdkAYrd-Guv)LvHU#8jT>od{$%CIo=b77XX_pkwd ziC1phJ+=#rkKV`e?rC0lJN3;MKGrkESl_E$#oX5zu>Tm*^yD$m6!%y6`_JQp@_ep1 z>Dfno^^*4qo#Pp$uacAAzBTjX^ZUB-K`H(B;-tse zbk==8il)2MxeuS`X7cq93P)x?;)~@!Q9r)a&Icd9uP==0zYQlBrc`~$64;guT1oqQ z<(_@#=D)+|!r{b}HG)^N`Dh9IBIzzhmP6`a^u-HNpY7Z86EHtKY09ta-DkrX%e-;F z2>M>keg0EF;t_kwFTU>7aW6^$AC9>Z=F7fcuX~kgkLk(2>cID{cmDaDKOO_)_Yd8h zxqs>O_T~Axoew@JrT*vBn3G=soPbC1uGyYnY5)JP&J_dy8Gg*y?{&`~##-zxIMc81 zZ_o7A`TG55@IkrjyIg0-$@!MoFUxEGo`R3vLzq|1GpuAid zoCM+q>jjl&IrU#)t@B>r*(cMz=ezq_USJ6QD#)SvJR!+~AV>DwlWEFi%=L`GFMGGq zJp;Y_oU1Fz$Ggj?{PBTx0{?U9=NkPP!&5pcZQiH;K|N$P=f)?TG5YiJ?2e1`isYO> zQsv zB%z5=h_}SQ#YrePwTh2e@8D?}EJMX7GESz8&t)DSryPyPB}?(Rp=bUe+UcxfY>C%PUx$;MLG3m>{pw251B1 zcc4=+Mefs5wKVxXXt}r&IdhZV5Ii<$=?I1f#SjsJ97jSQAZfx)hWj8q6GVzggNDCM zJOv^TPXV;`jeusp0x=5OS4ZP1gFe1ed^tf(!ndRk*9OguQ$(?tif0rwG){y2bWsD{ zrL)95q%a@ah1}4Oy9%kU#!~?eL+eEnG)Q?w6*LJoiGiY7v_M}CCJxas7(3|@`F&w)2ZbSKQ7k7wp(3pES%5)E&5zw1^5T*K= z_!Yh)ow;`LYdo{WVLWr8G4c_(KPrAJa-l=_cOnitbpHSilYbO{!8x)Q@GOGnp_ian z^<_MZp<(DBz{&~nFW}>KJR0ixB+_~d&j{$9d>byO@GKGU;F&4j#j^lVSs31fXkur$|+aKmC#F4D?KpR z%X(1?y}-@TkhW2_Kr2ZrbX{8IcDY?dLF45Pn0LyZaNi|&iHXn`yhjW{>%2=8LND-7 z0PmMS6-Cev{0k8cZI};Kl4AYFAX(pX%CWvWf2-8eF(@ZF6W+!wh5w(zp=}SP3O2hP}pcB$C{XMvl zvSA5YxlI&-TJDCK=p>ElBph^d2w{E=+K@~eHl_{IF#S8Ua-xj^OdBCg8xxr}l9)Ei zm^KD7Z43rwoJ0&n6=_TrlBuGcsbVx!MJZE72~$NOQ^g3TiY%szu}l@2OcljU6&Xww ziA)uVOcjw#6&Xww$xIc4m?~13Dh4rCq%c(^GF3!MqKZVOid3eGRHlklrV2NxVucvW zRN-c-7{=PN9kKzmVP&1!!{sK>hJ~rZ!t`Kada$zoYzuns+u=((vx7k)_W=@xXErl?S+s7%sY3ko5+Dqy;TPBGBcNT#bWrmI0rS1C+agP5*Tn64z#RT|UPD5k41 zOjqNWuCkb}#xq?_V7jWnsCmh50HHD53kY59Y_4Qj!|+Om$qX-IID_FzhG`5_8AdS- zWjL4NWej5&?qnFtFpS}5hIWP}42J?j2Qiz&89Eq#o1tV_!O+Suo#EwxQmBwA?BZeg zEr!huCo*&cN}*C0?2^ZD1;fz{;~5TSn8YxH;R1$30Ih;zaSG}D68(|Il`5cNO3cAO z2z^r|#=sQxMH%Ra@-c#sL7!8NUS~S`nEB{i)}SA0=KkYi^ck0-uehGI(BF=+^j`D| z52Igr6urQcp#Hyt(ocY9Fp7XeHe$TE814U?&=`M%yiwjHzb|i=`{fVhEuaKwj>pLG z0O^ZI%ZH|T=!rjy*8Bom@vEc{9w`aAN0eMG@cA9)G1cal*aRtobmPEjG3uY6R(fCic%pqUP zDuT{*Hn+gO$Fh&jw_6^7?Qu3gYdK*#W%<$?2_z$DfQSLG9RKO~FT;PWbu-|l)@uR3 z&%T^P>-}tg)QTKhk6GUU{M6=z3etG|Q*8z0Yn#k)4nyR})?(Yi<|`TQx7`P~!)$)S z_KfXC+iSLug|J)6#U5=>w~uuF*oE9GXs@)-g!^*)dcZyQ7KVt)@V(xC04@q*mxlc@ z`!gKRaXWD45Du2AJAf6AKI79l??}m@O&1{DH$13TnZqnWrZmNIr4e|j;B*0^lsSp6Od6@3ezO=lmOn@q$t-HzRC)$@J%3$M>Y_L=9k?`HdS?M5S4iERG^Y$}&mkX_pZ4U}$q5d8=Ba2WHn8*R69h;n7u zKN4=+!$OF0Y~QQhWPG2%_MzVRhbuc;@nr~Yk>4e}+F=wO?9;&^spVzCF2n>ti|o)p z6mF)^9A($Ojrb{)1$HTHjm%weSbu!#i<;U7u^3}=-H!w*ar7SX6yHU>K z@0tNsjCH#y#W%buu4Vhby#8y*E(d%4C$oJJ`CF*Ir?PzoVqOEAMb5@KST{H=Ay%`` zpS178PI;UTyATC{7C8gqABW8%v%N7_u}`zbj_{T6izK@?4z>F%h19aVZBMXIg14nK zl3gD1+Q+l~*Ir7T#`Zh4FXeiHkm6H>Ttp_B0=S$_Z^L~J`c8}dtv8-QyD7|X;jeHt zhV8dgKIAiSokw=`=w^>FhwY!kM}^--cFX@FX1C>G)Pjrc(?FG%iu>TROn(e9U5fDA z*{1?NS7J=FSZejhb#Fz27&ny+fGaf0ONr@Q;R@MMmM42~~#m@PQ6(Knu8gGkiPQ(MuhMZcWNTgR*=5 zFJ}7zZ+dNPM~?+e!6??T-Pdm|XZtkdHyyqf>C0&;d~9%Ieh1u6Hu6y*m%tt>J28c} zXjaXp*)@me)WWqGElx|+25N)hFXdhliZ2PDQ}L&7>hXq7PxSXfd>{XV_&XC!1ZP$yL0`d7-fL*>wcD$`I{sgryYnVV5#p zyA`gJ@wFQ?AjBvRm81RI2vx}T7;g%>u!CIy#8`yVhl+UEQ!yGrQ@F%v2?-IUb3Gup z3yalTqs#D>Mf(Cis>~b7ZrOvpQtlynf?bFrK#8{-(w^sqR#`oLo(OHIZEF%K%*8^9 zD!(PZvm#|%*^;UhQBW~w9^kaHC4lqF$UJY(G(brFb)z*bRb!HYM@M%g5r!ISUAOtl4^ zd7EsPo8=a{Rc@0Vn78f%ufAJ;L+$~`PIAV(-=1P zA-~i#ElKK{z}zwtbINGh1#D0av;dv>i5AFAU7}S+fGXQC_fHm^WreI@IyN!>F0{@2 z+JGJGtrK8BMDN-WXNz*APG!mDd-SL zB!`M&=xH)V4r-ZZQ6tdy3Ly_Ig1mDSWIAKetBiwGZ#+ho31T9oqveoqRzj{>4JqYh zmhVi%jB5tuf;E_T&Bp9(u9yee)O^SY7h>dGjQ(gTbO2w3v2%r3DQY1nU5&X}9dxg+ zgPhcZzG?$>tT%#kn#D$3G}(f=c`(wZmfk9$We+{rNil>aFRgxU46P4sE`#xLqqxb> z2XjTV$_#Fq7Mk;+l~Vhs7K?EbAtUjhW3)m{w$NLX6j89GM6&$DjalDK=zDLLH;XXL z`)(28@>Y4Ph`{Xc*CGTNnoz6iBJ^W%+Tv)F<0Lxc3?QouNQ8DPA;0x&^d379Ca1{@%-1x%9r00+wJ0F%*M zYGRPQ5innVAFx2~2b>~*061O#5O9XP4R9v@cF2t$#e0JM4d67x<+tqeIJ=-{mjCND z{f1gNj60RFHCIG$0kNUr0 z_K5|AZ6Y2mU76wM^ndjC*wzYZ8_^B1yF zyVYOvKJV!Kpx5dCL@6Kr9bm?eo@or)J3V92gILk)j6^Ro8@-qJ(cdspkN%Cgr0!1# z+Q$@Ny3lwQqUTZ1{RXB9+X3}LHvKR9Gv*_*^v}Fcm(CApCJX6JLi)2nFSF2Btp=Vh zLd{g89kzm=a?s0dLvFKCW3l?j$p2vEWfJgxFZ#+dG>l1%=Orjx0bBZj;C=>ZSKCEte|O@^ZexUKOATad4((8EWAOTfKIaGQ->-N=4R^iH&7 z7qAC^_#VS^3g=>ouSr0w9sw!&tsGK;+k9rs#wx`g{j~29IF$<5+c3tMk7{eQw&nj! zhAj&%Ezx*pp^bX_@!x}9>L!HChLpb+PPY1?SdhWx2kN3F=qgwZeMvR_MGxAAo zR6TJRW$6*Kn38*rPF`2#ny`mUZjfqz1DcV|yyQYFIkWiPoYA3*D4;xTr} zSexsr^ZT&G+gyE@%=cbL1}UFU9e zZ+2hozRZ1}`$6|3?kC)z1<4?HP*hM-(2$^;`g-i{Z8?r2BE$s5Jd&0g5w}j_~j|y)O-x>a^@HZodMQn&@iRg&g!M^iA`<)%S7Vw|#5;Jp2;<_W8Z;cg*i|zwi9c`t|z<`WO4(1^

j|ZQ__GM1U>X3aQ2SOeVc{b#7XnN@M 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 #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. --> - -- 2.49.1