/* ============================================================
   RoboKyle: Fly Game

   Bright rather than dark, unlike the other two, because the game
   behind it is a blue sky and a green sea and the chrome should
   not fight that.

   The house rules still apply: global border-box, nothing
   selectable, a frame that becomes the screen in fullscreen with
   the world filling it, menus in a centred column, HUD anchored
   to a corner, and .fs-btn styled here because the shared
   fullscreen script injects an unstyled button otherwise.
   ============================================================ */

/* Everything below is in rem, so the whole interface scales from here.

   This game is played by people who may not see small text well, and the
   default sixteen pixels is a compromise struck for dense documents
   rather than for a menu with eight things on it. There is room: nothing
   here is tight for space. */
:root { font-size: 112.5%; }

:root {
    --sky:      #7FC7EE;
    --sky-deep: #2E86C8;
    --sea:      #1F6FAE;
    --sand:     #F3E5B8;
    --ink:      #0E2436;
    --ink-soft: #3D5D75;
    --ink-mute: #6E8AA0;
    --panel:    #FFFFFF;
    --panel-2:  #EAF5FC;
    --line:     #BBD8EA;
    --line-soft:#D8EAF5;
    --red:      #E2402F;
    --gold:     #E8B444;
}

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

body.fly-body {
    margin: 0;
    min-height: 100vh;
    background: linear-gradient(180deg, #9BD6F2 0%, #CFEAF7 55%, #EAF5FC 100%);
    color: var(--ink);
    font-family: 'Inter', system-ui, -apple-system, "Segoe UI", sans-serif;
    -webkit-font-smoothing: antialiased;
    display: flex;
    flex-direction: column;
    overflow-x: hidden;
    -webkit-user-select: none;
    user-select: none;
    -webkit-touch-callout: none;
    -webkit-tap-highlight-color: transparent;
}

.game-frame img,
.game-frame canvas { -webkit-user-drag: none; user-select: none; }

.skip-link {
    position: absolute; top: -100px; left: 12px; z-index: 100;
    background: var(--ink); color: #fff;
    padding: .6rem .9rem; border-radius: 8px;
    text-decoration: none; font-weight: 600;
}
.skip-link:focus { top: 12px; }

.fly-body :focus-visible { outline: 3px solid var(--sky-deep); outline-offset: 3px; border-radius: 6px; }

.game-shell {
    flex: 1;
    display: flex;
    flex-direction: column;
    padding: clamp(.5rem, 1.6vw, 1.2rem);
}

.game-frame {
    position: relative;
    flex: 1 1 auto;
    min-height: min(560px, 78vh);
    display: flex;
    flex-direction: column;
    background: linear-gradient(180deg, #BEE4F6, #EAF5FC);
    border: 1px solid var(--line);
    border-radius: 14px;
    overflow: hidden;
    box-shadow: 0 18px 40px -26px rgba(14,36,54,.5);
}

/* ===== Screens ===== */

.screen { display: none; padding: clamp(1rem, 2.6vw, 2rem); min-height: 0; }
.screen.active { display: block; flex: 1 1 auto; overflow-y: auto; }
.screen.is-fly { padding: 0; }
.screen.is-fly.active { display: flex; flex-direction: column; overflow: hidden; }
.screen.is-title.active { display: grid; place-items: center; align-content: center; }

.sheet { max-width: 720px; margin: 0 auto; }

.topbar {
    display: flex; align-items: center; gap: 1rem;
    margin-bottom: 1.4rem; padding-bottom: .8rem;
    border-bottom: 1px solid var(--line-soft);
}
.screen-title {
    font-family: 'Black Ops One', Impact, sans-serif;
    font-size: clamp(1.3rem, 3.2vw, 1.9rem);
    margin: 0; text-transform: uppercase; letter-spacing: .02em;
}
.group-head {
    font-size: .84rem; letter-spacing: .18em; text-transform: uppercase;
    color: var(--ink-mute); margin: 1.6rem 0 .7rem; font-weight: 700;
}
.group-head:first-of-type { margin-top: 0; }

.menu-back {
    font: inherit; font-size: .96rem; font-weight: 600;
    color: var(--ink-soft); background: var(--panel);
    border: 1px solid var(--line); border-radius: 8px;
    padding: .45rem .8rem; cursor: pointer;
}
.menu-back:hover { color: var(--ink); border-color: var(--sky-deep); }

/* ===== Title ===== */

.title-inner { text-align: center; }

.fly-logo {
    font-family: 'Black Ops One', Impact, sans-serif;
    font-size: clamp(2.1rem, 6.4vw, 4rem);
    line-height: .95; margin: 0 0 2rem; text-transform: uppercase;
}
.fly-logo .robo { color: #FFFFFF; -webkit-text-stroke: 2px var(--ink); text-shadow: 0 4px 0 var(--ink); }
.fly-logo .kyle { color: var(--red); -webkit-text-stroke: 2px var(--ink); text-shadow: 0 4px 0 var(--ink); }
.fly-logo .sub {
    display: block; font-size: .34em; letter-spacing: .34em;
    color: var(--sky-deep); margin-top: .6em;
    -webkit-text-stroke: 0; text-shadow: none;
}

.menu-list { list-style: none; padding: 0; margin: 0; display: grid; gap: .7rem; justify-items: center; }
.menu-btn {
    font: inherit; font-weight: 700; font-size: 1.14rem;
    min-width: 15rem;
    color: var(--ink); background: var(--panel);
    border: 2px solid var(--ink);
    border-radius: 12px;
    padding: .85rem 1.2rem;
    cursor: pointer;
    box-shadow: 0 4px 0 var(--ink);
    transition: transform .1s ease, box-shadow .1s ease, background-color .15s ease;
}
.menu-btn:hover { background: var(--panel-2); }
.menu-btn:active { transform: translateY(3px); box-shadow: 0 1px 0 var(--ink); }
.menu-btn.is-primary { background: var(--gold); }
.menu-btn.is-primary:hover { background: #F2C55E; }

/* ===== Settings ===== */

.craft-list { list-style: none; padding: 0; margin: 0; display: grid; gap: .6rem; }
.craft-card {
    width: 100%; text-align: left; font: inherit; cursor: pointer;
    background: var(--panel); border: 2px solid var(--line);
    border-radius: 12px; padding: .8rem 1rem;
    transition: border-color .14s ease, background-color .14s ease;
}
.craft-card b { display: block; font-size: 1.12rem; }
.craft-card small { display: block; color: var(--ink-mute); font-size: .95rem; margin-top: .15rem; }
.craft-card:hover { border-color: var(--sky-deep); }
.craft-card.is-on { border-color: var(--ink); background: #FFF8E4; }

.opt-row {
    display: flex; align-items: center; justify-content: space-between;
    gap: 1rem; padding: .55rem 0; font-size: 1.06rem; font-weight: 500;
}
.opt-row input[type="range"] { flex: 0 0 min(58%, 20rem); accent-color: var(--sky-deep); }
.opt-check { display: flex; gap: .75rem; align-items: center; padding: .55rem 0; font-size: 1.06rem; font-weight: 500; cursor: pointer; }
.opt-check input { width: 1.4rem; height: 1.4rem; accent-color: var(--sky-deep); }

/* ===== Map select ===== */

.map-list { list-style: none; padding: 0; margin: 0; display: grid; gap: .8rem; }
.map-card {
    width: 100%; text-align: left; font: inherit; cursor: pointer;
    display: grid; grid-template-columns: 132px 1fr; gap: 1rem; align-items: center;
    background: var(--panel); border: 2px solid var(--ink);
    border-radius: 14px; padding: .8rem; overflow: hidden;
    box-shadow: 0 4px 0 var(--ink);
    transition: transform .1s ease, box-shadow .1s ease;
}
.map-card:hover { background: var(--panel-2); }
.map-card:active { transform: translateY(3px); box-shadow: 0 1px 0 var(--ink); }
.map-card .shot { width: 132px; height: 84px; border-radius: 8px; overflow: hidden; display: block; }
.map-card .shot svg { display: block; width: 100%; height: 100%; }
.map-card b { display: block; font-size: 1.18rem; }
.map-card .who { display: block; font-size: .9rem; font-weight: 700; letter-spacing: .1em;
                 text-transform: uppercase; color: var(--sky-deep); margin: .1rem 0 .2rem; }
.map-card small { display: block; color: var(--ink-mute); font-size: .96rem; line-height: 1.45; }

.map-card.is-soon {
    cursor: default; opacity: .62;
    border-color: var(--line); box-shadow: none;
    border-style: dashed;
}
.map-card.is-soon:hover { background: var(--panel); }
.map-card.is-soon:active { transform: none; box-shadow: none; }
.map-card .tag {
    display: inline-block; font-size: .76rem; font-weight: 800; letter-spacing: .12em;
    text-transform: uppercase; color: var(--ink-mute);
    border: 1px solid var(--line); border-radius: 999px; padding: .15rem .5rem; margin-top: .35rem;
}

@media (max-width: 520px) {
    .map-card { grid-template-columns: 1fr; }
    .map-card .shot { width: 100%; height: 96px; }
}

/* ===== Flying ===== */

.fly-wrap { position: relative; width: 100%; flex: 1 1 auto; min-height: 0; background: #CFEAF7; }
/* Absolutely positioned on purpose. Left in flow with height:100% and no
   ancestor holding a definite height, the canvas falls back to its own
   backing store for its height, which grows the wrapper, which makes resize()
   pick a taller backing store, and round it goes. Out of flow it fills the
   wrapper and contributes nothing back to it. */
#fly-canvas {
    position: absolute;
    inset: 0;
    display: block;
    width: 100%;
    height: 100%;
    cursor: none;
}

/* Where the cursor is, is where the nose is going. Showing it matters more
   here than in a normal game, because the aircraft answers a position rather
   than a movement and you want to see the position you are holding. */
.reticle {
    position: absolute; top: 0; left: 0;
    width: 48px; height: 48px; margin: -24px 0 0 -24px;
    color: #FFFFFF;
    pointer-events: none;
    filter: drop-shadow(0 1px 2px rgba(0,0,0,.55));
}
.reticle svg { width: 100%; height: 100%; display: block; }

.hud {
    position: absolute; inset: auto auto 0 0;
    display: flex; gap: .6rem;
    padding: clamp(.6rem, 1.4vh, 1.1rem) clamp(.7rem, 1.4vw, 1.9rem);
    pointer-events: none;
}
.gauge {
    display: flex; align-items: baseline; gap: .4rem;
    background: rgba(255,255,255,.82);
    border: 2px solid var(--ink);
    border-radius: 10px;
    padding: .4rem .7rem;
    box-shadow: 0 3px 0 rgba(14,36,54,.35);
}
.gauge .k { font-size: .74rem; letter-spacing: .14em; text-transform: uppercase; color: var(--ink-soft); font-weight: 700; }
.gauge .v { font-size: 1.6rem; font-weight: 800; font-variant-numeric: tabular-nums; min-width: 2.6ch; text-align: right; }
.gauge .u { font-size: .78rem; font-weight: 700; color: var(--ink-mute); }

/* One soft tint over the whole view when you go in, warm for fire and cold
   for water. Deliberately a single fade with no repeat: a flashing overlay
   is exactly the thing to avoid. */
.fly-flash {
    position: absolute;
    inset: 0;
    pointer-events: none;
    opacity: 0;
    background: transparent;
}
.fly-flash.is-on { animation: flyFlash 1.1s ease-out forwards; }
/* Clear through the middle. The first version was an opaque wash over the
   whole view, which tinted the screen and hid the very thing it was meant
   to punctuate. Tinting only the edge frames the crash instead. */
.fly-flash.is-land {
    background: radial-gradient(circle at 50% 52%,
        rgba(255,180,90,0) 22%, rgba(214,86,34,.42) 62%, rgba(120,32,12,.68) 100%);
}
.fly-flash.is-water {
    background: radial-gradient(circle at 50% 52%,
        rgba(220,242,255,0) 22%, rgba(74,158,212,.42) 62%, rgba(22,74,124,.66) 100%);
}
/* Taking fragments. Shorter and weaker than either crash tint, because this
   one fires repeatedly and must never become the thing you are looking at. */
.fly-flash.is-hit {
    background: radial-gradient(circle at 50% 52%,
        rgba(255,210,150,0) 30%, rgba(220,110,50,.3) 68%, rgba(150,40,20,.42) 100%);
}
.fly-flash.is-hit.is-on { animation: flyHit .45s ease-out forwards; }
@keyframes flyHit {
    0%   { opacity: .8; }
    100% { opacity: 0; }
}

@keyframes flyFlash {
    0%   { opacity: .9; }
    100% { opacity: 0; }
}

.fly-pause {
    /* Above the thumb pads, which are z-index 6. Without this the pause
       card draws under them, and in landscape the sticks sit exactly
       where Keep flying and Menu are, so the menu cannot be closed. */
    position: absolute; inset: 0; z-index: 30;
    display: grid; place-items: center;
    background: rgba(207,234,247,.78);
    backdrop-filter: blur(4px); -webkit-backdrop-filter: blur(4px);
}
.fly-pause[hidden] { display: none; }
.pause-card {
    background: var(--panel); border: 2px solid var(--ink);
    border-radius: 16px; padding: 1.8rem 2rem; text-align: center;
    box-shadow: 0 6px 0 var(--ink);
    /* With the settings on it and the type a size up, this card can be
       taller than a short window. It is a column with a scrolling middle:
       the options give way, and Keep flying and Menu stay where they are.
       Scrolling the whole card instead pushes the buttons off the bottom,
       which is the one thing that must never happen to a pause menu. */
    max-height: calc(100% - 1.6rem);
    display: flex;
    flex-direction: column;
    align-items: center;
}
.pause-card h2 {
    font-family: 'Black Ops One', Impact, sans-serif;
    margin: 0 0 1.2rem; text-transform: uppercase; letter-spacing: .06em;
}

.noscript { padding: 2rem; }

/* The shared fullscreen script injects this. Unstyled it renders as a
   default button wrapping a 24x24 viewBox SVG with no intrinsic size, which
   lays out at 300x150 and pushes the frame past the bottom of the viewport. */
.fs-btn {
    position: absolute; top: .6rem; right: .6rem; z-index: 60;
    display: inline-flex; align-items: center; gap: .35rem; cursor: pointer;
    background: rgba(255,255,255,.85);
    border: 2px solid var(--ink); border-radius: 8px;
    padding: .3rem .55rem; color: var(--ink);
    font: inherit; font-size: .78rem; font-weight: 700;
    letter-spacing: .12em; text-transform: uppercase;
}
.fs-btn:hover { background: #fff; }
.fs-btn svg { width: 13px; height: 13px; flex: none; }

/* ===== Footer ===== */

.back-strip {
    display: flex; flex-wrap: wrap; justify-content: center;
    gap: clamp(1rem, 4vw, 2.2rem); padding: 1.1rem 0 .3rem;
}
.back-strip a { color: var(--ink-soft); text-decoration: none; font-size: .96rem; font-weight: 500; }
.back-strip a:hover { color: var(--sky-deep); }
body.is-flying .back-strip { opacity: .55; }

@media (max-height: 620px) { .back-strip { display: none; } }

/* ===== Fullscreen ===== */

.game-frame.is-fullscreen,
.game-frame:fullscreen,
.game-frame.is-maxed {
    border-radius: 0; border: 0; box-shadow: none;
    width: 100vw; height: 100vh; height: 100dvh;
    max-width: none; max-height: none; min-height: 0;
    display: flex; flex-direction: column;
}
.game-frame.is-maxed { position: fixed; inset: 0; z-index: 999; }

.game-frame.is-fullscreen .screen.active,
.game-frame:fullscreen .screen.active,
.game-frame.is-maxed .screen.active {
    flex: 1 1 auto; min-height: 0;
    padding-inline: max(clamp(1rem, 2.6vw, 2rem), calc((100% - 1240px) / 2));
    display: flex; flex-direction: column;
    justify-content: center;
    justify-content: safe center;
}
.game-frame.is-fullscreen .screen.is-fly.active,
.game-frame:fullscreen .screen.is-fly.active,
.game-frame.is-maxed .screen.is-fly.active {
    padding: 0; overflow: hidden; justify-content: stretch;
}

@media (prefers-reduced-motion: reduce) {
    .menu-btn { transition: none; }
}


/* ===== Threat arrow =====

   One per hostile that is currently shooting at you. In view, it hangs
   above the ship pointing down at it, well clear of the hull, because a
   marker sat on top of the thing you are trying to hit is worse than no
   marker at all. Off screen or behind, it rides an ellipse inset from the
   frame edge and turns to face outward. Small: it is a pointer, not a
   badge. */
.threat {
  position: absolute;
  left: 50%; top: 50%;
  width: 1.75rem; height: 1.75rem;
  margin: -.875rem 0 0 -.875rem;
  color: #FF5A47;
  pointer-events: none;
  filter: drop-shadow(0 1px 3px rgba(0, 0, 0, .55));
  transition: opacity .18s ease;
  animation: threat-pulse 1.1s ease-in-out infinite;
}
.threat[hidden] { display: none; }
.threat svg { width: 100%; height: 100%; display: block; }
@keyframes threat-pulse { 50% { opacity: .55; } }
@media (prefers-reduced-motion: reduce) { .threat { animation: none; } }

/* ===== Opening caption ===== */
.intro-card {
  position: absolute;
  left: 50%; bottom: 14%;
  transform: translateX(-50%);
  display: grid;
  justify-items: center;
  gap: .25rem;
  padding: .8rem 1.6rem;
  border-radius: 1rem;
  background: rgba(12, 26, 40, .42);
  backdrop-filter: blur(3px);
  color: #FFF;
  text-align: center;
  pointer-events: none;
  animation: intro-in .8s ease both;
}
.intro-card[hidden] { display: none; }
.intro-card b { font-size: 1.5rem; letter-spacing: .04em; }
.intro-card small { font-size: .98rem; opacity: .85; }
.intro-card.is-out { animation: intro-out .7s ease both; }
@keyframes intro-in  { from { opacity: 0; transform: translate(-50%, 1rem); } }
@keyframes intro-out { to   { opacity: 0; transform: translate(-50%, -.6rem); } }
@media (prefers-reduced-motion: reduce) {
  .intro-card, .intro-card.is-out { animation: none; }
}

/* ===== Settings inside the pause card =====

   Same rows as the settings screen, narrower and quieter, with a rule
   above them so the card still reads as a menu with options on it rather
   than as an options screen with two buttons at the bottom. */
/* The pause card is a white panel, not a dark one. Everything in here was
   written for a dark card and came out white on white, which is why the
   preset buttons could not be seen at all. */
.pause-opts {
  width: min(28rem, 80vw);
  margin: .2rem 0 1.1rem;
  padding-top: .6rem;
  border-top: 1px solid rgba(14, 36, 54, .16);
  text-align: left;
  overflow-y: auto;
  min-height: 0;
  overscroll-behavior: contain;
}
.pause-opts .group-head {
  margin: .7rem 0 .1rem;
  font-size: .86rem;
  opacity: .7;
}
.pause-opts .group-head:first-child { margin-top: 0; }
.pause-opts .opt-row { padding: .35rem 0; font-size: 1.02rem; }
.pause-opts .opt-row input[type="range"] { flex: 0 0 min(52%, 13rem); }
.pause-opts .opt-check { padding: .4rem 0; font-size: 1.02rem; }


/* Nothing but the aeroplane during the opening pan. */
.fly-wrap.is-intro .hud,
.fly-wrap.is-intro .reticle { opacity: 0; }
.fly-wrap .hud, .fly-wrap .reticle { transition: opacity .5s ease; }


/* The credit CC BY obliges. Quiet, but on the screen the music controls
   are on, which is where someone looking for it would look, and readable
   rather than hidden away: the licence asks for somewhere people can find
   it, not merely somewhere it exists. */
.credit {
  margin: 1.1rem 0 0;
  padding-top: .8rem;
  border-top: 1px solid rgba(0, 0, 0, .1);
  font-size: .9rem;
  line-height: 1.5;
  opacity: .78;
}
.credit a { color: inherit; }



/* ===== The way into the menu =====

   Escape still works, but nobody is holding a keyboard: this is a game
   played entirely with a mouse, so there has to be something to click.
   Top left, out of the way of the horizon and the gauges, and dressed
   like the pause card it opens. */
.hud-btn {
  position: absolute; top: 0; left: 0;
  margin: clamp(.6rem, 1.4vh, 1.1rem) clamp(.7rem, 1.4vw, 1.9rem);
  width: 2.7rem; height: 2.7rem;
  display: grid; place-items: center;
  padding: 0;
  border: 1px solid rgba(255, 255, 255, .18);
  border-radius: .85rem;
  background: rgba(12, 26, 40, .42);
  backdrop-filter: blur(3px);
  color: #FFF;
  cursor: pointer;
  pointer-events: auto;
  transition: background .15s ease, transform .15s ease;
}
.hud-btn:hover { background: rgba(12, 26, 40, .68); transform: scale(1.06); }
.hud-btn:active { transform: scale(.97); }
.hud-btn svg { width: 64%; height: 64%; display: block; }
.fly-wrap.is-intro .hud-btn { opacity: 0; pointer-events: none; }
.fly-wrap .hud-btn { transition: opacity .5s ease, background .15s ease, transform .15s ease; }

/* ===== Setting presets =====

   Six stops under every slider. A slider is precise and fiddly, and most
   people want a number rather than a position; these give the number in
   one click and leave the slider there for anyone who wants to fine tune.
   Laid out under the slider and the same width as it, so the row reads as
   belonging to that control and not the next one. */
.preset-row {
  display: flex;
  gap: .4rem;
  width: min(58%, 20rem);
  margin: .45rem 0 .9rem auto;
}
.preset {
  flex: 1 1 0;
  min-height: 2.3rem;
  padding: .5rem 0;
  font: inherit;
  font-size: .95rem;
  font-weight: 700;
  line-height: 1;
  color: inherit;
  border: 2px solid rgba(0, 0, 0, .14);
  border-radius: .6rem;
  background: #FFF;
  box-shadow: 0 1px 2px rgba(0, 0, 0, .08);
  cursor: pointer;
  transition: background .12s ease, color .12s ease, transform .1s ease;
}
.preset:hover { background: #EAF4FC; border-color: rgba(0, 0, 0, .28); }
.preset:active { transform: translateY(1px); }
.preset.is-on {
  background: var(--sky-deep);
  border-color: transparent;
  color: #FFF;
  box-shadow: 0 2px 5px rgba(0, 0, 0, .18);
}

/* The pause card is dark, so the same row has to be the other way round. */
.pause-opts .preset-row { width: min(74%, 18rem); margin-bottom: .7rem; }
.pause-opts .preset {
  min-height: 2.2rem;
  font-size: .9rem;
}

/* Out on the rim it is a warning rather than a label, so it gets a harder
   edge and a heavier shadow to sit against a bright sea. */
.threat.is-edge {
  filter: drop-shadow(0 2px 6px rgba(0, 0, 0, .75));
}


/* ===== Touch =====

   Themed to the game rather than restyled: the shared layer exposes
   three colours and takes care of everything else.

   The HUD and the gear move up out of the way when the pads are live,
   because a gauge under a thumb is a gauge nobody reads. */
.tl.fly-touch {
  --tl-line: rgba(255, 255, 255, .5);
  --tl-fill: rgba(12, 32, 50, .38);
  --tl-knob: rgba(255, 255, 255, .9);
  --tl-ink: #FFF;
}
.fly-wrap.is-intro .tl { opacity: 0; pointer-events: none; }
.fly-wrap .tl { transition: opacity .4s ease; }

/* With pads on screen the reticle is the only thing saying where the
   nose is going, so it gets bigger and brighter. */
html.has-touch .reticle { width: 62px; height: 62px; margin: -31px 0 0 -31px; }

/* With pads on screen the bottom corners belong to thumbs, so the gauges
   move up beside the gear. A readout under a thumb is a readout nobody
   reads, and the speed and altitude are the two numbers that matter. */
html.has-touch .hud {
  inset: 0 auto auto calc(5.2rem + env(safe-area-inset-left, 0px));
}

/* Fly Game has no right stick, so the trigger takes the corner a right
   thumb is already resting in rather than floating above an empty one. */
.tl.fly-touch .tl-stack-right {
  bottom: calc(1.4rem + env(safe-area-inset-bottom, 0px));
  right: calc(1.4rem + env(safe-area-inset-right, 0px));
}
