/* surfaces.css — owned by new-ux lane 06; see docs/APP.md "The split stylesheets". */

/*
 * The floating-surface primitives (docs/new-ux/specs/06-foundation.md
 * §B5, docs/new-ux/CONTRACTS.md §7.4): the layer every slip, drawer,
 * and dialog renders into, the surface chrome (head, title, stack-count
 * pill, close, body, resize grip), the dialog backdrop, and the
 * app-shell feedback states app_foundation.js paints — toasts, the
 * loading skeleton, the error and not-found panels, and the provider
 * `.fs-mark` chip.  app_surface.js writes each surface's geometry as
 * the four `--srf-*` custom properties and a resolved `z-index` on the
 * element's inline `style=`; this sheet consumes them.
 *
 * Token discipline (tests/test_app_design_tokens.py): every colour is a
 * `var(--c-*)` token (no hex literal), every `font-size` a `var(--fs-*)`
 * type-scale rung, every `box-shadow` a composition of `var(--sh-*)` /
 * `var(--focus-ring)` parts, every `z-index` a `var(--z-*)` rung.  The
 * geometry properties are spelt `--srf-*`, as spec 06 §B5 spells them
 * too: `--fs-*` is the type-scale family, and a `var(--fs-x)` here
 * would be an unresolved dangling reference — the `--srf-*` prefix is
 * observably identical and outside that family.  Edge law: the
 * slip's accent rule is a 2px border on the TOP
 * edge; left edges (the toast status rules) stay at the single 3px
 * status width.
 *
 * This sheet carries exactly the two conditional preludes a split sheet
 * may own (R33, first sanctioned use): the 720px phone breakpoint that
 * turns every surface into a bottom sheet, and the reduced-motion guard
 * that drops the entrance animations and transitions.  The five-block
 * responsive tail stays style.css's (tests/test_app_static.py).
 */

/* The layer app_surface.js renders every surface into.  A static
   container on purpose: its children (the surfaces and the dialog
   backdrop) are all position: fixed, so the layer needs no box of its
   own — and it must never take `position` or `z-index`, either of
   which would mint a stacking context (position: fixed alone does, in
   modern browsers) that traps every surface's resolved inline z-index
   in one root-ladder slot and lifts a drawer's 60-94 over the
   --z-panel chrome at 95.  Each surface competes on the root ladder
   alone; pointer-events: none is defence for any host style that
   gives the empty layer a box. */
#surface-layer {
    pointer-events: none;
}

/* The surface shell.  Geometry rides the inline `--srf-*` properties;
   the resolved `z-index` rides inline too, so this rule sets none. */
.fs-surface {
    position: fixed;
    left: var(--srf-x);
    top: var(--srf-y);
    width: var(--srf-w);
    height: var(--srf-h);
    display: flex;
    flex-direction: column;
    overflow: hidden;
    background: var(--c-sand-card);
    color: var(--c-ink);
    font-family: var(--font-ui);
    font-size: var(--fs-body);
    border: 1px solid var(--c-sand-rule);
    border-radius: var(--radius-lg);
    box-shadow: var(--sh-lift);
    pointer-events: auto;
    animation: fs-surface-in var(--t-base) both;
}

/* The slip: a light, transient card whose accent lives on the TOP edge
   (the letterhead device), never the left.  Desktop chrome is the
   mockup's (dc.html :945): 2px accent top, radius 8, the overlay
   shadow — moved off 14px/--sh-float by the fidelity audit
   (2026-09-11; task-detail catalogue row 46). */
.fs-slip {
    border-top: 2px solid var(--c-accent);
    border-radius: var(--radius);
    box-shadow: var(--sh-overlay);
}

/* The drawer: a tall right-hand column that stacks and can persist.
   Chrome per the mockup (dc.html :1014; drawer catalogue rows 1, 3, 4):
   the 3px gold LEFT rule — the edge law's one status width — radius
   12, the overlay shadow, and a slide-in from 28px right over .4s on
   the mockup's curve (two easings cannot share the shorthand, so the
   duration is the mockup's literal, not --t-slow). */
.fs-drawer {
    border-left: 3px solid var(--c-gold);
    border-radius: var(--r-12);
    box-shadow: var(--sh-overlay);
    animation: fs-drawer-in 0.4s cubic-bezier(0.2, 0.7, 0.2, 1) both;
}

