GameSentenceMiner 2.14.9__py3-none-any.whl → 2.14.10__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. GameSentenceMiner/ai/__init__.py +0 -0
  2. GameSentenceMiner/ai/ai_prompting.py +473 -0
  3. GameSentenceMiner/ocr/__init__.py +0 -0
  4. GameSentenceMiner/ocr/gsm_ocr_config.py +174 -0
  5. GameSentenceMiner/ocr/ocrconfig.py +129 -0
  6. GameSentenceMiner/ocr/owocr_area_selector.py +629 -0
  7. GameSentenceMiner/ocr/owocr_helper.py +638 -0
  8. GameSentenceMiner/ocr/ss_picker.py +140 -0
  9. GameSentenceMiner/owocr/owocr/__init__.py +1 -0
  10. GameSentenceMiner/owocr/owocr/__main__.py +9 -0
  11. GameSentenceMiner/owocr/owocr/config.py +148 -0
  12. GameSentenceMiner/owocr/owocr/lens_betterproto.py +1238 -0
  13. GameSentenceMiner/owocr/owocr/ocr.py +1690 -0
  14. GameSentenceMiner/owocr/owocr/run.py +1818 -0
  15. GameSentenceMiner/owocr/owocr/screen_coordinate_picker.py +109 -0
  16. GameSentenceMiner/tools/__init__.py +0 -0
  17. GameSentenceMiner/tools/audio_offset_selector.py +215 -0
  18. GameSentenceMiner/tools/ss_selector.py +135 -0
  19. GameSentenceMiner/tools/window_transparency.py +214 -0
  20. GameSentenceMiner/util/__init__.py +0 -0
  21. GameSentenceMiner/util/communication/__init__.py +22 -0
  22. GameSentenceMiner/util/communication/send.py +7 -0
  23. GameSentenceMiner/util/communication/websocket.py +94 -0
  24. GameSentenceMiner/util/configuration.py +1199 -0
  25. GameSentenceMiner/util/db.py +408 -0
  26. GameSentenceMiner/util/downloader/Untitled_json.py +472 -0
  27. GameSentenceMiner/util/downloader/__init__.py +0 -0
  28. GameSentenceMiner/util/downloader/download_tools.py +194 -0
  29. GameSentenceMiner/util/downloader/oneocr_dl.py +250 -0
  30. GameSentenceMiner/util/electron_config.py +259 -0
  31. GameSentenceMiner/util/ffmpeg.py +571 -0
  32. GameSentenceMiner/util/get_overlay_coords.py +366 -0
  33. GameSentenceMiner/util/gsm_utils.py +323 -0
  34. GameSentenceMiner/util/model.py +206 -0
  35. GameSentenceMiner/util/notification.py +157 -0
  36. GameSentenceMiner/util/text_log.py +214 -0
  37. GameSentenceMiner/util/win10toast/__init__.py +154 -0
  38. GameSentenceMiner/util/win10toast/__main__.py +22 -0
  39. GameSentenceMiner/web/__init__.py +0 -0
  40. GameSentenceMiner/web/service.py +132 -0
  41. GameSentenceMiner/web/static/__init__.py +0 -0
  42. GameSentenceMiner/web/static/apple-touch-icon.png +0 -0
  43. GameSentenceMiner/web/static/favicon-96x96.png +0 -0
  44. GameSentenceMiner/web/static/favicon.ico +0 -0
  45. GameSentenceMiner/web/static/favicon.svg +3 -0
  46. GameSentenceMiner/web/static/site.webmanifest +21 -0
  47. GameSentenceMiner/web/static/style.css +292 -0
  48. GameSentenceMiner/web/static/web-app-manifest-192x192.png +0 -0
  49. GameSentenceMiner/web/static/web-app-manifest-512x512.png +0 -0
  50. GameSentenceMiner/web/templates/__init__.py +0 -0
  51. GameSentenceMiner/web/templates/index.html +50 -0
  52. GameSentenceMiner/web/templates/text_replacements.html +238 -0
  53. GameSentenceMiner/web/templates/utility.html +483 -0
  54. GameSentenceMiner/web/texthooking_page.py +584 -0
  55. GameSentenceMiner/wip/__init___.py +0 -0
  56. {gamesentenceminer-2.14.9.dist-info → gamesentenceminer-2.14.10.dist-info}/METADATA +1 -1
  57. gamesentenceminer-2.14.10.dist-info/RECORD +79 -0
  58. gamesentenceminer-2.14.9.dist-info/RECORD +0 -24
  59. {gamesentenceminer-2.14.9.dist-info → gamesentenceminer-2.14.10.dist-info}/WHEEL +0 -0
  60. {gamesentenceminer-2.14.9.dist-info → gamesentenceminer-2.14.10.dist-info}/entry_points.txt +0 -0
  61. {gamesentenceminer-2.14.9.dist-info → gamesentenceminer-2.14.10.dist-info}/licenses/LICENSE +0 -0
  62. {gamesentenceminer-2.14.9.dist-info → gamesentenceminer-2.14.10.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,154 @@
1
+ from __future__ import absolute_import
2
+ from __future__ import print_function
3
+ from __future__ import unicode_literals
4
+
5
+ __all__ = ['ToastNotifier']
6
+
7
+ # #############################################################################
8
+ # ########## Libraries #############
9
+ # ##################################
10
+ # standard library
11
+ import logging
12
+ import threading
13
+ from os import path
14
+ from time import sleep
15
+
16
+ # 3rd party modules
17
+ from win32api import GetModuleHandle
18
+ from win32api import PostQuitMessage
19
+ from win32con import CW_USEDEFAULT
20
+ from win32con import IDI_APPLICATION
21
+ from win32con import IMAGE_ICON
22
+ from win32con import LR_DEFAULTSIZE
23
+ from win32con import LR_LOADFROMFILE
24
+ from win32con import WM_DESTROY
25
+ from win32con import WM_USER
26
+ from win32con import WS_OVERLAPPED
27
+ from win32con import WS_SYSMENU
28
+ from win32gui import CreateWindow
29
+ from win32gui import DestroyWindow
30
+ from win32gui import LoadIcon
31
+ from win32gui import LoadImage
32
+ from win32gui import NIF_ICON
33
+ from win32gui import NIF_INFO
34
+ from win32gui import NIF_MESSAGE
35
+ from win32gui import NIF_TIP
36
+ from win32gui import NIM_ADD
37
+ from win32gui import NIM_DELETE
38
+ from win32gui import NIM_MODIFY
39
+ from win32gui import RegisterClass
40
+ from win32gui import UnregisterClass
41
+ from win32gui import Shell_NotifyIcon
42
+ from win32gui import UpdateWindow
43
+ from win32gui import WNDCLASS
44
+
45
+ # ############################################################################
46
+ # ########### Classes ##############
47
+ # ##################################
48
+
49
+
50
+ class ToastNotifier(object):
51
+ """Create a Windows 10 toast notification.
52
+
53
+ from: https://github.com/jithurjacob/Windows-10-Toast-Notifications
54
+ """
55
+
56
+ def __init__(self):
57
+ """Initialize."""
58
+ self._thread = None
59
+
60
+ def _show_toast(self, title, msg,
61
+ icon_path, duration):
62
+ """Notification settings.
63
+
64
+ :title: notification title
65
+ :msg: notification message
66
+ :icon_path: path to the .ico file to custom notification
67
+ :duration: delay in seconds before notification self-destruction
68
+ """
69
+ message_map = {WM_DESTROY: self.on_destroy, }
70
+
71
+ # Register the window class.
72
+ self.wc = WNDCLASS()
73
+ self.hinst = self.wc.hInstance = GetModuleHandle(None)
74
+ self.wc.lpszClassName = str("PythonTaskbar") # must be a string
75
+ self.wc.lpfnWndProc = message_map # could also specify a wndproc.
76
+ try:
77
+ self.classAtom = RegisterClass(self.wc)
78
+ except:
79
+ pass #not sure of this
80
+ style = WS_OVERLAPPED | WS_SYSMENU
81
+ self.hwnd = CreateWindow(self.classAtom, "Taskbar", style,
82
+ 0, 0, CW_USEDEFAULT,
83
+ CW_USEDEFAULT,
84
+ 0, 0, self.hinst, None)
85
+ UpdateWindow(self.hwnd)
86
+
87
+ # icon
88
+ if icon_path is not None:
89
+ icon_path = path.realpath(icon_path)
90
+ else:
91
+ icon_path = path.join(path.dirname(path.abspath(__file__)), 'data', 'python.ico')
92
+ icon_flags = LR_LOADFROMFILE | LR_DEFAULTSIZE
93
+ try:
94
+ hicon = LoadImage(self.hinst, icon_path,
95
+ IMAGE_ICON, 0, 0, icon_flags)
96
+ except Exception as e:
97
+ logging.error("Some trouble with the icon ({}): {}"
98
+ .format(icon_path, e))
99
+ hicon = LoadIcon(0, IDI_APPLICATION)
100
+
101
+ # Taskbar icon
102
+ flags = NIF_ICON | NIF_MESSAGE | NIF_TIP
103
+ nid = (self.hwnd, 0, flags, WM_USER + 20, hicon, "Tooltip")
104
+ Shell_NotifyIcon(NIM_ADD, nid)
105
+ Shell_NotifyIcon(NIM_MODIFY, (self.hwnd, 0, NIF_INFO,
106
+ WM_USER + 20,
107
+ hicon, "Balloon Tooltip", msg, 200,
108
+ title))
109
+ # take a rest then destroy
110
+ sleep(duration)
111
+ DestroyWindow(self.hwnd)
112
+ UnregisterClass(self.wc.lpszClassName, None)
113
+ return None
114
+
115
+ def show_toast(self, title="Notification", msg="Here comes the message",
116
+ icon_path=None, duration=5, threaded=False):
117
+ """Notification settings.
118
+
119
+ :title: notification title
120
+ :msg: notification message
121
+ :icon_path: path to the .ico file to custom notification
122
+ :duration: delay in seconds before notification self-destruction
123
+ """
124
+ if not threaded:
125
+ self._show_toast(title, msg, icon_path, duration)
126
+ else:
127
+ if self.notification_active():
128
+ # We have an active notification, let is finish so we don't spam them
129
+ return False
130
+
131
+ self._thread = threading.Thread(target=self._show_toast, args=(title, msg, icon_path, duration))
132
+ self._thread.start()
133
+ return True
134
+
135
+ def notification_active(self):
136
+ """See if we have an active notification showing"""
137
+ if self._thread != None and self._thread.is_alive():
138
+ # We have an active notification, let is finish we don't spam them
139
+ return True
140
+ return False
141
+
142
+ def on_destroy(self, hwnd, msg, wparam, lparam):
143
+ """Clean after notification ended.
144
+
145
+ :hwnd:
146
+ :msg:
147
+ :wparam:
148
+ :lparam:
149
+ """
150
+ nid = (self.hwnd, 0)
151
+ Shell_NotifyIcon(NIM_DELETE, nid)
152
+ PostQuitMessage(0)
153
+
154
+ return 0
@@ -0,0 +1,22 @@
1
+ from win10toast import ToastNotifier
2
+ import time
3
+
4
+ # #############################################################################
5
+ # ###### Stand alone program ########
6
+ # ###################################
7
+ if __name__ == "__main__":
8
+ # Example
9
+ toaster = ToastNotifier()
10
+ toaster.show_toast(
11
+ "Hello World!!!",
12
+ "Python is 10 seconds awsm!",
13
+ duration=10)
14
+ toaster.show_toast(
15
+ "Example two",
16
+ "This notification is in it's own thread!",
17
+ icon_path=None,
18
+ duration=5,
19
+ threaded=True
20
+ )
21
+ # Wait for threaded notification to finish
22
+ while toaster.notification_active(): time.sleep(0.1)
File without changes
@@ -0,0 +1,132 @@
1
+ import os
2
+ import shutil
3
+ import subprocess
4
+ import threading
5
+
6
+
7
+ from GameSentenceMiner import anki
8
+ from GameSentenceMiner.util import ffmpeg, notification
9
+ from GameSentenceMiner.util.configuration import gsm_state, logger, get_config, get_temporary_directory
10
+ from GameSentenceMiner.util.ffmpeg import get_video_timings
11
+ from GameSentenceMiner.util.text_log import GameLine
12
+
13
+
14
+ def set_get_audio_from_video_callback(func):
15
+ global get_audio_from_video
16
+ get_audio_from_video = func
17
+
18
+
19
+ def handle_texthooker_button(video_path=''):
20
+ try:
21
+ if gsm_state.line_for_audio:
22
+ line: GameLine = gsm_state.line_for_audio
23
+ gsm_state.line_for_audio = None
24
+ if line == gsm_state.previous_line_for_audio:
25
+ logger.info("Line is the same as the last one, skipping processing.")
26
+ if get_config().advanced.audio_player_path:
27
+ play_audio_in_external(gsm_state.previous_audio)
28
+ elif get_config().advanced.video_player_path:
29
+ play_video_in_external(line, video_path)
30
+ else:
31
+ import sounddevice as sd
32
+ data, samplerate = gsm_state.previous_audio
33
+ sd.play(data, samplerate)
34
+ sd.wait()
35
+ return
36
+ gsm_state.previous_line_for_audio = line
37
+ if get_config().advanced.audio_player_path:
38
+ audio = get_audio_from_video(line, line.next.time if line.next else None, video_path,
39
+ temporary=True)
40
+ play_audio_in_external(audio)
41
+ gsm_state.previous_audio = audio
42
+ elif get_config().advanced.video_player_path:
43
+ play_video_in_external(line, video_path)
44
+ else:
45
+ import sounddevice as sd
46
+ import soundfile as sf
47
+ audio = get_audio_from_video(line, line.next.time if line.next else None, video_path,
48
+ temporary=True)
49
+ data, samplerate = sf.read(audio)
50
+ sd.play(data, samplerate)
51
+ sd.wait()
52
+ gsm_state.previous_audio = (data, samplerate)
53
+ return
54
+ if gsm_state.line_for_screenshot:
55
+ line: GameLine = gsm_state.line_for_screenshot
56
+ gsm_state.line_for_screenshot = None
57
+ gsm_state.previous_line_for_screenshot = line
58
+ screenshot = ffmpeg.get_screenshot_for_line(video_path, line, True)
59
+ if gsm_state.anki_note_for_screenshot:
60
+ gsm_state.anki_note_for_screenshot = None
61
+ encoded_image = ffmpeg.process_image(screenshot)
62
+ if get_config().anki.update_anki and get_config().screenshot.screenshot_hotkey_updates_anki:
63
+ last_note = anki.get_last_anki_card()
64
+ if last_note:
65
+ anki.add_image_to_card(last_note, encoded_image)
66
+ notification.send_screenshot_updated(last_note.get_field(get_config().anki.word_field))
67
+ if get_config().features.open_anki_edit:
68
+ notification.open_anki_card(last_note.noteId)
69
+ else:
70
+ notification.send_screenshot_saved(encoded_image)
71
+ else:
72
+ notification.send_screenshot_saved(encoded_image)
73
+ else:
74
+ os.startfile(screenshot)
75
+ return
76
+ except Exception as e:
77
+ logger.error(f"Error Playing Audio/Video: {e}")
78
+ logger.debug(f"Error Playing Audio/Video: {e}", exc_info=True)
79
+ return
80
+ finally:
81
+ gsm_state.previous_replay = video_path
82
+ gsm_state.videos_to_remove.add(video_path)
83
+
84
+
85
+ def play_audio_in_external(filepath):
86
+ exe = get_config().advanced.audio_player_path
87
+
88
+ filepath = os.path.normpath(filepath)
89
+
90
+ command = [exe, "--no-video", filepath]
91
+
92
+ try:
93
+ subprocess.Popen(command)
94
+ print(f"Opened {filepath} in {exe}.")
95
+ except Exception as e:
96
+ print(f"An error occurred: {e}")
97
+
98
+
99
+ def play_video_in_external(line, filepath):
100
+ command = [get_config().advanced.video_player_path]
101
+
102
+ start, _, _, _ = get_video_timings(filepath, line)
103
+
104
+ if start:
105
+ if "vlc" in get_config().advanced.video_player_path:
106
+ command.extend(["--start-time", convert_to_vlc_seconds(start), '--one-instance'])
107
+ else:
108
+ command.extend(["--start", convert_to_vlc_seconds(start)])
109
+ command.append(os.path.normpath(filepath))
110
+
111
+ logger.info(" ".join(command))
112
+
113
+
114
+
115
+ try:
116
+ subprocess.Popen(command)
117
+ logger.info(f"Opened {filepath} in {get_config().advanced.video_player_path}.")
118
+ except FileNotFoundError:
119
+ logger.error("VLC not found. Make sure it's installed and in your PATH.")
120
+ except Exception as e:
121
+ logger.error(f"An error occurred: {e}")
122
+
123
+
124
+ def convert_to_vlc_seconds(time_str):
125
+ """Converts HH:MM:SS.milliseconds to VLC-compatible seconds."""
126
+ try:
127
+ hours, minutes, seconds_ms = time_str.split(":")
128
+ seconds, milliseconds = seconds_ms.split(".")
129
+ total_seconds = (int(hours) * 3600) + (int(minutes) * 60) + int(seconds) + (int(milliseconds) / 1000.0)
130
+ return str(total_seconds)
131
+ except ValueError:
132
+ return "Invalid time format"
File without changes
Binary file
@@ -0,0 +1,3 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:svgjs="http://svgjs.dev/svgjs" width="67" height="64" viewBox="0 0 67 64"><image width="67" height="64" xlink:href="data:image/png;base64,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"></image><style>@media (prefers-color-scheme: light) { :root { filter: none; } }
2
+ @media (prefers-color-scheme: dark) { :root { filter: none; } }
3
+ </style></svg>
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "MyWebSite",
3
+ "short_name": "MySite",
4
+ "icons": [
5
+ {
6
+ "src": "/web-app-manifest-192x192.png",
7
+ "sizes": "192x192",
8
+ "type": "image/png",
9
+ "purpose": "maskable"
10
+ },
11
+ {
12
+ "src": "/web-app-manifest-512x512.png",
13
+ "sizes": "512x512",
14
+ "type": "image/png",
15
+ "purpose": "maskable"
16
+ }
17
+ ],
18
+ "theme_color": "#ffffff",
19
+ "background_color": "#ffffff",
20
+ "display": "standalone"
21
+ }
@@ -0,0 +1,292 @@
1
+ body {
2
+ background-color: #121212;
3
+ color: #e0e0e0;
4
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
5
+ margin: 20px; /* Keep existing margin */
6
+ }
7
+
8
+ h1 {
9
+ color: #ffffff;
10
+ text-align: center;
11
+ font-weight: 300;
12
+ margin-bottom: 24px; /* Added margin to match previous layout */
13
+ }
14
+
15
+ /* Styles for the main container */
16
+ .container {
17
+ max-width: 64rem; /* Equivalent to max-w-4xl */
18
+ margin-left: auto;
19
+ margin-right: auto;
20
+ background-color: #1e1e1e; /* Darker background for the container */
21
+ padding: 32px; /* Equivalent to p-8 */
22
+ border-radius: 8px; /* rounded-lg */
23
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); /* shadow-md */
24
+ }
25
+
26
+
27
+ .inputField {
28
+ background-color: #1e1e1e;
29
+ color: #e0e0e0;
30
+ border: 1px solid #333;
31
+ padding: 10px;
32
+ font-size: 16px;
33
+ /* margin-bottom: 15px; Removed as it's part of flex/gap now */
34
+ border-radius: 5px;
35
+ flex-grow: 1; /* Added to make it fill space in flex container */
36
+ }
37
+ /* Placeholder color for the search input */
38
+ .inputField::placeholder {
39
+ color: #a0a0a0; /* Slightly lighter placeholder */
40
+ }
41
+ .inputField:focus {
42
+ border-color: #1a73e8; /* Highlight color on focus */
43
+ outline: none;
44
+ }
45
+
46
+
47
+ .control {
48
+ margin-bottom: 24px; /* Adjusted margin to match previous layout */
49
+ display: flex;
50
+ flex-direction: column; /* Default to column for small screens */
51
+ gap: 16px; /* Space between items */
52
+ }
53
+
54
+ @media (min-width: 640px) { /* sm breakpoint equivalent */
55
+ .control {
56
+ flex-direction: row; /* Row layout for larger screens */
57
+ gap: 16px; /* Space between items */
58
+ }
59
+ }
60
+
61
+
62
+ button {
63
+ background-color: #1a73e8;
64
+ color: #ffffff;
65
+ border: none;
66
+ padding: 10px 20px;
67
+ font-size: 16px;
68
+ cursor: pointer;
69
+ transition: background-color 0.3s;
70
+ border-radius: 5px;
71
+ }
72
+
73
+ button:disabled {
74
+ background-color: #444;
75
+ cursor: not-allowed;
76
+ }
77
+
78
+ button:hover:not(:disabled) {
79
+ background-color: #1669c1;
80
+ }
81
+
82
+ /* Specific button styles based on previous Tailwind colors */
83
+ .button-blue {
84
+ background-color: #1a73e8; /* Match existing button style */
85
+ }
86
+ .button-blue:hover:not(:disabled) {
87
+ background-color: #1669c1; /* Match existing button hover */
88
+ }
89
+
90
+ .button-green {
91
+ background-color: #34a853; /* Google Green */
92
+ }
93
+ .button-green:hover:not(:disabled) {
94
+ background-color: #2e8b4a;
95
+ }
96
+
97
+ .button-gray {
98
+ background-color: #5f6368; /* Google Gray */
99
+ }
100
+ .button-gray:hover:not(:disabled) {
101
+ background-color: #54575c;
102
+ }
103
+
104
+
105
+ select {
106
+ background-color: #1e1e1e;
107
+ color: #e0e0e0;
108
+ border: 1px solid #333;
109
+ padding: 10px;
110
+ width: 220px;
111
+ font-size: 16px;
112
+ margin-left: 5px;
113
+ margin-right: 5px;
114
+ border-radius: 5px;
115
+ }
116
+
117
+ /* Removed generic div margin to avoid conflicts */
118
+ /* div {
119
+ margin-bottom: 15px;
120
+ } */
121
+
122
+ /* Table Styles */
123
+ .table-container {
124
+ overflow-x: auto;
125
+ border-radius: 8px;
126
+ border: 1px solid #333;
127
+ }
128
+
129
+ .data-table {
130
+ width: 100%; /* Use width instead of min-width */
131
+ border-collapse: collapse;
132
+ }
133
+
134
+ .data-table thead {
135
+ background-color: #333;
136
+ }
137
+
138
+ .data-table th {
139
+ padding: 12px 24px;
140
+ text-align: left;
141
+ font-size: 0.75rem;
142
+ font-weight: 500;
143
+ color: #b0b0b0; /* Lighter gray for header text */
144
+ text-transform: uppercase;
145
+ letter-spacing: 0.05em;
146
+ }
147
+ .data-table th:last-child {
148
+ text-align: right;
149
+ }
150
+
151
+ .data-table tbody tr {
152
+ background-color: #1e1e1e; /* Match input field background */
153
+ border-bottom: 1px solid #333; /* Border between rows */
154
+ }
155
+
156
+ .data-table tbody tr:last-child {
157
+ border-bottom: none; /* No border on the last row */
158
+ }
159
+
160
+
161
+ .data-table td {
162
+ padding: 16px 24px;
163
+ white-space: nowrap;
164
+ font-size: 0.875rem;
165
+ color: #e0e0e0; /* Default text color for cells */
166
+ }
167
+
168
+ .data-table td:nth-child(1) {
169
+ font-weight: 500;
170
+ color: #ffffff; /* White color for the key */
171
+ }
172
+
173
+ .data-table td:last-child {
174
+ text-align: right;
175
+ font-weight: 500;
176
+ display: flex;
177
+ justify-content: flex-end;
178
+ gap: 8px;
179
+ }
180
+
181
+ /* Action buttons in the table */
182
+ /* Styles for Edit Button */
183
+ .action-button.edit-btn {
184
+ color: #a0c3ff; /* Lighter blue for edit */
185
+ cursor: pointer;
186
+ background: none; /* Ensure no background */
187
+ border: none; /* Ensure no border */
188
+ padding: 0; /* Remove padding */
189
+ font-size: 0.875rem;
190
+ text-decoration: underline; /* Add underline for link-like appearance */
191
+ transition: color 0.3s ease; /* Smooth transition for color */
192
+ }
193
+ .action-button.edit-btn:hover {
194
+ color: #7ba7ff; /* Darker blue on hover */
195
+ text-decoration: none; /* Remove underline on hover */
196
+ }
197
+
198
+ /* Styles for Delete Button */
199
+ .action-button.delete-btn {
200
+ color: #ff6b6b; /* Red for delete */
201
+ cursor: pointer;
202
+ background: none; /* Ensure no background */
203
+ border: none; /* Ensure no border */
204
+ padding: 0; /* Remove padding */
205
+ font-size: 0.875rem;
206
+ text-decoration: underline; /* Add underline for link-like appearance */
207
+ transition: color 0.3s ease; /* Smooth transition for color */
208
+ }
209
+ .action-button.delete-btn:hover {
210
+ color: #ff4c4c; /* Darker red on hover */
211
+ text-decoration: none; /* Remove underline on hover */
212
+ }
213
+
214
+
215
+ .no-entries-message {
216
+ color: #b0b0b0; /* Match header text color */
217
+ padding: 16px;
218
+ text-align: center;
219
+ }
220
+
221
+ /* Modal styles */
222
+ .modal {
223
+ display: none; /* Hidden by default */
224
+ position: fixed; /* Stay in place */
225
+ z-index: 1000; /* Sit on top */
226
+ left: 0;
227
+ top: 0;
228
+ width: 100%; /* Full width */
229
+ height: 100%; /* Full height */
230
+ overflow: auto; /* Enable scroll if needed */
231
+ background-color: rgba(0,0,0,0.6); /* Darker overlay */
232
+ align-items: center;
233
+ justify-content: center;
234
+ }
235
+ .modal-content {
236
+ background-color: #1e1e1e; /* Match input field background */
237
+ margin: auto;
238
+ padding: 20px;
239
+ border-radius: 8px;
240
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3); /* Darker shadow */
241
+ width: 90%;
242
+ max-width: 500px;
243
+ color: #e0e0e0;
244
+ }
245
+ .close-button {
246
+ color: #b0b0b0; /* Match header text color */
247
+ float: right;
248
+ font-size: 28px;
249
+ font-weight: bold;
250
+ }
251
+ .close-button:hover,
252
+ .close-button:focus {
253
+ color: #ffffff; /* White on hover */
254
+ text-decoration: none;
255
+ cursor: pointer;
256
+ }
257
+
258
+ .form-group {
259
+ margin-bottom: 15px; /* Consistent margin */
260
+ }
261
+
262
+ .form-label {
263
+ display: block;
264
+ font-size: 0.875rem;
265
+ font-weight: 500;
266
+ color: #b0b0b0; /* Match header text color */
267
+ margin-bottom: 5px; /* Spacing below label */
268
+ }
269
+
270
+ .form-input {
271
+ display: block;
272
+ width: 95%;
273
+ padding: 10px 12px; /* Slightly more padding */
274
+ border: 1px solid #333;
275
+ border-radius: 5px; /* Match other inputs */
276
+ box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
277
+ outline: none;
278
+ background-color: #121212; /* Darker background for input */
279
+ color: #e0e0e0;
280
+ }
281
+ .form-input::placeholder {
282
+ color: #a0a0a0;
283
+ }
284
+ .form-input:focus {
285
+ border-color: #1a73e8;
286
+ box-shadow: 0 0 0 1px #1a73e8;
287
+ }
288
+
289
+ .flex-end {
290
+ display: flex;
291
+ justify-content: flex-end;
292
+ }
File without changes