:root {
    /* Warm & trustworthy palette — teal accent on warm paper, no gradients.
       Variable names are kept stable so every component picks up the new
       values; semantic aliases (--bg/--surface/--border/--text/--muted) are
       provided for new code. */
    --primary: #2F8F83;          /* teal — primary actions */
    --primary-light: #DCECE9;    /* soft teal tint (backgrounds) */
    --primary-dark: #256F66;     /* teal hover/pressed */
    --secondary: #3D9A6D;        /* warm green — "signed in" / success */
    --secondary-light: #CDE8DA;
    --secondary-dark: #2E7D57;
    --success: #3D9A6D;
    --success-dark: #2E7D57;
    --warning: #E0863A;          /* amber */
    --danger: #C6553B;           /* warm terracotta — sign-out / delete */
    --dark: #2B2A28;             /* warm near-black text */
    --gray: #6F6B64;             /* warm muted text */
    --light-gray: #EFEBE4;       /* warm light surface */
    --light: #FBFAF7;

    /* Semantic aliases */
    --bg: #F7F5F1;               /* warm paper page background */
    --surface: #FFFFFF;          /* cards */
    --border: #E7E2DA;           /* hairline borders */
    --text: #2B2A28;
    --muted: #6F6B64;

    /* --- Text-safe partners for the brand hues (WCAG AA pass) --------------
       --primary / --secondary / --success / --warning / --danger above are
       BRAND hues. They are sized for borders, icon fills and left-rails, where
       WCAG only asks 3:1 (SC 1.4.11). They are NOT dark enough to carry text:
       white on --primary is 3.91:1 and white on --secondary is 3.48:1, both
       short of the 4.5:1 body-text floor.

       RULE: --warning (#E0863A) is a BACKGROUND / BORDER colour only and must
       never carry text, and must never sit under white text. White on it is
       2.75:1 and it is 2.31:1 on --light-gray. Use --warning-strong when amber
       must be a surface under white text, --warning-text when amber must be
       the text. The same discipline applies to the other brand hues below.

       Every ratio here was measured with the WCAG 2.x relative-luminance
       formula and is locked in by tests/test_contrast.py. */
    --primary-strong: #256F66;    /* teal surface/foreground that may carry text: white on it 5.93:1, and 5.93:1 on white. Deliberately the same value as --primary-dark; kept as its own name so "safe for text" is greppable. */
    --primary-stronger: #1C554E;  /* hover/pressed for --primary-strong surfaces: white on it 8.54:1 */
    --success-strong: #2E7D57;    /* green surface under white text: 5.02:1. Same value as --success-dark / --secondary-dark. */
    --success-stronger: #20563B;  /* hover for --success-strong: white on it 8.55:1 */
    --danger-strong: #A8432E;     /* terracotta surface under white text: 5.99:1, and 5.99:1 as text on white */
    --danger-stronger: #8E3826;   /* hover for --danger-strong: white on it 7.65:1 */
    --warning-strong: #96530F;    /* the ONLY amber that may sit under white text: 5.93:1 */
    --warning-text: #92400E;      /* amber as TEXT: 7.09:1 on --surface, 5.97:1 on --light-gray, 6.17:1 on the 15% amber tint */

    /* Foregrounds for the soft tint chips/badges. The tints are the brand hue
       composited over white, so a foreground has to be much darker than the
       hue itself; using the hue directly is what put "Signed In" and
       "Signed Out" below AA. */
    --on-tint-green: #245F42;     /* 6.41:1 on the 15% green tint (#E2F0E9); 5.79:1 on --secondary-light */
    --on-tint-red: #9A3D28;       /* 5.87:1 on the 12% red tint (#F8EBE7) */
    --on-tint-gray: #57534B;      /* 6.54:1 on the 12% gray tint (#EEEDEC); white on it 7.65:1 */

    --border-radius: 10px;
    --box-shadow: 0 1px 2px rgba(43, 42, 40, 0.04), 0 2px 8px rgba(43, 42, 40, 0.06);
}

body {
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
    margin: 0;
    padding: 0;
    background-color: var(--bg);
    color: var(--dark);
    line-height: 1.5;
}

/* Plain text links ("Forgot your password?", "Return home", an account name
   in a table) had no colour at all and rendered in the browser's default
   #0000EE — the one saturated blue on a warm teal-on-paper page. Classed links
   (.menu-button, .back-button, the button classes) set their own colour; no
   a:hover here, because its specificity would beat theirs. */
a {
    color: var(--primary-strong);   /* 5.93:1 on white, 5.44:1 on --bg */
}

.container {
    max-width: 1200px;
    margin: 0 auto;
    padding: 20px;
}

h1, h2, h3, h4, h5, h6 {
    margin-top: 0;
    color: var(--dark);
}

/* A page-level section heading needs air above it. With margin-top: 0 above,
   "Invite a guardian", "Change your password" and the like sat flush against
   the table or button of the previous section and read as part of it. Only
   headings that follow something on the page itself, so a heading opening a
   card is unaffected. */
.container > * + h2,
.container > * + h3 {
    margin-top: 32px;
}

/* Authentication styles */
.auth-container {
    display: flex;
    justify-content: center;
    align-items: center;
    min-height: 80vh;
}

.auth-box {
    background-color: white;
    border-radius: var(--border-radius);
    padding: 30px;
    width: 100%;
    max-width: 400px;
    box-shadow: var(--box-shadow);
}

.auth-box h2 {
    margin-bottom: 20px;
    text-align: center;
}

.alert {
    padding: 10px 15px;
    border-radius: var(--border-radius);
    margin-bottom: 20px;
}

.alert-danger {
    background-color: rgba(198, 85, 59, 0.12);
    color: var(--on-tint-red);   /* was --danger on its own tint: 3.78:1 -> 5.87:1 */
    border: 1px solid rgba(198, 85, 59, 0.12);
}

/* Menu Buttons */
.button-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
    gap: 20px;
}

.menu-button {
    display: flex;
    flex-direction: column;
    background-color: var(--surface);
    padding: 24px;
    min-height: 108px;
    border: 1px solid var(--border);
    border-left: 5px solid var(--border);
    border-radius: var(--border-radius);
    box-shadow: var(--box-shadow);
    transition: transform 0.15s, box-shadow 0.15s, border-left-color 0.15s;
    text-decoration: none;
    color: var(--dark);
}

.menu-button:hover {
    transform: translateY(-3px);
    border-left-color: var(--primary);
    box-shadow: 0 10px 18px -6px rgba(43, 42, 40, 0.14);
}

.menu-button i {
    font-size: 26px;
    margin-bottom: 12px;
}

.menu-button-title { font-weight: 600; font-size: 1.05rem; }
.menu-button-sub { font-size: 0.9rem; color: var(--muted); margin-top: 4px; }

/* Main-menu top bar */
.menu-topbar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 16px;
    flex-wrap: wrap;
    margin-bottom: 24px;
}
.menu-title { margin: 0; font-size: 1.6rem; }
.menu-subtitle { margin: 2px 0 0; color: var(--muted); }
.logout-link {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    text-decoration: none;
    color: var(--dark);
    font-weight: 500;
    padding: 10px 16px;
    border: 1px solid var(--border);
    border-radius: var(--border-radius);
    background-color: var(--surface);
    transition: border-color 0.15s, color 0.15s;
}
/* Text goes to --danger-strong (5.99:1); the border may stay on the brand
   --danger, which only owes 3:1 as a non-text indicator. */
.logout-link:hover { color: var(--danger-strong); border-color: var(--danger); }

/* Form elements */
/* Browsers do not let form controls inherit the page font, so every date
   heading, nav button and search box rendered in the platform default (Arial
   on the tablet) beside Segoe UI text. */
button, input, select, textarea {
    font-family: inherit;
}

