/* My Sticker Book (kf-sticker-book.js) - game-page sheet, D-071.
 * Loaded on the game pages only, AFTER kf-games.css (which supplies the shared
 * .kfe-confetti). Engine prefix: kfsb-.
 *
 * The stage renders the FRAME LAW surface: desk surround (decorative,
 * pointer-events none by construction: it is a background) with the book
 * filling the frame. Every animated property here is transform/opacity/filter;
 * tokens.css zeroes durations under prefers-reduced-motion and the engine's
 * REDUCED flag swaps the choreography.
 *
 * No flashing anywhere: every glow/pulse is a soft >=0.9s ease. */

.kf-stickerbook {
  position: relative;
  margin: 0 auto;
  max-width: 1180px;
}

/* ---------- stage ---------- */
.kfsb-stage {
  position: relative;
  width: 100%;
  /* No-JS / pre-boot fallback height. Since the 2026-08-09 fix round the
     ENGINE sizes the stage at boot (fitStage): the stage plus its toolbar
     must be fully visible at load, so the engine fits it to the height the
     viewport leaves under the page chrome and scrolls it into view when the
     chrome is taller than the room. The engine writes an inline height and
     clears it in fullscreen (where the 100% rule below owns it). */
  height: clamp(430px, 76vh, 900px);
  border-radius: var(--radius-lg);
  overflow: hidden;
  background: #caa06d url('/games/sticker-book/desk.webp?v=1') center / cover no-repeat;
  box-shadow: var(--shadow-soft);
  touch-action: none;
  user-select: none;
  -webkit-user-select: none;
}
.kfsb-stage:fullscreen {
  height: 100%;
  border-radius: 0;
}
/* Portrait phones: give the book nearly the whole viewport (Game Shell:
   fullscreen strongly encouraged, but even embedded the stage is the page). */
@media (max-width: 480px) {
  .kfsb-stage { height: min(78vh, 620px); }
}

/* ---------- toolbar (Game Shell: exit / mute / restart / fullscreen) ------ */
.kfsb-toolbar {
  position: absolute;
  left: 0;
  right: 0;
  z-index: 40;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap; /* the come-to-life pill can drop to its own row on a narrow
                      phone rather than overflow the strip (Deniz accepted the
                      crowding when he chose the strip; wrapping is how it fits) */
  gap: 10px;
  padding: 4px 8px;
  pointer-events: none; /* the strip is air; only the buttons take taps */
}
/* BOTH arrangements dock the toolbar to the bottom (Nova's ruling D,
   2026-08-10). Landscape used to pin it to the top, where the page chrome eats
   the first band: the exit pill measured 0% hittable at 640x360 and 96% once
   it moved down. It is also the thumb zone on a phone. */
.kfsb-stage .kfsb-toolbar { bottom: 0; }
.kfsb-toolbar > * { pointer-events: auto; }

