@import url("/css/vendor/pretendard.css");
@import url("/css/vendor/paperlogy.css");

/* Paperlogy is declared globally (via the vendor import above) so the
   header / brand wordmark renders the same on EVERY page — not just the
   ones whose per-page CSS happens to include @font-face. Previously
   /about lacked Paperlogy entirely (about.css had no face declarations),
   which made the header nav fall back to Pretendard there while /stage
   rendered it in Paperlogy. Two weights cover the whole UI: 400 body +
   900 display. Self-hosted via static.s.show (see vendor/*.css). */

body,
html {
    margin: 0;
    padding: 0;
    height: 100%;
    width: 100%;
    max-width: 100%;
    overflow-x: hidden;
    font-family: "Pretendard Variable", Pretendard, -apple-system, BlinkMacSystemFont, system-ui, Roboto, "Helvetica Neue", "Segoe UI", "Apple SD Gothic Neo", "Noto Sans KR", "Malgun Gothic", "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", sans-serif;
    /* background-color: var(--global-background-color); */
    color: var(--global-text-color);
    touch-action: pan-x pan-y;
    -webkit-tap-highlight-color: transparent;
    color: var(--global-text-color);
}

div,
input,
textarea,
select {
    box-sizing: border-box;
}

a {
    color: inherit;
    text-decoration: none;
}

/* Minimal cross-browser button normalization.
   ONLY fixes UA quirks that break alignment (font/box-sizing/margin/line-height).
   Does NOT touch padding/border/background/color/min-height — those belong to
   each component (site chrome, editor header, SSHOW core) and must not be
   overridden globally. */
button {
    font-family: inherit;
    font-size: 100%;
    line-height: inherit;
    margin: 0;
    box-sizing: border-box;
}

/* Site chrome buttons only.
   Scoped to the account area + footer so neither SSHOW core nor the editor's
   own header controls (.header-editor-*) get overridden. DI primitives
   (.di-btn, .di-btn-icon) own their full chrome and must not be reset. */
#header-account button:not(.di-btn):not(.di-btn-icon):not(.header-menu-trigger):not(.header-user-trigger),
#wrapper-footer button:not(.di-btn):not(.di-btn-icon) {
    box-sizing: border-box;
    font-family: inherit;
    cursor: pointer;
    border: 1px solid transparent;
    background: transparent;
    color: inherit;
    padding: 0;
}


#wrapper-header {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: var(--header-height);
    /* Solid fallback first — required for iOS Safari, which silently
     * drops backdrop-filter under low memory or while a WebGL canvas
     * is active. Without this fallback the fixed header reads as
     * "transparent" against scrolling content and looks invisible. */
    background: var(--header-background-color);
    z-index: 1000;
    display: flex;
    align-items: center;
    justify-content: center;
    border-bottom: 1px solid rgba(var(--global-text-color-rgb), 0.06);

    /* Pin the header to its own GPU compositor layer. iOS Safari was
     * flickering / disappearing the fixed bar whenever a descendant
     * page used a WebGL canvas with mix-blend-mode (about page aurora
     * + constellation), because the compositor kept re-promoting and
     * losing the fixed layer. translateZ(0) + will-change locks it. */
    transform: translateZ(0);
    -webkit-transform: translateZ(0);
    -webkit-backface-visibility: hidden;
    backface-visibility: hidden;
    /* Isolate paint so the header doesn't get re-rasterised when the
     * page below repaints (scroll-driven canvases, etc).
     * NOTE: `contain: paint` would clip the user-menu dropdown popover
     * to the header's height. We keep style/layout containment for the
     * compositor benefit but drop `paint` so popovers can escape. */
    contain: layout style;
    isolation: isolate;
    overflow: visible;
}

/* Apply the blur only when the browser actually supports it AND we
 * are not in a reduced-transparency context. iOS Safari with this
 * @supports gate behaves much more stably than blanket application. */
@supports ((backdrop-filter: blur(10px)) or (-webkit-backdrop-filter: blur(10px))) {
    #wrapper-header {
        backdrop-filter: blur(10px);
        -webkit-backdrop-filter: blur(10px);
    }
}

/* =====================================================================
 * Global announcement banner
 * ---------------------------------------------------------------------
 * Fixed strip at the very top, z-indexed above the header (1100 vs
 * header's 1000). When `body.has-announcement` is present, the header
 * and content offsets shift down by --announcement-h so the banner
 * doesn't cover anything. Banner content is server-rendered into
 * announcement.ejs; dismiss is JS-driven via /js/announcement.js.
 * ===================================================================== */
:root { --announcement-h: 0px; }

body.has-announcement {
    --announcement-h: 40px;
    padding-top: var(--announcement-h);
}
body.has-announcement #wrapper-header { top: var(--announcement-h); }

.announcement-banner {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: var(--announcement-h);
    z-index: 1100;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 0 16px;
    font-size: 13px;
    color: var(--global-text-color);
    background: var(--state-hover-bg);
    border-bottom: 1px solid var(--global-border-color);
}
/* Warning uses brass — the warm jewel stop (§2.4). Tinted surface with
   brass-toned text keeps the v4 single-palette story intact instead of
   reintroducing the legacy candy amber. */