input, select, textarea {
    /* border-box: the width below is meant to INCLUDE the padding and border.
       Under the default content-box every full-width field was 26px wider than
       its column (54px for the padded search boxes), which ran the kiosk search
       bar off the right edge and scrolled the whole page sideways. */
    box-sizing: border-box;
    width: 100%;
    padding: 12px;
    margin-bottom: 15px;
    border: 1px solid #D8D2C8;
    border-radius: var(--border-radius);
    font-size: 16px;
    transition: border-color 0.3s;
}

/* The full-width rule above is for text fields. A tick box that inherits it
   is centred alone on a line of its own, a full row away from the label it
   belongs to (API key scopes, webhook events, "keep forever"). Components that
   style their own box (.switch, .custom-checkbox, .meal-checkbox) come later
   and still win. */
input[type="checkbox"],
input[type="radio"] {
    width: auto;
    padding: 0;
    margin: 0 8px 0 0;
    vertical-align: middle;
}

input:focus, select:focus, textarea:focus {
    outline: none;
    border-color: var(--primary);
    box-shadow: 0 0 0 3px rgba(47, 143, 131, 0.25);
}

.custom-select {
    appearance: none;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%236F6B64'%3E%3Cpath d='M7 10l5 5 5-5H7z'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 12px center;
    background-size: 16px;
}

textarea {
    min-height: 100px;
    resize: vertical;
}

label {
    display: block;
    margin-bottom: 8px;
    font-weight: 500;
    color: var(--dark);
}

.form-group {
    margin-bottom: 20px;
}

/* The button classes are also put on <a> links ("Go to my portal", "Search
   all forms"). A <button> is border-box by default and a link is not, so the
   same class gave a link 26px of extra height (its label sat at the top of a
   74px slab) and, with .btn-primary's width:100%, 40px of extra width that hung
   out of its card. A link also kept its underline. */
.btn-primary, .btn-secondary, .btn-danger {
    box-sizing: border-box;
}
a.btn-primary, a.btn-secondary, a.btn-danger, a.nav-button {
    text-decoration: none;
}

.btn-primary {
    /* --primary-strong, not --primary: white on the brand teal is 3.91:1,
       below the 4.5:1 needed for a 16px label. Now 5.93:1. */
    background-color: var(--primary-strong);
    color: white;
    border: none;
    padding: 13px 20px;
    min-height: 48px;
    border-radius: var(--border-radius);
    cursor: pointer;
    font-size: 16px;
    font-weight: 600;
    transition: background-color 0.15s, transform 0.05s;
    display: inline-block;
    text-align: center;
    width: 100%;
}

.btn-primary:hover {
    background-color: var(--primary-stronger);
}

.btn-primary:active { transform: translateY(1px); }

.btn-secondary {
    background-color: var(--surface);
    color: var(--dark);
    border: 1px solid #D8D2C8;
    padding: 13px 20px;
    min-height: 48px;
    border-radius: var(--border-radius);
    cursor: pointer;
    font-size: 16px;
    font-weight: 600;
    transition: background-color 0.15s, border-color 0.15s;
    display: inline-block;
    text-align: center;
}

.btn-secondary:hover {
    background-color: var(--light-gray);
    border-color: var(--muted);
}

/* The destructive twin of .btn-primary, for confirm-delete dialogs.
   `templates/manage_children.html` has always asked for `.btn-danger`, but only
   `.btn-danger-sm` was ever defined — so the button that permanently destroys a
   child's attendance history rendered as an unstyled browser default beside a
   fully-styled 48px Cancel. Geometry is .btn-secondary's (the Cancel it sits
   next to in .modal-footer, a flex row) rather than .btn-primary's, because
   .btn-primary carries width:100% and would push Cancel off the row. */
.btn-danger {
    background-color: var(--danger-strong);   /* white on it 5.99:1 */
    color: white;
    border: none;
    padding: 13px 20px;
    min-height: 48px;
    border-radius: var(--border-radius);
    cursor: pointer;
    font-size: 16px;
    font-weight: 600;
    transition: background-color 0.15s, transform 0.05s;
    display: inline-block;
    text-align: center;
}

.btn-danger:hover { background-color: var(--danger-stronger); }
.btn-danger:active { transform: translateY(1px); }

/* The keyboard focus ring lives in one place only, near the end of this file
   (search "Visible keyboard focus everywhere"). A duplicate teal rule used to
   sit here and was silently overridden by that later one, so every focus ring
   in the product rendered flat-UI blue. `.menu-button` is an <a>, so the
   `a:focus-visible` half of the surviving rule still covers it. */

.text-button {
    background: none;
    border: none;
    color: var(--primary-strong);   /* was --primary: 3.91:1 on white. Now 5.93:1. */
    cursor: pointer;
    font-weight: 500;
    padding: 5px;
    transition: color 0.2s;
}

.text-button:hover {
    color: var(--primary-stronger);
    text-decoration: underline;
}

.back-button {
    display: inline-flex;
    align-items: center;
    color: var(--dark);
    text-decoration: none;
    /* The way out of every screen, so a full 44px touch target rather than
       the 24px line of text it was; the margin gives back most of the height. */
    min-height: 44px;
    margin-bottom: 10px;
    font-weight: 500;
    transition: color 0.2s;
}

.back-button:hover {
    color: var(--primary-strong);   /* was --primary: 3.59:1 on --bg. Now 5.44:1. */
}

.back-button i {
    margin-right: 8px;
}

/* Custom Checkbox */
.checkbox-container {
    margin-bottom: 15px;
}

.custom-checkbox {
    display: flex;
    align-items: center;
    margin-bottom: 10px;
}

.custom-checkbox input[type="checkbox"] {
    width: auto;
    margin-right: 10px;
    margin-bottom: 0;
}

.custom-checkbox label {
    margin-bottom: 0;
    cursor: pointer;
}

.option-group {
    display: flex;
    gap: 20px;
    margin-bottom: 20px;
}

/* Child Attendance */
.attendance-summary {
    display: flex;
    justify-content: space-between;
    margin-bottom: 20px;
    gap: 15px;
}

.summary-box {
    background-color: white;
    padding: 15px;
    border-radius: var(--border-radius);
    flex: 1;
    text-align: center;
    box-shadow: var(--box-shadow);
}

.summary-label {
    display: block;
    font-size: 14px;
    color: var(--gray);
    margin-bottom: 5px;
}

.summary-value {
    display: block;
    font-size: 24px;
    font-weight: 600;
    color: var(--primary);
}

.summary-sub {
    display: block;
    margin-top: 2px;
    font-size: 13px;
    color: var(--gray);
}

.children-grid {
    display: grid;
    /* 340px, not 280px: the photo, status chip, gaps and padding take ~217px
       before the name gets any, so 280px left a child's name about 60px —
       "Emily…", "Mich…" on the screen whose job is telling staff who is in the
       building. 340px leaves ~120px, and the name now WRAPS rather than
       truncating (below), so a long name costs a line, never letters. */
    grid-template-columns: repeat(auto-fill, minmax(340px, 1fr));
    gap: 18px;
    margin-top: 20px;
}

/* Child cards share the staff-card layout: horizontal, round photo, status
   chip on the right, status-colored left border. */
.child-card {
    min-width: 0;   /* may shrink to its grid column; see .staff-grid */
    display: flex;
    align-items: center;
    gap: 14px;
    background-color: white;
    border-radius: var(--border-radius);
    padding: 16px;
    box-shadow: var(--box-shadow);
    transition: transform 0.15s, box-shadow 0.15s;
    cursor: pointer;
    border-left: 5px solid #E7E2DA;
}

.child-card:hover {
    transform: translateY(-3px);
    box-shadow: 0 10px 15px -3px rgba(0,0,0,0.1), 0 4px 6px -2px rgba(0,0,0,0.05);
}

