/* =============================================================================
   Xandev V3 - components
   Load after tokens.css and base.css.
   ========================================================================== */

/* -----------------------------------------------------------------------------
   Buttons
   -------------------------------------------------------------------------- */

.btn {
    --btn-bg: transparent;
    --btn-fg: var(--text-primary);
    --btn-border: var(--line-strong);

    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-2);

    padding: 0.7rem 1.25rem;
    border: 1px solid var(--btn-border);
    border-radius: var(--radius-sm);

    background: var(--btn-bg);
    color: var(--btn-fg);

    font-family: var(--font-body);
    font-size: var(--text-sm);
    font-weight: 600;
    letter-spacing: -0.005em;
    line-height: 1;
    white-space: nowrap;
    text-decoration: none;

    cursor: pointer;
    transition: background-color var(--duration) var(--ease),
                border-color var(--duration) var(--ease),
                color var(--duration) var(--ease),
                transform var(--duration) var(--ease);
}

/* 1px, not the V2 lift-and-glow. At this scale the smaller movement reads as
   responsive; the larger one reads as a template. */
.btn:hover { transform: translateY(-1px); }
.btn:active { transform: translateY(0); }

.btn:disabled,
.btn[aria-disabled="true"] {
    opacity: 0.5;
    pointer-events: none;
}

.btn--primary {
    --btn-bg: var(--brand);
    --btn-fg: var(--text-inverse);
    --btn-border: var(--brand);
}

.btn--primary:hover {
    --btn-bg: var(--brand-strong);
    --btn-border: var(--brand-strong);
}

.btn--accent {
    --btn-bg: var(--accent);
    --btn-fg: var(--text-inverse);
    --btn-border: var(--accent);
}

.btn--accent:hover {
    --btn-bg: var(--accent-strong);
    --btn-border: var(--accent-strong);
}

.btn--ghost {
    --btn-border: transparent;
}

.btn--ghost:hover {
    --btn-bg: var(--surface-hover);
}

.btn--outline:hover {
    --btn-border: var(--brand);
    --btn-fg: var(--brand-text);
}

.btn--danger {
    --btn-bg: var(--danger);
    --btn-fg: #fff;
    --btn-border: var(--danger);
}

.btn--sm { padding: 0.45rem 0.85rem; font-size: var(--text-xs); }
.btn--lg { padding: 0.9rem 1.75rem; font-size: var(--text-base); }
.btn--block { display: flex; width: 100%; }

/* -----------------------------------------------------------------------------
   Surfaces

   Solid fills with a hairline border, not glass. V2 blurred everything, which
   costs GPU on every scroll and leaves nothing visually special. Here the blur
   is spent in exactly one place: the sticky header.
   -------------------------------------------------------------------------- */

.card {
    padding: var(--space-5);
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    background: var(--surface-raised);
    box-shadow: var(--shadow-1), var(--edge-highlight);
    transition: border-color var(--duration) var(--ease),
                box-shadow var(--duration) var(--ease),
                transform var(--duration) var(--ease);
}

.card--interactive:hover {
    border-color: var(--line-strong);
    box-shadow: var(--shadow-3), var(--edge-highlight);
    transform: translateY(-2px);
}

.card--flush { padding: 0; overflow: hidden; }

.panel {
    padding: var(--space-5);
    border: 1px solid var(--line);
    border-radius: var(--radius-md);
    background: var(--surface-sunken);
}

/* -----------------------------------------------------------------------------
   Forms
   -------------------------------------------------------------------------- */

.field { display: grid; gap: var(--space-2); }

.field > label {
    font-size: var(--text-sm);
    font-weight: 600;
    color: var(--text-secondary);
}

.field__hint {
    font-size: var(--text-xs);
    color: var(--text-muted);
}

.field__error {
    font-size: var(--text-xs);
    color: var(--danger);
}

/* Fields side by side, collapsing to a stack on narrow screens. --narrow is
   for the ones that only ever hold a date or a short enum, so a title field
   next to a date picker does not get half the row each. */
