/* Seitai — shared styles. Served by Apache at /static/css/common.css. */

:root {
    --bg: #f6f4f0;
    --surface: #fffdfa;
    --ink: #23201c;
    --muted: #6f6960;
    --line: #e2ddd4;
    --accent: #4a6b57;
    --accent-ink: #fffdfa;
    --danger: #a33a2c;
    --radius: 10px;
    /* What you type into, one shade below the card it sits on. A token rather
       than a literal because every field also needs repainting for dark mode,
       and a hand-kept list of which selectors to repaint is a list somebody
       eventually adds a rule without joining — which is exactly how the admin
       role dropdown ended up black-on-black. */
    --field: #fff;
}

* { box-sizing: border-box; }

body {
    margin: 0;
    background: var(--bg);
    color: var(--ink);
    font: 16px/1.55 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
}

a { color: var(--accent); }

.topbar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    padding: 0.9rem 1.25rem;
    background: var(--surface);
    border-bottom: 1px solid var(--line);
}

/* The signed-in address, beside the brand — so it is never a guess which
   account is being looked at, particularly with a second one open for testing. */
.brandbar {
    display: flex;
    align-items: baseline;
    gap: 0.75rem;
    min-width: 0;
}

.whoami {
    color: var(--muted);
    font-size: 0.85rem;
    text-decoration: none;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.brand {
    font-weight: 600;
    font-size: 1.15rem;
    text-decoration: none;
    color: var(--ink);
}

.topbar nav {
    display: flex;
    align-items: center;
    gap: 1rem;
}

.langswitch {
    display: inline-flex;
    gap: 0.4rem;
    padding-left: 0.9rem;
    border-left: 1px solid var(--line);
    font-size: 0.85rem;
}

main {
    max-width: 46rem;
    margin: 0 auto;
    padding: 1.75rem 1.25rem 3rem;
}

.card {
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    padding: 1.5rem;
}

h1 { margin: 0 0 0.75rem; font-size: 1.5rem; }

.lead { color: var(--muted); }
.muted { color: var(--muted); font-size: 0.9rem; }
.empty { margin-top: 1.25rem; }

.btn {
    display: inline-block;
    padding: 0.6rem 1.1rem;
    border: 0;
    border-radius: var(--radius);
    background: var(--accent);
    color: var(--accent-ink);
    font: inherit;
    text-decoration: none;
    cursor: pointer;
}

.btn:disabled { opacity: 0.6; cursor: default; }

.auth form {
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
    max-width: 22rem;
}

.auth label {
    margin-top: 0.6rem;
    font-size: 0.85rem;
    color: var(--muted);
}

.auth input {
    padding: 0.55rem 0.7rem;
    border: 1px solid var(--line);
    border-radius: var(--radius);
    background: var(--field);
    color: var(--ink);
    font: inherit;
}

.auth .btn { margin-top: 1rem; }

.formerror {
    margin: 0.75rem 0 0;
    color: var(--danger);
    font-size: 0.9rem;
}

.switch { margin-top: 1.25rem; font-size: 0.9rem; }

/* The second way in, below the password form and visibly separate from it. */
.passwordless {
    margin-top: 2rem;
    padding-top: 1.5rem;
    border-top: 1px solid var(--line);
    max-width: 22rem;
}

.passwordless h2 { font-size: 1rem; margin-bottom: 0.35rem; }
.passwordless input { width: 100%; }
.passwordless #otp { font-size: 1.4rem; letter-spacing: 0.3em; text-align: center; }

.footer {
    max-width: 46rem;
    margin: 0 auto;
    padding: 0 1.25rem 2rem;
    color: var(--muted);
    font-size: 0.85rem;
}

/* ------------------------------------------------------------ calendars */

main > .card + .card { margin-top: 1.25rem; }

.cardhead {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 1rem;
}

.crumbs { margin: 0 0 1rem; font-size: 0.9rem; }

h2 { margin: 0 0 0.9rem; font-size: 1.15rem; }

.stack {
    display: flex;
    flex-direction: column;
    gap: 0.3rem;
    max-width: 30rem;
}

.stack label:not(.check) {
    margin-top: 0.7rem;
    font-size: 0.85rem;
    color: var(--muted);
}

.stack input,
.stack select,
.stack textarea {
    padding: 0.5rem 0.65rem;
    border: 1px solid var(--line);
    border-radius: var(--radius);
    background: var(--field);
    color: var(--ink);
    font: inherit;
    width: 100%;
}

.stack textarea { resize: vertical; }

/* The open dropdown list is painted by the browser rather than by the page,
   and does not reliably inherit what the closed select is set to — on Linux it
   falls back to the system palette, which is how options stay black even once
   the select itself is right. Naming them is the only fix. */
select option {
    background: var(--field);
    color: var(--ink);
}

.pair {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 1rem;
}

.check {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    margin-top: 1rem;
}

.check input { width: auto; }

.hint {
    margin: 0.25rem 0 0;
    color: var(--muted);
    font-size: 0.8rem;
}

/* A value shown where a field used to be, because it can no longer be edited. */
.fixedfield {
    margin: 0;
    padding: 0.5rem 0.65rem;
    border: 1px dashed var(--line);
    border-radius: var(--radius);
}

.row {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    margin-top: 1rem;
}

.btn.ghost {
    background: transparent;
    color: var(--accent);
    border: 1px solid var(--line);
}

.btn.small { padding: 0.3rem 0.6rem; font-size: 0.85rem; }
.btn.warn { background: var(--danger); }

.saved { color: var(--accent); font-size: 0.85rem; }

.callist {
    list-style: none;
    margin: 1rem 0 0;
    padding: 0;
}

.callist li {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 1rem;
    padding: 0.7rem 0;
    border-top: 1px solid var(--line);
}

.caltitle { font-weight: 600; text-decoration: none; }

.tags { display: flex; flex-wrap: wrap; gap: 0.4rem; }

.tag {
    padding: 0.1rem 0.5rem;
    border: 1px solid var(--line);
    border-radius: 999px;
    color: var(--muted);
    font-size: 0.75rem;
    white-space: nowrap;
}

.tag.warn { color: var(--danger); border-color: var(--danger); }

/* -------------------------------------------------------- hours editor */

.day { padding: 0.6rem 0; border-top: 1px solid var(--line); }

.dayhead {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    margin-bottom: 0.35rem;
}

.windows { display: flex; flex-direction: column; gap: 0.4rem; }

.window {
    display: flex;
    align-items: center;
    gap: 0.5rem;
}

.window input {
    padding: 0.35rem 0.5rem;
    border: 1px solid var(--line);
    border-radius: var(--radius);
    background: var(--field);
    color: var(--ink);
    font: inherit;
}

.off { font-size: 0.85rem; }

/* ------------------------------------------------------------- sharing */

.grants { width: 100%; border-collapse: collapse; margin-bottom: 1rem; }

.grants td {
    padding: 0.5rem 0.5rem 0.5rem 0;
    border-top: 1px solid var(--line);
    vertical-align: middle;
}

.grants td:first-child { width: 100%; }

.grants select {
    padding: 0.3rem 0.4rem;
    border: 1px solid var(--line);
    border-radius: var(--radius);
    background: var(--field);
    color: var(--ink);
    font: inherit;
}

.card.danger { border-color: var(--danger); }

/* The create form sits below what already exists, and is deliberately not
   dressed like the cards above it: dashed and recessed, so the eye reads the
   page as "here is what you have" and only then "here is how to add one". */
.card.newcal {
    background: transparent;
    border-style: dashed;
}

.card.newcal h2 { color: var(--muted); font-size: 1rem; }

/* ------------------------------------------------- profile and its owner */

.profilefield { border-top: 1px solid var(--line); padding-top: 0.6rem; margin-top: 0.6rem; }
.profilefield:first-of-type { border-top: 0; }

/* The label on the left, its Público toggle hard right on the same line. */
.fieldhead {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 1rem;
}

.fieldhead label:not(.check) { margin-top: 0; }

.profilefield .check.ispublic {
    margin-top: 0;
    flex: none;
    font-size: 0.85rem;
    color: var(--muted);
}

.imagefield { display: flex; flex-direction: column; gap: 0.5rem; align-items: flex-start; }
.imagefield .btn { cursor: pointer; }

.preview {
    border: 1px solid var(--line);
    border-radius: var(--radius);
    background: var(--bg);
}

.preview.photo { width: 6rem; height: 6rem; object-fit: cover; border-radius: 50%; }
.preview.background { width: 100%; max-height: 9rem; object-fit: cover; }

/* The practitioner's own picture, behind the top of their pages. */
/* A user's own background, behind the whole page. Two fixed layers: the
   image, and a wash of the page colour over it.

   The wash is what makes this safe to turn on. Cards paint an opaque surface
   and are readable over anything, but loose text — headings, hints, crumbs —
   sits directly on the image, and a photograph behind small type is
   unreadable. At 82% the image reads as texture through the gutters and every
   word stays legible. That one number is the whole trade: lower it to let the
   photo through, and check the hint text on the busiest image you have. */
body.hasbg::before,
body.hasbg::after {
    content: "";
    position: fixed;
    inset: 0;
    pointer-events: none;
}

body.hasbg::before {
    z-index: -2;
    background-image: var(--userbg);
    background-size: cover;
    background-position: center;
}

body.hasbg::after {
    z-index: -1;
    background: color-mix(in srgb, var(--bg) 82%, transparent);
}

/* The bars would otherwise cut the image with a band of flat colour. */
body.hasbg .topbar,
body.hasbg .footer {
    background: color-mix(in srgb, var(--surface) 88%, transparent);
    backdrop-filter: blur(6px);
}

.ownerphoto.big { width: 7rem; height: 7rem; }

.datefield { font-variant-numeric: tabular-nums; }

/* An address in seven parts. Each row is a pair that belongs together — street
   and number, city and state — so they wrap as pairs rather than leaving a
   number stranded on a line of its own. */
.addressfields { display: flex; flex-direction: column; gap: 0.5rem; }

.addrrow { display: flex; gap: 0.75rem; }
.addrrow > span { display: flex; flex-direction: column; flex: 1; min-width: 0; }
.addrrow label { margin-top: 0; font-size: 0.85rem; color: var(--muted); }

.addrcep { max-width: 11rem; }
.addrnum { flex: 0 0 7rem; }
.addruf  { flex: 0 0 5.5rem; }
.addrstreet > span:first-child { flex: 3; }

@media (max-width: 34rem) {
    .addrrow { flex-wrap: wrap; }
    .addrnum, .addruf { flex-basis: 6rem; }
}

/* A warning is not a hint. It had the hint's grey, which made "your maximum
   duration is shorter than the gap between start times" read as advice rather
   than as something to look at. */
.warnhint {
    color: var(--danger);
    font-weight: 500;
}

/* A label styled as a button and a real button sat a pixel or two apart,
   because one is inline-block with a line box and the other is not. Making
   both inline-flex settles it wherever they appear together. */
.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    line-height: 1.2;
}