.child-card.status-none { border-left-color: #E7E2DA; }
.child-card.status-in { border-left-color: var(--secondary); }
.child-card.status-out { border-left-color: var(--danger); }
.child-card.status-absent { border-left-color: var(--gray); }

.child-photo {
    width: 60px;
    height: 60px;
    border-radius: 50%;
    overflow: hidden;
    flex-shrink: 0;
    background-color: #E7E2DA;
    display: flex;
    align-items: center;
    justify-content: center;
}

.child-photo img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.default-avatar {
    width: 60px;
    height: 60px;
    background-color: var(--primary-light);
    border-radius: 50%;
    display: flex;
    justify-content: center;
    align-items: center;
    /* Was `white` on the pale teal tint: 1.22:1, i.e. the placeholder icon was
       invisible on every child/staff card without a photo. */
    color: var(--primary-strong);   /* 4.86:1 on --primary-light */
}

.default-avatar i {
    font-size: 28px;
}

.child-info {
    flex: 1;
    min-width: 0;
}

.child-info h3 {
    margin: 0 0 2px 0;
    font-size: 1.05rem;
    line-height: 1.25;
    /* Never ellipsize a child's name on the kiosk: wrap at spaces, and break
       inside a word only if one word alone is wider than the column. */
    white-space: normal;
    overflow-wrap: break-word;
}

.child-info small {
    color: var(--gray);
    font-size: 0.85rem;
    /* "DOB: 03/15/2023" is one unit; the label wrapping onto its own line
       above the date read as two unrelated fields. */
    white-space: nowrap;
}

.child-status {
    display: flex;
    align-items: center;
    flex-shrink: 0;
}

.date-navigation {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 20px;
    background-color: white;
    padding: 12px 15px;
    border-radius: var(--border-radius);
    box-shadow: var(--box-shadow);
}

.date-display {
    /* It is an <input>, so the form rule gave it width: 100% and a 15px bottom
       margin: it squeezed "Previous Day" / "Next Day" onto two lines each and
       sat the date above the buttons' centre line. It takes the space that
       is left instead. */
    flex: 1 1 auto;
    width: auto;
    min-width: 0;
    margin: 0;
    font-weight: 600;
    font-size: 1.1rem;
    text-align: center;
    padding: 8px;
    border: none;
    cursor: pointer;
    background: transparent;
}

.nav-button {
    white-space: nowrap;
    background-color: var(--light-gray);
    border: 1px solid var(--border);
    padding: 11px 16px;
    min-height: 44px;
    border-radius: var(--border-radius);
    cursor: pointer;
    display: flex;
    align-items: center;
    font-weight: 500;
    color: var(--dark);
    transition: background-color 0.15s, border-color 0.15s;
}

.nav-button:hover {
    background-color: var(--surface);
    border-color: var(--muted);
}

.search-container {
    position: relative;
    margin-bottom: 20px;
}

.search-container input {
    width: 100%;
    padding-left: 40px;
    /* The container supplies the spacing below. The field's own 15px margin
       made the container taller than the field, so the icon — centred on the
       container — sat 7px below the text it labels. */
    margin-bottom: 0;
}

.search-icon {
    position: absolute;
    left: 12px;
    top: 50%;
    transform: translateY(-50%);
    color: var(--gray);
}

.empty-message {
    text-align: center;
    color: var(--gray);
    grid-column: 1 / -1;
    padding: 30px;
    background-color: white;
    border-radius: var(--border-radius);
    box-shadow: var(--box-shadow);
}

/* Modal */
.modal {
    display: none;
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background-color: rgba(0, 0, 0, 0.5);
    justify-content: center;
    align-items: center;
    z-index: 1000;
}

.modal-content {
    background-color: white;
    border-radius: var(--border-radius);
    width: 90%;
    max-width: 500px;
    max-height: 90vh;
    overflow-y: auto;
    box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
}

.modal-header {
    padding: 20px;
    border-bottom: 1px solid #E7E2DA;
    display: flex;
    justify-content: space-between;
    align-items: center;
}

.modal-header h2 {
    margin: 0;
    font-size: 1.5rem;
}

.close-button {
    background: none;
    border: none;
    font-size: 24px;
    cursor: pointer;
    color: var(--gray);
    padding: 0;
}

.modal-body {
    padding: 20px;
}

.modal-footer {
    padding: 15px 20px;
    border-top: 1px solid #E7E2DA;
    display: flex;
    justify-content: flex-end;
    gap: 10px;
}

/* Signature pad */
.signature-container {
    border: 1px solid #D8D2C8;
    border-radius: var(--border-radius);
    overflow: hidden;
    background-color: white;
}

canvas#signaturePad {
    width: 100%;
    height: 200px;
    background-color: white;
    display: block;
}

/* Custom Alert */
.custom-alert {
    display: none;
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background-color: rgba(0, 0, 0, 0.5);
    justify-content: center;
    align-items: center;
    z-index: 2000;
}

.alert-content {
    background-color: white;
    border-radius: var(--border-radius);
    width: 90%;
    max-width: 400px;
    padding: 20px;
    text-align: center;
    box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
}

.alert-icon {
    font-size: 48px;
    /* --warning was 2.75:1 on the white dialog — below even the 3:1 that
       SC 1.4.11 asks of a meaningful graphic, let alone text. */
    color: var(--warning-text);   /* 7.09:1 */
    margin-bottom: 15px;
}

.alert-message {
    margin-bottom: 20px;
    font-size: 16px;
}

.alert-buttons {
    display: flex;
    justify-content: center;
}

/* Status badges — an in-flow chip, identical in behaviour to .status-chip.
   This was `position: absolute; top: 10px; right: 10px` with no positioned
   ancestor anywhere in the chain (.visitor-card / .visitor-info / .container /
   main are all static), so every badge resolved against the initial containing
   block and they stacked on top of each other in the page's top-right corner:
   a visitor's SIGNED IN / SIGNED OUT and a child's Active / Inactive were
   simply missing from their row. Made an inline-flex chip rather than pinning
   `position: relative` on an ancestor, so nothing downstream can re-break it by
   introducing a new positioned wrapper.
   `align-self: flex-start` matters: in visitor_log.html the badge is a direct
   child of `.visitor-info`, a column flex container, which would otherwise
   stretch the chip to the full card width (this is why the sibling
   `.sign-out-btn` already carries the same declaration). It is inert inside a
   table cell, which is where manage_children.html uses it. */
.status-badge {
    display: inline-flex;
    align-items: center;
    align-self: flex-start;
    gap: 5px;
    padding: 5px 10px;
    border-radius: 999px;
    font-size: 0.8rem;
    font-weight: 600;
    white-space: nowrap;
}

/* Foregrounds match .chip-* below — same tints, same job, same ratios. */
.badge-green {
    background-color: rgba(61, 154, 109, 0.15);
    color: var(--on-tint-green);   /* was --secondary-dark, 4.27:1 -> 6.41:1 */
}

.badge-red {
    background-color: rgba(198, 85, 59, 0.12);
    color: var(--on-tint-red);     /* was --danger, 3.78:1 -> 5.87:1 */
}

.badge-gray {
    background-color: rgba(111, 107, 100, 0.12);
    color: var(--on-tint-gray);    /* was --gray, 4.53:1 -> 6.54:1 */
}

/* Visitor card styles */
.visitor-card {
    background-color: white;
    border-radius: var(--border-radius);
    padding: 20px;
    margin-bottom: 15px;
    box-shadow: var(--box-shadow);
    border-left: 5px solid var(--secondary);
}

.visitor-info {
    display: flex;
    flex-direction: column;
    gap: 10px;
}

.visitor-info h3 {
    margin: 0;
    font-size: 18px;
}

.visitor-info p {
    margin: 0;
    color: var(--gray);
}

.sign-out-btn {
    align-self: flex-start;
    background-color: var(--primary-strong);   /* 3.91:1 -> 5.93:1 */
    color: white;
    border: none;
    border-radius: var(--border-radius);
    padding: 8px 15px;
    cursor: pointer;
    margin-top: 15px;
    transition: background-color 0.2s;
}

.sign-out-btn:hover {
    background-color: var(--primary-stronger);
}