/* The dialog: the modal surface — the deepest elevation, over its
   backdrop.  The mockup's card (dc.html :674; account catalogue D1):
   2px gold top, radius 10, sized to its content and centred — so the
   dialog ignores --srf-y/--srf-h: `top`/`bottom` 24px with auto block
   margins and a fit-content height centre it, capped at the mockup's
   calc(100vh - 48px).  app_surface.js still writes --srf-x/--srf-w. */
.fs-dialog {
    top: 24px;
    bottom: 24px;
    height: fit-content;
    max-height: calc(100vh - 48px);
    margin-block: auto;
    border-top: 2px solid var(--c-gold);
    border-radius: var(--r-10);
    box-shadow: var(--sh-overlay);
}

/* The dialog backdrop: a translucent scrim (the mockup's .28 ink,
   dc.html :673) fixed over the whole viewport, seated on the --z-panel rung of the root ladder so it
   veils the drawers (capped at 94) and the slip beneath the dialog
   while the dialog itself, at the modal ceiling, stays above it. */
.fs-backdrop {
    position: fixed;
    inset: 0;
    z-index: var(--z-panel);
    background: var(--c-ink);
    opacity: 0.28;
    pointer-events: auto;
    animation: fs-fade-in var(--t-base) both;
}

/* The drag header: title, the narrow stack-count pill, and the close
   button.  `data-surface-drag` is app_surface.js's drag grip. */
.fs-head {
    display: flex;
    align-items: center;
    gap: var(--s-2);
    padding: var(--s-3) var(--s-4);
    background: var(--c-soft);
    border-bottom: 1px solid var(--c-sand-rule);
    cursor: grab;
    touch-action: none;
}

.fs-title {
    flex: 1 1 auto;
    min-width: 0;
    margin: 0;
    font-family: var(--font-ui);
    font-size: var(--fs-title);
    font-weight: 600;
    line-height: 1.2;
    color: var(--c-ink);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    outline: none;
}

/* The `+N` stack-count pill: hidden at rest, un-hidden by
   app_surface.js on the topmost bottom sheet when surfaces are stacked
   beneath it. */
.fs-stack-count {
    flex: 0 0 auto;
    padding: var(--s-1) var(--s-2);
    background: var(--c-sand-card);
    color: var(--c-ink-muted);
    font-size: var(--fs-micro);
    font-weight: 600;
    border: 1px solid var(--c-sand-rule);
    border-radius: var(--radius-pill);
}

/* The close ×: the mockup's bare glyph (dc.html :763, :677, :723) —
   20px ink-muted, `padding: 4px 8px`, no box and no hover ground, ink
   on hover.  Was a 32×32 box with a sand hover fill before the fidelity
   audit (2026-09-11; drawer catalogue row 6). */
.fs-close {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 4px 8px;
    background: transparent;
    color: var(--c-ink-muted);
    font-size: var(--fs-title);
    line-height: 1;
    border: 0;
    border-radius: var(--radius-sm);
    cursor: pointer;
    transition: color var(--t-fast);
}

.fs-close:hover {
    color: var(--c-ink);
}

.fs-close:focus-visible {
    outline: none;
    box-shadow: var(--focus-ring);
}

/* The scrollable body the caller's render() fills. */
.fs-body {
    flex: 1 1 auto;
    min-height: 0;
    overflow: auto;
    padding: var(--s-4);
    font-size: var(--fs-body);
}

/* Per-kind chrome, each from the mockup's inline styles (fidelity audit
   2026-09-11).  Drawer (dc.html :757-766; drawer catalogue rows 5, 6,
   12): head `10px 10px 10px 18px`, a Big Caslon 18px regular title,
   20px body padding.  Slip (dc.html :718-725; task-detail catalogue
   row 45): head `12px 16px 12px 18px`, a 16px Caslon text title, an
   18px ×, body `18px 20px 20px`.  Dialog (dc.html :674-677; account
   catalogue D1): no header band and no grab cursor — the head is a
   bare title row with the × at its top right — a Big Caslon 26px
   wrapping title, body `16px 24px 22px`. */
.fs-drawer .fs-head {
    padding: 10px 10px 10px 18px;
}

.fs-drawer .fs-title {
    font-family: var(--font-display);
    font-size: var(--fs-18);
    font-weight: 400;
}

.fs-drawer .fs-body {
    padding: 20px;
}

