/* aleste.net - boot screen + landing page */

:root {
  --bg: #07080c;
  --panel: #11141b;
  --line: #232936;
  --text: #e9ecf3;
  --muted: #8d95a6;
  --accent: #47b4ff;

  /* MSX rainbow, used for the wordmark and section accents */
  --c1: #e4322b;
  --c2: #f0871e;
  --c3: #f2cf24;
  --c4: #57b94c;
  --c5: #2aa5d6;
  --c6: #3f5fd4;
  --c7: #9a4fd0;
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  background: var(--bg);
  color: var(--text);
  font: 16px/1.6 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
        "Helvetica Neue", Arial, sans-serif;
  -webkit-text-size-adjust: 100%;
}

a { color: inherit; text-decoration: none; }

.nowrap { white-space: nowrap; }
.dim { color: var(--muted); }

.ico {
  width: 18px; height: 18px;
  fill: none; stroke: currentColor; stroke-width: 1.8;
  stroke-linecap: round; stroke-linejoin: round;
}

/* ==========================================================================
   Boot screen
   Hidden unless boot.js adds .booting to <html>, so no-JS visitors and
   crawlers get the page itself.
   ========================================================================== */

.boot { display: none; }

html.booting .boot {
  display: block;
  position: fixed;
  inset: 0;
  z-index: 9999;
  background: #000;
}

html.booting body { overflow: hidden; }

/* page content waits behind the boot screen */
html.booting .top,
html.booting main,
html.booting .foot {
  opacity: 0;
  transform: translateY(14px);
}

html.ready .top,
html.ready main,
html.ready .foot {
  opacity: 1;
  transform: none;
  transition: opacity .55s ease, transform .55s cubic-bezier(.2, .7, .3, 1);
}

html.ready main { transition-delay: .06s; }
html.ready .foot { transition-delay: .12s; }

.crt {
  position: absolute;
  inset: 0;
  overflow: hidden;
  background: #000;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* The tube keeps the 4:3 shape an MSX was actually shown on, so the 256x212
   picture is stretched exactly the way a CRT stretched it. */
.tube {
  position: relative;
  width: min(100vw, 133.33vh);
  height: min(75vw, 100vh);
  max-width: 100vw;
  max-height: 100vh;
  overflow: hidden;
  opacity: 0;
}

.boot.on .tube { opacity: 1; transition: opacity .12s ease; }

#screen {
  width: 100%;
  height: 100%;
  display: block;
  image-rendering: pixelated;
  image-rendering: crisp-edges;
}

.scanlines {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: repeating-linear-gradient(
      to bottom,
      rgba(0, 0, 0, 0) 0 2px,
      rgba(0, 0, 0, .22) 2px 4px);
  opacity: 0;
}

.boot.on .scanlines { opacity: 1; transition: opacity .3s ease .1s; }

/* a soft phosphor bloom and the curve of the glass */
.glare {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background:
    radial-gradient(70% 55% at 50% 38%, rgba(255, 255, 255, .06), transparent 68%),
    radial-gradient(125% 105% at 50% 50%, transparent 58%, rgba(0, 0, 0, .45) 100%);
}

/* the horizontal line a CRT throws out as it powers up */
.power {
  position: absolute;
  left: 0;
  right: 0;
  top: 50%;
  height: 3px;
  background: #dfe9ff;
  box-shadow: 0 0 30px 8px rgba(200, 225, 255, .8);
  transform: translateY(-50%) scaleX(.02);
  opacity: 0;
}

.boot.power-on .power { animation: powerOn .46s ease-out forwards; }

@keyframes powerOn {
  0%   { opacity: 0; transform: translateY(-50%) scaleX(.02) scaleY(1); }
  20%  { opacity: 1; transform: translateY(-50%) scaleX(1) scaleY(1); }
  70%  { opacity: .9; transform: translateY(-50%) scaleX(1) scaleY(26); }
  100% { opacity: 0; transform: translateY(-50%) scaleX(1) scaleY(80); }
}

.flash {
  position: absolute;
  inset: 0;
  background: #cfe0ff;
  opacity: 0;
  pointer-events: none;
}

.boot.flash-out .flash { animation: flashOut .42s ease-out forwards; }

@keyframes flashOut {
  0%   { opacity: 0; }
  22%  { opacity: .45; }
  100% { opacity: 0; }
}

.boot.done {
  opacity: 0;
  pointer-events: none;
  transition: opacity .38s ease;
}

.skip {
  position: absolute;
  right: max(16px, env(safe-area-inset-right));
  bottom: max(16px, env(safe-area-inset-bottom));
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 8px 14px;
  border-radius: 999px;
  border: 1px solid rgba(255, 255, 255, .2);
  background: rgba(0, 0, 0, .45);
  color: #cdd6e6;
  font-size: 13px;
  letter-spacing: .02em;
  cursor: pointer;
  opacity: 0;
  z-index: 2;
}