.announcement-banner[data-level="warning"] {
    background: rgba(var(--brand-brass-rgb), 0.16);
    color: rgb(124, 84, 36);
    border-bottom-color: rgba(var(--brand-brass-rgb), 0.36);
}
@media (prefers-color-scheme: dark) {
    .announcement-banner[data-level="warning"] {
        background: rgba(var(--brand-brass-rgb), 0.14);
        color: rgb(221, 180, 142);
        border-bottom-color: rgba(var(--brand-brass-rgb), 0.32);
    }
}

.announcement-banner-inner {
    display: inline-flex;
    align-items: center;
    gap: 10px;
    max-width: 1200px;
    min-width: 0;
}
.announcement-banner-icon {
    width: 16px;
    height: 16px;
    flex-shrink: 0;
}
.announcement-banner-text {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    letter-spacing: -0.005em;
}
.announcement-banner-link {
    flex-shrink: 0;
    font-weight: 500;
    color: inherit;
    text-decoration: underline;
    text-underline-offset: 3px;
    border-radius: 4px;
}
.announcement-banner-link:hover { opacity: 0.85; }
.announcement-banner-link:focus-visible {
    outline: 2px solid var(--focus-outline-color);
    outline-offset: 2px;
}

.announcement-banner-dismiss {
    flex-shrink: 0;
    width: 24px;
    height: 24px;
    padding: 0;
    margin-left: 4px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: transparent;
    border: none;
    border-radius: 999px;
    color: inherit;
    cursor: pointer;
    opacity: 0.7;
    transition: opacity 0.15s ease, background-color 0.15s ease;
}
.announcement-banner-dismiss:hover {
    opacity: 1;
    background: var(--state-active-bg);
}
.announcement-banner-dismiss:focus-visible {
    opacity: 1;
    outline: 2px solid var(--focus-outline-color);
    outline-offset: 2px;
}
.announcement-banner-dismiss svg { width: 14px; height: 14px; }

@media (prefers-reduced-motion: reduce) {
    .announcement-banner-dismiss { transition: none; }
}
@media (max-width: 640px) {
    .announcement-banner { padding: 0 12px; font-size: 12.5px; }
    .announcement-banner-link { display: none; }
}

#header-contents {
    position: relative;
    height: 100%;
    width: 100%;
    max-width: var(--header-max-width);
    padding: 0 calc(var(--global-padding) * 1.6);
    /* 3-column grid so the primary nav is perfectly centered, independent
     * of logo / account-cluster widths. Matches apple.com's globalnav. */
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    align-items: center;
    gap: var(--global-gap);
    background: transparent;
}

#header-logo {
    height: 100%;
    grid-column: 1;
    justify-self: start;
    display: flex;
    align-items: center;
    gap: 10px;
    font-family: 'Paperlogy', 'Pretendard Variable', Pretendard, -apple-system, sans-serif;
    font-weight: 800;
    font-size: 1.05em;
    letter-spacing: -0.01em;
    cursor: pointer;
    user-select: none;
    /* Lockup is an <a> for keyboard / middle-click semantics; suppress
       the default underline + color inheritance so it looks identical
       to the previous <div>+onclick version. */
    text-decoration: none;
    color: inherit;
}

#header-logo svg {
    /* Explicit pixel height — percentage heights against a flex item
     * inside a grid cell are unreliable on iOS Safari and were causing
     * the logo to render at 0px on first paint until a tap forced a
     * relayout. 22px = roughly the previous 48% of the 55px header. */
    height: 22px;
    width: auto;
    flex-shrink: 0;
    fill: var(--global-text-color);
    transition: opacity 0.2s ease;
}

#header-logo:hover svg {
    opacity: 0.75;
}

/* Header nav: pin the family explicitly so the active-state weight
 * (500) synthesises from the same Paperlogy face on every page. */
#header-nav {
    grid-column: 2;
    justify-self: center;
    display: flex;
    align-items: center;
    gap: 22px;
    margin: 0;
}
#header-nav .header-nav-link {
    display: inline-flex;
    align-items: center;
    padding: 6px 2px;
    font-size: 13px;
    line-height: 1;
    color: var(--global-text-color);
    opacity: 0.72;
    transition: opacity 0.18s ease;
    font-family: 'Pretendard Variable', Pretendard, -apple-system, BlinkMacSystemFont, system-ui, sans-serif;
    font-weight: 400;
    letter-spacing: -0.005em;
    background: transparent;
    border-radius: 0;
}
#header-nav .header-nav-link:hover {
    opacity: 1;
    background: transparent;
}
#header-nav .header-nav-link.is-active {
    opacity: 1;
    font-weight: 500;
    background: transparent;
}

/* Replace the UA-default focus ring (a hard rectangle around the link
   that appears after tab navigation and on some browsers after clicks)
   with a brand-toned underline — matches the hairline aesthetic used
   across the rest of the page (auth pages, profile tabs, etc.). The
   plain `:focus` outline is killed too so non-keyboard interactions
   never paint a focus ring. */
