diff --git a/src/main/java/org/to/telegramfinalproject/Database/GroupDatabase.java b/src/main/java/org/to/telegramfinalproject/Database/GroupDatabase.java index e64917d..259fe86 100644 --- a/src/main/java/org/to/telegramfinalproject/Database/GroupDatabase.java +++ b/src/main/java/org/to/telegramfinalproject/Database/GroupDatabase.java @@ -679,4 +679,83 @@ public class GroupDatabase { // return result; // } + public static JSONObject getGroupInfo(UUID groupId, UUID viewerUuid) throws SQLException { + JSONObject result = new JSONObject(); + + try (Connection conn = ConnectionDb.connect()) { + // === Group header (name, member count, etc.) + String groupQuery = """ + SELECT g.internal_uuid, g.group_id, g.group_name, g.image_url, + COUNT(m.user_id) as member_count + FROM groups g + LEFT JOIN group_members m ON g.internal_uuid = m.group_id + WHERE g.internal_uuid = ? + GROUP BY g.internal_uuid, g.group_id, g.group_name, g.image_url + """; + + try (PreparedStatement ps = conn.prepareStatement(groupQuery)) { + ps.setObject(1, groupId); // ✅ compare UUID with UUID + ResultSet rs = ps.executeQuery(); + if (rs.next()) { + result.put("internal_uuid", rs.getString("internal_uuid")); + result.put("group_id", rs.getString("group_id")); // varchar handle + result.put("group_name", rs.getString("group_name")); + result.put("member_count", rs.getInt("member_count")); + result.put("image_url", rs.getString("image_url")); + } else { + return null; // no such group + } + } + + // === Members (id, name, role, status, image) + JSONArray membersArr = new JSONArray(); + + String membersQuery = """ + SELECT u.internal_uuid, u.profile_name, u.user_id, u.image_url, + gm.role, u.status, u.last_seen + FROM group_members gm + JOIN users u ON gm.user_id = u.internal_uuid + WHERE gm.group_id = ? + """; + + try (PreparedStatement ps = conn.prepareStatement(membersQuery)) { + ps.setObject(1, groupId); // ✅ gm.group_id is UUID + ResultSet rs = ps.executeQuery(); + while (rs.next()) { + JSONObject member = new JSONObject(); + member.put("user_id", rs.getString("internal_uuid")); + member.put("profile_name", rs.getString("profile_name")); + member.put("username", rs.getString("user_id")); + member.put("image_url", rs.getString("image_url")); + member.put("role", rs.getString("role")); + member.put("status", rs.getString("status")); + member.put("last_seen", rs.getString("last_seen")); + membersArr.put(member); + } + } + + result.put("members", membersArr); + + // === Viewer role (to decide if they can delete group, etc.) + String roleQuery = """ + SELECT role + FROM group_members + WHERE group_id = ? AND user_id = ? + """; + + try (PreparedStatement ps = conn.prepareStatement(roleQuery)) { + ps.setObject(1, groupId); + ps.setObject(2, viewerUuid); + ResultSet rs = ps.executeQuery(); + if (rs.next()) { + result.put("my_role", rs.getString("role")); + } else { + result.put("my_role", ""); // not a member + } + } + } + + return result; + } + } \ No newline at end of file diff --git a/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java b/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java index f72258a..a85e9c6 100644 --- a/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java +++ b/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java @@ -2997,7 +2997,29 @@ public class ClientHandler implements Runnable { break; } + case "view_group": { + if (currentUser == null) { + response = new ResponseModel("error", "Unauthorized. Please login first."); + break; + } + try { + UUID groupId = UUID.fromString(requestJson.getString("group_id")); + UUID viewerId = UUID.fromString(requestJson.getString("viewer_id")); + + // Query group details + JSONObject groupData = GroupDatabase.getGroupInfo(groupId, viewerId); + + if (groupData == null) { + response = new ResponseModel("error", "Group not found."); + } else { + response = new ResponseModel("success", "Group info fetched.", groupData); + } + } catch (Exception e) { + response = new ResponseModel("error", "Error processing group info: " + e.getMessage()); + } + break; + } default: diff --git a/src/main/java/org/to/telegramfinalproject/UI/BlockedUsersController.java b/src/main/java/org/to/telegramfinalproject/UI/BlockedUsersController.java index e92249a..1539b5c 100644 --- a/src/main/java/org/to/telegramfinalproject/UI/BlockedUsersController.java +++ b/src/main/java/org/to/telegramfinalproject/UI/BlockedUsersController.java @@ -51,7 +51,7 @@ public class BlockedUsersController { ThemeManager.getInstance().darkModeProperty().addListener((obs, oldVal, newVal) -> updateBackIcon(newVal)); updateBackIcon(ThemeManager.getInstance().isDarkMode()); - // اسکرول نرم + // Smooth scroll feel blockedList.getStylesheets().add( getClass().getResource("/org/to/telegramfinalproject/CSS/scrollpane.css").toExternalForm() ); diff --git a/src/main/java/org/to/telegramfinalproject/UI/ChatPageController.java b/src/main/java/org/to/telegramfinalproject/UI/ChatPageController.java index 801a559..872d47e 100644 --- a/src/main/java/org/to/telegramfinalproject/UI/ChatPageController.java +++ b/src/main/java/org/to/telegramfinalproject/UI/ChatPageController.java @@ -954,28 +954,38 @@ public class ChatPageController { } private void configureHeaderActions(ChatEntry entry) { - chatMoreMenu.getItems().clear(); + // Hide everything by default + archiveItem.setVisible(false); + viewProfileItem.setVisible(false); + deleteChatItem.setVisible(false); + viewGroupItem.setVisible(false); + leaveGroupItem.setVisible(false); + viewChannelItem.setVisible(false); + leaveChannelItem.setVisible(false); switch (entry.getType().toLowerCase(Locale.ROOT)) { case "private" -> { + archiveItem.setVisible(true); + viewProfileItem.setVisible(true); + deleteChatItem.setVisible(true); + archiveItem.setOnAction(e -> toggleArchive(entry)); viewProfileItem.setOnAction(e -> openInfoScene(entry)); deleteChatItem.setOnAction(e -> deleteChatButton(entry)); - chatMoreMenu.getItems().addAll(archiveItem, viewProfileItem, deleteChatItem); } case "group" -> { - MenuItem viewGroup = new MenuItem("View group info"); - viewGroup.setOnAction(e -> openInfoScene(entry)); - MenuItem leaveGroup = new MenuItem("Leave group"); - leaveGroup.setOnAction(e -> leaveGroupButton(entry)); - chatMoreMenu.getItems().addAll(viewGroup, leaveGroup); + viewGroupItem.setVisible(true); + leaveGroupItem.setVisible(true); + + viewGroupItem.setOnAction(e -> openInfoScene(entry)); + leaveGroupItem.setOnAction(e -> leaveGroupButton(entry)); } case "channel" -> { - MenuItem viewChannel = new MenuItem("View channel info"); - viewChannel.setOnAction(e -> openInfoScene(entry)); - MenuItem leaveChannel = new MenuItem("Leave channel"); - leaveChannel.setOnAction(e -> leaveChannelButton(entry)); - chatMoreMenu.getItems().addAll(viewChannel, leaveChannel); + viewChannelItem.setVisible(true); + leaveChannelItem.setVisible(true); + + viewChannelItem.setOnAction(e -> openInfoScene(entry)); + leaveChannelItem.setOnAction(e -> leaveChannelButton(entry)); } } @@ -1027,7 +1037,8 @@ public class ChatPageController { } case "group" -> { req.put("action", "view_group") - .put("group_id", entry.getId().toString()); + .put("group_id", entry.getId().toString()) + .put("viewer_id", Session.getUserUUID()); } case "channel" -> { req.put("action", "view_channel") @@ -3036,6 +3047,14 @@ public class ChatPageController { if (deleteChatItem != null && deleteChatItem.getGraphic() instanceof ImageView iv) { iv.setImage(loadIcon("delete_red.png")); // stays red in both themes } + + if (viewGroupItem != null && viewGroupItem.getGraphic() instanceof ImageView iv) { + iv.setImage(loadIcon("group" + suffix)); + } + + if (viewChannelItem != null && viewChannelItem.getGraphic() instanceof ImageView iv) { + iv.setImage(loadIcon("group" + suffix)); + } } // ChatPageController diff --git a/src/main/java/org/to/telegramfinalproject/UI/GroupInfoController.java b/src/main/java/org/to/telegramfinalproject/UI/GroupInfoController.java index e0d4a76..b71f1ce 100644 --- a/src/main/java/org/to/telegramfinalproject/UI/GroupInfoController.java +++ b/src/main/java/org/to/telegramfinalproject/UI/GroupInfoController.java @@ -1,11 +1,249 @@ package org.to.telegramfinalproject.UI; +import javafx.application.Platform; +import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.geometry.Pos; +import javafx.scene.Node; +import javafx.scene.control.*; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.layout.*; import org.json.JSONObject; +import org.to.telegramfinalproject.Client.ActionHandler; +import org.to.telegramfinalproject.Client.AvatarLocalResolver; import org.to.telegramfinalproject.Models.ChatEntry; +import java.io.IOException; +import java.net.URL; + public class GroupInfoController { + + @FXML private VBox groupCard; + @FXML private Pane overlayBackground; + @FXML private Button closeButton; + + @FXML private ImageView groupImage; + @FXML private Label groupName; + @FXML private Label memberCount; + + @FXML private Button infoMoreButton; + @FXML private ContextMenu infoMoreMenu; + @FXML private MenuItem addMemberItem; + @FXML private MenuItem manageGroupItem; + @FXML private MenuItem deleteGroupItem; + @FXML private ImageView moreIcon; + + @FXML private VBox membersList; + @FXML private Label membersHeader; + @FXML private Button addMemberButton; + @FXML private ImageView membersIcon; + @FXML private ScrollPane membersScroll; + + private String groupId; // the UUID of this group + + private static final String ICON_PATH = "/org/to/telegramfinalproject/Icons/"; + + @FXML + private void initialize() { + closeButton.setOnAction(e -> + MainController.getInstance().closeOverlay(groupCard.getParent())); + overlayBackground.setOnMouseClicked(e -> + MainController.getInstance().closeOverlay(groupCard.getParent())); + + infoMoreButton.setOnAction(e -> { + if (infoMoreMenu != null) infoMoreMenu.show(infoMoreButton, javafx.geometry.Side.BOTTOM, 0, 0); + }); + + deleteGroupItem.setOnAction(e -> handleDeleteGroup()); + + Platform.runLater(() -> { + if (groupCard.getScene() != null) { + ThemeManager.getInstance().registerScene(groupCard.getScene()); + } + }); + + ThemeManager.getInstance().darkModeProperty().addListener((obs, oldVal, newVal) -> { + updateIcons(newVal); + }); + + updateIcons(ThemeManager.getInstance().isDarkMode()); + + // Smooth scroll feel + membersScroll.getStylesheets().add( + getClass().getResource("/org/to/telegramfinalproject/CSS/scrollpane.css").toExternalForm() + ); + membersScroll.skinProperty().addListener((obs, oldSkin, newSkin) -> { + if (newSkin != null) { + ScrollBar vBar = (ScrollBar) membersScroll.lookup(".scroll-bar:vertical"); + if (vBar != null) { + membersScroll.setOnScroll(event -> { + double deltaY = event.getDeltaY() * 0.003; + double newValue = vBar.getValue() - deltaY; + vBar.setValue(Math.max(0, Math.min(newValue, 1))); + }); + } + } + }); + + if (addMemberButton != null) { + addMemberButton.setOnAction(e -> openAddMemberScene()); + } + if (addMemberItem != null) { + addMemberItem.setOnAction(e -> openAddMemberScene()); + } + } + public void setGroupDataFromJson(ChatEntry entry, JSONObject data) { - // read name, status, bio, image url from JSON - // update labels/images accordingly + this.groupId = data.optString("internal_uuid", entry.getId().toString()); + + groupName.setText(data.optString("group_name", entry.getName())); + memberCount.setText(data.optInt("member_count", 0) + " members"); + + // show/hide Delete button if user is owner + String myRole = data.optString("my_role", "member"); + if ("owner".equalsIgnoreCase(myRole)) { + deleteGroupItem.setVisible(true); + } else { + deleteGroupItem.setVisible(false); + } + + // Group picture + String imgUrl = data.optString("image_url", ""); + if (!imgUrl.isBlank()) { + try { + Image img = AvatarLocalResolver.load(imgUrl); + if (img != null) groupImage.setImage(img); + } catch (Exception ignore) {} + } else { + groupImage.setImage( + new Image(getClass().getResourceAsStream( + "/org/to/telegramfinalproject/Avatars/default_group_profile.png")) + ); + } + + // Members + membersList.getChildren().clear(); + var arr = data.optJSONArray("members"); + if (arr != null) { + membersHeader.setText(arr.length() + " MEMBERS"); + for (int i = 0; i < arr.length(); i++) { + JSONObject m = arr.getJSONObject(i); + addMemberRow(m); + } + } + } + + private void addMemberRow(JSONObject m) { + HBox row = new HBox(10); + row.getStyleClass().add("member-row"); + row.setAlignment(Pos.CENTER_LEFT); + + // === Avatar === + ImageView avatar = new ImageView(); + avatar.setFitWidth(36); + avatar.setFitHeight(36); + avatar.setPreserveRatio(true); + AvatarFX.circleClip(avatar, 36); + + String imgUrl = m.optString("image_url", ""); + if (!imgUrl.isBlank()) { + Image img = AvatarLocalResolver.load(imgUrl); + if (img != null) avatar.setImage(img); + } else { + avatar.setImage(new Image(getClass().getResourceAsStream( + "/org/to/telegramfinalproject/Avatars/default_user_profile.png" + ))); + } + + // === Name + Status === + VBox nameBox = new VBox(2); + Label name = new Label(m.optString("profile_name", "Unknown")); + name.getStyleClass().add("member-name"); + + // Status = online / last seen recently + String status; + if (m.optBoolean("is_online", false)) { + status = "online"; + } else { + status = ChatPageController.getInstance().userStatusText( + false, + m.optString("last_seen", null) + ); + } + Label statusLbl = new Label(status); + statusLbl.getStyleClass().add("member-status"); + + nameBox.getChildren().addAll(name, statusLbl); + + // === Role (owner/admin/member) === + Label role = new Label(); + String roleStr = m.optString("role", ""); + if (!roleStr.isBlank()) { + role.setText(roleStr.toLowerCase()); + role.getStyleClass().add("member-role"); + } + + Region spacer = new Region(); + HBox.setHgrow(spacer, Priority.ALWAYS); + + row.getChildren().addAll(avatar, nameBox, spacer, role); + membersList.getChildren().add(row); + } + + private void openAddMemberScene() { + try { + FXMLLoader loader = new FXMLLoader(getClass().getResource( + "/org/to/telegramfinalproject/Fxml/add_member.fxml")); + Node overlay = loader.load(); + + AddMembersController controller = loader.getController(); + // 👇 Pass group id so AddMember scene knows which group to add to + controller.setGroupForAdd(groupId, groupName); + + MainController.getInstance().showOverlay(overlay); + } catch (IOException e) { + e.printStackTrace(); + MainController.getInstance().showAlert( + "Error", + "Could not load Add Member scene.", + Alert.AlertType.ERROR + ); + } + } + + private void handleDeleteGroup() { + System.out.println("Deleting group..."); + // TODO: implement backend call + } + + private void updateIcons(boolean dark) { + String suffix = dark ? "_light.png" : "_dark.png"; + + moreIcon.setImage(loadImage(ICON_PATH + "more" + suffix)); + membersIcon.setImage(loadImage(ICON_PATH + "group" + suffix)); + addMemberButton.setGraphic(makeIcon(ICON_PATH + "add_member" + suffix)); + membersIcon.setImage(loadImage(ICON_PATH + "group_member" + suffix)); + + } + + // --- helpers ------------------------------------------------------------- + + private ImageView makeIcon(String path) { + ImageView iv = new ImageView(); + Image img = loadImage(path); + if (img != null) { + iv.setImage(img); + iv.setFitWidth(22); + iv.setFitHeight(22); + iv.setPreserveRatio(true); + } + return iv; + } + + private Image loadImage(String path) { + URL res = getClass().getResource(path); + if (res == null) return null; + return new Image(res.toExternalForm()); } } diff --git a/src/main/java/org/to/telegramfinalproject/UI/NewChannelController.java b/src/main/java/org/to/telegramfinalproject/UI/NewChannelController.java index 620b4ab..5988989 100644 --- a/src/main/java/org/to/telegramfinalproject/UI/NewChannelController.java +++ b/src/main/java/org/to/telegramfinalproject/UI/NewChannelController.java @@ -63,7 +63,7 @@ public class NewChannelController { createButton.setOnAction(e -> onCreateChannel()); - // محدودیت توضیح + // Limit description final int MAX_LENGTH = 255; channelDescField.addEventFilter(javafx.scene.input.KeyEvent.KEY_TYPED, e -> { if (channelDescField.getText().length() >= MAX_LENGTH) e.consume(); @@ -79,7 +79,7 @@ public class NewChannelController { }); descCounter.setText("0 / 255"); - // پاک کردن استایل خطا هنگام تایپ + // Reset error when user types again channelNameField.textProperty().addListener((obs, ov, nv) -> { if (!nv.trim().isEmpty()) { channelNameField.getStyleClass().remove("error"); diff --git a/src/main/resources/org/to/telegramfinalproject/CSS/dark_theme.css b/src/main/resources/org/to/telegramfinalproject/CSS/dark_theme.css index 5a5e578..f85b051 100644 --- a/src/main/resources/org/to/telegramfinalproject/CSS/dark_theme.css +++ b/src/main/resources/org/to/telegramfinalproject/CSS/dark_theme.css @@ -1132,3 +1132,59 @@ -fx-border-radius: 50%; -fx-border-width: 1; } + +.member-row { + -fx-alignment: CENTER_LEFT; +} + +.member-scroll { + -fx-background-color: transparent; + -fx-border-color: transparent; +} + +.profile-card { + -fx-background-radius: 12; + -fx-background-color: #1f2a36; /* dark gray card */ + -fx-padding: 16; +} + +.profile-title { + -fx-font-size: 16px; + -fx-font-weight: bold; + -fx-text-fill: #e8f1f8; /* light text */ +} + + +.member-row { + -fx-padding: 6 0; +} + +.member-name { + -fx-font-size: 14px; + -fx-font-weight: bold; + -fx-text-fill: #e8f1f8; /* Telegram light text */ +} + +.member-status { + -fx-font-size: 12px; + -fx-text-fill: #8ea1b2; /* grayish */ +} + +.member-role { + -fx-font-size: 12px; + -fx-text-fill: #4fa8f0; /* blue */ + -fx-font-weight: bold; +} + +/* Group name (bold, light text) */ +.info-title { + -fx-font-size: 16px; + -fx-font-weight: bold; + -fx-text-fill: #e8f1f8; /* light gray/white */ +} + +/* Members count (blue link style like Telegram dark mode) */ +.info-subtitle { + -fx-font-size: 13px; + -fx-text-fill: #4fa8f0; /* same Telegram blue */ +} diff --git a/src/main/resources/org/to/telegramfinalproject/CSS/light_theme.css b/src/main/resources/org/to/telegramfinalproject/CSS/light_theme.css index b701157..fd946ba 100644 --- a/src/main/resources/org/to/telegramfinalproject/CSS/light_theme.css +++ b/src/main/resources/org/to/telegramfinalproject/CSS/light_theme.css @@ -1123,3 +1123,58 @@ -fx-border-radius: 50%; -fx-border-width: 1; } + +.member-row { + -fx-alignment: CENTER_LEFT; +} + +.member-scroll { + -fx-background-color: transparent; + -fx-border-color: transparent; +} + +.profile-card { + -fx-background-radius: 12; + -fx-background-color: #ffffff; /* pure white card */ + -fx-padding: 16; +} + +.profile-title { + -fx-font-size: 16px; + -fx-font-weight: bold; + -fx-text-fill: #0f141a; /* dark text */ +} + +.member-row { + -fx-padding: 6 0; +} + +.member-name { + -fx-font-size: 14px; + -fx-font-weight: bold; + -fx-text-fill: #0f141a; /* Telegram dark gray for names */ +} + +.member-status { + -fx-font-size: 12px; + -fx-text-fill: #8ea1b2; /* grayish */ +} + +.member-role { + -fx-font-size: 12px; + -fx-text-fill: #4fa8f0; /* blue */ + -fx-font-weight: bold; +} + +/* Group name (bold, dark text) */ +.info-title { + -fx-font-size: 16px; + -fx-font-weight: bold; + -fx-text-fill: #0f141a; /* deep gray */ +} + +/* Members count (blue link style like Telegram) */ +.info-subtitle { + -fx-font-size: 13px; + -fx-text-fill: #4fa8f0; /* Telegram blue */ +} diff --git a/src/main/resources/org/to/telegramfinalproject/Fxml/chat_page.fxml b/src/main/resources/org/to/telegramfinalproject/Fxml/chat_page.fxml index f208933..308fc29 100644 --- a/src/main/resources/org/to/telegramfinalproject/Fxml/chat_page.fxml +++ b/src/main/resources/org/to/telegramfinalproject/Fxml/chat_page.fxml @@ -50,28 +50,60 @@ - + + - - + - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/org/to/telegramfinalproject/Fxml/group_info.fxml b/src/main/resources/org/to/telegramfinalproject/Fxml/group_info.fxml index 5b28061..a962a03 100644 --- a/src/main/resources/org/to/telegramfinalproject/Fxml/group_info.fxml +++ b/src/main/resources/org/to/telegramfinalproject/Fxml/group_info.fxml @@ -1,14 +1,119 @@ - - - - + + + - + + - + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/org/to/telegramfinalproject/Icons/add_member_dark.png b/src/main/resources/org/to/telegramfinalproject/Icons/add_member_dark.png new file mode 100644 index 0000000..2aab4c1 Binary files /dev/null and b/src/main/resources/org/to/telegramfinalproject/Icons/add_member_dark.png differ diff --git a/src/main/resources/org/to/telegramfinalproject/Icons/add_member_light.png b/src/main/resources/org/to/telegramfinalproject/Icons/add_member_light.png new file mode 100644 index 0000000..a5fdc03 Binary files /dev/null and b/src/main/resources/org/to/telegramfinalproject/Icons/add_member_light.png differ diff --git a/src/main/resources/org/to/telegramfinalproject/Icons/group_dark.png b/src/main/resources/org/to/telegramfinalproject/Icons/group_dark.png new file mode 100644 index 0000000..73e7ab7 Binary files /dev/null and b/src/main/resources/org/to/telegramfinalproject/Icons/group_dark.png differ diff --git a/src/main/resources/org/to/telegramfinalproject/Icons/group_light.png b/src/main/resources/org/to/telegramfinalproject/Icons/group_light.png new file mode 100644 index 0000000..4e802ef Binary files /dev/null and b/src/main/resources/org/to/telegramfinalproject/Icons/group_light.png differ diff --git a/src/main/resources/org/to/telegramfinalproject/Icons/group_member_dark.png b/src/main/resources/org/to/telegramfinalproject/Icons/group_member_dark.png new file mode 100644 index 0000000..4820aa1 Binary files /dev/null and b/src/main/resources/org/to/telegramfinalproject/Icons/group_member_dark.png differ diff --git a/src/main/resources/org/to/telegramfinalproject/Icons/group_member_light.png b/src/main/resources/org/to/telegramfinalproject/Icons/group_member_light.png new file mode 100644 index 0000000..1af6c74 Binary files /dev/null and b/src/main/resources/org/to/telegramfinalproject/Icons/group_member_light.png differ diff --git a/src/main/resources/org/to/telegramfinalproject/Icons/manage_dark.png b/src/main/resources/org/to/telegramfinalproject/Icons/manage_dark.png new file mode 100644 index 0000000..8292878 Binary files /dev/null and b/src/main/resources/org/to/telegramfinalproject/Icons/manage_dark.png differ diff --git a/src/main/resources/org/to/telegramfinalproject/Icons/manage_light.png b/src/main/resources/org/to/telegramfinalproject/Icons/manage_light.png new file mode 100644 index 0000000..9bd2bf8 Binary files /dev/null and b/src/main/resources/org/to/telegramfinalproject/Icons/manage_light.png differ