.skip-key {
  font: 11px/1 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  padding: 3px 5px;
  border-radius: 4px;
  border: 1px solid rgba(255, 255, 255, .22);
  color: #9fb0c8;
}

.boot.on .skip { opacity: 1; transition: opacity .4s ease .6s; }
.skip:hover { background: rgba(0, 0, 0, .7); color: #fff; }

/* ==========================================================================
   The sunset across the top of the page
   A photograph in 256 colours, animated by rotating its palette. backdrop.js
   draws it at whatever pixel size the data declares - 384x256 today - and the
   CSS scales that up with nearest-neighbour, so the pixels stay square and
   hard, like the boot screen and the footer scene.
   ========================================================================== */

/* Held to the same column as the header and the cards, with the same side
   padding, so its edges line up with theirs rather than running to the
   window. */
.backdrop {
  position: relative;
  max-width: 1080px;
  margin: 0 auto;
  padding: 0 max(20px, env(safe-area-inset-left)) 0
             max(20px, env(safe-area-inset-right));
}

.backdrop canvas {
  display: block;
  width: 100%;
  height: clamp(300px, 46vw, 520px);
  background: #000;
  object-fit: cover;
  object-position: 50% 60%;   /* sun near the top third, empty water cropped */
  image-rendering: pixelated;
}

/* Nothing to see while the boot screen is up, and display:none also stops
   backdrop.js cycling a palette nobody is looking at. */
html.booting .backdrop { display: none; }

/* ==========================================================================
   The page
   ========================================================================== */

/* The header rides over the sunset. Both things in it carry their own solid
   plate, so they stay readable against a bright sky without a scrim. */
.top {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 20px max(20px, env(safe-area-inset-left)) 8px
           max(20px, env(safe-area-inset-right));
  max-width: 1080px;
  margin: 0 auto;
}

/* the same logo the boot screen draws: white on a black plate */
.brand-word {
  display: inline-flex;
  align-items: center;
  padding: 7px 11px;
  background: #000;
  color: #fff;
  font-weight: 900;
  font-size: 19px;
  line-height: 1;
}

/* Text set in the MSX character set. Until msxfont.js swaps in the pixel
   rendering these keep their normal type, so a browser without JavaScript
   still shows readable words.

   Heights are always a whole multiple of the 8px glyph grid - anything else
   lands stems on half pixels and the letters go mushy. The negative margin
   is one grid row, the blank row under the baseline, so pixel text sits on
   the same line as ordinary text beside it. */
.msx-svg {
  --msx-h: 16px;
  display: block;
  height: var(--msx-h);
  width: auto;
  margin-bottom: calc(var(--msx-h) / -8);
  shape-rendering: crispEdges;
}

.brand-word.msx-on .msx-svg { --msx-h: 16px; margin-bottom: 0; }

h1 { display: flex; align-items: baseline; gap: .34em; flex-wrap: wrap; }
h1 .msx-on { display: inline-block; }
h1 .msx-on .msx-svg { --msx-h: 40px; }

.card-go .msx-on { display: inline-block; vertical-align: -2px; }
.card-go .msx-on .msx-svg { --msx-h: 16px; }

@media (max-width: 720px) {
  h1 .msx-on .msx-svg { --msx-h: 32px; }
}

@media (max-width: 420px) {
  h1 .msx-on .msx-svg { --msx-h: 24px; }
}

.replay {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 8px 14px;
  border-radius: 999px;
  border: 1px solid var(--line);
  background: var(--panel);
  color: var(--muted);
  font-size: 13px;
  cursor: pointer;
  transition: color .15s, border-color .15s;
}

.replay:hover { color: var(--text); border-color: #39425a; }

main {
  max-width: 1080px;
  margin: 0 auto;
  padding: 0 max(20px, env(safe-area-inset-left)) 40px
             max(20px, env(safe-area-inset-right));
}

.hero { padding: 44px 0 34px; max-width: 660px; }

.hero h1 {
  margin: 0 0 16px;
  font-size: clamp(30px, 5.4vw, 46px);
  line-height: 1.14;
  letter-spacing: -.03em;
  font-weight: 800;
}

.lede { margin: 0 0 12px; font-size: 17px; color: #c3cad8; }
.sub { margin: 0; font-size: 14px; color: var(--muted); }

.cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(258px, 1fr));
  gap: 16px;
}

.card {
  display: flex;
  gap: 15px;
  padding: 20px;
  border-radius: 15px;
  background: var(--panel);
  border: 1px solid var(--line);
  transition: border-color .18s, background .18s, transform .18s;
}

.card:hover {
  border-color: #3b465f;
  background: #151924;
  transform: translateY(-2px);
}

.card-ico {
  flex-shrink: 0;
  width: 42px;
  height: 42px;
  border-radius: 11px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: #1b2130;
  border: 1px solid #262f42;
}

.card-ico svg {
  width: 22px;
  height: 22px;
  fill: none;
  stroke: var(--accent);
  stroke-width: 1.7;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.card:nth-child(2) .card-ico svg { stroke: var(--c4); }
.card:nth-child(3) .card-ico svg { stroke: var(--c3); }

.card-body { display: flex; flex-direction: column; min-width: 0; }

.card-title {
  font-size: 17px;
  font-weight: 700;
  letter-spacing: -.01em;
  margin-bottom: 5px;
}

.card-desc { font-size: 14px; color: var(--muted); margin-bottom: 12px; }

.card-go {
  margin-top: auto;
  font: 12.5px/1 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  color: var(--accent);
  letter-spacing: .02em;
}

.card:nth-child(2) .card-go { color: var(--c4); }
.card:nth-child(3) .card-go { color: var(--c3); }

/* A card with nowhere to go yet. It must not offer the hover lift the others
   do, and the line where they print their address says why instead. Placed
   after the nth-child rules above so it wins the tie on specificity. */
.card.card-soon { cursor: default; }

.card.card-soon:hover {
  border-color: var(--line);
  background: var(--panel);
  transform: none;
}

.card.card-soon .card-go { color: var(--muted); }

.arrow {
  display: inline-block;
  margin-left: 7px;
  transition: transform .18s;
}

.card:hover .arrow { transform: translateX(4px); }

.foot {
  max-width: 1080px;
  margin: 0 auto;
  padding: 22px max(20px, env(safe-area-inset-left)) 34px
              max(20px, env(safe-area-inset-right));
  border-top: 1px solid var(--line);
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  font-size: 12.5px;
  color: #6d768a;
}

@media (max-width: 560px) {
  .hero { padding: 30px 0 26px; }
  .card { padding: 17px; }
  .foot { font-size: 12px; }
  .replay span { display: none; }
  .replay { padding: 8px 10px; }
}

/* Anyone who has asked for less motion gets the page, not the light show. */
@media (prefers-reduced-motion: reduce) {
  html.booting .boot { display: none; }
  html.booting .top,
  html.booting main,
  html.booting .foot { opacity: 1; transform: none; }
  .card, .arrow { transition: none; }
  .card:hover { transform: none; }
}

/* the machine and its washing machine, down in the corner */
.scene {
  width: 80px;
  height: 88px;
  display: block;
  image-rendering: pixelated;
  image-rendering: crisp-edges;
  flex-shrink: 0;
}

.foot-right {
  display: flex;
  align-items: center;
  gap: 14px;
}

@media (prefers-reduced-motion: reduce) {
  .scene { opacity: .75; }
}

/* ---------------------------------------------------------- leave a note */

.leave { margin-top: 34px; }

.note-box {
  border: 1px solid var(--line);
  border-radius: 14px;
  background: var(--panel);
  padding: 0 18px;
}

.note-box summary {
  cursor: pointer;
  padding: 15px 0;
  font-size: 15px;
  font-weight: 600;
  list-style: none;
  display: flex;
  align-items: center;
  gap: 9px;
}

.note-box summary::-webkit-details-marker { display: none; }

.note-box summary::before {
  content: "+";
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  border-radius: 5px;
  background: #1b2130;
  border: 1px solid #262f42;
  color: var(--accent);
  font-size: 14px;
  line-height: 1;
}

.note-box[open] summary::before { content: "\2212"; }

.note-intro { margin: 0 0 14px; color: var(--muted); font-size: 14px; }

.note-form { padding-bottom: 18px; }

.note-field { display: block; margin-bottom: 12px; }

.note-field > span {
  display: block;
  font-size: 12.5px;
  color: var(--muted);
  margin-bottom: 5px;
}

.note-field em { font-style: normal; opacity: .65; }

.note-field textarea,
.note-field input[type="text"] {
  width: 100%;
  padding: 10px 12px;
  border-radius: 9px;
  border: 1px solid var(--line);
  background: #0d1015;
  color: var(--text);
  font: inherit;
  font-size: 15px;
  resize: vertical;
}

.note-field textarea:focus,
.note-field input:focus { outline: none; border-color: var(--accent); }

.note-row { display: flex; gap: 12px; flex-wrap: wrap; }
.note-row .note-field { flex: 1; min-width: 190px; }

/* the honeypot has to be reachable to a bot but never to a person */
.note-trap {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

.note-actions { display: flex; align-items: center; gap: 12px; }

.btn-send {
  padding: 10px 20px;
  border-radius: 999px;
  border: none;
  background: var(--accent);
  color: #06121f;
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
}

.btn-send:hover { background: #6fb4ff; }
.btn-send:disabled { opacity: .55; cursor: default; }

.note-status { font-size: 13.5px; color: var(--muted); }
.note-status.good { color: #7fd6a4; }
.note-status.bad { color: #ff9ea6; }
