20.68.131.221
As of: Jul 12, 2025 1:15pm UTC |
Latest
{
"ip": "20.68.131.221",
"services": [
{
"_decoded": "ssh",
"_encoding": {
"banner": "DISPLAY_UTF8",
"banner_hex": "DISPLAY_HEX"
},
"banner": "SSH-2.0-OpenSSH_9.6p1 Ubuntu-3ubuntu13.11",
"banner_hashes": [
"sha256:c446e6135b7a20699ecbdafd3e1015ece7374d8589994c7acea7a3afef1c383e"
],
"banner_hex": "5353482d322e302d4f70656e5353485f392e367031205562756e74752d337562756e747531332e3131",
"discovery_method": "IPV4_WALK_CLOUD_PRIORITY_1",
"extended_service_name": "SSH",
"labels": [
"remote-access"
],
"observed_at": "2025-07-12T11:31:44.132102902Z",
"perspective_id": "PERSPECTIVE_UNKNOWN",
"port": 22,
"service_name": "SSH",
"software": [
{
"product": "openssh",
"other": {
"comment": "Ubuntu-3ubuntu13.11"
},
"source": "OSI_APPLICATION_LAYER"
},
{
"uniform_resource_identifier": "cpe:2.3:o:canonical:ubuntu_linux:*:*:*:*:*:*:*:*",
"part": "o",
"vendor": "Ubuntu",
"product": "Linux",
"other": {
"family": "Linux"
},
"source": "OSI_APPLICATION_LAYER"
},
{
"uniform_resource_identifier": "cpe:2.3:a:openbsd:openssh:9.6p1:*:*:*:*:*:*:*",
"part": "a",
"vendor": "OpenBSD",
"product": "OpenSSH",
"version": "9.6p1",
"other": {
"family": "OpenSSH"
},
"source": "OSI_APPLICATION_LAYER"
}
],
"source_ip": "206.168.34.79",
"ssh": {
"endpoint_id": {
"_encoding": {
"raw": "DISPLAY_UTF8"
},
"raw": "SSH-2.0-OpenSSH_9.6p1 Ubuntu-3ubuntu13.11",
"protocol_version": "2.0",
"software_version": "OpenSSH_9.6p1",
"comment": "Ubuntu-3ubuntu13.11"
},
"kex_init_message": {
"kex_algorithms": [
"[email protected]",
"curve25519-sha256",
"[email protected]",
"ecdh-sha2-nistp256",
"ecdh-sha2-nistp384",
"ecdh-sha2-nistp521",
"diffie-hellman-group-exchange-sha256",
"diffie-hellman-group16-sha512",
"diffie-hellman-group18-sha512",
"diffie-hellman-group14-sha256",
"ext-info-s",
"[email protected]"
],
"host_key_algorithms": [
"rsa-sha2-512",
"rsa-sha2-256",
"ecdsa-sha2-nistp256",
"ssh-ed25519"
],
"client_to_server_ciphers": [
"[email protected]",
"aes128-ctr",
"aes192-ctr",
"aes256-ctr",
"[email protected]",
"[email protected]"
],
"server_to_client_ciphers": [
"[email protected]",
"aes128-ctr",
"aes192-ctr",
"aes256-ctr",
"[email protected]",
"[email protected]"
],
"client_to_server_macs": [
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"hmac-sha2-256",
"hmac-sha2-512",
"hmac-sha1"
],
"server_to_client_macs": [
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"hmac-sha2-256",
"hmac-sha2-512",
"hmac-sha1"
],
"client_to_server_compression": [
"none",
"[email protected]"
],
"server_to_client_compression": [
"none",
"[email protected]"
],
"first_kex_follows": false
},
"algorithm_selection": {
"kex_algorithm": "[email protected]",
"host_key_algorithm": "ecdsa-sha2-nistp256",
"client_to_server_alg_group": {
"cipher": "aes128-ctr",
"mac": "hmac-sha2-256",
"compression": "none"
},
"server_to_client_alg_group": {
"cipher": "aes128-ctr",
"mac": "hmac-sha2-256",
"compression": "none"
}
},
"server_host_key": {
"fingerprint_sha256": "bdbd4015cfb8d48b42180bcbac017ca3c0958b14837b78943f23dd331428dbc9",
"ecdsa_public_key": {
"_encoding": {
"b": "DISPLAY_BASE64",
"gx": "DISPLAY_BASE64",
"gy": "DISPLAY_BASE64",
"n": "DISPLAY_BASE64",
"p": "DISPLAY_BASE64",
"x": "DISPLAY_BASE64",
"y": "DISPLAY_BASE64"
},
"b": "WsY12Ko6k+ez671VdpiGvGUdBrDMU7D2O848PifSYEs=",
"curve": "P-256",
"gx": "axfR8uEsQkf4vOblY6RA8ncDfYEt6zOg9KE5RdiYwpY=",
"gy": "T+NC4v4af5uO5+tKfA+eFivOM1drMV7Oy7ZAaDe/UfU=",
"length": 256,
"n": "/////wAAAAD//////////7zm+q2nF56E87nKwvxjJVE=",
"p": "/////wAAAAEAAAAAAAAAAAAAAAD///////////////8=",
"x": "I87gI9SZAUeVXYXkoNx4CSFzlYdQwFN9iR0l1tVQ8/U=",
"y": "5Vs0qWC8E46bzjWMuTml8pMsD1U4jDxkJJEDCuH+Hkk="
}
},
"hassh_fingerprint": "e42184b06d45385a906f0803d04c83da"
},
"transport_protocol": "TCP",
"truncated": false
},
{
"_decoded": "http",
"_encoding": {
"banner": "DISPLAY_UTF8",
"banner_hex": "DISPLAY_HEX"
},
"banner": "HTTP/1.1 200 OK\r\nServer: nginx/1.24.0 (Ubuntu)\r\nDate: <REDACTED>\r\nContent-Type: text/html\r\nLast-Modified: Fri, 25 Apr 2025 12:33:08 GMT\r\nTransfer-Encoding: chunked\r\nConnection: keep-alive\r\nETag: W/\"680b8104-267\"\r\nContent-Encoding: gzip\r\n",
"banner_hashes": [
"sha256:85fcc35f6e155a2c09dbcdc9d70f0d2dae44fe2f7b968268a759391953627812"
],
"banner_hex": "485454502f312e3120323030204f4b0d0a5365727665723a206e67696e782f312e32342e3020285562756e7475290d0a446174653a20203c52454441435445443e0d0a436f6e74656e742d547970653a20746578742f68746d6c0d0a4c6173742d4d6f6469666965643a204672692c2032352041707220323032352031323a33333a303820474d540d0a5472616e736665722d456e636f64696e673a206368756e6b65640d0a436f6e6e656374696f6e3a206b6565702d616c6976650d0a455461673a20572f2236383062383130342d323637220d0a436f6e74656e742d456e636f64696e673a20677a69700d0a",
"discovery_method": "IPV4_WALK_CLOUD_PRIORITY_1",
"extended_service_name": "HTTP",
"http": {
"request": {
"method": "GET",
"uri": "http://20.68.131.221/",
"headers": {
"User_Agent": [
"Mozilla/5.0 (compatible; CensysInspect/1.1; +https://about.censys.io/)"
],
"_encoding": {
"User_Agent": "DISPLAY_UTF8",
"Accept": "DISPLAY_UTF8"
},
"Accept": [
"*/*"
]
}
},
"response": {
"protocol": "HTTP/1.1",
"status_code": 200,
"status_reason": "OK",
"headers": {
"Server": [
"nginx/1.24.0 (Ubuntu)"
],
"_encoding": {
"Server": "DISPLAY_UTF8",
"Content_Type": "DISPLAY_UTF8",
"Transfer_Encoding": "DISPLAY_UTF8",
"Connection": "DISPLAY_UTF8",
"Date": "DISPLAY_UTF8",
"Content_Encoding": "DISPLAY_UTF8",
"ETag": "DISPLAY_UTF8",
"Last_Modified": "DISPLAY_UTF8"
},
"Content_Type": [
"text/html"
],
"Transfer_Encoding": [
"chunked"
],
"Connection": [
"keep-alive"
],
"Date": [
"<REDACTED>"
],
"Content_Encoding": [
"gzip"
],
"ETag": [
"W/\"680b8104-267\""
],
"Last_Modified": [
"Fri, 25 Apr 2025 12:33:08 GMT"
]
},
"_encoding": {
"html_tags": "DISPLAY_UTF8",
"body": "DISPLAY_UTF8",
"body_hash": "DISPLAY_UTF8",
"html_title": "DISPLAY_UTF8"
},
"html_tags": [
"<title>Welcome to nginx!</title>"
],
"body_size": 615,
"body": "<!DOCTYPE html>\n<html>\n<head>\n<title>Welcome to nginx!</title>\n<style>\nhtml { color-scheme: light dark; }\nbody { width: 35em; margin: 0 auto;\nfont-family: Tahoma, Verdana, Arial, sans-serif; }\n</style>\n</head>\n<body>\n<h1>Welcome to nginx!</h1>\n<p>If you see this page, the nginx web server is successfully installed and\nworking. Further configuration is required.</p>\n\n<p>For online documentation and support please refer to\n<a href=\"http://nginx.org/\">nginx.org</a>.<br/>\nCommercial support is available at\n<a href=\"http://nginx.com/\">nginx.com</a>.</p>\n\n<p><em>Thank you for using nginx.</em></p>\n</body>\n</html>\n",
"body_hashes": [
"sha256:fb47468a2cd3953c7131431991afcc6a2703f14640520102eea0a685a7e8d6de",
"sha1:c51a3f0e6de4eb802d5630941c3fd9e1d0efae4b",
"tlsh:52f0029be3002227b48343013cb35a21775503e40354cb5578c74dd3ef2a913f4175b8"
],
"body_hash": "sha1:c51a3f0e6de4eb802d5630941c3fd9e1d0efae4b",
"html_title": "Welcome to nginx!"
},
"supports_http2": false
},
"labels": [
"default-landing-page"
],
"observed_at": "2025-07-12T07:39:38.014298022Z",
"perspective_id": "PERSPECTIVE_UNKNOWN",
"port": 80,
"service_name": "HTTP",
"software": [
{
"uniform_resource_identifier": "cpe:2.3:o:*:linux:*:*:*:*:*:*:*:*",
"part": "o",
"product": "linux",
"source": "OSI_TRANSPORT_LAYER"
},
{
"uniform_resource_identifier": "cpe:2.3:a:f5:nginx:1.24.0:*:*:*:*:*:*:*",
"part": "a",
"vendor": "nginx",
"product": "nginx",
"version": "1.24.0",
"other": {
"family": "nginx"
},
"source": "OSI_APPLICATION_LAYER"
}
],
"source_ip": "199.45.155.73",
"transport_fingerprint": {
"id": 426,
"os": "Ubuntu",
"raw": "65160,64,true,MSTNW,1440,false,false"
},
"transport_protocol": "TCP",
"truncated": false
},
{
"_decoded": "http",
"_encoding": {
"banner": "DISPLAY_UTF8",
"certificate": "DISPLAY_HEX",
"banner_hex": "DISPLAY_HEX"
},
"banner": "HTTP/1.1 502 Bad Gateway\r\nServer: nginx/1.24.0 (Ubuntu)\r\nDate: <REDACTED>\r\nContent-Type: text/html\r\nContent-Length: 166\r\nConnection: keep-alive\r\n",
"banner_hashes": [
"sha256:d1c2b8ae6f94bafafb09ec76fd65c2aef2e575ff46a185cd5a9e88765f9280af"
],
"banner_hex": "485454502f312e31203530322042616420476174657761790d0a5365727665723a206e67696e782f312e32342e3020285562756e7475290d0a446174653a20203c52454441435445443e0d0a436f6e74656e742d547970653a20746578742f68746d6c0d0a436f6e74656e742d4c656e6774683a203136360d0a436f6e6e656374696f6e3a206b6565702d616c6976650d0a",
"certificate": "5f01e82c05e8b7117b430bbb6a5ed67677c518d00f8b36bc79b9be4b453683a6",
"discovery_method": "PREDICTIVE_METHOD_20",
"extended_service_name": "HTTPS",
"http": {
"request": {
"method": "GET",
"uri": "https://20.68.131.221/",
"headers": {
"User_Agent": [
"Mozilla/5.0 (compatible; CensysInspect/1.1; +https://about.censys.io/)"
],
"_encoding": {
"User_Agent": "DISPLAY_UTF8",
"Accept": "DISPLAY_UTF8"
},
"Accept": [
"*/*"
]
}
},
"response": {
"protocol": "HTTP/1.1",
"status_code": 502,
"status_reason": "Bad Gateway",
"headers": {
"Date": [
"<REDACTED>"
],
"_encoding": {
"Date": "DISPLAY_UTF8",
"Server": "DISPLAY_UTF8",
"Content_Length": "DISPLAY_UTF8",
"Content_Type": "DISPLAY_UTF8",
"Connection": "DISPLAY_UTF8"
},
"Server": [
"nginx/1.24.0 (Ubuntu)"
],
"Content_Length": [
"166"
],
"Content_Type": [
"text/html"
],
"Connection": [
"keep-alive"
]
},
"_encoding": {
"html_tags": "DISPLAY_UTF8",
"body": "DISPLAY_UTF8",
"body_hash": "DISPLAY_UTF8",
"html_title": "DISPLAY_UTF8"
},
"html_tags": [
"<title>502 Bad Gateway</title>"
],
"body_size": 166,
"body": "<html>\r\n<head><title>502 Bad Gateway</title></head>\r\n<body>\r\n<center><h1>502 Bad Gateway</h1></center>\r\n<hr><center>nginx/1.24.0 (Ubuntu)</center>\r\n</body>\r\n</html>\r\n",
"body_hashes": [
"sha256:76440d13ed742192bf6577638dbc565059fb80edab73982971686169bf58c273",
"sha1:4227bb1e60f7fd4295b358e51e5fc9a54c37f910",
"tlsh:01c08c76a6023c1ce4f7b67d20c36190c29ad1a043d80a028040895731c319a8fcf392"
],
"body_hash": "sha1:4227bb1e60f7fd4295b358e51e5fc9a54c37f910",
"html_title": "502 Bad Gateway"
},
"supports_http2": false
},
"jarm": {
"_encoding": {
"fingerprint": "DISPLAY_HEX",
"cipher_and_version_fingerprint": "DISPLAY_HEX",
"tls_extensions_sha256": "DISPLAY_HEX"
},
"fingerprint": "27d40d40d00040d00042d43d000000d2e61cae37a985f75ecafb81b33ca523",
"cipher_and_version_fingerprint": "27d40d40d00040d00042d43d000000",
"tls_extensions_sha256": "d2e61cae37a985f75ecafb81b33ca523",
"observed_at": "2025-07-09T18:07:50.933490878Z"
},
"observed_at": "2025-07-12T03:18:17.519333629Z",
"perspective_id": "PERSPECTIVE_UNKNOWN",
"port": 443,
"service_name": "HTTP",
"software": [
{
"uniform_resource_identifier": "cpe:2.3:o:*:linux:*:*:*:*:*:*:*:*",
"part": "o",
"product": "linux",
"source": "OSI_TRANSPORT_LAYER"
},
{
"uniform_resource_identifier": "cpe:2.3:a:f5:nginx:1.24.0:*:*:*:*:*:*:*",
"part": "a",
"vendor": "nginx",
"product": "nginx",
"version": "1.24.0",
"other": {
"family": "nginx"
},
"source": "OSI_APPLICATION_LAYER"
}
],
"source_ip": "199.45.154.117",
"tls": {
"version_selected": "TLSv1_3",
"cipher_selected": "TLS_CHACHA20_POLY1305_SHA256",
"certificates": {
"_encoding": {
"leaf_fp_sha_256": "DISPLAY_HEX",
"chain_fps_sha_256": "DISPLAY_HEX"
},
"leaf_fp_sha_256": "5f01e82c05e8b7117b430bbb6a5ed67677c518d00f8b36bc79b9be4b453683a6",
"chain_fps_sha_256": [
"76e9e288aafc0e37f4390cbf946aad997d5c1c901b3ce513d3d8fadbabe2ab85"
],
"leaf_data": {
"names": [
"anytest.mavistech.cloud"
],
"subject_dn": "CN=anytest.mavistech.cloud",
"issuer_dn": "C=US, O=Let's Encrypt, CN=E6",
"pubkey_bit_size": 256,
"pubkey_algorithm": "ECDSA",
"tbs_fingerprint": "823bbb54bbb492127e04ceef8f270d9eabaa85da577ae0ac3bd4a0bd79dc3336",
"fingerprint": "5f01e82c05e8b7117b430bbb6a5ed67677c518d00f8b36bc79b9be4b453683a6",
"issuer": {
"common_name": [
"E6"
],
"organization": [
"Let's Encrypt"
],
"country": [
"US"
]
},
"subject": {
"common_name": [
"anytest.mavistech.cloud"
]
},
"public_key": {
"key_algorithm": "ECDSA",
"ecdsa": {
"_encoding": {
"b": "DISPLAY_BASE64",
"gx": "DISPLAY_BASE64",
"gy": "DISPLAY_BASE64",
"n": "DISPLAY_BASE64",
"p": "DISPLAY_BASE64",
"x": "DISPLAY_BASE64",
"y": "DISPLAY_BASE64"
},
"b": "WsY12Ko6k+ez671VdpiGvGUdBrDMU7D2O848PifSYEs=",
"curve": "P-256",
"gx": "axfR8uEsQkf4vOblY6RA8ncDfYEt6zOg9KE5RdiYwpY=",
"gy": "T+NC4v4af5uO5+tKfA+eFivOM1drMV7Oy7ZAaDe/UfU=",
"length": 256,
"n": "/////wAAAAD//////////7zm+q2nF56E87nKwvxjJVE=",
"p": "/////wAAAAEAAAAAAAAAAAAAAAD///////////////8=",
"x": "UYH2JusrMsEKpGFJLv6iMkNJd7AdhzCmwATYnA4K6VU=",
"y": "FQXXNZwgElQAoHxFlgQMyyq01tSHXHjEpokuU3gv99k="
},
"fingerprint": "76215a3862c9bc7097dc8bc3be08eaf4cb53107dbac5e4128bd8c826243aa63c"
},
"signature": {
"signature_algorithm": "ECDSA-SHA384",
"self_signed": false
}
},
"chain": [
{
"fingerprint": "76e9e288aafc0e37f4390cbf946aad997d5c1c901b3ce513d3d8fadbabe2ab85",
"subject_dn": "C=US, O=Let's Encrypt, CN=E6",
"issuer_dn": "C=US, O=Internet Security Research Group, CN=ISRG Root X1"
}
]
},
"_encoding": {
"ja3s": "DISPLAY_HEX"
},
"ja3s": "475c9302dc42b2751db9edcac3b74891",
"ja4s": "t130200_1303_a56c5b993250",
"versions": [
{
"tls_version": "TLSv1_3",
"_encoding": {
"ja3s": "DISPLAY_HEX"
},
"ja3s": "475c9302dc42b2751db9edcac3b74891",
"ja4s": "t130200_1303_a56c5b993250"
},
{
"tls_version": "TLSv1_2",
"_encoding": {
"ja3s": "DISPLAY_HEX"
},
"ja3s": "954f7e9207d4c9012fd0692885732b12",
"ja4s": "t120200_cca9_344b4dce5a52"
}
]
},
"transport_fingerprint": {
"id": 426,
"os": "Ubuntu",
"raw": "65160,64,true,MSTNW,1440,false,false"
},
"transport_protocol": "TCP",
"truncated": false
},
{
"_decoded": "http",
"_encoding": {
"banner": "DISPLAY_UTF8",
"banner_hex": "DISPLAY_HEX"
},
"banner": "HTTP/1.1 200 OK\r\nServer: Werkzeug/3.1.3 Python/3.12.3\r\nDate: <REDACTED>\r\nContent-Type: text/html; charset=utf-8\r\nContent-Length: 12658\r\nAccess-Control-Allow-Origin: *\r\nConnection: close\r\n",
"banner_hashes": [
"sha256:0d597e07a717a4de713a0312ec61a1c2ac372f3bc4f9d2fd9b7baf13e03eab54"
],
"banner_hex": "485454502f312e3120323030204f4b0d0a5365727665723a205765726b7a6575672f332e312e3320507974686f6e2f332e31322e330d0a446174653a20203c52454441435445443e0d0a436f6e74656e742d547970653a20746578742f68746d6c3b20636861727365743d7574662d380d0a436f6e74656e742d4c656e6774683a2031323635380d0a4163636573732d436f6e74726f6c2d416c6c6f772d4f726967696e3a202a0d0a436f6e6e656374696f6e3a20636c6f73650d0a",
"discovery_method": "IPV4_WALK_FULL_PRIORITY_1",
"extended_service_name": "HTTP",
"http": {
"request": {
"method": "GET",
"uri": "http://20.68.131.221:5000/",
"headers": {
"User_Agent": [
"Mozilla/5.0 (compatible; CensysInspect/1.1; +https://about.censys.io/)"
],
"_encoding": {
"User_Agent": "DISPLAY_UTF8",
"Accept": "DISPLAY_UTF8"
},
"Accept": [
"*/*"
]
}
},
"response": {
"protocol": "HTTP/1.1",
"status_code": 200,
"status_reason": "OK",
"headers": {
"Connection": [
"close"
],
"_encoding": {
"Connection": "DISPLAY_UTF8",
"Date": "DISPLAY_UTF8",
"Server": "DISPLAY_UTF8",
"Content_Length": "DISPLAY_UTF8",
"Content_Type": "DISPLAY_UTF8",
"Access_Control_Allow_Origin": "DISPLAY_UTF8"
},
"Date": [
"<REDACTED>"
],
"Server": [
"Werkzeug/3.1.3 Python/3.12.3"
],
"Content_Length": [
"12658"
],
"Content_Type": [
"text/html; charset=utf-8"
],
"Access_Control_Allow_Origin": [
"*"
]
},
"_encoding": {
"html_tags": "DISPLAY_UTF8",
"body": "DISPLAY_UTF8",
"body_hash": "DISPLAY_UTF8",
"html_title": "DISPLAY_UTF8"
},
"html_tags": [
"<title>Stateful Navigation API (Split Audio/Data + Integrated Milestones)</title>",
"<meta charset=\"UTF-8\">"
],
"body_size": 12658,
"body": "\n <!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <title>Stateful Navigation API (Split Audio/Data + Integrated Milestones)</title>\n <style>\n body { font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; line-height: 1.6; padding: 25px; max-width: 900px; margin: auto; color: #333; }\n h1, h2, h3, h4 { color: #111; border-bottom: 1px solid #eee; padding-bottom: 5px; }\n h1 { text-align: center; border-bottom-width: 2px; }\n h2 { margin-top: 30px; }\n code { background-color: #eef; padding: 3px 6px; border-radius: 4px; font-family: 'Courier New', Courier, monospace; font-size: 0.95em; }\n pre { background-color: #eef; padding: 12px; border-radius: 4px; overflow-x: auto; font-family: 'Courier New', Courier, monospace; font-size: 0.9em; word-wrap: break-word; white-space: pre-wrap;}\n .endpoint { border-left: 4px solid #007bff; padding-left: 20px; margin: 25px 0; background-color: #f8f9fa; border-radius: 5px; padding: 15px; }\n .method { font-weight: bold; color: #28a745; /* Green for POST */ }\n .header { color: #dc3545; /* Red for Header */ }\n .param { font-weight: bold; color: #555; }\n .field { font-style: italic; color: #0056b3; } /* Blue for JSON fields */\n .status { font-weight: bold; }\n .status-success { color: #198754; } /* Green */\n .status-progress { color: #ffc107; } /* Yellow */\n .status-info { color: #0dcaf0; } /* Cyan */\n .status-error { color: #dc3545; } /* Red */\n .flow-step { font-weight: bold; margin-top: 15px; color: #0d6efd; /* Blue */}\n .note { font-style: italic; color: #6c757d; margin-top: 5px; display: block; }\n .highlight { background-color: #fff3cd; padding: 2px 4px; border-radius: 3px; font-weight: bold; } /* Highlight milestones */\n ul { padding-left: 20px; }\n li { margin-bottom: 8px; }\n table { border-collapse: collapse; width: 100%; margin: 15px 0; }\n th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }\n th { background-color: #f2f2f2; }\n </style>\n</head>\n<body>\n\n <h1>Stateful Navigation API (Split Audio/Data + Integrated Milestones)</h1>\n <p>API for audio-driven, turn-by-turn navigation. Uses a <strong>split audio/data flow</strong> for initiation. Navigation steps are provided intelligently based on proximity/time. Nearby user-defined <span class=\"highlight\">Milestones</span> are announced directly within navigation instructions.</p>\n\n <h2>Core Details</h2>\n <ul>\n <li><strong>Base URL:</strong> <code>http://20.68.131.221:5000</code></li>\n <li><strong>Authentication:</strong> Requires <code class=\"header\">Authentication: YOUR_USER_ID</code> header for all endpoints.</li>\n <li><strong>Input Audio:</strong> Raw binary to <code>/upload_audio</code>.</li>\n <li><strong>Output Audio:</strong> Base64 encoded WAV (16Khz, 16-bit Mono PCM) in JSON responses.</li>\n <li><strong>Data Requests:</strong> Use <code>application/json</code> for location/context endpoints.</li>\n </ul>\n\n <hr>\n\n <h2>Audio Navigation Workflow</h2>\n <p>The main flow involves multiple steps due to separate audio/data submission and the stateful step loop:</p>\n\n <!-- Step 1: Upload Query Audio -->\n <div class=\"endpoint\">\n <h3>Step 1: Upload Query Audio</h3>\n <p><code class=\"method\">POST /upload_audio</code></p>\n <p>Uploads the user's spoken destination query.</p>\n <h4>Input</h4>\n <ul>\n <li><strong>Header:</strong> <code class=\"header\">Authentication: YOUR_USER_ID</code></li>\n <li><strong>Body:</strong> Raw binary audio data.</li>\n </ul>\n <h4>Output (JSON)</h4>\n <pre><code>{ \"status\": \"AudioReceived\", \"upload_id\": \"up_...\" }</code></pre>\n <span class=\"note\">Save the `upload_id`.</span>\n </div>\n\n <!-- Step 2: Process Query Location -->\n <div class=\"endpoint\">\n <h3>Step 2: Send Location for Query</h3>\n <p><code class=\"method\">POST /process_query_location</code></p>\n <p>Processes the query using the uploaded audio and location. Fetches milestones if navigation starts.</p>\n <h4>Input (<code>application/json</code>)</h4>\n <pre><code>{\n \"upload_id\": \"up_...\", // From Step 1\n \"latitude\": float,\n \"longitude\": float,\n \"heading\": float // Optional\n}</code></pre>\n <h4>Output (JSON) - Key Scenarios</h4>\n <ul>\n <li><span class=\"status status-progress\">Status: \"AwaitingSelection\"</span>\n <ul>\n <li>POI options found. Play <code class=\"field\">audio_response_b64</code> or use TTS on <code class=\"field\">prompt_text</code>.</li>\n <li>Save <code class=\"field\">state_id</code> (e.g., `poi_...`) for Step 4.</li>\n </ul>\n </li>\n <li><span class=\"status status-success\">Status: \"NavigationStarted\"</span>\n <ul>\n <li>Direct navigation begins.</li>\n <li>Play <code class=\"field\">initial_audio_steps_b64</code> list or use TTS on <code class=\"field\">initial_steps_text</code> list (first few steps, <span class=\"highlight\">may include milestone announcements at the start</span>).</li>\n <li>Save <code class=\"field\">navigation_session_id</code> (e.g., `nav_...`) for Step 5 onwards.</li>\n </ul>\n </li>\n <li><span class=\"status status-error\">Status: \"Error\"</span> (4xx/5xx)\n <ul>\n <li>Play <code class=\"field\">audio_response_b64</code> or use TTS on <code class=\"field\">error_message</code>.</li>\n </ul>\n </li>\n </ul>\n </div>\n\n <hr>\n <p><strong>--- If Status was \"AwaitingSelection\", proceed to Steps 3 & 4 ---</strong></p>\n\n <!-- Step 3: Upload Selection Audio -->\n <div class=\"endpoint\">\n <h3>Step 3: Upload Selection Audio</h3>\n <p><code class=\"method\">POST /upload_audio</code></p>\n <p>Uploads the user's spoken POI option number.</p>\n <h4>Input</h4>\n <ul>\n <li><strong>Header:</strong> <code class=\"header\">Authentication: YOUR_USER_ID</code></li>\n <li><strong>Body:</strong> Raw binary audio data.</li>\n </ul>\n <h4>Output (JSON)</h4>\n <pre><code>{ \"status\": \"AudioReceived\", \"upload_id\": \"up_...\" }</code></pre>\n <span class=\"note\">Save the NEW `upload_id`.</span>\n </div>\n\n <!-- Step 4: Process Selection -->\n <div class=\"endpoint\">\n <h3>Step 4: Send Selection Context</h3>\n <p><code class=\"method\">POST /process_selection_location</code></p>\n <p>Processes the selection, starts navigation, and fetches milestones.</p>\n <h4>Input (<code>application/json</code>)</h4>\n <pre><code>{\n \"upload_id\": \"up_...\", // From Step 3\n \"state_id\": \"poi_...\" // From Step 2\n}</code></pre>\n <h4>Output (JSON) - Key Scenarios</h4>\n <ul>\n <li><span class=\"status status-success\">Status: \"NavigationStarted\"</span>\n <ul>\n <li>POI selected. Navigation begins.</li>\n <li>Play <code class=\"field\">initial_audio_steps_b64</code> list or use TTS on <code class=\"field\">initial_steps_text</code> list (first few steps, <span class=\"highlight\">may include milestone announcements at the start</span>).</li>\n <li>Save <code class=\"field\">navigation_session_id</code> (e.g., `nav_...`) for Step 5 onwards.</li>\n </ul>\n </li>\n <li><span class=\"status status-error\">Status: \"Error\"</span> (4xx/5xx)\n <ul>\n <li>Selection failed. Play <code class=\"field\">audio_response_b64</code> or use TTS on <code class=\"field\">error_message</code>.</li>\n <li>May return original <code class=\"field\">state_id</code> for retry of Steps 3 & 4.</li>\n </ul>\n </li>\n </ul>\n </div>\n\n <hr>\n <p><strong>--- If Status was \"NavigationStarted\" (from Step 2 or Step 4), proceed to Step 5 ---</strong></p>\n\n <!-- Step 5: Get Next Steps (Loop) -->\n <div class=\"endpoint\">\n <h3>Step 5: Get Next Steps (Repeat with Smart Logic + Integrated Milestones)</h3>\n <p><code class=\"method\">POST /get_next_steps</code></p>\n <p>Call periodically (e.g., every 5-10s). Server checks proximity to next turn AND nearby milestones, returning relevant instructions only.</p>\n <h4>Input: <code>application/json</code></h4>\n <pre><code>{\n \"navigation_session_id\": \"nav_...\", // Session ID from Step 2 or 4\n \"latitude\": CURRENT_LATITUDE,\n \"longitude\": CURRENT_LONGITUDE,\n \"heading\": CURRENT_HEADING // Optional\n}</code></pre>\n <h4>Output (JSON) - Key Scenarios (200 OK)</h4>\n <ul>\n <li><span class=\"status status-progress\">Status: \"NextSteps\"</span>\n <ul>\n <li><strong>Condition:</strong> User near next maneuver OR time threshold met.</li>\n <li><strong>Action:</strong> Play <code class=\"field\">audio_steps_b64</code> list or use TTS on <code class=\"field\">steps_text</code> list. <span class=\"highlight\">Nearby milestone announcements (e.g., \"Park Gate ahead.\") will be included as items at the beginning of these lists if applicable.</span></li>\n <li>Repeat Step 5.</li>\n </ul>\n </li>\n <li><span class=\"status status-progress\">Status: \"ApproachingDestination\"</span>\n <ul>\n <li><strong>Condition:</strong> Same as \"NextSteps\", but batch includes arrival step.</li>\n <li><strong>Action:</strong> Play <code class=\"field\">audio_steps_b64</code> / use TTS on <code class=\"field\">steps_text</code>. <span class=\"highlight\">Milestone announcements may be prepended.</span></li>\n <li>Repeat Step 5 (optional).</li>\n </ul>\n </li>\n <li><span class=\"status status-info\">Status: \"OnRoute\"</span>\n <ul>\n <li><strong>Condition:</strong> No new navigation step needed yet.</li>\n <li><strong>Action:</strong> Play optional <code class=\"field\">audio_message_b64</code> or use TTS on <code class=\"field\">message</code>. <span class=\"highlight\">This message will include nearby milestone announcements prepended if applicable (e.g., \"Library to your right. Continue. Next turn in 80 meters.\").</span></li>\n <li>Repeat Step 5.</li>\n </ul>\n </li>\n <li><span class=\"status status-success\">Status: \"Arrived\"</span>\n <ul>\n <li><strong>Condition:</strong> Server determines arrival.</li>\n <li><strong>Action:</strong> Play <code class=\"field\">audio_message_b64</code> OR use TTS on <code class=\"field\">message</code>.</li>\n <li>Stop calling this endpoint.</li>\n </ul>\n </li>\n <li><span class=\"status status-error\">Status: \"Error\"</span> (4xx/5xx)\n <ul>\n <li><strong>Condition:</strong> Session invalid, internal error, etc.</li>\n <li><strong>Action:</strong> Use TTS on <code class=\"field\">error_message</code>.</li>\n <li>Stop calling this endpoint.</li>\n </ul>\n </li>\n </ul>\n <span class=\"note\">Milestone announcements are now part of the main audio/text fields, not a separate list.</span>\n </div>\n\n <hr>\n\n <h2>Other Endpoints</h2>\n <!-- Text POI Search -->\n <div class=\"endpoint\">\n <h3>Text POI Search</h3>\n <p><code class=\"method\">POST /search_poi</code></p>\n <p>(Stateless) Input JSON: {\"query\", \"latitude\", \"longitude\"}. Returns JSON: {\"status\":\"ok\", \"results\": [...]}.</p>\n </div>\n <!-- Text Navigation Start -->\n <div class=\"endpoint\">\n <h3>Start Text Navigation</h3>\n <p><code class=\"method\">POST /navigate</code></p>\n <p>(Starts stateful session) Input JSON: {\"destination_query\", ...}. Returns JSON: {\"status\":\"NavigationStarted\", \"navigation_session_id\", ...}. Use `/get_next_steps` to continue.</p>\n </div>\n <!-- Documentation -->\n <div class=\"endpoint\">\n <h3>API Documentation</h3>\n <p><code>GET /</code></p><p>Returns this HTML documentation.</p>\n </div>\n\n</body>\n</html>\n ",
"body_hashes": [
"sha256:84cec51cb278c78663ede35589f58b470084f5048b8fe93fde8b8791b71471b5",
"sha1:4698154328131a2ad6155fc1ec4d30b5cdd7275e",
"tlsh:c7423252d4f51103496b81c76e6183b53eaf904be60a22827dec835c9f89c81ad7774f"
],
"body_hash": "sha1:4698154328131a2ad6155fc1ec4d30b5cdd7275e",
"html_title": "Stateful Navigation API (Split Audio/Data + Integrated Milestones)"
},
"supports_http2": false
},
"observed_at": "2025-07-12T13:15:14.298927575Z",
"perspective_id": "PERSPECTIVE_UNKNOWN",
"port": 5000,
"service_name": "HTTP",
"software": [
{
"product": "python",
"version": "3.12.3",
"source": "OSI_APPLICATION_LAYER"
},
{
"uniform_resource_identifier": "cpe:2.3:a:palletsprojects:werkzeug:3.1.3:*:*:*:*:*:*:*",
"part": "a",
"vendor": "PalletsProjects",
"product": "Werkzeug",
"version": "3.1.3",
"source": "OSI_APPLICATION_LAYER"
}
],
"source_ip": "206.168.34.216",
"transport_protocol": "TCP",
"truncated": false
},
{
"_decoded": "http",
"_encoding": {
"banner": "DISPLAY_UTF8",
"banner_hex": "DISPLAY_HEX"
},
"banner": "HTTP/1.1 200 OK\r\nServer: Werkzeug/3.1.3 Python/3.12.3\r\nDate: <REDACTED>\r\nContent-Type: text/html; charset=utf-8\r\nContent-Length: 2972\r\nConnection: close\r\n",
"banner_hashes": [
"sha256:5a6dc32a2cf1cbb1f0a1ae51902e92aa2c70497c39d961bc0584d1d433dd6c18"
],
"banner_hex": "485454502f312e3120323030204f4b0d0a5365727665723a205765726b7a6575672f332e312e3320507974686f6e2f332e31322e330d0a446174653a20203c52454441435445443e0d0a436f6e74656e742d547970653a20746578742f68746d6c3b20636861727365743d7574662d380d0a436f6e74656e742d4c656e6774683a20323937320d0a436f6e6e656374696f6e3a20636c6f73650d0a",
"discovery_method": "PREDICTIVE_METHOD_18",
"extended_service_name": "HTTP",
"http": {
"request": {
"method": "GET",
"uri": "http://20.68.131.221:5001/",
"headers": {
"User_Agent": [
"Mozilla/5.0 (compatible; CensysInspect/1.1; +https://about.censys.io/)"
],
"_encoding": {
"User_Agent": "DISPLAY_UTF8",
"Accept": "DISPLAY_UTF8"
},
"Accept": [
"*/*"
]
}
},
"response": {
"protocol": "HTTP/1.1",
"status_code": 200,
"status_reason": "OK",
"headers": {
"Date": [
"<REDACTED>"
],
"_encoding": {
"Date": "DISPLAY_UTF8",
"Server": "DISPLAY_UTF8",
"Content_Length": "DISPLAY_UTF8",
"Content_Type": "DISPLAY_UTF8",
"Connection": "DISPLAY_UTF8"
},
"Server": [
"Werkzeug/3.1.3 Python/3.12.3"
],
"Content_Length": [
"2972"
],
"Content_Type": [
"text/html; charset=utf-8"
],
"Connection": [
"close"
]
},
"_encoding": {
"html_tags": "DISPLAY_UTF8",
"body": "DISPLAY_UTF8",
"body_hash": "DISPLAY_UTF8",
"html_title": "DISPLAY_UTF8"
},
"html_tags": [
"<title>Voice Assistant API (Binary V3)</title>",
"<meta charset=\"UTF-8\">"
],
"body_size": 2972,
"body": "\n<!DOCTYPE html>\n<html lang=\"en\">\n<head><meta charset=\"UTF-8\"><title>Voice Assistant API (Binary V3)</title><style>body{font-family:Arial,sans-serif;margin:20px;line-height:1.6;color:#333}h1,h2,h3{color:#0056b3}h1{border-bottom:2px solid #0056b3;padding-bottom:10px}h2{margin-top:35px;border-bottom:1px solid #ccc;padding-bottom:5px}h3{margin-top:25px;color:#1a73e8}code{background:#e8f0fe;padding:3px 6px;border-radius:4px;font-family:Consolas,\"Courier New\",monospace;color:#3c4043;border:1px solid #d1e0ff}pre{background:#f8f9fa;padding:15px;border-radius:5px;overflow-x:auto;border:1px solid #ddd;font-size:.9em}table{border-collapse:collapse;width:100%;margin-top:15px}th,td{border:1px solid #ddd;padding:12px;text-align:left}th{background-color:#f2f2f2}.note{background-color:#fff3cd;border:1px solid #ffeeba;padding:15px;border-radius:4px;margin-bottom:20px}.command-note{background-color:#d1ecf1;border:1px solid #bee5eb;padding:15px;border-radius:4px;margin-bottom:20px}</style></head>\n<body><h1>Voice Assistant API (Binary Input & Portal Messaging)</h1>\n<p>Processes raw binary audio or text. Handles commands (Call/SMS enriched via Contact & Portal APIs) and general AI queries.</p>\n<div class=\"note\"><strong>Auth:</strong> <code>Authentication: <UID></code> header required for <code>/assistant</code>, <code>/api/text</code>.</div>\n<div class=\"command-note\"><strong>Logic:</strong> 1. Hardcoded commands (Call/SMS try contact lookup). 2. AI fallback for Nav/Call/SMS (tries contact lookup). 3. Message to \"PORTAL\" uses dedicated API. 4. General AI. <strong>Commands always return JSON.</strong></div>\n<h3>POST /assistant</h3><p>Body: Raw PCM audio (e.g., <code>Content-Type: audio/l16; rate=16000</code>)</p>\n<p>Response: Command JSON (<code>Result:0, X-Response-Type:command</code>) or AI Text (<code>Result:0, X-Response-Type:ai_text</code>) or AI Audio Stream (<code>Result:1, X-Response-Type:ai_audio</code> with <code>X-Response-Text</code> header).</p>\n<pre>\n// Example Command Response (Call Mom)\n{\n \"feature\": 7,\n \"parameters\": { \"who\": \"Mom\", \"to\": \"+123...\", \"contact_name\": \"Mom (Mobile)\" }\n}\n// Example Command Response (Message Portal Success - Handled by Server, User gets Confirmation via AI Text/Audio)\n// The direct response from /assistant would be the confirmation text/audio.\n// If you need ESP32 to know portal message was attempted, server would need to send command JSON like:\n// { \"feature\": 9, \"parameters\": { \"recipient\": \"PORTAL\", \"status\": \"sent_to_portal\", \"api_response\": { ...portal_api_json_response... } } }\n// Current logic: if portal send succeeds/fails, it returns (user_message, False) to trigger AI text/audio response.\n</pre>\n<h3>POST /api/text</h3><p>Body: JSON <code>{\"text\": \"user query\"}</code></p><p>Response: Command JSON or AI Text JSON <code>{\"response\": \"...\", \"is_command\": false}</code>.</p>\n<h3>GET /health</h3><p>Service status.</p>\n<!-- Add Command Feature Summary Table if space/needed -->\n</body></html>",
"body_hashes": [
"sha256:1ee7bb085c555bb0c8346725e398641565f83925fb143d85719d6f1b26885885",
"sha1:9747567304e9a82322117b5a3500a28bef910591",
"tlsh:e651a825d58c01076a82c1dcf401bacd369e5314d16e92edbda5b728d8cc5aed42664d"
],
"body_hash": "sha1:9747567304e9a82322117b5a3500a28bef910591",
"html_title": "Voice Assistant API (Binary V3)"
},
"supports_http2": false
},
"observed_at": "2025-07-12T08:57:26.901677752Z",
"perspective_id": "PERSPECTIVE_UNKNOWN",
"port": 5001,
"service_name": "HTTP",
"software": [
{
"product": "python",
"version": "3.12.3",
"source": "OSI_APPLICATION_LAYER"
},
{
"uniform_resource_identifier": "cpe:2.3:a:palletsprojects:werkzeug:3.1.3:*:*:*:*:*:*:*",
"part": "a",
"vendor": "PalletsProjects",
"product": "Werkzeug",
"version": "3.1.3",
"source": "OSI_APPLICATION_LAYER"
}
],
"source_ip": "162.142.125.213",
"transport_protocol": "TCP",
"truncated": false
},
{
"_decoded": "http",
"_encoding": {
"banner": "DISPLAY_UTF8",
"banner_hex": "DISPLAY_HEX"
},
"banner": "HTTP/1.1 200 OK\r\nServer: Werkzeug/3.1.3 Python/3.12.3\r\nDate: <REDACTED>\r\nContent-Type: text/html; charset=utf-8\r\nContent-Length: 17604\r\nConnection: close\r\n",
"banner_hashes": [
"sha256:01e1f056e98202b92b35be11ec9401197516184f688b8c139df3e17ca4fa6f6b"
],
"banner_hex": "485454502f312e3120323030204f4b0d0a5365727665723a205765726b7a6575672f332e312e3320507974686f6e2f332e31322e330d0a446174653a20203c52454441435445443e0d0a436f6e74656e742d547970653a20746578742f68746d6c3b20636861727365743d7574662d380d0a436f6e74656e742d4c656e6774683a2031373630340d0a436f6e6e656374696f6e3a20636c6f73650d0a",
"discovery_method": "PREDICTIVE_METHOD_24",
"extended_service_name": "HTTP",
"http": {
"request": {
"method": "GET",
"uri": "http://20.68.131.221:5002/",
"headers": {
"User_Agent": [
"Mozilla/5.0 (compatible; CensysInspect/1.1; +https://about.censys.io/)"
],
"_encoding": {
"User_Agent": "DISPLAY_UTF8",
"Accept": "DISPLAY_UTF8"
},
"Accept": [
"*/*"
]
}
},
"response": {
"protocol": "HTTP/1.1",
"status_code": 200,
"status_reason": "OK",
"headers": {
"Date": [
"<REDACTED>"
],
"_encoding": {
"Date": "DISPLAY_UTF8",
"Server": "DISPLAY_UTF8",
"Content_Length": "DISPLAY_UTF8",
"Content_Type": "DISPLAY_UTF8",
"Connection": "DISPLAY_UTF8"
},
"Server": [
"Werkzeug/3.1.3 Python/3.12.3"
],
"Content_Length": [
"17604"
],
"Content_Type": [
"text/html; charset=utf-8"
],
"Connection": [
"close"
]
},
"_encoding": {
"html_tags": "DISPLAY_UTF8",
"body": "DISPLAY_UTF8",
"body_hash": "DISPLAY_UTF8",
"html_title": "DISPLAY_UTF8"
},
"html_tags": [
"<title>Mavis Tech Service Status</title>",
"<meta charset=\"utf-8\">",
"<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">"
],
"body_size": 17604,
"body": "<!doctype html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n <title>Mavis Tech Service Status</title>\n <!-- Bootstrap CSS -->\n <link href=\"https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css\" rel=\"stylesheet\" integrity=\"sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN\" crossorigin=\"anonymous\">\n <!-- Bootstrap Icons -->\n <link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/[email protected]/font/bootstrap-icons.css\">\n <style>\n body {\n /* Background Image Configuration */\n \n background-repeat: no-repeat; /* Don't tile the image */\n background-position: center center; /* Center the image */\n background-size: cover; /* Scale to cover the entire viewport */\n background-attachment: fixed; /* Keep background fixed during scroll */\n\n /* Fallback background color if image fails to load */\n background-color: #f8f9fa;\n /* Ensure minimum height for background visibility even with little content */\n min-height: 100vh;\n }\n .header-section {\n background-color: #343a40; /* Dark background for header */\n color: white; /* White text for header */\n padding: 1.5rem 1rem; /* Reduced header padding */\n border-radius: 0.5rem; /* Optional rounded corners */\n margin-bottom: 2rem; /* Space below header */\n }\n .header-section img {\n max-height: 50px; /* Slightly smaller logo */\n margin-bottom: 0.75rem;\n }\n .header-section h1 { /* We removed the H1 text, but keep style if added back */\n color: white;\n font-size: 1.75rem;\n }\n .header-section .text-muted {\n color: #adb5bd !important; /* Lighter gray for muted text on dark bg */\n font-size: 0.9em;\n }\n .card {\n transition: transform .2s ease-in-out, box-shadow .2s ease-in-out;\n margin-bottom: 1rem;\n /* Add a subtle transparency to cards to let background show through slightly (optional) */\n /* background-color: rgba(255, 255, 255, 0.95); */\n }\n .card:hover {\n transform: translateY(-5px);\n box-shadow: 0 .5rem 1rem rgba(0,0,0,.15)!important;\n }\n .card-header {\n padding: 0.5rem 0.75rem;\n font-size: 0.95rem;\n /* Slightly darker header for better contrast if card is transparent */\n /* background-color: rgba(248, 249, 250, 0.9); */\n /* border-bottom: 1px solid rgba(0,0,0,.125); */\n }\n .card-body {\n padding: 0.75rem;\n }\n .status-badge {\n font-size: 0.85rem;\n padding: 0.3em 0.6em;\n }\n .url-link {\n font-size: 0.75rem;\n word-break: break-all;\n margin-bottom: 0.5rem;\n }\n .status-details {\n font-size: 0.7rem;\n margin-top: 0.1rem !important;\n }\n .card-header i {\n margin-right: 6px;\n }\n /* Add some color to icons */\n .bi-display { color: cornflowerblue; }\n .bi-person-badge { color: darkcyan; }\n .bi-box-arrow-in-down { color: darkorange; }\n .bi-geo-alt-fill { color: forestgreen; }\n .bi-mic-fill { color: mediumpurple; }\n .bi-camera-fill { color: tomato; }\n .bi-hdd-stack-fill { color: slategray; }\n .bi-cpu-fill { color: #6f42c1; } /* Bootstrap purple */\n .bi-shield-lock-fill { color: goldenrod; }\n\n h2 { /* Category headings */\n font-size: 1.5rem;\n margin-bottom: 1rem !important;\n margin-top: 1.5rem !important;\n /* Add text shadow for readability against background image */\n text-shadow: 0px 1px 3px rgba(0, 0, 0, 0.2);\n /* Optional: Add a very subtle background/padding to H2 if needed */\n /* background-color: rgba(255, 255, 255, 0.7); */\n /* display: inline-block; */ /* Needed if using background-color */\n /* padding: 0.2em 0.4em; */\n /* border-radius: 0.25rem; */\n }\n </style>\n</head>\n<body>\n <div class=\"container mt-4 mb-5\">\n\n <!-- Updated Header Section -->\n <div class=\"text-center header-section shadow-sm\">\n <img src=\"https://mavistech.uk/wp-content/uploads/2024/02/logo.png\" alt=\"Mavis Tech Logo\">\n <p class=\"text-muted\">Last checked: 2025-07-12 05:34:03 </p>\n </div>\n <!-- End of Header Section -->\n\n \n <h2>APIs</h2>\n <div class=\"row row-cols-1 row-cols-md-2 row-cols-lg-3 g-3\">\n \n <div class=\"col\">\n <div class=\"card shadow-sm\">\n <div class=\"card-header fw-bold\">\n <i class=\"bi bi-geo-alt-fill\"></i> GPS API\n </div>\n <div class=\"card-body\">\n <div>\n <p class=\"url-link mb-2\">\n <a href=\"https://gps.mavistech.cloud\" target=\"_blank\" rel=\"noopener noreferrer\">https://gps.mavistech.cloud</a>\n </p>\n </div>\n <div class=\"text-center\">\n \n <span class=\"badge rounded-pill bg-success status-badge\"><i class=\"bi bi-check-circle-fill me-1\"></i>Online</span>\n \n <small class=\"text-muted d-block status-details\">(200)</small>\n \n \n </div>\n </div>\n </div>\n </div>\n \n <div class=\"col\">\n <div class=\"card shadow-sm\">\n <div class=\"card-header fw-bold\">\n <i class=\"bi bi-camera-fill\"></i> OCR API\n </div>\n <div class=\"card-body\">\n <div>\n <p class=\"url-link mb-2\">\n <a href=\"https://ocr.mavistech.cloud\" target=\"_blank\" rel=\"noopener noreferrer\">https://ocr.mavistech.cloud</a>\n </p>\n </div>\n <div class=\"text-center\">\n \n <span class=\"badge rounded-pill bg-success status-badge\"><i class=\"bi bi-check-circle-fill me-1\"></i>Online</span>\n \n <small class=\"text-muted d-block status-details\">(404)</small>\n \n \n </div>\n </div>\n </div>\n </div>\n \n <div class=\"col\">\n <div class=\"card shadow-sm\">\n <div class=\"card-header fw-bold\">\n <i class=\"bi bi-file-earmark-text\"></i> Photo Viewer - OCR \n </div>\n <div class=\"card-body\">\n <div>\n <p class=\"url-link mb-2\">\n <a href=\"http://20.68.131.221:8001\" target=\"_blank\" rel=\"noopener noreferrer\">http://20.68.131.221:8001</a>\n </p>\n </div>\n <div class=\"text-center\">\n \n <span class=\"badge rounded-pill bg-success status-badge\"><i class=\"bi bi-check-circle-fill me-1\"></i>Online</span>\n \n <small class=\"text-muted d-block status-details\">(200)</small>\n \n \n </div>\n </div>\n </div>\n </div>\n \n <div class=\"col\">\n <div class=\"card shadow-sm\">\n <div class=\"card-header fw-bold\">\n <i class=\"bi bi-image\"></i> Photo Viewer - Scene \n </div>\n <div class=\"card-body\">\n <div>\n <p class=\"url-link mb-2\">\n <a href=\"http://20.68.131.221:8002\" target=\"_blank\" rel=\"noopener noreferrer\">http://20.68.131.221:8002</a>\n </p>\n </div>\n <div class=\"text-center\">\n \n <span class=\"badge rounded-pill bg-success status-badge\"><i class=\"bi bi-check-circle-fill me-1\"></i>Online</span>\n \n <small class=\"text-muted d-block status-details\">(200)</small>\n \n \n </div>\n </div>\n </div>\n </div>\n \n <div class=\"col\">\n <div class=\"card shadow-sm\">\n <div class=\"card-header fw-bold\">\n <i class=\"bi bi-hdd-stack-fill\"></i> Scene API\n </div>\n <div class=\"card-body\">\n <div>\n <p class=\"url-link mb-2\">\n <a href=\"https://scene.mavistech.cloud\" target=\"_blank\" rel=\"noopener noreferrer\">https://scene.mavistech.cloud</a>\n </p>\n </div>\n <div class=\"text-center\">\n \n <span class=\"badge rounded-pill bg-success status-badge\"><i class=\"bi bi-check-circle-fill me-1\"></i>Online</span>\n \n <small class=\"text-muted d-block status-details\">(404)</small>\n \n \n </div>\n </div>\n </div>\n </div>\n \n <div class=\"col\">\n <div class=\"card shadow-sm\">\n <div class=\"card-header fw-bold\">\n <i class=\"bi bi-mic-fill\"></i> Voice Assistant API\n </div>\n <div class=\"card-body\">\n <div>\n <p class=\"url-link mb-2\">\n <a href=\"https://assistant.mavistech.cloud\" target=\"_blank\" rel=\"noopener noreferrer\">https://assistant.mavistech.cloud</a>\n </p>\n </div>\n <div class=\"text-center\">\n \n <span class=\"badge rounded-pill bg-success status-badge\"><i class=\"bi bi-check-circle-fill me-1\"></i>Online</span>\n \n <small class=\"text-muted d-block status-details\">(200)</small>\n \n \n </div>\n </div>\n </div>\n </div>\n \n </div>\n \n <h2>Device Management</h2>\n <div class=\"row row-cols-1 row-cols-md-2 row-cols-lg-3 g-3\">\n \n <div class=\"col\">\n <div class=\"card shadow-sm\">\n <div class=\"card-header fw-bold\">\n <i class=\"bi bi-cpu-fill\"></i> ESP32 Management\n </div>\n <div class=\"card-body\">\n <div>\n <p class=\"url-link mb-2\">\n <a href=\"http://20.117.120.208:8003\" target=\"_blank\" rel=\"noopener noreferrer\">http://20.117.120.208:8003</a>\n </p>\n </div>\n <div class=\"text-center\">\n \n <span class=\"badge rounded-pill bg-success status-badge\"><i class=\"bi bi-check-circle-fill me-1\"></i>Online</span>\n \n <small class=\"text-muted d-block status-details\">(200)</small>\n \n \n </div>\n </div>\n </div>\n </div>\n \n </div>\n \n <h2>Websites & Portals</h2>\n <div class=\"row row-cols-1 row-cols-md-2 row-cols-lg-3 g-3\">\n \n <div class=\"col\">\n <div class=\"card shadow-sm\">\n <div class=\"card-header fw-bold\">\n <i class=\"bi bi-shield-lock-fill\"></i> Admin Panel\n </div>\n <div class=\"card-body\">\n <div>\n <p class=\"url-link mb-2\">\n <a href=\"http://20.117.120.208:8000/admin/\" target=\"_blank\" rel=\"noopener noreferrer\">http://20.117.120.208:8000/admin/</a>\n </p>\n </div>\n <div class=\"text-center\">\n \n <span class=\"badge rounded-pill bg-success status-badge\"><i class=\"bi bi-check-circle-fill me-1\"></i>Online</span>\n \n <small class=\"text-muted d-block status-details\">(200)</small>\n \n \n </div>\n </div>\n </div>\n </div>\n \n <div class=\"col\">\n <div class=\"card shadow-sm\">\n <div class=\"card-header fw-bold\">\n <i class=\"bi bi-person-badge\"></i> Customer Portal\n </div>\n <div class=\"card-body\">\n <div>\n <p class=\"url-link mb-2\">\n <a href=\"https://office.mavistech.uk\" target=\"_blank\" rel=\"noopener noreferrer\">https://office.mavistech.uk</a>\n </p>\n </div>\n <div class=\"text-center\">\n \n <span class=\"badge rounded-pill bg-success status-badge\"><i class=\"bi bi-check-circle-fill me-1\"></i>Online</span>\n \n <small class=\"text-muted d-block status-details\">(200)</small>\n \n \n </div>\n </div>\n </div>\n </div>\n \n <div class=\"col\">\n <div class=\"card shadow-sm\">\n <div class=\"card-header fw-bold\">\n <i class=\"bi bi-box-arrow-in-down\"></i> OTA Update Portal\n </div>\n <div class=\"card-body\">\n <div>\n <p class=\"url-link mb-2\">\n <a href=\"http://20.117.120.208:5002\" target=\"_blank\" rel=\"noopener noreferrer\">http://20.117.120.208:5002</a>\n </p>\n </div>\n <div class=\"text-center\">\n \n <span class=\"badge rounded-pill bg-success status-badge\"><i class=\"bi bi-check-circle-fill me-1\"></i>Online</span>\n \n <small class=\"text-muted d-block status-details\">(200)</small>\n \n \n </div>\n </div>\n </div>\n </div>\n \n <div class=\"col\">\n <div class=\"card shadow-sm\">\n <div class=\"card-header fw-bold\">\n <i class=\"bi bi-telephone-forward\"></i> PBX Portal\n </div>\n <div class=\"card-body\">\n <div>\n <p class=\"url-link mb-2\">\n <a href=\"https://pbx.mavistech.cloud/admin/config.php\" target=\"_blank\" rel=\"noopener noreferrer\">https://pbx.mavistech.cloud/admin/config.php</a>\n </p>\n </div>\n <div class=\"text-center\">\n \n <span class=\"badge rounded-pill bg-success status-badge\"><i class=\"bi bi-check-circle-fill me-1\"></i>Online</span>\n \n <small class=\"text-muted d-block status-details\">(200)</small>\n \n \n </div>\n </div>\n </div>\n </div>\n \n </div>\n \n\n </div>\n\n <!-- Bootstrap JS Bundle (includes Popper) -->\n <script src=\"https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js\" integrity=\"sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL\" crossorigin=\"anonymous\"></script>\n</body>\n</html>",
"body_hashes": [
"sha256:41b7131c82f154c03e65a8c01a154965b754f2f8291a256167525ce2b308657a",
"sha1:667c2731887e7cc439592c57b50a727c402a043d",
"tlsh:8682c15455f235ab11c7c0f07a226b2b6f91d68bca5b8e5873ed1bc08f42d82ec57248"
],
"body_hash": "sha1:667c2731887e7cc439592c57b50a727c402a043d",
"html_title": "Mavis Tech Service Status"
},
"supports_http2": false
},
"labels": [
"bootstrap"
],
"observed_at": "2025-07-12T05:33:58.471612589Z",
"perspective_id": "PERSPECTIVE_UNKNOWN",
"port": 5002,
"service_name": "HTTP",
"software": [
{
"product": "python",
"version": "3.12.3",
"source": "OSI_APPLICATION_LAYER"
},
{
"uniform_resource_identifier": "cpe:2.3:a:palletsprojects:werkzeug:3.1.3:*:*:*:*:*:*:*",
"part": "a",
"vendor": "PalletsProjects",
"product": "Werkzeug",
"version": "3.1.3",
"source": "OSI_APPLICATION_LAYER"
}
],
"source_ip": "162.142.125.200",
"transport_protocol": "TCP",
"truncated": false
},
{
"_decoded": "http",
"_encoding": {
"banner": "DISPLAY_UTF8",
"banner_hex": "DISPLAY_HEX"
},
"banner": "HTTP/1.1 404 NOT FOUND\r\nServer: Werkzeug/3.1.3 Python/3.12.3\r\nDate: <REDACTED>\r\nContent-Type: text/html; charset=utf-8\r\nContent-Length: 207\r\nConnection: close\r\n",
"banner_hashes": [
"sha256:1200de7e0ca756c984e52d80f312858a5f60183453e530b9eac39f3ac0754867"
],
"banner_hex": "485454502f312e3120343034204e4f5420464f554e440d0a5365727665723a205765726b7a6575672f332e312e3320507974686f6e2f332e31322e330d0a446174653a20203c52454441435445443e0d0a436f6e74656e742d547970653a20746578742f68746d6c3b20636861727365743d7574662d380d0a436f6e74656e742d4c656e6774683a203230370d0a436f6e6e656374696f6e3a20636c6f73650d0a",
"discovery_method": "PREDICTIVE_METHOD_25",
"extended_service_name": "HTTP",
"http": {
"request": {
"method": "GET",
"uri": "http://20.68.131.221:5003/",
"headers": {
"User_Agent": [
"Mozilla/5.0 (compatible; CensysInspect/1.1; +https://about.censys.io/)"
],
"_encoding": {
"User_Agent": "DISPLAY_UTF8",
"Accept": "DISPLAY_UTF8"
},
"Accept": [
"*/*"
]
}
},
"response": {
"protocol": "HTTP/1.1",
"status_code": 404,
"status_reason": "NOT FOUND",
"headers": {
"Date": [
"<REDACTED>"
],
"_encoding": {
"Date": "DISPLAY_UTF8",
"Server": "DISPLAY_UTF8",
"Content_Length": "DISPLAY_UTF8",
"Content_Type": "DISPLAY_UTF8",
"Connection": "DISPLAY_UTF8"
},
"Server": [
"Werkzeug/3.1.3 Python/3.12.3"
],
"Content_Length": [
"207"
],
"Content_Type": [
"text/html; charset=utf-8"
],
"Connection": [
"close"
]
},
"_encoding": {
"html_tags": "DISPLAY_UTF8",
"body": "DISPLAY_UTF8",
"body_hash": "DISPLAY_UTF8",
"html_title": "DISPLAY_UTF8"
},
"html_tags": [
"<title>404 Not Found</title>"
],
"body_size": 207,
"body": "<!doctype html>\n<html lang=en>\n<title>404 Not Found</title>\n<h1>Not Found</h1>\n<p>The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.</p>\n",
"body_hashes": [
"sha256:e9639e3c4681ce85f852fbac48e2eeee5ba51296dbfec57c200d59b76237ab80",
"sha1:d767b3cb0ad66544c649e4165fc4b37e3c17e370",
"tlsh:dad0224ed30a032b0a20071035c11beb998f1322757612398f42583e6185b2d81e23c8"
],
"body_hash": "sha1:d767b3cb0ad66544c649e4165fc4b37e3c17e370",
"html_title": "404 Not Found"
},
"supports_http2": false
},
"observed_at": "2025-07-12T08:55:50.036702977Z",
"perspective_id": "PERSPECTIVE_UNKNOWN",
"port": 5003,
"service_name": "HTTP",
"software": [
{
"product": "python",
"version": "3.12.3",
"source": "OSI_APPLICATION_LAYER"
},
{
"uniform_resource_identifier": "cpe:2.3:a:palletsprojects:werkzeug:3.1.3:*:*:*:*:*:*:*",
"part": "a",
"vendor": "PalletsProjects",
"product": "Werkzeug",
"version": "3.1.3",
"source": "OSI_APPLICATION_LAYER"
}
],
"source_ip": "206.168.34.71",
"transport_protocol": "TCP",
"truncated": false
},
{
"_decoded": "http",
"_encoding": {
"banner": "DISPLAY_UTF8",
"banner_hex": "DISPLAY_HEX"
},
"banner": "HTTP/1.1 404 NOT FOUND\r\nServer: Werkzeug/3.1.3 Python/3.12.3\r\nDate: <REDACTED>\r\nContent-Type: application/json\r\nContent-Length: 459\r\nConnection: close\r\n",
"banner_hashes": [
"sha256:20b70952e400878d02bf5ab58c4be42e25875d7d8cac94690d59503e7139a1e6"
],
"banner_hex": "485454502f312e3120343034204e4f5420464f554e440d0a5365727665723a205765726b7a6575672f332e312e3320507974686f6e2f332e31322e330d0a446174653a20203c52454441435445443e0d0a436f6e74656e742d547970653a206170706c69636174696f6e2f6a736f6e0d0a436f6e74656e742d4c656e6774683a203435390d0a436f6e6e656374696f6e3a20636c6f73650d0a",
"discovery_method": "PREDICTIVE_METHOD_18",
"extended_service_name": "HTTP",
"http": {
"request": {
"method": "GET",
"uri": "http://20.68.131.221:5004/",
"headers": {
"User_Agent": [
"Mozilla/5.0 (compatible; CensysInspect/1.1; +https://about.censys.io/)"
],
"_encoding": {
"User_Agent": "DISPLAY_UTF8",
"Accept": "DISPLAY_UTF8"
},
"Accept": [
"*/*"
]
}
},
"response": {
"protocol": "HTTP/1.1",
"status_code": 404,
"status_reason": "NOT FOUND",
"headers": {
"Date": [
"<REDACTED>"
],
"_encoding": {
"Date": "DISPLAY_UTF8",
"Server": "DISPLAY_UTF8",
"Content_Length": "DISPLAY_UTF8",
"Content_Type": "DISPLAY_UTF8",
"Connection": "DISPLAY_UTF8"
},
"Server": [
"Werkzeug/3.1.3 Python/3.12.3"
],
"Content_Length": [
"459"
],
"Content_Type": [
"application/json"
],
"Connection": [
"close"
]
},
"body_size": 459,
"_encoding": {
"body": "DISPLAY_UTF8",
"body_hash": "DISPLAY_UTF8"
},
"body": "{\n \"available_endpoints\": [\n \"/analyze (POST) - Analyze image and get audio description\",\n \"/images/list (GET) - List all saved images\",\n \"/images/view/<filename> (GET) - View specific image\",\n \"/images/download/<filename> (GET) - Download specific image\",\n \"/gallery (GET) - View image gallery\",\n \"/health (GET) - Service health check\"\n ],\n \"documentation\": \"See API documentation for more details\",\n \"error\": \"Endpoint not found: /\"\n}\n",
"body_hashes": [
"sha256:b023b15a9efef62e8b68dedb46c41330d471d35cc043c6fda9d1e0ef053ba132",
"sha1:3f8231d4133116a067fe2f3f485c0e05b0f6093c",
"tlsh:ebf0206a8d47988b1957b551391e228a71e66107a846f710bbed0a0c0b3f62f593309f"
],
"body_hash": "sha1:3f8231d4133116a067fe2f3f485c0e05b0f6093c"
},
"supports_http2": false
},
"observed_at": "2025-07-12T08:47:59.534902002Z",
"perspective_id": "PERSPECTIVE_UNKNOWN",
"port": 5004,
"service_name": "HTTP",
"software": [
{
"product": "python",
"version": "3.12.3",
"source": "OSI_APPLICATION_LAYER"
},
{
"uniform_resource_identifier": "cpe:2.3:a:palletsprojects:werkzeug:3.1.3:*:*:*:*:*:*:*",
"part": "a",
"vendor": "PalletsProjects",
"product": "Werkzeug",
"version": "3.1.3",
"source": "OSI_APPLICATION_LAYER"
}
],
"source_ip": "167.94.146.50",
"transport_protocol": "TCP",
"truncated": false
},
{
"_decoded": "http",
"_encoding": {
"banner": "DISPLAY_UTF8",
"banner_hex": "DISPLAY_HEX"
},
"banner": "HTTP/1.1 200 OK\r\nServer: Werkzeug/3.1.3 Python/3.12.3\r\nDate: <REDACTED>\r\nContent-Type: text/html; charset=utf-8\r\nContent-Length: 25030\r\nAccess-Control-Allow-Origin: *\r\nConnection: close\r\n",
"banner_hashes": [
"sha256:ce8e6a8a8a534d03550848c27642e10d33c4cd663c5565f72f205b9cd5fced42"
],
"banner_hex": "485454502f312e3120323030204f4b0d0a5365727665723a205765726b7a6575672f332e312e3320507974686f6e2f332e31322e330d0a446174653a20203c52454441435445443e0d0a436f6e74656e742d547970653a20746578742f68746d6c3b20636861727365743d7574662d380d0a436f6e74656e742d4c656e6774683a2032353033300d0a4163636573732d436f6e74726f6c2d416c6c6f772d4f726967696e3a202a0d0a436f6e6e656374696f6e3a20636c6f73650d0a",
"discovery_method": "PREDICTIVE_METHOD_24",
"extended_service_name": "HTTP",
"http": {
"request": {
"method": "GET",
"uri": "http://20.68.131.221:5005/",
"headers": {
"User_Agent": [
"Mozilla/5.0 (compatible; CensysInspect/1.1; +https://about.censys.io/)"
],
"_encoding": {
"User_Agent": "DISPLAY_UTF8",
"Accept": "DISPLAY_UTF8"
},
"Accept": [
"*/*"
]
}
},
"response": {
"protocol": "HTTP/1.1",
"status_code": 200,
"status_reason": "OK",
"headers": {
"Connection": [
"close"
],
"_encoding": {
"Connection": "DISPLAY_UTF8",
"Date": "DISPLAY_UTF8",
"Server": "DISPLAY_UTF8",
"Content_Length": "DISPLAY_UTF8",
"Content_Type": "DISPLAY_UTF8",
"Access_Control_Allow_Origin": "DISPLAY_UTF8"
},
"Date": [
"<REDACTED>"
],
"Server": [
"Werkzeug/3.1.3 Python/3.12.3"
],
"Content_Length": [
"25030"
],
"Content_Type": [
"text/html; charset=utf-8"
],
"Access_Control_Allow_Origin": [
"*"
]
},
"_encoding": {
"html_tags": "DISPLAY_UTF8",
"body": "DISPLAY_UTF8",
"body_hash": "DISPLAY_UTF8",
"html_title": "DISPLAY_UTF8"
},
"html_tags": [
"<title>Face Recognition API Documentation (v2.2)</title>",
"<meta charset=\"UTF-8\">",
"<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">"
],
"body_size": 25030,
"body": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>Face Recognition API Documentation (v2.2)</title>\n <style>\n body { font-family: Arial, sans-serif; line-height: 1.6; margin: 0; padding: 0; background-color: #f8f9fa; color: #212529; }\n .container { max-width: 960px; margin: 20px auto; background: #fff; padding: 25px 35px; border-radius: 8px; box-shadow: 0 4px 15px rgba(0,0,0,0.1); }\n header { text-align: center; margin-bottom: 30px; padding-bottom: 20px; border-bottom: 1px solid #dee2e6;}\n header h1 { color: #007bff; margin-bottom: 5px; }\n header p { font-size: 1.1em; color: #6c757d; }\n h2 { color: #0056b3; border-bottom: 2px solid #007bff; padding-bottom: 8px; margin-top: 40px; margin-bottom: 20px; }\n h3 { color: #17a2b8; margin-top: 30px; margin-bottom: 15px; border-left: 4px solid #17a2b8; padding-left: 10px;}\n h4 { color: #28a745; margin-top: 25px; margin-bottom: 10px; }\n pre { background: #e9ecef; padding: 15px; border-radius: 5px; overflow-x: auto; font-family: 'Courier New', Courier, monospace; font-size: 0.95em; border: 1px solid #ced4da; color: #495057; }\n code { background: #f8f9fa; padding: 3px 6px; border-radius: 4px; border: 1px solid #dee2e6; font-family: 'Courier New', Courier, monospace; color: #e83e8c; }\n .endpoint { margin-bottom: 40px; padding: 20px; border: 1px solid #dee2e6; border-radius: 6px; background-color: #ffffff; }\n .endpoint h3:first-child { margin-top: 0; }\n table { width: 100%; border-collapse: collapse; margin-bottom: 20px; }\n th, td { border: 1px solid #dee2e6; padding: 10px 12px; text-align: left; }\n th { background-color: #e9ecef; font-weight: 600; }\n .note { background-color: #fff3cd; border-left: 5px solid #ffc107; padding: 12px 15px; margin: 20px 0; border-radius: 4px; color: #856404;}\n .important { background-color: #f8d7da; border-left: 5px solid #dc3545; padding: 12px 15px; margin: 20px 0; border-radius: 4px; color: #721c24; font-weight: bold;}\n .success-response { background-color: #d4edda; border-left: 5px solid #28a745; padding: 10px; margin-bottom: 10px; border-radius: 4px; color: #155724;}\n .error-response { background-color: #f8d7da; border-left: 5px solid #dc3545; padding: 10px; margin-bottom: 10px; border-radius: 4px; color: #721c24;}\n ul { padding-left: 20px; }\n li { margin-bottom: 8px; }\n </style>\n</head>\n<body>\n <div class=\"container\">\n <header>\n <h1>Face Recognition API Documentation (v2.2)</h1>\n <p>For ESP32 and other client integrations.</p>\n <p><strong>Base URL:</strong> <code>http://<your-server-ip>:<port></code> (e.g., <code>http://20.68.131.221:5005</code>)</p>\n </header>\n\n <div class=\"important\">\n <strong>Authentication Required:</strong> All API endpoints (except <code>/</code>, <code>/docs</code>, and <code>/ping</code>) now require an <code>Authentication</code> HTTP header. The value of this header must be the valid device/user UID.\n <pre>Authentication: YOUR_DEVICE_UID</pre>\n </div>\n\n <div class=\"endpoint\">\n <h2>1. Recognize Faces in an Image</h2>\n <p>This section describes how to send an image for face recognition. The primary method for ESP32 is sending raw binary image data. An alternative multipart method is also available.</p>\n\n <hr style=\"margin: 30px 0;\">\n\n <h3>1.1 Recognize via Raw Binary Image (Recommended for ESP32)</h3>\n <p>This endpoint takes raw binary image data directly in the request body and attempts to identify known persons previously registered under the UID provided in the <code>Authentication</code> header. The response format (text or audio) depends on the user's preference.</p>\n <h4>Endpoint Details:</h4>\n <ul>\n <li><strong>Method:</strong> <code>POST</code></li>\n <li><strong>URL:</strong> <code>/recognize_binary</code></li>\n <li><strong>Authentication:</strong> Required (UID in <code>Authentication</code> header).</li>\n </ul>\n\n <h4>Request Format:</h4>\n <p>The request body **is the raw binary image data** (e.g., JPEG, PNG bytes).</p>\n <h4>Required Headers:</h4>\n <table>\n <thead>\n <tr>\n <th>Header Name</th>\n <th>Example Value</th>\n <th>Description</th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td><code>Authentication</code></td>\n <td><code>YOUR_DEVICE_UID</code></td>\n <td>The unique device/user UID.</td>\n </tr>\n <tr>\n <td><code>Content-Type</code></td>\n <td><code>image/jpeg</code></td>\n <td>**Crucial.** The MIME type of the image being sent (e.g., <code>image/jpeg</code>, <code>image/png</code>).</td>\n </tr>\n </tbody>\n </table>\n\n <h4>Example <code>curl</code> Request (Binary Body):</h4>\n <pre>\ncurl -X POST \\\n http://<your-server-ip>:5005/recognize_binary \\\n -H \"Authentication: YOUR_DEVICE_UID\" \\\n -H \"Content-Type: image/jpeg\" \\\n --data-binary \"@/path/to/scene_with_faces.jpg\" \\\n --output recognized_output --dump-header headers.txt\n# Note: --data-binary sends the file content as raw binary.\n# --output and --dump-header are for observing the response.\n </pre>\n <div class=\"note\">\n <strong>ESP32 Client Notes (Binary Upload):</strong>\n <ul>\n <li>This is generally simpler for ESP32.</li>\n <li>Ensure the <code>Authentication</code> header is set.</li>\n <li>**Must set the `Content-Type` header** correctly (e.g., \"image/jpeg\" if sending a JPEG from ESP32-CAM).</li>\n <li>The HTTP request body should contain only the raw bytes of the image.</li>\n </ul>\n </div>\n\n <!-- Response Format Section - Common for both /recognize and /recognize_binary -->\n <h3>Response Format (Based on User Preference - Applies to both /recognize_binary and /recognize)</h3>\n <p>The server queries an external API to determine the user's preferred response format (Text or Audio) based on the authenticated UID. The HTTP status code (e.g., <code>200 OK</code> or <code>500 Internal Server Error</code>) remains a primary indicator of overall request success or critical failure.</p>\n\n <h4>A. Text Preference (or Audio Fallback)</h4>\n <ul>\n <li><strong>HTTP Status Code:</strong> <code>200 OK</code> (for successful processing, even if no faces are found/recognized) or <code>500 Internal Server Error</code> (if there was a server-side issue like loading registered faces).</li>\n <li><strong>Headers:</strong>\n <ul>\n <li><code>Content-Type: text/plain</code></li>\n <li><code>Result: 0</code></li>\n <li><code>X-Response-Type: ai_text</code> (This type is used for all text responses, whether normal informational messages, successful recognitions, or error descriptions that are presented as text.)</li>\n <li><code>X-Response-Type: ai_text_fallback</code> (This type is used if audio was preferred but TTS was unavailable, so a text response is provided instead. The content will be similar to <code>ai_text</code>.)</li>\n </ul>\n </li>\n <li><strong>Body:</strong> Plain text string describing the outcome.\n <div class=\"success-response\">\n Examples:\n <pre>I see Alice Wonderland.</pre>\n <pre>I see one person, but I don't recognize them.</pre>\n <pre>(Audio unavailable) No faces are registered for your user ID YOUR_DEVICE_UID.</pre>\n <pre>Error loading registered faces for your user ID YOUR_DEVICE_UID. Please contact support.</pre>\n <pre>I couldn't detect any faces in the image you sent.</pre>\n </div>\n </li>\n </ul>\n\n <h4>B. Audio Preference</h4>\n <ul>\n <li><strong>HTTP Status Code:</strong> <code>200 OK</code> or <code>500 Internal Server Error</code> (as above).</li>\n <li><strong>Headers:</strong>\n <ul>\n <li><code>Content-Type: audio/x-raw</code> (PCM, 16kHz, 16-bit Mono)</li>\n <li><code>Result: 1</code></li>\n <li><code>X-Response-Type: ai_audio</code> (This type is used for all audio responses. The spoken content will indicate success, information, or an error.)</li>\n <li><code>Transfer-Encoding: chunked</code> (Audio is streamed)</li>\n <li><code>X-Response-Text: <The original text that was synthesized></code> (Optional, for client reference/debugging. Newlines removed. This text reflects what is spoken, be it success or an error description.)</li>\n </ul>\n </li>\n <li><strong>Body:</strong> Streamed raw PCM audio data. The client needs to be able to play this format. The spoken audio will convey the result (e.g., \"I see Alice,\" or \"Error: No faces were detected\").</li>\n </ul>\n\n <div class=\"note\">\n <strong>Handling Responses on ESP32:</strong>\n <ul>\n <li>First, check the HTTP Status Code for overall success/failure of the API call.</li>\n <li>Then, check the <code>Result</code> header (<code>0</code> for Text, <code>1</code> for Audio) and <code>Content-Type</code> to determine how to process the body.</li>\n <li>If audio (<code>Result: 1</code>, <code>Content-Type: audio/x-raw</code>), the client should read the body as a stream of raw audio bytes and send it to an appropriate audio output peripheral (e.g., I2S DAC).</li>\n <li>The <code>X-Response-Text</code> header provides the text version of what was spoken, which can be useful for debugging or displaying if needed.</li>\n <li>The content of the text or audio itself will describe the specific outcome of the recognition attempt (e.g., names recognized, no faces found, error messages).</li>\n </ul>\n </div>\n\n <h3>General Error Responses (for /recognize_binary and /recognize, non-preference specific for critical client errors):</h3>\n <div class=\"error-response\">\n <p><strong>400 Bad Request:</strong>\n <ul>\n <li>(for <code>/recognize_binary</code>) Missing/invalid <code>Content-Type</code> header: <pre>{\"error\": \"Missing or invalid 'Content-Type' header...\"}</pre></li>\n <li>(for <code>/recognize_binary</code>) No image data in body: <pre>{\"error\": \"No image data found in request body.\"}</pre></li>\n <li>(for <code>/recognize</code> multipart) Missing image file: <pre>{\"error\": \"No image file provided\"}</pre></li>\n </ul>\n </p>\n <p><strong>401 Unauthorized:</strong> (Missing or malformed <code>Authentication</code> header)</p>\n <pre>{\"error\": \"Authentication header required (containing UID)\"}</pre>\n <p><strong>403 Forbidden:</strong> (UID in <code>Authentication</code> header is invalid/unverified)</p>\n <pre>{\"error\": \"Invalid or unverifiable UID\"}</pre>\n <p><strong>500 Internal Server Error:</strong> (e.g., major database issue before preference check. For other 500s where preference *can* be checked, it tries to use preferred format for the error message itself.)</p>\n <pre>{\"error\": \"An internal error occurred.\"}</pre> <!-- This is for very generic internal errors -->\n </div>\n\n <hr style=\"margin: 30px 0;\">\n\n <h3>1.2 Recognize via Multipart Form Data (Alternative)</h3>\n <p>This endpoint also recognizes faces but expects the image as part of a <code>multipart/form-data</code> request. This might be used by clients other than ESP32 or if the ESP32 library handles multipart better for some reason.</p>\n <h4>Endpoint Details:</h4>\n <ul>\n <li><strong>Method:</strong> <code>POST</code></li>\n <li><strong>URL:</strong> <code>/recognize</code></li>\n <li><strong>Authentication:</strong> Required (UID in <code>Authentication</code> header).</li>\n </ul>\n\n <h4>Request Format (Multipart):</h4>\n <p>The request must be a <code>multipart/form-data</code> POST request.</p>\n <h5>Required Headers:</h5>\n <ul><li><code>Authentication: YOUR_DEVICE_UID</code></li></ul>\n <h5>Form Fields:</h5>\n <table>\n <thead>\n <tr>\n <th>Field Name</th>\n <th>Type</th>\n <th>Description</th>\n <th>Required</th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td><code>image</code></td>\n <td>File (image/jpeg, image/png)</td>\n <td>The image file captured by the device, containing faces to be recognized.</td>\n <td>Yes</td>\n </tr>\n </tbody>\n </table>\n\n <h4>Example <code>curl</code> Request (Multipart):</h4>\n <pre>\ncurl -X POST \\\n http://<your-server-ip>:5005/recognize \\\n -H \"Authentication: YOUR_DEVICE_UID\" \\\n -F \"image=@/path/to/scene_with_faces.jpg\" \\\n --output recognized_output --dump-header headers.txt\n </pre>\n <p><strong>Response Format:</strong> Same as for <code>/recognize_binary</code> (see section 1.1 Response Format above).</p>\n </div>\n\n\n <div class=\"endpoint\">\n <h2>2. Register a New Face</h2>\n <p>This endpoint allows you to register one or more images for a specific person. The UID for registration is taken from the <code>Authentication</code> header.</p>\n <h3>Endpoint Details:</h3>\n <ul>\n <li><strong>Method:</strong> <code>POST</code></li>\n <li><strong>URL:</strong> <code>/register</code></li>\n <li><strong>Authentication:</strong> Required (UID in <code>Authentication</code> header).</li>\n </ul>\n\n <h3>Request Format:</h3>\n <p>The request must be a <code>multipart/form-data</code> POST request.</p>\n <h4>Required Headers:</h4>\n <ul><li><code>Authentication: YOUR_DEVICE_UID</code></li></ul>\n <h4>Form Fields:</h4>\n <table>\n <thead>\n <tr>\n <th>Field Name</th>\n <th>Type</th>\n <th>Description</th>\n <th>Required</th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td><code>name</code></td>\n <td>String</td>\n <td>The name of the person whose face is being registered. This name will be used in recognition responses.</td>\n <td>Yes</td>\n </tr>\n <tr>\n <td><code>image</code></td>\n <td>File (image/jpeg, image/png)</td>\n <td>The image file containing a clear, single face to be registered.</td>\n <td>Yes</td>\n </tr>\n </tbody>\n </table>\n\n <h4>Example <code>curl</code> Request:</h4>\n <pre>\ncurl -X POST \\\n http://<your-server-ip>:5005/register \\\n -H \"Authentication: YOUR_DEVICE_UID\" \\\n -F \"name=Alice Wonderland\" \\\n -F \"image=@/path/to/alice_center.jpg\"\n </pre>\n <div class=\"note\">\n <strong>ESP32 Client Notes (Register):</strong>\n <ul>\n <li>Ensure the <code>Authentication</code> header is set with the device UID.</li>\n <li>Construct a <code>multipart/form-data</code> request. Libraries like `HTTPClient` on ESP32 can help manage this.</li>\n <li>Send one image per request for optimal registration.</li>\n </ul>\n </div>\n\n <h3>Registration Image Guidance:</h3>\n <p>For robust recognition, especially with variable camera quality like ESP32-CAM, we **strongly recommend registering multiple photos per person**. Call the <code>/register</code> endpoint multiple times (once for each photo) with the same <code>Authentication</code> header UID and the same <code>name</code>, but a different image file.</p>\n <p>Suggested poses/conditions for registration images (clear, well-lit, single face):</p>\n <ul>\n <li><strong>Center (Neutral):</strong> Looking directly at the camera, neutral expression.</li>\n <li><strong>Center (Smiling):</strong> Looking directly at the camera, smiling.</li>\n <li><strong>Look Left (Slightly):</strong> Head turned slightly to their left.</li>\n <li><strong>Look Right (Slightly):</strong> Head turned slightly to their right.</li>\n <li><em>(Optional)</em> Images from the ESP32-CAM itself under typical operating conditions.</li>\n </ul>\n <p>Aim for **3-5 diverse images per person**. The API registers the first face detected in the submitted image.</p>\n\n <h3>Success Response (201 Created):</h3>\n <div class=\"success-response\">\n <pre>\n{\n \"message\": \"Successfully registered face for Alice Wonderland under authenticated UID YOUR_DEVICE_UID\"\n}\n </pre>\n </div>\n\n <h3>Error Responses (Register):</h3>\n <div class=\"error-response\">\n <p><strong>400 Bad Request:</strong> (e.g., missing fields, no face in image, name empty)</p>\n <pre>{\"error\": \"No face found in the provided image\"}</pre>\n <p><strong>401 Unauthorized:</strong> (Missing or malformed <code>Authentication</code> header)</p>\n <pre>{\"error\": \"Authentication header required (containing UID)\"}</pre>\n <p><strong>403 Forbidden:</strong> (UID in <code>Authentication</code> header is invalid/unverified)</p>\n <pre>{\"error\": \"Invalid or unverifiable UID\"}</pre>\n <p><strong>500 Internal Server Error:</strong> (e.g., database issue)</p>\n <pre>{\"error\": \"Database error during registration.\"}</pre>\n </div>\n </div>\n\n <div class=\"endpoint\">\n <h2>3. Manage Registered Faces</h2>\n <p>These endpoints allow listing and deleting registered face entries for the authenticated UID.</p>\n\n <hr style=\"margin: 30px 0;\">\n\n <h3>3.1 List Registered Faces</h3>\n <p>Retrieves a list of all face entries (individual image registrations) associated with the authenticated UID.</p>\n <h4>Endpoint Details:</h4>\n <ul>\n <li><strong>Method:</strong> <code>GET</code></li>\n <li><strong>URL:</strong> <code>/faces/list</code></li>\n <li><strong>Authentication:</strong> Required (UID in <code>Authentication</code> header).</li>\n </ul>\n\n <h4>Example <code>curl</code> Request:</h4>\n <pre>\ncurl -X GET \\\n http://<your-server-ip>:5005/faces/list \\\n -H \"Authentication: YOUR_DEVICE_UID\"\n </pre>\n\n <h4>Success Response (200 OK):</h4>\n <div class=\"success-response\">\n <p>If faces are registered:</p>\n <pre>\n{\n \"message\": \"Successfully retrieved registered faces.\",\n \"registered_face_entries\": [\n {\"id\": 1, \"name\": \"Alice\"},\n {\"id\": 2, \"name\": \"Bob\"},\n {\"id\": 5, \"name\": \"Alice\"} \n ]\n}\n </pre>\n <p>If no faces are registered:</p>\n <pre>\n{\n \"message\": \"No faces are currently registered for this UID.\",\n \"registered_persons\": [] \n}\n </pre>\n <p class=\"note\">The <code>id</code> in each entry is the unique database identifier for that specific image registration. It is used for deletion.</p>\n </div>\n <h3>Error Responses (List Faces):</h3>\n <div class=\"error-response\">\n <p><strong>401 Unauthorized / 403 Forbidden:</strong> Standard authentication errors.</p>\n <p><strong>500 Internal Server Error:</strong> (e.g., database issue)</p>\n <pre>{\"error\": \"A database error occurred while retrieving face list.\"}</pre>\n </div>\n\n\n <hr style=\"margin: 30px 0;\">\n\n <h3>3.2 Delete a Specific Face Entry</h3>\n <p>Deletes a single registered face entry using its unique <code>id</code>. The entry must belong to the authenticated UID.</p>\n <h4>Endpoint Details:</h4>\n <ul>\n <li><strong>Method:</strong> <code>DELETE</code></li>\n <li><strong>URL:</strong> <code>/faces/delete/<face_id></code> (Replace <code><face_id></code> with the actual ID from the list, e.g., <code>/faces/delete/5</code>)</li>\n <li><strong>Authentication:</strong> Required (UID in <code>Authentication</code> header).</li>\n </ul>\n <h4>URL Parameters:</h4>\n <table>\n <thead>\n <tr>\n <th>Parameter</th>\n <th>Type</th>\n <th>Description</th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td><code><face_id></code></td>\n <td>Integer</td>\n <td>The unique ID of the face entry to delete (obtained from the <code>/faces/list</code> endpoint).</td>\n </tr>\n </tbody>\n </table>\n\n <h4>Example <code>curl</code> Request:</h4>\n <pre>\ncurl -X DELETE \\\n http://<your-server-ip>:5005/faces/delete/5 \\\n -H \"Authentication: YOUR_DEVICE_UID\"\n </pre>\n\n <h4>Success Response (200 OK):</h4>\n <div class=\"success-response\">\n <pre>\n{\n \"message\": \"Successfully deleted face entry for 'Alice' (ID: 5).\"\n}\n </pre>\n </div>\n <h3>Error Responses (Delete Face):</h3>\n <div class=\"error-response\">\n <p><strong>401 Unauthorized / 403 Forbidden:</strong> Standard authentication errors.</p>\n <p><strong>404 Not Found:</strong> If the <code>face_id</code> does not exist, or if it exists but does not belong to the authenticated UID.</p>\n <pre>{\"error\": \"Face entry not found or not authorized for deletion.\"}</pre>\n <p><strong>500 Internal Server Error:</strong> (e.g., database issue)</p>\n <pre>{\"error\": \"A database error occurred during face deletion.\"}</pre>\n </div>\n </div>\n\n\n <div class=\"endpoint\">\n <h2>4. Ping (Health Check)</h2>\n <p>A simple endpoint to check if the server is alive and responding. Does not require authentication.</p>\n <h3>Endpoint Details:</h3>\n <ul>\n <li><strong>Method:</strong> <code>GET</code></li>\n <li><strong>URL:</strong> <code>/ping</code></li>\n <li><strong>Authentication:</strong> Not Required.</li>\n </ul>\n\n <h3>Success Response (200 OK):</h3>\n <div class=\"success-response\">\n <pre>\n{\n \"message\": \"Pong! Face Recognition Server is alive.\"\n}\n </pre>\n </div>\n </div>\n\n <div class=\"important\">\n <strong>ESP32-CAM Image Quality:</strong> The reliability of face recognition heavily depends on the image quality provided by the ESP32-CAM. Poor lighting, motion blur, low resolution, and incorrect focus will significantly reduce accuracy. Optimize image capture on the ESP32 side as much as possible.\n </div>\n </div>\n</body>\n</html>",
"body_hashes": [
"sha256:08ade58cc36a902730beb34554c9053bfd82db224c2af1b186b22cde9174f58a",
"sha1:b71827c1109fec21e476b85fac18e42dc777814d",
"tlsh:ffb285329af832139641819dbd014b577f5fc11bd26a16a13e8c83bd6fc6965c8b334e"
],
"body_hash": "sha1:b71827c1109fec21e476b85fac18e42dc777814d",
"html_title": "Face Recognition API Documentation (v2.2)"
},
"supports_http2": false
},
"observed_at": "2025-07-12T09:00:38.825278095Z",
"perspective_id": "PERSPECTIVE_UNKNOWN",
"port": 5005,
"service_name": "HTTP",
"software": [
{
"product": "python",
"version": "3.12.3",
"source": "OSI_APPLICATION_LAYER"
},
{
"uniform_resource_identifier": "cpe:2.3:a:palletsprojects:werkzeug:3.1.3:*:*:*:*:*:*:*",
"part": "a",
"vendor": "PalletsProjects",
"product": "Werkzeug",
"version": "3.1.3",
"source": "OSI_APPLICATION_LAYER"
}
],
"source_ip": "162.142.125.123",
"transport_protocol": "TCP",
"truncated": false
},
{
"_decoded": "http",
"_encoding": {
"banner": "DISPLAY_UTF8",
"banner_hex": "DISPLAY_HEX"
},
"banner": "HTTP/1.1 200 OK\r\nServer: Werkzeug/3.1.3 Python/3.12.3\r\nDate: <REDACTED>\r\nContent-Type: text/html; charset=utf-8\r\nContent-Length: 218701\r\nConnection: close\r\n",
"banner_hashes": [
"sha256:e9dd95bb97bf27aed561ce9b73d38dd4b505a3c8f2bb56668a160dd2494bfb65"
],
"banner_hex": "485454502f312e3120323030204f4b0d0a5365727665723a205765726b7a6575672f332e312e3320507974686f6e2f332e31322e330d0a446174653a20203c52454441435445443e0d0a436f6e74656e742d547970653a20746578742f68746d6c3b20636861727365743d7574662d380d0a436f6e74656e742d4c656e6774683a203231383730310d0a436f6e6e656374696f6e3a20636c6f73650d0a",
"discovery_method": "IPV4_WALK_CLOUD_PRIORITY_1",
"extended_service_name": "HTTP",
"http": {
"request": {
"method": "GET",
"uri": "http://20.68.131.221:8001/",
"headers": {
"User_Agent": [
"Mozilla/5.0 (compatible; CensysInspect/1.1; +https://about.censys.io/)"
],
"_encoding": {
"User_Agent": "DISPLAY_UTF8",
"Accept": "DISPLAY_UTF8"
},
"Accept": [
"*/*"
]
}
},
"response": {
"protocol": "HTTP/1.1",
"status_code": 200,
"status_reason": "OK",
"headers": {
"Date": [
"<REDACTED>"
],
"_encoding": {
"Date": "DISPLAY_UTF8",
"Server": "DISPLAY_UTF8",
"Content_Length": "DISPLAY_UTF8",
"Content_Type": "DISPLAY_UTF8",
"Connection": "DISPLAY_UTF8"
},
"Server": [
"Werkzeug/3.1.3 Python/3.12.3"
],
"Content_Length": [
"218701"
],
"Content_Type": [
"text/html; charset=utf-8"
],
"Connection": [
"close"
]
},
"_encoding": {
"html_tags": "DISPLAY_UTF8",
"body": "DISPLAY_UTF8",
"body_hash": "DISPLAY_UTF8",
"html_title": "DISPLAY_UTF8"
},
"html_tags": [
"<title>Real-time Photo Feed</title>",
"<meta charset=\"UTF-8\">",
"<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">"
],
"body_size": 65536,
"body": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>Real-time Photo Feed</title>\n\n <!-- Fonts -->\n <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">\n <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin>\n <link href=\"https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700&display=swap\" rel=\"stylesheet\">\n\n <!-- Embedded CSS -->\n <style>\n /* --- Base and UI Styling --- */\n /* Custom scrollbar for webkit browsers (GLOBAL) */\n ::-webkit-scrollbar { width: 8px; }\n ::-webkit-scrollbar-track { background: #2d3748; border-radius: 10px; }\n ::-webkit-scrollbar-thumb { background: #718096; border-radius: 10px; }\n ::-webkit-scrollbar-thumb:hover { background: #a0aec0; }\n\n body {\n font-family: 'Inter', sans-serif; /* Using Inter font */\n margin: 0;\n background-color: #f4f4f4;\n color: #333;\n line-height: 1.6;\n }\n header {\n background-color: #333;\n color: #fff;\n padding: 1rem;\n text-align: center;\n }\n .gallery-container {\n max-width: 1200px;\n margin: 20px auto;\n padding: 15px;\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));\n gap: 15px;\n }\n .photo-item {\n background-color: #fff;\n border: 1px solid #ddd;\n border-radius: 5px;\n overflow: hidden;\n box-shadow: 0 2px 4px rgba(0,0,0,0.1);\n transition: transform 0.2s ease-in-out, box-shadow 0.2s ease-in-out;\n animation: fadeIn 0.5s ease-out;\n display: flex;\n flex-direction: column;\n padding-bottom: 10px; /* Add padding for link */\n }\n .photo-item:hover {\n transform: scale(1.03);\n box-shadow: 0 5px 15px rgba(0,0,0,0.2);\n }\n .photo-item a {\n text-decoration: none; /* Remove underline from image link */\n }\n .photo-item img {\n display: block;\n width: 100%;\n height: 180px;\n object-fit: cover;\n background-color: #eee;\n cursor: pointer; /* Indicate image is clickable */\n }\n .photo-item .filename-container { /* Wrap filename and download */\n padding: 10px;\n text-align: center;\n border-top: 1px solid #eee;\n background-color: #f9f9f9;\n flex-grow: 1;\n }\n .photo-item .filename {\n font-size: 0.85em;\n word-break: break-all;\n color: #555;\n margin-bottom: 5px; /* Space below filename */\n }\n .status {\n text-align: center;\n padding: 10px;\n color: #888;\n font-style: italic;\n }\n\n @keyframes fadeIn {\n from { opacity: 0; transform: translateY(10px); }\n to { opacity: 1; transform: translateY(0); }\n }\n .new-photo-highlight {\n animation: highlight 1.5s ease-out;\n }\n @keyframes highlight {\n 0% { background-color: #fff3cd; } /* Yellowish highlight */\n 100% { background-color: #fff; }\n }\n\n /* --- Lightbox Styles --- */\n .lightbox-modal {\n display: none; /* Hidden by default */\n position: fixed; /* Stay in place */\n z-index: 1000; /* Sit on top */\n left: 0;\n top: 0;\n width: 100%;\n height: 100%;\n overflow: auto; /* Enable scroll if needed */\n background-color: rgba(0, 0, 0, 0.85); /* Black w/ opacity */\n cursor: pointer; /* Indicate clicking background closes it */\n align-items: center; /* Center vertically */\n justify-content: center; /* Center horizontally */\n }\n\n .lightbox-modal.active {\n display: flex; /* Show the modal using flex alignment */\n }\n\n .lightbox-content {\n margin: auto;\n display: block;\n max-width: 90%;\n max-height: 85%;\n cursor: default; /* Default cursor over the image itself */\n box-shadow: 0 4px 8px rgba(0,0,0,0.2), 0 6px 20px rgba(0,0,0,0.19);\n animation: zoomIn 0.4s ease-out;\n }\n\n @keyframes zoomIn {\n from { transform: scale(0.8); opacity: 0; }\n to { transform: scale(1); opacity: 1; }\n }\n\n .lightbox-close {\n position: absolute;\n top: 20px;\n right: 35px;\n color: #f1f1f1;\n font-size: 40px;\n font-weight: bold;\n transition: 0.3s;\n text-decoration: none;\n cursor: pointer;\n }\n\n .lightbox-close:hover,\n .lightbox-close:focus {\n color: #bbb;\n }\n\n /* --- Download Link Style --- */\n .download-link {\n display: inline-block;\n margin-top: 8px;\n padding: 5px 10px;\n font-size: 0.8em;\n color: #fff;\n background-color: #555;\n border-radius: 4px;\n text-decoration: none;\n text-align: center;\n transition: background-color 0.2s ease;\n }\n\n .download-link:hover {\n background-color: #777;\n }\n\n </style>\n</head>\n<body>\n <header>\n <h1>OCR Photo Feed</h1>\n <div id=\"connection-status\" class=\"status\">Connecting...</div>\n </header>\n\n <div id=\"gallery\" class=\"gallery-container\">\n <!-- Initial photos rendered by Flask -->\n \n <div class=\"photo-item\" data-filename=\"text_read_transformed_42.87.04.15.D8.05_20250630_100244.jpg\">\n <a href=\"/photos/text_read_transformed_42.87.04.15.D8.05_20250630_100244.jpg\" class=\"lightbox-trigger\"> \n <img src=\"/photos/text_read_transformed_42.87.04.15.D8.05_20250630_100244.jpg\" alt=\"text_read_transformed_42.87.04.15.D8.05_20250630_100244.jpg\" loading=\"lazy\">\n </a>\n <div class=\"filename-container\"> \n <div class=\"filename\">text_read_transformed_42.87.04.15.D8.05_20250630_100244.jpg</div>\n <a href=\"/photos/text_read_transformed_42.87.04.15.D8.05_20250630_100244.jpg\" download=\"text_read_transformed_42.87.04.15.D8.05_20250630_100244.jpg\" class=\"download-link\"> \n Download\n </a>\n </div>\n </div>\n \n <div class=\"photo-item\" data-filename=\"text_read_transformed_4A.44.78.63.49.44_20250614_081439.jpg\">\n <a href=\"/photos/text_read_transformed_4A.44.78.63.49.44_20250614_081439.jpg\" class=\"lightbox-trigger\"> \n <img src=\"/photos/text_read_transformed_4A.44.78.63.49.44_20250614_081439.jpg\" alt=\"text_read_transformed_4A.44.78.63.49.44_20250614_081439.jpg\" loading=\"lazy\">\n </a>\n <div class=\"filename-container\"> \n <div class=\"filename\">text_read_transformed_4A.44.78.63.49.44_20250614_081439.jpg</div>\n <a href=\"/photos/text_read_transformed_4A.44.78.63.49.44_20250614_081439.jpg\" download=\"text_read_transformed_4A.44.78.63.49.44_20250614_081439.jpg\" class=\"download-link\"> \n Download\n </a>\n </div>\n </div>\n \n <div class=\"photo-item\" data-filename=\"text_read_transformed_63.C1.A4.CD.3F.F4_20250613_143546.jpg\">\n <a href=\"/photos/text_read_transformed_63.C1.A4.CD.3F.F4_20250613_143546.jpg\" class=\"lightbox-trigger\"> \n <img src=\"/photos/text_read_transformed_63.C1.A4.CD.3F.F4_20250613_143546.jpg\" alt=\"text_read_transformed_63.C1.A4.CD.3F.F4_20250613_143546.jpg\" loading=\"lazy\">\n </a>\n <div class=\"filename-container\"> \n <div class=\"filename\">text_read_transformed_63.C1.A4.CD.3F.F4_20250613_143546.jpg</div>\n <a href=\"/photos/text_read_transformed_63.C1.A4.CD.3F.F4_20250613_143546.jpg\" download=\"text_read_transformed_63.C1.A4.CD.3F.F4_20250613_143546.jpg\" class=\"download-link\"> \n Download\n </a>\n </div>\n </div>\n \n <div class=\"photo-item\" data-filename=\"text_read_transformed_63.C1.A4.CD.3F.F4_20250613_142604.jpg\">\n <a href=\"/photos/text_read_transformed_63.C1.A4.CD.3F.F4_20250613_142604.jpg\" class=\"lightbox-trigger\"> \n <img src=\"/photos/text_read_transformed_63.C1.A4.CD.3F.F4_20250613_142604.jpg\" alt=\"text_read_transformed_63.C1.A4.CD.3F.F4_20250613_142604.jpg\" loading=\"lazy\">\n </a>\n <div class=\"filename-container\"> \n <div class=\"filename\">text_read_transformed_63.C1.A4.CD.3F.F4_20250613_142604.jpg</div>\n <a href=\"/photos/text_read_transformed_63.C1.A4.CD.3F.F4_20250613_142604.jpg\" download=\"text_read_transformed_63.C1.A4.CD.3F.F4_20250613_142604.jpg\" class=\"download-link\"> \n Download\n </a>\n </div>\n </div>\n \n <div class=\"photo-item\" data-filename=\"text_read_transformed_63.C1.A4.CD.3F.F4_20250613_124651.jpg\">\n <a href=\"/photos/text_read_transformed_63.C1.A4.CD.3F.F4_20250613_124651.jpg\" class=\"lightbox-trigger\"> \n <img src=\"/photos/text_read_transformed_63.C1.A4.CD.3F.F4_20250613_124651.jpg\" alt=\"text_read_transformed_63.C1.A4.CD.3F.F4_20250613_124651.jpg\" loading=\"lazy\">\n </a>\n <div class=\"filename-container\"> \n <div class=\"filename\">text_read_transformed_63.C1.A4.CD.3F.F4_20250613_124651.jpg</div>\n <a href=\"/photos/text_read_transformed_63.C1.A4.CD.3F.F4_20250613_124651.jpg\" download=\"text_read_transformed_63.C1.A4.CD.3F.F4_20250613_124651.jpg\" class=\"download-link\"> \n Download\n </a>\n </div>\n </div>\n \n <div class=\"photo-item\" data-filename=\"text_read_transformed_63.C1.A4.CD.3F.F4_20250613_124605.jpg\">\n <a href=\"/photos/text_read_transformed_63.C1.A4.CD.3F.F4_20250613_124605.jpg\" class=\"lightbox-trigger\"> \n <img src=\"/photos/text_read_transformed_63.C1.A4.CD.3F.F4_20250613_124605.jpg\" alt=\"text_read_transformed_63.C1.A4.CD.3F.F4_20250613_124605.jpg\" loading=\"lazy\">\n </a>\n <div class=\"filename-container\"> \n <div class=\"filename\">text_read_transformed_63.C1.A4.CD.3F.F4_20250613_124605.jpg</div>\n <a href=\"/photos/text_read_transformed_63.C1.A4.CD.3F.F4_20250613_124605.jpg\" download=\"text_read_transformed_63.C1.A4.CD.3F.F4_20250613_124605.jpg\" class=\"download-link\"> \n Download\n </a>\n </div>\n </div>\n \n <div class=\"photo-item\" data-filename=\"text_read_transformed_EB.48.4A.FB.AF.4D_20250612_091615.jpg\">\n <a href=\"/photos/text_read_transformed_EB.48.4A.FB.AF.4D_20250612_091615.jpg\" class=\"lightbox-trigger\"> \n <img src=\"/photos/text_read_transformed_EB.48.4A.FB.AF.4D_20250612_091615.jpg\" alt=\"text_read_transformed_EB.48.4A.FB.AF.4D_20250612_091615.jpg\" loading=\"lazy\">\n </a>\n <div class=\"filename-container\"> \n <div class=\"filename\">text_read_transformed_EB.48.4A.FB.AF.4D_20250612_091615.jpg</div>\n <a href=\"/photos/text_read_transformed_EB.48.4A.FB.AF.4D_20250612_091615.jpg\" download=\"text_read_transformed_EB.48.4A.FB.AF.4D_20250612_091615.jpg\" class=\"download-link\"> \n Download\n </a>\n </div>\n </div>\n \n <div class=\"photo-item\" data-filename=\"text_read_transformed_63.C1.A4.CD.3F.F4_20250611_194520.jpg\">\n <a href=\"/photos/text_read_transformed_63.C1.A4.CD.3F.F4_20250611_194520.jpg\" class=\"lightbox-trigger\"> \n <img src=\"/photos/text_read_transformed_63.C1.A4.CD.3F.F4_20250611_194520.jpg\" alt=\"text_read_transformed_63.C1.A4.CD.3F.F4_20250611_194520.jpg\" loading=\"lazy\">\n </a>\n <div class=\"filename-container\"> \n <div class=\"filename\">text_read_transformed_63.C1.A4.CD.3F.F4_20250611_194520.jpg</div>\n <a href=\"/photos/text_read_transformed_63.C1.A4.CD.3F.F4_20250611_194520.jpg\" download=\"text_read_transformed_63.C1.A4.CD.3F.F4_20250611_194520.jpg\" class=\"download-link\"> \n Download\n </a>\n </div>\n </div>\n \n <div class=\"photo-item\" data-filename=\"text_read_transformed_EB.48.4A.FB.AF.4D_20250610_143410.jpg\">\n <a href=\"/photos/text_read_transformed_EB.48.4A.FB.AF.4D_20250610_143410.jpg\" class=\"lightbox-trigger\"> \n <img src=\"/photos/text_read_transformed_EB.48.4A.FB.AF.4D_20250610_143410.jpg\" alt=\"text_read_transformed_EB.48.4A.FB.AF.4D_20250610_143410.jpg\" loading=\"lazy\">\n </a>\n <div class=\"filename-container\"> \n <div class=\"filename\">text_read_transformed_EB.48.4A.FB.AF.4D_20250610_143410.jpg</div>\n <a href=\"/photos/text_read_transformed_EB.48.4A.FB.AF.4D_20250610_143410.jpg\" download=\"text_read_transformed_EB.48.4A.FB.AF.4D_20250610_143410.jpg\" class=\"download-link\"> \n Download\n </a>\n </div>\n </div>\n \n <div class=\"photo-item\" data-filename=\"text_read_transformed_EB.48.4A.FB.AF.4D_20250610_143313.jpg\">\n <a href=\"/photos/text_read_transformed_EB.48.4A.FB.AF.4D_20250610_143313.jpg\" class=\"lightbox-trigger\"> \n <img src=\"/photos/text_read_transformed_EB.48.4A.FB.AF.4D_20250610_143313.jpg\" alt=\"text_read_transformed_EB.48.4A.FB.AF.4D_20250610_143313.jpg\" loading=\"lazy\">\n </a>\n <div class=\"filename-container\"> \n <div class=\"filename\">text_read_transformed_EB.48.4A.FB.AF.4D_20250610_143313.jpg</div>\n <a href=\"/photos/text_read_transformed_EB.48.4A.FB.AF.4D_20250610_143313.jpg\" download=\"text_read_transformed_EB.48.4A.FB.AF.4D_20250610_143313.jpg\" class=\"download-link\"> \n Download\n </a>\n </div>\n </div>\n \n <div class=\"photo-item\" data-filename=\"text_read_transformed_EB.48.4A.FB.AF.4D_20250610_143206.jpg\">\n <a href=\"/photos/text_read_transformed_EB.48.4A.FB.AF.4D_20250610_143206.jpg\" class=\"lightbox-trigger\"> \n <img src=\"/photos/text_read_transformed_EB.48.4A.FB.AF.4D_20250610_143206.jpg\" alt=\"text_read_transformed_EB.48.4A.FB.AF.4D_20250610_143206.jpg\" loading=\"lazy\">\n </a>\n <div class=\"filename-container\"> \n <div class=\"filename\">text_read_transformed_EB.48.4A.FB.AF.4D_20250610_143206.jpg</div>\n <a href=\"/photos/text_read_transformed_EB.48.4A.FB.AF.4D_20250610_143206.jpg\" download=\"text_read_transformed_EB.48.4A.FB.AF.4D_20250610_143206.jpg\" class=\"download-link\"> \n Download\n </a>\n </div>\n </div>\n \n <div class=\"photo-item\" data-filename=\"text_read_transformed_EB.48.4A.FB.AF.4D_20250610_142930.jpg\">\n <a href=\"/photos/text_read_transformed_EB.48.4A.FB.AF.4D_20250610_142930.jpg\" class=\"lightbox-trigger\"> \n <img src=\"/photos/text_read_transformed_EB.48.4A.FB.AF.4D_20250610_142930.jpg\" alt=\"text_read_transformed_EB.48.4A.FB.AF.4D_20250610_142930.jpg\" loading=\"lazy\">\n </a>\n <div class=\"filename-container\"> \n <div class=\"filename\">text_read_transformed_EB.48.4A.FB.AF.4D_20250610_142930.jpg</div>\n <a href=\"/photos/text_read_transformed_EB.48.4A.FB.AF.4D_20250610_142930.jpg\" download=\"text_read_transformed_EB.48.4A.FB.AF.4D_20250610_142930.jpg\" class=\"download-link\"> \n Download\n </a>\n </div>\n </div>\n \n <div class=\"photo-item\" data-filename=\"text_read_transformed_EB.48.4A.FB.AF.4D_20250610_142910.jpg\">\n <a href=\"/photos/text_read_transformed_EB.48.4A.FB.AF.4D_20250610_142910.jpg\" class=\"lightbox-trigger\"> \n <img src=\"/photos/text_read_transformed_EB.48.4A.FB.AF.4D_20250610_142910.jpg\" alt=\"text_read_transformed_EB.48.4A.FB.AF.4D_20250610_142910.jpg\" loading=\"lazy\">\n </a>\n <div class=\"filename-container\"> \n <div class=\"filename\">text_read_transformed_EB.48.4A.FB.AF.4D_20250610_142910.jpg</div>\n <a href=\"/photos/text_read_transformed_EB.48.4A.FB.AF.4D_20250610_142910.jpg\" download=\"text_read_transformed_EB.48.4A.FB.AF.4D_20250610_142910.jpg\" class=\"download-link\"> \n Download\n </a>\n </div>\n </div>\n \n <div class=\"photo-item\" data-filename=\"text_read_transformed_93.08.40.DC.EB.AF_20250610_142801.jpg\">\n <a href=\"/photos/text_read_transformed_93.08.40.DC.EB.AF_20250610_142801.jpg\" class=\"lightbox-trigger\"> \n <img src=\"/photos/text_read_transformed_93.08.40.DC.EB.AF_20250610_142801.jpg\" alt=\"text_read_transformed_93.08.40.DC.EB.AF_20250610_142801.jpg\" loading=\"lazy\">\n </a>\n <div class=\"filename-container\"> \n <div class=\"filename\">text_read_transformed_93.08.40.DC.EB.AF_20250610_142801.jpg</div>\n <a href=\"/photos/text_read_transformed_93.08.40.DC.EB.AF_20250610_142801.jpg\" download=\"text_read_transformed_93.08.40.DC.EB.AF_20250610_142801.jpg\" class=\"download-link\"> \n Download\n </a>\n </div>\n </div>\n \n <div class=\"photo-item\" data-filename=\"text_read_transformed_EB.48.4A.FB.AF.4D_20250610_142755.jpg\">\n <a href=\"/photos/text_read_transformed_EB.48.4A.FB.AF.4D_20250610_142755.jpg\" class=\"lightbox-trigger\"> \n <img src=\"/photos/text_read_transformed_EB.48.4A.FB.AF.4D_20250610_142755.jpg\" alt=\"text_read_transformed_EB.48.4A.FB.AF.4D_20250610_142755.jpg\" loading=\"lazy\">\n </a>\n <div class=\"filename-container\"> \n <div class=\"filename\">text_read_transformed_EB.48.4A.FB.AF.4D_20250610_142755.jpg</div>\n <a href=\"/photos/text_read_transformed_EB.48.4A.FB.AF.4D_20250610_142755.jpg\" download=\"text_read_transformed_EB.48.4A.FB.AF.4D_20250610_142755.jpg\" class=\"download-link\"> \n Download\n </a>\n </div>\n </div>\n \n <div class=\"photo-item\" data-filename=\"text_read_transformed_93.08.40.DC.EB.AF_20250610_142745.jpg\">\n <a href=\"/photos/text_read_transformed_93.08.40.DC.EB.AF_20250610_142745.jpg\" class=\"lightbox-trigger\"> \n <img src=\"/photos/text_read_transformed_93.08.40.DC.EB.AF_20250610_142745.jpg\" alt=\"text_read_transformed_93.08.40.DC.EB.AF_20250610_142745.jpg\" loading=\"lazy\">\n </a>\n <div class=\"filename-container\"> \n <div class=\"filename\">text_read_transformed_93.08.40.DC.EB.AF_20250610_142745.jpg</div>\n <a href=\"/photos/text_read_transformed_93.08.40.DC.EB.AF_20250610_142745.jpg\" download=\"text_read_transformed_93.08.40.DC.EB.AF_20250610_142745.jpg\" class=\"download-link\"> \n Download\n </a>\n </div>\n </div>\n \n <div class=\"photo-item\" data-filename=\"text_read_transformed_EB.48.4A.FB.AF.4D_20250610_142622.jpg\">\n <a href=\"/photos/text_read_transformed_EB.48.4A.FB.AF.4D_20250610_142622.jpg\" class=\"lightbox-trigger\"> \n <img src=\"/photos/text_read_transformed_EB.48.4A.FB.AF.4D_20250610_142622.jpg\" alt=\"text_read_transformed_EB.48.4A.FB.AF.4D_20250610_142622.jpg\" loading=\"lazy\">\n </a>\n <div class=\"filename-container\"> \n <div class=\"filename\">text_read_transformed_EB.48.4A.FB.AF.4D_20250610_142622.jpg</div>\n <a href=\"/photos/text_read_transformed_EB.48.4A.FB.AF.4D_20250610_142622.jpg\" download=\"text_read_transformed_EB.48.4A.FB.AF.4D_20250610_142622.jpg\" class=\"download-link\"> \n Download\n </a>\n </div>\n </div>\n \n <div class=\"photo-item\" data-filename=\"text_read_transformed_EB.48.4A.FB.AF.4D_20250610_142543.jpg\">\n <a href=\"/photos/text_read_transformed_EB.48.4A.FB.AF.4D_20250610_142543.jpg\" class=\"lightbox-trigger\"> \n <img src=\"/photos/text_read_transformed_EB.48.4A.FB.AF.4D_20250610_142543.jpg\" alt=\"text_read_transformed_EB.48.4A.FB.AF.4D_20250610_142543.jpg\" loading=\"lazy\">\n </a>\n <div class=\"filename-container\"> \n <div class=\"filename\">text_read_transformed_EB.48.4A.FB.AF.4D_20250610_142543.jpg</div>\n <a href=\"/photos/text_read_transformed_EB.48.4A.FB.AF.4D_20250610_142543.jpg\" download=\"text_read_transformed_EB.48.4A.FB.AF.4D_20250610_142543.jpg\" class=\"download-link\"> \n Download\n </a>\n </div>\n </div>\n \n <div class=\"photo-item\" data-filename=\"text_read_transformed_EB.48.4A.FB.AF.4D_20250610_142500.jpg\">\n <a href=\"/photos/text_read_transformed_EB.48.4A.FB.AF.4D_20250610_142500.jpg\" class=\"lightbox-trigger\"> \n <img src=\"/photos/text_read_transformed_EB.48.4A.FB.AF.4D_20250610_142500.jpg\" alt=\"text_read_transformed_EB.48.4A.FB.AF.4D_20250610_142500.jpg\" loading=\"lazy\">\n </a>\n <div class=\"filename-container\"> \n <div class=\"filename\">text_read_transformed_EB.48.4A.FB.AF.4D_20250610_142500.jpg</div>\n <a href=\"/photos/text_read_transformed_EB.48.4A.FB.AF.4D_20250610_142500.jpg\" download=\"text_read_transformed_EB.48.4A.FB.AF.4D_20250610_142500.jpg\" class=\"download-link\"> \n Download\n </a>\n </div>\n </div>\n \n <div class=\"photo-item\" data-filename=\"text_read_transformed_EB.48.4A.FB.AF.4D_20250610_140841.jpg\">\n <a href=\"/photos/text_read_transformed_EB.48.4A.FB.AF.4D_20250610_140841.jpg\" class=\"lightbox-trigger\"> \n <img src=\"/photos/text_read_transformed_EB.48.4A.FB.AF.4D_20250610_140841.jpg\" alt=\"text_read_transformed_EB.48.4A.FB.AF.4D_20250610_140841.jpg\" loading=\"lazy\">\n </a>\n <div class=\"filename-container\"> \n <div class=\"filename\">text_read_transformed_EB.48.4A.FB.AF.4D_20250610_140841.jpg</div>\n <a href=\"/photos/text_read_transformed_EB.48.4A.FB.AF.4D_20250610_140841.jpg\" download=\"text_read_transformed_EB.48.4A.FB.AF.4D_20250610_140841.jpg\" class=\"download-link\"> \n Download\n </a>\n </div>\n </div>\n \n <div class=\"photo-item\" data-filename=\"text_read_transformed_EB.48.4A.FB.AF.4D_20250610_140440.jpg\">\n <a href=\"/photos/text_read_transformed_EB.48.4A.FB.AF.4D_20250610_140440.jpg\" class=\"lightbox-trigger\"> \n <img src=\"/photos/text_read_transformed_EB.48.4A.FB.AF.4D_20250610_140440.jpg\" alt=\"text_read_transformed_EB.48.4A.FB.AF.4D_20250610_140440.jpg\" loading=\"lazy\">\n </a>\n <div class=\"filename-container\"> \n <div class=\"filename\">text_read_transformed_EB.48.4A.FB.AF.4D_20250610_140440.jpg</div>\n <a href=\"/photos/text_read_transformed_EB.48.4A.FB.AF.4D_20250610_140440.jpg\" download=\"text_read_transformed_EB.48.4A.FB.AF.4D_20250610_140440.jpg\" class=\"download-link\"> \n Download\n </a>\n </div>\n </div>\n \n <div class=\"photo-item\" data-filename=\"text_read_transformed_EB.48.4A.FB.AF.4D_20250610_135914.jpg\">\n <a href=\"/photos/text_read_transformed_EB.48.4A.FB.AF.4D_20250610_135914.jpg\" class=\"lightbox-trigger\"> \n <img src=\"/photos/text_read_transformed_EB.48.4A.FB.AF.4D_20250610_135914.jpg\" alt=\"text_read_transformed_EB.48.4A.FB.AF.4D_20250610_135914.jpg\" loading=\"lazy\">\n </a>\n <div class=\"filename-container\"> \n <div class=\"filename\">text_read_transformed_EB.48.4A.FB.AF.4D_20250610_135914.jpg</div>\n <a href=\"/photos/text_read_transformed_EB.48.4A.FB.AF.4D_20250610_135914.jpg\" download=\"text_read_transformed_EB.48.4A.FB.AF.4D_20250610_135914.jpg\" class=\"download-link\"> \n Download\n </a>\n </div>\n </div>\n \n <div class=\"photo-item\" data-filename=\"text_read_transformed_EB.48.4A.FB.AF.4D_20250610_114721.jpg\">\n <a href=\"/photos/text_read_transformed_EB.48.4A.FB.AF.4D_20250610_114721.jpg\" class=\"lightbox-trigger\"> \n <img src=\"/photos/text_read_transformed_EB.48.4A.FB.AF.4D_20250610_114721.jpg\" alt=\"text_read_transformed_EB.48.4A.FB.AF.4D_20250610_114721.jpg\" loading=\"lazy\">\n </a>\n <div class=\"filename-container\"> \n <div class=\"filename\">text_read_transformed_EB.48.4A.FB.AF.4D_20250610_114721.jpg</div>\n <a href=\"/photos/text_read_transformed_EB.48.4A.FB.AF.4D_20250610_114721.jpg\" download=\"text_read_transformed_EB.48.4A.FB.AF.4D_20250610_114721.jpg\" class=\"download-link\"> \n Download\n </a>\n </div>\n </div>\n \n <div class=\"photo-item\" data-filename=\"text_read_transformed_EB.48.4A.FB.AF.4D_20250610_114644.jpg\">\n <a href=\"/photos/text_read_transformed_EB.48.4A.FB.AF.4D_20250610_114644.jpg\" class=\"lightbox-trigger\"> \n <img src=\"/photos/text_read_transformed_EB.48.4A.FB.AF.4D_20250610_114644.jpg\" alt=\"text_read_transformed_EB.48.4A.FB.AF.4D_20250610_114644.jpg\" loading=\"lazy\">\n </a>\n <div class=\"filename-container\"> \n <div class=\"filename\">text_read_transformed_EB.48.4A.FB.AF.4D_20250610_114644.jpg</div>\n <a href=\"/photos/text_read_transformed_EB.48.4A.FB.AF.4D_20250610_114644.jpg\" download=\"text_read_transformed_EB.48.4A.FB.AF.4D_20250610_114644.jpg\" class=\"download-link\"> \n Download\n </a>\n </div>\n </div>\n \n <div class=\"photo-item\" data-filename=\"text_read_transformed_EB.48.4A.FB.AF.4D_20250610_113712.jpg\">\n <a href=\"/photos/text_read_transformed_EB.48.4A.FB.AF.4D_20250610_113712.jpg\" class=\"lightbox-trigger\"> \n <img src=\"/photos/text_read_transformed_EB.48.4A.FB.AF.4D_20250610_113712.jpg\" alt=\"text_read_transformed_EB.48.4A.FB.AF.4D_20250610_113712.jpg\" loading=\"lazy\">\n </a>\n <div class=\"filename-container\"> \n <div class=\"filename\">text_read_transformed_EB.48.4A.FB.AF.4D_20250610_113712.jpg</div>\n <a href=\"/photos/text_read_transformed_EB.48.4A.FB.AF.4D_20250610_113712.jpg\" download=\"text_read_transformed_EB.48.4A.FB.AF.4D_20250610_113712.jpg\" class=\"download-link\"> \n Download\n </a>\n </div>\n </div>\n \n <div class=\"photo-item\" data-filename=\"text_read_transformed_EB.48.4A.FB.AF.4D_20250610_113119.jpg\">\n <a href=\"/photos/text_read_transformed_EB.48.4A.FB.AF.4D_20250610_113119.jpg\" class=\"lightbox-trigger\"> \n <img src=\"/photos/text_read_transformed_EB.48.4A.FB.AF.4D_20250610_113119.jpg\" alt=\"text_read_transformed_EB.48.4A.FB.AF.4D_20250610_113119.jpg\" loading=\"lazy\">\n </a>\n <div class=\"filename-container\"> \n <div class=\"filename\">text_read_transformed_EB.48.4A.FB.AF.4D_20250610_113119.jpg</div>\n <a href=\"/photos/text_read_transformed_EB.48.4A.FB.AF.4D_20250610_113119.jpg\" download=\"text_read_transformed_EB.48.4A.FB.AF.4D_20250610_113119.jpg\" class=\"download-link\"> \n Download\n </a>\n </div>\n </div>\n \n <div class=\"photo-item\" data-filename=\"text_read_transformed_EB.48.4A.FB.AF.4D_20250610_112341.jpg\">\n <a href=\"/photos/text_read_transformed_EB.48.4A.FB.AF.4D_20250610_112341.jpg\" class=\"lightbox-trigger\"> \n <img src=\"/photos/text_read_transformed_EB.48.4A.FB.AF.4D_20250610_112341.jpg\" alt=\"text_read_transformed_EB.48.4A.FB.AF.4D_20250610_112341.jpg\" loading=\"lazy\">\n </a>\n <div class=\"filename-container\"> \n <div class=\"filename\">text_read_transformed_EB.48.4A.FB.AF.4D_20250610_112341.jpg</div>\n <a href=\"/photos/text_read_transformed_EB.48.4A.FB.AF.4D_20250610_112341.jpg\" download=\"text_read_transformed_EB.48.4A.FB.AF.4D_20250610_112341.jpg\" class=\"download-link\"> \n Download\n </a>\n </div>\n </div>\n \n <div class=\"photo-item\" data-filename=\"text_read_transformed_EB.48.4A.FB.AF.4D_20250610_112320.jpg\">\n <a href=\"/photos/text_read_transformed_EB.48.4A.FB.AF.4D_20250610_112320.jpg\" class=\"lightbox-trigger\"> \n <img src=\"/photos/text_read_transformed_EB.48.4A.FB.AF.4D_20250610_112320.jpg\" alt=\"text_read_transformed_EB.48.4A.FB.AF.4D_20250610_112320.jpg\" loading=\"lazy\">\n </a>\n <div class=\"filename-container\"> \n <div class=\"filename\">text_read_transformed_EB.48.4A.FB.AF.4D_20250610_112320.jpg</div>\n <a href=\"/photos/text_read_transformed_EB.48.4A.FB.AF.4D_20250610_112320.jpg\" download=\"text_read_transformed_EB.48.4A.FB.AF.4D_20250610_112320.jpg\" class=\"download-link\"> \n Download\n </a>\n </div>\n </div>\n \n <div class=\"photo-item\" data-filename=\"text_read_transformed_EB.48.4A.FB.AF.4D_20250610_112151.jpg\">\n <a href=\"/photos/text_read_transformed_EB.48.4A.FB.AF.4D_20250610_112151.jpg\" class=\"lightbox-trigger\"> \n <img src=\"/photos/text_read_transformed_EB.48.4A.FB.AF.4D_20250610_112151.jpg\" alt=\"text_read_transformed_EB.48.4A.FB.AF.4D_20250610_112151.jpg\" loading=\"lazy\">\n </a>\n <div class=\"filename-container\"> \n <div class=\"filename\">text_read_transformed_EB.48.4A.FB.AF.4D_20250610_112151.jpg</div>\n <a href=\"/photos/text_read_transformed_EB.48.4A.FB.AF.4D_20250610_112151.jpg\" download=\"text_read_transformed_EB.48.4A.FB.AF.4D_20250610_112151.jpg\" class=\"download-link\"> \n Download\n </a>\n </div>\n </div>\n \n <div class=\"photo-item\" data-filename=\"text_read_transformed_EB.48.4A.FB.AF.4D_20250610_111449.jpg\">\n <a href=\"/photos/text_read_transformed_EB.48.4A.FB.AF.4D_20250610_111449.jpg\" class=\"lightbox-trigger\"> \n <img src=\"/photos/text_read_transformed_EB.48.4A.FB.AF.4D_20250610_111449.jpg\" alt=\"text_read_transformed_EB.48.4A.FB.AF.4D_20250610_111449.jpg\" loading=\"lazy\">\n </a>\n <div class=\"filename-container\"> \n <div class=\"filename\">text_read_transformed_EB.48.4A.FB.AF.4D_20250610_111449.jpg</div>\n <a href=\"/photos/text_read_transformed_EB.48.4A.FB.AF.4D_20250610_111449.jpg\" download=\"text_read_transformed_EB.48.4A.FB.AF.4D_20250610_111449.jpg\" class=\"download-link\"> \n Download\n </a>\n </div>\n </div>\n \n <div class=\"photo-item\" data-filename=\"text_read_transformed_EB.48.4A.FB.AF.4D_20250610_111048.jpg\">\n <a href=\"/photos/text_read_transformed_EB.48.4A.FB.AF.4D_20250610_111048.jpg\" class=\"lightbox-trigger\"> \n <img src=\"/photos/text_read_transformed_EB.48.4A.FB.AF.4D_20250610_111048.jpg\" alt=\"text_read_transformed_EB.48.4A.FB.AF.4D_20250610_111048.jpg\" loading=\"lazy\">\n </a>\n <div class=\"filename-container\"> \n <div class=\"filename\">text_read_transformed_EB.48.4A.FB.AF.4D_20250610_111048.jpg</div>\n <a href=\"/photos/text_read_transformed_EB.48.4A.FB.AF.4D_20250610_111048.jpg\" download=\"text_read_transformed_EB.48.4A.FB.AF.4D_20250610_111048.jpg\" class=\"download-link\"> \n Download\n </a>\n </div>\n </div>\n \n <div class=\"photo-item\" data-filename=\"text_read_transformed_EB.48.4A.FB.AF.4D_20250610_110827.jpg\">\n <a href=\"/photos/text_read_transformed_EB.48.4A.FB.AF.4D_20250610_110827.jpg\" class=\"lightbox-trigger\"> \n <img src=\"/photos/text_read_transformed_EB.48.4A.FB.AF.4D_20250610_110827.jpg\" alt=\"text_read_transformed_EB.48.4A.FB.AF.4D_20250610_110827.jpg\" loading=\"lazy\">\n </a>\n <div class=\"filename-container\"> \n <div class=\"filename\">text_read_transformed_EB.48.4A.FB.AF.4D_20250610_110827.jpg</div>\n <a href=\"/photos/text_read_transformed_EB.48.4A.FB.AF.4D_20250610_110827.jpg\" download=\"text_read_transformed_EB.48.4A.FB.AF.4D_20250610_110827.jpg\" class=\"download-link\"> \n Download\n </a>\n </div>\n </div>\n \n <div class=\"photo-item\" data-filename=\"text_read_transformed_EB.48.4A.FB.AF.4D_20250610_105158.jpg\">\n <a href=\"/photos/text_read_transformed_EB.48.4A.FB.AF.4D_20250610_105158.jpg\" class=\"lightbox-trigger\"> \n <img src=\"/photos/text_read_transformed_EB.48.4A.FB.AF.4D_20250610_105158.jpg\" alt=\"text_read_transformed_EB.48.4A.FB.AF.4D_20250610_105158.jpg\" loading=\"lazy\">\n </a>\n <div class=\"filename-container\"> \n <div class=\"filename\">text_read_transformed_EB.48.4A.FB.AF.4D_20250610_105158.jpg</div>\n <a href=\"/photos/text_read_transformed_EB.48.4A.FB.AF.4D_20250610_105158.jpg\" download=\"text_read_transformed_EB.48.4A.FB.AF.4D_20250610_105158.jpg\" class=\"download-link\"> \n Download\n </a>\n </div>\n </div>\n \n <div class=\"photo-item\" data-filename=\"text_read_transformed_EB.48.4A.FB.AF.4D_20250610_105125.jpg\">\n <a href=\"/photos/text_read_transformed_EB.48.4A.FB.AF.4D_20250610_105125.jpg\" class=\"lightbox-trigger\"> \n <img src=\"/photos/text_read_transformed_EB.48.4A.FB.AF.4D_20250610_105125.jpg\" alt=\"text_read_transformed_EB.48.4A.FB.AF.4D_20250610_105125.jpg\" loading=\"lazy\">\n </a>\n <div class=\"filename-container\"> \n <div class=\"filename\">text_read_transformed_EB.48.4A.FB.AF.4D_20250610_105125.jpg</div>\n <a href=\"/photos/text_read_transformed_EB.48.4A.FB.AF.4D_20250610_105125.jpg\" download=\"text_read_transformed_EB.48.4A.FB.AF.4D_20250610_105125.jpg\" class=\"download-link\"> \n Download\n </a>\n </div>\n </div>\n \n <div class=\"photo-item\" data-filename=\"text_read_transformed_EB.48.4A.FB.AF.4D_20250610_104921.jpg\">\n <a href=\"/photos/text_read_transformed_EB.48.4A.FB.AF.4D_20250610_104921.jpg\" class=\"lightbox-trigger\"> \n <img src=\"/photos/text_read_transformed_EB.48.4A.FB.AF.4D_20250610_104921.jpg\" alt=\"text_read_transformed_EB.48.4A.FB.AF.4D_20250610_104921.jpg\" loading=\"lazy\">\n </a>\n <div class=\"filename-container\"> \n <div class=\"filename\">text_read_transformed_EB.48.4A.FB.AF.4D_20250610_104921.jpg</div>\n <a href=\"/photos/text_read_transformed_EB.48.4A.FB.AF.4D_20250610_104921.jpg\" download=\"text_read_transformed_EB.48.4A.FB.AF.4D_20250610_104921.jpg\" class=\"download-link\"> \n Download\n </a>\n </div>\n </div>\n \n <div class=\"photo-item\" data-filename=\"text_read_transformed_4A.44.78.63.49.44_20250608_220724.jpg\">\n <a href=\"/photos/text_read_transformed_4A.44.78.63.49.44_20250608_220724.jpg\" class=\"lightbox-trigger\"> \n <img src=\"/photos/text_read_transformed_4A.44.78.63.49.44_20250608_220724.jpg\" alt=\"text_read_transformed_4A.44.78.63.49.44_20250608_220724.jpg\" loading=\"lazy\">\n </a>\n <div class=\"filename-container\"> \n <div class=\"filename\">text_read_transformed_4A.44.78.63.49.44_20250608_220724.jpg</div>\n <a href=\"/photos/text_read_transformed_4A.44.78.63.49.44_20250608_220724.jpg\" download=\"text_read_transformed_4A.44.78.63.49.44_20250608_220724.jpg\" class=\"download-link\"> \n Download\n </a>\n </div>\n </div>\n \n <div class=\"photo-item\" data-filename=\"text_read_transformed_63.C1.A4.CD.3F.F4_20250608_215911.jpg\">\n <a href=\"/photos/text_read_transformed_63.C1.A4.CD.3F.F4_20250608_215911.jpg\" class=\"lightbox-trigger\"> \n <img src=\"/photos/text_read_transformed_63.C1.A4.CD.3F.F4_20250608_215911.jpg\" alt=\"text_read_transformed_63.C1.A4.CD.3F.F4_20250608_215911.jpg\" loading=\"lazy\">\n </a>\n <div class=\"filename-container\"> \n <div class=\"filename\">text_read_transformed_63.C1.A4.CD.3F.F4_20250608_215911.jpg</div>\n <a href=\"/photos/text_read_transformed_63.C1.A4.CD.3F.F4_20250608_215911.jpg\" download=\"text_read_transformed_63.C1.A4.CD.3F.F4_20250608_215911.jpg\" class=\"download-link\"> \n Download\n </a>\n </div>\n </div>\n \n <div class=\"photo-item\" data-filename=\"text_read_transformed_4A.44.78.63.49.44_20250608_215357.jpg\">\n <a href=\"/photos/text_read_transformed_4A.44.78.63.49.44_20250608_215357.jpg\" class=\"lightbox-trigger\"> \n <img src=\"/photos/text_read_transformed_4A.44.78.63.49.44_20250608_215357.jpg\" alt=\"text_read_transformed_4A.44.78.63.49.44_20250608_215357.jpg\" loading=\"lazy\">\n </a>\n <div class=\"filename-container\"> \n <div class=\"filename\">text_read_transformed_4A.44.78.63.49.44_20250608_215357.jpg</div>\n <a href=\"/photos/text_read_transformed_4A.44.78.63.49.44_20250608_215357.jpg\" download=\"text_read_transformed_4A.44.78.63.49.44_20250608_215357.jpg\" class=\"download-link\"> \n Download\n </a>\n </div>\n </div>\n \n <div class=\"photo-item\" data-filename=\"text_read_transformed_63.C1.A4.CD.3F.F4_20250607_085302.jpg\">\n <a href=\"/photos/text_read_transformed_63.C1.A4.CD.3F.F4_20250607_085302.jpg\" class=\"lightbox-trigger\"> \n <img src=\"/photos/text_read_transformed_63.C1.A4.CD.3F.F4_20250607_085302.jpg\" alt=\"text_read_transformed_63.C1.A4.CD.3F.F4_20250607_085302.jpg\" loading=\"lazy\">\n </a>\n <div class=\"filename-container\"> \n <div class=\"filename\">text_read_transformed_63.C1.A4.CD.3F.F4_20250607_085302.jpg</div>\n <a href=\"/photos/text_read_transformed_63.C1.A4.CD.3F.F4_20250607_085302.jpg\" download=\"text_read_transformed_63.C1.A4.CD.3F.F4_20250607_085302.jpg\" class=\"download-link\"> \n Download\n </a>\n </div>\n </div>\n \n <div class=\"photo-item\" data-filename=\"text_read_transformed_63.C1.A4.CD.3F.F4_20250606_224901.jpg\">\n <a href=\"/photos/text_read_transformed_63.C1.A4.CD.3F.F4_20250606_224901.jpg\" class=\"lightbox-trigger\"> \n <img src=\"/photos/text_read_transformed_63.C1.A4.CD.3F.F4_20250606_224901.jpg\" alt=\"text_read_transformed_63.C1.A4.CD.3F.F4_20250606_224901.jpg\" loading=\"lazy\">\n </a>\n <div class=\"filename-container\"> \n <div class=\"filename\">text_read_transformed_63.C1.A4.CD.3F.F4_20250606_224901.jpg</div>\n <a href=\"/photos/text_read_transformed_63.C1.A4.CD.3F.F4_20250606_224901.jpg\" download=\"text_read_transformed_63.C1.A4.CD.3F.F4_20250606_224901.jpg\" class=\"download-link\"> \n Download\n </a>\n </div>\n </div>\n \n <div class=\"photo-item\" data-filename=\"text_read_transformed_63.C1.A4.CD.3F.F4_20250606_224605.jpg\">\n <a href=\"/photos/text_read_transformed_63.C1.A4.CD.3F.F4_20250606_224605.jpg\" class=\"lightbox-trigger\"> \n <img src=\"/photos/text_read_transformed_63.C1.A4.CD.3F.F4_20250606_224605.jpg\" alt=\"text_read_transformed_63.C1.A4.CD.3F.F4_20250606_224605.jpg\" loading=\"lazy\">\n </a>\n <div class=\"filename-container\"> \n <div class=\"filename\">text_read_transformed_63.C1.A4.CD.3F.F4_20250606_224605.jpg</div>\n <a href=\"/photos/text_read_transformed_63.C1.A4.CD.3F.F4_20250606_224605.jpg\" download=\"text_read_transformed_63.C1.A4.CD.3F.F4_20250606_224605.jpg\" class=\"download-link\"> \n Download\n </a>\n </div>\n </div>\n \n <div class=\"photo-item\" data-filename=\"text_read_transformed_63.C1.A4.CD.3F.F4_20250606_223716.jpg\">\n <a href=\"/photos/text_read_transformed_63.C1.A4.CD.3F.F4_20250606_223716.jpg\" class=\"lightbox-trigger\"> \n <img src=\"/photos/text_read_transformed_63.C1.A4.CD.3F.F4_20250606_223716.jpg\" alt=\"text_read_transformed_63.C1.A4.CD.3F.F4_20250606_223716.jpg\" loading=\"lazy\">\n </a>\n <div class=\"filename-container\"> \n <div class=\"filename\">text_read_transformed_63.C1.A4.CD.3F.F4_20250606_223716.jpg</div>\n <a href=\"/photos/text_read_transformed_63.C1.A4.CD.3F.F4_20250606_223716.jpg\" download=\"text_read_transformed_63.C1.A4.CD.3F.F4_20250606_223716.jpg\" class=\"download-link\"> \n Download\n </a>\n </div>\n </div>\n \n <div class=\"photo-item\" data-filename=\"text_read_transformed_63.C1.A4.CD.3F.F4_20250606_223636.jpg\">\n <a href=\"/photos/text_read_transformed_63.C1.A4.CD.3F.F4_20250606_223636.jpg\" class=\"lightbox-trigger\"> \n <img src=\"/photos/text_read_transformed_63.C1.A4.CD.3F.F4_20250606_223636.jpg\" alt=\"text_read_transformed_63.C1.A4.CD.3F.F4_20250606_223636.jpg\" loading=\"lazy\">\n </a>\n <div class=\"filename-container\"> \n <div class=\"filename\">text_read_transformed_63.C1.A4.CD.3F.F4_20250606_223636.jpg</div>\n <a href=\"/photos/text_read_transformed_63.C1.A4.CD.3F.F4_20250606_223636.jpg\" download=\"text_read_transformed_63.C1.A4.CD.3F.F4_20250606_223636.jpg\" class=\"download-link\"> \n Download\n </a>\n </div>\n </div>\n \n <div class=\"photo-item\" data-filename=\"text_read_transformed_63.C1.A4.CD.3F.F4_20250606_205142.jpg\">\n <a href=\"/photos/text_read_transformed_63.C1.A4.CD.3F.F4_20250606_205142.jpg\" class=\"lightbox-trigger\"> \n <img src=\"/photos/text_read_transformed_63.C1.A4.CD.3F.F4_20250606_205142.jpg\" alt=\"text_read_transformed_63.C1.A4.CD.3F.F4_20250606_205142.jpg\" loading=\"lazy\">\n </a>\n <div class=\"filename-container\"> \n <div class=\"filename\">text_read_transformed_63.C1.A4.CD.3F.F4_20250606_205142.jpg</div>\n <a href=\"/photos/text_read_transformed_63.C1.A4.CD.3F.F4_20250606_205142.jpg\" download=\"text_read_transformed_63.C1.A4.CD.3F.F4_20250606_205142.jpg\" class=\"download-link\"> \n Download\n </a>\n </div>\n </div>\n \n <div class=\"photo-item\" data-filename=\"text_read_transformed_63.C1.A4.CD.3F.F4_20250606_200450.jpg\">\n <a href=\"/photos/text_read_transformed_63.C1.A4.CD.3F.F4_20250606_200450.jpg\" class=\"lightbox-trigger\"> \n <img src=\"/photos/text_read_transformed_63.C1.A4.CD.3F.F4_20250606_200450.jpg\" alt=\"text_read_transformed_63.C1.A4.CD.3F.F4_20250606_200450.jpg\" loading=\"lazy\">\n </a>\n <div class=\"filename-container\"> \n <div class=\"filename\">text_read_transformed_63.C1.A4.CD.3F.F4_20250606_200450.jpg</div>\n <a href=\"/photos/text_read_transformed_63.C1.A4.CD.3F.F4_20250606_200450.jpg\" download=\"text_read_transformed_63.C1.A4.CD.3F.F4_20250606_200450.jpg\" class=\"download-link\"> \n Download\n </a>\n </div>\n </div>\n \n <div class=\"photo-item\" data-filename=\"text_read_transformed_63.C1.A4.CD.3F.F4_20250606_200418.jpg\">\n <a href=\"/photos/text_read_transformed_63.C1.A4.CD.3F.F4_20250606_200418.jpg\" class=\"lightbox-trigger\"> \n <img src=\"/photos/text_read_transformed_63.C1.A4.CD.3F.F4_20250606_200418.jpg\" alt=\"text_read_transformed_63.C1.A4.CD.3F.F4_20250606_200418.jpg\" loading=\"lazy\">\n </a>\n <div class=\"filename-container\"> \n <div class=\"filename\">text_read_transformed_63.C1.A4.CD.3F.F4_20250606_200418.jpg</div>\n <a href=\"/photos/text_read_transformed_63.C1.A4.CD.3F.F4_20250606_200418.jpg\" download=\"text_read_transformed_63.C1.A4.CD.3F.F4_20250606_200418.jpg\" class=\"download-link\"> \n Download\n </a>\n </div>\n </div>\n \n <div class=\"photo-item\" data-filename=\"text_read_transformed_63.C1.A4.CD.3F.F4_20250606_194121.jpg\">\n <a href=\"/photos/text_read_transformed_63.C1.A4.CD.3F.F4_20250606_194121.jpg\" class=\"lightbox-trigger\"> \n <img src=\"/photos/text_read_transformed_63.C1.A4.CD.3F.F4_20250606_194121.jpg\" alt=\"text_read_transformed_63.C1.A4.CD.3F.F4_20250606_194121.jpg\" loading=\"lazy\">\n </a>\n <div class=\"filename-container\"> \n <div class=\"filename\">text_read_transformed_63.C1.A4.CD.3F.F4_20250606_194121.jpg</div>\n <a href=\"/photos/text_read_transformed_63.C1.A4.CD.3F.F4_20250606_194121.jpg\" download=\"text_read_transformed_63.C1.A4.CD.3F.F4_20250606_194121.jpg\" class=\"download-link\"> \n Download\n </a>\n </div>\n </div>\n \n <div class=\"photo-item\" data-filename=\"text_read_transformed_63.C1.A4.CD.3F.F4_20250606_191505.jpg\">\n <a href=\"/photos/text_read_transformed_63.C1.A4.CD.3F.F4_20250606_191505.jpg\" class=\"lightbox-trigger\"> \n <img src=\"/photos/text_read_transformed_63.C1.A4.CD.3F.F4_20250606_191505.jpg\" alt=\"text_read_transformed_63.C1.A4.CD.3F.F4_20250606_191505.jpg\" loading=\"lazy\">\n </a>\n <div class=\"filename-container\"> \n <div class=\"filename\">text_read_transformed_63.C1.A4.CD.3F.F4_20250606_191505.jpg</div>\n <a href=\"/photos/text_read_transformed_63.C1.A4.CD.3F.F4_20250606_191505.jpg\" download=\"text_read_transformed_63.C1.A4.CD.3F.F4_20250606_191505.jpg\" class=\"download-link\"> \n Download\n </a>\n </div>\n </div>\n \n <div class=\"photo-item\" data-filename=\"text_read_transformed_63.C1.A4.CD.3F.F4_20250606_185635.jpg\">\n <a href=\"/photos/text_read_transformed_63.C1.A4.CD.3F.F4_20250606_185635.jpg\" class=\"lightbox-trigger\"> \n <img src=\"/photos/text_read_transformed_63.C1.A4.CD.3F.F4_20250606_185635.jpg\" alt=\"text_read_transformed_63.C1.A4.CD.3F.F4_20250606_185635.jpg\" loading=\"lazy\">\n </a>\n <div class=\"filename-container\"> \n <div class=\"filename\">text_read_transformed_63.C1.A4.CD.3F.F4_20250606_185635.jpg</div>\n <a href=\"/photos/text_read_transformed_63.C1.A4.CD.3F.F4_20250606_185635.jpg\" download=\"text_read_transformed_63.C1.A4.CD.3F.F4_20250606_185635.jpg\" class=\"download-link\"> \n Download\n </a>\n </div>\n </div>\n \n <div class=\"photo-item\" data-filename=\"text_read_transformed_63.C1.A4.CD.3F.F4_20250606_185024.jpg\">\n <a href=\"/photos/text_read_transformed_63.C1.A4.CD.3F.F4_20250606_185024.jpg\" class=\"lightbox-trigger\"> \n <img src=\"/photos/text_read_transformed_63.C1.A4.CD.3F.F4_20250606_185024.jpg\" alt=\"text_read_transformed_63.C1.A4.CD.3F.F4_20250606_185024.jpg\" loading=\"lazy\">\n </a>\n <div class=\"filename-container\"> \n <div class=\"filename\">text_read_transformed_63.C1.A4.CD.3F.F4_20250606_185024.jpg</div>\n <a href=\"/photos/text_read_transformed_63.C1.A4.CD.3F.F4_20250606_185024.jpg\" download=\"text_read_transformed_63.C1.A4.CD.3F.F4_20250606_185024.jpg\" class=\"download-link\"> \n Download\n </a>\n </div>\n </div>\n \n <div class=\"photo-item\" data-filename=\"text_read_transformed_63.C1.A4.CD.3F.F4_20250606_184949.jpg\">\n <a href=\"/photos/text_read_transformed_63.C1.A4.CD.3F.F4_20250606_184949.jpg\" class=\"lightbox-trigger\"> \n <img src=\"/photos/text_read_transformed_63.C1.A4.CD.3F.F4_20250606_184949.jpg\" alt=\"text_read_transformed_63.C1.A4.CD.3F.F4_20250606_184949.jpg\" loading=\"lazy\">\n </a>\n <div class=\"filename-container\"> \n <div class=\"filename\">text_read_transformed_63.C1.A4.CD.3F.F4_20250606_184949.jpg</div>\n <a href=\"/photos/text_read_transformed_63.C1.A4.CD.3F.F4_20250606_184949.jpg\" download=\"text_read_transformed_63.C1.A4.CD.3F.F4_20250606_184949.jpg\" class=\"download-link\"> \n Download\n </a>\n </div>\n </div>\n \n <div class=\"photo-item\" data-filename=\"text_read_transformed_63.C1.A4.CD.3F.F4_20250606_184853.jpg\">\n <a href=\"/photos/text_read_transformed_63.C1.A4.CD.3F.F4_20250606_184853.jpg\" class=\"lightbox-trigger\"> \n <img src=\"/photos/text_read_transformed_63.C1.A4.CD.3F.F4_20250606_184853.jpg\" alt=\"text_read_transformed_63.C1.A4.CD.3F.F4_20250606_184853.jpg\" loading=\"lazy\">\n </a>\n <div class=\"filename-container\"> \n <div class=\"filename\">text_read_transformed_63.C1.A4.CD.3F.F4_20250606_184853.jpg</div>\n <a href=\"/photos/text_read_transformed_63.C1.A4.CD.3F.F4_20250606_184853.jpg\" download=\"text_read_transformed_63.C1.A4.CD.3F.F4_20250606_184853.jpg\" class=\"download-link\"> \n Download\n </a>\n </div>\n </div>\n \n <div class=\"photo-item\" data-filename=\"text_read_transformed_63.C1.A4.CD.3F.F4_20250606_184629.jpg\">\n <a href=\"/photos/text_read_transformed_63.C1.A4.CD.3F.F4_20250606_184629.jpg\" class=\"lightbox-trigger\"> \n <img src=\"/photos/text_read_transformed_63.C1.A4.CD.3F.F4_20250606_184629.jpg\" alt=\"text_read_transformed_63.C1.A4.CD.3F.F4_20250606_184629.jpg\" loading=\"lazy\">\n </a>\n <div class=\"filename-container\"> \n <div class=\"filename\">text_read_transformed_63.C1.A4.CD.3F.F4_20250606_184629.jpg</div>\n <a href=\"/photos/text_read_transformed_63.C1.A4.CD.3F.F4_20250606_184629.jpg\" download=\"text_read_transformed_63.C1.A4.CD.3F.F4_20250606_184629.jpg\" class=\"download-link\"> \n Download\n </a>\n </div>\n </div>\n \n <div class=\"photo-item\" data-filename=\"text_read_transformed_63.C1.A4.CD.3F.F4_20250606_184608.jpg\">\n <a href=\"/photos/text_read_transformed_63.C1.A4.CD.3F.F4_20250606_184608.jpg\" class=\"lightbox-trigger\"> \n <img src=\"/photos/text_read_transformed_63.C1.A4.CD.3F.F4_20250606_184608.jpg\" alt=\"text_read_transformed_63.C1.A4.CD.3F.F4_20250606_184608.jpg\" loading=\"lazy\">\n </a>\n <div class=\"filename-container\"> \n <div class=\"filename\">text_read_transformed_63.C1.A4.CD.3F.F4_20250606_184608.jpg</div>\n <a href=\"/photos/text_read_transformed_63.C1.A4.CD.3F.F4_20250606_184608.jpg\" download=\"text_read_transformed_63.C1.A4.CD.3F.F4_20250606_184608.jpg\" class=\"download-link\"> \n Download\n </a>\n </div>\n </div>\n \n <div class=\"photo-item\" data-filename=\"text_read_transformed_63.C1.A4.CD.3F.F4_20250606_183628.jpg\">\n <a href=\"/photos/text_read_transformed_63.C1.A4.CD.3F.F4_20250606_183628.jpg\" class=\"lightbox-trigger\"> \n <img src=\"/photos/text_read_transformed_63.C1.A4.CD.3F.F4_20250606_183628.jpg\" alt=\"text_read_transformed_63.C1.A4.CD.3F.F4_20250606_183628.jpg\" loading=\"lazy\">\n </a>\n <div class=\"filename-container\"> \n <div class=\"filename\">text_read_transformed_63.C1.A4.CD.3F.F4_20250606_183628.jpg</div>\n <a href=\"/photos/text_read_transformed_63.C1.A4.CD.3F.F4_20250606_183628.jpg\" download=\"text_read_transformed_63.C1.A4.CD.3F.F4_20250606_183628.jpg\" class=\"download-link\"> \n Download\n </a>\n </div>\n </div>\n \n <div class=\"photo-item\" data-filename=\"text_read_transformed_63.C1.A4.CD.3F.F4_20250606_181951.jpg\">\n <a href=\"/photos/text_read_transformed_63.C1.A4.CD.3F.F4_20250606_181951.jpg\" class=\"lightbox-trigger\"> \n <img src=\"/photos/text_read_transformed_63.C1.A4.CD.3F.F4_20250606_181951.jpg\" alt=\"text_read_transformed_63.C1.A4.CD.3F.F4_20250606_181951.jpg\" loading=\"lazy\">\n </a>\n <div class=\"filename-container\"> \n <div class=\"filename\">text_read_transformed_63.C1.A4.CD.3F.F4_20250606_181951.jpg</div>\n <a href=\"/photos/text_read_transformed_63.C1.A4.CD.3F.F4_20250606_181951.jpg\" download=\"text_read_transformed_63.C1.A4.CD.3F.F4_20250606_181951.jpg\" class=\"download-link\"> \n Download\n </a>\n </div>\n </div>\n \n <div class=\"photo-item\" data-filename=\"text_read_transformed_4A.44.78.63.49.44_20250606_174847.jpg\">\n <a href=\"/photos/text_read_transformed_4A.44.78.63.49.44_20250606_174847.jpg\" class=\"lightbox-trigger\"> \n <img src=\"/photos/text_read_transformed_4A.44.78.63.49.44_20250606_174847.jpg\" alt=\"text_read_transformed_4A.44.78.63.49.44_20250606_174847.jpg\" loading=\"lazy\">\n </a>\n <div class=\"filename-container\"> \n <div class=\"filename\">text_read_transformed_4A.44.78.63.49.44_20250606_174847.jpg</div>\n <a href=\"/photos/text_read_transformed_4A.44.78.63.49.44_20250606_174847.jpg\" download=\"text_read_transformed_4A.44.78.63.49.44_20250606_174847.jpg\" class=\"download-link\"> \n Download\n </a>\n </div>\n </div>\n \n <div class=\"photo-item\" data-filename=\"text_read_transformed_63.C1.A4.CD.3F.F4_20250606_173946.jpg\">\n <a href=\"/photos/text_read_transformed_63.C1.A4.CD.3F.F4_20250606_173946.jpg\" class=\"lightbox-trigger\"> \n <img src=\"/photos/text_read_transformed_63.C1.A4.CD.3F.F4_20250606_173946.jpg\" alt=\"text_read_transformed_63.C1.A4.CD.3F.F4_20250606_173946.jpg\" loading=\"lazy\">\n </a>\n <div class=\"filename-container\"> \n <div class=\"filename\">text_read_transformed_63.C1.A4.CD.3F.F4_20250606_173946.jpg</div>\n <a href=\"/photos/text_read_transformed_63.C1.A4.CD.3F.F4_20250606_173946.jpg\" download=\"text_read_transformed_63.C1.A4.CD.3F.F4_20250606_173946.jpg\" class=\"download-link\"> \n Download\n </a>\n </div>\n </div>\n \n <div class=\"photo-item\" data-filename=\"text_read_transformed_63.C1.A4.CD.3F.F4_20250606_173917.jpg\">\n <a href=\"/photos/text_read_transformed_63.C1.A4.CD.3F.F4_20250606_173917.jpg\" class=\"lightbox-trigger\"> \n <img src=\"/photos/text_read_transformed_63.C1.A4.CD.3F.F4_20250606_173917.jpg\" alt=\"text_read_transformed_63.C1.A4.CD.3F.F4_20250606_173917.jpg\" loading=\"lazy\">\n </a>\n <div class=\"filename-container\"> \n <div class=\"filename\">text_read_transformed_63.C1.A4.CD.3F.F4_20250606_173917.jpg</div>\n <a href=\"/photos/text_read_transformed_63.C1.A4.CD.3F.F4_20250606_173917.jpg\" download=\"text_read_transformed_63.C1.A4.CD.3F.F4_20250606_173917.jpg\" class=\"download-link\"> \n Download\n </a>\n </div>\n </div>\n \n <div class=\"photo-item\" data-filename=\"text_read_transformed_4A.44.78.63.49.44_20250606_173102.jpg\">\n <a href=\"/photos/text_read_transformed_4A.44.78.63.49.44_20250606_173102.jpg\" class=\"lightbox-trigger\"> \n <img src=\"/photos/text_read_transformed_4A.44.78.63.49.44_20250606_173102.jpg\" alt=\"text_read_transformed_4A.44.78.63.49.44_20250606_173102.jpg\" loading=\"lazy\">\n </a>\n <div class=\"filename-container\"> \n <div class=\"filename\">text_read_transformed_4A.44.78.63.49.44_20250606_173102.jpg</div>\n <a href=\"/photos/text_read_transformed_4A.44.78.63.49.44_20250606_173102.jpg\" download=\"text_read_transformed_4A.44.78.63.49.44_20250606_173102.jpg\" class=\"download-link\"> \n Download\n </a>\n </div>\n </div>\n \n <div class=\"photo-item\" data-filename=\"text_read_transformed_63.C1.A4.CD.3F.F4_20250606_172817.jpg\">\n <a href=\"/photos/text_read_transformed_63.C1.A4.CD.3F.F4_20250606_172817.jpg\" class=\"lightbox-trigger\"> \n <img src=\"/photos/text_read_transformed_63.C1.A4.CD.3F.F4_20250606_172817.jpg\" alt=\"text_read_transformed_63.C1.A4.CD.3F.F4_20250606_172817.jpg\" loading=\"lazy\">\n </a>\n <div class=\"filename-container\"> \n <div class=\"filename\">text_read_transformed_63.C1.A4.CD.3F.F4_20250606_172817.jpg</div>\n <a href=\"/photos/text_read_transformed_63.C1.A4.CD.3F.F4_20250606_172817.jpg\" download=\"text_read_transformed_63.C1.A4.CD.3F.F4_20250606_172817.jpg\" class=\"download-link\"> \n Download\n </a>\n </div>\n </div>\n \n <div class=\"photo-item\" data-filename=\"text_read_transformed_4A.44.78.63.49.44_20250606_144624.jpg\">\n <a href=\"/photos/text_read_transformed_4A.44.78.63.49.44_20250606_144624.jpg\" class=\"lightbox-trigger\"> \n <img src=\"/photos/text_read_transformed_4A.44.78.63.49.44_20250606_144624.jpg\" alt=\"text_read_transformed_4A.44.78.63.49.44_20250606_144624.jpg\" loading=\"lazy\">\n </a>\n <div class=\"filename-container\"> \n <div class=\"filename\">text_read_transformed_4A.44.78.63.49.44_20250606_144624.jpg</div>\n <a href=\"/photos/text_read_transformed_4A.44.78.63.49.44_20250606_144624.jpg\" download=\"text_read_transformed_4A.44.78.63.49.44_20250606_144624.jpg\" class=\"download-link\"> \n Download\n </a>\n </div>\n </div>\n \n <div class=\"photo-item\" data-filename=\"text_read_transformed_4A.44.78.63.49.44_20250606_142602.jpg\">\n <a href=\"/photos/text_read_transformed_4A.44.78.63.49.44_20250606_142602.jpg\" class=\"lightbox-trigger\"> \n <img src=\"/photos/text_read_transformed_4A.44.78.63.49.44_20250606_142602.jpg\" alt=\"text_read_transformed_4A.44.78.63.49.44_20250606_142602.jpg\" loading=\"lazy\">\n </a>\n <div class=\"filename-container\"> \n <div class=\"filename\">text_read_transformed_4A.44.78.63.49.44_20250606_142602.jpg</div>\n <a href=\"/photos/text_read_transformed_4A.44.78.63.49.44_20250606_142602.jpg\" download=\"text_read_transformed_4A.44.78.63.49.44_20250606_142602.jpg\" class=\"download-link\"> \n Download\n </a>\n </div>\n </div>\n \n <div class=\"photo-item\" data-filename=\"text_read_transformed_4A.44.78.63.49.44_20250606_135310.jpg\">\n <a href=\"/photos/text_read_transformed_4A.44.78.63.49.44_20250606_135310.jpg\" class=\"lightbox-trigger\"> \n <img src=\"/photos/text_read_transformed_4A.44.78.63.49.44_20250606_135310.jpg\" alt=\"text_read_transformed_4A.44.78.63.49.44_20250606_135310.jpg\" loading=\"lazy\">\n </a>\n <div class=\"filename-container\"> \n <div class=\"filename\">text_read_transformed_4A.44.78.63.49.44_20250606_135310.jpg</div>\n <a href=\"/photos/text_read_transformed_4A.44.78.63.49.44_20250606_135310.jpg\" download=\"text_read_transformed_4A.44.78.63.49.44_20250606_135310.jpg\" class=\"download-link\"> \n Download\n </a>\n </div>\n </div>\n \n <div class=\"photo-item\" data-filename=\"text_read_transformed_4A.44.78.63.49.44_20250606_134923.jpg\">\n <a href=\"/photos/text_read_transformed_4A.44.78.63.49.44_20250606_134923.jpg\" class=\"lightbox-trigger\"> \n <img src=\"/photos/text_read_transformed_4A.44.78.63.49.44_20250606_134923.jpg\" alt=\"text_read_transformed_4A.44.78.63.49.44_20250606_134923.jpg\" loading=\"lazy\">\n </a>\n <div class=\"filename-container\"> \n <div class=\"filename\">text_read_transformed_4A.44.78.63.49.44_20250606_134923.jpg</div>\n <a href=\"/photos/text_read_transformed_4A.44.78.63.49.44_20250606_134923.jpg\" download=\"text_read_transformed_4A.44.78.63.49.44_20250606_134923.jpg\" class=\"download-link\"> \n Download\n </a>\n </div>\n </div>\n \n <div class=\"photo-item\" data-filename=\"text_read_transformed_4A.44.78.63.49.44_20250606_134806.jpg\">\n <a href=\"/photos/text_read_transformed_4A.44.78.63.49.44_20250606_134806.jpg\" class=\"lightbox-trigger\"> \n <img src=\"/photos/text_read_transformed_4A.44.78.63.49.44_20250606_134806.jpg\" alt=\"text_read_transformed_4A.44.78.63.49.44_20250606_134806.jpg\" loading=\"lazy\">\n </a>\n <div class=\"filename-container\"> \n <div class=\"filename\">text_read_transformed_4A.44.78.63.49.44_20250606_134806.jpg</div>\n <a href=\"/photos/text_read_transformed_4A.44.78.63.49.44_20250606_134806.jpg\" download=\"text_read_transformed_4A.44.78.63.49.44_20250606_134806.jpg\" class=\"download-link\"> \n Download\n </a>\n </div>\n </div>\n \n <div class=\"photo-item\" data-filename=\"text_read_transformed_4A.44.78.63.49.44_20250606_134750.jpg\">\n <a href=\"/photos/text_read_transformed_4A.44.78.63.49.44_20250606_134750.jpg\" class=\"lightbox-trigger\"> \n <img src=\"/photos/text_read_transformed_4A.44.78.63.49.44_20250606_134750.jpg\" alt=\"text_read_transformed_4A.44.78.63.49.44_20250606_134750.jpg\" loading=\"lazy\">\n </a>\n <div class=\"filename-container\"> \n <div class=\"filename\">text_read_transformed_4A.44.78.63.49.44_20250606_134750.jpg</di",
"body_hashes": [
"sha256:deb02606f03545c02cb63f2275158e2286ae83b88c6e9335bff3a9898e9c1b3f",
"sha1:c62145a2bf0f20284b0e97b9d8a0d3971be30a6e",
"tlsh:12537a06dde4801350577006dfe0fbc9978ad38355479a8bbbac43475f89e97eba220e"
],
"body_hash": "sha1:c62145a2bf0f20284b0e97b9d8a0d3971be30a6e",
"html_title": "Real-time Photo Feed"
},
"supports_http2": false
},
"observed_at": "2025-07-11T17:39:25.660342821Z",
"perspective_id": "PERSPECTIVE_UNKNOWN",
"port": 8001,
"service_name": "HTTP",
"software": [
{
"product": "python",
"version": "3.12.3",
"source": "OSI_APPLICATION_LAYER"
},
{
"uniform_resource_identifier": "cpe:2.3:a:palletsprojects:werkzeug:3.1.3:*:*:*:*:*:*:*",
"part": "a",
"vendor": "PalletsProjects",
"product": "Werkzeug",
"version": "3.1.3",
"source": "OSI_APPLICATION_LAYER"
}
],
"source_ip": "162.142.125.39",
"transport_protocol": "TCP",
"truncated": false
},
{
"_decoded": "http",
"_encoding": {
"banner": "DISPLAY_UTF8",
"banner_hex": "DISPLAY_HEX"
},
"banner": "HTTP/1.1 200 OK\r\nServer: Werkzeug/3.1.3 Python/3.12.3\r\nDate: <REDACTED>\r\nContent-Type: text/html; charset=utf-8\r\nContent-Length: 12772\r\nConnection: close\r\n",
"banner_hashes": [
"sha256:6299a711da12c028f730e9689eecdf1e0d10d9f3ca2d00b7b20cb562430522d7"
],
"banner_hex": "485454502f312e3120323030204f4b0d0a5365727665723a205765726b7a6575672f332e312e3320507974686f6e2f332e31322e330d0a446174653a20203c52454441435445443e0d0a436f6e74656e742d547970653a20746578742f68746d6c3b20636861727365743d7574662d380d0a436f6e74656e742d4c656e6774683a2031323737320d0a436f6e6e656374696f6e3a20636c6f73650d0a",
"discovery_method": "PREDICTIVE_METHOD_15",
"extended_service_name": "HTTP",
"http": {
"request": {
"method": "GET",
"uri": "http://20.68.131.221:8002/",
"headers": {
"User_Agent": [
"Mozilla/5.0 (compatible; CensysInspect/1.1; +https://about.censys.io/)"
],
"_encoding": {
"User_Agent": "DISPLAY_UTF8",
"Accept": "DISPLAY_UTF8"
},
"Accept": [
"*/*"
]
}
},
"response": {
"protocol": "HTTP/1.1",
"status_code": 200,
"status_reason": "OK",
"headers": {
"Date": [
"<REDACTED>"
],
"_encoding": {
"Date": "DISPLAY_UTF8",
"Server": "DISPLAY_UTF8",
"Content_Length": "DISPLAY_UTF8",
"Content_Type": "DISPLAY_UTF8",
"Connection": "DISPLAY_UTF8"
},
"Server": [
"Werkzeug/3.1.3 Python/3.12.3"
],
"Content_Length": [
"12772"
],
"Content_Type": [
"text/html; charset=utf-8"
],
"Connection": [
"close"
]
},
"_encoding": {
"html_tags": "DISPLAY_UTF8",
"body": "DISPLAY_UTF8",
"body_hash": "DISPLAY_UTF8",
"html_title": "DISPLAY_UTF8"
},
"html_tags": [
"<title>Real-time Photo Feed</title>",
"<meta charset=\"UTF-8\">",
"<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">"
],
"body_size": 12772,
"body": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>Real-time Photo Feed</title>\n\n <!-- Fonts -->\n <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">\n <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin>\n <link href=\"https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700&display=swap\" rel=\"stylesheet\">\n\n <!-- Embedded CSS -->\n <style>\n /* --- Base and UI Styling --- */\n /* Custom scrollbar for webkit browsers (GLOBAL) */\n ::-webkit-scrollbar { width: 8px; }\n ::-webkit-scrollbar-track { background: #2d3748; border-radius: 10px; }\n ::-webkit-scrollbar-thumb { background: #718096; border-radius: 10px; }\n ::-webkit-scrollbar-thumb:hover { background: #a0aec0; }\n\n body {\n font-family: 'Inter', sans-serif; /* Using Inter font */\n margin: 0;\n background-color: #f4f4f4;\n color: #333;\n line-height: 1.6;\n }\n header {\n background-color: #333;\n color: #fff;\n padding: 1rem;\n text-align: center;\n }\n .gallery-container {\n max-width: 1200px;\n margin: 20px auto;\n padding: 15px;\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));\n gap: 15px;\n }\n .photo-item {\n background-color: #fff;\n border: 1px solid #ddd;\n border-radius: 5px;\n overflow: hidden;\n box-shadow: 0 2px 4px rgba(0,0,0,0.1);\n transition: transform 0.2s ease-in-out, box-shadow 0.2s ease-in-out;\n animation: fadeIn 0.5s ease-out;\n display: flex;\n flex-direction: column;\n padding-bottom: 10px; /* Add padding for link */\n }\n .photo-item:hover {\n transform: scale(1.03);\n box-shadow: 0 5px 15px rgba(0,0,0,0.2);\n }\n .photo-item a {\n text-decoration: none; /* Remove underline from image link */\n }\n .photo-item img {\n display: block;\n width: 100%;\n height: 180px;\n object-fit: cover;\n background-color: #eee;\n cursor: pointer; /* Indicate image is clickable */\n }\n .photo-item .filename-container { /* Wrap filename and download */\n padding: 10px;\n text-align: center;\n border-top: 1px solid #eee;\n background-color: #f9f9f9;\n flex-grow: 1;\n }\n .photo-item .filename {\n font-size: 0.85em;\n word-break: break-all;\n color: #555;\n margin-bottom: 5px; /* Space below filename */\n }\n .status {\n text-align: center;\n padding: 10px;\n color: #888;\n font-style: italic;\n }\n\n @keyframes fadeIn {\n from { opacity: 0; transform: translateY(10px); }\n to { opacity: 1; transform: translateY(0); }\n }\n .new-photo-highlight {\n animation: highlight 1.5s ease-out;\n }\n @keyframes highlight {\n 0% { background-color: #fff3cd; } /* Yellowish highlight */\n 100% { background-color: #fff; }\n }\n\n /* --- Lightbox Styles --- */\n .lightbox-modal {\n display: none; /* Hidden by default */\n position: fixed; /* Stay in place */\n z-index: 1000; /* Sit on top */\n left: 0;\n top: 0;\n width: 100%;\n height: 100%;\n overflow: auto; /* Enable scroll if needed */\n background-color: rgba(0, 0, 0, 0.85); /* Black w/ opacity */\n cursor: pointer; /* Indicate clicking background closes it */\n align-items: center; /* Center vertically */\n justify-content: center; /* Center horizontally */\n }\n\n .lightbox-modal.active {\n display: flex; /* Show the modal using flex alignment */\n }\n\n .lightbox-content {\n margin: auto;\n display: block;\n max-width: 90%;\n max-height: 85%;\n cursor: default; /* Default cursor over the image itself */\n box-shadow: 0 4px 8px rgba(0,0,0,0.2), 0 6px 20px rgba(0,0,0,0.19);\n animation: zoomIn 0.4s ease-out;\n }\n\n @keyframes zoomIn {\n from { transform: scale(0.8); opacity: 0; }\n to { transform: scale(1); opacity: 1; }\n }\n\n .lightbox-close {\n position: absolute;\n top: 20px;\n right: 35px;\n color: #f1f1f1;\n font-size: 40px;\n font-weight: bold;\n transition: 0.3s;\n text-decoration: none;\n cursor: pointer;\n }\n\n .lightbox-close:hover,\n .lightbox-close:focus {\n color: #bbb;\n }\n\n /* --- Download Link Style --- */\n .download-link {\n display: inline-block;\n margin-top: 8px;\n padding: 5px 10px;\n font-size: 0.8em;\n color: #fff;\n background-color: #555;\n border-radius: 4px;\n text-decoration: none;\n text-align: center;\n transition: background-color 0.2s ease;\n }\n\n .download-link:hover {\n background-color: #777;\n }\n\n </style>\n</head>\n<body>\n <header>\n <h1>Scene Desc Photo Feed</h1>\n <div id=\"connection-status\" class=\"status\">Connection Status : Good</div>\n </header>\n\n <div id=\"gallery\" class=\"gallery-container\">\n <!-- Initial photos rendered by Flask -->\n \n <div class=\"photo-item\" data-filename=\"OV5640_image_20250703_123916_bat-1_uidDA_23_09_B7_93_B7.jpg\">\n <a href=\"/photos/OV5640_image_20250703_123916_bat-1_uidDA_23_09_B7_93_B7.jpg\" class=\"lightbox-trigger\"> \n <img src=\"/photos/OV5640_image_20250703_123916_bat-1_uidDA_23_09_B7_93_B7.jpg\" alt=\"OV5640_image_20250703_123916_bat-1_uidDA_23_09_B7_93_B7.jpg\" loading=\"lazy\">\n </a>\n <div class=\"filename-container\"> \n <div class=\"filename\">OV5640_image_20250703_123916_bat-1_uidDA_23_09_B7_93_B7.jpg</div>\n <a href=\"/photos/OV5640_image_20250703_123916_bat-1_uidDA_23_09_B7_93_B7.jpg\" download=\"OV5640_image_20250703_123916_bat-1_uidDA_23_09_B7_93_B7.jpg\" class=\"download-link\"> \n Download\n </a>\n </div>\n </div>\n \n </div>\n\n <!-- The Lightbox Modal -->\n <div id=\"lightbox-modal\" class=\"lightbox-modal\">\n <span id=\"lightbox-close\" class=\"lightbox-close\" title=\"Close\">\u00d7</span>\n <img id=\"lightbox-image\" class=\"lightbox-content\" src=\"\" alt=\"Enlarged photo\">\n </div>\n\n\n <script>\n document.addEventListener('DOMContentLoaded', () => {\n const gallery = document.getElementById('gallery');\n const statusDiv = document.getElementById('connection-status');\n const noPhotosMsg = document.getElementById('no-photos-msg');\n\n // Lightbox elements\n const lightboxModal = document.getElementById('lightbox-modal');\n const lightboxImage = document.getElementById('lightbox-image');\n const lightboxClose = document.getElementById('lightbox-close');\n\n function addPhotoToGallery(photoData) {\n if (gallery.querySelector(`.photo-item[data-filename=\"${photoData.filename}\"]`)) {\n console.log(`Photo already exists: ${photoData.filename}`);\n return;\n }\n\n if (noPhotosMsg) {\n noPhotosMsg.remove();\n }\n\n const photoItem = document.createElement('div');\n photoItem.className = 'photo-item new-photo-highlight';\n photoItem.setAttribute('data-filename', photoData.filename);\n\n const imgLink = document.createElement('a');\n imgLink.href = photoData.url;\n imgLink.classList.add('lightbox-trigger');\n\n const img = document.createElement('img');\n img.src = photoData.url;\n img.alt = photoData.filename;\n img.loading = 'lazy';\n\n imgLink.appendChild(img);\n\n const filenameContainer = document.createElement('div');\n filenameContainer.className = 'filename-container';\n\n const filenameDiv = document.createElement('div');\n filenameDiv.className = 'filename';\n filenameDiv.textContent = photoData.filename;\n\n const downloadLink = document.createElement('a');\n downloadLink.href = photoData.url;\n downloadLink.setAttribute('download', photoData.filename);\n downloadLink.className = 'download-link';\n downloadLink.textContent = 'Download';\n\n filenameContainer.appendChild(filenameDiv);\n filenameContainer.appendChild(downloadLink);\n\n photoItem.appendChild(imgLink);\n photoItem.appendChild(filenameContainer);\n\n gallery.insertBefore(photoItem, gallery.firstChild);\n\n setTimeout(() => {\n photoItem.classList.remove('new-photo-highlight');\n }, 1500);\n }\n\n // --- Lightbox Event Handlers ---\n function openLightbox(imgSrc) {\n if(lightboxModal && lightboxImage) {\n lightboxImage.src = imgSrc;\n lightboxModal.classList.add('active');\n document.body.style.overflow = 'hidden';\n }\n }\n\n function closeLightbox() {\n if(lightboxModal) {\n lightboxModal.classList.remove('active');\n lightboxImage.src = \"\";\n document.body.style.overflow = '';\n }\n }\n\n gallery.addEventListener('click', function(event) {\n const trigger = event.target.closest('.lightbox-trigger');\n if (trigger) {\n event.preventDefault();\n openLightbox(trigger.href);\n }\n });\n\n if(lightboxClose) {\n lightboxClose.addEventListener('click', closeLightbox);\n }\n if(lightboxModal) {\n lightboxModal.addEventListener('click', function(event) {\n if (event.target === lightboxModal) {\n closeLightbox();\n }\n });\n }\n document.addEventListener('keydown', function(event) {\n if (event.key === 'Escape' && lightboxModal && lightboxModal.classList.contains('active')) {\n closeLightbox();\n }\n });\n // --- End Lightbox Event Handlers ---\n\n\n // --- SSE Connection ---\n function connectSSE() {\n console.log('Attempting to connect to SSE...');\n const eventSource = new EventSource('/photo_updates');\n\n eventSource.onopen = function() {\n console.log('SSE Connection established.');\n statusDiv.textContent = 'Connected';\n statusDiv.style.color = 'green';\n };\n\n eventSource.onerror = function(err) {\n console.error('SSE Error:', err);\n statusDiv.textContent = 'Disconnected (Retrying...)';\n statusDiv.style.color = 'red';\n eventSource.close();\n setTimeout(connectSSE, 5000);\n };\n\n eventSource.onmessage = function(event) {\n console.log('SSE Message:', event.data);\n try {\n const newPhotos = JSON.parse(event.data);\n if (Array.isArray(newPhotos)) {\n newPhotos.forEach(addPhotoToGallery);\n } else {\n console.warn(\"Received SSE data is not an array:\", newPhotos);\n }\n } catch (e) {\n console.error(\"Failed to parse SSE data as JSON:\", event.data, e);\n }\n };\n }\n\n // Initial connection\n connectSSE();\n });\n </script>\n</body>\n</html>",
"body_hashes": [
"sha256:01d0e531debcd212dc6b2dbe90428b7df3d48f23abd67e3f15f25ec1ebaca28a",
"sha1:712904fbb5d699e3d8a535922292769351ac6924",
"tlsh:6f42a61e5ab700261253703f2fdba3a827744143650add583f9c8785df91ea4a9f3f88"
],
"body_hash": "sha1:712904fbb5d699e3d8a535922292769351ac6924",
"html_title": "Real-time Photo Feed"
},
"supports_http2": false
},
"observed_at": "2025-07-12T09:12:58.139040409Z",
"perspective_id": "PERSPECTIVE_UNKNOWN",
"port": 8002,
"service_name": "HTTP",
"software": [
{
"product": "python",
"version": "3.12.3",
"source": "OSI_APPLICATION_LAYER"
},
{
"uniform_resource_identifier": "cpe:2.3:a:palletsprojects:werkzeug:3.1.3:*:*:*:*:*:*:*",
"part": "a",
"vendor": "PalletsProjects",
"product": "Werkzeug",
"version": "3.1.3",
"source": "OSI_APPLICATION_LAYER"
}
],
"source_ip": "206.168.34.76",
"transport_protocol": "TCP",
"truncated": false
}
],
"location": {
"continent": "Europe",
"country": "United Kingdom",
"country_code": "GB",
"city": "London",
"postal_code": "E1W",
"timezone": "Europe/London",
"province": "England",
"coordinates": {
"latitude": 51.50853,
"longitude": -0.12574
}
},
"location_updated_at": "2025-07-01T12:10:39.465966564Z",
"autonomous_system": {
"asn": 8075,
"description": "MICROSOFT-CORP-MSN-AS-BLOCK",
"bgp_prefix": "20.64.0.0/10",
"name": "MICROSOFT-CORP-MSN-AS-BLOCK",
"country_code": "US"
},
"autonomous_system_updated_at": "2025-07-01T12:10:39.466059404Z",
"whois": {
"network": {
"handle": "MSFT",
"name": "Microsoft Corporation",
"cidrs": [
"20.33.0.0/16",
"20.34.0.0/15",
"20.36.0.0/14",
"20.40.0.0/13",
"20.48.0.0/12",
"20.64.0.0/10",
"20.128.0.0/16"
],
"created": "2017-10-18T00:00:00Z",
"updated": "2021-12-14T00:00:00Z",
"allocation_type": "ALLOCATION"
},
"organization": {
"handle": "MSFT",
"name": "Microsoft Corporation",
"street": "One Microsoft Way",
"city": "Redmond",
"state": "WA",
"postal_code": "98052",
"country": "US",
"abuse_contacts": [
{
"handle": "MAC74-ARIN",
"name": "Microsoft Abuse Contact",
"email": "[email protected]"
}
],
"admin_contacts": [
{
"handle": "IPHOS5-ARIN",
"name": "IPHostmaster IPHostmaster",
"email": "[email protected]"
}
],
"tech_contacts": [
{
"handle": "BEDAR6-ARIN",
"name": "Dawn Bedard",
"email": "[email protected]"
},
{
"handle": "IPHOS5-ARIN",
"name": "IPHostmaster IPHostmaster",
"email": "[email protected]"
},
{
"handle": "MRPD-ARIN",
"name": "Microsoft Routing, Peering, and DNS",
"email": "[email protected]"
},
{
"handle": "SINGH683-ARIN",
"name": "Prachi Singh",
"email": "[email protected]"
}
]
}
},
"operating_system": {
"uniform_resource_identifier": "cpe:2.3:o:canonical:ubuntu_linux:*:*:*:*:*:*:*:*",
"part": "o",
"vendor": "Ubuntu",
"product": "Linux",
"other": {
"family": "Linux"
}
},
"dns": {
"names": [
"services.mavistech.cloud",
"anytest.mavistech.cloud",
"ocr.mavistech.cloud",
"assistant.mavistech.cloud",
"face.mavistech.cloud",
"gps.mavistech.cloud",
"identify.mavistech.cloud",
"scene.mavistech.cloud"
],
"records": {
"services.mavistech.cloud": {
"record_type": "A",
"resolved_at": "2025-07-09T06:58:57.968937087Z"
},
"face.mavistech.cloud": {
"record_type": "A",
"resolved_at": "2025-07-02T13:02:19.478464908Z"
},
"gps.mavistech.cloud": {
"record_type": "A",
"resolved_at": "2025-07-07T18:29:34.428442753Z"
},
"identify.mavistech.cloud": {
"record_type": "A",
"resolved_at": "2025-06-15T13:09:18.662962507Z"
},
"scene.mavistech.cloud": {
"record_type": "A",
"resolved_at": "2025-07-06T13:09:05.482442997Z"
},
"ocr.mavistech.cloud": {
"record_type": "A",
"resolved_at": "2025-06-19T13:11:41.981600800Z"
},
"anytest.mavistech.cloud": {
"record_type": "A",
"resolved_at": "2025-06-18T13:22:20.982578379Z"
},
"assistant.mavistech.cloud": {
"record_type": "A",
"resolved_at": "2025-07-08T13:03:48.737121767Z"
}
}
},
"last_updated_at": "2025-07-12T13:15:32.961Z",
"labels": [
"bootstrap",
"default-landing-page",
"remote-access"
]
}