/*
  GANZ · the app frame and the CSS the screens bring with them

  tokens.css is the single source for every value (rule 5). Nothing here
  invents a colour or a pixel number: everything is a token.

  Two parts:
  1. The frame. The design files are whole pages; the app is one page that
     puts a screen, and sometimes a sheet over it, inside itself.
  2. The rules the design carries in each screen's <helmet>. They are the same
     in every screen that has them, so they live once here rather than 28
     times. Source: the helmet of the design files, reported by
     tools/screens_from_design.py.
*/

/* ---------------------------------------------------------------- the frame */

html {
  background: var(--ink);          /* behind the screen on a wide window */
  -webkit-text-size-adjust: 100%;  /* iOS must not resize the type */
}

body {
  margin: 0;
  display: flex;
  justify-content: center;
  font-family: var(--font-sans);
  color: var(--ink);
  /* The background is the ground of the screen showing; router.js sets it. */

  /* The screens already reserve --screen-pad-top and --screen-pad-bottom for
     the notch and the home bar, which is enough at the design size. On a
     phone that needs more, add only the shortfall (DESIGN-NOTES section 2,
     step 10). Needs viewport-fit=cover in index.html to be anything but 0. */
  padding-top: max(0px, calc(env(safe-area-inset-top) - var(--screen-pad-top)));
  padding-bottom: max(0px, calc(env(safe-area-inset-bottom) - var(--screen-pad-bottom)));
}

/* The app is one 390 wide column. Taller screens scroll with the page, which
   is how the design draws them: the canvas itself is taller than 844
   (DESIGN-NOTES section 2, step 10). */
#app {
  position: relative;
  width: var(--screen-w);
  flex-shrink: 0;
}

/* A sheet or the popup sits over the page, not over the inert copy that the
   design file carries. It covers the window, so a sheet opened on a long
   page is in reach without scrolling. */
#over {
  position: fixed;
  top: 0;
  left: 50%;
  margin-left: calc(-1 * var(--screen-w) / 2);
  width: var(--screen-w);
  height: 100%;
  z-index: var(--z-sheet);
}

#over[hidden] {
  display: none;
}

/* One layer per overlay, stacked in the order they were opened: S_Future can
   be tapped from inside a sheet, so there can be two. */
.ganz-layer {
  position: absolute;
  top: 0;
  left: 0;
  width: var(--screen-w);
  height: 100%;
}

/* The tab bar is fixed by the design itself since version 92 (G-02): the
   screen is a fixed 844 column, the content scrolls in a div of its own, and
   the bar sits outside it at --z-tabbar. Nothing is needed here. */

/* The design sets the link colour per ground: ink on paper and signal,
   chalk on the Mind screens. router.js sets --link-color per screen. */
a {
  color: var(--link-color, var(--ink));
  text-decoration: none;
}

a:hover {
  color: var(--link-color, var(--ink));
}

/* ------------------------------------------------------- the plain message */

/* Not designed: the state for a failed save is open (FRAGEN F-014). Plain,
   unmissable, and entirely from tokens, so it changes with the design rather
   than against it. Above everything, including a sheet. */
#message .ganz-message {
  position: fixed;
  left: 50%;
  bottom: 0;
  margin-left: calc(-1 * var(--screen-w) / 2);
  width: var(--screen-w);
  box-sizing: border-box;
  display: flex;
  align-items: flex-start;
  gap: var(--space-12);
  padding: var(--space-14) var(--space-20) var(--screen-pad-bottom);
  border-top: var(--line) solid var(--ink);
  background: var(--chalk);
  color: var(--ink);
  z-index: calc(var(--z-sheet) + 10);
}

#message .ganz-message-text {
  flex-grow: 1;
  font-size: var(--text-body);
  font-weight: var(--weight-medium);
  line-height: var(--lh-130);
}

#message .ganz-message-close {
  flex-shrink: 0;
  min-height: var(--tap);
  padding: 0 var(--space-10);
  border: var(--line) solid var(--ink);
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font-mono);
  font-size: var(--text-mono);
  letter-spacing: var(--ls-mono);
  cursor: pointer;
}

/* ------------------------------------------- what the screens' helmets carry */

