/*
 * The route sets sail from the harbor logo, so the logo and the chart nodes
 * share one coordinate system: the title block's top edge sits at --harbor-y
 * (this section's padding-top) and the route is measured from the same value.
 *
 * The dotted lines themselves are drawn by the <script> at the bottom of
 * index.astro, which measures where these coordinates actually landed
 * (.route-origin/.node-tokyo/.node-worldwide) and sets an SVG <line>'s
 * endpoints directly — rather than computing each segment's length and angle
 * in CSS, which needs hypot()/atan2() and isn't worth the compatibility cost.
 * Coordinates stay fluid and the last leg still bleeds off the edge.
 */
.chart-hero {
  --chart-ink: #15334b;
  --chart-grid: rgba(21, 51, 75, 0.2);
  --chart-orange: #e6443b;
  --chart-white: #f7f4eb;
  /* Split out so the ring and any shadow stacked beside it can be composed
     independently — box-shadow is a single list, so without this the texture
     pass would have to restate the ring once per colour scheme. */
  --icon-ring: rgba(247, 244, 235, 0.38);
  /* A map laid on a table: pivoting from the bottom edge keeps it flush with
     .landing-sections below (no seam), so only the far/top edge recedes. A long
     perspective distance keeps the foreshortening light — everything here is
     still laid out and measured as flat 2D beforehand, so this is purely a
     final render-time tilt; it doesn't touch the route's own geometry. Must
     match .chart-backdrop's transform and transform-origin exactly, or the two
     plates tilt apart instead of together.

     This is also the fallback/rest value: the <script> at the bottom of
     index.astro overwrites it live (as an inline style on .chart-hero, which
     both layers still read via var()) to lean the whole plate slightly with
     the mouse or the device's tilt. rotateY(0deg) is written out even though
     it's the identity, so the string shape matches what JS produces and
     nothing has to be reconstructed on the first frame. */
  --chart-tilt: perspective(2400px) rotateX(15deg) rotateY(0deg);
  /* Same side-margin formula as .page-shell/.site-footer in style.css (24px,
     growing once the 1120px content column stops filling the viewport), so the
     hero's content lines up with the rest of the site instead of drifting
     toward the physical viewport edge on very wide screens. Uses % rather than
     vw: vw includes the scrollbar gutter and % doesn't, so vw here would drift
     a few px off the footer's edge on any page tall enough to scroll. */
  --content-max: 1120px;
  --gutter: max(24px, (100% - var(--content-max)) / 2);
  --main-pad-x: var(--gutter);
  --hero-width: min(54vw, 720px);

  /* Centre of the harbor logo, which straddles the title block's top edge. */
  --harbor-x: calc(var(--gutter) + var(--hero-width) / 2);
  --harbor-y: clamp(88px, 12vh, 190px);

  /* Where the route departs from — the harbor itself on desktop. */
  --origin-x: var(--harbor-x);
  --origin-y: var(--harbor-y);

  --tokyo-x: calc(50vw + 40px);
  /* A fraction of --harbor-y rather than a fixed offset above it, so Tokyo keeps
     clear of the top edge on short viewports instead of being pushed off it. */
  --tokyo-y: calc(var(--harbor-y) * 0.42);
  /* Inset from the column's right edge rather than the viewport's, so the dot
     (and its label, which hangs right of it) can't cross the footer's edge once
     the gutter starts growing past 1168px wide. */
  --world-x: calc(100vw - var(--gutter) - 66px);
  --world-y: calc(var(--tokyo-y) + 160px);

  /* Inset of the coordinate pair from the bottom-right corner. */
  --coord-inset: 34px;

  /* A flat backdrop grid (unlike .chart-sheet's, which tilts) meets the sheet's
     foreshortened one at a hard, mismatched angle right where they'd need to
     line up — see .chart-backdrop below for the actual fix. */
  position: relative;
  overflow: hidden;
}

