DawnC commited on
Commit
d8f7637
1 Parent(s): 15d9b0d

Upload 5 files

Browse files
Files changed (5) hide show
  1. breed_comparison.py +371 -0
  2. breed_detection.py +33 -0
  3. html_templates.py +1015 -0
  4. recommendation_html_format.py +496 -0
  5. styles.py +1160 -0
breed_comparison.py ADDED
@@ -0,0 +1,371 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import gradio as gr
3
+ from dog_database import get_dog_description
4
+
5
+ def create_comparison_tab(dog_breeds, get_dog_description):
6
+ """创建品种比较标签页
7
+
8
+ Args:
9
+ dog_breeds: 狗品种列表
10
+ get_dog_description: 获取品种描述的函数
11
+ """
12
+ with gr.TabItem("Breed Comparison"):
13
+ gr.HTML("<p style='text-align: center;'>Select two dog breeds to compare their characteristics and care requirements.</p>")
14
+
15
+ with gr.Row():
16
+ breed1_dropdown = gr.Dropdown(
17
+ choices=dog_breeds,
18
+ label="Select First Breed",
19
+ value="Golden_Retriever"
20
+ )
21
+ breed2_dropdown = gr.Dropdown(
22
+ choices=dog_breeds,
23
+ label="Select Second Breed",
24
+ value="Border_Collie"
25
+ )
26
+
27
+ compare_btn = gr.Button("Compare Breeds")
28
+ comparison_output = gr.HTML(label="Comparison Results")
29
+
30
+ def get_comparison_styles():
31
+ return """
32
+ /* Comparison specific styles */
33
+ .comparison-grid {
34
+ display: flex;
35
+ flex-direction: column;
36
+ gap: 0;
37
+ position: relative;
38
+ }
39
+
40
+ .breed-column {
41
+ padding: 24px;
42
+ position: relative;
43
+ }
44
+
45
+ .breed-column:first-child {
46
+ margin-bottom: 60px;
47
+ padding-bottom: 40px;
48
+ }
49
+
50
+ .breed-column:first-child::after {
51
+ content: '';
52
+ position: absolute;
53
+ bottom: -30px;
54
+ left: 0;
55
+ right: 0;
56
+ height: 2px;
57
+ background: linear-gradient(
58
+ to right,
59
+ transparent,
60
+ #cbd5e0 10%,
61
+ #cbd5e0 90%,
62
+ transparent
63
+ );
64
+ box-shadow: 0 1px 2px rgba(0,0,0,0.1);
65
+ }
66
+
67
+ .breed-column:first-child::before {
68
+ content: '•••';
69
+ position: absolute;
70
+ bottom: -38px;
71
+ left: 50%;
72
+ transform: translateX(-50%);
73
+ font-size: 24px;
74
+ letter-spacing: 8px;
75
+ color: #94a3b8;
76
+ text-align: center;
77
+ background: white;
78
+ padding: 0 20px;
79
+ z-index: 1;
80
+ }
81
+
82
+ .breed-column:first-child .action-section {
83
+ margin-bottom: 0;
84
+ padding-bottom: 0;
85
+ }
86
+
87
+ @media (max-width: 768px) {
88
+ .breed-column:first-child {
89
+ margin-bottom: 50px;
90
+ padding-bottom: 30px;
91
+ }
92
+
93
+ .breed-column:first-child::after {
94
+ bottom: -25px;
95
+ }
96
+
97
+ .breed-column:first-child::before {
98
+ bottom: -33px;
99
+ font-size: 20px;
100
+ }
101
+ }
102
+
103
+ .dog-info-card {
104
+ background: white;
105
+ position: relative;
106
+ z-index: 0;
107
+ }
108
+
109
+ .breed-column:nth-child(2) {
110
+ position: relative;
111
+ margin-top: 20px;
112
+ }
113
+ """
114
+
115
+ def show_comparison(breed1, breed2):
116
+ if not breed1 or not breed2:
117
+ return "Please select two breeds to compare"
118
+
119
+ # 获取所有信息
120
+ breed1_info = get_dog_description(breed1)
121
+ breed2_info = get_dog_description(breed2)
122
+ breed1_noise = breed_noise_info.get(breed1, {}).get('noise_notes', '').strip().split('\n')
123
+ breed2_noise = breed_noise_info.get(breed2, {}).get('noise_notes', '').strip().split('\n')
124
+ breed1_health = breed_health_info.get(breed1, {}).get('health_notes', '').strip().split('\n')
125
+ breed2_health = breed_health_info.get(breed2, {}).get('health_notes', '').strip().split('\n')
126
+
127
+ def format_noise_info(noise_data):
128
+ characteristics = []
129
+ triggers = []
130
+ noise_level = "Moderate" # 默认值
131
+
132
+ in_characteristics = False
133
+ in_triggers = False
134
+
135
+ for line in noise_data:
136
+ line = line.strip()
137
+ if "Typical noise characteristics:" in line:
138
+ in_characteristics = True
139
+ continue
140
+ elif "Barking triggers:" in line:
141
+ in_triggers = True
142
+ in_characteristics = False
143
+ continue
144
+ elif "Noise level:" in line:
145
+ noise_level = line.split(':')[1].strip()
146
+ continue
147
+
148
+ if line.startswith('•'):
149
+ if in_characteristics:
150
+ characteristics.append(line[1:].strip())
151
+ elif in_triggers:
152
+ triggers.append(line[1:].strip())
153
+
154
+ return {
155
+ 'characteristics': characteristics,
156
+ 'triggers': triggers,
157
+ 'noise_level': noise_level
158
+ }
159
+
160
+ def format_health_info(health_data):
161
+ considerations = []
162
+ screenings = []
163
+
164
+ in_considerations = False
165
+ in_screenings = False
166
+
167
+ for line in health_data:
168
+ line = line.strip()
169
+ if "Common breed-specific health considerations" in line:
170
+ in_considerations = True
171
+ in_screenings = False
172
+ continue
173
+ elif "Recommended health screenings:" in line:
174
+ in_screenings = True
175
+ in_considerations = False
176
+ continue
177
+
178
+ if line.startswith('•'):
179
+ if in_considerations:
180
+ considerations.append(line[1:].strip())
181
+ elif in_screenings:
182
+ screenings.append(line[1:].strip())
183
+
184
+ return {
185
+ 'considerations': considerations,
186
+ 'screenings': screenings
187
+ }
188
+
189
+ def create_breed_column(breed, info, noise_data, health_data):
190
+ noise_info = format_noise_info(noise_data)
191
+ health_info = format_health_info(health_data)
192
+
193
+ basic_info = f"""
194
+ <div class="breed-column">
195
+ <h2 class="section-title">
196
+ <span class="icon">🐕</span> {breed.replace('_', ' ')}
197
+ </h2>
198
+
199
+ <div class="info-section">
200
+ <div class="info-item">
201
+ <span class="tooltip">
202
+ <span class="icon">📏</span>
203
+ <span class="label">Size:</span>
204
+ <span class="tooltip-icon">ⓘ</span>
205
+ <span class="tooltip-text">
206
+ <strong>Size Categories:</strong><br>
207
+ • Small: Under 20 pounds<br>
208
+ • Medium: 20-60 pounds<br>
209
+ • Large: Over 60 pounds
210
+ </span>
211
+ <span class="value">{info.get('Size', 'Not available')}</span>
212
+ </span>
213
+ </div>
214
+ <div class="info-item">
215
+ <span class="tooltip">
216
+ <span class="icon">🏃</span>
217
+ <span class="label">Exercise:</span>
218
+ <span class="tooltip-icon">ⓘ</span>
219
+ <span class="tooltip-text">
220
+ <strong>Exercise Needs:</strong><br>
221
+ • Low: Short walks<br>
222
+ • Moderate: 1-2 hours daily<br>
223
+ • High: 2+ hours daily
224
+ </span>
225
+ <span class="value">{info.get('Exercise Needs', 'Not available')}</span>
226
+ </span>
227
+ </div>
228
+ <div class="info-item">
229
+ <span class="tooltip">
230
+ <span class="icon">✂️</span>
231
+ <span class="label">Grooming:</span>
232
+ <span class="tooltip-icon">ⓘ</span>
233
+ <span class="tooltip-text">
234
+ <strong>Grooming Requirements:</strong><br>
235
+ • Low: Occasional brushing<br>
236
+ • Moderate: Weekly grooming<br>
237
+ • High: Daily maintenance
238
+ </span>
239
+ <span class="value">{info.get('Grooming Needs', 'Not available')}</span>
240
+ </span>
241
+ </div>
242
+ <div class="info-item">
243
+ <span class="tooltip">
244
+ <span class="icon">👨‍👩‍👧‍👦</span>
245
+ <span class="label">With Children:</span>
246
+ <span class="tooltip-icon">ⓘ</span>
247
+ <span class="tooltip-text">
248
+ <strong>Child Compatibility:</strong><br>
249
+ • Yes: Excellent with kids<br>
250
+ • Moderate: Good with older children<br>
251
+ • No: Better for adult households
252
+ </span>
253
+ <span class="value">{info.get('Good with Children', 'Not available')}</span>
254
+ </span>
255
+ </div>
256
+ <div class="info-item">
257
+ <span class="tooltip">
258
+ <span class="icon">⏳</span>
259
+ <span class="label">Lifespan:</span>
260
+ <span class="tooltip-icon">ⓘ</span>
261
+ <span class="tooltip-text">
262
+ <strong>Average Lifespan:</strong><br>
263
+ • Short: 6-8 years<br>
264
+ • Average: 10-15 years<br>
265
+ • Long: 12-20 years
266
+ </span>
267
+ <span class="value">{info.get('Lifespan', 'Not available')}</span>
268
+ </span>
269
+ </div>
270
+ </div>
271
+ """
272
+
273
+ # Noise Section
274
+ noise_section = f"""
275
+ <div class="noise-section">
276
+ <h3 class="section-header">
277
+ <span class="icon">🔊</span> Noise Behavior
278
+ <span class="tooltip">
279
+ <span class="tooltip-icon">ⓘ</span>
280
+ <span class="tooltip-text">Information about typical barking patterns and noise levels</span>
281
+ </span>
282
+ </h3>
283
+ <div class="noise-info">
284
+ <div class="noise-details">
285
+ <h4 class="section-header">Typical noise characteristics:</h4>
286
+ <div class="characteristics-list">
287
+ {' '.join([f'<div class="list-item">{item}</div>' for item in noise_info['characteristics']]) or '<div class="list-item">Information not available</div>'}
288
+ </div>
289
+
290
+ <div class="noise-level-display">
291
+ <h4 class="section-header">Noise level:</h4>
292
+ <div class="level-indicator">
293
+ <span class="level-text">{noise_info['noise_level']}</span>
294
+ </div>
295
+ </div>
296
+
297
+ <h4 class="section-header">Barking triggers:</h4>
298
+ <div class="triggers-list">
299
+ {' '.join([f'<div class="list-item">{item}</div>' for item in noise_info['triggers']]) or '<div class="list-item">Information not available</div>'}
300
+ </div>
301
+ </div>
302
+ </div>
303
+ </div>
304
+ """
305
+
306
+ # Health Section
307
+ health_section = f"""
308
+ <div class="health-section">
309
+ <h3 class="section-header">
310
+ <span class="icon">🏥</span> Health Insights
311
+ <span class="tooltip">
312
+ <span class="tooltip-icon">ⓘ</span>
313
+ <span class="tooltip-text">
314
+ Health information is compiled from multiple sources including veterinary resources,
315
+ breed guides, and international canine health databases.
316
+ </span>
317
+ </span>
318
+ </h3>
319
+ <div class="health-info">
320
+ <div class="health-details">
321
+ <h4 class="section-header">Common health considerations:</h4>
322
+ <div class="health-grid">
323
+ {' '.join([f'<div class="health-item">{item}</div>' for item in health_info['considerations']]) or '<div class="health-item">Information not available</div>'}
324
+ </div>
325
+
326
+ <h4 class="section-header">Recommended screenings:</h4>
327
+ <div class="health-grid">
328
+ {' '.join([f'<div class="health-item screening">{item}</div>' for item in health_info['screenings']]) or '<div class="health-item screening">Information not available</div>'}
329
+ </div>
330
+ </div>
331
+ </div>
332
+ </div>
333
+
334
+ <div class="action-section">
335
+ <a href="https://www.akc.org/dog-breeds/{breed.lower().replace('_', '-')}/"
336
+ target="_blank"
337
+ class="akc-button">
338
+ <span class="icon">🌐</span>
339
+ Learn More about {breed.replace('_', ' ')} on AKC
340
+ </a>
341
+ </div>
342
+ </div>
343
+ """
344
+
345
+ return basic_info + noise_section + health_section
346
+
347
+ html_output = f"""
348
+ <div class="dog-info-card">
349
+ <div class="comparison-grid">
350
+ {create_breed_column(breed1, breed1_info, breed1_noise, breed1_health)}
351
+ {create_breed_column(breed2, breed2_info, breed2_noise, breed2_health)}
352
+ </div>
353
+ <style>
354
+ {get_comparison_styles()}
355
+ </style>
356
+ </div>
357
+ """
358
+ return html_output
359
+
360
+ compare_btn.click(
361
+ show_comparison,
362
+ inputs=[breed1_dropdown, breed2_dropdown],
363
+ outputs=comparison_output
364
+ )
365
+
366
+ return {
367
+ 'breed1_dropdown': breed1_dropdown,
368
+ 'breed2_dropdown': breed2_dropdown,
369
+ 'compare_btn': compare_btn,
370
+ 'comparison_output': comparison_output
371
+ }
breed_detection.py ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import re
2
+ import gradio as gr
3
+ from PIL import Image
4
+
5
+ def create_detection_tab(predict_fn, example_images):
6
+ with gr.TabItem("Breed Detection"):
7
+ gr.HTML("<p style='text-align: center;'>Upload a picture of a dog, and the model will predict its breed and provide detailed information!</p>")
8
+ gr.HTML("<p style='text-align: center; color: #666; font-size: 0.9em;'>Note: The model's predictions may not always be 100% accurate, and it is recommended to use the results as a reference.</p>")
9
+
10
+ with gr.Row():
11
+ input_image = gr.Image(label="Upload a dog image", type="pil")
12
+ output_image = gr.Image(label="Annotated Image")
13
+
14
+ output = gr.HTML(label="Prediction Results")
15
+ initial_state = gr.State()
16
+
17
+ input_image.change(
18
+ predict_fn,
19
+ inputs=input_image,
20
+ outputs=[output, output_image, initial_state]
21
+ )
22
+
23
+ gr.Examples(
24
+ examples=example_images,
25
+ inputs=input_image
26
+ )
27
+
28
+ return {
29
+ 'input_image': input_image,
30
+ 'output_image': output_image,
31
+ 'output': output,
32
+ 'initial_state': initial_state
33
+ }
html_templates.py ADDED
@@ -0,0 +1,1015 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ from typing import Dict, List, Union, Any, Optional, Callable
3
+ from urllib.parse import quote
4
+
5
+ def get_akc_breeds_link(breed: str) -> str:
6
+ """Generate AKC breed page URL with intelligent name handling."""
7
+ breed_name = breed.lower()
8
+ breed_name = breed_name.replace('_', '-')
9
+ breed_name = breed_name.replace("'", '')
10
+ breed_name = breed_name.replace(" ", '-')
11
+
12
+ special_cases = {
13
+ 'mexican-hairless': 'xoloitzcuintli',
14
+ 'brabancon-griffon': 'brussels-griffon',
15
+ 'bull-mastiff': 'bullmastiff',
16
+ 'walker-hound': 'treeing-walker-coonhound'
17
+ }
18
+
19
+ breed_name = special_cases.get(breed_name, breed_name)
20
+ return f"https://www.akc.org/dog-breeds/{breed_name}/"
21
+
22
+ def get_color_scheme(is_single_dog: bool) -> Union[str, List[str]]:
23
+ """Get color scheme for dog detection visualization."""
24
+ single_dog_color = '#34C759' # 清爽的綠色作為單狗顏色
25
+ color_list = [
26
+ '#FF5733', # 珊瑚紅
27
+ '#28A745', # 深綠色
28
+ '#3357FF', # 寶藍色
29
+ '#FF33F5', # 粉紫色
30
+ '#FFB733', # 橙黃色
31
+ '#33FFF5', # 青藍色
32
+ '#A233FF', # 紫色
33
+ '#FF3333', # 紅色
34
+ '#33FFB7', # 青綠色
35
+ '#FFE033' # 金黃色
36
+ ]
37
+ return single_dog_color if is_single_dog else color_list
38
+
39
+ def format_warning_html(message: str) -> str:
40
+ """Format warning messages in a consistent style."""
41
+ return f'''
42
+ <div class="dog-info-card">
43
+ <div class="breed-info">
44
+ <p class="warning-message">
45
+ <span class="icon">⚠️</span>
46
+ {message}
47
+ </p>
48
+ </div>
49
+ </div>
50
+ '''
51
+
52
+
53
+ def format_error_message(color: str, index: int) -> str:
54
+ """Format error message when confidence is too low."""
55
+ return f'''
56
+ <div class="dog-info-card" style="border-left: 8px solid {color};">
57
+ <div class="dog-info-header" style="background-color: {color}10;">
58
+ <span class="dog-label" style="color: {color};">Dog {index}</span>
59
+ </div>
60
+ <div class="breed-info">
61
+ <div class="warning-message">
62
+ <span class="icon">⚠️</span>
63
+ The image is unclear or the breed is not in the dataset. Please upload a clearer image.
64
+ </div>
65
+ </div>
66
+ </div>
67
+ '''
68
+
69
+ def format_description_html(description: Dict[str, Any], breed: str) -> str:
70
+ """Format basic breed description with tooltips."""
71
+ if not isinstance(description, dict):
72
+ return f"<p>{description}</p>"
73
+
74
+ fields_order = [
75
+ "Size", "Lifespan", "Temperament", "Exercise Needs",
76
+ "Grooming Needs", "Care Level", "Good with Children",
77
+ "Description"
78
+ ]
79
+
80
+ html_parts = []
81
+ for field in fields_order:
82
+ if field in description:
83
+ value = description[field]
84
+ tooltip_html = format_tooltip(field, value)
85
+ html_parts.append(f'<li style="margin-bottom: 10px;">{tooltip_html}</li>')
86
+
87
+ # Add any remaining fields
88
+ for key, value in description.items():
89
+ if key not in fields_order and key != "Breed":
90
+ html_parts.append(f'<li style="margin-bottom: 10px;"><strong>{key}:</strong> {value}</li>')
91
+
92
+ return f'<ul style="list-style-type: none; padding-left: 0;">{" ".join(html_parts)}</ul>'
93
+
94
+
95
+ def format_tooltip(key: str, value: str) -> str:
96
+ """Format tooltip with content for each field."""
97
+ tooltip_contents = {
98
+ "Size": {
99
+ "title": "Size Categories",
100
+ "items": [
101
+ "Small: Under 20 pounds",
102
+ "Medium: 20-60 pounds",
103
+ "Large: Over 60 pounds",
104
+ "Giant: Over 100 pounds",
105
+ "Varies: Depends on variety"
106
+ ]
107
+ },
108
+ "Exercise Needs": {
109
+ "title": "Exercise Needs",
110
+ "items": [
111
+ "Low: Short walks and play sessions",
112
+ "Moderate: 1-2 hours of daily activity",
113
+ "High: Extensive exercise (2+ hours/day)",
114
+ "Very High: Constant activity and mental stimulation needed"
115
+ ]
116
+ },
117
+ "Grooming Needs": {
118
+ "title": "Grooming Requirements",
119
+ "items": [
120
+ "Low: Basic brushing, occasional baths",
121
+ "Moderate: Weekly brushing, occasional grooming",
122
+ "High: Daily brushing, frequent professional grooming needed",
123
+ "Professional care recommended for all levels"
124
+ ]
125
+ },
126
+ "Care Level": {
127
+ "title": "Care Level Explained",
128
+ "items": [
129
+ "Low: Basic care and attention needed",
130
+ "Moderate: Regular care and routine needed",
131
+ "High: Significant time and attention needed",
132
+ "Very High: Extensive care, training and attention required"
133
+ ]
134
+ },
135
+ "Good with Children": {
136
+ "title": "Child Compatibility",
137
+ "items": [
138
+ "Yes: Excellent with kids, patient and gentle",
139
+ "Moderate: Good with older children",
140
+ "No: Better suited for adult households"
141
+ ]
142
+ },
143
+ "Lifespan": {
144
+ "title": "Average Lifespan",
145
+ "items": [
146
+ "Short: 6-8 years",
147
+ "Average: 10-15 years",
148
+ "Long: 12-20 years",
149
+ "Varies by size: Larger breeds typically have shorter lifespans"
150
+ ]
151
+ },
152
+ "Temperament": {
153
+ "title": "Temperament Guide",
154
+ "items": [
155
+ "Describes the dog's natural behavior and personality",
156
+ "Important for matching with owner's lifestyle",
157
+ "Can be influenced by training and socialization"
158
+ ]
159
+ }
160
+ }
161
+
162
+ tooltip = tooltip_contents.get(key, {"title": key, "items": []})
163
+ tooltip_content = "<br>".join([f"• {item}" for item in tooltip["items"]])
164
+
165
+ return f'''
166
+ <span class="tooltip">
167
+ <strong>{key}:</strong>
168
+ <span class="tooltip-icon">ⓘ</span>
169
+ <span class="tooltip-text">
170
+ <strong>{tooltip["title"]}:</strong><br>
171
+ {tooltip_content}
172
+ </span>
173
+ </span> {value}
174
+ '''
175
+
176
+ def format_single_dog_result(breed: str, description: Dict[str, Any], color: str = "#34C759") -> str:
177
+ """Format single dog detection result into HTML."""
178
+ # 獲取noise和health資訊
179
+ noise_info = breed_noise_info.get(breed, {})
180
+ health_info = breed_health_info.get(breed, {})
181
+
182
+ # 處理噪音資訊
183
+ noise_notes = noise_info.get('noise_notes', '').split('\n')
184
+ noise_characteristics = []
185
+ barking_triggers = []
186
+ noise_level = noise_info.get('noise_level', 'Information not available')
187
+
188
+ in_section = None
189
+ for line in noise_notes:
190
+ line = line.strip()
191
+ if 'Typical noise characteristics:' in line:
192
+ in_section = 'characteristics'
193
+ elif 'Barking triggers:' in line:
194
+ in_section = 'triggers'
195
+ elif line.startswith('•'):
196
+ if in_section == 'characteristics':
197
+ noise_characteristics.append(line[1:].strip())
198
+ elif in_section == 'triggers':
199
+ barking_triggers.append(line[1:].strip())
200
+
201
+ # 處理健康資訊
202
+ health_notes = health_info.get('health_notes', '').split('\n')
203
+ health_considerations = []
204
+ health_screenings = []
205
+
206
+ in_section = None
207
+ for line in health_notes:
208
+ line = line.strip()
209
+ if 'Common breed-specific health considerations' in line:
210
+ in_section = 'considerations'
211
+ elif 'Recommended health screenings:' in line:
212
+ in_section = 'screenings'
213
+ elif line.startswith('•'):
214
+ if in_section == 'considerations':
215
+ health_considerations.append(line[1:].strip())
216
+ elif in_section == 'screenings':
217
+ health_screenings.append(line[1:].strip())
218
+
219
+ return f'''
220
+ <div class="dog-info-card" style="background: {color}10;">
221
+ <div class="breed-title" style="display: flex; align-items: center; gap: 10px; margin: 0 0 20px 20px;">
222
+ <span class="icon" style="font-size: 2.0em;">🐾</span>
223
+ <h2 style="
224
+ margin: 0;
225
+ padding: 10px 20px;
226
+ background: ${color}15;
227
+ border-left: 4px solid ${color};
228
+ border-radius: 6px;
229
+ color: ${color};
230
+ font-weight: 600;
231
+ font-size: 2.0em;
232
+ letter-spacing: 0.5px;">
233
+ {breed.replace('_', ' ')}
234
+ </h2>
235
+ </div>
236
+
237
+ <div class="breed-info">
238
+ <!-- Basic Information -->
239
+ <div class="section-header" style="margin-bottom: 15px;">
240
+ <h3>
241
+ <span class="icon">📋</span> BASIC INFORMATION
242
+ </h3>
243
+ </div>
244
+ <div class="info-cards" style="display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin-bottom: 30px;">
245
+ <div class="info-card" style="background: #fff; padding: 15px; border-radius: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.1);">
246
+ <span class="tooltip">
247
+ <span class="icon">📏</span>
248
+ <span class="label">Size</span>
249
+ <span class="tooltip-icon">ⓘ</span>
250
+ <span class="tooltip-text">
251
+ <strong>Size Categories:</strong><br>
252
+ • Small: Under 20 pounds<br>
253
+ • Medium: 20-60 pounds<br>
254
+ • Large: Over 60 pounds<br>
255
+ • Giant: Over 100 pounds
256
+ </span>
257
+ </span>
258
+ <span>{description['Size']}</span>
259
+ </div>
260
+ <div class="info-card" style="background: #fff; padding: 15px; border-radius: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.1);">
261
+ <span class="tooltip">
262
+ <span class="icon">⏳</span>
263
+ <span class="label">Lifespan</span>
264
+ <span class="tooltip-icon">ⓘ</span>
265
+ <span class="tooltip-text">
266
+ <strong>Lifespan Categories:</strong><br>
267
+ • Short: 6-8 years<br>
268
+ • Average: 10-15 years<br>
269
+ • Long: 12-20 years
270
+ </span>
271
+ </span>
272
+ <span>{description['Lifespan']}</span>
273
+ </div>
274
+ </div>
275
+
276
+ <!-- Care Requirements -->
277
+ <div class="section-header" style="margin-bottom: 15px;">
278
+ <h3>
279
+ <span class="icon">💪</span> CARE REQUIREMENTS
280
+ </h3>
281
+ </div>
282
+ <div class="info-cards" style="display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 20px; margin-bottom: 30px;">
283
+ <div class="info-card" style="background: #fff; padding: 15px; border-radius: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.1);">
284
+ <span class="tooltip">
285
+ <span class="icon">🏃</span>
286
+ <span class="label">Exercise</span>
287
+ <span class="tooltip-icon">ⓘ</span>
288
+ <span class="tooltip-text">
289
+ <strong>Exercise Needs:</strong><br>
290
+ • Low: Short walks<br>
291
+ • Moderate: 1-2 hours daily<br>
292
+ • High: 2+ hours daily
293
+ </span>
294
+ </span>
295
+ <span>{description['Exercise Needs']}</span>
296
+ </div>
297
+ <div class="info-card" style="background: #fff; padding: 15px; border-radius: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.1);">
298
+ <span class="tooltip">
299
+ <span class="icon">✂️</span>
300
+ <span class="label">Grooming</span>
301
+ <span class="tooltip-icon">ⓘ</span>
302
+ <span class="tooltip-text">
303
+ <strong>Grooming Requirements:</strong><br>
304
+ • Low: Basic brushing<br>
305
+ • Moderate: Weekly grooming<br>
306
+ • High: Daily maintenance
307
+ </span>
308
+ </span>
309
+ <span>{description['Grooming Needs']}</span>
310
+ </div>
311
+ <div class="info-card" style="background: #fff; padding: 15px; border-radius: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.1);">
312
+ <span class="tooltip">
313
+ <span class="icon">⭐</span>
314
+ <span class="label">Care Level</span>
315
+ <span class="tooltip-icon">ⓘ</span>
316
+ <span class="tooltip-text">
317
+ <strong>Care Level:</strong><br>
318
+ • Low: Basic care<br>
319
+ • Moderate: Regular care<br>
320
+ • High: Extensive care
321
+ </span>
322
+ </span>
323
+ <span>{description['Care Level']}</span>
324
+ </div>
325
+ </div>
326
+
327
+ <!-- Noise Behavior -->
328
+ <div class="section-header" style="margin-bottom: 15px;">
329
+ <h3>
330
+ <span class="tooltip">
331
+ <span class="icon">🔊</span>
332
+ <span>NOISE BEHAVIOR</span>
333
+ <span class="tooltip-icon">ⓘ</span>
334
+ <span class="tooltip-text">
335
+ <strong>Noise Behavior:</strong><br>
336
+ • Typical vocalization patterns<br>
337
+ • Common triggers and frequency<br>
338
+ • Based on breed characteristics
339
+ </span>
340
+ </span>
341
+ </h3>
342
+ </div>
343
+ <div class="noise-section" style="background: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); margin-bottom: 30px;">
344
+ <div class="noise-level" style="margin-bottom: 15px;">
345
+ <span class="label">Noise Level:</span>
346
+ <span class="value">{noise_level}</span>
347
+ </div>
348
+ <div class="characteristics-list">
349
+ {format_noise_items(noise_characteristics[:2])}
350
+ </div>
351
+
352
+ <details class="expandable-section" style="margin-top: 15px;">
353
+ <summary class="expand-header" style="cursor: pointer; padding: 10px; background: #f8f9fa; border-radius: 6px;">
354
+ Show Complete Noise Information
355
+ <span class="expand-icon">▼</span>
356
+ </summary>
357
+ <div class="expanded-content" style="margin-top: 15px;">
358
+ <div class="characteristics-list">
359
+ <h4>All Characteristics:</h4>
360
+ {format_noise_items(noise_characteristics)}
361
+ </div>
362
+ <div class="triggers-list">
363
+ <h4>Barking Triggers:</h4>
364
+ {format_noise_items(barking_triggers)}
365
+ </div>
366
+ <div class="disclaimer-section" style="margin-top: 15px; font-size: 0.9em; color: #666;">
367
+ <p class="disclaimer-text source-text">Source: Compiled from various breed behavior resources, 2024</p>
368
+ <p class="disclaimer-text">Individual dogs may vary in their vocalization patterns.</p>
369
+ <p class="disclaimer-text">Training can significantly influence barking behavior.</p>
370
+ <p class="disclaimer-text">Environmental factors may affect noise levels.</p>
371
+ </div>
372
+ </div>
373
+ </details>
374
+ </div>
375
+
376
+ <!-- Health Insights -->
377
+ <div class="section-header" style="margin-bottom: 15px;">
378
+ <h3>
379
+ <span class="tooltip">
380
+ <span class="icon">🏥</span>
381
+ <span>HEALTH INSIGHTS</span>
382
+ <span class="tooltip-icon">ⓘ</span>
383
+ <span class="tooltip-text">
384
+ <strong>Health Information:</strong><br>
385
+ • Common breed-specific conditions<br>
386
+ • Recommended health screenings<br>
387
+ • General health considerations
388
+ </span>
389
+ </span>
390
+ </h3>
391
+ </div>
392
+ <div class="health-section" style="background: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); margin-bottom: 30px;">
393
+ <div class="key-considerations">
394
+ {format_health_items(health_considerations[:2])}
395
+ </div>
396
+
397
+ <details class="expandable-section" style="margin-top: 15px;">
398
+ <summary class="expand-header" style="cursor: pointer; padding: 10px; background: #f8f9fa; border-radius: 6px;">
399
+ Show Complete Health Information
400
+ <span class="expand-icon">▼</span>
401
+ </summary>
402
+ <div class="expanded-content" style="margin-top: 15px;">
403
+ <div class="considerations-list">
404
+ <h4>All Health Considerations:</h4>
405
+ {format_health_items(health_considerations)}
406
+ </div>
407
+ <div class="screenings-list">
408
+ <h4>Recommended Screenings:</h4>
409
+ {format_health_items(health_screenings)}
410
+ </div>
411
+ <div class="disclaimer-section" style="margin-top: 15px; font-size: 0.9em; color: #666;">
412
+ <p class="disclaimer-text source-text">Source: Compiled from various veterinary and breed information resources, 2024</p>
413
+ <p class="disclaimer-text">This information is for reference only and based on breed tendencies.</p>
414
+ <p class="disclaimer-text">Each dog is unique and may not develop any or all of these conditions.</p>
415
+ <p class="disclaimer-text">Always consult with qualified veterinarians for professional advice.</p>
416
+ </div>
417
+ </div>
418
+ </details>
419
+ </div>
420
+
421
+ <!-- Description -->
422
+ <div class="section-header" style="margin-bottom: 15px;">
423
+ <h3>
424
+ <span class="icon">📝</span> DESCRIPTION
425
+ </h3>
426
+ </div>
427
+ <div class="description-section" style="background: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); margin-bottom: 30px;">
428
+ <p style="line-height: 1.6;">{description.get('Description', '')}</p>
429
+ </div>
430
+
431
+ <!-- Action Section -->
432
+ <div class="action-section" style="text-align: center; margin-top: 30px;">
433
+ <a href="{get_akc_breeds_link(breed)}"
434
+ target="_blank"
435
+ class="akc-button"
436
+ style="display: inline-block; padding: 12px 24px; background: linear-gradient(90deg, #4299e1, #48bb78); color: white; text-decoration: none; border-radius: 6px;">
437
+ <span class="icon">🌐</span>
438
+ Learn more about {breed} on AKC website
439
+ </a>
440
+ </div>
441
+ </div>
442
+ </div>
443
+ '''
444
+
445
+
446
+ def format_noise_items(items: List[str]) -> str:
447
+ """Format noise-related items into HTML list items."""
448
+ if not items:
449
+ return "<div class='list-item'>Information not available</div>"
450
+ return "\n".join([f"<div class='list-item'>• {item}</div>" for item in items])
451
+
452
+ def format_health_items(items: List[str]) -> str:
453
+ """Format health-related items into HTML list items."""
454
+ if not items:
455
+ return "<div class='list-item'>Information not available</div>"
456
+ return "\n".join([f"<div class='list-item'>• {item}</div>" for item in items])
457
+
458
+
459
+ def format_multiple_breeds_result(
460
+ topk_breeds: List[str],
461
+ relative_probs: List[str],
462
+ color: str,
463
+ index: int,
464
+ get_dog_description: Callable
465
+ ) -> str:
466
+ """Format multiple breed predictions into HTML with complete information."""
467
+ result = f'''
468
+ <!-- 主標題區塊 -->
469
+ <div style="background: {color}10; border-radius: 12px 12px 0 0; padding: 20px;">
470
+ <div style="display: flex; align-items: center;">
471
+ <span style="font-size: 1.6em; color: {color}; margin-right: 12px;">🐾</span>
472
+ <span style="font-size: 1.4em; font-weight: 600; color: {color};">Dog {index}</span>
473
+ </div>
474
+ </div>
475
+
476
+ <!-- 內容區塊 -->
477
+ <div style="padding: 20px;">
478
+ <!-- 不確定性提示 -->
479
+ <div style="margin-bottom: 20px;">
480
+ <div style="display: flex; align-items: center; gap: 8px; padding: 12px; background: #f8f9fa; border-radius: 8px;">
481
+ <span>ℹ️</span>
482
+ <span>Note: The model is showing some uncertainty in its predictions.
483
+ Here are the most likely breeds based on the available visual features.</span>
484
+ </div>
485
+ </div>
486
+
487
+ <!-- 品種列表容器 -->
488
+ <div class="breeds-list" style="display: grid; gap: 20px;">
489
+ '''
490
+
491
+ for j, (breed, prob) in enumerate(zip(topk_breeds, relative_probs)):
492
+ description = get_dog_description(breed)
493
+ noise_info = breed_noise_info.get(breed, {})
494
+ health_info = breed_health_info.get(breed, {})
495
+
496
+ # 處理噪音資訊
497
+ noise_notes = noise_info.get('noise_notes', '').split('\n')
498
+ noise_characteristics = []
499
+ barking_triggers = []
500
+ noise_level = noise_info.get('noise_level', 'Information not available')
501
+
502
+ in_section = None
503
+ for line in noise_notes:
504
+ line = line.strip()
505
+ if 'Typical noise characteristics:' in line:
506
+ in_section = 'characteristics'
507
+ elif 'Barking triggers:' in line:
508
+ in_section = 'triggers'
509
+ elif line.startswith('•'):
510
+ if in_section == 'characteristics':
511
+ noise_characteristics.append(line[1:].strip())
512
+ elif in_section == 'triggers':
513
+ barking_triggers.append(line[1:].strip())
514
+
515
+ # 處理健康資訊
516
+ health_notes = health_info.get('health_notes', '').split('\n')
517
+ health_considerations = []
518
+ health_screenings = []
519
+
520
+ in_section = None
521
+ for line in health_notes:
522
+ line = line.strip()
523
+ if 'Common breed-specific health considerations' in line:
524
+ in_section = 'considerations'
525
+ elif 'Recommended health screenings:' in line:
526
+ in_section = 'screenings'
527
+ elif line.startswith('•'):
528
+ if in_section == 'considerations':
529
+ health_considerations.append(line[1:].strip())
530
+ elif in_section == 'screenings':
531
+ health_screenings.append(line[1:].strip())
532
+
533
+ result += f'''
534
+ <div class="dog-info-card" style="background: #fff; border-radius: 12px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); margin-bottom: 48px;">
535
+ <div style="border-left: 8px solid {color};">
536
+ <!-- Title Section -->
537
+ <div class="breed-title" style="padding: 24px; background: #f8f9fa;">
538
+ <div style="display: flex; align-items: center; justify-content: space-between; padding: 12px; background: #fff; border-radius: 8px; box-shadow: 0 1px 2px rgba(0,0,0,0.05);">
539
+ <div style="display: flex; align-items: center; gap: 14px;">
540
+ <span style="font-size: 1.4em;">🐾</span>
541
+ <h2 style="margin: 0; font-size: 1.8em; color: #1a202c; font-weight: 600;">
542
+ {'Option ' + str(j+1) + ': ' if prob else ''}{breed}
543
+ </h2>
544
+ </div>
545
+ {f'<span style="background: {color}12; color: {color}; padding: 8px 16px; border-radius: 8px; font-size: 1em; font-weight: 500; box-shadow: 0 1px 2px {color}20;">Confidence: {prob}</span>' if prob else ''}
546
+ </div>
547
+ </div>
548
+
549
+ <div class="breed-info" style="padding: 24px;">
550
+ <!-- Basic Information -->
551
+ <div style="margin-bottom: 32px;">
552
+ <h3 style="display: flex; align-items: center; gap: 10px; margin: 0 0 20px 0; padding: 12px; background: #f8f9fa; border-radius: 6px;">
553
+ <span style="font-size: 1.2em;">📋</span>
554
+ <span style="font-size: 1.2em; font-weight: 600; color: #2d3748;">BASIC INFORMATION</span>
555
+ </h3>
556
+ <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 24px;">
557
+ <!-- Size -->
558
+ <div style="padding: 16px; border-radius: 10px; background: #fff; border: 1px solid #e2e8f0;">
559
+ <div style="display: flex; align-items: center; gap: 10px;">
560
+ <span style="font-size: 1.1em;">📏</span>
561
+ <span style="font-weight: 500;">Size</span>
562
+ <div style="position: relative; display: inline-block;"
563
+ onmouseover="this.querySelector('.tooltip-content').style.visibility='visible';this.querySelector('.tooltip-content').style.opacity='1';"
564
+ onmouseout="this.querySelector('.tooltip-content').style.visibility='hidden';this.querySelector('.tooltip-content').style.opacity='0';">
565
+ <span style="cursor: help; color: #718096;">ⓘ</span>
566
+ <div class="tooltip-content" style="
567
+ visibility: hidden;
568
+ opacity: 0;
569
+ position: absolute;
570
+ background: #2C3E50;
571
+ color: white;
572
+ padding: 12px;
573
+ border-radius: 8px;
574
+ font-size: 14px;
575
+ width: 250px;
576
+ {f'top: -130px; left: 0;' if not prob else 'top: 50%; right: -270px; transform: translateY(-50%); left: auto;'};
577
+ z-index: 99999;
578
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
579
+ pointer-events: none;">
580
+ <strong style="display: block; margin-bottom: 8px; color: white;">Size Categories:</strong>
581
+ • Small: Under 20 pounds<br>
582
+ • Medium: 20-60 pounds<br>
583
+ • Large: Over 60 pounds<br>
584
+ • Giant: Over 100 pounds
585
+ <div style="position: absolute;
586
+ {f'left: 20px; bottom: -8px; border-top: 8px solid #2C3E50;' if not prob else 'top: 50%; right: 100%; transform: translateY(-50%); border-right: 8px solid #2C3E50;'};
587
+ width: 0;
588
+ height: 0;
589
+ border-left: 8px solid transparent;
590
+ border-right: 8px solid transparent;">
591
+ </div>
592
+ </div>
593
+ </div>
594
+ </div>
595
+ <span style="display: block; margin-top: 8px; color: #4a5568;">{description['Size']}</span>
596
+ </div>
597
+
598
+ <!-- Lifespan -->
599
+ <div style="padding: 16px; border-radius: 10px; background: #fff; border: 1px solid #e2e8f0;">
600
+ <div style="display: flex; align-items: center; gap: 10px;">
601
+ <span style="font-size: 1.1em;">⏳</span>
602
+ <span style="font-weight: 500;">Lifespan</span>
603
+ <div style="position: relative; display: inline-block;"
604
+ onmouseover="this.querySelector('.tooltip-content').style.visibility='visible';this.querySelector('.tooltip-content').style.opacity='1';"
605
+ onmouseout="this.querySelector('.tooltip-content').style.visibility='hidden';this.querySelector('.tooltip-content').style.opacity='0';">
606
+ <span style="cursor: help; color: #718096;">ⓘ</span>
607
+ <div class="tooltip-content" style="
608
+ visibility: hidden;
609
+ opacity: 0;
610
+ position: absolute;
611
+ background: #2C3E50;
612
+ color: white;
613
+ padding: 12px;
614
+ border-radius: 8px;
615
+ font-size: 14px;
616
+ width: 250px;
617
+ {f'top: -130px; left: 0;' if not prob else 'top: 50%; right: -270px; transform: translateY(-50%); left: auto;'};
618
+ z-index: 99999;
619
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
620
+ pointer-events: none;">
621
+ <strong style="display: block; margin-bottom: 8px; color: white;">Lifespan Categories:</strong>
622
+ • Short: 6-8 years<br>
623
+ • Average: 10-15 years<br>
624
+ • Long: 12-20 years
625
+ <div style="position: absolute;
626
+ {f'left: 20px; bottom: -8px; border-top: 8px solid #2C3E50;' if not prob else 'top: 50%; right: 100%; transform: translateY(-50%); border-right: 8px solid #2C3E50;'};
627
+ width: 0;
628
+ height: 0;
629
+ border-left: 8px solid transparent;
630
+ border-right: 8px solid transparent;">
631
+ </div>
632
+ </div>
633
+ </div>
634
+ </div>
635
+ <span style="display: block; margin-top: 8px; color: #4a5568;">{description['Lifespan']}</span>
636
+ </div>
637
+ </div>
638
+ </div>
639
+
640
+ <!-- Care Requirements -->
641
+ <div style="margin-bottom: 32px;">
642
+ <h3 style="display: flex; align-items: center; gap: 10px; margin: 0 0 20px 0; padding: 12px; background: #f8f9fa; border-radius: 6px;">
643
+ <span style="font-size: 1.2em;">💪</span>
644
+ <span style="font-size: 1.2em; font-weight: 600; color: #2d3748;">CARE REQUIREMENTS</span>
645
+ </h3>
646
+ <div style="display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 24px;">
647
+ <!-- Exercise -->
648
+ <div style="padding: 16px; border-radius: 10px; background: #fff; border: 1px solid #e2e8f0;">
649
+ <div style="display: flex; align-items: center; gap: 10px;">
650
+ <span style="font-size: 1.1em;">🏃</span>
651
+ <span style="font-weight: 500;">Exercise</span>
652
+ <div style="position: relative; display: inline-block;"
653
+ onmouseover="this.querySelector('.tooltip-content').style.visibility='visible';this.querySelector('.tooltip-content').style.opacity='1';"
654
+ onmouseout="this.querySelector('.tooltip-content').style.visibility='hidden';this.querySelector('.tooltip-content').style.opacity='0';">
655
+ <span style="cursor: help; color: #718096;">ⓘ</span>
656
+ <div class="tooltip-content" style="
657
+ visibility: hidden;
658
+ opacity: 0;
659
+ position: absolute;
660
+ background: #2C3E50;
661
+ color: white;
662
+ padding: 12px;
663
+ border-radius: 8px;
664
+ font-size: 14px;
665
+ width: 250px;
666
+ {f'top: -130px; left: 0;' if not prob else 'top: 50%; right: -270px; transform: translateY(-50%); left: auto;'};
667
+ z-index: 99999;
668
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
669
+ pointer-events: none;">
670
+ <strong style="display: block; margin-bottom: 8px; color: white;">Exercise Needs:</strong>
671
+ • Low: Short walks<br>
672
+ • Moderate: 1-2 hours daily<br>
673
+ • High: 2+ hours daily
674
+ <div style="position: absolute;
675
+ {f'left: 20px; bottom: -8px; border-top: 8px solid #2C3E50;' if not prob else 'top: 50%; right: 100%; transform: translateY(-50%); border-right: 8px solid #2C3E50;'};
676
+ width: 0;
677
+ height: 0;
678
+ border-left: 8px solid transparent;
679
+ border-right: 8px solid transparent;">
680
+ </div>
681
+ </div>
682
+ </div>
683
+ </div>
684
+ <span style="display: block; margin-top: 8px; color: #4a5568;">{description['Exercise Needs']}</span>
685
+ </div>
686
+
687
+ <!-- Grooming -->
688
+ <div style="padding: 16px; border-radius: 10px; background: #fff; border: 1px solid #e2e8f0;">
689
+ <div style="display: flex; align-items: center; gap: 10px;">
690
+ <span style="font-size: 1.1em;">✂️</span>
691
+ <span style="font-weight: 500;">Grooming</span>
692
+ <div style="position: relative; display: inline-block;"
693
+ onmouseover="this.querySelector('.tooltip-content').style.visibility='visible';this.querySelector('.tooltip-content').style.opacity='1';"
694
+ onmouseout="this.querySelector('.tooltip-content').style.visibility='hidden';this.querySelector('.tooltip-content').style.opacity='0';">
695
+ <span style="cursor: help; color: #718096;">ⓘ</span>
696
+ <div class="tooltip-content" style="
697
+ visibility: hidden;
698
+ opacity: 0;
699
+ position: absolute;
700
+ background: #2C3E50;
701
+ color: white;
702
+ padding: 12px;
703
+ border-radius: 8px;
704
+ font-size: 14px;
705
+ width: 250px;
706
+ {f'top: -130px; left: 0;' if not prob else 'top: 50%; right: -270px; transform: translateY(-50%); left: auto;'};
707
+ z-index: 99999;
708
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
709
+ pointer-events: none;">
710
+ <strong style="display: block; margin-bottom: 8px; color: white;">Grooming Requirements:</strong>
711
+ • Low: Basic brushing<br>
712
+ • Moderate: Weekly grooming<br>
713
+ • High: Daily maintenance
714
+ <div style="position: absolute;
715
+ {f'left: 20px; bottom: -8px; border-top: 8px solid #2C3E50;' if not prob else 'top: 50%; right: 100%; transform: translateY(-50%); border-right: 8px solid #2C3E50;'};
716
+ width: 0;
717
+ height: 0;
718
+ border-left: 8px solid transparent;
719
+ border-right: 8px solid transparent;">
720
+ </div>
721
+ </div>
722
+ </div>
723
+ </div>
724
+ <span style="display: block; margin-top: 8px; color: #4a5568;">{description['Grooming Needs']}</span>
725
+ </div>
726
+
727
+ <!-- Care Level -->
728
+ <div style="padding: 16px; border-radius: 10px; background: #fff; border: 1px solid #e2e8f0;">
729
+ <div style="display: flex; align-items: center; gap: 10px;">
730
+ <span style="font-size: 1.1em;">⭐</span>
731
+ <span style="font-weight: 500;">Care Level</span>
732
+ <div style="position: relative; display: inline-block;"
733
+ onmouseover="this.querySelector('.tooltip-content').style.visibility='visible';this.querySelector('.tooltip-content').style.opacity='1';"
734
+ onmouseout="this.querySelector('.tooltip-content').style.visibility='hidden';this.querySelector('.tooltip-content').style.opacity='0';">
735
+ <span style="cursor: help; color: #718096;">ⓘ</span>
736
+ <div class="tooltip-content" style="
737
+ visibility: hidden;
738
+ opacity: 0;
739
+ position: absolute;
740
+ background: #2C3E50;
741
+ color: white;
742
+ padding: 12px;
743
+ border-radius: 8px;
744
+ font-size: 14px;
745
+ width: 250px;
746
+ {f'top: -130px; left: 0;' if not prob else 'top: 50%; right: -270px; transform: translateY(-50%); left: auto;'};
747
+ z-index: 99999;
748
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
749
+ pointer-events: none;">
750
+ <strong style="display: block; margin-bottom: 8px; color: white;">Care Level:</strong>
751
+ • Low: Basic care<br>
752
+ • Moderate: Regular care<br>
753
+ • High: Extensive care
754
+ <div style="position: absolute;
755
+ {f'left: 20px; bottom: -8px; border-top: 8px solid #2C3E50;' if not prob else 'top: 50%; right: 100%; transform: translateY(-50%); border-right: 8px solid #2C3E50;'};
756
+ width: 0;
757
+ height: 0;
758
+ border-left: 8px solid transparent;
759
+ border-right: 8px solid transparent;">
760
+ </div>
761
+ </div>
762
+ </div>
763
+ </div>
764
+ <span style="display: block; margin-top: 8px; color: #4a5568;">{description['Care Level']}</span>
765
+ </div>
766
+ </div>
767
+ </div>
768
+
769
+ <!-- Noise Behavior -->
770
+ <div style="margin-bottom: 32px;">
771
+ <h3 style="display: flex; align-items: center; gap: 10px; margin: 0 0 20px 0; padding: 12px; background: #f8f9fa; border-radius: 6px;">
772
+ <span style="font-size: 1.2em;">🔊</span>
773
+ <span style="font-size: 1.2em; font-weight: 600; color: #2d3748;">NOISE BEHAVIOR</span>
774
+ <div style="position: relative; display: inline-block;"
775
+ onmouseover="this.querySelector('.tooltip-content').style.visibility='visible';this.querySelector('.tooltip-content').style.opacity='1';"
776
+ onmouseout="this.querySelector('.tooltip-content').style.visibility='hidden';this.querySelector('.tooltip-content').style.opacity='0';">
777
+ <span style="cursor: help; color: #718096;">ⓘ</span>
778
+ <div class="tooltip-content" style="
779
+ visibility: hidden;
780
+ opacity: 0;
781
+ position: absolute;
782
+ background: #2C3E50;
783
+ color: white;
784
+ padding: 12px;
785
+ border-radius: 8px;
786
+ font-size: 14px;
787
+ width: 250px;
788
+ {f'top: -130px; left: 0;' if not prob else 'top: 50%; right: -270px; transform: translateY(-50%); left: auto;'};
789
+ z-index: 99999;
790
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
791
+ pointer-events: none;">
792
+ <strong style="display: block; margin-bottom: 8px; color: white;">Noise Behavior:</strong>
793
+ • Typical vocalization patterns<br>
794
+ • Common triggers and frequency<br>
795
+ • Based on breed characteristics
796
+ <div style="position: absolute;
797
+ {f'left: 20px; bottom: -8px; border-top: 8px solid #2C3E50;' if not prob else 'top: 50%; right: 100%; transform: translateY(-50%); border-right: 8px solid #2C3E50;'};
798
+ width: 0;
799
+ height: 0;
800
+ border-left: 8px solid transparent;
801
+ border-right: 8px solid transparent;">
802
+ </div>
803
+ </div>
804
+ </div>
805
+ </h3>
806
+ <div style="background: #fff; padding: 20px; border-radius: 8px; border: 1px solid #e2e8f0;">
807
+ <div style="margin-bottom: 16px;">
808
+ <div style="display: flex; align-items: center; gap: 8px; margin-bottom: 12px;">
809
+ <span style="font-weight: 500;">Noise Level:</span>
810
+ <span>{noise_level}</span>
811
+ </div>
812
+ <div style="margin-bottom: 16px;">
813
+ {format_noise_items(noise_characteristics[:2])}
814
+ </div>
815
+ </div>
816
+ <details style="margin-top: 20px;">
817
+ <summary style="cursor: pointer; padding: 12px; background: #f8f9fa; border-radius: 8px; border: 1px solid #e2e8f0; outline: none; list-style-type: none;">
818
+ <span style="display: flex; align-items: center; justify-content: space-between;">
819
+ <span style="font-weight: 500;">Show Complete Noise Information</span>
820
+ <span style="transition: transform 0.2s;">▶</span>
821
+ </span>
822
+ </summary>
823
+ <!-- 展開內容部分 -->
824
+ <div style="margin-top: 16px; padding: 20px; border: 1px solid #e2e8f0; border-radius: 8px; background: #fff;">
825
+ <div style="margin-bottom: 24px;">
826
+ <h4 style="margin: 0 0 12px 0; color: #2d3748;">All Characteristics</h4>
827
+ {format_noise_items(noise_characteristics)}
828
+ </div>
829
+ <div style="margin-bottom: 24px;">
830
+ <h4 style="margin: 0 0 12px 0; color: #2d3748;">Barking Triggers</h4>
831
+ {format_noise_items(barking_triggers)}
832
+ </div>
833
+ <div style="margin-top: 20px; padding-top: 16px; border-top: 1px solid #e2e8f0;">
834
+ <p style="margin: 0 0 8px 0; color: #4a5568; font-size: 0.9em;">Source: Compiled from various breed behavior resources, 2024</p>
835
+ <p style="margin: 0 0 8px 0; color: #4a5568; font-size: 0.9em;">Individual dogs may vary in their vocalization patterns.</p>
836
+ <p style="margin: 0 0 8px 0; color: #4a5568; font-size: 0.9em;">Training can significantly influence barking behavior.</p>
837
+ <p style="margin: 0 0 8px 0; color: #4a5568; font-size: 0.9em;">Environmental factors may affect noise levels.</p>
838
+ </div>
839
+ </div>
840
+ </details>
841
+ </div>
842
+ </div>
843
+
844
+ <!-- Health Insights -->
845
+ <div style="margin-bottom: 32px;">
846
+ <h3 style="display: flex; align-items: center; gap: 10px; margin: 0 0 20px 0; padding: 12px; background: #f8f9fa; border-radius: 6px;">
847
+ <span style="font-size: 1.2em;">🏥</span>
848
+ <span style="font-size: 1.2em; font-weight: 600; color: #2d3748;">HEALTH INSIGHTS</span>
849
+ <div style="position: relative; display: inline-block;"
850
+ onmouseover="this.querySelector('.tooltip-content').style.visibility='visible';this.querySelector('.tooltip-content').style.opacity='1';"
851
+ onmouseout="this.querySelector('.tooltip-content').style.visibility='hidden';this.querySelector('.tooltip-content').style.opacity='0';">
852
+ <span style="cursor: help; color: #718096;">ⓘ</span>
853
+ <div class="tooltip-content" style="
854
+ visibility: hidden;
855
+ opacity: 0;
856
+ position: absolute;
857
+ background: #2C3E50;
858
+ color: white;
859
+ padding: 12px;
860
+ border-radius: 8px;
861
+ font-size: 14px;
862
+ width: 250px;
863
+ {f'top: -130px; left: 0;' if not prob else 'top: 50%; right: -270px; transform: translateY(-50%); left: auto;'};
864
+ z-index: 99999;
865
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
866
+ pointer-events: none;">
867
+ <strong style="display: block; margin-bottom: 8px; color: white;">Health Information:</strong>
868
+ • Common breed-specific conditions<br>
869
+ • Recommended health screenings<br>
870
+ • General health considerations
871
+ <div style="position: absolute;
872
+ {f'left: 20px; bottom: -8px; border-top: 8px solid #2C3E50;' if not prob else 'top: 50%; right: 100%; transform: translateY(-50%); border-right: 8px solid #2C3E50;'};
873
+ width: 0;
874
+ height: 0;
875
+ border-left: 8px solid transparent;
876
+ border-right: 8px solid transparent;">
877
+ </div>
878
+ </div>
879
+ </div>
880
+ </h3>
881
+ <div style="background: #fff; padding: 20px; border-radius: 8px; border: 1px solid #e2e8f0;">
882
+ <div style="margin-bottom: 16px;">
883
+ {format_health_items(health_considerations[:2])}
884
+ </div>
885
+ <details style="margin-top: 20px;">
886
+ <summary style="cursor: pointer; padding: 12px; background: #f8f9fa; border-radius: 8px; border: 1px solid #e2e8f0; outline: none; list-style-type: none;">
887
+ <span style="display: flex; align-items: center; justify-content: space-between;">
888
+ <span style="font-weight: 500;">Show Complete Health Information</span>
889
+ <span style="transition: transform 0.2s;">▶</span>
890
+ </span>
891
+ </summary>
892
+ <div style="margin-top: 16px; padding: 20px; border: 1px solid #e2e8f0; border-radius: 8px; background: #fff;">
893
+ <div style="margin-bottom: 24px;">
894
+ <h4 style="margin: 0 0 12px 0; color: #2d3748;">All Health Considerations</h4>
895
+ {format_health_items(health_considerations)}
896
+ </div>
897
+ <div style="margin-bottom: 24px;">
898
+ <h4 style="margin: 0 0 12px 0; color: #2d3748;">Recommended Screenings</h4>
899
+ {format_health_items(health_screenings)}
900
+ </div>
901
+ <div style="margin-top: 20px; padding-top: 16px; border-top: 1px solid #e2e8f0;">
902
+ <p style="margin: 0 0 8px 0; color: #4a5568; font-size: 0.9em;">Source: Compiled from veterinary resources and breed health studies, 2024</p>
903
+ <p style="margin: 0 0 8px 0; color: #4a5568; font-size: 0.9em;">Regular vet check-ups are essential for all breeds.</p>
904
+ <p style="margin: 0 0 8px 0; color: #4a5568; font-size: 0.9em;">Early detection and prevention are key to managing health issues.</p>
905
+ <p style="margin: 0 0 8px 0; color: #4a5568; font-size: 0.9em;">Not all dogs will develop these conditions.</p>
906
+ </div>
907
+ </div>
908
+ </details>
909
+ </div>
910
+ </div>
911
+
912
+ <!-- Description -->
913
+ <div style="margin-bottom: 32px;">
914
+ <h3 style="display: flex; align-items: center; gap: 10px; margin: 0 0 20px 0; padding: 12px; background: #f8f9fa; border-radius: 6px;">
915
+ <span style="font-size: 1.2em;">📝</span>
916
+ <span style="font-size: 1.2em; font-weight: 600; color: #2d3748;">DESCRIPTION</span>
917
+ </h3>
918
+ <div style="background: #fff; padding: 20px; border-radius: 8px; border: 1px solid #e2e8f0;">
919
+ <p style="line-height: 1.6; margin: 0; color: #2d3748;">{description.get('Description', '')}</p>
920
+ </div>
921
+ </div>
922
+
923
+ <!-- Action Section -->
924
+ <div style="text-align: center; margin-top: 32px;">
925
+ <a href="{get_akc_breeds_link(breed)}"
926
+ target="_blank"
927
+ rel="noopener noreferrer"
928
+ style="display: inline-flex; align-items: center; gap: 8px; padding: 14px 28px; background: linear-gradient(90deg, #4299e1, #48bb78); color: white; text-decoration: none; border-radius: 8px; font-weight: 500; transition: opacity 0.2s; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
929
+ <span style="font-size: 1.2em;">🌐</span>
930
+ Learn more about {breed} on AKC website
931
+ </a>
932
+ </div>
933
+ </div>
934
+ </div>
935
+ </div>
936
+ '''
937
+
938
+ return result
939
+
940
+
941
+ def format_multi_dog_container(dogs_info: str) -> str:
942
+ """Wrap multiple dog detection results in a container."""
943
+ return f"""
944
+ <div class="dog-info-card">
945
+ {dogs_info}
946
+ </div>
947
+ """
948
+
949
+ def format_breed_details_html(description: Dict[str, Any], breed: str) -> str:
950
+ """Format breed details for the show_details_html function."""
951
+ return f"""
952
+ <div class="dog-info">
953
+ <h2>{breed}</h2>
954
+ <div class="breed-details">
955
+ {format_description_html(description, breed)}
956
+ <div class="action-section">
957
+ <a href="{get_akc_breeds_link(breed)}" target="_blank" class="akc-button">
958
+ <span class="icon">🌐</span>
959
+ Learn more about {breed} on AKC website
960
+ </a>
961
+ </div>
962
+ </div>
963
+ </div>
964
+ """
965
+
966
+ def format_comparison_result(breed1: str, breed2: str, comparison_data: Dict) -> str:
967
+ """Format breed comparison results into HTML."""
968
+ return f"""
969
+ <div class="comparison-container">
970
+ <div class="comparison-header">
971
+ <h3>Comparison: {breed1} vs {breed2}</h3>
972
+ </div>
973
+ <div class="comparison-content">
974
+ <div class="breed-column">
975
+ <h4>{breed1}</h4>
976
+ {format_comparison_details(comparison_data[breed1])}
977
+ </div>
978
+ <div class="breed-column">
979
+ <h4>{breed2}</h4>
980
+ {format_comparison_details(comparison_data[breed2])}
981
+ </div>
982
+ </div>
983
+ </div>
984
+ """
985
+
986
+ def format_comparison_details(breed_data: Dict) -> str:
987
+ """Format individual breed details for comparison."""
988
+ original_data = breed_data.get('Original_Data', {})
989
+ return f"""
990
+ <div class="comparison-details">
991
+ <p><strong>Size:</strong> {original_data.get('Size', 'N/A')}</p>
992
+ <p><strong>Exercise Needs:</strong> {original_data.get('Exercise Needs', 'N/A')}</p>
993
+ <p><strong>Care Level:</strong> {original_data.get('Care Level', 'N/A')}</p>
994
+ <p><strong>Grooming Needs:</strong> {original_data.get('Grooming Needs', 'N/A')}</p>
995
+ <p><strong>Good with Children:</strong> {original_data.get('Good with Children', 'N/A')}</p>
996
+ <p><strong>Temperament:</strong> {original_data.get('Temperament', 'N/A')}</p>
997
+ </div>
998
+ """
999
+
1000
+ def format_header_html() -> str:
1001
+ """Format the application header HTML."""
1002
+ return """
1003
+ <header style='text-align: center; padding: 20px; margin-bottom: 20px;'>
1004
+ <h1 style='font-size: 2.5em; margin-bottom: 10px; color: #2D3748;'>
1005
+ 🐾 PawMatch AI
1006
+ </h1>
1007
+ <h2 style='font-size: 1.2em; font-weight: normal; color: #4A5568; margin-top: 5px;'>
1008
+ Your Smart Dog Breed Guide
1009
+ </h2>
1010
+ <div style='width: 50px; height: 3px; background: linear-gradient(90deg, #4299e1, #48bb78); margin: 15px auto;'></div>
1011
+ <p style='color: #718096; font-size: 0.9em;'>
1012
+ Powered by AI • Breed Recognition • Smart Matching • Companion Guide
1013
+ </p>
1014
+ </header>
1015
+ """
recommendation_html_format.py ADDED
@@ -0,0 +1,496 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sqlite3
2
+ import traceback
3
+ from typing import List, Dict
4
+ from breed_health_info import breed_health_info, default_health_note
5
+ from breed_noise_info import breed_noise_info
6
+ from dog_database import get_dog_description
7
+ from scoring_calculation_system import (
8
+ UserPreferences,
9
+ calculate_compatibility_score
10
+ )
11
+
12
+ def format_recommendation_html(recommendations: List[Dict]) -> str:
13
+ """將推薦結果格式化為HTML"""
14
+ html_content = "<div class='recommendations-container'>"
15
+
16
+ for rec in recommendations:
17
+ breed = rec['breed']
18
+ scores = rec['scores']
19
+ info = rec['info']
20
+ rank = rec.get('rank', 0)
21
+ final_score = rec.get('final_score', scores['overall'])
22
+ bonus_score = rec.get('bonus_score', 0)
23
+
24
+ health_info = breed_health_info.get(breed, {"health_notes": default_health_note})
25
+ noise_info = breed_noise_info.get(breed, {
26
+ "noise_notes": "Noise information not available",
27
+ "noise_level": "Unknown",
28
+ "source": "N/A"
29
+ })
30
+
31
+ # 解析噪音資訊
32
+ noise_notes = noise_info.get('noise_notes', '').split('\n')
33
+ noise_characteristics = []
34
+ barking_triggers = []
35
+ noise_level = ''
36
+
37
+ current_section = None
38
+ for line in noise_notes:
39
+ line = line.strip()
40
+ if 'Typical noise characteristics:' in line:
41
+ current_section = 'characteristics'
42
+ elif 'Noise level:' in line:
43
+ noise_level = line.replace('Noise level:', '').strip()
44
+ elif 'Barking triggers:' in line:
45
+ current_section = 'triggers'
46
+ elif line.startswith('•'):
47
+ if current_section == 'characteristics':
48
+ noise_characteristics.append(line[1:].strip())
49
+ elif current_section == 'triggers':
50
+ barking_triggers.append(line[1:].strip())
51
+
52
+ # 生成特徵和觸發因素的HTML
53
+ noise_characteristics_html = '\n'.join([f'<li>{item}</li>' for item in noise_characteristics])
54
+ barking_triggers_html = '\n'.join([f'<li>{item}</li>' for item in barking_triggers])
55
+
56
+ # 處理健康資訊
57
+ health_notes = health_info.get('health_notes', '').split('\n')
58
+ health_considerations = []
59
+ health_screenings = []
60
+
61
+ current_section = None
62
+ for line in health_notes:
63
+ line = line.strip()
64
+ if 'Common breed-specific health considerations' in line:
65
+ current_section = 'considerations'
66
+ elif 'Recommended health screenings:' in line:
67
+ current_section = 'screenings'
68
+ elif line.startswith('•'):
69
+ if current_section == 'considerations':
70
+ health_considerations.append(line[1:].strip())
71
+ elif current_section == 'screenings':
72
+ health_screenings.append(line[1:].strip())
73
+
74
+ health_considerations_html = '\n'.join([f'<li>{item}</li>' for item in health_considerations])
75
+ health_screenings_html = '\n'.join([f'<li>{item}</li>' for item in health_screenings])
76
+
77
+ # 獎勵原因計算
78
+ bonus_reasons = []
79
+ temperament = info.get('Temperament', '').lower()
80
+ if any(trait in temperament for trait in ['friendly', 'gentle', 'affectionate']):
81
+ bonus_reasons.append("Positive temperament traits")
82
+ if info.get('Good with Children') == 'Yes':
83
+ bonus_reasons.append("Excellent with children")
84
+ try:
85
+ lifespan = info.get('Lifespan', '10-12 years')
86
+ years = int(lifespan.split('-')[0])
87
+ if years > 12:
88
+ bonus_reasons.append("Above-average lifespan")
89
+ except:
90
+ pass
91
+
92
+ html_content += f"""
93
+ <div class="dog-info-card recommendation-card">
94
+ <div class="breed-info">
95
+ <h2 class="section-title">
96
+ <span class="icon">🏆</span> #{rank} {breed.replace('_', ' ')}
97
+ <span class="score-badge">
98
+ Overall Match: {final_score*100:.1f}%
99
+ </span>
100
+ </h2>
101
+ <div class="compatibility-scores">
102
+ <div class="score-item">
103
+ <span class="label">Space Compatibility:</span>
104
+ <div class="progress-bar">
105
+ <div class="progress" style="width: {scores['space']*100}%"></div>
106
+ </div>
107
+ <span class="percentage">{scores['space']*100:.1f}%</span>
108
+ </div>
109
+ <div class="score-item">
110
+ <span class="label">Exercise Match:</span>
111
+ <div class="progress-bar">
112
+ <div class="progress" style="width: {scores['exercise']*100}%"></div>
113
+ </div>
114
+ <span class="percentage">{scores['exercise']*100:.1f}%</span>
115
+ </div>
116
+ <div class="score-item">
117
+ <span class="label">Grooming Match:</span>
118
+ <div class="progress-bar">
119
+ <div class="progress" style="width: {scores['grooming']*100}%"></div>
120
+ </div>
121
+ <span class="percentage">{scores['grooming']*100:.1f}%</span>
122
+ </div>
123
+ <div class="score-item">
124
+ <span class="label">Experience Match:</span>
125
+ <div class="progress-bar">
126
+ <div class="progress" style="width: {scores['experience']*100}%"></div>
127
+ </div>
128
+ <span class="percentage">{scores['experience']*100:.1f}%</span>
129
+ </div>
130
+ <div class="score-item">
131
+ <span class="label">
132
+ Noise Compatibility:
133
+ <span class="tooltip">
134
+ <span class="tooltip-icon">ⓘ</span>
135
+ <span class="tooltip-text">
136
+ <strong>Noise Compatibility Score:</strong><br>
137
+ • Based on your noise tolerance preference<br>
138
+ • Considers breed's typical noise level<br>
139
+ • Accounts for living environment
140
+ </span>
141
+ </span>
142
+ </span>
143
+ <div class="progress-bar">
144
+ <div class="progress" style="width: {scores['noise']*100}%"></div>
145
+ </div>
146
+ <span class="percentage">{scores['noise']*100:.1f}%</span>
147
+ </div>
148
+ {f'''
149
+ <div class="score-item bonus-score">
150
+ <span class="label">
151
+ Breed Bonus:
152
+ <span class="tooltip">
153
+ <span class="tooltip-icon">ⓘ</span>
154
+ <span class="tooltip-text">
155
+ <strong>Breed Bonus Points:</strong><br>
156
+ • {('<br>• '.join(bonus_reasons)) if bonus_reasons else 'No additional bonus points'}<br>
157
+ <br>
158
+ <strong>Bonus Factors Include:</strong><br>
159
+ • Friendly temperament<br>
160
+ • Child compatibility<br>
161
+ • Longer lifespan<br>
162
+ • Living space adaptability
163
+ </span>
164
+ </span>
165
+ </span>
166
+ <div class="progress-bar">
167
+ <div class="progress" style="width: {bonus_score*100}%"></div>
168
+ </div>
169
+ <span class="percentage">{bonus_score*100:.1f}%</span>
170
+ </div>
171
+ ''' if bonus_score > 0 else ''}
172
+ </div>
173
+ <div class="breed-details-section">
174
+ <h3 class="subsection-title">
175
+ <span class="icon">📋</span> Breed Details
176
+ </h3>
177
+ <div class="details-grid">
178
+ <div class="detail-item">
179
+ <span class="tooltip">
180
+ <span class="icon">📏</span>
181
+ <span class="label">Size:</span>
182
+ <span class="tooltip-icon">ⓘ</span>
183
+ <span class="tooltip-text">
184
+ <strong>Size Categories:</strong><br>
185
+ • Small: Under 20 pounds<br>
186
+ • Medium: 20-60 pounds<br>
187
+ • Large: Over 60 pounds
188
+ </span>
189
+ <span class="value">{info['Size']}</span>
190
+ </span>
191
+ </div>
192
+ <div class="detail-item">
193
+ <span class="tooltip">
194
+ <span class="icon">🏃</span>
195
+ <span class="label">Exercise Needs:</span>
196
+ <span class="tooltip-icon">ⓘ</span>
197
+ <span class="tooltip-text">
198
+ <strong>Exercise Needs:</strong><br>
199
+ • Low: Short walks<br>
200
+ • Moderate: 1-2 hours daily<br>
201
+ • High: 2+ hours daily<br>
202
+ • Very High: Constant activity
203
+ </span>
204
+ <span class="value">{info['Exercise Needs']}</span>
205
+ </span>
206
+ </div>
207
+ <div class="detail-item">
208
+ <span class="tooltip">
209
+ <span class="icon">👨‍👩‍👧‍👦</span>
210
+ <span class="label">Good with Children:</span>
211
+ <span class="tooltip-icon">ⓘ</span>
212
+ <span class="tooltip-text">
213
+ <strong>Child Compatibility:</strong><br>
214
+ • Yes: Excellent with kids<br>
215
+ • Moderate: Good with older children<br>
216
+ • No: Better for adult households
217
+ </span>
218
+ <span class="value">{info['Good with Children']}</span>
219
+ </span>
220
+ </div>
221
+ <div class="detail-item">
222
+ <span class="tooltip">
223
+ <span class="icon">⏳</span>
224
+ <span class="label">Lifespan:</span>
225
+ <span class="tooltip-icon">ⓘ</span>
226
+ <span class="tooltip-text">
227
+ <strong>Average Lifespan:</strong><br>
228
+ • Short: 6-8 years<br>
229
+ • Average: 10-15 years<br>
230
+ • Long: 12-20 years<br>
231
+ • Varies by size: Larger breeds typically have shorter lifespans
232
+ </span>
233
+ </span>
234
+ <span class="value">{info['Lifespan']}</span>
235
+ </div>
236
+ </div>
237
+ </div>
238
+ <div class="description-section">
239
+ <h3 class="subsection-title">
240
+ <span class="icon">📝</span> Description
241
+ </h3>
242
+ <p class="description-text">{info.get('Description', '')}</p>
243
+ </div>
244
+ <div class="noise-section">
245
+ <h3 class="section-header">
246
+ <span class="icon">🔊</span> Noise Behavior
247
+ <span class="tooltip">
248
+ <span class="tooltip-icon">ⓘ</span>
249
+ <span class="tooltip-text">
250
+ <strong>Noise Behavior:</strong><br>
251
+ • Typical vocalization patterns<br>
252
+ • Common triggers and frequency<br>
253
+ • Based on breed characteristics
254
+ </span>
255
+ </span>
256
+ </h3>
257
+ <div class="noise-info">
258
+ <div class="noise-details">
259
+ <h4 class="section-header">Typical noise characteristics:</h4>
260
+ <div class="characteristics-list">
261
+ <div class="list-item">Moderate to high barker</div>
262
+ <div class="list-item">Alert watch dog</div>
263
+ <div class="list-item">Attention-seeking barks</div>
264
+ <div class="list-item">Social vocalizations</div>
265
+ </div>
266
+
267
+ <div class="noise-level-display">
268
+ <h4 class="section-header">Noise level:</h4>
269
+ <div class="level-indicator">
270
+ <span class="level-text">Moderate-High</span>
271
+ <div class="level-bars">
272
+ <span class="bar"></span>
273
+ <span class="bar"></span>
274
+ <span class="bar"></span>
275
+ </div>
276
+ </div>
277
+ </div>
278
+
279
+ <h4 class="section-header">Barking triggers:</h4>
280
+ <div class="triggers-list">
281
+ <div class="list-item">Separation anxiety</div>
282
+ <div class="list-item">Attention needs</div>
283
+ <div class="list-item">Strange noises</div>
284
+ <div class="list-item">Excitement</div>
285
+ </div>
286
+ </div>
287
+ <div class="noise-disclaimer">
288
+ <p class="disclaimer-text source-text">Source: Compiled from various breed behavior resources, 2024</p>
289
+ <p class="disclaimer-text">Individual dogs may vary in their vocalization patterns.</p>
290
+ <p class="disclaimer-text">Training can significantly influence barking behavior.</p>
291
+ <p class="disclaimer-text">Environmental factors may affect noise levels.</p>
292
+ </div>
293
+ </div>
294
+ </div>
295
+
296
+ <div class="health-section">
297
+ <h3 class="section-header">
298
+ <span class="icon">🏥</span> Health Insights
299
+ <span class="tooltip">
300
+ <span class="tooltip-icon">ⓘ</span>
301
+ <span class="tooltip-text">
302
+ Health information is compiled from multiple sources including veterinary resources, breed guides, and international canine health databases.
303
+ Each dog is unique and may vary from these general guidelines.
304
+ </span>
305
+ </span>
306
+ </h3>
307
+ <div class="health-info">
308
+ <div class="health-details">
309
+ <div class="health-block">
310
+ <h4 class="section-header">Common breed-specific health considerations:</h4>
311
+ <div class="health-grid">
312
+ <div class="health-item">Patellar luxation</div>
313
+ <div class="health-item">Progressive retinal atrophy</div>
314
+ <div class="health-item">Von Willebrand's disease</div>
315
+ <div class="health-item">Open fontanel</div>
316
+ </div>
317
+ </div>
318
+
319
+ <div class="health-block">
320
+ <h4 class="section-header">Recommended health screenings:</h4>
321
+ <div class="health-grid">
322
+ <div class="health-item screening">Patella evaluation</div>
323
+ <div class="health-item screening">Eye examination</div>
324
+ <div class="health-item screening">Blood clotting tests</div>
325
+ <div class="health-item screening">Skull development monitoring</div>
326
+ </div>
327
+ </div>
328
+ </div>
329
+ <div class="health-disclaimer">
330
+ <p class="disclaimer-text source-text">Source: Compiled from various veterinary and breed information resources, 2024</p>
331
+ <p class="disclaimer-text">This information is for reference only and based on breed tendencies.</p>
332
+ <p class="disclaimer-text">Each dog is unique and may not develop any or all of these conditions.</p>
333
+ <p class="disclaimer-text">Always consult with qualified veterinarians for professional advice.</p>
334
+ </div>
335
+ </div>
336
+ </div>
337
+
338
+ <div class="action-section">
339
+ <a href="https://www.akc.org/dog-breeds/{breed.lower().replace('_', '-')}/"
340
+ target="_blank"
341
+ class="akc-button">
342
+ <span class="icon">🌐</span>
343
+ Learn more about {breed.replace('_', ' ')} on AKC website
344
+ </a>
345
+ </div>
346
+ </div>
347
+ </div>
348
+ """
349
+
350
+ html_content += "</div>"
351
+ return html_content
352
+
353
+ def get_breed_recommendations(user_prefs: UserPreferences, top_n: int = 10) -> List[Dict]:
354
+ """基於使用者偏好推薦狗品種,確保正確的分數排序"""
355
+ print("Starting get_breed_recommendations")
356
+ recommendations = []
357
+ seen_breeds = set()
358
+
359
+ try:
360
+ # 獲取所有品種
361
+ conn = sqlite3.connect('animal_detector.db')
362
+ cursor = conn.cursor()
363
+ cursor.execute("SELECT Breed FROM AnimalCatalog")
364
+ all_breeds = cursor.fetchall()
365
+ conn.close()
366
+
367
+ # 收集所有品種的分數
368
+ for breed_tuple in all_breeds:
369
+ breed = breed_tuple[0]
370
+ base_breed = breed.split('(')[0].strip()
371
+
372
+ if base_breed in seen_breeds:
373
+ continue
374
+ seen_breeds.add(base_breed)
375
+
376
+ # 獲取品種資訊
377
+ breed_info = get_dog_description(breed)
378
+ if not isinstance(breed_info, dict):
379
+ continue
380
+
381
+ # 獲取噪音資訊
382
+ noise_info = breed_noise_info.get(breed, {
383
+ "noise_notes": "Noise information not available",
384
+ "noise_level": "Unknown",
385
+ "source": "N/A"
386
+ })
387
+
388
+ # 將噪音資訊整合到品種資訊中
389
+ breed_info['noise_info'] = noise_info
390
+
391
+ # 計算基礎相容性分數
392
+ compatibility_scores = calculate_compatibility_score(breed_info, user_prefs)
393
+
394
+ # 計算品種特定加分
395
+ breed_bonus = 0.0
396
+
397
+ # 壽命加分
398
+ try:
399
+ lifespan = breed_info.get('Lifespan', '10-12 years')
400
+ years = [int(x) for x in lifespan.split('-')[0].split()[0:1]]
401
+ longevity_bonus = min(0.02, (max(years) - 10) * 0.005)
402
+ breed_bonus += longevity_bonus
403
+ except:
404
+ pass
405
+
406
+ # 性格特徵加分
407
+ temperament = breed_info.get('Temperament', '').lower()
408
+ positive_traits = ['friendly', 'gentle', 'affectionate', 'intelligent']
409
+ negative_traits = ['aggressive', 'stubborn', 'dominant']
410
+
411
+ breed_bonus += sum(0.01 for trait in positive_traits if trait in temperament)
412
+ breed_bonus -= sum(0.01 for trait in negative_traits if trait in temperament)
413
+
414
+ # 與孩童相容性加分
415
+ if user_prefs.has_children:
416
+ if breed_info.get('Good with Children') == 'Yes':
417
+ breed_bonus += 0.02
418
+ elif breed_info.get('Good with Children') == 'No':
419
+ breed_bonus -= 0.03
420
+
421
+ # 噪音相關加分
422
+ if user_prefs.noise_tolerance == 'low':
423
+ if noise_info['noise_level'].lower() == 'high':
424
+ breed_bonus -= 0.03
425
+ elif noise_info['noise_level'].lower() == 'low':
426
+ breed_bonus += 0.02
427
+ elif user_prefs.noise_tolerance == 'high':
428
+ if noise_info['noise_level'].lower() == 'high':
429
+ breed_bonus += 0.01
430
+
431
+ # 計算最終分數
432
+ breed_bonus = round(breed_bonus, 4)
433
+ final_score = round(compatibility_scores['overall'] + breed_bonus, 4)
434
+
435
+ recommendations.append({
436
+ 'breed': breed,
437
+ 'base_score': round(compatibility_scores['overall'], 4),
438
+ 'bonus_score': round(breed_bonus, 4),
439
+ 'final_score': final_score,
440
+ 'scores': compatibility_scores,
441
+ 'info': breed_info,
442
+ 'noise_info': noise_info # 添加噪音資訊到推薦結果
443
+ })
444
+ # 嚴格按照 final_score 排序
445
+ recommendations.sort(key=lambda x: (round(-x['final_score'], 4), x['breed'] )) # 負號使其降序排列,並確保4位小數
446
+
447
+ # 選擇前N名並確保正確排序
448
+ final_recommendations = []
449
+ last_score = None
450
+ rank = 1
451
+
452
+ for rec in recommendations:
453
+ if len(final_recommendations) >= top_n:
454
+ break
455
+
456
+ current_score = rec['final_score']
457
+
458
+ # 確保分數遞減
459
+ if last_score is not None and current_score > last_score:
460
+ continue
461
+
462
+ # 添加排名資訊
463
+ rec['rank'] = rank
464
+ final_recommendations.append(rec)
465
+
466
+ last_score = current_score
467
+ rank += 1
468
+
469
+ # 驗證最終排序
470
+ for i in range(len(final_recommendations)-1):
471
+ current = final_recommendations[i]
472
+ next_rec = final_recommendations[i+1]
473
+
474
+ if current['final_score'] < next_rec['final_score']:
475
+ print(f"Warning: Sorting error detected!")
476
+ print(f"#{i+1} {current['breed']}: {current['final_score']}")
477
+ print(f"#{i+2} {next_rec['breed']}: {next_rec['final_score']}")
478
+
479
+ # 交換位置
480
+ final_recommendations[i], final_recommendations[i+1] = \
481
+ final_recommendations[i+1], final_recommendations[i]
482
+
483
+ # 打印最終結果以供驗證
484
+ print("\nFinal Rankings:")
485
+ for rec in final_recommendations:
486
+ print(f"#{rec['rank']} {rec['breed']}")
487
+ print(f"Base Score: {rec['base_score']:.4f}")
488
+ print(f"Bonus: {rec['bonus_score']:.4f}")
489
+ print(f"Final Score: {rec['final_score']:.4f}\n")
490
+
491
+ return final_recommendations
492
+
493
+ except Exception as e:
494
+ print(f"Error in get_breed_recommendations: {str(e)}")
495
+ print(f"Traceback: {traceback.format_exc()}")
496
+ return []
styles.py ADDED
@@ -0,0 +1,1160 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ def get_css_styles():
3
+ return """
4
+ .dog-info-card {
5
+ margin: 0 0 20px 0;
6
+ padding: 0;
7
+ border-radius: 12px;
8
+ box-shadow: 0 2px 12px rgba(0,0,0,0.08);
9
+ overflow: hidden;
10
+ transition: all 0.3s ease;
11
+ background: white;
12
+ border: 1px solid #e1e4e8;
13
+ position: relative;
14
+ }
15
+ .dog-info-card:hover {
16
+ box-shadow: 0 4px 16px rgba(0,0,0,0.12);
17
+ }
18
+ .dog-info-card:before {
19
+ content: '';
20
+ position: absolute;
21
+ left: 0;
22
+ top: 0;
23
+ bottom: 0;
24
+ width: 8px;
25
+ background-color: inherit;
26
+ }
27
+ .dog-info-header {
28
+ padding: 24px 28px; /* 增加內距 */
29
+ margin: 0;
30
+ font-size: 22px;
31
+ font-weight: bold;
32
+ border-bottom: 1px solid #e1e4e8;
33
+ }
34
+ .dog-info-header {
35
+ background-color: transparent;
36
+ }
37
+ .colored-border {
38
+ position: absolute;
39
+ left: 0;
40
+ top: 0;
41
+ bottom: 0;
42
+ width: 8px;
43
+ }
44
+ .dog-info-header {
45
+ border-left-width: 8px;
46
+ border-left-style: solid;
47
+ }
48
+ .breed-info {
49
+ padding: 28px; /* 增加整體內距 */
50
+ line-height: 1.6;
51
+ font-size: 1rem;
52
+ border: none;
53
+ }
54
+ .section-title {
55
+ font-size: 1.2em !important;
56
+ font-weight: 700;
57
+ color: #2c3e50;
58
+ margin: 32px 0 20px 0;
59
+ padding: 12px 0;
60
+ border-bottom: 2px solid #e1e4e8;
61
+ text-transform: uppercase;
62
+ letter-spacing: 0.5px;
63
+ display: flex;
64
+ align-items: center;
65
+ gap: 8px;
66
+ position: relative;
67
+ }
68
+ .section-header {
69
+ color: #2c3e50;
70
+ font-size: 1.15rem;
71
+ font-weight: 600;
72
+ margin: 20px 0 12px 0;
73
+ display: flex;
74
+ align-items: center;
75
+ gap: 8px;
76
+ }
77
+ .icon {
78
+ font-size: 1.2em;
79
+ display: inline-flex;
80
+ align-items: center;
81
+ justify-content: center;
82
+ }
83
+ .info-section, .care-section, .family-section {
84
+ display: flex;
85
+ flex-wrap: wrap;
86
+ gap: 16px;
87
+ margin-bottom: 28px; /* 增加底部間距 */
88
+ padding: 20px; /* 增加內距 */
89
+ background: #f8f9fa;
90
+ border-radius: 12px;
91
+ border: 1px solid #e1e4e8; /* 添加邊框 */
92
+ }
93
+ .info-item {
94
+ background: white; /* 改為白色背景 */
95
+ padding: 14px 18px; /* 增加內距 */
96
+ border-radius: 8px;
97
+ display: flex;
98
+ align-items: center;
99
+ gap: 10px;
100
+ box-shadow: 0 2px 4px rgba(0,0,0,0.05);
101
+ border: 1px solid #e1e4e8;
102
+ flex: 1 1 auto;
103
+ min-width: 200px;
104
+ }
105
+ .label {
106
+ color: #666;
107
+ font-weight: 600;
108
+ font-size: 1.1rem;
109
+ }
110
+ .value {
111
+ color: #2c3e50;
112
+ font-weight: 500;
113
+ font-size: 1.1rem;
114
+ }
115
+ .temperament-section {
116
+ background: #f8f9fa;
117
+ padding: 20px; /* 增加內距 */
118
+ border-radius: 12px;
119
+ margin-bottom: 28px; /* 增加間距 */
120
+ color: #444;
121
+ border: 1px solid #e1e4e8; /* 添加邊框 */
122
+ }
123
+ .description-section {
124
+ background: #f8f9fa;
125
+ padding: 24px; /* 增加內距 */
126
+ border-radius: 12px;
127
+ margin: 28px 0; /* 增加上下間距 */
128
+ line-height: 1.8;
129
+ color: #444;
130
+ border: 1px solid #e1e4e8; /* 添加邊框 */
131
+ fontsize: 1.1rem;
132
+ }
133
+ .description-section p {
134
+ margin: 0;
135
+ padding: 0;
136
+ text-align: justify; /* 文字兩端對齊 */
137
+ word-wrap: break-word; /* 確保長單字會換行 */
138
+ white-space: pre-line; /* 保留換行但合併空白 */
139
+ max-width: 100%; /* 確保不會超出容器 */
140
+ }
141
+ .action-section {
142
+ margin-top: 24px;
143
+ text-align: center;
144
+ }
145
+ .akc-button,
146
+ .breed-section .akc-link,
147
+ .breed-option .akc-link {
148
+ display: inline-flex;
149
+ align-items: center;
150
+ padding: 14px 28px;
151
+ background: linear-gradient(145deg, #00509E, #003F7F);
152
+ color: white;
153
+ border-radius: 12px; /* 增加圓角 */
154
+ text-decoration: none;
155
+ gap: 12px; /* 增加圖標和文字間距 */
156
+ transition: all 0.3s ease;
157
+ font-weight: 600;
158
+ font-size: 1.1em;
159
+ box-shadow:
160
+ 0 2px 4px rgba(0,0,0,0.1),
161
+ inset 0 1px 1px rgba(255,255,255,0.1);
162
+ border: 1px solid rgba(255,255,255,0.1);
163
+ }
164
+ .akc-button:hover,
165
+ .breed-section .akc-link:hover,
166
+ .breed-option .akc-link:hover {
167
+ background: linear-gradient(145deg, #003F7F, #00509E);
168
+ transform: translateY(-2px);
169
+ color: white;
170
+ box-shadow:
171
+ 0 6px 12px rgba(0,0,0,0.2),
172
+ inset 0 1px 1px rgba(255,255,255,0.2);
173
+ border: 1px solid rgba(255,255,255,0.2);
174
+ }
175
+ .icon {
176
+ font-size: 1.3em;
177
+ filter: drop-shadow(0 1px 1px rgba(0,0,0,0.2));
178
+ }
179
+ .warning-message {
180
+ display: flex;
181
+ align-items: center;
182
+ gap: 8px;
183
+ color: #ff3b30;
184
+ font-weight: 500;
185
+ margin: 0;
186
+ padding: 16px;
187
+ background: #fff5f5;
188
+ border-radius: 8px;
189
+ }
190
+ .model-uncertainty-note {
191
+ display: flex;
192
+ align-items: center;
193
+ gap: 12px;
194
+ padding: 16px;
195
+ background-color: #f8f9fa;
196
+ margin-bottom: 20px;
197
+ color: #495057;
198
+ border-radius: 4px;
199
+ }
200
+ .breeds-list {
201
+ display: flex;
202
+ flex-direction: column;
203
+ gap: 20px;
204
+ }
205
+ .breed-option {
206
+ background: white;
207
+ border: 1px solid #e1e4e8;
208
+ border-radius: 8px;
209
+ overflow: hidden;
210
+ }
211
+ .breed-header {
212
+ display: flex;
213
+ align-items: center;
214
+ padding: 16px;
215
+ background: #f8f9fa;
216
+ gap: 12px;
217
+ border-bottom: 1px solid #e1e4e8;
218
+ }
219
+ .option-number {
220
+ font-weight: 600;
221
+ color: #666;
222
+ padding: 4px 8px;
223
+ background: #e1e4e8;
224
+ border-radius: 4px;
225
+ }
226
+
227
+ .option-item {
228
+ padding: 15px;
229
+ margin-bottom: 10px;
230
+ border-bottom: 1px solid #ddd;
231
+ }
232
+
233
+ .option-item:last-child {
234
+ border-bottom: none; /* 最後一個選項去除邊線 */
235
+ }
236
+
237
+ .breed-name {
238
+ font-size: 1.2em !important; # 從 1.5em 改為 1.2em
239
+ font-weight: bold;
240
+ color: #2c3e50;
241
+ flex-grow: 1;
242
+ }
243
+ .confidence-badge {
244
+ padding: 4px 12px;
245
+ border-radius: 20px;
246
+ font-size: 0.9em;
247
+ font-weight: 500;
248
+ }
249
+ .breed-content {
250
+ padding: 20px;
251
+ }
252
+ .breed-content li {
253
+ margin-bottom: 8px;
254
+ display: flex;
255
+ align-items: flex-start; /* 改為頂部對齊 */
256
+ gap: 8px;
257
+ flex-wrap: wrap; /* 允許內容換行 */
258
+ }
259
+ .breed-content li strong {
260
+ flex: 0 0 auto; /* 不讓標題縮放 */
261
+ min-width: 100px; /* 給標題一個固定最小寬度 */
262
+ }
263
+ ul {
264
+ padding-left: 0;
265
+ margin: 0;
266
+ list-style-type: none;
267
+ }
268
+ li {
269
+ margin-bottom: 8px;
270
+ display: flex;
271
+ align-items: center;
272
+ gap: 8px;
273
+ }
274
+ .action-section {
275
+ margin-top: 20px;
276
+ padding: 15px;
277
+ text-align: center;
278
+ border-top: 1px solid #dee2e6;
279
+ }
280
+ .akc-button {
281
+ display: inline-block;
282
+ padding: 12px 24px;
283
+ background-color: #007bff;
284
+ color: white !important;
285
+ text-decoration: none;
286
+ border-radius: 5px;
287
+ font-weight: 500;
288
+ transition: background-color 0.3s;
289
+ }
290
+ .akc-button:hover {
291
+ background-color: #0056b3;
292
+ text-decoration: none;
293
+ }
294
+ .akc-button .icon {
295
+ margin-right: 8px;
296
+ }
297
+ .akc-link {
298
+ color: white;
299
+ text-decoration: none;
300
+ font-weight: 600;
301
+ font-size: 1.1em;
302
+ transition: all 0.3s ease;
303
+ }
304
+ .akc-link:hover {
305
+ text-decoration: underline;
306
+ color: #D3E3F0;
307
+ }
308
+ .tooltip {
309
+ position: relative;
310
+ display: inline-flex;
311
+ align-items: center;
312
+ gap: 4px;
313
+ cursor: help;
314
+ }
315
+ .tooltip .tooltip-icon {
316
+ font-size: 14px;
317
+ color: #666;
318
+ }
319
+ .tooltip .tooltip-text {
320
+ visibility: hidden;
321
+ width: 250px;
322
+ background-color: rgba(44, 62, 80, 0.95);
323
+ color: white;
324
+ text-align: left;
325
+ border-radius: 8px;
326
+ padding: 8px 10px;
327
+ position: absolute;
328
+ z-index: 100;
329
+ bottom: 150%;
330
+ left: 50%;
331
+ transform: translateX(-50%);
332
+ opacity: 0;
333
+ transition: all 0.3s ease;
334
+ font-size: 14px;
335
+ line-height: 1.3;
336
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.2);
337
+ border: 1px solid rgba(255, 255, 255, 0.1)
338
+ margin-bottom: 10px;
339
+ }
340
+ .tooltip.tooltip-left .tooltip-text {
341
+ left: 0;
342
+ transform: translateX(0);
343
+ }
344
+ .tooltip.tooltip-right .tooltip-text {
345
+ left: auto;
346
+ right: 0;
347
+ transform: translateX(0);
348
+ }
349
+ .tooltip-text strong {
350
+ color: white !important;
351
+ background-color: transparent !important;
352
+ display: block; /* 讓標題獨立一行 */
353
+ margin-bottom: 2px; /* 增加標題下方間距 */
354
+ padding-bottom: 2px; /* 加入小間距 */
355
+ border-bottom: 1px solid rgba(255,255,255,0.2);
356
+ }
357
+ .tooltip-text {
358
+ font-size: 13px; /* 稍微縮小字體 */
359
+ }
360
+ /* 調整列表符號和文字的間距 */
361
+ .tooltip-text ul {
362
+ margin: 0;
363
+ padding-left: 15px; /* 減少列表符號的縮進 */
364
+ }
365
+ .tooltip-text li {
366
+ margin-bottom: 1px; /* 減少列表項目間的間距 */
367
+ }
368
+ .tooltip-text br {
369
+ line-height: 1.2; /* 減少行距 */
370
+ }
371
+ .tooltip .tooltip-text::after {
372
+ content: "";
373
+ position: absolute;
374
+ top: 100%;
375
+ left: 20%; /* 調整箭頭位置 */
376
+ margin-left: -5px;
377
+ border-width: 5px;
378
+ border-style: solid;
379
+ border-color: rgba(44, 62, 80, 0.95) transparent transparent transparent;
380
+ }
381
+ .tooltip-left .tooltip-text::after {
382
+ left: 20%;
383
+ }
384
+ /* 右側箭頭 */
385
+ .tooltip-right .tooltip-text::after {
386
+ left: 80%;
387
+ }
388
+ .tooltip:hover .tooltip-text {
389
+ visibility: visible;
390
+ opacity: 1;
391
+ }
392
+ .tooltip .tooltip-text::after {
393
+ content: "";
394
+ position: absolute;
395
+ top: 100%;
396
+ left: 50%;
397
+ transform: translateX(-50%);
398
+ border-width: 8px;
399
+ border-style: solid;
400
+ border-color: rgba(44, 62, 80, 0.95) transparent transparent transparent;
401
+ }
402
+ .uncertainty-mode .tooltip .tooltip-text {
403
+ position: absolute;
404
+ left: 100%;
405
+ bottom: auto;
406
+ top: 50%;
407
+ transform: translateY(-50%);
408
+ margin-left: 10px;
409
+ z-index: 1000; /* 確保提示框在最上層 */
410
+ }
411
+ .uncertainty-mode .tooltip .tooltip-text::after {
412
+ content: "";
413
+ position: absolute;
414
+ top: 50%;
415
+ right: 100%;
416
+ transform: translateY(-50%);
417
+ border-width: 5px;
418
+ border-style: solid;
419
+ border-color: transparent rgba(44, 62, 80, 0.95) transparent transparent;
420
+ }
421
+ .uncertainty-mode .breed-content {
422
+ font-size: 1rem !important; /* 增加字體大小 */
423
+ }
424
+ .description-section,
425
+ .description-section p,
426
+ .temperament-section,
427
+ .temperament-section .value,
428
+ .info-item,
429
+ .info-item .value,
430
+ .breed-content {
431
+ font-size: 1rem !important; /* 使用 !important 確保覆蓋其他樣式 */
432
+ }
433
+ .recommendation-card {
434
+ margin-bottom: 40px;
435
+ }
436
+ .compatibility-scores {
437
+ background: #f8f9fa;
438
+ padding: 24px;
439
+ border-radius: 12px;
440
+ margin: 20px 0;
441
+ }
442
+ .score-item {
443
+ margin: 15px 0;
444
+ }
445
+ .progress-bar {
446
+ height: 12px;
447
+ background-color: #e9ecef;
448
+ border-radius: 6px;
449
+ overflow: hidden;
450
+ margin: 8px 0;
451
+ }
452
+ .progress {
453
+ height: 100%;
454
+ background: linear-gradient(90deg, #34C759, #30B350);
455
+ border-radius: 6px;
456
+ transition: width 0.6s ease;
457
+ }
458
+ .percentage {
459
+ float: right;
460
+ color: #34C759;
461
+ font-weight: 600;
462
+ }
463
+ .breed-details-section {
464
+ margin: 30px 0;
465
+ }
466
+ .subsection-title {
467
+ font-size: 1.2em;
468
+ color: #2c3e50;
469
+ margin-bottom: 20px;
470
+ display: flex;
471
+ align-items: center;
472
+ gap: 8px;
473
+ }
474
+ .details-grid {
475
+ display: grid;
476
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
477
+ gap: 20px;
478
+ background: #f8f9fa;
479
+ padding: 20px;
480
+ border-radius: 12px;
481
+ border: 1px solid #e1e4e8;
482
+ }
483
+ .detail-item {
484
+ background: white;
485
+ padding: 15px;
486
+ border-radius: 8px;
487
+ border: 1px solid #e1e4e8;
488
+ }
489
+ .description-text {
490
+ line-height: 1.8;
491
+ color: #444;
492
+ margin: 0;
493
+ padding: 24px 30px; /* 調整內部間距,從 20px 改為 24px 30px */
494
+ background: #f8f9fa;
495
+ border-radius: 12px;
496
+ border: 1px solid #e1e4e8;
497
+ text-align: justify; /* 添加文字對齊 */
498
+ word-wrap: break-word; /* 確保長文字會換行 */
499
+ word-spacing: 1px; /* 加入字間距 */
500
+ }
501
+ /* 工具提示改進 */
502
+ .tooltip {
503
+ position: relative;
504
+ display: inline-flex;
505
+ align-items: center;
506
+ gap: 4px;
507
+ cursor: help;
508
+ padding: 5px 0;
509
+ }
510
+ .tooltip .tooltip-text {
511
+ visibility: hidden;
512
+ width: 280px;
513
+ background-color: rgba(44, 62, 80, 0.95);
514
+ color: white;
515
+ text-align: left;
516
+ border-radius: 8px;
517
+ padding: 12px 15px;
518
+ position: absolute;
519
+ z-index: 1000;
520
+ bottom: calc(100% + 15px);
521
+ left: 50%;
522
+ transform: translateX(-50%);
523
+ opacity: 0;
524
+ transition: all 0.3s ease;
525
+ font-size: 14px;
526
+ line-height: 1.4;
527
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.2);
528
+ white-space: normal;
529
+ }
530
+ .tooltip:hover .tooltip-text {
531
+ visibility: visible;
532
+ opacity: 1;
533
+ }
534
+ .score-badge {
535
+ background-color: #34C759;
536
+ color: white;
537
+ padding: 6px 12px;
538
+ border-radius: 20px;
539
+ font-size: 0.9em;
540
+ margin-left: 10px;
541
+ font-weight: 500;
542
+ box-shadow: 0 2px 4px rgba(52, 199, 89, 0.2);
543
+ }
544
+ .bonus-score .tooltip-text {
545
+ width: 250px;
546
+ line-height: 1.4;
547
+ padding: 10px;
548
+ }
549
+ .bonus-score .progress {
550
+ background: linear-gradient(90deg, #48bb78, #68d391);
551
+ }
552
+ .health-section {
553
+ margin: 25px 0;
554
+ padding: 24px;
555
+ background-color: #f8f9fb;
556
+ border-radius: 12px;
557
+ border: 1px solid #e1e4e8;
558
+ }
559
+ .health-section .subsection-title {
560
+ font-size: 1.3em;
561
+ font-weight: 600;
562
+ margin-bottom: 20px;
563
+ display: flex;
564
+ align-items: center;
565
+ gap: 8px;
566
+ color: #2c3e50;
567
+ }
568
+ .health-info {
569
+ background-color: white;
570
+ padding: 24px;
571
+ border-radius: 8px;
572
+ margin: 15px 0;
573
+ border: 1px solid #e1e4e8;
574
+ }
575
+ .health-details {
576
+ font-size: 1.1rem;
577
+ line-height: 1.6;
578
+ }
579
+ .health-details h4 {
580
+ color: #2c3e50;
581
+ font-size: 1.15rem;
582
+ font-weight: 600;
583
+ margin: 20px 0 15px 0;
584
+ }
585
+ .health-details h4:first-child {
586
+ margin-top: 0;
587
+ }
588
+ .health-details ul {
589
+ list-style-type: none;
590
+ padding-left: 0;
591
+ margin: 0 0 25px 0;
592
+ }
593
+ .health-details ul li {
594
+ margin-bottom: 12px;
595
+ padding-left: 20px;
596
+ position: relative;
597
+ }
598
+ .health-details ul li:before {
599
+ content: "•";
600
+ position: absolute;
601
+ left: 0;
602
+ color: #2c3e50;
603
+ }
604
+ .health-item:before {
605
+ content: "•";
606
+ color: #dc3545;
607
+ font-weight: bold;
608
+ }
609
+ .health-item.screening:before {
610
+ color: #28a745;
611
+ }
612
+ /* 區塊間距 */
613
+ .health-block, .noise-block {
614
+ margin-bottom: 24px;
615
+ }
616
+ .health-disclaimer {
617
+ margin-top: 20px;
618
+ padding-top: 20px;
619
+ border-top: 1px solid #e1e4e8;
620
+ }
621
+ .health-disclaimer p {
622
+ margin: 6px 0;
623
+ padding-left: 20px;
624
+ position: relative;
625
+ color: #888; /* 統一設定灰色 */
626
+ font-size: 0.95rem;
627
+ line-height: 1.5;
628
+ font-style: italic;
629
+ }
630
+ .health-disclaimer p:before {
631
+ content: "›";
632
+ position: absolute;
633
+ left: 0;
634
+ color: #999;
635
+ font-style: normal;
636
+ font-weight: 500;
637
+ }
638
+ .health-disclaimer p:first-child {
639
+ font-style: normal; /* 取消斜體 */
640
+ font-weight: 500; /* 加粗 */
641
+ color: #666; /* 稍深的灰色 */
642
+ }
643
+ .health-disclaimer p span,
644
+ .health-disclaimer p strong,
645
+ .health-disclaimer p em {
646
+ color: inherit;
647
+ }
648
+ .health-list li:before {
649
+ content: "•";
650
+ color: #dc3545;
651
+ }
652
+ .history-container {
653
+ max-width: 800px;
654
+ margin: 0 auto;
655
+ padding: 20px;
656
+ }
657
+ .history-entry {
658
+ background-color: #f8f9fa;
659
+ border-radius: 8px;
660
+ padding: 15px;
661
+ margin-bottom: 20px;
662
+ box-shadow: 0 1px 3px rgba(0,0,0,0.1);
663
+ }
664
+ .history-header {
665
+ display: flex;
666
+ justify-content: space-between;
667
+ align-items: center;
668
+ margin-bottom: 10px;
669
+ padding-bottom: 10px;
670
+ border-bottom: 1px solid #eee;
671
+ }
672
+ .timestamp {
673
+ color: #666;
674
+ font-size: 0.9em;
675
+ }
676
+ .delete-btn {
677
+ background: none;
678
+ border: none;
679
+ cursor: pointer;
680
+ font-size: 1.2em;
681
+ padding: 5px;
682
+ }
683
+ .delete-btn:hover {
684
+ color: #dc3545;
685
+ }
686
+ .search-params ul {
687
+ list-style: none;
688
+ padding-left: 20px;
689
+ }
690
+ .search-params li {
691
+ margin: 5px 0;
692
+ color: #555;
693
+ }
694
+ .top-results ol {
695
+ padding-left: 25px;
696
+ }
697
+ .top-results li {
698
+ margin: 5px 0;
699
+ color: #333;
700
+ }
701
+ .breed-item {
702
+ display: flex;
703
+ justify-content: space-between;
704
+ align-items: center;
705
+ padding: 12px 16px;
706
+ margin: 8px 0;
707
+ background-color: white;
708
+ border-radius: 6px;
709
+ border: 1px solid #e1e4e8;
710
+ transition: all 0.2s ease;
711
+ }
712
+ .breed-item:hover {
713
+ transform: translateX(5px);
714
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
715
+ }
716
+ .breed-rank {
717
+ font-weight: 600;
718
+ color: #666;
719
+ margin-right: 12px;
720
+ min-width: 30px;
721
+ }
722
+ .breed-name {
723
+ flex: 1;
724
+ font-weight: 500;
725
+ color: #2c3e50;
726
+ padding: 0 12px;
727
+ }
728
+ .breed-score {
729
+ font-weight: 600;
730
+ color: #34C759;
731
+ padding: 4px 8px;
732
+ border-radius: 20px;
733
+ background-color: rgba(52, 199, 89, 0.1);
734
+ min-width: 70px;
735
+ text-align: center;
736
+ }
737
+ .history-entry {
738
+ background-color: #f8f9fa;
739
+ border-radius: 12px;
740
+ padding: 20px;
741
+ margin-bottom: 25px;
742
+ box-shadow: 0 2px 8px rgba(0,0,0,0.05);
743
+ border: 1px solid #e1e4e8;
744
+ }
745
+ .history-header {
746
+ margin-bottom: 15px;
747
+ padding-bottom: 12px;
748
+ border-bottom: 1px solid #e1e4e8;
749
+ }
750
+ .history-header .timestamp {
751
+ color: #666;
752
+ font-size: 0.9em;
753
+ display: flex;
754
+ align-items: center;
755
+ gap: 6px;
756
+ }
757
+ h4 {
758
+ color: #2c3e50;
759
+ font-size: 1.15rem;
760
+ font-weight: 600;
761
+ margin: 20px 0 12px 0;
762
+ }
763
+ .params-list ul {
764
+ list-style: none;
765
+ padding-left: 0;
766
+ margin: 10px 0;
767
+ }
768
+ .params-list li {
769
+ margin: 8px 0;
770
+ color: #4a5568;
771
+ display: flex;
772
+ align-items: center;
773
+ }
774
+ .empty-history {
775
+ text-align: center;
776
+ padding: 40px 20px;
777
+ color: #666;
778
+ font-size: 1.1em;
779
+ background-color: #f8f9fa;
780
+ border-radius: 12px;
781
+ border: 1px dashed #e1e4e8;
782
+ margin: 20px 0;
783
+ }
784
+ .noise-section {
785
+ margin: 25px 0;
786
+ padding: 24px;
787
+ background-color: #f8f9fb;
788
+ border-radius: 12px;
789
+ border: 1px solid #e1e4e8;
790
+ }
791
+ .noise-info {
792
+ background-color: white;
793
+ padding: 24px;
794
+ border-radius: 8px;
795
+ margin: 15px 0;
796
+ border: 1px solid #e1e4e8;
797
+ }
798
+ .noise-details {
799
+ font-size: 1.1rem;
800
+ line-height: 1.6;
801
+ }
802
+ .noise-level {
803
+ margin-bottom: 20px;
804
+ padding: 10px 15px;
805
+ background: #f8f9fa;
806
+ border-radius: 6px;
807
+ font-weight: 500;
808
+ }
809
+ .noise-level-block {
810
+ background: #f8f9fa;
811
+ padding: 20px;
812
+ border-radius: 8px;
813
+ margin: 20px 0;
814
+ }
815
+ .noise-level-display {
816
+ background: #f8f9fa;
817
+ padding: 16px;
818
+ border-radius: 8px;
819
+ margin: 16px 0;
820
+ }
821
+ .level-indicator {
822
+ background: white;
823
+ padding: 12px 16px;
824
+ border-radius: 8px;
825
+ border: 1px solid #e1e4e8;
826
+ display: flex;
827
+ align-items: center;
828
+ justify-content: space-between;
829
+ }
830
+ .level-text {
831
+ font-weight: 500;
832
+ color: #2c3e50;
833
+ }
834
+ .level-bars {
835
+ display: flex;
836
+ gap: 4px;
837
+ }
838
+ .level-bars .bar {
839
+ width: 4px;
840
+ height: 16px;
841
+ background: #e9ecef;
842
+ border-radius: 2px;
843
+ }
844
+ .level-indicator.low .bar:nth-child(1) {
845
+ background: #4CAF50;
846
+ }
847
+ .level-indicator.medium .bar:nth-child(1),
848
+ .level-indicator.medium .bar:nth-child(2) {
849
+ background: #FFA726;
850
+ }
851
+ .level-indicator.high .bar {
852
+ background: #EF5350;
853
+ }
854
+ .feature-list, .health-list, .screening-list {
855
+ list-style: none;
856
+ padding: 0;
857
+ margin: 16px 0;
858
+ display: grid;
859
+ grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
860
+ gap: 12px;
861
+ }
862
+ .feature-list li, .health-list li, .screening-list li {
863
+ background: white;
864
+ padding: 12px 16px;
865
+ border-radius: 6px;
866
+ border: 1px solid #e1e4e8;
867
+ display: flex;
868
+ align-items: center;
869
+ gap: 8px;
870
+ font-size: 0.95rem;
871
+ }
872
+ .feature-list li:before {
873
+ content: "•";
874
+ color: #2c3e50;
875
+ }
876
+ .noise-notes {
877
+ font-family: inherit;
878
+ white-space: pre-wrap;
879
+ margin: 15px 0;
880
+ padding: 0;
881
+ background: transparent;
882
+ border: none;
883
+ font-size: 1.1rem;
884
+ line-height: 1.6;
885
+ color: #333;
886
+ }
887
+ .characteristics-block, .health-considerations, .health-screenings {
888
+ margin-bottom: 24px;
889
+ }
890
+ .characteristics-block h4, .health-considerations h4, .health-screenings h4 {
891
+ color: #2c3e50;
892
+ font-size: 1.1em;
893
+ font-weight: 600;
894
+ margin-bottom: 12px;
895
+ }
896
+ .characteristics-list,
897
+ .triggers-list,
898
+ .health-considerations-list,
899
+ .health-screenings-list {
900
+ display: grid;
901
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
902
+ gap: 12px;
903
+ margin: 16px 0;
904
+ }
905
+ .noise-details, .health-details {
906
+ font-size: 1.1rem;
907
+ line-height: 1.6;
908
+ }
909
+ .noise-details ul, .health-details ul {
910
+ list-style: none;
911
+ padding-left: 0;
912
+ margin: 0 0 20px 0;
913
+ }
914
+ .noise-details li, .health-details li {
915
+ padding-left: 20px;
916
+ position: relative;
917
+ margin-bottom: 10px;
918
+ line-height: 1.5;
919
+ }
920
+ .noise-details li:before, .health-details li:before {
921
+ content: "•";
922
+ position: absolute;
923
+ left: 0;
924
+ color: #666;
925
+ }
926
+ .noise-section, .health-section {
927
+ margin: 25px 0;
928
+ padding: 24px;
929
+ background-color: #f8f9fb;
930
+ border-radius: 12px;
931
+ border: 1px solid #e1e4e8;
932
+ }
933
+ .noise-info, .health-info {
934
+ background-color: white;
935
+ padding: 24px;
936
+ border-radius: 8px;
937
+ margin: 15px 0;
938
+ border: 1px solid #e1e4e8;
939
+ }
940
+ .breed-info .description-tooltip {
941
+ position: relative;
942
+ display: inline-flex;
943
+ align-items: center;
944
+ gap: 4px;
945
+ cursor: help;
946
+ }
947
+ .description-tooltip .tooltip-icon {
948
+ font-size: 14px;
949
+ color: #666;
950
+ margin-left: 4px;
951
+ cursor: help;
952
+ }
953
+ .description-tooltip .tooltip-text {
954
+ visibility: hidden;
955
+ width: 280px;
956
+ background-color: rgba(44, 62, 80, 0.95);
957
+ color: white;
958
+ text-align: left;
959
+ border-radius: 8px;
960
+ padding: 12px 15px;
961
+ position: absolute;
962
+ z-index: 1000;
963
+ bottom: calc(100% + 15px);
964
+ left: 50%;
965
+ transform: translateX(-50%);
966
+ opacity: 0;
967
+ transition: all 0.3s ease;
968
+ font-size: 14px;
969
+ line-height: 1.4;
970
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.2);
971
+ white-space: normal;
972
+ }
973
+ .description-tooltip:hover .tooltip-text {
974
+ visibility: visible;
975
+ opacity: 1;
976
+ }
977
+ .description-tooltip .tooltip-text::after {
978
+ content: "";
979
+ position: absolute;
980
+ top: 100%;
981
+ left: 50%;
982
+ transform: translateX(-50%);
983
+ border-width: 8px;
984
+ border-style: solid;
985
+ border-color: rgba(44, 62, 80, 0.95) transparent transparent transparent;
986
+ }
987
+ .description-header {
988
+ display: flex;
989
+ align-items: center;
990
+ gap: 8px;
991
+ margin-bottom: 10px;
992
+ }
993
+ .description-header h3 {
994
+ margin: 0;
995
+ font-size: 1.2em;
996
+ color: #2c3e50;
997
+ }
998
+ .screening-list li:before {
999
+ content: "•";
1000
+ color: #28a745;
1001
+ }
1002
+ .noise-disclaimer, .health-disclaimer {
1003
+ margin-top: 20px;
1004
+ padding-top: 20px;
1005
+ border-top: 1px solid #e1e4e8;
1006
+ color: #666;
1007
+ }
1008
+ .noise-disclaimer p, .health-disclaimer p {
1009
+ margin: 8px 0;
1010
+ padding-left: 20px;
1011
+ position: relative;
1012
+ }
1013
+ .noise-disclaimer p:before, .health-disclaimer p:before {
1014
+ content: "›";
1015
+ position: absolute;
1016
+ left: 0;
1017
+ color: #999;
1018
+ }
1019
+ .disclaimer-text {
1020
+ margin: 8px 0;
1021
+ padding-left: 20px;
1022
+ position: relative;
1023
+ font-size: 0.95rem;
1024
+ line-height: 1.5;
1025
+ font-style: italic;
1026
+ color: #888;
1027
+ }
1028
+ .disclaimer-text:before {
1029
+ content: "›";
1030
+ position: absolute;
1031
+ left: 0;
1032
+ color: #999;
1033
+ font-style: normal;
1034
+ font-weight: 500;
1035
+ }
1036
+ .list-item {
1037
+ background: white;
1038
+ padding: 12px 16px;
1039
+ border-radius: 8px;
1040
+ border: 1px solid #e1e4e8;
1041
+ display: flex;
1042
+ align-items: center;
1043
+ gap: 8px;
1044
+ margin: 4px 0;
1045
+ font-size: 0.95rem;
1046
+ color: #2c3e50;
1047
+ }
1048
+ .source-text {
1049
+ font-style: normal !important;
1050
+ font-weight: 500 !important;
1051
+ color: #666 !important;
1052
+ }
1053
+ .health-grid, .noise-grid {
1054
+ display: grid;
1055
+ grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
1056
+ gap: 12px;
1057
+ margin: 16px 0;
1058
+ }
1059
+ .health-item, .noise-item {
1060
+ background: white;
1061
+ padding: 12px 16px;
1062
+ border-radius: 8px;
1063
+ border: 1px solid #e1e4e8;
1064
+ display: flex;
1065
+ align-items: center;
1066
+ gap: 8px;
1067
+ transition: all 0.2s ease;
1068
+ }
1069
+ .health-item:hover, .noise-item:hover {
1070
+ transform: translateY(-1px);
1071
+ box-shadow: 0 2px 4px rgba(0,0,0,0.05);
1072
+ }
1073
+
1074
+ @media (max-width: 768px) {
1075
+ /* 在小螢幕上改為單列顯示 */
1076
+ .health-grid, .noise-grid {
1077
+ grid-template-columns: 1fr;
1078
+ }
1079
+
1080
+ /* 減少內邊距 */
1081
+ .health-section, .noise-section {
1082
+ padding: 16px;
1083
+ }
1084
+
1085
+ /* 調整字體大小 */
1086
+ .section-header {
1087
+ font-size: 1rem;
1088
+ }
1089
+
1090
+ /* 調整項目內邊距 */
1091
+ .health-item, .noise-item {
1092
+ padding: 10px 14px;
1093
+ }
1094
+ }
1095
+
1096
+ /* 較小的手機螢幕 */
1097
+ @media (max-width: 480px) {
1098
+ .health-grid, .noise-grid {
1099
+ gap: 8px;
1100
+ }
1101
+
1102
+ .health-item, .noise-item {
1103
+ padding: 8px 12px;
1104
+ font-size: 0.9rem;
1105
+ }
1106
+ }
1107
+
1108
+ .expandable-section {
1109
+ margin-top: 1rem;
1110
+ }
1111
+
1112
+ .expand-header {
1113
+ cursor: pointer;
1114
+ padding: 0.5rem;
1115
+ background-color: #f3f4f6;
1116
+ border-radius: 0.375rem;
1117
+ display: flex;
1118
+ justify-content: space-between;
1119
+ align-items: center;
1120
+ }
1121
+
1122
+ .expand-header:hover {
1123
+ background-color: #e5e7eb;
1124
+ }
1125
+
1126
+ .expand-icon {
1127
+ transition: transform 0.2s;
1128
+ }
1129
+
1130
+ .expandable-section[open] .expand-icon {
1131
+ transform: rotate(180deg);
1132
+ }
1133
+
1134
+ .expanded-content {
1135
+ padding: 1rem;
1136
+ background-color: #ffffff;
1137
+ border-radius: 0.375rem;
1138
+ margin-top: 0.5rem;
1139
+ }
1140
+
1141
+ .info-cards > div:first-child .tooltip .tooltip-text,
1142
+ .info-cards > div:nth-child(3n+1) .tooltip .tooltip-text {
1143
+ left: calc(100% + 20px); /* 向右移動更多 */
1144
+ }
1145
+
1146
+ .info-cards > div:first-child .tooltip .tooltip-text::after,
1147
+ .info-cards > div:nth-child(3n+1) .tooltip .tooltip-text::after {
1148
+ right: calc(100% - 2px); /* 向右移動箭頭 */
1149
+ }
1150
+
1151
+ .section-header h3 .tooltip .tooltip-text {
1152
+ left: calc(100% + 20px); /* 向右移動更多 */
1153
+ }
1154
+
1155
+ .section-header h3 .tooltip .tooltip-text::after {
1156
+ right: calc(100% - 2px); /* 向右移動箭頭 */
1157
+
1158
+ }
1159
+
1160
+ """