.fs-slip .fs-head {
    padding: 12px 16px 12px 18px;
}

/* The drawer head's meta slot (dc.html :758-760): the count line sits
   inline after the title, 12px burnished on one line, and never grows
   at the title's expense. */
.fs-title-meta {
    flex: none;
    display: inline-flex;
    align-items: center;
    gap: var(--s-1);
    color: var(--c-burnished-text);
    font-size: var(--fs-meta);
    white-space: nowrap;
}

/* Unused, the slot must not even cost the head's gap. */
.fs-title-meta:empty {
    display: none;
}

/* The slip head's integration square is the mockup's 22px (dc.html
   :720), a rung under the shared 26px mark. */
.fs-slip .fs-head .fs-mark {
    width: 22px;
    height: 22px;
    border-radius: 5px;
    font-size: var(--fs-10);
}

.fs-slip .fs-title {
    font-family: var(--font-text);
    font-size: var(--fs-base);
    font-weight: 400;
}

.fs-slip .fs-close {
    padding: 4px 6px;
    font-size: var(--fs-18);
}

.fs-slip .fs-body {
    padding: 18px 20px 20px;
}

.fs-dialog .fs-head {
    align-items: flex-start;
    padding: 22px 24px 0;
    background: transparent;
    border-bottom: 0;
    cursor: default;
}

.fs-dialog .fs-title {
    font-family: var(--font-display);
    font-size: var(--fs-26);
    font-weight: 400;
    line-height: 1.15;
    white-space: normal;
}

.fs-dialog .fs-close {
    padding: 2px 6px;
}

.fs-dialog .fs-body {
    padding: 16px 24px 22px;
}

/* The corner resize grip: hidden at rest, un-hidden by app_surface.js
   on a desktop slip or drawer. */
.fs-resize {
    position: absolute;
    right: 2px;
    bottom: 2px;
    width: var(--s-4);
    height: var(--s-4);
    cursor: nwse-resize;
    touch-action: none;
    background:
        linear-gradient(
            135deg,
            transparent 0 50%,
            var(--c-rule-strong) 50% 62%,
            transparent 62% 74%,
            var(--c-rule-strong) 74% 86%,
            transparent 86%
        );
}

/* The entrance animations — a lift-and-fade for a floating surface, a
   slide-up for a bottom sheet, a plain fade for the scrim.  All are
   dropped under the reduced-motion guard below. */
@keyframes fs-surface-in {
    from { opacity: 0; transform: translateY(8px); }
    to   { opacity: 1; transform: translateY(0); }
}

@keyframes fs-sheet-in {
    from { transform: translateY(100%); }
    to   { transform: translateY(0); }
}

@keyframes fs-fade-in {
    from { opacity: 0; }
    to   { opacity: 0.28; }
}

/* The drawer's entrance (dc.html :31 `slideIn`): in from the right. */
@keyframes fs-drawer-in {
    from { opacity: 0; transform: translateX(28px); }
    to   { opacity: 1; transform: none; }
}

/* Every drawer turn and block rises in (dc.html :30 `rise`, used at
   :769, :779, :789, :799, :813).  The keyframe and the reduced-motion
   guard live here because spec 10 :222 assigns motion to lane 06;
   conversations.css declares none. */
@keyframes fs-rise {
    from { opacity: 0; transform: translateY(6px); }
    to   { opacity: 1; transform: none; }
}

.cv-turn,
.bk-text,
.bk-figure,
.bk-item,
.bk-ask,
.bk-draft {
    animation: fs-rise 0.45s cubic-bezier(0.2, 0.7, 0.2, 1) both;
}

/* The per-index entrance stagger (dc.html :779, :789, :799, :813 read
   `animation-delay:{{ f.delay }}` etc.; the steps are :1021-1023 —
   figures k*70ms, items k*80ms, asks k*80ms, drafts k*90ms).  The index
   is `--bk-i`, stamped through the CSSOM once per minted node by
   app_ctrl_blocks.js; an unstamped node starts at once. */
.bk-figure { animation-delay: calc(var(--bk-i, 0) * 70ms); }
.bk-item   { animation-delay: calc(var(--bk-i, 0) * 80ms); }
.bk-ask    { animation-delay: calc(var(--bk-i, 0) * 80ms); }
.bk-draft  { animation-delay: calc(var(--bk-i, 0) * 90ms); }

