/* =============================================================================
   Peepful design tokens — "The Field Notes" (see DESIGN.md)
   Warm ink on paper. One rationed clay accent. Flat by default.
   ============================================================================= */
:root {
    /* Shoelace primary ramp, remapped to Clay (the single accent) */
    --sl-color-primary-50: #f8efe9;
    --sl-color-primary-100: #f1ddd2;
    --sl-color-primary-200: #e8c5b5;
    --sl-color-primary-300: #d9a48c;
    --sl-color-primary-400: #c57a5e;
    --sl-color-primary-500: #b1543a;
    --sl-color-primary-600: #9e4a33;
    --sl-color-primary-700: #843a29;
    --sl-color-primary-800: #6b3022;
    --sl-color-primary-900: #54271c;
    --sl-color-primary-950: #3a1a13;

    /* Warm neutral surfaces for Shoelace components (panels, inputs, light fills) */
    /* Shoelace's neutral ramp, warmed. Only the three surface steps used to be
       overridden, so everything from 200 up stayed Shoelace's zinc — hue 240,
       a cold blue-grey — and the app reaches for 200/300 as border tones and
       700 as text. That put cold grey in the account menu and cold hairlines
       around every dropdown panel, against a rule that says tint every neutral
       warm.

       Each step below is its zinc counterpart at the *same* OKLCH lightness on
       the palette's warm axis (hue 78, chroma 0.012), so contrast against
       paper is unchanged to within 0.01 at every step and only the hue moves.
       The two exceptions are deliberate: 200 is Stone, the documented hairline,
       so the Shoelace border and --border-color stop being two values for one
       role; and 1000 is Ink, because pure black is banned outright. */
    --sl-color-neutral-0: #fcf9f3;
    --sl-color-neutral-50: #f6f2e9;
    --sl-color-neutral-100: #efe9dd;
    --sl-color-neutral-200: var(--border-color);
    --sl-color-neutral-300: #d9d4cc;
    --sl-color-neutral-400: #a6a19a;
    --sl-color-neutral-500: #76716a;
    --sl-color-neutral-600: #57524c;
    --sl-color-neutral-700: #433f39;
    --sl-color-neutral-800: #2b2721;
    --sl-color-neutral-900: #1b1812;
    --sl-color-neutral-950: #16130d;
    --sl-color-neutral-1000: var(--text-color);
    --sl-panel-background-color: #fcf9f3;
    --sl-input-background-color: #fcf9f3;

    /* App palette — warm ink on paper */
    --primary-color: #b1543a;    /* Clay — primary actions, links, focus, active */
    --primary-hover: #97462f;    /* Clay Deep — pressed/hover on clay */
    --clay-tint: #f1ddd2;        /* Wash behind avatars / clay chips */
    /* Clay on Clay Tint is 3.81:1: under the 4.5:1 that avatar initials and
       chip labels need at 14–17px. Only the one 2rem glyph clears it as large
       text, and a rule with a size exception is a rule nobody applies — so all
       clay text on --clay-tint takes this ink, which is Clay Deep named for
       the role rather than for a hover state. */
    --clay-on-tint: #97462f;     /* 4.95:1 on --clay-tint */
    /* Clay marks things well but reads marginally as body-size text: #b1543a
       is 4.48:1 on paper and 4.14:1 on a sunken row, both under 4.5. Link
       text therefore takes the deeper clay, which clears it on all three
       surfaces (5.81 / 6.18 / 5.37). Same ink as Clay Deep, named for the
       role — DESIGN.md still describes links as Clay and is due a pass. */
    --link-color: #97462f;       /* 5.81:1 on --bg-color */
    --secondary-color: #6f675b;  /* Taupe */
    --secondary-hover: #565047;
    --success-color: #5e7a4f;    /* Moss */
    --error-color: #9f4030;      /* Brick */
    --error-hover: #8a3727;      /* Brick Deep — pressed/hover on brick */
    --warning-color: #bd7e36;    /* Ochre */
    --text-color: #25221c;       /* Ink */
    --text-muted: #6f675b;       /* Taupe */
    --bg-color: #f6f2e9;         /* Paper — the page field */
    --card-bg: #fcf9f3;          /* Paper Surface — records, header, footer */
    --paper-sunken: #efe9dd;     /* Hover fills, inset wells */
    --border-color: #e6ddcf;     /* Stone — the 1px structural border */

    /* Elevation — flat at rest; warm lift only on hover, clay ring on focus */
    --hover-lift: 0 1px 2px rgba(40, 33, 24, 0.05), 0 6px 20px rgba(40, 33, 24, 0.07);
    --focus-ring: 0 0 0 3px rgba(177, 84, 58, 0.20);

    /* Typography — one voice */
    --sl-font-sans: 'IBM Plex Sans', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
    --sl-font-size-medium: 1rem;

    /* The small end of the ramp, as two roles rather than a dozen near-misses.
       Body is 1rem; below it the interface only ever does two jobs, so it only
       gets two steps. Anything tempted toward a third — 0.82, 0.85, 0.9 — is a
       step nobody can see, and picking one is a decision nobody can defend.
       Reach for the role, never a literal. */
    --text-display: clamp(2rem, 5vw, 2.5rem);  /* marketing hero only */
    --text-headline: 1.6rem;  /* page titles on primary surfaces, prominent figures */
    --text-title: 1.25rem;    /* utility page titles, section and card headings, lead */
    /* Body is 1rem and needs no token: it is what everything inherits. */
    --text-meta: 0.875rem;  /* 14px — secondary text, helper/error, control labels */
    --text-label: 0.75rem;  /* 12px — uppercase tracked labels, compact chips */

    /* Icons are sized with font-size but are NOT on the type ramp — an icon step
       answers "how big is this glyph", not "what is this text for". Keeping them
       separate is what stops the next consolidation from resizing every icon in
       the app. Sizes tuned to a specific container (avatar circles) stay local. */
    --icon-xs: 0.9rem;   /* riding alongside meta text: pills, row metadata */
    --icon-sm: 1.25rem;  /* inline with text: nav, card and row icons */
    --icon-md: 1.5rem;   /* section, form and brand marks */
    --icon-lg: 2rem;     /* page-header marks, upload zones */
    --icon-xl: 3rem;     /* empty states, success confirmations */

    /* Radius — crisp, lightly rounded */
    --sl-border-radius-small: 3px;
    --sl-border-radius-medium: 6px;
    --sl-border-radius-large: 8px;
}

