/* ============================================================
   Portfolio — Emmanuel Nguyen
   Exploration — the journal as a full-screen constellation.

   The only page of the site that does not scroll: the HUD keeps
   its height, the scene takes everything under it, and the list
   is an overlay that scrolls inside itself.

   Without JS, or under reduced motion, none of that happens: the
   list is simply the page, and exploration-feed.css styles it exactly as
   it always did.
   ============================================================ */

.expl-page {
  display: flex;
  flex-direction: column;
  min-height: 100dvh;
}

.expl {
  position: relative;
  flex: 1 1 auto;
  /* Without this a flex child refuses to shrink below its content,
     and the list would push the scene past the viewport. */
  min-height: 0;
}

/* ---------- The scene ----------
   Hidden until the module says it has one: an empty canvas on a
   page with no title is a blank screen. */

.expl__canvas { display: none; }
.js-expl .expl__canvas {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  cursor: crosshair;
}
.js-expl.is-list .expl__canvas { display: none; }

/* ---------- The five neons ----------
   Named once, here, and read as --pal by everything below: the
   theme's label in the list, the number of each of its logs, and
   the cartouche the map raises over its astre. Written out per
   component they drift, and the same constellation ends up with
   two colours in two views.

   Set on an element, --pal reaches its descendants on its own —
   which is how a theme section paints the numbers inside it. */