.kfsb-tbtn {
  box-sizing: border-box;
  min-width: 64px;
  min-height: 64px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  border: 3px solid var(--kf-lilac);
  border-radius: var(--radius-pill);
  background: rgba(253, 250, 244, 0.94);
  color: var(--kf-purple-deep);
  font-family: var(--font-heading);
  font-weight: 700;
  font-size: 1rem;
  padding: 6px 16px;
  cursor: pointer;
  text-decoration: none;
  box-shadow: 0 3px 12px rgba(15, 10, 36, 0.28);
}
.kfsb-tbtn:hover { background: #fff; }
.kfsb-tbtn svg { display: block; }
/* the arrow is an inline SVG now, not a font glyph, so the span has to align
   it the way the character used to align itself (spec 18, 1.1) */
.kfsb-exit .kfsb-exit-arrow {
  color: var(--color-link);
  font-weight: 800;
  display: inline-flex;
  align-items: center;
}
.kfsb-exit span:last-child { white-space: nowrap; }
/* the exit keeps its word on phones too, but sheds letter-spacing room */
@media (max-width: 430px) {
  .kfsb-tbtn { padding: 6px 10px; font-size: 0.88rem; gap: 6px; }
  .kfsb-toolbar { gap: 6px; padding: 4px 6px; }
  .kfsb-wake-big { padding: 6px 14px; font-size: 0.95rem; gap: 8px; }
  .kfsb-wake-leaf { width: 28px; height: 28px; }
}
/* NARROW STRIP: when the full row (exit + gold pill + three icons ~= 640px)
   no longer fits, the come-to-life pill drops to its OWN row at the strip's
   foot (order after everything, full width), leaving the four familiar
   controls as the row above, exactly their pre-existing single-row layout.
   layout()'s dynamic bar-room reserve keeps the book clear of however many
   rows this produces; this just keeps it to a tidy two. */
@media (max-width: 660px) {
  .kfsb-wake-big { order: 5; flex-basis: 100%; }
}

/* ---------- the come-to-life pill (in the toolbar, free page only) --------
 * A big gold labelled button, the Free Drawing page's `.kfd-bring` treatment
 * carried into this game's toolbar strip: gold body, purple label (6.87:1),
 * the shared leaf, a raised lower edge. It reads as the star action next to the
 * quiet lilac icon controls. Same element as the old page-corner leaf, so the
 * arm/lock logic is unchanged. */
.kfsb-wake-big {
  gap: 10px;
  padding: 6px 20px;
  background: var(--kf-gold);
  border-color: var(--kf-gold-deep);
  color: var(--kf-purple-deep);
  box-shadow: 0 4px 0 var(--kf-gold-deep), 0 3px 12px rgba(15, 10, 36, 0.28);
  font-size: 1.05rem;
}
.kfsb-wake-big:hover { background: var(--kf-gold); filter: brightness(1.05); }
.kfsb-wake-leaf {
  flex: none;
  width: 34px;
  height: 34px;
  object-fit: contain;
  display: block;
}
/* dimmed until there is something to bring to life: a prominent labelled button
   must not invite a dead press on an empty page (wake() already no-ops). */
.kfsb-wake-big:not(.kfsb-wake-ready) {
  opacity: 0.5;
  box-shadow: none;
  cursor: default;
}

/* ---------- the waiting popup (while the picture is being made) -----------
 * Mirrors the Free Drawing page's making card. Sits over the play area, stops
 * at the toolbar's top edge (bottom: 78px) so the exit pill stays reachable
 * during the wait, and under the toolbar's z (40) so the toolbar wins. Visual
 * only; the engine marks it aria-hidden and the AT channel is the ori-making
 * live-region mirror. */
.kfsb-making-overlay {
  position: absolute;
  left: 0;
  right: 0;
  top: 0;
  bottom: 78px;
  z-index: 35;
  display: flex;
  align-items: center;        /* CENTERED so the child sees it (Deniz, the
                                 Free Drawing example): a popup exists to grab
                                 attention, not to hide at the foot. */
  justify-content: center;
  padding: 16px;
  /* NO cream wash: the come-to-life light sweep + sticker shimmer play on the
     build UNDERNEATH and must stay visible (Deniz: "o ışık hareketini
     kaldırmışsın ... onu kaldır demedim"). Transparent + pointer-events:none so
     the animation shows around the card and the toolbar/exit stay live. */
  background: transparent;
  pointer-events: none;
}
.kfsb-making-overlay[hidden] { display: none; }
.kfsb-making-card {
  max-width: 24rem;
  text-align: center;
  background: var(--color-surface, #fdfaf4);
  border: 3px solid var(--kf-lilac);
  border-radius: var(--radius-lg, 20px);
  padding: 22px 26px;
  box-shadow: 0 14px 40px rgba(15, 10, 36, 0.42);
  pointer-events: none;
  animation: kfsb-pop 220ms ease-out both;
}
/* a one-shot entrance so it reads as a popup arriving (not a loop, no flashing) */
@keyframes kfsb-pop {
  from { transform: scale(0.9); opacity: 0; }
  to { transform: scale(1); opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .kfsb-making-card { animation: none; }
}
.kfsb-making-copy {
  margin: 0;
  font-family: var(--font-heading);
  font-weight: 700;
  font-size: 1.18rem;
  line-height: 1.35;
  color: var(--kf-purple-deep);
}

/* fullscreen nudge: ONE soft 600ms pulse, once per session, <480px only */
.kfsb-fs-nudge { animation: kfsb-nudge 0.6s ease 1; }
@keyframes kfsb-nudge {
  0% { transform: scale(1); }
  50% { transform: scale(1.12); box-shadow: 0 0 0 6px rgba(212, 168, 67, 0.4); }
  100% { transform: scale(1); }
}
.kfsb-fs-nudge-static { box-shadow: 0 0 0 4px rgba(212, 168, 67, 0.55); }

/* muted board hint, attached to the toolbar edge so "the bell button" is the
   nearest control to the words */
.kfsb-muted-hint {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  z-index: 39;
  width: max-content;
  max-width: 92%;
  background: rgba(45, 27, 78, 0.82);
  color: #fdfaf4;
  font-size: 0.85rem;
  border-radius: var(--radius-pill);
  padding: 6px 14px;
  text-align: center;
}
/* the hint follows the toolbar, which is now at the bottom in both */
.kfsb-stage .kfsb-muted-hint { bottom: 76px; }

/* ---------- the book ---------- */
.kfsb-book {
  position: absolute;
  background-size: 100% 100%;
  background-repeat: no-repeat;
  z-index: 5;
}
.kfsb-book[data-orient='h'] { background-image: url('/games/sticker-book/spread-h.webp?v=1'); }
.kfsb-book[data-orient='v'] { background-image: url('/games/sticker-book/spread-v.webp?v=1'); }

/* subtle paper grain over each page: inline SVG turbulence, multiply (the
   cancelled texture asset's replacement; a finish, not a drawn shape). */
.kfsb-grain {
  position: absolute;
  pointer-events: none;
  mix-blend-mode: multiply;
  opacity: 0.5;
  z-index: 6;
}

.kfsb-page-title {
  position: absolute;
  z-index: 7;
  font-family: var(--font-heading);
  font-weight: 700;
  /* 0.75 alpha: 0.5 measured 2.97:1 over the cream page (Sage B2, WCAG
     1.4.3); composited at 0.75 it reads ~5.7:1 */
  color: rgba(45, 27, 78, 0.75);
  font-size: 0.95rem;
  text-align: center;
  transform: translateX(-50%);
  pointer-events: none;
}

/* ---------- slots (dotted targets) ---------- */
.kfsb-slot {
  position: absolute;
  left: 0;
  top: 0;
  margin: 0;
  padding: 0;
  border: 0;
  background: transparent;
  cursor: pointer;
  z-index: 10; /* under every piece */
  touch-action: none;
  /* Taps land on the painted SHAPE, not the rectangular button box: the b1
     door slot's box was covering the square slot's center (pointer-grid scan,
     2026-08-09), which sent a square tapped mid-shape flying to the door.
     Keyboard focus is unaffected: pointer-events never gates focus. */
  pointer-events: none;
}
.kfsb-slot svg { display: block; width: 100%; height: 100%; }
.kfsb-slot .kfsb-t-fill { pointer-events: visiblePainted; }
.kfsb-slot[aria-disabled='true'] { cursor: default; }
/* hint ladder rung 2: thicker outline + soft glow; rung 3: full glow breathing */
.kfsb-slot.kfsb-hint2 { filter: drop-shadow(0 0 9px rgba(212, 168, 67, 0.55)); }
.kfsb-slot.kfsb-hint3 { animation: kfsb-breathe 1.8s ease-in-out infinite; }
@keyframes kfsb-breathe {
  0%, 100% { filter: drop-shadow(0 0 8px rgba(212, 168, 67, 0.5)); }
  50% { filter: drop-shadow(0 0 16px rgba(212, 168, 67, 0.95)); }
}
.kfsb-hint3-static { filter: drop-shadow(0 0 13px rgba(212, 168, 67, 0.9)); }

/* ---------- pieces (stickers) ---------- */
.kfsb-piece {
  position: absolute;
  left: 0;
  top: 0;
  margin: 0;
  padding: 0;
  border: 0;
  background: transparent;
  cursor: grab;
  z-index: 20;
  touch-action: none;
  will-change: transform;
  -webkit-tap-highlight-color: transparent;
}
/* overflow: visible so the drop-shadow is NOT clipped by the viewBox. The shape
   fills its box to within ~2.4px, but the contact shadow reaches ~11px past the
   bottom; with the SVG's default overflow:hidden that shadow was sliced off in a
   hard straight line, which read as the sticker being "cut at the edges"
   (Deniz, 2026-08-10). The fill and the hairline were never clipped. */
.kfsb-piece svg { display: block; width: 100%; height: 100%; pointer-events: none; overflow: visible; }
/* Two layers, not one (spec 17, section 1): the tight layer is the contact
   edge where the sticker meets the paper, the soft layer is the lift. As the
   rim thins from a flat 10px to a fraction of the piece, the shadow becomes
   the primary "stuck on paper" cue and one soft blur alone stops carrying it.
   The lifted state below is unchanged. */
.kfsb-piece .kfsb-shadow {
  filter: drop-shadow(0 1px 1px rgba(45, 27, 78, 0.34)) drop-shadow(0 4px 7px rgba(45, 27, 78, 0.20));
}
/* the size pop: old size -> 1 on the GPU, no layout animation */
.kfsb-piece.kfsb-anim-pop { transition: transform 0.18s ease; }
/* turn ladder rung 2: one small wobble IN THE TURN DIRECTION. Uses the
   independent `rotate` property, like .kfsb-wiggle, so it composes on top of
   the transform's own rotation instead of fighting it. */
.kfsb-piece.kfsb-turnwobble { animation: kfsb-turnwobble 0.7s ease-in-out 1; }
@keyframes kfsb-turnwobble {
  0% { rotate: 0deg; }
  45% { rotate: 7deg; }
  100% { rotate: 0deg; }
}
.kfsb-piece.kfsb-lift { cursor: grabbing; z-index: 30; }
.kfsb-piece.kfsb-lift .kfsb-shadow { filter: drop-shadow(0 9px 14px rgba(45, 27, 78, 0.34)); }
/* tap-tap selected state: the strong halo (white ground variant, D-046) */
.kfsb-piece.kfsb-selected { border-radius: 14px; box-shadow: var(--halo-select-strong); }
.kfsb-piece.kfsb-placed { cursor: default; }
/* motion helpers the engine toggles around transform writes */
.kfsb-piece.kfsb-anim { transition: transform 0.3s ease; }
.kfsb-piece.kfsb-anim-fast { transition: transform 0.15s ease; }
.kfsb-piece.kfsb-anim-flight { transition: transform 0.35s cubic-bezier(0.3, 0.7, 0.4, 1); }
.kfsb-piece.kfsb-wiggle { animation: kfsb-wiggle 0.25s ease 1; }
@keyframes kfsb-wiggle {
  0% { rotate: 0deg; }
  33% { rotate: 2deg; }
  66% { rotate: -2deg; }
  100% { rotate: 0deg; }
}
.kfsb-piece.kfsb-glow-soft { filter: drop-shadow(0 0 10px rgba(212, 168, 67, 0.6)); }
.kfsb-piece.kfsb-sway { animation: kfsb-sway 0.9s ease-in-out 1; }
@keyframes kfsb-sway {
  0%, 100% { rotate: 0deg; }
  30% { rotate: 3deg; }
  70% { rotate: -3deg; }
}
.kfsb-piece.kfsb-wake-shimmer svg { filter: drop-shadow(0 0 8px rgba(212, 168, 67, 0.85)); }

/* free play sheet */
.kfsb-sheet {
  position: absolute;
  z-index: 8;
  background: rgba(255, 255, 255, 0.55);
  border: 2px dashed rgba(114, 99, 130, 0.55);
  border-radius: var(--radius-md);
  pointer-events: none;
}

/* wake sweep: one gold shine crossing the build, 1.5s, self-ending */
.kfsb-wake-sweep {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 26%;
  z-index: 34;
  pointer-events: none;
  background: linear-gradient(100deg, transparent 0%, rgba(255, 231, 160, 0.55) 45%, rgba(255, 214, 96, 0.75) 50%, rgba(255, 231, 160, 0.55) 55%, transparent 100%);
  mix-blend-mode: screen;
  animation: kfsb-sweep 1.5s ease-in-out 1 forwards;
}
@keyframes kfsb-sweep {
  from { left: -30%; opacity: 0.9; }
  to { left: 104%; opacity: 0; }
}

/* round action buttons that live on the book (smaller, bigger, turn, wake
   leaf, hear-again).
   BORDER: --kf-lilac measured 2.22:1 against the button's own fill, 1.74:1 on
   the plan paper and 1.44:1 on the supply paper, so the outline of the one big
   control on the page failed WCAG 2.2 1.4.11 everywhere and failed it worst
   exactly where the turn button sits. --kf-lilac-deep measures 5.22 / 4.09 /
   3.39 on the same three grounds. Existing token, no new value.
   Z-INDEX: free-play stickers carry z-index up to 81 inside the book's own
   stacking context, so a sticker could render over these buttons and take
   their taps. 90 puts every control above every sticker; the book is z-index 5
   in the STAGE, so overlays, the cover and the toolbar are unaffected. */
.kfsb-round {
  position: absolute;
  z-index: 90;
  box-sizing: border-box;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  border: 3px solid var(--kf-lilac-deep);
  background: rgba(253, 250, 244, 0.96);
  cursor: pointer;
  box-shadow: 0 3px 12px rgba(15, 10, 36, 0.25);
  padding: 0;
}
@media (hover: hover) {
  .kfsb-round:hover { background: #fff; }
}
.kfsb-round:active { transform: scale(0.94); transition: transform 0.12s ease; }
/* the hidden attribute must win over the display above (pointer-grid scan
   caught the rotate/wake buttons rendering on a setting-A page) */
.kfsb-round[hidden], .kfsb-tbtn[hidden] { display: none; }
/* 80px, from 72: free play includes 4-5, so anything a four-year-old touches
   sits on the --tap-play-min 75px floor and 72 was a live violation. 80 also
   matches the wake leaf, so the strip and the leaf read as one family. */
.kfsb-rotate, .kfsb-grow, .kfsb-shrink { width: 80px; height: 80px; }
/* the round on-page wake leaf was retired 2026-08-16: the come-to-life control
   is now the labelled .kfsb-wake-big pill in the toolbar (see top of file). */
/* 80px, not 64 and not the 76 first recommended: every other round child
   control on this surface is 80 (the three strip buttons and the wake leaf),
   and three sizes read worse than one. 80 clears the 75px --tap-play-min floor
   with 5px to spare. */
.kfsb-hear { width: 80px; height: 80px; }
.kfsb-hear svg { width: 36px; height: 36px; }

/* ---------- the shared action glyph: the child's own sticker + a gold mark -
   One icon register for all three on-book action buttons. The mini shape
   tracks the live piece's shape, colour and ANGLE, so pressing turn four times
   visibly walks the shape around inside the button: a live readout, not a
   fixed icon. */
.kfsb-glyph { display: block; width: 48px; height: 48px; pointer-events: none; }
.kfsb-glyph svg { display: block; width: 100%; height: 100%; }
.kfsb-glyph-shape,
.kfsb-glyph-sweeper {
  transform-box: view-box;
  transform-origin: 50% 50%;
}
.kfsb-glyph-shape { transition: transform 0.2s ease; }
/* dormant: the whole glyph quiets to .55 and the mini shape is a lilac square.
   The button is visibly WAITING, never gone: a control that comes and goes is
   worse than one that is quiet. */
.kfsb-glyph--dormant { opacity: 0.55; }
/* the one-off teaching sweep: 360 degrees over 700ms, once per session */
.kfsb-glyph-sweep { animation: kfsb-glyph-sweep 0.7s ease 1; }
@keyframes kfsb-glyph-sweep {
  from { transform: rotate(0deg); }
  to { transform: rotate(360deg); }
}

/* armed: a piece is active, so the piece takes --halo-select-strong and the
   button takes the SAME gold ring in the same frame. Simultaneity is what
   connects them; they are too far apart for proximity to do it. */
.kfsb-round.kfsb-armed {
  box-shadow: 0 3px 12px rgba(15, 10, 36, 0.25), 0 0 0 4px var(--kf-gold), 0 0 0 6px rgba(45, 27, 78, 0.55);
}
/* end of the size ladder: aria-disabled, never disabled, so the button stays
   focusable and reviewable. It keeps its border and loses its shadow. */
.kfsb-round[aria-disabled='true'] { box-shadow: none; }
.kfsb-round[aria-disabled='true'] .kfsb-glyph { opacity: 0.38; }

/* the turn button's own pulses. Every cycle is 1.2s, so nothing approaches a
   flash; the teaching nudge reuses the shipped kfsb-nudge keyframe. */
.kfsb-round.kfsb-nudge { animation: kfsb-nudge 0.6s ease 1; }
.kfsb-round.kfsb-turnpulse { animation: kfsb-turnpulse 1.2s ease-in-out 2; }
.kfsb-round.kfsb-turnpulse2 { --kfsb-pulse: 9px; }
.kfsb-round.kfsb-turnpulse3 { --kfsb-pulse: 13px; }
.kfsb-round.kfsb-turnpulse-static { box-shadow: 0 0 0 4px rgba(212, 168, 67, 0.7); }
@keyframes kfsb-turnpulse {
  0%, 100% { box-shadow: 0 3px 12px rgba(15, 10, 36, 0.25), 0 0 0 0 rgba(212, 168, 67, 0); }
  50% { box-shadow: 0 3px 12px rgba(15, 10, 36, 0.25), 0 0 0 var(--kfsb-pulse, 6px) rgba(212, 168, 67, 0.6); }
}

/* ---------- selection badges: remove (x) + recolour --------------------
   Deniz asked for both actions on the selected sticker. The control strip
   could not take them (four 80px buttons need 356px of run and the landscape
   column measures 275.4 inside the supply page, 306.0 of book, 312.0 of stage
   under the toolbar at 640x360), so they attach to the SELECTION as corner
   badges: 75px transparent hit box on the --tap-play-min floor, 44px visible
   disc inside it, and the engine's 108px frame keeps the two hit boxes 33px
   apart, over the 8px adjacent-target floor and over the 16px this band
   wants.
   Z-INDEX 85: above every sticker (free play reaches 81) and below the round
   controls (90). That ordering is deliberate, and it is why the engine's
   placement search keeps a badge clear of the strip, the wake leaf and
   hear-again rather than trusting stacking to sort it out.
   RING COLOUR IS A DEVIATION, flagged to Nova: spec 18 (2.3) says
   `3px solid var(--kf-lilac)`, "the same ring as the toolbar pills". Measured,
   --kf-lilac is 2.24:1 on cream, 1.74:1 on the plan paper and 1.44:1 on the
   supply paper, which is the exact WCAG 2.2 1.4.11 failure spec 17 (2.3) found
   on .kfsb-round one round ago and fixed by swapping this same token. A cream
   disc on cream paper has nothing BUT this ring for an edge, and spec 18's own
   governing rule is that a 2.13:1 mark may never carry meaning. So the badge
   takes --kf-lilac-deep (5.27:1 on cream, 4.09 / 3.39 on the two papers) and
   the deviation is reported rather than taken silently. */
.kfsb-badge {
  position: absolute;
  z-index: 85;
  box-sizing: border-box;
  width: 75px;
  height: 75px;
  margin: 0;
  padding: 0;
  border: 0;
  background: none;
  display: grid;
  place-items: center;
  cursor: pointer;
  touch-action: none;
  -webkit-tap-highlight-color: transparent;
}
.kfsb-badge-disc {
  display: grid;
  place-items: center;
  /* border-box, so the DISC is 44px as specced rather than 44 + two 3px
     borders = 50: this sheet inherits no global border-box reset, and the
     44 is the number that was reasoned about against the 24px WCAG floor
     and the 33px gutter */
  box-sizing: border-box;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  background: #fdfaf4;
  border: 3px solid var(--kf-lilac-deep);
  box-shadow: 0 2px 6px rgba(15, 10, 36, 0.18);
}
.kfsb-badge-disc svg { display: block; width: 24px; height: 24px; }
@media (hover: hover) {
  .kfsb-badge:hover .kfsb-badge-disc { background: #fff; }
}
.kfsb-badge:active .kfsb-badge-disc { transform: scale(0.94); transition: transform 0.12s ease; }
/* one 180ms settle as the pair appears; nothing flashes and nothing repeats */
.kfsb-badge-in { animation: kfsb-badge-in 0.18s ease 1; }
@keyframes kfsb-badge-in {
  from { transform: scale(0.78); }
  to { transform: scale(1); }
}

/* ---------- bloom layer ---------- */
.kfsb-bloom {
  position: absolute;
  pointer-events: none;
  opacity: 0;
  transition: opacity 1.1s ease;
}
.kfsb-bloom.kfsb-in { opacity: 1; }
.kfsb-bloom.kfsb-fast { transition-duration: 0.25s; }
/* bloom art ships pre-keyed (-alpha.webp derivatives): no blend modes, no
   masks; cheaper on a low-end compositor and immune to blend-mode support
   gaps. The fx layer clips everything to the plan page. */
.kfsb-fx {
  position: absolute;
  overflow: hidden;
  pointer-events: none;
  z-index: 24;
}
.kfsb-bloom > img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
}
.kfsb-fadeout { transition: opacity 1.1s ease; opacity: 0 !important; }
.kfsb-fadeout-fast { transition-duration: 0.25s; }

/* ---------- come to life: the "being made" glow-breath (Phase B) ---------
   One light on the whole build, no stagger and no geometry change (no scale on
   tiny stickers). 2.6s ease-in-out, 0.38 Hz: far under the 3/s flash line. The
   reduced-motion path swaps in the static .kfsb-glow-soft from JS instead. */
.kfsb-piece.kfsb-making { animation: kfsb-making-breath 2.6s ease-in-out infinite; }
@keyframes kfsb-making-breath {
  0%, 100% { filter: drop-shadow(0 0 8px rgba(212, 168, 67, 0.30)); }
  50% { filter: drop-shadow(0 0 15px rgba(212, 168, 67, 0.62)); }
}

/* ---------- come to life: the framed picture (Phase C/D) -----------------
   The build dissolves up into this cream-matted frame sitting where it was
   built. Frame aspect == image aspect, so object-fit:cover fills it exactly;
   the cream padding (set inline by the engine) is the mat. */
.kfsb-picture {
  position: absolute;
  opacity: 0;
  transform: scale(0.94);
  transform-origin: 50% 50%;
  transition: opacity 1.1s ease, transform 1.1s ease;
  box-sizing: border-box;
  background: var(--kf-cream);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-lift);
  overflow: hidden;
  pointer-events: none; /* decorative; the controls opt back into taps below */
}
.kfsb-picture.kfsb-in { opacity: 1; transform: scale(1); }
.kfsb-picture > img { display: block; width: 100%; height: 100%; object-fit: cover; }

/* the keep-building pill and the download button live INSIDE the fx layer
   (pointer-events:none), so they must opt back into taps. */
.kfsb-keep, .kfsb-save { pointer-events: auto; }
/* download is a grown-up utility: 64px, deliberately smaller than the 80px
   child controls, still over the 44-48px platform floor (spec 21 §4a). */
.kfsb-save { width: 64px; height: 64px; }
.kfsb-save svg { display: block; width: 26px; height: 26px; }
/* INDIR + PAYLAS ARTIK ALT SERITTE (Deniz, 2026-08-17: tablette resmin
   kenarindan tasip kesiliyorlardi). Ikisi de seridin kendi pil bicimini
   giyiyor, cunku oradaki komsulari (cikis, ses, bastan basla, tam ekran)
   oyle: ayni satirda iki farkli dugme dili olmaz.
   .kfsb-tbtn olculerini serit veriyor; buradaki tek is, paylas dugmesinin
   sarmalayicisinin serit icinde bir pil gibi davranmasi. Sarmalayici
   gerekli, cunku menu ona gore konumlaniyor. */
.kfsb-toolbar .kfsh-wrap { display: inline-flex; pointer-events: auto; }
.kfsb-toolbar .kfsh-wrap[hidden] { display: none; }
.kfsb-save-tb svg, .kfsb-share-tb svg { display: block; width: 26px; height: 26px; }
/* Menu seridin USTUNDE acilir: serit sahnenin en altinda oldugu icin asagi
   acilsaydi ekranin disinda kalirdi. Modul zaten dar durumda yukari
   ceviriyor, bu onu garantiye aliyor. */
.kfsb-toolbar .kfsh-menu { bottom: calc(100% + 8px); top: auto; }
/* Phase D controls arrive on one soft 180ms fade (instant under reduced). */
.kfsb-appear { animation: kfsb-appear 0.18s ease 1; }
@keyframes kfsb-appear { from { opacity: 0; } to { opacity: 1; } }

.kfsb-glowpt {
  position: absolute;
  width: 14%;
  aspect-ratio: 1;
  border-radius: 50%;
  transform: translate(-50%, -50%);
  background: radial-gradient(circle, rgba(255, 219, 120, 0.85) 0%, rgba(255, 219, 120, 0) 70%);
  mix-blend-mode: screen;
  opacity: 0;
  transition: opacity 0.9s ease;
}
.kfsb-bloom.kfsb-lit .kfsb-glowpt { opacity: 1; }

.kfsb-mote {
  position: absolute;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: radial-gradient(circle, #ffe9a8 20%, rgba(255, 233, 168, 0) 75%);
  box-shadow: 0 0 6px 2px rgba(255, 219, 120, 0.5);
  animation: kfsb-mote 2s ease-out 1 forwards;
  opacity: 0;
}
@keyframes kfsb-mote {
  0% { opacity: 0; transform: translateY(0); }
  15% { opacity: 0.9; }
  100% { opacity: 0; transform: translateY(-90px); }
}
.kfsb-firefly {
  position: absolute;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: #ffe9a8;
  box-shadow: 0 0 8px 3px rgba(255, 219, 120, 0.65);
  animation: kfsb-firefly 5.2s ease-in-out infinite;
}
@keyframes kfsb-firefly {
  0%, 100% { transform: translate(0, 0); opacity: 0.55; }
  50% { transform: translate(11px, -13px); opacity: 0.95; }
}

.kfsb-creature {
  position: absolute;
  z-index: 2; /* above the bloom inside the fx layer */
  pointer-events: none;
  opacity: 0;
}
.kfsb-creature img {
  width: 100%;
  height: 100%;
  object-fit: contain;
}
.kfsb-creature.kfsb-enter { opacity: 1; transition: transform 1s ease-out, opacity 0.4s ease; }
.kfsb-creature.kfsb-settle { animation: kfsb-squash 0.3s ease 1; }
@keyframes kfsb-squash {
  0% { scale: 1 1; }
  50% { scale: 1.04 0.94; }
  100% { scale: 1 1; }
}

/* ---------- cover ---------- */
.kfsb-cover {
  position: absolute;
  inset: 0;
  z-index: 50;
  display: flex;
  align-items: center;
  justify-content: center;
  /* OPAQUE desk, not a 35% tan wash (fix 2026-08-10, Deniz: "ilk açılırken
     kitap zeminli geliyor ve görüntü bozuluyor"). At 0.35 alpha the OPEN book
     spread underneath bled straight through the closed-book cover, so the two
     books stacked. The cover now fully hides the spread: a closed book on the
     same wood desk the stage uses, and when it fades out the real spread is
     revealed with nothing showing through mid-transition. */
  background: #caa06d url('/games/sticker-book/desk.webp?v=1') center / cover no-repeat;
  cursor: pointer;
  border: 0;
  padding: 0;
  transition: opacity 0.6s ease, transform 0.6s ease;
}
.kfsb-cover-book {
  position: relative;
  height: 88%;
  aspect-ratio: 896 / 1200;
  max-width: 88%;
  background: url('/games/sticker-book/cover.webp?v=1') center / contain no-repeat;
}
.kfsb-cover-title {
  position: absolute;
  left: 31%;
  top: 32.5%;
  width: 38%;
  height: 16.5%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  font-family: var(--font-heading);
  font-weight: 700;
  color: var(--kf-purple-deep);
  line-height: 1.15;
  pointer-events: none;
}
.kfsb-cover-sub {
  position: absolute;
  left: 20%;
  top: 50.5%;
  width: 60%;
  text-align: center;
  font-family: var(--font-heading);
  color: rgba(253, 250, 244, 0.92);
  font-size: 0.85rem;
  pointer-events: none;
  text-shadow: 0 1px 6px rgba(45, 27, 78, 0.6);
}
.kfsb-cover.kfsb-open { opacity: 0; transform: scale(1.06); pointer-events: none; }

/* ---------- overlays: celebration, quit sheet, states ---------- */
.kfsb-overlay {
  position: absolute;
  inset: 0;
  z-index: 60;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 12px;
  background: var(--scrim-navy);
}
.kfsb-card {
  position: relative;
  box-sizing: border-box;
  background: var(--kf-cream);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lift);
  padding: var(--space-4);
  max-width: min(90vw, 460px);
  max-height: 94%;
  overflow: auto;
  text-align: center;
}
.kfsb-card h2 {
  font-size: var(--text-xl);
  margin: var(--space-2) 0 var(--space-2);
  color: var(--kf-purple-deep);
}
.kfsb-card p { margin: 0 0 var(--space-3); color: var(--color-text); }
.kfsb-ori {
  width: min(190px, 42%);
  border-radius: var(--radius-md);
  border: 3px solid #fff;
  box-shadow: var(--shadow-soft);
  display: block;
  margin: 0 auto;
}
.kfsb-ori--sm { width: 96px; }
.kfsb-pills { display: flex; flex-wrap: wrap; gap: 10px; justify-content: center; }
.kfsb-pill {
  box-sizing: border-box;
  min-height: 64px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-pill);
  font-family: var(--font-heading);
  font-weight: 800;
  font-size: 1.05rem;
  padding: 10px 26px;
  cursor: pointer;
  border: 0;
  text-decoration: none;
}
.kfsb-pill--gold {
  background: linear-gradient(180deg, #ffd94d 0%, #f0c437 100%);
  color: var(--kf-purple-deep);
  box-shadow: 0 4px 14px rgba(15, 10, 36, 0.24);
}
.kfsb-pill--purple {
  background: var(--kf-purple-deep);
  color: #fdfaf4;
  box-shadow: 0 4px 14px rgba(15, 10, 36, 0.24);
}
.kfsb-pill:hover { filter: brightness(1.05); }
/* a card carrying the corner close button: the padding drops the content
   start, and the DOM puts the NARROW snapshot (not the full-width headline)
   in the X's row, so the headline never runs under the X at 360 (fix round
   2026-08-09; padding alone left the wide headline brushing the X) */
.kfsb-card--closable { padding-top: calc(var(--space-4) + 44px); }
.kfsb-card--closable .kfsb-snapshot { max-width: 132px; }
/* small stages: both pills must sit inside the card without scrolling */
@media (max-width: 420px) {
  .kfsb-card { padding: var(--space-3); }
  .kfsb-card--closable { padding-top: calc(var(--space-3) + 40px); }
  /* width, not max-width: the base rule's min-width would win otherwise */
  .kfsb-card--closable .kfsb-snapshot { width: 96px; min-width: 0; margin-bottom: var(--space-2); }
  .kfsb-card h2 { font-size: var(--text-lg); margin-top: 0; }
}
.kfsb-close {
  position: absolute;
  top: 8px;
  right: 8px;
  width: 64px;
  height: 64px;
  border-radius: 50%;
  border: 0;
  background: var(--kf-lilac-soft);
  color: var(--kf-purple-deep);
  font-size: 1.6rem;
  font-weight: 700;
  cursor: pointer;
  line-height: 1;
}
.kfsb-close:hover { background: var(--kf-lilac); }
.kfsb-snapshot {
  display: block;
  margin: 0 auto var(--space-3);
  width: 40%;
  min-width: 130px;
  background: #fff;
  border-radius: var(--radius-sm);
  border: 2px solid var(--kf-lilac-soft);
  padding: 6px;
}
.kfsb-snapshot svg { display: block; width: 100%; height: auto; }

/* loading skeleton: cream book silhouette, gentle pulse */
.kfsb-skeleton {
  position: absolute;
  inset: 0;
  z-index: 55;
  display: flex;
  align-items: center;
  justify-content: center;
  background: #d9b98c;
}
.kfsb-skeleton-book {
  width: 70%;
  height: 78%;
  max-width: 640px;
  border-radius: 18px;
  background: rgba(253, 250, 244, 0.85);
  border: 10px solid rgba(150, 110, 160, 0.5);
  animation: kfsb-pulse 1.6s ease-in-out infinite;
}
@keyframes kfsb-pulse {
  0%, 100% { opacity: 0.65; }
  50% { opacity: 0.95; }
}
.kfsb-loading-line {
  position: absolute;
  bottom: 12%;
  left: 0;
  right: 0;
  text-align: center;
  font-family: var(--font-heading);
  font-weight: 700;
  color: var(--kf-purple-deep);
}

/* confetti reuses .kfe-confetti from kf-games.css (root must clip) */
.kfsb-stage .kfe-confetti { border-radius: var(--radius-lg); z-index: 58; }

/* visually hidden live region (the AT channel; sighted co-signals are visual) */
.kfsb-sr {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

@media (prefers-reduced-motion: reduce) {
  .kfsb-skeleton-book { animation: none; opacity: 0.85; }
  .kfsb-slot.kfsb-hint3 { animation: none; filter: drop-shadow(0 0 13px rgba(212, 168, 67, 0.9)); }
  .kfsb-firefly { animation: none; opacity: 0.8; }
  .kfsb-wake-sweep { display: none; }
  /* the three new animations, all off: the glyph JUMPS to the piece's angle,
     the size change is instant, and both button pulses become the static gold
     ring. Every meaning still arrives; only the motion is dropped. */
  .kfsb-glyph-shape { transition: none; }
  .kfsb-glyph-sweep { animation: none; }
  .kfsb-piece.kfsb-anim-pop { transition: none; }
  .kfsb-piece.kfsb-turnwobble { animation: none; }
  .kfsb-round:active { transform: none; }
  .kfsb-round.kfsb-nudge { animation: none; box-shadow: 0 0 0 4px rgba(212, 168, 67, 0.55); }
  .kfsb-round.kfsb-turnpulse { animation: none; box-shadow: 0 0 0 4px rgba(212, 168, 67, 0.7); }
  /* the badges appear with no scale-in; the piece keeps its existing lift and
     every meaning still arrives (the engine also gates the class on REDUCED,
     so this is the second of two locks, not the only one) */
  .kfsb-badge-in { animation: none; }
  .kfsb-badge:active .kfsb-badge-disc { transform: none; }
  /* come to life: no breath (JS swaps in the static glow-soft instead), and
     the picture arrives with no scale and no transition (spec 21 table). */
  .kfsb-piece.kfsb-making { animation: none; }
  .kfsb-picture { transition: none; transform: none; }
  .kfsb-picture.kfsb-in { transform: none; }
  .kfsb-appear { animation: none; }
}