/* Reset and base styles */
*, *::before, *::after {
    box-sizing: border-box;
    margin: 0;
    padding: 0;
}

/* Prevent the flash of undefined custom elements (FOUCE).
   The nav and much of the UI are Shoelace web components registered by JS via
   the autoloader. Until they're defined, the browser paints them as raw,
   unstyled markup. Hide undefined custom elements until Shoelace upgrades them;
   visibility (not display) keeps their layout space so nothing jumps. */
:not(:defined) {
    visibility: hidden;
}

/* An undefined <sl-icon> is an empty inline box with no width, so text sitting
   next to one slides sideways the moment it upgrades. Reserve the 1em box the
   defined icon will occupy. */
sl-icon:not(:defined) {
    display: inline-block;
    width: 1em;
    height: 1em;
}

/* The reset above is a document-origin rule, and those outrank the ::slotted()
   spacing a component declares inside its own shadow root — so `margin: 0` was
   quietly deleting the gap Shoelace puts around slotted prefix and suffix
   icons, leaving every search and sort glyph jammed against its field border.
   Restore Shoelace's intended values from Shoelace's own tokens, so these track
   the theme rather than becoming a second set of magic numbers.
   (sl-button is unaffected: it spaces its prefix with an internal gap.) */
sl-input,
sl-select {
    --field-slot-spacing: var(--sl-input-spacing-medium);
}

sl-input[size="small"],
sl-select[size="small"] {
    --field-slot-spacing: var(--sl-input-spacing-small);
}

sl-input[size="large"],
sl-select[size="large"] {
    --field-slot-spacing: var(--sl-input-spacing-large);
}

sl-input > [slot="prefix"] {
    margin-inline-start: var(--field-slot-spacing);
}

sl-input > [slot="suffix"] {
    margin-inline-end: var(--field-slot-spacing);
}

/* A select's prefix leads its label, so its spacing falls on the other side. */
sl-select > [slot="prefix"] {
    margin-inline-end: var(--field-slot-spacing);
}

sl-select > [slot="suffix"] {
    margin-inline-start: var(--sl-spacing-small);
}

/* A select renders its value into an <input> styled `text-overflow: clip`, so a
   label one pixel too wide is chopped mid-glyph with nothing to say it was. The
   controls below are sized to hold their longest option outright; this is the
   valve for the day someone adds a longer one. */
sl-select::part(display-input) {
    text-overflow: ellipsis;
}

body {
    font-family: var(--sl-font-sans);
    line-height: 1.6;
    color: var(--text-color);
    background-color: var(--bg-color);
    min-height: 100vh;
    display: flex;
    flex-direction: column;
}

.container {
    max-width: 1200px;
    margin: 0 auto;
    padding: 0 1rem;
    width: 100%;
}

/* Header */
.app-header {
    /* Settled height of a medium Shoelace control (--sl-input-height-medium).
       The header row reserves it so the nav can't change the header's height as
       it upgrades; see the FOUCE note below. */
    --nav-control-height: 2.5rem;

    background-color: var(--card-bg);
    border-bottom: 1px solid var(--border-color);
    padding: 0.75rem 0;
    position: sticky;
    top: 0;
    z-index: 100;
}

/* Touch input: 44px minimum target, so the row grows to match. */
@media (pointer: coarse) {
    .app-header {
        --nav-control-height: 2.75rem;
    }
}

.header-inner {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 1rem;
    min-height: var(--nav-control-height);
}

.brand {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    font-size: var(--text-title);
    font-weight: 600;
    color: var(--text-color);
    text-decoration: none;
}

.brand:hover {
    color: var(--primary-color);
}