#header-nav .header-nav-link:focus { outline: none; }
#header-nav .header-nav-link:focus-visible {
    outline: none;
    text-decoration: underline;
    text-decoration-thickness: 1.5px;
    text-underline-offset: 6px;
    text-decoration-color: currentColor;
}
/* Narrow viewports — keep the nav, just tighten it. Korean labels are
 * short enough that they fit next to the logo on a phone; the real
 * clutter was the beta badge + generous paddings, which we trim below. */
@media (max-width: 640px) {
    #header-nav { gap: 14px; }
    #header-nav .header-nav-link { font-size: 12.5px; padding: 6px 0; }
}

.header-logo-badge {
    display: inline-flex;
    align-items: center;
    height: 20px;
    padding: 0 7px;
    font-family: "Pretendard Variable", Pretendard, -apple-system, sans-serif;
    font-size: 9px;
    font-weight: 600;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--global-text-muted);
    border: 1px solid rgba(var(--global-text-color-rgb), 0.1);
    border-radius: 999px;
    line-height: 1;
    white-space: nowrap;
}

/* Environment marker (stg/dev). Same neutral pill geometry as the BETA
 * badge — no brand color (DESIGN.md reserves it) — but filled with a
 * neutral state tint so it reads as a distinct ops cue, not a product
 * label. Only rendered when ENV !== 'prod'. */
.header-logo-badge--env {
    color: var(--global-text-color);
    background: rgba(var(--global-text-color-rgb), 0.08);
    border-color: rgba(var(--global-text-color-rgb), 0.16);
}

/* On phones, keep the BETA pill (brand cue) but shrink it so the
 * header cluster stays comfortable on the Korean build. Only hide
 * on extremely narrow screens where wrap is unavoidable. */
@media (max-width: 640px) {
    .header-logo-badge {
        height: 16px;
        padding: 0 5px;
        font-size: 8px;
        letter-spacing: 0.08em;
    }
    #header-contents { padding: 0 var(--global-padding); gap: 8px; }
}
@media (max-width: 360px) {
    .header-logo-badge { display: none; }
}

#header-menu {
    height: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: calc(var(--global-gap) * 3);
    list-style: none;
    margin: 0;
    padding: 0;
    font-size: 0.9em;
    color: var(--global-text-muted);
}

#header-menu li {
    cursor: pointer;
    transition: color 0.2s ease;
}

#header-menu li:hover {
    color: var(--global-text-color);
}

#header-account {
    height: 100%;
    grid-column: 3;
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 6px;
}

/* Storage usage gauge — header chip showing tenant-scoped quota %.
   Sits at the leading edge of #header-account (before settings/avatar)
   so users see their headroom at a glance during studio work. The chip
   geometry matches the 28px header pill family. State colors:
     ok       — neutral text/border
     warn     — amber when ≥80%
     critical — red when ≥95% (also triggers an upgrade modal once)
     unlimited — dim "∞" badge for ENTERPRISE
   Hidden until the first /api/projects/usage/me payload to avoid a
   visual flash for unauthenticated/error states. */
#header-account .header-storage-gauge {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    height: 28px;
    padding: 0 10px;
    line-height: 1;
    border-radius: 999px;
    color: var(--global-text-muted, rgba(var(--global-text-color-rgb), 0.7));
    background: transparent;
    border: 1px solid rgba(var(--global-text-color-rgb), 0.1);
    cursor: pointer;
    font-size: 12px;
    font-weight: 500;
    transition: background-color 0.18s ease, border-color 0.18s ease, color 0.18s ease;
}
#header-account .header-storage-gauge[hidden] { display: none; }
#header-account .header-storage-gauge:hover {
    color: var(--global-text-color);
    background: rgba(var(--global-text-color-rgb), 0.05);
    border-color: rgba(var(--global-text-color-rgb), 0.2);
}
#header-account .header-storage-gauge:focus-visible {
    outline: 2px solid var(--focus-outline-color);
    outline-offset: 2px;
}
#header-account .header-storage-gauge .storage-gauge-bar {
    position: relative;
    width: 56px;
    height: 6px;
    border-radius: 999px;
    background: rgba(var(--global-text-color-rgb), 0.1);
    overflow: hidden;
}
#header-account .header-storage-gauge .storage-gauge-fill {
    position: absolute;
    inset: 0 auto 0 0;
    width: 0%;
    background: var(--global-text-color);
    transition: width 0.4s ease, background-color 0.2s ease;
}
#header-account .header-storage-gauge .storage-gauge-label {
    font-variant-numeric: tabular-nums;
    min-width: 2.4em;
    text-align: right;
}
/* Warn (≥80%) — amber */
#header-account .header-storage-gauge[data-state="warn"] {
    color: #b45309;
    border-color: rgba(180, 83, 9, 0.35);
}
#header-account .header-storage-gauge[data-state="warn"] .storage-gauge-fill {
    background: #f59e0b;
}
/* Critical (≥95%) — red */
#header-account .header-storage-gauge[data-state="critical"] {
    color: #b91c1c;
    border-color: rgba(185, 28, 28, 0.4);
    background: rgba(185, 28, 28, 0.04);
}
#header-account .header-storage-gauge[data-state="critical"] .storage-gauge-fill {
    background: #dc2626;
}
/* Unlimited — quiet badge */
#header-account .header-storage-gauge[data-state="unlimited"] .storage-gauge-bar {
    display: none;
}
#header-account .header-storage-gauge[data-state="unlimited"] .storage-gauge-label {
    min-width: auto;
    font-size: 14px;
}

