/* knihozor.sk — built to the reference, glossar.app.
 *
 * Palette, type and metrics are all read out of that site's own stylesheet, and the
 * palette is now taken along with everything else: Richard asked for the white page and
 * for none of the app's colours to appear.
 *
 *   page #fff · panel #f5f5f7 · line #f0f0f1 · ink #050505 · muted #6e6e75
 *   Geist / Geist Mono · pills 999px · panels 28px
 *   type 11/14/15/17/20/24 · letter-spacing -0.15 / -0.25px · gaps 8/12/16/32/48
 *   stack min(1520px, 100% - clamp(40px,3.2vw,80px)) · side copy clamp(220px,22vw,360px)
 *   easing cubic-bezier(.16,1,.3,1), which it calls --island-ease
 *
 * The components are the reference's: an island that travels with the reader, a hero that
 * leaves as you scroll past it, and scenes whose panel pins to the viewport while the
 * phone parallaxes and the captions arrive. Progress comes from `app.js` as `--p` per
 * scene and `--hero-leave` globally, the same arrangement that site uses.
 *
 * Light only, no dark mode. The reference has a light/dark/system switch; the white page
 * is what was asked for and a second palette is not what this is for.
 *
 * Without JavaScript, `html` never gets `.choreographed` and every scene is laid out in
 * its finished state — readable, just not animated. That is why the transforms live behind
 * that class rather than in the base rules.
 */

:root {
  --page: #ffffff;
  --panel: #f5f5f7;
  --line: #f0f0f1;
  /* The ground the app screenshots sit on, so that part of the page reads as its own
   * surface rather than more white. The reference does the same — `#f5f5f7` with a fine
   * tiled grain — and a hair warmer here to sit with the paper the app itself uses without
   * bringing the app's green onto a page that is deliberately neutral. */
  --panel: #f6f6f4;
  --panel-line: #ececea;
  /* A grain, not an image. The reference tiles a 64px PNG at 6.4px; one dot per 6.4px
   * square in a gradient is the same texture with nothing to download. */
  --grain: radial-gradient(circle at 1px 1px, rgba(5, 5, 5, 0.05) 1px, transparent 0);
  --ink: #050505;
  --muted: #6e6e75;
  --ease: cubic-bezier(0.16, 1, 0.3, 1);
  --stack: min(1520px, calc(100% - clamp(40px, 3.2vw, 80px)));
  --copy: clamp(220px, 22vw, 360px);
  --hero-leave: 0;
  --lift: 0 1px 2px rgba(5, 5, 5, 0.04), 0 18px 50px rgba(5, 5, 5, 0.07);
}

*,
*::before,
*::after {
  box-sizing: border-box;
}

html {
  scroll-behavior: smooth;
}

body {
  margin: 0;
  background: var(--page);
  color: var(--ink);
  font-family: Geist, -apple-system, BlinkMacSystemFont, "SF Pro Text", system-ui,
    sans-serif;
  font-size: 17px;
  line-height: 1.5;
  letter-spacing: -0.15px;
  -webkit-font-smoothing: antialiased;
}

img {
  max-width: 100%;
  /* Load-bearing: the tags carry width and height so space is reserved, and with only
   * `max-width` the width scales while the height stays at the attribute's 1955px. */
  height: auto;
  display: block;
}

h1,
h2 {
  margin: 0;
  font-weight: 600;
  letter-spacing: -0.25px;
  line-height: 1.08;
  text-wrap: balance;
}

p {
  margin: 0;
}

a {
  color: inherit;
}

/* ---- the island ------------------------------------------------------------------- */








/* Two stores, side by side, wrapping rather than shrinking on a narrow screen. */
.stores {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  /* 12px against a 48px badge is a quarter of its height — Apple's required clear space.
   *
   * The row gap is 52px and only appears when the badges wrap, which they now do on a
   * phone: three badges are about 515px wide and a 390px screen cannot hold them. Each
   * "čoskoro" note is absolutely positioned and so contributes nothing to its row's
   * height, which means a wrapped second row would otherwise be drawn straight through
   * the note belonging to the first. `row-gap` costs nothing on a single line — it
   * applies only *between* rows — so the desktop layout is unchanged. */
  gap: 52px 12px;
  margin-top: 32px;
}

