/* PixelDeck.
 *
 * The palette is the device's own, straight out of firmware/pixeldeck/config.h — the same
 * five BASE_ colours the panel draws with when no companion is attached. The site and the
 * hardware are not "coordinated"; they are literally the same numbers.
 *
 * Everything here is square. No border-radius, no gradients, no blur: the product's identity
 * is a 320x240 panel with 80x64 tiles, and a soft-edged site would be describing a different
 * product. Shadows are hard offsets, the way a two-colour sprite fakes depth.
 */

:root {
  --bg:      #14161d;  /* darker than the bar, so the bar reads as a raised surface */
  --bar-bg:  #282c38;  /* BASE_BAR_BG  0x2967 */
  --ok:      #00f4b0;  /* BASE_OK      0x07B6 */
  --warn:    #f8a400;  /* BASE_WARN    0xFD20 */
  --press:   #009fff;  /* BASE_PRESS   0x04FF */
  --missing: #606060;  /* BASE_MISSING 0x630C */
  --text:    #ffffff;
  --dim:     #9aa0b0;

  /* One knob for the whole mockup. Integer only — see .panel. */
  --px: 2;

  /* Named faces before ui-monospace, which is the opposite of the usual advice and is here for
     one reason: on a Japanese-locale Windows it resolves to MS Gothic, and MS Gothic draws
     U+005C as a yen sign. The download page tells people to type `.\pixeldeck.exe`, so a stack that
     can render the backslash as ¥ is a stack that gives them the wrong command. */
  --mono: "Cascadia Mono", Consolas, "SF Mono", Menlo, ui-monospace, monospace;
}

* { box-sizing: border-box; }

html { color-scheme: dark; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--mono);
  font-size: 16px;
  line-height: 1.6;
  /* Pixel art must never be resampled by the browser. */
  image-rendering: pixelated;
}

/* Sharp text is part of the look, but only for the display type — antialiasing off for body
   copy just makes it harder to read at small sizes. */
h1, h2, h3, .wordmark { -webkit-font-smoothing: none; font-smooth: never; }

a { color: var(--press); }
a:hover { color: var(--ok); }

/* ---- wordmark ---------------------------------------------------------------------- */

.wordmark {
  display: inline-flex; align-items: center; gap: 0.55rem;
  text-decoration: none; font-weight: 700; letter-spacing: 0.12em; font-size: 1.15rem;
}
.wm-a { color: var(--text); }
.wm-b { color: var(--ok); }

/* The mark, at the size it was drawn. Locked to 16px rather than scaled to the text: it is a
   16x16 grid, and the one thing a pixel mark must never do is land on fractional pixels. */
.mark { width: 16px; height: 16px; display: block; flex: none; }

/* ---- chrome ------------------------------------------------------------------------ */

.bar {
  display: flex; align-items: center; justify-content: space-between; gap: 1rem; flex-wrap: wrap;
  padding: 1rem 1.5rem;
  background: var(--bar-bg);
  border-bottom: 2px solid #000;
}
.bar nav { display: flex; gap: 1.25rem; flex-wrap: wrap; }
.bar nav a { color: var(--dim); text-decoration: none; font-size: 0.9rem; }
.bar nav a:hover { color: var(--ok); }

main { max-width: 68rem; margin: 0 auto; padding: 0 1.5rem; }

section { padding: 3.5rem 0; border-bottom: 2px solid var(--bar-bg); }
section:last-child { border-bottom: 0; }

h1 { font-size: clamp(1.8rem, 5vw, 3rem); line-height: 1.15; margin: 0 0 1rem; }
h1 em { color: var(--ok); font-style: normal; }
h2 { font-size: clamp(1.3rem, 3vw, 1.8rem); margin: 0 0 1.5rem; }
h3 { font-size: 1.05rem; margin: 0 0 0.75rem; display: flex; align-items: center; gap: 0.6rem; }

