Code Sketch


yoiiiiiiiiiiiiiiiiiiiiiiii
By: Mhalsakant School
Category: Programming
import javax.swing._
import javax.swing.border._
import java.awt._
import java.awt.event._
import java.awt.image.BufferedImage
import java.awt.datatransfer.StringSelection
import javax.imageio.ImageIO
import java.io._
import java.text.SimpleDateFormat
import java.util.Date
import java.security.MessageDigest
import scala.collection.mutable.ArrayBuffer

// KUNDALI MATH COMPATIBILITY: use java.lang.Math explicitly so Kojo/Scala
// resolves floor, sin, cos, tan and angle-conversion functions reliably.

// ==========================================================
// ULTRA SCALA SWING AI LEARNING STUDIO
// FULL MASTER PROMPT VERSION
// ==========================================================

// ----------------------------------------------------------
// COLORS
// ----------------------------------------------------------

val BG =
  new Color(
    16,
    20,
    28
  )

val PANEL_BG =
  new Color(
    24,
    30,
    42
  )

val CARD_BG =
  new Color(
    31,
    39,
    54
  )

val TEXT =
  new Color(
    235,
    240,
    250
  )

val MUTED =
  new Color(
    165,
    175,
    195
  )

val ACCENT =
  new Color(
    90,
    150,
    255
  )

val SUCCESS =
  new Color(
    70,
    200,
    120
  )

val WARNING =
  new Color(
    245,
    180,
    70
  )

val DANGER =
  new Color(
    235,
    85,
    85
  )

// ----------------------------------------------------------
// PASSWORDS
// ----------------------------------------------------------

val passwordSecret =
  Array(
    121,
    97,
    100,
    110,
    101,
    115,
    104,
    50,
    48,
    49,
    51
  ).map(
    _.toChar
  ).mkString

val masterPromptPasswordHash =
  "689c6803e3a0afaf86119370e6cb7173694eff7f0b8bd093782c38e510bd8dfa"

// ----------------------------------------------------------
// DATA
// ----------------------------------------------------------

val activityHistory =
  ArrayBuffer[String]()

val notesData =
  ArrayBuffer[String]()

val tasksData =
  ArrayBuffer[String]()

val projectData =
  ArrayBuffer[String]()

// ----------------------------------------------------------
// HELPERS
// ----------------------------------------------------------

def sha256(
    value: String
): String = {

  val md =
    MessageDigest.getInstance(
      "SHA-256"
    )

  val bytes =
    md.digest(
      value.getBytes(
        "UTF-8"
      )
    )

  bytes
    .map { b =>

      String.format(
        "%02x",
        Byte.box(
          b
        )
      )
    }
    .mkString
}

def nowText(): String = {

  new SimpleDateFormat(
    "yyyy-MM-dd HH:mm:ss"
  ).format(
    new Date()
  )
}

def recordActivity(
    message: String
): Unit = {

  activityHistory +=
    "[" +
      nowText() +
      "] " +
      message
}

def makeLabel(
    text: String,
    size: Int,
    bold: Boolean
): JLabel = {

  val label =
    new JLabel(
      text
    )

  label.setForeground(
    TEXT
  )

  label.setFont(
    new Font(
      "SansSerif",
      if (
        bold
      )
        Font.BOLD
      else
        Font.PLAIN,
      size
    )
  )

  label
}

def makeButton(
    text: String
): JButton = {

  val button =
    new JButton(
      text
    )

  button.setFocusPainted(
    false
  )

  button.setBackground(
    CARD_BG
  )

  button.setForeground(
    TEXT
  )

  button.setBorder(
    new CompoundBorder(
      new LineBorder(
        new Color(
          70,
          85,
          110
        )
      ),
      new EmptyBorder(
        8,
        14,
        8,
        14
      )
    )
  )

  button
}

def makeArea(): JTextArea = {

  val area =
    new JTextArea()

  area.setLineWrap(
    true
  )

  area.setWrapStyleWord(
    true
  )

  area.setBackground(
    new Color(
      12,
      16,
      23
    )
  )

  area.setForeground(
    TEXT
  )

  area.setCaretColor(
    TEXT
  )

  area.setFont(
    new Font(
      "Monospaced",
      Font.PLAIN,
      14
    )
  )

  area.setBorder(
    new EmptyBorder(
      10,
      10,
      10,
      10
    )
  )

  area
}

def makeTextField(): JTextField = {

  val field =
    new JTextField()

  field.setBackground(
    new Color(
      12,
      16,
      23
    )
  )

  field.setForeground(
    TEXT
  )

  field.setCaretColor(
    TEXT
  )

  field.setFont(
    new Font(
      "SansSerif",
      Font.PLAIN,
      14
    )
  )

  field
}

def makeScroll(
    component: Component
): JScrollPane = {

  val scroll =
    new JScrollPane(
      component
    )

  scroll.setBorder(
    new LineBorder(
      new Color(
        65,
        80,
        105
      )
    )
  )

  scroll.getViewport.setBackground(
    BG
  )

  scroll
}

def showInfo(
    parent: Component,
    title: String,
    message: String
): Unit = {

  JOptionPane.showMessageDialog(
    parent,
    message,
    title,
    JOptionPane.INFORMATION_MESSAGE
  )
}

def showWarning(
    parent: Component,
    title: String,
    message: String
): Unit = {

  JOptionPane.showMessageDialog(
    parent,
    message,
    title,
    JOptionPane.WARNING_MESSAGE
  )
}

def showError(
    parent: Component,
    title: String,
    message: String
): Unit = {

  JOptionPane.showMessageDialog(
    parent,
    message,
    title,
    JOptionPane.ERROR_MESSAGE
  )
}

def copyToClipboard(
    text: String
): Unit = {

  Toolkit
    .getDefaultToolkit
    .getSystemClipboard
    .setContents(
      new StringSelection(
        text
      ),
      null
    )
}

def saveTextFile(
    text: String,
    defaultName: String
): Unit = {

  val chooser =
    new JFileChooser()

  chooser.setSelectedFile(
    new File(
      defaultName
    )
  )

  val result =
    chooser.showSaveDialog(
      null
    )

  if (
    result ==
      JFileChooser.APPROVE_OPTION
  ) {

    val file =
      chooser.getSelectedFile

    try {

      val writer =
        new BufferedWriter(
          new FileWriter(
            file
          )
        )

      try {

        writer.write(
          text
        )

      } finally {

        writer.close()
      }

      showInfo(
        null,
        "FILE SAVED",
        "File saved successfully:\n" +
          file.getAbsolutePath
      )

    } catch {

      case ex: Throwable =>

        showError(
          null,
          "SAVE ERROR",
          ex.toString
        )
    }
  }
}

def scaleImage(
    image: BufferedImage,
    maxW: Int,
    maxH: Int
): BufferedImage = {

  val width =
    image.getWidth

  val height =
    image.getHeight

  if (
    width <= 0 ||
    height <= 0
  ) {

    return image
  }

  val scale =
    Math.min(
      maxW.toDouble / width.toDouble,
      maxH.toDouble / height.toDouble
    )

  val newW =
    Math.max(
      1,
      (width * scale).toInt
    )

  val newH =
    Math.max(
      1,
      (height * scale).toInt
    )

  val output =
    new BufferedImage(
      newW,
      newH,
      BufferedImage.TYPE_INT_ARGB
    )

  val g =
    output.createGraphics()

  try {

    g.drawImage(
      image,
      0,
      0,
      newW,
      newH,
      null
    )

  } finally {

    g.dispose()
  }

  output
}

// ==========================================================
// MASTER PROMPT GENERATOR - RUN-SAFE VERSION
// ==========================================================

def createMasterPrompt(
    idea: String
): String = {

  val safeIdea =
    if (idea == null || idea.trim.isEmpty)
      "No idea provided."
    else
      idea.trim

  val prompt =
    s"""
MASTER KOJO APPLICATION ENGINEER ? SELF-CONTAINED GENERATION CONTRACT
=====================================================================

ROLE
====
You are an expert Kojo developer, Scala developer, Java Swing developer,
Java2D rendering engineer, game programmer, UI/UX designer, debugger,
compiler-error analyst and software architect.

The USER IDEA below is the complete specification. Build that project.

USER IDEA
=========
$safeIdea

IMPORTANT CONTEXT INDEPENDENCE
==============================
This prompt may be pasted into a completely fresh ChatGPT conversation on a
computer where there is no sign-in, no previous conversation, no memory, no
uploaded file and no earlier answer.

Therefore:
- Never depend on previous chats.
- Never say that a previous file is required.
- Never refer to hidden memory.
- Never say "as before" or "continue the earlier code".
- Treat USER IDEA above as the only product specification.
- Reconstruct every required part from this prompt and USER IDEA.

PRIMARY RESULT
==============
Generate ONE complete Scala source file intended to be pasted directly into
Kojo and run.

The generated source must implement the requested project itself. It must not
be a tutorial, pseudo-code sample, plan, mock-up, launcher-only screen,
code-viewer-only program, or partial implementation.

The first visible window must be the ACTUAL REQUESTED APPLICATION or GAME.
Do not show a generic launcher first unless the USER IDEA explicitly requests
a launcher, login screen, menu, or other first screen.

===============================================================
ABSOLUTE RAW-SOURCE OUTPUT CONTRACT
===============================================================

Return ONLY raw Scala source code.

Never output:
- Markdown
- three consecutive backtick characters
- a language-tagged Markdown code block
- Markdown code fences
- triple-single-quote markers
- filename headings
- commentary before code
- commentary after code
- JSON around the code
- an explanation around the code
- an incomplete patch
- "rest of code omitted"
- TODO placeholders
- "insert code here"
- "continue from here"

The first character of the answer must be part of valid Scala source.
The final character of the answer must be part of valid Scala source.

Do not place Markdown fences inside the generated source either.

===============================================================
KOJO TARGET ? HARD COMPATIBILITY CONTRACT
===============================================================

Target environment: standard desktop Kojo with its normal Scala + Java
runtime.

Prefer APIs that are normally available without installing packages:
- javax.swing.*
- java.awt.*
- java.awt.event.*
- java.awt.geom.*
- java.awt.image.*
- java.io.*
- java.nio.file.* when persistence is appropriate
- javax.sound.sampled.* when sound is requested and safely supported
- standard Scala collections and utilities

Do NOT assume these are installed or available:
- JavaFX
- LWJGL
- LibGDX
- Processing
- Unity
- Unreal
- OpenGL bindings
- external game engines
- Maven dependencies
- Gradle dependencies
- SBT plugins
- third-party libraries
- browser frameworks
- Python
- Node.js
- cloud APIs
- remote servers

Do not import a library merely because it would make the feature easier.

===============================================================
KOJO STARTUP RULE ? MOST IMPORTANT
===============================================================

The generated application MUST visibly open when the user presses RUN in Kojo.

For Kojo, do NOT depend on worksheet-specific hidden behavior.
Do NOT wrap the entire project in "object Main extends App".
Do NOT define a launcher method and forget to call it.
Do NOT create a JFrame inside an unused method.
Do NOT require the user to manually call a startup function.
Do NOT rely on console input before the GUI appears.
Do NOT block startup with long loops.

Use a direct, reliable startup path suitable for Kojo, preferably:

SwingUtilities.invokeLater(
  new Runnable {
    def run(): Unit = {
      // construct the actual requested application here
      // install the actual requested content here
      // show the real window here
    }
  }
)

The startup path MUST reach all of the following for the real requested
application window:

frame.setContentPane(...)
frame.setSize(...)
frame.setLocationRelativeTo(null)
frame.setVisible(true)

A valid alternative is a directly executed top-level startup block that reaches
the same operations, provided it is reliable in Kojo.

Use WindowConstants.EXIT_ON_CLOSE for normal standalone-window projects.
Never use JFrame.EXIT_ON_CLOSE.

After setVisible(true), make the requested UI focused and usable:
- request focus on the primary interaction component when appropriate
- start animation timers only after the UI exists
- call repaint after state changes

===============================================================
FIRST-WINDOW REQUIREMENT
===============================================================

When RUN is pressed, the user must see a useful first window.

The window must NOT be:
- blank
- only a title bar
- an invisible frame
- a tiny accidental frame
- a console-only program
- a placeholder
- a generic "hello world" panel

If the USER IDEA is a game, show the actual game or its requested first phase.
If the USER IDEA requires login before gameplay, show the real login UI and make
its button/event path functional.
If the USER IDEA does not ask for login, do not invent a login screen.

===============================================================
USER-IDEA FIDELITY
===============================================================

Extract and implement:
A. requested purpose
B. requested screens/phases
C. requested entities
D. player/avatar/vehicle where applicable
E. requested controls
F. requested world/environment
G. requested game/application state
H. requested interactions
I. requested score/progress/results
J. requested saving/loading
K. requested audio
L. requested camera/media behavior
M. requested multiplayer behavior
N. requested 2D/3D appearance
O. requested restart/error recovery
P. any other explicit user requirement

Do not silently remove major requested features.
Do not replace the concept with another concept.
Do not claim an unavailable feature exists.

===============================================================
FEATURE-CONNECTION RULE
===============================================================

Every advertised feature must have a complete path:

INPUT / EVENT
 -> STATE CHANGE
 -> LOGIC UPDATE
 -> RENDER / UI UPDATE
 -> USER-VISIBLE RESULT

If a feature is mentioned in the UI, it must actually do something.
Do not create dead buttons or dead keyboard commands.

===============================================================
STATE-MACHINE RULE
===============================================================

For multi-phase projects use an explicit state variable, for example:

var gameState = "MENU"

Every state must have:
- clear entry logic
- clear rendering logic
- valid input handling
- valid transition logic
- reset behavior when re-entered

Do not mix incompatible states together.

===============================================================
SCALA TYPE / SCOPE DISCIPLINE
===============================================================

Use simple, explicit types when helpful.

Rules:
- Declare every variable before it is used.
- Avoid forward references.
- Avoid duplicate variable names in the same scope.
- Avoid a val and def with the same name.
- Avoid shadowing important components.
- Use var only for changing state.
- Use val for stable references.
- Never reassign a val.
- Convert numeric types explicitly when required.
- Do not pass Double where Swing requires Int without conversion.
- Do not return Unit where a Component is required.
- Do not call methods on Unit.
- Do not use an undeclared helper class or method.

===============================================================
SWING COMPONENT RULES
===============================================================

Every component referenced by a listener must already exist before the listener
is attached.

Use standard patterns such as:

val button = new JButton("PLAY")
button.addActionListener(
  new ActionListener {
    override def actionPerformed(e: ActionEvent): Unit = {
      // action
    }
  }
)

Do not write code where a Unit-returning helper is accidentally inserted into a
panel. For example, never do:

panel.add(someMethod())

when someMethod() returns Unit.

Instead create the Component first and add the Component.

===============================================================
PAINTING CONTRACT
===============================================================

For custom graphics:
- use JPanel or JComponent
- override paintComponent
- call super.paintComponent(g) first
- cast to Graphics2D when appropriate
- use stable rendering code
- never dispose the Swing-supplied Graphics object
- only dispose Graphics objects created with create()

Prefer reusable helper methods for repeated shapes.
Do not create excessive objects inside every frame when avoidable.

===============================================================
3D / PSEUDO-3D CONTRACT
===============================================================

When USER IDEA requests 3D, realistic 3D, driving 3D, first person, third
person, an open world, an aircraft view, a 3D city, a 3D vehicle, or similar:

Do NOT make a static 2D screen and label it 3D.

If a real external 3D engine is unavailable, build a self-contained software
3D / pseudo-3D renderer using Java2D.

Use meaningful spatial concepts where appropriate:
- world X/Y/Z
- camera X/Y/Z
- camera yaw/pitch/roll where useful
- perspective projection
- horizon
- depth
- near/far clipping
- field of view
- distance scaling
- polygon surfaces
- road perspective
- object depth
- simple lighting/shading
- fog/depth fade when useful
- shadows where practical
- animated scenery

A simple projection may follow the concept:

screenX = centerX + worldX * focalLength / safeDepth
screenY = horizonY + worldY * focalLength / safeDepth

but choose a consistent coordinate system and implement it correctly.

ALWAYS protect projection math:
- never divide by zero
- reject or clamp unusable depth
- reject NaN
- reject Infinity
- clamp screen coordinates before Int conversion when necessary
- avoid enormous coordinates that can overflow

For polygon drawing, use java.awt.Polygon or addPoint calls.
Do NOT pass Array[Point] to Graphics2D.fillPolygon.

===============================================================
GAME LOOP CONTRACT
===============================================================

For real-time animation/gameplay, prefer one javax.swing.Timer.

Rules:
- create at most the timers actually required
- do not start duplicate timers
- start the timer after the UI exists
- stop the timer when leaving the active mode when appropriate
- update state in the timer
- call repaint()
- never block the Swing Event Dispatch Thread

Avoid expensive per-frame operations such as sorting huge collections unless
truly required.

===============================================================
KEYBOARD / MOUSE CONTRACT
===============================================================

Keyboard controls must actually work.

For focus-sensitive games:
- make the real game component focusable
- request focus after showing the window
- attach key handling to a reliable focused component
- remove or reset stale key state when changing game states

For mouse controls:
- attach listeners to the real visible component
- convert coordinates consistently
- handle clicks only in valid UI/game states

===============================================================
COLLISION / PHYSICS CONTRACT
===============================================================

When the idea needs collision:
- define a clear coordinate system
- use stable collision shapes
- clamp player position
- separate collision detection from rendering
- implement recovery after collision
- reset collision flags correctly

For driving games, include sensible:
- speed
- acceleration
- braking
- steering
- friction/deceleration
- road boundaries
- traffic/obstacle collision
- crash/reset behavior when requested

===============================================================
SAVE / PROFILE CONTRACT
===============================================================

When the idea requests accounts, IDs, passwords, scores or persistence:
- implement a complete local save system when internet/backend is unavailable
- never require a server merely to start the application
- store progress in a predictable local file
- catch file I/O errors
- do not let an optional save failure prevent the main window from opening

For passwords:
- never store the plaintext password when a local account system is requested
- use a standard digest such as SHA-256 for the local demo
- do not pretend this is production-grade authentication

When an account is reopened:
- restore the same profile values
- restore points exactly as saved
- restore coins/levels where applicable
- preserve IDs where applicable

===============================================================
AUDIO CONTRACT
===============================================================

When sound/music is requested:
- first prefer self-contained Java Sound
- safely handle systems with no available audio device
- never let a sound failure stop application startup
- optionally use simple generated tones where appropriate
- do not require external audio files unless the USER IDEA explicitly provides
  them

If audio is unavailable, show a harmless status such as "Audio unavailable"
and keep gameplay working.

===============================================================
CAMERA / WEBCAM CONTRACT
===============================================================

If the USER IDEA requests a camera or webcam:

Do not assume a webcam library exists in Kojo.
Do not import third-party webcam packages.
Do not make startup depend on a camera.

Use a graceful design:
- provide the requested camera concept in the UI
- attempt only supported standard APIs if genuinely available
- catch failures
- show a clear fallback state
- keep the rest of the application fully usable

Never fake a live camera feed by claiming a static image is a camera stream.

===============================================================
MULTIPLAYER CONTRACT
===============================================================

Distinguish these clearly:
1. local same-computer multiplayer
2. LAN multiplayer
3. internet/server multiplayer

Do not falsely claim that local two-player is online multiplayer.

If no server/backend is available, implement local multiplayer where sensible.
If online multiplayer is explicitly requested but no backend exists, provide a
complete local architecture and a clear offline-safe fallback without pretending
that real internet matchmaking exists.

===============================================================
PROCEDURAL-ASSET CONTRACT
===============================================================

When external images/models/sounds are unavailable, create original procedural
visuals with Java2D:
- vehicles
- buildings
- roads
- trees
- terrain
- aircraft
- UI icons
- particles
- shadows
- lighting effects

Do not require copyrighted game assets.
Do not embed external URLs as a runtime dependency.

===============================================================
UI / UX QUALITY CONTRACT
===============================================================

Make the requested project visually clear and polished.
Use:
- sensible window size
- readable fonts
- spacing
- panels/cards only when helpful
- status text
- helpful controls
- clear state indicators
- accessible button labels

Do not overcrowd the UI merely to make the source longer.
Quality and correctness are more important than line count.

===============================================================
RESET / ERROR-RECOVERY CONTRACT
===============================================================

Every restartable system must reset ALL relevant state.

Audit variables such as:
- health
- ammo
- score
- coins
- timers
- crash ticks
- landing ticks
- animation counters
- collision flags
- camera state
- enemy arrays
- player position
- level state
- selected item
- input state

A common state variable must NEVER be referenced before declaration.
Examples include variables like:
crashTicks
landingTicks
restartTicks
cooldownTicks
animationTicks

Declare them before any method that uses them.

===============================================================
COMMON COMPILER-ERROR PREVENTION
===============================================================

Explicitly audit for these known failure patterns:

1. fillPolygon(Array(...)) passed where Polygon is required
2. JFrame.EXIT_ON_CLOSE
3. object Main extends App used as the only startup mechanism
4. unused startup function that is never called
5. undefined variables
6. undefined methods
7. duplicate vals
8. duplicate defs
9. forward references
10. val reassignment
11. Unit used as a Component
12. addActionListener called on Unit
13. Int/Double mismatch
14. Float/Double mismatch
15. Long/Int mismatch
16. Array index out of bounds
17. collection index out of bounds
18. division by zero
19. NaN or Infinity in projection math
20. missing braces
21. missing parentheses
22. missing brackets
23. broken strings
24. broken multiline strings
25. accidental Markdown fences
26. accidental triple-single-quote markers
27. timers started multiple times
28. controls attached to the wrong component
29. game window never made visible
30. first window is blank
31. feature button with no action
32. state transition with no rendering
33. stale game state after restart
34. optional file/resource failure blocking startup
35. external library assumed without proof

===============================================================
MASTER GENERATED-CODE SELF-AUDIT
===============================================================

Before producing the final source, perform FOUR separate mental passes.

PASS 1 ? RAW SOURCE
Check:
- first line is Scala
- last line is Scala
- no Markdown fences
- no decorative non-Scala wrapper
- strings are closed
- multiline strings are closed

PASS 2 ? COMPILATION
Check:
- declarations
- scope
- types
- method signatures
- listener signatures
- timer types
- collection types
- numeric conversions
- component types
- imports

PASS 3 ? FEATURE PATHS
For EACH major feature, trace:
input -> state -> logic -> render -> result.
Delete or fix any advertised feature that has no working path.

PASS 4 ? STARTUP
Trace exactly:
RUN
-> Kojo executes source
-> startup block executes
-> application JFrame is constructed
-> content is created
-> listeners are attached
-> timer is started after UI setup
-> setContentPane
-> setSize / pack as appropriate
-> setLocationRelativeTo(null)
-> setVisible(true)
-> requested project is visible
-> input is usable

If this trace fails anywhere, the source is NOT finished.

===============================================================
FINAL STARTUP REQUIREMENT
===============================================================

The generated source is considered incomplete unless a visible requested
window appears automatically after RUN.

A green "Run successful" indicator alone is NOT sufficient.

===============================================================
DEPENDENCY AUDIT
===============================================================

Before final output, inspect every import.
For each non-core import ask:
"Will a normal Kojo installation have this without extra setup?"

If not, remove it or replace the implementation with a standard API.

===============================================================
NO FALSE CLAIMS
===============================================================

Do not claim "100% zero compiler errors" unless the code has actually been
compiled in the target environment.

Instead, perform the strongest source-level audit possible and ensure the code
is internally consistent.

Do not claim a camera is live when it is not.
Do not claim internet multiplayer when it is only local.
Do not claim true engine-level 3D when using software pseudo-3D.

===============================================================
CODE SIZE / QUALITY
===============================================================

Do NOT inflate the source simply to increase line count.
Every line should serve the requested project, safety, readability,
maintainability, or visual quality.

Prefer a smaller correct application over a huge broken application.

===============================================================
FINAL OUTPUT CONTRACT
===============================================================

Return exactly ONE complete raw Scala source file.

The source must:
- be self-contained
- be Kojo-oriented
- auto-start
- open the actual requested window
- implement the actual requested project
- use safe standard APIs
- contain no missing sections
- contain no TODOs
- contain no placeholders
- contain no undefined state
- contain no Markdown wrapper
- contain no extra explanation

Do the final review again after writing the source.

END OF MASTER KOJO APPLICATION ENGINEER CONTRACT
""".trim

  prompt
}

// ==========================================================
// LOGIN
// ==========================================================

def openLogin(): Unit = {

  val frame =
    new JFrame(
      "ULTRA SCALA SWING AI LEARNING STUDIO"
    )

  frame.setDefaultCloseOperation(
    WindowConstants.EXIT_ON_CLOSE
  )

  frame.setSize(
    520,
    430
  )

  frame.setLocationRelativeTo(
    null
  )

  val root =
    new JPanel(
      new BorderLayout(
        16,
        16
      )
    )

  root.setBackground(
    BG
  )

  root.setBorder(
    new EmptyBorder(
      28,
      28,
      28,
      28
    )
  )

  val title =
    makeLabel(
      "ULTRA SCALA SWING",
      30,
      true
    )

  val subtitle =
    makeLabel(
      "AI LEARNING STUDIO",
      20,
      true
    )

  val top =
    new JPanel(
      new GridLayout(
        2,
        1,
        4,
        4
      )
    )

  top.setBackground(
    BG
  )

  top.add(
    title
  )

  top.add(
    subtitle
  )

  root.add(
    top,
    BorderLayout.NORTH
  )

  val center =
    new JPanel(
      new GridLayout(
        4,
        1,
        8,
        8
      )
    )

  center.setBackground(
    BG
  )

  val info =
    makeLabel(
      "SECURE LOGIN",
      17,
      true
    )

  val passwordField =
    new JPasswordField()

  passwordField.setBackground(
    new Color(
      12,
      16,
      23
    )
  )

  passwordField.setForeground(
    TEXT
  )

  passwordField.setCaretColor(
    TEXT
  )

  passwordField.setFont(
    new Font(
      "SansSerif",
      Font.PLAIN,
      18
    )
  )

  val attemptLabel =
    makeLabel(
      "3 attempts remaining",
      14,
      false
    )

  val loginButton =
    makeButton(
      "LOGIN"
    )

  center.add(
    info
  )

  center.add(
    passwordField
  )

  center.add(
    attemptLabel
  )

  center.add(
    loginButton
  )

  root.add(
    center,
    BorderLayout.CENTER
  )

  root.add(
    makeLabel(
      "Learn ? Build ? Debug ? Create",
      13,
      false
    ),
    BorderLayout.SOUTH
  )

  var remaining =
    3

  def doLogin(): Unit = {

    val entered =
      new String(
        passwordField.getPassword
      )

    if (
      sha256(
        entered
      ) ==
        sha256(
          passwordSecret
        )
    ) {

      recordActivity(
        "Successful login"
      )

      frame.dispose()

      openStudio()

    } else {

      remaining -= 1

      attemptLabel.setText(
        remaining +
          " attempts remaining"
      )

      passwordField.setText(
        ""
      )

      recordActivity(
        "Failed login attempt"
      )

      if (
        remaining <= 0
      ) {

        showError(
          frame,
          "SECURITY LOCK",
          "3 incorrect password attempts.\n\nThe application will close."
        )

        frame.dispose()

      } else {

        showWarning(
          frame,
          "ACCESS DENIED",
          "Wrong password.\n\n" +
            remaining +
            " attempts remaining."
        )
      }
    }
  }

  loginButton.addActionListener(
    new ActionListener {

      override def actionPerformed(
          e: ActionEvent
      ): Unit = {

        doLogin()
      }
    }
  )

  passwordField.addActionListener(
    new ActionListener {

      override def actionPerformed(
          e: ActionEvent
      ): Unit = {

        doLogin()
      }
    }
  )

  frame.setContentPane(
    root
  )

  frame.setVisible(
    true
  )

  SwingUtilities.invokeLater(
    new Runnable {

      def run(): Unit = {

        passwordField.requestFocusInWindow()
      }
    }
  )
}

// ==========================================================
// STUDIO
// ==========================================================