button {
  font: inherit;
}

input::placeholder,
textarea::placeholder {
  color: var(--mute);
  opacity: 1;
}

/*
  A figure the design draws as text and the person has to type into: O_You's
  name, height, weight and born (E-79). The behaviour swaps the drawn figure
  for an input carrying the same inline style, so this only takes away what a
  browser adds to an input of its own and lets the type come from the design.
  The caret is the signal colour, as the design draws it beside the name.
  The empty value is the dash in ink, like every other empty figure
  (CLAUDE.md section 2), not the grey of a hint.
*/
.ganz-field {
  margin: 0;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: transparent;
  color: inherit;
  font-family: inherit;
  outline: none;
  caret-color: var(--signal);
  -webkit-appearance: none;
  appearance: none;
}

.ganz-field::placeholder {
  color: inherit;
}

/* The sheet slides up, its scrim fades in (DESIGN-NOTES section 6, "Sheet"). */
@keyframes ganzSheetUp {
  from { transform: translateY(100%); }
  to { transform: none; }
}

@keyframes ganzScrim {
  from { opacity: 0; }
  to { opacity: 1; }
}

.ganz-sheet {
  animation: ganzSheetUp var(--dur-sheet) var(--ease-out) both;
}

.ganz-scrim {
  animation: ganzScrim var(--dur-scrim) ease-out both;
}

/* S_Future pops up 16px, and its scrim is quicker and lighter than a sheet's. */
@keyframes ganzPopUp {
  from { transform: translateY(var(--space-16)); opacity: 0; }
  to { transform: none; opacity: 1; }
}

.ganz-pop {
  animation: ganzPopUp var(--dur-pop) var(--ease-out) both;
}

#over:has(.ganz-pop) .ganz-scrim {
  animation-duration: var(--dur-pop-scrim);
}

/* The breath rings on the Mind players, one per pattern, copied from the
   helmets of M_Breath, M_Breath_Intro and M_Breath_478 (design version 96).

   The shape of each one is the pattern's own: where in the cycle the ring
   stands still, and for how long. `animation-delay` is how the player starts
   it in the middle of a breath after the app comes back from the background.
   Which pattern uses which is the design's mapping, not the code's. */
@keyframes breathe {
  0% { transform: scale(0.62); }
  50% { transform: scale(1); }
  100% { transform: scale(0.62); }
}

@keyframes bin {
  0% { transform: scale(0.62); }
  100% { transform: scale(1); }
}

@keyframes bres {
  0% { transform: scale(0.62); }
  50% { transform: scale(1); }
  100% { transform: scale(0.62); }
}

@keyframes bresl {
  0% { transform: scale(0.62); }
  40% { transform: scale(1); }
  100% { transform: scale(0.62); }
}

@keyframes bsigh {
  0% { transform: scale(0.62); }
  25% { transform: scale(0.9); }
  42% { transform: scale(1); }
  100% { transform: scale(0.62); }
}

@keyframes bhum {
  0% { transform: scale(0.62); }
  33% { transform: scale(1); }
  100% { transform: scale(0.62); }
}

@keyframes b478 {
  0% { transform: scale(0.62); }
  21% { transform: scale(1); }
  58% { transform: scale(1); }
  100% { transform: scale(0.62); }
}

@keyframes bbox {
  0% { transform: scale(0.62); }
  25% { transform: scale(1); }
  50% { transform: scale(1); }
  75% { transform: scale(0.62); }
  100% { transform: scale(0.62); }
}

@keyframes settle {
  0% { transform: scale(0.9); }
  50% { transform: scale(1); }
  100% { transform: scale(0.9); }
}

/* The note field on M_Done is a real text field; its placeholder is muted
   like every other one (design version 93). */
.ganz-note::placeholder {
  color: var(--mute);
  opacity: 1;
}

/* No motion when the phone asks for none. tokens.css already zeroes the
   durations; this covers the animations that are not driven by a token, and
   the players' rings, which carry their animation in a style attribute. */
@media (prefers-reduced-motion: reduce) {
  .ganz-sheet,
  .ganz-scrim,
  .ganz-pop,
  [style*="animation"] {
    animation: none !important;
  }
}