.field-row {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr));
    gap: var(--space-4);
    align-items: start;
}

.field-row .field--narrow { max-width: 12rem; }

@media (max-width: 34rem) {
    .field-row { grid-template-columns: minmax(0, 1fr); }
    .field-row .field--narrow { max-width: none; }
}

/* Checkbox and its label as one target. The input keeps its native appearance
   so the checked state does not depend on CSS we would then have to get right
   in both themes. */
.checkbox {
    display: flex;
    align-items: flex-start;
    gap: var(--space-3);
    font-size: var(--text-sm);
    font-weight: 500;
    color: var(--text-secondary);
    cursor: pointer;
}

.checkbox input {
    flex: none;
    width: 1rem;
    height: 1rem;
    margin-top: 0.15rem;
    accent-color: var(--brand);
}

.checkbox span { line-height: var(--leading-normal); }

.select--sm {
    padding: 0.35rem 2rem 0.35rem 0.6rem;
    font-size: var(--text-xs);
}

.input,
.textarea,
.select {
    width: 100%;
    padding: 0.7rem 0.9rem;

    border: 1px solid var(--line-strong);
    border-radius: var(--radius-sm);
    background: var(--surface-sunken);
    color: var(--text-primary);

    font-size: var(--text-sm);
    transition: border-color var(--duration) var(--ease),
                background-color var(--duration) var(--ease);
}

.input::placeholder,
.textarea::placeholder {
    color: var(--text-muted);
}

.input:hover,
.textarea:hover,
.select:hover {
    border-color: color-mix(in oklab, var(--brand) 40%, var(--line-strong));
}

.input:focus,
.textarea:focus,
.select:focus {
    outline: none;
    border-color: var(--brand);
    background: var(--surface-raised);
    box-shadow: 0 0 0 3px color-mix(in oklab, var(--brand) 22%, transparent);
}

.input[aria-invalid="true"],
.textarea[aria-invalid="true"] {
    border-color: var(--danger);
}

/*
   A file input carries a button the browser draws itself, and it does not
   inherit anything from the field around it: on the portal's Files screen and
   the admin's media and project uploads, a gray OS button sat inside a themed
   field and was the one control on the page that looked like it belonged to a
   different site. ::file-selector-button is the hook for it.

   Padding is trimmed on the field itself because the button provides its own,
   and line-height keeps the "no file chosen" text on the button's center line.
*/
.input[type="file"] {
    padding: 0.35rem 0.4rem;
    line-height: 1.9;
    cursor: pointer;
}

.input[type="file"]::file-selector-button {
    margin-inline-end: 0.75rem;
    padding: 0.4rem 0.75rem;

    border: 1px solid var(--line-strong);
    border-radius: var(--radius-xs);
    background: var(--surface-raised);
    color: var(--text-secondary);

    font-family: inherit;
    font-size: var(--text-xs);
    font-weight: 600;
    cursor: pointer;

    transition: border-color var(--duration) var(--ease),
                color var(--duration) var(--ease);
}

.input[type="file"]:hover::file-selector-button {
    border-color: color-mix(in oklab, var(--brand) 45%, var(--line-strong));
    color: var(--text-primary);
}

.textarea { min-height: 8rem; resize: vertical; }

/* Native select arrow varies wildly across platforms; this replaces it with
   one that matches the theme. Encoded inline so no extra request is made. */
.select {
    appearance: none;
    padding-inline-end: 2.25rem;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8' fill='none'%3E%3Cpath d='M1 1.5L6 6.5L11 1.5' stroke='%236C7385' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 0.9rem center;
}

/* Honeypot: hidden from people, visible to naive bots. Not display:none,
   because some bots skip those; positioned off-canvas instead. */
.hp-field {
    position: absolute !important;
    left: -9999px !important;
    opacity: 0;
    pointer-events: none;
}

/* -----------------------------------------------------------------------------
   Badges and status
   -------------------------------------------------------------------------- */