/* ---------- Storage gauge popover ----------------------------------
   Re-uses the existing `.header-user-dropdown` shell (border, shadow,
   animation) so the popover frame matches the user menu exactly. The
   classes below only style the *content* inside that shell. */
#header-storage-menu {
    position: relative;
    display: inline-flex;
    align-items: center;
}
#header-account .header-user-dropdown.header-storage-dropdown {
    /* Slightly wider than the user menu — needs room for two-column dl. */
    min-width: 260px;
    padding: 12px 12px 6px;
    gap: 0;
}
.header-storage-pop-head {
    margin: 0 2px 8px;
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--global-text-muted);
}
.header-storage-pop-bar {
    position: relative;
    height: 8px;
    border-radius: 999px;
    background: rgba(var(--global-text-color-rgb), 0.08);
    overflow: hidden;
    margin: 0 2px;
}
.header-storage-pop-bar-fill {
    position: absolute;
    inset: 0 auto 0 0;
    width: 0%;
    background: var(--global-text-color);
    border-radius: 999px;
    transition: width 0.4s ease;
}
.header-storage-pop-bar[data-state="warn"]            { background: rgba(245, 158, 11, 0.12); }
.header-storage-pop-bar[data-state="warn"] .header-storage-pop-bar-fill     { background: #f59e0b; }
.header-storage-pop-bar[data-state="critical"]        { background: rgba(220, 38, 38, 0.12); }
.header-storage-pop-bar[data-state="critical"] .header-storage-pop-bar-fill { background: #dc2626; }
.header-storage-pop-bar[data-state="unlimited"] .header-storage-pop-bar-fill { background: var(--brand-iris, #6750D8); }
.header-storage-pop-summary {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    margin: 6px 2px 10px;
    font-size: 12px;
    color: var(--global-text-muted);
    font-variant-numeric: tabular-nums;
}
.header-storage-pop-numbers strong {
    color: var(--global-text-color);
    font-weight: 600;
}
.header-storage-pop-divider { margin: 0 4px; opacity: 0.5; }
.header-storage-pop-percent {
    font-size: 13px;
    font-weight: 600;
    color: var(--global-text-color);
}
.header-storage-pop-bar[data-state="warn"]      ~ .header-storage-pop-summary .header-storage-pop-percent { color: #b45309; }
.header-storage-pop-bar[data-state="critical"]  ~ .header-storage-pop-summary .header-storage-pop-percent { color: #b91c1c; }

.header-storage-pop-grid {
    display: grid;
    grid-template-columns: 1fr auto;
    column-gap: 12px;
    row-gap: 6px;
    margin: 6px 2px 0;
    font-size: 12px;
}
.header-storage-pop-grid dt {
    color: var(--global-text-muted);
    margin: 0;
}
.header-storage-pop-grid dd {
    margin: 0;
    text-align: right;
    color: var(--global-text-color);
    font-weight: 500;
    font-variant-numeric: tabular-nums;
}
.header-user-dropdown.header-storage-dropdown .header-user-sep {
    margin: 8px -6px;
}

/* Header account buttons — quiet text-link feel; primary action keeps a soft pill.
   Scoped to #header-account so the editor's header controls are not affected.
   DI primitives (.di-btn, .di-btn-icon) own their full chrome and must
   not be re-skinned here. The mobile hamburger (.header-menu-trigger) and
   the user-menu trigger pill (.header-user-trigger) are also excluded —
   they own their geometry, and `:not(.x):not(.y)` here carries specificity
   (0,1,2,1) which would otherwise outrank their own `display: none` /
   pill-shape rules and force them visible on every viewport. */
#header-account button:not(.di-btn):not(.di-btn-icon):not(.header-menu-trigger):not(.header-user-trigger) {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    height: auto;
    min-height: 32px;
    padding: 0 14px;
    font-size: 13px;
    font-weight: 500;
    line-height: 1;
    letter-spacing: -0.005em;
    border-radius: 999px;
    background: transparent;
    color: var(--global-text-color);
    border: 1px solid transparent;
    transition: background-color 0.18s ease, border-color 0.18s ease, opacity 0.18s ease;
}

#header-account button:not(.di-btn):not(.di-btn-icon):not(.header-menu-trigger):not(.header-user-trigger):hover {
    background: rgba(var(--global-text-color-rgb), 0.06);
}

#header-account button#button-header-login {
    background: var(--global-button-background-color);
    color: var(--global-button-text-color);
    font-weight: 600;
}

#header-account button#button-header-login:hover {
    opacity: 0.85;
    background: var(--global-button-background-color);
}

/* =====================================================================
 * Unified user menu (avatar trigger + dropdown popover)
 * ---------------------------------------------------------------------
 * One component shared by the marketing header (/) AND the studio
 * header (/studio, /studio/:id). Replaces the previous user-chip +
 * Studio + Logout button trio.
 *
 * Reference: /designIdentity §08 Dropdown.
 * Spec: docs/DESIGN.md §7.10 Dropdown menu.
 * ===================================================================== */
.header-user-menu {
    position: relative;
    display: inline-flex;
    align-items: center;
}

/* Trigger pill — avatar + name + caret. Uses --global tokens so it
   adapts to light/dark and to the studio shell automatically.
   The selector is intentionally generic (no #header-account ancestor)
   so the same component renders identically on /designIdentity and any
   future host. The :where() raises specificity just enough to beat
   the global #header-account button reset without becoming a !important
   sledgehammer. */
.header-user-trigger {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    height: 34px;
    padding: 3px 10px 3px 3px;
    background: transparent;
    border: 1px solid rgba(var(--global-text-color-rgb), 0.1);
    border-radius: 999px;
    color: var(--global-text-color);
    font-size: 12.5px;
    font-weight: 500;
    line-height: 1;
    letter-spacing: -0.005em;
    cursor: pointer;
    /* Reset the generic #header-account button rule when nested there. */
    min-height: 0;
    transition:
        background-color 0.18s cubic-bezier(0.22, 0.61, 0.36, 1),
        border-color 0.18s cubic-bezier(0.22, 0.61, 0.36, 1);
}
/* Specificity bump for header context. `#header-account button` (1-0-1)
   outranks `.header-user-trigger` (0-1-0) and would otherwise reset our
   pill geometry, so we re-assert ALL the visuals that the generic
   header-button rule clobbers (padding, border, background, min-height,
   gap). The base `.header-user-trigger` rule above still drives the
   /designIdentity demo and any future host outside #header-account. */
#header-account button.header-user-trigger {
    gap: 8px;
    height: 34px;
    min-height: 0;
    padding: 3px 10px 3px 3px;
    background: transparent;
    border: 1px solid rgba(var(--global-text-color-rgb), 0.1);
    border-radius: 999px;
    color: var(--global-text-color);
    font-size: 12.5px;
    font-weight: 500;
    line-height: 1;
    letter-spacing: -0.005em;
}
#header-account button.header-user-trigger:hover,
#header-account button.header-user-trigger[aria-expanded="true"] {
    background: rgba(var(--global-text-color-rgb), 0.05);
    border-color: rgba(var(--global-text-color-rgb), 0.18);
}