.chart-hero .chart-backdrop {
  position: absolute;
  z-index: 0;
  /* Bleeds past the hero's own box on every edge, so that once tilted its
     receded edges still fully cover the hero rectangle — same idea as a
     tablecloth sized bigger than the table. Bleeding equally on every side
     keeps this box's own centre exactly on hero's centre, so no calc()
     compensation is needed to keep this in step with .chart-sheet's origin;
     "centre" already means the same physical point for both.

     Must be sized against the LARGEST tilt the script can produce, currently
     BASE_RX 20 + DEVICE_RX_RANGE 12 = 32deg (see index.astro). Verified by
     sampling what is actually painted at the hero's corners with
     elementFromPoint at each extreme: 128px leaks at 3 of them and 192px at 2
     — getBoundingClientRect is no use for checking this, as it reports the
     axis-aligned box of the sheared trapezoid rather than the trapezoid, and
     so reports full coverage in exactly the cases that leak. Raise this again
     if the tilt range grows. Kept a multiple of the 64px grid tile so the
     pattern's phase is unchanged.

     Written as longhands rather than `inset`, which has nothing to fall back
     to: this element carries no width/height, so if the shorthand is not
     understood it collapses to nothing and takes the entire blue-and-grid
     chart with it. The four longhands say exactly the same thing and are
     understood everywhere. */
  top: -256px;
  right: -256px;
  bottom: -256px;
  left: -256px;
  background-color: var(--blue);
  background-image:
    linear-gradient(var(--chart-grid) 1px, transparent 1px),
    linear-gradient(90deg, var(--chart-grid) 1px, transparent 1px);
  background-size: 64px 64px;
  transform: var(--chart-tilt);
}

.chart-hero .chart-sheet {
  position: relative;
  z-index: 2;
  display: grid;
  align-items: start;
  justify-items: center;
  /* Not clipped here (was overflow:hidden): that clipped the route lines'
     overshoot at .chart-sheet's own tilted, foreshortened edge, so the visible
     dotted line fell short of the real edge instead of bleeding off it as
     intended. .chart-hero's overflow:hidden below does the actual crop, in
     flat, untilted screen space, so it lands at the true edge. */
  padding: var(--harbor-y) var(--main-pad-x) clamp(40px, 6vh, 88px);
  color: var(--chart-ink);
  transform: var(--chart-tilt);
  /* Pivoting from the centre (rather than the old bottom-edge anchor) rocks
     the whole plate evenly around its middle as the tilt changes, instead of
     hinging everything off one edge — .chart-hero's own box never moves or
     resizes regardless, so the crisp handoff to .landing-sections below holds
     either way; this only changes how the content inside behaves. */
  transform-origin: center;
}

/* Zero-size marker: not visible itself, just a DOM point at the route's
   departure coordinate for the script below to measure. */
.chart-hero .route-origin {
  position: absolute;
  top: var(--origin-y);
  left: var(--origin-x);
}

.chart-hero .route-lines {
  position: absolute;
  z-index: 0;
  inset: 0;
  width: 100%;
  height: 100%;
  /* The crossing line is meant to run off the edge of the frame; without this
     its SVG viewport would clip it at .chart-sheet's own box first. */
  overflow: visible;
}

.chart-hero .route-lines line {
  fill: none;
  stroke: rgba(21, 51, 75, 0.62);
  stroke-width: 3;
  stroke-linecap: round;
  /* A ~0-length dash with a round cap draws as a dot; 11px spacing matches the
     old dotted border-top's rhythm. Endpoints are set by the script below,
     which measures where CSS actually placed .route-origin/.node-tokyo/
     .node-worldwide rather than computing them in CSS with hypot()/atan2() —
     recent CSS additions this keeps the page clear of entirely. */
  stroke-dasharray: 0 11;
}

.chart-hero .route-node {
  --node-dot: 20px;

  position: absolute;
  z-index: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 15px;
  font-size: 15px;
  font-weight: 600;
  /* Sits the dot's centre, not its top edge, on the coordinate. */
  transform: translate(-50%, calc(var(--node-dot) / -2));
}

.chart-hero .route-node::before {
  width: var(--node-dot);
  height: var(--node-dot);
  border: 3px solid var(--chart-ink);
  border-radius: 50%;
  background: var(--chart-white);
  box-shadow: 0 0 0 10px rgba(247, 244, 235, 0.38);
  content: "";
}

.chart-hero .node-tokyo {
  top: var(--tokyo-y);
  left: var(--tokyo-x);
}

.chart-hero .node-worldwide {
  top: var(--world-y);
  left: var(--world-x);
}

.chart-hero .hero-stack {
  position: relative;
  z-index: 2;
  display: flex;
  /* Shares --hero-width with --harbor-x so the logo cannot drift off the route;
     the left inset comes from --main-pad-x on the section itself, so this edge
     lines up with --gutter exactly as the footer does. */
  width: var(--hero-width);
  flex-direction: column;
  align-items: center;
  justify-self: start;
}

.chart-hero .title-block {
  position: relative;
  width: 100%;
  /* Horizontal padding yields on narrow desktops, where the card is too tight to
     afford the square inset and the wordmark would otherwise break in two. It
     interpolates back to the full 92px by 1440px, so wide layouts are unchanged. */
  padding: clamp(44px, 7vw, 92px) clamp(20px, 9.7vw - 48px, 92px);
  background: rgba(247, 244, 235, 0.8);
  text-align: center;
}