.badge {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);

    padding: 0.25rem 0.6rem;
    border-radius: var(--radius-full);

    font-family: var(--font-mono);
    font-size: var(--text-3xs);
    font-weight: 500;
    letter-spacing: 0.06em;
    text-transform: uppercase;

    background: var(--surface-overlay);
    color: var(--text-secondary);

    /* A badge is one token. Letting it break mid-value turns a pill into two
       stacked half-pills, which reads as a rendering fault rather than as a
       status. If it does not fit, the column scrolls. */
    white-space: nowrap;
}

.badge--success { background: var(--success-surface); color: var(--success); }
.badge--warning { background: var(--warning-surface); color: var(--warning); }
.badge--danger  { background: var(--danger-surface);  color: var(--danger); }
.badge--info    { background: var(--info-surface);    color: var(--info); }

.dot {
    width: 0.5rem;
    height: 0.5rem;
    border-radius: var(--radius-full);
    background: currentColor;
    flex-shrink: 0;
}

/* See .eyebrow: top-aligned so a wrapped label keeps its dot on line one. */
.eyebrow .dot { margin-block-start: 0.42em; }

/* -----------------------------------------------------------------------------
   Alerts / flash messages
   -------------------------------------------------------------------------- */

/* Block, not flex. As a flex container every inline child (a <code>, an <a>,
   an <strong>) became its own flex item, so any alert containing markup broke
   into a column of fragments instead of flowing as a sentence. */
.alert {
    display: block;
    padding: var(--space-4);
    line-height: var(--leading-relaxed);
    border: 1px solid var(--line);
    border-radius: var(--radius-md);
    background: var(--surface-raised);
    font-size: var(--text-sm);
}

.alert--success { border-color: color-mix(in oklab, var(--success) 40%, transparent); background: var(--success-surface); }
.alert--warning { border-color: color-mix(in oklab, var(--warning) 40%, transparent); background: var(--warning-surface); }
.alert--danger  { border-color: color-mix(in oklab, var(--danger)  40%, transparent); background: var(--danger-surface); }
.alert--info    { border-color: color-mix(in oklab, var(--info)    40%, transparent); background: var(--info-surface); }

/* -----------------------------------------------------------------------------
   Site header - the one glass surface
   -------------------------------------------------------------------------- */

.site-header {
    position: sticky;
    top: 0;
    z-index: var(--z-sticky);

    border-bottom: 1px solid transparent;
    transition: border-color var(--duration) var(--ease),
                background-color var(--duration) var(--ease);
}

/* Class toggled by JS once the page scrolls, so the header is transparent over
   the hero and becomes a surface once content passes beneath it. */
.site-header.is-stuck {
    border-bottom-color: var(--line);
    background: var(--glass-bg);
    -webkit-backdrop-filter: var(--glass-blur);
    backdrop-filter: var(--glass-blur);
}

.site-header__inner {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-5);
    min-height: 4.5rem;
}

.site-nav {
    display: flex;
    align-items: center;
    gap: var(--space-5);
}

.site-nav a {
    position: relative;
    padding-block: var(--space-2);
    font-size: var(--text-sm);
    font-weight: 500;
    color: var(--text-secondary);
    text-decoration: none;
}

.site-nav a:hover { color: var(--text-primary); }

/* The gradient's second and final appearance: marking the current page. */
.site-nav a[aria-current="page"] {
    color: var(--text-primary);
}

.site-nav a[aria-current="page"]::after {
    content: "";
    position: absolute;
    inset-inline: 0;
    bottom: 0;
    height: 2px;
    border-radius: var(--radius-full);
    background: var(--brand-gradient);
}

/* -----------------------------------------------------------------------------
   Theme toggle
   -------------------------------------------------------------------------- */

.theme-toggle {
    display: grid;
    place-items: center;
    width: 2.25rem;
    height: 2.25rem;

    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
    background: transparent;
    color: var(--text-secondary);

    transition: border-color var(--duration) var(--ease),
                color var(--duration) var(--ease),
                background-color var(--duration) var(--ease);
}

