From aba64722920287b64808c57c448e3337e0cd7bd2 Mon Sep 17 00:00:00 2001 From: claude-timemachine Date: Thu, 4 Jun 2026 23:32:32 +0200 Subject: [PATCH] style(ui): login dialog matches Steam-layout in Prism dark MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Same skeleton as the conflict dialog: header (Prism-green badge + uppercase title), body copy, content, foot row. Specifics: - badge: '+' on Prism-green circle (vs the conflict's '!' on light gray) — green reads as 'setup / new', not warning - command callout: monospace, green text, dark inset card - token input: monospace, dark inset, green focus border, green selection highlight - 'Skip cloud sync' = secondary (dark surface, green hover border) - 'Save and continue' = primary (Prism-green fill, black text) Same Enter-to-submit + format validation behavior as before; on-screen error reuses Prism's BrightText red. --- cloud_sync/ui_qt.py | 159 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 131 insertions(+), 28 deletions(-) diff --git a/cloud_sync/ui_qt.py b/cloud_sync/ui_qt.py index c4aef09..d4aea86 100644 --- a/cloud_sync/ui_qt.py +++ b/cloud_sync/ui_qt.py @@ -191,12 +191,94 @@ class QtProgressWindow: # --------------------------------------------------------------------------- +_LOGIN_QSS = """ +QDialog { background: #313131; } +QLabel#title { + color: white; + font-size: 20pt; + font-weight: bold; + letter-spacing: 2px; +} +QLabel#badge { + color: #313131; + background: #96db59; + border-radius: 16px; + font-size: 20pt; + font-weight: bold; + qproperty-alignment: AlignCenter; + min-width: 32px; + max-width: 32px; + min-height: 32px; + max-height: 32px; +} +QLabel#body { + color: #c8c8c8; + font-size: 10pt; +} +QLabel#hint { + color: #8a8a8a; + font-size: 9pt; +} +QLabel#error { + color: #ff7373; + font-size: 9pt; +} +QLabel#cmd { + color: #96db59; + background: #222222; + border: 1px solid #3a3a3a; + border-radius: 3px; + padding: 6px 10px; + font-family: "JetBrains Mono", "Source Code Pro", Menlo, Consolas, monospace; + font-size: 10pt; +} +QLineEdit#token { + background: #222222; + border: 1px solid #3a3a3a; + border-radius: 3px; + padding: 8px 10px; + color: white; + font-family: "JetBrains Mono", "Source Code Pro", Menlo, Consolas, monospace; + font-size: 10pt; + selection-background-color: #96db59; + selection-color: black; +} +QLineEdit#token:focus { border: 1px solid #96db59; } +QPushButton#secondary { + background: #303030; + color: white; + border: 1px solid #4a4a4a; + border-radius: 2px; + padding: 8px 20px; + font-size: 10pt; +} +QPushButton#secondary:hover { background: #3a3a3a; border-color: #96db59; } +QPushButton#secondary:pressed { background: #222222; } +QPushButton#primary { + background: #96db59; + color: #1a1a1a; + border: none; + border-radius: 2px; + padding: 8px 24px; + font-size: 10pt; + font-weight: bold; +} +QPushButton#primary:hover { background: #a8e670; } +QPushButton#primary:pressed { background: #7fbf48; } +QPushButton#primary:disabled { background: #4a5a3a; color: #888888; } +""" + + def prompt_login_qt() -> str | None: """Modal dialog to collect a fresh ``discord_id:password`` token. Returned string is the validated raw token (caller writes to disk). Returns ``None`` if the user picked "Skip cloud sync" or closed the dialog — caller should treat that as "don't sync, don't block launch." + + Visually matches the conflict dialog: Prism dark surface, circled + badge + uppercase title, monospace input field with Prism-green + focus accent, primary "Save and continue" button in Prism green. """ QtWidgets, QtCore, _ = import_qt() app_existed = QtWidgets.QApplication.instance() is not None @@ -205,58 +287,79 @@ def prompt_login_qt() -> str | None: _apply_prism_dark(app) dialog = QtWidgets.QDialog() - dialog.setWindowTitle("Cloud sync — first time setup") - dialog.setFixedSize(480, 260) + dialog.setWindowTitle("Cloud sync — connect account") + dialog.setFixedSize(560, 360) + dialog.setStyleSheet(_LOGIN_QSS) - layout = QtWidgets.QVBoxLayout(dialog) - layout.setContentsMargins(20, 20, 20, 20) - layout.setSpacing(10) + outer = QtWidgets.QVBoxLayout(dialog) + outer.setContentsMargins(28, 24, 28, 20) + outer.setSpacing(14) - title = QtWidgets.QLabel("Connect to cloud save") - font = title.font() - font.setBold(True) - font.setPointSize(font.pointSize() + 2) - title.setFont(font) - layout.addWidget(title) + header = QtWidgets.QHBoxLayout() + header.setSpacing(12) + badge = QtWidgets.QLabel("+") + badge.setObjectName("badge") + header.addWidget(badge) + title = QtWidgets.QLabel("CONNECT CLOUD SAVE") + title.setObjectName("title") + header.addWidget(title) + header.addStretch(1) + outer.addLayout(header) body = QtWidgets.QLabel( - "In Discord, message the bot:\n" - " /cloud register\n\n" - "It will DM you a one-line token. Paste it below:" + "To enable cross-machine save sync, message the Discord bot to " + "register this account. The bot will DM you a one-line token — " + "paste it below." ) + body.setObjectName("body") body.setWordWrap(True) - layout.addWidget(body) + outer.addWidget(body) + + cmd = QtWidgets.QLabel("/cloud register") + cmd.setObjectName("cmd") + cmd.setTextInteractionFlags(QtCore.Qt.TextInteractionFlag.TextSelectableByMouse) + cmd.setFixedWidth(180) + outer.addWidget(cmd) + + outer.addSpacing(4) + + field_label = QtWidgets.QLabel("Token") + field_label.setObjectName("hint") + outer.addWidget(field_label) field = QtWidgets.QLineEdit() - field.setPlaceholderText("123456789012345678:a1b2c3d4…") + field.setObjectName("token") + field.setPlaceholderText("123456789012345678:a1b2c3d4e5f6…") field.setEchoMode(QtWidgets.QLineEdit.EchoMode.Password) - layout.addWidget(field) + outer.addWidget(field) error = QtWidgets.QLabel("") - error.setStyleSheet("color: #ff6b6b;") - layout.addWidget(error) + error.setObjectName("error") + outer.addWidget(error) - layout.addStretch(1) + outer.addStretch(1) - button_row = QtWidgets.QHBoxLayout() + foot = QtWidgets.QHBoxLayout() skip = QtWidgets.QPushButton("Skip cloud sync") - button_row.addWidget(skip) - button_row.addStretch(1) + skip.setObjectName("secondary") + foot.addWidget(skip) + foot.addStretch(1) save = QtWidgets.QPushButton("Save and continue") + save.setObjectName("primary") save.setDefault(True) - button_row.addWidget(save) - layout.addLayout(button_row) + foot.addWidget(save) + outer.addLayout(foot) chosen: dict[str, str | None] = {"value": None} def on_save() -> None: token = field.text().strip() if ":" not in token: - error.setText("Token must be discord_id:password") + error.setText("Token must look like discord_id:password.") return - head, sep, tail = token.partition(":") + head, _sep, tail = token.partition(":") if not head.isdigit() or not tail: - error.setText("discord_id must be numeric; password must be non-empty") + error.setText("discord_id must be numeric and password non-empty.") return chosen["value"] = token dialog.accept()