/* Doto and Kode Mono, self hosted. Both are SIL Open Font License; the
   licence text sits next to the files in web/fonts. Served from this origin so
   the page has no third party to wait on and nothing to tell Google about who
   is reading it. Four files, about 30 KB in total, and a browser only fetches
   the accented set if the text needs it. */

/* Doto, latin-ext */
@font-face {
  font-family: "Doto";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("/fonts/doto-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* Doto, latin */
@font-face {
  font-family: "Doto";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("/fonts/doto-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* Kode Mono, latin-ext */
@font-face {
  font-family: "Kode Mono";
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url("/fonts/kode-mono-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* Kode Mono, latin */
@font-face {
  font-family: "Kode Mono";
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url("/fonts/kode-mono-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* Point par point.
   Doto is a typeface built out of dots, the same material the print head lays
   down, so the name and the button are literally made of the thing the machine
   makes. That, and the printer icon, is where the dots belong. A dot grid
   behind the writing was tried and removed: as a background it stopped being
   material and became noise.

   Light only, on purpose. Thermal paper does not have a dark mode, and the
   whole point of the page is that it looks like the thing it produces.

   One ink, because the printer has one ink. Red appears in exactly two
   places, both of them a stop sign: the service is paused, or the message
   will not fit. Nowhere else. */

:root {
  color-scheme: light;

  --paper: #ffffff;
  --ink: #0e0e0e;
  --dot: #d4d4d4;
  --rule: #e2e2e2;
  --mute: #767676;
  --slot: #c2c2c2;
  --alert: #d71921;

  --field-line: #c4c4c4;

  /* One printed text line is 24 dots tall, so the writing sits on the same
     rhythm the head lays down. */
  --line: 24px;

  --display: "Doto", ui-monospace, monospace;
  --mono: "Kode Mono", ui-monospace, "SF Mono", Menlo, monospace;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--mono);
  font-size: 15px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
}

main {
  max-width: 31rem;
  margin: 0 auto;
  padding: 3.5rem 1.25rem 5rem;
}

:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }

.sr-only {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap; border: 0;
}

/* Small technical labels, set the way the machine would set them. */
.meter, .state {
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  font-variant-numeric: tabular-nums;
}

/* --- head --- */

.head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
}
.head__side { display: flex; align-items: center; gap: 0.7rem; }

h1 {
  margin: 0;
  font-family: var(--display);
  font-weight: 900;
  font-size: clamp(30px, 8vw, 40px);
  line-height: 1;
  letter-spacing: 0.05em;
}

.state {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin: 0;
  color: var(--mute);
}
.state__mark {
  width: 8px; height: 8px; flex: none;
  border: 1px solid var(--mute);
  background: var(--mute);
}
/* Ready and answering: the square is lit. Awake but the printer is asleep: the
   same square, unlit. Those are two different truths and they look different. */
.state--ready { color: var(--ink); }
.state--ready .state__mark { border-color: var(--ink); background: var(--ink); }
.state--warn .state__mark { border-color: var(--ink); background: transparent; }
.state--paused { color: var(--alert); }
.state--paused .state__mark { border-color: var(--alert); background: var(--alert); }

/* How many tickets have come out. Only that number: how many were turned
   away is nobody's business but the desk's. */
.tally {
  margin: 0;
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  font-variant-numeric: tabular-nums;
  color: var(--mute);
}
.tally[hidden] { display: none; }

/* --- the rules, behind an i --- */

.info {
  width: 22px; height: 22px; flex: none; padding: 0;
  border: 1px solid var(--rule);
  background: none;
  color: var(--mute);
  font-family: var(--display);
  font-weight: 900;
  font-size: 13px;
  line-height: 1;
  cursor: pointer;
}
.info:hover, .info[aria-expanded="true"] { border-color: var(--ink); color: var(--ink); }

/* Printed like everything else here: same paper, same tear at the top. */
.notes {
  margin: 1.25rem 0 0;
  padding: 0.9rem 1rem;
  border: 1px solid var(--rule);
  border-top: 1px dashed var(--ink);
}
.notes[hidden] { display: none; }
.notes ul {
  margin: 0; padding: 0; list-style: none;
  display: flex; flex-direction: column; gap: 0.55rem;
}
.notes li {
  position: relative;
  padding-left: 1rem;
  font-size: 12.5px;
  line-height: 1.55;
  color: var(--mute);
}
.notes li::before {
  content: "";
  position: absolute; left: 0; top: 0.6em;
  width: 4px; height: 4px;
  background: var(--ink);
}

.lede {
  margin: 1.5rem 0 2.5rem;
  max-width: 30rem;
  color: var(--mute);
}

/* --- the field --- */

/* The dot grid used to sit behind the writing, on the argument that it was the
   trame the head prints on. It read as noise: it crossed the text, and the box
   stopped looking like something you type into. The dots live in the typeface
   and the icon, which is enough of them. Here, plain paper and a border you
   can actually see. */
.field {
  background: var(--paper);
  border: 1px solid var(--field-line);
  padding: 0.7rem 0.85rem 0.6rem;
}
.field:focus-within { border-color: var(--ink); }

/* The label is on the page rather than only in the accessibility tree, so
   there is no guessing about where the message goes. */
.field-label {
  display: block;
  margin: 0 0 0.5rem;
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--mute);
}

textarea {
  display: block;
  width: 100%;
  min-height: calc(var(--line) * 5);
  padding: 0;
  border: 0;
  background: none;
  color: var(--ink);
  font: inherit;
  line-height: var(--line);
  caret-color: var(--ink);
  resize: vertical;
}
textarea:focus { outline: none; }
textarea::placeholder { color: #9b9b9b; }

/* The optional line: same box as the message, one row tall. */
.field--one { padding: 0; }
.field--one input {
  display: block; width: 100%;
  padding: 0.7rem 0.85rem;
  border: 0; background: none; color: var(--ink);
  font: inherit; line-height: var(--line);
  caret-color: var(--ink);
}
.field--one input:focus { outline: none; }
.field--one input::placeholder { color: #9b9b9b; }

/* "optional" has to read as a note on the label, not as part of it. */
.opt {
  margin-left: 0.5rem;
  letter-spacing: 0.1em;
  color: #a8a8a8;
}

.hint {
  margin: 0.5rem 0 1.75rem;
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--mute);
}

.meter {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  margin: 0.65rem 0 1.5rem;
  color: var(--mute);
}
.count--full { color: var(--alert); }

/* --- button --- */

button#send {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.75rem;
  width: 100%;
  padding: 1rem;
  border: 1px solid var(--ink);
  background: var(--ink);
  color: var(--paper);
  font-family: var(--display);
  font-size: 18px;
  font-weight: 900;
  letter-spacing: 0.12em;
  cursor: pointer;
  transition: background-color 0.12s linear, color 0.12s linear;
}
button#send:hover:not(:disabled) { background: var(--paper); color: var(--ink); }
button#send:active:not(:disabled) { transform: translateY(1px); }
button#send:disabled {
  border-color: var(--rule); background: var(--rule);
  color: var(--mute); cursor: not-allowed;
}

/* The Lucide printer, redrawn dot by dot on the same grid as the rest of the
   page. Painted through a mask rather than dropped in as an image, so it takes
   the button's own colour and inverts with it on hover. */
.send__icon {
  flex: none;
  /* Matches the icon's 24 by 20 viewBox, so contain has nothing to letterbox. */
  width: 1.5em;
  height: 1.25em;
  background: currentColor;
  -webkit-mask-image: url("/printer.svg");
  mask-image: url("/printer.svg");
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-position: center;
  mask-position: center;
  -webkit-mask-size: contain;
  mask-size: contain;
  mask-mode: alpha;
}

/* --- the print ------------------------------------------------------------
   There is no preview any more. Nothing shows the ticket until the button is
   pressed, and then the page itself gets out of the way: it goes white, a slot
   opens, and the paper comes out of it. */

.print {
  position: fixed;
  inset: 0;
  z-index: 20;
  background: var(--paper);
  overflow-y: auto;
  opacity: 0;
  transition: opacity 0.45s ease;
}
.print[hidden] { display: none; }
.print.is-white { opacity: 1; }

.print__stage {
  width: 100%;
  /* One screen pixel per printer dot at full size. 384 while the machine was
     the 58 mm MXW01, 512 since the TRP 100 III - the paper on screen is as
     wide as the paper in the flat, or the animation is of something else. */
  max-width: 512px;
  margin: 0 auto;
  padding: 20vh 1.25rem 5rem;
}

/* The slot. One grey line, and the only thing on screen while the message is
   still travelling: the machine is there and waiting. */
.print__slot {
  height: 3px;
  background: var(--slot);
  transform: scaleX(0);
  transition: transform 0.4s cubic-bezier(0.22, 1, 0.36, 1);
}
.print.is-slot .print__slot,
.print.is-feeding .print__slot,
.print.is-out .print__slot { transform: scaleX(1); }

.print__strip { overflow: hidden; height: 0; }
.print.is-feeding .print__strip,
.print.is-out .print__strip {
  height: var(--strip-h, 200px);
  border-bottom: 1px dashed var(--dot);
}

canvas {
  display: block;
  width: 100%;
  height: auto;
  image-rendering: pixelated;
}

/* The paper does not slide in finished: it is printed, one band of rows at a
   time, and it advances by the same increment each time. The step count and
   the duration come from app.js, sized to the real ticket. */
@keyframes feed {
  from { clip-path: inset(0 0 100% 0); transform: translateY(-8px); }
  to   { clip-path: inset(0 0 0 0);    transform: translateY(0); }
}

.print__note, .print__close {
  opacity: 0;
  transition: opacity 0.3s ease;
}
.print.is-out .print__note, .print.is-out .print__close { opacity: 1; }

.print__note {
  margin: 1.4rem 0 0;
  font-size: 12.5px;
  color: var(--mute);
  min-height: 1.4rem;
}
.print__note--error { color: var(--alert); }

/* A refused message never opens the slot: no line, no paper, and the message
   sits at the top of the stage instead of where a ticket would have hung. */
.print.is-refused .print__slot { transform: scaleX(0); }
.print.is-refused .print__strip { height: 0; border-bottom: 0; }
.print.is-refused .print__note { margin-top: 0; }

.print__close {
  margin-top: 0.5rem;
  padding: 0.55rem 0;
  border: 0;
  border-bottom: 1px solid var(--rule);
  background: none;
  color: var(--ink);
  font: inherit;
  font-size: 13px;
  cursor: pointer;
}
.print__close:hover { border-bottom-color: var(--ink); }

/* --- the invitation --- */

.sheet {
  position: fixed;
  left: 0; right: 0; bottom: 0;
  z-index: 30;
  padding: 0 1.25rem 1.25rem;
  pointer-events: none;
}
.sheet[hidden] { display: none; }

.sheet__ticket {
  max-width: 28.5rem;
  margin: 0 auto;
  padding: 1.35rem 1.35rem 1.15rem;
  background: var(--paper);
  border: 1px solid var(--rule);
  border-top: 1px dashed var(--ink);
  box-shadow: 0 -12px 32px -18px rgba(14, 14, 14, 0.5);
  pointer-events: auto;
  transform: translateY(115%);
  transition: transform 0.42s cubic-bezier(0.22, 1, 0.36, 1);
}
.sheet.is-open .sheet__ticket { transform: translateY(0); }

.sheet h2 {
  margin: 0 0 0.4rem;
  font-family: var(--display);
  font-size: 20px;
  font-weight: 900;
  letter-spacing: 0.05em;
  line-height: 1.15;
}
.sheet p { margin: 0 0 1.1rem; font-size: 13px; color: var(--mute); }

.sheet__cta {
  display: inline-block;
  padding: 0.7rem 1.1rem;
  background: var(--ink);
  color: var(--paper);
  font-size: 13px;
  font-weight: 600;
  text-decoration: none;
  border: 1px solid var(--ink);
  transition: background-color 0.12s linear, color 0.12s linear;
}
.sheet__cta:hover { background: var(--paper); color: var(--ink); }

.sheet__close {
  margin-left: 0.35rem;
  padding: 0.7rem 0.5rem;
  border: 0;
  background: none;
  color: var(--mute);
  font: inherit;
  font-size: 12px;
  cursor: pointer;
}
.sheet__close:hover { color: var(--ink); }

/* --- the end of a season ---

   The last ticket the page prints. Same paper, same one ink, same dashed top
   edge as the notes and the invitation sheet - it reads as something that came
   out of the machine rather than as an error page, which is the difference
   between "it is over" and "it is broken". */
.over {
  margin: 1.5rem 0 0;
  padding: 1.4rem 1.35rem 1.5rem;
  border: 1px solid var(--rule);
  border-top: 1px dashed var(--ink);
}
.over[hidden] { display: none; }

.over__stamp {
  margin: 0 0 0.9rem;
  font-size: 11px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--mute);
}

.over__title {
  margin: 0 0 1rem;
  font-family: var(--display);
  font-size: 26px;
  font-weight: 900;
  letter-spacing: 0.04em;
  line-height: 1.1;
}

.over p { margin: 0 0 0.95rem; font-size: 13.5px; line-height: 1.65; color: var(--mute); }
.over p:last-of-type { color: var(--ink); }

.over__cta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.9rem 1.1rem;
  margin-top: 1.4rem;
  padding-top: 1.3rem;
  border-top: 1px dashed var(--rule);
}

.over__link {
  color: var(--mute);
  font-size: 13px;
  text-decoration: none;
  border-bottom: 1px solid var(--rule);
  padding-bottom: 2px;
  transition: color 0.12s linear, border-color 0.12s linear;
}
.over__link:hover { color: var(--ink); border-bottom-color: var(--ink); }

@media (prefers-reduced-motion: reduce) {
  .print, .print__slot, .sheet__ticket, .print__note, .print__close,
  button#send, .sheet__cta, .over__link { transition: none; }
  canvas { animation: none !important; }
  button#send:active:not(:disabled) { transform: none; }
}