.brand-icon {
    font-size: var(--icon-md);
    color: var(--primary-color);
}

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

/* Before the autoloader upgrades them, these controls are raw inline text that
   wraps into a tall stack — the header used to load at 259px and snap to 65px.
   The global :not(:defined) rule hides that text but keeps its layout box, so
   take the un-upgraded controls out of flow entirely. With .header-inner
   holding the row open, first paint and settled paint are the same geometry:
   the nav fades in where it already had room, instead of resizing the page. */
.app-nav > :not(:defined) {
    display: none;
}

.app-nav sl-button::part(base) {
    font-weight: 500;
    min-height: var(--nav-control-height);
}

/* A username is arbitrary-length text in a fixed-width row; cap it so a long
   one truncates instead of pushing the nav off-screen. */
.nav-account::part(label) {
    max-width: 14ch;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* The menu repeats the username only when the trigger has stopped showing it. */
.nav-account-name {
    display: none;
}

/* Current section: bolder, full clay, with a 2px clay underline so "you are
   here" reads clearly (the text buttons are already clay-ish) — DESIGN.md active nav. */
.app-nav sl-button.is-active::part(base) {
    color: var(--primary-color);
    font-weight: 600;
    box-shadow: inset 0 -2px 0 var(--primary-color);
}

.app-nav sl-dropdown sl-menu-item a,
.app-nav sl-dropdown sl-menu-item .logout-button {
    color: inherit;
    text-decoration: none;
    display: block;
    width: 100%;
}

/* The full-width <a>/<button> in the label slot sits flush against the prefix
   icon, so give the icon some breathing room before the text. */
.app-nav sl-dropdown sl-menu-item sl-icon[slot="prefix"] {
    margin-inline-end: 0.625rem;
}

/* Logout is a POST form (Django requires POST); make the button read as a menu link */
.app-nav sl-dropdown sl-menu-item .logout-form {
    margin: 0;
}

.app-nav sl-dropdown sl-menu-item .logout-button {
    background: none;
    border: none;
    padding: 0;
    font: inherit;
    text-align: left;
    cursor: pointer;
}

/* The header sheds one affordance at a time, in order of how little it costs.
   Each boundary is where the row actually stops fitting, measured with the real
   copy (Plex Sans loaded, a four-letter username) — full row 809px, compact
   693px with the wordmark, 599px without, 501px on the short label. Re-measure
   these when a nav item is added or renamed; the ladder is arithmetic, not
   taste. Adding Check-ins as the fifth destination moved every tier up by
   roughly 140px, which is why the labels now go at 500px rather than 379px.

     ≥809   full — icons, username, wordmark
     693    link icons off, account becomes its glyph
     599    wordmark off, brand glyph carries home
     501    "Conversations" becomes "Log"
     ≤500   labels off, five glyphs in a row

   Compact nav. The labels are what make the sections scannable, so the
   decoration goes first: link icons off, and the account control — whose label
   is a username of unknowable length — becomes the person glyph alone. */
@media (max-width: 808px) {
    .app-nav {
        gap: 0.125rem;
        /* Flex items floor at their content width unless told otherwise; without
           this the nav can't shrink and the safety valve below never engages. */
        min-width: 0;
    }

    .app-nav sl-button::part(base) {
        padding-inline: 0.625rem;
    }

    .nav-link::part(prefix) {
        display: none;
    }

    /* Safety valve, not a layout: the labels fit at every size handled below,
       but a wide fallback font mid-load, or a viewport narrower than 320px,
       would otherwise push the nav off the page. Let them truncate instead. */
    .nav-link {
        min-width: 0;
    }

    .nav-link::part(label) {
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
    }

    .nav-account::part(caret) {
        display: none;
    }

    .nav-account::part(base) {
        min-width: var(--nav-control-height);
        justify-content: center;
    }

    /* Clipped, not hidden: the username stays the trigger's accessible name. */
    .nav-account::part(label) {
        position: absolute;
        width: 1px;
        height: 1px;
        padding: 0;
        overflow: hidden;
        clip-path: inset(50%);
        white-space: nowrap;
    }

    .nav-account-name {
        display: block;
    }
}

/* The wordmark and the compact nav together need 693px of row, which no phone
   in portrait has. The wordmark is the least load-bearing thing there — the
   glyph still reads as home, and the section labels are what users aim at — so
   below 693px it goes. Clipped, not removed: the link keeps its accessible name. */
@media (max-width: 692px) {
    .brand-wordmark {
        position: absolute;
        width: 1px;
        height: 1px;
        overflow: hidden;
        clip-path: inset(50%);
        white-space: nowrap;
    }
}

/* Small tablets and large phones in landscape land here. Shrinking the type
   would undo the zoom a 200%-zoom reader asked for, so the longest label gives
   way to the product's own short name for it instead, at full size. */
.nav-label-tight {
    display: none;
}

@media (max-width: 598px) {
    .app-nav sl-button::part(base) {
        padding-inline: 0.4375rem;
    }

    .nav-label {
        display: none;
    }

    .nav-label-tight {
        display: inline;
    }
}

/* The floor: every phone in portrait lands here, because five labelled
   destinations need 501px even on the short label, and no phone has that at a
   type size worth reading. So the labels go and the row becomes five glyphs —
   every destination still visible, still one tap, still 44px under a finger,
   288px in all so a 320px 200%-zoom reader still fits. The active section
   keeps its clay underline, and the h1 below names the page the glyph led to.
   Labels are clipped rather than dropped, so the full word — "Conversations",
   not the abbreviation — is what a screen reader announces. */
@media (max-width: 500px) {
    .app-nav {
        gap: 0.25rem;
    }

    /* Carrying the label's whole job now, so sized to sit with the 24px brand
       glyph rather than to trail a word that is no longer there. */
    .nav-link::part(prefix) {
        display: flex;
        font-size: var(--icon-sm);
    }

    .nav-link::part(base) {
        min-width: var(--nav-control-height);
        padding-inline: 0;
        justify-content: center;
    }

    .nav-link::part(label) {
        position: absolute;
        width: 1px;
        height: 1px;
        padding: 0;
        overflow: hidden;
        clip-path: inset(50%);
        white-space: nowrap;
    }

    /* Announce the full word, not the row's abbreviation of it. */
    .nav-label {
        display: inline;
    }

    .nav-label-tight {
        display: none;
    }
}

/* Main content */
.main-content {
    flex: 1;
    /* padding-block, never the `2rem 0` shorthand. This element is also a
       .container, and this rule sits after it at equal specificity, so the
       shorthand's implicit horizontal 0 silently deleted the page's 1rem side
       gutter. Above 1200px the max-width's auto margins disguised it; at every
       width below that — every phone, tablet, and narrowed window — body text
       ran flush to the edge of the screen. */
    padding-block: 2rem;
}

/* Messages */
.messages {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    margin-bottom: 1.5rem;
}

.messages sl-alert {
    --sl-panel-background-color: var(--card-bg);
}

/* Page header pattern */
.page-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 1.5rem;
    gap: 1rem;
    flex-wrap: wrap;
}

