/* =====================================================================
   HAULAT — the dialog system  (assets/css/dialog.css)

   Paired with assets/js/dialog.js. Loaded on every screen, including the
   auth and public shells, because feedback is not a feature of the
   signed-in app.

   It reads as the same material as everything else: glass, the hairline,
   the same radii. What separates the five states is an accent stripe, an
   icon and a word — never colour alone, because a green rectangle with a
   full stop in it is invisible to a colour-blind user and meaningless to
   a screen reader.
   ===================================================================== */

/* =====================================================================
   HOST
   Top-centre. Toasts appear where the top bar is, which is where the eye
   already goes after an action; bottom-right is a desktop convention
   that puts them under a thumb on a phone.
   ===================================================================== */
.hd-host {
    position: fixed; z-index: 200;
    top: calc(env(safe-area-inset-top, 0px) + 14px);
    left: 50%; transform: translateX(-50%);
    width: min(440px, calc(100vw - 24px));
    display: flex; flex-direction: column; gap: 10px;
    pointer-events: none;
}

/* =====================================================================
   TOAST
   ===================================================================== */
.hd-toast {
    position: relative;
    display: flex; align-items: flex-start; gap: 12px;
    padding: 14px 14px 15px;
    border-radius: 18px;
    overflow: hidden;
    pointer-events: auto;
    cursor: default;

    background: var(--hd-bg, rgba(28, 18, 21, .82));
    border: 1px solid var(--hd-line, rgba(255,255,255,.14));
    backdrop-filter: blur(24px) saturate(1.3);
    -webkit-backdrop-filter: blur(24px) saturate(1.3);
    box-shadow: 0 18px 44px rgba(0,0,0,.42), inset 0 1px 0 rgba(255,255,255,.10);

    opacity: 0; transform: translateY(-14px) scale(.96);
    transition: opacity .26s ease,
                transform .38s cubic-bezier(.22, 1.2, .36, 1),
                height .3s cubic-bezier(.22,1,.36,1),
                margin .3s cubic-bezier(.22,1,.36,1);
}
.hd-toast.is-in  { opacity: 1; transform: none; }
.hd-toast.is-out { opacity: 0; transform: translateY(-10px) scale(.97); }

/* The stripe is the state, restated. Colour is never the only signal —
   the icon and the wording carry it too — but the stripe is what makes
   an error legible from the corner of your eye. */
.hd-toast::before {
    content: ''; position: absolute; left: 0; top: 0; bottom: 0; width: 3px;
    background: var(--hd-accent, var(--txt-3));
}

html[data-resolved="light"] .hd-toast,
html.app[data-resolved="light"] .hd-toast {
    --hd-bg: rgba(255,255,255,.88);
    --hd-line: rgba(20,5,7,.10);
    box-shadow: 0 18px 44px rgba(110,11,18,.14), inset 0 1px 0 rgba(255,255,255,.9);
}

