Compare commits
3
Commits
3ba6410972
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c1ca4891fc | ||
|
|
7406d00687 | ||
|
|
ecad447d60 |
@@ -48,6 +48,29 @@ jobs:
|
|||||||
cd /docker/FlockPal-dev
|
cd /docker/FlockPal-dev
|
||||||
docker compose -f docker-compose.dev.yaml up -d --build
|
docker compose -f docker-compose.dev.yaml up -d --build
|
||||||
|
|
||||||
|
- name: Notify Discord
|
||||||
|
if: always()
|
||||||
|
env:
|
||||||
|
DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }}
|
||||||
|
JOB_STATUS: ${{ job.status }}
|
||||||
|
DEPLOY_ENVIRONMENT: dev
|
||||||
|
run: |
|
||||||
|
if [ -z "$DISCORD_WEBHOOK_URL" ]; then
|
||||||
|
echo "DISCORD_WEBHOOK_URL is not configured; skipping Discord notification."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$JOB_STATUS" = "success" ]; then
|
||||||
|
STATUS_LABEL="SUCCESS"
|
||||||
|
else
|
||||||
|
STATUS_LABEL="FAILED"
|
||||||
|
fi
|
||||||
|
|
||||||
|
curl --fail --show-error --silent \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "{\"content\":\"[${STATUS_LABEL}] FlockPal ${DEPLOY_ENVIRONMENT} deploy completed with status: ${JOB_STATUS}\\nBranch: ${{ github.ref_name }}\\nCommit: ${{ github.sha }}\\nRun: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}\"}" \
|
||||||
|
"$DISCORD_WEBHOOK_URL" || echo "Discord notification failed."
|
||||||
|
|
||||||
deploy-prod:
|
deploy-prod:
|
||||||
if: ${{ github.event_name == 'workflow_dispatch' || (github.event_name == 'push' && github.ref_name == 'main') }}
|
if: ${{ github.event_name == 'workflow_dispatch' || (github.event_name == 'push' && github.ref_name == 'main') }}
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -85,4 +108,33 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
set -e
|
set -e
|
||||||
cd /docker/FlockPal
|
cd /docker/FlockPal
|
||||||
docker compose -f docker-compose.prod.yml up -d --build
|
docker compose -f docker-compose.prod.yml up -d --build --wait --wait-timeout 180
|
||||||
|
|
||||||
|
- name: Verify production API readiness
|
||||||
|
run: |
|
||||||
|
set -e
|
||||||
|
cd /docker/FlockPal
|
||||||
|
docker compose -f docker-compose.prod.yml exec -T backend node dist/healthcheck.js api-ready
|
||||||
|
|
||||||
|
- name: Notify Discord
|
||||||
|
if: always()
|
||||||
|
env:
|
||||||
|
DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }}
|
||||||
|
JOB_STATUS: ${{ job.status }}
|
||||||
|
DEPLOY_ENVIRONMENT: production
|
||||||
|
run: |
|
||||||
|
if [ -z "$DISCORD_WEBHOOK_URL" ]; then
|
||||||
|
echo "DISCORD_WEBHOOK_URL is not configured; skipping Discord notification."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$JOB_STATUS" = "success" ]; then
|
||||||
|
STATUS_LABEL="SUCCESS"
|
||||||
|
else
|
||||||
|
STATUS_LABEL="FAILED"
|
||||||
|
fi
|
||||||
|
|
||||||
|
curl --fail --show-error --silent \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "{\"content\":\"[${STATUS_LABEL}] FlockPal ${DEPLOY_ENVIRONMENT} deploy completed with status: ${JOB_STATUS}\\nBranch: ${{ github.ref_name }}\\nCommit: ${{ github.sha }}\\nRun: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}\"}" \
|
||||||
|
"$DISCORD_WEBHOOK_URL" || echo "Discord notification failed."
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import nodemailer, { type SendMailOptions } from 'nodemailer';
|
|||||||
import Stripe from 'stripe';
|
import Stripe from 'stripe';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
import { db } from './db/client.js';
|
||||||
import { ensureSchema } from './db/schema.js';
|
import { ensureSchema } from './db/schema.js';
|
||||||
import { adoptionReportQueueEvents, enqueueAdoptionReportJob } from './queues/adoptionReportQueue.js';
|
import { adoptionReportQueueEvents, enqueueAdoptionReportJob } from './queues/adoptionReportQueue.js';
|
||||||
import { enqueueBirdMilestoneReminderJob, getBirdMilestoneReminderQueueCounts } from './queues/birdMilestoneReminderQueue.js';
|
import { enqueueBirdMilestoneReminderJob, getBirdMilestoneReminderQueueCounts } from './queues/birdMilestoneReminderQueue.js';
|
||||||
@@ -2514,6 +2515,24 @@ app.get('/api/health', (_req: Request, res: Response) => {
|
|||||||
res.json({ ok: true });
|
res.json({ ok: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
app.get('/api/health/live', (_req: Request, res: Response) => {
|
||||||
|
res.json({ ok: true, status: 'live' });
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get('/api/health/ready', async (_req: Request, res: Response) => {
|
||||||
|
try {
|
||||||
|
await Promise.all([
|
||||||
|
db.query('SELECT 1'),
|
||||||
|
getBirdMilestoneReminderQueueCounts(),
|
||||||
|
getMedicationReminderQueueCounts(),
|
||||||
|
]);
|
||||||
|
res.json({ ok: true, status: 'ready' });
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Readiness check failed', error);
|
||||||
|
res.status(503).json({ ok: false, status: 'not_ready' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
app.get('/api/metrics', requireAuth, requireAdmin, async (_req: Request, res: Response, next: NextFunction) => {
|
app.get('/api/metrics', requireAuth, requireAdmin, async (_req: Request, res: Response, next: NextFunction) => {
|
||||||
const memoryUsage = process.memoryUsage();
|
const memoryUsage = process.memoryUsage();
|
||||||
const averageDurationMs = requestMetrics.totalRequests > 0 ? requestMetrics.totalDurationMs / requestMetrics.totalRequests : 0;
|
const averageDurationMs = requestMetrics.totalRequests > 0 ? requestMetrics.totalDurationMs / requestMetrics.totalRequests : 0;
|
||||||
|
|||||||
@@ -95,6 +95,12 @@ services:
|
|||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
redis:
|
redis:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "node -e \"fetch('http://127.0.0.1:5000/api/health/ready').then(r => { if (!r.ok) process.exit(1) }).catch(() => process.exit(1))\""]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 3
|
||||||
|
start_period: 30s
|
||||||
ports:
|
ports:
|
||||||
- "5000:5000"
|
- "5000:5000"
|
||||||
command: >
|
command: >
|
||||||
@@ -103,6 +109,7 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- ./backend:/app
|
- ./backend:/app
|
||||||
- /app/node_modules
|
- /app/node_modules
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
worker:
|
worker:
|
||||||
build:
|
build:
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ body,
|
|||||||
#root {
|
#root {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
min-height: 100%;
|
min-height: 100%;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
@@ -48,6 +49,8 @@ body {
|
|||||||
color: var(--ink);
|
color: var(--ink);
|
||||||
position: relative;
|
position: relative;
|
||||||
isolation: isolate;
|
isolation: isolate;
|
||||||
|
overflow-x: hidden;
|
||||||
|
-webkit-text-size-adjust: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
body::before {
|
body::before {
|
||||||
@@ -121,6 +124,7 @@ textarea {
|
|||||||
.content-shell {
|
.content-shell {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 1.5rem;
|
gap: 1.5rem;
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.top-alert-notification {
|
.top-alert-notification {
|
||||||
@@ -193,11 +197,13 @@ textarea {
|
|||||||
display: grid;
|
display: grid;
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
align-self: start;
|
align-self: start;
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.side-nav {
|
.side-nav {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 1.25rem;
|
gap: 1.25rem;
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.brand-lockup {
|
.brand-lockup {
|
||||||
@@ -453,6 +459,7 @@ textarea {
|
|||||||
.stack-grid {
|
.stack-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 1.5rem;
|
gap: 1.5rem;
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hero-card,
|
.hero-card,
|
||||||
@@ -536,6 +543,7 @@ textarea {
|
|||||||
.forms-grid {
|
.forms-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 1.5rem;
|
gap: 1.5rem;
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hero-stats {
|
.hero-stats {
|
||||||
@@ -721,6 +729,7 @@ textarea {
|
|||||||
border-radius: 28px;
|
border-radius: 28px;
|
||||||
padding: 1.5rem;
|
padding: 1.5rem;
|
||||||
align-self: start;
|
align-self: start;
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.panel-header {
|
.panel-header {
|
||||||
@@ -729,6 +738,7 @@ textarea {
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pulse-panel .panel-header {
|
.pulse-panel .panel-header {
|
||||||
@@ -747,6 +757,7 @@ textarea {
|
|||||||
display: flex;
|
display: flex;
|
||||||
gap: 0.75rem;
|
gap: 0.75rem;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.member-header-actions {
|
.member-header-actions {
|
||||||
@@ -847,6 +858,7 @@ textarea {
|
|||||||
.bird-list {
|
.bird-list {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 0.9rem;
|
gap: 0.9rem;
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bird-card-header {
|
.bird-card-header {
|
||||||
@@ -1106,6 +1118,11 @@ textarea {
|
|||||||
var(--card-bg);
|
var(--card-bg);
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-card .weight-chart {
|
||||||
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.overview-chart-card::before {
|
.overview-chart-card::before {
|
||||||
@@ -1590,6 +1607,7 @@ textarea {
|
|||||||
.bird-detail-tab-panel {
|
.bird-detail-tab-panel {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.profile-copy {
|
.profile-copy {
|
||||||
@@ -1957,6 +1975,7 @@ label {
|
|||||||
|
|
||||||
.bulk-weight-table-shell {
|
.bulk-weight-table-shell {
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
border-radius: 22px;
|
border-radius: 22px;
|
||||||
border: 1px solid rgba(53, 129, 98, 0.18);
|
border: 1px solid rgba(53, 129, 98, 0.18);
|
||||||
background: rgba(255, 252, 246, 0.72);
|
background: rgba(255, 252, 246, 0.72);
|
||||||
@@ -2565,3 +2584,451 @@ label {
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: 720px) {
|
||||||
|
:root {
|
||||||
|
background:
|
||||||
|
radial-gradient(circle at 18% 8%, rgba(222, 124, 58, 0.18), transparent 24%),
|
||||||
|
radial-gradient(circle at 82% 10%, rgba(53, 136, 110, 0.18), transparent 24%),
|
||||||
|
linear-gradient(180deg, #fef5e7 0%, #ece3c8 48%, #dceee3 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
body::before {
|
||||||
|
opacity: 0.22;
|
||||||
|
background-size: auto 1200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input,
|
||||||
|
textarea,
|
||||||
|
select {
|
||||||
|
min-height: 46px;
|
||||||
|
border-radius: 14px;
|
||||||
|
padding: 0.78rem 0.85rem;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-shell,
|
||||||
|
.auth-shell {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-shell {
|
||||||
|
gap: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-shell,
|
||||||
|
.stack-grid,
|
||||||
|
.dashboard-grid,
|
||||||
|
.forms-grid,
|
||||||
|
.settings-grid,
|
||||||
|
.settings-column,
|
||||||
|
.flock-detail-column,
|
||||||
|
.flock-member-panel,
|
||||||
|
.flock-member-sections {
|
||||||
|
gap: 0.85rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-card,
|
||||||
|
.panel {
|
||||||
|
border-radius: 20px;
|
||||||
|
padding: 1rem;
|
||||||
|
box-shadow: 0 14px 26px rgba(89, 48, 42, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-card {
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-card h1 {
|
||||||
|
max-width: none;
|
||||||
|
font-size: 2.15rem;
|
||||||
|
line-height: 1.04;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-header {
|
||||||
|
display: grid;
|
||||||
|
gap: 0.85rem;
|
||||||
|
margin-bottom: 0.85rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-header .button-row,
|
||||||
|
.member-header-actions,
|
||||||
|
.overview-alert-actions {
|
||||||
|
justify-content: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.primary-button,
|
||||||
|
.secondary-button,
|
||||||
|
.danger-button,
|
||||||
|
.range-alert-button {
|
||||||
|
min-height: 44px;
|
||||||
|
border-radius: 14px;
|
||||||
|
padding: 0.72rem 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-row > button,
|
||||||
|
.button-row > a,
|
||||||
|
.top-alert-actions > button {
|
||||||
|
flex: 1 1 9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.side-rail {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 25;
|
||||||
|
margin: -0.75rem -0.75rem 0;
|
||||||
|
padding: 0.6rem 0.75rem 0.7rem;
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
gap: 0.55rem;
|
||||||
|
background: linear-gradient(180deg, rgba(254, 245, 231, 0.98), rgba(254, 245, 231, 0.88));
|
||||||
|
border-bottom: 1px solid rgba(53, 129, 98, 0.16);
|
||||||
|
backdrop-filter: blur(14px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand-lockup {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.side-nav.panel {
|
||||||
|
grid-template-columns: minmax(0, 1fr) auto;
|
||||||
|
padding: 0;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 0;
|
||||||
|
background: transparent;
|
||||||
|
box-shadow: none;
|
||||||
|
backdrop-filter: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-tabs {
|
||||||
|
margin: 0 -0.2rem;
|
||||||
|
padding: 0.1rem 0.2rem 0.25rem;
|
||||||
|
grid-auto-columns: minmax(5.4rem, 1fr);
|
||||||
|
gap: 0.35rem;
|
||||||
|
scroll-snap-type: x proximity;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-tab {
|
||||||
|
min-height: 40px;
|
||||||
|
min-width: 5.4rem;
|
||||||
|
padding: 0.5rem 0.55rem;
|
||||||
|
border-radius: 999px;
|
||||||
|
font-size: 0.86rem;
|
||||||
|
scroll-snap-align: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.side-nav .secondary-button {
|
||||||
|
min-height: 40px;
|
||||||
|
padding: 0.5rem 0.7rem;
|
||||||
|
border-radius: 999px;
|
||||||
|
font-size: 0.86rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.workspace-switcher {
|
||||||
|
gap: 0.4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.workspace-switcher-list {
|
||||||
|
margin: 0 -0.2rem;
|
||||||
|
padding: 0 0.2rem 0.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.workspace-switcher-item {
|
||||||
|
min-width: min(180px, 78vw);
|
||||||
|
padding: 0.5rem 0.65rem;
|
||||||
|
border-radius: 999px;
|
||||||
|
font-size: 0.86rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-alert-notification {
|
||||||
|
gap: 0.65rem;
|
||||||
|
padding: 0.75rem;
|
||||||
|
border-radius: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-bell {
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-card {
|
||||||
|
padding: 0.75rem;
|
||||||
|
overflow-x: auto;
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-card .weight-chart {
|
||||||
|
min-width: 520px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.overview-chart-card {
|
||||||
|
min-height: 230px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-footer {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-grid,
|
||||||
|
.detail-grid,
|
||||||
|
.summary-grid {
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(min(150px, 100%), 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
|
.bird-list-panel {
|
||||||
|
order: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flock-detail-column {
|
||||||
|
order: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bird-list {
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(min(240px, 100%), 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
|
.bird-card {
|
||||||
|
border-radius: 18px;
|
||||||
|
padding: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bird-avatar {
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
border-radius: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bird-detail-panel {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bird-detail-tabs {
|
||||||
|
position: sticky;
|
||||||
|
top: 4.35rem;
|
||||||
|
z-index: 15;
|
||||||
|
margin: -0.2rem -0.35rem 0.65rem;
|
||||||
|
padding: 0.2rem 0.35rem 0.35rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
overflow-x: auto;
|
||||||
|
gap: 0.35rem;
|
||||||
|
background: linear-gradient(180deg, rgba(250, 244, 232, 0.96), rgba(250, 244, 232, 0.84));
|
||||||
|
border-radius: 16px;
|
||||||
|
backdrop-filter: blur(12px);
|
||||||
|
transform: none;
|
||||||
|
scrollbar-width: thin;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bird-detail-tab {
|
||||||
|
width: 42px;
|
||||||
|
height: 40px;
|
||||||
|
min-width: 42px;
|
||||||
|
border-left: 1px solid rgba(39, 105, 179, 0.14);
|
||||||
|
border-radius: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bird-detail-tab.active {
|
||||||
|
box-shadow: 0 8px 16px rgba(39, 105, 179, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-hero {
|
||||||
|
grid-template-columns: auto minmax(0, 1fr);
|
||||||
|
align-items: start;
|
||||||
|
padding: 0.85rem;
|
||||||
|
gap: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-actions {
|
||||||
|
position: static;
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
justify-content: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-photo {
|
||||||
|
width: 86px;
|
||||||
|
height: 86px;
|
||||||
|
border-radius: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-copy h3 {
|
||||||
|
font-size: 1.35rem;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inline-form,
|
||||||
|
.settings-nested-grid,
|
||||||
|
.education-admin-basics,
|
||||||
|
.quiz-editor-options,
|
||||||
|
.dose-schedule-row,
|
||||||
|
.verified-location-search-row,
|
||||||
|
.verified-location-search-row.has-selected-location,
|
||||||
|
.medication-dose-row {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.verified-location-search-row button,
|
||||||
|
.verified-location-search-row.has-selected-location button {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-field {
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-row {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.75rem;
|
||||||
|
border-radius: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.segmented-control {
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(min(8rem, 100%), 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
|
.segmented-option {
|
||||||
|
border-radius: 14px;
|
||||||
|
padding: 0.72rem 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.photo-editor {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
justify-items: start;
|
||||||
|
padding: 0.85rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.color-preview-card {
|
||||||
|
align-items: start;
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bulk-weight-table {
|
||||||
|
min-width: 560px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bulk-weight-table th,
|
||||||
|
.bulk-weight-table td {
|
||||||
|
padding: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-modal-backdrop {
|
||||||
|
padding: 0.75rem;
|
||||||
|
align-items: end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-modal {
|
||||||
|
max-height: min(88vh, 760px);
|
||||||
|
border-radius: 22px;
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 430px) {
|
||||||
|
.app-shell,
|
||||||
|
.auth-shell {
|
||||||
|
padding: 0.55rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.side-rail {
|
||||||
|
margin: -0.55rem -0.55rem 0;
|
||||||
|
padding: 0.5rem 0.55rem 0.6rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-shell,
|
||||||
|
.stack-grid,
|
||||||
|
.dashboard-grid,
|
||||||
|
.forms-grid,
|
||||||
|
.settings-grid,
|
||||||
|
.settings-column,
|
||||||
|
.flock-detail-column,
|
||||||
|
.flock-member-panel,
|
||||||
|
.flock-member-sections {
|
||||||
|
gap: 0.7rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-card,
|
||||||
|
.panel {
|
||||||
|
border-radius: 18px;
|
||||||
|
padding: 0.85rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-card h1 {
|
||||||
|
font-size: 1.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-tabs {
|
||||||
|
grid-auto-columns: minmax(4.8rem, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-tab {
|
||||||
|
min-width: 4.8rem;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.side-nav .secondary-button {
|
||||||
|
padding-inline: 0.58rem;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-alert-notification {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-bell {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-row > button,
|
||||||
|
.button-row > a,
|
||||||
|
.top-alert-actions > button,
|
||||||
|
.settings-save-row > button {
|
||||||
|
flex: 1 1 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bird-card-header {
|
||||||
|
gap: 0.65rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bird-card-title {
|
||||||
|
align-items: flex-start;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-hero {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-photo {
|
||||||
|
width: 96px;
|
||||||
|
height: 96px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-actions {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-icon-button {
|
||||||
|
flex: 1 1 42px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-card {
|
||||||
|
margin-inline: -0.1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-nested-card,
|
||||||
|
.inset-panel,
|
||||||
|
.legend-card,
|
||||||
|
.detail-card,
|
||||||
|
.summary-card,
|
||||||
|
.weight-reference-card,
|
||||||
|
.vet-visit-card,
|
||||||
|
.toggle-card {
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-modal-backdrop {
|
||||||
|
padding: 0.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user