.page-header h1 {
    margin: 0;
    font-size: var(--text-headline);
    /* Stating the weight, because the UA default is bold and this rule set
       only the size — so every page title in the app shipped at 700 against a
       documented Headline of 600. */
    font-weight: 600;
}

/* Hero section */
.hero {
    text-align: center;
    padding: 4rem 0;
}

/* Was an inline style attribute carrying a 4rem magic number. */
.hero-mark {
    font-size: var(--icon-xl);
    color: var(--primary-color);
    margin-bottom: 1rem;
}

.hero h1 {
    /* The one place a fluid size belongs: this is the marketing hero, and its
       range (2rem–2.5rem) is exactly what the old pair of breakpoint overrides
       was hand-stepping between. */
    font-size: var(--text-display);
    margin-bottom: 1rem;
    color: var(--text-color);
}

.hero p {
    font-size: var(--text-title);
    color: var(--text-muted);
    margin-bottom: 2rem;
}

.cta {
    display: flex;
    gap: 1rem;
    justify-content: center;
    flex-wrap: wrap;
}

/* Cards */
.sl-card, .card {
    background-color: var(--card-bg);
    border: 1px solid var(--border-color);
    border-radius: var(--sl-border-radius-large);
    padding: 1.5rem;
    transition: box-shadow 0.2s, transform 0.2s;
}

.sl-card:hover, .card:hover {
    box-shadow: var(--hover-lift);
}

.card h3 {
    margin-bottom: 0.5rem;
}

.card p {
    color: var(--text-muted);
    margin-bottom: 1rem;
}

/* Tag badges */
.tag-badge {
    display: inline-flex;
    align-items: center;
    padding: 0.25rem 0.75rem;
    border-radius: 9999px;
    font-size: var(--text-meta);
    font-weight: 500;
    border: 1px solid;
    white-space: nowrap;
}

.tag-badge-sm {
    padding: 0.125rem 0.5rem;
    font-size: var(--text-label);
}

/* Forms */
.form-page {
    max-width: 600px;
    margin: 0 auto;
}

.form-page h1 {
    margin-bottom: 1.5rem;
}

.form-group {
    margin-bottom: 1.25rem;
}

.form-group label {
    display: block;
    margin-bottom: 0.5rem;
    font-weight: 500;
    color: var(--text-color);
}

.form-actions {
    display: flex;
    gap: 0.75rem;
    margin-top: 1.5rem;
    padding-top: 1.5rem;
    border-top: 1px solid var(--border-color);
}

/* A form that is already on its way (js/submit-guard.js). Native submit buttons
   are dimmed here rather than `disabled`, which would drop a named submitter's
   value from the POST; an <sl-button> shows its own `loading` spinner instead. */
form[data-submitting] button[type="submit"] {
    opacity: 0.55;
    cursor: progress;
    pointer-events: none;
}

/* Shoelace input styling */
sl-input::part(base),
sl-textarea::part(base),
sl-select::part(combobox) {
    border-color: var(--border-color);
}