.header-user-trigger:hover,
.header-user-trigger[aria-expanded="true"] {
    background: rgba(var(--global-text-color-rgb), 0.05);
    border-color: rgba(var(--global-text-color-rgb), 0.18);
}

.header-user-avatar {
    display: block;
    width: 26px;
    height: 26px;
    border-radius: 999px;
    flex-shrink: 0;
    object-fit: cover;
    background: rgba(var(--global-text-color-rgb), 0.04);
    box-shadow: inset 0 0 0 1px rgba(var(--global-text-color-rgb), 0.08);
}

.header-user-name {
    max-width: 140px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    color: var(--global-text-color);
}

.header-user-caret {
    color: var(--global-text-muted);
    flex-shrink: 0;
    transition: transform 0.18s cubic-bezier(0.22, 0.61, 0.36, 1);
}
.header-user-trigger[aria-expanded="true"] .header-user-caret {
    transform: rotate(180deg);
}

/* Popover */
.header-user-dropdown {
    position: absolute;
    top: calc(100% + 8px);
    right: 0;
    min-width: 200px;
    padding: 6px;
    background: var(--global-card-background, #fff);
    border: 1px solid rgba(var(--global-text-color-rgb), 0.1);
    border-radius: 12px;
    box-shadow:
        0 1px 2px rgba(0, 0, 0, 0.04),
        0 16px 40px rgba(10, 10, 12, 0.14);
    display: flex;
    flex-direction: column;
    gap: 2px;
    z-index: 1100;
    transform-origin: top right;
    animation: header-user-dropdown-in 160ms cubic-bezier(0.22, 0.61, 0.36, 1);
}
.header-user-dropdown[hidden] { display: none; }

@keyframes header-user-dropdown-in {
    from { opacity: 0; transform: translateY(-4px) scale(0.98); }
    to   { opacity: 1; transform: translateY(0) scale(1); }
}

/* Items — mix of <a> (links) and <button> (actions). Both must size
   identically so the bumped selector below outranks the generic
   #header-account button reset when nested inside the header. */
.header-user-item {
    display: inline-flex;
    align-items: center;
    justify-content: flex-start;
    gap: 10px;
    width: 100%;
    height: 34px;
    padding: 0 10px;
    background: transparent;
    border: 0;
    border-radius: 8px;
    color: var(--global-text-color);
    font-family: inherit;
    font-size: 13px;
    font-weight: 500;
    text-align: left;
    text-decoration: none;
    cursor: pointer;
    box-sizing: border-box;
    min-height: 0;
    transition:
        background-color 0.18s cubic-bezier(0.22, 0.61, 0.36, 1),
        color 0.18s cubic-bezier(0.22, 0.61, 0.36, 1);
}
/* Specificity bump for header context — same reasoning as the trigger
   above: `#header-account button` clobbers our padding/border/etc, so
   we re-assert the full pill-item geometry here. */
#header-account button.header-user-item {
    gap: 10px;
    height: 34px;
    min-height: 0;
    padding: 0 10px;
    background: transparent;
    border: 0;
    border-radius: 8px;
    color: var(--global-text-color);
    font-family: inherit;
    font-size: 13px;
    font-weight: 500;
    text-align: left;
    justify-content: flex-start;
    letter-spacing: -0.005em;
}