.theme-toggle:hover {
    border-color: var(--line-strong);
    background: var(--surface-hover);
    color: var(--text-primary);
}

.theme-toggle svg {
    width: 1.05rem;
    height: 1.05rem;
}

/* Each icon is present in the DOM; the theme decides which one is shown, so
   there is no icon swap flicker and no JS needed to change the markup. */
.theme-toggle__moon { display: none; }
.theme-toggle__sun  { display: block; }

[data-theme="light"] .theme-toggle__moon { display: block; }
[data-theme="light"] .theme-toggle__sun  { display: none; }

/* -----------------------------------------------------------------------------
   Footer
   -------------------------------------------------------------------------- */

/* No large top margin: the preceding section already contributes
   --section-y of padding, and stacking both left a void above the footer. */
.site-footer {
    margin-block-start: var(--space-6);
    border-top: 1px solid var(--line);
    padding-block: var(--space-8) var(--space-6);
    font-size: var(--text-sm);
    color: var(--text-muted);
}

.site-footer a {
    color: var(--text-secondary);
    text-decoration: none;
}

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

/* -----------------------------------------------------------------------------
   Tables - invoices, time logs, anything with numbers
   -------------------------------------------------------------------------- */

/*
   position: relative is load bearing, not decoration.

   .visually-hidden is position: absolute with no offsets, so it keeps its
   static position but takes its containing block from the nearest positioned
   ancestor. With nothing positioned between it and the page, a screen-reader
   label in the last column of a table wider than the phone was placed at the
   page's coordinates rather than the scroller's, escaping overflow-x and
   dragging the document sideways. On the portal invoice list that was 155px
   of horizontal scroll caused by a one-pixel element nobody can see.

   Making the wrap a containing block puts those descendants back inside the
   thing that scrolls, where the overflow rule can reach them.
*/
.table-wrap {
    position: relative;
    overflow-x: auto;
    border: 1px solid var(--line);
    border-radius: var(--radius-md);
}

.table {
    width: 100%;
    border-collapse: collapse;
    font-size: var(--text-sm);
}

.table th,
.table td {
    padding: var(--space-3) var(--space-4);
    text-align: left;
    border-bottom: 1px solid var(--line-faint);
}

/* Column headers only. Scoped to thead because a <th scope="row"> is a row
   label carrying real content - a person's name, a client - and uppercasing
   it in a mono face made every row read like a column heading. */
.table thead th {
    font-family: var(--font-mono);
    font-size: var(--text-3xs);
    font-weight: 500;
    letter-spacing: var(--tracking-label);
    text-transform: uppercase;
    color: var(--text-muted);
    background: var(--surface-sunken);
    white-space: nowrap;
}

/* Row headers read as content, not as chrome. */
.table tbody th {
    font-weight: 600;
    color: var(--text-primary);
    text-align: left;
}

.table tbody tr:last-child td { border-bottom: none; }
.table tbody tr:hover { background: var(--surface-hover); }

.table td.numeric,
.table th.numeric {
    text-align: right;
    font-variant-numeric: tabular-nums;
}

/* -----------------------------------------------------------------------------
   Empty states - an empty screen should tell you what to do next
   -------------------------------------------------------------------------- */

.empty-state {
    display: grid;
    justify-items: center;
    gap: var(--space-3);
    padding: var(--space-9) var(--space-5);
    text-align: center;
    color: var(--text-muted);
}

.empty-state h3 {
    font-size: var(--text-lg);
    color: var(--text-secondary);
}

/* =============================================================================
   Page compositions

   Everything below exists because the Content Security Policy forbids inline
   style attributes. A CSP nonce authorises <style> elements and <link> tags,
   it does NOT authorise style="..." on an element; that needs 'unsafe-inline',
   which would also authorise any style an attacker manages to inject.

   So layout lives in classes. That constraint is worth keeping: it is the
   difference between a policy that genuinely stops injected CSS and one that
   only looks like it does.
   ========================================================================== */

