/* style — failed, pinned, upres, 3D, audio, overlays, errors.
   One section of the old style.css. The pieces load in filename order
   and that order is the cascade, so do not rearrange them. */
/* ---------- a failed generation ----------
   The card stops being a dead end: what went wrong is readable and
   copy-pasteable, and there are two ways forward without leaving the tile. */
.pending-card.failed { position: relative; }
.pending-card.failed .fail-acts {
  position: absolute; top: 5px; right: 5px; z-index: 4;
  display: flex; gap: 3px;
}
/* Sized and shaped like the gallery's own action buttons, because they are
   the same two actions. */
.fail-btn {
  background: rgba(10,10,12,.82); color: var(--text); border: 1px solid var(--border);
  border-radius: 3px; font-family: inherit; font-size: 12px; font-weight: 600;
  width: 22px; height: 22px; padding: 0; cursor: pointer; line-height: 1;
  display: flex; align-items: center; justify-content: center;
}
.fail-btn svg { width: 13px; height: 13px; display: block; }
.fail-btn:hover { background: var(--accent); border-color: var(--accent); color: #fff; }
.fail-btn[disabled] { opacity: .5; cursor: default; }
.fail-btn.fail-x { font-size: 14px; }
.fail-msg { position: relative; padding: 5px 5px 6px; }
.fail-msg textarea {
  width: 100%; min-height: 0; height: 46px; resize: vertical;
  background: #17090b; border: 1px solid rgba(255,107,107,.42); border-radius: 3px;
  color: #ffd9d9; font-family: "Geist Mono", Consolas, monospace; font-size: 9px;
  line-height: 1.4; padding: 4px 34px 4px 6px; outline: none; user-select: text;
}
.fail-msg textarea:focus { border-color: var(--red); }
.fail-copy {
  position: absolute; right: 9px; top: 9px;
  background: rgba(10,10,12,.8); color: var(--text-dim); border: 1px solid var(--border);
  border-radius: 3px; font-family: inherit; font-size: 8.5px; padding: 2px 5px; cursor: pointer;
}
.fail-copy:hover { color: #fff; border-color: var(--accent); }


/* ---------- pinned + favourite ----------
   Their own colour, because they are neither an edit (pink) nor a file
   action (white) — they are how you mark a thing as mattering. Amber for a
   pin, gold for a favourite. Lit ones stay visible without hovering. */
.gal-actions-w .gact.gmark { color: #ffc266; }
.gal-actions-w .gact.gmark.fav { color: var(--warn); }
.gal-actions-w .gact.gmark:hover { background: #ffc266; color: var(--warn-bg); }
.gal-actions-w .gact.gmark.fav:hover { background: var(--warn); color: var(--warn-bg); }
.gal-actions-w .gact.gmark.on {
  opacity: 1; background: #ffc266; color: var(--warn-bg);
}
.gal-actions-w .gact.gmark.fav.on { background: var(--warn); color: var(--warn-bg); }

/* ---------- a picture with a working file behind it ----------
   The Edit button on a Crop Enhance export reopens the document rather than
   loading the flattened picture into a model, and those are different enough
   that the button should not look identical. A dot in the corner: enough to
   notice once you know what it means, not enough to shout. */
.gal-actions .gact.g-crop { position: relative; }
.gal-actions .gact.g-crop::after {
  content: ''; position: absolute; top: 2px; right: 2px;
  width: 4px; height: 4px; border-radius: 50%;
  background: var(--accent); box-shadow: 0 0 0 1px var(--scrim);
}
.gal-actions .gact.g-crop:hover::after { background: #fff; }
.gal-item.is-pinned .gal-actions-w, .gal-item.is-fav .gal-actions-w { opacity: 1; }
.gal-item.is-pinned .gal-actions-w .gact:not(.on),
.gal-item.is-fav .gal-actions-w .gact:not(.on) { opacity: 0; }
.gal-item:hover .gal-actions-w .gact:not(.on) { opacity: 1; }
/* A pinned tile reads as pinned before you look at any icon. */
.gal-item.is-pinned { border-color: var(--accent); }
/* WHITE ON THE PINK, AND FILLED. Simon: "when you click the favorites
   filter icon in the gallery, the favorites icon itself becomes white so you
   can see it against the pink highlight." This rule used to paint the star
   accent-on-accent -- the one button in the row whose active state was
   invisible. The pill's own rule (21-luma-player.css) already makes an
   active button white on pink; this only adds the fill, so the star reads
   as "on" the way the tile's own favourite star does. */
.gfilter .gf-fav.active { color: #fff; }
.gfilter .gf-fav.active svg { fill: currentColor; }


/* ---------- a 3D scene being built from this picture ----------
   Over the whole tile, not tucked in a corner: a build takes minutes and costs
   real money, and a 26px chip reads as a decoration you might have imagined.
   The cube draws itself in — back face, top edges, then the vertical — in the
   order the pipeline actually goes, so the wait reads as progress.

   But over is all it is. For those minutes the tile is still an ordinary
   gallery item: you can open it, drag it into a reference slot, favourite it,
   start something else from it. The first version put a blurred scrim at
   z-index 6 and hid the tile's own buttons, which announced the build by
   confiscating the picture it was made from. So now: no blur, a tint light
   enough to read the image through, pointer-events off so every click lands on
   whatever is underneath, and a z-index BELOW the action groups (2) so the
   buttons draw over the cube on hover instead of under it. */
.gal-item .g3d-build {
  position: absolute; inset: 0; z-index: 1;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 7px;
  background: rgba(8, 8, 10, .26);
  border-radius: inherit;
  color: var(--accent);
  animation: cubeFade .25s ease;
  pointer-events: none;
}
.gal-item .g3d-build .cube-build {
  width: 36%; max-width: 62px; height: auto; aspect-ratio: 1; display: block;
  filter: drop-shadow(0 2px 10px rgba(0, 0, 0, .75));
}
.gal-item .g3d-build .cb-lab {
  font-size: 8.5px; font-weight: 700; letter-spacing: .7px; text-transform: uppercase;
  color: #fff; opacity: .9; text-align: center; padding: 0 6px;
  text-shadow: 0 1px 4px rgba(0, 0, 0, .9);
}
/* Which step, in words, along the bottom of the frame. Stops short of the
   right-hand edge so it never runs under the bottom-right button group, and
   sits on its own dark strip because it has to stay readable over whatever the
   picture happens to be. */
.gal-item .g3d-build .cb-step {
  position: absolute; left: 4px; bottom: 4px; max-width: calc(100% - 52px);
  font-size: 8px; line-height: 1.3; color: #e8e8ee;
  background: rgba(0, 0, 0, .78); padding: 3px 6px; border-radius: 3px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.gal-item .g3d-build .cb-step:empty { display: none; }
.cube-build path { stroke-dasharray: 46; stroke-dashoffset: 46; }
.cube-build .cb-a { animation: cubeDraw 2.4s ease-in-out infinite; }
.cube-build .cb-b { animation: cubeDraw 2.4s ease-in-out .35s infinite; }
.cube-build .cb-c { animation: cubeDraw 2.4s ease-in-out .7s infinite; }
@keyframes cubeDraw {
  0%   { stroke-dashoffset: 46; opacity: .2; }
  35%  { stroke-dashoffset: 0;  opacity: 1; }
  75%  { stroke-dashoffset: 0;  opacity: 1; }
  100% { stroke-dashoffset: 0;  opacity: .2; }
}
@keyframes cubeFade { from { opacity: 0; } to { opacity: 1; } }
/* The buttons stay. Only the hover info line stands down, because the step
   line has taken its corner and two stacked captions in the same 4px would be
   unreadable — and mid-build "what is it doing" beats "what was the prompt". */
.gal-item .g3d-build ~ .ginfo { display: none; }
/* …with one exception: the button that starts a 3D build. A build is already
   running on this picture, so pressing it again would start a second one over
   the top of the first. The badge appears without a gallery reload, so this
   has to be CSS rather than a rebuild. */
.gal-item .g3d-build ~ .gal-actions .g3d-make { display: none; }


/* ---------- a generation that just landed ----------
   Simon: "as a new generation appears in the gallery, it has a colored
   outline in the gallery so the user can tell it's a new generation." A
   glowing ring outside the tile's own 1px border (box-shadow, so
   .gal-item's overflow:hidden does not clip it) — deliberately not the
   same trick .is-pinned uses just above (swapping border-color), because
   pinned means "kept" and this means "just happened", and a returning
   pinned tile silently turning into a "new" one would say the wrong thing.

   Fades on its own, in CSS alone, once GAL_NEW_MS has had its say — see
   isGalleryNew in 47-gallery-load.js — so nothing has to come back and
   strip the class off a tile once the gallery has moved on. The delay +
   duration below (4.5s solid, 1.6s fade = 6.1s) is chosen to land just
   under GAL_NEW_MS (6.2s) so a tile that gets rebuilt from scratch near
   the end of its life is already visually faded rather than still glowing
   when the class stops being applied. */
.gal-item.gal-new {
  box-shadow: 0 0 0 2px var(--accent), 0 0 16px 2px var(--accent);
  animation: galNewFade 1.6s ease-in 4.5s 1 forwards;
}
@keyframes galNewFade {
  from { box-shadow: 0 0 0 2px var(--accent), 0 0 16px 2px var(--accent); }
  to   { box-shadow: 0 0 0 2px transparent, 0 0 0 0 transparent; }
}


/* ---------- audio in the gallery ----------
   No waveform, because drawing one means decoding the file just to show a
   thumbnail. A name and a player you can press is the honest version. */
.gal-item .gthumb.gaudio { background: linear-gradient(160deg, #1b1b24, #121218); }
.gal-item .gthumb.gaudio .ga-box {
  position: absolute; inset: 0; display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: 7px; padding: 8px;
}
.gal-item .gthumb.gaudio .ga-ico { color: var(--accent); opacity: .8; }
.gal-item .gthumb.gaudio .ga-ico svg { width: 26px; height: 26px; display: block; }
.gal-item .gthumb.gaudio audio { width: 100%; max-width: 190px; height: 26px; }


/* ---------- overlays live in the workspace, not over the furniture ----------

   Every editor overlay was `inset: 0` at a z-index below the rail (210) and
   the header (200), so the rail sat on top of its left 46 pixels and the
   header on top of its first ~50. On the keyframe editor those are exactly the
   two strips that matter: the mode column down the left and the title-and-close
   row along the top. The overlay looked like it had been cropped.

   So they open where the workspace is. The rail and the header stay reachable —
   which is the point of them being fixed — and the overlay is fully visible
   without having to fight either. Declared here, after everything, so these
   offsets win over the `inset: 0` in each rule regardless of source order.

   Two deliberate exceptions below: a viewer wants the height, and a modal
   block wants everything. */
#kfedit,
#pointpick,
#lumatl,
#trimmod,
#framepick,
#reflib {
  left: 46px;
  top: var(--hdr-h, 50px);
}

/* Viewers keep the full height — the picture is the whole point, and they
   carry their own close affordance. Still clear of the rail. */
#wn-back {
  left: 46px;
  top: 0;
}

/* EXCEPT THE FULL PREVIEW, WHICH NOW COVERS THE RAIL TOO. Simon: "we can't
   see anything else other than what we see in this modal window when it's
   selected." It keeps `inset: 0` from its own rule and sits at --z-modal
   (see 20-lightbox.css) rather than being held clear of a rail it is
   supposed to be in front of. */

/* The busy veil is meant to stop you touching ANYTHING, rail included, so it
   stays edge to edge on purpose. */
#busy-veil { left: 0; top: 0; }


/* ---------- errors you can actually read ----------
   Below the header, never under it: the top is set in JS from the header's
   measured bottom edge at the moment the banner appears, because the CSS
   variable is set once at boot and a header that has since grown a taller row
   leaves it stale — which is exactly how a message ends up cut in half by the
   title strip.

   z-index above every modal in the app (the highest is 420). An error raised
   while the crop canvas or the node space is open is the one you most need to
   read, and those cover the whole window. */
#panel-err {
  position: fixed; left: 46px; right: 0; top: 50px; z-index: var(--z-modal);
  background: #2b1114; border-bottom: 1px solid rgba(255,107,107,.55);
  box-shadow: 0 8px 24px var(--scrim);
  animation: peIn .16s ease-out;
}
#panel-err.hidden { display: none; }
@keyframes peIn { from { opacity: 0; transform: translateY(-6px); } to { opacity: 1; transform: none; } }
.pe-row {
  display: flex; align-items: flex-start; gap: 9px;
  padding: 8px 10px 9px 12px;
}
.pe-tag {
  flex: none; margin-top: 1px;
  font-size: 8.5px; font-weight: 800; letter-spacing: 1px; text-transform: uppercase;
  color: #ffb3b3; background: rgba(255,107,107,.15);
  border: 1px solid rgba(255,107,107,.4); border-radius: 3px; padding: 2px 6px;
}
/* Scrolls rather than growing without limit: a provider stack trace should be
   readable, not take the whole panel. */
.pe-msg {
  flex: 1; min-width: 0; max-height: 96px; overflow-y: auto;
  color: #ffdcdc; font-size: 11px; line-height: 1.45;
  font-family: "Geist Mono", Consolas, monospace;
  white-space: pre-wrap; overflow-wrap: anywhere; word-break: break-word;
  user-select: text; cursor: text;
}
.pe-n { flex: none; color: #ff9b9b; font-size: 10px; font-weight: 800; margin-top: 2px; }
.pe-b {
  flex: none; background: rgba(10,10,12,.5); color: #ffdcdc;
  border: 1px solid rgba(255,107,107,.4); border-radius: 3px;
  font-family: inherit; font-size: 10px; font-weight: 700;
  padding: 3px 8px; cursor: pointer;
}
.pe-b:hover { background: var(--red); border-color: var(--red); color: #fff; }
.pe-x { font-size: 13px; line-height: 1; padding: 2px 7px 4px; }
