/* ---------- motion ---------- */
/* Everything that moves is defined here, and every duration and curve comes from these
   tokens, so the feel of the whole page can be retimed in one place. The character is
   The Book Shelf's: things arrive from 85% scale over 400ms, ease out, and stagger. */
:root{
  --t-fast:150ms;
  --t:400ms;
  --t-slow:600ms;
  --ease-out:cubic-bezier(0,0,.2,1);
  --ease-in:cubic-bezier(.4,0,1,1);
  --pop:.85;      /* the scale things arrive from and leave to */
  --stagger:50ms;
}

/* ---------- shared keyframes ---------- */
/* Pairs with the same frames under two names are how an entrance is restarted on an
   element that stays in the page: switching the name replays it (data-reveal on #stage,
   data-swap from swapText() and bump() in js/dom.js). */
@keyframes reveal{from{opacity:0;transform:scale(var(--pop))}}
@keyframes revealAgain{from{opacity:0;transform:scale(var(--pop))}}
@keyframes riseIn{from{opacity:0;transform:translateY(14px)}}
@keyframes swapIn{from{opacity:0;transform:translateY(7px)}}
@keyframes swapInAgain{from{opacity:0;transform:translateY(7px)}}
@keyframes fadeIn{from{opacity:0}}
@keyframes fadeOut{to{opacity:0}}

/* ---------- first load: one entrance, in reading order ---------- */
.appbar{animation:riseIn var(--t-slow) var(--ease-out) both}
.filters{animation:riseIn var(--t-slow) var(--ease-out) 100ms both}
.resultsbar{animation:riseIn var(--t-slow) var(--ease-out) 150ms both}
#stage{animation:fadeIn var(--t-slow) var(--ease-out) 150ms both}
.side{animation:riseIn var(--t-slow) var(--ease-out) 250ms both}
.caption{animation:fadeIn var(--t-slow) var(--ease-out) 400ms both}
/* Under 1100px .side dissolves into the grid and its children arrive on their own. */
@media (max-width:1099.98px){.rail{animation:riseIn var(--t-slow) var(--ease-out) 80ms both}}

/* ---------- results ---------- */
/* A new set of results arrives card by card. Cards that are merely updated are morphed
   in place and never see this. */
#stage[data-reveal="0"] .list > li{animation:reveal var(--t) var(--ease-out) both}
#stage[data-reveal="1"] .list > li{animation:revealAgain var(--t) var(--ease-out) both}
.list > li:nth-child(2){animation-delay:calc(var(--stagger)*1)}
.list > li:nth-child(3){animation-delay:calc(var(--stagger)*2)}
.list > li:nth-child(4){animation-delay:calc(var(--stagger)*3)}
.list > li:nth-child(5){animation-delay:calc(var(--stagger)*4)}
.list > li:nth-child(6){animation-delay:calc(var(--stagger)*5)}
.list > li:nth-child(7){animation-delay:calc(var(--stagger)*6)}
.list > li:nth-child(8){animation-delay:calc(var(--stagger)*7)}
.list > li:nth-child(n+9){animation-delay:calc(var(--stagger)*8)}
#noticeSlot > .notice{animation:riseIn var(--t) var(--ease-out) both}

/* Selection moves as a change of colour on cards that stay where they are. */
/* Hover and selection are the same change of colour, and take the full --t. */
.card,.thumb{transition:background-color var(--t) var(--ease-out),color var(--t) var(--ease-out),filter var(--t) var(--ease-out),transform var(--t) var(--ease-out),box-shadow var(--t) var(--ease-out)}

/* A card under the pointer does more than change colour. It lifts a little, with no
   shadow under it; its tile leans in and grows, its title takes the accent, and the
   bookmark gives a little hop, as if to say it can be saved. Its pin on the map swells
   and rings at the same moment (.hot, from link() in js/render.js), and hovering a pin
   lifts its card the same way. Pointer devices only. */
@keyframes hop{0%,100%{transform:translateY(0)}35%{transform:translateY(-5px)}65%{transform:translateY(1px)}}
@keyframes pulse{0%{box-shadow:0 0 0 0 var(--brand)}100%{box-shadow:0 0 0 12px transparent}}
.card h3{transition:color var(--t) var(--ease-out)}
@media (hover:hover){
  .card:hover,.card.hot{transform:translateY(-3px)}
  .card.hot{background:var(--accent-hover)}
  .card:hover .thumb,.card.hot .thumb{transform:scale(1.07) rotate(-3deg)}
  .card:hover h3,.card.hot h3{color:var(--accent)}
  .card:hover .savemark svg{animation:hop var(--t-slow) var(--ease-out)}
  .card:hover .savemark.on svg{animation:hop var(--t-slow) var(--ease-out)}
  .pin.hot{transform:translate(-50%,-50%) scale(1.35);z-index:3;animation:pulse 900ms var(--ease-out) infinite}
}