/* Food attendance styles */
.food-attendance-card {
    display: flex;
    justify-content: space-between;
    align-items: center;
    background-color: white;
    border-radius: var(--border-radius);
    padding: 20px;
    margin-bottom: 15px;
    box-shadow: var(--box-shadow);
    transition: transform 0.2s;
}

.food-attendance-card:hover {
    transform: translateY(-3px);
}

.meal-btn {
    background-color: var(--light);
    border: none;
    padding: 8px 15px;
    border-radius: var(--border-radius);
    margin-left: 5px;
    cursor: pointer;
    font-weight: 500;
    transition: background-color 0.2s, color 0.2s;
}

.meal-btn:hover {
    background-color: #E7E2DA;
}

.meal-btn-active {
    background-color: var(--primary-strong);   /* 3.91:1 -> 5.93:1 */
    color: white;
}

.meal-checkbox {
    display: flex;
    align-items: center;
    gap: 8px;
}

.meal-checkbox input[type="checkbox"] {
    margin: 0;
    width: auto;
}

/* Export data page styles */
.section {
    background-color: white;
    border-radius: var(--border-radius);
    padding: 20px;
    box-shadow: var(--box-shadow);
    margin-bottom: 20px;
}

.date-range {
    display: flex;
    flex-wrap: wrap;
    gap: 15px;
}

.date-range label {
    display: block;
    width: 100%;
}

.data-table {
    width: 100%;
    border-collapse: collapse;
}

.data-table th, .data-table td {
    padding: 12px;
    text-align: left;
    border-bottom: 1px solid #E7E2DA;
}

.data-table th {
    background-color: #FBFAF7;
    font-weight: 600;
    color: var(--dark);
}

.data-table tr:hover td {
    background-color: #FBFAF7;
}

.btn-sm {
    display: inline-flex;
    align-items: center;
    background-color: var(--primary-strong);   /* 3.91:1 -> 5.93:1 */
    color: white;
    border: none;
    padding: 6px 12px;
    border-radius: var(--border-radius);
    text-decoration: none;
    font-size: 0.9rem;
    transition: background-color 0.2s;
}

.btn-sm:hover {
    background-color: var(--primary-stronger);
}

.btn-sm i {
    margin-right: 5px;
}

/* Responsive adjustments */
@media (max-width: 768px) {
    /* A roster card is a 60px photo that cannot shrink, a status chip that
       cannot wrap (~92px), 28px of gaps, 32px of padding and a 5px border:
       about 217px before the name has any room at all. At the old 200px floor
       `auto-fill` fitted three columns at 768px, `.child-info` collapsed to
       zero and THE CHILD'S NAME DISAPPEARED — on the one screen whose job is
       telling staff who is in the building. `.staff-grid` got this right
       (single column below 768px); this did not. */
    .children-grid {
        /* One full-width column on a phone/portrait tablet: two 240px columns
           left the name ~20px. */
        grid-template-columns: minmax(0, 1fr);
    }
    
    .attendance-summary {
        flex-direction: column;
    }
    
    .date-navigation {
        flex-direction: column;
        gap: 10px;
        align-items: stretch;
    }
    
    .option-group {
        flex-direction: column;
        gap: 10px;
    }
}
/* === Admin portal (P5-6c) === */
.portal-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    margin-bottom: 1rem;
    flex-wrap: wrap;
}
.portal-sub, .auth-sub { color: var(--muted); margin: .25rem 0 1rem; }   /* was #666 */
.auth-alt { margin-top: 1rem; font-size: .9rem; color: var(--muted); }   /* was #666 */
.portal-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
    gap: 1rem;
    margin-top: 1rem;
}
.invite-form { max-width: 480px; }
/* Small destructive button (Revoke / Remove / Cancel subscription). Same colour
   family as .btn-danger above; was the off-brand flat-UI #c0392b. */
.btn-danger-sm {
    background: var(--danger-strong);   /* was #c0392b, 5.44:1 -> 5.99:1 */
    color: #fff;
    border: none;
    padding: .35rem .7rem;
    border-radius: var(--border-radius);
    cursor: pointer;
    font-size: .85rem;
}
.btn-danger-sm:hover { background: var(--danger-stronger); }   /* was #a93226 */

.error-box { text-align: center; }
.error-box code { background: var(--light-gray); padding:.1rem .3rem; border-radius:3px; }

/* === Staff attendance page (redesign) === */
.page-heading {
    display: flex;
    align-items: center;
    gap: 12px;
    flex-wrap: wrap;
    margin-bottom: 8px;
}
.page-heading h1 { margin: 0; }
.page-heading h1 i { color: var(--primary); margin-right: 6px; }

.mode-pill {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-size: 0.8rem;
    font-weight: 600;
    padding: 4px 12px;
    border-radius: 999px;
}
.mode-pill-quick {
    background-color: rgba(224, 134, 58, 0.15);
    color: var(--warning-text);   /* was #B45309: 4.04:1 on the tint over --bg. Now 5.71:1. */
}

.staff-grid {
    display: grid;
    /* 340px for the reason given at .children-grid: photo, status block, gaps
       and padding left a 280px card ~86px for the name, so a teacher's name
       read "Test Teac…". */
    grid-template-columns: repeat(auto-fill, minmax(340px, 1fr));
    gap: 18px;
    margin-top: 20px;
}