sl-input::part(base):focus-within,
sl-textarea::part(base):focus-within,
sl-select::part(combobox):focus-within {
    border-color: var(--primary-color);
    box-shadow: var(--focus-ring);
}

/* The link floor. Anything the app has not given its own colour otherwise
   falls to the browser's #0000EE (and #551A8B once visited) — a colour that
   belongs to no design system, and specifically the blue this palette exists
   to escape. Classed links override this by specificity, so it repaints
   nothing that was already deliberate; it only catches the ones nobody
   styled, like "JSON API" on the profile page and the people-list link in the
   Check-ins empty state, which is the first screen a new account sees. */
a {
    color: var(--link-color);
}

/* Hover goes Ink, the same move the nav already makes. */
a:hover {
    color: var(--text-color);
}

/* Visible keyboard focus for native interactive elements. Shoelace components
   (sl-button, sl-input, …) carry their own focus ring; this covers the native
   links and buttons they don't — person names, the +Tag/tag toggles, nav and
   menu links — which otherwise focus invisibly (a WCAG 2.4.7 failure). */
a:focus-visible,
button:focus-visible,
summary:focus-visible,
[tabindex]:focus-visible {
    outline: 2px solid var(--primary-color);
    outline-offset: 2px;
}

/* Auth forms */
.auth-form {
    max-width: 400px;
    margin: 0 auto;
    background-color: var(--card-bg);
    padding: 2rem;
    border-radius: var(--sl-border-radius-large);
    border: 1px solid var(--border-color);
}

.auth-form h1 {
    margin-bottom: 1.5rem;
    text-align: center;
}

.auth-form p {
    margin-bottom: 1rem;
}

.auth-form label {
    display: block;
    margin-bottom: 0.25rem;
    font-weight: 500;
}

.auth-form input[type="text"],
.auth-form input[type="email"],
.auth-form input[type="password"],
.auth-form textarea,
.auth-form select {
    width: 100%;
    padding: 0.75rem;
    border: 1px solid var(--border-color);
    border-radius: 0.375rem;
    font-size: 1rem;
    margin-bottom: 1rem;
    transition: border-color 0.2s, box-shadow 0.2s;
}

.auth-form input:focus,
.auth-form textarea:focus,
.auth-form select:focus {
    outline: none;
    border-color: var(--primary-color);
    box-shadow: var(--focus-ring);
}

.auth-form button {
    width: 100%;
    margin-top: 0.5rem;
}

.auth-link {
    text-align: center;
    margin-top: 1.5rem;
    color: var(--text-muted);
}

.auth-link a {
    color: var(--primary-color);
    text-decoration: none;
    font-weight: 500;
}

.auth-link a:hover {
    text-decoration: underline;
}

/* Profile page */
.profile h1 {
    margin-bottom: 1.5rem;
}

.profile h2 {
    margin-top: 2rem;
    margin-bottom: 1rem;
    font-size: var(--text-title);
}

.profile-info {
    background-color: var(--card-bg);
    padding: 1.5rem;
    border-radius: var(--sl-border-radius-large);
    border: 1px solid var(--border-color);
    margin-bottom: 1.5rem;
}

.profile-info p {
    margin-bottom: 0.5rem;
    display: flex;
    gap: 0.5rem;
}

.profile-info strong {
    min-width: 100px;
    color: var(--text-muted);
    font-weight: 500;
}

.profile form {
    background-color: var(--card-bg);
    padding: 1.5rem;
    border-radius: var(--sl-border-radius-large);
    border: 1px solid var(--border-color);
}

.profile-actions {
    margin-top: 2rem;
}

/* Footer */
.app-footer {
    background-color: var(--card-bg);
    border-top: 1px solid var(--border-color);
    padding: 1.5rem 0;
    text-align: center;
    color: var(--text-muted);
    font-size: var(--text-meta);
}

/* The one footer link sits in muted running text, so it takes the text's
   colour and keeps only the underline: the footer stays quiet. */
.app-footer a {
    color: inherit;
}

.app-footer a:hover {
    color: var(--text-color);
}

/* Changelog ("What's new"): releases ruled off by a tracked label, entries as
   plain reading text. Entry files and the release flow: docs/changelog-guide.md */
.changelog-page {
    max-width: 640px;
    margin: 0 auto;
}

.changelog-running {
    color: var(--text-muted);
    font-size: var(--text-meta);
}

.changelog-release {
    margin-bottom: 2rem;
}

.changelog-release-label {
    display: flex;
    justify-content: space-between;
    gap: 1rem;
    margin: 0 0 0.75rem;
    padding-bottom: 0.5rem;
    border-bottom: 1px solid var(--border-color);
    font-size: var(--text-label);
    font-weight: 600;
    line-height: 1.2;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--text-muted);
}

.changelog-entries {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 1rem;
}

.changelog-entry-title {
    margin: 0;
    font-size: 1rem;
    font-weight: 600;
}

.changelog-entry-body {
    margin-top: 0.25rem;
    color: var(--text-muted);
}