/* The entrance plays once per turn: an in-place repaint of a turn that
   already showed its blocks (app_ctrl_assistant.js `_paintTurnInto`
   marks the li `data-cv-repainted`) re-mints them without replaying the
   rise or its stagger. */
.cv-turn[data-cv-repainted] .bk-text,
.cv-turn[data-cv-repainted] .bk-figure,
.cv-turn[data-cv-repainted] .bk-item,
.cv-turn[data-cv-repainted] .bk-ask,
.cv-turn[data-cv-repainted] .bk-draft {
    animation: none;
}

/* The three-dot thinking indicator's blink (dc.html :35, :838-844):
   the drawer controller emits `span.cv-thinking > span × 3` and
   conversations.css sizes the 7px gold dots; the motion is this
   sheet's, delayed 0 / .2 / .4s across the three. */
@keyframes fs-blink {
    0%, 80%, 100% { opacity: 0.25; transform: scale(0.85); }
    40%           { opacity: 1; transform: none; }
}

.cv-thinking > span {
    animation: fs-blink 1.2s infinite;
}

.cv-thinking > span:nth-child(2) {
    animation-delay: 0.2s;
}

.cv-thinking > span:nth-child(3) {
    animation-delay: 0.4s;
}

@keyframes fs-skeleton-pulse {
    0%, 100% { opacity: 1; }
    50%      { opacity: 0.55; }
}

/* Toasts (§7.9): the region app_foundation.js appends `.fs-toast` rows
   to, and the toast card itself.  A left status edge carries kind at
   the single 3px width. */
#toast-region {
    position: fixed;
    inset: auto var(--s-4) var(--s-4) auto;
    z-index: var(--z-modal);
    display: flex;
    flex-direction: column;
    gap: var(--s-2);
    max-width: min(92vw, 24rem);
    pointer-events: none;
}

.fs-toast {
    display: flex;
    align-items: flex-start;
    gap: var(--s-3);
    padding: var(--s-3) var(--s-4);
    background: var(--c-sand-card);
    color: var(--c-ink);
    font-size: var(--fs-ui);
    line-height: 1.4;
    border: 1px solid var(--c-sand-rule);
    border-radius: var(--radius);
    box-shadow: var(--sh-lift);
    pointer-events: auto;
    animation: fs-surface-in var(--t-base) both;
}

.fs-toast[data-toast-kind="success"] {
    border-left: 3px solid var(--c-status-complete);
}

.fs-toast[data-toast-kind="error"] {
    border-left: 3px solid var(--c-status-failed);
}

.fs-toast[data-toast-kind="warn"] {
    border-left: 3px solid var(--c-gold);
}

.fs-toast [data-toast-close] {
    flex: 0 0 auto;
    margin-left: auto;
    padding: 0 var(--s-1);
    background: transparent;
    color: var(--c-ink-muted);
    font-size: var(--fs-body);
    line-height: 1.4;
    border: 0;
    cursor: pointer;
}

.fs-toast [data-toast-close]:focus-visible {
    outline: none;
    box-shadow: var(--focus-ring);
}

/* The loading skeleton: soft bars that pulse until content resolves. */
.fs-loading {
    display: flex;
    flex-direction: column;
    gap: var(--s-3);
    padding: var(--s-4);
}

.fs-skeleton-row {
    height: var(--s-4);
    background: var(--c-soft);
    border-radius: var(--radius-sm);
    /* An infinite pulse names its own duration, the way every other
       pulse in the app does (home.css `hb-pulse` 1.2s / 1.6s): the
       --t-* tokens already carry an `ease`, and naming a second timing
       function beside one makes the whole declaration invalid, which is
       why this row never pulsed.  Stilled under reduced motion below. */
    animation: fs-skeleton-pulse 1.2s ease-in-out infinite;
}

.fs-skeleton-row:nth-child(even) {
    width: 82%;
}

/* The error panel and its retry control. */
.fs-error {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--s-3);
    padding: var(--s-5) var(--s-4);
    color: var(--c-ink-soft);
    font-size: var(--fs-body);
    border-left: 3px solid var(--c-status-failed);
    background: var(--c-sand-card);
}

.fs-error [data-retry] {
    padding: var(--s-2) var(--s-4);
    background: var(--c-accent);
    color: var(--c-sand-card);
    font-family: var(--font-ui);
    font-size: var(--fs-ui);
    font-weight: 600;
    border: 0;
    border-radius: var(--radius-sm);
    cursor: pointer;
    transition: background var(--t-fast);
}

