/* =============================================================================
   mobile.css — everything under 900px. Owned by the mobile package (P3b).

   1  Scale + tokens     a 16px root, phone-sized type tokens, a 1.25rem gutter
   2  Landing            one column that scrolls: mark, welcome, copy, socials
   3  Board, grid mode   4 square columns on graph paper, static captions,
                         colour and pattern filling the gaps
   4  Project shell      the aside as a top block, Back first, then the column
   5  Minibar            fixed Back + title, slides in once the title is gone
   6  Blocks restacked   rowed, challenges, metrics, sets, prototype
   7  More work          two rows: thumb + title, then the blurb
   8  404

   The desktop page is one fixed frame scaled by viewport width; a phone is
   the opposite: the body scrolls and the type holds still. So the root stops
   tracking vw here and sits at 16px, which is also what makes 2.75rem a 44px
   tap target and 1rem a 16px gutter. Type stays on the same three tokens —
   they are only given phone values. svh, never vh: a full-height box must not
   move while the address bar slides. Nothing here needs :hover, so there is
   none. Tap feedback is the pages' own :active presses, which hold here too
   (the browser's tap box is off, base.css §1).
============================================================================= */

@media (max-width: 900px) {


  /* -- 1  Scale + tokens ----------------------------------------------------- */

  html { font-size: 16px; }

  :root {
    --fs-display: clamp(2rem, 1.4rem + 2.5vw, 2.75rem);
    --fs-body:    1rem;
    --fs-small:   0.8125rem;

    --page-pad:  1.25rem;
    --minibar-h: 3.25rem;
    --tap:       2.75rem;
  }


  /* -- 2  Landing ------------------------------------------------------------ */
  /*
     The 40 / 60 room becomes a column read top to bottom: who, what, how to
     reach him, then the work. min-height, not height, and the body scrolls.
  */

  .home {
    display: block;
    height: auto;
    min-height: 100svh;
    overflow: visible;
  }

  /* No board band to span here (board.js clears --board-top / --board-h
     in grid mode); the intro is a plain block above the board. */
  .intro {
    display: block;
    margin-top: 0;
    height: auto;
    padding: 2rem var(--page-pad) 2.5rem;
  }

  .intro-head { gap: 1.75rem; }

  .mark { width: 3.75rem; height: 4rem; }
  .mark:not(.mark--svg) { width: 3.5rem; height: 3.5rem; }

  /* 2.25rem, derived from the body token (1rem here) — not a fourth size. */
  .welcome { font-size: calc(var(--fs-body) * 2.25); }

  .intro-body {
    gap: 1rem;
    padding-top: 1.75rem;
  }

  .intro-copy { max-width: 34em; }

  /* The same row of text links as the desktop, allowed to wrap. Each link
     is a full tap tall; the row's own padding shrinks so the hairline above
     it does not drift away from the words. */
  .socials {
    --soc-gap: 0.25rem;
    column-gap: 1.75rem;
    margin-top: 0.75rem;
  }
  .social {
    min-height: var(--tap);
    align-items: center;
  }

  /* The board is the page's second chapter, so it is set apart from the
     socials by a whole gap, not a line's worth: 2.5rem (the intro's foot)
     from the bottom of the socials' tap boxes to the board's frame. */
  .board-wrap {
    display: block;
    padding: 0 var(--page-pad) 3rem;
  }


  /* -- 3  Board, grid mode --------------------------------------------------- */
  /*
     board.js writes --cell as a whole px quarter of the content box, so the
     columns are exactly one cell wide and the graph paper (the same layer the
     desktop board draws) lands on every tile edge. Empty cells left by the
     packing stay as paper — the same sheet the desktop board is cut from.
  */

  .board.is-grid {
    width: calc(var(--cell) * 4);
    margin-inline: auto;
    grid-template-columns: repeat(4, var(--cell));
  }

  /* Packed tiles touch, so each one reaches a hairline past its right and
     bottom edge and its frame lands on its neighbour's: one line between
     two tiles, not two. The paper reaches the same hairline further, so its
     closing edges meet the tiles' frames rather than sitting a hair inside. */
  .board.is-grid .board-paper {
    display: block;
    inset: 0 calc(var(--hair) * -1) calc(var(--hair) * -1) 0;
  }

  /* The sheet's edge, as on the desktop: one --line-strong hairline round
     the whole board (board.css §1 draws it on the paper's ::after), lifted
     over the paper's grid. Every tile wears the same ink frame as on the
     desktop (board.css §2), so where a tile reaches the edge its own line
     is that edge, as the loudest line on the board always is: tiles go
     above the sheet's edge. A grid item honours z-index while static, so
     the edge is a layer at 2 and every tile one at 3.

     The paper must not be a stacking context for the ::after to escape it.
     base.css means it never to rise ([data-seq="paper"] { translate: none })
     but the arrived state's `translate: 0 0` outranks that, and any
     translate but none makes a stacking context; this restores the intent.
     While the paper is still fading in, its opacity makes one anyway, so
     the edge settles above the tiles as the fade ends — by then the tiles
     at the edge are near-opaque, so nothing reads as a pop. */
  .board.is-grid .board-paper[data-seq] { translate: none; }
  .board.is-grid .board-paper::after { z-index: 2; }
  .board.is-grid .tile { z-index: 3; }
  /* The focus ring stands a quarter rem off the tile, over its neighbours;
     the focused tile goes above all of them so no print or later tile
     covers a side of it. */
  .board.is-grid .tile:focus-visible { z-index: 4; }

  /* The paper keeps its lines and its grain but not its index labels or
     confetti: in a packed four-column grid there are no empty cells for a
     label to name, and a chip has no quadrant to sit in. Type fragments go
     too — cut glyphs need the desktop's composition around them to read
     as anything. Colour and pattern stay, one cell each (board.js), and
     fill the holes the packing leaves — only those: a piece the packing
     could only put after the last content tile fills nothing and goes too
     (.is-past, board.js trimGrid), so the grid ends on the work. When the
     colour and pattern run out before the holes do, a type piece comes
     back as a plain swatch (.is-filler), its glyph gone, ordered last so
     it takes a hole and moves nothing. A post slot is laid out only once
     it holds a post: the desktop holds a slot's place so nothing moves
     when the feed answers, but here an invisible 2 × 1 is two blank cells
     in a column that scrolls. */
  .board.is-grid .board-index,
  .board.is-grid .board-bit,
  .board.is-grid .tile--type,
  .board.is-grid .tile--deco.is-past,
  .board.is-grid .tile--post.is-pending,
  .board.is-grid .tile--post.is-empty { display: none; }
  .board.is-grid .tile--type.is-filler { display: block; order: 1; }
  .board.is-grid .tile--type.is-filler .deco-type > span { display: none; }

  /* The tile is the link; its box is at least a cell, so always a fair tap. */
  .board.is-grid .tile {
    min-width: 0;
    min-height: 0;
    margin: 0 calc(var(--hair) * -1) calc(var(--hair) * -1) 0;
  }

  /* A project tile is its picture with its name underneath, inside the same
     box: the image cover-fits whatever the strip leaves. The strip is a
     tap tall on every tile, so the pictures above them line up; it holds
     two lines, so a long name on a 1-wide tile ("Rindu Alam Papua") wraps
     — and a name that needs a third line makes its strip taller rather
     than being cut (min-height, never a clamp). */
  .board.is-grid .tile--project .tile-cap { height: auto; min-height: 2.75rem; display: flex; }

  /* The strip is the card's mat, not the sheet: the frame's whole inside
     (the 3px mat and the name under the picture) is the lighter paper, so
     the name reads as printed on the card rather than on the board. */
  .board.is-grid .tile--project > .tile-in { background: var(--paper-2); }
  .board.is-grid .tile--project .tile-cap-in {
    display: flex;
    flex-direction: column;
    justify-content: center;   /* one line or two, centred in the strip */
    flex: 1 1 auto;
    height: auto;
    /* 0.5rem from the frame's inside edge: the mat is already part of it,
       and a 1-wide tile needs every px for "Rindu Alam / Papua". */
    padding: 0.375rem calc(0.5rem - var(--mat, 0px));
    font-size: var(--fs-small);
    line-height: 1.2;
  }
  .board.is-grid .tile--project .tile-title { font-weight: 500; }

  /* Logos carry their own name. The image box is 60% of the cell it sits
     in, centred; the files carry their own white margin. */
  .board.is-grid .tile--logo .tile-media { align-items: center; }
  .board.is-grid .tile--logo .tile-media > img {
    width: calc(var(--cell) * 0.6 * var(--logo-scale, 1));
    height: calc(var(--cell) * 0.6 * var(--logo-scale, 1));
    padding: 0;
  }

  /* The blog tile is its own caption: the label, then the title. */
  .board.is-grid .tile-blog {
    padding: 0.5rem 0.625rem;
    font-size: var(--fs-small);
    line-height: 1.3;
  }
  .board.is-grid .tile-blog-title {
    max-width: none;
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 3;
    overflow: hidden;
  }

  /* All posts and the post tiles, at the phone's inset. A post's title is
     the feed's, of any length, so it keeps its three-line clamp — it is
     the tile's face, not a caption. */
  .board.is-grid .tile-all { padding: 0.5rem 0.625rem; }
  .board.is-grid .tile-post {
    padding: 0.5rem 0.625rem;
    line-height: 1.3;
  }


  /* -- 4  Project shell ------------------------------------------------------ */
  /*
     One column, in reading order: the way back, then what this is (number,
     name, facts, blurb), then the picture, then the argument, then more
     work. The aside is not sticky here — the minibar takes over its one job
     once the title has scrolled away.
  */

  .project {
    display: block;
    min-height: 100svh;
  }

  /* One column: there is no boundary to rule. */
  .project::before { display: none; }

  /* A column of its own children, so Back can be lifted to the top with
     `order` and the DOM stays as core renders it. The foot dissolves
     (display: contents — a plain div, nothing semantic is lost) so its
     three children are the aside's own and can be ordered with the rest.
     Back is the aside's only focusable thing and the first on the page, so
     tab order and visual order still agree. */
  .aside {
    position: static;
    display: flex;
    flex-direction: column;
    height: auto;
    padding: 1rem var(--page-pad) 2.5rem;
  }

  .aside-foot { display: contents; }

  /* At the foot, under the blurb, Back read as the page's footer. On a
     phone it is the first row: 1rem below the top edge, a full tap tall,
     the disc's left edge on the gutter. Its geometry is the minibar's
     (disc, gap, inset), so the Back that scrolls away and the Back that
     slides in are visibly the same control. */
  .aside-back {
    order: -1;
    align-self: flex-start;
    gap: 0.5rem;
    min-height: var(--tap);
    margin: 0 0 0 -0.5rem;
    padding: 0 0.75rem 0 0.5rem;
  }
  .aside-back .aside-back-dot { width: 1.75rem; height: 1.75rem; }
  .aside-back .aside-back-dot svg { width: 0.8125rem; height: 0.8125rem; }

  .aside-no {
    margin-top: 1.25rem;
    font-size: calc(var(--fs-display) * 2.4);
  }

  .aside-title {
    margin-top: 1.25rem;
    max-width: 14em;
  }

  /* The foot's 2rem, now that the foot is no box to pad. */
  .aside-meta { margin-top: 2rem; }

  .aside-row > dt,
  .aside-row > dd { padding: 0.625rem 0 1rem; }

  .aside-blurb { margin-top: 1rem; }

  /* The progress line belongs to the minibar here, where it can be seen. */
  .aside-progress { display: none; }

  .column {
    min-height: 0;
    padding: 0 var(--page-pad) 3rem;
  }

  /* The hero is a beat after the blurb (the aside's 2.5rem foot plus this),
     and square unless the project authored a ratio: the exports are phone
     screens, and a 4/3 letterbox at this width cut them to a strip. The
     flyer measures the hero after mount, so it lands on the square. */
  .hero {
    --hero-default: 1 / 1;
    margin-top: 0.5rem;
  }

  /* Anything a link or the keyboard scrolls to clears the minibar. */
  .hero,
  .blk,
  .more { scroll-margin-top: calc(var(--minibar-h) + 1rem); }


  /* -- 5  Minibar ------------------------------------------------------------ */
  /*
     project.js mounts it (narrow only) inside the project root, so it inherits
     --progress and leaves with the page. Off: parked above the viewport and
     hidden, so nothing in it can be tabbed to. On: it slides down on the
     hover curve. Fixed, so showing it never moves the page under it.
  */

  .minibar {
    position: fixed;
    top: 0;
    inset-inline: 0;
    z-index: 20;
    display: flex;
    align-items: center;
    gap: 0.75rem;
    height: calc(var(--minibar-h) + env(safe-area-inset-top, 0rem));
    padding: env(safe-area-inset-top, 0rem) var(--page-pad) 0 calc(var(--page-pad) - 0.5rem);
    background: var(--paper);
    translate: 0 -100%;
    visibility: hidden;
    transition:
      translate  var(--t-hover) var(--ease-enter),
      visibility 0s linear var(--t-hover);
  }

  .minibar.is-on {
    translate: 0 0;
    visibility: visible;
    transition:
      translate  var(--t-hover) var(--ease-enter),
      visibility 0s linear 0s;
  }

  /* It fades with its leaving page instead of vanishing at unmount. */
  .project.is-leaving .minibar {
    opacity: 0;
    transition: opacity var(--t-exit) ease-in;
  }

  /* The bottom hairline doubles as the reading progress. */
  .minibar-rule {
    position: absolute;
    inset: auto 0 0;
    height: var(--hair);
    background: var(--line);
    overflow: hidden;
  }
  .minibar-rule > span {
    display: block;
    height: 100%;
    background: var(--ink);
    transform-origin: 0 50%;
    scale: var(--progress, 0) 1;
  }

  .minibar-back {
    flex: none;
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    min-width: var(--tap);
    min-height: var(--tap);
    padding: 0 0.5rem;
    font-size: var(--fs-small);
    font-weight: 500;
  }

  .minibar-back .aside-back-dot { width: 1.75rem; height: 1.75rem; }
  .minibar-back .aside-back-dot svg { width: 0.8125rem; height: 0.8125rem; }

  .minibar-title {
    flex: 1 1 auto;
    min-width: 0;
    padding-left: 0.75rem;
    border-left: var(--hair) solid var(--line);
    font-size: var(--fs-small);
    font-weight: 500;
    line-height: 1.3;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }


  /* -- 6  Blocks restacked --------------------------------------------------- */

  .hero + .blk,
  .blk + .blk { margin-top: 2.5rem; }

  /* Above a highlight's hairline only, as on the desktop (project.css §1). */
  .blk + .blk--highlight { margin-top: 3.5rem; }

  /* The question's beat (project.css §1), in the phone's proportions: a
     step more than an ordinary gap above it, a step less after. */
  .hero + .blk--question { margin-top: 3rem; }
  .blk--question + .blk { margin-top: 2rem; }

  .q { max-width: none; }

  /* A highlight's head, a step tighter: title over prose, as on the desktop. */
  .rowed { row-gap: 0.5rem; }

  .hl-media { margin-top: 2.5rem; }

  /* Challenges: one column, each under its own rule. */
  .ch-list {
    grid-template-columns: minmax(0, 1fr);
    row-gap: 1.75rem;
  }
  .ch-title { margin-top: 0.75rem; }

  /* Metrics: two up, one line per value, fitted by the same --mscale as the
     desktop (project.js §6). When two up would need less than 0.75 the list
     gets .metric-list--one and the values stand one per row at full size. */
  .metric-list {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    column-gap: 1rem;
    row-gap: 1.75rem;
  }
  .metric-list--one { grid-template-columns: minmax(0, 1fr); }
  .metric-value {
    padding-top: 1rem;
    font-size: calc(var(--fs-display) * var(--mscale, 1));
  }

  /* Trend: six columns of "27,647" do not fit 360px, so the bars lie down —
     one row per period: name, then the bar from a shared left edge, then its
     value just past the bar's end. Same --v, read as a width now; the room
     kept back is the period's column and the widest value. */
  .trend { margin-top: 2.25rem; }
  .trend-bars {
    grid-auto-flow: row;
    row-gap: 0.5rem;
  }
  .trend-bar {
    flex-direction: row;
    align-items: center;
  }
  .trend-period {
    order: 0;
    flex: 0 0 3rem;
    padding-top: 0;
    border-top: 0;
  }
  .trend-fill {
    order: 1;
    flex: none;
    height: 1.25rem;
    width: calc((100% - 7.5rem) * var(--v, 0));
    min-width: var(--hair);
    border-radius: 0 0.125rem 0.125rem 0;
  }
  .trend-value {
    order: 2;
    padding: 0 0 0 0.5rem;
  }

  .quote-text { font-size: calc(var(--fs-display) * 0.8); }
  /* The hung mark has no gutter to hang into on a phone. */
  .quote-text::before { position: static; padding-right: 0; }

  /* Stages keep their shapes (4/3, 16/10) and tighten their padding and
     gaps, so three portrait screens still stand side by side in a 16/10
     row at 360. Phone frames tighten with them; radii stay concentric. */
  .stage { padding: 1.25rem; gap: 1.25rem; }
  .stage--row { padding: 1rem; gap: 0.75rem; }
  .phone { padding: 0.3125rem; border-radius: 1.5rem; }
  .phone > .media { border-radius: 1.1875rem; }
  .stage--row .phone { padding: 0.25rem; border-radius: 1rem; }
  .stage--row .phone > .media { border-radius: 0.75rem; }

  /* Landscape grids: three up only while a cell is at least 6rem,
     otherwise one up. Never two-plus-one. */
  .set--grid { container-type: inline-size; }
  .set--grid .set-grid { grid-template-columns: repeat(min(var(--cols, 3), 3), minmax(0, 1fr)); }

  @container (max-width: 18.25rem) {
    .set--grid .set-grid { grid-template-columns: minmax(0, 1fr); row-gap: 1rem; }
  }

  /* The prototype links out on a phone (project.js); a tall landscape
     poster still never outgrows the screen. A portrait one is on a 4/3
     stage already, and a max-height there would break its ratio. */
  .proto:not(.proto--stage) .proto-stage { max-height: 70svh; }
  .proto:not(.proto--stage) .proto-frame { max-height: 70svh; }
  .proto-cta { min-height: var(--tap); }

  /* Portrait: the link sits under the stage, not on the screen it opens.
     On paper it needs no chip of its own. */
  .proto--stage .proto-cta--below {
    flex-direction: row;
    gap: 0.75rem;
    margin-top: 1rem;
  }
  .proto-cta--below .proto-word { padding: 0; background: none; }
  .proto-disc { width: 3.25rem; height: 3.25rem; }
  .proto-close { min-height: var(--tap); }


  /* -- 7  More work ---------------------------------------------------------- */
  /*
     Two rows per entry: the thumb beside number, title and roles, then the
     blurb across the full width. The whole row is still the link.
  */

  .more { margin-top: 4rem; }

  .more-row {
    grid-template-columns: 4.5rem minmax(0, 1fr);
    grid-template-areas:
      'thumb when'
      'thumb main'
      'blurb blurb';
    grid-template-rows: auto 1fr auto;
    column-gap: 1rem;
    row-gap: 0.375rem;
    padding-block: 1.25rem;
    --more-pad: 0;   /* the row pads here, not its cells (the thumb frame reads it) */
  }

  .more-thumb { grid-area: thumb; width: 4.5rem; height: 4.5rem; }

  .more-when {
    grid-area: when;
    flex-direction: row;
    gap: 0.5rem;
    font-size: var(--fs-small);
    line-height: 1.4;
  }

  .more-main {
    grid-area: main;
    gap: 0.625rem;
    padding-left: 0;
    border-left: 0;
  }

  .more-tags { margin-top: 0; }

  .more-blurb {
    grid-area: blurb;
    margin-top: 0.625rem;
  }


  /* -- 8  404 ---------------------------------------------------------------- */

  .notfound {
    display: block;
    height: auto;
    min-height: 100svh;
  }

  .notfound-body {
    min-height: 100svh;
    padding: 2rem var(--page-pad);
  }

  /* One column: no right half for the 404's sheet. */
  .notfound-room { display: none; }

  .notfound-foot { padding-top: 0.25rem; }
  .notfound-link {
    align-items: center;
    min-height: var(--tap);
  }
}