def openStudio(): Unit = {

  val frame =
    new JFrame(
      "ULTRA SCALA SWING AI LEARNING STUDIO"
    )

  frame.setDefaultCloseOperation(
    WindowConstants.EXIT_ON_CLOSE
  )

  frame.setSize(
    1360,
    840
  )

  frame.setMinimumSize(
    new Dimension(
      1000,
      650
    )
  )

  frame.setLocationRelativeTo(
    null
  )

  val tabs =
    new JTabbedPane()

  tabs.setBackground(
    BG
  )

  tabs.setForeground(
    TEXT
  )

  // ========================================================
  // DASHBOARD
  // ========================================================

  val dashboardPanel =
    new JPanel(
      new BorderLayout(
        12,
        12
      )
    )

  dashboardPanel.setBackground(
    PANEL_BG
  )

  dashboardPanel.setBorder(
    new EmptyBorder(
      16,
      16,
      16,
      16
    )
  )

  val dashboardHeader =
    new JPanel(
      new GridLayout(
        2,
        1,
        4,
        4
      )
    )

  dashboardHeader.setBackground(
    PANEL_BG
  )

  dashboardHeader.add(
    makeLabel(
      "WELCOME TO ULTRA STUDIO",
      30,
      true
    )
  )

  dashboardHeader.add(
    makeLabel(
      "Scala ? Swing ? AI ? Coding ? Learning",
      16,
      false
    )
  )

  dashboardPanel.add(
    dashboardHeader,
    BorderLayout.NORTH
  )

  val dashboardText =
    makeArea()

  dashboardText.setEditable(
    false
  )

  dashboardText.setText(
    """ULTRA SCALA SWING AI LEARNING STUDIO

Main Features:

? Classroom
? Ask Teacher
? Code Lab
? Error Fix
? Notes
? Tasks
? Quiz
? Image Studio
? Prompt Lab
? Projects
? Utilities
? Feature Library
? History
? Settings
? MASTER ACCESS

MASTER PROMPT:

Enter the MASTER ACCESS password to open the hidden MASTER PROMPT module.

The MASTER PROMPT module does NOT ask for UID.

It does NOT ask for Name.

It asks only for your IDEA.

Write an idea such as:

"Make a realistic 3D car racing game with road,
traffic, enemies, perspective, score, speed,
keyboard steering, collisions and restart."

Then generate the MASTER PROMPT.

Copy that prompt into ChatGPT.

ChatGPT must use the exact idea and create a complete
Scala Swing project with strong compile-safety and
3D-style Java2D rendering when 3D is requested."""
  )

  dashboardPanel.add(
    makeScroll(
      dashboardText
    ),
    BorderLayout.CENTER
  )

  val dashboardMasterButton =
    makeButton(
      "MASTER ACCESS"
    )

  val dashboardHistoryButton =
    makeButton(
      "OPEN HISTORY"
    )

  val dashboardSettingsButton =
    makeButton(
      "SETTINGS"
    )

  val dashboardButtons =
    new JPanel(
      new FlowLayout(
        FlowLayout.LEFT
      )
    )

  dashboardButtons.setBackground(
    PANEL_BG
  )

  dashboardButtons.add(
    dashboardMasterButton
  )

  dashboardButtons.add(
    dashboardHistoryButton
  )

  dashboardButtons.add(
    dashboardSettingsButton
  )

  dashboardPanel.add(
    dashboardButtons,
    BorderLayout.SOUTH
  )

  // ========================================================
  // CLASSROOM
  // ========================================================

  val classroomPanel =
    new JPanel(
      new BorderLayout(
        10,
        10
      )
    )

  classroomPanel.setBackground(
    PANEL_BG
  )

  classroomPanel.setBorder(
    new EmptyBorder(
      14,
      14,
      14,
      14
    )
  )

  val lessonList =
    new JList[String](
      Array(
        "Scala Basics",
        "val and var",
        "Methods",
        "Conditions",
        "Loops",
        "Collections",
        "Classes",
        "Swing Basics",
        "Event Handling",
        "Swing Timer",
        "Games",
        "3D Rendering",
        "Debugging"
      )
    )

  lessonList.setBackground(
    new Color(
      12,
      16,
      23
    )
  )

  lessonList.setForeground(
    TEXT
  )

  lessonList.setFont(
    new Font(
      "SansSerif",
      Font.PLAIN,
      15
    )
  )

  val lessonArea =
    makeArea()

  lessonArea.setEditable(
    false
  )

  val lessonData =
    Array(
      "Scala Basics\n\nScala is a programming language that supports object-oriented and functional programming.",
      "val and var\n\nval is immutable. var is mutable.",
      "Methods\n\nUse def to declare a method.\n\ndef add(a: Int, b: Int): Int = {\n  a + b\n}",
      "Conditions\n\nUse if and else for decisions.",
      "Loops\n\nScala supports useful iteration constructs such as for and while.",
      "Collections\n\nUseful collections include Array, List, Vector, Set and Map.",
      "Classes\n\nClasses combine related data and behavior.",
      "Swing Basics\n\nJava Swing provides desktop UI components.",
      "Event Handling\n\nButtons can respond through ActionListener.",
      "Swing Timer\n\njavax.swing.Timer can drive repeated UI updates.",
      "Games\n\nA Swing game normally combines state, input, drawing and timing.",
      "3D Rendering\n\nJava2D can be used to create self-contained perspective and pseudo-3D effects with camera math and polygon projection.",
      "Debugging\n\nStart from the first compiler error and inspect declarations, scope, braces, types and event code."
    )

  lessonList.addListSelectionListener(
    new javax.swing.event.ListSelectionListener {

      def valueChanged(
          e: javax.swing.event.ListSelectionEvent
      ): Unit = {

        if (
          !e.getValueIsAdjusting
        ) {

          val index =
            lessonList.getSelectedIndex

          if (
            index >= 0 &&
            index < lessonData.length
          ) {

            lessonArea.setText(
              lessonData(
                index
              )
            )
          }
        }
      }
    }
  )

  lessonList.setSelectedIndex(
    0
  )

  classroomPanel.add(
    makeLabel(
      "CLASSROOM",
      24,
      true
    ),
    BorderLayout.NORTH
  )

  classroomPanel.add(
    makeScroll(
      lessonList
    ),
    BorderLayout.WEST
  )

  classroomPanel.add(
    makeScroll(
      lessonArea
    ),
    BorderLayout.CENTER
  )

  // ========================================================
  // ASK TEACHER
  // ========================================================

  val teacherPanel =
    new JPanel(
      new BorderLayout(
        10,
        10
      )
    )

  teacherPanel.setBackground(
    PANEL_BG
  )

  teacherPanel.setBorder(
    new EmptyBorder(
      14,
      14,
      14,
      14
    )
  )

  val teacherInput =
    makeArea()

  val teacherOutput =
    makeArea()

  teacherOutput.setEditable(
    false
  )

  val askTeacherButton =
    makeButton(
      "ASK TEACHER"
    )

  val clearTeacherButton =
    makeButton(
      "CLEAR"
    )

  askTeacherButton.addActionListener(
    new ActionListener {

      override def actionPerformed(
          e: ActionEvent
      ): Unit = {

        val question =
          teacherInput
            .getText
            .trim
            .toLowerCase

        val answer =
          if (
            question.isEmpty
          ) {

            "Please type your coding question."

          } else if (
            question.contains(
              "val"
            )
          ) {

            "val creates an immutable value."

          } else if (
            question.contains(
              "var"
            )
          ) {

            "var creates a mutable variable."

          } else if (
            question.contains(
              "swing"
            )
          ) {

            "Scala can use Java Swing components such as JFrame, JPanel, JButton, JLabel and JTextArea."

          } else if (
            question.contains(
              "listener"
            )
          ) {

            "Listeners allow Swing components to react to user actions."

          } else if (
            question.contains(
              "timer"
            )
          ) {

            "Use javax.swing.Timer for repeated Swing updates and animation."

          } else if (
            question.contains(
              "3d"
            )
          ) {

            "A Swing application can create strong 3D-style visuals using Java2D, perspective projection, camera math and procedural polygon rendering."

          } else {

            "Break the problem into input, state, UI, events and output. Then implement one part at a time."
          }

        teacherOutput.setText(
          answer
        )

        recordActivity(
          "Used Ask Teacher"
        )
      }
    }
  )

  clearTeacherButton.addActionListener(
    new ActionListener {

      override def actionPerformed(
          e: ActionEvent
      ): Unit = {

        teacherInput.setText(
          ""
        )

        teacherOutput.setText(
          ""
        )
      }
    }
  )

  val teacherButtons =
    new JPanel(
      new FlowLayout(
        FlowLayout.LEFT
      )
    )

  teacherButtons.setBackground(
    PANEL_BG
  )

  teacherButtons.add(
    askTeacherButton
  )

  teacherButtons.add(
    clearTeacherButton
  )

  val teacherSplit =
    new JSplitPane(
      JSplitPane.VERTICAL_SPLIT,
      makeScroll(
        teacherInput
      ),
      makeScroll(
        teacherOutput
      )
    )

  teacherSplit.setResizeWeight(
    0.45
  )

  teacherPanel.add(
    makeLabel(
      "ASK TEACHER",
      24,
      true
    ),
    BorderLayout.NORTH
  )

  teacherPanel.add(
    teacherSplit,
    BorderLayout.CENTER
  )

  teacherPanel.add(
    teacherButtons,
    BorderLayout.SOUTH
  )

  // ========================================================
  // CODE LAB
  // ========================================================

  val codeLabPanel =
    new JPanel(
      new BorderLayout(
        10,
        10
      )
    )

  codeLabPanel.setBackground(
    PANEL_BG
  )

  codeLabPanel.setBorder(
    new EmptyBorder(
      14,
      14,
      14,
      14
    )
  )

  val codeArea =
    makeArea()

  codeArea.setText(
    """import javax.swing._
import java.awt._

val frame =
  new JFrame("Scala Swing")

frame.setDefaultCloseOperation(
  WindowConstants.EXIT_ON_CLOSE
)

frame.setSize(
  500,
  300
)

val label =
  new JLabel("Hello Scala Swing!")

frame.add(
  label
)

frame.setLocationRelativeTo(
  null
)

frame.setVisible(
  true
)
"""
  )

  val codeOutput =
    makeArea()

  codeOutput.setEditable(
    false
  )

  val analyzeCodeButton =
    makeButton(
      "ANALYZE CODE"
    )

  val copyCodeButton =
    makeButton(
      "COPY"
    )

  val saveCodeButton =
    makeButton(
      "SAVE"
    )

  val clearCodeButton =
    makeButton(
      "CLEAR"
    )

  analyzeCodeButton.addActionListener(
    new ActionListener {

      override def actionPerformed(
          e: ActionEvent
      ): Unit = {

        val code =
          codeArea.getText

        val problems =
          ArrayBuffer[String]()

        if (
          code.trim.isEmpty
        ) {

          problems +=
            "Code area is empty."
        }

        if (
          code.contains(
            "JFrame.EXIT_ON_CLOSE"
          )
        ) {

          problems +=
            "Use WindowConstants.EXIT_ON_CLOSE."
        }

        if (
          code.count(
            _ == '{'
          ) !=
            code.count(
              _ == '}'
            )
        ) {

          problems +=
            "Possible { } mismatch."
        }

        if (
          code.count(
            _ == '('
          ) !=
            code.count(
              _ == ')'
            )
        ) {

          problems +=
            "Possible ( ) mismatch."
        }

        if (
          code.count(
            _ == '['
          ) !=
            code.count(
              _ == ']'
            )
        ) {

          problems +=
            "Possible [ ] mismatch."
        }

        if (
          code.contains(
            "def refreshHistory"
          ) &&
          code.contains(
            "val refreshHistory"
          )
        ) {

          problems +=
            "Possible duplicate refreshHistory name. Use refreshHistoryButton and refreshHistoryView."
        }

        if (
          problems.isEmpty
        ) {

          codeOutput.setText(
            "No obvious common issue detected.\n\n" +
              "This quick analyzer is not a replacement for the Scala compiler."
          )

        } else {

          codeOutput.setText(
            problems.mkString(
              "Possible issues:\n\n",
              "\n\n",
              ""
            )
          )
        }

        recordActivity(
          "Analyzed Code Lab code"
        )
      }
    }
  )

  copyCodeButton.addActionListener(
    new ActionListener {

      override def actionPerformed(
          e: ActionEvent
      ): Unit = {

        copyToClipboard(
          codeArea.getText
        )

        showInfo(
          frame,
          "COPIED",
          "Code copied."
        )
      }
    }
  )

  saveCodeButton.addActionListener(
    new ActionListener {

      override def actionPerformed(
          e: ActionEvent
      ): Unit = {

        saveTextFile(
          codeArea.getText,
          "ScalaCode.scala"
        )
      }
    }
  )

  clearCodeButton.addActionListener(
    new ActionListener {

      override def actionPerformed(
          e: ActionEvent
      ): Unit = {

        codeArea.setText(
          ""
        )

        codeOutput.setText(
          ""
        )
      }
    }
  )

  val codeButtons =
    new JPanel(
      new FlowLayout(
        FlowLayout.LEFT
      )
    )

  codeButtons.setBackground(
    PANEL_BG
  )

  codeButtons.add(
    analyzeCodeButton
  )

  codeButtons.add(
    copyCodeButton
  )

  codeButtons.add(
    saveCodeButton
  )

  codeButtons.add(
    clearCodeButton
  )

  val codeSplit =
    new JSplitPane(
      JSplitPane.VERTICAL_SPLIT,
      makeScroll(
        codeArea
      ),
      makeScroll(
        codeOutput
      )
    )

  codeSplit.setResizeWeight(
    0.65
  )

  codeLabPanel.add(
    makeLabel(
      "CODE LAB",
      24,
      true
    ),
    BorderLayout.NORTH
  )

  codeLabPanel.add(
    codeSplit,
    BorderLayout.CENTER
  )

  codeLabPanel.add(
    codeButtons,
    BorderLayout.SOUTH
  )

  // ========================================================
  // ERROR FIX
  // ========================================================

  val errorPanel =
    new JPanel(
      new BorderLayout(
        10,
        10
      )
    )

  errorPanel.setBackground(
    PANEL_BG
  )

  errorPanel.setBorder(
    new EmptyBorder(
      14,
      14,
      14,
      14
    )
  )

  val errorInput =
    makeArea()

  val errorOutput =
    makeArea()

  errorOutput.setEditable(
    false
  )

  val fixErrorButton =
    makeButton(
      "FIX ERROR"
    )

  val clearErrorButton =
    makeButton(
      "CLEAR"
    )

  fixErrorButton.addActionListener(
    new ActionListener {

      override def actionPerformed(
          e: ActionEvent
      ): Unit = {

        val error =
          errorInput
            .getText
            .trim

        val answer =
          if (
            error.contains(
              "EXIT_ON_CLOSE"
            )
          ) {

            """Use:

frame.setDefaultCloseOperation(
  WindowConstants.EXIT_ON_CLOSE
)

Do not use JFrame.EXIT_ON_CLOSE."""

          } else if (
            error.contains(
              "already defined as value"
            )
          ) {

            """The same name is being used twice.

For example:

val refreshHistory = ...
def refreshHistory(): Unit = ...

Use unique names:

val refreshHistoryButton = ...
def refreshHistoryView(): Unit = ..."""

          } else if (
            error.contains(
              "cannot be applied to (Unit)"
            )
          ) {

            """A method returning Unit is being passed where a Component is required.

Wrong:
historyButtons.add(
  refreshHistory()
)

Correct:
historyButtons.add(
  refreshHistoryButton
)"""

          } else if (
            error.contains(
              "addActionListener is not a member of Unit"
            )
          ) {

            """The variable before addActionListener is resolving to Unit.

Make sure it is actually a JButton or another valid Swing component."""

          } else if (
            error.contains(
              "reassignment to val"
            )
          ) {

            """A val cannot be reassigned.

Use var only for state that truly changes, or calculate a new value instead."""

          } else if (
            error.contains(
              "required: Int"
            )
          ) {

            """A Swing method usually expects an Int, but a Double or another numeric type was provided.

Convert safely with .toInt when appropriate."""

          } else {

            "Check the first compiler error first.\n\n" +
              "? missing braces\n" +
              "? duplicate names\n" +
              "? undefined variables\n" +
              "? undefined methods\n" +
              "? wrong Swing component types\n" +
              "? forward references\n" +
              "? incorrect listeners\n" +
              "? numeric type mismatch\n" +
              "? invalid timer or rendering code"
          }

        errorOutput.setText(
          if (
            error.isEmpty
          )
            "Paste a compiler error first."
          else
            answer
        )

        recordActivity(
          "Used Error Fix"
        )
      }
    }
  )

  clearErrorButton.addActionListener(
    new ActionListener {

      override def actionPerformed(
          e: ActionEvent
      ): Unit = {

        errorInput.setText(
          ""
        )

        errorOutput.setText(
          ""
        )
      }
    }
  )

  val errorButtons =
    new JPanel(
      new FlowLayout(
        FlowLayout.LEFT
      )
    )

  errorButtons.setBackground(
    PANEL_BG
  )

  errorButtons.add(
    fixErrorButton
  )

  errorButtons.add(
    clearErrorButton
  )

  val errorSplit =
    new JSplitPane(
      JSplitPane.VERTICAL_SPLIT,
      makeScroll(
        errorInput
      ),
      makeScroll(
        errorOutput
      )
    )

  errorSplit.setResizeWeight(
    0.45
  )

  errorPanel.add(
    makeLabel(
      "ERROR FIX",
      24,
      true
    ),
    BorderLayout.NORTH
  )

  errorPanel.add(
    errorSplit,
    BorderLayout.CENTER
  )

  errorPanel.add(
    errorButtons,
    BorderLayout.SOUTH
  )

  // ========================================================
  // NOTES
  // ========================================================

  val notesPanel =
    new JPanel(
      new BorderLayout(
        10,
        10
      )
    )

  notesPanel.setBackground(
    PANEL_BG
  )

  notesPanel.setBorder(
    new EmptyBorder(
      14,
      14,
      14,
      14
    )
  )

  val notesTitleField =
    makeTextField()

  val notesArea =
    makeArea()

  val notesModel =
    new DefaultListModel[String]()

  val notesList =
    new JList[String](
      notesModel
    )

  notesList.setBackground(
    new Color(
      12,
      16,
      23
    )
  )

  notesList.setForeground(
    TEXT
  )

  val saveNoteButton =
    makeButton(
      "SAVE NOTE"
    )

  val loadNoteButton =
    makeButton(
      "LOAD"
    )

  val deleteNoteButton =
    makeButton(
      "DELETE"
    )

  saveNoteButton.addActionListener(
    new ActionListener {

      override def actionPerformed(
          e: ActionEvent
      ): Unit = {

        val title =
          notesTitleField
            .getText
            .trim

        val body =
          notesArea.getText

        if (
          title.isEmpty
        ) {

          showWarning(
            frame,
            "NOTES",
            "Enter a note title."
          )

        } else {

          notesData +=
            title +
              "||" +
              body

          notesModel.addElement(
            title
          )

          notesTitleField.setText(
            ""
          )

          notesArea.setText(
            ""
          )

          recordActivity(
            "Saved note: " +
              title
          )
        }
      }
    }
  )

  loadNoteButton.addActionListener(
    new ActionListener {

      override def actionPerformed(
          e: ActionEvent
      ): Unit = {

        val index =
          notesList.getSelectedIndex

        if (
          index >= 0 &&
          index < notesData.length
        ) {

          val parts =
            notesData(
              index
            ).split(
              "\\|\\|",
              2
            )

          if (
            parts.length == 2
          ) {

            notesTitleField.setText(
              parts(
                0
              )
            )

            notesArea.setText(
              parts(
                1
              )
            )
          }
        }
      }
    }
  )

  deleteNoteButton.addActionListener(
    new ActionListener {

      override def actionPerformed(
          e: ActionEvent
      ): Unit = {

        val index =
          notesList.getSelectedIndex

        if (
          index >= 0 &&
          index < notesData.length
        ) {

          notesData.remove(
            index
          )

          notesModel.remove(
            index
          )

          notesTitleField.setText(
            ""
          )

          notesArea.setText(
            ""
          )
        }
      }
    }
  )

  val notesButtons =
    new JPanel(
      new FlowLayout(
        FlowLayout.LEFT
      )
    )

  notesButtons.setBackground(
    PANEL_BG
  )

  notesButtons.add(
    saveNoteButton
  )

  notesButtons.add(
    loadNoteButton
  )

  notesButtons.add(
    deleteNoteButton
  )

  val notesEditor =
    new JPanel(
      new BorderLayout(
        8,
        8
      )
    )

  notesEditor.setBackground(
    PANEL_BG
  )

  notesEditor.add(
    notesTitleField,
    BorderLayout.NORTH
  )

  notesEditor.add(
    makeScroll(
      notesArea
    ),
    BorderLayout.CENTER
  )

  notesEditor.add(
    notesButtons,
    BorderLayout.SOUTH
  )

  notesPanel.add(
    makeLabel(
      "NOTES",
      24,
      true
    ),
    BorderLayout.NORTH
  )

  notesPanel.add(
    makeScroll(
      notesList
    ),
    BorderLayout.WEST
  )

  notesPanel.add(
    notesEditor,
    BorderLayout.CENTER
  )

  // ========================================================
  // TASKS
  // ========================================================

  val tasksPanel =
    new JPanel(
      new BorderLayout(
        10,
        10
      )
    )

  tasksPanel.setBackground(
    PANEL_BG
  )

  tasksPanel.setBorder(
    new EmptyBorder(
      14,
      14,
      14,
      14
    )
  )

  val taskField =
    makeTextField()

  val taskModel =
    new DefaultListModel[String]()

  val taskList =
    new JList[String](
      taskModel
    )

  taskList.setBackground(
    new Color(
      12,
      16,
      23
    )
  )

  taskList.setForeground(
    TEXT
  )

  val addTaskButton =
    makeButton(
      "ADD TASK"
    )

  val completeTaskButton =
    makeButton(
      "COMPLETE"
    )

  val deleteTaskButton =
    makeButton(
      "DELETE"
    )

  addTaskButton.addActionListener(
    new ActionListener {

      override def actionPerformed(
          e: ActionEvent
      ): Unit = {

        val task =
          taskField
            .getText
            .trim

        if (
          task.nonEmpty
        ) {

          tasksData +=
            task

          taskModel.addElement(
            task
          )

          taskField.setText(
            ""
          )

          recordActivity(
            "Added task: " +
              task
          )
        }
      }
    }
  )

  completeTaskButton.addActionListener(
    new ActionListener {

      override def actionPerformed(
          e: ActionEvent
      ): Unit = {

        val index =
          taskList.getSelectedIndex

        if (
          index >= 0 &&
          index < taskModel.getSize
        ) {

          val text =
            taskModel.getElementAt(
              index
            )

          if (
            !text.startsWith(
              "[DONE]"
            )
          ) {

            taskModel.setElementAt(
              "[DONE] " +
                text,
              index
            )
          }
        }
      }
    }
  )

  deleteTaskButton.addActionListener(
    new ActionListener {

      override def actionPerformed(
          e: ActionEvent
      ): Unit = {

        val index =
          taskList.getSelectedIndex

        if (
          index >= 0 &&
          index < taskModel.getSize
        ) {

          taskModel.remove(
            index
          )

          if (
            index < tasksData.length
          ) {

            tasksData.remove(
              index
            )
          }
        }
      }
    }
  )

  val taskTop =
    new JPanel(
      new BorderLayout(
        6,
        6
      )
    )

  taskTop.setBackground(
    PANEL_BG
  )

  taskTop.add(
    taskField,
    BorderLayout.CENTER
  )

  taskTop.add(
    addTaskButton,
    BorderLayout.EAST
  )

  val taskBottom =
    new JPanel(
      new FlowLayout(
        FlowLayout.LEFT
      )
    )

  taskBottom.setBackground(
    PANEL_BG
  )

  taskBottom.add(
    completeTaskButton
  )

  taskBottom.add(
    deleteTaskButton
  )

  val taskCenter =
    new JPanel(
      new BorderLayout(
        8,
        8
      )
    )

  taskCenter.setBackground(
    PANEL_BG
  )

  taskCenter.add(
    taskTop,
    BorderLayout.NORTH
  )

  taskCenter.add(
    makeScroll(
      taskList
    ),
    BorderLayout.CENTER
  )

  taskCenter.add(
    taskBottom,
    BorderLayout.SOUTH
  )

  tasksPanel.add(
    makeLabel(
      "TASKS",
      24,
      true
    ),
    BorderLayout.NORTH
  )

  tasksPanel.add(
    taskCenter,
    BorderLayout.CENTER
  )

  // ========================================================
  // QUIZ
  // ========================================================

  val quizPanel =
    new JPanel(
      new BorderLayout(
        10,
        10
      )
    )

  quizPanel.setBackground(
    PANEL_BG
  )

  quizPanel.setBorder(
    new EmptyBorder(
      14,
      14,
      14,
      14
    )
  )

  val quizQuestion =
    makeLabel(
      "QUIZ READY",
      20,
      true
    )

  val quizButtonsPanel =
    new JPanel(
      new GridLayout(
        4,
        1,
        8,
        8
      )
    )

  quizButtonsPanel.setBackground(
    PANEL_BG
  )

  val quizOptions =
    Array(
      makeButton(""),
      makeButton(""),
      makeButton(""),
      makeButton("")
    )

  quizOptions.foreach(
    quizButtonsPanel.add
  )

  val quizQuestions =
    Array(
      (
        "Which keyword declares an immutable value?",
        Array(
          "var",
          "val",
          "let",
          "const"
        ),
        1
      ),
      (
        "Which component accepts multi-line text?",
        Array(
          "JLabel",
          "JTextArea",
          "JButton",
          "JFrame"
        ),
        2
      ),
      (
        "Which constant is correct for JFrame closing?",
        Array(
          "JFrame.EXIT_ON_CLOSE",
          "WindowConstants.EXIT_ON_CLOSE",
          "Frame.EXIT",
          "JWindow.EXIT"
        ),
        2
      )
    )

  var quizIndex =
    0

  var quizScore =
    0

  var quizAnswered =
    false

  val quizScoreLabel =
    makeLabel(
      "Score: 0",
      15,
      true
    )

  val nextQuizButton =
    makeButton(
      "NEXT"
    )

  def loadQuiz(): Unit = {

    if (
      quizIndex <
        quizQuestions.length
    ) {

      val q =
        quizQuestions(
          quizIndex
        )

      quizQuestion.setText(
        (
          quizIndex + 1
        ) +
          ". " +
          q._1
      )

      var i =
        0

      while (
        i <
          4
      ) {

        quizOptions(
          i
        ).setText(
          q._2(
            i
          )
        )

        quizOptions(
          i
        ).setEnabled(
          true
        )

        i += 1
      }

      quizAnswered =
        false

      nextQuizButton.setEnabled(
        quizIndex <
          quizQuestions.length
      )

    } else {

      quizQuestion.setText(
        "QUIZ COMPLETE!"
      )

      var i =
        0

      while (
        i <
          4
      ) {

        quizOptions(
          i
        ).setText(
          ""
        )

        quizOptions(
          i
        ).setEnabled(
          false
        )

        i += 1
      }

      nextQuizButton.setEnabled(
        false
      )
    }

    quizScoreLabel.setText(
      "Score: " +
        quizScore +
        " / " +
        quizQuestions.length
    )
  }

  var quizButtonIndex =
    0

  while (
    quizButtonIndex <
      quizOptions.length
  ) {

    val selectedIndex =
      quizButtonIndex

    quizOptions(
      quizButtonIndex
    ).addActionListener(
      new ActionListener {

        override def actionPerformed(
            e: ActionEvent
        ): Unit = {

          if (
            !quizAnswered &&
            quizIndex <
              quizQuestions.length
          ) {

            quizAnswered =
              true

            val correctIndex =
              quizQuestions(
                quizIndex
              )._3 - 1

            if (
              selectedIndex ==
                correctIndex
            ) {

              quizScore +=
                1

              quizQuestion.setText(
                quizQuestion.getText +
                  "   ? Correct"
              )

            } else {

              quizQuestion.setText(
                quizQuestion.getText +
                  "   ? Wrong"
              )
            }

            var i =
              0

            while (
              i <
                quizOptions.length
            ) {

              quizOptions(
                i
              ).setEnabled(
                false
              )

              i += 1
            }

            quizScoreLabel.setText(
              "Score: " +
                quizScore +
                " / " +
                quizQuestions.length
            )
          }
        }
      }
    )

    quizButtonIndex += 1
  }

  nextQuizButton.addActionListener(
    new ActionListener {

      override def actionPerformed(
          e: ActionEvent
      ): Unit = {

        if (
          quizIndex <
            quizQuestions.length
        ) {

          quizIndex +=
            1

          loadQuiz()
        }
      }
    }
  )

  val quizBottom =
    new JPanel(
      new FlowLayout(
        FlowLayout.LEFT
      )
    )

  quizBottom.setBackground(
    PANEL_BG
  )

  quizBottom.add(
    quizScoreLabel
  )

  quizBottom.add(
    nextQuizButton
  )

  quizPanel.add(
    makeLabel(
      "QUIZ",
      24,
      true
    ),
    BorderLayout.NORTH
  )

  val quizCenter =
    new JPanel(
      new BorderLayout(
        10,
        10
      )
    )

  quizCenter.setBackground(
    PANEL_BG
  )

  quizCenter.add(
    quizQuestion,
    BorderLayout.NORTH
  )

  quizCenter.add(
    quizButtonsPanel,
    BorderLayout.CENTER
  )

  quizCenter.add(
    quizBottom,
    BorderLayout.SOUTH
  )

  quizPanel.add(
    quizCenter,
    BorderLayout.CENTER
  )

  loadQuiz()

  // ========================================================
  // IMAGE STUDIO
  // ========================================================

  val imagePanel =
    new JPanel(
      new BorderLayout(
        10,
        10
      )
    )

  imagePanel.setBackground(
    PANEL_BG
  )

  imagePanel.setBorder(
    new EmptyBorder(
      14,
      14,
      14,
      14
    )
  )

  val imagePreview =
    new JLabel(
      "No image selected",
      SwingConstants.CENTER
    )

  imagePreview.setForeground(
    MUTED
  )

  val openImageButton =
    makeButton(
      "OPEN IMAGE"
    )

  val clearImageButton =
    makeButton(
      "CLEAR"
    )

  openImageButton.addActionListener(
    new ActionListener {

      override def actionPerformed(
          e: ActionEvent
      ): Unit = {

        val chooser =
          new JFileChooser()

        if (
          chooser.showOpenDialog(
            frame
          ) ==
            JFileChooser.APPROVE_OPTION
        ) {

          val file =
            chooser.getSelectedFile

          try {

            val image =
              ImageIO.read(
                file
              )

            if (
              image == null
            ) {

              showError(
                frame,
                "IMAGE",
                "Unsupported image."
              )

            } else {

              imagePreview.setIcon(
                new ImageIcon(
                  scaleImage(
                    image,
                    800,
                    550
                  )
                )
              )

              imagePreview.setText(
                ""
              )

              recordActivity(
                "Opened image: " +
                  file.getName
              )
            }

          } catch {

            case ex: Throwable =>

              showError(
                frame,
                "IMAGE",
                ex.toString
              )
          }
        }
      }
    }
  )

  clearImageButton.addActionListener(
    new ActionListener {

      override def actionPerformed(
          e: ActionEvent
      ): Unit = {

        imagePreview.setIcon(
          null
        )

        imagePreview.setText(
          "No image selected"
        )
      }
    }
  )

  val imageButtons =
    new JPanel(
      new FlowLayout(
        FlowLayout.LEFT
      )
    )

  imageButtons.setBackground(
    PANEL_BG
  )

  imageButtons.add(
    openImageButton
  )

  imageButtons.add(
    clearImageButton
  )

  imagePanel.add(
    makeLabel(
      "IMAGE STUDIO",
      24,
      true
    ),
    BorderLayout.NORTH
  )

  imagePanel.add(
    imagePreview,
    BorderLayout.CENTER
  )

  imagePanel.add(
    imageButtons,
    BorderLayout.SOUTH
  )

  // ========================================================
  // PROMPT LAB
  // ========================================================

  val promptLabPanel =
    new JPanel(
      new BorderLayout(
        10,
        10
      )
    )

  promptLabPanel.setBackground(
    PANEL_BG
  )

  promptLabPanel.setBorder(
    new EmptyBorder(
      14,
      14,
      14,
      14
    )
  )

  val promptIdeaArea =
    makeArea()

  val promptOutputArea =
    makeArea()

  promptOutputArea.setEditable(
    false
  )

  val generatePromptButton =
    makeButton(
      "GENERATE PROMPT"
    )

  val copyPromptButton =
    makeButton(
      "COPY"
    )

  generatePromptButton.addActionListener(
    new ActionListener {

      override def actionPerformed(
          e: ActionEvent
      ): Unit = {

        val idea =
          promptIdeaArea
            .getText
            .trim

        promptOutputArea.setText(
          if (
            idea.isEmpty
          ) {

            "Write your idea first."

          } else {

            createMasterPrompt(
              idea
            )
          }
        )

        recordActivity(
          "Generated prompt from Prompt Lab"
        )
      }
    }
  )

  copyPromptButton.addActionListener(
    new ActionListener {

      override def actionPerformed(
          e: ActionEvent
      ): Unit = {

        val generated =
          promptOutputArea
            .getText
            .trim

        if (
          generated.isEmpty
        ) {

          showWarning(
            frame,
            "PROMPT LAB",
            "Generate the prompt first."
          )

        } else {

          copyToClipboard(
            generated
          )

          showInfo(
            frame,
            "COPIED",
            "Prompt copied."
          )
        }
      }
    }
  )

  val promptButtons =
    new JPanel(
      new FlowLayout(
        FlowLayout.LEFT
      )
    )

  promptButtons.setBackground(
    PANEL_BG
  )

  promptButtons.add(
    generatePromptButton
  )

  promptButtons.add(
    copyPromptButton
  )

  val promptSplit =
    new JSplitPane(
      JSplitPane.VERTICAL_SPLIT,
      makeScroll(
        promptIdeaArea
      ),
      makeScroll(
        promptOutputArea
      )
    )

  promptSplit.setResizeWeight(
    0.4
  )

  promptLabPanel.add(
    makeLabel(
      "PROMPT LAB",
      24,
      true
    ),
    BorderLayout.NORTH
  )

  promptLabPanel.add(
    promptSplit,
    BorderLayout.CENTER
  )

  promptLabPanel.add(
    promptButtons,
    BorderLayout.SOUTH
  )

  // ========================================================
  // PROJECTS
  // ========================================================

  val projectsPanel =
    new JPanel(
      new BorderLayout(
        10,
        10
      )
    )

  projectsPanel.setBackground(
    PANEL_BG
  )

  projectsPanel.setBorder(
    new EmptyBorder(
      14,
      14,
      14,
      14
    )
  )

  val projectNameField =
    makeTextField()

  val projectDescriptionArea =
    makeArea()

  val projectModel =
    new DefaultListModel[String]()

  val projectList =
    new JList[String](
      projectModel
    )

  projectList.setBackground(
    new Color(
      12,
      16,
      23
    )
  )

  projectList.setForeground(
    TEXT
  )

  val saveProjectButton =
    makeButton(
      "SAVE"
    )

  val loadProjectButton =
    makeButton(
      "LOAD"
    )

  val deleteProjectButton =
    makeButton(
      "DELETE"
    )

  saveProjectButton.addActionListener(
    new ActionListener {

      override def actionPerformed(
          e: ActionEvent
      ): Unit = {

        val name =
          projectNameField
            .getText
            .trim

        if (
          name.isEmpty
        ) {

          showWarning(
            frame,
            "PROJECTS",
            "Enter a project name."
          )

        } else {

          projectData +=
            name +
              "||" +
              projectDescriptionArea.getText

          projectModel.addElement(
            name
          )

          projectNameField.setText(
            ""
          )

          projectDescriptionArea.setText(
            ""
          )

          recordActivity(
            "Saved project: " +
              name
          )
        }
      }
    }
  )

  loadProjectButton.addActionListener(
    new ActionListener {

      override def actionPerformed(
          e: ActionEvent
      ): Unit = {

        val index =
          projectList.getSelectedIndex

        if (
          index >= 0 &&
          index < projectData.length
        ) {

          val parts =
            projectData(
              index
            ).split(
              "\\|\\|",
              2
            )

          if (
            parts.length == 2
          ) {

            projectNameField.setText(
              parts(
                0
              )
            )

            projectDescriptionArea.setText(
              parts(
                1
              )
            )
          }
        }
      }
    }
  )

  deleteProjectButton.addActionListener(
    new ActionListener {

      override def actionPerformed(
          e: ActionEvent
      ): Unit = {

        val index =
          projectList.getSelectedIndex

        if (
          index >= 0 &&
          index < projectModel.getSize
        ) {

          projectModel.remove(
            index
          )

          if (
            index < projectData.length
          ) {

            projectData.remove(
              index
            )
          }
        }
      }
    }
  )

  val projectEditor =
    new JPanel(
      new BorderLayout(
        8,
        8
      )
    )

  projectEditor.setBackground(
    PANEL_BG
  )

  projectEditor.add(
    projectNameField,
    BorderLayout.NORTH
  )

  projectEditor.add(
    makeScroll(
      projectDescriptionArea
    ),
    BorderLayout.CENTER
  )

  val projectButtons =
    new JPanel(
      new FlowLayout(
        FlowLayout.LEFT
      )
    )

  projectButtons.setBackground(
    PANEL_BG
  )

  projectButtons.add(
    saveProjectButton
  )

  projectButtons.add(
    loadProjectButton
  )

  projectButtons.add(
    deleteProjectButton
  )

  projectEditor.add(
    projectButtons,
    BorderLayout.SOUTH
  )

  projectsPanel.add(
    makeLabel(
      "PROJECTS",
      24,
      true
    ),
    BorderLayout.NORTH
  )

  projectsPanel.add(
    makeScroll(
      projectList
    ),
    BorderLayout.WEST
  )

  projectsPanel.add(
    projectEditor,
    BorderLayout.CENTER
  )

  // ========================================================
  // UTILITIES
  // ========================================================

  val utilitiesPanel =
    new JPanel(
      new BorderLayout(
        10,
        10
      )
    )

  utilitiesPanel.setBackground(
    PANEL_BG
  )

  utilitiesPanel.setBorder(
    new EmptyBorder(
      14,
      14,
      14,
      14
    )
  )

  val stopwatchLabel =
    makeLabel(
      "00:00:00",
      42,
      true
    )

  stopwatchLabel.setHorizontalAlignment(
    SwingConstants.CENTER
  )

  var stopwatchSeconds =
    0L

  var stopwatchRunning =
    false

  val stopwatchTimer =
    new javax.swing.Timer(
      1000,
      null
    )

  stopwatchTimer.addActionListener(
    new ActionListener {

      override def actionPerformed(
          e: ActionEvent
      ): Unit = {

        if (
          stopwatchRunning
        ) {

          stopwatchSeconds +=
            1

          val h =
            stopwatchSeconds / 3600

          val m =
            (
              stopwatchSeconds % 3600
            ) / 60

          val s =
            stopwatchSeconds % 60

          stopwatchLabel.setText(
            "%02d:%02d:%02d".format(
              h,
              m,
              s
            )
          )
        }
      }
    }
  )

  val startStopwatchButton =
    makeButton(
      "START"
    )

  val stopStopwatchButton =
    makeButton(
      "STOP"
    )

  val resetStopwatchButton =
    makeButton(
      "RESET"
    )

  startStopwatchButton.addActionListener(
    new ActionListener {

      override def actionPerformed(
          e: ActionEvent
      ): Unit = {

        stopwatchRunning =
          true

        stopwatchTimer.start()
      }
    }
  )

  stopStopwatchButton.addActionListener(
    new ActionListener {

      override def actionPerformed(
          e: ActionEvent
      ): Unit = {

        stopwatchRunning =
          false
      }
    }
  )

  resetStopwatchButton.addActionListener(
    new ActionListener {

      override def actionPerformed(
          e: ActionEvent
      ): Unit = {

        stopwatchRunning =
          false

        stopwatchSeconds =
          0L

        stopwatchLabel.setText(
          "00:00:00"
        )
      }
    }
  )

  val utilityButtons =
    new JPanel(
      new FlowLayout(
        FlowLayout.LEFT
      )
    )

  utilityButtons.setBackground(
    PANEL_BG
  )

  utilityButtons.add(
    startStopwatchButton
  )

  utilityButtons.add(
    stopStopwatchButton
  )

  utilityButtons.add(
    resetStopwatchButton
  )

  utilitiesPanel.add(
    makeLabel(
      "UTILITIES",
      24,
      true
    ),
    BorderLayout.NORTH
  )

  utilitiesPanel.add(
    stopwatchLabel,
    BorderLayout.CENTER
  )

  utilitiesPanel.add(
    utilityButtons,
    BorderLayout.SOUTH
  )

  // ========================================================
  // FEATURE LIBRARY
  // ========================================================

  val libraryPanel =
    new JPanel(
      new BorderLayout(
        10,
        10
      )
    )

  libraryPanel.setBackground(
    PANEL_BG
  )

  libraryPanel.setBorder(
    new EmptyBorder(
      14,
      14,
      14,
      14
    )
  )

  val libraryArea =
    makeArea()

  libraryArea.setEditable(
    false
  )

  libraryArea.setText(
    """FEATURE LIBRARY

SECURE LOGIN
3-attempt password protection.

CLASSROOM
Scala and Swing learning lessons.

ASK TEACHER
Simple coding guidance.

CODE LAB
Write and analyze code.

ERROR FIX
Common Scala Swing error guidance.

NOTES
Save notes during the session.

TASKS
Manage coding tasks.

QUIZ
Practice Scala and Swing knowledge.

IMAGE STUDIO
Open and preview images.

PROMPT LAB
Generate reusable coding prompts.

PROJECTS
Store project ideas.

UTILITIES
Stopwatch.

HISTORY
Track activity.

SETTINGS
Application settings.

MASTER ACCESS
Protected MASTER PROMPT area.

MASTER PROMPT
No UID.
No NAME.
Only IDEA.

3D RULES
When a 3D game is requested, the generated prompt
requires a self-contained software 3D / pseudo-3D
renderer using Scala Swing and Java2D when an external
3D engine is not guaranteed.

The generated prompt also requires complete compile
review, type safety, correct event handling, timer
safety, rendering safety, game state handling,
restart, win/lose and playable controls."""
  )

  libraryPanel.add(
    makeLabel(
      "FEATURE LIBRARY",
      24,
      true
    ),
    BorderLayout.NORTH
  )

  libraryPanel.add(
    makeScroll(
      libraryArea
    ),
    BorderLayout.CENTER
  )

  // ========================================================
  // HISTORY
  // ========================================================

  val historyPanel =
    new JPanel(
      new BorderLayout(
        8,
        8
      )
    )

  historyPanel.setBackground(
    PANEL_BG
  )

  historyPanel.setBorder(
    new EmptyBorder(
      12,
      12,
      12,
      12
    )
  )

  val historyArea =
    makeArea()

  historyArea.setEditable(
    false
  )

  val refreshHistoryButton =
    makeButton(
      "REFRESH"
    )

  val exportHistoryButton =
    makeButton(
      "EXPORT"
    )

  val clearHistoryButton =
    makeButton(
      "CLEAR"
    )

  val historyButtons =
    new JPanel(
      new FlowLayout(
        FlowLayout.LEFT
      )
    )

  historyButtons.setBackground(
    PANEL_BG
  )

  historyButtons.add(
    refreshHistoryButton
  )

  historyButtons.add(
    exportHistoryButton
  )

  historyButtons.add(
    clearHistoryButton
  )

  def refreshHistoryView(): Unit = {

    if (
      activityHistory.isEmpty
    ) {

      historyArea.setText(
        "No activity yet."
      )

    } else {

      historyArea.setText(
        activityHistory
          .reverse
          .mkString(
            "\n"
          )
      )
    }

    historyArea.setCaretPosition(
      0
    )
  }

  refreshHistoryButton.addActionListener(
    new ActionListener {

      override def actionPerformed(
          e: ActionEvent
      ): Unit = {

        refreshHistoryView()
      }
    }
  )

  exportHistoryButton.addActionListener(
    new ActionListener {

      override def actionPerformed(
          e: ActionEvent
      ): Unit = {

        if (
          activityHistory.isEmpty
        ) {

          showInfo(
            frame,
            "HISTORY",
            "Nothing to export."
          )

        } else {

          saveTextFile(
            activityHistory
              .reverse
              .mkString(
                "\n"
              ),
            "ActivityHistory.txt"
          )
        }
      }
    }
  )

  clearHistoryButton.addActionListener(
    new ActionListener {

      override def actionPerformed(
          e: ActionEvent
      ): Unit = {

        activityHistory.clear()

        refreshHistoryView()
      }
    }
  )

  historyPanel.add(
    makeLabel(
      "ACTIVITY HISTORY",
      24,
      true
    ),
    BorderLayout.NORTH
  )

  historyPanel.add(
    makeScroll(
      historyArea
    ),
    BorderLayout.CENTER
  )

  historyPanel.add(
    historyButtons,
    BorderLayout.SOUTH
  )

  // ========================================================
  // SETTINGS
  // ========================================================

  val settingsPanel =
    new JPanel(
      new BorderLayout(
        10,
        10
      )
    )

  settingsPanel.setBackground(
    PANEL_BG
  )

  settingsPanel.setBorder(
    new EmptyBorder(
      14,
      14,
      14,
      14
    )
  )

  val settingsText =
    makeArea()

  settingsText.setEditable(
    false
  )

  settingsText.setText(
    """ULTRA SCALA SWING AI LEARNING STUDIO

STATUS: READY

MASTER PROMPT:
Password protected.

No UID field.
No NAME field.

Only the IDEA is required.

3D support:
The MASTER PROMPT requests a self-contained
Java2D/Swing software 3D or pseudo-3D renderer when
the user's idea requires 3D."""
  )

  settingsPanel.add(
    makeLabel(
      "SETTINGS",
      24,
      true
    ),
    BorderLayout.NORTH
  )

  settingsPanel.add(
    makeScroll(
      settingsText
    ),
    BorderLayout.CENTER
  )

  // ========================================================
  // MASTER PROMPT
  // ========================================================

  var masterUnlocked =
    false

  var masterTabAdded =
    false

  val masterPromptPanel =
    new JPanel(
      new BorderLayout(
        10,
        10
      )
    )

  masterPromptPanel.setBackground(
    PANEL_BG
  )

  masterPromptPanel.setBorder(
    new EmptyBorder(
      14,
      14,
      14,
      14
    )
  )

  val masterTitle =
    makeLabel(
      "MASTER PROMPT",
      26,
      true
    )

  val masterStatus =
    makeLabel(
      "ACCESS GRANTED",
      16,
      true
    )

  masterStatus.setForeground(
    SUCCESS
  )

  val masterIntro =
    makeLabel(
      "Enter your IDEA only. No UID. No Name.",
      15,
      false
    )

  val masterIdeaArea =
    makeArea()

  masterIdeaArea.setToolTipText(
    "Write only your game/app/software idea here."
  )

  val masterOutputArea =
    makeArea()

  masterOutputArea.setEditable(
    false
  )

  val generateMasterPromptButton =
    makeButton(
      "GENERATE MASTER PROMPT"
    )

  val copyMasterPromptButton =
    makeButton(
      "COPY MASTER PROMPT"
    )

  val clearMasterPromptButton =
    makeButton(
      "CLEAR"
    )

  val lockMasterPromptButton =
    makeButton(
      "LOCK MASTER PROMPT"
    )

  val masterButtons =
    new JPanel(
      new FlowLayout(
        FlowLayout.LEFT
      )
    )

  masterButtons.setBackground(
    PANEL_BG
  )

  masterButtons.add(
    generateMasterPromptButton
  )

  masterButtons.add(
    copyMasterPromptButton
  )

  masterButtons.add(
    clearMasterPromptButton
  )

  masterButtons.add(
    lockMasterPromptButton
  )

  val masterIdeaPanel =
    new JPanel(
      new BorderLayout(
        6,
        6
      )
    )

  masterIdeaPanel.setBackground(
    PANEL_BG
  )

  masterIdeaPanel.add(
    masterIntro,
    BorderLayout.NORTH
  )

  masterIdeaPanel.add(
    makeScroll(
      masterIdeaArea
    ),
    BorderLayout.CENTER
  )

  val masterOutputPanel =
    new JPanel(
      new BorderLayout(
        6,
        6
      )
    )

  masterOutputPanel.setBackground(
    PANEL_BG
  )

  masterOutputPanel.add(
    makeLabel(
      "GENERATED MASTER PROMPT",
      17,
      true
    ),
    BorderLayout.NORTH
  )

  masterOutputPanel.add(
    makeScroll(
      masterOutputArea
    ),
    BorderLayout.CENTER
  )

  val masterSplit =
    new JSplitPane(
      JSplitPane.VERTICAL_SPLIT,
      masterIdeaPanel,
      masterOutputPanel
    )

  masterSplit.setResizeWeight(
    0.38
  )

  val masterHeader =
    new JPanel(
      new BorderLayout(
        10,
        4
      )
    )

  masterHeader.setBackground(
    PANEL_BG
  )

  val masterHeading =
    new JPanel(
      new GridLayout(
        2,
        1,
        2,
        2
      )
    )

  masterHeading.setBackground(
    PANEL_BG
  )

  masterHeading.add(
    masterTitle
  )

  masterHeading.add(
    masterStatus
  )

  masterHeader.add(
    masterHeading,
    BorderLayout.WEST
  )

  masterPromptPanel.add(
    masterHeader,
    BorderLayout.NORTH
  )

  masterPromptPanel.add(
    masterSplit,
    BorderLayout.CENTER
  )

  masterPromptPanel.add(
    masterButtons,
    BorderLayout.SOUTH
  )

  // --------------------------------------------------------
  // GENERATE MASTER PROMPT
  // --------------------------------------------------------

  generateMasterPromptButton.addActionListener(
    new ActionListener {

      override def actionPerformed(
          e: ActionEvent
      ): Unit = {

        if (
          !masterUnlocked
        ) {

          showWarning(
            frame,
            "MASTER PROMPT",
            "MASTER PROMPT is locked."
          )

        } else {

          val idea =
            masterIdeaArea
              .getText
              .trim

          if (
            idea.isEmpty
          ) {

            showWarning(
              frame,
              "MASTER PROMPT",
              "Write your IDEA first."
            )

          } else {

            masterOutputArea.setText(
              createMasterPrompt(
                idea
              )
            )

            masterOutputArea.setCaretPosition(
              0
            )

            recordActivity(
              "Generated MASTER PROMPT"
            )
          }
        }
      }
    }
  )

  // --------------------------------------------------------
  // COPY MASTER PROMPT
  // --------------------------------------------------------

  copyMasterPromptButton.addActionListener(
    new ActionListener {

      override def actionPerformed(
          e: ActionEvent
      ): Unit = {

        val generated =
          masterOutputArea
            .getText
            .trim

        if (
          generated.isEmpty
        ) {

          showWarning(
            frame,
            "MASTER PROMPT",
            "Generate the prompt first."
          )

        } else {

          copyToClipboard(
            generated
          )

          showInfo(
            frame,
            "MASTER PROMPT",
            "MASTER PROMPT copied to clipboard."
          )

          recordActivity(
            "Copied MASTER PROMPT"
          )
        }
      }
    }
  )

  // --------------------------------------------------------
  // CLEAR MASTER PROMPT
  // --------------------------------------------------------

  clearMasterPromptButton.addActionListener(
    new ActionListener {

      override def actionPerformed(
          e: ActionEvent
      ): Unit = {

        masterIdeaArea.setText(
          ""
        )

        masterOutputArea.setText(
          ""
        )
      }
    }
  )

  // --------------------------------------------------------
  // LOCK MASTER PROMPT
  // --------------------------------------------------------

  def lockMasterPrompt(): Unit = {

    if (
      masterTabAdded
    ) {

      tabs.remove(
        masterPromptPanel
      )

      masterTabAdded =
        false
    }

    masterUnlocked =
      false

    masterIdeaArea.setText(
      ""
    )

    masterOutputArea.setText(
      ""
    )

    masterStatus.setText(
      "LOCKED"
    )

    masterStatus.setForeground(
      WARNING
    )

    recordActivity(
      "Locked MASTER PROMPT"
    )

    if (
      tabs.getTabCount > 0
    ) {

      tabs.setSelectedIndex(
        0
      )
    }
  }

  lockMasterPromptButton.addActionListener(
    new ActionListener {

      override def actionPerformed(
          e: ActionEvent
      ): Unit = {

        lockMasterPrompt()
      }
    }
  )

  // --------------------------------------------------------
  // UNLOCK MASTER PROMPT
  // --------------------------------------------------------

  def unlockMasterPrompt(): Unit = {

    val passwordField =
      new JPasswordField()

    passwordField.setPreferredSize(
      new Dimension(
        320,
        35
      )
    )

    val wrapper =
      new JPanel(
        new BorderLayout(
          8,
          8
        )
      )

    wrapper.add(
      new JLabel(
        "Enter MASTER ACCESS password:"
      ),
      BorderLayout.NORTH
    )

    wrapper.add(
      passwordField,
      BorderLayout.CENTER
    )

    val result =
      JOptionPane.showConfirmDialog(
        frame,
        wrapper,
        "MASTER PROMPT ACCESS",
        JOptionPane.OK_CANCEL_OPTION,
        JOptionPane.PLAIN_MESSAGE
      )

    if (
      result ==
        JOptionPane.OK_OPTION
    ) {

      val entered =
        new String(
          passwordField.getPassword
        )

      val enteredHash =
        sha256(
          entered
        )

      if (
        enteredHash ==
          masterPromptPasswordHash
      ) {

        masterUnlocked =
          true

        masterStatus.setText(
          "ACCESS GRANTED"
        )

        masterStatus.setForeground(
          SUCCESS
        )

        if (
          !masterTabAdded
        ) {

          tabs.addTab(
            "MASTER PROMPT",
            masterPromptPanel
          )

          masterTabAdded =
            true
        }

        tabs.setSelectedComponent(
          masterPromptPanel
        )

        recordActivity(
          "MASTER PROMPT unlocked"
        )

      } else {

        masterUnlocked =
          false

        showWarning(
          frame,
          "ACCESS DENIED",
          "Wrong MASTER ACCESS password.\n\nMASTER PROMPT remains hidden."
        )

        recordActivity(
          "Failed MASTER PROMPT access"
        )
      }
    }
  }

  dashboardMasterButton.addActionListener(
    new ActionListener {

      override def actionPerformed(
          e: ActionEvent
      ): Unit = {

        unlockMasterPrompt()
      }
    }
  )

  dashboardHistoryButton.addActionListener(
    new ActionListener {

      override def actionPerformed(
          e: ActionEvent
      ): Unit = {

        tabs.setSelectedComponent(
          historyPanel
        )

        refreshHistoryView()
      }
    }
  )

  dashboardSettingsButton.addActionListener(
    new ActionListener {

      override def actionPerformed(
          e: ActionEvent
      ): Unit = {

        tabs.setSelectedComponent(
          settingsPanel
        )
      }
    }
  )


  // ========================================================
  // AI DOCTOR - SAFE EDUCATIONAL HEALTH ASSISTANT
  // ========================================================

  val doctorPanel =
    new JPanel(
      new BorderLayout(12, 12)
    )

  doctorPanel.setBackground(
    PANEL_BG
  )

  doctorPanel.setBorder(
    new EmptyBorder(12, 12, 12, 12)
  )

  var doctorUnlocked = false
  var doctorMood = "NEUTRAL"
  var doctorPulse = 0
  var doctorStatusText = "LOCKED"
  var doctorTimer: javax.swing.Timer = null

  val doctorTitle =
    makeLabel(
      "AI DOCTOR - HEALTH ASSISTANT",
      26,
      true
    )

  val doctorStatus =
    makeLabel(
      "LOCKED",
      15,
      true
    )

  doctorStatus.setForeground(
    WARNING
  )

  val doctorHeader =
    new JPanel(
      new BorderLayout(10, 10)
    )

  doctorHeader.setBackground(
    PANEL_BG
  )

  doctorHeader.add(
    doctorTitle,
    BorderLayout.WEST
  )

  doctorHeader.add(
    doctorStatus,
    BorderLayout.EAST
  )

  doctorPanel.add(
    doctorHeader,
    BorderLayout.NORTH
  )

  // --------------------------------------------------------
  // ANIMATED AI DOCTOR AVATAR
  // --------------------------------------------------------

  val doctorAvatar =
    new JPanel {

      setOpaque(false)

      override def paintComponent(
          graphics: Graphics
      ): Unit = {

        super.paintComponent(graphics)

        val g =
          graphics.asInstanceOf[Graphics2D]

        g.setRenderingHint(
          RenderingHints.KEY_ANTIALIASING,
          RenderingHints.VALUE_ANTIALIAS_ON
        )

        val w = getWidth
        val h = getHeight
        val cx = w / 2
        val cy = h / 2

        val glow =
          36 + Math.abs(doctorPulse % 20)

        g.setColor(
          new Color(60, 150, 255, 22)
        )
        g.fillOval(
          cx - 100 - glow,
          cy - 100 - glow,
          200 + glow * 2,
          200 + glow * 2
        )

        var ring = 0
        while (ring < 3) {
          g.setColor(
            new Color(
              80,
              170,
              255,
              35 + ring * 18
            )
          )
          g.drawOval(
            cx - 90 - ring * 18,
            cy - 90 - ring * 18,
            180 + ring * 36,
            180 + ring * 36
          )
          ring += 1
        }

        // Head
        g.setColor(
          new Color(205, 220, 235)
        )
        g.fillOval(
          cx - 64,
          cy - 72,
          128,
          128
        )

        g.setColor(
          new Color(70, 80, 95)
        )
        g.fillArc(
          cx - 64,
          cy - 78,
          128,
          65,
          0,
          180
        )

        // Ears
        g.fillRoundRect(
          cx - 76,
          cy - 20,
          18,
          38,
          10,
          10
        )
        g.fillRoundRect(
          cx + 58,
          cy - 20,
          18,
          38,
          10,
          10
        )

        val happy =
          doctorMood == "HAPPY"

        val sad =
          doctorMood == "SAD"

        // Eyes
        g.setColor(
          new Color(30, 40, 50)
        )
        if (happy) {
          g.drawArc(
            cx - 40,
            cy - 15,
            22,
            14,
            0,
            180
          )
          g.drawArc(
            cx + 18,
            cy - 15,
            22,
            14,
            0,
            180
          )
        } else {
          g.fillOval(
            cx - 33,
            cy - 12,
            14,
            14
          )
          g.fillOval(
            cx + 20,
            cy - 12,
            14,
            14
          )
        }

        // Nose
        g.drawLine(
          cx,
          cy - 4,
          cx - 5,
          cy + 17
        )
        g.drawLine(
          cx - 5,
          cy + 17,
          cx + 6,
          cy + 17
        )

        // Mouth
        if (sad) {
          g.drawArc(
            cx - 25,
            cy + 15,
            50,
            25,
            0,
            180
          )
        } else {
          g.drawArc(
            cx - 25,
            cy + 5,
            50,
            30,
            180,
            180
          )
        }

        // Medical badge / coat
        g.setColor(
          new Color(242, 246, 250)
        )
        g.fillRoundRect(
          cx - 83,
          cy + 54,
          166,
          94,
          24,
          24
        )

        g.setColor(
          DANGER
        )
        g.fillRect(
          cx - 10,
          cy + 70,
          20,
          55
        )
        g.fillRect(
          cx - 27,
          cy + 87,
          54,
          20
        )

        g.setColor(
          new Color(30, 45, 60)
        )
        g.setFont(
          new Font(
            "SansSerif",
            Font.BOLD,
            15
          )
        )
        g.drawString(
          doctorMood,
          cx - 40,
          cy + 176
        )

        g.setFont(
          new Font(
            "SansSerif",
            Font.PLAIN,
            11
          )
        )
        g.drawString(
          doctorStatusText,
          cx - 65,
          cy + 195
        )
      }
    }

  doctorAvatar.setPreferredSize(
    new Dimension(300, 390)
  )

  // --------------------------------------------------------
  // DOCTOR LOGIN
  // --------------------------------------------------------

  val doctorPasswordArea =
    new JPanel(
      new BorderLayout(8, 8)
    )

  doctorPasswordArea.setBackground(
    PANEL_BG
  )

  val doctorPasswordTitle =
    makeLabel(
      "DOCTOR ACCESS",
      18,
      true
    )

  val doctorPasswordField =
    new JPasswordField()

  doctorPasswordField.setFont(
    new Font(
      "SansSerif",
      Font.PLAIN,
      18
    )
  )

  val doctorLoginButton =
    makeButton(
      "OPEN AI DOCTOR"
    )

  doctorPasswordArea.add(
    doctorPasswordTitle,
    BorderLayout.NORTH
  )

  doctorPasswordArea.add(
    doctorPasswordField,
    BorderLayout.CENTER
  )

  doctorPasswordArea.add(
    doctorLoginButton,
    BorderLayout.SOUTH
  )

  // --------------------------------------------------------
  // ADVANCED MULTILINGUAL CHECKUP INPUTS
  // --------------------------------------------------------

  val doctorSymptomArea =
    makeArea()

  doctorSymptomArea.setLineWrap(
    true
  )

  doctorSymptomArea.setWrapStyleWord(
    true
  )

  doctorSymptomArea.setText(
    "????? / ?????? / English / mixed language ????? symptoms ????.\n" +
    "Example: ??? ????? ???, nose blocked ??? ??? ???? cough ???."
  )

  val doctorTempField =
    makeTextField()

  val doctorDaysField =
    makeTextField()

  val doctorLocationField =
    makeTextField()

  val doctorAgeField =
    makeTextField()

  val doctorSexField =
    makeTextField()

  doctorSexField.setToolTipText(
    "Optional: Male / Female / Other"
  )

  val doctorLanguageBox =
    new JComboBox[String](
      Array(
        "AUTO DETECT",
        "MARATHI",
        "HINDI",
        "ENGLISH",
        "MIXED"
      )
    )

  doctorLanguageBox.setFont(
    new Font(
      "SansSerif",
      Font.PLAIN,
      13
    )
  )

  val doctorResultArea =
    makeArea()

  doctorResultArea.setEditable(
    false
  )

  doctorResultArea.setLineWrap(
    true
  )

  doctorResultArea.setWrapStyleWord(
    true
  )

  val doctorCheckupButton =
    makeButton(
      "FULL CHECKUP"
    )

  val doctorUnderstandButton =
    makeButton(
      "UNDERSTAND TEXT"
    )

  val doctorMedicineButton =
    makeButton(
      "MEDICINE INFORMATION"
    )

  val doctorClearButton =
    makeButton(
      "CLEAR"
    )

  val doctorSafetyButton =
    makeButton(
      "SAFETY GUIDE"
    )

  val doctorModeLabel =
    makeLabel(
      "MODE: MULTILINGUAL OFFLINE UNDERSTANDING",
      12,
      true
    )
  doctorModeLabel.setForeground(
    new Color(150, 210, 255)
  )

  val doctorForm =
    new JPanel(
      new BorderLayout(8, 8)
    )

  doctorForm.setBackground(
    PANEL_BG
  )

  val doctorFields =
    new JPanel(
      new GridLayout(
        6,
        2,
        8,
        8
      )
    )

  doctorFields.setBackground(
    PANEL_BG
  )

  doctorFields.add(
    makeLabel(
      "Temperature (optional)",
      12,
      false
    )
  )
  doctorFields.add(
    doctorTempField
  )

  doctorFields.add(
    makeLabel(
      "How many days?",
      12,
      false
    )
  )
  doctorFields.add(
    doctorDaysField
  )

  doctorFields.add(
    makeLabel(
      "Pain / location",
      12,
      false
    )
  )
  doctorFields.add(
    doctorLocationField
  )

  doctorFields.add(
    makeLabel(
      "Age (optional)",
      12,
      false
    )
  )
  doctorFields.add(
    doctorAgeField
  )

  doctorFields.add(
    makeLabel(
      "Sex (optional)",
      12,
      false
    )
  )
  doctorFields.add(
    doctorSexField
  )

  doctorFields.add(
    makeLabel(
      "Input language",
      12,
      false
    )
  )
  doctorFields.add(
    doctorLanguageBox
  )

  doctorForm.add(
    doctorFields,
    BorderLayout.NORTH
  )

  doctorForm.add(
    makeScroll(
      doctorSymptomArea
    ),
    BorderLayout.CENTER
  )

  val doctorButtons =
    new JPanel(
      new GridLayout(
        2,
        3,
        6,
        6
      )
    )

  doctorButtons.setBackground(
    PANEL_BG
  )

  doctorButtons.add(
    doctorCheckupButton
  )
  doctorButtons.add(
    doctorUnderstandButton
  )
  doctorButtons.add(
    doctorMedicineButton
  )
  doctorButtons.add(
    doctorClearButton
  )
  doctorButtons.add(
    doctorSafetyButton
  )
  doctorButtons.add(
    doctorModeLabel
  )

  doctorForm.add(
    doctorButtons,
    BorderLayout.SOUTH
  )

  val doctorMain =
    new JSplitPane(
      JSplitPane.HORIZONTAL_SPLIT,
      doctorAvatar,
      doctorForm
    )

  doctorMain.setResizeWeight(
    0.29
  )

  doctorMain.setDividerLocation(
    330
  )

  val doctorOutputPanel =
    new JPanel(
      new BorderLayout(8, 8)
    )

  doctorOutputPanel.setBackground(
    PANEL_BG
  )

  doctorOutputPanel.add(
    makeLabel(
      "CHECKUP / UNDERSTANDING REPORT",
      17,
      true
    ),
    BorderLayout.NORTH
  )

  doctorOutputPanel.add(
    makeScroll(
      doctorResultArea
    ),
    BorderLayout.CENTER
  )

  val doctorStack =
    new JSplitPane(
      JSplitPane.VERTICAL_SPLIT,
      doctorMain,
      doctorOutputPanel
    )

  doctorStack.setResizeWeight(
    0.57
  )

  doctorPanel.add(
    doctorStack,
    BorderLayout.CENTER
  )

  doctorResultArea.setText(
    "AI DOCTOR LOCKED\n\n" +
    "Open with the Doctor Access password first.\n\n" +
    "Then type symptoms in Marathi, Hindi, English, transliterated text, or a mixture.\n" +
    "The app performs offline keyword-based understanding and safety triage.\n\n" +
    "MEDICINE INFORMATION mode gives general educational guidance only. It does not issue a personal prescription or dosage."
  )

  // --------------------------------------------------------
  // ADVANCED OFFLINE LANGUAGE / SYMPTOM ENGINE
  // --------------------------------------------------------

  def doctorNormalize(
      text: String
  ): String = {
    if (text == null)
      ""
    else
      text
        .trim
        .toLowerCase
        .replace("?", ".")
        .replace(",", " ")
        .replace(";", " ")
        .replace("/", " ")
  }

  def doctorContainsAny(
      text: String,
      words: Array[String]
  ): Boolean = {
    var i = 0
    while (i < words.length) {
      if (text.contains(words(i)))
        return true
      i += 1
    }
    false
  }

  def doctorDetectLanguage(
      raw: String
  ): String = {
    if (raw == null || raw.trim.isEmpty)
      "UNKNOWN"
    else {
      var marathiHindi = 0
      var latin = 0
      var other = 0
      var i = 0
      while (i < raw.length) {
        val ch = raw.charAt(i).toInt
        if (ch >= 0x0900 && ch <= 0x097F)
          marathiHindi += 1
        else if (
          (ch >= 65 && ch <= 90) ||
          (ch >= 97 && ch <= 122)
        )
          latin += 1
        else if (!Character.isWhitespace(raw.charAt(i)))
          other += 1
        i += 1
      }
      if (marathiHindi > 0 && latin > 0)
        "MIXED MARATHI/HINDI + ENGLISH"
      else if (marathiHindi > latin && marathiHindi >= other)
        "MARATHI/HINDI SCRIPT"
      else if (latin > 0)
        "ENGLISH / LATIN"
      else
        "OTHER / UNRECOGNIZED SCRIPT"
    }
  }

  def doctorCanonicalize(
      raw: String
  ): String = {
    var t = doctorNormalize(raw)

    val replacements = Array(
      ("jukham", " cold "),
      ("zukam", " cold "),
      ("sardi", " cold "),
      ("sardi hui", " cold "),
      ("khokla", " cough "),
      ("khaansi", " cough "),
      ("khansi", " cough "),
      ("bukhar", " fever "),
      ("taap", " fever "),
      ("tap", " fever "),
      ("dok duk", " headache "),
      ("doka duk", " headache "),
      ("ang duk", " body pain "),
      ("ghasa duk", " sore throat "),
      ("gala dard", " sore throat "),
      ("pet dard", " stomach pain "),
      ("ulti", " vomiting "),
      ("julaab", " diarrhea "),
      ("dast", " diarrhea "),
      ("naak band", " blocked nose "),
      ("naak vahate", " runny nose "),
      ("nausea", " nausea "),
      ("chakkar", " dizziness "),
      ("thakva", " fatigue "),
      ("thakwat", " fatigue "),
      ("male", " male "),
      ("female", " female ")
    )

    var i = 0
    while (i < replacements.length) {
      t = t.replace(
        replacements(i)._1,
        replacements(i)._2
      )
      i += 1
    }
    t
  }

  def doctorBuildUnderstanding(
      raw: String
  ): String = {
    val text = doctorCanonicalize(raw)
    val language = doctorDetectLanguage(raw)

    val cold = doctorContainsAny(
      text,
      Array("cold", "?????", "????", "runny nose", "blocked nose", "sneeze")
    )
    val cough = doctorContainsAny(
      text,
      Array("cough", "?????", "?????", "?????")
    )
    val fever = doctorContainsAny(
      text,
      Array("fever", "???", "?????", "????")
    )
    val headache = doctorContainsAny(
      text,
      Array("headache", "????????", "?? ????", "???? ???")
    )
    val throat = doctorContainsAny(
      text,
      Array("sore throat", "throat pain", "??? ???", "??? ????")
    )
    val stomach = doctorContainsAny(
      text,
      Array("stomach pain", "abdominal pain", "???????", "??? ????")
    )
    val vomiting = doctorContainsAny(
      text,
      Array("vomiting", "vomit", "????", "?????")
    )
    val diarrhea = doctorContainsAny(
      text,
      Array("diarrhea", "loose motion", "?????", "????")
    )
    val bodyPain = doctorContainsAny(
      text,
      Array("body pain", "muscle pain", "??? ???", "???? ???")
    )
    val dizziness = doctorContainsAny(
      text,
      Array("dizziness", "?????", "????")
    )
    val fatigue = doctorContainsAny(
      text,
      Array("fatigue", "tired", "????", "?????")
    )
    val rash = doctorContainsAny(
      text,
      Array("rash", "????", "????", "skin rash")
    )
    val earPain = doctorContainsAny(
      text,
      Array("ear pain", "??? ???", "??? ????")
    )

    val sb = new StringBuilder
    sb.append("AI DOCTOR - TEXT UNDERSTANDING\n")
    sb.append("================================\n\n")
    sb.append("Detected writing style: ").append(language).append("\n")
    sb.append("Normalized symptom text:\n")
    sb.append(text.trim).append("\n\n")
    sb.append("Recognized topics:\n")
    sb.append("  Cold / nose       : ").append(cold).append("\n")
    sb.append("  Cough             : ").append(cough).append("\n")
    sb.append("  Fever             : ").append(fever).append("\n")
    sb.append("  Headache          : ").append(headache).append("\n")
    sb.append("  Sore throat       : ").append(throat).append("\n")
    sb.append("  Stomach pain      : ").append(stomach).append("\n")
    sb.append("  Vomiting          : ").append(vomiting).append("\n")
    sb.append("  Diarrhea          : ").append(diarrhea).append("\n")
    sb.append("  Body pain         : ").append(bodyPain).append("\n")
    sb.append("  Dizziness         : ").append(dizziness).append("\n")
    sb.append("  Fatigue           : ").append(fatigue).append("\n")
    sb.append("  Rash              : ").append(rash).append("\n")
    sb.append("  Ear pain          : ").append(earPain).append("\n\n")
    sb.append("Note: this is offline text matching, not unrestricted translation or a clinical diagnosis.\n")
    sb.toString
  }

  def doctorMedicineInformation(
      raw: String
  ): String = {
    val text = doctorCanonicalize(raw)

    val cold = doctorContainsAny(
      text,
      Array("cold", "?????", "runny nose", "blocked nose")
    )
    val cough = doctorContainsAny(
      text,
      Array("cough", "?????", "?????", "?????")
    )
    val fever = doctorContainsAny(
      text,
      Array("fever", "???", "?????")
    )
    val headache = doctorContainsAny(
      text,
      Array("headache", "????????", "?? ????")
    )
    val diarrhea = doctorContainsAny(
      text,
      Array("diarrhea", "loose motion", "?????", "????")
    )
    val vomiting = doctorContainsAny(
      text,
      Array("vomiting", "vomit", "????", "?????")
    )

    val sb = new StringBuilder
    sb.append("MEDICINE INFORMATION MODE\n")
    sb.append("===========================\n\n")
    sb.append("This is GENERAL EDUCATIONAL information, not a personal prescription.\n")
    sb.append("No individual drug choice, dose, duration, or combination is issued by this app.\n\n")

    if (cold) {
      sb.append("Cold / nasal symptoms:\n")
      sb.append("- Non-drug supportive care may include fluids, rest, and saline nasal care.\n")
      sb.append("- Ask a pharmacist/clinician whether any age-appropriate OTC option is suitable.\n\n")
    }

    if (cough) {
      sb.append("Cough:\n")
      sb.append("- The right treatment depends on whether the cough is dry, mucus-producing, allergic, infectious, or another cause.\n")
      sb.append("- Some cough/cold products are not appropriate for every age or situation; check with a pharmacist/clinician.\n\n")
    }

    if (fever || headache) {
      sb.append("Fever / headache:\n")
      sb.append("- Rest, fluids, and monitoring are useful supportive steps.\n")
      sb.append("- A clinician/pharmacist can advise whether an age-appropriate fever/pain medicine is appropriate.\n\n")
    }

    if (diarrhea || vomiting) {
      sb.append("Vomiting / diarrhea:\n")
      sb.append("- Hydration is important; an oral rehydration solution may be discussed with a pharmacist/clinician.\n")
      sb.append("- Persistent vomiting, blood, severe weakness, or signs of dehydration need medical assessment.\n\n")
    }

    if (!cold && !cough && !fever && !headache && !diarrhea && !vomiting) {
      sb.append("No specific medicine-information category was confidently matched.\n\n")
    }

    sb.append("Before taking any medicine, tell the pharmacist/clinician about:\n")
    sb.append("age, allergies, other medicines, long-term conditions, pregnancy possibility when relevant, and the full symptom history.\n\n")
    sb.append("This module intentionally avoids personalized prescriptions and dosages.\n")
    sb.toString
  }

  def runDoctorCheckup(): Unit = {
    val raw = doctorSymptomArea.getText.trim

    if (!doctorUnlocked) {
      doctorResultArea.setText(
        "DOCTOR IS LOCKED.\n\nOpen AI Doctor using the access password first."
      )
      doctorMood = "SAD"
      doctorStatusText = "ACCESS REQUIRED"
      doctorAvatar.repaint()
      return
    }

    if (raw.isEmpty) {
      doctorResultArea.setText(
        "Please write your symptoms first.\n\n" +
        "Example: ??? ????? ???, nose blocked ??? ??? ???? cough ???."
      )
      doctorMood = "SAD"
      doctorStatusText = "NEED MORE INFORMATION"
      doctorAvatar.repaint()
      return
    }

    val text = doctorCanonicalize(raw)
    val temperature = doctorTempField.getText.trim
    val duration = doctorDaysField.getText.trim
    val location = doctorLocationField.getText.trim
    val age = doctorAgeField.getText.trim
    val sex = doctorSexField.getText.trim

    val breathingRedFlag = doctorContainsAny(
      text,
      Array(
        "????? ???? ??? ????",
        "????? ??????? ?????",
        "?? ?????",
        "???? ???? ??? ??????",
        "???? ???? ? ???",
        "breathing difficulty",
        "difficulty breathing",
        "shortness of breath"
      )
    )

    val chestRedFlag = doctorContainsAny(
      text,
      Array(
        "????? ????? ?????",
        "???? ?????",
        "???? ??? ????",
        "chest pain",
        "severe chest pain"
      )
    )

    val faintRedFlag = doctorContainsAny(
      text,
      Array(
        "???????",
        "????? ?????",
        "?????",
        "faint",
        "fainted",
        "unconscious"
      )
    )

    val seriousBleeding = doctorContainsAny(
      text,
      Array(
        "??? ????",
        "??????????? ????? ????",
        "???? ???",
        "heavy bleeding",
        "bleeding won't stop"
      )
    )

    val severeAllergy = doctorContainsAny(
      text,
      Array(
        "????? ?????",
        "?? ?????",
        "??? ?????",
        "????? ???",
        "???? ???",
        "severe allergy",
        "swollen tongue",
        "face swelling"
      )
    )

    val seizureRedFlag = doctorContainsAny(
      text,
      Array(
        "seizure",
        "????",
        "fits",
        "??????"
      )
    )

    val blueLipsRedFlag = doctorContainsAny(
      text,
      Array(
        "blue lips",
        "?? ????",
        "???? ????"
      )
    )

    val emergencyFlag =
      breathingRedFlag ||
      chestRedFlag ||
      faintRedFlag ||
      seriousBleeding ||
      severeAllergy ||
      seizureRedFlag ||
      blueLipsRedFlag

    val cold = doctorContainsAny(
      text,
      Array("cold", "?????", "??? ?????", "runny nose", "blocked nose", "sneeze")
    )
    val cough = doctorContainsAny(
      text,
      Array("?????", "?????", "?????", "cough")
    )
    val fever = doctorContainsAny(
      text,
      Array("???", "?????", "????", "fever")
    )
    val headache = doctorContainsAny(
      text,
      Array("????????", "???? ???", "?? ????", "headache")
    )
    val throat = doctorContainsAny(
      text,
      Array("??? ???", "??? ????", "??? ????", "sore throat", "throat pain")
    )
    val stomach = doctorContainsAny(
      text,
      Array("???????", "??? ???", "??? ????", "stomach pain", "abdominal pain")
    )
    val vomiting = doctorContainsAny(
      text,
      Array("????", "?????", "vomit", "vomiting")
    )
    val diarrhea = doctorContainsAny(
      text,
      Array("?????", "????? ???????", "????", "diarrhea", "loose motion")
    )
    val bodyPain = doctorContainsAny(
      text,
      Array("??? ???", "???? ???", "body pain", "muscle pain")
    )
    val dizziness = doctorContainsAny(
      text,
      Array("?????", "????", "dizziness")
    )
    val rash = doctorContainsAny(
      text,
      Array("????", "????", "rash", "skin rash")
    )
    val earPain = doctorContainsAny(
      text,
      Array("??? ???", "??? ????", "ear pain")
    )
    val fatigue = doctorContainsAny(
      text,
      Array("????", "?????", "tired", "fatigue")
    )

    val sb = new StringBuilder

    doctorMood = if (emergencyFlag) "SAD" else "HAPPY"
    doctorStatusText = if (emergencyFlag) "URGENT HUMAN HELP" else "CHECKUP COMPLETE"

    sb.append("AI DOCTOR - MULTILINGUAL CHECKUP\n")
    sb.append("================================\n\n")
    sb.append("Detected writing style: ")
    sb.append(doctorDetectLanguage(raw)).append("\n")
    sb.append("Selected input mode: ")
    sb.append(doctorLanguageBox.getSelectedItem.toString).append("\n")
    sb.append("Reported symptoms:\n")
    sb.append(raw).append("\n\n")

    if (age.nonEmpty)
      sb.append("Age entered: ").append(age).append("\n")
    if (sex.nonEmpty)
      sb.append("Sex entered: ").append(sex).append("\n")
    if (temperature.nonEmpty)
      sb.append("Temperature entered: ").append(temperature).append("\n")
    if (duration.nonEmpty)
      sb.append("Duration entered: ").append(duration).append("\n")
    if (location.nonEmpty)
      sb.append("Pain/location entered: ").append(location).append("\n")

    sb.append("\n")
    sb.append("UNDERSTANDING\n")
    sb.append("------------\n")
    sb.append("Cold/nose: ").append(cold).append(" | Cough: ").append(cough).append(" | Fever: ").append(fever).append("\n")
    sb.append("Headache: ").append(headache).append(" | Throat: ").append(throat).append(" | Body pain: ").append(bodyPain).append("\n")
    sb.append("Stomach: ").append(stomach).append(" | Vomiting: ").append(vomiting).append(" | Diarrhea: ").append(diarrhea).append("\n")
    sb.append("Dizziness: ").append(dizziness).append(" | Rash: ").append(rash).append(" | Ear pain: ").append(earPain).append(" | Fatigue: ").append(fatigue).append("\n\n")

    if (emergencyFlag) {
      sb.append("URGENT SAFETY ALERT\n")
      sb.append("-------------------\n")
      sb.append("The text contains one or more possible emergency warning signs.\n")
      sb.append("Tell a parent/guardian or another trusted adult immediately and seek urgent medical care.\n")
      sb.append("Do not wait for this program to diagnose the cause.\n\n")
    } else {
      sb.append("GENERAL SUPPORT\n")
      sb.append("----------------\n")
      sb.append("Rest, fluids, comfortable surroundings, and monitoring can be useful while arranging appropriate medical advice.\n")
      if (cold || cough || throat)
        sb.append("Respiratory/cold-like wording was recognized.\n")
      if (fever)
        sb.append("Fever-like wording was recognized; ask a trusted adult to help monitor temperature and overall condition.\n")
      if (stomach || vomiting || diarrhea)
        sb.append("Digestive symptoms were recognized; hydration is important and worsening/persistent symptoms need clinical advice.\n")
      if (rash)
        sb.append("A rash was mentioned; worsening, widespread, or rapidly developing rash needs medical assessment.\n")
      sb.append("\n")
    }

    sb.append("MEDICINE INFORMATION\n")
    sb.append("---------------------\n")
    sb.append("Use the MEDICINE INFORMATION button for general educational guidance.\n")
    sb.append("This app does not issue a personal prescription, drug selection, or dosage.\n\n")

    sb.append("WHEN TO GET HELP\n")
    sb.append("----------------\n")
    sb.append("Get medical advice for severe, worsening, unusual, or persistent symptoms.\n")
    sb.append("Urgent warning signs include severe breathing difficulty, severe chest pain, fainting, uncontrolled bleeding, seizures, blue lips, or major swelling affecting breathing.\n\n")

    sb.append("IMPORTANT\n")
    sb.append("---------\n")
    sb.append("This is an educational software assistant using local text patterns. It is not a doctor or emergency service.\n")

    doctorResultArea.setText(
      sb.toString
    )
    doctorResultArea.setCaretPosition(
      0
    )
    doctorAvatar.repaint()
    recordActivity(
      "Used AI Doctor multilingual checkup"
    )
  }

  def doctorAccessGranted(): Unit = {
    doctorUnlocked = true
    doctorMood = "HAPPY"
    doctorStatusText = "READY"
    doctorStatus.setText(
      "ACCESS GRANTED"
    )
    doctorStatus.setForeground(
      SUCCESS
    )
    doctorResultArea.setText(
      "AI DOCTOR READY\n\n" +
      "Type Marathi, Hindi, English, transliteration, or mixed text.\n\n" +
      "UNDERSTAND TEXT = show what the offline parser recognized.\n" +
      "FULL CHECKUP = safety triage + symptom summary.\n" +
      "MEDICINE INFORMATION = general educational guidance only; no personal prescription or dosage."
    )
    doctorAvatar.repaint()
    doctorPasswordField.setText(
      ""
    )
    recordActivity(
      "AI Doctor access granted"
    )
  }

  doctorLoginButton.addActionListener(
    new ActionListener {
      override def actionPerformed(
          e: ActionEvent
      ): Unit = {
        val entered =
          new String(
            doctorPasswordField.getPassword
          ).trim

        val passA =
          "yadnesh@member2026"

        if (entered == passA) {
          doctorAccessGranted()
        } else {
          doctorUnlocked = false
          doctorMood = "SAD"
          doctorStatusText = "ACCESS DENIED"
          doctorStatus.setText(
            "ACCESS DENIED"
          )
          doctorStatus.setForeground(
            DANGER
          )
          doctorResultArea.setText(
            "ACCESS DENIED\n\nThe Doctor Access password is incorrect.\n\nThe AI Doctor remains locked."
          )
          doctorAvatar.repaint()
          recordActivity(
            "Failed AI Doctor access"
          )
        }
      }
    }
  )

  doctorPasswordField.addActionListener(
    new ActionListener {
      override def actionPerformed(
          e: ActionEvent
      ): Unit = {
        doctorLoginButton.doClick()
      }
    }
  )

  doctorCheckupButton.addActionListener(
    new ActionListener {
      override def actionPerformed(
          e: ActionEvent
      ): Unit = {
        runDoctorCheckup()
      }
    }
  )

  doctorUnderstandButton.addActionListener(
    new ActionListener {
      override def actionPerformed(
          e: ActionEvent
      ): Unit = {
        if (!doctorUnlocked) {
          doctorResultArea.setText(
            "DOCTOR IS LOCKED.\n\nOpen AI Doctor first."
          )
          doctorMood = "SAD"
          doctorStatusText = "ACCESS REQUIRED"
        } else {
          val raw = doctorSymptomArea.getText.trim
          if (raw.isEmpty) {
            doctorResultArea.setText(
              "Please write symptoms first."
            )
            doctorMood = "SAD"
            doctorStatusText = "NEED TEXT"
          } else {
            doctorResultArea.setText(
              doctorBuildUnderstanding(raw)
            )
            doctorResultArea.setCaretPosition(0)
            doctorMood = "HAPPY"
            doctorStatusText = "TEXT UNDERSTOOD"
            recordActivity("Used AI Doctor text understanding")
          }
        }
        doctorAvatar.repaint()
      }
    }
  )

  doctorMedicineButton.addActionListener(
    new ActionListener {
      override def actionPerformed(
          e: ActionEvent
      ): Unit = {
        if (!doctorUnlocked) {
          doctorResultArea.setText(
            "DOCTOR IS LOCKED.\n\nOpen AI Doctor first."
          )
          doctorMood = "SAD"
          doctorStatusText = "ACCESS REQUIRED"
        } else {
          val raw = doctorSymptomArea.getText.trim
          if (raw.isEmpty) {
            doctorResultArea.setText(
              "Write the symptom description first, then press MEDICINE INFORMATION."
            )
            doctorMood = "SAD"
            doctorStatusText = "NEED TEXT"
          } else {
            doctorResultArea.setText(
              doctorMedicineInformation(raw)
            )
            doctorResultArea.setCaretPosition(0)
            doctorMood = "HAPPY"
            doctorStatusText = "MEDICINE INFO"
            recordActivity("Opened AI Doctor medicine information")
          }
        }
        doctorAvatar.repaint()
      }
    }
  )

  doctorClearButton.addActionListener(
    new ActionListener {
      override def actionPerformed(
          e: ActionEvent
      ): Unit = {
        doctorSymptomArea.setText("")
        doctorTempField.setText("")
        doctorDaysField.setText("")
        doctorLocationField.setText("")
        doctorAgeField.setText("")
        doctorSexField.setText("")
        doctorLanguageBox.setSelectedIndex(0)
        doctorResultArea.setText(
          "Fields cleared. Write symptoms and choose the action you need."
        )
        doctorMood = if (doctorUnlocked) "HAPPY" else "NEUTRAL"
        doctorStatusText = if (doctorUnlocked) "READY" else "LOCKED"
        doctorAvatar.repaint()
      }
    }
  )

  doctorSafetyButton.addActionListener(
    new ActionListener {
      override def actionPerformed(
          e: ActionEvent
      ): Unit = {
        doctorMood = "HAPPY"
        doctorStatusText = "SAFETY GUIDE"
        doctorResultArea.setText(
          "SAFETY GUIDE\n\n" +
          "1. Severe breathing difficulty, severe chest pain, fainting, uncontrolled bleeding, seizures, blue lips, or major swelling affecting breathing requires urgent human medical help.\n\n" +
          "2. Severe, worsening, unusual, or persistent symptoms need medical advice.\n\n" +
          "3. Do not use this program to choose, combine, stop, or dose medicines. Ask a qualified clinician/pharmacist and a trusted adult.\n\n" +
          "4. Keep the original medicine packaging/label available when asking a pharmacist or clinician about a product."
        )
        doctorAvatar.repaint()
      }
    }
  )

  // --------------------------------------------------------
  // LIGHTWEIGHT DOCTOR ANIMATION
  // --------------------------------------------------------

  doctorTimer =
    new javax.swing.Timer(
      70,
      new ActionListener {
        override def actionPerformed(
            e: ActionEvent
        ): Unit = {
          doctorPulse =
            (doctorPulse + 1) % 120
          doctorAvatar.repaint()
        }
      }
    )

  doctorTimer.setCoalesce(
    true
  )

  doctorTimer.start()

  // ========================================================
  // AIR TO WATER - REAL PHYSICS ENGINE + 3D PLANT MODEL
  // ========================================================

  val airWaterPanel =
    new JPanel(
      new BorderLayout(10, 10)
    )

  airWaterPanel.setBackground(PANEL_BG)
  airWaterPanel.setBorder(
    new EmptyBorder(10, 10, 10, 10)
  )

  // --------------------------------------------------------
  // REAL-WORLD INPUTS
  // --------------------------------------------------------

  var airWaterRunning = false
  var airWaterHumidity = 60
  var airWaterTemperature = 27
  var airWaterCoilTemperature = 8
  var airWaterAirflow = 900
  var airWaterPressureKPa = 101
  var airWaterHXEfficiency = 78
  var airWaterCollectionEfficiency = 88
  var airWaterCOP = 3.0
  var airWaterFanPressurePa = 180
  var airWaterFanEfficiency = 62
  var airWaterCO2ppm = 420
  var airWaterCO2Capture = false
  var airWaterCO2Efficiency = 55
  var airWaterRuntimeHours = 1.0

  var airWaterTank = 0.0
  var airWaterProduced = 0.0
  var airWaterCO2Captured = 0.0
  var airWaterFrame = 0
  var airWaterTotalEnergy = 0.0

  val airWaterTitle =
    makeLabel(
      "AIR TO WATER - ENGINEERING ESTIMATE + 3D PLANT",
      23,
      true
    )

  val airWaterStatus =
    makeLabel(
      "READY - ENGINEERING MODEL",
      13,
      true
    )

  airWaterStatus.setForeground(SUCCESS)

  val airWaterHeader =
    new JPanel(
      new BorderLayout(8, 8)
    )

  airWaterHeader.setBackground(PANEL_BG)
  airWaterHeader.add(
    airWaterTitle,
    BorderLayout.WEST
  )
  airWaterHeader.add(
    airWaterStatus,
    BorderLayout.EAST
  )
  airWaterPanel.add(
    airWaterHeader,
    BorderLayout.NORTH
  )

  // --------------------------------------------------------
  // PHYSICS HELPERS
  // --------------------------------------------------------

  def airWaterClamp(
      value: Double,
      low: Double,
      high: Double
  ): Double = {
    Math.max(
      low,
      Math.min(high, value)
    )
  }

  def airWaterSaturationVaporPressurePa(
      tempC: Double
  ): Double = {
    val t =
      airWaterClamp(tempC, -45.0, 70.0)

    val esKPa =
      0.61094 *
        Math.exp(
          (17.625 * t) / (t + 243.04)
        )

    esKPa * 1000.0
  }

  def airWaterHumidityRatio(
      tempC: Double,
      rhPercent: Double,
      pressurePa: Double
  ): Double = {
    val rh =
      airWaterClamp(
        rhPercent,
        0.1,
        100.0
      )

    val pws =
      airWaterSaturationVaporPressurePa(
        tempC
      )

    val pv =
      airWaterClamp(
        pws * rh / 100.0,
        0.0,
        pressurePa * 0.98
      )

    0.62198 * pv /
      Math.max(
        500.0,
        pressurePa - pv
      )
  }

  def airWaterDewPointC(
      tempC: Double,
      rhPercent: Double
  ): Double = {
    val rh =
      airWaterClamp(
        rhPercent,
        1.0,
        100.0
      )

    val a = 17.625
    val b = 243.04
    val gamma =
      Math.log(rh / 100.0) +
        (a * tempC) /
          (b + tempC)

    (b * gamma) /
      Math.max(
        0.0001,
        a - gamma
      )
  }

  def airWaterMoistAirSpecificVolume(
      tempC: Double,
      humidityRatio: Double,
      pressurePa: Double
  ): Double = {
    val rd = 287.055
    val tk =
      Math.max(
        200.0,
        tempC + 273.15
      )

    rd * tk *
      (1.0 + 1.6078 * humidityRatio) /
      Math.max(
        500.0,
        pressurePa
      )
  }

  def airWaterEnthalpyKJPerKgDryAir(
      tempC: Double,
      humidityRatio: Double
  ): Double = {
    1.006 * tempC +
      humidityRatio *
        (2501.0 + 1.86 * tempC)
  }

  def airWaterCoilState(): (Double, Double, Double, Double, Double) = {
    val pressure = airWaterPressureKPa.toDouble * 1000.0
    val tin = airWaterTemperature.toDouble
    val rh = airWaterHumidity.toDouble
    val coil = airWaterCoilTemperature.toDouble
    val eff = airWaterHXEfficiency.toDouble / 100.0

    val win =
      airWaterHumidityRatio(
        tin,
        rh,
        pressure
      )

    val dew =
      airWaterDewPointC(
        tin,
        rh
      )

    val shouldCondense =
      coil < dew

    val wsCoil =
      airWaterHumidityRatio(
        coil,
        100.0,
        pressure
      )

    val wout =
      if (shouldCondense)
        wsCoil +
          (1.0 - eff) *
            Math.max(
              0.0,
              win - wsCoil
            )
      else
        win

    val tout =
      if (shouldCondense)
        coil +
          (1.0 - eff) *
            Math.max(
              0.0,
              tin - coil
            )
      else
        tin

    val specificVolume =
      airWaterMoistAirSpecificVolume(
        tin,
        win,
        pressure
      )

    val dryAirKgPerM3 =
      1.0 /
        Math.max(
          0.05,
          specificVolume
        )

    val dryAirKgPerH =
      airWaterAirflow.toDouble *
        dryAirKgPerM3

    val waterKgPerH =
      dryAirKgPerH *
        Math.max(
          0.0,
          win - wout
        )

    val hin =
      airWaterEnthalpyKJPerKgDryAir(
        tin,
        win
      )

    val hout =
      airWaterEnthalpyKJPerKgDryAir(
        tout,
        wout
      )

    val coolingKW =
      Math.max(
        0.0,
        dryAirKgPerH *
          Math.max(0.0, hin - hout) /
          3600.0
      )

    val airflowM3PerS =
      airWaterAirflow.toDouble /
        3600.0

    val fanKW =
      airflowM3PerS *
        airWaterFanPressurePa.toDouble /
        Math.max(
          0.20,
          airWaterFanEfficiency.toDouble /
            100.0
        ) /
        1000.0

    val compressorKW =
      coolingKW /
        Math.max(
          1.0,
          airWaterCOP
        )

    val totalKW =
      compressorKW + fanKW

    val litresPerH =
      waterKgPerH *
        airWaterCollectionEfficiency.toDouble /
        100.0

    (
      dew,
      win,
      wout,
      litresPerH,
      totalKW
    )
  }

  def airWaterWaterLitresPerH(): Double = {
    airWaterCoilState()._4
  }

  def airWaterTheoreticalMoistureAvailableKgPerH(): Double = {
    val pressure =
      airWaterPressureKPa.toDouble * 1000.0
    val w =
      airWaterHumidityRatio(
        airWaterTemperature.toDouble,
        airWaterHumidity.toDouble,
        pressure
      )
    val v =
      airWaterMoistAirSpecificVolume(
        airWaterTemperature.toDouble,
        w,
        pressure
      )
    val dryAirKgPerM3 =
      1.0 /
        Math.max(0.05, v)
    airWaterAirflow.toDouble *
      dryAirKgPerM3 *
      w
  }

  def airWaterPowerKW(): Double = {
    airWaterCoilState()._5
  }

  def airWaterCO2KgPerH(): Double = {
    val pressure = 101325.0
    val tk =
      airWaterTemperature.toDouble +
        273.15
    val r = 8.314462618
    val molarMassKg =
      44.01 / 1000.0
    val moleFraction =
      airWaterCO2ppm.toDouble /
        1000000.0

    airWaterAirflow.toDouble *
      moleFraction *
      pressure /
      (r * Math.max(200.0, tk)) *
      molarMassKg
  }

  def airWaterCO2CaptureRateKgPerH(): Double = {
    if (airWaterCO2Capture)
      airWaterCO2KgPerH() *
        airWaterCO2Efficiency.toDouble /
        100.0
    else
      0.0
  }

  // --------------------------------------------------------
  // 3D PROCEDURAL PLANT VIEW
  // --------------------------------------------------------

  val airWaterCanvas =
    new JPanel {

      setBackground(
        new Color(8, 15, 23)
      )

      def boxFront(
          x: Int,
          y: Int,
          w: Int,
          h: Int
      ): Polygon = {
        val p = new Polygon()
        p.addPoint(x, y)
        p.addPoint(x + w, y)
        p.addPoint(x + w, y + h)
        p.addPoint(x, y + h)
        p
      }

      def boxTop(
          x: Int,
          y: Int,
          w: Int,
          depth: Int
      ): Polygon = {
        val p = new Polygon()
        p.addPoint(x, y)
        p.addPoint(x + depth, y - depth)
        p.addPoint(x + w + depth, y - depth)
        p.addPoint(x + w, y)
        p
      }

      def boxSide(
          x: Int,
          y: Int,
          w: Int,
          h: Int,
          depth: Int
      ): Polygon = {
        val p = new Polygon()
        p.addPoint(x + w, y)
        p.addPoint(x + w + depth, y - depth)
        p.addPoint(x + w + depth, y + h - depth)
        p.addPoint(x + w, y + h)
        p
      }

      def drawPipe(
          g: Graphics2D,
          x1: Int,
          y1: Int,
          x2: Int,
          y2: Int,
          c: Color,
          width: Float
      ): Unit = {
        val oldStroke = g.getStroke
        g.setColor(c)
        g.setStroke(
          new BasicStroke(
            width,
            BasicStroke.CAP_ROUND,
            BasicStroke.JOIN_ROUND
          )
        )
        g.drawLine(
          x1,
          y1,
          x2,
          y2
        )
        g.setStroke(oldStroke)
      }

      override def paintComponent(
          graphics: Graphics
      ): Unit = {
        super.paintComponent(graphics)

        val g =
          graphics.asInstanceOf[Graphics2D]

        g.setRenderingHint(
          RenderingHints.KEY_ANTIALIASING,
          RenderingHints.VALUE_ANTIALIAS_ON
        )

        val cw = getWidth
        val ch = getHeight

        // Sky gradient
        var sy = 0
        while (sy < ch) {
          val t =
            sy.toDouble /
              Math.max(1, ch).toDouble
          val rr =
            (7.0 + 26.0 * t).toInt
          val gg =
            (18.0 + 50.0 * t).toInt
          val bb =
            (38.0 + 66.0 * t).toInt
          g.setColor(
            new Color(
              rr,
              gg,
              bb
            )
          )
          g.fillRect(0, sy, cw, 1)
          sy += 1
        }

        // Sun glow
        val pulse =
          8 +
            Math.abs(
              (airWaterFrame % 60) - 30
            )

        g.setColor(
          new Color(
            255,
            220,
            110,
            25
          )
        )
        g.fillOval(
          34 - pulse,
          24 - pulse,
          86 + pulse * 2,
          86 + pulse * 2
        )

        g.setColor(
          new Color(
            255,
            219,
            112,
            190
          )
        )
        g.fillOval(
          47,
          37,
          60,
          60
        )

        // Atmospheric particles
        var ap = 0
        while (ap < 34) {
          val phase =
            (airWaterFrame * 4 + ap * 37) %
              Math.max(100, cw - 50)
          val x =
            12 +
              (phase + ap * 53) %
                Math.max(80, cw - 22)
          val y =
            88 +
              ((ap * 49 + airWaterFrame / 3) %
                Math.max(100, ch - 190))
          val s =
            3 + ap % 5
          g.setColor(
            new Color(
              150,
              225,
              255,
              45 + ap * 4
            )
          )
          g.fillOval(
            x,
            y,
            s,
            s
          )
          ap += 1
        }

        // 3D floor
        val floorTop =
          Math.max(
            300,
            ch - 225
          )
        val floor = new Polygon()
        floor.addPoint(0, floorTop)
        floor.addPoint(cw, floorTop)
        floor.addPoint(cw, ch)
        floor.addPoint(0, ch)
        g.setColor(
          new Color(16, 34, 40)
        )
        g.fillPolygon(floor)

        g.setColor(
          new Color(84, 126, 135, 70)
        )
        var fx = 0
        while (fx <= cw) {
          val q = new Polygon()
          q.addPoint(
            cw / 2,
            floorTop
          )
          q.addPoint(
            fx,
            ch
          )
          q.addPoint(
            fx + 4,
            ch
          )
          q.addPoint(
            cw / 2 + 4,
            floorTop
          )
          g.fillPolygon(q)
          fx += 80
        }

        var fy = floorTop + 25
        while (fy < ch) {
          g.drawLine(
            0,
            fy,
            cw,
            fy
          )
          fy += 32
        }

        // ----------------------------------------------------
        // 1. AIR INTAKE
        // ----------------------------------------------------
        val ax = 22
        val ay = 205
        val aw = 145
        val ah = 155
        val ad = 24

        g.setColor(
          new Color(55, 75, 95)
        )
        g.fillPolygon(
          boxFront(
            ax,
            ay,
            aw,
            ah
          )
        )
        g.setColor(
          new Color(78, 101, 126)
        )
        g.fillPolygon(
          boxTop(
            ax,
            ay,
            aw,
            ad
          )
        )
        g.setColor(
          new Color(37, 53, 67)
        )
        g.fillPolygon(
          boxSide(
            ax,
            ay,
            aw,
            ah,
            ad
          )
        )

        g.setColor(
          new Color(12, 18, 23)
        )
        var grillY = ay + 18
        while (grillY < ay + ah - 10) {
          g.fillRoundRect(
            ax + 20,
            grillY,
            aw - 40,
            5,
            4,
            4
          )
          grillY += 16
        }

        g.setColor(TEXT)
        g.setFont(
          new Font(
            "SansSerif",
            Font.BOLD,
            11
          )
        )
        g.drawString(
          "AIR INTAKE",
          ax + 32,
          ay + ah + 20
        )

        // Air arrows
        g.setColor(
          new Color(150, 225, 255, 180)
        )
        var ai = 0
        while (ai < 5) {
          val xx =
            ax - 48 +
              ((airWaterFrame * 3 + ai * 22) % 55)
          val yy =
            ay + 30 + ai * 20
          g.drawLine(
            xx,
            yy,
            xx + 20,
            yy
          )
          g.fillPolygon(
            new Polygon(
              Array(
                xx + 20,
                xx + 14,
                xx + 14
              ),
              Array(
                yy,
                yy - 4,
                yy + 4
              ),
              3
            )
          )
          ai += 1
        }

        // ----------------------------------------------------
        // 2. PRE-FILTER
        // ----------------------------------------------------
        val px = 198
        val py = 213
        val pw = 140
        val ph = 118
        val pd = 19

        g.setColor(
          new Color(68, 100, 76)
        )
        g.fillPolygon(
          boxFront(
            px,
            py,
            pw,
            ph
          )
        )
        g.setColor(
          new Color(90, 130, 96)
        )
        g.fillPolygon(
          boxTop(
            px,
            py,
            pw,
            pd
          )
        )
        g.setColor(
          new Color(46, 67, 51)
        )
        g.fillPolygon(
          boxSide(
            px,
            py,
            pw,
            ph,
            pd
          )
        )

        g.setColor(TEXT)
        g.setFont(
          new Font(
            "SansSerif",
            Font.BOLD,
            11
          )
        )
        g.drawString(
          "PRE-FILTER",
          px + 33,
          py + 45
        )
        g.setFont(
          new Font(
            "SansSerif",
            Font.PLAIN,
            9
          )
        )
        g.drawString(
          "DUST / PARTICLES",
          px + 14,
          py + 69
        )
        g.drawString(
          "NOT GAS SEPARATION",
          px + 10,
          py + 90
        )

        // ----------------------------------------------------
        // 3. HEAT EXCHANGER / CONDENSER
        // ----------------------------------------------------
        val cx = 372
        val cy = 150
        val cw0 = 225
        val ch0 = 265
        val cd = 28

        g.setColor(
          new Color(50, 69, 94)
        )
        g.fillPolygon(
          boxFront(
            cx,
            cy,
            cw0,
            ch0
          )
        )
        g.setColor(
          new Color(74, 96, 124)
        )
        g.fillPolygon(
          boxTop(
            cx,
            cy,
            cw0,
            cd
          )
        )
        g.setColor(
          new Color(35, 47, 66)
        )
        g.fillPolygon(
          boxSide(
            cx,
            cy,
            cw0,
            ch0,
            cd
          )
        )

        g.setColor(
          new Color(105, 208, 255, 170)
        )
        var coil = cy + 42
        while (coil < cy + ch0 - 25) {
          g.drawRoundRect(
            cx + 24,
            coil,
            cw0 - 56,
            18,
            8,
            8
          )
          coil += 28
        }

        val compPulse =
          10 +
            Math.abs(
              (airWaterFrame % 30) - 15
            )
        g.setColor(
          new Color(80, 185, 255, 24)
        )
        g.fillOval(
          cx + 62 - compPulse,
          cy + 92 - compPulse,
          92 + compPulse * 2,
          92 + compPulse * 2
        )

        g.setColor(TEXT)
        g.setFont(
          new Font(
            "SansSerif",
            Font.BOLD,
            14
          )
        )
        g.drawString(
          "CONDENSER",
          cx + 78,
          cy + 27
        )
        g.setFont(
          new Font(
            "SansSerif",
            Font.PLAIN,
            10
          )
        )
        g.drawString(
          "COIL: " +
            String.format(
              "%.1f",
              airWaterCoilTemperature.toDouble
            ) +
            " C",
          cx + 72,
          cy + ch0 - 13
        )

        // Condensation droplets
        val dropletOn =
          airWaterWaterLitresPerH() > 0.001
        g.setColor(
          new Color(105, 220, 255, 205)
        )
        var dp = 0
        while (dp < 24) {
          val phase =
            (airWaterFrame * 5 + dp * 23) % 190
          val dx =
            cx + 34 +
              (dp * 7) % 155
          val dy =
            cy + 108 + phase
          if (dropletOn && dy < cy + ch0 - 8) {
            g.fillOval(
              dx,
              dy,
              6,
              10
            )
          }
          dp += 1
        }

        // ----------------------------------------------------
        // 4. COLLECTION TANK
        // ----------------------------------------------------
        val tx = 670
        val ty = 348
        val tw = 205
        val th = 188
        val td = 24

        g.setColor(
          new Color(57, 84, 94)
        )
        g.fillPolygon(
          boxFront(
            tx,
            ty,
            tw,
            th
          )
        )
        g.setColor(
          new Color(80, 113, 125)
        )
        g.fillPolygon(
          boxTop(
            tx,
            ty,
            tw,
            td
          )
        )
        g.setColor(
          new Color(39, 57, 64)
        )
        g.fillPolygon(
          boxSide(
            tx,
            ty,
            tw,
            th,
            td
          )
        )

        val fillRatio =
          airWaterClamp(
            airWaterTank / 100.0,
            0.0,
            1.0
          )
        val fillH =
          (th.toDouble * fillRatio).toInt

        g.setColor(
          new Color(80, 190, 235, 170)
        )
        g.fillRect(
          tx + 11,
          ty + th - 11 - fillH,
          tw - 22,
          fillH
        )

        g.setColor(
          new Color(190, 235, 255, 170)
        )
        var bubble = 0
        while (bubble < 12) {
          val bx =
            tx + 20 +
              ((bubble * 31 + airWaterFrame * 2) %
                Math.max(25, tw - 40))
          val by =
            ty + th - 20 -
              ((bubble * 17 + airWaterFrame * 3) %
                Math.max(30, fillH + 10))
          if (by > ty + th - fillH - 5) {
            g.fillOval(
              bx,
              by,
              5,
              5
            )
          }
          bubble += 1
        }

        g.setColor(TEXT)
        g.setFont(
          new Font(
            "SansSerif",
            Font.BOLD,
            13
          )
        )
        g.drawString(
          "RAW CONDENSATE TANK",
          tx + 33,
          ty + 28
        )
        g.setFont(
          new Font(
            "SansSerif",
            Font.PLAIN,
            10
          )
        )
        g.drawString(
          "Estimated level: " +
            String.format(
              "%.1f L",
              airWaterTank
            ),
          tx + 38,
          ty + th + 22
        )

        // ----------------------------------------------------
        // 5. TREATMENT TRAIN
        // ----------------------------------------------------
        val ux = 915
        val uy = 215
        val uw = 235
        val uh = 220

        g.setColor(
          new Color(57, 81, 66)
        )
        g.fillRoundRect(
          ux,
          uy,
          uw,
          uh,
          20,
          20
        )
        g.setColor(
          new Color(92, 139, 101)
        )
        g.fillRoundRect(
          ux + 12,
          uy + 12,
          uw - 24,
          42,
          12,
          12
        )

        g.setColor(TEXT)
        g.setFont(
          new Font(
            "SansSerif",
            Font.BOLD,
            13
          )
        )
        g.drawString(
          "POST-TREATMENT TRAIN",
          ux + 35,
          uy + 38
        )

        g.setFont(
          new Font(
            "SansSerif",
            Font.PLAIN,
            11
          )
        )
        g.drawString(
          "1  Particle / carbon filtration",
          ux + 23,
          uy + 82
        )
        g.drawString(
          "2  Disinfection stage",
          ux + 23,
          uy + 110
        )
        g.drawString(
          "3  Mineral / chemistry check",
          ux + 23,
          uy + 138
        )
        g.drawString(
          "4  Water-quality verification",
          ux + 23,
          uy + 166
        )
        g.drawString(
          "5  Sealed storage / output",
          ux + 23,
          uy + 194
        )

        // ----------------------------------------------------
        // 6. CO2 SIDE PATH
        // ----------------------------------------------------
        val ccx = 1090
        val ccy = 475

        g.setColor(
          if (airWaterCO2Capture)
            new Color(107, 79, 60)
          else
            new Color(55, 63, 70)
        )
        g.fillRoundRect(
          ccx,
          ccy,
          135,
          88,
          12,
          12
        )

        g.setColor(
          if (airWaterCO2Capture)
            new Color(239, 165, 78)
          else
            new Color(136, 143, 152)
        )
        g.fillOval(
          ccx + 49,
          ccy + 14,
          36,
          36
        )

        g.setColor(TEXT)
        g.setFont(
          new Font(
            "SansSerif",
            Font.BOLD,
            11
          )
        )
        g.drawString(
          "CO2 SIDE PATH",
          ccx + 20,
          ccy + 65
        )

        if (airWaterCO2Capture) {
          var cp = 0
          while (cp < 10) {
            val qq =
              (airWaterFrame * 3 + cp * 21) % 90
            g.setColor(
              new Color(240, 185, 110, 160)
            )
            g.fillOval(
              ccx - qq,
              ccy + 18 + cp * 5,
              5,
              5
            )
            cp += 1
          }
        }

        // Pipes and animated flow dots
        drawPipe(
          g,
          ax + aw + 8,
          ay + 78,
          px,
          py + 58,
          new Color(80, 170, 230),
          7f
        )
        drawPipe(
          g,
          px + pw + 18,
          py + 61,
          cx,
          cy + 121,
          new Color(90, 180, 235),
          7f
        )
        drawPipe(
          g,
          cx + cw0 / 2,
          cy + ch0 + 3,
          tx + 28,
          ty,
          new Color(95, 205, 240),
          8f
        )
        drawPipe(
          g,
          tx + tw,
          ty + 88,
          ux,
          uy + 89,
          new Color(95, 210, 225),
          8f
        )

        var fd = 0
        while (fd < 16) {
          val progress =
            (airWaterFrame * 4 + fd * 31) % 100
          val xx =
            ax + aw + 8 +
              (px - (ax + aw + 8)) * progress / 100
          val yy =
            ay + 78 +
              (py + 58 - (ay + 78)) * progress / 100
          g.setColor(
            new Color(170, 230, 255, 170)
          )
          g.fillOval(
            xx,
            yy,
            4,
            4
          )
          fd += 1
        }

        // ----------------------------------------------------
        // LIVE DATA HUD
        // ----------------------------------------------------
        val values =
          airWaterCoilState()

        val dew = values._1
        val litresH = values._4
        val totalKW = values._5
        val waterPerKWh =
          if (totalKW > 0.0001)
            litresH / totalKW
          else
            0.0

        g.setColor(
          new Color(8, 14, 20, 230)
        )
        g.fillRoundRect(
          18,
          18,
          560,
          110,
          16,
          16
        )

        g.setColor(new Color(255, 215, 0))
        g.setFont(
          new Font(
            "SansSerif",
            Font.BOLD,
            14
          )
        )
        g.drawString(
          "REAL-TIME ATMOSPHERIC WATER CALCULATION",
          34,
          42
        )

        g.setColor(TEXT)
        g.setFont(
          new Font(
            "SansSerif",
            Font.PLAIN,
            12
          )
        )
        g.drawString(
          "Ambient: " +
            airWaterTemperature +
            " C / " +
            airWaterHumidity +
            "% RH",
          34,
          68
        )
        g.drawString(
          "Dew point: " +
            String.format(
              "%.2f",
              dew
            ) +
            " C",
          220,
          68
        )
        g.drawString(
          "Coil: " +
            String.format(
              "%.1f",
              airWaterCoilTemperature.toDouble
            ) +
            " C",
          380,
          68
        )
        g.drawString(
          "Estimated water: " +
            String.format(
              "%.2f",
              litresH
            ) +
            " L/h",
          34,
          94
        )
        g.drawString(
          "Electrical load: " +
            String.format(
              "%.2f",
              totalKW
            ) +
            " kW",
          220,
          94
        )
        g.drawString(
          "Water / kWh: " +
            String.format(
              "%.2f",
              waterPerKWh
            ),
          390,
          94
        )

        // 3D labels
        g.setColor(MUTED)
        g.setFont(
          new Font(
            "SansSerif",
            Font.PLAIN,
            10
          )
        )
        g.drawString(
          "AIR + H2O VAPOR",
          ax + 20,
          ay - 12
        )
        g.drawString(
          "CLEANER AIR",
          px + 24,
          py - 12
        )
        g.drawString(
          "DEW-POINT CONTROL",
          cx + 45,
          cy - 14
        )
        g.drawString(
          "CONDENSED LIQUID",
          tx + 36,
          ty - 13
        )
        g.drawString(
          "TREATED OUTPUT",
          ux + 59,
          uy - 13
        )

        // ====================================================
        // DETAILED PROCESS-CIRCUIT LABELS / CALLOUTS
        // ====================================================
        g.setColor(
          new Color(235, 242, 248, 215)
        )
        g.setFont(
          new Font(
            "SansSerif",
            Font.BOLD,
            12
          )
        )
        g.drawString(
          "LABELED AIR-TO-WATER PROCESS CIRCUIT",
          1285,
          92
        )

        def callout(
            g2: Graphics2D,
            x1: Int,
            y1: Int,
            x2: Int,
            y2: Int,
            titleText: String,
            bodyText: String
        ): Unit = {
          g2.setColor(
            new Color(160, 215, 240, 155)
          )
          g2.setStroke(
            new BasicStroke(
              1.2f,
              BasicStroke.CAP_ROUND,
              BasicStroke.JOIN_ROUND
            )
          )
          g2.drawLine(
            x1,
            y1,
            x2,
            y2
          )
          g2.fillOval(
            x1 - 3,
            y1 - 3,
            6,
            6
          )
          g2.setColor(
            new Color(8, 14, 20, 232)
          )
          g2.fillRoundRect(
            x2,
            y2 - 24,
            236,
            58,
            12,
            12
          )
          g2.setColor(
            new Color(125, 185, 220)
          )
          g2.drawRoundRect(
            x2,
            y2 - 24,
            236,
            58,
            12,
            12
          )
          g2.setFont(
            new Font(
              "SansSerif",
              Font.BOLD,
              11
            )
          )
          g2.setColor(TEXT)
          g2.drawString(
            titleText,
            x2 + 12,
            y2 - 4
          )
          g2.setFont(
            new Font(
              "SansSerif",
              Font.PLAIN,
              9
            )
          )
          g2.setColor(MUTED)
          g2.drawString(
            bodyText,
            x2 + 12,
            y2 + 16
          )
        }

        callout(
          g,
          ax + 70,
          ay + 42,
          1285,
          142,
          "S1 / AIR INTAKE",
          "T1 + RH1 + P1 : incoming air condition"
        )
        callout(
          g,
          px + 70,
          py + 44,
          1285,
          214,
          "F1 / PRE-FILTER",
          "Particle protection before cooling stage"
        )
        callout(
          g,
          cx + 112,
          cy + 114,
          1285,
          286,
          "HX-01 / CONDENSER",
          "Cooling surface + dew-point crossing"
        )
        callout(
          g,
          cx + 112,
          cy + ch0 - 12,
          1285,
          358,
          "DRAIN-01 / WATER SEPARATOR",
          "Condensate collection from cooled air"
        )
        callout(
          g,
          tx + tw / 2,
          ty + th / 2,
          1285,
          430,
          "TK-01 / RAW WATER TANK",
          "Level = estimated accumulated condensate"
        )
        callout(
          g,
          ux + uw / 2,
          uy + uh / 2,
          1285,
          510,
          "WQ-01 / TREATMENT TRAIN",
          "Filtration + disinfection + quality gate"
        )

        // Engineering mini-panels on the far right.
        g.setColor(
          new Color(7, 12, 18, 238)
        )
        g.fillRoundRect(1530, 130, 270, 470, 18, 18)
        g.setColor(
          new Color(120, 170, 205)
        )
        g.drawRoundRect(1530, 130, 270, 470, 18, 18)

        g.setColor(TEXT)
        g.setFont(
          new Font(
            "SansSerif",
            Font.BOLD,
            13
          )
        )
        g.drawString(
          "LIVE ENGINEERING CHECKS",
          1552,
          158
        )

        val dryFlow = airWaterAirflow
        val inletW = values._2
        val outletW = values._3
        val grossWater = Math.max(0.0, (inletW - outletW))
        val dewGap =
          airWaterTemperature.toDouble - dew
        val coilMargin =
          dew - airWaterCoilTemperature.toDouble
        val energyPerL =
          if (litresH > 0.0001)
            totalKW / litresH
          else
            0.0

        val checks = Array(
          "AIRFLOW        " + dryFlow + " m3/h",
          "DEW-POINT GAP  " + String.format("%.1f C", dewGap),
          "COIL MARGIN    " + String.format("%.1f C", coilMargin),
          "H2O RATIO DROP  " + String.format("%.6f", grossWater),
          "CONDENSATE      " + String.format("%.3f L/h", litresH),
          "POWER           " + String.format("%.3f kW", totalKW),
          "ENERGY / L      " + String.format("%.3f kWh/L", energyPerL),
          "CO2 MODE        " + (if (airWaterCO2Capture) "CAPTURE" else "SEPARATE"),
          "MODEL STATE     " + (if (airWaterRunning) "RUNNING" else "HOLD")
        )

        var ci = 0
        while (ci < checks.length) {
          val yy = 190 + ci * 34
          val ok =
            if (ci == 2) coilMargin > 0.0
            else if (ci == 4) litresH > 0.01
            else true
          g.setColor(
            if (ok) SUCCESS else WARNING
          )
          g.fillOval(1553, yy - 9, 9, 9)
          g.setColor(TEXT)
          g.setFont(
            new Font(
              "Monospaced",
              Font.PLAIN,
              10
            )
          )
          g.drawString(
            checks(ci),
            1570,
            yy
          )
          ci += 1
        }

        g.setColor(
          new Color(120, 155, 175, 150)
        )
        g.drawLine(1550, 500, 1780, 500)
        g.setColor(MUTED)
        g.setFont(
          new Font(
            "SansSerif",
            Font.PLAIN,
            9
          )
        )
        g.drawString(
          "The labels describe the software model only.",
          1552,
          526
        )
        g.drawString(
          "They are not a construction drawing.",
          1552,
          544
        )

        // Numbered process chain.
        val stepXs = Array(92, 268, 485, 770, 1035, 1200)
        val stepYs = Array(392, 392, 392, 547, 325, 442)
        var si = 0
        while (si < stepXs.length) {
          g.setColor(
            new Color(255, 215, 90, 220)
          )
          g.fillOval(
            stepXs(si) - 10,
            stepYs(si) - 10,
            20,
            20
          )
          g.setColor(
            new Color(25, 28, 34)
          )
          g.setFont(
            new Font(
              "SansSerif",
              Font.BOLD,
              10
            )
          )
          g.drawString(
            (si + 1).toString,
            stepXs(si) - 3,
            stepYs(si) + 4
          )
          si += 1
        }
      }
    }

  airWaterCanvas.setPreferredSize(
    new Dimension(
      1850,
      700
    )
  )

  // Horizontal mouse-wheel navigation for the wide engineering circuit.
  // The canvas can be explored left/right without shrinking the 3D view.
  val airWaterCanvasScroll =
    new JScrollPane(
      airWaterCanvas
    )

  airWaterCanvasScroll.setHorizontalScrollBarPolicy(
    ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED
  )
  airWaterCanvasScroll.setVerticalScrollBarPolicy(
    ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED
  )
  airWaterCanvasScroll.getHorizontalScrollBar.setUnitIncrement(45)
  airWaterCanvasScroll.getHorizontalScrollBar.setBlockIncrement(180)
  airWaterCanvasScroll.getVerticalScrollBar.setUnitIncrement(35)
  airWaterCanvasScroll.getVerticalScrollBar.setBlockIncrement(160)

  airWaterCanvas.addMouseWheelListener(
    new MouseWheelListener {
      override def mouseWheelMoved(
          e: MouseWheelEvent
      ): Unit = {
        val bar =
          airWaterCanvasScroll.getHorizontalScrollBar
        val delta =
          e.getWheelRotation *
            bar.getUnitIncrement
        val maxValue =
          Math.max(
            0,
            bar.getMaximum - bar.getVisibleAmount
          )
        val next =
          Math.max(
            0,
            Math.min(
              maxValue,
              bar.getValue + delta
            )
          )
        bar.setValue(next)
        e.consume()
      }
    }
  )

  // --------------------------------------------------------
  // CONTROL PANEL
  // --------------------------------------------------------

  val airWaterControlPanel =
    new JPanel(
      new GridLayout(
        15,
        2,
        5,
        5
      )
    )

  airWaterControlPanel.setBackground(PANEL_BG)

  val airWaterHumiditySlider =
    new JSlider(
      10,
      95,
      airWaterHumidity
    )

  val airWaterTempSlider =
    new JSlider(
      5,
      45,
      airWaterTemperature
    )

  val airWaterCoilSlider =
    new JSlider(
      0,
      25,
      airWaterCoilTemperature
    )

  val airWaterAirflowSlider =
    new JSlider(
      100,
      3000,
      airWaterAirflow
    )

  val airWaterPressureSlider =
    new JSlider(
      80,
      105,
      airWaterPressureKPa
    )

  val airWaterHXSlider =
    new JSlider(
      45,
      95,
      airWaterHXEfficiency
    )

  val airWaterCollectionSlider =
    new JSlider(
      60,
      98,
      airWaterCollectionEfficiency
    )

  val airWaterCOPSlider =
    new JSlider(
      15,
      50,
      30
    )

  val airWaterFanPressureSlider =
    new JSlider(
      50,
      500,
      airWaterFanPressurePa
    )

  val airWaterFanEfficiencySlider =
    new JSlider(
      40,
      90,
      airWaterFanEfficiency
    )

  val airWaterCO2Slider =
    new JSlider(
      300,
      2000,
      airWaterCO2ppm
    )

  val airWaterCO2EffSlider =
    new JSlider(
      10,
      95,
      airWaterCO2Efficiency
    )

  val airWaterStartButton =
    makeButton(
      "START / PAUSE REAL MODEL"
    )

  val airWaterResetButton =
    makeButton(
      "RESET"
    )

  val airWaterCO2Button =
    makeButton(
      "CO2 CAPTURE: OFF"
    )

  val airWaterExplainButton =
    makeButton(
      "REAL PHYSICS REPORT"
    )

  val airWaterDiagnosticsButton =
    makeButton(
      "SYSTEM DIAGNOSTICS"
    )

  val airWaterKundaliButton =
    makeButton(
      "OPEN KUNDALI PURAN"
    )

  airWaterControlPanel.add(
    makeLabel(
      "Humidity (%)",
      11,
      false
    )
  )
  airWaterControlPanel.add(
    airWaterHumiditySlider
  )
  airWaterControlPanel.add(
    makeLabel(
      "Ambient temp (C)",
      11,
      false
    )
  )
  airWaterControlPanel.add(
    airWaterTempSlider
  )
  airWaterControlPanel.add(
    makeLabel(
      "Coil temp (C)",
      11,
      false
    )
  )
  airWaterControlPanel.add(
    airWaterCoilSlider
  )
  airWaterControlPanel.add(
    makeLabel(
      "Airflow (m3/h)",
      11,
      false
    )
  )
  airWaterControlPanel.add(
    airWaterAirflowSlider
  )
  airWaterControlPanel.add(
    makeLabel(
      "Pressure (kPa)",
      11,
      false
    )
  )
  airWaterControlPanel.add(
    airWaterPressureSlider
  )
  airWaterControlPanel.add(
    makeLabel(
      "HX effectiveness (%)",
      11,
      false
    )
  )
  airWaterControlPanel.add(
    airWaterHXSlider
  )
  airWaterControlPanel.add(
    makeLabel(
      "Water collection (%)",
      11,
      false
    )
  )
  airWaterControlPanel.add(
    airWaterCollectionSlider
  )
  airWaterControlPanel.add(
    makeLabel(
      "COP x10",
      11,
      false
    )
  )
  airWaterControlPanel.add(
    airWaterCOPSlider
  )
  airWaterControlPanel.add(
    makeLabel(
      "Fan pressure drop (Pa)",
      11,
      false
    )
  )
  airWaterControlPanel.add(
    airWaterFanPressureSlider
  )
  airWaterControlPanel.add(
    makeLabel(
      "Fan efficiency (%)",
      11,
      false
    )
  )
  airWaterControlPanel.add(
    airWaterFanEfficiencySlider
  )
  airWaterControlPanel.add(
    makeLabel(
      "CO2 ppm",
      11,
      false
    )
  )
  airWaterControlPanel.add(
    airWaterCO2Slider
  )
  airWaterControlPanel.add(
    makeLabel(
      "CO2 capture (%)",
      11,
      false
    )
  )
  airWaterControlPanel.add(
    airWaterCO2EffSlider
  )
  airWaterControlPanel.add(
    airWaterStartButton
  )
  airWaterControlPanel.add(
    airWaterResetButton
  )
  airWaterControlPanel.add(
    airWaterCO2Button
  )
  airWaterControlPanel.add(
    airWaterExplainButton
  )
  airWaterControlPanel.add(
    airWaterDiagnosticsButton
  )
  airWaterControlPanel.add(
    airWaterKundaliButton
  )

  val airWaterReadout =
    makeArea()

  airWaterReadout.setEditable(false)

  val airWaterTopSplit =
    new JSplitPane(
      JSplitPane.HORIZONTAL_SPLIT,
      airWaterCanvasScroll,
      airWaterControlPanel
    )

  airWaterTopSplit.setDividerLocation(875)
  airWaterTopSplit.setResizeWeight(0.76)

  val airWaterBottomSplit =
    new JSplitPane(
      JSplitPane.VERTICAL_SPLIT,
      airWaterTopSplit,
      makeScroll(
        airWaterReadout
      )
    )

  airWaterBottomSplit.setDividerLocation(575)
  airWaterBottomSplit.setResizeWeight(0.80)

  airWaterPanel.add(
    airWaterBottomSplit,
    BorderLayout.CENTER
  )

  // --------------------------------------------------------
  // REAL CALCULATION REPORT
  // --------------------------------------------------------

  def refreshAirWaterReadout(): Unit = {
    val values =
      airWaterCoilState()

    val dew = values._1
    val win = values._2
    val wout = values._3
    val litresH = values._4
    val powerKW = values._5
    val lpkwh =
      if (powerKW > 0.0001)
        litresH / powerKW
      else
        0.0
    val co2In =
      airWaterCO2KgPerH()
    val theoretical =
      airWaterTheoreticalMoistureAvailableKgPerH()
    val co2Captured =
      airWaterCO2CaptureRateKgPerH()
    val dryAirFlow =
      airWaterAirflow
    val daily =
      litresH *
        airWaterRuntimeHours

    airWaterReadout.setText(
      "AIR TO WATER - REAL PHYSICS REPORT\n" +
        "=====================================\n\n" +
        "WHAT THE MODEL CALCULATES\n" +
        "-------------------------\n" +
        "This version no longer uses an arbitrary water-production index.\n" +
        "It estimates condensate from atmospheric water vapour using\n" +
        "air temperature, relative humidity, pressure, airflow, coil\n" +
        "temperature and heat-exchanger effectiveness.\n\n" +
        "CURRENT AMBIENT CONDITIONS\n" +
        "---------------------------\n" +
        "Air temperature: " +
          airWaterTemperature +
          " C\n" +
        "Relative humidity: " +
          airWaterHumidity +
          " %\n" +
        "Dew point: " +
          String.format(
            "%.2f",
            dew
          ) +
          " C\n" +
        "Airflow: " +
          dryAirFlow +
          " m3/h\n" +
        "Air pressure: " +
          airWaterPressureKPa +
          " kPa\n" +
        "Coil temperature: " +
          String.format(
            "%.1f",
            airWaterCoilTemperature.toDouble
          ) +
          " C\n" +
        "HX effectiveness: " +
          airWaterHXEfficiency +
          " %\n" +
        "Collection efficiency: " +
          airWaterCollectionEfficiency +
          " %\n" +
        "Estimated COP: " +
          String.format(
            "%.1f",
            airWaterCOP
          ) +
          "\n\n" +
        "PSYCHROMETRIC RESULT\n" +
        "--------------------\n" +
        "Inlet humidity ratio: " +
          String.format(
            "%.6f",
            win
          ) +
          " kg water/kg dry air\n" +
        "Outlet humidity ratio: " +
          String.format(
            "%.6f",
            wout
          ) +
          " kg water/kg dry air\n" +
        "Theoretical moisture available: " +
          String.format(
            "%.3f",
            theoretical
          ) +
          " kg/h\n" +
        "Estimated liquid production: " +
          String.format(
            "%.3f",
            litresH
          ) +
          " L/h\n" +
        "Estimated 8-hour production: " +
          String.format(
            "%.2f",
            litresH * 8.0
          ) +
          " L\n" +
        "At selected runtime (" +
          String.format(
            "%.1f",
            airWaterRuntimeHours
          ) +
          " h): " +
          String.format(
            "%.2f",
            daily
          ) +
          " L\n\n" +
        "ENERGY ESTIMATE\n" +
        "----------------\n" +
        "Approx. electrical load: " +
          String.format(
            "%.3f",
            powerKW
          ) +
          " kW\n" +
        "Fan pressure drop: " +
          airWaterFanPressurePa +
          " Pa\n" +
        "Fan efficiency: " +
          airWaterFanEfficiency +
          " %\n" +
        "Water yield per electrical kWh: " +
          String.format(
            "%.3f",
            lpkwh
          ) +
          " L/kWh\n\n" +
        "CO2 SIDE PATH - SEPARATE FROM WATER\n" +
        "-----------------------------------\n" +
        "Ambient CO2: " +
          airWaterCO2ppm +
          " ppm\n" +
        "CO2 entering with selected airflow: " +
          String.format(
            "%.4f",
            co2In
          ) +
          " kg/h\n" +
        "Capture path: " +
          (if (airWaterCO2Capture) "ON" else "OFF") +
          "\n" +
        "Estimated captured CO2: " +
          String.format(
            "%.4f",
            co2Captured
          ) +
          " kg/h\n\n" +
        "IMPORTANT CHEMISTRY\n" +
        "-------------------\n" +
        "The water comes from H2O vapour that is already in the air.\n" +
        "CO2 does NOT become water by simply being mixed with air.\n" +
        "The CO2 path is therefore calculated separately.\n\n" +
        "ENGINEERING LIMITS\n" +
        "------------------\n" +
        "Real equipment performance depends on altitude, pressure,\n" +
        "air leakage, coil fouling, fan pressure drop, condenser\n" +
        "approach temperature, compressor performance, control strategy,\n" +
        "ambient weather and water-treatment performance.\n" +
        "The displayed L/h and kW values are calculated estimates from the selected\n" +
        "conditions and simplified component-performance assumptions; field output must\n" +
        "be verified with calibrated sensors and measured equipment performance.\n\n" +
        "POTABLE WATER SAFETY\n" +
        "--------------------\n" +
        "Condensed atmospheric water must not be assumed potable without\n" +
        "appropriate treatment, maintenance and water-quality testing.\n" +
        "The 3D treatment blocks are a system model, not a construction recipe."
    )

    airWaterReadout.setCaretPosition(0)
  }

  // --------------------------------------------------------
  // BUTTON EVENTS
  // --------------------------------------------------------

  airWaterStartButton.addActionListener(
    new ActionListener {
      override def actionPerformed(
          e: ActionEvent
      ): Unit = {
        airWaterRunning =
          !airWaterRunning

        if (airWaterRunning) {
          airWaterStatus.setText(
            "RUNNING - REAL MASS + ENERGY BALANCE"
          )
          airWaterStatus.setForeground(
            SUCCESS
          )
        } else {
          airWaterStatus.setText(
            "PAUSED - ENGINEERING MODEL HOLD"
          )
          airWaterStatus.setForeground(
            WARNING
          )
        }
      }
    }
  )

  airWaterResetButton.addActionListener(
    new ActionListener {
      override def actionPerformed(
          e: ActionEvent
      ): Unit = {
        airWaterRunning = false
        airWaterHumidity = 60
        airWaterTemperature = 27
        airWaterCoilTemperature = 8
        airWaterAirflow = 900
        airWaterPressureKPa = 101
        airWaterHXEfficiency = 78
        airWaterCollectionEfficiency = 88
        airWaterCOP = 3.0
        airWaterFanPressurePa = 180
        airWaterFanEfficiency = 62
        airWaterCO2ppm = 420
        airWaterCO2Capture = false
        airWaterCO2Efficiency = 55
        airWaterTank = 0.0
        airWaterProduced = 0.0
        airWaterCO2Captured = 0.0
        airWaterTotalEnergy = 0.0

        airWaterHumiditySlider.setValue(
          airWaterHumidity
        )
        airWaterTempSlider.setValue(
          airWaterTemperature
        )
        airWaterCoilSlider.setValue(
          airWaterCoilTemperature
        )
        airWaterAirflowSlider.setValue(
          airWaterAirflow
        )
        airWaterPressureSlider.setValue(
          airWaterPressureKPa
        )
        airWaterHXSlider.setValue(
          airWaterHXEfficiency
        )
        airWaterCollectionSlider.setValue(
          airWaterCollectionEfficiency
        )
        airWaterCOPSlider.setValue(30)
        airWaterFanPressureSlider.setValue(
          airWaterFanPressurePa
        )
        airWaterFanEfficiencySlider.setValue(
          airWaterFanEfficiency
        )
        airWaterCO2Slider.setValue(
          airWaterCO2ppm
        )
        airWaterCO2EffSlider.setValue(
          airWaterCO2Efficiency
        )
        airWaterCO2Button.setText(
          "CO2 CAPTURE: OFF"
        )
        airWaterStatus.setText(
          "READY - PHYSICS MODEL"
        )
        airWaterStatus.setForeground(
          SUCCESS
        )
        refreshAirWaterReadout()
        airWaterCanvas.repaint()
      }
    }
  )

  airWaterCO2Button.addActionListener(
    new ActionListener {
      override def actionPerformed(
          e: ActionEvent
      ): Unit = {
        airWaterCO2Capture =
          !airWaterCO2Capture

        airWaterCO2Button.setText(
          if (airWaterCO2Capture)
            "CO2 CAPTURE: ON"
          else
            "CO2 CAPTURE: OFF"
        )

        refreshAirWaterReadout()
        airWaterCanvas.repaint()
      }
    }
  )

  airWaterExplainButton.addActionListener(
    new ActionListener {
      override def actionPerformed(
          e: ActionEvent
      ): Unit = {
        showInfo(
          frame,
          "AIR TO WATER - REAL MODEL",
          "REAL calculation chain:\n\n" +
            "1. Atmospheric air enters the intake.\n" +
            "2. Temperature + RH determine water-vapour content.\n" +
            "3. Dew point is calculated.\n" +
            "4. Air is cooled toward the coil temperature.\n" +
            "5. A humidity-ratio mass balance estimates condensation.\n" +
            "6. Collection efficiency converts gross condensate to recovered liquid.\n" +
            "7. Enthalpy change + COP + fan pressure/efficiency estimate electricity use.\n" +
            "8. CO2 entering the system is calculated separately.\n\n" +
            "Water source: H2O vapour already in the atmosphere.\n" +
            "CO2 is not converted into water by simple mixing.\n\n" +
            "This is a software engineering model. Actual hardware needs\n" +
            "site measurement, validated components, controls, sanitation\n" +
            "and water-quality testing before potable use."
        )
      }
    }
  )

  airWaterDiagnosticsButton.addActionListener(
    new ActionListener {
      override def actionPerformed(
          e: ActionEvent
      ): Unit = {
        val s = airWaterCoilState()
        val dew = s._1
        val litres = s._4
        val power = s._5
        val coilMargin =
          dew - airWaterCoilTemperature.toDouble
        val status =
          if (litres <= 0.01)
            "LOW / NO CONDENSATION"
          else if (coilMargin <= 0.0)
            "CHECK COIL VS DEW POINT"
          else
            "CONDENSATION CONDITIONS PRESENT"

        showInfo(
          frame,
          "AIR TO WATER - SYSTEM DIAGNOSTICS",
          "LIVE DIAGNOSTIC SNAPSHOT\n\n" +
            "STATE: " + status + "\n\n" +
            "Ambient temperature : " + airWaterTemperature + " C\n" +
            "Relative humidity   : " + airWaterHumidity + " %\n" +
            "Pressure            : " + airWaterPressureKPa + " kPa\n" +
            "Dew point           : " + String.format("%.2f", dew) + " C\n" +
            "Coil temperature    : " + String.format("%.1f", airWaterCoilTemperature.toDouble) + " C\n" +
            "Dew/coil margin     : " + String.format("%.2f", coilMargin) + " C\n" +
            "Airflow             : " + airWaterAirflow + " m3/h\n" +
            "HX effectiveness    : " + airWaterHXEfficiency + " %\n" +
            "Collection           : " + airWaterCollectionEfficiency + " %\n" +
            "Estimated water      : " + String.format("%.3f", litres) + " L/h\n" +
            "Estimated power      : " + String.format("%.3f", power) + " kW\n" +
            "Water per kWh        : " + String.format("%.3f", if (power > 0.0001) litres / power else 0.0) + " L/kWh\n\n" +
            "CO2 path              : " + (if (airWaterCO2Capture) "CAPTURE ENABLED" else "SEPARATE / OFF") + "\n\n" +
            "This diagnostic is calculated from the model inputs; it is not a field calibration report."
        )
      }
    }
  )

  airWaterHumiditySlider.addChangeListener(
    new javax.swing.event.ChangeListener {
      def stateChanged(
          e: javax.swing.event.ChangeEvent
      ): Unit = {
        airWaterHumidity =
          airWaterHumiditySlider.getValue
        refreshAirWaterReadout()
        airWaterCanvas.repaint()
      }
    }
  )

  airWaterTempSlider.addChangeListener(
    new javax.swing.event.ChangeListener {
      def stateChanged(
          e: javax.swing.event.ChangeEvent
      ): Unit = {
        airWaterTemperature =
          airWaterTempSlider.getValue
        refreshAirWaterReadout()
        airWaterCanvas.repaint()
      }
    }
  )

  airWaterCoilSlider.addChangeListener(
    new javax.swing.event.ChangeListener {
      def stateChanged(
          e: javax.swing.event.ChangeEvent
      ): Unit = {
        airWaterCoilTemperature =
          airWaterCoilSlider.getValue
        refreshAirWaterReadout()
        airWaterCanvas.repaint()
      }
    }
  )

  airWaterAirflowSlider.addChangeListener(
    new javax.swing.event.ChangeListener {
      def stateChanged(
          e: javax.swing.event.ChangeEvent
      ): Unit = {
        airWaterAirflow =
          airWaterAirflowSlider.getValue
        refreshAirWaterReadout()
        airWaterCanvas.repaint()
      }
    }
  )

  airWaterHXSlider.addChangeListener(
    new javax.swing.event.ChangeListener {
      def stateChanged(
          e: javax.swing.event.ChangeEvent
      ): Unit = {
        airWaterHXEfficiency =
          airWaterHXSlider.getValue
        refreshAirWaterReadout()
        airWaterCanvas.repaint()
      }
    }
  )

  airWaterCollectionSlider.addChangeListener(
    new javax.swing.event.ChangeListener {
      def stateChanged(
          e: javax.swing.event.ChangeEvent
      ): Unit = {
        airWaterCollectionEfficiency =
          airWaterCollectionSlider.getValue
        refreshAirWaterReadout()
        airWaterCanvas.repaint()
      }
    }
  )

  airWaterPressureSlider.addChangeListener(
    new javax.swing.event.ChangeListener {
      def stateChanged(
          e: javax.swing.event.ChangeEvent
      ): Unit = {
        airWaterPressureKPa =
          airWaterPressureSlider.getValue
        refreshAirWaterReadout()
        airWaterCanvas.repaint()
      }
    }
  )

  airWaterCOPSlider.addChangeListener(
    new javax.swing.event.ChangeListener {
      def stateChanged(
          e: javax.swing.event.ChangeEvent
      ): Unit = {
        airWaterCOP =
          airWaterCOPSlider.getValue.toDouble /
            10.0
        refreshAirWaterReadout()
        airWaterCanvas.repaint()
      }
    }
  )

  airWaterFanPressureSlider.addChangeListener(
    new javax.swing.event.ChangeListener {
      def stateChanged(
          e: javax.swing.event.ChangeEvent
      ): Unit = {
        airWaterFanPressurePa =
          airWaterFanPressureSlider.getValue
        refreshAirWaterReadout()
        airWaterCanvas.repaint()
      }
    }
  )

  airWaterFanEfficiencySlider.addChangeListener(
    new javax.swing.event.ChangeListener {
      def stateChanged(
          e: javax.swing.event.ChangeEvent
      ): Unit = {
        airWaterFanEfficiency =
          airWaterFanEfficiencySlider.getValue
        refreshAirWaterReadout()
        airWaterCanvas.repaint()
      }
    }
  )

  airWaterCO2Slider.addChangeListener(
    new javax.swing.event.ChangeListener {
      def stateChanged(
          e: javax.swing.event.ChangeEvent
      ): Unit = {
        airWaterCO2ppm =
          airWaterCO2Slider.getValue
        refreshAirWaterReadout()
        airWaterCanvas.repaint()
      }
    }
  )

  airWaterCO2EffSlider.addChangeListener(
    new javax.swing.event.ChangeListener {
      def stateChanged(
          e: javax.swing.event.ChangeEvent
      ): Unit = {
        airWaterCO2Efficiency =
          airWaterCO2EffSlider.getValue
        refreshAirWaterReadout()
        airWaterCanvas.repaint()
      }
    }
  )

  // --------------------------------------------------------
  // REAL-TIME SIMULATION TIMER
  // --------------------------------------------------------

  val airWaterTimer =
    new javax.swing.Timer(
      60,
      new ActionListener {
        override def actionPerformed(
            e: ActionEvent
        ): Unit = {
          airWaterFrame =
            (airWaterFrame + 1) % 100000

          if (airWaterRunning) {
            val rate =
              airWaterWaterLitresPerH()

            val power =
              airWaterPowerKW()

            airWaterProduced +=
              rate * 0.06 /
                60.0

            airWaterTank +=
              rate * 0.06 /
                60.0

            airWaterTotalEnergy +=
              power * 0.06 /
                3600.0

            if (airWaterTank > 100.0) {
              airWaterTank = 100.0
            }

            if (airWaterCO2Capture) {
              airWaterCO2Captured +=
                airWaterCO2CaptureRateKgPerH() *
                  0.06 /
                  3600.0
            }
          }

          if (airWaterFrame % 4 == 0) {
            refreshAirWaterReadout()
          }

          airWaterCanvas.repaint()
        }
      }
    )

  airWaterTimer.setCoalesce(true)
  airWaterTimer.start()
  refreshAirWaterReadout()

  // ========================================================
  // KUNDALI PURAN - INTEGRATED VEDIC / SIDEREAL MODULE
  // Based on the supplied standalone Kundali Puran source,
  // redesigned as a rich studio tab with chart visualization,
  // report tools, animation and navigation back to Air-to-Water.
  // ========================================================

  val kundaliPanel =
    new JPanel(
      new BorderLayout(10, 10)
    )

  kundaliPanel.setBackground(
    new Color(8, 8, 28)
  )
  kundaliPanel.setBorder(
    new EmptyBorder(10, 10, 10, 10)
  )

  var kundaliJD = 0.0
  var kundaliCalculated = false
  var kundaliAnimFrame = 0
  var kundaliLastReport = ""
  var kundaliShowDegrees = true
  var kundaliGlow = true
  var kundaliPrecisionMode = "STANDALONE APPROXIMATION"

  val kundaliHeader =
    new JPanel(
      new BorderLayout(8, 8)
    )
  kundaliHeader.setBackground(
    new Color(14, 12, 35)
  )
  kundaliHeader.setBorder(
    BorderFactory.createLineBorder(
      new Color(220, 170, 70),
      1
    )
  )

  val kundaliTitle =
    makeLabel(
      "KUNDALI PURAN - VEDIC / SIDEREAL BIRTH CHART",
      23,
      true
    )
  kundaliTitle.setForeground(
    new Color(255, 215, 110)
  )

  val kundaliStatus =
    makeLabel(
      "READY - ENTER BIRTH DETAILS",
      12,
      true
    )
  kundaliStatus.setForeground(
    new Color(190, 205, 220)
  )

  kundaliHeader.add(
    kundaliTitle,
    BorderLayout.WEST
  )
  kundaliHeader.add(
    kundaliStatus,
    BorderLayout.EAST
  )
  kundaliPanel.add(
    kundaliHeader,
    BorderLayout.NORTH
  )

  // --------------------------------------------------------
  // KUNDALI INPUT CARD
  // --------------------------------------------------------

  val kundaliInputCard =
    new JPanel()
  kundaliInputCard.setLayout(null)
  kundaliInputCard.setBackground(
    new Color(24, 20, 50)
  )
  kundaliInputCard.setBorder(
    BorderFactory.createLineBorder(
      new Color(220, 170, 70),
      2
    )
  )
  kundaliInputCard.setPreferredSize(
    new Dimension(330, 590)
  )

  val kundaliInputTitle =
    new JLabel(
      "BIRTH DETAILS",
      SwingConstants.CENTER
    )
  kundaliInputTitle.setForeground(
    new Color(255, 220, 120)
  )
  kundaliInputTitle.setFont(
    new Font(
      "Serif",
      Font.BOLD,
      22
    )
  )
  kundaliInputTitle.setBounds(15, 14, 300, 34)
  kundaliInputCard.add(kundaliInputTitle)

  def kundaliFieldLabel(
      t: String,
      y: Int
  ): JLabel = {
    val l = new JLabel(t)
    l.setForeground(
      new Color(240, 230, 205)
    )
    l.setFont(
      new Font(
        "SansSerif",
        Font.BOLD,
        12
      )
    )
    l.setBounds(16, y, 115, 27)
    kundaliInputCard.add(l)
    l
  }

  def kundaliField(
      y: Int
  ): JTextField = {
    val f = new JTextField()
    f.setFont(
      new Font(
        "SansSerif",
        Font.PLAIN,
        14
      )
    )
    f.setBackground(
      new Color(250, 245, 230)
    )
    f.setForeground(
      new Color(30, 25, 40)
    )
    f.setBounds(125, y, 185, 30)
    kundaliInputCard.add(f)
    f
  }

  kundaliFieldLabel("First Name", 64)
  val kundaliFirstName = kundaliField(61)

  kundaliFieldLabel("Surname", 101)
  val kundaliSurname = kundaliField(98)

  kundaliFieldLabel("Birth Date", 138)
  val kundaliBirthDate = kundaliField(135)
  kundaliBirthDate.setToolTipText("DD/MM/YYYY")

  kundaliFieldLabel("Birth Time", 175)
  val kundaliBirthTime = kundaliField(172)
  kundaliBirthTime.setToolTipText("HH:MM or HH:MM:SS")

  kundaliFieldLabel("Birth Place", 212)
  val kundaliBirthPlace = kundaliField(209)

  kundaliFieldLabel("Latitude", 249)
  val kundaliLatitude = kundaliField(246)

  kundaliFieldLabel("Longitude", 286)
  val kundaliLongitude = kundaliField(283)

  kundaliFieldLabel("Timezone", 323)
  val kundaliTimezone = kundaliField(320)
  kundaliTimezone.setText("5.5")

  val kundaliInfo =
    new JLabel(
      "<html><center>Use the real birth-place coordinates.<br>India commonly uses UTC +5.5.<br>This module is a traditional sidereal-style calculator.</center></html>",
      SwingConstants.CENTER
    )
  kundaliInfo.setForeground(
    new Color(205, 192, 160)
  )
  kundaliInfo.setFont(
    new Font(
      "SansSerif",
      Font.PLAIN,
      10
    )
  )
  kundaliInfo.setBounds(15, 357, 300, 55)
  kundaliInputCard.add(kundaliInfo)

  val kundaliCalculateButton =
    makeButton(
      "CALCULATE KUNDALI"
    )
  kundaliCalculateButton.setBounds(16, 420, 294, 40)
  kundaliInputCard.add(kundaliCalculateButton)

  val kundaliSampleButton =
    makeButton(
      "LOAD SAMPLE"
    )
  kundaliSampleButton.setBounds(16, 467, 142, 38)
  kundaliInputCard.add(kundaliSampleButton)

  val kundaliClearButton =
    makeButton(
      "CLEAR"
    )
  kundaliClearButton.setBounds(168, 467, 142, 38)
  kundaliInputCard.add(kundaliClearButton)

  val kundaliCopyButton =
    makeButton(
      "COPY REPORT"
    )
  kundaliCopyButton.setBounds(16, 512, 142, 38)
  kundaliInputCard.add(kundaliCopyButton)

  val kundaliExportButton =
    makeButton(
      "EXPORT TXT"
    )
  kundaliExportButton.setBounds(168, 512, 142, 38)
  kundaliInputCard.add(kundaliExportButton)

  val kundaliFullPackButton =
    makeButton(
      "FULL KUNDALI PACK"
    )
  kundaliFullPackButton.setBounds(16, 547, 294, 38)
  kundaliInputCard.add(kundaliFullPackButton)

  // --------------------------------------------------------
  // KUNDALI MATH - SAME BASIC TERMS / STRUCTURE AS SOURCE
  // --------------------------------------------------------

  def kundaliNormalize(
      value: Double
  ): Double = {
    var x = value % 360.0
    if (x < 0.0) x += 360.0
    x
  }

  def kundaliJulianDay(
      year: Int,
      month: Int,
      day: Int,
      hour: Int,
      minute: Int,
      second: Int,
      timezone: Double
  ): Double = {
    var y = year
    var m = month
    if (m <= 2) {
      y -= 1
      m += 12
    }
    val a = Math.floor(y / 100.0)
    val b = 2.0 - a + Math.floor(a / 4.0)
    val decimalHour =
      hour +
        minute / 60.0 +
        second / 3600.0 -
        timezone
    Math.floor(365.25 * (y + 4716)) +
      Math.floor(30.6001 * (m + 1)) +
      day + b -
      1524.5 +
      decimalHour / 24.0
  }

  def kundaliLahiriAyanamsha(
      jd: Double
  ): Double = {
    val t =
      (jd - 2451545.0) / 36525.0
    23.85675 + 0.013968 * t
  }

  def kundaliSidereal(
      tropical: Double,
      jd: Double
  ): Double = {
    kundaliNormalize(
      tropical -
        kundaliLahiriAyanamsha(jd)
    )
  }

  def kundaliSignName(
      longitude: Double
  ): String = {
    val names = Array(
      "Mesha (Aries)",
      "Vrishabha (Taurus)",
      "Mithuna (Gemini)",
      "Karka (Cancer)",
      "Simha (Leo)",
      "Kanya (Virgo)",
      "Tula (Libra)",
      "Vrishchika (Scorpio)",
      "Dhanu (Sagittarius)",
      "Makara (Capricorn)",
      "Kumbha (Aquarius)",
      "Meena (Pisces)"
    )
    names(
      Math.floor(
        kundaliNormalize(longitude) / 30.0
      ).toInt
    )
  }

  def kundaliDegreeText(
      longitude: Double
  ): String = {
    val x = kundaliNormalize(longitude)
    val signDegree = x % 30.0
    val d = Math.floor(signDegree).toInt
    val min =
      Math.floor((signDegree - d) * 60.0).toInt
    val sec =
      Math.floor(
        (((signDegree - d) * 60.0) - min) * 60.0
      ).toInt
    d + " deg " + min + "' " + sec + "\""
  }

  def kundaliSunLongitude(
      jd: Double
  ): Double = {
    val n = jd - 2451545.0
    val l =
      kundaliNormalize(
        280.460 + 0.9856474 * n
      )
    val g =
      Math.toRadians(
        kundaliNormalize(
          357.528 + 0.9856003 * n
        )
      )
    kundaliNormalize(
      l +
        1.915 * Math.sin(g) +
        0.020 * Math.sin(2.0 * g)
    )
  }

  def kundaliMoonLongitude(
      jd: Double
  ): Double = {
    val n = jd - 2451545.0
    val l =
      kundaliNormalize(
        218.316 + 13.176396 * n
      )
    val m =
      Math.toRadians(
        kundaliNormalize(
          134.963 + 13.064993 * n
        )
      )
    val f =
      Math.toRadians(
        kundaliNormalize(
          93.272 + 13.229350 * n
        )
      )
    kundaliNormalize(
      l +
        6.289 * Math.sin(m) +
        1.274 *
          Math.sin(
            Math.toRadians(
              2.0 * kundaliNormalize(297.850 + 12.190749 * n)
            ) - m
          ) +
        0.658 *
          Math.sin(
            Math.toRadians(
              2.0 * kundaliNormalize(357.529 + 0.9856003 * n)
            )
          ) +
        0.214 * Math.sin(2.0 * m) -
        0.186 *
          Math.sin(
            Math.toRadians(
              kundaliNormalize(357.529 + 0.9856003 * n)
            )
          ) +
        0.040 * Math.sin(f)
    )
  }

  def kundaliLinearLongitude(
      jd: Double,
      base: Double,
      rate: Double
  ): Double = {
    kundaliNormalize(
      base +
        rate * (jd - 2451545.0)
    )
  }

  def kundaliMercuryLongitude(jd: Double): Double =
    kundaliLinearLongitude(jd, 252.25084, 4.09233445)

  def kundaliVenusLongitude(jd: Double): Double =
    kundaliLinearLongitude(jd, 181.97973, 1.60213034)

  def kundaliMarsLongitude(jd: Double): Double =
    kundaliLinearLongitude(jd, 355.433, 0.5240208)

  def kundaliJupiterLongitude(jd: Double): Double =
    kundaliLinearLongitude(jd, 34.351, 0.0830853)

  def kundaliSaturnLongitude(jd: Double): Double =
    kundaliLinearLongitude(jd, 50.077, 0.0334442)

  def kundaliRahuLongitude(jd: Double): Double =
    kundaliNormalize(
      125.04452 -
        0.0529538083 * (jd - 2451545.0)
    )

  def kundaliKetuLongitude(jd: Double): Double =
    kundaliNormalize(
      kundaliRahuLongitude(jd) + 180.0
    )

  def kundaliNakshatraName(
      longitude: Double
  ): String = {
    val names = Array(
      "Ashwini", "Bharani", "Krittika", "Rohini", "Mrigashira",
      "Ardra", "Punarvasu", "Pushya", "Ashlesha", "Magha",
      "Purva Phalguni", "Uttara Phalguni", "Hasta", "Chitra",
      "Swati", "Vishakha", "Anuradha", "Jyeshtha", "Mula",
      "Purva Ashadha", "Uttara Ashadha", "Shravana", "Dhanishtha",
      "Shatabhisha", "Purva Bhadrapada", "Uttara Bhadrapada", "Revati"
    )
    val i =
      Math.floor(
        kundaliNormalize(longitude) / (360.0 / 27.0)
      ).toInt
    names(
      Math.min(
        26,
        Math.max(0, i)
      )
    )
  }

  def kundaliNakshatraPada(
      longitude: Double
  ): Int = {
    val span = 360.0 / 108.0
    (Math.floor(kundaliNormalize(longitude) / span).toInt % 4) + 1
  }

  def kundaliAscendant(
      jd: Double,
      latitude: Double,
      longitude: Double
  ): Double = {
    val t =
      (jd - 2451545.0) / 36525.0
    val gmst =
      280.46061837 +
        360.98564736629 * (jd - 2451545.0) +
        0.000387933 * t * t -
        t * t * t / 38710000.0
    val lst =
      kundaliNormalize(
        gmst + longitude
      )
    val phi = Math.toRadians(latitude)
    val eps = Math.toRadians(23.439291)
    val theta = Math.toRadians(lst)
    val asc =
      Math.atan2(
        -Math.cos(theta),
        Math.sin(theta) * Math.cos(eps) +
          Math.tan(phi) * Math.sin(eps)
      )
    kundaliSidereal(
      kundaliNormalize(Math.toDegrees(asc)),
      jd
    )
  }

  def kundaliHouseFromLongitude(
      planet: Double,
      lagna: Double
  ): Int = {
    Math.floor(
      kundaliNormalize(planet - lagna) / 30.0
    ).toInt + 1
  }

  def kundaliNavamsaNumber(
      longitude: Double
  ): Int = {
    val withinSign =
      kundaliNormalize(longitude) % 30.0
    Math.floor(
      withinSign / (30.0 / 9.0)
    ).toInt + 1
  }

  def kundaliPlanetArray(
      jd: Double
  ): Array[(String, Double)] = {
    Array(
      ("Sun", kundaliSidereal(kundaliSunLongitude(jd), jd)),
      ("Moon", kundaliSidereal(kundaliMoonLongitude(jd), jd)),
      ("Mercury", kundaliSidereal(kundaliMercuryLongitude(jd), jd)),
      ("Venus", kundaliSidereal(kundaliVenusLongitude(jd), jd)),
      ("Mars", kundaliSidereal(kundaliMarsLongitude(jd), jd)),
      ("Jupiter", kundaliSidereal(kundaliJupiterLongitude(jd), jd)),
      ("Saturn", kundaliSidereal(kundaliSaturnLongitude(jd), jd)),
      ("Rahu", kundaliSidereal(kundaliRahuLongitude(jd), jd)),
      ("Ketu", kundaliSidereal(kundaliKetuLongitude(jd), jd))
    )
  }

  def kundaliPlanetShort(name: String): String = {
    name match {
      case "Sun" => "Su"
      case "Moon" => "Mo"
      case "Mercury" => "Me"
      case "Venus" => "Ve"
      case "Mars" => "Ma"
      case "Jupiter" => "Ju"
      case "Saturn" => "Sa"
      case "Rahu" => "Ra"
      case "Ketu" => "Ke"
      case _ => name.take(2)
    }
  }

  def kundaliAddApproxYears(
      base: java.util.GregorianCalendar,
      years: Double
  ): java.util.GregorianCalendar = {
    val copy =
      new java.util.GregorianCalendar()
    copy.setTimeInMillis(
      base.getTimeInMillis
    )
    val millisPerYear =
      365.2425 * 24.0 * 60.0 * 60.0 * 1000.0
    copy.setTimeInMillis(
      copy.getTimeInMillis +
        (years * millisPerYear).toLong
    )
    copy
  }

  def kundaliDashaText(
      moonLongitude: Double,
      year: Int,
      month: Int,
      day: Int
  ): String = {
    val dashaLords =
      Array(
        "Ketu",
        "Venus",
        "Sun",
        "Moon",
        "Mars",
        "Rahu",
        "Jupiter",
        "Saturn",
        "Mercury"
      )

    val dashaYears =
      Array(
        7.0,
        20.0,
        6.0,
        10.0,
        7.0,
        18.0,
        16.0,
        19.0,
        17.0
      )

    val nakSize =
      360.0 / 27.0
    val nakIndex =
      Math.floor(
        kundaliNormalize(moonLongitude) / nakSize
      ).toInt
    val lordIndex =
      nakIndex % 9
    val withinNak =
      kundaliNormalize(moonLongitude) % nakSize
    val fractionElapsed =
      withinNak / nakSize
    val firstRemaining =
      dashaYears(lordIndex) *
        (1.0 - fractionElapsed)

    val birth =
      new java.util.GregorianCalendar(
        year,
        Math.max(0, month - 1),
        day
      )
    val fmt =
      new SimpleDateFormat("dd/MM/yyyy")

    val sb =
      new StringBuilder()

    sb.append("VIMSHOTTARI MAHADASHA - TRADITIONAL TIMING VIEW\n")
    sb.append("=================================================\n\n")
    sb.append("Moon Nakshatra: ")
      .append(kundaliNakshatraName(moonLongitude))
      .append("\n")
    sb.append("Starting Mahadasha: ")
      .append(dashaLords(lordIndex))
      .append("\n")
    sb.append("Approximate balance at birth: ")
      .append(String.format("%.2f", firstRemaining))
      .append(" years\n\n")

    var currentIndex = lordIndex
    var startDate = birth
    var duration = firstRemaining
    var totalYears = 0.0
    var row = 0

    while (row < 9) {
      val endDate =
        kundaliAddApproxYears(
          startDate,
          duration
        )

      sb.append(
        String.format(
          "%d. %-8s  %.2f years   %s  ->  %s   (age %.2f to %.2f)\n",
          row + 1,
          dashaLords(currentIndex),
          duration,
          fmt.format(startDate.getTime),
          fmt.format(endDate.getTime),
          totalYears,
          totalYears + duration
        )
      )

      startDate = endDate
      totalYears += duration
      currentIndex =
        (currentIndex + 1) % 9
      duration =
        dashaYears(currentIndex)
      row += 1
    }

    sb.append("\nInterpretation note\n")
    sb.append("-------------------\n")
    sb.append("The dates above are an approximate traditional Vimshottari timing calculation based on the standalone Moon position in this app.\n")
    sb.append("They are not scientifically validated predictions and should not be treated as guaranteed future events.\n")
    sb.toString
  }

  def kundaliKarmicText(
      lagna: Double,
      moon: Double,
      rahu: Double,
      ketu: Double
  ): String = {
    val sb = new StringBuilder
    sb.append("KARMIC / LIFE-THEME REFLECTION\n")
    sb.append("==============================\n\n")
    sb.append("This section presents traditional astrological symbolism, not verified facts about past or future lives.\n\n")

    sb.append("PAST-LIFE SYMBOLISM (TRADITIONAL)\n")
    sb.append("----------------------------------\n")
    sb.append("Ketu sign: ")
      .append(kundaliSignName(ketu))
      .append("\n")
    sb.append("Ketu nakshatra: ")
      .append(kundaliNakshatraName(ketu))
      .append("\n")
    sb.append("Traditional reading uses Ketu as a symbolic marker for familiar patterns, detachment, or previously developed themes.\n\n")

    sb.append("FUTURE / GROWTH SYMBOLISM (TRADITIONAL)\n")
    sb.append("----------------------------------------\n")
    sb.append("Rahu sign: ")
      .append(kundaliSignName(rahu))
      .append("\n")
    sb.append("Rahu nakshatra: ")
      .append(kundaliNakshatraName(rahu))
      .append("\n")
    sb.append("Traditional reading uses Rahu as a symbolic marker for unfamiliar goals, growth, experimentation, or areas of strong desire.\n\n")

    sb.append("CORE THEMES\n")
    sb.append("-----------\n")
    sb.append("Lagna Rashi : ")
      .append(kundaliSignName(lagna))
      .append("\n")
    sb.append("Moon Rashi  : ")
      .append(kundaliSignName(moon))
      .append("\n")
    sb.append("Moon Nakshatra : ")
      .append(kundaliNakshatraName(moon))
      .append("\n\n")

    sb.append("ABOUT 'WHAT WILL HAPPEN'\n")
    sb.append("-------------------------\n")
    sb.append("No chart can establish an exact, guaranteed event, date, cause, or future-life outcome. This app therefore presents timing as traditional reflection windows rather than certainty.\n")
    sb.toString
  }

  def kundaliPrecisionText(): String = {
    "PRECISION TRANSPARENCY\n" +
    "=======================\n\n" +
    "This standalone build uses self-contained astronomical approximations.\n" +
    "It is useful for an educational/traditional chart display but should not be called professional-grade ephemeris precision.\n\n" +
    "For high-precision chart work, compare the birth data and planetary positions against a dedicated ephemeris engine.\n"
  }

  def kundaliCreateReport(
      name: String,
      date: String,
      time: String,
      place: String,
      lat: Double,
      lon: Double,
      tz: Double,
      jd: Double
  ): String = {
    val lagna = kundaliAscendant(jd, lat, lon)
    val planets = kundaliPlanetArray(jd)
    val moon = planets(1)._2
    val sb = new StringBuilder

    sb.append("==============================================================\n")
    sb.append("                     KUNDALI PURAN\n")
    sb.append("               VEDIC / SIDEREAL REPORT\n")
    sb.append("==============================================================\n\n")
    sb.append("PERSONAL DETAILS\n")
    sb.append("--------------------------------------------------------------\n")
    sb.append("Name        : ").append(name).append("\n")
    sb.append("Birth Date  : ").append(date).append("\n")
    sb.append("Birth Time  : ").append(time).append("\n")
    sb.append("Birth Place : ").append(place).append("\n")
    sb.append("Latitude    : ").append(String.format("%.6f", lat)).append("\n")
    sb.append("Longitude   : ").append(String.format("%.6f", lon)).append("\n")
    sb.append("Timezone    : UTC ").append(String.format("%.2f", tz)).append("\n\n")

    sb.append("CALCULATION\n")
    sb.append("--------------------------------------------------------------\n")
    sb.append("Julian Day       : ").append(String.format("%.6f", jd)).append("\n")
    sb.append("Lahiri Ayanamsha : ").append(String.format("%.6f", kundaliLahiriAyanamsha(jd))).append(" deg\n\n")

    sb.append("LAGNA / ASCENDANT\n")
    sb.append("--------------------------------------------------------------\n")
    sb.append("Lagna Degree : ").append(kundaliDegreeText(lagna)).append("\n")
    sb.append("Lagna Rashi  : ").append(kundaliSignName(lagna)).append("\n")
    sb.append("Lagna House  : 1\n\n")

    sb.append("MOON / JANMA DETAILS\n")
    sb.append("--------------------------------------------------------------\n")
    sb.append("Moon Degree     : ").append(kundaliDegreeText(moon)).append("\n")
    sb.append("Moon Rashi      : ").append(kundaliSignName(moon)).append("\n")
    sb.append("Nakshatra       : ").append(kundaliNakshatraName(moon)).append("\n")
    sb.append("Nakshatra Pada  : ").append(kundaliNakshatraPada(moon)).append("\n")
    sb.append("Navamsa Number  : ").append(kundaliNavamsaNumber(moon)).append("\n\n")

    sb.append("PLANETARY POSITIONS\n")
    sb.append("--------------------------------------------------------------\n")
    var i = 0
    while (i < planets.length) {
      val p = planets(i)
      sb.append(
        String.format(
          "%-9s | %-18s | %-12s | House %2d | Navamsa %d\n",
          p._1,
          kundaliDegreeText(p._2),
          kundaliSignName(p._2),
          kundaliHouseFromLongitude(p._2, lagna),
          kundaliNavamsaNumber(p._2)
        )
      )
      i += 1
    }

    sb.append("\n12 HOUSE STRUCTURE\n")
    sb.append("--------------------------------------------------------------\n")
    var h = 1
    while (h <= 12) {
      val houseLongitude =
        kundaliNormalize(lagna + (h - 1) * 30.0)
      sb.append(
        String.format(
          "House %2d : %-18s : start %s\n",
          h,
          kundaliSignName(houseLongitude),
          kundaliDegreeText(houseLongitude)
        )
      )
      h += 1
    }

    sb.append("\nTRADITIONAL CHART MARKERS\n")
    sb.append("--------------------------------------------------------------\n")
    sb.append("Janma Rashi     : ").append(kundaliSignName(moon)).append("\n")
    sb.append("Janma Nakshatra : ").append(kundaliNakshatraName(moon)).append("\n")
    sb.append("Pada            : ").append(kundaliNakshatraPada(moon)).append("\n")
    sb.append("Lagna Rashi     : ").append(kundaliSignName(lagna)).append("\n\n")

    sb.append("\nTRADITIONAL KARMIC / GROWTH VIEW\n")
    sb.append("--------------------------------------------------------------\n")
    
    sb.append("\nVIMSHOTTARI TIMING VIEW\n")
    sb.append("--------------------------------------------------------------\n")
    val datePartsForDasha = date.split("/")
    if (datePartsForDasha.length == 3) {
      try {
        sb.append(
          kundaliDashaText(
            moon,
            datePartsForDasha(2).toInt,
            datePartsForDasha(1).toInt,
            datePartsForDasha(0).toInt
          )
        )
      } catch {
        case _: Throwable =>
          sb.append("Dasha timing could not be generated from the entered date.\n")
      }
    }
    sb.append("\n")
    sb.append(kundaliPrecisionText())
    sb.append("\nIMPORTANT NOTE\n")
    sb.append("--------------------------------------------------------------\n")
    sb.append("This standalone studio module follows a simplified sidereal-style\n")
    sb.append("calculation approach based on the supplied Kundali Puran source.\n")
    sb.append("It is not a professional-grade ephemeris engine. For high-precision\n")
    sb.append("astronomy, a dedicated ephemeris such as Swiss Ephemeris is required.\n")
    sb.append("Astrological interpretations shown here are traditional/cultural\n")
    sb.append("content and should not be treated as scientific predictions.\n")
    sb.append("==============================================================\n")
    sb.toString
  }

  // --------------------------------------------------------
  // KUNDALI REPORT VIEW
  // --------------------------------------------------------

  val kundaliReportArea = makeArea()
  kundaliReportArea.setEditable(false)
  kundaliReportArea.setLineWrap(false)
  kundaliReportArea.setBackground(
    new Color(10, 10, 28)
  )
  kundaliReportArea.setForeground(
    new Color(250, 235, 190)
  )
  kundaliReportArea.setFont(
    new Font(
      "Monospaced",
      Font.PLAIN,
      12
    )
  )

  val kundaliReportScroll =
    makeScroll(kundaliReportArea)

  // --------------------------------------------------------
  // KUNDALI PLANET TABLE
  // --------------------------------------------------------

  val kundaliTableModel =
    new javax.swing.table.DefaultTableModel(
      Array[Object](
        "Planet", "Degree", "Rashi", "House", "Nakshatra", "Navamsa"
      ),
      0
    ) {
      override def isCellEditable(
          row: Int,
          column: Int
      ): Boolean = false
    }

  val kundaliTable =
    new JTable(kundaliTableModel)
  kundaliTable.setRowHeight(24)
  kundaliTable.setBackground(
    new Color(20, 18, 40)
  )
  kundaliTable.setForeground(
    new Color(245, 232, 188)
  )
  kundaliTable.setGridColor(
    new Color(85, 70, 110)
  )
  kundaliTable.getTableHeader.setBackground(
    new Color(60, 46, 85)
  )
  kundaliTable.getTableHeader.setForeground(Color.WHITE)
  kundaliTable.getTableHeader.setFont(
    new Font("SansSerif", Font.BOLD, 12)
  )

  val kundaliTableScroll =
    new JScrollPane(kundaliTable)

  // --------------------------------------------------------
  // KUNDALI CHART CANVAS
  // --------------------------------------------------------

  val kundaliChartCanvas =
    new JPanel {

      setBackground(
        new Color(8, 8, 28)
      )
      setPreferredSize(
        new Dimension(515, 515)
      )

      def drawStarField(
          g: Graphics2D,
          w: Int,
          h: Int
      ): Unit = {
        var s = 0
        while (s < 42) {
          val sx =
            (s * 97 + kundaliAnimFrame * (1 + s % 3)) % Math.max(1, w)
          val sy =
            (s * 53 + kundaliAnimFrame / 2) % Math.max(1, h)
          val alpha =
            70 + ((s * 13 + kundaliAnimFrame) % 120)
          g.setColor(
            new Color(255, 235, 160, alpha)
          )
          val size =
            2 + s % 3
          g.fillOval(
            sx,
            sy,
            size,
            size
          )
          s += 1
        }
      }

      def houseCenters(
          cx: Int,
          cy: Int,
          r: Int
      ): Array[(Int, Int)] = {
        Array(
          (cx, cy - r / 2),
          (cx - r / 2, cy - r / 2),
          (cx - 3 * r / 4, cy),
          (cx - r / 2, cy + r / 2),
          (cx, cy + r / 2),
          (cx + r / 2, cy + r / 2),
          (cx + 3 * r / 4, cy),
          (cx + r / 2, cy - r / 2),
          (cx, cy - 3 * r / 4),
          (cx + r / 2, cy),
          (cx, cy),
          (cx - r / 2, cy)
        )
      }

      override def paintComponent(
          graphics: Graphics
      ): Unit = {
        super.paintComponent(graphics)
        val g = graphics.asInstanceOf[Graphics2D]
        g.setRenderingHint(
          RenderingHints.KEY_ANTIALIASING,
          RenderingHints.VALUE_ANTIALIAS_ON
        )

        val w = getWidth
        val h = getHeight
        drawStarField(g, w, h)

        val cx = w / 2
        val cy = h / 2 + 8
        val r = Math.min(w, h) / 2 - 42

        if (kundaliGlow) {
          val pulse =
            8 + Math.abs((kundaliAnimFrame % 40) - 20)
          g.setColor(
            new Color(255, 215, 90, 18)
          )
          g.fillOval(
            cx - r - pulse,
            cy - r - pulse,
            (r + pulse) * 2,
            (r + pulse) * 2
          )
        }

        g.setColor(
          new Color(24, 20, 42, 245)
        )
        g.fillRect(
          cx - r,
          cy - r,
          r * 2,
          r * 2
        )

        g.setColor(
          new Color(220, 170, 70)
        )
        g.setStroke(
          new BasicStroke(2.0f)
        )
        g.drawRect(
          cx - r,
          cy - r,
          r * 2,
          r * 2
        )

        val diamond = new Polygon()
        diamond.addPoint(cx, cy - r)
        diamond.addPoint(cx + r, cy)
        diamond.addPoint(cx, cy + r)
        diamond.addPoint(cx - r, cy)
        g.drawPolygon(diamond)

        g.drawLine(cx - r, cy - r, cx, cy)
        g.drawLine(cx + r, cy - r, cx, cy)
        g.drawLine(cx - r, cy + r, cx, cy)
        g.drawLine(cx + r, cy + r, cx, cy)

        if (!kundaliCalculated) {
          g.setColor(
            new Color(235, 220, 170)
          )
          g.setFont(
            new Font(
              "Serif",
              Font.BOLD,
              20
            )
          )
          g.drawString(
            "KUNDALI CHAKRA",
            cx - 82,
            cy + 10
          )
          g.setFont(
            new Font(
              "SansSerif",
              Font.PLAIN,
              11
            )
          )
          g.setColor(MUTED)
          g.drawString(
            "Calculate a birth chart to populate houses and planets.",
            cx - 151,
            cy + 34
          )
        } else {
          val lagna = kundaliAscendant(
            kundaliJD,
            kundaliLatitude.getText.trim.toDouble,
            kundaliLongitude.getText.trim.toDouble
          )
          val planets = kundaliPlanetArray(kundaliJD)
          val houses = houseCenters(cx, cy, r)
          val houseText = Array.fill(12)("")

          var pi = 0
          while (pi < planets.length) {
            val p = planets(pi)
            val house =
              kundaliHouseFromLongitude(p._2, lagna) - 1
            val old = houseText(house)
            houseText(house) =
              old +
                kundaliPlanetShort(p._1) +
                " " +
                String.format("%.0f", p._2 % 30.0) +
                "\n"
            pi += 1
          }

          var hi = 0
          while (hi < 12) {
            val center = houses(hi)
            g.setColor(
              new Color(150, 122, 74)
            )
            g.setFont(
              new Font(
                "SansSerif",
                Font.BOLD,
                11
              )
            )
            g.drawString(
              (hi + 1).toString,
              center._1 - 4,
              center._2 - 14
            )
            g.setFont(
              new Font(
                "Monospaced",
                Font.BOLD,
                10
              )
            )
            g.setColor(
              new Color(245, 226, 175)
            )
            val lines =
              houseText(hi).split("\n").filter(_.nonEmpty)
            var li = 0
            while (li < lines.length && li < 4) {
              g.drawString(
                lines(li),
                center._1 - 15,
                center._2 + li * 13
              )
              li += 1
            }
            hi += 1
          }

          g.setColor(
            new Color(255, 213, 102)
          )
          g.setFont(
            new Font(
              "SansSerif",
              Font.BOLD,
              12
            )
          )
          g.drawString(
            "LAGNA",
            cx - 22,
            cy + 5
          )
          g.setFont(
            new Font(
              "SansSerif",
              Font.PLAIN,
              10
            )
          )
          g.setColor(MUTED)
          g.drawString(
            kundaliSignName(lagna),
            cx - 47,
            cy + 20
          )
        }

        g.setColor(
          new Color(255, 220, 120)
        )
        g.setFont(
          new Font(
            "Serif",
            Font.BOLD,
            18
          )
        )
        g.drawString(
          "KUNDALI PURAN",
          18,
          28
        )
        g.setColor(MUTED)
        g.setFont(
          new Font(
            "SansSerif",
            Font.PLAIN,
            10
          )
        )
        g.drawString(
          "North-Indian-style visual chart | traditional layout",
          18,
          46
        )
      }
    }

  val kundaliChartScroll =
    new JScrollPane(kundaliChartCanvas)
  kundaliChartScroll.setBorder(
    BorderFactory.createLineBorder(
      new Color(100, 80, 120),
      1
    )
  )

  // --------------------------------------------------------
  // KUNDALI DETAIL TABS
  // --------------------------------------------------------

  val kundaliDetailsTabs =
    new JTabbedPane()
  kundaliDetailsTabs.setBackground(
    new Color(18, 17, 42)
  )
  kundaliDetailsTabs.setForeground(Color.WHITE)

  val kundaliOverviewPanel =
    new JPanel(
      new BorderLayout(8, 8)
    )
  kundaliOverviewPanel.setBackground(
    new Color(18, 17, 42)
  )

  val kundaliOverviewText = makeArea()
  kundaliOverviewText.setEditable(false)
  kundaliOverviewText.setLineWrap(true)
  kundaliOverviewText.setWrapStyleWord(true)
  kundaliOverviewText.setFont(
    new Font(
      "SansSerif",
      Font.PLAIN,
      13
    )
  )
  kundaliOverviewText.setBackground(
    new Color(10, 10, 28)
  )
  kundaliOverviewText.setForeground(
    new Color(245, 232, 188)
  )
  kundaliOverviewPanel.add(
    makeScroll(kundaliOverviewText),
    BorderLayout.CENTER
  )

  val kundaliMarkerPanel =
    new JPanel(
      new GridLayout(0, 1, 4, 4)
    )
  kundaliMarkerPanel.setBackground(
    new Color(24, 20, 50)
  )
  kundaliMarkerPanel.setBorder(
    new EmptyBorder(8, 8, 8, 8)
  )

  val kundaliMarkers = Array(
    "Lagna : -",
    "Moon Rashi : -",
    "Nakshatra : -",
    "Pada : -",
    "Ayanamsha : -"
  )
  var km = 0
  while (km < kundaliMarkers.length) {
    val lab = makeLabel(
      kundaliMarkers(km),
      12,
      true
    )
    lab.setForeground(
      new Color(255, 218, 130)
    )
    kundaliMarkerPanel.add(lab)
    km += 1
  }

  kundaliOverviewPanel.add(
    kundaliMarkerPanel,
    BorderLayout.NORTH
  )

  val kundaliNavamsaArea = makeArea()
  kundaliNavamsaArea.setEditable(false)
  kundaliNavamsaArea.setFont(
    new Font(
      "Monospaced",
      Font.PLAIN,
      12
    )
  )
  kundaliNavamsaArea.setBackground(
    new Color(10, 10, 28)
  )
  kundaliNavamsaArea.setForeground(
    new Color(240, 225, 180)
  )

  val kundaliTraditionalArea = makeArea()
  kundaliTraditionalArea.setEditable(false)
  kundaliTraditionalArea.setLineWrap(true)
  kundaliTraditionalArea.setWrapStyleWord(true)
  kundaliTraditionalArea.setBackground(
    new Color(10, 10, 28)
  )
  kundaliTraditionalArea.setForeground(
    new Color(240, 225, 180)
  )
  kundaliTraditionalArea.setFont(
    new Font(
      "SansSerif",
      Font.PLAIN,
      13
    )
  )

  kundaliDetailsTabs.addTab(
    "OVERVIEW",
    kundaliOverviewPanel
  )
  kundaliDetailsTabs.addTab(
    "REPORT",
    kundaliReportScroll
  )
  kundaliDetailsTabs.addTab(
    "PLANETS",
    kundaliTableScroll
  )
  kundaliDetailsTabs.addTab(
    "NAVAMSA",
    makeScroll(kundaliNavamsaArea)
  )
  kundaliDetailsTabs.addTab(
    "TRADITIONAL NOTES",
    makeScroll(kundaliTraditionalArea)
  )

  val kundaliKarmicArea = makeArea()
  kundaliKarmicArea.setEditable(false)
  kundaliKarmicArea.setLineWrap(true)
  kundaliKarmicArea.setWrapStyleWord(true)
  kundaliKarmicArea.setBackground(
    new Color(10, 10, 28)
  )
  kundaliKarmicArea.setForeground(
    new Color(240, 225, 180)
  )
  kundaliDetailsTabs.addTab(
    "KARMIC / GROWTH",
    makeScroll(kundaliKarmicArea)
  )

  val kundaliTimingArea = makeArea()
  kundaliTimingArea.setEditable(false)
  kundaliTimingArea.setLineWrap(true)
  kundaliTimingArea.setWrapStyleWord(true)
  kundaliTimingArea.setBackground(
    new Color(10, 10, 28)
  )
  kundaliTimingArea.setForeground(
    new Color(240, 225, 180)
  )
  kundaliDetailsTabs.addTab(
    "DASHA / TIMING",
    makeScroll(kundaliTimingArea)
  )

  val kundaliRightSplit =
    new JSplitPane(
      JSplitPane.HORIZONTAL_SPLIT,
      kundaliChartScroll,
      kundaliDetailsTabs
    )
  kundaliRightSplit.setResizeWeight(0.47)
  kundaliRightSplit.setDividerLocation(520)

  val kundaliCenterSplit =
    new JSplitPane(
      JSplitPane.HORIZONTAL_SPLIT,
      kundaliInputCard,
      kundaliRightSplit
    )
  kundaliCenterSplit.setResizeWeight(0.24)
  kundaliCenterSplit.setDividerLocation(340)

  kundaliPanel.add(
    kundaliCenterSplit,
    BorderLayout.CENTER
  )

  val kundaliBottomBar =
    new JPanel(
      new BorderLayout(8, 8)
    )
  kundaliBottomBar.setBackground(
    new Color(14, 12, 35)
  )

  val kundaliAnimationButton =
    makeButton(
      "CHART GLOW: ON"
    )
  val kundaliDegreesButton =
    makeButton(
      "DETAILS: ON"
    )
  val kundaliBackButton =
    makeButton(
      "BACK TO AIR TO WATER"
    )

  val kundaliFooter =
    makeLabel(
      "Traditional / cultural chart module - calculation display only",
      10,
      false
    )
  kundaliFooter.setForeground(
    new Color(175, 165, 145)
  )

  kundaliBottomBar.add(
    kundaliFooter,
    BorderLayout.CENTER
  )

  val kundaliBottomButtons =
    new JPanel(
      new GridLayout(1, 3, 5, 5)
    )
  kundaliBottomButtons.setBackground(
    new Color(14, 12, 35)
  )
  kundaliBottomButtons.add(kundaliAnimationButton)
  kundaliBottomButtons.add(kundaliDegreesButton)
  kundaliBottomButtons.add(kundaliBackButton)

  kundaliBottomBar.add(
    kundaliBottomButtons,
    BorderLayout.EAST
  )

  kundaliPanel.add(
    kundaliBottomBar,
    BorderLayout.SOUTH
  )

  // --------------------------------------------------------
  // KUNDALI REFRESH / TABLE / DASHBOARD HELPERS
  // --------------------------------------------------------

  def refreshKundaliView(): Unit = {
    if (!kundaliCalculated) {
      kundaliStatus.setText(
        "READY - ENTER BIRTH DETAILS"
      )
      kundaliReportArea.setText(
        "Enter birth date, time, place, latitude, longitude and timezone, then press CALCULATE KUNDALI."
      )
      kundaliOverviewText.setText(
        "KUNDALI PURAN READY\n\nThe visual chart, planet table, navamsa view and report will populate after calculation."
      )
      kundaliNavamsaArea.setText("")
      kundaliTraditionalArea.setText("")
      kundaliKarmicArea.setText("")
      kundaliTimingArea.setText("")
      kundaliChartCanvas.repaint()
      return
    }

    val lat = kundaliLatitude.getText.trim.toDouble
    val lon = kundaliLongitude.getText.trim.toDouble
    val tz = kundaliTimezone.getText.trim.toDouble
    val lagna = kundaliAscendant(kundaliJD, lat, lon)
    val planets = kundaliPlanetArray(kundaliJD)
    val moon = planets(1)._2
    val report =
      kundaliCreateReport(
        kundaliFirstName.getText.trim + " " + kundaliSurname.getText.trim,
        kundaliBirthDate.getText.trim,
        kundaliBirthTime.getText.trim,
        kundaliBirthPlace.getText.trim,
        lat,
        lon,
        tz,
        kundaliJD
      )
    kundaliLastReport = report
    kundaliReportArea.setText(report)
    kundaliReportArea.setCaretPosition(0)

    val markerTexts = Array(
      "Lagna : " + kundaliSignName(lagna),
      "Moon Rashi : " + kundaliSignName(moon),
      "Nakshatra : " + kundaliNakshatraName(moon),
      "Pada : " + kundaliNakshatraPada(moon),
      "Ayanamsha : " + String.format("%.4f", kundaliLahiriAyanamsha(kundaliJD)) + " deg"
    )
    var mi = 0
    while (mi < kundaliMarkerPanel.getComponentCount && mi < markerTexts.length) {
      kundaliMarkerPanel.getComponent(mi).asInstanceOf[JLabel].setText(markerTexts(mi))
      mi += 1
    }

    while (kundaliTableModel.getRowCount > 0) {
      kundaliTableModel.removeRow(0)
    }

    var pi = 0
    while (pi < planets.length) {
      val p = planets(pi)
      kundaliTableModel.addRow(
        Array[Object](
          p._1,
          kundaliDegreeText(p._2),
          kundaliSignName(p._2),
          Integer.valueOf(kundaliHouseFromLongitude(p._2, lagna)),
          kundaliNakshatraName(p._2),
          Integer.valueOf(kundaliNavamsaNumber(p._2))
        )
      )
      pi += 1
    }

    val ns = new StringBuilder
    ns.append("NAVAMSA DETAIL TABLE\n")
    ns.append("====================\n\n")
    ns.append("Navamsa number is derived from each planet's position within its 30-degree sign.\n")
    ns.append("This is a traditional Vedic chart marker, not a scientific measurement.\n\n")
    pi = 0
    while (pi < planets.length) {
      val p = planets(pi)
      ns.append(
        String.format(
          "%-9s  Sign=%-18s  Navamsa=%d\n",
          p._1,
          kundaliSignName(p._2),
          kundaliNavamsaNumber(p._2)
        )
      )
      pi += 1
    }
    kundaliNavamsaArea.setText(ns.toString)
    kundaliNavamsaArea.setCaretPosition(0)

    val tr = new StringBuilder
    tr.append("TRADITIONAL CHART SUMMARY\n")
    tr.append("=========================\n\n")
    tr.append("Lagna Rashi: ").append(kundaliSignName(lagna)).append("\n")
    tr.append("Moon Rashi: ").append(kundaliSignName(moon)).append("\n")
    tr.append("Moon Nakshatra: ").append(kundaliNakshatraName(moon)).append("\n")
    tr.append("Nakshatra Pada: ").append(kundaliNakshatraPada(moon)).append("\n\n")
    tr.append("Planet-to-house placements shown in the PLANETS tab are derived\n")
    tr.append("from the simplified ascendant and sidereal longitudes used by\n")
    tr.append("this module. Interpretations remain traditional/cultural.\n")
    kundaliTraditionalArea.setText(tr.toString)
    kundaliTraditionalArea.setCaretPosition(0)

    kundaliKarmicArea.setText(
      kundaliKarmicText(
        lagna,
        moon,
        planets(7)._2,
        planets(8)._2
      )
    )
    kundaliKarmicArea.setCaretPosition(0)

    try {
      val dps = kundaliBirthDate.getText.trim.split("/")
      if (dps.length == 3) {
        kundaliTimingArea.setText(
          kundaliDashaText(
            moon,
            dps(2).toInt,
            dps(1).toInt,
            dps(0).toInt
          )
        )
      } else {
        kundaliTimingArea.setText(
          "Enter a valid date to build the traditional dasha timing view."
        )
      }
    } catch {
      case _: Throwable =>
        kundaliTimingArea.setText(
          "The timing view could not be generated from the current date input."
        )
    }
    kundaliTimingArea.setCaretPosition(0)

    kundaliStatus.setText(
      "CALCULATED - " + kundaliSignName(moon)
    )
    kundaliStatus.setForeground(
      new Color(150, 225, 170)
    )
    kundaliChartCanvas.repaint()
    kundaliDetailsTabs.repaint()
  }

  // --------------------------------------------------------
  // KUNDALI CALCULATE EVENT
  // --------------------------------------------------------

  kundaliCalculateButton.addActionListener(
    new ActionListener {
      override def actionPerformed(
          e: ActionEvent
      ): Unit = {
        try {
          val first = kundaliFirstName.getText.trim
          val last = kundaliSurname.getText.trim
          val date = kundaliBirthDate.getText.trim
          val time = kundaliBirthTime.getText.trim
          val place = kundaliBirthPlace.getText.trim
          val lat = kundaliLatitude.getText.trim.toDouble
          val lon = kundaliLongitude.getText.trim.toDouble
          val tz = kundaliTimezone.getText.trim.toDouble

          if (
            first.isEmpty ||
            last.isEmpty ||
            date.isEmpty ||
            time.isEmpty ||
            place.isEmpty
          ) {
            showWarning(
              frame,
              "KUNDALI PURAN",
              "Please fill all birth details."
            )
          } else {
            val dp = date.split("/")
            val tp = time.split(":")
            if (
              dp.length != 3 ||
              (tp.length != 2 && tp.length != 3)
            ) {
              showWarning(
                frame,
                "INVALID FORMAT",
                "Use Date: DD/MM/YYYY\nUse Time: HH:MM or HH:MM:SS"
              )
            } else {
              val day = dp(0).toInt
              val month = dp(1).toInt
              val year = dp(2).toInt
              val hour = tp(0).toInt
              val minute = tp(1).toInt
              val second =
                if (tp.length == 3) tp(2).toInt else 0

              if (
                day < 1 || day > 31 ||
                month < 1 || month > 12 ||
                hour < 0 || hour > 23 ||
                minute < 0 || minute > 59 ||
                second < 0 || second > 59 ||
                lat < -90.0 || lat > 90.0 ||
                lon < -180.0 || lon > 180.0 ||
                tz < -14.0 || tz > 14.0
              ) {
                showWarning(
                  frame,
                  "INVALID DETAILS",
                  "Please check date, time, latitude, longitude and timezone."
                )
              } else {
                kundaliJD =
                  kundaliJulianDay(
                    year,
                    month,
                    day,
                    hour,
                    minute,
                    second,
                    tz
                  )
                kundaliCalculated = true
                refreshKundaliView()
                recordActivity(
                  "Calculated Kundali Puran chart"
                )
              }
            }
          }
        } catch {
          case _: NumberFormatException =>
            showWarning(
              frame,
              "KUNDALI INPUT ERROR",
              "Please enter valid numeric values for latitude, longitude and timezone."
            )
          case _: Throwable =>
            showWarning(
              frame,
              "KUNDALI CALCULATION ERROR",
              "Please check all birth details and formats."
            )
        }
      }
    }
  )

  kundaliSampleButton.addActionListener(
    new ActionListener {
      override def actionPerformed(
          e: ActionEvent
      ): Unit = {
        kundaliFirstName.setText("Sample")
        kundaliSurname.setText("User")
        kundaliBirthDate.setText("15/08/2000")
        kundaliBirthTime.setText("12:00:00")
        kundaliBirthPlace.setText("Pune")
        kundaliLatitude.setText("18.5204")
        kundaliLongitude.setText("73.8567")
        kundaliTimezone.setText("5.5")
        kundaliStatus.setText("SAMPLE LOADED")
        kundaliStatus.setForeground(
          new Color(255, 215, 110)
        )
      }
    }
  )

  kundaliClearButton.addActionListener(
    new ActionListener {
      override def actionPerformed(
          e: ActionEvent
      ): Unit = {
        kundaliFirstName.setText("")
        kundaliSurname.setText("")
        kundaliBirthDate.setText("")
        kundaliBirthTime.setText("")
        kundaliBirthPlace.setText("")
        kundaliLatitude.setText("")
        kundaliLongitude.setText("")
        kundaliTimezone.setText("5.5")
        kundaliJD = 0.0
        kundaliCalculated = false
        kundaliLastReport = ""
        while (kundaliTableModel.getRowCount > 0) {
          kundaliTableModel.removeRow(0)
        }
        refreshKundaliView()
      }
    }
  )

  kundaliCopyButton.addActionListener(
    new ActionListener {
      override def actionPerformed(
          e: ActionEvent
      ): Unit = {
        if (kundaliLastReport.trim.isEmpty) {
          showWarning(
            frame,
            "KUNDALI PURAN",
            "Calculate the chart before copying the report."
          )
        } else {
          val clip =
            Toolkit.getDefaultToolkit.getSystemClipboard
          clip.setContents(
            new StringSelection(kundaliLastReport),
            null
          )
          recordActivity("Copied Kundali Puran report")
          showInfo(
            frame,
            "KUNDALI PURAN",
            "Report copied to clipboard."
          )
        }
      }
    }
  )

  kundaliExportButton.addActionListener(
    new ActionListener {
      override def actionPerformed(
          e: ActionEvent
      ): Unit = {
        if (kundaliLastReport.trim.isEmpty) {
          showWarning(
            frame,
            "KUNDALI PURAN",
            "Calculate the chart before exporting."
          )
        } else {
          val chooser =
            new JFileChooser()
          chooser.setDialogTitle(
            "Export Kundali Report"
          )
          if (
            chooser.showSaveDialog(frame) ==
              JFileChooser.APPROVE_OPTION
          ) {
            val file = chooser.getSelectedFile
            val out =
              new PrintWriter(
                new OutputStreamWriter(
                  new FileOutputStream(file),
                  "UTF-8"
                )
              )
            try {
              out.print(kundaliLastReport)
            } finally {
              out.close()
            }
            showInfo(
              frame,
              "KUNDALI PURAN",
              "Report exported successfully."
            )
            recordActivity("Exported Kundali Puran report")
          }
        }
      }
    }
  )

  kundaliFullPackButton.addActionListener(
    new ActionListener {
      override def actionPerformed(
          e: ActionEvent
      ): Unit = {
        if (kundaliLastReport.trim.isEmpty) {
          showWarning(
            frame,
            "KUNDALI PURAN",
            "Calculate the chart before exporting the full pack."
          )
        } else {
          val chooser = new JFileChooser()
          chooser.setDialogTitle(
            "Export Full Kundali Pack"
          )
          if (chooser.showSaveDialog(frame) == JFileChooser.APPROVE_OPTION) {
            val base = chooser.getSelectedFile
            val out = new PrintWriter(
              new OutputStreamWriter(
                new FileOutputStream(base),
                "UTF-8"
              )
            )
            try {
              out.print(kundaliLastReport)
              out.print("\n\n")
              out.print("================ KARMIC / GROWTH ================\n\n")
              out.print(kundaliKarmicArea.getText)
              out.print("\n\n================ DASHA / TIMING ================\n\n")
              out.print(kundaliTimingArea.getText)
            } finally {
              out.close()
            }
            showInfo(
              frame,
              "KUNDALI PURAN",
              "Full Kundali Pack exported successfully."
            )
            recordActivity(
              "Exported full Kundali Puran pack"
            )
          }
        }
      }
    }
  )

  kundaliAnimationButton.addActionListener(
    new ActionListener {
      override def actionPerformed(
          e: ActionEvent
      ): Unit = {
        kundaliGlow = !kundaliGlow
        kundaliAnimationButton.setText(
          if (kundaliGlow) "CHART GLOW: ON" else "CHART GLOW: OFF"
        )
        kundaliChartCanvas.repaint()
      }
    }
  )

  kundaliDegreesButton.addActionListener(
    new ActionListener {
      override def actionPerformed(
          e: ActionEvent
      ): Unit = {
        kundaliShowDegrees = !kundaliShowDegrees
        kundaliDegreesButton.setText(
          if (kundaliShowDegrees) "DETAILS: ON" else "DETAILS: BASIC"
        )
        kundaliChartCanvas.repaint()
      }
    }
  )

  val kundaliAnimationTimer =
    new javax.swing.Timer(
      55,
      new ActionListener {
        override def actionPerformed(
            e: ActionEvent
        ): Unit = {
          kundaliAnimFrame =
            (kundaliAnimFrame + 1) % 100000
          kundaliChartCanvas.repaint()
          if (kundaliAnimFrame % 10 == 0) {
            kundaliHeader.repaint()
          }
        }
      }
    )
  kundaliAnimationTimer.setCoalesce(true)
  kundaliAnimationTimer.start()

  // --------------------------------------------------------
  // NAVIGATION BUTTONS BETWEEN MODULES
  // --------------------------------------------------------

  airWaterKundaliButton.addActionListener(
    new ActionListener {
      override def actionPerformed(
          e: ActionEvent
      ): Unit = {
        tabs.setSelectedComponent(
          kundaliPanel
        )
        recordActivity(
          "Opened Kundali Puran from Air-to-Water"
        )
      }
    }
  )

  kundaliBackButton.addActionListener(
    new ActionListener {
      override def actionPerformed(
          e: ActionEvent
      ): Unit = {
        tabs.setSelectedComponent(
          airWaterPanel
        )
        recordActivity(
          "Returned to Air-to-Water from Kundali Puran"
        )
      }
    }
  )

  // --------------------------------------------------------
  // FINAL KUNDALI INITIAL STATE
  // --------------------------------------------------------

  refreshKundaliView()

  // ========================================================
  // NORMAL TABS
  // ========================================================

  tabs.addTab(
    "DASHBOARD",
    dashboardPanel
  )

  tabs.addTab(
    "AIR TO WATER REAL 3D",
    airWaterPanel
  )

  tabs.addTab(
    "KUNDALI PURAN",
    kundaliPanel
  )

  tabs.addTab(
    "AI DOCTOR",
    doctorPanel
  )

  tabs.addTab(
    "CLASSROOM",
    classroomPanel
  )

  tabs.addTab(
    "ASK TEACHER",
    teacherPanel
  )

  tabs.addTab(
    "CODE LAB",
    codeLabPanel
  )

  tabs.addTab(
    "ERROR FIX",
    errorPanel
  )

  tabs.addTab(
    "NOTES",
    notesPanel
  )

  tabs.addTab(
    "TASKS",
    tasksPanel
  )

  tabs.addTab(
    "QUIZ",
    quizPanel
  )

  tabs.addTab(
    "IMAGE STUDIO",
    imagePanel
  )

  tabs.addTab(
    "PROMPT LAB",
    promptLabPanel
  )

  tabs.addTab(
    "PROJECTS",
    projectsPanel
  )

  tabs.addTab(
    "UTILITIES",
    utilitiesPanel
  )

  tabs.addTab(
    "FEATURE LIBRARY",
    libraryPanel
  )

  tabs.addTab(
    "HISTORY",
    historyPanel
  )

  tabs.addTab(
    "SETTINGS",
    settingsPanel
  )

  // ========================================================
  // MENU
  // ========================================================

  val menuBar =
    new JMenuBar()

  val fileMenu =
    new JMenu(
      "File"
    )

  val historyMenuItem =
    new JMenuItem(
      "Open History"
    )

  val masterMenuItem =
    new JMenuItem(
      "MASTER ACCESS"
    )

  val exitMenuItem =
    new JMenuItem(
      "Exit"
    )

  historyMenuItem.addActionListener(
    new ActionListener {

      override def actionPerformed(
          e: ActionEvent
      ): Unit = {

        tabs.setSelectedComponent(
          historyPanel
        )

        refreshHistoryView()
      }
    }
  )

  masterMenuItem.addActionListener(
    new ActionListener {

      override def actionPerformed(
          e: ActionEvent
      ): Unit = {

        unlockMasterPrompt()
      }
    }
  )

  exitMenuItem.addActionListener(
    new ActionListener {

      override def actionPerformed(
          e: ActionEvent
      ): Unit = {

        frame.dispose()
      }
    }
  )

  fileMenu.add(
    historyMenuItem
  )

  fileMenu.addSeparator()

  fileMenu.add(
    masterMenuItem
  )

  fileMenu.addSeparator()

  fileMenu.add(
    exitMenuItem
  )

  val helpMenu =
    new JMenu(
      "Help"
    )

  val aboutItem =
    new JMenuItem(
      "About"
    )

  aboutItem.addActionListener(
    new ActionListener {

      override def actionPerformed(
          e: ActionEvent
      ): Unit = {

        showInfo(
          frame,
          "ABOUT",
          "ULTRA SCALA SWING AI LEARNING STUDIO\n\n" +
            "MASTER PROMPT enabled.\n" +
            "No UID field.\n" +
            "No NAME field.\n" +
            "Only IDEA is required.\n\n" +
            "Enhanced 3D-oriented prompt generation enabled."
        )
      }
    }
  )

  helpMenu.add(
    aboutItem
  )

  menuBar.add(
    fileMenu
  )

  menuBar.add(
    helpMenu
  )

  frame.setJMenuBar(
    menuBar
  )

  // ========================================================
  // KEYBOARD SHORTCUT
  // ========================================================

  val rootPane =
    frame.getRootPane

  val inputMap =
    rootPane.getInputMap(
      JComponent.WHEN_IN_FOCUSED_WINDOW
    )

  val actionMap =
    rootPane.getActionMap

  inputMap.put(
    KeyStroke.getKeyStroke(
      KeyEvent.VK_H,
      InputEvent.CTRL_DOWN_MASK
    ),
    "openHistory"
  )

  actionMap.put(
    "openHistory",
    new AbstractAction {

      override def actionPerformed(
          e: ActionEvent
      ): Unit = {

        tabs.setSelectedComponent(
          historyPanel
        )

        refreshHistoryView()
      }
    }
  )

  inputMap.put(
    KeyStroke.getKeyStroke(
      KeyEvent.VK_F1,
      0
    ),
    "showAbout"
  )

  actionMap.put(
    "showAbout",
    new AbstractAction {

      override def actionPerformed(
          e: ActionEvent
      ): Unit = {

        showInfo(
          frame,
          "HELP",
          "Ctrl+H = History\nF1 = Help\n\nMASTER ACCESS = Protected MASTER PROMPT"
        )
      }
    }
  )

  // ========================================================
  // START
  // ========================================================

  recordActivity(
    "Studio opened"
  )

  refreshHistoryView()

  frame.setContentPane(
    tabs
  )

  frame.setVisible(
    true
  )

  frame.addWindowListener(
    new WindowAdapter {
      override def windowClosed(
          e: WindowEvent
      ): Unit = {
        if (doctorTimer != null)
          doctorTimer.stop()
      }
    }
  )
}

// ==========================================================
// SAFE STARTUP
// ==========================================================

try {

  SwingUtilities.invokeLater(
    new Runnable {

      def run(): Unit = {

        try {

          openLogin()

        } catch {

          case ex: Throwable =>

            JOptionPane.showMessageDialog(
              null,
              ex.toString,
              "Startup Error",
              JOptionPane.ERROR_MESSAGE
            )
        }
      }
    }
  )

} catch {

  case ex: Throwable =>

    JOptionPane.showMessageDialog(
      null,
      ex.toString,
      "Startup Error",
      JOptionPane.ERROR_MESSAGE
    )
}