/* ==========================================================================
   Reset
   ========================================================================== */

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

* {
  margin: 0;
  padding: 0;
}

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
}

body {
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}

img,
picture,
video,
canvas,
svg {
  display: block;
  max-width: 100%;
}

input,
button,
textarea,
select {
  font: inherit;
}

h1,
h2,
h3,
h4,
h5,
h6,
p {
  overflow-wrap: break-word;
}

/* ==========================================================================
   Tokens
   ========================================================================== */

:root {
  color-scheme: dark;

  /* Colour: pure black field, white lettering, no accent. */
  --colour-bg: #000000;
  --colour-fg: #ffffff;

  /* Type scale, fluid between a 320px and a 1440px viewport. */
  --text-s: clamp(0.875rem, 0.85rem + 0.13vw, 0.9375rem);
  --text-m: clamp(1rem, 0.96rem + 0.2vw, 1.125rem);
  --text-l: clamp(1.25rem, 1.14rem + 0.55vw, 1.625rem);
  --text-xl: clamp(2.25rem, 1.6rem + 3.25vw, 4rem);

  /* Spacing scale, same fluid basis. */
  --space-3xs: clamp(0.25rem, 0.24rem + 0.05vw, 0.3125rem);
  --space-2xs: clamp(0.5rem, 0.48rem + 0.11vw, 0.5625rem);
  --space-xs: clamp(0.75rem, 0.72rem + 0.16vw, 0.875rem);
  --space-s: clamp(1rem, 0.96rem + 0.22vw, 1.125rem);
  --space-m: clamp(1.5rem, 1.44rem + 0.33vw, 1.6875rem);
  --space-l: clamp(2rem, 1.91rem + 0.43vw, 2.25rem);
  --space-xl: clamp(3rem, 2.87rem + 0.65vw, 3.375rem);

  /* Component dimensions, derived rather than typed in at the use site. */
  --measure: 46ch;
  --logo-width: clamp(11rem, 38vmin, 26rem);
  --tap-target: 2.75rem;

  /* Two column layout. The stacking threshold is a media query rather than a
     token, because media queries cannot read custom properties. */
  --layout-max: 68rem;
  --art-ratio: 16 / 9;

  --font-body: "Montserrat", system-ui, -apple-system, "Segoe UI", sans-serif;

  --rule-thin: 1px;
  --rule-thick: 2px;

  /* Section joins. --slant-rise is the drop measured across a full viewport
     width, so stating it in vw is what fixes the angle: the rise stays the
     same fraction of the width it crosses, whatever that width is. 10vw is
     a slope of about 5.7 degrees (atan(0.1)). A shape stretched to fit its
     box could not hold an angle this way, it would flatten or steepen with
     the viewport.

     --slant-band is the thickness of the black band where two backgrounds
     meet. It is constant across the width, which is what makes that join a
     parallelogram rather than a wedge, and it is derived from the rise so
     the two stay in proportion. It must not be set below the rise: the band
     is split across two sections, and a thinner one would need negative
     thickness on one side of the join. */
  --slant-rise: 10vw;
  --slant-band: var(--slant-rise);
}

/* ==========================================================================
   Page
   ========================================================================== */

body {
  background-color: var(--colour-bg);
  color: var(--colour-fg);
  font-family: var(--font-body);
  font-size: var(--text-m);
}

::selection {
  background-color: var(--colour-fg);
  color: var(--colour-bg);
}

/* Every full height section shares one three row frame: the middle row holds
   the content and centres it, and the outer rows balance it. The hero uses
   the last row to pin its scroll cue to the bottom edge, so nothing needs
   absolute positioning or a margin that has to be cancelled later. */
.hero,
.game {
  min-block-size: 100vh;
  min-block-size: 100svh;
  padding: var(--space-l) var(--space-s);

  display: grid;
  grid-template-rows: 1fr auto 1fr;
  justify-items: center;
}

.hero__content {
  grid-row: 2;

  display: grid;
  justify-items: center;
  gap: var(--space-m);

  inline-size: 100%;
  max-inline-size: var(--measure);
  text-align: center;
}

/* ==========================================================================
   Hero
   ========================================================================== */

.logo {
  font-size: inherit;
  font-weight: inherit;
  line-height: 0;
}

.logo__image {
  inline-size: var(--logo-width);
  block-size: auto;
}

.lede {
  text-wrap: balance;
}