.header-user-item:hover,
.header-user-item:focus-visible {
    background: rgba(var(--global-text-color-rgb), 0.05);
    outline: none;
}
#header-account button.header-user-item:hover {
    background: rgba(var(--global-text-color-rgb), 0.05);
}

.header-user-item svg {
    color: var(--global-text-muted);
    flex-shrink: 0;
    transition: color 0.18s cubic-bezier(0.22, 0.61, 0.36, 1);
}
.header-user-item:hover svg { color: var(--global-text-color); }

/* Brand-anchored item in the menu (e.g. "Studio" in the marketing
   header dropdown). The label keeps the standard text color; the
   icon stays neutral too — the iridescent header CTA carries the
   brand cue, so a solo-violet icon here would double-tell. Per the
   v4 interaction-state rule (DESIGN.md §2.5 + §0 principle 5): brand color
   is for ambient or coordinated sets, never solo accents. */
.header-user-item-accent { color: var(--global-text-color); }
.header-user-item-accent svg { color: var(--global-text-color); opacity: 0.85; }
.header-user-item-accent:hover svg { color: var(--global-text-color); opacity: 1; }

.header-user-item-danger { color: #d04848; }
.header-user-item-danger svg { color: #d04848; opacity: 0.85; }
/* Header context bump \u2014 the trigger/item bumps above set color to
   --global-text-color, so danger needs to re-assert its red here too. */
#header-account button.header-user-item-danger { color: #d04848; }
.header-user-item-danger:hover,
#header-account button.header-user-item-danger:hover {
    background: rgba(208, 72, 72, 0.08);
    color: #b53333;
}
.header-user-item-danger:hover svg { color: #b53333; opacity: 1; }

.header-user-sep {
    height: 1px;
    margin: 4px 6px;
    background: rgba(var(--global-text-color-rgb), 0.08);
}

/* =====================================================================
 * (Pending invitations badge / count rules removed — the invite
 *  inbox is now an in-header bell + popover; see inbox-popover.css.)
 * ===================================================================== */

/* On phones, collapse the user trigger to a chromeless 28×28 — same
   footprint as the hamburger so the avatar + menu read as a single
   right-side cluster. The desktop pill border / asymmetric padding
   would otherwise leak through (specificity battle with the
   `#header-account button` reset) and inflate the button to 31×34.

   The hover / aria-expanded states keep their background tint, which
   carries enough affordance to signal "this is a button" without the
   always-visible border. */
@media (max-width: 640px) {
    #header-account button.header-user-trigger,
    .header-user-trigger {
        width: 28px;
        height: 28px;
        padding: 0;
        border: 0;
    }
    .header-user-name,
    .header-user-caret { display: none; }
    .header-user-dropdown { min-width: 180px; }
}
/* =====================================================================
 * Iridescent CTA \u2014 shared brand keyframe + utility class.
 * ---------------------------------------------------------------------
 * Single source of truth for the "always-on chromatic drift" animation.
 * Used by:
 *   \u2022 header Studio CTA (#button-header-studio)         \u00b7 index.css
 *   \u2022 .di-btn-iridescent variant of the unified buttons \u00b7 designIdentity.css
 *   \u2022 anywhere else a button needs the brand iridescent treatment
 * Per docs/DESIGN.md \u00a77.4 "Buttons \u2014 unified system".
 * ===================================================================== */

@keyframes brand-iridescent-shift {
    0%   { background-position:   0% 50%; }
    50%  { background-position: 100% 50%; }
    100% { background-position:   0% 50%; }
}

.btn-iridescent {
    background: var(--brand-iridescent-gradient);
    background-size: 300% 300%;
    color: #fff;
    border: 1px solid transparent;
    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.15);
    animation: brand-iridescent-shift 20s ease infinite;
}

/* =====================================================================
 * Visually-hidden utility — for labels we want screen readers to
 * announce but that should not consume layout. Used by the mobile menu
 * sheet's `<h2 class="sr-only">` and similar microcopy.
 * ===================================================================== */
.sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    border: 0;
    overflow: hidden;
    clip: rect(0 0 0 0);
    clip-path: inset(50%);
    white-space: nowrap;
}

/* =====================================================================
 * Mobile menu — hamburger trigger + full-bleed sheet
 * ---------------------------------------------------------------------
 * Surfaces ONLY ≤640px. Above that the trigger is display:none and the
 * sheet is display:none, so neither contributes a single layout/paint
 * cycle on desktop.
 *
 * The panel does NOT animate in/out — appearance is a synchronous
 * display:none ↔ block flip. The only thing that transitions is the
 * hamburger glyph morphing to an X (transform only). Rationale: a
 * full-bleed opaque panel doesn't benefit from a slide-in, and a fade
 * would cost a layer-promotion + composite for no visual gain.
 *
 * Rendering contract — every state change here goes through one of:
 *   1. attribute toggle (hidden ↔ shown)  → display: none ↔ block
 *   2. body class toggle (.is-menu-open) → scroll lock + hamburger X
 * We never animate `visibility`/`display`/`height` because each triggers
 * a layout or a synchronous repaint without smooth interpolation.
 * ===================================================================== */