.eyebrow { color: var(--warn); letter-spacing: 0.18em; font-size: 0.78rem; margin: 0 0 0.75rem; }
.lede { font-size: 1.05rem; color: #d8dce6; }
.wide { max-width: 46rem; }
.note { color: var(--dim); font-size: 0.88rem; }

/* ---- hero -------------------------------------------------------------------------- */

.hero { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 3rem; align-items: center; }
@media (max-width: 60rem) { .hero { grid-template-columns: 1fr; } }

.status-line {
  display: inline-flex; align-items: center; gap: 0.6rem;
  margin-top: 1.5rem; padding: 0.5rem 0.9rem;
  background: var(--bar-bg); border: 2px solid #000;
  font-size: 0.85rem; color: var(--dim);
}
.dot { width: 8px; height: 8px; background: var(--warn); display: inline-block; }

/* ---- buttons ------------------------------------------------------------------------ */

/* The card's shadow, at the size a thing you press should have. It moves into the shadow on
   :active rather than dimming, because that is what the device's own tiles do. */
.cta { display: flex; flex-wrap: wrap; gap: 0.75rem; margin: 1.5rem 0 0; }

.btn {
  display: inline-block;
  padding: 0.7rem 1.4rem;
  background: var(--bar-bg);
  color: var(--text);
  border: 2px solid #000;
  box-shadow: 4px 4px 0 #000;
  text-decoration: none;
  font: inherit;
  font-size: 0.95rem;
  cursor: pointer;
}
.btn:hover { color: var(--ok); }
.btn:active { transform: translate(4px, 4px); box-shadow: none; }

.btn-go { background: var(--ok); color: #000; }
.btn-go:hover { background: #fff; color: #000; }

/* ---- signup ------------------------------------------------------------------------- */

/* Guard: .signup-done is display:flex when shown, and a bare display rule would beat the
   hidden attribute and leak the thank-you onto every first paint. */
[hidden] { display: none !important; }

.signup { max-width: 34rem; }
.signup label { display: block; font-size: 0.85rem; color: var(--dim); margin-bottom: 0.5rem; }
.signup-row { display: flex; flex-wrap: wrap; gap: 0.75rem; }

.signup input {
  flex: 1 1 16rem;
  min-width: 0;
  padding: 0.7rem 0.9rem;
  background: #0d0f14;
  color: var(--text);
  border: 2px solid var(--bar-bg);
  font: inherit;
  font-size: 0.95rem;
}
.signup input:focus { outline: none; border-color: var(--press); }
.signup input::placeholder { color: var(--missing); }
/* Only after the browser has decided the value is wrong — :invalid alone paints an empty
   required field red before it has been touched. */
.signup input:user-invalid { border-color: var(--warn); }

.signup-done {
  display: flex; align-items: center; gap: 0.6rem;
  max-width: 34rem;
  padding: 0.9rem 1.1rem;
  background: var(--bar-bg);
  border: 2px solid #000;
  box-shadow: 4px 4px 0 #000;
}

/* ---- the panel mockup --------------------------------------------------------------- */

/* 320x240 scaled by an integer factor only. A fractional zoom lands tile edges on half
   pixels, which is the one thing a pixel-art product must not show on its own front page. */
.panel {
  width: calc(320px * var(--px));
  height: calc(240px * var(--px));
  background: #000;
  border: 2px solid var(--bar-bg);
  display: flex; flex-direction: column;
  position: relative;   /* for the page mark, which overlays the bar's top two rows */
}
@media (max-width: 44rem) { :root { --px: 1; } }

/* Footage of the display is natively 320x240 — this frame's exact size — so it only has to
   fill the box the mockup would have filled. `fill` rather than `contain`: at 1:1 they are the
   same, and if a clip is ever captured at the wrong size, a stretched frame is a visible
   mistake where letterboxing is an invisible one. */
.panel-video, .panel-shot { width: 100%; height: 100%; display: block; object-fit: fill; }

/* A screenshot of the companion is the one image on this site that must NOT be nearest-
   neighboured. Everything else here is pixel art shown at a whole multiple, which is why
   `body` sets `image-rendering: pixelated` — but a 1322px-wide window shown in a 46rem column
   is downscaled by some fraction, and pixelated downscaling drops whole rows of text rather
   than averaging them. It comes out looking like a corrupted JPEG of a working program.

   Wearing the card's border and shadow, because that is what a raised surface looks like here
   and a screenshot floating with no edge reads as a hole in the page. */
.shot {
  display: block;
  width: 100%;
  height: auto;
  max-width: 46rem;
  image-rendering: auto;
  border: 2px solid #000;
  box-shadow: 6px 6px 0 #000;
}

.grid {
  display: grid;
  grid-template-columns: repeat(4, calc(80px * var(--px)));
  grid-template-rows: repeat(3, calc(64px * var(--px)));
}

/* The 2px ring is the border the device itself owns and paints — press feedback and status
   colour live there, which is why it is a border here and not an outline. */
.tile {
  --ring: #000;
  border: calc(2px * var(--px)) solid var(--ring);
  background: #1b1f2a;
  display: flex; align-items: center; justify-content: center;
  font-size: calc(9px * var(--px));
  letter-spacing: 0.06em;
  color: #c9cfdd;
  position: relative;   /* for the corner mark below */
}

/* The corner mark the companion bakes into every tile's art — `render::corner_mark`, drawn at
   the same numbers: 4px in from the edge, 10px arms, 2px thick, in a lighten of the tile's own
   face. Inside the ring rather than on it, because the device repaints the ring blind on every
   press and anything drawn there survives exactly one touch.

   Here for the same reason the meters and the clock are here: this mockup claims to be what the
   panel renders, so when the panel gained a mark, so did it. */
.tile::before, .tile::after {
  content: ""; position: absolute;
  width: calc(10px * var(--px)); height: calc(10px * var(--px));
  border-color: #616573; border-style: solid;
}
.tile::before { top: calc(4px * var(--px)); left: calc(4px * var(--px)); border-width: calc(2px * var(--px)) 0 0 calc(2px * var(--px)); }
.tile::after { bottom: calc(4px * var(--px)); right: calc(4px * var(--px)); border-width: 0 calc(2px * var(--px)) calc(2px * var(--px)) 0; }

/* The seam between the grid and the bar — which page of the profile the deck is showing, as a
   segment of the panel's whole width. `ui::pageSeam` draws exactly this: two device pixels at
   y=192, which is inside the bar's 48 rather than above them. That band is the only one free
   whether the strip carries telemetry or the install invitation, which is why it won.

   Absolute rather than a third flex child: the panel is exactly 240 rows and a row taken here
   would be a row the grid or the bar does not have. Half-width, so the mockup is page 1 of 2. */
.pagemark {
  position: absolute; left: 0;
  top: calc(192px * var(--px));
  width: 100%;
  height: calc(2px * var(--px));
  background: #000;
}
.pagemark i { display: block; width: 50%; height: 100%; background: var(--ok); }

.statusbar {
  height: calc(48px * var(--px));
  background: var(--bar-bg);
  display: flex; align-items: center; gap: calc(4px * var(--px));
  padding: 0 calc(2px * var(--px));
  font-size: calc(6px * var(--px));
  line-height: 1.35;
}
.appicon {
  width: calc(32px * var(--px)); height: calc(32px * var(--px));
  background: var(--press); flex: none;
}
.bartext { flex: 1; min-width: 0; }
.title { color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: clip; }
.row { display: flex; align-items: center; gap: calc(3px * var(--px)); color: var(--ok); }
.row.dim { color: var(--dim); }
.row .time { margin-left: auto; }
.meter {
  width: calc(40px * var(--px)); height: calc(5px * var(--px));
  background: #000; display: inline-block; flex: none;
}
.meter b { display: block; height: 100%; background: var(--ok); }

/* Round, and deliberately the only round thing on the page: it is a clock face, and the
   device really does draw it anti-aliased. */
.clock {
  width: calc(44px * var(--px)); height: calc(44px * var(--px));
  border: calc(1px * var(--px)) solid var(--dim); border-radius: 50%;
  position: relative; flex: none;
}
.clock i, .clock b {
  position: absolute; left: 50%; top: 50%; background: var(--text);
  transform-origin: 0 0;
}
.clock i { width: calc(11px * var(--px)); height: 1px; transform: rotate(-30deg); }
.clock b { width: calc(16px * var(--px)); height: 1px; transform: rotate(115deg); }

.caption { color: var(--dim); font-size: 0.78rem; margin: 0.75rem 0 0; max-width: calc(320px * var(--px)); }

/* ---- cards -------------------------------------------------------------------------- */

.cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr)); gap: 1.5rem; }