/* ==========================================================================
   Scroll cue
   ========================================================================== */

.scroll-cue {
  grid-row: 3;
  align-self: end;

  display: grid;
  place-items: center;
  min-inline-size: var(--tap-target);
  min-block-size: var(--tap-target);

  color: inherit;
}

.scroll-cue__icon {
  animation: scroll-cue-pulse 2.4s ease-in-out infinite;
}

/* Starts and ends at rest, so when reduced motion freezes the animation on
   its final frame the arrow is left fully visible and in place. */
@keyframes scroll-cue-pulse {
  0%,
  100% {
    transform: translateY(0);
    opacity: 1;
  }
  50% {
    transform: translateY(0.45em);
    opacity: 0.45;
  }
}

/* ==========================================================================
   Games
   ========================================================================== */

/* Heading, artwork and copy are siblings so the artwork can sit between the
   heading and the copy when the section is stacked. Placement is by named
   area, which keeps the source in its reading order in both arrangements. */
.game__layout {
  grid-row: 2;

  display: grid;
  align-items: center;
  gap: var(--space-m) var(--space-xl);

  grid-template-columns: repeat(2, minmax(0, 1fr));
  grid-template-areas:
    "title art"
    "copy  art";

  inline-size: 100%;
  max-inline-size: var(--layout-max);
}

/* Alternating: AVOID takes the artwork on the left, Magnus on the right. */
.game--avoid .game__layout {
  grid-template-areas:
    "art title"
    "art copy";
}

/* The one breakpoint on the site. Two columns cannot become a heading,
   artwork, copy stack fluidly: the artwork has to move from beside the copy
   to between the heading and it, and no intrinsic sizing expresses that.
   Portrait covers the devices asked for, and the width bound catches a
   landscape window too narrow to carry two columns. */
@media (orientation: portrait), (max-width: 49.5rem) {
  .game__layout,
  .game--avoid .game__layout {
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas:
      "title"
      "art"
      "copy";
  }
}

.game__title {
  grid-area: title;
}

.game__copy {
  grid-area: copy;

  display: grid;
  justify-items: start;
  gap: var(--space-m);

  /* Capped independently of the column, so the artwork takes the slack on a
     wide viewport rather than the line length running past comfort. */
  max-inline-size: var(--measure);
  text-align: start;
}

/* The ratio is pinned here rather than left to the file, so artwork of a
   different shape later crops to the same frame instead of changing the
   layout. The stripes sit behind the image and show only while it loads.
   min-inline-size: 0 stops the artwork's intrinsic 1920px acting as its
   automatic minimum size and pushing the column wider than its track. */
.game__art {
  grid-area: art;

  aspect-ratio: var(--art-ratio);
  object-fit: cover;
  min-inline-size: 0;
  /* The height attribute would otherwise set the used height and the ratio
     above would never bind, same as .logo__image. */
  block-size: auto;

  background-color: var(--colour-bg);
  background-image: repeating-linear-gradient(
    135deg,
    #0a0a0a,
    #0a0a0a var(--space-s),
    #141414 var(--space-s),
    #141414 var(--space-l)
  );
}

.game__title {
  font-size: var(--text-xl);
  font-weight: 800;
  letter-spacing: 0.08em;
  line-height: 1.05;
  text-transform: uppercase;
}

.game__description {
  font-size: var(--text-l);
  font-weight: 600;
  text-wrap: pretty;
}

.game__body {
  text-wrap: pretty;
}

.todo {
  padding: var(--space-2xs) var(--space-xs);
  border: var(--rule-thin) dashed currentColor;
  font-size: var(--text-s);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  opacity: 0.65;
}

/* ==========================================================================
   Landscape decoration
   ========================================================================== */

/* The blurred artwork and the slanted section joins are landscape only. In
   portrait none of this is declared, so the sections simply show the black
   the body already paints. Nothing has to be switched back off. */