.staff-card {
    min-width: 0;   /* may shrink to its grid column; see .staff-grid */
    display: flex;
    align-items: center;
    gap: 14px;
    background-color: white;
    border-radius: var(--border-radius);
    padding: 16px;
    box-shadow: var(--box-shadow);
    transition: transform 0.15s, box-shadow 0.15s;
    border-left: 5px solid #E7E2DA;
}
.staff-card:hover {
    transform: translateY(-3px);
    box-shadow: 0 10px 15px -3px rgba(0,0,0,0.1), 0 4px 6px -2px rgba(0,0,0,0.05);
}
.staff-card.status-in { border-left-color: var(--secondary); }
.staff-card.status-out { border-left-color: var(--danger); }
.staff-card.status-none { border-left-color: #E7E2DA; }

.staff-photo {
    width: 60px;
    height: 60px;
    border-radius: 50%;
    overflow: hidden;
    flex-shrink: 0;
    background-color: #E7E2DA;
    display: flex;
    align-items: center;
    justify-content: center;
}
.staff-photo img { width: 100%; height: 100%; object-fit: cover; }

.staff-info {
    flex: 1;
    min-width: 0;
}
.staff-info h3 {
    margin: 0 0 2px 0;
    font-size: 1.05rem;
    line-height: 1.25;
    /* Wrap, never ellipsize — as for the child cards. */
    white-space: normal;
    overflow-wrap: break-word;
}
.staff-info p {
    margin: 0;
    color: var(--gray);
    font-size: 0.88rem;
}

.staff-status {
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: 8px;
    flex-shrink: 0;
}

/* `.status-chip` is defined ONCE, further down with the portal chips. It used
   to be declared here as well, and the later block won for every chip in the
   product — so this rule's `inline-flex`, `align-items` and `gap` were dead and
   the kiosk chips were spacing their icon on the whitespace in the markup. The
   surviving rule carries those properties, which is why they are not repeated
   here. */
/* These three carry safety meaning — "Signed In" / "Signed Out" / "Not
   Arrived" — at 0.75rem, so they need the full 4.5:1, not the large-text 3:1.
   Each used the brand hue itself as the foreground, which is far too light
   against its own tint. Ratios are measured on the tint composited over the
   white card. */
.chip-green { background-color: rgba(61, 154, 109, 0.15); color: var(--on-tint-green); }  /* 4.27:1 -> 6.41:1 */
.chip-red   { background-color: rgba(198, 85, 59, 0.12); color: var(--on-tint-red); }     /* 3.78:1 -> 5.87:1 */
.chip-gray  { background-color: rgba(111, 107, 100, 0.12); color: var(--on-tint-gray); }  /* 4.53:1 -> 6.54:1 */
/* The "Absent" state. Amber as text needs --warning-text; --warning itself is
   2.31:1 on --light-gray, which is roughly invisible. */
.chip-warning { background-color: var(--light-gray); color: var(--warning-text); }         /* 2.31:1 -> 5.97:1 */

.sign-action {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    border: none;
    border-radius: var(--border-radius);
    padding: 9px 16px;
    font-size: 0.9rem;
    font-weight: 600;
    color: white;
    cursor: pointer;
    transition: background-color 0.15s, opacity 0.15s;
    white-space: nowrap;
}
/* All three carry white text, so the surface must clear 4.5:1 against white.
   The brand hues did not: --secondary 3.48:1, --danger 4.41:1, --warning
   2.75:1. */
.sign-action.sign-in { background-color: var(--success-strong); }      /* 3.48:1 -> 5.02:1 */
.sign-action.sign-in:hover { background-color: var(--success-stronger); }
.sign-action.sign-out { background-color: var(--danger-strong); }      /* 4.41:1 -> 5.99:1 */
.sign-action.sign-out:hover { background-color: var(--danger-stronger); }
/* Was styled only by an inline `background-color: var(--warning)` in
   staff_attendance.html — 2.75:1 white-on-amber, the worst pair in the app. */
.sign-action.mark-absent { background-color: var(--warning-strong); }  /* 2.75:1 -> 5.93:1 */
.sign-action.mark-absent:hover { background-color: var(--warning-text); }
.sign-action.is-busy,
.sign-action:disabled { opacity: 0.6; cursor: default; }

@media (max-width: 768px) {
    /* minmax(0, 1fr), not 1fr: a grid track's floor is its content's
       min-content width, and a card's min-content is the photo + status block
       + the longest unbreakable word of the name — 383px for
       "Anastasia Konstantinopoulou" on a 390px phone, so the page scrolled
       sideways. With a zero floor the card takes the column and the name wraps. */
    .staff-grid { grid-template-columns: minmax(0, 1fr); }
}

.quick-hint {
    display: flex;
    align-items: center;
    gap: 8px;
    background-color: rgba(47, 143, 131, 0.09);
    color: var(--primary-dark);
    border-radius: var(--border-radius);
    padding: 10px 14px;
    margin: 0 0 16px;
    font-size: 0.9rem;
}
.quick-hint i { flex-shrink: 0; }

.child-card.is-busy {
    opacity: 0.55;
    pointer-events: none;
}

/* === Super Admin console (P6) === */
.stat-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
    gap: 1rem;
    margin: 1rem 0 1.5rem;
}
/* This block (Super Admin / Billing / API keys) was written against a second,
   flat-UI palette — #2c3e50, #27ae60, #2980b9, #e67e22, #c0392b, 8px/4px radii,
   #e3e3e3 borders, #555/#666 greys. Converted onto the ADR-045 tokens. Class
   names are unchanged; templates depend on them. */
.stat-card {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--border-radius);
    padding: 1rem;
    display: flex;
    flex-direction: column;
    align-items: center;
}
.stat-num { font-size: 2rem; font-weight: 700; color: var(--text); }   /* was #2c3e50, 10.98:1 -> 14.34:1 */
.stat-label { color: var(--muted); font-size: .85rem; margin-top: .25rem; }   /* was #666, 5.74:1 -> 5.30:1, still AA */
/* Solid status pills carrying white text, so each surface must clear 4.5:1
   against white — three of the five did not. */
.badge { padding: .15rem .5rem; border-radius: 999px; font-size: .8rem; color: #fff; }
.badge-active { background: var(--success-strong); }    /* was #27ae60, 2.87:1 -> 5.02:1 */
.badge-trialing { background: var(--primary-strong); }  /* was #2980b9, 4.30:1 -> 5.93:1 */
.badge-past_due { background: var(--warning-strong); }  /* was #e67e22, 2.85:1 -> 5.93:1 */
.badge-suspended { background: var(--danger-strong); }  /* was #c0392b, 5.44:1 -> 5.99:1 */
.badge-canceled { background: var(--on-tint-gray); }    /* was #7f8c8d, 3.48:1 -> 7.65:1 */
.inline-search { display: flex; gap: .5rem; margin-bottom: 1rem; }
.status-actions { margin: 1rem 0; }

/* === Billing (P7) === */
.plan-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
    gap: 1rem;
    margin: 1rem 0;
}
.plan-card {
    border: 1px solid var(--border);
    border-radius: var(--border-radius);
    padding: 1rem;
    background: var(--surface);
}
.plan-current { border-color: var(--success-strong); box-shadow: 0 0 0 2px rgba(46, 125, 87, .2); }
.plan-price { font-size: 1.4rem; font-weight: 700; color: var(--text); }        /* was #2c3e50 */
.plan-limits { list-style: none; padding: 0; margin: .5rem 0; font-size: .9rem; color: var(--muted); }   /* was #555, 7.46:1 -> 5.30:1, still AA */
.plan-limits li { padding: .1rem 0; }
.plan-badge { color: var(--on-tint-green); font-weight: 600; }                  /* was #27ae60, 2.87:1 -> 7.53:1 */
.cancel-form { margin-top: 1.5rem; }
.billing-current { margin-bottom: 1rem; }

/* === API keys (P9 API) === */
/* Was a Material-amber box (#fff8e1 / #ffd54f) wrapping a neon-lime-on-slate
   code block (#aeea00 on #263238). Same "here is your one-time secret" job, on
   the amber tint the rest of the app uses for it (cf. .past-day-banner), and a
   warm near-black code block. Contrast goes 9.13:1 -> 13.74:1, so the change is
   purely a palette one. */
.new-key-box { background: rgba(224, 134, 58, 0.12); border: 1px solid rgba(224, 134, 58, 0.45); }
.new-key { display: block; margin-top: .5rem; padding: .5rem; background: var(--text);
    color: var(--light); border-radius: var(--border-radius); word-break: break-all; font-size: .9rem; }
.checkbox-row { display: block; font-weight: normal; margin: .2rem 0; }

/* === Access keys (ADR-064) ===
   superadmin/keys.html and portal/billing.html were written against classes no
   stylesheet defined, so the issue form was a raw browser fieldset, help text
   was full-strength body copy, and Set / Suspend were unstyled grey system
   buttons beside a full-width number box. */
.muted { color: var(--muted); font-size: .92rem; }
.mono { font-family: ui-monospace, SFMono-Regular, Consolas, monospace; }
.selectable { user-select: all; overflow-wrap: anywhere; }
.callout {
    padding: 16px 20px;
    margin-bottom: 20px;
    border-radius: var(--border-radius);
    border: 1px solid var(--border);
    background-color: var(--surface);
}
/* The same 15% green tint as .chip-green; body text stays --dark on it. */
.callout-success {
    background-color: rgba(61, 154, 109, 0.15);
    border-color: var(--secondary);
}
.callout h3 { margin-bottom: 8px; }
.stack-form { max-width: 480px; margin-bottom: 24px; }
.stack-form fieldset {
    margin: 0;
    padding: 16px 20px 20px;
    border: 1px solid var(--border);
    border-radius: var(--border-radius);
    background-color: var(--surface);
}
.stack-form legend { padding: 0 6px; font-weight: 600; }
.stack-form .muted { margin-top: -6px; }
/* Several small forms side by side in one table cell (allowance, suspend,
   revoke). */
.inline-form {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    margin: 0 6px 6px 0;
    vertical-align: middle;
}
.input-narrow { width: 6rem; margin-bottom: 0; padding: 8px 10px; }
/* The neutral twin of .btn-danger-sm, styled like .nav-button. */
.btn-secondary-sm {
    background-color: var(--light-gray);
    color: var(--dark);
    border: 1px solid var(--border);
    padding: .35rem .7rem;
    border-radius: var(--border-radius);
    cursor: pointer;
    font-size: .85rem;
    font-weight: 500;
}
.btn-secondary-sm:hover { background-color: var(--surface); border-color: var(--muted); }