/* A card's detail opens by height and closes the same way (.leaving, set in render.js). */
@keyframes detailOpen{from{grid-template-rows:0fr;opacity:0;margin-top:0;padding-top:0;border-top-color:transparent}}
@keyframes detailClose{to{grid-template-rows:0fr;opacity:0;margin-top:0;padding-top:0;border-top-color:transparent}}
.detail{animation:detailOpen var(--t) var(--ease-out)}
.detail.leaving{animation:detailClose 200ms var(--ease-in) forwards}

/* A status chip arrives; and the card whose status has just changed under the visitor
   rings once in its status colour, so the eye finds what changed without it moving. */
@keyframes chipIn{from{opacity:0;transform:scale(var(--pop))}}
@keyframes ring{0%{box-shadow:0 0 0 0 var(--warn)}100%{box-shadow:0 0 0 14px transparent}}
.chip{animation:chipIn var(--t) var(--ease-out) backwards;transform-origin:left center}
.card.changed{animation:ring 900ms var(--ease-out) 2}

/* Saving: the bookmark pops as it fills, and the label slides out from beside it. */
@keyframes markPop{0%{transform:scale(1)}40%{transform:scale(1.45)}100%{transform:scale(1)}}
@keyframes labelIn{from{opacity:0;transform:translateX(14px) scale(var(--pop))}}
.savemark.on svg{animation:markPop var(--t) var(--ease-out)}
.savedlabel{animation:labelIn var(--t) var(--ease-out);transform-origin:right center}
.savemark{transition:background-color var(--t) var(--ease-out),color var(--t) var(--ease-out)}

/* Text that changes is seen changing: the count, the search title and what narrows it,
   the scenario description, the latest announcement. */
[data-swap="0"]{animation:swapIn var(--t) var(--ease-out)}
[data-swap="1"]{animation:swapInAgain var(--t) var(--ease-out)}
.capline[data-swap="0"],.capline[data-swap="1"]{animation-duration:250ms}

/* Save search keeps its place in the bar and fades in and out of it. */
#saveSearch{transition:opacity var(--t) var(--ease-out),background-color var(--t) var(--ease-out),color var(--t) var(--ease-out)}
#saveSearch[hidden]{opacity:0;transition:opacity var(--t-fast) var(--ease-in),visibility 0s var(--t-fast)}

/* ---------- map ---------- */
@keyframes pinIn{from{opacity:0;transform:translate(-50%,-50%) scale(.3)}}
@keyframes pinOut{to{opacity:0;transform:translate(-50%,-50%) scale(.3)}}
.pin{
  animation:pinIn var(--t) var(--ease-out) backwards;   /* backwards only: the pin's own opacity must apply afterwards */
  transition:transform var(--t) var(--ease-out),opacity var(--t) var(--ease-out),width var(--t) var(--ease-out),height var(--t) var(--ease-out),background-color var(--t) var(--ease-out),color var(--t) var(--ease-out),box-shadow var(--t) var(--ease-out);
}
.pin.leaving{animation:pinOut 250ms var(--ease-in) forwards;pointer-events:none}
.drawn{transition:opacity var(--t) var(--ease-out)}
.tilefail{animation:fadeIn var(--t) var(--ease-out)}
.mapsel{animation:riseIn var(--t) var(--ease-out)}

/* ---------- filters ---------- */
/* A filter that does not apply folds away, and unfolds when it does again. It keeps
   display so it can transition; visibility is what takes it out of reach, after the
   fold. Unfolding, the contents wait a beat so they appear into space that has opened. */
.filters .fieldset,.filters .check{
  max-height:200px;
  transition:max-height var(--t) var(--ease-out),margin var(--t) var(--ease-out),opacity var(--t-fast) var(--ease-out) var(--t-fast);
}
.filters .fieldset[hidden],.filters .check[hidden]{
  display:flex;visibility:hidden;overflow:hidden;max-height:0;opacity:0;
  transition:max-height var(--t) var(--ease-out),margin var(--t) var(--ease-out),opacity var(--t-fast) var(--ease-in),visibility 0s var(--t);
}
.filters .fieldset[hidden]{margin-top:-20px}   /* the column's gap, which would otherwise stay */
.filters .check[hidden]{margin-top:-9px}       /* the fieldset's gap */
#reset{transition:opacity var(--t) var(--ease-out)}
#reset[hidden]{display:block;visibility:hidden;opacity:0;transition:opacity var(--t-fast) var(--ease-in),visibility 0s var(--t-fast)}

/* ---------- controls ---------- */
/* Everything pressable answers the press, and changes colour, not just state. */
.btn,.statebtn,.dtab,.segmented button,.linkbtn{
  transition:background-color var(--t) var(--ease-out),color var(--t) var(--ease-out),transform 90ms var(--ease-out),filter var(--t) var(--ease-out);
}
.btn:active,.statebtn:active,.dtab:active,.segmented button:active{transform:scale(.96)}

