/*
  The top bar. Markup: library/topBar.php. Behaviour: script/top-bar.js.

  Everything the bar needs lives here, including the branch-database marker,
  which moved out of combined.css when it moved into the bar - it used to be
  absolutely positioned into the corner the avatar now occupies.

  Colours come from the tokens in combined.css and the gradients recorded in
  docs/branding.md. Nothing here invents a value.
*/

:root {
 --top-bar-height: 64px;
}

.top-bar {
 position: sticky;
 top: 0;
 z-index: 30;
 display: flex;
 align-items: center;
 justify-content: space-between;
 gap: 16px;
 height: var(--top-bar-height);
 padding: 0 24px;
 border-bottom: 1px solid var(--app-border);
 /* Half-opaque over a blur, so content scrolling underneath is felt rather
    than seen. Falls back to near-solid white where backdrop-filter is not
    supported, which is the safe direction: legible either way. */
 background-color: rgba(255, 255, 255, 0.72);
 backdrop-filter: blur(12px);
 -webkit-backdrop-filter: blur(12px);
}

.top-bar-side {
 display: flex;
 align-items: center;
 gap: 8px;
 min-width: 0;
}

.top-bar-side--right {
 margin-left: auto;
}

/* ---- branch-database marker ------------------------------------------- */

/* library/topBar.php renders this whenever BRANCH_DB_SUFFIX is set, so it is on
   screen for the whole session on dev and stage and never in production. That
   makes it a state indicator, not an alarm, and it was once styled as the
   opposite: --red (#f00, the loudest value in the palette) at 17px bold in a
   42px slab, which read as a primary button and spent the one colour real
   errors need on a condition that never clears.

   Monospace is doing real work here: the value is a raw .env string, and
   setting it in the same face as the rest of the shell would let it pass for a
   label. The colour is the brand purple deliberately - this marks which
   database you are on, it is not an error. If it ever needs to shout, the amber
   warn family is the step up and --red-error stays reserved for things that are
   actually broken. */
.stage-site-callout {
 display: inline-flex;
 align-items: center;
 padding: 5px 10px 5px 9px;
 border: 1px solid var(--app-primary);
 border-left-width: 4px;
 border-radius: 6px;
 background-color: var(--app-primary-tint);
 color: var(--app-primary-strong);
 font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
 font-size: 12px;
 font-weight: 600;
 line-height: 1.2;
 white-space: nowrap;
 /* A read-out, not a control: never let it swallow a click. */
 pointer-events: none;
}

/* ---- credits ----------------------------------------------------------- */

/* Flex, not block. The trigger inside is inline-flex, so in a block parent it
   would sit on the parent's text baseline rather than in the middle of it - and
   because the credits trigger sets 14px type while the avatar sets 12px, the
   two ended up on different baselines and visibly failed to line up, by 2.75px.
   Making the wrappers flex containers takes baseline alignment out of it. */
.top-bar-credits {
 position: relative;
 display: flex;
 align-items: center;
}

.top-bar-credits-trigger {
 display: inline-flex;
 align-items: center;
 gap: 8px;
 height: 34px;
 padding: 0 12px;
 border: 1px solid transparent;
 border-radius: 8px;
 background-color: rgba(147, 51, 234, 0.06);
 color: var(--app-primary-strong);
 font-size: 14px;
 font-weight: 600;
 line-height: 1;
 cursor: pointer;
 transition: background-color 0.15s ease, border-color 0.15s ease;
}

.top-bar-credits-trigger:hover,
.top-bar-credits-trigger[aria-expanded="true"] {
 border-color: rgba(147, 51, 234, 0.28);
 background-color: rgba(147, 51, 234, 0.12);
}

.top-bar-credits-trigger i,
.top-bar-credits-trigger svg {
 width: 16px;
 height: 16px;
}