/* ---------- Hamburger trigger -------------------------------------- */
/* Geometry mirrors the avatar pill on mobile (28×28) so the right
   cluster reads as a single tap-target band. The two hairlines morph
   into an X via translateY + rotate when <body> carries .is-menu-open. */
.header-menu-trigger {
    display: none; /* surfaces at ≤640px; see media query below */
    width: 28px;
    height: 28px;
    padding: 0;
    background: transparent;
    border: 0;
    border-radius: 999px;
    color: var(--global-text-color);
    cursor: pointer;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    /* Match the avatar trigger's hover tint so the cluster feels uniform. */
    transition: background-color 0.18s cubic-bezier(0.22, 0.61, 0.36, 1);
}
.header-menu-trigger:hover,
.header-menu-trigger[aria-expanded="true"] {
    background: rgba(var(--global-text-color-rgb), 0.06);
}
/* Specificity bump — the generic #header-account button reset would
   otherwise re-paint the chip with the border/padding of a text link. */
#header-account button.header-menu-trigger {
    display: none;
    width: 28px;
    height: 28px;
    min-height: 0;
    padding: 0;
    background: transparent;
    border: 0;
    border-radius: 999px;
    color: var(--global-text-color);
}
#header-account button.header-menu-trigger:hover,
#header-account button.header-menu-trigger[aria-expanded="true"] {
    background: rgba(var(--global-text-color-rgb), 0.06);
}

/* Two-line glyph. Both bars are anchored at the icon's vertical centre
   (top: 50%) and pushed apart by translateY at rest — when .is-menu-open
   the translate snaps to 0 and a rotate is added, so the two bars meet
   EXACTLY at the centre to form a clean X. The earlier "top: 2px /
   bottom: 2px" geometry left the bars off-centre and the rotation pivot
   was nowhere near where the bars meet, which made the X look skewed. */
.header-menu-trigger-icon {
    position: relative;
    display: block;
    width: 16px;
    height: 14px;
    flex-shrink: 0;
}
.header-menu-trigger-icon > span {
    position: absolute;
    top: 50%;
    left: 0;
    right: 0;
    height: 1.5px;
    margin-top: -0.75px; /* perfect centring on top:50% */
    background: currentColor;
    border-radius: 1px;
    /* Animate transform only — never width/height/top. No `will-change`
       here: a 1.5px line on a tiny button doesn't benefit from a pinned
       GPU layer, and reserving one per span keeps a layer alive forever
       (one of those "harmless tax" lines we explicitly don't pay). */
    transition: transform 0.22s cubic-bezier(0.22, 0.61, 0.36, 1);
}
.header-menu-trigger-icon > span:nth-child(1) { transform: translateY(-4px); }
.header-menu-trigger-icon > span:nth-child(2) { transform: translateY( 4px); }

body.is-menu-open .header-menu-trigger-icon > span:nth-child(1) {
    transform: rotate(45deg);
}
body.is-menu-open .header-menu-trigger-icon > span:nth-child(2) {
    transform: rotate(-45deg);
}

/* ---------- Sheet shell -------------------------------------------- */
/* Desktop default: kill the whole tree so it costs nothing.
   `display: none` beats the `hidden` attribute when both are set, but
   we keep `hidden` as the JS-side toggle for accessibility (the
   attribute also implies inert content to AT). */
.header-menu-sheet { display: none; }
.header-menu-sheet[hidden] { display: none; }

