/* ─────────────────────────────────────────────────────────────
   mobile.css — the only stylesheet loaded on every page.

   The 17 pages are otherwise self-contained (each carries its own
   inline <style>), so anything that has to be true everywhere on a
   phone lives here rather than being copy-pasted 17 times.

   Verified on iOS 26.5 simulators, which are the only rig that shows
   safe-area behaviour, rubber-band overscroll and the collapsing URL
   bar. A 390px desktop window does not. Checked on an iPhone 17 Pro
   (Dynamic Island) and an iPhone SE 3rd gen (home button, no notch),
   which sit at the two ends of the range.
   ───────────────────────────────────────────────────────────── */

/* How tall the iOS status bar is, in CSS pixels.

   We have to hard-code this because env(safe-area-inset-top) reports 0 in
   a normal Safari tab — it only fills in for the installed standalone app.
   Yet a scrolled Safari page DOES run under the status bar once the URL bar
   collapses: confirmed on both devices, with the clock landing on live text.
   So the inset is needed and the browser will not tell us its size.

   780px splits the range cleanly and permanently: every iPhone with a notch
   or a Dynamic Island is 812pt tall or more, and every home-button iPhone is
   667pt or less. Nothing has ever shipped in between. A single 52px floor
   was leaving ~32px of dead space above the pinned label on an SE. */
:root { --ios-statusbar: 52px; }
@media (max-device-height: 780px) { :root { --ios-statusbar: 24px; } }

/* ── Top-level pages: the hero topmark, lifted out so it can pin ──
   About/Projects/Blog/Uses/FAQ/Contact had no bar to grow over the status
   bar and nothing to navigate by. Rather than add a breadcrumb they do not
   need, the line they already open with ("~/ABOUT · THE LONGER VERSION")
   moves out of .hero and becomes that bar. These two rules put the spacing
   back exactly where the hero's own padding used to put it, at every width,
   so desktop is unchanged. */
.page-crumb { padding: calc(110px + env(safe-area-inset-top)) 8% 0; }
/* The topmark's own 50px margin-bottom now belongs to the hero instead. Two
   reasons: inside .page-crumb it made the pinned bar 50px taller than its
   text needs, and leaving it there PLUS a hero padding-top was adding the
   gap twice, which is not what "desktop is unchanged" was supposed to mean.
   Total from page top to the headline is the same as it was. */
.page-crumb .hero-topmark { margin-bottom: 0; }
.page-crumb + .hero { padding-top: 50px; }

@media (max-width: 768px) {
  /* Full-bleed top under the Dynamic Island. The topmark pins right below the
     status bar, and its bottom border read as a stray line across the very
     top — stopping the matrix rain / particle field from reaching the edge.
     Kill just the line on phones (desktop keeps it; installed PWA already
     dropped it) so the animated background runs edge to edge under the island.
     Border only — padding stays, so nothing shifts. */
  .hero-topmark { border-bottom: none; }

  /* Desktop's tight 1.04 leading clips the descenders (the g/p in
     "gatekeepers" on /contact) when a hero heading wraps to 2-3 lines on a
     narrow phone — the last line's tails collide with the dek below and read
     as "cut off". Give the wrapped headline room on phones only. */
  .hero-head { line-height: 1.16; }

  /* One row when it pins. The second line is flavour, not location, and on
     a narrow screen it stacks into exactly the second bar Xavier called out
     on the sub-pages. */
  html.is-scrolled .page-crumb .hero-topmark span + span { display: none; }

  /* Stack the two lines. Seven of the eight pages carry this in their own
     inline <style>; blog.html never had it, so its topmark stayed side by
     side on a phone. Declaring it here fixes blog and stops the eight
     copies drifting apart again (privacy and terms were already missing
     align-items). */
  .page-crumb .hero-topmark {
    flex-direction: column;
    gap: 8px;
    align-items: flex-start;
  }

  /* THE TOP OF THE PAGE, in two states.

     At rest nothing covers the status bar: the dot grid and the matrix
     rain run all the way up and read as continuing behind the Dynamic
     Island. That is the whole point of the full-bleed layout, and an
     earlier fixed gradient scrim killed it.

     Once you start scrolling, the breadcrumb bar grows to swallow the
     status bar strip and goes opaque, so body copy passing underneath
     never collides with the clock. One element doing both jobs, no
     second bar. a11y.js puts .is-scrolled on <html>.

     A fixed element cannot do this: it does not track Safari's visual
     viewport while the toolbar collapses. Sticky does. */
  .crumb-bar {
    position: sticky;
    top: 0;
    z-index: 60;
    background: transparent;
    transition: padding-top .18s ease, background-color .18s ease;
  }
  html.is-scrolled .crumb-bar {
    /* Safari's URL bar still expanded: the viewport starts below the status
       bar, so the page never reaches it and no inset is owed. env() is real
       in the standalone app, where the page always reaches it. */
    padding-top: calc(env(safe-area-inset-top) + 14px);
    background: var(--paper, #14141e);
  }
  /* URL bar collapsed, page now running under the status bar. a11y.js sets
     .is-immersive off window.innerHeight, the only signal iOS gives us —
     env() reports 0 for both states in a tab, which is what made an
     env()-only value double-inset here and come up short there. */
  html.is-scrolled.is-immersive .crumb-bar {
    padding-top: calc(max(env(safe-area-inset-top), var(--ios-statusbar)) + 14px);
  }

  /* "BACK TO PROJECTS" duplicates the "Projects" crumb link two lines
     above it, and on mobile .crumb-inner stacks, so it landed as a
     second pinned bar. One row is enough. */
  .crumb-back { display: none; }

  /* The accessibility button is fixed over the content column, which
     on a phone is the full width of the text. Fade it while the
     reader is moving down the page; a11y.js sets .a11y-hidden. */
  .a11y-fab.a11y-hidden {
    opacity: 0;
    /* visibility, not just opacity: opacity alone leaves the button in the
       tab order, so a keyboard user paging down with Space and then tabbing
       inside the 700ms window lands focus on something invisible. Bad
       anywhere, worse inside the accessibility widget. */
    visibility: hidden;
    pointer-events: none;
    transform: translateY(8px);
  }
}

/* Reduced motion turns off the transition, so snap rather than slide. */
html.a11y-reduce-motion .a11y-fab.a11y-hidden { transform: none; }
