diff --git a/HMCL/src/main/java/com/jfoenix/skins/JFXButtonSkin.java b/HMCL/src/main/java/com/jfoenix/skins/JFXButtonSkin.java index b8d5dc2207..65aa2c2b4d 100644 --- a/HMCL/src/main/java/com/jfoenix/skins/JFXButtonSkin.java +++ b/HMCL/src/main/java/com/jfoenix/skins/JFXButtonSkin.java @@ -23,6 +23,10 @@ import javafx.scene.control.Label; import javafx.scene.effect.DropShadow; import javafx.scene.layout.Background; import javafx.scene.layout.BackgroundFill; +import javafx.scene.layout.Border; +import javafx.scene.layout.BorderStroke; +import javafx.scene.layout.BorderStrokeStyle; +import javafx.scene.layout.BorderWidths; import javafx.scene.layout.CornerRadii; import javafx.scene.layout.StackPane; import javafx.scene.paint.Color; @@ -30,8 +34,20 @@ import javafx.scene.shape.Shape; import javafx.util.Duration; import org.jackhuang.hmcl.ui.FXUtils; +/// JFoenix button skin with Anthropic skill tactile press feedback. +/// +/// Upstream skin rewrote Modena's cyan `:pressed` fill to transparent and thereby +/// also ate author CSS press backgrounds. This build keeps CSS fills, maps the +/// Modena cyan to a skill wash, and paints an unmistakable armed overlay +/// (background + border + opacity) on every JFXButton. public class JFXButtonSkin extends ButtonSkin { + private static final Color SKILL_PRESS_FILL = Color.web("#D97757", 0.40); + private static final Color SKILL_PRESS_BORDER = Color.web("#C96442"); + private static final Color SKILL_PRESS_DIM = Color.web("#141413", 0.28); + private final StackPane buttonContainer = new StackPane(); + /// Full-bleed press veil — opacity toggled from armed/pressed, independent of CSS. + private final StackPane pressOverlay = new StackPane(); private final JFXRippler buttonRippler = new JFXRippler(new StackPane()) { protected Node getMask() { StackPane mask = new StackPane(); @@ -64,6 +80,17 @@ public class JFXButtonSkin extends ButtonSkin { public JFXButtonSkin(JFXButton button) { super(button); + + this.pressOverlay.setMouseTransparent(true); + this.pressOverlay.setOpacity(0); + this.pressOverlay.setBackground(new Background(new BackgroundFill( + SKILL_PRESS_FILL, this.defaultRadii, Insets.EMPTY))); + this.pressOverlay.setBorder(new Border(new BorderStroke( + SKILL_PRESS_BORDER, + BorderStrokeStyle.SOLID, + this.defaultRadii, + new BorderWidths(2.0)))); + this.getSkinnable().armedProperty().addListener((o, oldVal, newVal) -> { if (newVal) { this.releaseManualRippler = this.buttonRippler.createManualRipple(); @@ -81,21 +108,23 @@ public class JFXButtonSkin extends ButtonSkin { this.clickedAnimation.play(); } } - + this.updatePressOverlay(button); }); - this.buttonContainer.getChildren().add(this.buttonRippler); + this.buttonContainer.getChildren().addAll(this.buttonRippler, this.pressOverlay); button.buttonTypeProperty().addListener((o, oldVal, newVal) -> this.updateButtonType(newVal)); button.setOnMousePressed((e) -> { if (this.clickedAnimation != null) { this.clickedAnimation.setRate(1.0F); this.clickedAnimation.play(); } + this.updatePressOverlay(button); }); button.setOnMouseReleased((e) -> { if (this.clickedAnimation != null) { this.clickedAnimation.setRate(-1.0F); this.clickedAnimation.play(); } + this.updatePressOverlay(button); }); ReadOnlyBooleanProperty focusVisibleProperty = FXUtils.focusVisibleProperty(button); @@ -111,20 +140,34 @@ public class JFXButtonSkin extends ButtonSkin { this.buttonRippler.hideOverlay(); } }); - button.pressedProperty().addListener((o, oldVal, newVal) -> this.buttonRippler.hideOverlay()); + button.pressedProperty().addListener((o, oldVal, newVal) -> { + this.buttonRippler.hideOverlay(); + this.updatePressOverlay(button); + }); button.setPickOnBounds(false); this.buttonContainer.setPickOnBounds(false); this.buttonContainer.shapeProperty().bind(this.getSkinnable().shapeProperty()); this.buttonContainer.borderProperty().bind(this.getSkinnable().borderProperty()); + // Mirror CSS background onto the container. Never call button.setBackground() here — + // that used to stomp author :pressed fills (and wiped Modena cyan to transparent). this.buttonContainer.backgroundProperty().bind(Bindings.createObjectBinding(() -> { - if (button.getBackground() == null || this.isJavaDefaultBackground(button.getBackground()) || this.isJavaDefaultClickedBackground(button.getBackground())) { - button.setBackground(new Background(new BackgroundFill(Color.TRANSPARENT, this.defaultRadii, null))); + Background background = button.getBackground(); + if (background == null || background.getFills().isEmpty() || this.isJavaDefaultBackground(background)) { + return new Background(new BackgroundFill(Color.TRANSPARENT, this.defaultRadii, Insets.EMPTY)); + } + if (this.isJavaDefaultClickedBackground(background)) { + CornerRadii radii = background.getFills().get(0).getRadii(); + return new Background(new BackgroundFill(SKILL_PRESS_FILL, + radii != null ? radii : this.defaultRadii, Insets.EMPTY)); } - try { - return new Background(new BackgroundFill(this.getSkinnable().getBackground() != null ? this.getSkinnable().getBackground().getFills().get(0).getFill() : Color.TRANSPARENT, this.getSkinnable().getBackground() != null ? this.getSkinnable().getBackground().getFills().get(0).getRadii() : this.defaultRadii, Insets.EMPTY)); + BackgroundFill fill = background.getFills().get(0); + return new Background(new BackgroundFill( + fill.getFill(), + fill.getRadii() != null ? fill.getRadii() : this.defaultRadii, + Insets.EMPTY)); } catch (Exception var3) { - return this.getSkinnable().getBackground(); + return background; } }, this.getSkinnable().backgroundProperty())); button.ripplerFillProperty().addListener((o, oldVal, newVal) -> this.buttonRippler.setRipplerFill(newVal)); @@ -134,6 +177,40 @@ public class JFXButtonSkin extends ButtonSkin { this.updateButtonType(button.getButtonType()); this.updateChildren(); + this.updatePressOverlay(button); + } + + /// Shows a hard-to-miss press veil: orange wash + warm border; dimming on raised CTAs. + private void updatePressOverlay(JFXButton button) { + boolean active = !button.isDisabled() && (button.isArmed() || button.isPressed()); + boolean raised = button.getButtonType() == JFXButton.ButtonType.RAISED + || button.getStyleClass().contains("jfx-button-raised") + || button.getStyleClass().contains("launch-button") + || button.getStyleClass().contains("menu-button"); + + CornerRadii radii = this.defaultRadii; + Background bg = button.getBackground(); + if (bg != null && !bg.getFills().isEmpty() && bg.getFills().get(0).getRadii() != null) { + radii = bg.getFills().get(0).getRadii(); + } + + Color fill = raised ? SKILL_PRESS_DIM : SKILL_PRESS_FILL; + this.pressOverlay.setBackground(new Background(new BackgroundFill(fill, radii, Insets.EMPTY))); + this.pressOverlay.setBorder(new Border(new BorderStroke( + SKILL_PRESS_BORDER, + BorderStrokeStyle.SOLID, + radii, + new BorderWidths(active ? 2.0 : 0.0)))); + this.pressOverlay.setOpacity(active ? 1.0 : 0.0); + // Drive opacity via style class so CSS keeps ownership (no setOpacity stomping). + final String armedClass = "anthropic-armed"; + if (active) { + if (!button.getStyleClass().contains(armedClass)) { + button.getStyleClass().add(armedClass); + } + } else { + button.getStyleClass().remove(armedClass); + } } protected void updateChildren() { @@ -186,6 +263,7 @@ public class JFXButtonSkin extends ButtonSkin { private boolean isJavaDefaultClickedBackground(Background background) { try { + // Modena default armed cyan — replace with skill wash instead of wiping to transparent. return "0x039ed3ff".equals(background.getFills().get(0).getFill().toString()); } catch (Exception var3) { return false; diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/Metadata.java b/HMCL/src/main/java/org/jackhuang/hmcl/Metadata.java index 78b69a8bd8..ac911a484d 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/Metadata.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/Metadata.java @@ -19,8 +19,8 @@ public final class Metadata { private Metadata() { } - public static final String NAME = "HMCL"; - public static final String FULL_NAME = "Hello Minecraft! Launcher"; + public static final String NAME = "TDLM"; + public static final String FULL_NAME = "TDLM"; public static final String VERSION = System.getProperty("hmcl.version.override", JarUtils.getAttribute("hmcl.version", "@develop@")); /// Explicit Application User Model ID used for Windows taskbar grouping and pinning. diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/FontManager.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/FontManager.java index 2627ff5be2..49a256b930 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/FontManager.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/FontManager.java @@ -36,7 +36,22 @@ public final class FontManager { public static final double DEFAULT_FONT_SIZE = 12.0f; + /// Bundled Anthropic UI title face (Source Han Sans SC Bold subset). + public static final String TITLE_FONT_FAMILY = "Source Han Sans SC"; + + /// Bundled Anthropic UI body face (Source Han Serif SC Regular subset). + public static final String BODY_FONT_FAMILY = "Source Han Serif SC"; + + private static final String BUNDLED_TITLE_FONT = "/assets/fonts/SourceHanSansSC-Bold.otf"; + private static final String BUNDLED_BODY_FONT = "/assets/fonts/SourceHanSerifSC-Regular.otf"; + + private static volatile boolean bundledFontsLoaded; + private static volatile @Nullable String loadedTitleFamily; + private static volatile @Nullable String loadedBodyFamily; + private static final Lazy DEFAULT_FONT = new Lazy<>(() -> { + ensureBundledFontsLoaded(); + Font font; // Recommended @@ -70,6 +85,11 @@ public final class FontManager { return font; } + // Anthropic UI hard-bind: bundled Source Han Serif body face + if (loadedBodyFamily != null) { + return Font.font(loadedBodyFamily, DEFAULT_FONT_SIZE); + } + // Default String fcMatchPattern; @@ -83,8 +103,62 @@ public final class FontManager { private static final ObjectProperty font = new SimpleObjectProperty<>(); static { + ensureBundledFontsLoaded(); updateFont(); - LOG.info("Font: " + (font.get() != null ? font.get().family() : "System")); + LOG.info("Font: " + (font.get() != null ? font.get().family() : "System") + + ", title=" + Objects.requireNonNullElse(loadedTitleFamily, "unavailable") + + ", body=" + Objects.requireNonNullElse(loadedBodyFamily, "unavailable")); + } + + /// Registers the bundled Source Han subsets so CSS family names resolve. + public static void ensureBundledFontsLoaded() { + if (bundledFontsLoaded) { + return; + } + synchronized (FontManager.class) { + if (bundledFontsLoaded) { + return; + } + loadedTitleFamily = loadBundledFont(BUNDLED_TITLE_FONT, TITLE_FONT_FAMILY); + loadedBodyFamily = loadBundledFont(BUNDLED_BODY_FONT, BODY_FONT_FAMILY); + bundledFontsLoaded = true; + } + } + + /// Returns the resolved title font family after bundled registration, or the hard-bind name. + public static String getTitleFontFamily() { + ensureBundledFontsLoaded(); + return Objects.requireNonNullElse(loadedTitleFamily, TITLE_FONT_FAMILY); + } + + /// Returns the resolved body font family after bundled registration, or the hard-bind name. + public static String getBodyFontFamily() { + ensureBundledFontsLoaded(); + return Objects.requireNonNullElse(loadedBodyFamily, BODY_FONT_FAMILY); + } + + private static @Nullable String loadBundledFont(String resourcePath, String expectedFamily) { + try (var input = FontManager.class.getResourceAsStream(resourcePath)) { + if (input == null) { + LOG.warning("Bundled font missing: " + resourcePath); + return null; + } + Font loaded = Font.loadFont(input, DEFAULT_FONT_SIZE); + if (loaded == null) { + LOG.warning("Failed to decode bundled font: " + resourcePath); + return null; + } + String family = loaded.getFamily(); + LOG.info("Loaded bundled font " + resourcePath + " as family '" + family + "'"); + if (!expectedFamily.equalsIgnoreCase(family)) { + LOG.warning("Bundled font family mismatch for " + resourcePath + + ": expected '" + expectedFamily + "', got '" + family + "'"); + } + return family; + } catch (Exception e) { + LOG.warning("Failed to load bundled font " + resourcePath, e); + return null; + } } private static void updateFont() { @@ -96,7 +170,7 @@ public final class FontManager { if (fontFamily == null) { Font defaultFont = DEFAULT_FONT.get(); - font.set(defaultFont != null ? new FontReference(defaultFont) : null); + font.set(defaultFont != null ? new FontReference(defaultFont) : new FontReference(BODY_FONT_FAMILY)); } else { font.set(new FontReference(fontFamily)); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/StyleSheets.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/StyleSheets.java index 7945ddfbce..d8c354b108 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/StyleSheets.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/StyleSheets.java @@ -10,7 +10,6 @@ import javafx.scene.paint.Color; import org.glavo.monetfx.Brightness; import org.glavo.monetfx.ColorRole; import org.glavo.monetfx.ColorScheme; -import org.jackhuang.hmcl.theme.ResolvedTheme; import org.jackhuang.hmcl.theme.ThemeColor; import org.jackhuang.hmcl.theme.Themes; @@ -26,6 +25,8 @@ public final class StyleSheets { private static final int FONT_STYLE_SHEET_INDEX = 0; private static final int THEME_STYLE_SHEET_INDEX = 1; private static final int BRIGHTNESS_SHEET_INDEX = 2; + /// Loaded after root.css so solid :pressed fills beat Monet / Modena cyan. + private static final int PRESS_FEEDBACK_SHEET_INDEX = 4; private static final ObservableList stylesheets; @@ -34,9 +35,15 @@ public final class StyleSheets { getFontStyleSheet(), getThemeStyleSheet(), getBrightnessStyleSheet(), - "/assets/css/root.css" + "/assets/css/root.css", + "/assets/css/press-feedback.css" }; stylesheets = FXCollections.observableList(Arrays.asList(array)); + // Keep the constant honest for future inserts ahead of press-feedback. + if (stylesheets.size() <= PRESS_FEEDBACK_SHEET_INDEX + || !"/assets/css/press-feedback.css".equals(stylesheets.get(PRESS_FEEDBACK_SHEET_INDEX))) { + throw new IllegalStateException("press-feedback.css must remain the last stylesheet"); + } FontManager.fontProperty().addListener(o -> stylesheets.set(FONT_STYLE_SHEET_INDEX, getFontStyleSheet())); Themes.colorSchemeProperty().addListener(o -> { @@ -50,51 +57,72 @@ public final class StyleSheets { } private static String getFontStyleSheet() { - final String defaultCss = "/assets/css/font.css"; + FontManager.ensureBundledFontsLoaded(); + final FontManager.FontReference font = FontManager.getFont(); - - if (font == null || "System".equals(font.family())) - return defaultCss; - - String fontFamily = font.family(); - String style = font.style(); + String bodyFamily = FontManager.getBodyFontFamily(); + String titleFamily = FontManager.getTitleFontFamily(); String weight = null; String posture = null; - if (style != null) { - style = style.toLowerCase(Locale.ROOT); + // Explicit user / env override replaces body face only; titles stay Source Han Sans Bold. + if (font != null && !"System".equals(font.family()) + && !FontManager.BODY_FONT_FAMILY.equalsIgnoreCase(font.family()) + && !FontManager.TITLE_FONT_FAMILY.equalsIgnoreCase(font.family())) { + bodyFamily = font.family(); + String style = font.style(); + if (style != null) { + style = style.toLowerCase(Locale.ROOT); - if (style.contains("thin")) - weight = "100"; - else if (style.contains("extralight") || style.contains("extra light") || style.contains("ultralight") | style.contains("ultra light")) - weight = "200"; - else if (style.contains("medium")) - weight = "500"; - else if (style.contains("semibold") || style.contains("semi bold") || style.contains("demibold") || style.contains("demi bold")) - weight = "600"; - else if (style.contains("extrabold") || style.contains("extra bold") || style.contains("ultrabold") || style.contains("ultra bold")) - weight = "800"; - else if (style.contains("black") || style.contains("heavy")) - weight = "900"; - else if (style.contains("light")) - weight = "lighter"; - else if (style.contains("bold")) - weight = "bold"; + if (style.contains("thin")) + weight = "100"; + else if (style.contains("extralight") || style.contains("extra light") || style.contains("ultralight") | style.contains("ultra light")) + weight = "200"; + else if (style.contains("medium")) + weight = "500"; + else if (style.contains("semibold") || style.contains("semi bold") || style.contains("demibold") || style.contains("demi bold")) + weight = "600"; + else if (style.contains("extrabold") || style.contains("extra bold") || style.contains("ultrabold") || style.contains("ultra bold")) + weight = "800"; + else if (style.contains("black") || style.contains("heavy")) + weight = "900"; + else if (style.contains("light")) + weight = "lighter"; + else if (style.contains("bold")) + weight = "bold"; - posture = style.contains("italic") || style.contains("oblique") ? "italic" : null; + posture = style.contains("italic") || style.contains("oblique") ? "italic" : null; + } } StringBuilder builder = new StringBuilder(); builder.append(".root {"); - builder.append("-fx-font-family:\"").append(fontFamily).append("\";"); - + builder.append("-fx-font-family:\"").append(bodyFamily).append("\", \"Source Han Serif\", serif;"); if (weight != null) builder.append("-fx-font-weight:").append(weight).append(";"); - if (posture != null) builder.append("-fx-font-style:").append(posture).append(";"); + builder.append("}\n"); - builder.append('}'); + builder.append(""" + .jfx-decorator-title, + .jfx-tool-bar-second .label, + .title-label, + .class-title, + .class-title Text, + .jfx-layout-heading, + .launch-pane > .launch-button .label, + .announcement .title, + .method-title, + .radio-button-title-label, + .two-line-list-item > .title, + .advanced-list-item > .rippler-container > .container > .title, + .jfx-button-raised, + .jfx-button-raised * { + """); + builder.append("-fx-font-family:\"").append(titleFamily).append("\", \"Source Han Sans\", \"思源黑体\", sans-serif;"); + builder.append("-fx-font-weight:bold;"); + builder.append("}\n"); return toStyleSheetUri(builder.toString()); } @@ -121,13 +149,133 @@ public final class StyleSheets { .append(";\n"); } + private static void putHex(StringBuilder builder, String name, String hex) { + builder.append(" ").append(name).append(": ").append(hex).append(";\n"); + } + + /// Maps anthropic-style-cn skill base.css light/dark pairs onto Monet roles so existing CSS flips end-to-end. + private static void applySkillTokenOverrides(StringBuilder builder, boolean dark) { + // Skill accent (CTA / focus) — never Material blue. + final String accent = "#D97757"; + final String accentWarm = "#C96442"; + final String error = "#C0453A"; + final String onAccent = "#FAF9F5"; + + if (dark) { + putHex(builder, "-monet-surface", "#1A1916"); + putHex(builder, "-monet-background", "#1A1916"); + putHex(builder, "-monet-surface-bright", "#2C2B26"); + putHex(builder, "-monet-surface-dim", "#141413"); + putHex(builder, "-monet-surface-container-lowest", "#141413"); + putHex(builder, "-monet-surface-container-low", "#222119"); + putHex(builder, "-monet-surface-container", "#222119"); + putHex(builder, "-monet-surface-container-high", "#2C2B26"); + putHex(builder, "-monet-surface-container-highest", "#2C2B26"); + putHex(builder, "-monet-surface-variant", "#2C2B26"); + putHex(builder, "-monet-on-surface", "#EAE7DC"); + putHex(builder, "-monet-on-background", "#EAE7DC"); + putHex(builder, "-monet-on-surface-variant", "#9D9A91"); + putHex(builder, "-monet-outline", "#3A3830"); + putHex(builder, "-monet-outline-variant", "#2E2D27"); + putHex(builder, "-monet-inverse-surface", "#F5F3EC"); + putHex(builder, "-monet-inverse-on-surface", "#141413"); + putHex(builder, "-monet-secondary-container", "#2C2B26"); + putHex(builder, "-monet-on-secondary-container", "#EAE7DC"); + putHex(builder, "-anthropic-shadow", "rgba(0, 0, 0, 0.50)"); + putHex(builder, "-anthropic-shadow-strong", "rgba(0, 0, 0, 0.65)"); + putHex(builder, "-anthropic-scrollbar-track", "#222119"); + putHex(builder, "-anthropic-scrollbar-thumb", "#5C5A54"); + putHex(builder, "-anthropic-hover-wash", "rgba(234, 231, 220, 0.08)"); + putHex(builder, "-anthropic-press-wash", "rgba(234, 231, 220, 0.14)"); + } else { + putHex(builder, "-monet-surface", "#ECE9E0"); + putHex(builder, "-monet-background", "#ECE9E0"); + putHex(builder, "-monet-surface-bright", "#FDFCF8"); + putHex(builder, "-monet-surface-dim", "#D8D5CC"); + putHex(builder, "-monet-surface-container-lowest", "#FDFCF8"); + putHex(builder, "-monet-surface-container-low", "#F5F3EC"); + putHex(builder, "-monet-surface-container", "#F5F3EC"); + putHex(builder, "-monet-surface-container-high", "#FDFCF8"); + putHex(builder, "-monet-surface-container-highest", "#FDFCF8"); + putHex(builder, "-monet-surface-variant", "#E8E6DC"); + putHex(builder, "-monet-on-surface", "#141413"); + putHex(builder, "-monet-on-background", "#141413"); + putHex(builder, "-monet-on-surface-variant", "#6B6860"); + putHex(builder, "-monet-outline", "#D8D5CC"); + putHex(builder, "-monet-outline-variant", "#E8E6DC"); + putHex(builder, "-monet-inverse-surface", "#141413"); + putHex(builder, "-monet-inverse-on-surface", "#FAF9F5"); + putHex(builder, "-monet-secondary-container", "#E8E6DC"); + putHex(builder, "-monet-on-secondary-container", "#141413"); + putHex(builder, "-anthropic-shadow", "rgba(20, 20, 19, 0.10)"); + putHex(builder, "-anthropic-shadow-strong", "rgba(20, 20, 19, 0.16)"); + putHex(builder, "-anthropic-scrollbar-track", "#E8E6DC"); + putHex(builder, "-anthropic-scrollbar-thumb", "#9B9890"); + putHex(builder, "-anthropic-hover-wash", "rgba(20, 20, 19, 0.06)"); + putHex(builder, "-anthropic-press-wash", "rgba(20, 20, 19, 0.12)"); + } + + putHex(builder, "-monet-primary", accent); + putHex(builder, "-monet-primary-seed", accent); + putHex(builder, "-monet-on-primary", onAccent); + putHex(builder, "-monet-primary-container", accentWarm); + putHex(builder, "-monet-on-primary-container", onAccent); + putHex(builder, "-monet-primary-fixed", dark ? "#4A2E24" : "#F6E0D6"); + putHex(builder, "-monet-primary-fixed-dim", dark ? "#C96442" : accent); + putHex(builder, "-monet-on-primary-fixed", dark ? "#FAF9F5" : "#141413"); + putHex(builder, "-monet-on-primary-fixed-variant", dark ? "#EAE7DC" : "#6B6860"); + putHex(builder, "-monet-inverse-primary", accent); + putHex(builder, "-monet-surface-tint", accent); + + // Secondary / tertiary: warm orange family only — kill Monet teal/purple companions. + putHex(builder, "-monet-secondary", accentWarm); + putHex(builder, "-monet-on-secondary", onAccent); + putHex(builder, "-monet-secondary-fixed", dark ? "#3A2A22" : "#F0E4DC"); + putHex(builder, "-monet-secondary-fixed-dim", dark ? accentWarm : accent); + putHex(builder, "-monet-on-secondary-fixed", dark ? "#EAE7DC" : "#141413"); + putHex(builder, "-monet-on-secondary-fixed-variant", dark ? "#9D9A91" : "#6B6860"); + putHex(builder, "-monet-tertiary", accent); + putHex(builder, "-monet-on-tertiary", onAccent); + putHex(builder, "-monet-tertiary-container", accentWarm); + putHex(builder, "-monet-on-tertiary-container", onAccent); + putHex(builder, "-monet-tertiary-fixed", dark ? "#4A2E24" : "#F6E0D6"); + putHex(builder, "-monet-tertiary-fixed-dim", dark ? accentWarm : accent); + putHex(builder, "-monet-on-tertiary-fixed", dark ? "#FAF9F5" : "#141413"); + putHex(builder, "-monet-on-tertiary-fixed-variant", dark ? "#EAE7DC" : "#6B6860"); + + putHex(builder, "-monet-error", error); + putHex(builder, "-monet-on-error", onAccent); + putHex(builder, "-monet-error-container", dark ? "#4A1A1F" : "#FCEBEB"); + putHex(builder, "-monet-on-error-container", dark ? "#F9DEDC" : "#410E0B"); + + putHex(builder, "-anthropic-accent", accent); + putHex(builder, "-anthropic-accent-warm", accentWarm); + putHex(builder, "-anthropic-focus-ring", accent); + putHex(builder, "-anthropic-focus-glow", "rgba(217, 119, 87, 0.35)"); + putHex(builder, "-anthropic-primary-hover", dark ? "#E08A6C" : accentWarm); + putHex(builder, "-anthropic-primary-press", dark ? "#EFA08A" : "#B4573A"); + putHex(builder, "-anthropic-primary-container-hover", dark ? "#D97757" : "#B4573A"); + putHex(builder, "-anthropic-primary-container-press", dark ? "#E08A6C" : "#9A4A32"); + putHex(builder, "-anthropic-selected-wash", dark ? "rgba(217, 119, 87, 0.22)" : "rgba(217, 119, 87, 0.14)"); + // Stronger wash for tiny icon buttons where 6–8% alpha is invisible. + putHex(builder, "-anthropic-icon-hover-wash", dark ? "rgba(234, 231, 220, 0.14)" : "rgba(20, 20, 19, 0.10)"); + putHex(builder, "-anthropic-icon-press-wash", dark ? "rgba(217, 119, 87, 0.32)" : "rgba(217, 119, 87, 0.22)"); + + // Transparent helpers consumed by root.css + putHex(builder, "-monet-primary-transparent-50", accent + "80"); + putHex(builder, "-monet-primary-transparent-80", accent + "CC"); + putHex(builder, "-monet-surface-transparent-50", dark ? "#1A191680" : "#ECE9E080"); + putHex(builder, "-monet-surface-transparent-80", dark ? "#1A1916CC" : "#ECE9E0CC"); + putHex(builder, "-monet-surface-container-low-transparent-80", dark ? "#222119CC" : "#F5F3ECCC"); + putHex(builder, "-monet-secondary-container-transparent-50", dark ? "#2C2B2680" : "#E8E6DC80"); + putHex(builder, "-monet-secondary-container-transparent-80", dark ? "#2C2B26CC" : "#E8E6DCCC"); + putHex(builder, "-monet-on-surface-variant-transparent-38", dark ? "#9D9A9161" : "#6B686061"); + putHex(builder, "-monet-inverse-surface-transparent-80", dark ? "#F5F3ECCC" : "#141413CC"); + } + private static String getThemeStyleSheet() { - final String blueCss = "/assets/css/blue.css"; - - if (ResolvedTheme.DEFAULT.equals(Themes.getTheme())) - return blueCss; - ColorScheme scheme = Themes.getColorScheme(); + boolean dark = scheme.getBrightness() == Brightness.DARK; StringBuilder builder = new StringBuilder(); builder.append("* {\n"); @@ -147,6 +295,9 @@ public final class StyleSheets { addColor(builder, scheme, ColorRole.SECONDARY_CONTAINER, 0.8); addColor(builder, scheme, ColorRole.INVERSE_SURFACE, 0.8); + // Skill authority: overwrite Monet surfaces/text/borders/accent for light+dark. + applySkillTokenOverrides(builder, dark); + builder.append("}\n"); return toStyleSheetUri(builder.toString()); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/theme/ResolvedTheme.java b/HMCL/src/main/java/org/jackhuang/hmcl/theme/ResolvedTheme.java index ea2768830a..62bb963d78 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/theme/ResolvedTheme.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/theme/ResolvedTheme.java @@ -24,10 +24,19 @@ public record ResolvedTheme(ThemeColor primaryColorSeed, /// Converts these resolved values to a MonetFX color scheme. /// + /// Warm secondary / tertiary / neutral seeds keep Monet from inventing teal or purple + /// companions next to the orange accent; CSS + [SkillPalette] still pin exact skill hex. + /// /// @return the generated color scheme public ColorScheme toColorScheme() { + boolean darkNeutrals = brightness == Brightness.DARK; return ColorScheme.newBuilder() .setPrimaryColorSeed(primaryColorSeed.color()) + .setSecondaryColorSeed(SkillPalette.ACCENT_WARM) + .setTertiaryColorSeed(SkillPalette.ACCENT) + .setNeutralColorSeed(darkNeutrals ? SkillPalette.DARK_SURFACE : SkillPalette.LIGHT_SURFACE) + .setNeutralVariantColorSeed(darkNeutrals ? SkillPalette.DARK_OUTLINE : SkillPalette.LIGHT_OUTLINE) + .setErrorColorSeed(SkillPalette.ERROR) .setColorStyle(colorStyle) .setBrightness(brightness) .setSpecVersion(ColorSpecVersion.SPEC_2025) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/theme/SkillPalette.java b/HMCL/src/main/java/org/jackhuang/hmcl/theme/SkillPalette.java new file mode 100644 index 0000000000..2e9f501972 --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/theme/SkillPalette.java @@ -0,0 +1,91 @@ +// SPDX-FileCopyrightText: Copyright (C) 2025 The HMCL Authors +// SPDX-License-Identifier: GPL-3.0-or-later WITH AdditionRef-HMCL +package org.jackhuang.hmcl.theme; + +import javafx.scene.paint.Color; +import org.glavo.monetfx.Brightness; +import org.glavo.monetfx.ColorScheme; +import org.jetbrains.annotations.NotNullByDefault; + +/// Anthropic-style-cn skill light/dark palette used for JavaFX chrome that bypasses CSS. +/// +/// Monet's generated scheme from an orange seed still drifts pink / teal; these exact +/// skill tokens keep title bar, frame, and theme-color backgrounds aligned with +/// [org.jackhuang.hmcl.setting.StyleSheets] CSS overrides. +@NotNullByDefault +public final class SkillPalette { + public static final String ACCENT_HEX = "#D97757"; + public static final String ACCENT_WARM_HEX = "#C96442"; + public static final String ERROR_HEX = "#C0453A"; + public static final String ON_ACCENT_HEX = "#FAF9F5"; + + public static final Color ACCENT = Color.web(ACCENT_HEX); + public static final Color ACCENT_WARM = Color.web(ACCENT_WARM_HEX); + public static final Color ERROR = Color.web(ERROR_HEX); + public static final Color ON_ACCENT = Color.web(ON_ACCENT_HEX); + + public static final Color LIGHT_SURFACE = Color.web("#ECE9E0"); + public static final Color LIGHT_SURFACE_RAISED = Color.web("#F5F3EC"); + public static final Color LIGHT_SURFACE_OVERLAY = Color.web("#FDFCF8"); + public static final Color LIGHT_SURFACE_VARIANT = Color.web("#E8E6DC"); + public static final Color LIGHT_ON_SURFACE = Color.web("#141413"); + public static final Color LIGHT_ON_SURFACE_VARIANT = Color.web("#6B6860"); + public static final Color LIGHT_OUTLINE = Color.web("#D8D5CC"); + public static final Color LIGHT_OUTLINE_VARIANT = Color.web("#E8E6DC"); + + public static final Color DARK_SURFACE = Color.web("#1A1916"); + public static final Color DARK_SURFACE_RAISED = Color.web("#222119"); + public static final Color DARK_SURFACE_OVERLAY = Color.web("#2C2B26"); + public static final Color DARK_SURFACE_VARIANT = Color.web("#2C2B26"); + public static final Color DARK_ON_SURFACE = Color.web("#EAE7DC"); + public static final Color DARK_ON_SURFACE_VARIANT = Color.web("#9D9A91"); + public static final Color DARK_OUTLINE = Color.web("#3A3830"); + public static final Color DARK_OUTLINE_VARIANT = Color.web("#2E2D27"); + + private SkillPalette() { + } + + public static boolean isDark(ColorScheme scheme) { + return scheme.getBrightness() == Brightness.DARK; + } + + public static boolean isDark() { + return isDark(Themes.getColorScheme()); + } + + public static Color surface(boolean dark) { + return dark ? DARK_SURFACE : LIGHT_SURFACE; + } + + public static Color surfaceContainer(boolean dark) { + return dark ? DARK_SURFACE_RAISED : LIGHT_SURFACE_RAISED; + } + + public static Color surfaceContainerHigh(boolean dark) { + return dark ? DARK_SURFACE_OVERLAY : LIGHT_SURFACE_OVERLAY; + } + + public static Color onSurface(boolean dark) { + return dark ? DARK_ON_SURFACE : LIGHT_ON_SURFACE; + } + + public static Color onSurfaceVariant(boolean dark) { + return dark ? DARK_ON_SURFACE_VARIANT : LIGHT_ON_SURFACE_VARIANT; + } + + public static Color outline(boolean dark) { + return dark ? DARK_OUTLINE : LIGHT_OUTLINE; + } + + public static Color surface() { + return surface(isDark()); + } + + public static Color surfaceContainer() { + return surfaceContainer(isDark()); + } + + public static Color onSurface() { + return onSurface(isDark()); + } +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/theme/ThemeColor.java b/HMCL/src/main/java/org/jackhuang/hmcl/theme/ThemeColor.java index cf09a212a7..233c9d254f 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/theme/ThemeColor.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/theme/ThemeColor.java @@ -26,10 +26,13 @@ import java.util.Objects; @JsonSerializable public record ThemeColor(@NotNull String name, @NotNull Color color) { - public static final ThemeColor DEFAULT = new ThemeColor("blue", Color.web("#5C6BC0")); + /// Default Anthropic accent seed (skill: `--color-accent-orange`). + public static final ThemeColor DEFAULT = new ThemeColor("anthropic_orange", Color.web("#D97757")); public static final List STANDARD_COLORS = List.of( DEFAULT, + new ThemeColor("stone", Color.web("#3D453C")), + new ThemeColor("blue", Color.web("#5C6BC0")), new ThemeColor("darker_blue", Color.web("#283593")), new ThemeColor("green", Color.web("#43A047")), new ThemeColor("orange", Color.web("#E67E22")), diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/theme/Themes.java b/HMCL/src/main/java/org/jackhuang/hmcl/theme/Themes.java index 9d54e6019c..098ba40469 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/theme/Themes.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/theme/Themes.java @@ -29,7 +29,6 @@ import javafx.stage.Stage; import javafx.stage.WindowEvent; import org.glavo.monetfx.Brightness; import org.glavo.monetfx.ColorScheme; -import org.glavo.monetfx.ColorRole; import org.glavo.monetfx.ColorStyle; import org.glavo.monetfx.Contrast; import org.glavo.monetfx.beans.property.ColorSchemeProperty; @@ -800,7 +799,8 @@ public final class Themes { opacity = background.opacity(); } - Paint themeColorPaint = appearance.toResolvedTheme(context).toColorScheme().getColor(ColorRole.SURFACE_CONTAINER); + boolean darkFallback = appearance.toResolvedTheme(context).toColorScheme().getBrightness() == Brightness.DARK; + Paint themeColorPaint = SkillPalette.surfaceContainer(darkFallback); BackgroundType fallbackType = Objects.requireNonNullElse( settings().backgroundFallbackTypeProperty().get(), BackgroundType.BUILTIN); @@ -960,7 +960,7 @@ public final class Themes { /// Creates a JavaFX background from the current theme color scheme surface container. private static LauncherBackground createThemeColorBackground(double opacity) { - return createPaintBackground(getColorScheme().getColor(ColorRole.SURFACE_CONTAINER), opacity); + return createPaintBackground(SkillPalette.surfaceContainer(), opacity); } /// Creates a JavaFX image background with a separately stored node opacity. @@ -1103,10 +1103,11 @@ public final class Themes { ); /// The title text fill derived from the current color scheme. + /// + /// Title chrome uses surface tones (Anthropic-like calm bar), so fill stays on-surface + /// whether or not the bar is translucent. private static final ObjectBinding titleFill = Bindings.createObjectBinding( - () -> titleBarTransparent.get() - ? getColorScheme().getOnSurface() - : getColorScheme().getOnPrimaryContainer(), + () -> SkillPalette.onSurface(), colorSchemeProperty(), titleBarTransparent ); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/LayoutMetrics.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/LayoutMetrics.java new file mode 100644 index 0000000000..3cf6673392 --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/LayoutMetrics.java @@ -0,0 +1,52 @@ +// SPDX-FileCopyrightText: Copyright (C) 2020 The HMCL Authors +// SPDX-License-Identifier: GPL-3.0-or-later WITH AdditionRef-HMCL +package org.jackhuang.hmcl.ui; + +/// Golden-ratio layout metrics for Anthropic tool-mode chrome. +/// +/// Primary splits use φ ≈ 1.618 (sidebar:content = 1:φ). Spacing uses the φ ladder +/// from an 8px base so hierarchy stays dense — not landing-page whitespace. +public final class LayoutMetrics { + /// Golden ratio. + public static final double PHI = 1.618033988749895; + + /// Sidebar share of a page: 1 / (1 + φ) ≈ 0.382 so sidebar:content = 1:φ. + public static final double SIDEBAR_FRACTION = 1.0 / (1.0 + PHI); + + /// Design-minimum page width used when the node has not been laid out yet. + public static final double DESIGN_PAGE_WIDTH = 800.0; + + /// Tool-density clamp: keep the rail usable without eating the content column. + public static final double SIDEBAR_MIN = 220.0; + + /// φ share of [#DESIGN_PAGE_WIDTH] — exact 1:φ at the launcher minimum width. + public static final double SIDEBAR_MAX = Math.rint(DESIGN_PAGE_WIDTH * SIDEBAR_FRACTION); // 306 + + /// φ spacing ladder from an 8px base (…, 5, 8, 13, 21, 34). + public static final double SPACE_XS = 5.0; + public static final double SPACE_SM = 8.0; + public static final double SPACE_MD = 13.0; + public static final double SPACE_LG = 21.0; + public static final double SPACE_XL = 34.0; + + /// Title-bar / touch target height (a11y floor from the skill). + public static final double TITLE_BAR = 44.0; + + /// Primary CTA height on the home page (Fibonacci neighbour of title bar / φ body). + public static final double LAUNCH_HEIGHT = 55.0; + + /// Primary CTA width (Fibonacci; pairs with the φ sidebar rail). + public static final double LAUNCH_WIDTH = 233.0; + + private LayoutMetrics() { + } + + /// Resolves the fixed sidebar width for a page of the given width. + /// + /// @param pageWidth the animated page width in pixels; values ≤ 1 fall back to the design minimum + /// @return a width clamped to [{@link #SIDEBAR_MIN}, {@link #SIDEBAR_MAX}] on the 1:φ fraction + public static double sidebarWidth(double pageWidth) { + double width = pageWidth > 1.0 ? pageWidth : DESIGN_PAGE_WIDTH; + return Math.rint(Math.max(SIDEBAR_MIN, Math.min(SIDEBAR_MAX, width * SIDEBAR_FRACTION))); + } +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/animation/ContainerAnimations.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/animation/ContainerAnimations.java index 439be301f2..b0a3536f95 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/animation/ContainerAnimations.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/animation/ContainerAnimations.java @@ -121,7 +121,7 @@ public enum ContainerAnimations implements TransitionPane.AnimationProducer { /// Animates forward navigation with temporary incoming-page caching. @Override public Timeline animate(Pane container, Node previousNode, Node nextNode) { - double offset = container.getWidth() > 0 ? container.getWidth() * 0.2 : 50; + double offset = container.getWidth() > 0 ? container.getWidth() * 0.06 : 16; return new Timeline( new KeyFrame(Duration.ZERO, new KeyValue(nextNode.cacheProperty(), isCacheAllowed(nextNode), Interpolator.DISCRETE), @@ -147,12 +147,12 @@ public enum ContainerAnimations implements TransitionPane.AnimationProducer { } }, - /// @see Transitions - Material Design 3 + /// Soft reverse of [#FORWARD] (reduced travel distance for Anthropic-calm chrome). BACKWARD { /// Animates backward navigation with temporary incoming-page caching. @Override public Timeline animate(Pane container, Node previousNode, Node nextNode) { - double offset = container.getWidth() > 0 ? container.getWidth() * 0.2 : 50; + double offset = container.getWidth() > 0 ? container.getWidth() * 0.06 : 16; return new Timeline( new KeyFrame(Duration.ZERO, new KeyValue(nextNode.cacheProperty(), isCacheAllowed(nextNode), Interpolator.DISCRETE), @@ -210,8 +210,8 @@ public enum ContainerAnimations implements TransitionPane.AnimationProducer { new KeyValue(left.translateXProperty(), 0, DEFAULT_INTERPOLATOR), new KeyValue(center.translateXProperty(), 0, DEFAULT_INTERPOLATOR))); timeline.getKeyFrames().add(new KeyFrame(DEFAULT_DURATION_HALF, - new KeyValue(left.translateXProperty(), -30, DEFAULT_INTERPOLATOR), - new KeyValue(center.translateXProperty(), 30, DEFAULT_INTERPOLATOR))); + new KeyValue(left.translateXProperty(), -10, DEFAULT_INTERPOLATOR), + new KeyValue(center.translateXProperty(), 10, DEFAULT_INTERPOLATOR))); timeline.getKeyFrames().add(new KeyFrame(DEFAULT_DURATION, new KeyValue(left.cacheProperty(), left.isCache(), Interpolator.DISCRETE), new KeyValue(center.cacheProperty(), center.isCache(), Interpolator.DISCRETE))); @@ -233,8 +233,8 @@ public enum ContainerAnimations implements TransitionPane.AnimationProducer { new KeyValue(left.cacheProperty(), isCacheAllowed(left), Interpolator.DISCRETE), new KeyValue(center.cacheProperty(), cacheCenter, Interpolator.DISCRETE))); timeline.getKeyFrames().add(new KeyFrame(DEFAULT_DURATION_HALF, - new KeyValue(left.translateXProperty(), -30, DEFAULT_INTERPOLATOR), - new KeyValue(center.translateXProperty(), 30, DEFAULT_INTERPOLATOR))); + new KeyValue(left.translateXProperty(), -10, DEFAULT_INTERPOLATOR), + new KeyValue(center.translateXProperty(), 10, DEFAULT_INTERPOLATOR))); timeline.getKeyFrames().add(new KeyFrame(DEFAULT_DURATION, new KeyValue(left.cacheProperty(), left.isCache(), Interpolator.DISCRETE), new KeyValue(center.cacheProperty(), center.isCache(), Interpolator.DISCRETE), diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/animation/Motion.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/animation/Motion.java index 06a1d74177..34554bf8d7 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/animation/Motion.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/animation/Motion.java @@ -22,6 +22,18 @@ public final class Motion { /// @see curve_linear.mp4 public static final Interpolator LINEAR = Interpolator.LINEAR; + /// Anthropic skill default ease: `cubic-bezier(0.16, 1, 0.3, 1)` — transform/opacity only. + public static final Interpolator ANTHROPIC = new Cubic(0.16, 1.0, 0.3, 1.0); + + /// Skill `--duration-fast` (150ms). + public static final Duration FAST = Duration.millis(150); + + /// Skill `--duration-normal` (250ms). + public static final Duration NORMAL = Duration.millis(250); + + /// Skill `--duration-slow` (400ms). + public static final Duration SLOW = Duration.millis(400); + /// The emphasizedAccelerate easing curve in the Material specification. /// /// See also: diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/animation/TransitionPane.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/animation/TransitionPane.java index 02973b83b6..8b4b0836c3 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/animation/TransitionPane.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/animation/TransitionPane.java @@ -22,11 +22,11 @@ import org.jetbrains.annotations.Nullable; @NotNullByDefault public class TransitionPane extends StackPane { - /// The default easing curve for page transitions. - public static final Interpolator DEFAULT_INTERPOLATOR = Motion.EASE; + /// Skill ease: cubic-bezier(0.16, 1, 0.3, 1) — no Material overshoot. + public static final Interpolator DEFAULT_INTERPOLATOR = Motion.ANTHROPIC; - /// The default duration for page transitions. - public static final Duration DEFAULT_DURATION = Motion.SHORT4; + /// Skill `--duration-fast` (150ms). + public static final Duration DEFAULT_DURATION = Motion.FAST; /// Half the default duration for transitions with two phases. public static final Duration DEFAULT_DURATION_HALF = DEFAULT_DURATION.divide(2); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TabController.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TabController.java index 9f4b5be0b4..020e55b274 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TabController.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TabController.java @@ -188,8 +188,9 @@ public final class TabController implements PageAware { /// Mirrors the original slide-up and fade-in transition vertically. TAB_DOWN(-1.0); - private static final Duration DURATION = Motion.MEDIUM4; + private static final Duration DURATION = Motion.NORMAL; private static final Duration HALF_DURATION = DURATION.divide(2); + private static final Interpolator EASING = Motion.ANTHROPIC; private final double direction; @@ -200,22 +201,23 @@ public final class TabController implements PageAware { @Override public Animation animate(Pane container, Node previousNode, Node nextNode) { double extent = container.getHeight(); - double offset = (extent > 0 ? extent * 0.2 : 50) * direction; + // Small vertical drift + fade — avoids Material emphasized/overshoot motion. + double offset = (extent > 0 ? extent * 0.06 : 16) * direction; var previousTranslation = previousNode.translateYProperty(); var nextTranslation = nextNode.translateYProperty(); return new Timeline( new KeyFrame(Duration.ZERO, new KeyValue(nextNode.cacheProperty(), isCacheAllowed(nextNode), Interpolator.DISCRETE), - new KeyValue(previousTranslation, 0, Motion.EASE_IN_OUT_CUBIC_EMPHASIZED), - new KeyValue(previousNode.opacityProperty(), 1, Motion.EASE_IN_OUT_CUBIC_EMPHASIZED), - new KeyValue(nextNode.opacityProperty(), 0, Motion.EASE_IN_OUT_CUBIC_EMPHASIZED), - new KeyValue(nextTranslation, offset, Motion.EASE_IN_OUT_CUBIC_EMPHASIZED)), + new KeyValue(previousTranslation, 0, EASING), + new KeyValue(previousNode.opacityProperty(), 1, EASING), + new KeyValue(nextNode.opacityProperty(), 0, EASING), + new KeyValue(nextTranslation, offset, EASING)), new KeyFrame(HALF_DURATION, - new KeyValue(previousNode.opacityProperty(), 0, Motion.EASE_IN_OUT_CUBIC_EMPHASIZED)), + new KeyValue(previousNode.opacityProperty(), 0, EASING)), new KeyFrame(DURATION, new KeyValue(nextNode.cacheProperty(), nextNode.isCache(), Interpolator.DISCRETE), - new KeyValue(nextNode.opacityProperty(), 1, Motion.EASE_IN_OUT_CUBIC_EMPHASIZED), - new KeyValue(nextTranslation, 0, Motion.EASE_IN_OUT_CUBIC_EMPHASIZED))); + new KeyValue(nextNode.opacityProperty(), 1, EASING), + new KeyValue(nextTranslation, 0, EASING))); } @Override diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/Decorator.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/Decorator.java index 28d7ce7e87..dbca3076c6 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/Decorator.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/Decorator.java @@ -83,8 +83,8 @@ public final class Decorator { /// The minimum width, in pixels, of the visible window content. private static final double MIN_CONTENT_WIDTH = 800.0; - /// The minimum height, in pixels, including the 450-pixel background and 40-pixel title bar. - private static final double MIN_CONTENT_HEIGHT = 450.0 + 40.0; + /// The minimum height, in pixels, including the 450-pixel background and 44-pixel title bar. + private static final double MIN_CONTENT_HEIGHT = 450.0 + 44.0; /// The minimum visible overlap, in pixels, used to accept a persisted window position. private static final double MIN_VISIBLE_WINDOW_AREA = 20.0; @@ -975,13 +975,13 @@ public final class Decorator { Timeline timeline = new Timeline( new KeyFrame(Duration.ZERO, - new KeyValue(root.opacityProperty(), 0, Motion.EASE), - new KeyValue(root.scaleXProperty(), 0.8, Motion.EASE), - new KeyValue(root.scaleYProperty(), 0.8, Motion.EASE)), - new KeyFrame(Duration.millis(600), - new KeyValue(root.opacityProperty(), 1, Motion.EASE), - new KeyValue(root.scaleXProperty(), 1, Motion.EASE), - new KeyValue(root.scaleYProperty(), 1, Motion.EASE))); + new KeyValue(root.opacityProperty(), 0, Motion.ANTHROPIC), + new KeyValue(root.scaleXProperty(), 0.985, Motion.ANTHROPIC), + new KeyValue(root.scaleYProperty(), 0.985, Motion.ANTHROPIC)), + new KeyFrame(Motion.NORMAL, + new KeyValue(root.opacityProperty(), 1, Motion.ANTHROPIC), + new KeyValue(root.scaleXProperty(), 1, Motion.ANTHROPIC), + new KeyValue(root.scaleYProperty(), 1, Motion.ANTHROPIC))); playWindowAnimation(timeline, () -> { resetRootTransform(); @@ -1003,15 +1003,11 @@ public final class Decorator { if (!nativeDecorationEnabled && AnimationUtils.playWindowAnimation() && OperatingSystem.CURRENT_OS != OperatingSystem.MACOS) { Timeline timeline = new Timeline( new KeyFrame(Duration.ZERO, - new KeyValue(root.opacityProperty(), 1, Motion.EASE), - new KeyValue(root.translateYProperty(), 0, Motion.EASE), - new KeyValue(root.scaleXProperty(), 1, Motion.EASE), - new KeyValue(root.scaleYProperty(), 1, Motion.EASE)), - new KeyFrame(Motion.SHORT4, - new KeyValue(root.opacityProperty(), 0, Motion.EASE), - new KeyValue(root.translateYProperty(), 200, Motion.EASE), - new KeyValue(root.scaleXProperty(), 0.4, Motion.EASE), - new KeyValue(root.scaleYProperty(), 0.4, Motion.EASE))); + new KeyValue(root.opacityProperty(), 1, Motion.ANTHROPIC), + new KeyValue(root.translateYProperty(), 0, Motion.ANTHROPIC)), + new KeyFrame(Motion.FAST, + new KeyValue(root.opacityProperty(), 0, Motion.ANTHROPIC), + new KeyValue(root.translateYProperty(), 24, Motion.ANTHROPIC))); playWindowAnimation(timeline, () -> { if (getStage() == currentStage) { currentStage.setIconified(true); @@ -1032,13 +1028,9 @@ public final class Decorator { if (!nativeDecorationEnabled && AnimationUtils.playWindowAnimation()) { Timeline timeline = new Timeline( new KeyFrame(Duration.ZERO, - new KeyValue(root.opacityProperty(), 1, Motion.EASE), - new KeyValue(root.scaleXProperty(), 1, Motion.EASE), - new KeyValue(root.scaleYProperty(), 1, Motion.EASE)), - new KeyFrame(Duration.millis(200), - new KeyValue(root.opacityProperty(), 0, Motion.EASE), - new KeyValue(root.scaleXProperty(), 0.8, Motion.EASE), - new KeyValue(root.scaleYProperty(), 0.8, Motion.EASE))); + new KeyValue(root.opacityProperty(), 1, Motion.ANTHROPIC)), + new KeyFrame(Motion.FAST, + new KeyValue(root.opacityProperty(), 0, Motion.ANTHROPIC))); playWindowAnimation(timeline, Launcher::stopApplication); } else { stopWindowAnimation(); @@ -1172,15 +1164,11 @@ public final class Decorator { Timeline timeline = new Timeline( new KeyFrame(Duration.ZERO, - new KeyValue(root.opacityProperty(), 0, Motion.EASE), - new KeyValue(root.translateYProperty(), 200, Motion.EASE), - new KeyValue(root.scaleXProperty(), 0.4, Motion.EASE), - new KeyValue(root.scaleYProperty(), 0.4, Motion.EASE)), - new KeyFrame(Motion.SHORT4, - new KeyValue(root.opacityProperty(), 1, Motion.EASE), - new KeyValue(root.translateYProperty(), 0, Motion.EASE), - new KeyValue(root.scaleXProperty(), 1, Motion.EASE), - new KeyValue(root.scaleYProperty(), 1, Motion.EASE))); + new KeyValue(root.opacityProperty(), 0, Motion.ANTHROPIC), + new KeyValue(root.translateYProperty(), 24, Motion.ANTHROPIC)), + new KeyFrame(Motion.FAST, + new KeyValue(root.opacityProperty(), 1, Motion.ANTHROPIC), + new KeyValue(root.translateYProperty(), 0, Motion.ANTHROPIC))); playWindowAnimation(timeline, this::resetRootTransform); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorAnimatedPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorAnimatedPage.java index fd9122768c..07be88820f 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorAnimatedPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorAnimatedPage.java @@ -2,16 +2,19 @@ // SPDX-License-Identifier: GPL-3.0-or-later WITH AdditionRef-HMCL package org.jackhuang.hmcl.ui.decorator; +import javafx.beans.binding.Bindings; import javafx.geometry.Insets; import javafx.scene.Node; import javafx.scene.layout.BorderPane; import javafx.scene.layout.StackPane; -import org.jackhuang.hmcl.ui.FXUtils; +import org.jackhuang.hmcl.ui.LayoutMetrics; import org.jackhuang.hmcl.ui.construct.AdvancedListBox; import org.jetbrains.annotations.NotNullByDefault; import org.jetbrains.annotations.Nullable; -/// Lays out a fixed-width sidebar and a growing content area as independent animation targets. +/// Lays out a φ-proportioned sidebar and a growing content area as independent animation targets. +/// +/// Sidebar:content ≈ 1:φ (see [LayoutMetrics]), clamped for tool-mode density. @NotNullByDefault public class DecoratorAnimatedPage extends BorderPane { @@ -25,7 +28,12 @@ public class DecoratorAnimatedPage extends BorderPane { /// Creates both animation targets without requiring CSS or skin initialization. public DecoratorAnimatedPage() { getStyleClass().add("gray-background"); - FXUtils.setLimitWidth(left, 200); + var sidebarWidth = Bindings.createDoubleBinding( + () -> LayoutMetrics.sidebarWidth(getWidth()), + widthProperty()); + left.minWidthProperty().bind(sidebarWidth); + left.prefWidthProperty().bind(sidebarWidth); + left.maxWidthProperty().bind(sidebarWidth); sidebar.setMinSize(0, 0); left.setCenter(sidebar); setLeft(left); @@ -42,11 +50,11 @@ public class DecoratorAnimatedPage extends BorderPane { return left.getBottom(); } - /// Replaces the bottom content, reserving its preferred height and a 12-pixel bottom gap. + /// Replaces the bottom content, reserving its preferred height and a φ-md bottom gap. /// Passing null removes it without replacing the sidebar animation target. public void setSidebarBottom(@Nullable Node node) { if (node != null) { - BorderPane.setMargin(node, new Insets(0, 0, 12, 0)); + BorderPane.setMargin(node, new Insets(0, 0, LayoutMetrics.SPACE_MD, 0)); } left.setBottom(node); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/MainWindowPane.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/MainWindowPane.java index ff94c48283..1c7ba0bc07 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/MainWindowPane.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/MainWindowPane.java @@ -24,8 +24,8 @@ import javafx.scene.layout.StackPane; import javafx.scene.paint.Color; import javafx.scene.shape.Rectangle; import javafx.util.Duration; -import org.glavo.monetfx.ColorRole; import org.glavo.monetfx.ColorScheme; +import org.jackhuang.hmcl.theme.SkillPalette; import org.jackhuang.hmcl.theme.Themes; import org.jackhuang.hmcl.ui.*; import org.jackhuang.hmcl.ui.animation.ContainerAnimations; @@ -81,7 +81,7 @@ final class MainWindowPane extends StackPane { () -> Themes.windowTransparentProperty().get() ? null : new Background(new BackgroundFill( - Themes.getColorScheme().getColor(ColorRole.SURFACE_CONTAINER), + SkillPalette.surfaceContainer(), CornerRadii.EMPTY, Insets.EMPTY)), Themes.windowTransparentProperty(), @@ -173,7 +173,7 @@ final class MainWindowPane extends StackPane { HBox buttons = new HBox(); buttons.setAlignment(Pos.TOP_RIGHT); decorator.capableDraggingWindow(buttons); - buttons.setMaxSize(Region.USE_PREF_SIZE, 40); + buttons.setMaxSize(Region.USE_PREF_SIZE, 44); JFXButton minimizeButton = new JFXButton(); minimizeButton.setFocusTraversable(false); @@ -194,19 +194,22 @@ final class MainWindowPane extends StackPane { } /// Recomputes the title-bar background for the current theme and transparency preference. + /// + /// Uses surface / surface-container tones so the chrome stays calm and brand-forward + /// instead of a saturated primary strip. private void updateTitleBarBackground() { @Nullable ColorScheme colorScheme = Themes.getColorScheme(); if (colorScheme == null) { titleBar.setBackground(null); } else if (Themes.titleBarTransparentProperty().get()) { - Color surface = colorScheme.getSurface(); + Color surface = SkillPalette.surface(SkillPalette.isDark(colorScheme)); titleBar.setBackground(new Background(new BackgroundFill( new Color(surface.getRed(), surface.getGreen(), surface.getBlue(), 0.5), CornerRadii.EMPTY, Insets.EMPTY))); } else { titleBar.setBackground(new Background(new BackgroundFill( - colorScheme.getPrimaryContainer(), + SkillPalette.surfaceContainer(SkillPalette.isDark(colorScheme)), CornerRadii.EMPTY, Insets.EMPTY))); } @@ -326,7 +329,7 @@ final class MainWindowPane extends StackPane { /// {@inheritDoc} @Override public Timeline animate(Pane container, Node previousNode, Node nextNode) { - return createTimeline(previousNode, nextNode, 50, -50); + return createTimeline(previousNode, nextNode, 18, -18); } /// {@inheritDoc} @@ -350,7 +353,7 @@ final class MainWindowPane extends StackPane { /// {@inheritDoc} @Override public Timeline animate(Pane container, Node previousNode, Node nextNode) { - return createTimeline(previousNode, nextNode, -50, 50); + return createTimeline(previousNode, nextNode, -18, 18); } /// {@inheritDoc} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/DownloadSettingsPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/DownloadSettingsPage.java index 46c8e9737d..b2f90db436 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/DownloadSettingsPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/DownloadSettingsPage.java @@ -15,6 +15,7 @@ import javafx.scene.layout.*; import org.jackhuang.hmcl.setting.ProxyType; import org.jackhuang.hmcl.task.FetchTask; import org.jackhuang.hmcl.ui.FXUtils; +import org.jackhuang.hmcl.ui.LayoutMetrics; import org.jackhuang.hmcl.ui.WeakListenerHolder; import org.jackhuang.hmcl.ui.construct.*; import org.jackhuang.hmcl.util.Holder; @@ -30,8 +31,8 @@ public class DownloadSettingsPage extends StackPane { private final WeakListenerHolder holder = new WeakListenerHolder(); public DownloadSettingsPage() { - VBox content = new VBox(10); - content.setPadding(new Insets(10)); + VBox content = new VBox(LayoutMetrics.SPACE_MD); + content.setPadding(new Insets(LayoutMetrics.SPACE_SM, LayoutMetrics.SPACE_MD, LayoutMetrics.SPACE_MD, LayoutMetrics.SPACE_MD)); content.setFillWidth(true); ScrollPane scrollPane = new ScrollPane(content); FXUtils.smoothScrolling(scrollPane); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/MainPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/MainPage.java index 702850522a..bdd408ad7b 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/MainPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/MainPage.java @@ -27,6 +27,7 @@ import org.jackhuang.hmcl.setting.GameDirectoryManager; import org.jackhuang.hmcl.theme.Themes; import org.jackhuang.hmcl.ui.Controllers; import org.jackhuang.hmcl.ui.FXUtils; +import org.jackhuang.hmcl.ui.LayoutMetrics; import org.jackhuang.hmcl.ui.SVG; import org.jackhuang.hmcl.ui.animation.AnimationUtils; import org.jackhuang.hmcl.ui.decorator.DecoratorPage; @@ -79,7 +80,8 @@ public final class MainPage extends StackPane implements DecoratorPage { state.setValue(new State(null, titleNode, false, false, true)); - setPadding(new Insets(20)); + // Tool-density φ ladder (skill): md/lg insets — not landing-page whitespace. + setPadding(new Insets(LayoutMetrics.SPACE_MD, LayoutMetrics.SPACE_MD, LayoutMetrics.SPACE_LG, LayoutMetrics.SPACE_MD)); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/PersonalizationPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/PersonalizationPage.java index 96496cc5bd..7391c7f9fb 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/PersonalizationPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/PersonalizationPage.java @@ -1017,7 +1017,7 @@ public class PersonalizationPage extends StackPane { } } - vbox.getChildren().add(new Label("Hello Minecraft! Launcher")); + vbox.getChildren().add(new Label("TDLM")); fontPane.getContent().add(vbox); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/SettingsPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/SettingsPage.java index 715ecf9fde..663b287621 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/SettingsPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/SettingsPage.java @@ -14,6 +14,7 @@ import org.jackhuang.hmcl.Metadata; import org.jackhuang.hmcl.task.Schedulers; import org.jackhuang.hmcl.ui.Controllers; import org.jackhuang.hmcl.ui.FXUtils; +import org.jackhuang.hmcl.ui.LayoutMetrics; import org.jackhuang.hmcl.ui.construct.*; import org.jackhuang.hmcl.ui.construct.MessageDialogPane.MessageType; import org.jackhuang.hmcl.util.Lang; @@ -47,8 +48,9 @@ public final class SettingsPage extends ScrollPane { public SettingsPage() { this.setFitToWidth(true); - VBox rootPane = new VBox(10); - rootPane.setPadding(new Insets(10)); + // Title:body φ ladder — tight tool density (sm gap, md/lg insets). + VBox rootPane = new VBox(LayoutMetrics.SPACE_SM); + rootPane.setPadding(new Insets(LayoutMetrics.SPACE_SM, LayoutMetrics.SPACE_MD, LayoutMetrics.SPACE_MD, LayoutMetrics.SPACE_MD)); this.setContent(rootPane); FXUtils.smoothScrolling(this); diff --git a/HMCL/src/main/resources/assets/css/brightness-dark.css b/HMCL/src/main/resources/assets/css/brightness-dark.css index 97f1f7c805..4dca8758ef 100644 --- a/HMCL/src/main/resources/assets/css/brightness-dark.css +++ b/HMCL/src/main/resources/assets/css/brightness-dark.css @@ -1,35 +1,48 @@ +/* + * Anthropic UI — dark brightness tokens (skill charcoal #1A1916 family). + * Loaded after the generated Monet theme sheet; keep AA contrast on surfaces/text. + */ +* { + -anthropic-shadow: rgba(0, 0, 0, 0.50); + -anthropic-shadow-strong: rgba(0, 0, 0, 0.65); + -anthropic-scrollbar-track: #222119; + -anthropic-scrollbar-thumb: #5C5A54; + -anthropic-hairline: -monet-outline-variant; + -anthropic-focus-ring: #D97757; +} + .hint { - -fx-hyperlink-fill: -monet-inverse-primary; + -fx-hyperlink-fill: -monet-primary; } .tag { - -fixed-warning-tag-background: #2c0b0e; + -fixed-warning-tag-background: #4A1A1F; } .log-window { - -fixed-log-background: #282828; - -fixed-log-toggle-selected: #DEE2E6; - -fixed-log-toggle-unselected: #6C757D; - -fixed-log-text-fill: #e3e3e3; - -fixed-log-trace-background: #495057; - -fixed-log-debug-background: #343A40; - -fixed-log-info-background: #212529; - -fixed-log-warn-background: #413c26; - -fixed-log-warn-text-fill: #fdf3aa; - -fixed-log-error-background: #4e3534; - -fixed-log-error-text-fill: #f9dedc; + -fixed-log-background: -monet-surface; + -fixed-log-toggle-selected: -monet-on-surface; + -fixed-log-toggle-unselected: -monet-on-surface-variant; + -fixed-log-text-fill: -monet-on-surface; + -fixed-log-trace-background: -monet-surface-container-high; + -fixed-log-debug-background: -monet-surface-container; + -fixed-log-info-background: -monet-surface; + -fixed-log-warn-background: #3F3A26; + -fixed-log-warn-text-fill: #F7E8A0; + -fixed-log-error-background: #4E3534; + -fixed-log-error-text-fill: #F9DEDC; -fixed-log-fatal-background: #842029; - -fixed-log-selected: #3e3e3e; + -fixed-log-selected: -monet-surface-container-highest; } .addon-channel-icon.alpha .svg { - -fx-fill: #ff496e; + -fx-fill: #E08A8A; } .addon-channel-icon.beta .svg { - -fx-fill: #ffa347; + -fx-fill: #E08A6C; } .addon-channel-icon.release .svg { - -fx-fill: #1bd96a; + -fx-fill: #A3B58A; } diff --git a/HMCL/src/main/resources/assets/css/brightness-light.css b/HMCL/src/main/resources/assets/css/brightness-light.css index 2f888e3817..74d8034c5e 100644 --- a/HMCL/src/main/resources/assets/css/brightness-light.css +++ b/HMCL/src/main/resources/assets/css/brightness-light.css @@ -1,3 +1,16 @@ +/* + * Anthropic UI — light brightness tokens (skill-aligned warm neutrals). + * Loaded after the generated Monet theme sheet; keep AA contrast on surfaces/text. + */ +* { + -anthropic-shadow: rgba(20, 20, 19, 0.10); + -anthropic-shadow-strong: rgba(20, 20, 19, 0.16); + -anthropic-scrollbar-track: #E8E6DC; + -anthropic-scrollbar-thumb: #9B9890; + -anthropic-hairline: -monet-outline-variant; + -anthropic-focus-ring: #D97757; +} + .hint { -fx-hyperlink-fill: -monet-primary; } @@ -7,29 +20,29 @@ } .log-window { - -fixed-log-background: #ffffff; - -fixed-log-toggle-selected: #000000; - -fixed-log-toggle-unselected: #6C757D; - -fixed-log-text-fill: #1f1f1f; - -fixed-log-trace-background: #EEE9E0; - -fixed-log-debug-background: #EEE9E0; - -fixed-log-info-background: #FFFFFF; - -fixed-log-warn-background: #fef6d5; - -fixed-log-warn-text-fill: #3e2f00; - -fixed-log-error-background: #fcebeb; - -fixed-log-error-text-fill: #410e0b; + -fixed-log-background: -monet-surface; + -fixed-log-toggle-selected: -monet-on-surface; + -fixed-log-toggle-unselected: -monet-on-surface-variant; + -fixed-log-text-fill: -monet-on-surface; + -fixed-log-trace-background: -monet-surface-container; + -fixed-log-debug-background: -monet-surface-container-low; + -fixed-log-info-background: -monet-surface; + -fixed-log-warn-background: #F7F0D8; + -fixed-log-warn-text-fill: #3E2F00; + -fixed-log-error-background: #FCEBEB; + -fixed-log-error-text-fill: #410E0B; -fixed-log-fatal-background: #F7A699; - -fixed-log-selected: #f2f2f2; + -fixed-log-selected: -monet-surface-container-highest; } .addon-channel-icon.alpha .svg { - -fx-fill: #cb2245; + -fx-fill: #C0453A; } .addon-channel-icon.beta .svg { - -fx-fill: #e08325; + -fx-fill: #C96442; } .addon-channel-icon.release .svg { - -fx-fill: #00af5c; + -fx-fill: #5F7A4A; } diff --git a/HMCL/src/main/resources/assets/css/font.css b/HMCL/src/main/resources/assets/css/font.css index 7ee8fbf036..a891e83f6e 100644 --- a/HMCL/src/main/resources/assets/css/font.css +++ b/HMCL/src/main/resources/assets/css/font.css @@ -1,8 +1,53 @@ /* - * SPDX-FileCopyrightText: Copyright (C) 2025 The HMCL Authors - * SPDX-License-Identifier: GPL-3.0-or-later WITH AdditionRef-HMCL + * Anthropic skill type roles (CN+Latin): + * - Sans = UI / nav / buttons / chrome titles (Source Han Sans) + * - Serif = body / narrative copy (Source Han Serif) */ .root { - /* https://github.com/HMCL-dev/HMCL/pull/3423 */ - -fx-font-family: -fx-base-font-family; -} \ No newline at end of file + -fx-font-family: "Source Han Serif SC", "Source Han Serif", "思源宋体", serif; +} + +/* UI chrome + interactive — sans */ +.jfx-decorator-title, +.jfx-tool-bar-second .label, +.jfx-tool-bar Label, +.title-label, +.class-title, +.class-title Text, +.jfx-layout-heading, +.launch-pane > .launch-button .label, +.announcement .title, +.method-title, +.radio-button-title-label, +.two-line-list-item > .title, +.advanced-list-item > .rippler-container > .container > .title, +.iconed-item .iconed-item-container, +.iconed-menu-item .iconed-item-container, +.options-list-item .label, +.jfx-button, +.jfx-button *, +.jfx-button-raised, +.jfx-button-raised *, +.jfx-button-border, +.jfx-button-border *, +.tab-label, +.dialog-accept, +.dialog-cancel, +.line-component .title-label, +.menu-container .title-label { + -fx-font-family: "Source Han Sans SC", "Source Han Sans", "思源黑体", sans-serif; + -fx-font-weight: bold; +} + +/* Body / secondary copy — serif */ +.subtitle-label, +.two-line-list-item > .subtitle, +.advanced-list-item > .rippler-container > .container > .subtitle, +.announcement text, +.method-desc, +.options-list-item .label.subtitle, +.hint, +.html { + -fx-font-family: "Source Han Serif SC", "Source Han Serif", "思源宋体", serif; + -fx-font-weight: normal; +} diff --git a/HMCL/src/main/resources/assets/css/press-feedback.css b/HMCL/src/main/resources/assets/css/press-feedback.css new file mode 100644 index 0000000000..d9829c6378 --- /dev/null +++ b/HMCL/src/main/resources/assets/css/press-feedback.css @@ -0,0 +1,113 @@ +/* + * Anthropic skill — hard press feedback (loaded LAST, beats Monet/root). + * Forced :pressed / :armed change background AND border AND opacity. + * Solid hex only (no fragile lookups) so Modena cyan cannot win on specificity. + */ + +.jfx-button:pressed, +.jfx-button:armed, +.toggle-icon4:pressed, +.toggle-icon4:armed, +.toggle-icon-tiny:pressed, +.toggle-icon-tiny:armed, +.announcement-close-button:pressed, +.announcement-close-button:armed, +.jfx-decorator-button:pressed, +.jfx-decorator-button:armed, +.jfx-tool-bar-button:pressed, +.jfx-tool-bar-button:armed, +.jfx-tool-bar .jfx-decorator-button:pressed, +.jfx-tool-bar .jfx-decorator-button:armed { + -fx-background-color: rgba(217, 119, 87, 0.42); + -fx-border-color: #C96442; + -fx-border-width: 2px; + -fx-border-radius: 6px; + -fx-background-radius: 6px; + -fx-opacity: 0.84; + -fx-scale-x: 0.94; + -fx-scale-y: 0.94; +} + +.jfx-button:hover, +.toggle-icon4:hover, +.toggle-icon-tiny:hover, +.announcement-close-button:hover, +.jfx-decorator-button:hover, +.jfx-tool-bar-button:hover { + -fx-background-color: rgba(217, 119, 87, 0.16); + -fx-border-color: rgba(201, 100, 66, 0.55); + -fx-border-width: 1px; + -fx-opacity: 1; +} + +.jfx-button-raised:pressed, +.jfx-button-raised:armed, +.jfx-button-raised-round:pressed, +.jfx-button-raised-round:armed, +.launch-pane > .jfx-button:pressed, +.launch-pane > .jfx-button:armed { + -fx-background-color: #9A4A32; + -fx-border-color: #F5F3EC; + -fx-border-width: 2px; + -fx-opacity: 0.90; + -fx-scale-x: 0.96; + -fx-scale-y: 0.96; +} + +.jfx-button-border:pressed, +.jfx-button-border:armed { + -fx-background-color: rgba(217, 119, 87, 0.28); + -fx-border-color: #C96442; + -fx-border-width: 2px; + -fx-opacity: 0.88; + -fx-scale-x: 0.96; + -fx-scale-y: 0.96; +} + +.toggle-icon4:pressed, +.toggle-icon4:armed, +.toggle-icon-tiny:pressed, +.toggle-icon-tiny:armed, +.announcement-close-button:pressed, +.announcement-close-button:armed { + -fx-background-radius: 50px; + -fx-border-radius: 50px; +} + +.jfx-button:disabled:pressed, +.jfx-button:disabled:armed, +.toggle-icon4:disabled:pressed, +.toggle-icon4:disabled:armed, +.toggle-icon-tiny:disabled:pressed, +.toggle-icon-tiny:disabled:armed, +.jfx-decorator-button:disabled:pressed, +.jfx-decorator-button:disabled:armed, +.jfx-tool-bar-button:disabled:pressed, +.jfx-tool-bar-button:disabled:armed { + -fx-opacity: 0.45; + -fx-background-color: transparent; + -fx-border-width: 0; + -fx-scale-x: 1; + -fx-scale-y: 1; +} + +/* Skin-driven armed class — mirrors :pressed when Modena/CSS lose the race. */ +.jfx-button.anthropic-armed, +.toggle-icon4.anthropic-armed, +.toggle-icon-tiny.anthropic-armed, +.jfx-decorator-button.anthropic-armed, +.jfx-tool-bar-button.anthropic-armed, +.announcement-close-button.anthropic-armed { + -fx-background-color: rgba(217, 119, 87, 0.42); + -fx-border-color: #C96442; + -fx-border-width: 2px; + -fx-opacity: 0.84; +} + +.jfx-button-raised.anthropic-armed, +.launch-pane > .jfx-button.anthropic-armed { + -fx-background-color: #9A4A32; + -fx-border-color: #F5F3EC; + -fx-border-width: 2px; + -fx-opacity: 0.90; +} diff --git a/HMCL/src/main/resources/assets/css/root.css b/HMCL/src/main/resources/assets/css/root.css index 7afc94b49c..14894da192 100644 --- a/HMCL/src/main/resources/assets/css/root.css +++ b/HMCL/src/main/resources/assets/css/root.css @@ -120,7 +120,7 @@ .radio-button-title-label { -fx-font-size: 16px; -fx-padding: 14.0 0.0 -20.0 0.0; - -fx-text-fill: rgba(0.0, 0.0, 0.0, 0.87); + -fx-text-fill: -monet-on-surface; } .announcement { @@ -144,7 +144,8 @@ } .rippler-container { - -jfx-feedback-fill: -monet-on-surface; + /* Anthropic chrome: no Material ripple / press ink */ + -jfx-feedback-fill: transparent; } .rippler-container:press-feedback-enabled { @@ -185,6 +186,18 @@ .advanced-list-item { -jfx-selected-background-fill: -monet-secondary-container; + -fx-background-radius: 6px; +} + +.advanced-list-item:hover > .rippler-container { + -fx-background-color: -anthropic-hover-wash; + -fx-background-radius: 6px; +} + +.advanced-list-item:pressed > .rippler-container, +.advanced-list-item:armed > .rippler-container { + -fx-background-color: -anthropic-press-wash; + -fx-background-radius: 6px; } .advanced-list-item:selected > .rippler-container > .container > .title { @@ -229,8 +242,9 @@ } .class-title { - -fx-font-size: 12px; - -fx-padding: 8 16 8 16; + /* Title block of title:body ≈ 1:φ — md/sm padding under tool density */ + -fx-font-size: 11px; + -fx-padding: 13 13 8 13; } .class-title Text { @@ -245,7 +259,7 @@ } .advanced-list-box-content { - -fx-padding: 12 0 0 0; + -fx-padding: 13 0 0 0; -fx-spacing: 0; } @@ -258,8 +272,9 @@ } .iconed-item .iconed-item-container { - -fx-padding: 10 16 10 16; - -fx-spacing: 10; + /* Body row under φ title:body — sm/md denser than landing */ + -fx-padding: 8 13 8 13; + -fx-spacing: 13; -fx-font-size: 14; -fx-alignment: CENTER_LEFT; } @@ -274,14 +289,14 @@ } .iconed-menu-item .iconed-item-container { - -fx-padding: 8 16 8 16; - -fx-spacing: 10; + -fx-padding: 8 13 8 13; + -fx-spacing: 13; -fx-font-size: 12; -fx-alignment: CENTER_LEFT; } .menu-container { - -fx-padding: 8 16 8 16; + -fx-padding: 8 13 8 13; } .popup-menu .scroll-bar .thumb { @@ -392,7 +407,7 @@ } .installer-item-wrapper:card { - -fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.26), 10, 0.12, -1, 2); + -fx-effect: dropshadow(gaussian, -anthropic-shadow, 8, 0.08, 0, 1); } .installer-item-wrapper .installer-item:list-item { @@ -421,57 +436,82 @@ ******************************************************************************/ .launch-pane { - -fx-max-height: 57px; - -fx-min-height: 57px; - -fx-max-width: 230px; - -fx-min-width: 230px; + /* φ/Fibonacci CTA: 55×233 pairs with title bar 44 and sidebar rail */ + -fx-max-height: 55px; + -fx-min-height: 55px; + -fx-max-width: 233px; + -fx-min-width: 233px; } .launch-pane > .jfx-button { -fx-translate-y: 1px; - -fx-max-height: 55px; - -fx-min-height: 55px; - -fx-background-color: -monet-primary-container; + -fx-max-height: 53px; + -fx-min-height: 53px; + -fx-background-color: -monet-primary; -fx-opacity: 1; /* Override the opacity of disabled button */ -fx-cursor: hand; + -fx-scale-x: 1; + -fx-scale-y: 1; +} + +.launch-pane > .jfx-button:hover { + -fx-background-color: -anthropic-primary-hover; + -fx-scale-x: 1.02; + -fx-scale-y: 1.02; +} + +.launch-pane > .jfx-button:pressed, +.launch-pane > .jfx-button:armed { + -fx-background-color: #9A4A32; + -fx-border-color: #F5F3EC; + -fx-border-width: 2px; + -fx-opacity: 0.90; + -fx-scale-x: 0.96; + -fx-scale-y: 0.96; +} + +.launch-pane > .jfx-button:focused { + -fx-border-color: -anthropic-focus-ring; + -fx-border-width: 1.5px; } .launch-pane > .jfx-button.launch-button { - -fx-max-width: 200px; - -fx-min-width: 200px; - -fx-border-width: 0 3px 0 0; + -fx-max-width: 201px; + -fx-min-width: 201px; + -fx-border-width: 0 1px 0 0; -fx-border-color: linear-gradient( - to bottom, - transparent 0%, - transparent 20%, - -monet-on-surface-variant 20%, - -monet-on-surface-variant 80%, - transparent 80%, + to bottom, + transparent 0%, + transparent 22%, + -monet-on-primary 22%, + -monet-on-primary 78%, + transparent 78%, transparent 100% ); - -fx-background-radius: 4px 0 0 4px; + -fx-background-radius: 8px 0 0 8px; } .launch-pane > .jfx-button.menu-button { - -fx-max-width: 27px; - -fx-min-width: 27px; + -fx-max-width: 32px; + -fx-min-width: 32px; -fx-font-size: 15px; - -fx-background-radius: 0 4px 4px 0; + -fx-background-radius: 0 8px 8px 0; } .launch-pane > .jfx-button > StackPane > .jfx-rippler { - -jfx-rippler-fill: -monet-on-primary-container; + -jfx-rippler-disabled: true; + -jfx-rippler-fill: transparent; -jfx-mask-type: CIRCLE; -fx-padding: 0; } .launch-pane > .launch-button .label { - -fx-text-fill: -monet-on-primary-container; - -fx-font-size: 14px; + -fx-text-fill: -monet-on-primary; + -fx-font-size: 15px; } .launch-pane .svg { - -fx-fill: -monet-on-primary-container; + -fx-fill: -monet-on-primary; } /******************************************************************************* @@ -498,7 +538,8 @@ } .jfx-tab-pane .jfx-rippler { - -jfx-rippler-fill: white; + -jfx-rippler-disabled: true; + -jfx-rippler-fill: transparent; } .tab-label { @@ -520,8 +561,8 @@ ******************************************************************************/ .dialog-trigger { - -fx-background-color: WHITE; - -jfx-button-type: RAISED; + -fx-background-color: -monet-surface-container-highest; + -jfx-button-type: FLAT; -fx-font-size: 14.0px; } @@ -657,7 +698,7 @@ ******************************************************************************/ .icon { - -fx-fill: #FE774D; + -fx-fill: -monet-primary; -fx-padding: 10.0; -fx-cursor: hand; } @@ -671,17 +712,18 @@ .jfx-tool-bar { -fx-font-size: 13.0; -fx-pref-width: 100.0%; - -fx-min-height: 40px; - -fx-max-height: 40px; - -fx-pref-height: 40px; + -fx-min-height: 44px; + -fx-max-height: 44px; + -fx-pref-height: 44px; } .jfx-tool-bar .jfx-decorator-title { -fx-font-weight: BOLD; + -fx-font-size: 16px; } .jfx-tool-bar.background { - -fx-background-color: -monet-primary-container; + -fx-background-color: -monet-surface-container; } /*.jfx-tool-bar HBox {*/ @@ -690,21 +732,21 @@ /*}*/ .jfx-tool-bar .jfx-decorator-button { - -fx-min-width: 40px; - -fx-max-width: 40px; - -fx-pref-width: 40px; - -fx-min-height: 40px; - -fx-max-height: 40px; - -fx-pref-height: 40px; + -fx-min-width: 44px; + -fx-max-width: 44px; + -fx-pref-width: 44px; + -fx-min-height: 44px; + -fx-max-height: 44px; + -fx-pref-height: 44px; -fx-cursor: hand; } .jfx-tool-bar .jfx-decorator-button .svg { - -fx-fill: -monet-on-primary-container; + -fx-fill: -monet-on-surface; } .jfx-decorator-button .jfx-spinner .arc { - -fx-stroke: -monet-on-primary-container; + -fx-stroke: -monet-on-surface; } .jfx-tool-bar Label { @@ -712,7 +754,7 @@ } .jfx-tool-bar.gray-background Label { - -fx-text-fill: BLACK; + -fx-text-fill: -monet-on-surface; } .jfx-tool-bar Text { @@ -732,15 +774,15 @@ } .jfx-tool-bar-second { - -fx-pref-height: 42; - -fx-padding: 2 2 2 2; - -fx-background-color: -monet-primary-container; + -fx-pref-height: 48; + -fx-padding: 4 8 4 8; + -fx-background-color: -monet-surface-container; -fx-alignment: CENTER-LEFT; -fx-spacing: 8; } .jfx-tool-bar-second .label { - -fx-text-fill: -monet-on-primary-container; + -fx-text-fill: -monet-on-surface; -fx-font-size: 16; -fx-font-weight: bold; } @@ -758,12 +800,31 @@ -fx-min-height: -fx-toggle-icon4-size; -fx-background-radius: 5px; -fx-background-color: transparent; + -fx-opacity: 1; + -fx-scale-x: 1; + -fx-scale-y: 1; -jfx-toggle-color: -monet-on-surface; -jfx-untoggle-color: transparent; } +.jfx-tool-bar-button:hover { + -fx-background-color: -anthropic-icon-hover-wash; + -fx-scale-x: 1.04; + -fx-scale-y: 1.04; +} + +.jfx-tool-bar-button:pressed, +.jfx-tool-bar-button:armed { + -fx-background-color: rgba(217, 119, 87, 0.42); + -fx-border-color: #C96442; + -fx-border-width: 2px; + -fx-opacity: 0.84; + -fx-scale-x: 0.94; + -fx-scale-y: 0.94; +} + .jfx-tool-bar-button .icon { - -fx-fill: rgb(204.0, 204.0, 51.0); + -fx-fill: -monet-on-surface; -fx-padding: 10.0; } @@ -774,13 +835,35 @@ .jfx-decorator-button { -fx-max-width: 40px; - -fx-background-radius: 5px; + -fx-background-radius: 8px; -fx-max-height: 40px; -fx-background-color: transparent; } +.jfx-decorator-button:hover { + -fx-background-color: -anthropic-hover-wash; + -fx-scale-x: 1.04; + -fx-scale-y: 1.04; +} + +.jfx-decorator-button:pressed, +.jfx-decorator-button:armed { + -fx-background-color: rgba(217, 119, 87, 0.42); + -fx-border-color: #C96442; + -fx-border-width: 2px; + -fx-opacity: 0.84; + -fx-scale-x: 0.94; + -fx-scale-y: 0.94; +} + +.jfx-decorator-button:focused { + -fx-border-color: -anthropic-focus-ring; + -fx-border-width: 1.5px; + -fx-border-radius: 8px; +} + .jfx-decorator-button .icon { - -fx-fill: rgb(204.0, 204.0, 51.0); + -fx-fill: -monet-on-surface; -fx-padding: 0.0; } @@ -848,6 +931,11 @@ * * *******************************************************************************/ +.jfx-rippler { + -jfx-rippler-disabled: true; + -jfx-rippler-fill: transparent; +} + .jfx-rippler:hover { -fx-cursor: hand; } @@ -861,20 +949,80 @@ .jfx-button { -jfx-disable-visual-focus: true; -fx-cursor: hand; + -fx-background-radius: 6px; + -fx-background-color: transparent; + -fx-scale-x: 1; + -fx-scale-y: 1; } .jfx-button .jfx-rippler { - -jfx-rippler-fill: -monet-on-surface; + -jfx-rippler-disabled: true; + -jfx-rippler-fill: transparent; -jfx-mask-type: CIRCLE; -fx-padding: 0.0; } +/* Calm tactile feedback: tone + border + opacity (no Material ripple). */ +.jfx-button:hover { + -fx-background-color: -anthropic-hover-wash; + -fx-border-color: rgba(201, 100, 66, 0.45); + -fx-border-width: 1px; + -fx-scale-x: 1.02; + -fx-scale-y: 1.02; +} + +.jfx-button:pressed, +.jfx-button:armed { + -fx-background-color: rgba(217, 119, 87, 0.42); + -fx-border-color: #C96442; + -fx-border-width: 2px; + -fx-opacity: 0.84; + -fx-scale-x: 0.94; + -fx-scale-y: 0.94; +} + +.jfx-button:focused { + -fx-border-color: -anthropic-focus-ring; + -fx-border-width: 2px; + -fx-border-radius: 6px; + -fx-border-insets: 0; + -fx-effect: dropshadow(gaussian, -anthropic-focus-glow, 0, 0, 0, 0); +} + +.jfx-button:disabled, +.jfx-button:disabled:hover, +.jfx-button:disabled:pressed, +.jfx-button:disabled:armed { + -fx-scale-x: 1; + -fx-scale-y: 1; + -fx-opacity: 0.45; + -fx-background-color: transparent; +} + .jfx-button-raised { -fx-background-color: -monet-primary; + -fx-background-radius: 6px; +} + +.jfx-button-raised:hover { + -fx-background-color: -anthropic-primary-hover; + -fx-scale-x: 1.02; + -fx-scale-y: 1.02; +} + +.jfx-button-raised:pressed, +.jfx-button-raised:armed { + -fx-background-color: #9A4A32; + -fx-border-color: #F5F3EC; + -fx-border-width: 2px; + -fx-opacity: 0.90; + -fx-scale-x: 0.96; + -fx-scale-y: 0.96; } .jfx-button-raised .jfx-rippler { - -jfx-rippler-fill: -monet-on-primary; + -jfx-rippler-disabled: true; + -jfx-rippler-fill: transparent; -jfx-mask-type: CIRCLE; -fx-padding: 0.0; } @@ -887,11 +1035,33 @@ .jfx-button-border { -fx-background-color: transparent; -fx-border-color: -monet-outline; - -fx-border-radius: 5px; - -fx-border-width: 0.2px; + -fx-border-radius: 6px; + -fx-border-width: 1px; -fx-padding: 8px; } +.jfx-button-border:hover { + -fx-background-color: -anthropic-hover-wash; + -fx-border-color: -monet-primary; + -fx-scale-x: 1.02; + -fx-scale-y: 1.02; +} + +.jfx-button-border:pressed, +.jfx-button-border:armed { + -fx-background-color: rgba(217, 119, 87, 0.28); + -fx-border-color: #C96442; + -fx-border-width: 2px; + -fx-opacity: 0.88; + -fx-scale-x: 0.96; + -fx-scale-y: 0.96; +} + +.jfx-button-border:focused { + -fx-border-color: -anthropic-focus-ring; + -fx-border-width: 1.5px; +} + .jfx-button-border, .jfx-button-border * { -fx-text-fill: -monet-primary; } @@ -901,6 +1071,22 @@ -fx-background-radius: 50px; } +.jfx-button-raised-round:hover { + -fx-background-color: -anthropic-primary-container-hover; + -fx-scale-x: 1.03; + -fx-scale-y: 1.03; +} + +.jfx-button-raised-round:pressed, +.jfx-button-raised-round:armed { + -fx-background-color: #9A4A32; + -fx-border-color: #F5F3EC; + -fx-border-width: 2px; + -fx-opacity: 0.90; + -fx-scale-x: 0.96; + -fx-scale-y: 0.96; +} + .menu-up-down-button .label { -fx-text-fill: -monet-on-surface; } @@ -993,7 +1179,7 @@ .list-cell:selected, .jfx-list-cell:selected, .list-cell:hover, .jfx-list-cell:hover { - -fx-text-fill: black; + -fx-text-fill: -monet-on-surface; } .jfx-list-cell .jfx-rippler { @@ -1040,31 +1226,31 @@ .options-list { -fx-background-color: transparent; - -fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.26), 5, 0.06, -0.5, 1); + -fx-effect: dropshadow(gaussian, -anthropic-shadow, 4, 0.04, 0, 1); } .depth-0 { - -fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0), 0, 0, 0, 0); + -fx-effect: dropshadow(gaussian, transparent, 0, 0, 0, 0); } .depth-1 { - -fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.26), 10, 0.12, -1, 2); + -fx-effect: dropshadow(gaussian, -anthropic-shadow, 8, 0.08, 0, 1); } .depth-2 { - -fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.26), 15, 0.16, 0, 4); + -fx-effect: dropshadow(gaussian, -anthropic-shadow-strong, 12, 0.10, 0, 2); } .depth-3 { - -fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.26), 20, 0.19, 0, 6); + -fx-effect: dropshadow(gaussian, -anthropic-shadow-strong, 16, 0.12, 0, 3); } .depth-4 { - -fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.26), 25, 0.25, 0, 8); + -fx-effect: dropshadow(gaussian, -anthropic-shadow-strong, 20, 0.14, 0, 4); } .depth-5 { - -fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.26), 30, 0.3, 0, 10); + -fx-effect: dropshadow(gaussian, -anthropic-shadow-strong, 24, 0.16, 0, 5); } .padding-8 { @@ -1073,30 +1259,30 @@ .card { -fx-background-color: -monet-surface-container-low-transparent-80; - -fx-background-radius: 4; - -fx-padding: 8px; + -fx-background-radius: 8; + -fx-padding: 13px; - -fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.26), 10, 0.12, -1, 2); + -fx-effect: dropshadow(gaussian, -anthropic-shadow, 6, 0.06, 0, 1); } .card-no-padding { -fx-background-color: -monet-surface-container-low-transparent-80; - -fx-background-radius: 4; + -fx-background-radius: 8; - -fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.26), 10, 0.12, -1, 2); + -fx-effect: dropshadow(gaussian, -anthropic-shadow, 6, 0.06, 0, 1); } .card-non-transparent { -fx-background-color: -monet-surface-container-low; - -fx-background-radius: 4; - -fx-padding: 8px; + -fx-background-radius: 8; + -fx-padding: 13px; - -fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.26), 5, 0.06, -0.5, 1); + -fx-effect: dropshadow(gaussian, -anthropic-shadow, 4, 0.04, 0, 1); } .card-list { - -fx-padding: 10; - -fx-spacing: 10; + -fx-padding: 13; + -fx-spacing: 13; } .md-list-cell { @@ -1132,7 +1318,8 @@ -fx-background-color: -monet-surface; -fx-border-color: -monet-outline-variant; -fx-border-width: 1 0 0 0; - -fx-padding: 10 16 10 16; + /* Body of title:body — sm vertical / md horizontal */ + -fx-padding: 8 13 8 13; -fx-font-size: 12; } @@ -1324,9 +1511,11 @@ .jfx-decorator-button { -fx-pref-width: 50px; - -fx-background-radius: 50px; + -fx-background-radius: 8px; -fx-pref-height: 50px; -fx-background-color: transparent; + -fx-border-width: 0; + -fx-opacity: 1; } @@ -1340,17 +1529,38 @@ -fx-min-height: -fx-toggle-icon4-size; -fx-background-radius: 50px; -fx-background-color: transparent; + -fx-opacity: 1; + -fx-scale-x: 1; + -fx-scale-y: 1; -jfx-toggle-color: -monet-primary; -jfx-untoggle-color: transparent; } +.toggle-icon4:hover { + -fx-background-color: -anthropic-icon-hover-wash; + -fx-scale-x: 1.06; + -fx-scale-y: 1.06; +} + +.toggle-icon4:pressed, +.toggle-icon4:armed { + -fx-background-color: rgba(217, 119, 87, 0.42); + -fx-border-color: #C96442; + -fx-border-width: 2px; + -fx-border-radius: 50px; + -fx-opacity: 0.84; + -fx-scale-x: 0.90; + -fx-scale-y: 0.90; +} + .toggle-icon4 .icon { - -fx-fill: rgb(204.0, 204.0, 51.0); + -fx-fill: -monet-on-surface; -fx-padding: 10.0; } .toggle-icon4 .jfx-rippler { - -jfx-rippler-fill: -monet-primary; + -jfx-rippler-disabled: true; + -jfx-rippler-fill: transparent; -jfx-mask-type: CIRCLE; } @@ -1365,12 +1575,33 @@ -fx-padding: 0; -fx-background-radius: 25px; -fx-background-color: transparent; + -fx-opacity: 1; + -fx-scale-x: 1; + -fx-scale-y: 1; -jfx-toggle-color: -monet-primary; -jfx-untoggle-color: transparent; } +.toggle-icon-tiny:hover { + -fx-background-color: -anthropic-icon-hover-wash; + -fx-opacity: 0.92; + -fx-scale-x: 1.08; + -fx-scale-y: 1.08; +} + +.toggle-icon-tiny:pressed, +.toggle-icon-tiny:armed { + -fx-background-color: rgba(217, 119, 87, 0.42); + -fx-border-color: #C96442; + -fx-border-width: 2px; + -fx-border-radius: 50px; + -fx-opacity: 0.80; + -fx-scale-x: 0.86; + -fx-scale-y: 0.86; +} + .toggle-icon-tiny .icon { - -fx-fill: rgb(204.0, 204.0, 51.0); + -fx-fill: -monet-on-surface; -fx-padding: 0; } @@ -1379,7 +1610,8 @@ } .toggle-icon-tiny .jfx-rippler { - -jfx-rippler-fill: -monet-primary; + -jfx-rippler-disabled: true; + -jfx-rippler-fill: transparent; -jfx-mask-type: CIRCLE; } @@ -1393,10 +1625,31 @@ -fx-min-height: -fx-announcement-close-button-size; -fx-background-radius: 50px; -fx-background-color: transparent; + -fx-opacity: 1; + -fx-scale-x: 1; + -fx-scale-y: 1; +} + +.announcement-close-button:hover { + -fx-background-color: -anthropic-icon-hover-wash; + -fx-scale-x: 1.06; + -fx-scale-y: 1.06; +} + +.announcement-close-button:pressed, +.announcement-close-button:armed { + -fx-background-color: rgba(217, 119, 87, 0.42); + -fx-border-color: #C96442; + -fx-border-width: 2px; + -fx-border-radius: 50px; + -fx-opacity: 0.84; + -fx-scale-x: 0.90; + -fx-scale-y: 0.90; } .announcement-close-button .jfx-rippler { - -jfx-rippler-fill: -monet-primary; + -jfx-rippler-disabled: true; + -jfx-rippler-fill: transparent; -jfx-mask-type: CIRCLE; } @@ -1598,12 +1851,12 @@ } .jfx-combo-box-warning { - -jfx-focus-color: #D34336; - -jfx-unfocus-color: #D34336; + -jfx-focus-color: -monet-error; + -jfx-unfocus-color: -monet-error; } .jfx-combo-box-warning .text { - -fx-fill: #D34336; + -fx-fill: -monet-error; } /*.combo-box-popup .list-view .jfx-list-cell .jfx-rippler {*/ @@ -1781,15 +2034,16 @@ *******************************************************************************/ .jfx-decorator { - -fx-decorator-color: -monet-primary-container; + -fx-decorator-color: -monet-surface-container; } .jfx-decorator-drawer { - -fx-background-color: rgba(244, 244, 244, 0.5); + -fx-background-color: -monet-surface-transparent-80; } .jfx-decorator-title { - -fx-font-size: 14; + -fx-font-size: 16; + -fx-font-weight: bold; } .debug-border { @@ -1798,11 +2052,11 @@ } .content-background { - -fx-background-color: -monet-surface-transparent-50; + -fx-background-color: -monet-surface-transparent-80; } .gray-background { - -fx-background-color: -monet-surface-transparent-50; + -fx-background-color: -monet-surface-container-low-transparent-80; } /******************************************************************************* @@ -1812,12 +2066,12 @@ ******************************************************************************/ .scroll-bar:vertical > .track-background, .scroll-bar:horizontal > .track-background { - -fx-background-color: #F1F1F1; + -fx-background-color: -anthropic-scrollbar-track; -fx-background-insets: 0.0; } .scroll-bar:vertical .thumb, .scroll-bar:horizontal .thumb { - -fx-background-color: #BCBCBC; + -fx-background-color: -anthropic-scrollbar-thumb; -fx-background-insets: 0.0; -fx-background-radius: 1.0; } @@ -1831,7 +2085,7 @@ } .scroll-bar > .increment-button > .increment-arrow, .scroll-bar > .decrement-button > .decrement-arrow { - -fx-background-color: rgb(150.0, 150.0, 150.0); + -fx-background-color: -monet-on-surface-variant; } .scroll-bar > .increment-button > .increment-arrow { diff --git a/HMCL/src/main/resources/assets/fonts/LICENSE-SourceHan.txt b/HMCL/src/main/resources/assets/fonts/LICENSE-SourceHan.txt new file mode 100644 index 0000000000..3ff0ccaba0 --- /dev/null +++ b/HMCL/src/main/resources/assets/fonts/LICENSE-SourceHan.txt @@ -0,0 +1,96 @@ +Copyright 2014-2025 Adobe (http://www.adobe.com/), with Reserved Font +Name 'Source'. Source is a trademark of Adobe in the United States +and/or other countries. + +This Font Software is licensed under the SIL Open Font License, +Version 1.1. + +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font +creation efforts of academic and linguistic communities, and to +provide a free and open framework in which fonts may be shared and +improved in partnership with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply to +any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software +components as distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, +deleting, or substituting -- in part or in whole -- any of the +components of the Original Version, by changing formats or by porting +the Font Software to a new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, +modify, redistribute, and sell modified and unmodified copies of the +Font Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, in +Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the +corresponding Copyright Holder. This restriction only applies to the +primary font name as presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created using +the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/HMCL/src/main/resources/assets/fonts/SourceHanSansSC-Bold.otf b/HMCL/src/main/resources/assets/fonts/SourceHanSansSC-Bold.otf new file mode 100644 index 0000000000..c197e35e77 Binary files /dev/null and b/HMCL/src/main/resources/assets/fonts/SourceHanSansSC-Bold.otf differ diff --git a/HMCL/src/main/resources/assets/fonts/SourceHanSerifSC-Regular.otf b/HMCL/src/main/resources/assets/fonts/SourceHanSerifSC-Regular.otf new file mode 100644 index 0000000000..e0cd3e6160 Binary files /dev/null and b/HMCL/src/main/resources/assets/fonts/SourceHanSerifSC-Regular.otf differ diff --git a/HMCL/src/main/resources/assets/lang/I18N.properties b/HMCL/src/main/resources/assets/lang/I18N.properties index 397a047fca..9b8ca0924b 100644 --- a/HMCL/src/main/resources/assets/lang/I18N.properties +++ b/HMCL/src/main/resources/assets/lang/I18N.properties @@ -936,9 +936,9 @@ launcher.cache_directory.disabled=Disabled launcher.cache_directory.invalid=Failed to create a cache directory, falling back to default. launcher.crash=HMCL has encountered a fatal error! Please copy the following log and ask for help on GitHub or other Minecraft forums. launcher.crash.java_internal_error=HMCL has encountered a fatal error because your Java is corrupted. Please uninstall your Java and download a suitable Java here. -launcher.title=Hello Minecraft! Launcher v%s -launcher.title.modified=Hello Minecraft! Launcher v%s (Modified) -launcher.title.unofficial=Hello Minecraft! Launcher v%s (Unofficial Build) +launcher.title=TDLM v%s +launcher.title.modified=TDLM v%s (Modified) +launcher.title.unofficial=TDLM v%s (Unofficial Build) launcher.update_java=Please update your Java version. libraries.download=Downloading Libraries diff --git a/HMCL/src/main/resources/assets/lang/I18N_lzh.properties b/HMCL/src/main/resources/assets/lang/I18N_lzh.properties index 6bea50fe42..09ad8bc4a6 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_lzh.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_lzh.properties @@ -756,9 +756,9 @@ launcher.cache_directory.disabled=禁 (恒用戲案夾之徑) launcher.cache_directory.invalid=自訂之快取夾不能立。既復初。 launcher.crash=HMCL 有謬而弗能正。宜鈔下文而報謬于右下之鈕。 launcher.crash.java_internal_error=HMCL 不能行,以爪哇壞也。宜去是爪哇,而擊此以置爪哇之適者。 -launcher.title=Hello Minecraft! Launcher v%s -launcher.title.modified=Hello Minecraft! Launcher %s (已改) -launcher.title.unofficial=Hello Minecraft! Launcher %s (非官版) +launcher.title=TDLM v%s +launcher.title.modified=TDLM %s (已改) +launcher.title.unofficial=TDLM %s (非官版) launcher.update_java=宜迭更爪哇。\n君可求助於 https://docs.hmcl.net/help.html。 libraries.download=引之所依 diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh_Hans.properties b/HMCL/src/main/resources/assets/lang/I18N_zh_Hans.properties index 746fd278dc..3726fb6f26 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh_Hans.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh_Hans.properties @@ -759,9 +759,9 @@ launcher.cache_directory.disabled=禁用 (总是使用游戏文件夹路径) launcher.cache_directory.invalid=无法创建自定义的缓存文件夹。已经恢复到默认设置。 launcher.crash=HMCL 遇到了无法处理的错误。请复制下列内容并点击右下角的按钮反馈问题。 launcher.crash.java_internal_error=HMCL 由于当前 Java 损坏而无法继续运行。请卸载当前 Java,点击 此处 安装合适的 Java 版本。 -launcher.title=Hello Minecraft! Launcher %s -launcher.title.modified=Hello Minecraft! Launcher %s (已修改) -launcher.title.unofficial=Hello Minecraft! Launcher %s (非官方构建) +launcher.title=TDLM %s +launcher.title.modified=TDLM %s (已修改) +launcher.title.unofficial=TDLM %s (非官方构建) launcher.update_java=请更新你的 Java。\n你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。 libraries.download=下载依赖库 diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh_Hant.properties b/HMCL/src/main/resources/assets/lang/I18N_zh_Hant.properties index 50930c7fd3..213687717a 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh_Hant.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh_Hant.properties @@ -753,9 +753,9 @@ launcher.cache_directory.disabled=停用 launcher.cache_directory.invalid=無法建立自訂的快取目錄。已還原至預設設定。 launcher.crash=HMCL 遇到了無法處理的錯誤。請複製下列內容並透過 GitHub 或其他 Minecraft 論壇回報問題。 launcher.crash.java_internal_error=HMCL 由於目前 Java 損壞而無法繼續執行。請移除目前 Java,點擊 此處 安裝合適的 Java 版本。 -launcher.title=Hello Minecraft! Launcher %s -launcher.title.modified=Hello Minecraft! Launcher %s (已修改) -launcher.title.unofficial=Hello Minecraft! Launcher %s (非官方建置) +launcher.title=TDLM %s +launcher.title.modified=TDLM %s (已修改) +launcher.title.unofficial=TDLM %s (非官方建置) launcher.update_java=請更新你的 Java libraries.download=下載依賴庫 diff --git a/HMCL/src/test/java/org/jackhuang/hmcl/setting/ThemeColorTest.java b/HMCL/src/test/java/org/jackhuang/hmcl/setting/ThemeColorTest.java index 74c9db2bc9..80d0e1d74a 100644 --- a/HMCL/src/test/java/org/jackhuang/hmcl/setting/ThemeColorTest.java +++ b/HMCL/src/test/java/org/jackhuang/hmcl/setting/ThemeColorTest.java @@ -15,6 +15,8 @@ public final class ThemeColorTest { @Test public void testOf() { assertEquals(new ThemeColor("#AABBCC", Color.web("#AABBCC")), ThemeColor.of("#AABBCC")); + assertEquals(new ThemeColor("anthropic_orange", Color.web("#D97757")), ThemeColor.of("anthropic_orange")); + assertEquals(new ThemeColor("stone", Color.web("#3D453C")), ThemeColor.of("stone")); assertEquals(new ThemeColor("blue", Color.web("#5C6BC0")), ThemeColor.of("blue")); assertEquals(new ThemeColor("darker_blue", Color.web("#283593")), ThemeColor.of("darker_blue")); assertEquals(new ThemeColor("green", Color.web("#43A047")), ThemeColor.of("green"));