.imagefield .row { flex-wrap: wrap; align-items: center; }

/* Beside the field, not only at the foot of the form. An upload that failed
   is about one control, and the form's own error box can be a screen away. */
.fielderror {
    margin: 0.4rem 0 0;
    color: var(--danger);
    font-size: 0.85rem;
}

/* A Save repeated mid-form. Set off above and below so it reads as a place to
   stop, rather than as another field's control. */
.saverow {
    align-items: center;
    gap: 0.75rem;
    margin: 0.35rem 0 1.1rem;
    padding-top: 0.9rem;
    border-top: 1px solid var(--line);
}

.saverow .hint { margin: 0; }

/* A room, with its capacity and the therapies it can take. Set off as a block
   because each one is several fields that belong together — a flat list of
   inputs would read as one long form rather than as three rooms. */
.roomcard {
    padding: 1rem 0;
    border-top: 1px solid var(--line);
}

.roomcard .fieldhead { margin-bottom: 0.5rem; }

.roomtherapies {
    display: flex;
    flex-wrap: wrap;
    gap: 0.25rem 1.25rem;
    margin-top: 0.35rem;
}

/* The check label's own margin-top is for stacked forms; inside a wrapping
   row it just makes the rows sit unevenly. */
.roomtherapies .check { margin-top: 0.35rem; }