.brand-mark {
    display: flex;
    align-items: center;
    flex-shrink: 0;
}

/*
   The logo is inlined SVG, so its two parts theme independently: the brackets
   take the brand color, and the word inherits currentColor and therefore
   flips with the light/dark theme without a second asset.
*/
.site-logo {
    height: 1.75rem;
    width: auto;
    overflow: visible;
}

.site-logo__bracket { stroke: var(--brand); fill: none; }
.site-logo__word    { fill: currentColor; }

.brand-mark { color: var(--text-primary); }
.brand-mark--sm .site-logo { height: 1.5rem; }

.header-actions {
    display: flex;
    align-items: center;
    gap: var(--space-3);
}

/* --- Generic arrangement helpers ------------------------------------------ */

.between {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-4);
}

.between--top { align-items: flex-start; }

.grid-auto {
    display: grid;
    gap: var(--space-4);
    grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
}

.center-text { text-align: center; }
.center-items { justify-content: center; }

.lead {
    font-size: var(--text-lg);
    color: var(--text-secondary);
}

/* --- Status page (Phase 1 only; removed once the real home page lands) ----- */

.status-card__head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--space-3);
}

.status-card__value {
    font-family: var(--font-mono);
    font-size: var(--text-sm);
    color: var(--text-primary);
    word-break: break-word;
}

.specimen__display {
    font-family: var(--font-display);
    font-stretch: var(--width-display);
    font-weight: 800;
    font-size: var(--text-4xl);
    letter-spacing: var(--tracking-display);
    line-height: 1;
}

.specimen__mono {
    font-family: var(--font-mono);
    font-size: var(--text-3xs);
    letter-spacing: var(--tracking-label);
    text-transform: uppercase;
    color: var(--text-muted);
}

/* --- Footer --------------------------------------------------------------- */

/* A grid rather than space-between. The blurb caps at 22rem, so on a wide
   screen space-between parked both link columns against the right edge with
   half the footer empty between the two halves. Columns with a width of their
   own sit where the rest of the page's columns sit. */
.footer-top {
    display: grid;
    align-items: start;
    gap: var(--space-8);
}

@media (min-width: 48rem) {
    .footer-top { grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr); }
}

.footer-blurb {
    max-width: 22rem;
    font-size: var(--text-sm);
}

.footer-nav {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    align-items: start;
    gap: var(--space-8);
}

.footer-col {
    display: grid;
    gap: var(--space-2);
    align-content: start;
}

.footer-rule {
    margin-block: var(--space-6) var(--space-4);
    opacity: 0.35;
}

.footer-bottom {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
    font-size: var(--text-xs);
}

/* --- Error page ----------------------------------------------------------- */

.error-title { font-size: var(--text-3xl); }

.error-detail {
    text-align: left;
    font-size: var(--text-2xs);
    max-height: 22rem;
}

/* --- Flash region --------------------------------------------------------- */

.flash-region {
    padding-block-start: var(--space-5);
    display: grid;
    gap: var(--space-3);
}

/* Footer social row. Icon-only links, so each carries an aria-label - and a
   40px hit area, because a 16px icon is not a tappable target on a phone. */
.social-links {
    display: flex;
    align-items: center;
    gap: var(--space-1);
    list-style: none;
}

.social-links a {
    display: grid;
    place-items: center;
    width: 2.25rem;
    height: 2.25rem;
    border-radius: var(--radius-sm);
    color: var(--text-muted);
    transition: color var(--duration) var(--ease),
                background-color var(--duration) var(--ease);
}

.social-links a:hover {
    color: var(--text-primary);
    background: var(--surface-hover);
}

.social-links svg { width: 1.15rem; height: 1.15rem; }

/* -----------------------------------------------------------------------------
   Analytics: the engagement popup and the chat widget

   Both are injected by tracker.js, so they are the only elements on the site
   whose markup lives in a script rather than a template. Their classes are
   prefixed xdv- for exactly that reason: a rename here breaks something that
   grep over templates/ will not find.
   -------------------------------------------------------------------------- */

