From aa4b2ba3bb3de4ebcb62b7d523c4131c808e44da Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 20 Mar 2026 23:27:25 +0000 Subject: [PATCH] Hoist hardcoded colors and strings to OpticsLabColors and i18n MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add blockerFillProperty, glassBorderStrokeProperty, measuringTapeTextColorProperty, measuringTapeBackgroundColorProperty, wavelengthThumbStrokeProperty, and halfPlaneGlassFill() to OpticsLabColors.ts so all colours are profile-aware. - CircleBlockerView: use OpticsLabColors.blockerFillProperty / blockerBackStrokeProperty instead of local constants. - CircleGlassView: use OpticsLabColors.glassFillProperty / glassStrokeProperty (same values already defined there). - HalfPlaneGlassView: use OpticsLabColors.glassBorderStrokeProperty and the new halfPlaneGlassFill() helper. - SymmetricWavelengthThumb: use OpticsLabColors.wavelengthThumbStrokeProperty. - EditControlHelpers: use OpticsLabColors.overlayInputBackgroundProperty / overlayInputBorderProperty (matching values already in colors file); use i18n wavelengthStringProperty for the λ label. - SimScreenView: use OpticsLabColors for measuring tape text/background colours; use i18n metersUnitStringProperty for the unit name. - Add ui.metersUnit and controls.wavelength to strings_en.json and strings_fr.json; expose them through StringManager. https://claude.ai/code/session_01LazmDycdJFcxjWRsS49ibG --- src/OpticsLabColors.ts | 27 +++++++++++++++++++ src/common/view/SimScreenView.ts | 10 ++++--- src/common/view/SymmetricWavelengthThumb.ts | 3 ++- src/common/view/blockers/CircleBlockerView.ts | 7 +++-- .../view/edit-controls/EditControlHelpers.ts | 15 ++++++----- src/common/view/glass/CircleGlassView.ts | 9 +++---- src/common/view/glass/HalfPlaneGlassView.ts | 20 +++----------- src/i18n/StringManager.ts | 4 +++ src/i18n/strings_en.json | 4 ++- src/i18n/strings_fr.json | 4 ++- 10 files changed, 64 insertions(+), 39 deletions(-) diff --git a/src/OpticsLabColors.ts b/src/OpticsLabColors.ts index cdab746..12c6740 100644 --- a/src/OpticsLabColors.ts +++ b/src/OpticsLabColors.ts @@ -201,8 +201,35 @@ const OpticsLabColors = { // ── Carousel icons ────────────────────────────────────────────────────────── iconRayStrokeProperty: profileColor("iconRayStroke", "#44ee66", "#22cc44"), pointSourceFillProperty: profileColor("pointSourceFill", "#ff8844", "#ff8844"), + + // ── Blocker fill ──────────────────────────────────────────────────────────── + blockerFillProperty: profileColor("blockerFill", "rgba(30, 30, 30, 0.5)", "rgba(30, 30, 30, 0.5)"), + + // ── Glass border (high-opacity stroke for half-plane boundary line) ───────── + glassBorderStrokeProperty: profileColor("glassBorderStroke", "rgba(60, 130, 210, 0.95)", "rgba(60, 130, 210, 0.95)"), + + // ── Measuring tape ────────────────────────────────────────────────────────── + measuringTapeTextColorProperty: profileColor("measuringTapeTextColor", "white", "black"), + measuringTapeBackgroundColorProperty: profileColor( + "measuringTapeBackground", + "rgba(0,0,0,0.65)", + "rgba(255,255,255,0.65)", + ), + + // ── Wavelength thumb outline ──────────────────────────────────────────────── + wavelengthThumbStrokeProperty: profileColor("wavelengthThumbStroke", "rgba(0,0,0,0.55)", "rgba(0,0,0,0.55)"), }; opticsLab.register("OpticsLabColors", OpticsLabColors); +/** + * Returns the fill color for a half-plane glass element, scaling opacity + * with the refractive index so denser glass appears more opaque. + * n=1 → ~0.05 (barely visible), n=3 → ~0.40 + */ +export function halfPlaneGlassFill(refIndex: number): string { + const opacity = 0.05 + ((refIndex - 1.0) / 2.0) * 0.35; + return `rgba(100, 160, 255, ${opacity.toFixed(3)})`; +} + export default OpticsLabColors; diff --git a/src/common/view/SimScreenView.ts b/src/common/view/SimScreenView.ts index 5f0b15f..c57eb70 100644 --- a/src/common/view/SimScreenView.ts +++ b/src/common/view/SimScreenView.ts @@ -237,12 +237,16 @@ export class RayTracingCommonView extends ScreenView { const protractorVisibleProperty = new BooleanProperty(false); // Measuring tape – uses model coordinates (metres) - const measuringTapeUnitsProperty = new Property({ name: "m", multiplier: 1 }); + const uiStringsForTape = StringManager.getInstance().getUIStrings(); + const measuringTapeUnitsProperty = new Property({ + name: uiStringsForTape.metersUnitStringProperty.value, + multiplier: 1, + }); const measuringTapeNode = new MeasuringTapeNode(measuringTapeUnitsProperty, { modelViewTransform: modelViewTransform, significantFigures: 2, - textColor: "white", - textBackgroundColor: "rgba(0,0,0,0.65)", + textColor: OpticsLabColors.measuringTapeTextColorProperty, + textBackgroundColor: OpticsLabColors.measuringTapeBackgroundColorProperty, basePositionProperty: new Property(new Vector2(2, 1)), tipPositionProperty: new Property(new Vector2(3, 1)), baseDragStarted: () => { diff --git a/src/common/view/SymmetricWavelengthThumb.ts b/src/common/view/SymmetricWavelengthThumb.ts index 4f34d69..a621763 100644 --- a/src/common/view/SymmetricWavelengthThumb.ts +++ b/src/common/view/SymmetricWavelengthThumb.ts @@ -18,6 +18,7 @@ import { Shape } from "scenerystack/kite"; import { Node, Path } from "scenerystack/scenery"; import { VisibleColor } from "scenerystack/scenery-phet"; import { Tandem } from "scenerystack/tandem"; +import OpticsLabColors from "../../OpticsLabColors.js"; import { SLIDER_THUMB_HEIGHT, SLIDER_THUMB_WIDTH, @@ -59,7 +60,7 @@ export class SymmetricWavelengthThumb extends Node { super({ tandem: Tandem.OPT_OUT }); const body = new Path(buildBowtiePath(), { - stroke: "rgba(0,0,0,0.55)", + stroke: OpticsLabColors.wavelengthThumbStrokeProperty, lineWidth: WAVELENGTH_THUMB_OUTLINE_WIDTH, }); diff --git a/src/common/view/blockers/CircleBlockerView.ts b/src/common/view/blockers/CircleBlockerView.ts index 9053335..7caa940 100644 --- a/src/common/view/blockers/CircleBlockerView.ts +++ b/src/common/view/blockers/CircleBlockerView.ts @@ -8,14 +8,13 @@ import { Shape } from "scenerystack/kite"; import type { ModelViewTransform2 } from "scenerystack/phetcommon"; import { type Circle, Path, type RichDragListener } from "scenerystack/scenery"; +import OpticsLabColors from "../../../OpticsLabColors.js"; import { GLASS_STROKE_WIDTH } from "../../../OpticsLabConstants.js"; import opticsLab from "../../../OpticsLabNamespace.js"; import type { CircleBlocker } from "../../model/blockers/CircleBlocker.js"; import { BaseOpticalElementView } from "../BaseOpticalElementView.js"; import { attachEndpointDrag, attachTranslationDrag, createHandle } from "../ViewHelpers.js"; -const BLOCKER_FILL = "rgba(30, 30, 30, 0.5)"; -const BLOCKER_STROKE = "#555"; const BLOCKER_STROKE_WIDTH = GLASS_STROKE_WIDTH; export class CircleBlockerView extends BaseOpticalElementView { @@ -31,8 +30,8 @@ export class CircleBlockerView extends BaseOpticalElementView { super(); this.circlePath = new Path(null, { - fill: BLOCKER_FILL, - stroke: BLOCKER_STROKE, + fill: OpticsLabColors.blockerFillProperty, + stroke: OpticsLabColors.blockerBackStrokeProperty, lineWidth: BLOCKER_STROKE_WIDTH, }); this.handleCenter = createHandle(blocker.p1, modelViewTransform); diff --git a/src/common/view/edit-controls/EditControlHelpers.ts b/src/common/view/edit-controls/EditControlHelpers.ts index cb03c16..fbc2c69 100644 --- a/src/common/view/edit-controls/EditControlHelpers.ts +++ b/src/common/view/edit-controls/EditControlHelpers.ts @@ -11,6 +11,7 @@ import { Dimension2, Range } from "scenerystack/dot"; import type { Node } from "scenerystack/scenery"; import { NumberControl, SpectrumSliderTrack, VisibleColor } from "scenerystack/scenery-phet"; import { Tandem } from "scenerystack/tandem"; +import { StringManager } from "../../../i18n/StringManager.js"; import OpticsLabColors from "../../../OpticsLabColors.js"; import { SEGMENT_LENGTH_MAX, @@ -79,8 +80,8 @@ export function makeControl( numberDisplayOptions: { decimalPlaces: decimalPlacesForDelta(delta), textOptions: { fill: OpticsLabColors.overlayValueFillProperty, font: LABEL_FONT }, - backgroundFill: "rgba(0,0,0,0.35)", - backgroundStroke: "rgba(100,100,120,0.6)", + backgroundFill: OpticsLabColors.overlayInputBackgroundProperty, + backgroundStroke: OpticsLabColors.overlayInputBorderProperty, }, sliderOptions: { trackSize: SLIDER_TRACK_SIZE, @@ -116,7 +117,7 @@ export function makeWavelengthControl( const thumbNode = new SymmetricWavelengthThumb(prop); - return new NumberControl("λ", prop, range, { + return new NumberControl(StringManager.getInstance().getControlStrings().wavelengthStringProperty, prop, range, { delta: WAVELENGTH_CONTROL_DELTA, includeArrowButtons: true, soundGenerator: null, @@ -129,8 +130,8 @@ export function makeWavelengthControl( decimalPlaces: 0, valuePattern: "{0} nm", textOptions: { fill: OpticsLabColors.overlayValueFillProperty, font: LABEL_FONT }, - backgroundFill: "rgba(0,0,0,0.35)", - backgroundStroke: "rgba(100,100,120,0.6)", + backgroundFill: OpticsLabColors.overlayInputBackgroundProperty, + backgroundStroke: OpticsLabColors.overlayInputBorderProperty, }, sliderOptions: { trackNode, @@ -154,8 +155,8 @@ export function numberControlOptions(delta: number, decimalPlaces: number) { numberDisplayOptions: { decimalPlaces, textOptions: { fill: OpticsLabColors.overlayValueFillProperty, font: LABEL_FONT }, - backgroundFill: "rgba(0,0,0,0.35)", - backgroundStroke: "rgba(100,100,120,0.6)", + backgroundFill: OpticsLabColors.overlayInputBackgroundProperty, + backgroundStroke: OpticsLabColors.overlayInputBorderProperty, }, sliderOptions: { trackSize: SLIDER_TRACK_SIZE, diff --git a/src/common/view/glass/CircleGlassView.ts b/src/common/view/glass/CircleGlassView.ts index 3550ab3..ed730a5 100644 --- a/src/common/view/glass/CircleGlassView.ts +++ b/src/common/view/glass/CircleGlassView.ts @@ -9,16 +9,13 @@ import { Shape } from "scenerystack/kite"; import type { ModelViewTransform2 } from "scenerystack/phetcommon"; import { type Circle, Path, type RichDragListener } from "scenerystack/scenery"; +import OpticsLabColors from "../../../OpticsLabColors.js"; import { GLASS_STROKE_WIDTH } from "../../../OpticsLabConstants.js"; import opticsLab from "../../../OpticsLabNamespace.js"; import type { CircleGlass } from "../../model/glass/CircleGlass.js"; import { BaseOpticalElementView } from "../BaseOpticalElementView.js"; import { attachEndpointDrag, attachTranslationDrag, createHandle } from "../ViewHelpers.js"; -// ── Styling constants ───────────────────────────────────────────────────────── -const GLASS_FILL = "rgba(100, 180, 255, 0.22)"; -const GLASS_STROKE = "rgba(60, 130, 210, 0.8)"; - export class CircleGlassView extends BaseOpticalElementView { public readonly bodyDragListener: RichDragListener; private readonly circlePath: Path; @@ -32,8 +29,8 @@ export class CircleGlassView extends BaseOpticalElementView { super(); this.circlePath = new Path(null, { - fill: GLASS_FILL, - stroke: GLASS_STROKE, + fill: OpticsLabColors.glassFillProperty, + stroke: OpticsLabColors.glassStrokeProperty, lineWidth: GLASS_STROKE_WIDTH, }); this.handleCenter = createHandle(glass.p1, modelViewTransform); diff --git a/src/common/view/glass/HalfPlaneGlassView.ts b/src/common/view/glass/HalfPlaneGlassView.ts index 8159054..ee6b0c1 100644 --- a/src/common/view/glass/HalfPlaneGlassView.ts +++ b/src/common/view/glass/HalfPlaneGlassView.ts @@ -12,6 +12,7 @@ import { Shape } from "scenerystack/kite"; import type { ModelViewTransform2 } from "scenerystack/phetcommon"; import { type Circle, Path, type RichDragListener } from "scenerystack/scenery"; +import OpticsLabColors, { halfPlaneGlassFill } from "../../../OpticsLabColors.js"; import { HALF_PLANE_BORDER_WIDTH, HALF_PLANE_GLASS_DEPTH_PX, @@ -28,19 +29,6 @@ import { createLineBodyHitPath, } from "../ViewHelpers.js"; -// ── Styling constants ───────────────────────────────────────────────────────── -const BORDER_STROKE = "rgba(60, 130, 210, 0.95)"; - -/** Map refractive index to a fill opacity so denser glass looks more opaque. */ -function glassOpacity(refIndex: number): number { - // n=1 → ~0.05 (barely visible), n=3 → ~0.40 - return 0.05 + ((refIndex - 1.0) / 2.0) * 0.35; -} - -function glassFill(refIndex: number): string { - return `rgba(100, 160, 255, ${glassOpacity(refIndex).toFixed(3)})`; -} - export class HalfPlaneGlassView extends BaseOpticalElementView { public readonly bodyDragListener: RichDragListener; private readonly glassPath: Path; @@ -56,11 +44,11 @@ export class HalfPlaneGlassView extends BaseOpticalElementView { super(); this.glassPath = new Path(null, { - fill: glassFill(glass.refIndex), + fill: halfPlaneGlassFill(glass.refIndex), pickable: false, }); this.borderPath = new Path(null, { - stroke: BORDER_STROKE, + stroke: OpticsLabColors.glassBorderStrokeProperty, lineWidth: HALF_PLANE_BORDER_WIDTH, lineCap: "butt", pickable: false, @@ -125,7 +113,7 @@ export class HalfPlaneGlassView extends BaseOpticalElementView { public override rebuild(): void { // Update fill opacity to reflect current refractive index - this.glassPath.fill = glassFill(this.glass.refIndex); + this.glassPath.fill = halfPlaneGlassFill(this.glass.refIndex); const { p1, p2 } = this.glass; const dx = p2.x - p1.x; diff --git a/src/i18n/StringManager.ts b/src/i18n/StringManager.ts index 858dcef..9ebdced 100644 --- a/src/i18n/StringManager.ts +++ b/src/i18n/StringManager.ts @@ -61,6 +61,7 @@ export class StringManager { measuringTapeStringProperty: ReadOnlyProperty; protractorStringProperty: ReadOnlyProperty; extendedRaysStringProperty: ReadOnlyProperty; + metersUnitStringProperty: ReadOnlyProperty; } { return { gridStringProperty: this.stringProperties.ui.gridStringProperty, @@ -69,10 +70,12 @@ export class StringManager { measuringTapeStringProperty: this.stringProperties.ui.measuringTapeStringProperty, protractorStringProperty: this.stringProperties.ui.protractorStringProperty, extendedRaysStringProperty: this.stringProperties.ui.extendedRaysStringProperty, + metersUnitStringProperty: this.stringProperties.ui.metersUnitStringProperty, }; } public getControlStrings(): { + wavelengthStringProperty: ReadOnlyProperty; brightnessStringProperty: ReadOnlyProperty; emissionAngleStringProperty: ReadOnlyProperty; divergenceStringProperty: ReadOnlyProperty; @@ -94,6 +97,7 @@ export class StringManager { } { const ctrl = this.stringProperties.controls; return { + wavelengthStringProperty: ctrl.wavelengthStringProperty, brightnessStringProperty: ctrl.brightnessStringProperty, emissionAngleStringProperty: ctrl.emissionAngleStringProperty, divergenceStringProperty: ctrl.divergenceStringProperty, diff --git a/src/i18n/strings_en.json b/src/i18n/strings_en.json index 38212d3..da629f4 100644 --- a/src/i18n/strings_en.json +++ b/src/i18n/strings_en.json @@ -26,10 +26,12 @@ "rayDensity": "Ray Density", "measuringTape": "Measuring Tape", "protractor": "Protractor", - "extendedRays": "Extended Rays" + "extendedRays": "Extended Rays", + "metersUnit": "m" }, "controls": { "brightness": "Brightness", + "wavelength": "λ", "emissionAngle": "Emission Angle (°)", "divergence": "Divergence (°)", "height": "Height (m)", diff --git a/src/i18n/strings_fr.json b/src/i18n/strings_fr.json index b8ebf23..69fa257 100644 --- a/src/i18n/strings_fr.json +++ b/src/i18n/strings_fr.json @@ -26,10 +26,12 @@ "rayDensity": "Densité des rayons", "measuringTape": "Ruban à mesurer", "protractor": "Rapporteur", - "extendedRays": "Rayons prolongés" + "extendedRays": "Rayons prolongés", + "metersUnit": "m" }, "controls": { "brightness": "Luminosité", + "wavelength": "λ", "emissionAngle": "Angle d'émission (°)", "divergence": "Divergence (°)", "height": "Hauteur (m)",