/* ---------------------------------------------------------------- admin */

/* The admin reads rows, not prose, so it gets the whole window. */
.card.wide { max-width: none; }
body:has(.card.wide) main { max-width: 72rem; }

.admintable {
    width: 100%;
    border-collapse: collapse;
    font-size: 0.9rem;
}

.admintable th {
    text-align: left;
    font-weight: 600;
    color: var(--muted);
    font-size: 0.8rem;
    border-bottom: 1px solid var(--line);
    padding: 0.4rem 0.6rem 0.4rem 0;
}

.admintable td {
    padding: 0.45rem 0.6rem 0.45rem 0;
    border-top: 1px solid var(--line);
    vertical-align: top;
}

.admintable td.num { text-align: right; font-variant-numeric: tabular-nums; }
.admintable .muted { display: block; font-size: 0.8rem; }
.admintable code { font-size: 0.85em; }

/* A row of data can be any width; the page must not be. */
.tablescroll { overflow-x: auto; max-width: 100%; }
.tablescroll .admintable { min-width: max-content; }
.tablescroll td { white-space: nowrap; max-width: 22rem; overflow: hidden; text-overflow: ellipsis; }

.admintable select {
    padding: 0.2rem 0.35rem;
    border: 1px solid var(--line);
    border-radius: var(--radius);
    background: var(--field);
    color: var(--ink);
    font: inherit;
    font-size: 0.85rem;
}