/* === Accessibility (a11y pass) === */
.skip-link {
    position: absolute;
    left: -9999px;
    top: 0;
    background: var(--text);   /* was #2c3e50 (off-brand slate): 10.98:1 -> 14.34:1 */
    color: #fff;
    padding: .6rem 1rem;
    z-index: 1000;
    border-radius: 0 0 var(--border-radius) 0;
}
.skip-link:focus {
    left: 0;
}
main:focus {
    outline: none;
}
/* Visible keyboard focus everywhere (was relying on browser default only).
   This is the single source of truth for the focus ring — do not add another
   one earlier in the file, it will lose on source order. Solid --primary:
   3.91:1 against --surface and 3.59:1 against --bg, both clearing the 3:1 that
   WCAG SC 1.4.11 asks of a focus indicator. It was #2980b9 (off-brand blue),
   and the teal rule it overrode was rgba(...,0.45), too faint to see. */
a:focus-visible,
button:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible,
[tabindex]:focus-visible {
    outline: 3px solid var(--primary);
    outline-offset: 2px;
}
/* Screen-reader-only helper. */
.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;
}
@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
        scroll-behavior: auto !important;
    }
}

/* === Manage-pages controls (children/staff/menus tables) === */
.button-container {
    margin-bottom: 20px;
}

.filter-container {
    display: flex;
    align-items: center;
    gap: 12px;
    flex-wrap: wrap;
    background-color: white;
    padding: 12px 15px;
    border-radius: var(--border-radius);
    box-shadow: var(--box-shadow);
    margin-bottom: 20px;
}
.filter-label { font-weight: 600; color: var(--gray); }
.filter-options { display: flex; gap: 8px; }

.filter-btn {
    background-color: var(--light);
    border: 1px solid #E7E2DA;
    color: var(--dark);
    padding: 7px 16px;
    border-radius: var(--border-radius);
    cursor: pointer;
    font-weight: 500;
    transition: background-color 0.15s, color 0.15s, border-color 0.15s;
}
.filter-btn:hover { background-color: #E7E2DA; }
.filter-active {
    background-color: var(--primary-strong);   /* 3.91:1 -> 5.93:1 */
    border-color: var(--primary-strong);
    color: white;
}

.table-container {
    background-color: white;
    border-radius: var(--border-radius);
    box-shadow: var(--box-shadow);
    padding: 8px;
    overflow-x: auto;
}

.action-btn {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    background-color: var(--light);
    border: 1px solid #E7E2DA;
    color: var(--dark);
    padding: 6px 12px;
    border-radius: var(--border-radius);
    cursor: pointer;
    font-size: 0.85rem;
    font-weight: 500;
    margin: 2px;
    transition: background-color 0.15s, color 0.15s, border-color 0.15s;
}
.action-btn:hover { background-color: #E7E2DA; }
/* Hover fills carry white text, so they use the text-safe partners. */
.action-btn.edit-btn:hover { background-color: var(--primary-strong); border-color: var(--primary-strong); color: white; }
.action-btn.activate-btn:hover { background-color: var(--success-strong); border-color: var(--success-strong); color: white; }
.action-btn.deactivate-btn:hover,
.action-btn.delete-btn:hover { background-color: var(--danger-strong); border-color: var(--danger-strong); color: white; }

/* Manage Staff list: one row per person, actions pinned right. */
.staff-row-inner {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 12px 0;
    border-bottom: 1px solid var(--border);
}
.staff-row:last-child .staff-row-inner { border-bottom: none; }
.staff-row-who {
    display: flex;
    align-items: center;
    gap: 12px;
    min-width: 0;
}
.staff-row-actions {
    display: flex;
    gap: 8px;
    flex-shrink: 0;
}
/* Icon-only actions. 44px square because they are the only way to edit or
   delete a person and are used on a touchscreen; they were 33x27. White text
   needs the -strong surfaces (the brand hues are below 4.5:1). */
.icon-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    border: none;
    border-radius: var(--border-radius);
    background-color: var(--primary-strong);
    color: white;
    cursor: pointer;
}
.icon-btn:hover { background-color: var(--primary-stronger); }
.icon-btn-danger { background-color: var(--danger-strong); }
.icon-btn-danger:hover { background-color: var(--danger-stronger); }

.menu-card {
    background-color: white;
    border-radius: var(--border-radius);
    box-shadow: var(--box-shadow);
    padding: 16px;
    margin-bottom: 15px;
    border-left: 5px solid var(--primary);
}

/* === Food attendance page === */
.meal-selector {
    background-color: white;
    border-radius: var(--border-radius);
    box-shadow: var(--box-shadow);
    padding: 16px 20px;
    margin-bottom: 20px;
}
.meal-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 12px;
}
.meal-header h2 { margin: 0; font-size: 1.2rem; }
.meal-tabs {
    display: flex;
    gap: 8px;
    flex-wrap: wrap;
}
.meal-times {
    margin-top: 12px;
    color: var(--gray);
    font-size: 0.9rem;
}
.meal-times p { margin: 0; }
.meal-attendance {
    display: flex;
    align-items: center;
    gap: 10px;
}

/* === Settings cards + feature toggle switches === */
.settings-card {
    background-color: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--border-radius);
    box-shadow: var(--box-shadow);
    padding: 20px;
    margin-bottom: 20px;
}
.settings-card-title { font-size: 1.3rem; margin: 0 0 6px; }
.settings-card-hint { color: var(--muted); margin: 0 0 8px; font-size: 0.92rem; }

.feature-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 16px;
    padding: 14px 0;
    border-top: 1px solid var(--border);
    cursor: pointer;
}
.feature-row.is-core { cursor: default; }
.feature-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.feature-name { font-weight: 600; }
.feature-name i { color: var(--primary); margin-right: 8px; width: 18px; text-align: center; }
.feature-desc { color: var(--muted); font-size: 0.88rem; }
.feature-core-badge {
    flex-shrink: 0;
    font-size: 0.78rem;
    font-weight: 600;
    color: var(--on-tint-green);   /* was --secondary-dark: 3.86:1 on --secondary-light -> 5.79:1 */
    background-color: var(--secondary-light);
    padding: 5px 12px;
    border-radius: 999px;
}

/* Toggle switch */
.switch { position: relative; display: inline-flex; flex-shrink: 0; }
.switch input {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    margin: 0;
    opacity: 0;
    cursor: pointer;
}
.switch-track {
    width: 48px;
    height: 28px;
    border-radius: 999px;
    background-color: #D8D2C8;
    transition: background-color 0.15s;
    display: inline-block;
    position: relative;
}
.switch-thumb {
    position: absolute;
    top: 3px;
    left: 3px;
    width: 22px;
    height: 22px;
    background-color: #fff;
    border-radius: 50%;
    box-shadow: 0 1px 2px rgba(43, 42, 40, 0.25);
    transition: transform 0.15s;
}
.switch input:checked + .switch-track { background-color: var(--primary); }
.switch input:checked + .switch-track .switch-thumb { transform: translateX(20px); }
/* The real <input> is transparent and stacked over the track, so the ring has
   to be drawn on the track. Matches the global focus ring above. */
.switch input:focus-visible + .switch-track {
    outline: 3px solid var(--primary);
    outline-offset: 2px;
}

/* --- roster photo cell (manage children / staff) ------------------------- */
/* A face is the fastest way for staff to confirm the right child at pickup,
   so the cell is a real control: click to replace, with a text fallback for
   keyboard and screen-reader users. */
