Files
citypulse/dashboard/styles.css
Zahar ec3c7320d7 feat(chatbot): integrate OpenRouter API for AI assistance in dashboard
- Added a Chatbot component to the dashboard for user interaction and support.
- Created a README for the Chatbot detailing setup, features, and usage instructions.
- Introduced environment variables for secure API key management.
- Updated app.js to include the Chatbot component.
- Implemented a configuration server to serve API keys securely.
- Enhanced styles for the Chatbot interface to improve user experience.
2025-09-27 15:00:30 +08:00

715 lines
14 KiB
CSS

:root{
--bg:#f8fafc;
--panel:#ffffff;
--muted:#6b7280;
--accent:#0ea5a4;
--severity-high:#D32F2F;
--severity-medium:#F57C00;
--severity-low:#388E3C;
--status-submitted:#1976D2;
--status-in_progress:#7B1FA2;
--status-fixed:#455A64;
--shadow: 0 6px 18px rgba(15,23,42,0.08);
--surface-contrast: #111827;
}
*{box-sizing:border-box}
html,body,#root{height:100%}
body{
margin:0;
font-family: Inter, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial;
background:var(--bg);
color:var(--surface-contrast);
-webkit-font-smoothing:antialiased;
-moz-osx-font-smoothing:grayscale;
font-size:14px;
}
.header{
height:56px;
display:flex;
align-items:center;
justify-content:space-between;
padding:0 20px;
background:var(--panel);
border-bottom:1px solid #e5e7eb;
box-shadow:0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
z-index:100;
}
.brand{
font-weight:700;
font-size:20px;
color:#111827;
background:linear-gradient(135deg, var(--accent), #0d9488);
-webkit-background-clip:text;
-webkit-text-fill-color:transparent;
background-clip:text;
}
.lang-toggle{
display:flex;
align-items:center;
gap:8px;
}
.lang-toggle select{
padding:8px 12px;
border:1px solid #d1d5db;
border-radius:6px;
background:white;
font-size:14px;
color:#374151;
cursor:pointer;
transition:border-color 0.2s ease;
}
.lang-toggle select:focus{
outline:none;
border-color:var(--accent);
box-shadow:0 0 0 3px rgba(14,165,164,0.1);
}
.app-root{height:100vh;display:flex;flex-direction:column}
.container{
height:calc(100vh - 56px);
display:flex;
flex-direction:column;
gap:16px;
padding:16px;
max-width:1400px;
margin:0 auto;
width:100%;
}
/* main area */
.main{
display:grid;
grid-template-columns:300px 1fr 400px;
gap:12px;
align-items:stretch;
flex:1;
}
/* panels */
.panel{
background:var(--panel);
border-radius:12px;
box-shadow:0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
padding:20px;
display:flex;
flex-direction:column;
min-height:0;
border:1px solid rgba(0,0,0,0.05);
}
.filters h3{
margin:0 0 20px 0;
font-size:18px;
font-weight:600;
color:#111827;
border-bottom:2px solid #f3f4f6;
padding-bottom:12px;
}
.filter-group{
margin-bottom:24px;
padding:16px;
background:#fafbfc;
border-radius:8px;
border:1px solid #e5e7eb;
overflow:hidden;
}
.filter-group:last-child{margin-bottom:0;}
.checkbox-row{
display:flex;
flex-direction:column;
gap:12px;
max-height:240px;
overflow-y:auto;
padding-right:8px;
}
.checkbox-row label{
font-size:14px;
color:#374151;
font-weight:500;
display:flex;
align-items:center;
gap:10px;
padding:8px;
border-radius:6px;
transition:background-color 0.2s ease;
cursor:pointer;
}
.checkbox-row label:hover{
background-color:#f3f4f6;
}
/* chips/buttons */
.btn{
background:var(--accent);
color:white;
border:none;
padding:10px 20px;
border-radius:8px;
cursor:pointer;
font-weight:600;
font-size:14px;
transition:all 0.2s ease;
box-shadow:0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
}
.btn:hover{
background:#0d9488;
transform:translateY(-1px);
box-shadow:0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
}
.btn.secondary{
background:#f8fafc;
color:#475569;
border:1px solid #e2e8f0;
}
.btn.secondary:hover{
background:#f1f5f9;
border-color:#cbd5e1;
}
.btn.ghost{
background:transparent;
border:1px solid #e2e8f0;
color:#475569;
padding:8px 16px;
}
.btn.ghost:hover{
background:#f8fafc;
border-color:#cbd5e1;
}
.btn:focus{
outline:2px solid rgba(14,165,164,0.25);
outline-offset:2px;
}
.multi-select{
display:flex;
gap:10px;
flex-wrap:wrap;
margin-top:12px;
}
.chip{
display:inline-flex;
align-items:center;
padding:6px 12px;
border-radius:20px;
font-size:13px;
font-weight:500;
color:white;
transition:all 0.2s ease;
cursor:pointer;
border:none;
box-shadow:0 1px 3px 0 rgba(0, 0, 0, 0.1);
}
.chip:hover{
transform:translateY(-1px);
box-shadow:0 2px 4px 0 rgba(0, 0, 0, 0.15);
}
.chip.severity-high{background:var(--severity-high)}
.chip.severity-medium{background:var(--severity-medium)}
.chip.severity-low{background:var(--severity-low)}
.chip.status-submitted{background:var(--status-submitted)}
.chip.status-in_progress{background:var(--status-in_progress)}
.chip.status-fixed{background:var(--status-fixed)}
/* severity buttons in filter */
button.chip{
opacity:0.9;
transition:opacity 0.2s ease, filter 0.2s ease;
}
button.chip[aria-pressed="false"]{
opacity:0.4;
filter:grayscale(0.3) saturate(0.5);
}
button.chip[aria-pressed="true"]{
opacity:1;
filter:none;
}
/* map panel */
.map-panel{position:relative;min-height:0;height:100%;padding:0;overflow:hidden}
#map{width:100%;height:100%}
.map-panel .map-empty{
display:none;
position:absolute;
left:0;right:0;top:0;bottom:0;
align-items:center;justify-content:center;
font-size:18px;color:#374151;background:rgba(255,255,255,0.85);
z-index:800;
}
.map-panel.no-reports .map-empty{display:flex}
/* queue list */
.queue-list{display:flex;flex-direction:column;gap:8px;overflow:auto;padding-right:6px;max-height:calc(100vh - 200px)}
.queue-item{display:flex;align-items:center;gap:12px;padding:8px;border-radius:8px;border:1px solid #eef2f7;background:linear-gradient(180deg,#fff,#fbfdff);min-height:48px}
.thumb{width:56px;height:56px;border-radius:6px;background:linear-gradient(180deg,#eef2ff,#fff);display:flex;align-items:center;justify-content:center;color:#0f172a;font-weight:700;flex-shrink:0}
.item-main{flex:1;min-width:0;display:flex;flex-direction:column;gap:4px}
.item-title{font-weight:600;text-overflow:ellipsis;overflow:hidden;white-space:nowrap;font-size:14px}
.item-title.clickable{
cursor:pointer;
transition:all 0.2s ease;
border-radius:4px;
padding:2px 4px;
margin:-2px -4px;
}
.item-title.clickable:hover{
background-color:rgba(14,165,164,0.1);
color:var(--accent);
transform:translateY(-1px);
box-shadow:0 2px 8px rgba(14,165,164,0.15);
}
.item-meta{display:flex;gap:8px;align-items:center;margin-top:2px;font-size:12px;color:var(--muted);flex-wrap:wrap}
.item-actions{display:flex;align-items:center;gap:8px;flex-shrink:0}
/* Ensure proper spacing in queue items */
.queue-item > * {
flex-shrink: 0;
}
.queue-item .item-main {
flex-shrink: 1;
min-width: 0;
}
/* drawer */
.drawer{
position:fixed;
top:56px;
right:0;
bottom:0;
width:380px;
transform:translateX(100%);
transition:transform .28s ease;
z-index:1200;
display:flex;
align-items:flex-start;
pointer-events:none;
}
.drawer.open{transform:translateX(0);pointer-events:auto}
.drawer-content{
width:100%;
height:100%;
background:var(--panel);
box-shadow:-12px 0 30px rgba(2,6,23,0.12);
padding:16px;
overflow:auto;
}
.drawer-close{position:absolute;right:12px;top:8px;background:transparent;border:none;font-size:20px;cursor:pointer}
.drawer-header{display:flex;align-items:center}
.drawer-thumb.large{width:84px;height:84px;border-radius:8px;background:#f3f4f6;display:flex;align-items:center;justify-content:center;font-weight:700}
.drawer-body{margin-top:12px;color:#111827}
.drawer-actions{display:flex;gap:8px;margin-top:16px}
/* marker custom */
.leaflet-container .custom-marker{display:flex;align-items:center;justify-content:center}
/* date range styling */
.filter-group .row {
display:flex;
align-items:center;
justify-content:space-between;
margin-bottom:12px;
}
.filter-group .row strong {
font-size:14px;
font-weight:600;
color:#111827;
}
.filter-group .date-inputs {
display:flex;
gap:8px;
margin-top:8px;
align-items:flex-end;
}
.filter-group .date-input-group {
flex:1;
min-width:0;
display:flex;
flex-direction:column;
}
.filter-group .date-input-group label {
font-size:12px;
font-weight:500;
color:#6b7280;
margin-bottom:4px;
white-space:nowrap;
}
.filter-group .date-input-group input[type="date"] {
padding:6px 8px;
border:1px solid #d1d5db;
border-radius:6px;
font-size:14px;
background:white;
transition:border-color 0.2s ease, box-shadow 0.2s ease;
width:100%;
box-sizing:border-box;
}
.filter-group .date-input-group input[type="date"]:focus {
outline:none;
border-color:var(--accent);
box-shadow:0 0 0 3px rgba(14,165,164,0.1);
}
/* button group styling */
.filter-group .button-group {
display:flex;
gap:8px;
margin-top:16px;
justify-content:flex-end;
}
.filter-group .button-group .btn {
padding:8px 16px;
font-size:13px;
min-width:60px;
}
/* small screens */
@media (max-width:900px){
.main{grid-template-columns:1fr;grid-auto-rows:auto}
.drawer{top:56px;width:100%}
.drawer.open{transform:none}
.header{padding:8px 12px}
.filters{order:2}
.map-panel{order:1}
.panel{padding:16px}
.filter-group .date-inputs {
flex-direction:column;
gap:12px;
}
.filter-group .date-input-group {
min-width:auto;
}
.filter-group .button-group {
flex-direction:column;
gap:8px;
}
.queue-item .item-actions {
flex-direction:column;
align-items:stretch;
gap:6px;
min-width:auto;
}
.queue-item .item-actions select,
.queue-item .item-actions button {
width:100%;
text-align:center;
}
}
/* medium screens - adjust for better button layout */
@media (max-width:1200px) and (min-width:901px){
.main{grid-template-columns:280px 1fr 360px}
}
/* footer styling */
.footer{
background:var(--panel);
border-radius:12px;
padding:16px 20px;
display:flex;
justify-content:space-between;
align-items:center;
border:1px solid rgba(0,0,0,0.05);
box-shadow:0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
}
.stats{
display:flex;
align-items:center;
gap:16px;
font-size:14px;
}
.stats > div{
display:flex;
align-items:center;
gap:6px;
color:#374151;
font-weight:500;
}
/* accessibility tweaks */
.chip, .btn{font-family:inherit}
/* Chatbot styles */
.chatbot-toggle {
position: fixed;
bottom: 20px;
right: 20px;
background: var(--accent);
color: white;
border: none;
border-radius: 50px;
padding: 12px 20px;
display: flex;
align-items: center;
gap: 8px;
cursor: pointer;
box-shadow: 0 4px 12px rgba(14, 165, 164, 0.3);
transition: all 0.3s ease;
z-index: 1000;
font-size: 14px;
font-weight: 600;
}
.chatbot-toggle:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(14, 165, 164, 0.4);
background: #0d9488;
}
.chatbot-toggle-icon {
font-size: 18px;
}
.chatbot-container {
position: fixed;
bottom: 20px;
right: 20px;
width: 350px;
height: 500px;
background: var(--panel);
border-radius: 12px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12);
display: flex;
flex-direction: column;
z-index: 1000;
overflow: hidden;
border: 1px solid rgba(0, 0, 0, 0.05);
}
.chatbot-header {
background: linear-gradient(135deg, var(--accent), #0d9488);
color: white;
padding: 16px 20px;
display: flex;
justify-content: space-between;
align-items: center;
}
.chatbot-header h3 {
margin: 0;
font-size: 16px;
font-weight: 600;
}
.chatbot-close {
background: none;
border: none;
color: white;
font-size: 20px;
cursor: pointer;
padding: 4px;
border-radius: 4px;
transition: background-color 0.2s ease;
}
.chatbot-close:hover {
background-color: rgba(255, 255, 255, 0.2);
}
.chatbot-messages {
flex: 1;
overflow-y: auto;
padding: 16px;
display: flex;
flex-direction: column;
gap: 12px;
}
.message {
display: flex;
gap: 8px;
max-width: 85%;
}
.message.bot {
align-self: flex-start;
}
.message.user {
align-self: flex-end;
flex-direction: row-reverse;
}
.message-avatar {
width: 32px;
height: 32px;
border-radius: 50%;
background: var(--accent);
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
flex-shrink: 0;
}
.message.user .message-avatar {
background: var(--severity-high);
}
.message-content {
flex: 1;
}
.message-text {
background: #f8fafc;
padding: 10px 12px;
border-radius: 12px;
font-size: 14px;
line-height: 1.5;
word-wrap: break-word;
white-space: pre-wrap;
font-family: inherit;
}
.message.user .message-text {
background: var(--accent);
color: white;
}
.message-time {
font-size: 11px;
color: #6b7280;
margin-top: 4px;
text-align: right;
}
.message.user .message-time {
text-align: left;
}
.typing-indicator {
display: flex;
gap: 4px;
align-items: center;
padding: 10px 12px;
}
.typing-indicator span {
width: 6px;
height: 6px;
border-radius: 50%;
background: #6b7280;
animation: typing 1.4s infinite ease-in-out;
}
.typing-indicator span:nth-child(2) {
animation-delay: 0.2s;
}
.typing-indicator span:nth-child(3) {
animation-delay: 0.4s;
}
@keyframes typing {
0%, 60%, 100% {
transform: translateY(0);
opacity: 0.4;
}
30% {
transform: translateY(-10px);
opacity: 1;
}
}
.chatbot-quick-actions {
padding: 12px 16px;
border-top: 1px solid #e5e7eb;
display: flex;
gap: 6px;
flex-wrap: wrap;
}
.quick-action-btn {
background: #f1f5f9;
border: 1px solid #e2e8f0;
border-radius: 16px;
padding: 6px 12px;
font-size: 11px;
color: #475569;
cursor: pointer;
transition: all 0.2s ease;
}
.quick-action-btn:hover {
background: var(--accent);
color: white;
border-color: var(--accent);
}
.chatbot-input-form {
padding: 16px;
border-top: 1px solid #e5e7eb;
display: flex;
gap: 8px;
align-items: center;
}
.chatbot-input {
flex: 1;
padding: 10px 12px;
border: 1px solid #d1d5db;
border-radius: 20px;
font-size: 14px;
outline: none;
transition: border-color 0.2s ease;
}
.chatbot-input:focus {
border-color: var(--accent);
box-shadow: 0 0 0 3px rgba(14, 165, 164, 0.1);
}
.chatbot-input:disabled {
background: #f8fafc;
cursor: not-allowed;
}
.chatbot-send-btn {
background: var(--accent);
color: white;
border: none;
border-radius: 20px;
padding: 10px 16px;
font-size: 14px;
font-weight: 600;
cursor: pointer;
transition: background-color 0.2s ease;
min-width: 60px;
}
.chatbot-send-btn:hover:not(:disabled) {
background: #0d9488;
}
.chatbot-send-btn:disabled {
background: #9ca3af;
cursor: not-allowed;
}
/* Mobile responsiveness for chatbot */
@media (max-width: 768px) {
.chatbot-container {
width: calc(100vw - 40px);
height: 400px;
bottom: 10px;
right: 10px;
left: 10px;
}
.chatbot-toggle {
bottom: 10px;
right: 10px;
padding: 10px 16px;
font-size: 13px;
}
}