/* Four cards, not three: at the 16rem track the last one is left alone on its own row. A wider
   minimum lands them 2x2 at full width and collapses to one column on the same breakpoints. */
#companion .cards { grid-template-columns: repeat(auto-fit, minmax(24rem, 1fr)); }

.card {
  background: var(--bar-bg);
  border: 2px solid #000;
  /* Hard offset, no blur — a sprite's drop shadow, not a photograph's. */
  box-shadow: 6px 6px 0 #000;
  padding: 1.5rem;
  position: relative;   /* for the corner mark below */
}

/* The corner mark, at web scale. Two opposite corners, drawn over the card's own 2px black
   border so the mark reads as part of the edge rather than as something sitting inside it —
   which is exactly where render::corner_mark puts it on a tile.

   This replaced three block glyphs (▚ ▟ ◤) that were recycled across eight headings in no
   particular order. They decorated; they never meant anything.

   Cards only, not .callout: the callout already carries a 6px warn rail down exactly the edge
   the top-left mark would land on, and two marks on one corner is one mark too many. */
.card::before, .card::after {
  content: ""; position: absolute;
  width: 14px; height: 14px;
  border-color: var(--ok); border-style: solid;
}
.card::before { top: -2px; left: -2px; border-width: 2px 0 0 2px; }
.card::after { bottom: -2px; right: -2px; border-width: 0 2px 2px 0; }