.photo-cell { width: 64px; }
.photo-thumb {
    width: 44px; height: 44px; border-radius: 50%;
    object-fit: cover; display: block; background: var(--light-gray);
}
.photo-placeholder {
    width: 44px; height: 44px; border-radius: 50%;
    background: var(--primary-light);
    /* Was --surface (white) on the pale teal tint: 1.22:1 — the child's initial
       was invisible, which defeats the whole point of the cell. */
    color: var(--primary-strong);   /* 4.86:1 */
    display: flex; align-items: center; justify-content: center;
    font-weight: 600; font-size: 0.95rem;
}
.photo-button {
    background: none; border: none; padding: 0; cursor: pointer;
    border-radius: 50%;
}
.photo-button:focus-visible { outline: 3px solid var(--primary); outline-offset: 2px; }

/* --- application-wide flash region -------------------------------------- */
/* One place, one style. Previously copied into 25 templates in three different
   looks and absent from thirteen more, so a refused action on any kiosk page
   was indistinguishable from a successful one. */
.flash-region:empty { display: none; }
.flash-region {
    max-width: 1100px;
    margin: 16px auto 0;
    padding: 0 20px;
}
.flash-message {
    /* Dark teal on the soft teal tint: 4.86:1, past the WCAG AA 4.5:1 floor for
       body text — the previous ad-hoc variants included white-on-tint, which is
       not readable. (The comment here used to claim ~7:1; measured, it is
       4.86:1. Still a pass, so the colours are unchanged.) */
    background-color: var(--primary-light);
    color: var(--primary-strong);
    border-left: 4px solid var(--primary);
    border-radius: var(--border-radius);
    padding: 12px 16px;
    margin-bottom: 12px;
    font-weight: 500;
}

/* --- Viewing a day other than today -----------------------------------------
   Quick mode stamps the time *now*, so it cannot apply to another day. The
   banner says which day is on screen and the read-only state takes the tap
   affordance away, rather than letting a card look tappable and then refusing.
   Both matter: the screen saying one date while a tap wrote to another is how a
   child who is in the building gets recorded as collected. */
.past-day-banner {
    display: flex;
    align-items: center;
    gap: 8px;
    background-color: rgba(224, 134, 58, 0.12);
    color: var(--warning-text);   /* now a real :root token; 5.71:1 on this tint */
    border: 1px solid rgba(224, 134, 58, 0.45);
    border-radius: var(--border-radius);
    padding: 10px 14px;
    margin: 0 0 16px;
    font-weight: 600;
}

.past-day-banner[hidden] { display: none; }
.past-day-banner i { flex-shrink: 0; }

.child-card.is-readonly {
    cursor: default;
    opacity: 0.85;
}

.child-card.is-readonly:hover {
    /* Cancel the lift that says "this does something". */
    transform: none;
    box-shadow: var(--box-shadow);
}