.expl [data-pal="site"],    .expl [data-expl-pal="site"]    { --pal: var(--acc); }
.expl [data-pal="magenta"], .expl [data-expl-pal="magenta"] { --pal: #ff3df0; }
.expl [data-pal="lime"],    .expl [data-expl-pal="lime"]    { --pal: #a8ff2e; }
.expl [data-pal="ice"],     .expl [data-expl-pal="ice"]     { --pal: #2f8bff; }
.expl [data-pal="ember"],   .expl [data-expl-pal="ember"]   { --pal: #ffc21a; }

/* ---------- Cartouche ---------- */

.expl__tip {
  position: absolute;
  top: 0;
  left: 0;
  z-index: 3;
  display: none;
  gap: 4px;
  /* Its width comes from its text, never from where it happens to be.
     An absolutely positioned box shrinks to the room left between its
     own `left` and the edge of the page, so the same cartouche grew
     narrow and gained a line as the star it names drifted right.
     max-content ignores that room; max-width still caps a long title. */
  width: max-content;
  max-width: 320px;
  padding: 10px 14px;
  border: 1px solid var(--pal, var(--acc));
  background: rgba(4, 9, 10, .94);
  pointer-events: none;
  opacity: 0;
  transform: translate(-50%, calc(-100% - 18px));
}
.js-expl .expl__tip { display: grid; }
.js-expl.is-list .expl__tip { display: none; }

.expl__tip-title { font-size: var(--fs-sm); line-height: 1.35; color: var(--txt); }
/* The cartouche wears the constellation it is raised over, frame and
   number alike — the same neon the left column of the list gives that
   theme. A log belongs to its constellation as much as its giant
   does, so nothing in the frame says otherwise.
   What tells a place from a document is then what is written: a name
   and a count, or a number and a title. */
.expl__tip .id { color: var(--pal, var(--acc)); }
.expl__tip--theme .expl__tip-title { color: var(--txt-3); }

/* ---------- Chrome over the scene ---------- */

/* Centred at the foot of the scene, the notice over the toggle it
   explains. It sat bottom left to keep clear of the consent banner;
   centred it clears it by height instead, the banner being a full
   band across the bottom right. */
.expl__bar {
  position: absolute;
  z-index: 4;
  left: 0;
  right: 0;
  bottom: 0;
  display: none;
  flex-direction: column;
  align-items: center;
  gap: 20px;
  padding: 24px var(--pad) 28px;
  text-align: center;
  /* A band the width of the page, with the scene showing through it
     out of focus. No rule along the top: the blur and the tint make
     the edge on their own, and a hairline there read as a panel
     stuck over the scene rather than the scene going quiet.
     The tint carries the band alone where backdrop-filter is refused
     — any browser told to spare the GPU. */
  background: rgba(5, 6, 7, .62);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
}
.js-expl .expl__bar { display: flex; }
/* Once the page itself scrolls, absolute would pin the band to the
   foot of the document — reachable only at the very end of the list.
   Fixed keeps it against the window, where a control belongs. */
.js-expl.is-list .expl__bar { position: fixed; }

/* Read at a glance from across the scene, so it takes the reading
   size rather than the instrument size of a .meta. */
.expl__hint {
  margin: 0;
  font-size: var(--fs-lg);
  line-height: var(--lh-lg);
  color: var(--txt-3);
}
/* Gone in list view, not merely invisible: the band takes its height
   from what it holds, and a reserved empty line would be a strip of
   blur over nothing. The buttons do not move for it — the band is
   anchored to the bottom, so only its top edge comes down. */
.js-expl.is-list .expl__hint { display: none; }

.expl__views { display: flex; gap: 8px; }
.expl__views .is-on { color: var(--acc); border-color: var(--acc); }
.expl__views .btn-out { --edge: var(--line); }
.expl__views .is-on.btn-out { --edge: var(--acc); }

/* ---------- The list ----------
   On its own it is a normal page. Once the scene exists it becomes
   an overlay: same entries, same exploration-feed.css, scrolling inside its
   own box rather than moving the page. */

.expl__list .log__feed { padding: 0; }
.expl__list .log__entry:hover { background: var(--row-hover); }
/* The fill says it on its own; the title stops changing colour. */
.expl__list .log__entry:hover .log__entry-title { color: var(--txt); }
/* The title carries the link to the log, but the row is what lights up:
   a cyan link inside a lit row would be two invitations for one target. */
.expl__list .log__entry-title a { color: inherit; }

/* And the whole row is the target, not the title alone — the fill
   already promised as much. The link's own box stays on the title,
   so a screen reader still announces one link and reads it by its
   name; only its hit area is stretched over the row. Wrapping the
   row in an <a> instead would have made the date, the summary and
   the tags part of that name. */
.expl__list .log__entry { position: relative; }
.expl__list .log__entry-title a::after {
  content: '';
  position: absolute;
  inset: 0;
}
/* The overlay has to resolve against the row, so the link must stay
   unpositioned. site.js gives a link .fx-rel — position: relative —
   the first time it is focused or scanned, which would quietly shrink
   the hit area back to the title, and only after the reader had
   touched the row once. Stated here, it cannot.
   The scan effect goes with it: it sweeps a label, and this link's
   box is a whole row. */
.expl__list .log__entry-title a { position: static; }
.expl__list .log__entry-title a .fx { display: none; }

/* ---------- The list, by theme ----------
   The list is grouped the way the sky is: one section per
   constellation, in the same order and the same colours — laid on
   the .sec grid of the About page, so the theme holds the left
   column and its logs the right one. The rules come with it. */

/* The label wears its constellation's own neon, which turns the left
   column into the map's legend: the same name in the same colour in
   both views. The dot before it is the astre itself, in miniature. */
.expl__theme-title {
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 0;
  font-family: var(--font-mono);
  font-size: var(--fs-label);
  letter-spacing: var(--ls-2);
  color: var(--pal, var(--txt-3));
}

/* currentColor, so the dot can never say a colour the name does not. */
.expl__dot {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  flex: 0 0 auto;
  background: currentColor;
  box-shadow: 0 0 10px currentColor;
}
/* One padding, named once, for the theme label and for every row:
   written twice they drift, and the label stops sharing the first
   row's top edge. The body keeps none of it — a hovered row then
   fills its whole cell, from the label's rule to the page's edge,
   the way a .prj row does on Projets. */
.expl__list { --row-pad: var(--pad); }
.expl__list .sec__body { padding: 0; }
.expl__list .sec__label { padding: var(--row-pad); }
.expl__list .log__entry { padding: var(--row-pad); }
.expl__list .expl__empty { padding: var(--row-pad); }

/* ---------- The log number ----------
   Logs count from #001 and dossiers from /001: two series that will
   overlap forever, separated by one thin glyph. So the number also
   wears its constellation's colour — a magenta #001 cannot be taken
   for a cyan /001, even out of the corner of the eye. */
.expl__theme .log__entry .id { color: var(--pal); }

/* An empty constellation says so, rather than showing a heading
   over nothing. */
.expl__empty {
  margin: 0;
  padding: 18px var(--pad) 8px;
  font-size: var(--fs-sm);
  color: var(--txt-4);
}

.expl__list-foot {
  margin: 0;
  padding: 28px var(--pad) 48px;
}

/* Between the first paint and the module's first line, the list is
   the page — and it showed, for the blink it takes an ES module to
   arrive. The inline script in the head sets this class before
   anything is drawn, and the map opens on the map.

   It is set only when a map is actually expected, and dropped again
   if the module never starts: a list hidden by a script that failed
   is a page with no content at all. */
.expl-boot .expl__list { display: none; }

/* Over the map it is a panel, laid on the scene and hidden with it.
   Opened, it stops being a panel: it goes back into the flow and the
   page scrolls, with the window's own bar down the whole side and the
   HUD sticking at the top — the way every other page of the site
   behaves. A scroller inside a frame gave a second, shorter bar
   floating between the nav and the band, which belongs to no page. */
.js-expl .expl__list {
  position: absolute;
  inset: 0;
  z-index: 2;
  background: var(--bg);
}

.js-expl.is-list .expl__list {
  position: static;
  /* Room for the band, which is fixed over the foot of the window:
     without it the last row and the cookie link read through blur. */
  padding-bottom: 120px;
}

/* No gap under the header: its bottom rule is the first line of the
   grid, and a margin there would break the run of rules that holds
   the whole list together. */
.js-expl:not(.is-list) .expl__list { display: none; }

@media (max-width: 720px) {
  /* The hint describes a hover, which a phone does not have. */
  .expl__hint { display: none; }
  /* And the consent banner is full width down there, so the band
     goes up under the nav rather than under the banner. Its edge
     moves with it, from its top to its bottom. */
  .expl__bar {
    top: 0;
    bottom: auto;
    padding: 14px var(--pad);
    /* Up there it meets the list head-on rather than the scene, so it
       keeps an edge — on the side the content is. */
    border-bottom: 1px solid var(--line-soft);
  }
  /* Up there, fixed would sit under the sticky nav, which paints over
     it. It stays at the top of the scene and scrolls away with the
     list — on a phone you come back up to switch view anyway. */
  .js-expl.is-list .expl__bar { position: absolute; }
  /* Up there it covers the list's header, so the list starts below it. */
  .js-expl.is-list .expl__list { padding-top: 62px; padding-bottom: 0; }
}

/* ---------- Reduced motion ----------
   No scene is built, so the page is the list and nothing here
   needs to undo an animation that never started. */

@media (prefers-reduced-motion: reduce) {
  .expl-page { min-height: 0; }
  .expl__canvas, .expl__tip, .expl__bar { display: none; }
}