.changelog-entry-body p {
    margin: 0;
}

.changelog-entry-body p + p {
    margin-top: 0.5rem;
}

/* Form errors */
.errorlist {
    list-style: none;
    color: var(--error-color);
    font-size: var(--text-meta);
    margin-bottom: 0.5rem;
}

.helptext {
    font-size: var(--text-meta);
    color: var(--text-muted);
    margin-top: -0.75rem;
    margin-bottom: 1rem;
}

/* Text utilities */
.text-muted {
    color: var(--text-muted);
}

.text-center {
    text-align: center;
}

/* Empty states */
.empty-state {
    text-align: center;
    padding: 3rem;
    color: var(--text-muted);
}

/* Direct child only: the large decorative icon. A descendant selector here
   also matches the small prefix icon inside the empty state's <sl-button>,
   blowing it up to 3rem and pushing it off-center. */
.empty-state > sl-icon {
    font-size: var(--icon-xl);
    margin-bottom: 1rem;
    opacity: 0.5;
}

.empty-state p {
    margin-bottom: 1rem;
}

/* Checkbox list for tags */
.checkbox-list {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
}

.checkbox-item {
    display: flex;
    align-items: center;
    gap: 0.25rem;
    cursor: pointer;
}

.checkbox-item input {
    margin-right: 0.25rem;
}

/* Search bar */
.search-bar {
    margin-bottom: 1.5rem;
}

.search-bar sl-input {
    width: 100%;
}

/* Grid layouts */
.grid-auto {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
    gap: 1.5rem;
}

/* Status badges using Shoelace badges — earthen, never neon */
sl-badge[variant="success"] {
    --sl-color-success-600: var(--success-color);
}

sl-badge[variant="warning"] {
    --sl-color-warning-600: var(--warning-color);
}

sl-badge[variant="danger"] {
    --sl-color-danger-600: var(--error-color);
}

/* Skeleton loading states */
.skeleton {
    background: linear-gradient(90deg, var(--border-color) 25%, var(--bg-color) 50%, var(--border-color) 75%);
    background-size: 200% 100%;
    animation: skeleton-loading 1.5s infinite;
    border-radius: var(--sl-border-radius-small);
}

@keyframes skeleton-loading {
    0% {
        background-position: 200% 0;
    }
    100% {
        background-position: -200% 0;
    }
}

/* Metadata / extracted facts (shared `metadata_facts` inclusion tag) */
.metadata-facts {
    margin: 0;
}

.metadata-facts dt {
    margin: 0;
    font-size: var(--text-label);
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--text-muted);
}

.metadata-facts dd {
    margin: 0;
    font-weight: 500;
    color: var(--text-color);
}

/* A ruled list of label-beside-value rows. `columns: 2 28rem` splits the list
   into two columns once the section is at least 2 × 28rem wide, and keeps one
   column below that, so no media query is needed. The columns fill top to
   bottom, so the facts still read in the stored order.

   Every row has the same fixed label width, so the values line up down a
   column. 10rem is what the widest label ("RELATIONSHIP TYPE", uppercase and
   tracked) needs to fit on one line; a longer custom label wraps inside it. */
.metadata-facts--list {
    columns: 2 28rem;
    column-gap: 2.5rem;
}

.metadata-facts--list .metadata-fact {
    display: grid;
    grid-template-columns: 10rem minmax(0, 1fr);
    align-items: baseline;
    gap: 1rem;
    padding: 0.6rem 0;
    border-bottom: 1px solid var(--border-color);
    break-inside: avoid;
}

.metadata-facts--list dd {
    overflow-wrap: anywhere;
}

/* On a phone, a 10rem label column would leave the value a narrow strip, so
   the label column shrinks and long labels wrap onto a second line. */
@media (max-width: 500px) {
    .metadata-facts--list .metadata-fact {
        grid-template-columns: 7rem minmax(0, 1fr);
        gap: 0.75rem;
    }
}

.metadata-facts--pills {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
}

.metadata-facts--pills .metadata-fact {
    display: inline-flex;
    align-items: baseline;
    gap: 0.4rem;
    padding: 0.2rem 0.6rem;
    border: 1px solid var(--border-color);
    border-radius: 999px;
    background: var(--bg-color);
    font-size: var(--text-meta);
}

.metadata-facts--pills dt {
    font-size: var(--text-label);
}

/* A typed URL fact. Underlined rather than coloured-only, so the link reads as
   a link without shouting; long URLs wrap instead of stretching a grid cell. */
.metadata-link {
    color: var(--primary-color);
    text-decoration: none;
    border-bottom: 1px solid color-mix(in oklab, var(--primary-color) 35%, transparent);
    overflow-wrap: anywhere;
}

.metadata-link:hover {
    border-bottom-color: var(--primary-color);
}

.metadata-link:focus-visible {
    outline: 2px solid var(--primary-color);
    outline-offset: 2px;
    border-radius: var(--sl-border-radius-small);
}

/* Import status (polled fragment — styles here, not inline, to avoid
   re-injecting a <style> block on every 2s poll) */