.subhead {
    margin: 1.25rem 0 0;
    font-size: 0.95rem;
    color: var(--muted);
}

.privatehead {
    margin: 1.75rem 0 0.25rem;
    padding-top: 1rem;
    border-top: 2px solid var(--line);
    font-size: 1rem;
}

/* Who the client is about to book with, above the calendar. */
.ownercard {
    display: flex;
    align-items: flex-start;
    gap: 1rem;
    margin: 0.75rem 0 0;
}

.ownerphoto {
    width: 4.5rem;
    height: 4.5rem;
    border-radius: 50%;
    object-fit: cover;
    border: 1px solid var(--line);
    flex: none;
}

.ownerwho { display: flex; flex-direction: column; gap: 0.15rem; min-width: 0; }
.ownerwho .lead { margin: 0.35rem 0; }
.ownerlinks { display: flex; flex-wrap: wrap; gap: 0.75rem; font-size: 0.9rem; }

/* ----------------------------------------------------- the profile page */

/* Its own layout rather than the booking page's compact header. The gaps here
   are the whole fix: at 0.15rem everything ran together and the page read as
   unfinished, whatever was in it. */
.profilehead {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    padding: 2rem 1.75rem;
}

.profilephoto {
    width: 8rem;
    height: 8rem;
    border-radius: 50%;
    object-fit: cover;
    border: 3px solid var(--surface);
    /* Lifts the photo off a background image without a hard edge. */
    box-shadow: 0 2px 12px rgba(0, 0, 0, 0.18);
    margin-bottom: 1.25rem;
}

.profilehead h1 { margin: 0; font-size: 1.75rem; line-height: 1.2; }

.profession {
    margin: 0.35rem 0 0;
    color: var(--accent);
    font-size: 1rem;
    font-weight: 500;
}

/* A measure, because a bio running the full width of a desktop card is a
   paragraph nobody finishes. Line breaks are kept: someone who wrote three
   paragraphs meant three. */
.profilebio {
    margin: 1.25rem 0 0;
    max-width: 38rem;
    white-space: pre-line;
    line-height: 1.65;
}

/* Where and how, together and set apart — they answer the same question and
   neither belongs in the middle of the biography. */
.profilecontact {
    margin-top: 1.5rem;
    padding-top: 1.25rem;
    border-top: 1px solid var(--line);
    width: 100%;
}

.profileaddress {
    margin: 0 0 0.85rem;
    color: var(--muted);
    font-size: 0.95rem;
}

.profilelinks { display: flex; flex-wrap: wrap; gap: 0.5rem; }

/* Links a visitor is meant to act on, so they look like something to press
   rather than words that happen to be blue. */
.chip {
    display: inline-flex;
    align-items: center;
    padding: 0.35rem 0.8rem;
    border: 1px solid var(--line);
    border-radius: 999px;
    color: var(--accent);
    font-size: 0.9rem;
    text-decoration: none;
    white-space: nowrap;
}

.chip:hover { border-color: var(--accent); }

@media (max-width: 34rem) {
    .profilehead { padding: 1.5rem 1.25rem; }
    .profilephoto { width: 6rem; height: 6rem; }
    .profilehead h1 { font-size: 1.45rem; }
}

/* ------------------------------------------------------------- booking */

.weeknav {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
    margin: 1.25rem 0 0.5rem;
}

.week { margin-top: 0.5rem; }

/* Shares .day with the hours editor, but here each day is a row of times. */
.week .day {
    display: grid;
    grid-template-columns: 7rem 1fr;
    align-items: baseline;
    gap: 0.75rem;
}

.dayname { font-size: 0.9rem; }

.slots {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
    padding: 0.1rem 0;
}

.slot {
    padding: 0.35rem 0.7rem;
    border: 1px solid var(--line);
    border-radius: var(--radius);
    background: var(--surface);
    color: var(--ink);
    font: inherit;
    font-variant-numeric: tabular-nums;
    cursor: pointer;
}

.slot:hover,
.slot:focus-visible {
    border-color: var(--accent);
    color: var(--accent);
}

/* The visitor's clock differing from the calendar's is the single mistake
   most likely to make someone miss an appointment, so it does not whisper. */