/* ---------- Sheet layout (mobile only) ----------------------------- */
@media (max-width: 640px) {
    /* On phones, swap the inline nav for the hamburger. The inline
       links are kept in the DOM (their click semantics still match
       desktop) but visually retired so the header cluster is just
       logo · avatar · hamburger. */
    #header-nav { display: none; }

    /* Surface the hamburger. We use inline-flex (not flex) so it sits
       beside the avatar trigger without consuming a row. */
    .header-menu-trigger,
    #header-account button.header-menu-trigger {
        display: inline-flex;
    }

    /* Sheet container — fills the viewport BELOW the fixed header
       (and BELOW the announcement banner when present). z-index sits
       above the header (1000) so the close-X / focus order stays
       reachable; the trigger itself stays in the header above the
       sheet to give a single, persistent "close" affordance.

       NOTE: `top` uses calc with both --header-height and
       --announcement-h so the sheet never covers the banner — the
       banner remains tappable for dismiss while the menu is open. */
    .header-menu-sheet {
        display: block;
        position: fixed;
        top: calc(var(--header-height) + var(--announcement-h, 0px));
        left: 0;
        right: 0;
        bottom: 0;
        z-index: 999; /* under the fixed header (1000) → trigger stays on top */
        pointer-events: none; /* enabled only when .is-menu-open */
        /* Paint isolation so opening the sheet does not invalidate the
           scroll-driven canvases below it. */
        contain: layout style;
        isolation: isolate;
    }
    .header-menu-sheet[hidden] { display: none; }
    body.is-menu-open .header-menu-sheet { pointer-events: auto; }

    /* Panel — the actual scrollable list surface. Appears instantly
       (no slide/fade) per spec — the only visible change on open is the
       hamburger morphing to an X and the panel taking over the viewport.
       Solid background = no flicker; no transition = nothing for the
       compositor to interpolate. */
    .header-menu-sheet-panel {
        position: absolute;
        inset: 0;
        overflow-y: auto;
        -webkit-overflow-scrolling: touch;
        background: var(--global-body-background);
        color: var(--global-text-color);
        padding: 12px 0 max(32px, env(safe-area-inset-bottom));
    }

    /* ---------- List + items ---------------------------------------- */
    .header-menu-sheet-list {
        display: flex;
        flex-direction: column;
        margin: 0;
        padding: 0;
    }

    /* Row geometry — Apple-style "large row" list, hairline divider
       between items. The whole row is the tap target so a thumb on
       either edge still hits it. */
    .header-menu-sheet-item {
        display: flex;
        align-items: center;
        justify-content: space-between;
        gap: 16px;
        width: 100%;
        height: 56px;
        padding: 0 calc(var(--global-padding) * 1.6);
        background: transparent;
        border: 0;
        border-bottom: 1px solid rgba(var(--global-text-color-rgb), 0.06);
        color: var(--global-text-color);
        font-family: inherit;
        font-size: 17px;
        font-weight: 500;
        line-height: 1;
        letter-spacing: -0.012em;
        text-align: left;
        text-decoration: none;
        cursor: pointer;
        box-sizing: border-box;
        transition: background-color 0.15s cubic-bezier(0.22, 0.61, 0.36, 1);
    }
    .header-menu-sheet-list:last-child .header-menu-sheet-item:last-child,
    .header-menu-sheet-list .header-menu-sheet-item:last-of-type {
        /* Trailing hairline duplicates the section separator below. */
        border-bottom: 0;
    }
    /* :active is the right state on a touch device — :hover lingers
       weirdly on iOS after a tap. */
    .header-menu-sheet-item:active {
        background: rgba(var(--global-text-color-rgb), 0.05);
    }
    .header-menu-sheet-item.is-active {
        font-weight: 700;
    }
    .header-menu-sheet-item.is-active .header-menu-sheet-chev {
        opacity: 1;
    }
    .header-menu-sheet-chev {
        color: var(--global-text-muted);
        opacity: 0.5;
        flex-shrink: 0;
        transition: transform 0.15s cubic-bezier(0.22, 0.61, 0.36, 1);
    }
    .header-menu-sheet-item:active .header-menu-sheet-chev {
        transform: translateX(2px);
    }

    /* Accent + danger variants — quiet, hairline only. Per DI v4 the
       brand iridescent is reserved for ambient surfaces; solo accent
       items here use weight + a heavier text color, no fill. */
    .header-menu-sheet-item-accent { font-weight: 700; }
    .header-menu-sheet-item-danger { color: #d04848; }
    .header-menu-sheet-item-danger:active {
        background: rgba(208, 72, 72, 0.06);
        color: #b53333;
    }

    /* Section separator — single hairline strip with a touch of
       vertical breathing room. Doubles up with the row's bottom border
       above, which is why the last-of-type rule kills that border. */
    .header-menu-sheet-sep {
        height: 1px;
        margin: 16px 0 0;
        background: rgba(var(--global-text-color-rgb), 0.08);
    }

    /* ---------- Footer meta (language + legal) ---------------------- */
    .header-menu-sheet-meta {
        margin-top: 28px;
        padding: 0 calc(var(--global-padding) * 1.6);
        display: flex;
        flex-direction: column;
        gap: 12px;
        font-size: 12.5px;
    }
    .header-menu-sheet-lang {
        display: inline-flex;
        align-items: center;
        gap: 8px;
        color: var(--global-text-muted);
    }
    .header-menu-sheet-lang-link {
        color: var(--global-text-muted);
        text-decoration: none;
        padding: 4px 2px;
        letter-spacing: -0.005em;
        transition: color 0.15s cubic-bezier(0.22, 0.61, 0.36, 1);
    }
    .header-menu-sheet-lang-link.is-active {
        color: var(--global-text-color);
        font-weight: 600;
    }
    .header-menu-sheet-legal {
        display: flex;
        flex-wrap: wrap;
        align-items: center;
        gap: 6px;
        color: rgba(var(--global-text-color-rgb), 0.42);
    }
    .header-menu-sheet-legal a {
        color: rgba(var(--global-text-color-rgb), 0.55);
        text-decoration: none;
    }
    .header-menu-sheet-legal a:active {
        color: var(--global-text-color);
        text-decoration: underline;
        text-underline-offset: 2px;
    }

    /* Body scroll lock — set by JS via the .is-menu-open class. Using
       overflow:hidden + touch-action:none stops the rubber-band scroll
       on iOS that would otherwise leak through the fixed sheet. */
    body.is-menu-open {
        overflow: hidden;
        touch-action: none;
    }
}

/* Honor the OS reduced-motion preference: the panel is already
   instant; this just collapses the hamburger X morph too. */
@media (prefers-reduced-motion: reduce) {
    .header-menu-trigger-icon > span {
        transition-duration: 0.01ms;
    }
}