/* The Mac badge is English on all three pages, and that is Apple's doing rather than a
 * shortcut. Their marketing toolbox publishes "Download on the Mac App Store" in a handful
 * of major locales only: de, es, fr, it, ja and en all answer, while sk-sk and cs-cz both
 * 404 — checked 12 Sep 2026, against an iOS badge endpoint that *does* serve both. Since
 * the artwork may not be altered or re-set in another typeface, English unmodified is the
 * only compliant option. The `alt` text is still localised, which is where a screen reader
 * needs the language.
 *
 * Apple's own badge, used unmodified.
 *
 * Their guidelines ask for the badge rather than a custom button, at least 40px tall on
 * screen, with clear space of a quarter its height around it, and the artwork left alone —
 * no recolouring, no effects, no angle. The `.stores` gap and this block's margin provide
 * the clear space; nothing here touches the image itself. The localised badges come from
 * Apple's own marketing tools, one per language, because they ask you never to translate
 * "App Store" or make your own.
 */
.badge-link {
  display: inline-block;
  line-height: 0;
  border-radius: 8px;
  transition: transform 0.5s var(--ease);
}

.badge-link:hover,
.badge-link:focus-visible {
  transform: translateY(-1px);
}

.badge {
  height: 48px;
  width: auto;
  display: block;
}

/* Google's badge PNG carries its own clear space: the artwork is 77% of the image height,
 * measured from the file. At a matched 48px image the visible pill would be 37px — smaller
 * than Apple's, which their guidelines forbid ("make sure the Google Play badge is the same
 * size or larger than the other badges"). 62px of image puts the pill at 48, equal to
 * Apple's. */
.badge.play {
  height: 62px;
}

/* Not clickable, because there is nothing to click yet — and *not* dimmed, because Google
 * forbids recolouring the badge or adding effects to it. The artwork is shown exactly as
 * they publish it; the note beside it carries the "not yet". */
.badge-link.is-soon {
  position: relative;
  cursor: default;
  display: inline-block;
  line-height: 0;
}

.badge-link.is-soon:hover,
.badge-link.is-soon:focus-visible {
  transform: none;
}

/* The handwritten aside, *below* the badge and never across it.
 *
 * The first attempt put it to the right with an arrow pointing back, and the arrow crossed
 * the badge's edge — which is drawing on the artwork, the one thing Google's guidelines
 * rule out. Underneath, it is unmistakably a note *about* the button and touches nothing.
 *
 * Absolutely positioned so it takes no space in the row: the two badges stay the thing that
 * is centred, and the note hangs off the bottom without pulling them sideways. */
.soon-note {
  position: absolute;
  top: calc(100% + 6px);
  left: 50%;
  display: flex;
  align-items: flex-end;
  gap: 5px;
  transform: translateX(-50%) rotate(-4deg);
  font-family: "Caveat", ui-rounded, cursive;
  font-size: 26px;
  line-height: 1;
  white-space: nowrap;
  color: var(--muted);
}

/* The arrow is an inline SVG (see SOON_ARROW in build.py) so it can have a head.
 *
 * The viewBox hugs the ink and the width matches its aspect ratio exactly. The first
 * version drew inside a 40x36 box whose tip sat at x=27, so twelve units of empty box sat
 * between the arrow and the word and no CSS `gap` could close it — they read as two
 * unrelated marks. Sized in em, so it tracks the handwriting. */