.top-bar-credits-label {
 color: var(--app-muted, #64748b);
 font-weight: 500;
}

/* ---- the counter rolling over -------------------------------------------

   Buying credits used to reload the page. It updates in place instead: the old
   total leaves downwards, the new one arrives from above, and a "+N" rises off
   the button. The clipping box is what makes it read as one number moving
   rather than two numbers crossfading.                                      */

.top-bar-credits-roll {
 position: relative;
 display: inline-block;
 overflow: hidden;
 height: 14px;
 /* Widened by script to fit the incoming figure before it arrives, so a total
    crossing into another digit does not snap the button wider mid-roll. */
 transition: width 320ms ease;
}

.top-bar-credits-roll .top-bar-credits-value {
 display: block;
 line-height: 14px;
}

.top-bar-credits-value--out {
 animation: topBarRollOut 420ms cubic-bezier(0.4, 0, 0.2, 1) forwards;
}

.top-bar-credits-value--in {
 position: absolute;
 top: 0;
 left: 0;
 animation: topBarRollIn 420ms cubic-bezier(0.4, 0, 0.2, 1) forwards;
}

@keyframes topBarRollOut {
 from { transform: translateY(0);     opacity: 1; }
 to   { transform: translateY(100%);  opacity: 0; }
}

@keyframes topBarRollIn {
 from { transform: translateY(-100%); opacity: 0; }
 to   { transform: translateY(0);     opacity: 1; }
}

/* The receipt: what was added, said once, on its way out of view. */
.top-bar-credits-bump {
 position: absolute;
 top: 50%;
 left: 50%;
 z-index: 45;
 transform: translate(-50%, -50%);
 padding: 2px 8px;
 border-radius: 999px;
 background-color: #16a34a;
 color: #fff;
 font-size: 12px;
 font-weight: 700;
 line-height: 1.4;
 white-space: nowrap;
 pointer-events: none;
 animation: topBarBump 1400ms ease-out forwards;
}

.top-bar-credits-bump[hidden] {
 display: none;
}

@keyframes topBarBump {
 0%   { opacity: 0; transform: translate(-50%, -10%)  scale(0.8); }
 18%  { opacity: 1; transform: translate(-50%, -60%)  scale(1); }
 70%  { opacity: 1; transform: translate(-50%, -110%) scale(1); }
 100% { opacity: 0; transform: translate(-50%, -170%) scale(1); }
}

/* The point is the new number, not the journey. Under reduced motion the value
   still changes and the "+N" is still shown - neither moves. */
@media (prefers-reduced-motion: reduce) {
 .top-bar-credits-roll {
  transition: none;
 }

 .top-bar-credits-value--out,
 .top-bar-credits-value--in {
  animation: none;
 }

 .top-bar-credits-value--out {
  display: none;
 }

 .top-bar-credits-value--in {
  position: static;
  opacity: 1;
  transform: none;
 }

 .top-bar-credits-bump {
  animation: none;
  transform: translate(-50%, -140%);
 }
}

.top-bar-popover {
 position: absolute;
 top: calc(100% + 8px);
 right: 0;
 z-index: 40;
 width: 360px;
 max-width: calc(100vw - 32px);
 border: 1px solid var(--app-border);
 border-radius: 12px;
 background-color: #fff;
 box-shadow: 0 12px 32px rgba(15, 23, 42, 0.14);
 overflow: hidden;
}

.top-bar-popover[hidden] {
 display: none;
}

.top-bar-popover-head {
 display: grid;
 grid-template-columns: 1fr auto;
 gap: 2px 8px;
 padding: 16px;
 border-bottom: 1px solid var(--app-border);
 background-image: linear-gradient(135deg, rgba(147, 51, 234, 0.10), rgba(249, 115, 22, 0.08));
}

.top-bar-popover-title {
 display: flex;
 align-items: center;
 justify-content: space-between;
 gap: 8px;
 font-size: 14px;
 font-weight: 600;
}

.top-bar-popover-title i,
.top-bar-popover-title svg {
 width: 15px;
 height: 15px;
 color: var(--app-primary);
}

.top-bar-popover-heading {
 display: flex;
 align-items: center;
 gap: 8px;
 margin: 0;
 padding: 14px 16px 0;
 font-size: 13px;
 font-weight: 600;
}

.top-bar-popover-heading i,
.top-bar-popover-heading svg {
 width: 15px;
 height: 15px;
 color: var(--app-primary);
}

.top-bar-popover-balance {
 grid-row: 1 / span 2;
 grid-column: 2;
 align-self: center;
 font-size: 28px;
 font-weight: 700;
 line-height: 1;
}

.top-bar-popover-sub {
 color: var(--app-muted, #64748b);
 font-size: 12px;
}

.top-bar-popover-body {
 padding: 12px;
}

.top-bar-popover-note {
 margin: 0;
 padding: 12px 4px;
 color: var(--app-muted, #64748b);
 font-size: 13px;
 text-align: center;
}

.top-bar-booster {
 display: flex;
 align-items: center;
 justify-content: space-between;
 gap: 12px;
 width: 100%;
 margin-bottom: 8px;
 padding: 12px;
 border: 1px solid var(--app-border);
 border-radius: 10px;
 background: none;
 text-align: left;
 cursor: pointer;
 transition: border-color 0.15s ease, background-color 0.15s ease;
}

.top-bar-booster:last-child {
 margin-bottom: 0;
}

/* ---- loading skeleton ---------------------------------------------------

   Same shimmer as .asp-skeleton in adspendplanner.css - same gradient, same
   1.2s linear sweep - so the app has one idea of what loading looks like
   rather than a new one per feature. The values are repeated rather than
   shared because stylesheets here are scoped per page by convention.        */

.top-bar-booster--skeleton {
 cursor: default;
}

.top-bar-booster--skeleton:hover {
 border-color: var(--app-border);
 background-color: transparent;
}

/* The heights are the line boxes of the text each placeholder stands in for -
   21px for the name, 18px for the description, 22.5px for the price - so a row
   of skeletons is exactly as tall as the row that replaces it and the popover
   does not resize under the cursor. Guessed values cost 34px of jump.

   The visible bar is inset rather than shorter: padding plus background-clip
   keeps the box at the full line height while the gradient paints only the
   middle, which is what stops it looking like a stack of slabs. */
.top-bar-skeleton {
 display: block;
 box-sizing: border-box;
 border-radius: 6px;
 background-image: linear-gradient(90deg, #f3effc 0%, #faf8ff 50%, #f3effc 100%);
 background-size: 200% 100%;
 background-clip: content-box;
 animation: topBarShimmer 1.2s linear infinite;
}

.top-bar-skeleton--name {
 width: 116px;
 height: 21px;
 padding: 3px 0;
}

.top-bar-skeleton--sub {
 width: 84px;
 height: 18px;
 padding: 3px 0;
}

.top-bar-skeleton--price {
 width: 48px;
 height: 22.5px;
 padding: 4px 0;
 flex: none;
}

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

/* A sweeping gradient is exactly the kind of motion this setting is for. The
   skeleton stays - it still holds the layout - it simply stops moving. */
@media (prefers-reduced-motion: reduce) {
 .top-bar-skeleton {
  animation: none;
  background-image: none;
  background-color: #f3effc;
 }
}

/* Announced to screen readers, which get nothing from a shimmering rectangle;
   the skeletons themselves are aria-hidden. */
.top-bar-sr-only {
 position: absolute;
 width: 1px;
 height: 1px;
 margin: -1px;
 padding: 0;
 overflow: hidden;
 clip: rect(0 0 0 0);
 white-space: nowrap;
 border: 0;
}

.top-bar-booster:hover {
 border-color: var(--app-primary);
 background-color: var(--app-primary-tint);
}

/* The name and its description are separate lines. Left as inline spans they
   ran together - "Credit Booster 100extra 100 credits" - because the row is a
   flex container and both were flex items on the same line. */
.top-bar-booster > span:first-child {
 display: flex;
 flex-direction: column;
 gap: 2px;
 min-width: 0;
}

.top-bar-booster-name {
 font-size: 14px;
 font-weight: 600;
}

.top-bar-booster-sub {
 color: var(--app-muted, #64748b);
 font-size: 12px;
}

.top-bar-booster-prices {
 display: flex;
 flex-direction: column;
 align-items: flex-end;
 gap: 2px;
 flex: none;
}

.top-bar-booster-price {
 font-size: 15px;
 font-weight: 700;
 white-space: nowrap;
}

/* The comparable figure. The headline price says what you pay; this says what
   you get for it, which is the only way to rank packages priced in round
   numbers against different credit counts. */
.top-bar-booster-per {
 color: var(--app-muted, #64748b);
 font-size: 11px;
 white-space: nowrap;
}

/* Earned, not assigned: the row with the lowest price per credit. */
.top-bar-booster--best {
 border-color: var(--app-primary);
 background-color: var(--app-primary-tint);
}

.top-bar-booster-badge {
 position: absolute;
 top: -8px;
 right: 10px;
 padding: 1px 8px;
 border-radius: 999px;
 background-color: var(--app-primary);
 color: var(--white);
 font-size: 10px;
 font-weight: 600;
 letter-spacing: 0.2px;
 line-height: 16px;
 white-space: nowrap;
}

/* The badge hangs off the top edge, so the row has to be a containing block
   and the list needs room for it not to be clipped by the row above. */
.top-bar-booster {
 position: relative;
}

.top-bar-popover-tip {
 margin: 0;
 padding: 12px 16px;
 border-top: 1px solid var(--app-border);
 background-color: rgba(15, 23, 42, 0.025);
 color: var(--app-muted, #64748b);
 font-size: 11px;
 line-height: 1.5;
 display: flex;
 gap: 8px;
}

.top-bar-popover-tip strong {
 color: inherit;
 font-weight: 600;
}

.top-bar-popover-tip-mark {
 flex: none;
}

/* ---- gear -------------------------------------------------------------- */

.top-bar-icon-link {
 display: inline-flex;
 align-items: center;
 justify-content: center;
 width: 34px;
 height: 34px;
 border-radius: 8px;
 color: var(--app-muted, #64748b);
 transition: background-color 0.15s ease, color 0.15s ease;
}

.top-bar-icon-link:hover {
 background-color: rgba(15, 23, 42, 0.06);
 color: var(--app-primary-strong);
}

.top-bar-icon-link i,
.top-bar-icon-link svg {
 width: 18px;
 height: 18px;
}

/* ---- avatar and its menu ----------------------------------------------- */

/* Same reason as .top-bar-credits above. */
.top-bar-user {
 position: relative;
 display: flex;
 align-items: center;
}

.top-bar-avatar {
 display: inline-flex;
 align-items: center;
 justify-content: center;
 width: 34px;
 height: 34px;
 padding: 0;
 overflow: hidden;
 border: 2px solid var(--app-border);
 border-radius: 50%;
 background-image: linear-gradient(135deg, rgba(147, 51, 234, 0.18), rgba(249, 115, 22, 0.18));
 color: var(--app-primary-strong);
 font-size: 12px;
 font-weight: 600;
 line-height: 1;
 cursor: pointer;
 transition: border-color 0.15s ease;
}

.top-bar-avatar:hover,
.top-bar-avatar[aria-expanded="true"] {
 border-color: var(--app-primary);
}

.top-bar-avatar img {
 width: 100%;
 height: 100%;
 object-fit: cover;
}

.top-bar-menu {
 position: absolute;
 top: calc(100% + 8px);
 right: 0;
 z-index: 40;
 min-width: 200px;
 padding: 6px;
 border: 1px solid var(--app-border);
 border-radius: 10px;
 background-color: #fff;
 box-shadow: 0 12px 32px rgba(15, 23, 42, 0.14);
}

.top-bar-menu[hidden] {
 display: none;
}

/* No rule under the name: the only separator in this menu is the one between
   Profile and Logout, which is the one that carries meaning. A second line here
   would just be a box drawn around a heading. */
.top-bar-menu-name {
 padding: 8px 10px 6px;
 font-size: 13px;
 font-weight: 600;
 color: var(--app-muted, #64748b);
 white-space: nowrap;
 overflow: hidden;
 text-overflow: ellipsis;
}

.top-bar-menu a {
 display: flex;
 align-items: center;
 gap: 10px;
 padding: 8px 10px;
 border-radius: 6px;
 color: inherit;
 font-size: 14px;
 text-decoration: none;
}

.top-bar-menu a:hover {
 background-color: rgba(15, 23, 42, 0.06);
}

.top-bar-menu a i,
.top-bar-menu a svg {
 width: 16px;
 height: 16px;
}

/* Sign-out is set apart from the navigation items above it: a rule across the
   menu, then the danger colour, so it is not hit on the way to Profile.

   Written as `.top-bar-menu a.top-bar-menu-danger` on purpose. `.top-bar-menu a`
   above is (0,1,1) and sets `color: inherit`; a bare `.top-bar-menu-danger` is
   (0,1,0) and loses to it, which is why the class on its own left this row the
   same black as the rest.

   #dc2626 rather than --red-error: it is the red the Sign out row on /profile
   already uses, and one danger colour beats two. Restrained on purpose - the
   colour marks the row, the tinted hover does the rest.

   The text darkens to red-700 on hover. That is what docs/branding.md asks of
   anything interactive here, and it is also what keeps the row legible: #dc2626
   measures 4.83:1 on white but only 4.41:1 once the tint is under it, which
   fails AA for 14px text. #b91c1c on the same tint is 5.91:1. */
.top-bar-menu-sep {
 height: 1px;
 margin: 6px 4px;
 background-color: var(--app-border);
}

.top-bar-menu a.top-bar-menu-danger {
 color: #dc2626;
}

.top-bar-menu a.top-bar-menu-danger:hover {
 background-color: #fef2f2;
 color: #b91c1c;
}

/* ---- signed out -------------------------------------------------------- */

.top-bar-signin {
 padding: 0 12px;
 color: var(--app-muted, #64748b);
 font-size: 14px;
 font-weight: 500;
 line-height: 34px;
 text-decoration: none;
}

.top-bar-signin:hover {
 color: var(--app-primary-strong);
}

.top-bar-cta {
 display: inline-flex;
 align-items: center;
 gap: 6px;
 height: 34px;
 padding: 0 16px;
 border-radius: 8px;
 /* docs/branding.md, primary button. Buttons darken on hover here rather than
    brightening - do not reach for filter: brightness(). */
 background-image: linear-gradient(to right, #9333ea, #f97316);
 color: #fff;
 font-size: 14px;
 font-weight: 600;
 text-decoration: none;
 box-shadow: 0 1px 2px rgba(15, 23, 42, 0.12);
}

.top-bar-cta:hover {
 background-image: linear-gradient(to right, #7e22ce, #ea580c);
 color: #fff;
}

.top-bar-cta i,
.top-bar-cta svg {
 width: 15px;
 height: 15px;
}

/* ---- mobile ------------------------------------------------------------ */

@media (max-width: 700px) {
 .top-bar {
  padding: 0 12px;
 }

 /* The label goes, the number stays: the figure is the point and it is the
    part that cannot be inferred from the icon. */
 .top-bar-credits-label {
  display: none;
 }

 .top-bar-credits-trigger {
  padding: 0 10px;
 }

 /* A 360px bubble anchored to the right edge does not fit a phone, so the
    popover spans the viewport instead of hanging off it. */
 .top-bar-popover {
  position: fixed;
  top: var(--top-bar-height);
  right: 8px;
  left: 8px;
  width: auto;
  max-width: none;
 }

 .top-bar-signin {
  padding: 0 8px;
 }

 .top-bar-cta {
  padding: 0 12px;
  font-size: 13px;
 }

 .stage-site-callout {
  font-size: 11px;
 }
}
