diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/Main.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/Main.java index 348cf5c..a0379ea 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/Main.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/Main.java @@ -19,6 +19,8 @@ import edu.ntnu.idi.idatt2003.g40.mappe.view.creategame.CreateGameView; import edu.ntnu.idi.idatt2003.g40.mappe.view.ingame.InGameController; import edu.ntnu.idi.idatt2003.g40.mappe.view.ingame.InGameView; +import edu.ntnu.idi.idatt2003.g40.mappe.view.ingame.quit.QuitDialogController; +import edu.ntnu.idi.idatt2003.g40.mappe.view.ingame.quit.QuitDialogView; import edu.ntnu.idi.idatt2003.g40.mappe.view.ingame.settings.InGameSettingsController; import edu.ntnu.idi.idatt2003.g40.mappe.view.ingame.settings.InGameSettingsView; import edu.ntnu.idi.idatt2003.g40.mappe.view.mainmenu.MainMenuController; @@ -222,6 +224,25 @@ public void handleEvent(final EventData eventData) { new InGameSettingsController(inGameSettingsView, eventManager, inGameView); topBarController.setSettingsAction(inGameSettingsController::show); + // In-game quit confirmation dialog: replaces the previous immediate + // "auto-save and return to main menu" behaviour with a popup that + // lets the player choose Continue / Save / Save and quit to main + // menu / Save and quit game. + QuitDialogView quitDialogView = new QuitDialogView(); + QuitDialogController quitDialogController = + new QuitDialogController(quitDialogView, eventManager, inGameView, + gameStateLoader, saveGameService); + quitDialogController.setOnExitApplication(() -> { + try { + javafx.application.Platform.exit(); + } finally { + // Fallback in case there are non-daemon background threads + // keeping the JVM alive after JavaFX shuts down. + System.exit(0); + } + }); + topBarController.setOnQuitRequested(quitDialogController::show); + topBarController.setOnQuitToMainMenu(() -> { System.out.println("[auto-save] Quit triggered, attempting snapshot..."); SaveGame snapshot = gameStateLoader.snapshotActiveSave(); diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/ingame/quit/QuitDialogActions.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/ingame/quit/QuitDialogActions.java new file mode 100644 index 0000000..7de92ca --- /dev/null +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/ingame/quit/QuitDialogActions.java @@ -0,0 +1,23 @@ +package edu.ntnu.idi.idatt2003.g40.mappe.view.ingame.quit; + +/** + * User-triggered actions available in the in-game quit dialog overlay. + * + *

The quit dialog is shown when the player clicks the "Quit" button + * from the in-game dashboard, and lets them choose between continuing, + * saving in place, saving and returning to the main menu, or saving + * and exiting the application.

+ * */ +public enum QuitDialogActions { + /** Closes the dialog and returns the player to the game. */ + CONTINUE, + + /** Saves the current game state without leaving the in-game session. */ + SAVE, + + /** Saves the current game state and returns to the main menu. */ + SAVE_AND_QUIT_TO_MAIN_MENU, + + /** Saves the current game state and exits the application. */ + SAVE_AND_QUIT_GAME; +} diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/ingame/quit/QuitDialogController.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/ingame/quit/QuitDialogController.java new file mode 100644 index 0000000..52a4e90 --- /dev/null +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/ingame/quit/QuitDialogController.java @@ -0,0 +1,188 @@ +package edu.ntnu.idi.idatt2003.g40.mappe.view.ingame.quit; + +import edu.ntnu.idi.idatt2003.g40.mappe.model.SaveGame; +import edu.ntnu.idi.idatt2003.g40.mappe.service.GameStateLoader; +import edu.ntnu.idi.idatt2003.g40.mappe.service.SaveGameService; +import edu.ntnu.idi.idatt2003.g40.mappe.service.event.EventManager; +import edu.ntnu.idi.idatt2003.g40.mappe.view.ViewController; +import edu.ntnu.idi.idatt2003.g40.mappe.view.ViewEnum; +import edu.ntnu.idi.idatt2003.g40.mappe.view.ingame.InGameView; + +/** + * Controller for {@link QuitDialogView}. + * + *

Wires the four dialog buttons to the appropriate save / scene-change + * / application-exit actions:

+ * + * + *

The save logic mirrors the auto-save hook in {@code Main}: a missing + * active save (e.g. before any game has been loaded) is treated as a + * no-op rather than an error, since there's nothing meaningful to write.

+ * */ +public final class QuitDialogController + extends ViewController { + + /** The in-game view hosting this overlay. */ + private final InGameView inGameView; + + /** Builds {@link SaveGame} snapshots of the current player/exchange. */ + private final GameStateLoader gameStateLoader; + + /** Persists save games to disk. */ + private final SaveGameService saveGameService; + + /** Runnable invoked to close the application. */ + private Runnable onExitApplication = () -> { }; + + /** + * Constructor. + * + * @param view the {@link QuitDialogView} this controller is + * attached to. + * @param eventManager the active {@link EventManager}, used to + * publish scene-change events. + * @param inGameView the in-game view that hosts this overlay. + * @param gameStateLoader the loader used to snapshot the active save. + * @param saveGameService the service used to persist saves to disk. + * + * @throws IllegalArgumentException if any constructor argument is null. + * */ + public QuitDialogController(final QuitDialogView view, + final EventManager eventManager, + final InGameView inGameView, + final GameStateLoader gameStateLoader, + final SaveGameService saveGameService) + throws IllegalArgumentException { + this.inGameView = inGameView; + this.gameStateLoader = gameStateLoader; + this.saveGameService = saveGameService; + super(view, eventManager); + if (inGameView == null + || gameStateLoader == null + || saveGameService == null) { + throw new IllegalArgumentException( + "Invalid QuitDialogController arguments!"); + } + } + + /** + * Installs the runnable used to close the application when the player + * chooses "Save and quit game". + * + *

Typically this is wired to {@code Platform.exit()} (plus a + * {@code System.exit(0)} fallback) from {@code Main}, so the controller + * itself stays decoupled from the JavaFX runtime.

+ * + * @param exitRunnable runnable invoked after the save completes; a null + * value resets the hook to a no-op. + * */ + public void setOnExitApplication(final Runnable exitRunnable) { + this.onExitApplication = + (exitRunnable != null) ? exitRunnable : () -> { }; + } + + /** {@inheritDoc} */ + @Override + protected void initInteractions() { + getViewElement().setOnAction(QuitDialogActions.CONTINUE, this::close); + + getViewElement().setOnAction(QuitDialogActions.SAVE, () -> { + boolean ok = performSave(); + if (ok) { + getViewElement().setStatus("Game saved.", false); + } + // Failures already wrote an error to the status label inside + // performSave(); nothing more to do here. + }); + + getViewElement().setOnAction( + QuitDialogActions.SAVE_AND_QUIT_TO_MAIN_MENU, () -> { + if (performSave()) { + close(); + changeScene(ViewEnum.MAIN_MENU); + } + }); + + getViewElement().setOnAction(QuitDialogActions.SAVE_AND_QUIT_GAME, () -> { + if (performSave()) { + close(); + onExitApplication.run(); + } + }); + } + + /** + * Shows the quit dialog overlay on the host {@link InGameView}. + * + *

Clears any stale status message left over from a previous opening + * so the player doesn't see "Game saved." from a save they made hours + * ago.

+ * */ + public void show() { + getViewElement().setStatus(null, false); + inGameView.showSettingsOverlay(getViewElement().getRootPane()); + } + + /** + * Hides the overlay if it's currently visible. + * */ + private void close() { + inGameView.hideSettingsOverlay(); + } + + /** + * Snapshots the active save and writes it to disk. + * + *

If no save is currently active (e.g. the player got into the + * in-game view through a path that doesn't load a save), this is + * treated as a successful no-op so the quit / exit flows can proceed. + * Any I/O failure is reported back to the player via the dialog's + * status label.

+ * + * @return {@code true} when the operation can be considered successful + * (a real save was written, or no save was needed); {@code false} + * when an actual error occurred. + * */ + private boolean performSave() { + SaveGame snapshot; + try { + snapshot = gameStateLoader.snapshotActiveSave(); + } catch (Exception e) { + System.err.println("[quit-dialog] Snapshot failed: " + + e.getMessage()); + getViewElement().setStatus( + "Could not prepare save: " + e.getMessage(), true); + return false; + } + + if (snapshot == null) { + System.out.println( + "[quit-dialog] No active save - nothing to write."); + // Nothing to persist, but the user's intent (continue past this + // step) is still valid - treat as success. + return true; + } + + try { + saveGameService.saveGame(snapshot); + System.out.println("[quit-dialog] Wrote save '" + + snapshot.getName() + "' to disk."); + return true; + } catch (Exception e) { + System.err.println("[quit-dialog] Save failed: " + e.getMessage()); + getViewElement().setStatus( + "Save failed: " + e.getMessage(), true); + return false; + } + } +} diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/ingame/quit/QuitDialogView.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/ingame/quit/QuitDialogView.java new file mode 100644 index 0000000..e749610 --- /dev/null +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/ingame/quit/QuitDialogView.java @@ -0,0 +1,178 @@ +package edu.ntnu.idi.idatt2003.g40.mappe.view.ingame.quit; + +import edu.ntnu.idi.idatt2003.g40.mappe.view.ViewElement; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.control.Button; +import javafx.scene.control.Label; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Priority; +import javafx.scene.layout.Region; +import javafx.scene.layout.StackPane; +import javafx.scene.layout.VBox; + +/** + * Overlay shown when the player clicks the "Quit" button in the in-game + * top bar. + * + *

The root of this view is a dimmer {@link StackPane} that covers the + * current center widget. Inside, a centered panel hosts four action buttons + * that let the player choose how to leave (or stay in) the game.

+ * + *

Layout: a header row with the title and a close (X) button, a short + * description, and a vertical stack of four buttons: Continue, Save, + * Save and quit to main menu, Save and quit game.

+ * + *

This view is purely presentational - the {@link QuitDialogController} + * wires the buttons to the actual save/quit logic.

+ * */ +public final class QuitDialogView + extends ViewElement { + + /** Close button shown in the panel's top-right corner. */ + private Button closeButton; + + /** Closes the dialog without saving. */ + private Button continueButton; + + /** Saves the game in place, without leaving. */ + private Button saveButton; + + /** Saves and returns to the main menu. */ + private Button saveAndQuitMainMenuButton; + + /** Saves and exits the application. */ + private Button saveAndQuitGameButton; + + /** Label used to communicate save status to the player. */ + private Label statusLabel; + + /** Modal-style card hosting the dialog controls. */ + private VBox panel; + + /** + * Constructor. + * */ + public QuitDialogView() { + super(new StackPane(), QuitDialogActions.class); + } + + /** + * Updates the status text shown below the action buttons. + * + *

Used by the controller to tell the player whether the last save + * succeeded or failed.

+ * + * @param text message to display, or {@code null} / empty to hide. + * @param errorStyle when {@code true}, the message is styled as an error. + * */ + public void setStatus(final String text, final boolean errorStyle) { + if (statusLabel == null) { + return; + } + statusLabel.getStyleClass().remove("quit-dialog-status-error"); + if (text == null || text.isEmpty()) { + statusLabel.setText(""); + statusLabel.setVisible(false); + statusLabel.setManaged(false); + return; + } + statusLabel.setText(text); + statusLabel.setVisible(true); + statusLabel.setManaged(true); + if (errorStyle) { + statusLabel.getStyleClass().add("quit-dialog-status-error"); + } + } + + /** {@inheritDoc} */ + @Override + protected void initLayout() { + // Header row: title + close button. + Label title = new Label("Quit game?"); + title.getStyleClass().add("quit-dialog-title"); + + Region headerSpacer = new Region(); + HBox.setHgrow(headerSpacer, Priority.ALWAYS); + + closeButton = new Button("X"); + closeButton.setFocusTraversable(false); + closeButton.getStyleClass().add("quit-dialog-close-button"); + + HBox header = new HBox(12, title, headerSpacer, closeButton); + header.setAlignment(Pos.CENTER_LEFT); + + Label description = new Label( + "Choose what to do with your current session."); + description.getStyleClass().add("quit-dialog-description"); + description.setWrapText(true); + + // The four action buttons. Each is full-width inside the panel so the + // longer labels do not overflow. + continueButton = new Button("Continue"); + saveButton = new Button("Save"); + saveAndQuitMainMenuButton = + new Button("Save and quit to main menu"); + saveAndQuitGameButton = new Button("Save and quit game"); + + continueButton.setFocusTraversable(false); + saveButton.setFocusTraversable(false); + saveAndQuitMainMenuButton.setFocusTraversable(false); + saveAndQuitGameButton.setFocusTraversable(false); + + continueButton.getStyleClass().addAll( + "quit-dialog-button", "quit-dialog-button-primary"); + saveButton.getStyleClass().add("quit-dialog-button"); + saveAndQuitMainMenuButton.getStyleClass().add("quit-dialog-button"); + saveAndQuitGameButton.getStyleClass().addAll( + "quit-dialog-button", "quit-dialog-button-danger"); + + continueButton.setMaxWidth(Double.MAX_VALUE); + saveButton.setMaxWidth(Double.MAX_VALUE); + saveAndQuitMainMenuButton.setMaxWidth(Double.MAX_VALUE); + saveAndQuitGameButton.setMaxWidth(Double.MAX_VALUE); + + VBox buttonColumn = new VBox(10, + continueButton, + saveButton, + saveAndQuitMainMenuButton, + saveAndQuitGameButton); + buttonColumn.setAlignment(Pos.CENTER); + + // Status label - hidden until the controller writes something into it. + statusLabel = new Label(""); + statusLabel.getStyleClass().add("quit-dialog-status"); + statusLabel.setWrapText(true); + statusLabel.setVisible(false); + statusLabel.setManaged(false); + + // Assemble panel. + panel = new VBox(18, header, description, buttonColumn, statusLabel); + panel.setAlignment(Pos.TOP_LEFT); + panel.setPadding(new Insets(26, 30, 28, 30)); + panel.setMaxWidth(440); + panel.setPrefWidth(440); + panel.getStyleClass().add("quit-dialog-panel"); + + // Dimmer wrapper. + getRootPane().getStyleClass().add("quit-dialog-overlay-dimmer"); + getRootPane().getChildren().add(panel); + StackPane.setAlignment(panel, Pos.CENTER); + + registerButton(QuitDialogActions.CONTINUE, continueButton); + registerButton(QuitDialogActions.SAVE, saveButton); + registerButton(QuitDialogActions.SAVE_AND_QUIT_TO_MAIN_MENU, + saveAndQuitMainMenuButton); + registerButton(QuitDialogActions.SAVE_AND_QUIT_GAME, + saveAndQuitGameButton); + // The X button reuses the CONTINUE action so we don't need a + // dedicated enum entry just for "close". + closeButton.setOnAction(e -> continueButton.fire()); + } + + /** {@inheritDoc} */ + @Override + protected void initStyling() { + // Styling is applied via style classes assigned in initLayout(). + } +} diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/ingame/quit/package-info.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/ingame/quit/package-info.java new file mode 100644 index 0000000..4456819 --- /dev/null +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/ingame/quit/package-info.java @@ -0,0 +1,15 @@ +/** + * In-game quit confirmation dialog. + * + *

Provides the overlay shown when the player clicks "Quit" from the + * in-game dashboard. The dialog offers four choices: continue (just close + * the popup), save in place, save and return to the main menu, or save and + * exit the application.

+ * + *

The overlay is hosted on top of the {@link + * edu.ntnu.idi.idatt2003.g40.mappe.view.ingame.InGameView InGameView}, + * reusing the same {@code showSettingsOverlay} mechanism used by the + * in-game settings panel so both overlays render and dismiss the same + * way.

+ * */ +package edu.ntnu.idi.idatt2003.g40.mappe.view.ingame.quit; diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/topbar/TopBarController.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/topbar/TopBarController.java index e338eb6..f647c49 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/topbar/TopBarController.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/topbar/TopBarController.java @@ -59,6 +59,22 @@ public class TopBarController extends ViewController { * */ private Runnable onQuitToMainMenu = () -> { }; + /** + * Optional hook invoked when the player presses the "Quit" button + * from the in-game dashboard (i.e. when no sub-view is currently + * active and the button would otherwise have exited straight to the + * main menu). + * + *

When set, this hook takes precedence over the default + * "auto-save and change scene" behaviour - the application can + * instead present a confirmation dialog and decide for itself + * whether and how to leave the session.

+ * + *

Defaults to {@code null}, in which case the original + * auto-save-and-leave behaviour is preserved.

+ * */ + private Runnable onQuitRequested = null; + /** * {@inheritDoc}. @@ -128,6 +144,12 @@ public void setMarketIntegration(final Consumer centerSwitcher, inStatsView = false; inTransactionsView = false; inMinigamesView = false; + } else if (onQuitRequested != null) { + // From the dashboard, hand control over to the application's + // quit-confirmation flow (typically a popup with continue / + // save / save-and-quit choices). The hook is responsible for + // any auto-save and scene change after the player chooses. + onQuitRequested.run(); } else { onQuitToMainMenu.run(); changeScene(ViewEnum.MAIN_MENU); @@ -199,4 +221,28 @@ public void setSettingsAction(final Runnable onSettings) { public void setOnQuitToMainMenu(final Runnable onQuit) { this.onQuitToMainMenu = (onQuit != null) ? onQuit : () -> { }; } + + /** + * Installs the hook invoked when the player presses "Quit" from the + * in-game dashboard. + * + *

When set (non-null), this hook takes over the default + * "auto-save and return to main menu" behaviour - the application can + * for instance open a confirmation dialog and let the player choose + * between continuing, saving in place, or leaving. The hook itself is + * responsible for triggering any save and scene change.

+ * + *

Setting this hook to {@code null} restores the original + * auto-save-and-leave behaviour.

+ * + *

The "Back" behaviour used inside sub-views (market / stats / + * transactions / minigames) is unaffected by this hook - those still + * return the player to the dashboard directly.

+ * + * @param onQuit runnable invoked when the dashboard quit button is + * pressed. + * */ + public void setOnQuitRequested(final Runnable onQuit) { + this.onQuitRequested = onQuit; + } } \ No newline at end of file diff --git a/src/main/resources/saves/Newbie.json b/src/main/resources/saves/Newbie.json index 2ea4a42..6163131 100644 --- a/src/main/resources/saves/Newbie.json +++ b/src/main/resources/saves/Newbie.json @@ -20,161 +20,161 @@ { "type": "PURCHASE", "symbol": "NVDA", "quantity": 5.0, "price": 191.27, "week": 1 } ], "stocks": [ - { "symbol": "CARR", "name": "Carrier Global", "price": 67.95 }, - { "symbol": "AAPL", "name": "Apple Inc.", "price": 258.10 }, - { "symbol": "SNDK", "name": "Sandisk Corporation", "price": 634.05 }, - { "symbol": "WYNN", "name": "Wynn Resorts", "price": 117.80 }, - { "symbol": "TSCO", "name": "Tractor Supply", "price": 51.12 }, - { "symbol": "AMGN", "name": "Amgen", "price": 329.74 }, - { "symbol": "TSLA", "name": "Tesla Inc.", "price": 376.58 }, - { "symbol": "GDDY", "name": "GoDaddy", "price": 92.35 }, - { "symbol": "SBUX", "name": "Starbucks", "price": 99.41 }, - { "symbol": "KVUE", "name": "Kenvue", "price": 17.97 }, - { "symbol": "META", "name": "Meta Platforms", "price": 756.48 }, - { "symbol": "DLTR", "name": "Dollar Tree", "price": 144.19 }, - { "symbol": "ABBV", "name": "AbbVie", "price": 251.10 }, - { "symbol": "HUBB", "name": "Hubbell Incorporated", "price": 548.39 }, - { "symbol": "JKHY", "name": "Jack Henry & Associates", "price": 180.51 }, - { "symbol": "FSLR", "name": "First Solar", "price": 206.22 }, - { "symbol": "FTNT", "name": "Fortinet", "price": 92.09 }, - { "symbol": "EPAM", "name": "EPAM Systems", "price": 199.46 }, - { "symbol": "POOL", "name": "Pool Corporation", "price": 284.22 }, - { "symbol": "MCHP", "name": "Microchip Technology", "price": 72.57 }, - { "symbol": "VRSK", "name": "Verisk Analytics", "price": 169.99 }, - { "symbol": "MRNA", "name": "Moderna", "price": 33.00 }, - { "symbol": "HOOD", "name": "Robinhood Markets Inc.", "price": 64.62 }, - { "symbol": "VRSN", "name": "Verisign", "price": 247.98 }, - { "symbol": "AMAT", "name": "Applied Materials", "price": 379.06 }, - { "symbol": "MDLZ", "name": "Mondelez International", "price": 66.56 }, - { "symbol": "PCAR", "name": "Paccar", "price": 128.11 }, - { "symbol": "NDAQ", "name": "Nasdaq Inc.", "price": 79.41 }, - { "symbol": "INTU", "name": "Intuit", "price": 403.44 }, - { "symbol": "HSIC", "name": "Henry Schein", "price": 83.94 }, - { "symbol": "FISV", "name": "Fiserv Inc.", "price": 65.43 }, - { "symbol": "CSGP", "name": "CoStar Group", "price": 48.87 }, - { "symbol": "DDOG", "name": "Datadog", "price": 129.43 }, - { "symbol": "BLDR", "name": "Builders FirstSource", "price": 118.27 }, - { "symbol": "AXON", "name": "Axon Enterprise", "price": 473.33 }, - { "symbol": "ALGN", "name": "Align Technology", "price": 187.51 }, - { "symbol": "FICO", "name": "Fair Isaac", "price": 1242.10 }, - { "symbol": "FITB", "name": "Fifth Third Bancorp", "price": 50.53 }, - { "symbol": "NTAP", "name": "NetApp", "price": 105.53 }, - { "symbol": "FOXA", "name": "Fox Corporation (Class A)", "price": 60.47 }, - { "symbol": "QCOM", "name": "Qualcomm", "price": 152.25 }, - { "symbol": "VTRS", "name": "Viatris", "price": 16.25 }, - { "symbol": "LRCX", "name": "Lam Research", "price": 239.39 }, - { "symbol": "PLTR", "name": "Palantir Technologies", "price": 132.98 }, - { "symbol": "PANW", "name": "Palo Alto Networks", "price": 170.52 }, - { "symbol": "NFLX", "name": "Netflix", "price": 75.58 }, - { "symbol": "KLAC", "name": "KLA Corporation", "price": 1342.60 }, - { "symbol": "NVDA", "name": "Nvidia", "price": 187.67 }, - { "symbol": "IBKR", "name": "Interactive Brokers Group", "price": 74.25 }, - { "symbol": "CRWD", "name": "CrowdStrike", "price": 448.16 }, - { "symbol": "ULTA", "name": "Ulta Beauty", "price": 577.29 }, - { "symbol": "JBHT", "name": "J.B. Hunt", "price": 225.21 }, - { "symbol": "SMCI", "name": "Supermicro", "price": 31.69 }, - { "symbol": "NXPI", "name": "NXP Semiconductors", "price": 258.77 }, - { "symbol": "VRTX", "name": "Vertex Pharmaceuticals", "price": 456.77 }, - { "symbol": "MTCH", "name": "Match Group", "price": 29.70 }, - { "symbol": "CBOE", "name": "Cboe Global Markets", "price": 261.74 }, - { "symbol": "CPRT", "name": "Copart", "price": 38.12 }, - { "symbol": "VICI", "name": "Vici Properties", "price": 33.48 }, - { "symbol": "CHRW", "name": "C.H. Robinson", "price": 182.86 }, - { "symbol": "INTC", "name": "Intel", "price": 44.51 }, - { "symbol": "ROST", "name": "Ross Stores", "price": 206.42 }, - { "symbol": "GEHC", "name": "GE HealthCare", "price": 86.95 }, - { "symbol": "SCHW", "name": "Charles Schwab Corporation", "price": 96.49 }, - { "symbol": "CVNA", "name": "Carvana Co.", "price": 368.51 }, - { "symbol": "IDXX", "name": "Idexx Laboratories", "price": 766.54 }, - { "symbol": "INCY", "name": "Incyte", "price": 101.02 }, - { "symbol": "GNRC", "name": "Generac", "price": 224.22 }, - { "symbol": "CPAY", "name": "Corpay", "price": 366.15 }, - { "symbol": "REGN", "name": "Regeneron Pharmaceuticals", "price": 833.09 }, - { "symbol": "CTAS", "name": "Cintas", "price": 230.80 }, - { "symbol": "FAST", "name": "Fastenal", "price": 42.07 }, - { "symbol": "AMZN", "name": "Amazon", "price": 207.86 }, - { "symbol": "ZBRA", "name": "Zebra Technologies", "price": 285.78 }, - { "symbol": "ODFL", "name": "Old Dominion", "price": 208.16 }, - { "symbol": "TTWO", "name": "Take-Two Interactive", "price": 176.58 }, - { "symbol": "CTRA", "name": "Coterra", "price": 28.69 }, - { "symbol": "LDOS", "name": "Leidos", "price": 172.90 }, - { "symbol": "ARES", "name": "Ares Management Corporation", "price": 131.22 }, - { "symbol": "CINF", "name": "Cincinnati Financial", "price": 172.93 }, - { "symbol": "ANET", "name": "Arista Networks", "price": 142.22 }, - { "symbol": "AMCR", "name": "Amcor", "price": 45.14 }, - { "symbol": "HBAN", "name": "Huntington Bancshares", "price": 20.67 }, - { "symbol": "EVRG", "name": "Evergy", "price": 75.68 }, - { "symbol": "ABNB", "name": "Airbnb", "price": 125.39 }, - { "symbol": "DASH", "name": "DoorDash", "price": 164.09 }, - { "symbol": "COIN", "name": "Coinbase Global", "price": 146.37 }, - { "symbol": "CIEN", "name": "Ciena Corporation", "price": 254.65 }, - { "symbol": "FANG", "name": "Diamondback Energy", "price": 155.78 }, - { "symbol": "PSKY", "name": "Paramount Skydance Corp", "price": 10.73 }, - { "symbol": "ORLY", "name": "O'Reilly Auto Parts", "price": 96.40 }, - { "symbol": "SBAC", "name": "SBA Communications", "price": 193.40 }, - { "symbol": "ACGL", "name": "Arch Capital Group", "price": 103.61 }, - { "symbol": "CTSH", "name": "Cognizant", "price": 60.84 }, - { "symbol": "VLTO", "name": "Veralto", "price": 102.80 }, - { "symbol": "MPWR", "name": "Monolithic Power Systems", "price": 1210.08 }, - { "symbol": "PODD", "name": "Insulet Corporation", "price": 264.60 }, - { "symbol": "MRSH", "name": "Marsh & McLennan Companies Inc.", "price": 159.69 }, - { "symbol": "PAYC", "name": "Paycom", "price": 112.44 }, - { "symbol": "NTRS", "name": "Northern Trust", "price": 138.26 }, - { "symbol": "ADSK", "name": "Autodesk", "price": 232.08 }, - { "symbol": "MSCI", "name": "MSCI", "price": 472.01 }, - { "symbol": "ORCL", "name": "Oracle Corporation", "price": 168.14 }, - { "symbol": "ERIE", "name": "Erie Indemnity", "price": 273.98 }, - { "symbol": "TECH", "name": "Bio-Techne", "price": 59.22 }, - { "symbol": "TRMB", "name": "Trimble Inc.", "price": 70.40 }, - { "symbol": "EBAY", "name": "eBay", "price": 67.25 }, - { "symbol": "INVH", "name": "Invitation Homes", "price": 26.09 }, - { "symbol": "NDSN", "name": "Nordson Corporation", "price": 289.84 }, - { "symbol": "DECK", "name": "Deckers Brands", "price": 114.76 }, - { "symbol": "ADBE", "name": "Adobe Inc.", "price": 258.37 }, - { "symbol": "SNPS", "name": "Synopsys", "price": 452.44 }, - { "symbol": "CHTR", "name": "Charter Communications", "price": 270.87 }, - { "symbol": "STLD", "name": "Steel Dynamics", "price": 193.65 }, - { "symbol": "BIIB", "name": "Biogen", "price": 193.61 }, - { "symbol": "TRGP", "name": "Targa Resources", "price": 242.36 }, - { "symbol": "SOLV", "name": "Solventum", "price": 70.47 }, - { "symbol": "TROW", "name": "T. Rowe Price", "price": 92.55 }, - { "symbol": "AVGO", "name": "Broadcom", "price": 365.11 }, - { "symbol": "CSCO", "name": "Cisco", "price": 75.74 }, - { "symbol": "CTVA", "name": "Corteva", "price": 76.63 }, - { "symbol": "EXPD", "name": "Expeditors International", "price": 169.73 }, - { "symbol": "EXPE", "name": "Expedia Group", "price": 281.69 }, - { "symbol": "AKAM", "name": "Akamai Technologies", "price": 85.09 }, - { "symbol": "CBRE", "name": "CBRE Group", "price": 137.79 }, - { "symbol": "FFIV", "name": "F5 Inc.", "price": 285.06 }, - { "symbol": "MSFT", "name": "Microsoft", "price": 389.46 }, - { "symbol": "COST", "name": "Costco", "price": 932.30 }, - { "symbol": "NCLH", "name": "Norwegian Cruise Line Holdings", "price": 24.07 }, - { "symbol": "KEYS", "name": "Keysight Technologies", "price": 207.44 }, - { "symbol": "SPGI", "name": "S&P Global", "price": 426.67 }, - { "symbol": "UBER", "name": "Uber", "price": 75.84 }, - { "symbol": "BKNG", "name": "Booking Holdings", "price": 4386.71 }, - { "symbol": "DELL", "name": "Dell Technologies", "price": 142.26 }, - { "symbol": "DXCM", "name": "Dexcom", "price": 65.13 }, - { "symbol": "PYPL", "name": "PayPal", "price": 32.31 }, - { "symbol": "GOOG", "name": "Alphabet Inc. (Class C)", "price": 282.90 }, - { "symbol": "BALL", "name": "Ball Corporation", "price": 60.67 }, - { "symbol": "WELL", "name": "Welltower", "price": 194.82 }, - { "symbol": "MNST", "name": "Monster Beverage", "price": 80.45 }, - { "symbol": "OTIS", "name": "Otis Worldwide", "price": 84.53 }, - { "symbol": "SWKS", "name": "Skyworks Solutions", "price": 63.13 }, - { "symbol": "GRMN", "name": "Garmin", "price": 185.49 }, - { "symbol": "WDAY", "name": "Workday Inc.", "price": 118.22 }, - { "symbol": "APTV", "name": "Aptiv", "price": 83.68 }, - { "symbol": "RVTY", "name": "Revvity", "price": 91.38 }, - { "symbol": "TMUS", "name": "T-Mobile US", "price": 204.96 }, - { "symbol": "LULU", "name": "Lululemon Athletica", "price": 167.54 }, - { "symbol": "HOLX", "name": "Hologic", "price": 64.16 }, - { "symbol": "NWSA", "name": "News Corp (Class A)", "price": 21.24 }, - { "symbol": "PAYX", "name": "Paychex", "price": 89.52 }, - { "symbol": "CDNS", "name": "Cadence Design Systems", "price": 295.12 }, - { "symbol": "ALLE", "name": "Allegion", "price": 198.52 }, - { "symbol": "GILD", "name": "Gilead Sciences", "price": 144.57 }, - { "symbol": "EQIX", "name": "Equinix", "price": 932.33 }, - { "symbol": "ISRG", "name": "Intuitive Surgical", "price": 590.38 } + { "symbol": "CARR", "name": "Carrier Global", "price": 68.59 }, + { "symbol": "AAPL", "name": "Apple Inc.", "price": 240.45 }, + { "symbol": "SNDK", "name": "Sandisk Corporation", "price": 740.83 }, + { "symbol": "WYNN", "name": "Wynn Resorts", "price": 111.25 }, + { "symbol": "TSCO", "name": "Tractor Supply", "price": 52.80 }, + { "symbol": "AMGN", "name": "Amgen", "price": 358.00 }, + { "symbol": "TSLA", "name": "Tesla Inc.", "price": 366.59 }, + { "symbol": "GDDY", "name": "GoDaddy", "price": 83.65 }, + { "symbol": "SBUX", "name": "Starbucks", "price": 105.29 }, + { "symbol": "KVUE", "name": "Kenvue", "price": 15.87 }, + { "symbol": "META", "name": "Meta Platforms", "price": 720.15 }, + { "symbol": "DLTR", "name": "Dollar Tree", "price": 169.51 }, + { "symbol": "ABBV", "name": "AbbVie", "price": 275.17 }, + { "symbol": "HUBB", "name": "Hubbell Incorporated", "price": 512.46 }, + { "symbol": "JKHY", "name": "Jack Henry & Associates", "price": 180.79 }, + { "symbol": "FSLR", "name": "First Solar", "price": 235.26 }, + { "symbol": "FTNT", "name": "Fortinet", "price": 101.07 }, + { "symbol": "EPAM", "name": "EPAM Systems", "price": 180.79 }, + { "symbol": "POOL", "name": "Pool Corporation", "price": 315.77 }, + { "symbol": "MCHP", "name": "Microchip Technology", "price": 67.75 }, + { "symbol": "VRSK", "name": "Verisk Analytics", "price": 174.19 }, + { "symbol": "MRNA", "name": "Moderna", "price": 31.99 }, + { "symbol": "HOOD", "name": "Robinhood Markets Inc.", "price": 51.69 }, + { "symbol": "VRSN", "name": "Verisign", "price": 231.93 }, + { "symbol": "AMAT", "name": "Applied Materials", "price": 400.38 }, + { "symbol": "MDLZ", "name": "Mondelez International", "price": 66.40 }, + { "symbol": "PCAR", "name": "Paccar", "price": 121.81 }, + { "symbol": "NDAQ", "name": "Nasdaq Inc.", "price": 79.17 }, + { "symbol": "INTU", "name": "Intuit", "price": 422.34 }, + { "symbol": "HSIC", "name": "Henry Schein", "price": 85.34 }, + { "symbol": "FISV", "name": "Fiserv Inc.", "price": 65.42 }, + { "symbol": "CSGP", "name": "CoStar Group", "price": 42.14 }, + { "symbol": "DDOG", "name": "Datadog", "price": 140.83 }, + { "symbol": "BLDR", "name": "Builders FirstSource", "price": 131.69 }, + { "symbol": "AXON", "name": "Axon Enterprise", "price": 496.46 }, + { "symbol": "ALGN", "name": "Align Technology", "price": 185.84 }, + { "symbol": "FICO", "name": "Fair Isaac", "price": 1531.67 }, + { "symbol": "FITB", "name": "Fifth Third Bancorp", "price": 54.20 }, + { "symbol": "NTAP", "name": "NetApp", "price": 112.35 }, + { "symbol": "FOXA", "name": "Fox Corporation (Class A)", "price": 58.78 }, + { "symbol": "QCOM", "name": "Qualcomm", "price": 152.02 }, + { "symbol": "VTRS", "name": "Viatris", "price": 17.02 }, + { "symbol": "LRCX", "name": "Lam Research", "price": 258.37 }, + { "symbol": "PLTR", "name": "Palantir Technologies", "price": 124.17 }, + { "symbol": "PANW", "name": "Palo Alto Networks", "price": 176.63 }, + { "symbol": "NFLX", "name": "Netflix", "price": 66.23 }, + { "symbol": "KLAC", "name": "KLA Corporation", "price": 1490.09 }, + { "symbol": "NVDA", "name": "Nvidia", "price": 203.67 }, + { "symbol": "IBKR", "name": "Interactive Brokers Group", "price": 63.52 }, + { "symbol": "CRWD", "name": "CrowdStrike", "price": 418.49 }, + { "symbol": "ULTA", "name": "Ulta Beauty", "price": 580.07 }, + { "symbol": "JBHT", "name": "J.B. Hunt", "price": 240.95 }, + { "symbol": "SMCI", "name": "Supermicro", "price": 32.34 }, + { "symbol": "NXPI", "name": "NXP Semiconductors", "price": 232.52 }, + { "symbol": "VRTX", "name": "Vertex Pharmaceuticals", "price": 461.97 }, + { "symbol": "MTCH", "name": "Match Group", "price": 31.01 }, + { "symbol": "CBOE", "name": "Cboe Global Markets", "price": 249.25 }, + { "symbol": "CPRT", "name": "Copart", "price": 40.68 }, + { "symbol": "VICI", "name": "Vici Properties", "price": 30.56 }, + { "symbol": "CHRW", "name": "C.H. Robinson", "price": 177.08 }, + { "symbol": "INTC", "name": "Intel", "price": 39.96 }, + { "symbol": "ROST", "name": "Ross Stores", "price": 201.01 }, + { "symbol": "GEHC", "name": "GE HealthCare", "price": 92.85 }, + { "symbol": "SCHW", "name": "Charles Schwab Corporation", "price": 81.80 }, + { "symbol": "CVNA", "name": "Carvana Co.", "price": 375.66 }, + { "symbol": "IDXX", "name": "Idexx Laboratories", "price": 872.55 }, + { "symbol": "INCY", "name": "Incyte", "price": 113.76 }, + { "symbol": "GNRC", "name": "Generac", "price": 213.47 }, + { "symbol": "CPAY", "name": "Corpay", "price": 395.18 }, + { "symbol": "REGN", "name": "Regeneron Pharmaceuticals", "price": 683.79 }, + { "symbol": "CTAS", "name": "Cintas", "price": 231.48 }, + { "symbol": "FAST", "name": "Fastenal", "price": 38.50 }, + { "symbol": "AMZN", "name": "Amazon", "price": 199.96 }, + { "symbol": "ZBRA", "name": "Zebra Technologies", "price": 273.76 }, + { "symbol": "ODFL", "name": "Old Dominion", "price": 187.76 }, + { "symbol": "TTWO", "name": "Take-Two Interactive", "price": 171.95 }, + { "symbol": "CTRA", "name": "Coterra", "price": 29.69 }, + { "symbol": "LDOS", "name": "Leidos", "price": 169.83 }, + { "symbol": "ARES", "name": "Ares Management Corporation", "price": 129.38 }, + { "symbol": "CINF", "name": "Cincinnati Financial", "price": 166.18 }, + { "symbol": "ANET", "name": "Arista Networks", "price": 145.62 }, + { "symbol": "AMCR", "name": "Amcor", "price": 42.20 }, + { "symbol": "HBAN", "name": "Huntington Bancshares", "price": 22.01 }, + { "symbol": "EVRG", "name": "Evergy", "price": 89.25 }, + { "symbol": "ABNB", "name": "Airbnb", "price": 116.35 }, + { "symbol": "DASH", "name": "DoorDash", "price": 162.34 }, + { "symbol": "COIN", "name": "Coinbase Global", "price": 136.11 }, + { "symbol": "CIEN", "name": "Ciena Corporation", "price": 241.53 }, + { "symbol": "FANG", "name": "Diamondback Energy", "price": 159.90 }, + { "symbol": "PSKY", "name": "Paramount Skydance Corp", "price": 10.08 }, + { "symbol": "ORLY", "name": "O'Reilly Auto Parts", "price": 88.49 }, + { "symbol": "SBAC", "name": "SBA Communications", "price": 200.89 }, + { "symbol": "ACGL", "name": "Arch Capital Group", "price": 92.91 }, + { "symbol": "CTSH", "name": "Cognizant", "price": 65.25 }, + { "symbol": "VLTO", "name": "Veralto", "price": 101.60 }, + { "symbol": "MPWR", "name": "Monolithic Power Systems", "price": 1111.92 }, + { "symbol": "PODD", "name": "Insulet Corporation", "price": 277.32 }, + { "symbol": "MRSH", "name": "Marsh & McLennan Companies Inc.", "price": 170.53 }, + { "symbol": "PAYC", "name": "Paycom", "price": 121.42 }, + { "symbol": "NTRS", "name": "Northern Trust", "price": 141.13 }, + { "symbol": "ADSK", "name": "Autodesk", "price": 221.31 }, + { "symbol": "MSCI", "name": "MSCI", "price": 407.02 }, + { "symbol": "ORCL", "name": "Oracle Corporation", "price": 183.95 }, + { "symbol": "ERIE", "name": "Erie Indemnity", "price": 344.39 }, + { "symbol": "TECH", "name": "Bio-Techne", "price": 51.89 }, + { "symbol": "TRMB", "name": "Trimble Inc.", "price": 69.10 }, + { "symbol": "EBAY", "name": "eBay", "price": 72.07 }, + { "symbol": "INVH", "name": "Invitation Homes", "price": 25.89 }, + { "symbol": "NDSN", "name": "Nordson Corporation", "price": 307.32 }, + { "symbol": "DECK", "name": "Deckers Brands", "price": 104.47 }, + { "symbol": "ADBE", "name": "Adobe Inc.", "price": 303.30 }, + { "symbol": "SNPS", "name": "Synopsys", "price": 453.75 }, + { "symbol": "CHTR", "name": "Charter Communications", "price": 307.39 }, + { "symbol": "STLD", "name": "Steel Dynamics", "price": 181.89 }, + { "symbol": "BIIB", "name": "Biogen", "price": 211.35 }, + { "symbol": "TRGP", "name": "Targa Resources", "price": 232.83 }, + { "symbol": "SOLV", "name": "Solventum", "price": 63.49 }, + { "symbol": "TROW", "name": "T. Rowe Price", "price": 95.44 }, + { "symbol": "AVGO", "name": "Broadcom", "price": 380.85 }, + { "symbol": "CSCO", "name": "Cisco", "price": 74.31 }, + { "symbol": "CTVA", "name": "Corteva", "price": 68.47 }, + { "symbol": "EXPD", "name": "Expeditors International", "price": 163.00 }, + { "symbol": "EXPE", "name": "Expedia Group", "price": 325.05 }, + { "symbol": "AKAM", "name": "Akamai Technologies", "price": 95.17 }, + { "symbol": "CBRE", "name": "CBRE Group", "price": 119.50 }, + { "symbol": "FFIV", "name": "F5 Inc.", "price": 278.60 }, + { "symbol": "MSFT", "name": "Microsoft", "price": 415.20 }, + { "symbol": "COST", "name": "Costco", "price": 934.12 }, + { "symbol": "NCLH", "name": "Norwegian Cruise Line Holdings", "price": 23.39 }, + { "symbol": "KEYS", "name": "Keysight Technologies", "price": 206.83 }, + { "symbol": "SPGI", "name": "S&P Global", "price": 412.77 }, + { "symbol": "UBER", "name": "Uber", "price": 79.67 }, + { "symbol": "BKNG", "name": "Booking Holdings", "price": 4282.77 }, + { "symbol": "DELL", "name": "Dell Technologies", "price": 146.30 }, + { "symbol": "DXCM", "name": "Dexcom", "price": 58.22 }, + { "symbol": "PYPL", "name": "PayPal", "price": 33.15 }, + { "symbol": "GOOG", "name": "Alphabet Inc. (Class C)", "price": 247.06 }, + { "symbol": "BALL", "name": "Ball Corporation", "price": 76.36 }, + { "symbol": "WELL", "name": "Welltower", "price": 201.33 }, + { "symbol": "MNST", "name": "Monster Beverage", "price": 78.98 }, + { "symbol": "OTIS", "name": "Otis Worldwide", "price": 89.40 }, + { "symbol": "SWKS", "name": "Skyworks Solutions", "price": 63.24 }, + { "symbol": "GRMN", "name": "Garmin", "price": 216.38 }, + { "symbol": "WDAY", "name": "Workday Inc.", "price": 101.25 }, + { "symbol": "APTV", "name": "Aptiv", "price": 86.79 }, + { "symbol": "RVTY", "name": "Revvity", "price": 80.62 }, + { "symbol": "TMUS", "name": "T-Mobile US", "price": 190.57 }, + { "symbol": "LULU", "name": "Lululemon Athletica", "price": 153.22 }, + { "symbol": "HOLX", "name": "Hologic", "price": 66.06 }, + { "symbol": "NWSA", "name": "News Corp (Class A)", "price": 19.59 }, + { "symbol": "PAYX", "name": "Paychex", "price": 91.49 }, + { "symbol": "CDNS", "name": "Cadence Design Systems", "price": 261.07 }, + { "symbol": "ALLE", "name": "Allegion", "price": 180.46 }, + { "symbol": "GILD", "name": "Gilead Sciences", "price": 149.07 }, + { "symbol": "EQIX", "name": "Equinix", "price": 901.41 }, + { "symbol": "ISRG", "name": "Intuitive Surgical", "price": 580.56 } ] } diff --git a/src/main/resources/styles.css b/src/main/resources/styles.css index 8eadfc4..b13aef1 100644 --- a/src/main/resources/styles.css +++ b/src/main/resources/styles.css @@ -2322,4 +2322,179 @@ .light-mode .ingame-settings-close-button:hover { -fx-background-color: #1296ec; -fx-effect: dropshadow(gaussian, rgba(10, 132, 217, 0.45), 10, 0.3, 0, 0); -} \ No newline at end of file +} +/* ======================================================== + * In-game quit confirmation dialog (dark mode defaults). + * Mirrors the .ingame-settings-* palette so the two overlays + * feel like they belong to the same family. + * ======================================================== */ + +.quit-dialog-overlay-dimmer { + -fx-background-color: rgba(0, 0, 0, 0.55); + -fx-alignment: center; +} + +.quit-dialog-panel { + -fx-background-color: rgba(15, 22, 38, 0.98); + -fx-background-radius: 14; + -fx-border-color: rgba(0, 212, 255, 0.35); + -fx-border-radius: 14; + -fx-border-width: 1.5; + -fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.55), 24, 0.3, 0, 6); +} + +.quit-dialog-title { + -fx-font-family: "Aptos"; + -fx-font-weight: bold; + -fx-font-size: 24px; + -fx-text-fill: #f0f4ff; +} + +.quit-dialog-description { + -fx-font-family: "Aptos"; + -fx-font-size: 14px; + -fx-text-fill: #cdd5ed; +} + +.quit-dialog-button { + -fx-background-color: rgba(20, 28, 46, 0.85); + -fx-background-radius: 8; + -fx-border-color: rgba(0, 212, 255, 0.25); + -fx-border-radius: 8; + -fx-border-width: 1.2; + -fx-padding: 11 18; + -fx-font-family: "Aptos"; + -fx-font-weight: bold; + -fx-font-size: 15px; + -fx-text-fill: #f0f4ff; + -fx-cursor: hand; +} + +.quit-dialog-button:hover { + -fx-background-color: rgba(0, 212, 255, 0.12); + -fx-border-color: rgba(0, 212, 255, 0.6); +} + +.quit-dialog-button-primary { + -fx-background-color: rgba(0, 212, 255, 0.85); + -fx-border-color: #00d4ff; + -fx-text-fill: #07142a; +} + +.quit-dialog-button-primary:hover { + -fx-background-color: #00e6ff; + -fx-effect: dropshadow(gaussian, rgba(0, 212, 255, 0.35), 10, 0.3, 0, 0); +} + +.quit-dialog-button-danger { + -fx-border-color: rgba(255, 99, 132, 0.55); + -fx-text-fill: #ffb3c1; +} + +.quit-dialog-button-danger:hover { + -fx-background-color: rgba(255, 99, 132, 0.15); + -fx-border-color: rgba(255, 99, 132, 0.85); + -fx-text-fill: #ffd9e1; +} + +.quit-dialog-close-button { + -fx-pref-width: 36px; + -fx-pref-height: 36px; + -fx-background-color: rgba(0, 212, 255, 0.85); + -fx-text-fill: #07142a; + -fx-font-weight: bold; + -fx-font-size: 16px; + -fx-background-radius: 18px; + -fx-border-radius: 18px; + -fx-cursor: hand; +} + +.quit-dialog-close-button:hover { + -fx-background-color: #00e6ff; + -fx-effect: dropshadow(gaussian, rgba(0, 212, 255, 0.45), 10, 0.3, 0, 0); +} + +.quit-dialog-status { + -fx-font-family: "Aptos"; + -fx-font-size: 13px; + -fx-text-fill: #4ad9ff; + -fx-padding: 2 0 0 2; +} + +.quit-dialog-status-error { + -fx-text-fill: #ff8aa3; +} + +/* ======================================================== + * In-game quit confirmation dialog - light mode overrides. + * ======================================================== */ + +.light-mode .quit-dialog-overlay-dimmer { + -fx-background-color: rgba(10, 27, 52, 0.35); +} + +.light-mode .quit-dialog-panel { + -fx-background-color: #e8eef8; + -fx-border-color: rgba(10, 132, 217, 0.4); + -fx-effect: dropshadow(gaussian, rgba(10, 27, 52, 0.25), 24, 0.3, 0, 6); +} + +.light-mode .quit-dialog-title { + -fx-text-fill: #0f1b34; +} + +.light-mode .quit-dialog-description { + -fx-text-fill: #2a3654; +} + +.light-mode .quit-dialog-button { + -fx-background-color: #eef3fa; + -fx-border-color: rgba(10, 132, 217, 0.35); + -fx-text-fill: #0f1b34; +} + +.light-mode .quit-dialog-button:hover { + -fx-background-color: rgba(10, 132, 217, 0.10); + -fx-border-color: #0a84d9; +} + +.light-mode .quit-dialog-button-primary { + -fx-background-color: #0a84d9; + -fx-border-color: #0a84d9; + -fx-text-fill: #ffffff; +} + +.light-mode .quit-dialog-button-primary:hover { + -fx-background-color: #1296ec; + -fx-effect: dropshadow(gaussian, rgba(10, 132, 217, 0.35), 10, 0.3, 0, 0); +} + +.light-mode .quit-dialog-button-danger { + -fx-background-color: #fdeaee; + -fx-border-color: rgba(204, 36, 76, 0.55); + -fx-text-fill: #b1284c; +} + +.light-mode .quit-dialog-button-danger:hover { + -fx-background-color: #f9d6dd; + -fx-border-color: #cc244c; + -fx-text-fill: #8a1e3b; +} + +.light-mode .quit-dialog-close-button { + -fx-background-color: #0a84d9; + -fx-text-fill: #ffffff; +} + +.light-mode .quit-dialog-close-button:hover { + -fx-background-color: #1296ec; + -fx-effect: dropshadow(gaussian, rgba(10, 132, 217, 0.45), 10, 0.3, 0, 0); +} + +.light-mode .quit-dialog-status { + -fx-text-fill: #0a84d9; +} + +.light-mode .quit-dialog-status-error { + -fx-text-fill: #b1284c; +}