.chart-hero .hero-icon {
  position: absolute;
  top: -38px;
  left: 50%;
  display: grid;
  width: 76px;
  height: 76px;
  place-items: center;
  border-radius: 50%;
  background: var(--chart-orange);
  box-shadow: 0 0 0 10px var(--icon-ring);
  color: var(--chart-white);
  transform: translateX(-50%);
}

.chart-hero .hero-icon svg {
  width: 40px;
  height: 40px;
}

.chart-hero h1 {
  /* style.css caps body copy headings at 12ch; the wordmark sets its own width. */
  max-width: none;
  margin: 0;
  color: var(--chart-ink);
  background: none;
  font-family: Inter, Arial, sans-serif;
  /* Capped so "Game Harbor" stays on one line once --hero-width tops out at
     720px. The block then has 536px of content width and the wordmark needs
     ~5.66x the font size, so anything above ~94px broke mid-name. */
  font-size: clamp(58px, 7vw, 92px);
  font-weight: 600;
  letter-spacing: -0.07em;
  line-height: 0.9;
  -webkit-text-fill-color: currentColor;
}

.chart-hero .tagline {
  margin: 28px 0 0;
  color: var(--chart-ink);
  font-size: clamp(20px, 2.2vw, 32px);
  font-weight: 500;
  letter-spacing: 0;
}

.chart-hero .primary-nav {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  width: min(50vw, 650px);
  gap: 10px;
  margin-top: clamp(22px, 3vw, 38px);
}

.chart-hero .primary-nav a {
  display: grid;
  min-height: 56px;
  place-items: center;
  padding: 14px 20px;
  background: var(--chart-ink);
  color: var(--chart-white);
  font-size: 14px;
  font-weight: 600;
  text-align: center;
  text-decoration: none;
}

.chart-hero .primary-nav a > span {
  display: inline-block;
}

.chart-hero .primary-nav a:hover,
.chart-hero .primary-nav a:focus-visible {
  background: var(--chart-orange);
  color: var(--chart-white);
  outline: none;
}

.chart-hero .primary-nav a:active > span {
  transform: translateY(2px);
}

/* Lives directly in .chart-hero, not .chart-sheet — it's hard to make
   consistently visible at a variety of angles. */
.chart-hero .coordinate {
  position: absolute;
  z-index: 3;
  font-family: "DM Mono", monospace;
  font-size: 10px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

/*
 * As on a real chart, latitude is the vertical scale and so runs down a side
 * margin, while longitude is the horizontal scale and runs along the bottom.
 * Meeting them in one corner reads like a chart's neatline, and keeps the rest
 * of the water clear — the top-left especially, which is too prominent a spot
 * to spend on an annotation.
 */
.chart-hero .latitude {
  right: var(--coord-inset);
  bottom: 52px;
  writing-mode: vertical-rl;
}

.chart-hero .longitude {
  right: var(--coord-inset);
  bottom: 20px;
}

.landing-sections {
  /* Same width cap and side margins as .page-shell/.site-footer in style.css. */
  display: flex;
  width: min(100% - 48px, 1120px);
  margin: 0 auto;
  flex-direction: column;
}

.landing-strip {
  padding: clamp(20px, 2.4vw, 30px) 0;
}

.portfolio-strip {
  background: var(--paper);
}

.services-strip {
  background: var(--paper);
}

.landing-strip-inner {
  /* Margins now come from .landing-sections; this just lays out its children. */
  display: grid;
  grid-template-columns: 1fr;
  gap: 14px;
}

.landing-strip h2 {
  font-size: 1.05rem;
  letter-spacing: -0.025em;
}

.strip-row {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 150px)) auto;
  align-items: center;
  justify-content: start;
  gap: clamp(10px, 1.6vw, 24px);
}

.portfolio-row img {
  display: block;
  width: 100%;
  height: auto;
  max-height: 70px;
  object-fit: contain;
}

.services-row {
  grid-template-columns: max-content auto;
  justify-content: start;
}

.services-list {
  display: flex;
  padding-left: 1.25rem;
  flex-wrap: wrap;
  gap: 10px 25px;
}

.strip-button {
  display: inline-flex;
  min-height: 44px;
  align-items: center;
  justify-content: center;
  padding: 10px 18px;
  background: var(--ink);
  color: var(--paper-light);
  font-size: 0.82rem;
  font-weight: 600;
  line-height: 1.1;
  text-align: center;
  text-decoration: none;
  white-space: nowrap;
}