.xdv-pop,
.xdv-chat {
    position: fixed;
    right: var(--space-5);
    bottom: var(--space-5);
    z-index: 60;
    width: min(22rem, calc(100vw - var(--space-6)));
    border: 1px solid var(--line);
    border-radius: var(--radius-md);
    background: var(--surface-overlay);
    box-shadow: 0 18px 48px rgb(0 0 0 / 0.34);
}

.xdv-pop { padding: var(--space-5); }

.xdv-pop__close,
.xdv-chat__close {
    position: absolute;
    top: var(--space-2);
    right: var(--space-3);
    padding: var(--space-1) var(--space-2);
    border: 0;
    background: none;
    color: var(--text-muted);
    font-size: var(--text-lg);
    line-height: 1;
    cursor: pointer;
}

.xdv-pop__close:where(:hover),
.xdv-chat__close:where(:hover) { color: var(--text-primary); }

.xdv-pop__title {
    margin: 0 0 var(--space-2);
    font-family: var(--font-display);
    font-size: var(--text-lg);
    font-weight: 700;
    color: var(--text-primary);
}

.xdv-pop__body {
    margin: 0 0 var(--space-4);
    color: var(--text-secondary);
    font-size: var(--text-sm);
    line-height: var(--leading-relaxed);
}

.xdv-pop__form { display: flex; flex-direction: column; gap: var(--space-2); }

.xdv-pop__label {
    font-family: var(--font-mono);
    font-size: var(--text-3xs);
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--text-muted);
}

.xdv-pop__input,
.xdv-chat__input {
    width: 100%;
    padding: var(--space-2) var(--space-3);
    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
    background: var(--surface-sunken);
    color: var(--text-primary);
    font: inherit;
    font-size: var(--text-sm);
}

.xdv-pop__input:focus-visible,
.xdv-chat__input:focus-visible { outline: 2px solid var(--brand); outline-offset: 1px; }

.xdv-pop__send,
.xdv-chat__send {
    align-self: flex-start;
    margin-top: var(--space-2);
    padding: var(--space-2) var(--space-4);
    border: 0;
    border-radius: var(--radius-sm);
    background: var(--brand);
    color: var(--on-brand);
    font-size: var(--text-sm);
    font-weight: 600;
    cursor: pointer;
}

/* --- Chat ----------------------------------------------------------------- */

.xdv-chat { display: flex; flex-direction: column; max-height: min(28rem, 70vh); }

.xdv-chat__head {
    position: relative;
    padding: var(--space-3) var(--space-4);
    border-bottom: 1px solid var(--line);
    font-family: var(--font-display);
    font-weight: 700;
    color: var(--text-primary);
}

.xdv-chat__log {
    flex: 1;
    overflow-y: auto;
    padding: var(--space-3) var(--space-4);
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
}

.xdv-chat__line {
    max-width: 85%;
    margin: 0;
    padding: var(--space-2) var(--space-3);
    border-radius: var(--radius-sm);
    font-size: var(--text-sm);
    line-height: var(--leading-relaxed);
    overflow-wrap: anywhere;
}

.xdv-chat__line--alex {
    align-self: flex-start;
    background: var(--surface-sunken);
    color: var(--text-primary);
}

.xdv-chat__line--you {
    align-self: flex-end;
    background: color-mix(in oklab, var(--brand) 22%, transparent);
    color: var(--text-primary);
}

.xdv-chat__form {
    display: flex;
    gap: var(--space-2);
    padding: var(--space-3) var(--space-4);
    border-top: 1px solid var(--line);
}

.xdv-chat__send { align-self: auto; margin-top: 0; }

@media (prefers-reduced-motion: no-preference) {
    .xdv-pop, .xdv-chat { animation: xdv-rise 220ms var(--ease) both; }
}

@keyframes xdv-rise {
    from { opacity: 0; transform: translateY(0.75rem); }
    to   { opacity: 1; transform: none; }
}