.soon-arrow {
  width: 0.96em;
  height: 1.35em;
  flex: none;
  margin-bottom: -0.06em;
  fill: none;
  stroke: currentColor;
  stroke-width: 2.4;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* Room underneath for the note to hang into.
 *
 * The note is absolutely positioned, so it contributes nothing to the row's own height and
 * the row has to reserve the space by hand. The first guess of 26px was for a mark 41px
 * tall, and it overlapped the line below by a pixel at every width measured — invisible on
 * a wide screen only because that line is centred and the note sits out to the right. */
.stores {
  padding-bottom: 52px;
}

/* ---- the stack --------------------------------------------------------------------- */

main {
  width: var(--stack);
  margin: 0 auto;
}

/* ---- hero, which leaves as you scroll past it -------------------------------------- */

.hero {
  min-height: 92vh;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  text-align: center;
  padding: 120px 0 80px;
}

.hero .mark {
  width: 76px;
  height: 76px;
  border-radius: 18px;
  margin-bottom: 40px;
  box-shadow: var(--lift);
}

.hero h1 {
  font-size: clamp(40px, 8vw, 88px);
  letter-spacing: -0.035em;
}

.dim {
  color: var(--muted);
}

.lede {
  margin-top: 26px;
  max-width: 30em;
  color: var(--muted);
  font-size: 17px;
}

.note {
  margin-top: 16px;
  font-size: 14px;
  color: var(--muted);
}

.choreographed .hero > * {
  /* Lifts and fades as the hero leaves, which is the reference's --hero-leave-progress. */
  opacity: calc(1 - var(--hero-leave) * 0.9);
  transform: translateY(calc(var(--hero-leave) * -40px));
  will-change: opacity, transform;
}

/* ---- the feature story: ONE stage that pins, with the screens changing inside it -----
 *
 * Read off the reference's own stylesheet rather than guessed at. Its `.featureStorySimple`
 * is a container `(steps + 1) * 100vh` tall holding a single `position: sticky; top: 0;
 * height: 100vh` viewport, with the phone screenshots as layers stacked `position:
 * absolute; inset: 0` that cross-fade as a scroll-driven custom property advances.
 *
 * The first version of this page pinned once per screenshot — eight stages each showing one
 * image — which reads as "one by one below and not changing anything just somehow moving".
 * Pinning eight times is a different effect from pinning once and changing what is inside.
 *
 * `--story-progress` (0 → 1) is written by app.js. Nothing here depends on it existing:
 * with the script off, the first slide and the first screenshot are the ones marked
 * `data-first`, and the page reads as a page.
 */

.story {
  /* The runway the pin travels through. One extra screen so the last step is readable
   * before the stage releases — the same `(steps + 1)` the reference uses. */
  --story-progress: 0;
  position: relative;
}

.story-viewport {
  display: none;
}

/* ---- the panel: what makes this part of the page a different surface ---- */

.story-panel {
  background-color: var(--panel);
  background-image: var(--grain);
  background-size: 6.4px 6.4px;
  background-repeat: repeat;
  border: 1px solid var(--panel-line);
  border-radius: 40px;
}

/* ---- the stacked fallback, which is what a phone gets ---- */

.story-stacked {
  display: grid;
  gap: 20px;
  padding: 40px 0 24px;
}

/* Each item is its own panel below the breakpoint, which is what the reference does —
 * one long panel behind a stacked list would just be a differently coloured page. */
.story-stacked-item {
  display: grid;
  justify-items: center;
  gap: 14px;
  text-align: center;
  background-color: var(--panel);
  background-image: var(--grain);
  background-size: 6.4px 6.4px;
  border: 1px solid var(--panel-line);
  border-radius: 28px;
  padding: 44px 22px;
}

.story-stacked-item img {
  width: 100%;
  max-width: 300px;
  border-radius: 30px;
  border: 1px solid var(--panel-line);
  box-shadow: var(--lift);
}

.story-stacked-item h2 {
  font-size: clamp(21px, 2.2vw, 24px);
  margin-top: 10px;
}

.story-stacked-item p {
  max-width: 34em;
  color: var(--muted);
  font-size: 15px;
}

.stat {
  color: var(--ink);
  font-size: 20px;
  font-weight: 500;
  letter-spacing: -0.25px;
}

.stat.dim {
  margin-top: 4px;
  color: var(--muted);
  font-weight: 400;
}

/* ---- the pinned stage, only where there is room for three columns ---- */

@media (min-width: 1281px) {
  .story {
    height: calc((var(--steps, 4) + 1) * 100vh);
  }

  .story-stacked {
    display: none;
  }

  .story-viewport {
    /* The whole effect, in four declarations. */
    position: sticky;
    top: 0;
    height: 100vh;
    display: flex;
    align-items: center;
    justify-content: center;
    overflow: hidden;
    /* Room for the panel's corners to show against the page. */
    padding: 24px 0;
  }

  .story-panel {
    display: flex;
    align-items: center;
    width: 100%;
    height: 100%;
    padding: clamp(20px, 3.2vw, 60px) clamp(44px, 4.12vw, 72px);
    overflow: hidden;
  }

  .story-rail {
    display: grid;
    grid-template-columns: var(--copy) minmax(0, 1fr) var(--copy);
    align-items: center;
    gap: 48px;
    width: 100%;
    padding: 40px 0 8px;
  }

  .story-copy {
    display: flex;
    align-items: center;
    gap: 24px;
  }

  /* A rail that fills as the story advances — the reference draws it as a gradient whose
   * stop is the progress value, which needs no extra element and no JS beyond the
   * property that is already being written. */
  .story-line {
    flex: none;
    width: 2px;
    height: clamp(120px, 18vh, 190px);
    border-radius: 2px;
    background: linear-gradient(
      to bottom,
      var(--ink) 0%,
      var(--ink) calc(var(--story-progress) * 100%),
      var(--line) calc(var(--story-progress) * 100%),
      var(--line) 100%
    );
  }

  /* Slides share one box and cross-fade in place, which is the point of the whole
   * arrangement: the stage does not move, the content in it changes. */
  .story-slides,
  .story-aside {
    position: relative;
    flex: 1;
    min-height: clamp(150px, 22vh, 210px);
  }

  /* app.js owns opacity and transform here — it computes both from the slide's own band
   * of the scroll, and a CSS fallback fighting it was how the first rebuild ended up
   * fading everything as one block. Without the script the first slide is the visible
   * one, which is the `data-first` rule below. */
  .story-slide {
    position: absolute;
    inset: 0;
    opacity: 0;
    transform: translateY(36px);
    will-change: opacity, transform;
  }

  .story-slide[data-first="true"] {
    opacity: 1;
    transform: translateY(0);
  }

  /* Dim until this word's turn. app.js raises them one at a time; the value here is what
   * an unlit word looks like, and what every word looks like with no script. */
  [data-story-word] {
    display: inline-block;
    opacity: 1;
    will-change: opacity;
  }

  .story-slide h2 {
    font-size: clamp(21px, 2.2vw, 25px);
  }

  .story-slide p {
    margin-top: 12px;
    color: var(--muted);
    font-size: 15px;
    max-width: 32ch;
  }

  .story-aside {
    text-align: right;
  }

  .story-phone {
    position: relative;
    /* The screenshots' real ratio, measured from the files — they are 414x900. The
     * markup used to declare 900x1955, which is close enough in ratio not to look
     * distorted and wrong anyway; a box that says one thing while the image is another
     * is how the last round of image bugs on this page started. */
    aspect-ratio: 414 / 900;
    /* Nearer life size, like the reference's 402px, but never taller than the stage it
     * has to sit inside — the height is the real constraint and the width follows from it.
     *
     * The 260px is everything above and below the phone inside the pin: the viewport's own
     * 48px of breathing room, the panel's vertical padding, and the rail's clearance for
     * the fixed island. Budgeting only 200 clipped the phone's bottom bezel against the
     * panel's `overflow: hidden`. */
    width: min(380px, calc((100vh - 260px) * 414 / 900), 100%);
    margin-inline: auto;
    justify-self: center;
  }

  .story-layer {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    opacity: 0;
    filter: blur(12px);
    border-radius: 30px;
    border: 1px solid var(--panel-line);
    box-shadow: var(--lift);
    /* Blur as well as opacity: the reference blurs through the swap rather than
     * dissolving, which is what stops two screenshots reading as a double exposure. */
    will-change: opacity, filter;
  }

  .story-layer:first-of-type {
    opacity: 1;
    filter: blur(0px);
  }
}

/* Someone who asked for less motion gets the stacked version at every width — a pinned
 * stage is motion whether or not anything eases. */
@media (prefers-reduced-motion: reduce) {
  .story {
    height: auto;
  }

  .story-viewport {
    display: none;
  }

  .story-stacked {
    display: grid;
  }
}


/* ---- closing and footer ------------------------------------------------------------ */

.closing {
  text-align: center;
  padding: 120px 0 140px;
}

.closing h2 {
  font-size: clamp(28px, 5vw, 56px);
  max-width: 20em;
  margin: 0 auto;
  letter-spacing: -0.035em;
}

footer.panel {
  background: var(--panel);
  border-radius: 28px;
  padding: 44px clamp(20px, 3vw, 40px) 38px;
  margin-bottom: 24px;
  text-align: center;
  color: var(--muted);
  font-size: 14px;
}

footer .stamp {
  width: 40px;
  height: 40px;
  border-radius: 10px;
  margin: 0 auto 24px;
}

footer nav {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 16px;
  margin-bottom: 24px;
}

footer a {
  text-decoration: none;
}

footer a:hover,
footer a:focus-visible {
  color: var(--ink);
}

footer .langs {
  display: inline-flex;
  gap: 12px;
  font-family: "Geist Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 11px;
  letter-spacing: 0.1px;
}

footer .fine {
  max-width: 62em;
  margin: 0 auto;
  font-size: 11px;
  line-height: 1.6;
  overflow-wrap: break-word;
}