/* ---- status vocabulary --------------------------------------------------------------
 *
 * A small fixed set, each with one meaning, used only where something actually has a state:
 *
 *   ●  ready      something is done or available
 *   ○  idle       nothing is happening, and that is correct
 *   !  warn       you should read the next sentence
 *   ×  error      it did not work
 *
 * These are states, so they get glyphs. A feature card is not a state — it gets the corner
 * mark instead, which is why `.card::before` exists and why the three block glyphs that used
 * to head every card are gone. */
.glyph { font-size: 1.3rem; line-height: 1; }
.glyph.ok { color: var(--ok); }
.glyph.press { color: var(--press); }
.glyph.warn { color: var(--warn); }

.card em { color: var(--text); font-style: normal; }

/* ---- specs -------------------------------------------------------------------------- */

table { border-collapse: collapse; width: 100%; max-width: 46rem; }
th, td { text-align: left; padding: 0.7rem 1rem; border-bottom: 2px solid var(--bar-bg); vertical-align: top; }
th { color: var(--dim); font-weight: 400; white-space: nowrap; width: 1%; }

/* ---- download page ------------------------------------------------------------------ */

/* Terminal transcripts, in a page already set in monospace: the box does the work, not the
   typeface. */
/* font-family explicitly, on both: the user-agent stylesheet gives pre and code their own
   `monospace`, which beats inheritance from body — so without this they are the one generic
   font on the page, and that is the font that draws a backslash as a yen sign. */
pre, code { font-family: var(--mono); }

pre {
  max-width: 46rem;
  margin: 0 0 1.25rem;
  padding: 1rem 1.25rem;
  background: #0d0f14;
  border: 2px solid var(--bar-bg);
  border-left: 4px solid var(--press);
  overflow-x: auto;
  font-size: 0.9rem;
  line-height: 1.7;
}
pre b { color: var(--ok); font-weight: 400; }
code { color: var(--ok); }