.fs-error [data-retry]:hover {
    background: var(--c-accent-hover);
}

.fs-error [data-retry]:focus-visible {
    outline: none;
    box-shadow: var(--focus-ring);
}

/* The empty / not-found panel. */
.fs-not-found {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--s-3);
    padding: var(--s-6) var(--s-4);
    color: var(--c-ink-muted);
    font-size: var(--fs-body);
    text-align: center;
}

.fs-not-found a {
    color: var(--c-accent);
    font-weight: 600;
}

.fs-not-found a:focus-visible {
    outline: none;
    box-shadow: var(--focus-ring);
}

/* The provider mark (§6.2): the mockup's brand square — 26×26, radius
   6, brand ground, a cream glyph at 60% of the square (dc.html :101,
   :130, :157; BRAND :1153 and `_decorate` :1157; the fidelity audit,
   2026-09-11).  Logo providers and Apollo sit on the gold ground, `you`
   is the oxblood "ME", and anything else — `other` foremost, which
   renders no glyph at all — takes a neutral soft ground.  The mockup
   has no fallback square of its own: its mark row is an `sc-for` over
   `markList` (dc.html :157), so a card with no known provider draws
   nothing, and `suggestion_card.js` skips the mark rather than paint a
   blank one.  `other` reaches this rule only where a screen asks for
   it by name.  The glyph is an <img> + filter (CSS image
   references and masks are banned in app sheets), so it paints white
   rather than the mockup's cream; text marks read --c-sand-card, four
   channel units off that cream.  The explicit width keeps a column flex parent
   from stretching the square into a strip.  Screens resize the square
   for their own sites (tasks 28, integrations 30) with a scoped rule;
   this is the base. */
.fs-mark {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: none;
    width: 26px;
    height: 26px;
    padding: 0;
    background: var(--c-soft);
    color: var(--c-ink-soft);
    font-size: var(--fs-micro);
    font-weight: 700;
    border-radius: var(--r-6);
    vertical-align: middle;
}

.fs-mark[data-mark="github"],
.fs-mark[data-mark="slack"],
.fs-mark[data-mark="linear"],
.fs-mark[data-mark="google"],
.fs-mark[data-mark="apollo"] {
    background: var(--c-gold);
    color: var(--c-sand-card);
}

.fs-mark[data-mark="you"] {
    background: var(--c-accent);
    color: var(--c-sand-card);
}

.fs-mark img {
    display: block;
    width: 60%;
    height: 60%;
    filter: brightness(0) invert(1);
}

/* Phone breakpoint (R33): every surface becomes a bottom sheet pinned
   to the foot of the viewport, full width, with a grab handle and a
   deep-enough max height; the resize grip has no place on a sheet. */
@media (max-width: 720px) {
    body[data-narrow="true"] .fs-surface { position: fixed; inset: auto 0 0 0; width: auto; height: auto; max-height: 88vh; border-radius: var(--radius-lg) var(--radius-lg) 0 0 }

    body[data-narrow="true"] .fs-surface {
        animation-name: fs-sheet-in;
    }

    body[data-narrow="true"] .fs-head {
        position: relative;
        min-height: 44px;
        padding-top: var(--s-4);
        cursor: grab;
    }

    body[data-narrow="true"] .fs-head::before {
        content: "";
        position: absolute;
        top: var(--s-2);
        left: 50%;
        width: var(--s-7);
        height: var(--s-1);
        transform: translateX(-50%);
        background: var(--c-rule-strong);
        border-radius: var(--radius-pill);
    }

    body[data-narrow="true"] .fs-resize {
        display: none;
    }

    #toast-region {
        inset: auto var(--s-3) var(--s-3) var(--s-3);
        max-width: none;
    }
}

/* Reduced-motion guard (R33): no entrance animation, no transition. */
@media (prefers-reduced-motion: reduce) {
    .fs-surface,
    .fs-backdrop,
    .fs-toast,
    .fs-skeleton-row,
    .cv-thinking > span,
    .cv-turn,
    .bk-text,
    .bk-figure,
    .bk-item,
    .bk-ask,
    .bk-draft {
        animation: none;
    }

    .fs-close,
    .fs-error [data-retry] {
        transition: none;
    }
}