.strip-button > span {
  display: inline-block;
}

.strip-button:hover,
.strip-button:focus-visible {
  background: var(--orange);
  color: var(--paper-light);
  outline: none;
}

.strip-button:active > span {
  transform: translateY(2px);
}

@media (prefers-color-scheme: dark) {
  .chart-hero {
    --chart-grid: rgba(247, 244, 235, 0.16);
    --icon-ring: rgba(247, 244, 235, 0.18);
  }

  .chart-hero .chart-sheet {
    color: var(--chart-white);
  }

  .chart-hero .route-lines line {
    stroke: rgba(247, 244, 235, 0.58);
  }

  .chart-hero .title-block {
    background: rgba(51, 47, 42, 0.8);
  }

  .chart-hero h1,
  .chart-hero .tagline {
    color: var(--chart-white);
  }

  .strip-button {
    background: var(--blue);
    color: var(--ink);
  }

  .strip-button:hover,
  .strip-button:focus-visible {
    color: #fffaf0;
  }
}

@media (max-width: 700px) {
  /*
   * The stacked mobile layout puts the logo below and to the right of Tokyo, so
   * departing from the harbor would double the route back on itself. Here it
   * keeps sailing in from off-screen left instead, and the route sits at fixed
   * coordinates rather than being derived from the harbor.
   */
  .chart-hero {
    /* Tokyo pulls left of centre so the crossing leg keeps a readable slope
       even at 320px, where the two nodes would otherwise nearly stack. */
    --tokyo-x: calc(45vw - 20px);
    --tokyo-y: 6px;
    --world-x: calc(100vw - 55px);
    --world-y: 65px;
    --origin-x: calc(var(--tokyo-x) - 200vw);
    --origin-y: calc(var(--tokyo-y) + 24vw);
    /* Sits the card clear of the Worldwide label, which hangs below its dot and
       nearly touches the card at the desktop label size. 20px shorter than the
       172px this wants on its own: the perspective tilt already recedes this
       edge into mostly-empty water, so there's little real content to lose. */
    --harbor-y: 120px;
    --main-pad-x: 22px;
    --coord-inset: 20px;

    /* Right margin held clear so the latitude can run down it exactly as it does
       on desktop, where the left-anchored hero leaves that room by itself. */
    --coord-gutter: 32px;
  }

  /* Same reasoning as --harbor-y above, applied to the bottom: 5px trimmed off
     the base clamp(40px, 6vh, 88px), shifting its whole range down rather than
     just one endpoint so the reduction holds regardless of viewport height. */
  .chart-hero .chart-sheet {
    padding-bottom: 36px;
  }

  .chart-hero .hero-stack {
    width: auto;
    /* Without a cap the card runs the full width as the viewport approaches the
       breakpoint. 380px is about the narrowest that still holds the wordmark on
       one line, and lands on the desktop card width at 701px. */
    max-width: 380px;
    justify-self: stretch;
    margin-left: 0;
    margin-right: var(--coord-gutter);
  }

  .chart-hero .title-block {
    padding: 53px 18px 27px;
  }

  /* Below ~456px the card is viewport-limited rather than capped at 380px — it
     gets 100vw less the 44px of hero padding and the 32px coordinate gutter — so
     the wordmark has to shrink with it to stay on one line. Holds to 320px;
     narrower than that it wraps, which is fine. */
  .chart-hero h1 {
    font-size: clamp(34px, 16.6vw - 19px, 58px);
  }

  .chart-hero .hero-icon {
    left: 50%;
  }

  .chart-hero .primary-nav {
    grid-template-columns: 1fr;
    /* Stacked buttons stop reading as buttons once they stretch into bars, so
       they cap short of the card and sit visibly inset from it. */
    width: min(100%, 270px);
    gap: 7px;
    margin-top: 10px;
  }

  .chart-hero .primary-nav a {
    min-height: 46px;
    padding: 10px 16px;
  }

  /* Matches --main-pad-x's 22px on .chart-hero at this breakpoint, so the strips
     stay flush with the hero card instead of picking up the desktop 24px. */
  .landing-sections {
    width: calc(100% - 44px);
  }

  .landing-strip-inner {
    gap: 16px;
  }

  .strip-row {
    display: flex;
    flex-wrap: wrap;
    justify-content: flex-start;
  }

  .portfolio-row img {
    width: min(150px, 100%);
    flex: 0 1 150px;
  }

}

@media (max-width: 480px) {
  .strip-button {
    width: fit-content;
  }
}

