From 52237fda7e3421b15fc2cb86e1fcd7de258653df Mon Sep 17 00:00:00 2001 From: Jon Kazama Date: Wed, 19 Aug 2026 21:50:21 +0200 Subject: [PATCH] feat(wizard): the profile cards and the welcome page speak the window's language The cards were the last surface still drawn from ImGui defaults: four emoji on stock child frames, sized to a fixed 2x2 grid that clipped the longer privacy descriptions in half the supported languages. They are tiles in the row language now -- a resting surface, an accent bar down the left edge when chosen, a held hover over both, tracked caps for the heading. The emoji are FontAwesome glyphs from the icon font the rest of the plugin already uses. Each card measures its own height from its text and the pair in a row takes the taller of the two, inside a scrolling frame, because cutting a privacy choice off mid-sentence is not a thing this plugin gets to do. Step three moves onto ToggleSwitch rows and a drawn theme field with a popup built from PopupRow, using the transient widget overloads throughout: the Func/Action pair saves on every click, and this step is staged until Finish. The welcome page is opaque, unlike every other window here. Those are read at a glance over the game; this one is read once and carries a privacy decision. The fox sat on a hardcoded off-white rectangle that read as paper taped to the window -- it is a disc now, tinted from the theme accent and only lightened as far as the black linework needs, measured rather than set. Three defects surfaced while doing it, all older than this work: - The GDPR notice for full history had been translated into 25 languages and drawn nowhere since the four-step rewrite dropped it on 2026-05-18. It is on the card where the choice is made. - Two cards claimed to be recommended: the badge sat on casual while the data minimisation heading still said "(recommended)" in every language. The suffix is gone, and the word it carried became the badge label. - The wizard had no way back into it at all. /hellion wizard reopens it, and OnOpen resets the staged state so a second run cannot commit picks from a first one the user never saw. The welcome text drops the fork framing: Chat 2 and this plugin have diverged far enough that the codebases no longer line up, so it reads as history in a muted line rather than as a justification up front. In its place is the notice that plugins are a grey area in this game and do not belong in public channels. Channel names in it come from Language..resx per language, so the German build says Sagen/Rufen/Schreien and the Polish one says Say/Yell/Shout, which is what a Polish player actually sees on an English client. --- HellionChat/Plugin.cs | 23 +- .../Resources/HellionStrings.Designer.cs | 18 +- HellionChat/Resources/HellionStrings.ca.resx | 14 +- HellionChat/Resources/HellionStrings.cs.resx | 14 +- HellionChat/Resources/HellionStrings.da.resx | 14 +- HellionChat/Resources/HellionStrings.de.resx | 14 +- HellionChat/Resources/HellionStrings.el.resx | 14 +- HellionChat/Resources/HellionStrings.es.resx | 14 +- HellionChat/Resources/HellionStrings.fi.resx | 14 +- HellionChat/Resources/HellionStrings.fr.resx | 14 +- HellionChat/Resources/HellionStrings.hu.resx | 14 +- HellionChat/Resources/HellionStrings.it.resx | 14 +- HellionChat/Resources/HellionStrings.ja.resx | 14 +- HellionChat/Resources/HellionStrings.ko.resx | 14 +- HellionChat/Resources/HellionStrings.nb.resx | 14 +- HellionChat/Resources/HellionStrings.nl.resx | 14 +- HellionChat/Resources/HellionStrings.pl.resx | 14 +- .../Resources/HellionStrings.pt-BR.resx | 14 +- .../Resources/HellionStrings.pt-PT.resx | 14 +- HellionChat/Resources/HellionStrings.resx | 14 +- HellionChat/Resources/HellionStrings.ro.resx | 14 +- HellionChat/Resources/HellionStrings.ru.resx | 14 +- HellionChat/Resources/HellionStrings.sv.resx | 14 +- HellionChat/Resources/HellionStrings.tr.resx | 14 +- HellionChat/Resources/HellionStrings.uk.resx | 14 +- .../Resources/HellionStrings.zh-Hans.resx | 14 +- .../Resources/HellionStrings.zh-Hant.resx | 14 +- HellionChat/Resources/Language.es.resx | 2 +- HellionChat/Ui/FirstRunWizard.cs | 950 ++++++++++++++---- 29 files changed, 1012 insertions(+), 331 deletions(-) diff --git a/HellionChat/Plugin.cs b/HellionChat/Plugin.cs index 99aadc6..a0d2a52 100755 --- a/HellionChat/Plugin.cs +++ b/HellionChat/Plugin.cs @@ -190,7 +190,7 @@ public sealed class Plugin : IAsyncDalamudPlugin // v1.9.0 B5: last full Draw() wall-time in ms, written once per frame at // the end of the UiBuilder.Draw handler. Covers the GlobalStyleScope push - // and the font push (§7.5 First-Frame-HITCH must include atlas/style + // and the font push (the first-frame hitch measurement must include atlas/style // prologue cost), not just WindowSystem.Draw — measuring the inner call // alone would drop the prologue and make the figure non-comparable to the // v1.5.6 baseline. Only accumulated here; the disk write happens in @@ -360,7 +360,7 @@ public sealed class Plugin : IAsyncDalamudPlugin Config.Version = 26; // Unpinned TempTabs are session-only and dropped on every load. Pinned - // TempTabs survive reload — Jin's tester feedback (v1.4.7). + // TempTabs survive reload -- tester feedback in v1.4.7. Config.Tabs.RemoveAll(TabLifecycleHelpers.ShouldStripOnLoad); // GP-04: clear stale Tab.PopOut flags now — the pool binds further down @@ -599,7 +599,7 @@ public sealed class Plugin : IAsyncDalamudPlugin _ = Task.Run( async () => { - // FQN: Plugin.Notification (Z.74) shadows the type name. + // FQN: the Plugin.Notification property shadows the type name. Dalamud.Interface.ImGuiNotification.IActiveNotification? notif = null; try { @@ -956,7 +956,7 @@ public sealed class Plugin : IAsyncDalamudPlugin { _hellionSettingsCmd = Commands.Register( "/hellion", - "Toggle Hellion Chat. /hellion settings opens settings, /hellion reset restores the default theme." + "Toggle Hellion Chat. /hellion settings opens settings, /hellion wizard reopens the setup wizard, /hellion reset restores the default theme." ); _hellionSettingsCmd.Execute += OnHellionSettingsCommand; @@ -1051,11 +1051,22 @@ public sealed class Plugin : IAsyncDalamudPlugin InputBarLab.Toggle(); return; } + + // The wizard had no way back into it: the reopen button the resource + // file still carries in 25 languages lost its call site in the + // four-step rewrite, and nothing replaced it. Without this the only + // route is closing the game and editing FirstRunCompleted by hand. + // Toggle, not IsOpen = true, so the same command closes it again. + if (arg.Equals("wizard", StringComparison.OrdinalIgnoreCase)) + { + FirstRunWizard.Toggle(); + return; + } #if DEBUG #endif if (arg.Equals("reset", StringComparison.OrdinalIgnoreCase)) { - // Recovery path documented in the v2.x master spec — drops a + // Documented recovery path -- drops a // broken custom theme out of the loader cache without touching // the user's JSON on disk. ThemeRegistry.SwitchSilent(Themes.ThemeRegistry.DefaultSlug); @@ -1359,7 +1370,7 @@ public sealed class Plugin : IAsyncDalamudPlugin // B3: the strip/restore mutates the tab LIST, so it shares TabsListLock // with the worker add/remove and the refilter snapshot. Re-entrant: the // one worker caller (HandleTell) already holds it; framework callers take - // it here. SavePluginConfig runs inside (short, in-memory) — the §8 fallback + // it here. SavePluginConfig runs inside (short, in-memory) — the documented fallback // (serialize a copy outside the lock) is a tracked pre-beta to-do. lock (TabsListLock) { diff --git a/HellionChat/Resources/HellionStrings.Designer.cs b/HellionChat/Resources/HellionStrings.Designer.cs index 9bbfa0c..a02a41a 100644 --- a/HellionChat/Resources/HellionStrings.Designer.cs +++ b/HellionChat/Resources/HellionStrings.Designer.cs @@ -116,10 +116,12 @@ internal class HellionStrings internal static string Wizard_Step1_Title => Get(nameof(Wizard_Step1_Title)); internal static string Wizard_Step1_Subtitle => Get(nameof(Wizard_Step1_Subtitle)); internal static string Wizard_Step1_Footer_Hint => Get(nameof(Wizard_Step1_Footer_Hint)); + internal static string Wizard_Step1_PluginNotice => Get(nameof(Wizard_Step1_PluginNotice)); + internal static string Wizard_Step1_Heritage => Get(nameof(Wizard_Step1_Heritage)); internal static string Wizard_Step1_Skip_Label => Get(nameof(Wizard_Step1_Skip_Label)); internal static string Wizard_Step1_Skip_Tooltip => Get(nameof(Wizard_Step1_Skip_Tooltip)); internal static string Wizard_Step2_Title => Get(nameof(Wizard_Step2_Title)); - internal static string Wizard_Step2_RecommendedFooter => Get(nameof(Wizard_Step2_RecommendedFooter)); + internal static string Wizard_Profile_Recommended_Badge => Get(nameof(Wizard_Profile_Recommended_Badge)); internal static string Wizard_Profile_Roleplay_Heading => Get(nameof(Wizard_Profile_Roleplay_Heading)); internal static string Wizard_Profile_Roleplay_Description => Get(nameof(Wizard_Profile_Roleplay_Description)); internal static string Wizard_Profile_Roleplay_Apply => Get(nameof(Wizard_Profile_Roleplay_Apply)); @@ -343,7 +345,7 @@ internal class HellionStrings // Hellion Chat — v1.2.1 Data Management tab section headings internal static string Settings_DataManagement_Advanced_Heading => Get(nameof(Settings_DataManagement_Advanced_Heading)); - // v1.5.6: Data & Privacy tab section titles (R6) + // v1.5.6: Data & Privacy tab section titles internal static string Settings_Section_PrivacyFilter => Get(nameof(Settings_Section_PrivacyFilter)); internal static string Settings_Section_Storage => Get(nameof(Settings_Section_Storage)); internal static string Settings_Section_Retention => Get(nameof(Settings_Section_Retention)); @@ -535,14 +537,14 @@ internal class HellionStrings internal static string Settings_General_CustomSoundVolume_Name => Get(nameof(Settings_General_CustomSoundVolume_Name)); internal static string Settings_General_CustomSoundVolume_Description => Get(nameof(Settings_General_CustomSoundVolume_Description)); - // v1.5.6: General tab collapsible section titles (R6) + // v1.5.6: General tab collapsible section titles internal static string Settings_Section_Input => Get(nameof(Settings_Section_Input)); internal static string Settings_Section_Sound => Get(nameof(Settings_Section_Sound)); internal static string Settings_Section_Language => Get(nameof(Settings_Section_Language)); internal static string Settings_Section_Performance => Get(nameof(Settings_Section_Performance)); internal static string Settings_Section_Sound_TabsHint => Get(nameof(Settings_Section_Sound_TabsHint)); - // v1.5.6: Chat tab collapsible section titles (R6) + // v1.5.6: Chat tab collapsible section titles internal static string Settings_Section_Messages => Get(nameof(Settings_Section_Messages)); internal static string Settings_Section_InputPreview => Get(nameof(Settings_Section_InputPreview)); internal static string Settings_Section_AutoTellTabs => Get(nameof(Settings_Section_AutoTellTabs)); @@ -550,7 +552,7 @@ internal class HellionStrings internal static string Settings_Section_LinksTooltips => Get(nameof(Settings_Section_LinksTooltips)); internal static string Settings_Section_NoviceNetwork => Get(nameof(Settings_Section_NoviceNetwork)); - // v1.5.6: Appearance tab collapsible section titles (R6) + // v1.5.6: Appearance tab collapsible section titles internal static string Settings_Section_Theme => Get(nameof(Settings_Section_Theme)); internal static string Settings_Section_Fonts => Get(nameof(Settings_Section_Fonts)); internal static string Settings_Section_Colours => Get(nameof(Settings_Section_Colours)); @@ -558,12 +560,12 @@ internal class HellionStrings internal static string Settings_Section_Timestamps => Get(nameof(Settings_Section_Timestamps)); internal static string Settings_Section_Animations => Get(nameof(Settings_Section_Animations)); - // v1.5.6: Window tab collapsible section titles (R6) + // v1.5.6: Window tab collapsible section titles internal static string Settings_Section_Hide => Get(nameof(Settings_Section_Hide)); internal static string Settings_Section_InactivityHide => Get(nameof(Settings_Section_InactivityHide)); internal static string Settings_Section_Frame => Get(nameof(Settings_Section_Frame)); - // v1.5.6: Tabs tab per-tab-item sub-section titles (R6) + // v1.5.6: Tabs tab per-tab-item sub-section titles internal static string Settings_Section_Tab_Channels => Get(nameof(Settings_Section_Tab_Channels)); internal static string Settings_Section_Tab_Display => Get(nameof(Settings_Section_Tab_Display)); internal static string Settings_Section_Tab_Notification => Get(nameof(Settings_Section_Tab_Notification)); @@ -571,7 +573,7 @@ internal class HellionStrings internal static string Settings_Section_Tab_PopOut => Get(nameof(Settings_Section_Tab_PopOut)); internal static string Settings_Section_Tab_Volume_AllTabsHint => Get(nameof(Settings_Section_Tab_Volume_AllTabsHint)); - // v1.5.6: About tab collapsible section titles (R6) + // v1.5.6: About tab collapsible section titles internal static string Settings_Section_Extensions => Get(nameof(Settings_Section_Extensions)); internal static string Settings_Section_PluginInfo => Get(nameof(Settings_Section_PluginInfo)); internal static string Settings_Section_Project => Get(nameof(Settings_Section_Project)); diff --git a/HellionChat/Resources/HellionStrings.ca.resx b/HellionChat/Resources/HellionStrings.ca.resx index c843d03..dec91cf 100644 --- a/HellionChat/Resources/HellionStrings.ca.resx +++ b/HellionChat/Resources/HellionStrings.ca.resx @@ -172,7 +172,7 @@ Tria un perfil inicial. Podràs ajustar-ho tot més tard a Configuració → Privadesa. - Minimització de dades (recomanat) + Minimització de dades Només es guarden les teves pròpies converses: tells, party, FC, linkshells, cross-world linkshells, alliance i ExtraChat. El xat públic, els diàlegs dels PNJ i el correu brossa del sistema es descarten al nivell d'emmagatzematge. La retenció segueix els valors per defecte de l'especificació (tells 365 dies, canals de conversa propis 90 dies). @@ -214,7 +214,13 @@ Benvingut a Hellion Chat - Un fork de Chat 2 de Hellion Forge amb valors per defecte respectuosos amb la privadesa, visuals coherents amb la marca i alguns retocs de qualitat de vida. + La teva finestra de xat, de Hellion Forge. Privadesa des del primer moment, en 25 idiomes, i l'organitzes com vulguis. + + + Hellion Chat va començar com un fork de Chat 2. Des d'aleshores tots dos s'han allunyat prou perquè les bases de codi ja no siguin compatibles. + + + Els plugins són una zona grisa a Final Fantasy XIV: les condicions d'ús de Square Enix no els cobreixen, i Naoki Yoshida ha demanat públicament que no se'n faci publicitat. Mantén, doncs, el tema fora de Say, Yell, Shout i de qualsevol altre canal públic. Tres passos ràpids. Podràs canviar-ho tot més tard a Configuració → Hellion Chat. @@ -228,8 +234,8 @@ Què es guarda? - - ★ = recomanat per a la majoria de jugadors. + + Recomanat Roleplay diff --git a/HellionChat/Resources/HellionStrings.cs.resx b/HellionChat/Resources/HellionStrings.cs.resx index 048653e..42b4770 100644 --- a/HellionChat/Resources/HellionStrings.cs.resx +++ b/HellionChat/Resources/HellionStrings.cs.resx @@ -172,7 +172,7 @@ Vyber si výchozí profil. Vše můžeš kdykoli později upravit v Nastavení → Soukromí. - Minimalizace dat (doporučeno) + Minimalizace dat Ukládají se pouze tvoje vlastní konverzace: telly, party, FC, linkshelly, cross-world linkshelly, aliance a ExtraChat. Veřejný chat, dialogy NPC a systémový spam se na úrovni úložiště zahodí. Uchovávání dle výchozích hodnot specifikace (telly 365 dní, vlastní konverzační kanály 90 dní). @@ -214,7 +214,13 @@ Vítej v Hellion Chat - Fork Chat 2 od Hellion Forge s výchozím nastavením ohleduplným ke soukromí, vizuálem odpovídajícím značce a pár vylepšeními kvality života. + Tvoje okno chatu od Hellion Forge. Soukromí od prvního spuštění, 25 jazyků a rozvržení si nastavíš sám. + + + Hellion Chat vznikl jako fork Chat 2. Od té doby se oba projekty rozešly natolik, že jejich kódové základny už nejsou kompatibilní. + + + Pluginy jsou ve Final Fantasy XIV šedá zóna: podmínky užívání Square Enix je nepokrývají a Naoki Yoshida veřejně požádal, aby se nepropagovaly. Nezmiňuj proto toto téma na Say, Yell, Shout ani na žádném jiném veřejném kanálu. Tři krátké kroky. Vše můžeš kdykoli změnit v Nastavení → Hellion Chat. @@ -228,8 +234,8 @@ Co se bude ukládat? - - ★ = doporučeno pro většinu hráčů. + + Doporučeno Roleplay diff --git a/HellionChat/Resources/HellionStrings.da.resx b/HellionChat/Resources/HellionStrings.da.resx index 2c9743c..302e2f5 100644 --- a/HellionChat/Resources/HellionStrings.da.resx +++ b/HellionChat/Resources/HellionStrings.da.resx @@ -172,7 +172,7 @@ Vælg en startprofil. Du kan justere alt efterfølgende under Indstillinger → Privatliv. - Dataminimering (anbefalet) + Dataminimering Kun dine egne samtaler gemmes: tells, gruppe, FC, linkshells, cross-world linkshells, alliance og ExtraChat. Offentlig chat, NPC-dialoger og systemstøj kasseres på lagerniveau. Opbevaring følger spec-standarder (tells 365 dage, egne samtalekanaler 90 dage). @@ -214,7 +214,13 @@ Velkommen til Hellion Chat - En Chat 2 fork fra Hellion Forge med privatlivsbevidste standarder, brandkonsistent udseende og et par praktiske forbedringer. + Dit chatvindue fra Hellion Forge. Privatliv fra første start, 25 sprog, og du sætter det op, som du vil. + + + Hellion Chat startede som en fork af Chat 2. De to har siden fjernet sig så meget fra hinanden, at kodebaserne ikke længere er kompatible. + + + Plugins er en gråzone i Final Fantasy XIV: Square Enix' brugsvilkår dækker dem ikke, og Naoki Yoshida har offentligt bedt om, at man ikke reklamerer for dem. Hold derfor emnet ude af Say, Yell, Shout og alle andre offentlige kanaler. Tre korte trin. Du kan ændre alt efterfølgende under Indstillinger → Hellion Chat. @@ -228,8 +234,8 @@ Hvad gemmes? - - ★ = anbefalet til de fleste spillere. + + Anbefalet Roleplay diff --git a/HellionChat/Resources/HellionStrings.de.resx b/HellionChat/Resources/HellionStrings.de.resx index 0ebcdee..b67cad4 100644 --- a/HellionChat/Resources/HellionStrings.de.resx +++ b/HellionChat/Resources/HellionStrings.de.resx @@ -172,7 +172,7 @@ Wähle ein Start-Profil. Du kannst später alles unter Einstellungen → Datenschutz anpassen. - Datensparsamkeit (empfohlen) + Datensparsamkeit Es werden nur deine eigenen Konversationen gespeichert: Flüsternachrichten, Gruppe, FC, Linkshells, Cross-World-Linkshells, Allianz und ExtraChat. Öffentlicher Chat, NPC-Dialoge und System-Spam werden auf der Storage-Ebene verworfen. Aufbewahrung nach Spec-Defaults (Flüsternachrichten 365 Tage, eigene Konversations-Kanäle 90 Tage). @@ -214,7 +214,13 @@ Willkommen bei Hellion Chat - Ein Chat 2 Fork von Hellion Forge mit DSGVO-konformen Defaults, brand-konsistentem Look und Quality-of-Life-Verbesserungen. + Dein Chat-Fenster von Hellion Forge. Datensparsam ab Werk, in 25 Sprachen, und du richtest es dir ein wie du willst. + + + Plugins sind in Final Fantasy XIV eine Grauzone: Die Nutzungsbedingungen von Square Enix decken sie nicht ab, und Naoki Yoshida hat öffentlich darum gebeten, nicht damit zu werben. Halte das Thema deshalb aus Sagen, Rufen, Schreien und allen anderen öffentlichen Kanälen heraus. + + + Hellion Chat ging ursprünglich aus Chat 2 hervor. Beide haben sich seitdem so weit auseinanderentwickelt, dass die Codebasen nicht mehr kompatibel sind. 3 kurze Schritte. Du kannst alles später unter Einstellungen → Hellion Chat ändern. @@ -228,8 +234,8 @@ Was darf gespeichert werden? - - ★ = empfohlen für die meisten Spieler. + + Empfohlen Roleplay diff --git a/HellionChat/Resources/HellionStrings.el.resx b/HellionChat/Resources/HellionStrings.el.resx index 33afe56..909ae82 100644 --- a/HellionChat/Resources/HellionStrings.el.resx +++ b/HellionChat/Resources/HellionStrings.el.resx @@ -172,7 +172,7 @@ Επίλεξε ένα αρχικό προφίλ. Μπορείς να ρυθμίσεις τα πάντα αργότερα στις Ρυθμίσεις → Απόρρητο. - Ελαχιστοποίηση δεδομένων (συνιστάται) + Ελαχιστοποίηση δεδομένων Αποθηκεύονται μόνο οι δικές σου συνομιλίες: tells, party, FC, linkshells, cross-world linkshells, alliance και ExtraChat. Το δημόσιο chat, οι διάλογοι NPC και το system spam απορρίπτονται σε επίπεδο αποθήκευσης. Η διατήρηση ακολουθεί τις προεπιλογές spec (tells 365 ημέρες, κανάλια ιδιωτικών συνομιλιών 90 ημέρες). @@ -214,7 +214,13 @@ Καλωσόρισες στο Hellion Chat - Ένα fork του Chat 2 από το Hellion Forge με προεπιλογές φιλικές στο απόρρητο, συνεπή εμφάνιση brand και μερικές βελτιώσεις ευχρηστίας. + Το παράθυρο συνομιλίας σου από το Hellion Forge. Ιδιωτικότητα από την αρχή, σε 25 γλώσσες, και το στήνεις όπως θέλεις. + + + Το Hellion Chat ξεκίνησε ως fork του Chat 2. Έκτοτε τα δύο έχουν απομακρυνθεί τόσο ώστε οι κώδικές τους να μην είναι πλέον συμβατοί. + + + Τα plugin αποτελούν γκρίζα ζώνη στο Final Fantasy XIV: οι όροι χρήσης της Square Enix δεν τα καλύπτουν και ο Naoki Yoshida έχει ζητήσει δημόσια να μην διαφημίζονται. Κράτα λοιπόν το θέμα μακριά από τα Say, Yell, Shout και κάθε άλλο δημόσιο κανάλι. Τρία σύντομα βήματα. Μπορείς να αλλάξεις τα πάντα αργότερα στις Ρυθμίσεις → Hellion Chat. @@ -228,8 +234,8 @@ Τι αποθηκεύεται; - - ★ = συνιστάται για τους περισσότερους παίκτες. + + Συνιστάται Roleplay diff --git a/HellionChat/Resources/HellionStrings.es.resx b/HellionChat/Resources/HellionStrings.es.resx index 81d99d8..7dfd531 100644 --- a/HellionChat/Resources/HellionStrings.es.resx +++ b/HellionChat/Resources/HellionStrings.es.resx @@ -172,7 +172,7 @@ Elige un perfil inicial. Puedes ajustarlo todo más tarde en Ajustes → Privacidad. - Minimización de datos (recomendado) + Minimización de datos Solo se almacenan tus propias conversaciones: tells, escuadrón, FC, linkshells, linkshells cross-world, alianza y ExtraChat. El chat público, los diálogos de PNJ y el spam del sistema se descartan a nivel de almacenamiento. La retención sigue los valores predeterminados de spec (tells 365 días, canales de conversación propios 90 días). @@ -214,7 +214,13 @@ Bienvenido a Hellion Chat - Un fork de Chat 2 de Hellion Forge con valores predeterminados respetuosos con la privacidad, diseño coherente con la marca y algunas mejoras de calidad de vida. + Tu ventana de chat, de Hellion Forge. Privacidad desde el primer momento, traducida a 25 idiomas, y la organizas a tu gusto. + + + Hellion Chat nació como un fork de Chat 2. Desde entonces ambos se han separado lo suficiente como para que sus bases de código ya no sean compatibles. + + + Los plugins son una zona gris en Final Fantasy XIV: las condiciones de uso de Square Enix no los contemplan, y Naoki Yoshida ha pedido públicamente que no se promocionen. Mantén el tema fuera de Decir, Gritar, Vociferar y de cualquier otro canal público. Tres pasos breves. Puedes cambiar todo más tarde en Ajustes → Hellion Chat. @@ -228,8 +234,8 @@ ¿Qué se almacena? - - ★ = recomendado para la mayoría de jugadores. + + Recomendado Roleplay diff --git a/HellionChat/Resources/HellionStrings.fi.resx b/HellionChat/Resources/HellionStrings.fi.resx index e9641cf..129fe69 100644 --- a/HellionChat/Resources/HellionStrings.fi.resx +++ b/HellionChat/Resources/HellionStrings.fi.resx @@ -172,7 +172,7 @@ Valitse aloitusprofiili. Voit muuttaa kaikkea myöhemmin kohdassa Asetukset → Tietosuoja. - Tietojen minimointi (suositeltu) + Tietojen minimointi Vain omat keskustelusi tallennetaan: tellit, ryhmä, FC, linkshells, cross-world linkshells, allianssi ja ExtraChat. Julkinen chat, NPC-dialogit ja järjestelmäroskaviestit hylätään tallennusvaiheessa. Säilytys noudattaa spec-oletuksia (tellit 365 päivää, omat keskustelukanavat 90 päivää). @@ -214,7 +214,13 @@ Tervetuloa Hellion Chatiin - Chat 2 -haarautuma Hellion Forgelta, tietosuojatietoisilla oletuksilla, yhtenäisellä visuaalisella ilmeellä ja muutamilla käytännöllisillä parannuksilla. + Chat-ikkunasi Hellion Forgelta. Yksityisyys heti alusta, 25 kieltä, ja järjestät sen kuten haluat. + + + Hellion Chat sai alkunsa Chat 2:n forkkina. Sittemmin ne ovat eronneet toisistaan niin paljon, etteivät koodipohjat ole enää yhteensopivia. + + + Pluginit ovat Final Fantasy XIV:ssä harmaata aluetta: Square Enixin käyttöehdot eivät kata niitä, ja Naoki Yoshida on julkisesti pyytänyt, ettei niitä mainostettaisi. Pidä aihe siis poissa kanavilta Say, Yell, Shout ja kaikilta muilta julkisilta kanavilta. Kolme lyhyttä vaihetta. Voit muuttaa kaikkea myöhemmin kohdassa Asetukset → Hellion Chat. @@ -228,8 +234,8 @@ Mitä tallennetaan? - - ★ = suositeltu useimmille pelaajille. + + Suositeltu Roleplay diff --git a/HellionChat/Resources/HellionStrings.fr.resx b/HellionChat/Resources/HellionStrings.fr.resx index a1baa48..dee3c6f 100644 --- a/HellionChat/Resources/HellionStrings.fr.resx +++ b/HellionChat/Resources/HellionStrings.fr.resx @@ -172,7 +172,7 @@ Choisissez un profil de départ. Vous pouvez tout ajuster par la suite dans Paramètres → Confidentialité. - Minimisation des données (recommandé) + Minimisation des données Seules vos propres conversations sont enregistrées : messages privés, équipe, CL, linkshells, linkshells inter-mondes, alliance et ExtraChat. Le chat public, les dialogues PNJ et le spam système sont écartés au niveau du stockage. La conservation suit les valeurs par défaut de la spécification (messages privés 365 jours, vos canaux de conversation 90 jours). @@ -214,7 +214,13 @@ Bienvenue dans Hellion Chat - Un fork de Chat 2 par Hellion Forge avec des valeurs par défaut axées sur la confidentialité, une identité visuelle cohérente et quelques améliorations de confort. + Ta fenêtre de discussion, signée Hellion Forge. Confidentialité par défaut, traduite en 25 langues, et à agencer comme tu veux. + + + Hellion Chat est né d'un fork de Chat 2. Les deux se sont depuis suffisamment éloignés pour que les bases de code ne soient plus compatibles. + + + Les plugins sont une zone grise dans Final Fantasy XIV : les conditions d'utilisation de Square Enix ne les couvrent pas, et Naoki Yoshida a publiquement demandé de ne pas en faire la promotion. Évite donc le sujet dans Dire, Crier, Hurler et tout autre canal public. Trois étapes courtes. Vous pouvez tout modifier plus tard dans Paramètres → Hellion Chat. @@ -228,8 +234,8 @@ Qu'est-ce qui est enregistré ? - - ★ = recommandé pour la plupart des joueurs. + + Recommandé Roleplay diff --git a/HellionChat/Resources/HellionStrings.hu.resx b/HellionChat/Resources/HellionStrings.hu.resx index f00d6b3..c81945b 100644 --- a/HellionChat/Resources/HellionStrings.hu.resx +++ b/HellionChat/Resources/HellionStrings.hu.resx @@ -172,7 +172,7 @@ Válassz egy kezdő profilt. Mindent később is módosíthatsz a Beállítások → Adatvédelem menüpontban. - Adatminimalizálás (ajánlott) + Adatminimalizálás Csak a saját beszélgetéseid tárolódnak: tellek, party, FC, linkshellек, cross-world linkshellек, szövetség és ExtraChat. A nyilvános chat, az NPC-párbeszédek és a rendszerüzenetek a tárolás szintjén eldobódnak. A megőrzés a spec-alapértelmezettet követi (tellek 365 nap, saját csatornák 90 nap). @@ -214,7 +214,13 @@ Üdvözöl a Hellion Chat - Egy Chat 2 fork a Hellion Forge-tól, adatvédelmet szem előtt tartó alapértelmezésekkel, egységes arculattal és néhány kényelmi funkcióval. + A te chatablakod a Hellion Forge-tól. Adatvédelem az első indítástól, 25 nyelven, és úgy rendezed be, ahogy szeretnéd. + + + A Hellion Chat a Chat 2 forkjaként indult. A kettő azóta annyira eltávolodott egymástól, hogy a kódbázisok már nem kompatibilisek. + + + A pluginek a Final Fantasy XIV-ben szürke zónát jelentenek: a Square Enix felhasználási feltételei nem terjednek ki rájuk, és Naoki Yoshida nyilvánosan kérte, hogy ne reklámozzák őket. Ne hozd tehát szóba a témát a Say, Yell, Shout és bármely más nyilvános csatornán. Három rövid lépés. Mindent megváltoztathatsz később a Beállítások → Hellion Chat menüpontban. @@ -228,8 +234,8 @@ Mi tárolódjon? - - ★ = a legtöbb játékosnak ajánlott. + + Ajánlott Roleplay diff --git a/HellionChat/Resources/HellionStrings.it.resx b/HellionChat/Resources/HellionStrings.it.resx index ef3f54e..3812831 100644 --- a/HellionChat/Resources/HellionStrings.it.resx +++ b/HellionChat/Resources/HellionStrings.it.resx @@ -172,7 +172,7 @@ Scegli un profilo di partenza. Puoi regolare tutto in seguito in Impostazioni → Privacy. - Minimizzazione dei dati (consigliata) + Minimizzazione dei dati Vengono salvate solo le tue conversazioni: tell, party, FC, linkshell, cross-world linkshell, alliance ed ExtraChat. La chat pubblica, i dialoghi NPC e lo spam di sistema vengono scartati a livello di archiviazione. La conservazione segue i valori predefiniti dello spec (tell 365 giorni, canali di conversazione propri 90 giorni). @@ -214,7 +214,13 @@ Benvenuto in Hellion Chat - Un fork di Chat 2 da Hellion Forge con impostazioni predefinite attente alla privacy, un'estetica coerente con il brand e qualche miglioramento alla qualità della vita. + La tua finestra di chat, firmata Hellion Forge. Privacy fin da subito, tradotta in 25 lingue, e la sistemi come vuoi. + + + Hellion Chat è nato come fork di Chat 2. Da allora i due si sono allontanati al punto che le basi di codice non sono più compatibili. + + + I plugin sono una zona grigia in Final Fantasy XIV: le condizioni d'uso di Square Enix non li contemplano, e Naoki Yoshida ha chiesto pubblicamente di non pubblicizzarli. Tieni quindi l'argomento fuori da Say, Yell, Shout e da ogni altro canale pubblico. Tre brevi passaggi. Puoi cambiare tutto in seguito in Impostazioni → Hellion Chat. @@ -228,8 +234,8 @@ Cosa viene salvato? - - ★ = consigliato per la maggior parte dei giocatori. + + Consigliato Roleplay diff --git a/HellionChat/Resources/HellionStrings.ja.resx b/HellionChat/Resources/HellionStrings.ja.resx index 9d700ef..8ba0665 100644 --- a/HellionChat/Resources/HellionStrings.ja.resx +++ b/HellionChat/Resources/HellionStrings.ja.resx @@ -172,7 +172,7 @@ 開始プロファイルを選択してください。後で設定 → プライバシーからいつでも変更できます。 - データ最小化(推奨) + データ最小化 自分の会話のみ保存されます: テル、パーティ、フリーカンパニー、リンクシェル、クロスワールドリンクシェル、アライアンス、ExtraChat。パブリックチャット、NPCの台詞、システムスパムはストレージレベルで破棄されます。保持期間は仕様デフォルト(テル 365日、自分の会話チャンネル 90日)に従います。 @@ -214,7 +214,13 @@ Hellion Chat へようこそ - Hellion Forge による Chat 2 フォーク。プライバシーに配慮したデフォルト設定、ブランド一貫のビジュアル、そしていくつかの利便性向上機能を備えています。 + Hellion Forge がお届けするチャットウィンドウ。初期設定からプライバシー重視、25言語対応、レイアウトは自由に組み替えられます。 + + + Hellion Chat は Chat 2 のフォークとして始まりました。その後、両者は大きく分かれ、コードベースはすでに互換性がありません。 + + + ファイナルファンタジーXIVにおいてプラグインはグレーゾーンです。スクウェア・エニックスの利用規約は対象としておらず、吉田直樹氏も公の場で宣伝しないよう求めています。Say、Yell、Shout をはじめとする公開チャンネルでは話題にしないでください。 3つの短いステップです。後で設定 → Hellion Chat からすべて変更できます。 @@ -228,8 +234,8 @@ 何が保存されますか? - - ★ = ほとんどのプレイヤーに推奨。 + + 推奨 ロールプレイ diff --git a/HellionChat/Resources/HellionStrings.ko.resx b/HellionChat/Resources/HellionStrings.ko.resx index eb431f0..7d0f10f 100644 --- a/HellionChat/Resources/HellionStrings.ko.resx +++ b/HellionChat/Resources/HellionStrings.ko.resx @@ -172,7 +172,7 @@ 시작 프로필을 선택하세요. 나중에 설정 → 개인정보에서 모두 조정할 수 있습니다. - 데이터 최소화 (권장) + 데이터 최소화 자신의 대화만 저장됩니다. 귓속말, 파티, 자유부대, 링크셸, 서버 초월 링크셸, 연합 파티, ExtraChat이 포함됩니다. 공개 채팅, NPC 대화, 시스템 스팸은 저장 단계에서 제외됩니다. 보존 기간은 기본 스펙을 따릅니다 (귓속말 365일, 개인 대화 채널 90일). @@ -214,7 +214,13 @@ Hellion Chat에 오신 것을 환영합니다 - Hellion Forge에서 만든 Chat 2 포크입니다. 개인정보 보호 기본값, 브랜드 일관성 있는 디자인, 그리고 몇 가지 편의 기능을 제공합니다. + Hellion Forge가 만든 채팅 창입니다. 기본값부터 개인정보 우선, 25개 언어 지원, 배치는 원하는 대로. + + + Hellion Chat은 Chat 2의 포크로 시작했습니다. 그 뒤로 둘은 코드베이스가 더 이상 호환되지 않을 만큼 멀어졌습니다. + + + 파이널 판타지 XIV에서 플러그인은 회색 지대입니다. 스퀘어 에닉스 이용약관은 이를 다루지 않으며, 요시다 나오키는 공개적으로 홍보하지 말아 달라고 요청했습니다. 말하기, 떠들기, 외치기를 비롯한 모든 공개 채널에서는 이 주제를 꺼내지 마세요. 세 가지 간단한 단계입니다. 나중에 설정 → Hellion Chat에서 모두 변경할 수 있습니다. @@ -228,8 +234,8 @@ 무엇을 저장할까요? - - ★ = 대부분의 플레이어에게 권장됩니다. + + 권장 롤플레이 diff --git a/HellionChat/Resources/HellionStrings.nb.resx b/HellionChat/Resources/HellionStrings.nb.resx index 0c5073e..15f8ae6 100644 --- a/HellionChat/Resources/HellionStrings.nb.resx +++ b/HellionChat/Resources/HellionStrings.nb.resx @@ -172,7 +172,7 @@ Velg en startprofil. Du kan justere alt senere under Innstillinger → Personvern. - Dataminimering (anbefalt) + Dataminimering Bare dine egne samtaler lagres: tells, party, FC, linkshells, cross-world linkshells, alliance og ExtraChat. Offentlig chat, NPC-dialoger og systemspam forkastes på lagringsnivå. Oppbevaring følger spec-standarder (tells 365 dager, egne samtalekanaler 90 dager). @@ -214,7 +214,13 @@ Velkommen til Hellion Chat - En Chat 2-fork fra Hellion Forge med personvernvennlige standarder, merkevarekonsekvente visuals og noen livskvalitetsforbedringer. + Chatvinduet ditt fra Hellion Forge. Personvern fra første start, 25 språk, og du setter det opp slik du vil. + + + Hellion Chat startet som en fork av Chat 2. De to har siden fjernet seg så mye fra hverandre at kodebasene ikke lenger er kompatible. + + + Plugins er en gråsone i Final Fantasy XIV: Square Enix' bruksvilkår dekker dem ikke, og Naoki Yoshida har offentlig bedt om at man ikke reklamerer for dem. Hold derfor temaet unna Say, Yell, Shout og alle andre offentlige kanaler. Tre korte steg. Du kan endre alt senere under Innstillinger → Hellion Chat. @@ -228,8 +234,8 @@ Hva blir lagret? - - ★ = anbefalt for de fleste spillere. + + Anbefalt Roleplay diff --git a/HellionChat/Resources/HellionStrings.nl.resx b/HellionChat/Resources/HellionStrings.nl.resx index a44f9b3..891720e 100644 --- a/HellionChat/Resources/HellionStrings.nl.resx +++ b/HellionChat/Resources/HellionStrings.nl.resx @@ -172,7 +172,7 @@ Kies een startprofiel. Je kunt later alles aanpassen via Instellingen → Privacy. - Dataminimalisatie (aanbevolen) + Dataminimalisatie Alleen je eigen gesprekken worden opgeslagen: tells, groep, FC, linkshells, cross-world linkshells, alliantie en ExtraChat. Openbare chat, NPC-dialogen en systeemspam worden op opslagniveau verwijderd. Retentie volgt spec-standaarden (tells 365 dagen, eigen gesprekkanalen 90 dagen). @@ -214,7 +214,13 @@ Welkom bij Hellion Chat - Een Chat 2 fork van Hellion Forge met privacybewuste standaarden, merkconforme uitstraling en handige quality-of-life verbeteringen. + Jouw chatvenster van Hellion Forge. Privacy vanaf de eerste start, in 25 talen, en je richt het in zoals jij wilt. + + + Hellion Chat begon als een fork van Chat 2. De twee zijn sindsdien zo ver uit elkaar gegroeid dat de codebases niet meer compatibel zijn. + + + Plugins zijn in Final Fantasy XIV een grijs gebied: de gebruiksvoorwaarden van Square Enix dekken ze niet, en Naoki Yoshida heeft publiekelijk gevraagd er geen reclame voor te maken. Houd het onderwerp dus buiten Zeg, Roep, Schreeuw en elk ander openbaar kanaal. Drie korte stappen. Je kunt later alles aanpassen via Instellingen → Hellion Chat. @@ -228,8 +234,8 @@ Wat wordt er opgeslagen? - - ★ = aanbevolen voor de meeste spelers. + + Aanbevolen Roleplay diff --git a/HellionChat/Resources/HellionStrings.pl.resx b/HellionChat/Resources/HellionStrings.pl.resx index 567421d..7c4c06e 100644 --- a/HellionChat/Resources/HellionStrings.pl.resx +++ b/HellionChat/Resources/HellionStrings.pl.resx @@ -172,7 +172,7 @@ Wybierz profil startowy. Wszystko możesz zmienić później w Ustawieniach → Prywatność. - Minimalizacja danych (zalecane) + Minimalizacja danych Przechowywane są tylko twoje własne rozmowy: tells, grupa, FC, linkshells, cross-world linkshells, sojusz i ExtraChat. Czat publiczny, dialogi NPC i spam systemowy są odrzucane na poziomie zapisu. Czas przechowywania według domyślnych wartości specyfikacji (tells 365 dni, własne kanały rozmów 90 dni). @@ -214,7 +214,13 @@ Witaj w Hellion Chat - Fork Chat 2 od Hellion Forge z domyślnymi ustawieniami chroniącymi prywatność, spójną identyfikacją wizualną i drobnymi usprawnieniami komfortu gry. + Twoje okno czatu od Hellion Forge. Prywatność od pierwszego uruchomienia, 25 języków, a układ ustawiasz sam. + + + Hellion Chat powstał jako fork Chat 2. Od tamtej pory oba projekty rozeszły się na tyle, że ich bazy kodu nie są już zgodne. + + + Pluginy to w Final Fantasy XIV szara strefa: warunki korzystania Square Enix ich nie obejmują, a Naoki Yoshida publicznie prosił, by ich nie reklamować. Nie poruszaj więc tego tematu na Say, Yell, Shout ani na żadnym innym kanale publicznym. Trzy krótkie kroki. Wszystko możesz zmienić później w Ustawieniach → Hellion Chat. @@ -228,8 +234,8 @@ Co ma być zapisywane? - - ★ = zalecane dla większości graczy. + + Zalecane Roleplay diff --git a/HellionChat/Resources/HellionStrings.pt-BR.resx b/HellionChat/Resources/HellionStrings.pt-BR.resx index fe29447..206b4a7 100644 --- a/HellionChat/Resources/HellionStrings.pt-BR.resx +++ b/HellionChat/Resources/HellionStrings.pt-BR.resx @@ -172,7 +172,7 @@ Escolha um perfil inicial. Você pode ajustar tudo depois em Configurações → Privacidade. - Minimização de dados (recomendado) + Minimização de dados Apenas suas próprias conversas são armazenadas: tells, grupo, FC, linkshells, cross-world linkshells, aliança e ExtraChat. Bate-papo público, diálogos de NPC e spam de sistema são descartados no nível de armazenamento. A retenção segue os padrões da spec (tells 365 dias, canais de conversa próprios 90 dias). @@ -214,7 +214,13 @@ Bem-vindo ao Hellion Chat - Um fork do Chat 2 pela Hellion Forge com padrões voltados para privacidade, visual consistente com a marca e alguns toques de qualidade de vida. + Sua janela de chat, da Hellion Forge. Privacidade desde o primeiro uso, 25 idiomas, e você organiza do seu jeito. + + + O Hellion Chat começou como um fork do Chat 2. Desde então os dois se afastaram a ponto de as bases de código não serem mais compatíveis. + + + Plugins são uma zona cinzenta no Final Fantasy XIV: os termos de uso da Square Enix não os cobrem, e Naoki Yoshida pediu publicamente que não sejam divulgados. Mantenha o assunto fora de Falar, Grita, Berrar e de qualquer outro canal público. Três passos rápidos. Você pode mudar tudo depois em Configurações → Hellion Chat. @@ -228,8 +234,8 @@ O que será armazenado? - - ★ = recomendado para a maioria dos jogadores. + + Recomendado Roleplay diff --git a/HellionChat/Resources/HellionStrings.pt-PT.resx b/HellionChat/Resources/HellionStrings.pt-PT.resx index 8a0b209..b8deb8b 100644 --- a/HellionChat/Resources/HellionStrings.pt-PT.resx +++ b/HellionChat/Resources/HellionStrings.pt-PT.resx @@ -172,7 +172,7 @@ Escolhe um perfil inicial. Podes ajustar tudo depois em Definições → Privacidade. - Minimização de dados (recomendado) + Minimização de dados Só as tuas próprias conversas são armazenadas: tells, grupo, FC, linkshells, cross-world linkshells, aliança e ExtraChat. O chat público, os diálogos de NPC e o spam de sistema são descartados ao nível do armazenamento. A retenção segue os valores predefinidos da spec (tells 365 dias, canais de conversas próprias 90 dias). @@ -214,7 +214,13 @@ Bem-vindo ao Hellion Chat - Um fork do Chat 2 da Hellion Forge com predefinições com privacidade em mente, visuais consistentes com a marca e alguns retoques de qualidade de vida. + A tua janela de conversa, da Hellion Forge. Privacidade desde o primeiro arranque, 25 idiomas, e organizas tudo como quiseres. + + + O Hellion Chat começou como um fork do Chat 2. Desde então os dois afastaram-se ao ponto de as bases de código já não serem compatíveis. + + + Os plugins são uma zona cinzenta no Final Fantasy XIV: os termos de utilização da Square Enix não os abrangem e Naoki Yoshida pediu publicamente que não fossem promovidos. Mantém, portanto, o assunto fora de Say, Yell, Shout e de qualquer outro canal público. Três passos rápidos. Podes mudar tudo depois em Definições → Hellion Chat. @@ -228,8 +234,8 @@ O que fica armazenado? - - ★ = recomendado para a maioria dos jogadores. + + Recomendado Roleplay diff --git a/HellionChat/Resources/HellionStrings.resx b/HellionChat/Resources/HellionStrings.resx index 4d12f3d..ece8e5f 100644 --- a/HellionChat/Resources/HellionStrings.resx +++ b/HellionChat/Resources/HellionStrings.resx @@ -172,7 +172,7 @@ Choose a starting profile. You can adjust everything later under Settings → Privacy. - Data minimisation (recommended) + Data minimisation Only your own conversations are stored: tells, party, FC, linkshells, cross-world linkshells, alliance, and ExtraChat. Public chat, NPC dialogues, and system spam are discarded at the storage level. Retention follows spec defaults (tells 365 days, own conversation channels 90 days). @@ -214,7 +214,13 @@ Welcome to Hellion Chat - A Chat 2 fork from Hellion Forge with privacy-aware defaults, brand-consistent visuals, and a few quality-of-life touches. + Your chat window, from Hellion Forge. Privacy-first out of the box, translated into 25 languages, and yours to arrange. + + + Plugins are a grey area in Final Fantasy XIV: Square Enix's terms of service do not cover them, and Naoki Yoshida has publicly asked that people not advertise them. Keep the subject out of Say, Yell, Shout and every other public channel. + + + Hellion Chat started out as a fork of Chat 2. The two have drifted far enough apart since that the codebases are no longer compatible. Three short steps. You can change everything later under Settings → Hellion Chat. @@ -228,8 +234,8 @@ What gets stored? - - ★ = recommended for most players. + + Recommended Roleplay diff --git a/HellionChat/Resources/HellionStrings.ro.resx b/HellionChat/Resources/HellionStrings.ro.resx index 2e97185..b235dbc 100644 --- a/HellionChat/Resources/HellionStrings.ro.resx +++ b/HellionChat/Resources/HellionStrings.ro.resx @@ -172,7 +172,7 @@ Alege un profil de start. Poți ajusta orice mai târziu din Setări → Confidențialitate. - Minimizarea datelor (recomandat) + Minimizarea datelor Sunt stocate doar propriile tale conversații: tells, party, FC, linkshells, cross-world linkshells, alliance și ExtraChat. Chatul public, dialogurile NPC și spam-ul de sistem sunt respinse la nivelul stocării. Retenția urmează implicite spec (tells 365 zile, canale de conversație proprii 90 zile). @@ -214,7 +214,13 @@ Bun venit în Hellion Chat - Un fork Chat 2 de la Hellion Forge cu setări implicite orientate spre confidențialitate, aspect consecvent cu brandul și câteva îmbunătățiri de calitate a vieții. + Fereastra ta de chat, de la Hellion Forge. Confidențialitate din start, 25 de limbi, iar aranjarea îți aparține. + + + Hellion Chat a pornit ca un fork al Chat 2. De atunci cele două s-au îndepărtat suficient încât bazele de cod nu mai sunt compatibile. + + + Pluginurile sunt o zonă gri în Final Fantasy XIV: termenii de utilizare Square Enix nu le acoperă, iar Naoki Yoshida a cerut public să nu fie promovate. Ține deci subiectul departe de Say, Yell, Shout și de orice alt canal public. Trei pași scurți. Poți schimba orice mai târziu din Setări → Hellion Chat. @@ -228,8 +234,8 @@ Ce se stochează? - - ★ = recomandat pentru cei mai mulți jucători. + + Recomandat Roleplay diff --git a/HellionChat/Resources/HellionStrings.ru.resx b/HellionChat/Resources/HellionStrings.ru.resx index d3e84fd..8d0a780 100644 --- a/HellionChat/Resources/HellionStrings.ru.resx +++ b/HellionChat/Resources/HellionStrings.ru.resx @@ -172,7 +172,7 @@ Выберите начальный профиль. Всё можно изменить позже в разделе Настройки → Конфиденциальность. - Минимизация данных (рекомендуется) + Минимизация данных Сохраняются только ваши собственные разговоры: ЛС, группа, свободная компания, Linkshells, межмировые Linkshells, альянс и ExtraChat. Публичный чат, диалоги NPC и системный спам отбрасываются на уровне хранения. Сроки хранения соответствуют значениям по умолчанию (ЛС — 365 дней, собственные каналы разговора — 90 дней). @@ -214,7 +214,13 @@ Добро пожаловать в Hellion Chat - Форк Chat 2 от Hellion Forge с настройками конфиденциальности по умолчанию, фирменным оформлением и рядом улучшений удобства использования. + Твоё окно чата от Hellion Forge. Приватность по умолчанию, 25 языков, и раскладка полностью в твоих руках. + + + Hellion Chat начинался как форк Chat 2. С тех пор проекты разошлись настолько, что кодовые базы больше не совместимы. + + + Плагины в Final Fantasy XIV находятся в серой зоне: условия использования Square Enix их не охватывают, а Наоки Ёсида публично просил не рекламировать их. Не поднимай эту тему в каналах Сказать, Вопль, Крик и любых других публичных. Три коротких шага. Всё можно изменить позже в разделе Настройки → Hellion Chat. @@ -228,8 +234,8 @@ Что будет сохраняться? - - ★ = рекомендуется для большинства игроков. + + Рекомендовано Roleplay diff --git a/HellionChat/Resources/HellionStrings.sv.resx b/HellionChat/Resources/HellionStrings.sv.resx index 62059bc..7ff6f01 100644 --- a/HellionChat/Resources/HellionStrings.sv.resx +++ b/HellionChat/Resources/HellionStrings.sv.resx @@ -172,7 +172,7 @@ Välj en startprofil. Du kan justera allt senare under Inställningar → Sekretess. - Dataminimering (rekommenderas) + Dataminimering Bara dina egna konversationer sparas: tells, grupp, FC, linkshells, flervärlds-linkshells, allians och ExtraChat. Offentlig chatt, NPC-dialoger och systemskräp kasseras på lagringsnivå. Lagring följer spec-standarder (tells 365 dagar, egna konversationskanaler 90 dagar). @@ -214,7 +214,13 @@ Välkommen till Hellion Chat - En Chat 2-fork från Hellion Forge med sekretessmedvetna standardinställningar, konsekvent varumärkesutseende och några livskvalitetsförbättringar. + Ditt chattfönster från Hellion Forge. Integritet från första start, 25 språk, och du ställer in det som du vill. + + + Hellion Chat började som en fork av Chat 2. De två har sedan dess glidit isär så mycket att kodbaserna inte längre är kompatibla. + + + Plugins är en gråzon i Final Fantasy XIV: Square Enix användarvillkor täcker dem inte, och Naoki Yoshida har offentligt bett om att man inte gör reklam för dem. Håll därför ämnet borta från Säg, Skrik, Ropa och alla andra offentliga kanaler. Tre korta steg. Du kan ändra allt senare under Inställningar → Hellion Chat. @@ -228,8 +234,8 @@ Vad sparas? - - ★ = rekommenderas för de flesta spelare. + + Rekommenderad Roleplay diff --git a/HellionChat/Resources/HellionStrings.tr.resx b/HellionChat/Resources/HellionStrings.tr.resx index 5d8b520..4b57636 100644 --- a/HellionChat/Resources/HellionStrings.tr.resx +++ b/HellionChat/Resources/HellionStrings.tr.resx @@ -172,7 +172,7 @@ Bir başlangıç profili seç. Her şeyi daha sonra Ayarlar → Gizlilik altında değiştirebilirsin. - Veri minimizasyonu (önerilen) + Veri minimizasyonu Yalnızca kendi konuşmaların saklanır: tell'ler, parti, FC, linkshell'ler, cross-world linkshell'ler, alliance ve ExtraChat. Genel sohbet, NPC diyalogları ve sistem spam'i depolama düzeyinde atılır. Saklama süresi spec varsayılanlarına göre ayarlanır (tell'ler 365 gün, kendi konuşma kanalları 90 gün). @@ -214,7 +214,13 @@ Hellion Chat'e hoş geldin - Hellion Forge'dan gizlilik odaklı varsayılanlar, marka tutarlı görsel tasarım ve birkaç kullanım kolaylığı dokunuşuyla Chat 2'nin bir fork'u. + Hellion Forge'un sohbet penceresi. İlk açılıştan itibaren gizlilik öncelikli, 25 dilde, ve düzenini kendin kurarsın. + + + Hellion Chat, Chat 2'nin bir fork'u olarak başladı. İkisi o zamandan beri kod tabanları artık uyumlu olmayacak kadar birbirinden uzaklaştı. + + + Pluginler Final Fantasy XIV'te gri bir alandadır: Square Enix'in kullanım koşulları onları kapsamaz ve Naoki Yoshida bunların tanıtılmamasını açıkça rica etmiştir. Bu yüzden konuyu Say, Yell, Shout ve diğer tüm herkese açık kanalların dışında tut. Üç kısa adım. Her şeyi daha sonra Ayarlar → Hellion Chat altında değiştirebilirsin. @@ -228,8 +234,8 @@ Ne saklanacak? - - ★ = çoğu oyuncu için önerilen. + + Önerilen Roleplay diff --git a/HellionChat/Resources/HellionStrings.uk.resx b/HellionChat/Resources/HellionStrings.uk.resx index 4274ec3..4bc8d79 100644 --- a/HellionChat/Resources/HellionStrings.uk.resx +++ b/HellionChat/Resources/HellionStrings.uk.resx @@ -172,7 +172,7 @@ Виберіть початковий профіль. Все можна налаштувати пізніше в розділі Налаштування → Конфіденційність. - Мінімізація даних (рекомендовано) + Мінімізація даних Зберігаються лише Ваші власні розмови: tells, група, FC, linkshells, cross-world linkshells, альянс і ExtraChat. Публічний чат, діалоги NPC та системний спам відкидаються на рівні зберігання. Термін зберігання за стандартними значеннями специфікації (tells — 365 днів, канали власних розмов — 90 днів). @@ -214,7 +214,13 @@ Ласкаво просимо до Hellion Chat - Форк Chat 2 від Hellion Forge з урахуванням конфіденційності, фірмовим оформленням та кількома зручними покращеннями. + Твоє вікно чату від Hellion Forge. Приватність за замовчуванням, 25 мов, і розкладка цілком у твоїх руках. + + + Hellion Chat починався як форк Chat 2. Відтоді проєкти розійшлися настільки, що кодові бази більше не сумісні. + + + Плагіни у Final Fantasy XIV перебувають у сірій зоні: умови використання Square Enix їх не охоплюють, а Наокі Йосіда публічно просив не рекламувати їх. Не піднімай цю тему в каналах Say, Yell, Shout та будь-яких інших публічних. Три коротких кроки. Все можна змінити пізніше в розділі Налаштування → Hellion Chat. @@ -228,8 +234,8 @@ Що зберігається? - - ★ = рекомендовано для більшості гравців. + + Рекомендовано Roleplay diff --git a/HellionChat/Resources/HellionStrings.zh-Hans.resx b/HellionChat/Resources/HellionStrings.zh-Hans.resx index 5605975..ab42f5f 100644 --- a/HellionChat/Resources/HellionStrings.zh-Hans.resx +++ b/HellionChat/Resources/HellionStrings.zh-Hans.resx @@ -172,7 +172,7 @@ 选择一个初始配置方案。之后可在设置 → 隐私中随时调整。 - 数据最小化(推荐) + 数据最小化 仅保存你自己的对话:密语、小队、部队、通讯贝、跨服通讯贝、团队以及 ExtraChat。公共聊天、NPC 对话和系统垃圾信息将在存储层直接丢弃。保留期限遵循规格默认值(密语 365 天,自有对话频道 90 天)。 @@ -214,7 +214,13 @@ 欢迎使用 Hellion Chat - 来自 Hellion Forge 的 Chat 2 分支,具备隐私友好的默认设置、品牌一致的视觉风格,以及若干实用改进。 + 来自 Hellion Forge 的聊天窗口。默认即注重隐私,支持 25 种语言,界面由你自己安排。 + + + Hellion Chat 最初是 Chat 2 的分支。此后两者已相去甚远,代码库不再兼容。 + + + 插件在《最终幻想14》中处于灰色地带:史克威尔艾尼克斯的使用条款并未涵盖插件,吉田直树也曾公开呼吁不要宣传。请不要在说话、呼喊、喊话以及其他任何公开频道谈论此事。 共三个简短步骤。之后可在设置 → Hellion Chat 中随时修改。 @@ -228,8 +234,8 @@ 保存哪些内容? - - ★ = 推荐大多数玩家使用。 + + 推荐 Roleplay diff --git a/HellionChat/Resources/HellionStrings.zh-Hant.resx b/HellionChat/Resources/HellionStrings.zh-Hant.resx index 337e273..0e6d95e 100644 --- a/HellionChat/Resources/HellionStrings.zh-Hant.resx +++ b/HellionChat/Resources/HellionStrings.zh-Hant.resx @@ -172,7 +172,7 @@ 選擇一個起始設定檔。之後可在設定 → 隱私中調整所有選項。 - 資料最小化(推薦) + 資料最小化 只儲存你自己的對話:悄悄話、小隊、部隊、通訊貝、跨服通訊貝、團隊和 ExtraChat。公開聊天、NPC 對話和系統垃圾訊息在儲存層即被丟棄。保留期限遵循規格預設值(悄悄話 365 天,自己的對話頻道 90 天)。 @@ -214,7 +214,13 @@ 歡迎使用 Hellion Chat - Hellion Forge 推出的 Chat 2 分支版本,具備重視隱私的預設值、品牌一致的視覺設計以及一些生活品質改善。 + 來自 Hellion Forge 的聊天視窗。預設即重視隱私,支援 25 種語言,版面由你自己安排。 + + + Hellion Chat 最初是 Chat 2 的分支。此後兩者已相去甚遠,程式碼庫不再相容。 + + + 外掛在《Final Fantasy XIV》中屬於灰色地帶:史克威爾艾尼克斯的使用條款並未涵蓋,吉田直樹也曾公開呼籲不要宣傳。請勿在説話、呼喊、喊話以及其他任何公開頻道談論此事。 共三個簡短步驟。之後可在設定 → Hellion Chat 中變更所有設定。 @@ -228,8 +234,8 @@ 哪些內容會被儲存? - - ★ = 推薦給大多數玩家。 + + 推薦 角色扮演 diff --git a/HellionChat/Resources/Language.es.resx b/HellionChat/Resources/Language.es.resx index 2676c71..976ef85 100644 --- a/HellionChat/Resources/Language.es.resx +++ b/HellionChat/Resources/Language.es.resx @@ -731,7 +731,7 @@ Decir - Shout + Vociferar Tell (saliente) diff --git a/HellionChat/Ui/FirstRunWizard.cs b/HellionChat/Ui/FirstRunWizard.cs index 061e4ef..53c14b0 100644 --- a/HellionChat/Ui/FirstRunWizard.cs +++ b/HellionChat/Ui/FirstRunWizard.cs @@ -1,6 +1,7 @@ using System.Globalization; using System.Numerics; using Dalamud.Bindings.ImGui; +using Dalamud.Interface; using Dalamud.Interface.Utility; using Dalamud.Interface.Utility.Raii; using Dalamud.Interface.Windowing; @@ -34,13 +35,28 @@ public sealed class FirstRunWizard : Window private readonly StyleEngine.SurfaceBackdrop _backdrop; private readonly WizardState _state = new(); + // Its own instance rather than the settings window's: TokenResolver is a + // stateless lookup, so a second one costs a field, while a constructor + // parameter would touch PluginHostFactory for nothing. + private readonly Components.Settings.SettingsWidgets _widgets; + internal FirstRunWizard(Plugin plugin, StyleEngine.SurfaceBackdrop backdrop) : base($"{HellionStrings.Wizard_Title}###hellion-firstrun") { Plugin = plugin; _backdrop = backdrop; + _widgets = new Components.Settings.SettingsWidgets( + plugin, + new Components.Settings.SettingsPalette(new StyleEngine.TokenResolver()) + ); Flags = ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.NoDocking; + + // Opaque, unlike every other window in the plugin. Those are read at a + // glance over the game; this one is read once, carries a privacy + // decision, and the free company list showing through its cards is + // noise the reader has to work past. + BgAlpha = 1f; SizeCondition = ImGuiCond.Appearing; Size = new Vector2(720, 480); SizeConstraints = new WindowSizeConstraints @@ -50,6 +66,12 @@ public sealed class FirstRunWizard : Window }; } + // Until v1.15.0 the wizard could only ever open once per session, so its + // state living as long as the plugin never showed. /hellion wizard makes + // reopening possible, and a second run that started on step 4 with the + // first run's choices still pending would commit picks the user never saw. + public override void OnOpen() => _state.Reset(); + public override void OnClose() { // OnClose fires on explicit X-click and on plugin dispose. We never @@ -63,7 +85,7 @@ public sealed class FirstRunWizard : Window { // Same floor as the settings window, full strength: the wizard is read // in glances, not line by line, and it is the first impression. - _backdrop.Draw(accentWashHeight: 46f, moteIntensity: 1f, strength: 1f); + _backdrop.Draw(accentWashHeight: 46f, moteIntensity: 1f, strength: 1f, opacityOverride: 1f); DrawPagination(); ImGui.Spacing(); @@ -290,48 +312,19 @@ public sealed class FirstRunWizard : Window ImGui.TextUnformatted(HellionStrings.Wizard_Step1_Title); ImGui.Spacing(); - // Fox-banner image: the embedded Hellion Forge fox artwork. The card - // behind the image gives the dark fox enough contrast against the - // plugin's dark UI so the logo reads clearly at a glance. - var banner = FoxBannerTexture.Shared.GetWrapOrDefault(); - if (banner is not null) + // Same scrolling frame as the other steps: the plugin notice is not + // optional reading, so it may not be the thing that falls off the + // bottom edge at a larger font or display scale. + var footerReserve = ImGui.GetFrameHeightWithSpacing() + ImGui.GetStyle().ItemSpacing.Y * 2f; + var bodyHeight = MathF.Max(1f, ImGui.GetContentRegionAvail().Y - footerReserve); + + using (ImRaii.PushColor(ImGuiCol.ChildBg, 0u)) + using (var body = ImRaii.Child("##wizard-welcome", new Vector2(-1f, bodyHeight))) { - const uint CardColor = 0xFFE8E8E8; // off-white fill so the dark fox pops - var imgHeight = 170f * ImGuiHelpers.GlobalScale; - var imgWidth = imgHeight * banner.Size.X / banner.Size.Y; - var pad = 14f * ImGuiHelpers.GlobalScale; - var cardWidth = imgWidth + pad * 2f; - var cardHeight = imgHeight + pad * 2f; - var rounding = 8f * ImGuiHelpers.GlobalScale; - - // Centre the card in the content region. Clamp to zero so the card - // never shifts left of the window edge on very narrow windows. - var offsetX = Math.Max(0f, (ImGui.GetContentRegionAvail().X - cardWidth) * 0.5f); - var cardOrigin = ImGui.GetCursorScreenPos() + new Vector2(offsetX, 0f); - - // Draw the rounded card behind the image, then place the image on top. - ImGui - .GetWindowDrawList() - .AddRectFilled( - cardOrigin, - cardOrigin + new Vector2(cardWidth, cardHeight), - CardColor, - rounding - ); - ImGui.SetCursorScreenPos(cardOrigin + new Vector2(pad, pad)); - ImGui.Image(banner.Handle, new Vector2(imgWidth, imgHeight)); - - // Advance the layout cursor past the full card so the content below - // starts at the right position and does not overlap the card. - ImGui.SetCursorScreenPos(cardOrigin); - ImGui.Dummy(new Vector2(cardWidth, cardHeight)); + if (body.Success) + DrawWelcomeBody(); } - ImGui.Spacing(); - ImGui.TextWrapped(HellionStrings.Wizard_Step1_Subtitle); - ImGui.Spacing(); - ImGui.TextWrapped(HellionStrings.Wizard_Step1_Footer_Hint); - DrawFooter( showBack: false, showSkip: true, @@ -340,65 +333,248 @@ public sealed class FirstRunWizard : Window ); } + private void DrawWelcomeBody() + { + var scale = StyleEngine.Metrics.Scale; + var c = Plugin.ThemeRegistry.Active.Colors; + var dl = ImGui.GetWindowDrawList(); + var surface = ColourUtil.RgbaToAbgr(c.WindowBg); + + // Mark beside the text, not stacked over it. Centred above a paragraph + // it left a column of empty window on either side, and the page read as + // things that happened to share a step. This is the shape the profile + // cards and the boutique callout use: the mark on the left, what it is + // about on the right. + var diameter = MedallionDiameterRaw * scale; + var gap = 18f * scale; + var width = ImGui.GetContentRegionAvail().X; + var textWidth = MathF.Max(1f, width - diameter - gap); + + var subtitle = HellionStrings.Wizard_Step1_Subtitle; + var subtitleHeight = ImGui.CalcTextSize(subtitle, false, textWidth).Y; + + var rowTop = ImGui.GetCursorScreenPos(); + var rowHeight = MathF.Max(diameter, subtitleHeight); + + DrawFoxMedallion( + rowTop + new Vector2(0f, MetricsMath.CenterY(rowHeight, diameter)), + diameter + ); + + dl.AddText( + ImGui.GetFont(), + ImGui.GetFontSize(), + new Vector2( + rowTop.X + diameter + gap, + rowTop.Y + MetricsMath.CenterY(rowHeight, subtitleHeight) + ), + ColourUtil.EnsureContrast(ColourUtil.RgbaToAbgr(c.TextPrimary), surface, 4.5f), + subtitle, + textWidth + ); + + ImGui.Dummy(new Vector2(width, rowHeight)); + ImGui.Spacing(); + + DrawPluginNotice(width); + + ImGui.Spacing(); + + // Both in the muted tone: where the plugin came from and how long this + // takes are context, not instructions. + var faint = ColourUtil.EnsureContrast(ColourUtil.RgbaToAbgr(c.TextMuted), surface, 4.5f); + DrawFaintParagraph(HellionStrings.Wizard_Step1_Heritage, width, faint); + ImGui.Spacing(); + DrawFaintParagraph(HellionStrings.Wizard_Step1_Footer_Hint, width, faint); + } + + private static void DrawFaintParagraph(string text, float width, uint colourAbgr) + { + var origin = ImGui.GetCursorScreenPos(); + var height = ImGui.CalcTextSize(text, false, width).Y; + + ImGui + .GetWindowDrawList() + .AddText(ImGui.GetFont(), ImGui.GetFontSize(), origin, colourAbgr, text, width); + + ImGui.Dummy(new Vector2(width, height)); + } + + // Third-party plugins sit outside what Square Enix's terms cover, and the + // game's producer has asked publicly that people not advertise them. The + // wizard is the one moment every user passes through, so it is where the + // ask belongs -- on its own surface, because a line of muted text under a + // welcome message is a line nobody reads. + // + // The notice names Say, Yell and Shout, and each translation spells them the + // way Language..resx does -- baked in rather than formatted at runtime, + // because the list is punctuated differently per language. That makes it a + // second place to fix whenever a ChatType_Say/Yell/Shout value changes: + // Spanish drifted exactly that way, carrying an untranslated "Shout" into + // the notice on 2026-08-19. + private void DrawPluginNotice(float width) + { + var scale = StyleEngine.Metrics.Scale; + var c = Plugin.ThemeRegistry.Active.Colors; + var dl = ImGui.GetWindowDrawList(); + + var text = HellionStrings.Wizard_Step1_PluginNotice; + var padX = 12f * scale; + var padY = 10f * scale; + var barWidth = 2f * scale; + var inner = MathF.Max(1f, width - barWidth - padX * 2f); + + var origin = ImGui.GetCursorScreenPos(); + var height = MeasureHangingNotice(text, inner) + padY * 2f; + var max = origin + new Vector2(width, height); + + var surface = ColourUtil.RgbaToAbgr(c.Surface); + var warn = ColourUtil.EnsureContrast(ColourUtil.RgbaToAbgr(c.StatusWarning), surface, 4.5f); + + dl.AddRectFilled(origin, max, surface); + dl.AddRectFilled(origin, new Vector2(origin.X + barWidth, max.Y), warn); + + DrawHangingNotice( + new Vector2(origin.X + barWidth + padX, origin.Y + padY), + text, + inner, + warn + ); + + ImGui.Dummy(new Vector2(width, height)); + } + + private const float MedallionDiameterRaw = 128f; + + // Contrast the disc has to reach against black. Higher washes the accent + // out towards white, lower keeps more colour and starts swallowing the + // grey brushwork in the artwork. This is the dial for how tinted the + // medallion reads. + private const float MedallionInkContrast = 9f; + + // The fox is drawn in black ink and the plugin is dark, so the artwork needs + // something light behind it. It used to get a hardcoded off-white rectangle, + // which read as a sheet of paper taped onto the window. + // + // A disc instead, tinted from the theme accent rather than neutral grey: it + // is still bright enough to carry black linework, but it is the window's own + // colour doing it, and a ring closes it off as a mark instead of a patch. + // The artwork itself is untouched -- it is the brand. + private void DrawFoxMedallion(Vector2 origin, float diameter) + { + var banner = FoxBannerTexture.Shared.GetWrapOrDefault(); + if (banner is null) + return; + + var scale = StyleEngine.Metrics.Scale; + var c = Plugin.ThemeRegistry.Active.Colors; + var dl = ImGui.GetWindowDrawList(); + + var radius = diameter * 0.5f; + var centre = origin + new Vector2(radius, radius); + + var accent = ColourUtil.RgbaToAbgr(c.Accent); + + // Measured against the ink rather than set to a fixed lightness. The + // artwork is black brushwork, so the disc has to clear it -- but + // EnsureContrast stops at the first step that does, which leaves as much + // of the theme accent in the disc as the drawing can afford. A fixed + // near-white cleared it by miles and looked like paper on the window. + var disc = ColourUtil.EnsureContrast(accent, 0xFF000000u, MedallionInkContrast); + dl.AddCircleFilled(centre, radius, disc, 64); + dl.AddCircle(centre, radius, accent, 64, MathF.Max(1f, 1.5f * scale)); + + // Short of the ring, because the ears reach the edge of the source + // square and would otherwise touch it. + var art = diameter * 0.82f; + var artSize = new Vector2(art, art * banner.Size.Y / banner.Size.X); + var artMin = centre - artSize * 0.5f; + dl.AddImage(banner.Handle, artMin, artMin + artSize); + } + + // The grid order, top-left to bottom-right, with the glyph each profile + // carries. Static because the pairing never changes -- the labels do, so + // those are looked up per frame and a language switch reaches them. + // ToIconString allocates on every call and caches nothing, so the four + // glyphs are resolved once here rather than per card per frame. + private static readonly (PrivacyProfile Profile, string Glyph, string Id)[] ProfileOrder = + [ + (PrivacyProfile.PrivacyFirst, FontAwesomeIcon.Lock.ToIconString(), "##profile-card-first"), + (PrivacyProfile.Casual, FontAwesomeIcon.Comments.ToIconString(), "##profile-card-casual"), + (PrivacyProfile.Roleplay, FontAwesomeIcon.TheaterMasks.ToIconString(), "##profile-card-rp"), + ( + PrivacyProfile.FullHistory, + FontAwesomeIcon.BookOpen.ToIconString(), + "##profile-card-full" + ), + ]; + + private static readonly string WarningGlyph = + FontAwesomeIcon.ExclamationTriangle.ToIconString(); + + // One card wore the badge while a second said "(recommended)" in its own + // heading, in all 25 languages -- the four-step rewrite moved the flag and + // left the older text where it was. Casual is the entry point most players + // want, so the suffix came out of the heading instead. + private const PrivacyProfile RecommendedProfile = PrivacyProfile.Casual; + + private const float CardPadXRaw = 14f; + private const float CardPadYRaw = 12f; + private const float CardGapRaw = 9f; + private const float CardAccentBarRaw = 2f; + + private static (string Heading, string Description) TextFor(PrivacyProfile profile) => + profile switch + { + PrivacyProfile.PrivacyFirst => ( + HellionStrings.Wizard_Profile_PrivacyFirst_Heading, + HellionStrings.Wizard_Profile_PrivacyFirst_Description + ), + PrivacyProfile.Casual => ( + HellionStrings.Wizard_Profile_Casual_Heading, + HellionStrings.Wizard_Profile_Casual_Description + ), + PrivacyProfile.Roleplay => ( + HellionStrings.Wizard_Profile_Roleplay_Heading, + HellionStrings.Wizard_Profile_Roleplay_Description + ), + _ => ( + HellionStrings.Wizard_Profile_FullHistory_Heading, + HellionStrings.Wizard_Profile_FullHistory_Description + ), + }; + + // Written for this card, translated into 25 languages, and drawn nowhere + // since the four-step rewrite dropped it on 2026-05-18. Full history turns + // the privacy filter off and keeps third-party messages indefinitely, which + // is exactly the case the notice is about -- so it belongs on the card where + // that choice is made, not in a resource file with no caller. + private static string? WarningFor(PrivacyProfile profile) => + profile == PrivacyProfile.FullHistory + ? HellionStrings.Wizard_Profile_FullHistory_GdprWarning + : null; + private void DrawStepPrivacy() { ImGui.TextUnformatted(HellionStrings.Wizard_Step2_Title); ImGui.Spacing(); - // Reserve footer height (separator + spacing + button row) so the - // 2x2 grid uses the rest of the window. - var footerReserve = - ImGui.GetFrameHeightWithSpacing() - + ImGui.GetStyle().ItemSpacing.Y * 3 - + ImGui.GetTextLineHeight(); - var grid = ImGui.GetContentRegionAvail(); - var cardWidth = (grid.X - ImGui.GetStyle().ItemSpacing.X) / 2f; - var cardHeight = (grid.Y - footerReserve - ImGui.GetStyle().ItemSpacing.Y) / 2f; + // DrawFooter pushes itself to the bottom of whatever room is left, so + // the grid has to stop short of it or the two fight over the same space. + var footerReserve = ImGui.GetFrameHeightWithSpacing() + ImGui.GetStyle().ItemSpacing.Y * 2f; + var gridHeight = MathF.Max(1f, ImGui.GetContentRegionAvail().Y - footerReserve); - // Top row. - DrawProfileCard( - PrivacyProfile.PrivacyFirst, - "🔒", - HellionStrings.Wizard_Profile_PrivacyFirst_Heading, - HellionStrings.Wizard_Profile_PrivacyFirst_Description, - recommended: false, - cardWidth, - cardHeight - ); - ImGui.SameLine(); - DrawProfileCard( - PrivacyProfile.Casual, - "💬", - HellionStrings.Wizard_Profile_Casual_Heading, - HellionStrings.Wizard_Profile_Casual_Description, - recommended: true, - cardWidth, - cardHeight - ); - - // Bottom row. - DrawProfileCard( - PrivacyProfile.Roleplay, - "🎭", - HellionStrings.Wizard_Profile_Roleplay_Heading, - HellionStrings.Wizard_Profile_Roleplay_Description, - recommended: false, - cardWidth, - cardHeight - ); - ImGui.SameLine(); - DrawProfileCard( - PrivacyProfile.FullHistory, - "📚", - HellionStrings.Wizard_Profile_FullHistory_Heading, - HellionStrings.Wizard_Profile_FullHistory_Description, - recommended: false, - cardWidth, - cardHeight - ); - - ImGui.Spacing(); - ImGui.TextDisabled(HellionStrings.Wizard_Step2_RecommendedFooter); + // Transparent, because the cards own their surfaces and a child + // background would draw a second panel around the set. The scroll is + // the point: four descriptions at 25 languages' worth of length do not + // fit a fixed 2x2 grid, and cutting a privacy choice off mid-sentence + // is not a thing this plugin gets to do. + using (ImRaii.PushColor(ImGuiCol.ChildBg, 0u)) + using (var grid = ImRaii.Child("##wizard-profiles", new Vector2(-1f, gridHeight))) + { + if (grid.Success) + DrawProfileGrid(); + } DrawFooter( showBack: true, @@ -408,151 +584,296 @@ public sealed class FirstRunWizard : Window ); } + private void DrawProfileGrid() + { + var gap = ImGui.GetStyle().ItemSpacing.X; + var cardWidth = MathF.Max(1f, (ImGui.GetContentRegionAvail().X - gap) / 2f); + + for (var i = 0; i < ProfileOrder.Length; i += 2) + { + var hasRight = i + 1 < ProfileOrder.Length; + + // Both cards in a row take the taller one's height. Sized apart + // they read as two unrelated tiles instead of one choice, and which + // description is longest changes with the language. + var height = MathF.Max( + MeasureProfileCard(ProfileOrder[i].Profile, cardWidth), + hasRight ? MeasureProfileCard(ProfileOrder[i + 1].Profile, cardWidth) : 0f + ); + + DrawProfileCard(ProfileOrder[i], cardWidth, height); + if (!hasRight) + continue; + + ImGui.SameLine(); + DrawProfileCard(ProfileOrder[i + 1], cardWidth, height); + } + } + + private static float CardInnerWidth(float width) => + MathF.Max(1f, width - (CardAccentBarRaw + CardPadXRaw * 2f) * StyleEngine.Metrics.Scale); + + // Measured rather than assumed, for the same reason SettingRow measures its + // description: the text wraps, and a card sized to one line would clip the + // rest of a privacy explanation. + private float MeasureProfileCard(PrivacyProfile profile, float width) + { + var scale = StyleEngine.Metrics.Scale; + var (_, description) = TextFor(profile); + + var inner = CardInnerWidth(width); + var height = + CardPadYRaw * 2f * scale + + TitleRowHeight() + + CardGapRaw * 2f * scale + + ImGui.CalcTextSize(description, false, inner).Y; + + if (WarningFor(profile) is { } warning) + height += CardGapRaw * scale + MeasureHangingNotice(warning, inner); + + return height; + } + + // The badge is a pill with a 22px floor, so it is taller than a line of + // text. Centring it into one would hang it over both edges of the title + // row, so the row takes the taller of the two -- and every card uses the + // same height, or a row of two would not line up. + private float TitleRowHeight() => MathF.Max(ImGui.GetTextLineHeight(), BadgeSize().Y); + + private int _badgeSizeFrame = -1; + private Vector2 _badgeSize; + + private Vector2 BadgeSize() + { + if (_badgeSizeFrame == ImGui.GetFrameCount()) + return _badgeSize; + + _badgeSizeFrame = ImGui.GetFrameCount(); + _badgeSize = StyleEngine.Widgets.Pill.CalcSize( + HellionStrings.Wizard_Profile_Recommended_Badge, + withDot: false + ); + return _badgeSize; + } + + // The notice hangs off its own icon column, so it wraps narrower than the + // description above it. + private float WarningTextWidth(float innerWidth) => MathF.Max(1f, innerWidth - WarningIndent()); + + private float MeasureHangingNotice(string text, float width) => + ImGui.CalcTextSize(text, false, WarningTextWidth(width)).Y; + + // Symbol in its own column, text hanging beside it. Shared by the welcome + // step's plugin notice and the full-history card, so the two read as the + // same kind of statement rather than two unrelated warnings. + private void DrawHangingNotice(Vector2 origin, string text, float width, uint colourAbgr) + { + var dl = ImGui.GetWindowDrawList(); + + using (Plugin.FontManager.FontAwesome.Push()) + dl.AddText(origin, colourAbgr, WarningGlyph); + + dl.AddText( + ImGui.GetFont(), + ImGui.GetFontSize(), + origin + new Vector2(WarningIndent(), 0f), + colourAbgr, + text, + WarningTextWidth(width) + ); + } + + private int _warningIndentFrame = -1; + private float _warningIndent; + + // Cached per frame: a font push allocates a lock and queues a deferred + // dispose, and the card measurement runs twice per row before anything is + // drawn. The glyph is the same one every time. + private float WarningIndent() + { + if (_warningIndentFrame == ImGui.GetFrameCount()) + return _warningIndent; + + _warningIndentFrame = ImGui.GetFrameCount(); + using (Plugin.FontManager.FontAwesome.Push()) + _warningIndent = + ImGui.CalcTextSize(WarningGlyph).X + CardGapRaw * StyleEngine.Metrics.Scale; + return _warningIndent; + } + + // Row's language as a tile: a resting surface because a card is something + // you press, an accent bar down the left edge when it is the chosen one, + // and a held hover over both. No rounding -- Row does not round either, and + // a rounded corner would cut the accent bar off at an angle. private void DrawProfileCard( - PrivacyProfile profile, - string emoji, - string heading, - string description, - bool recommended, + (PrivacyProfile Profile, string Glyph, string Id) card, float width, float height ) { - var isSelected = _state.PendingProfile == profile; - // GetStyleColorVec4 returns a pointer to the live style entry in - // Dalamud.Bindings.ImGui, which would require unsafe. Use the U32 - // packed-colour overload of PushColor for the default branch so we - // can stay in safe code while still matching the current border. - var borderColor = isSelected - ? ColourUtil.RgbaToAbgr( - ColourUtil.EnsureContrast( - ColourUtil.RgbaToAbgr(Plugin.ThemeRegistry.Active.Colors.Accent), - ColourUtil.RgbaToAbgr(Plugin.ThemeRegistry.Active.Colors.ChildBg), - 3f - ) - ) - : ImGui.GetColorU32(ImGuiCol.Border); + var (profile, glyph, id) = card; + var scale = StyleEngine.Metrics.Scale; + var c = Plugin.ThemeRegistry.Active.Colors; + var dl = ImGui.GetWindowDrawList(); + var (heading, description) = TextFor(profile); - using var _border = ImRaii.PushColor(ImGuiCol.Border, borderColor); - using var child = ImRaii.Child( - $"##profile-card-{profile}", - new Vector2(width, height), - true + var origin = ImGui.GetCursorScreenPos(); + var size = new Vector2(width, height); + var max = origin + size; + var isSelected = _state.PendingProfile == profile; + + if (ImGui.InvisibleButton(id, size)) + _state.PendingProfile = profile; + var amount = StyleEngine.HoverState.Query(ImGui.GetID(id), ImGui.IsItemHovered()); + + var surface = ColourUtil.RgbaToAbgr(c.Surface); + var accent = ColourUtil.EnsureContrast(ColourUtil.RgbaToAbgr(c.Accent), surface, 3f); + + dl.AddRectFilled(origin, max, surface); + if (isSelected) + dl.AddRectFilled(origin, max, ColourUtil.ApplyAlpha(accent, 0.10f)); + if (amount > 0f) + dl.AddRectFilled( + origin, + max, + ColourUtil.ApplyAlpha(ColourUtil.RgbaToAbgr(c.SurfaceHover), amount) + ); + + dl.AddRect( + origin, + max, + isSelected ? accent : ColourUtil.RgbaToAbgr(c.Border), + 0f, + ImDrawFlags.None, + MathF.Max(1f, scale) ); - if (!child.Success) + + if (isSelected) + dl.AddRectFilled( + origin, + new Vector2(origin.X + CardAccentBarRaw * scale, max.Y), + accent + ); + + var innerLeft = origin.X + (CardAccentBarRaw + CardPadXRaw) * scale; + var innerWidth = CardInnerWidth(width); + var titleHeight = TitleRowHeight(); + var y = origin.Y + CardPadYRaw * scale; + + var titleAbgr = ColourUtil.EnsureContrast( + ColourUtil.RgbaToAbgr(c.TextPrimary), + surface, + 4.5f + ); + + // The badge is measured before the title is drawn, so a long heading + // gets clipped short of it instead of running underneath. + var badgeWidth = 0f; + if (profile == RecommendedProfile) + { + var badgeSize = BadgeSize(); + badgeWidth = badgeSize.X + CardGapRaw * scale; + StyleEngine.Widgets.Pill.Draw( + new Vector2( + innerLeft + innerWidth - badgeSize.X, + y + MetricsMath.CenterY(titleHeight, badgeSize.Y) + ), + HellionStrings.Wizard_Profile_Recommended_Badge, + accent, + ColourUtil.EnsureContrast(ColourUtil.RgbaToAbgr(c.WindowBg), accent, 4.5f) + ); + } + + var x = innerLeft; + using (Plugin.FontManager.FontAwesome.Push()) + { + var glyphSize = ImGui.CalcTextSize(glyph); + dl.AddText( + new Vector2(x, y + MetricsMath.CenterY(titleHeight, glyphSize.Y)), + isSelected + ? accent + : ColourUtil.EnsureContrast(ColourUtil.RgbaToAbgr(c.TextMuted), surface, 3f), + glyph + ); + x += glyphSize.X + CardGapRaw * scale; + } + + // Tracked caps, the shape the finished windows use to give a name rank + // without a bold cut -- the plugin does not ship one. + var titleRight = innerLeft + innerWidth - badgeWidth; + if (titleRight > x) + { + var titleY = y + MetricsMath.CenterY(titleHeight, ImGui.GetTextLineHeight()); + dl.PushClipRect(new Vector2(x, y), new Vector2(titleRight, y + titleHeight), true); + dl.DrawTrackedText( + new Vector2(x, titleY), + heading.ToUpperInvariant(), + titleAbgr, + 1.4f * scale + ); + dl.PopClipRect(); + } + + y += titleHeight + CardGapRaw * scale; + + dl.DrawFadeRule( + new Vector2(innerLeft, y), + innerWidth, + ColourUtil.RgbaToAbgr(c.Border), + MathF.Max(1f, scale) + ); + + y += CardGapRaw * scale; + + dl.AddText( + ImGui.GetFont(), + ImGui.GetFontSize(), + new Vector2(innerLeft, y), + ColourUtil.EnsureContrast(ColourUtil.RgbaToAbgr(c.TextMuted), surface, 4.5f), + description, + innerWidth + ); + + if (WarningFor(profile) is not { } warning) return; - // InvisibleButton over the full card area, then SetCursorScreenPos - // back to draw the heading/description content on top. Selectable - // would be semantically wrong here — the card is a standalone - // choice tile, not a list-item inside a list/menu. The button - // takes the click for the entire card area, and IsItemHovered() - // on it (if we wire one up later) would naturally cover the full - // tile. Visual feedback comes from the border colour above. - var startPos = ImGui.GetCursorScreenPos(); - var cardArea = ImGui.GetContentRegionAvail(); - if (ImGui.InvisibleButton($"##profile-hit-{profile}", cardArea)) - _state.PendingProfile = profile; + y += ImGui.CalcTextSize(description, false, innerWidth).Y + CardGapRaw * scale; - ImGui.SetCursorScreenPos(startPos); - - ImGui.TextUnformatted($"{emoji} {heading}{(recommended ? " ★" : string.Empty)}"); - ImGui.Spacing(); - ImGui.Separator(); - ImGui.Spacing(); - ImGui.TextWrapped(description); + DrawHangingNotice( + new Vector2(innerLeft, y), + warning, + innerWidth, + ColourUtil.EnsureContrast(ColourUtil.RgbaToAbgr(c.StatusWarning), surface, 4.5f) + ); } private void DrawStepPowerSettings() { // Seed only the recommendation field here. Other fields remain null // until the user touches the corresponding control. - // Spec FR-4: the wizard explicitly recommends - // FilterIncludePreviousSessions = true (the Config default is false). - // The other four fields (AutoTellTabsHistoryPreload, UseCompactDensity, - // PrettierTimestamps, Theme) follow the generic null-semantics from - // Spec Z.176: a null pending means the user did not touch that control, - // so CommitPending must not write back. They are read live from - // Plugin.Config below for the ImGui ref-binding but never seeded into - // Pending* without a user gesture. + // The wizard actively recommends FilterIncludePreviousSessions = true, + // where the config default is false, so this one is seeded. + // + // The rest are not: a null pending means the user never touched that + // control, and CommitPending must not write those back. They are read + // live from Plugin.Config for display, never seeded without a gesture. _state.PendingFilterIncludePreviousSessions ??= true; ImGui.TextUnformatted(HellionStrings.Wizard_Step3_Title); ImGui.Spacing(); - // History section. - DrawHeading(HellionStrings.Wizard_Step3_Section_History); + // Same scrolling frame as the profile grid, for the same reason: a + // SettingRow carries its own padding, so four of them plus three + // headings are taller than the eight stock widgets they replaced, and + // display scaling makes that worse rather than better. + var footerReserve = ImGui.GetFrameHeightWithSpacing() + ImGui.GetStyle().ItemSpacing.Y * 2f; + var bodyHeight = MathF.Max(1f, ImGui.GetContentRegionAvail().Y - footerReserve); - // One checkbox, not two. LoadPreviousSession was asked for here, shown - // as applied in the summary and written to the config, and no code in - // the plugin has ever read it -- so the wizard was collecting a - // decision and reporting an effect that never happened. Its partner - // does the work on its own. - var filterPrev = _state.PendingFilterIncludePreviousSessions ?? true; - if ( - ImGui.Checkbox( - HellionStrings.Wizard_Step3_FilterIncludePreviousSessions_Label, - ref filterPrev - ) - ) + using (ImRaii.PushColor(ImGuiCol.ChildBg, 0u)) + using (var body = ImRaii.Child("##wizard-power", new Vector2(-1f, bodyHeight))) { - _state.PendingFilterIncludePreviousSessions = filterPrev; - } - - ImGui.Spacing(); - - // Tell-Tabs section. - DrawHeading(HellionStrings.Wizard_Step3_Section_TellTabs); - - var preload = - _state.PendingAutoTellTabsHistoryPreload ?? Plugin.Config.AutoTellTabsHistoryPreload; - if ( - ImGui.SliderInt( - HellionStrings.Wizard_Step3_AutoTellTabsHistoryPreload_Label, - ref preload, - 0, - 100 - ) - ) - _state.PendingAutoTellTabsHistoryPreload = preload; - - ImGui.Spacing(); - - // Visual section. - DrawHeading(HellionStrings.Wizard_Step3_Section_Visual); - - var compact = _state.PendingUseCompactDensity ?? Plugin.Config.UseCompactDensity; - if (ImGui.Checkbox(HellionStrings.Wizard_Step3_UseCompactDensity_Label, ref compact)) - _state.PendingUseCompactDensity = compact; - - // Theme dropdown — built-ins only. Custom themes are power-user - // territory and would clutter the first-run flow. - var currentSlug = _state.PendingTheme ?? Plugin.Config.Theme; - var builtIns = Plugin.ThemeRegistry.AllBuiltIns().ToList(); - var currentIndex = builtIns.FindIndex(t => - string.Equals(t.Slug, currentSlug, StringComparison.OrdinalIgnoreCase) - ); - if (currentIndex < 0) - currentIndex = 0; - - using ( - var combo = ImRaii.Combo( - HellionStrings.Wizard_Step3_Theme_Label, - builtIns[currentIndex].Name - ) - ) - { - if (combo.Success) - { - for (var i = 0; i < builtIns.Count; i++) - { - var isSelected = i == currentIndex; - if (ImGui.Selectable(builtIns[i].Name, isSelected)) - _state.PendingTheme = builtIns[i].Slug; - if (isSelected) - ImGui.SetItemDefaultFocus(); - } - } + if (body.Success) + DrawPowerSettingsBody(); } DrawFooter( @@ -563,6 +884,191 @@ public sealed class FirstRunWizard : Window ); } + // The transient widget overloads throughout, never the Func/Action pair: + // those save the config on every click, and this step is staged until the + // user reaches Finish. A wizard that wrote as it went would leave half a + // profile behind on "decide later". + private void DrawPowerSettingsBody() + { + DrawHeading(HellionStrings.Wizard_Step3_Section_History); + + // One checkbox, not two. LoadPreviousSession was asked for here, shown + // as applied in the summary and written to the config, and no code in + // the plugin has ever read it -- so the wizard was collecting a + // decision and reporting an effect that never happened. Its partner + // does the work on its own. + _state.PendingFilterIncludePreviousSessions = _widgets.ToggleRow( + ImGui.GetID("wizard.history.previous"u8), + HellionStrings.Wizard_Step3_FilterIncludePreviousSessions_Label, + null, + _state.PendingFilterIncludePreviousSessions ?? true + ); + + ImGui.Spacing(); + + DrawHeading(HellionStrings.Wizard_Step3_Section_TellTabs); + + var preload = + _state.PendingAutoTellTabsHistoryPreload ?? Plugin.Config.AutoTellTabsHistoryPreload; + var newPreload = _widgets.SliderIntRow( + ImGui.GetID("wizard.telltabs.preload"u8), + HellionStrings.Wizard_Step3_AutoTellTabsHistoryPreload_Label, + null, + preload, + 0, + 100 + ); + if (newPreload != preload) + _state.PendingAutoTellTabsHistoryPreload = newPreload; + + ImGui.Spacing(); + + DrawHeading(HellionStrings.Wizard_Step3_Section_Visual); + + var compact = _state.PendingUseCompactDensity ?? Plugin.Config.UseCompactDensity; + var newCompact = _widgets.ToggleRow( + ImGui.GetID("wizard.visual.compact"u8), + HellionStrings.Wizard_Step3_UseCompactDensity_Label, + null, + compact + ); + if (newCompact != compact) + _state.PendingUseCompactDensity = newCompact; + + DrawThemeRow(); + } + + // Built-ins only. Custom themes are power-user territory and would clutter + // the first-run flow. + private void DrawThemeRow() + { + var currentSlug = _state.PendingTheme ?? Plugin.Config.Theme; + + _widgets.Row( + ImGui.GetID("wizard.visual.theme"u8), + HellionStrings.Wizard_Step3_Theme_Label, + null, + ctx => + { + if ( + DrawDropdownAnchor( + "##wizard-theme", + Plugin.ThemeRegistry.Get(currentSlug).Name, + ctx.ControlOrigin, + ctx.ControlWidth + ) + ) + ImGui.OpenPopup("##wizard-theme-picker"); + + DrawThemePickerPopup(currentSlug); + } + ); + } + + private void DrawThemePickerPopup(string currentSlug) + { + if (!ImGui.BeginPopup("##wizard-theme-picker")) + return; + + try + { + // The popup must be sized from its rows: ImGui does not grow one for + // draw-list content, so without this the longest theme name clips. + var width = 0f; + foreach (var theme in Plugin.ThemeRegistry.AllBuiltIns()) + width = MathF.Max( + width, + StyleEngine.Widgets.PopupRow.CalcWidth(theme.Name, null, Plugin.FontManager) + ); + + ImGui.Dummy(new Vector2(width, 0f)); + + var i = 0; + foreach (var theme in Plugin.ThemeRegistry.AllBuiltIns()) + { + var isCurrent = string.Equals( + theme.Slug, + currentSlug, + StringComparison.OrdinalIgnoreCase + ); + if ( + StyleEngine.Widgets.PopupRow.Draw( + $"##wizard-theme-{i++}", + theme.Name, + isCurrent, + Plugin.FontManager + ) + ) + { + _state.PendingTheme = theme.Slug; + ImGui.CloseCurrentPopup(); + } + } + } + finally + { + ImGui.EndPopup(); + } + } + + // The field shape from Boutique.Inputs: draw the surface, then put the + // control on top of it -- rather than pushing FrameBg colours at an + // ImGui.Combo and leaving it the arrow button ImGui draws by default. + private bool DrawDropdownAnchor(string id, string value, Vector2 origin, float width) + { + var scale = StyleEngine.Metrics.Scale; + var c = Plugin.ThemeRegistry.Active.Colors; + var dl = ImGui.GetWindowDrawList(); + var size = new Vector2(width, ImGui.GetFrameHeight()); + + ImGui.SetCursorScreenPos(origin); + var clicked = ImGui.InvisibleButton(id, size); + var amount = StyleEngine.HoverState.Query(ImGui.GetID(id), ImGui.IsItemHovered()); + + var max = origin + size; + var surface = ColourUtil.RgbaToAbgr(c.FrameBg); + var accent = ColourUtil.EnsureContrast(ColourUtil.RgbaToAbgr(c.Accent), surface, 3f); + var rounding = 3f * scale; + + dl.AddRectFilled(origin, max, surface, rounding); + dl.AddRect( + origin, + max, + ColourUtil.Lerp(ColourUtil.RgbaToAbgr(c.Border), accent, amount), + rounding, + ImDrawFlags.None, + MathF.Max(1f, scale) + ); + + // A filled triangle, the shape Character Select+ settled on for its own + // pickers. A glyph would mean pushing the icon font for three pixels of + // arrow. + var padX = 10f * scale; + var r = 4f * scale; + var centre = new Vector2(max.X - padX - r, origin.Y + size.Y * 0.5f); + var muted = ColourUtil.EnsureContrast(ColourUtil.RgbaToAbgr(c.TextMuted), surface, 3f); + dl.AddTriangleFilled( + centre + new Vector2(-r, -r * 0.5f), + centre + new Vector2(r, -r * 0.5f), + centre + new Vector2(0f, r * 0.7f), + ColourUtil.Lerp(muted, accent, amount) + ); + + // Clipped short of the chevron so a long theme name cannot run under it. + dl.PushClipRect(origin, new Vector2(centre.X - r, max.Y), true); + dl.AddText( + new Vector2( + origin.X + padX, + origin.Y + MetricsMath.CenterY(size.Y, ImGui.GetTextLineHeight()) + ), + ColourUtil.EnsureContrast(ColourUtil.RgbaToAbgr(c.TextPrimary), surface, 4.5f), + value + ); + dl.PopClipRect(); + + return clicked; + } + private void DrawStepDone() { ImGui.TextUnformatted(HellionStrings.Wizard_Step4_Title); @@ -643,7 +1149,7 @@ public sealed class FirstRunWizard : Window ImGui.Spacing(); - // Inline FR-3 hint with placeholder for preload count. + // Inline hint, with the preload count filled in. var preloadForHint = _state.PendingAutoTellTabsHistoryPreload ?? Plugin.Config.AutoTellTabsHistoryPreload; using (ImRaii.PushColor(ImGuiCol.Text, AccentVec4())) @@ -791,5 +1297,17 @@ public sealed class FirstRunWizard : Window public int? PendingAutoTellTabsHistoryPreload { get; set; } public bool? PendingUseCompactDensity { get; set; } public string? PendingTheme { get; set; } + + // Back to null, not to the config's current values: null is what tells + // CommitPending the user never touched that control. + public void Reset() + { + CurrentStep = 1; + PendingProfile = null; + PendingFilterIncludePreviousSessions = null; + PendingAutoTellTabsHistoryPreload = null; + PendingUseCompactDensity = null; + PendingTheme = null; + } } }