.tzwarn {
    margin: 0.5rem 0 0;
    padding: 0.5rem 0.75rem;
    border: 1px solid var(--danger);
    border-radius: var(--radius);
    color: var(--danger);
    font-weight: 600;
}

/* Times for the day chosen on the grid above. */
.daytimes { margin-top: 1rem; }
.daytimes .dayname { display: block; margin-bottom: 0.5rem; }

/* A day with something free reads as available; the rest are simply out. */
.monthday.hasslots { border-color: var(--accent); color: var(--accent); font-weight: 600; }
.monthday.ischosen { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }
.monthday:disabled { opacity: 0.4; }

.chosen {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    flex-wrap: wrap;
    margin: 0 0 1rem;
}

.notice {
    margin: 0 0 1rem;
    padding: 0.6rem 0.85rem;
    border: 1px solid var(--accent);
    border-radius: var(--radius);
    color: var(--accent);
}

.details {
    display: grid;
    grid-template-columns: 9rem 1fr;
    gap: 0.4rem 1rem;
    margin: 0 0 1rem;
}

/* -------------------------------------------------- practitioner's week */

.agendaweek .day { padding: 0.55rem 0; }
.agendaweek .day.istoday .dayname { color: var(--accent); }

.appts { display: flex; flex-direction: column; gap: 0.35rem; }

.appt {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 0.5rem;
}

.apptwhen { font-variant-numeric: tabular-nums; white-space: nowrap; }
.apptwho { font-weight: 600; }
.apptcontact { font-size: 0.85rem; }

.apptactions { display: flex; flex-wrap: wrap; gap: 0.3rem; }

/* Cancelled and declined stay visible but stop competing for attention. */
.appt.status-cancelled,
.appt.status-declined { opacity: 0.55; }
.appt.status-cancelled .apptwhen { text-decoration: line-through; }
.appt.status-pending .apptwhen { color: var(--danger); }

#pending .appt { padding: 0.35rem 0; border-top: 1px solid var(--line); }

/* ------------------------------------------------------------ month grid */

.monthgrid { margin-top: 0.75rem; }

.monthhead,
.monthbody {
    display: grid;
    grid-template-columns: repeat(7, 1fr);
    gap: 0.3rem;
}

.monthhead {
    margin-bottom: 0.3rem;
    color: var(--muted);
    font-size: 0.75rem;
    text-align: center;
}

.monthday {
    padding: 0.55rem 0;
    border: 1px solid var(--line);
    border-radius: var(--radius);
    background: var(--surface);
    color: var(--ink);
    font: inherit;
    font-variant-numeric: tabular-nums;
    cursor: pointer;
}

.monthday:disabled { cursor: default; }
.monthday:hover:not(:disabled) { border-color: var(--accent); }
.monthday.istoday { font-weight: 700; border-color: var(--accent); }

.monthday.isoff {
    background: var(--danger);
    border-color: var(--danger);
    color: var(--accent-ink);
}

/* Part of a day off reads as struck through rather than filled in. */
.monthday.isoff.ispartial {
    background: var(--surface);
    color: var(--danger);
    text-decoration: line-through;
}

.details dt { color: var(--muted); font-size: 0.85rem; }
.details dd { margin: 0; }

@media (max-width: 34rem) {
    .pair { grid-template-columns: 1fr; }
    .callist li { flex-direction: column; gap: 0.35rem; }
    .week .day { grid-template-columns: 1fr; gap: 0.25rem; }
    .monthhead, .monthbody { gap: 0.2rem; }
    .brandbar { flex-direction: column; gap: 0; align-items: flex-start; }
    .ownercard { flex-direction: column; }
    .whoami { max-width: 12rem; }
    .monthday { padding: 0.4rem 0; font-size: 0.9rem; }
    .details { grid-template-columns: 1fr; gap: 0 0; }
    .details dd { margin-bottom: 0.6rem; }
}

@media (prefers-color-scheme: dark) {
    :root {
        --bg: #171614;
        --surface: #201f1c;
        --ink: #eae6df;
        --muted: #9a938a;
        --line: #33312c;
        --accent: #7fae90;
        --accent-ink: #16211a;
        --danger: #e08273;
        --field: #191816;
    }
    /* Nothing else belongs here. Every field takes its colours from --field
       and --ink in its own rule, so a new input is dark automatically instead
       of having to be remembered in a list down here. */
    .slot,
    .monthday { background: var(--field); }
    .monthday.isoff { background: var(--danger); }
    .monthday.isoff.ispartial { background: var(--field); }
}