/* ==========================================================================
 * TEXTURE PASS (chart) — self-contained. Delete from here to end of file to
 * revert. Its site-wide half — the paper grain, --object-shadow, and the
 * shared button shadow — lives at the end of style.css instead, because only
 * index.astro imports this file and those parts have to reach every page.
 *
 * Every rule below only *overrides* something declared earlier, and adds no
 * new elements, so removing the block restores the flat original exactly. It
 * sits after the dark-mode and breakpoint blocks so it wins on source order
 * without needing inflated selectors.
 *
 * One piece lives outside: the tilt-reactive lighting is fed by a delimited
 * block in index.astro's <script>. Leaving that behind after deleting this is
 * harmless — it writes custom properties that nothing would then read, and the
 * fallbacks below are what render anyway.
 * ========================================================================== */

.chart-hero {
  --chart-grid-major: rgba(21, 51, 75, 0.34);
  --chart-sheen: 0.3;
}

@media (prefers-color-scheme: dark) {
  .chart-hero {
    --chart-grid-major: rgba(247, 244, 235, 0.28);
    /* A white sheen reads much hotter against dark water. */
    --chart-sheen: 0.12;
  }
}

/* 1. Tilt-reactive shading, confined to the water so the ink stays crisp — an
   overlay across the whole hero washes the nav buttons from deep ink to slate.
   --shade-top and --sheen-x are rewritten per frame by index.astro; the
   fallbacks are what renders with JS off, which is a sensible fixed lighting
   rather than nothing at all. */
.chart-hero .chart-backdrop::after {
  content: "";
  position: absolute;
  inset: 0;
  background:
    linear-gradient(to bottom, rgba(12, 34, 52, var(--shade-top, 0.16)) 0%, rgba(12, 34, 52, 0) 48%),
    radial-gradient(58% 46% at var(--sheen-x, 50%) 88%, rgba(255, 255, 255, var(--chart-sheen)) 0%, rgba(255, 255, 255, 0) 68%);
  pointer-events: none;
}

/* 2. Grid hierarchy — a heavier line every 5th cell, as on a real chart.
   background-image is a list, so the two 64px layers have to be restated to
   sit under the new 320px ones; the base rule keeps its own copy untouched. */
.chart-hero .chart-backdrop {
  background-image:
    linear-gradient(var(--chart-grid-major) 1px, transparent 1px),
    linear-gradient(90deg, var(--chart-grid-major) 1px, transparent 1px),
    linear-gradient(var(--chart-grid) 1px, transparent 1px),
    linear-gradient(90deg, var(--chart-grid) 1px, transparent 1px);
  background-size: 320px 320px, 320px 320px, 64px 64px, 64px 64px;
}

/* 3. Objects lifted off the sheet, so they sit *on* the map rather than in it.
   Offsets come from --shadow-dx/--shadow-dy, rewritten per frame by
   index.astro so the shadows swing away from wherever the highlight currently
   is; static offsets read as stickers once the plate starts moving. Each
   element scales the same pair rather than carrying its own numbers, so the
   relative heights (card highest, buttons lowest) stay consistent as they
   move. The fallbacks are the resting values, i.e. what renders with JS off. */
.chart-hero .title-block {
  box-shadow: var(--shadow-dx, 0px) var(--shadow-dy, 15px) 34px -14px var(--object-shadow);
}

.chart-hero .primary-nav a {
  box-shadow:
    calc(var(--shadow-dx, 0px) * 0.45) calc(var(--shadow-dy, 15px) * 0.4) 14px -6px var(--object-shadow);
}

/* Ring and lift composed in one box-shadow via --icon-ring. This was
   filter: drop-shadow() to avoid restating the ring per scheme, but a filter
   rasterises and blurs the element's whole alpha silhouette every frame, which
   is a poor trade for something that now animates — box-shadow blurs a known
   rounded rect instead. */
.chart-hero .hero-icon {
  box-shadow:
    0 0 0 10px var(--icon-ring),
    calc(var(--shadow-dx, 0px) * 0.7) calc(var(--shadow-dy, 15px) * 0.55) 20px -6px var(--object-shadow);
}

/* 4. The page below overlaps the map rather than butting up against it, so the
   shadow belongs on the map's own bottom edge, cast upward by the sheet that
   covers it — not beneath the hero cast downward, which read as the map
   floating above the page. Sits above the water (z-index 0) but below the
   sheet (2) and coordinates (3), so it shades open water without dimming
   content near the fold. */
.chart-hero::after {
  content: "";
  position: absolute;
  z-index: 1;
  right: 0;
  bottom: 0;
  left: 0;
  height: 16px;
  background: linear-gradient(to top, var(--edge-shadow), transparent);
  pointer-events: none;
}