/* The two-way toggles get a thumb that slides between the options. :has() is what tells
   the track which side is chosen; without it the plain filled button in shell.css stays. */
@supports selector(:has(a)){
  .segmented{position:relative}
  .segmented button{flex:1 1 0;white-space:nowrap}
  .segmented button[aria-pressed="true"]{background:transparent}
  .segmented::before{
    content:"";position:absolute;top:2px;bottom:2px;left:2px;width:calc(50% - 3px);
    border-radius:3px;background:var(--accent);
    transition:transform var(--t) var(--ease-out);
  }
  .segmented:has(button:last-child[aria-pressed="true"])::before{transform:translateX(calc(100% + 2px))}
}

/* ---------- the bar ---------- */
/* The log opens upward by height; visibility waits for the fold before it leaves. */
.logpanel{transition:grid-template-rows var(--t) var(--ease-out)}
.logpanel[hidden]{transition:grid-template-rows var(--t-fast) var(--ease-in),visibility 0s var(--t-fast)}

/* ---------- dialogs ---------- */
/* They arrive from --pop and leave to it (data-closing, set by closeAnimated() in
   js/overlays.js, which waits for popOut before really closing). The contents follow
   the sheet in a beat behind, as in The Book Shelf. */
@keyframes popIn{from{opacity:0;transform:scale(var(--pop))}}
@keyframes popOut{to{opacity:0;transform:scale(var(--pop))}}
.sheet[open]{animation:popIn var(--t-slow) var(--ease-out)}
.sheet[open]::backdrop{animation:fadeIn 300ms ease both}
.sheet[open] .sheetbody{animation:riseIn var(--t) var(--ease-out) 200ms both}
.sheet[data-closing]{animation:popOut var(--t) var(--ease-in) forwards}
.sheet[data-closing]::backdrop{animation:fadeOut var(--t) var(--ease-in) forwards}
.sheet[data-closing] .sheetbody{animation:none}

/* Design decisions: the notes share one cell, so one fades out as the next rises in. */
.dpanel{transition:opacity var(--t) var(--ease-out) 120ms,transform var(--t) var(--ease-out) 120ms}
.dpanel[hidden]{opacity:0;transform:translateY(14px);transition:opacity 120ms var(--ease-in),transform 0s 120ms,visibility 0s 120ms}

/* ---------- loading loops ---------- */
/* The only motion on the page that repeats. Under reduced motion the reset below
   freezes all three: a still ring, flat placeholders, no sweep. */
@keyframes spin{to{transform:rotate(360deg)}}
@keyframes shimmer{from{background-position:120% 0}to{background-position:-120% 0}}
.results.loading .spin{animation:spin 700ms linear infinite}   /* only while it is on show */
.skel-tile,.skel-lines i{
  background-image:linear-gradient(100deg,transparent 30%,var(--surface) 50%,transparent 70%);
  background-size:220% 100%;background-repeat:no-repeat;
  animation:shimmer 1.2s linear infinite;
}
.skeleton{animation:fadeIn var(--t-fast) var(--ease-out)}

/* A button that is busy keeps its size and swaps its label for a ring. */
.btn.busy{position:relative;color:transparent!important;pointer-events:none}
.btn.busy::after{
  content:"";position:absolute;inset:0;margin:auto;width:14px;height:14px;border-radius:50%;
  border:2px solid var(--ink);border-right-color:transparent;
  animation:spin 700ms linear infinite;
}
.btn.primary.busy::after,.btn[aria-pressed="true"].busy::after{border-color:var(--accent-ink);border-right-color:transparent}

/* The map while it is being fetched: last search's pins dim and a light sweeps across. */
.plat.loading .pin{opacity:.3;pointer-events:none}
.plat.loading::after{
  content:"";position:absolute;inset:0;pointer-events:none;
  background:linear-gradient(100deg,transparent 35%,var(--surface) 50%,transparent 65%) no-repeat;
  background-size:220% 100%;opacity:.7;
  animation:shimmer 1.1s linear infinite;
}

/* ---------- theme ---------- */
/* While the theme is being switched (class set for a moment by setTheme()), colours
   cross-fade instead of snapping. */
.theming,.theming *,.theming *::before,.theming *::after{
  transition:background-color var(--t) var(--ease-out),color var(--t) var(--ease-out),border-color var(--t) var(--ease-out),fill var(--t) var(--ease-out)!important;
}

/* Reduced motion, and ?fast: nothing animates and nothing transitions. Last in the
   cascade, and !important, so no rule above can forget it. */
@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{animation-duration:.001ms!important;animation-delay:0s!important;animation-iteration-count:1!important;transition-duration:.001ms!important;transition-delay:0s!important}
}
.fast,.fast *,.fast *::before,.fast *::after{animation-duration:.001ms!important;animation-delay:0s!important;animation-iteration-count:1!important;transition-duration:.001ms!important;transition-delay:0s!important}