.hd-toast--success { --hd-accent: #35C77E; }
.hd-toast--error   { --hd-accent: #E8323A; }
.hd-toast--warning { --hd-accent: #E0A63A; }
.hd-toast--info    { --hd-accent: #7CA6FF; }
.hd-toast--loading { --hd-accent: var(--txt-3, #7C6B6F); }

html[data-resolved="light"] .hd-toast--success { --hd-accent: #128A55; }
html[data-resolved="light"] .hd-toast--error   { --hd-accent: #C4141C; }
html[data-resolved="light"] .hd-toast--warning { --hd-accent: #A9700F; }
html[data-resolved="light"] .hd-toast--info    { --hd-accent: #2C5BC4; }

/* ---- icon ----
   Drawn, not faded in. The tick writes itself left to right; the cross
   draws its two strokes in sequence. Motion that describes what happened
   reads faster than a symbol that simply appears. */
.hd-icon {
    flex: 0 0 auto; width: 30px; height: 30px; border-radius: 50%;
    display: grid; place-items: center;
    color: var(--hd-accent);
    background: color-mix(in srgb, var(--hd-accent) 16%, transparent);
}
.hd-icon svg { width: 18px; height: 18px; }
.hd-draw { transition: stroke-dashoffset .42s cubic-bezier(.65,0,.35,1) .06s; }
.hd-draw--2 { transition-delay: .2s; }
.hd-dot { fill: currentColor; stroke: none; animation: hd-pop .3s cubic-bezier(.22,1.4,.36,1) .34s both; }
@keyframes hd-pop { from { transform: scale(0); transform-origin: center; } to { transform: scale(1); } }

.hd-spin {
    stroke-dasharray: 42 16;
    transform-origin: 12px 12px;
    animation: hd-rot .9s linear infinite;
}
@keyframes hd-rot { to { transform: rotate(360deg); } }

/* ---- text ---- */
.hd-main { flex: 1; min-width: 0; padding-top: 3px; }
.hd-title {
    margin: 0; font-size: 14.5px; font-weight: 700; line-height: 1.4;
    color: var(--txt, #fff);
}
.hd-body {
    margin: 3px 0 0; font-size: 13.5px; line-height: 1.5;
    color: var(--txt-2, #B3A2A6);
}

.hd-actions { display: flex; gap: 8px; margin-top: 10px; }
.hd-action {
    padding: 7px 13px; border-radius: 999px;
    background: var(--glass, rgba(255,255,255,.10));
    border: 1px solid var(--glass-line, rgba(255,255,255,.3));
    font-size: 12.5px; font-weight: 700; color: var(--txt, #fff);
    cursor: pointer;
}
.hd-action:hover { background: var(--glass-hover, rgba(255,255,255,.2)); }

.hd-close {
    flex: 0 0 auto; width: 26px; height: 26px; margin: 1px -3px 0 0;
    border-radius: 50%; display: grid; place-items: center;
    color: var(--txt-3, #7C6B6F); background: none; border: 0; cursor: pointer;
    transition: color .14s ease, background .14s ease;
}
.hd-close:hover { color: var(--txt, #fff); background: var(--raise, rgba(255,255,255,.06)); }
.hd-close svg { width: 14px; height: 14px; }

/* ---- the countdown ----
   Shows how long is left rather than making it a surprise. Pausing on
   hover pauses this too, which is the point: a bar that keeps running
   while you read is a lie about what the timer is doing. */
.hd-bar {
    position: absolute; left: 0; right: 0; bottom: 0; height: 2px;
    background: var(--hd-accent);
    transform-origin: left center;
    opacity: .5;
}

/* =====================================================================
   MODAL DIALOG
   ===================================================================== */
.hd-scrim {
    position: fixed; inset: 0; z-index: 210;
    display: grid; place-items: center;
    padding: 20px;
    background: rgba(6, 2, 3, .62);
    backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px);
    opacity: 0; transition: opacity .22s ease;
}
.hd-scrim.is-in { opacity: 1; }
html[data-resolved="light"] .hd-scrim { background: rgba(20, 5, 7, .34); }

.hd-dialog {
    width: min(420px, 100%);
    padding: 26px 24px 20px;
    border-radius: 24px;
    background: var(--surface-2, #261A1E);
    border: 1px solid var(--line, rgba(255,255,255,.08));
    box-shadow: 0 30px 70px rgba(0,0,0,.5);
    transform: translateY(12px) scale(.97);
    transition: transform .3s cubic-bezier(.22, 1.2, .36, 1);
}
.hd-scrim.is-in .hd-dialog { transform: none; }
.hd-dialog--danger { border-color: color-mix(in srgb, var(--accent, #E8323A) 45%, transparent); }

/* Body font, sentence case. index.css sets every h1–h6 in Akira and
   uppercases it — which is right for a page heading and wrong for a
   question. "RELEASE THE REWARD?" shouts at somebody who is already
   nervous about pressing the next button, and uppercase is measurably
   slower to read at sentence length. */
.hd-dialog-title {
    margin: 0;
    font-family: var(--f-body, 'Inter', sans-serif);
    font-size: 20px; font-weight: 800; line-height: 1.3;
    text-transform: none; letter-spacing: -.01em;
    color: var(--txt, #fff);
}
.hd-dialog-body {
    margin: 10px 0 0; font-size: 14.5px; line-height: 1.6;
    color: var(--txt-2, #B3A2A6);
}
.hd-dialog-actions {
    display: flex; gap: 10px; justify-content: flex-end; margin-top: 22px;
}
.hd-dialog-actions .btn { flex: 0 1 auto; }

/* Scroll lock without the layout shift a plain overflow:hidden causes. */
body.hd-locked { overflow: hidden; }

/* =====================================================================
   MOBILE
   ===================================================================== */
@media (max-width: 767px) {
    .hd-host { width: calc(100vw - 20px); top: calc(env(safe-area-inset-top, 0px) + 10px); }
    .hd-toast { padding: 13px 12px 14px; border-radius: 16px; }
    .hd-dialog-actions { flex-direction: column-reverse; }
    .hd-dialog-actions .btn { width: 100%; }
}

/* =====================================================================
   REDUCED MOTION
   Everything collapses to a fade. The information was never in the
   movement — it is in the icon and the words.
   ===================================================================== */
@media (prefers-reduced-motion: reduce) {
    .hd-toast { transition: opacity .12s linear; transform: none; }
    .hd-toast.is-in, .hd-toast.is-out { transform: none; }
    .hd-draw { transition: none; }
    .hd-dot { animation: none; }
    .hd-spin { animation: hd-rot 2.4s linear infinite; }
    .hd-dialog { transition: none; transform: none; }
    .hd-bar { display: none; }
}

/* =====================================================================
   ACTION SHEET

   The phone shape of a destructive decision. A centred modal puts its
   buttons in the middle of a 390px screen — the part of a phone a thumb
   cannot reach — and leaves the page visible around all four edges, so
   it reads as floating rather than as a decision that has to be made.

   The sheet arrives from the bottom, sits under the thumb, and puts
   Cancel closest to the hand. The destructive row is full-width and
   further away: you have to travel to it.
   ===================================================================== */
.hd-scrim--sheet { place-items: end center; padding: 0; }

.hd-sheet {
    width: min(520px, 100%);
    padding: 10px 14px calc(14px + env(safe-area-inset-bottom, 0px));
    border-radius: 26px 26px 0 0;
    background: var(--surface-2, #261A1E);
    border: 1px solid var(--line, rgba(255,255,255,.08));
    border-bottom: 0;
    box-shadow: 0 -20px 60px rgba(0,0,0,.5);
    transform: translateY(100%);
    transition: transform .34s cubic-bezier(.22, 1, .36, 1);
    touch-action: none;
}
.hd-scrim--sheet.is-in .hd-sheet { transform: none; }

/* Says "this came from the bottom and can go back there", which is what
   makes the swipe-down discoverable instead of accidental. */
.hd-sheet-grip {
    display: block; width: 42px; height: 4px; margin: 4px auto 14px;
    border-radius: 999px; background: var(--txt-3, #7C6B6F); opacity: .4;
}

.hd-sheet-title {
    margin: 0 4px 4px;
    font-family: var(--f-body, 'Inter', sans-serif);
    font-size: 17px; font-weight: 800; line-height: 1.35;
    text-transform: none; letter-spacing: -.01em;
    color: var(--txt, #fff);
}
.hd-sheet-body {
    margin: 0 4px 14px; font-size: 13.5px; line-height: 1.55;
    color: var(--txt-2, #B3A2A6);
}

.hd-sheet-actions {
    display: flex; flex-direction: column; gap: 2px;
    border-radius: 16px; overflow: hidden;
    background: var(--raise, rgba(255,255,255,.06));
}
.hd-sheet-action {
    display: flex; align-items: center; gap: 12px;
    width: 100%; padding: 15px 16px;
    background: none; border: 0; cursor: pointer;
    font-size: 15.5px; font-weight: 600; text-align: left;
    color: var(--txt, #fff);
    transition: background .14s ease;
}
.hd-sheet-action:hover  { background: var(--raise-2, rgba(255,255,255,.1)); }
.hd-sheet-action:active { background: var(--raise-2, rgba(255,255,255,.14)); }
.hd-sheet-action.is-danger { color: #FF5A5F; }
.hd-sheet-action.is-danger:hover { background: color-mix(in srgb, #FF5A5F 14%, transparent); }

.hd-sheet-icon {
    flex: 0 0 auto; width: 34px; height: 34px; border-radius: 50%;
    display: grid; place-items: center;
    background: var(--raise-2, rgba(255,255,255,.08));
}
.hd-sheet-action.is-danger .hd-sheet-icon {
    background: color-mix(in srgb, #FF5A5F 18%, transparent);
}
.hd-sheet-icon svg { width: 18px; height: 18px; }

/* Cancel is a separate block with a gap above it, the way iOS does it.
   Grouping it with the actions makes it one more thing to read; sitting
   apart makes it the obvious way out. */
.hd-sheet-cancel {
    width: 100%; margin-top: 8px; padding: 15px 16px;
    border-radius: 16px; border: 0; cursor: pointer;
    background: var(--raise, rgba(255,255,255,.06));
    font-size: 15.5px; font-weight: 800;
    color: var(--txt, #fff);
    transition: background .14s ease;
}
.hd-sheet-cancel:hover { background: var(--raise-2, rgba(255,255,255,.1)); }

html[data-resolved="light"] .hd-sheet { background: #fff; border-color: rgba(20,5,7,.10); }
html[data-resolved="light"] .hd-sheet-action.is-danger { color: #C4141C; }

@media (min-width: 768px) {
    /* On a desktop the sheet is a floating card, not a full-width bar —
       a 1440px-wide drawer is not a thing anybody wants. */
    .hd-scrim--sheet { place-items: center; padding: 20px; }
    .hd-sheet { border-radius: 24px; border-bottom: 1px solid var(--line); transform: translateY(14px) scale(.98); }
    .hd-scrim--sheet.is-in .hd-sheet { transform: none; }
    .hd-sheet-grip { display: none; }
}

/* =====================================================================
   THE ISLAND

   A live activity. Not a toast: a toast reports an event that has
   finished, and this reports a condition that is currently true and
   will stop being true — a render working, a hunter claiming, a
   message arriving.

   It sits where the status bar is, collapses to a pill, and expands
   only as far as its content needs. One at a time.
   ===================================================================== */
.hd-island {
    position: fixed; z-index: 205;
    top: calc(env(safe-area-inset-top, 0px) + 10px);
    left: 50%;
    /* see the html.app override below */
    display: flex; align-items: center; gap: 10px;
    max-width: min(360px, calc(100vw - 28px));
    padding: 9px 14px;
    border-radius: 999px;
    background: rgba(10, 6, 7, .92);
    border: 1px solid rgba(255,255,255,.10);
    backdrop-filter: blur(26px) saturate(1.4);
    -webkit-backdrop-filter: blur(26px) saturate(1.4);
    box-shadow: 0 14px 40px rgba(0,0,0,.5);
    color: #fff;

    /* Starts as a dot and grows. The scale on the X axis is what gives
       it the "the notch stretched" read rather than "a bar appeared". */
    transform: translateX(-50%) scale(.55);
    opacity: 0;
    transition: transform .42s cubic-bezier(.22, 1.3, .36, 1),
                opacity .22s ease;
    pointer-events: none;
    white-space: nowrap;
}
.hd-island.is-open { transform: translateX(-50%) scale(1); opacity: 1; pointer-events: auto; }
.hd-island:focus-visible { outline: 2px solid var(--accent, #E8323A); outline-offset: 3px; }

/* ---- INSIDE THE APP, IT CLEARS OUR OWN HEADER ----------------------
   The metaphor is a pill in the status bar, and on a phone with a notch
   the safe-area inset puts it there. In a BROWSER that inset is 0, so
   `top: 10px` dropped it straight onto the Haulat top bar — the first
   screenshot of this feature shows it covering the wordmark with one
   edge and the notification bell with the other.

   The DOM assertions all passed. The element existed, was open, was
   centred, was 11px from the top. "Is it sitting on something else" is
   not a question the DOM can answer, and it took looking at the picture.

   So below the bar, always. It still drops from the top and still reads
   as the notch stretching; it just stops landing on our own chrome. */
html.app .hd-island {
    top: calc(env(safe-area-inset-top, 0px) + var(--topbar-h, 62px) + 2px);
}
/* When the bar has slid away on scroll-down there is nothing to clear,
   so the pill takes the space the bar gave up. */
html.app.nav-down .hd-island {
    top: calc(env(safe-area-inset-top, 0px) + 10px);
}
.hd-island { transition: transform .42s cubic-bezier(.22, 1.3, .36, 1),
                         opacity .22s ease, top .26s cubic-bezier(.22, 1, .36, 1); }

.hd-island-lead {
    flex: 0 0 auto; width: 26px; height: 26px; border-radius: 50%;
    display: grid; place-items: center;
    background: rgba(255,255,255,.10);
}
.hd-island-lead svg { width: 15px; height: 15px; }

/* No spinner unless something is actually spinning — a pulse says
   "ongoing" without claiming to know how long is left. */
.hd-island-pulse {
    width: 8px; height: 8px; border-radius: 50%;
    background: var(--accent, #E8323A);
    animation: hd-pulse 1.6s ease-in-out infinite;
}
@keyframes hd-pulse {
    0%, 100% { opacity: 1;  transform: scale(1); }
    50%      { opacity: .45; transform: scale(.72); }
}

.hd-island-mid { min-width: 0; display: flex; flex-direction: column; gap: 1px; overflow: hidden; }
.hd-island-title {
    font-size: 13px; font-weight: 700; line-height: 1.25;
    overflow: hidden; text-overflow: ellipsis;
}
.hd-island-body {
    font-size: 11.5px; line-height: 1.25; color: rgba(255,255,255,.62);
    overflow: hidden; text-overflow: ellipsis;
}
.hd-island-trail {
    flex: 0 0 auto; margin-left: 2px;
    font-size: 12px; font-weight: 700; font-variant-numeric: tabular-nums;
    color: rgba(255,255,255,.72);
}

.hd-island--success .hd-island-lead { background: color-mix(in srgb, #35C77E 26%, transparent); color: #35C77E; }
.hd-island--error   .hd-island-lead { background: color-mix(in srgb, #E8323A 26%, transparent); color: #E8323A; }
.hd-island--live    .hd-island-lead { background: color-mix(in srgb, var(--accent, #E8323A) 26%, transparent); }
/* Server flashes route through the island now, so the two types it never
   carried before need a lead colour of their own. Without these they
   inherited the neutral white wash and a warning looked like an ack. */
.hd-island--warning .hd-island-lead { background: color-mix(in srgb, #E0A63A 26%, transparent); color: #E0A63A; }
.hd-island--info    .hd-island-lead { background: rgba(255,255,255,.14); color: rgba(255,255,255,.92); }

/* A flash with a second line needs the pill to breathe — one line of
   13px and one of 11.5px in 9px of padding sits on the vertical edges. */
.hd-island:has(.hd-island-body) { padding-block: 11px; }

/* The island occupies the same strip as a toast, so push toasts down
   while one is open rather than letting them collide. */
body:has(.hd-island.is-open) .hd-host { top: calc(env(safe-area-inset-top, 0px) + 58px); }

@media (prefers-reduced-motion: reduce) {
    .hd-sheet  { transition: none; transform: none; }
    .hd-island { transition: opacity .12s linear; transform: translateX(-50%); }
    .hd-island.is-open { transform: translateX(-50%); }
    .hd-island-pulse { animation: none; }
}