.import-status {
    padding: 0 0 1.25rem 0;
    border-bottom: 1px solid var(--border-color);
}

.import-status-head {
    display: flex;
    justify-content: space-between;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.75rem;
    margin-bottom: 1rem;
}

.import-status-state {
    display: flex;
    align-items: center;
    gap: 0.6rem;
}

.import-status-spinner {
    font-size: var(--icon-sm);
    --indicator-color: var(--primary-color);
}

.import-status-count {
    display: flex;
    align-items: center;
    gap: 0.375rem;
    color: var(--text-muted);
    font-size: var(--text-meta);
    font-variant-numeric: tabular-nums;
}

.import-status-progress {
    margin-bottom: 1rem;
    --height: 6px;
    --indicator-color: var(--primary-color);
    --track-color: var(--paper-sunken);
}

.import-status-description,
.import-status-note {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    margin: 0;
    color: var(--text-muted);
}

.import-status-description sl-icon {
    color: var(--primary-color);
}

.import-status-note {
    margin-top: 0.75rem;
    font-size: var(--text-meta);
}

/* Responsive utilities. The type overrides that used to live here are gone: a
   role has one size, and the hero's own range is expressed by its clamp rather
   than by a breakpoint. What remains is a genuine layout change. */
@media (max-width: 768px) {
    .page-header {
        flex-direction: column;
        align-items: flex-start;
    }
}

/* Honor reduced-motion: neutralize non-essential animation and transitions */
@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;
    }
}

/* Ingest tabs: the Uploads area's three types (photos / conversations /
   contacts) as peer pages. Shared by uploads and imports templates. */
.ingest-tabs {
    display: flex;
    gap: 0.25rem;
    margin-bottom: 1.5rem;
    border-bottom: 1px solid var(--border-color);
}

.ingest-tab {
    display: inline-flex;
    align-items: center;
    gap: 0.45rem;
    padding: 0.55rem 0.9rem;
    margin-bottom: -1px;
    font-size: var(--text-meta);
    font-weight: 500;
    color: var(--text-muted);
    text-decoration: none;
    border-bottom: 2px solid transparent;
}

.ingest-tab:hover {
    color: var(--text-color);
}

.ingest-tab.is-active {
    color: var(--text-color);
    border-bottom-color: var(--primary-color);
}

/* Import history tables (Conversations / Contacts tabs) */
.imports-table-card::part(header) {
    padding: 1rem 1.25rem;
    border-bottom: 1px solid var(--border-color);
}

.imports-table-card::part(body) {
    padding: 0;
}

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

.imports-table th,
.imports-table td {
    padding: 0.85rem 1.25rem;
    text-align: left;
    border-bottom: 1px solid var(--border-color);
    vertical-align: middle;
}

.imports-table th {
    font-weight: 600;
    color: var(--text-muted);
    font-size: var(--text-label);
    text-transform: uppercase;
    letter-spacing: 0.025em;
    background: var(--bg-color);
}

.imports-table tr:last-child td {
    border-bottom: none;
}

.imports-table .actions-cell {
    text-align: right;
}

/* --- Circles and check-ins (docs/check-ins-plan.md) ---
   Shared by the person page, the people list and the Check-ins page, so they
   live here rather than in any one page's extra_css. Both controls are a plain
   form inside a hoisted sl-dropdown panel: a submit lands the user back where
   they were, and that page already shows the result. (The people list posts
   the circle form over HTMX instead and swaps the picker in place; its
   hover-reveal rules are in person_list.html.) */

/* The circle as a chip. Earthen and static: Paper Sunken fill, Taupe text, a
   Stone border — deliberately not a tag's colour, since it says how close
   someone is, not which group they belong to. */
.circle-chip {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    padding: 0.125rem 0.5rem;
    border-radius: 9999px;
    font-size: var(--text-label);
    font-weight: 500;
    font-family: inherit;
    color: var(--text-muted);
    background-color: var(--paper-sunken);
    border: 1px solid var(--border-color);
    white-space: nowrap;
}

.circle-chip sl-icon {
    font-size: var(--icon-xs);
}

/* As the picker's trigger it is a button; the empty state borrows the tag
   picker's dashed "+Tag" look so the two read as one family. */
.circle-chip--trigger {
    position: relative;
    min-height: 24px;
    cursor: pointer;
    transition: color 0.15s, border-color 0.15s, background-color 0.15s;
}

/* Touch: reach 44px by extending the hit area past the pill rather than by
   inflating the pill. This chip annotates a person's name and sits on the
   same line as it; a 44px-tall pill would out-weigh the name it qualifies.
   The overlay is centred on the chip, so the growth is symmetrical. */
@media (pointer: coarse) {
    .circle-chip--trigger::after {
        content: "";
        position: absolute;
        left: 0;
        right: 0;
        top: 50%;
        transform: translateY(-50%);
        height: 2.75rem;
    }
}

.circle-chip--trigger:hover {
    color: var(--sl-color-primary-700);
    border-color: var(--sl-color-primary-400);
    background-color: var(--sl-color-primary-50);
}