/* The SmartScreen warning is the first thing a new owner meets, so it gets the device's own
   warning colour rather than being another paragraph they scroll past. */
.callout {
  max-width: 46rem;
  margin: 0 0 1.5rem;
  padding: 1.25rem 1.5rem;
  background: var(--bar-bg);
  border: 2px solid #000;
  border-left: 6px solid var(--warn);
  box-shadow: 6px 6px 0 #000;
}
.callout h3 { margin-bottom: 0.5rem; }
.callout p:last-child { margin-bottom: 0; }

.steps { max-width: 46rem; padding-left: 1.5rem; }
.steps li { margin-bottom: 0.75rem; }

/* ---- footer ------------------------------------------------------------------------- */

footer {
  padding: 2.5rem 1.5rem;
  background: var(--bar-bg);
  border-top: 2px solid #000;
  text-align: center;
}
footer p { margin: 0.35rem 0; }

/* ---- the waitlist's three states ----------------------------------------------------- */

/* The server sets one class on #waitlist and CSS decides what is on screen, so the form, the
   thank-you and the try-again all stay in the HTML as ordinary markup. This is what replaced
   the script that used to guess at a confirmation it could not read. */
#waitlist .signup-done,
#waitlist .signup-err  { display: none; }
#waitlist.joined .signup-done { display: flex; }
#waitlist.joined .signup      { display: none; }
#waitlist.bad .signup-err     { display: flex; }

.signup-err {
  display: flex; align-items: center; gap: 0.6rem;
  max-width: 34rem;
  padding: 0.9rem 1.1rem;
  background: var(--bar-bg);
  border: 2px solid var(--warn);
  box-shadow: 4px 4px 0 #000;
  color: var(--warn);
}

/* Off-screen, deliberately not display:none - see the comment on the field itself. Kept out of
   the tab order and out of the accessibility tree by the markup rather than by this rule. */
.hp {
  position: absolute; left: -9999px;
  width: 1px; height: 1px; overflow: hidden;
}

.signup textarea {
  flex: 1 1 100%;
  font: inherit; font-family: var(--mono);
  padding: 0.7rem 0.9rem;
  color: var(--text); background: var(--bg);
  border: 2px solid var(--bar-bg);
}
.signup textarea:focus { outline: none; border-color: var(--press); }

/* ---- admin ---------------------------------------------------------------------------- */

/* Built from what the rest of the site already has: same palette, same square corners, same
   hard offset shadow, same mono. An admin panel that looked like a different product would be
   a second design language to maintain for an audience of one. */
.admin-grid {
  display: grid; gap: 1.25rem;
  grid-template-columns: repeat(auto-fit, minmax(18rem, 1fr));
  margin: 1.5rem 0;
}

.admin-card {
  padding: 1rem 1.2rem;
  background: var(--bar-bg);
  border: 2px solid #000;
  box-shadow: 4px 4px 0 #000;
}
.admin-card.live { border-color: var(--ok); }
.admin-card h3 { margin-bottom: 0.4rem; }
.admin-card .cta { margin-top: 1rem; }

.admin-files { list-style: none; margin: 0.5rem 0; font-size: 0.9rem; }
.admin-files li { padding: 0.3rem 0; }

/* Tables here can be wider than the column; they scroll in their own box rather than making
   the page scroll sideways. */
.admin-scroll { overflow-x: auto; max-height: 26rem; overflow-y: auto; }
.admin-table { max-width: none; font-size: 0.9rem; }
.admin-table th, .admin-table td { padding: 0.45rem 0.7rem; white-space: nowrap; }
.admin-table th { width: auto; }

.admin-check { display: inline-flex; align-items: center; gap: 0.4rem; color: var(--dim); }

.btn-sm {
  padding: 0.2rem 0.5rem;
  font-size: 0.8rem;
  box-shadow: 2px 2px 0 #000;
}
.btn-sm:active { transform: translate(2px, 2px); box-shadow: none; }