/* =============================================================================
   Admin portal & Super Admin console
   -----------------------------------------------------------------------------
   Lifted out of templates/portal/_ui_head.html so these rules live with the
   rest of the design system: as a template partial they sat outside
   tests/test_contrast.py, which is the guard that stops a colour pair silently
   regressing, and they gave the product a second place where a card or a chip
   was defined. Every colour pair below reuses a token that test already checks.
   ============================================================================= */
    /* --- Dashboard information architecture (audit 6.1) ------------------
       The portal home used to be one flat grid of fifteen equal-weight tiles.
       These rules give the grouped version its rhythm: a section is a heading,
       an optional one-line explanation, then its grid. */
    .portal-section { margin: 2rem 0 0; }
    .portal-section:first-of-type { margin-top: 1.25rem; }
    .portal-section-title {
        font-size: 1.15rem; margin: 0 0 .25rem;
        padding-bottom: .35rem; border-bottom: 1px solid var(--border);
    }
    .portal-section-sub { color: var(--muted); font-size: .9rem; margin: .4rem 0 0; }
    .portal-section .portal-grid { margin-top: .75rem; }

    /* Developer tools sit behind a disclosure: a director never needs them,
       and they were competing for attention with "Late sign-outs". */
    .portal-disclosure { margin-top: 2rem; }
    .portal-disclosure > summary {
        cursor: pointer; font-weight: 600; padding: .5rem 0;
        border-top: 1px solid var(--border); color: var(--muted);
        list-style: revert;
    }
    .portal-disclosure > summary:hover { color: var(--dark); }
    .portal-disclosure > summary:focus-visible {
        outline: 3px solid var(--primary); outline-offset: 2px;
    }
    .portal-disclosure .portal-grid { margin-top: .75rem; }

    /* --- Empty states (audit 7.3) ----------------------------------------
       Every empty state names the next action, in the shape backups.html and
       late_signouts.html already used. */
    .empty-state { text-align: center; padding: 28px 16px; color: var(--muted); }
    .empty-state > i {
        display: block; font-size: 2.2rem; color: var(--primary);
        margin-bottom: 10px;
    }
    .empty-state-title { margin: 0 0 6px; font-weight: 600; color: var(--dark); }
    .empty-state-hint { margin: 0 auto; max-width: 52ch; font-size: .92rem; }
    /* .empty-message is a kiosk grid tile (white card). Inside a table cell the
       card treatment reads as a phantom row, so strip it back. */
    td.empty-message, th.empty-message {
        background: transparent; box-shadow: none; padding: 0;
    }

    /* --- Status chips (audit 8.3) ----------------------------------------
       Raw enum values ("past_due", "trialing") were printed straight from the
       database. status_text() in portal/_ui.html humanises the word; these
       carry the tone. Deliberately NOT reusing .badge: .badge sets color:#fff
       and only five .badge-<status> backgrounds exist, so any status outside
       that set rendered white-on-white. */
    /* The single definition, serving both the kiosk chips (icon + label, which
       is why this is inline-FLEX with a gap rather than inline-block) and the
       portal's. */
    .status-chip {
        display: inline-flex; align-items: center; gap: 5px;
        padding: 3px 10px; border-radius: 999px;
        font-size: .8rem; font-weight: 600; white-space: nowrap;
    }
    .status-chip-ok { background: rgba(61, 154, 109, 0.15); color: var(--secondary-dark); }
    /* Each tint/foreground pair below is one that style.css already ships and
       tests/test_contrast.py already validates at WCAG AA (.badge-green,
       .badge-red, .mode-pill-quick, .backup-pill-none) — reused verbatim so
       these chips inherit that guarantee. */
    .status-chip-warn { background: rgba(224, 134, 58, 0.15); color: #B45309; }
    .status-chip-bad { background: rgba(198, 85, 59, 0.12); color: var(--danger); }
    .status-chip-muted { background: var(--light-gray); color: var(--muted); }

    /* --- Scrollable table regions (audit 4.1) -----------------------------
       .table-container is tabbable so a keyboard user can reach the scrollbar;
       it therefore needs a visible focus ring. */
    .table-container:focus-visible {
        outline: 3px solid var(--primary); outline-offset: 2px;
    }

    /* --- Card-stack fallback for wide tables (audit 4.1) ------------------
       A 7-column table that scrolls sideways on a phone technically works and
       is practically useless. Below 768px the marked tables become one card
       per row, each cell labelled from its data-label attribute.

       768px, not 760: the grid breakpoints above use 768, and the eight pixels
       between the two left a band where the grids were in phone layout while
       the tables were still in desktop scroll. */
    @media (max-width: 768px) {
        .stack-table thead {
            position: absolute; width: 1px; height: 1px;
            overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap;
        }
        /* The cells too, not just their row group: clipped, the header row
           was still laid out at full table width, so each <th> reported a box
           up to 280px past the right edge of a phone — invisible, but exactly
           what a layout check (or a browser deciding scroll extent) measures.
           Still in the accessibility tree; the cards carry the same labels. */
        .stack-table thead th {
            position: absolute; width: 1px; height: 1px; padding: 0;
            overflow: hidden; clip: rect(0 0 0 0);
        }
        .stack-table,
        .stack-table tbody,
        .stack-table tr,
        .stack-table td {
            display: block; width: 100%;
            /* The card's padding and border inside its 100%, not added to it:
               a stacked row was 26px wider than its table and the empty-state
               card was clipped at the right edge on a phone. */
            box-sizing: border-box;
        }
        .stack-table tr {
            border: 1px solid var(--border);
            border-radius: var(--border-radius);
            padding: 8px 12px; margin-bottom: 12px;
            background-color: var(--surface);
        }
        .stack-table tr:hover td { background-color: transparent; }
        /* Label floated left, value flowing beside it. This was a flex row,
           which made every node in the cell its own column: "owner@… <br>
           <span>user</span>" squeezed the second line to one letter per
           line, and "4 / 50 children (account total)" split into two
           columns. Inline flow keeps a mixed value together, and a value too
           long to fit beside the label drops below it at full width. */
        .stack-table td {
            border: none; padding: 6px 0;
            display: flow-root;
            text-align: right;
        }
        .stack-table td::before {
            content: attr(data-label);
            float: left; width: 42%;
            box-sizing: border-box; padding-right: 14px;
            font-weight: 600; color: var(--muted);
            text-align: left;
        }
        /* Action cells carry no label; and the empty state is not a record. */
        .stack-table td:not([data-label])::before,
        .stack-table td[data-label=""]::before { content: none; }
        .stack-table td.empty-message { display: block; }
        .stack-table td.empty-message::before { content: none; }
    }

/* --- Admin elevation notice --------------------------------------------------
   The kiosk tablet sits in an entrance and stays signed in all day by design.
   The PIN elevation layered on top of it is the dangerous part, so when it is
   active the main menu says so and offers a one-tap way to drop it. */
.admin-unlocked {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 12px;
    margin: 20px 0 0;
    padding: 12px 16px;
    background-color: rgba(224, 134, 58, 0.12);
    border: 1px solid rgba(224, 134, 58, 0.45);
    border-radius: var(--border-radius);
    color: var(--warning-text);
    font-weight: 600;
}

.admin-unlocked .btn-secondary { width: auto; }
.admin-lock-form { display: inline-block; margin-left: 16px; }

/* --- Allergy marker ----------------------------------------------------------
   Sits in the name cell because that is the cell staff scan, and an allergy is
   the one detail that must never be a click away. Amber is the surface here;
   the text uses the readable counterpart (--warning is 2.75:1 and never
   carries text — see tests/test_contrast.py). */
.allergy-flag {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    margin-left: 8px;
    padding: 2px 8px;
    border-radius: 999px;
    background-color: rgba(224, 134, 58, 0.15);
    color: var(--warning-text);
    font-size: 0.8rem;
    font-weight: 600;
}

.field-hint {
    display: block;
    margin-top: 4px;
    color: var(--muted);
    font-size: 0.85rem;
}

/* Plan features read as description, not as a checklist of entitlements —
   nothing enforces them yet (see app/billing.py). The limits above them are
   what the product actually checks. */
.plan-features {
    margin: 8px 0 0;
    color: var(--muted);
    font-size: 0.9rem;
}

/* =============================================================================
   Narrow viewports
   -----------------------------------------------------------------------------
   The kiosk is a wall-mounted tablet and is not used on a phone. The parent
   portal and the admin portal are — a parent checks their child's attendance on
   the way to work, a director reads the late sign-out list standing in a
   corridor. Everything below either undoes a kiosk rule that leaked into the
   portal, or lets an existing row wrap. None of it changes the desktop layout.
   ========================================================================== */

/* portal/my_children.html wraps each child in `.child-card`, which is the
   KIOSK roster card: a horizontal flex row, vertically centred, with a hover
   lift and a pointer cursor. So the child's name, the date-of-birth line, the
   health information and the whole five-column attendance table were laid out
   side by side and squeezed to min-content — roughly 700px of irreducible
   width inside a 320px screen. The page scrolled sideways at every width and
   the table was unreadable on a phone. Scoped to <section> so the kiosk card,
   which is a <div>, is untouched. */
section.child-card {
    display: block;
    cursor: default;
}
section.child-card:hover {
    transform: none;
    box-shadow: var(--box-shadow);
}

/* `.inline-search` is a non-wrapping flex row, and the attendance-summary
   filter puts two date inputs, a location select and a submit button in it —
   about 600px that cannot shrink, because a flex item's min-width defaults to
   its content. On a phone the Apply button was simply off the right edge, so
   a director could not change the reporting period at all. */
.inline-search {
    flex-wrap: wrap;
    align-items: end;
}
.inline-search .form-group {
    margin-bottom: 0;
    flex: 1 1 11rem;
    min-width: 0;
}
.inline-search .form-group input,
.inline-search .form-group select,
.inline-search > input,
.inline-search > select {
    margin-bottom: 0;
}
.inline-search > input,
.inline-search > select {
    flex: 1 1 11rem;
    min-width: 0;
}
.inline-search > label {
    margin-bottom: 0;
    align-self: center;
}
/* .btn-primary is width:100% by design; inside a flex row that made the submit
   button claim the whole bar and stretch to its full height. */
.inline-search .btn-primary,
.inline-search .btn-secondary {
    width: auto;
    flex: 0 0 auto;
}

/* Six .nav-button anchors in the Super Admin console header. .nav-button is
   display:flex, i.e. block-level, so they stacked into a six-high column at
   every width instead of sitting in a row. */
.portal-header nav {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
}

/* The dialog footer is a flex row and .btn-primary is width:100%, so the
   confirm button claimed the bar and squeezed Cancel — the safe control — to
   its minimum. */
.modal-footer .btn-primary {
    width: auto;
}

@media (max-width: 768px) {
    /* A long activation code or email address inside a stacked table card had
       nothing to wrap on and pushed the whole page sideways. */
    .stack-table td > * {
        min-width: 0;
        /* break-word, not anywhere: anywhere also let a short button label
           break ("Susp|end", "Rev|oke") because it lowers the min-content
           width. Not on the cell's own text: there break-word splits an email
           that is merely too long to sit BESIDE the label ("sample.tes|t"),
           where without it the whole address drops below the label instead. */
        overflow-wrap: break-word;
    }

    /* .btn-primary, .btn-secondary and .nav-button all carry a 44px minimum
       already. The small family never got it — and it is the family that
       revokes access, deletes a child and closes a dialog. The modal close
       button was a 16px-wide target, which on a touchscreen is the only way
       out of a dialog. */
    .btn-sm,
    .action-btn,
    .btn-danger-sm,
    .btn-secondary-sm,
    .filter-btn {
        min-height: 44px;
        padding-top: 10px;
        padding-bottom: 10px;
    }
    .close-button {
        min-width: 44px;
        min-height: 44px;
    }
    .text-button {
        min-height: 44px;
        padding: 10px 8px;
    }
}

/* Forms page: Children and Staff side by side. A fixed two-column grid kept
   both columns on a phone, each too narrow for "Food Enrollment Form" plus its
   count, and the page scrolled 110px sideways. One column below 640px. */
.forms-columns {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 20px;
    margin-bottom: 30px;
}
@media (max-width: 640px) {
    .forms-columns {
        grid-template-columns: 1fr;
    }
}

/* Tables that cannot stack (their markup carries no data-label) at least stop
   spending 96px of a 320px screen on cell padding. */
@media (max-width: 560px) {
    .table-container {
        padding: 4px;
    }
    .data-table th,
    .data-table td {
        padding: 8px 6px;
    }
}

/* Emergency contact rows inside the child dialog (never on the roster — see
   the comment in child_attendance.html for why). */
.contact-row {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 8px;
    padding: 6px 0;
    border-bottom: 1px solid var(--border);
}
.contact-row:last-child { border-bottom: none; }
.contact-rel { color: var(--muted); font-size: 0.9rem; }
.contact-phone {
    margin-left: auto;
    font-weight: 600;
    /* A phone number is the thing being reached for, so it gets the same
       text-safe teal the rest of the product uses for actions. */
    color: var(--primary-strong);
}
.contact-missing { color: var(--muted); font-weight: 400; }