.circle-chip--empty {
    background: transparent;
    border-style: dashed;
    border-color: var(--sl-color-neutral-300);
    font-weight: 400;
}

/* The ring alone is a 20%-alpha wash that composites to 1.30:1 on paper — far
   under SC 2.4.11's 3:1. It is a glow, not an indicator: keep the same solid
   clay outline every link and button already gets, and let the ring soften it. */
.circle-chip--trigger:focus-visible {
    outline: 2px solid var(--primary-color);
    outline-offset: 2px;
    box-shadow: var(--focus-ring);
}

/* Both panels: opaque paper, stone border, the warm hover lift. */
.circle-dropdown::part(panel),
.check-in-dropdown::part(panel) {
    background-color: var(--sl-color-neutral-0);
    border: 1px solid var(--sl-color-neutral-200);
    border-radius: var(--sl-border-radius-medium);
    box-shadow: var(--hover-lift);
}

.circle-picker {
    display: flex;
    flex-direction: column;
    padding: 0.375rem;
    min-width: 13rem;
}

.circle-option {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 1rem;
    width: 100%;
    padding: 0.4rem 0.625rem;
    border: none;
    border-radius: var(--sl-border-radius-small);
    background: transparent;
    font-family: inherit;
    font-size: var(--text-meta);
    color: var(--text-color);
    text-align: left;
    cursor: pointer;
    transition: background-color 0.15s;
}

.circle-option:hover {
    background-color: var(--paper-sunken);
}

/* Inset, because an option is a full-bleed row inside the dropdown panel and
   an offset outline would be clipped by it. */
.circle-option:focus-visible {
    outline: 2px solid var(--primary-color);
    outline-offset: -2px;
    box-shadow: var(--focus-ring);
}

.circle-option.is-selected {
    color: var(--primary-color);
    font-weight: 500;
}

.circle-option-cadence {
    font-size: var(--text-label);
    color: var(--text-muted);
    white-space: nowrap;
}

.circle-option--clear {
    margin-top: 0.25rem;
    border-top: 1px solid var(--border-color);
    border-radius: 0;
    color: var(--text-muted);
}

/* The check-in trigger comes in two weights: a default button beside the
   person page's clay primary, or a quiet text action in a list row. */
.check-in-trigger {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    font-family: inherit;
    cursor: pointer;
    transition: color 0.15s ease-out, background-color 0.15s ease-out, border-color 0.15s ease-out;
}

.check-in-trigger sl-icon {
    font-size: var(--icon-xs);
}

.check-in-trigger--button {
    min-height: var(--sl-input-height-small);
    padding: 0 0.875rem;
    font-size: var(--sl-button-font-size-small);
    font-weight: var(--sl-font-weight-semibold);
    color: var(--text-color);
    background-color: var(--sl-color-neutral-0);
    border: 1px solid var(--border-color);
    border-radius: var(--sl-input-border-radius-medium);
}

.check-in-trigger--button:hover {
    background-color: var(--paper-sunken);
    border-color: var(--sl-color-neutral-300);
}

.check-in-trigger--text {
    padding: 0;
    /* The text is 14px, so the box would be 18.5px tall — under the 24px
       floor. The box grows around the centred label without moving it. */
    min-height: 24px;
    font-size: var(--text-meta);
    font-weight: 500;
    color: var(--text-muted);
    background: none;
    border: none;
}

.check-in-trigger--text:hover {
    color: var(--primary-color);
}

/* Touch: the same 44px target the header already reserves. These sit on their
   own full-width line on a phone, so the row growing is the point — it puts
   real space between two adjacent thumb targets. */
@media (pointer: coarse) {
    .check-in-trigger--text {
        min-height: 2.75rem;
    }
}

.check-in-trigger:focus-visible {
    outline: 2px solid var(--primary-color);
    outline-offset: 2px;
    border-radius: var(--sl-border-radius-small);
    box-shadow: var(--focus-ring);
}

.check-in-form {
    display: flex;
    flex-direction: column;
    gap: 0.625rem;
    padding: 0.75rem;
    width: 17rem;
    max-width: calc(100vw - 2rem);
}

.check-in-field {
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
}

.check-in-label {
    font-size: var(--text-label);
    font-weight: 500;
    color: var(--text-muted);
}

.check-in-optional {
    font-weight: 400;
}

/* Native inputs dressed to DESIGN.md's field spec: paper fill, stone border,
   3px radius, clay on focus. Native because a date field inside a floating
   panel is simpler than a second web component upgrading inside it. */
.check-in-input {
    font-family: inherit;
    font-size: var(--text-meta);
    color: var(--text-color);
    background-color: var(--sl-color-neutral-0);
    border: 1px solid var(--border-color);
    border-radius: 3px;
    padding: 0.4rem 0.6rem;
    min-width: 0;
}

.check-in-input:focus {
    outline: none;
    border-color: var(--primary-color);
    box-shadow: var(--focus-ring);
}

.check-in-form sl-button {
    align-self: flex-end;
}