@media (orientation: landscape) {

  .game {
    /* isolation makes each section its own stacking context. That is what
       lets the wedges below sit above this background image while still
       staying under the section content: a negative z-index child paints
       after its stacking context's background and before its in-flow
       content. Without the isolation they would drop behind the artwork. */
    position: relative;
    isolation: isolate;

    background-position: center;
    background-size: cover;
    background-attachment: fixed;
  }

  .game--avoid {
    background-image: url("assets/avoid-blurred-background.webp");
  }

  .game--magnus {
    background-image: url("assets/magnus-blurred-background.webp");
  }

  .game--avoid::before,
  .game--avoid::after,
  .game--magnus::before,
  .game--magnus::after {
    content: "";
    position: absolute;
    inset-inline: 0;
    z-index: -1;
    background-color: var(--colour-bg);
  }

  /* Three joins, alternating direction: the hero edge leans one way, the
     join between the games leans the other, and the closing edge leans back.
     Every edge uses the same rise, so the alternation reads as a rhythm
     rather than as three unrelated angles. */

  /* Hero into AVOID: a single edge. The hero above is already black, so only
     one side of this join has anything to show. */
  .game--avoid::before {
    inset-block-start: 0;
    block-size: var(--slant-rise);
    clip-path: polygon(0 0, 100% 0, 0 100%);
  }

  /* AVOID into Magnus: the only join with a background on both sides, so the
     only one that shows a band. The two wedges slope the same way instead of
     opposing each other, and that is what holds the band at a constant
     --slant-band across the full width. Each wedge stays inside its own
     section, so neither adds page height.

     The split between the two is deliberately lopsided, with the whole of
     the band sitting on the Magnus side. The section boundary itself is
     invisible, so only the sum of the two thicknesses is visible, and
     halving it would buy nothing but a more awkward pair of polygons. */
  .game--avoid::after {
    inset-block-end: 0;
    block-size: var(--slant-rise);
    clip-path: polygon(0 0, 100% 100%, 0 100%);
  }

  .game--magnus::before {
    inset-block-start: 0;
    block-size: var(--slant-band);
    clip-path: polygon(
      0 0,
      100% 0,
      100% 100%,
      0 calc(100% - var(--slant-rise))
    );
  }

  /* Magnus into contact: a single edge again, leaning back the other way.
     The contact band below it is already black. */
  .game--magnus::after {
    inset-block-end: 0;
    block-size: var(--slant-rise);
    clip-path: polygon(0 100%, 100% 0, 100% 100%);
  }


}

/* ==========================================================================
   Actions
   ========================================================================== */

.actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-xs) var(--space-s);
}

.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-block-size: var(--tap-target);
  padding: var(--space-2xs) var(--space-m);

  border: var(--rule-thick) solid var(--colour-fg);
  background-color: transparent;
  color: var(--colour-fg);

  font-size: var(--text-m);
  font-weight: 600;
  letter-spacing: 0.02em;
  transition: background-color 140ms ease-out, color 140ms ease-out;
}

.button--primary {
  background-color: var(--colour-fg);
  color: var(--colour-bg);
}

.button:hover {
  background-color: var(--colour-fg);
  color: var(--colour-bg);
}

.button--primary:hover {
  background-color: transparent;
  color: var(--colour-fg);
}

/* ==========================================================================
   Contact
   ========================================================================== */

/* Its own band rather than a full viewport section: one address does not
   carry a whole screen, and the page should end on a quiet note. */
.contact {
  padding: var(--space-xl) var(--space-s);

  display: grid;
  justify-items: center;
}

.contact__content {
  display: grid;
  justify-items: center;
  gap: var(--space-2xs);

  inline-size: 100%;
  max-inline-size: var(--measure);
  text-align: center;
}

.contact__title {
  font-size: var(--text-s);
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  opacity: 0.65;
}

.contact__address {
  font-size: var(--text-l);
  letter-spacing: 0.02em;
}

/* ==========================================================================
   Links
   ========================================================================== */

a {
  color: inherit;
}

/* Only plain text links are underlined. Buttons and the scroll cue carry a
   class, so nothing has to switch the underline back off. */
a:not([class]) {
  text-decoration-line: underline;
  text-decoration-thickness: var(--rule-thin);
  text-underline-offset: 0.25em;
  transition: text-decoration-thickness 120ms ease-out;
}

a:not([class]):hover {
  text-decoration-thickness: var(--rule-thick);
}

a:focus-visible {
  outline: var(--rule-thick) solid var(--colour-fg);
  outline-offset: var(--space-3xs);
}

/* ==========================================================================
   Motion
   ========================================================================== */

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }

  /* A fixed attachment slides the artwork against the page as it scrolls,
     which is parallax. This rule follows the landscape block in source
     order, so it wins without needing !important. */
  .game {
    background-attachment: scroll;
  }
}
