lost-found-desk / frontend /src /styles /components.css
JacobLinCool's picture
Deploy Lost & Found Desk
f1164d7 verified
Raw
History Blame Contribute Delete
13.5 kB
/* ---------------------------------------------------------------------------
Shared component primitives. Page-level layout lives in each page's scoped
styles; anything used by more than one surface lives here.
--------------------------------------------------------------------------- */
/* ---- Shared keyframes (referenced by scoped styles too) ---- */
@keyframes fade-up {
from { opacity: 0; transform: translateY(6px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes fade-in {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes spin {
to { transform: rotate(360deg); }
}
@keyframes shimmer {
from { background-position: 200% 0; }
to { background-position: -200% 0; }
}
@keyframes pop {
0% { transform: scale(0.6); opacity: 0; }
100% { transform: scale(1); opacity: 1; }
}
/* ---------------------------------------------------------------------------
Buttons. Primary = ink, secondary = outlined surface, ghost = quiet,
danger = destructive only. Hover lifts, press settles — both subtle.
--------------------------------------------------------------------------- */
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.45rem;
border: 1px solid transparent;
border-radius: var(--r-md);
padding: 0.6rem 1.05rem;
font-weight: 600;
font-size: var(--text-base);
line-height: 1.25;
cursor: pointer;
text-decoration: none;
background: transparent;
color: var(--ink);
transition:
background-color var(--dur-1) var(--ease-out),
border-color var(--dur-1) var(--ease-out),
color var(--dur-1) var(--ease-out),
box-shadow var(--dur-1) var(--ease-out),
transform var(--dur-1) var(--ease-out);
}
.btn:active:not(:disabled) {
transform: translateY(1px);
}
.btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.btn-primary {
background: var(--btn-primary-bg);
color: var(--btn-primary-ink);
box-shadow: var(--shadow-1);
}
.btn-primary:hover:not(:disabled) {
background: var(--btn-primary-bg-hover);
box-shadow: var(--shadow-2);
}
.btn-secondary {
background: var(--surface);
border-color: var(--line-strong);
color: var(--ink);
box-shadow: var(--shadow-1);
}
.btn-secondary:hover:not(:disabled) {
border-color: var(--ink-3);
background: var(--surface-2);
}
.btn-ghost {
color: var(--ink-2);
}
.btn-ghost:hover:not(:disabled) {
background: var(--surface-3);
color: var(--ink);
}
.btn-danger {
background: var(--btn-danger-bg);
color: var(--btn-danger-ink);
}
.btn-danger:hover:not(:disabled) {
background: var(--btn-danger-bg-hover);
}
.btn-link {
padding: 0.25rem 0.4rem;
border-radius: var(--r-sm);
color: var(--accent);
font-weight: 600;
font-size: var(--text-sm);
}
.btn-link:hover:not(:disabled) {
background: var(--accent-soft);
color: var(--accent-on-soft);
}
.btn-sm {
padding: 0.38rem 0.72rem;
font-size: var(--text-sm);
border-radius: var(--r-sm);
}
.btn-block {
width: 100%;
}
/* Icon-only buttons stay square and ≥ 36px so they remain comfortable taps. */
.btn-icon {
padding: 0.5rem;
min-width: 2.25rem;
min-height: 2.25rem;
}
/* ---------------------------------------------------------------------------
Cards & panels
--------------------------------------------------------------------------- */
.card {
background: var(--surface);
border: 1px solid var(--line);
border-radius: var(--r-lg);
box-shadow: var(--shadow-1);
padding: var(--s-5);
}
.card-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--s-3);
margin-bottom: var(--s-4);
}
.card-head h2,
.card-head h3 {
display: flex;
align-items: center;
gap: var(--s-2);
}
.well {
background: var(--surface-2);
border: 1px solid var(--line);
border-radius: var(--r-md);
padding: var(--s-4);
}
/* ---------------------------------------------------------------------------
Status badges — a tinted chip with a dot, so state never relies on color
alone. Tones: neutral, ok, warn, danger, info.
--------------------------------------------------------------------------- */
.badge {
display: inline-flex;
align-items: center;
gap: 0.34rem;
padding: 0.18rem 0.58rem;
border-radius: var(--r-pill);
font-size: var(--text-xs);
font-weight: 600;
letter-spacing: 0.01em;
background: var(--surface-2);
color: var(--ink-2);
border: 1px solid var(--line);
white-space: nowrap;
}
.badge .dot {
width: 6px;
height: 6px;
border-radius: 50%;
background: currentColor;
flex: none;
}
.badge-ok {
background: var(--ok-soft);
color: var(--ok-on-soft);
border-color: color-mix(in srgb, var(--ok) 30%, transparent);
}
.badge-warn {
background: var(--warn-soft);
color: var(--warn-on-soft);
border-color: color-mix(in srgb, var(--warn) 30%, transparent);
}
.badge-danger {
background: var(--danger-soft);
color: var(--danger-on-soft);
border-color: color-mix(in srgb, var(--danger) 30%, transparent);
}
.badge-info {
background: var(--accent-soft);
color: var(--accent-on-soft);
border-color: color-mix(in srgb, var(--accent) 30%, transparent);
}
/* ---------------------------------------------------------------------------
Notes & inline messages
--------------------------------------------------------------------------- */
.note {
display: flex;
gap: var(--s-3);
align-items: flex-start;
padding: var(--s-3) var(--s-4);
border-radius: var(--r-md);
border: 1px solid var(--line);
background: var(--surface-2);
color: var(--ink-2);
font-size: var(--text-sm);
line-height: 1.5;
}
.note > svg {
flex: none;
margin-top: 0.1rem;
}
.note-warn {
background: var(--warn-soft);
border-color: color-mix(in srgb, var(--warn) 28%, transparent);
color: var(--warn-on-soft);
}
.note-danger {
background: var(--danger-soft);
border-color: color-mix(in srgb, var(--danger) 28%, transparent);
color: var(--danger-on-soft);
}
.note-ok {
background: var(--ok-soft);
border-color: color-mix(in srgb, var(--ok) 28%, transparent);
color: var(--ok-on-soft);
}
.note-info {
background: var(--accent-soft);
border-color: color-mix(in srgb, var(--accent) 28%, transparent);
color: var(--accent-on-soft);
}
/* Inline, per-field validation message. */
.field-error {
display: flex;
align-items: center;
gap: 0.35rem;
margin-top: var(--s-1);
color: var(--danger);
font-size: var(--text-sm);
animation: fade-up var(--dur-2) var(--ease-out);
}
/* ---------------------------------------------------------------------------
Chat bubbles (public claim + staff transcript)
--------------------------------------------------------------------------- */
.bubble {
max-width: 84%;
padding: 0.62rem 0.85rem;
border-radius: var(--r-lg);
line-height: 1.5;
word-break: break-word;
animation: fade-up var(--dur-3) var(--ease-out);
font-size: var(--text-base);
}
.bubble .who {
display: block;
font-size: var(--text-xs);
font-weight: 700;
margin-bottom: 0.12rem;
}
.bubble.assistant,
.bubble.system {
align-self: flex-start;
background: var(--surface-2);
border: 1px solid var(--line);
border-bottom-left-radius: var(--r-sm);
color: var(--ink);
}
.bubble.assistant .who {
color: var(--ink-3);
}
.bubble.user {
align-self: flex-end;
background: var(--accent-soft);
border: 1px solid color-mix(in srgb, var(--accent) 22%, transparent);
border-bottom-right-radius: var(--r-sm);
color: var(--ink);
}
.bubble.user .who {
color: var(--accent-on-soft);
}
.bubble.staff {
align-self: flex-start;
background: var(--warn-soft);
border: 1px solid color-mix(in srgb, var(--warn) 30%, transparent);
border-bottom-left-radius: var(--r-sm);
color: var(--ink);
}
.bubble.staff .who {
color: var(--warn-on-soft);
}
.bubble.photo-bubble {
padding: 0.3rem;
}
.bubble.photo-bubble img {
display: block;
max-width: 200px;
border-radius: calc(var(--r-lg) - 4px);
}
.typing-dots {
display: inline-flex;
gap: 4px;
align-items: center;
padding: 0.2rem 0;
}
.typing-dots i {
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--ink-3);
animation: typing-bounce 1.2s infinite var(--ease-out);
}
.typing-dots i:nth-child(2) { animation-delay: 0.15s; }
.typing-dots i:nth-child(3) { animation-delay: 0.3s; }
@keyframes typing-bounce {
0%, 60%, 100% { transform: translateY(0); opacity: 0.4; }
30% { transform: translateY(-4px); opacity: 1; }
}
/* ---------------------------------------------------------------------------
Lists & micro-layout utilities
--------------------------------------------------------------------------- */
.grid {
display: grid;
gap: var(--s-4);
}
.grid-2 {
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
}
.row {
display: flex;
align-items: center;
gap: var(--s-2);
min-width: 0;
}
/* A truncating child inside a .row must be allowed to shrink below its
content width, or it inflates the whole layout (flex min-width:auto). */
.row > .trunc {
flex: 1 1 auto;
min-width: 0;
}
.wrap {
flex-wrap: wrap;
}
.actions {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: var(--s-2);
}
.stack {
display: grid;
gap: var(--s-3);
}
.muted {
color: var(--ink-3);
}
.optional {
font-weight: 400;
color: var(--ink-3);
}
.brand-name {
font-weight: 700;
letter-spacing: -0.01em;
}
/* Top bar shared by Home, the public claim page, and the staff gate. */
.page-bar {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--s-3);
padding: var(--s-2) 0 var(--s-5);
}
.back-link {
display: flex;
align-items: center;
gap: var(--s-3);
text-decoration: none;
color: inherit;
border-radius: var(--r-md);
min-width: 0;
}
.section-title {
display: flex;
align-items: center;
gap: var(--s-2);
font-size: var(--text-base);
font-weight: 650;
margin-bottom: var(--s-2);
}
.section-title svg {
color: var(--ink-3);
}
.hint {
color: var(--ink-3);
font-size: var(--text-sm);
line-height: 1.5;
margin: var(--s-1) 0 0;
}
.req {
color: var(--danger);
margin-left: 0.12rem;
}
.trunc {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 100%;
}
.mono {
font-family: var(--font-mono);
}
.clamp-2,
.clamp-3 {
display: -webkit-box;
-webkit-box-orient: vertical;
overflow: hidden;
}
.clamp-2 { -webkit-line-clamp: 2; line-clamp: 2; }
.clamp-3 { -webkit-line-clamp: 3; line-clamp: 3; }
.meta {
color: var(--ink-3);
font-size: var(--text-sm);
line-height: 1.5;
}
/* Hide visually, keep for screen readers. */
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0 0 0 0);
white-space: nowrap;
border: 0;
}
/* File-picker disguised as a button; input stays focusable for keyboards. */
.file-btn {
display: inline-flex;
align-items: center;
gap: 0.45rem;
cursor: pointer;
padding: 0.45rem 0.85rem;
border-radius: var(--r-md);
border: 1px dashed var(--line-strong);
background: var(--surface);
color: var(--ink-2);
font-size: var(--text-sm);
font-weight: 600;
margin: 0;
transition: border-color var(--dur-1) var(--ease-out), background-color var(--dur-1) var(--ease-out), color var(--dur-1) var(--ease-out);
}
.file-btn:hover {
border-color: var(--ink-3);
background: var(--surface-2);
color: var(--ink);
}
.file-btn:focus-within {
outline: 2px solid var(--focus);
outline-offset: 2px;
}
.file-btn input[type="file"] {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0 0 0 0);
white-space: nowrap;
border: 0;
}
/* Inline confirmation for destructive actions — appears in place, cancellable,
and Esc-dismissable (wired in markup). */
.inline-confirm {
margin-top: var(--s-3);
padding: var(--s-3) var(--s-4);
border: 1px solid color-mix(in srgb, var(--danger) 30%, transparent);
border-radius: var(--r-md);
background: var(--danger-soft);
animation: fade-up var(--dur-2) var(--ease-out);
}
.inline-confirm p {
margin: 0 0 var(--s-2);
color: var(--danger-on-soft);
font-size: var(--text-sm);
}
/* Selectable list cards (claims, events). */
.list-card {
display: grid;
gap: 0.45rem;
width: 100%;
text-align: left;
padding: var(--s-3) var(--s-4);
border: 1px solid var(--line);
border-radius: var(--r-md);
background: var(--surface);
color: inherit;
cursor: pointer;
font: inherit;
transition:
border-color var(--dur-1) var(--ease-out),
background-color var(--dur-1) var(--ease-out),
transform var(--dur-1) var(--ease-out),
box-shadow var(--dur-1) var(--ease-out);
}
.list-card:hover {
border-color: var(--line-strong);
box-shadow: var(--shadow-1);
}
.list-card:active {
transform: translateY(1px);
}
.list-card.active {
border-color: var(--accent);
background: var(--accent-soft);
box-shadow: inset 2px 0 0 var(--accent);
}
[data-theme="dark"] .list-card.active {
background: var(--accent-soft);
}
/* On touch devices, grow compact controls to a comfortable tap size without
changing their visual weight (padding/min-height only, same font). */
@media (pointer: coarse) {
.btn-sm,
.btn-link {
min-height: 2.5rem;
}
.btn-icon {
min-width: 2.75rem;
min-height: 2.75rem;
}
.filter-chip {
min-height: 2.25rem;
}
.toast-close {
padding: 0.55rem;
}
.file-btn {
min-height: 2.5rem;
}
}