/* ==========================================================================
   styles.css - All presentation for the PuttyPNG page.

   This file holds every rule that decides how the page looks. It holds no
   behavior. Structure is in index.html and behavior is in scripts.js.

   The file is divided into seven sections. Read them in order and the page
   builds up from raw values, to bare elements, to layout, to components, to
   the way those components answer the user.
   ========================================================================== */

/* ==========================================================================
   SECTION 1 - HEADER / SETUP
   No imports. The body copy is set in system fonts on purpose, so it renders
   instantly. The one display face is served from fonts/ and never over a
   network, and index.html links that sheet before this one.
   ========================================================================== */

/* ==========================================================================
   SECTION 2 - TOKENS AND VARIABLES
   Every color, edge, and shadow value the page uses. Change a value
   here and it changes everywhere.
   ========================================================================== */

:root {
  --ink: #1a1a1a;
  --ink-soft: #444;
  --paper: #ffffff;
  --clay: #c96f52;         /* warm clay / salmon accent */
  --clay-deep: #a8532f;
  --clay-soft: #f7ede9;    /* clay tint for soft fills */
  --line: #ececec;
  --line-strong: #d8d8d8;
  --muted: #6f6f6f;    /* AA-compliant (>=4.5:1) muted text on white */
  --ok: #2e7d32;
  --bad: #c62828;
  --radius: 14px;
  --radius-sm: 9px;
  --shadow: 0 1px 2px rgba(0,0,0,.04), 0 10px 30px rgba(0,0,0,.05);
  --shadow-lift: 0 2px 6px rgba(0,0,0,.06), 0 18px 46px rgba(0,0,0,.09);
  --space: 16px;

  /* THE HOME BOARD.
     One token owns the meter column. The action bar and the deck under it
     both read it, so the slot can never drift away from the donut the disc
     comes out of. scripts.js sets it in pixels from the measured card, never
     as a per cent, because those two rows are different widths and a per cent
     would resolve differently in each of them. */
  --meter-col: 116px;
  --card-pad: 18px;
  --slot-w: calc(2 * var(--card-pad) + var(--meter-col));

  /* How long the board takes to answer a change of disc size. Everything that
     grows with the donut borrows this, so they all move together. */
  --grow: 520ms cubic-bezier(.3, 0, .2, 1);

  /* The display face, served from fonts/. It carries the name and the two
     column headings and nothing else. */
  --display-font: 'Lobster';
  --body-font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;

  /* Softer than the logo's clay, and still over the 3 to 1 a large heading
     needs against white. It measures 3.21 to 1. */
  --clay-soft-ink: #cd7a5c;
  --soft-red: #d9605a;
}

/* ==========================================================================
   SECTION 3 - BASE AND RESET
   Defaults for bare HTML elements, before any class is applied.
   ========================================================================== */

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

body {
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  color: var(--ink);
  background: var(--paper);
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  padding: 0 20px;
}

.wrap { max-width: 920px; margin: 0 auto; }

h2 { font-size: 24px; letter-spacing: -0.02em; }

h3 { font-size: 17px; letter-spacing: -0.01em; }

p { color: var(--ink-soft); }

a { color: var(--clay-deep); }

code, pre, .mono { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }

/* ==========================================================================
   SECTION 4 - LAYOUT
   The boxes that hold the page together: masthead, tabs, panels, cards,
   and the grids inside them.
   ========================================================================== */

/* Header */
/* Padding and title are smaller than the first design. The module has to
   clear the fold at 1366x768, which is the common laptop size. */
header.masthead { text-align: center; padding: 28px 0 16px; }

/* The name is two different materials. Putty takes the display face and the
   clay; PNG stays the body face in black. It leads the page, so it is set
   well above the two column headings under it rather than beside them. */
header.masthead h1 {
  font-family: var(--display-font), cursive;
  font-size: clamp(40px, 7.2vw, 62px);
  letter-spacing: 0;
  font-weight: 400;
  color: var(--clay);
}

header.masthead h1 .png {
  font-family: var(--body-font);
  letter-spacing: -0.035em;
  font-weight: 700;
  color: var(--ink);
}

header.masthead .subtitle {
  font-size: clamp(16px, 2.6vw, 21px);
  margin-top: 8px;
  font-weight: 400;
  color: var(--ink);
}

header.masthead .byline { font-size: 14px; color: var(--muted); margin-top: 10px; }

header.masthead .byline a {
  color: var(--clay-deep); font-weight: 600; text-decoration-color: rgba(168,83,47,.45);
  text-decoration-thickness: 1px; text-underline-offset: 3px;
  transition: color .15s ease, text-decoration-color .15s ease;
}

/* Tabs */
nav.tabs {
  display: flex; justify-content: center; gap: 2px; flex-wrap: wrap;
  border-bottom: 1px solid var(--line);
  margin-bottom: 22px; position: sticky; top: 0;
  background: rgba(255,255,255,.9); backdrop-filter: saturate(1.4) blur(8px);
  padding-top: 6px; z-index: 10;
}

nav.tabs button {
  border: none; background: none; font: inherit; font-size: 15px;
  color: var(--muted); padding: 12px 16px; cursor: pointer;
  border-bottom: 2px solid transparent; margin-bottom: -1px;
  transition: color .15s ease, border-color .15s ease;
}

.tab-panel { display: none; padding-bottom: 100px; }

/* Cards / sections */
.card {
  border: 1px solid var(--line); border-radius: var(--radius);
  padding: 26px; margin: 20px 0; box-shadow: var(--shadow); background: #fff;
}

/* The one module. The wide column carries whichever step is showing and the
   deck keeps its share of the row, so nothing moves sideways on a swap. */
.module {
  display: grid;
  /* The deck is a fixed width, not a fraction. The fascia has to fit six
     values around a window, and that needs a known space to design into. */
  grid-template-columns: minmax(0, 1fr) 250px;
  gap: 20px;
  align-items: start;
  margin: 20px 0;
}

/* Without this a grid child refuses to shrink below its content width and
   the deck gets squeezed out of the row. */
.module-main { min-width: 0; }

.module-deck { min-width: 0; }

.section-title { margin: 44px 0 6px; letter-spacing: -0.02em; }

.section-sub { color: var(--muted); margin-bottom: 8px; }

/* Fields */
.row { display: flex; gap: 12px; flex-wrap: wrap; align-items: center; }

.row > * { min-width: 0; }

.grid2 { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }

/* Snippets */
.foot { text-align: center; color: var(--muted); font-size: 13px; padding: 30px 0 60px; }

.divider { height: 1px; background: var(--line); margin: 22px 0; border: 0; }

/* Docs */
.subnav { display: flex; gap: 6px; flex-wrap: wrap; margin: 6px 0 26px; }

.subnav a {
  font-size: 13px; font-weight: 600; text-decoration: none; color: var(--ink-soft);
  border: 1px solid var(--line-strong); border-radius: 999px; padding: 6px 14px; background: #fff;
  transition: border-color .15s ease, color .15s ease, background .15s ease;
}

.doc-section { scroll-margin-top: 70px; margin-bottom: 10px; }

.doc-section > h2 { margin: 40px 0 4px; }

/* THE HOME BOARD.
   One grid holds the two headings, the two columns, and the deck, so a
   heading can never be separated from the column it names. */
.board {
  display: grid;
  grid-template-columns: 1fr 1fr;
  column-gap: 24px;
  row-gap: 8px;
  grid-template-areas: "hm hl" "cm cl" "sm sl";
  align-items: stretch;
}

.head.hm { grid-area: hm; }
.head.hl { grid-area: hl; }
.col.make { grid-area: cm; }
.col.load { grid-area: cl; }
.deckrow { grid-area: sm; }

.head { display: flex; align-items: baseline; gap: 12px; }

.head h2 {
  font-family: var(--display-font), cursive;
  font-size: clamp(27px, 4.5vw, 41px);
  font-weight: 400;
  letter-spacing: 0;
  margin: 0;
  line-height: 1.15;
  padding-left: 2px;
  color: var(--clay-soft-ink);
}

/* The line marks are kept for a change of mind. Nothing shows them today. */
.head .mark { display: none; color: var(--clay); flex: none; }

/* The two columns are always the same height, whatever either one holds. */
.col {
  display: flex;
  flex-direction: column;
  min-width: 0;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: #fff;
  box-shadow: var(--shadow);
  padding: var(--card-pad);
}

.col > .body { flex: 1; display: flex; flex-direction: column; min-height: 250px; }

/* ==========================================================================
   SECTION 5 - COMPONENTS
   Every reusable piece of interface in its resting state. Its reactions
   to the user live in Section 6.
   ========================================================================== */

* { margin: 0; padding: 0; box-sizing: border-box; }

/* Buttons */
.btn {
  font: inherit; font-weight: 600; font-size: 15px; line-height: 1;
  border-radius: 999px; padding: 12px 20px; cursor: pointer;
  border: 1px solid transparent; transition: transform .08s ease, background .15s ease, border-color .15s ease, color .15s ease;
  display: inline-flex; align-items: center; gap: 8px;
}

.btn-primary { background: var(--ink); color: #fff; }

.btn-clay { background: var(--clay); color: #fff; }

.btn-ghost { background: #fff; color: var(--ink); border-color: var(--line-strong); }

.btn-sm { padding: 8px 14px; font-size: 13px; }

/* Fields */
.field { margin-bottom: 14px; }

.field > label { display: block; font-size: 13px; font-weight: 600; margin-bottom: 6px; color: var(--ink); }

textarea, input[type="text"], input[type="password"], input[type="number"], select {
  width: 100%; font: inherit; font-size: 15px; color: var(--ink);
  border: 1px solid var(--line-strong); border-radius: var(--radius-sm);
  padding: 11px 13px; background: #fff; transition: border-color .15s ease, box-shadow .15s ease;
}

textarea { min-height: 120px; resize: vertical; line-height: 1.5; }

/* Room for the paperclip button in the top right corner. */
#exportText { padding-right: 46px; }

.switch { display: inline-flex; align-items: center; gap: 10px; cursor: pointer; font-size: 14px; }

.switch input { position: absolute; opacity: 0; width: 0; height: 0; }

.switch .track {
  width: 42px; height: 24px; border-radius: 999px; background: var(--line-strong);
  position: relative; transition: background .15s ease; flex: 0 0 auto;
}

.switch .track::after {
  content: ""; position: absolute; top: 2px; left: 2px; width: 20px; height: 20px;
  border-radius: 50%; background: #fff; box-shadow: 0 1px 3px rgba(0,0,0,.3); transition: transform .18s ease;
}

.seg { display: inline-flex; border: 1px solid var(--line-strong); border-radius: 999px; overflow: hidden; }

.seg-opt { padding: 8px 20px; font-size: 14px; font-weight: 600; cursor: pointer; color: var(--ink-soft); transition: background .15s ease, color .15s ease; }

.seg-opt + .seg-opt { border-left: 1px solid var(--line-strong); }

.seg-opt input { position: absolute; opacity: 0; width: 0; height: 0; }

/* Data input: text, drag-drop file, or attached-file chip */
/* Holds the textarea, the file chip, and the paperclip button. */
.data-drop { position: relative; }

.file-chip {
  display: flex; align-items: center; gap: 14px; min-height: 120px;
  border: 1px solid var(--line-strong); border-radius: var(--radius-sm); padding: 14px 16px; background: #fafafa;
}

.file-chip .file-ic { font-size: 30px; line-height: 1; }

.file-chip .file-meta { flex: 1; min-width: 0; }

.file-chip .fn { font-weight: 600; word-break: break-all; }

.file-chip .file-size { display: block; color: var(--muted); font-size: 13px; margin-top: 2px; }

.file-x {
  flex: 0 0 auto; width: 32px; height: 32px; border-radius: 50%; border: 1px solid var(--line-strong);
  background: #fff; color: var(--muted); font-size: 18px; line-height: 1; cursor: pointer;
  transition: border-color .15s ease, color .15s ease;
}

.input-meta { margin-top: 6px; display: flex; gap: 12px; align-items: center; flex-wrap: wrap; }

.slider-row { display: flex; flex-direction: column; gap: 4px; }

.slider-pair { display: flex; gap: 10px; align-items: center; }

.slider-pair input[type="range"] { flex: 1; accent-color: var(--clay); min-width: 0; }

.slider-pair input[type="number"] { width: 64px; flex: 0 0 auto; }

/* Confirm modal */
.modal-overlay-el {
  position: fixed; inset: 0; z-index: 1000; display: flex; align-items: center; justify-content: center;
  background: rgba(0,0,0,.45); padding: 20px;
}

.modal-card {
  background: #fff; border-radius: var(--radius); padding: 24px; width: 100%; max-width: 380px;
  box-shadow: 0 20px 60px rgba(0,0,0,.3);
}

.modal-card h3 { font-size: 18px; margin-bottom: 8px; letter-spacing: -0.01em; }

.modal-card p { font-size: 14px; color: var(--ink-soft); margin-bottom: 18px; }

.modal-actions { display: flex; gap: 8px; justify-content: flex-end; }

/* Advanced drawer */
.drawer-toggle {
  background: none; border: none; font: inherit; font-size: 14px; font-weight: 600;
  color: var(--clay-deep); cursor: pointer; padding: 6px 0; display: inline-flex; align-items: center; gap: 6px;
}

.drawer-toggle .chev { transition: transform .2s ease; }

.drawer {
  overflow: hidden; max-height: 0; transition: max-height .28s ease;
  border-left: 2px solid var(--clay-soft); margin: 6px 0 4px; padding-left: 0;
}

/* Open height is driven by JS: it animates max-height 0 -> scrollHeight, then

   releases it to 'none' on transitionend so the drawer never clips its content,

   no matter how many controls it holds. (No max-height here on purpose, so the

   0 -> px open transition fires transitionend cleanly.) */

.drawer-inner { padding: 14px 2px 4px 16px; }

/* Output preview */
.preview {
  display: flex; gap: 20px; align-items: flex-start; flex-wrap: wrap; margin-top: 8px;
}

.preview .frame {
  background:
    repeating-conic-gradient(#e9e9e9 0% 25%, #f6f6f6 0% 50%) 50% / 18px 18px;
  border: 1px solid var(--line-strong); border-radius: var(--radius-sm); padding: 10px;
  line-height: 0;
}

/* Display size is set by JS from the real PNG dimensions so the frame hugs

   the true aspect ratio (a square PNG shows square, a 2:1 shows 2:1). */

.preview .frame img { width: 200px; height: 200px; image-rendering: pixelated; border-radius: 4px; display: block; }

.preview .meta { flex: 1; min-width: 200px; }

.stat-row { display: flex; justify-content: space-between; gap: 12px; padding: 5px 0; border-bottom: 1px dashed var(--line); font-size: 14px; }

.stat-row .k { color: var(--muted); }

.stat-row .v { font-weight: 600; }

/* Dropzone */
/* The page-wide drop overlay. It covers everything, so it sits above the
   sticky tab bar and the toast has to stay above it in turn. */
.drop-veil {
  position: fixed; inset: 0; z-index: 60;
  background: rgba(255,255,255,.82);
  backdrop-filter: saturate(1.2) blur(3px);
  display: grid; place-items: center;
  padding: 24px;
}

.drop-card {
  border: 2px dashed var(--clay);
  border-radius: 20px;
  background: var(--paper);
  box-shadow: var(--shadow-lift);
  padding: 44px 56px;
  text-align: center;
  max-width: 460px;
}

.drop-icon { color: var(--clay); margin-bottom: 10px; }

.drop-big {
  font-size: 22px; font-weight: 700; letter-spacing: -0.02em;
  color: var(--ink);
}

.drop-sub { font-size: 14px; color: var(--muted); margin-top: 6px; }

/* A drop that could not mean anything. Quiet on purpose: the result it sits
   under is the important thing on screen, and this must not compete with it. */
.bad-drop {
  border: 1px solid var(--line-strong);
  border-left: 3px solid var(--bad);
  border-radius: var(--radius-sm);
  background: #fdf7f7;
  padding: 14px 16px;
  margin-top: 14px;
}

.bad-drop-head { font-weight: 700; font-size: 14px; color: var(--bad); }

.bad-drop-msg { font-size: 14px; color: var(--ink-soft); margin-top: 3px; }

/* The code is for a search box or a support message, not for reading first. */
.bad-drop-code { font-size: 12px; color: var(--muted); margin-top: 8px; }

/* Make sits at the right, nearest the deck. Advanced goes under it. */
.make-row { justify-content: flex-end; }

.adv-row { justify-content: flex-end; margin-top: 8px; }

/* A step is a card by another name. It is kept separate from .card so the
   module can change padding and shadow without touching every card. */
.step {
  border: 1px solid var(--line); border-radius: var(--radius);
  padding: 26px; box-shadow: var(--shadow); background: var(--paper);
}

.step > h2 { margin-bottom: 2px; }

/* Back sits above the result heading and reads as a control, not a link. */
.back {
  border: 1px solid var(--line-strong); background: var(--paper);
  font: inherit; font-size: 13px; color: var(--muted);
  padding: 5px 12px 5px 9px; border-radius: 999px; cursor: pointer;
  margin-bottom: 14px; display: inline-flex; align-items: center; gap: 5px;
  transition: color .15s ease, border-color .15s ease, background .15s ease;
}

.back .back-ic { font-size: 16px; line-height: 1; }

/* The deck. A square slot with a lip along the bottom edge. Phase 2 turns
   the lip into the front of a machine and adds the eject motion. */
/* The deck reads as a machine with a slot in it. Taller than wide, because
   the lower third is the face the disc comes out of. */
/* TALLER THAN THE DISC, ON PURPOSE.
   The bay has to be deeper than the disc is tall. Otherwise the disc cannot
   rise clear of the lip, and the layer change at the top of the move has to
   happen while the disc still overlaps the face, where it can be seen. */
.slot {
  position: relative;
  /* One name for the height of the face. The opening, the shade and the face
     itself all read it, so they can never drift apart. */
  --face-h: 50.8%;
  /* Was 1 / 1.7. The face grew by the height of the button, and the slot grew
     with it, so the opening stays 231px down and the picture is untouched.
     The deck is still shorter overall: the button left the row it had below
     the slot, which cost more than this. */
  aspect-ratio: 1 / 1.88;
  border: 1px solid var(--line); border-radius: var(--radius);
  background: #3a3a41;         /* the bay covers this; it stops a white edge */
  overflow: hidden;
  perspective: 900px;          /* gives the tilt real depth */
}

/* z0 THE BAY. A recess, not a panel. The hard shadow under its own top edge
   is what tells the eye it has depth. */
/* A medium grey where the picture sits, falling to near black at the opening.
   RULE: the dark end is not a matter of taste. The shade fades a moving disc
   into this exact colour, and the black line needs something lighter above it
   to read against. Carrying the grey all the way down would take both away. */
.bay {
  position: absolute; inset: 0; z-index: 0;
  background:
    radial-gradient(120% 62% at 50% 26%, rgba(255,255,255,.13), rgba(255,255,255,0) 72%),
    linear-gradient(180deg, #5e5e66 0%, #4a4a52 26%, #2e2e35 62%, #1c1c20 100%);
}

.bay::before {
  content: ""; position: absolute; left: 0; right: 0; top: 0; height: 7%;
  background: linear-gradient(180deg, rgba(0,0,0,.6), rgba(0,0,0,0));
}

.bay::after {
  content: ""; position: absolute; inset: 0;
  box-shadow: inset 6px 0 14px -7px rgba(0,0,0,.9),
              inset -6px 0 14px -7px rgba(0,0,0,.9);
}

/* z6 THE SHADE. What covers the disc up as it reaches the opening, so it is
   swallowed rather than clipped by a hard edge.
   RULE: this sits ABOVE the disc in every state, including when the disc is
   in front of the machine. If it did not, the shading would jump off the disc
   the moment the disc changed layer, which is the fault it exists to hide.
   It stops at the line, so it never overlaps the face and a high z-index
   costs nothing.
   It is short and steep on purpose. A tall soft one dimmed a disc that was
   sitting at rest, which is meant to be on show, not on its way in.
   RULE: it fades to the colour of the bay floor, NOT to black. Fading to
   black would leave the area above the opening as dark as the opening itself,
   and the black line would have nothing to stand out against. The disc has to
   disappear into the machine, and the line has to stay a line. */
.slot-shade {
  position: absolute; left: 0; right: 0;
  bottom: calc(var(--face-h) + 4px);
  height: 44px; z-index: 6;
  background: linear-gradient(180deg,
    rgba(28,28,32,0) 0%, rgba(28,28,32,.30) 44%, rgba(28,28,32,.80) 74%, rgba(28,28,32,1) 100%);
  /* Held back while nothing is moving. The resting place is only 13px above
     the line, so a shade at full strength would eat the bottom edge of a disc
     that has been handed back, which is meant to be on show. */
  opacity: .25;
  transition: opacity .25s ease;
  pointer-events: none;
}

/* On its way in, at full strength, so the disc is swallowed rather than cut
   off. The holder is drawn before the shade, so the shade can be styled from
   the state of the disc without any code being told about it. */
.disc-holder:not(.out) ~ .slot-shade { opacity: 1; }

/* Every transform lives on the holder. The image keeps the width and height
   the page sets from the real PNG, so the two never fight. */
.disc-holder {
  position: absolute; left: 30px; right: 30px; top: 26px;
  display: grid; place-items: center;
  z-index: 1;
  transform-origin: 50% 60%;
  will-change: transform;
  /* Gives the picture's own lean depth. The holder carries the travel, the
     picture inside carries the lean, so the two can never fight over one
     transform property. */
  perspective: 800px;
}

.slot .disc {
  width: 100%; height: auto;
  border-radius: 4px; display: block;
  /* SMOOTH, on purpose. A 256px cover drawn in a 190px box is a fractional
     scale, and nearest neighbour at a fractional scale breaks the rim text and
     stair-steps the edge. Nothing is lost by smoothing: the element is an
     image whose source is the file itself, so a right-click, a Copy and a
     Download all hand over the original 256px PNG whatever this says.
     The raw pixels are on show in the Pixels view instead. */
  image-rendering: auto;
  /* A drop-shadow filter, NOT a box shadow. A box shadow is the shadow of
     the element's rectangle, and a CD cover is transparent outside its circle,
     so a box shadow paints a dark square that has nothing casting it. It could
     not be seen against a near black bay. Against a grey one it is a halo. */
  filter: drop-shadow(0 5px 12px rgba(0,0,0,.5));
}

/* The face. Opaque, so the disc is hidden behind it and not cut off. */
.slot-face {
  position: absolute; left: 0; right: 0; bottom: 0; height: var(--face-h);
  z-index: 3;
  background: linear-gradient(180deg, #f0f0f0, #e6e6e6);
  display: flex; flex-direction: column; justify-content: center;
  padding: 8px 9px;
}

/* THE BUTTON, ON THE FASCIA.
   A key in a sunken shelf, so it reads as part of the machine.
   RULE: an empty drive hides the button but KEEPS its space. The face is a
   fixed share of the slot, and letting the row collapse would move the six
   values every time a disc came or went. */
.drive-shelf {
  padding: 6px; margin-bottom: 8px;
  border-radius: var(--radius-sm);
  background: linear-gradient(180deg, #dcdce0, #e8e8ec);
  box-shadow: inset 0 2px 4px rgba(0,0,0,.18), inset 0 -1px 0 rgba(255,255,255,.85);
}

.drive-shelf.empty { visibility: hidden; }

/* The lamp says the drive is running without anything having to be read. */
.drive-lamp {
  width: 6px; height: 6px; border-radius: 999px;
  background: #b9b9bd;
  box-shadow: inset 0 0 2px rgba(0,0,0,.45);
  transition: background .25s ease, box-shadow .25s ease;
}

.drive-lamp.lit {
  background: #63d16a;
  box-shadow: 0 0 6px rgba(99,209,106,.9), inset 0 0 2px rgba(0,0,0,.25);
}

/* THE FASCIA.
   The window keeps the middle. Three values a side, wing and wing.
   The height never changes, so nothing on the page moves when a disc comes
   and goes. Only the values and the window fade. */
.fascia {
  display: flex; align-items: center; gap: 6px;
  width: 100%;
}

.fascia .wing { flex: 1 1 0; min-width: 0; }

.fascia .cell {
  display: flex; justify-content: space-between; gap: 5px;
  border-bottom: 1px solid #9a9a9a;
  padding: 3px 0; margin-bottom: 4px;
}

.fascia .cell:last-child { margin-bottom: 0; }

.fascia .k { font-size: 8px; color: var(--muted); }

.fascia .v {
  font-size: 9.5px; font-weight: 700; color: var(--ink);
  transition: color .3s ease;
}

/* A drive with nothing in it keeps every label and shows no reading. */
.fascia .wing:not(.lit) .v { color: var(--muted); }

/* z4 THE LINE. The opening the disc goes into, and rises back out of. Hard
   edged and black, because that is what the front of a slot drive looks
   like. */
.slot-line {
  position: absolute; left: 0; right: 0; bottom: var(--face-h);
  height: 4px; z-index: 4;
  background: #000;
  /* A hairline of light along the top edge, which is the far side of the
     opening catching the room. It stops the line reading as a smudge. */
  box-shadow: inset 0 1px 0 rgba(255,255,255,.10);
}

/* The chamfer below the line, where the light catches the front edge. It is
   drawn here rather than as a third layer, because it belongs to the opening
   and never moves apart from it. */
.slot-line::after {
  content: ""; position: absolute; left: 0; right: 0; top: 100%;
  height: 5px;
  background: linear-gradient(180deg, #fdfdfd 0%, #dedee1 55%, #c9c9ce 100%);
  box-shadow: 0 2px 5px rgba(0,0,0,.14);
}

/* THE DRIVE.
   A window in the face, a button that says what happens next, and a slim
   footer that reads like the display on a real drive. */
/* THE SQUARE IS THE TARGET, not the circle. A round hover area 64px across is
   hard to hold, so the square around the window takes the pointer. */
.glass-wrap {
  position: relative;
  flex: 0 0 64px; width: 64px; height: 64px;
}

/* The same two acts as on the picture, on a card over the window.
   Icons alone: two named buttons will not fit in 64px, and the picture spells
   both of them out in full. */
/* BELOW the window, not above it. The button sits directly above, and a card
   there covered its words. Below is the same distance away and covers
   nothing. */
.win-tools {
  position: absolute; left: 50%; top: 100%; z-index: 20;
  transform: translate(-50%, 3px);
  display: flex; gap: 3px; padding: 3px;
  border: 1px solid var(--line-strong); border-radius: 8px;
  background: var(--paper);
  box-shadow: 0 4px 12px rgba(0,0,0,.18);
  opacity: 0; pointer-events: none;
  transition: opacity .16s ease;
}

/* Only when there is a disc in there to act on. Focus counts as well as
   hover, or the two buttons could never be reached from a keyboard. */
.glass-wrap.has-disc:hover .win-tools,
.glass-wrap.has-disc:focus-within .win-tools {
  opacity: 1; pointer-events: auto;
}

.win-btn {
  width: 22px; height: 22px; border-radius: 6px;
  border: 0; background: none; color: var(--ink);
  display: grid; place-items: center; padding: 0; cursor: pointer;
  transition: color .15s ease, background .15s ease;
}

.win-btn:hover { background: var(--clay-soft); color: var(--clay-deep); }
.win-btn:focus-visible { outline: 2px solid var(--clay); outline-offset: 1px; }

.glass {
  position: absolute; inset: 0;
  border-radius: 999px;
  background: #1d1d1f;
  box-shadow: inset 0 2px 6px rgba(0,0,0,.55), 0 1px 0 rgba(255,255,255,.7);
  overflow: hidden;
  transition: box-shadow .25s ease;
}

/* Loaded. The rim picks up a little light, so a running drive can be told
   from an empty one without reading the values. */
.glass.lit {
  box-shadow: inset 0 2px 6px rgba(0,0,0,.55),
              inset 0 0 0 1px rgba(255,255,255,.12),
              0 1px 0 rgba(255,255,255,.7);
}

/* Everything drawn inside the window sits on top of the other. */
.glass > * {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  transition: opacity .3s linear;
}

/* A pane of glass over the disc, so it reads as a window and not a hole. */
.glass::after {
  content: ""; position: absolute; inset: 0; border-radius: 999px; z-index: 7;
  background: linear-gradient(145deg, rgba(255,255,255,.42), rgba(255,255,255,0) 55%);
  pointer-events: none;
}

/* THE DISC IN THE WINDOW.
   It slides in from above and is cut off by the window, which is what sells
   it as going into a machine. It does not turn until it is all the way in. */
.spin-wrap {
  transform: translateY(-104%);
  transition: transform 220ms cubic-bezier(.35, 0, .2, 1);
}

.spin-wrap.seated { transform: translateY(0); }

/* One turning layer carries all three levels of blur. Cross-fading between
   them changes the blur without touching the turn, so the disc never jumps
   as it winds up. */
.spin-rotor { position: absolute; inset: 0; }

.spin-rotor.turning { animation: spin-turn 1.4s linear infinite; }

.spin-frame {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  transition: opacity .3s linear;
}

@keyframes spin-turn { to { transform: rotate(360deg); } }

.drive-btn {
  display: flex; align-items: center; justify-content: center; gap: 7px;
  width: 100%;
  border: 1px solid #b4b4b9; border-radius: 6px;
  background: linear-gradient(180deg, #ffffff, #e9e9ed);
  font: inherit; font-size: 12px; font-weight: 700;
  color: var(--ink); padding: 7px 10px; cursor: pointer;
  box-shadow: 0 1px 2px rgba(0,0,0,.16), inset 0 1px 0 #fff;
  transition: color .15s ease, border-color .15s ease;
}

.drive-btn:active { transform: translateY(1px); box-shadow: none; }

.drive-ic { display: flex; }

/* Save and Copy live on the picture, because that is what they act on.
   AT THE FOOT, CENTRED, not in a corner. A generated CD cover is transparent
   outside the disc, so a corner button would float over the bay instead of
   sitting on the picture it acts on. The centre of the foot is on the disc at
   every cover size. They cover a strip only, so the rest of the picture is
   still there to right-click or press and hold. */
/* STACKED, not side by side. Named buttons are far wider than the icons they
   replaced, and a generated CD cover is a circle: a row 167px wide had its
   corners off the edge of the disc, floating over the bay. A column is narrow
   enough to keep every corner on the picture at this height. */
.disc-tools {
  /* 8% up. The column has to clear the bottom of the circle, because a cover
     is transparent outside the disc, and it must not climb into the middle of
     the picture either. Three buttons at 22px fit between the two. */
  position: absolute; left: 50%; bottom: 8%;
  transform: translateX(-50%);
  display: flex; flex-direction: column; align-items: center; gap: 4px;
  opacity: 0;
  transition: opacity .18s ease;
}

.disc-btn {
  display: inline-flex; align-items: center; gap: 4px;
  height: 22px; padding: 0 8px; border-radius: 999px;
  border: 1px solid rgba(0,0,0,.12);
  background: rgba(255,255,255,.94);
  color: var(--ink); cursor: pointer;
  font: inherit; font-size: 10px; font-weight: 700;
  box-shadow: 0 1px 4px rgba(0,0,0,.18);
  transition: color .15s ease, background .15s ease;
}

/* The drive as a sheet, on a phone only. Hidden on a wide screen. */
.deck-handle {
  display: none;
  width: 100%; border: 0; background: none; padding: 10px 0 8px;
  cursor: pointer;
}

.deck-handle .grip {
  display: block; width: 42px; height: 4px; margin: 0 auto;
  border-radius: 999px; background: var(--line-strong);
}

/* ADVANCED, IN SIX GROUPS.
   One group on screen at a time. Every control is still one click away, and
   the drawer no longer opens onto a column of forty five fields. */
.subtabs {
  display: flex; flex-wrap: wrap; gap: 2px;
  border-bottom: 1px solid var(--line);
  margin-bottom: 16px;
}

.subtabs button {
  border: none; background: none; font: inherit; font-size: 13px;
  color: var(--muted); padding: 7px 11px; cursor: pointer;
  border-bottom: 2px solid transparent; margin-bottom: -1px;
  transition: color .15s ease, border-color .15s ease;
}

.subpanel { min-height: 10px; }

/* THE CELEBRATION.
   Ribbons fall from the thing that was made. Above the page and the drop
   overlay, below the toast, so a message is never hidden by a party. */
.confetti-host {
  position: fixed; inset: 0; z-index: 95;
  pointer-events: none; overflow: hidden;
}

.ribbon {
  position: absolute;
  will-change: transform, opacity;
}

.ribbon svg { display: block; }

/* The ripple is an animation on the path, not a redraw, so sixty ribbons cost
   almost nothing. Each starts at its own point in the cycle. */
.ribbon .rib {
  transform-origin: 50% 50%;
  animation: ribbon-ripple 620ms ease-in-out infinite;
}

@keyframes ribbon-ripple {
  0%   { transform: rotate(-9deg) skewX(7deg); }
  50%  { transform: rotate(9deg) skewX(-7deg); }
  100% { transform: rotate(-9deg) skewX(7deg); }
}

/* Reduced motion: the ribbons appear and fade where they are. */
.ribbon.fade-only .rib { animation: none; }

/* THE SHOW.
   Layers the animations draw into. All of them are added and removed by the
   page, and none of them survive a finished animation.

   z-index 1 puts them level with the disc and below the slot face, so a piece
   that falls goes behind the face instead of over it. */
/* Fixed and unclipped, so a kill crosses the whole page instead of stopping
   at the edge of the deck. Above the drop overlay at 60, below the toast at
   100 and the modal at 1000, both of which must stay on top. */
.show-layer {
  position: fixed; inset: 0; z-index: 90;
  pointer-events: none;
}

.kill-host {
  position: absolute; inset: 0;
  pointer-events: none;
}

.kill-piece {
  position: absolute;
  background-size: 100% 100%;
  background-repeat: no-repeat;
  border-radius: 4px;
  will-change: transform, opacity;
}

.kill-canvas {
  position: absolute;
  pointer-events: none;
}

/* The ones and zeros travel from the data box to the slot, so they sit above
   the page rather than inside the deck. */
.ingest-host {
  position: fixed; inset: 0; z-index: 55;
  pointer-events: none;
}

.sprite { position: absolute; width: 0; height: 0; overflow: hidden; }

.bit {
  position: absolute;
  width: 11px; height: 15px;
  overflow: visible;            /* the outer edge is drawn past the box */
  will-change: transform, opacity;
}

/* A digit culled before it lands. The move and the fade are pinned in the page
   code, because the value they are pinned at is only known while it runs.
   Only the flare belongs here, and it is a filter rather than a text shadow
   because a digit is a drawn shape now, not a letter. */
.bit.spark {
  filter: drop-shadow(0 0 5px var(--clay)) drop-shadow(0 0 13px rgba(201, 111, 82, .85));
}

/* The paperclip sits inside the data box.
   It is top right, not bottom right, because the textarea resize grip owns
   the bottom right corner and the two would overlap. */
.clip {
  position: absolute; top: 8px; right: 8px;
  width: 30px; height: 30px; border-radius: 999px;
  border: 1px solid var(--line-strong); background: rgba(255,255,255,.92);
  color: var(--muted); cursor: pointer;
  display: grid; place-items: center; padding: 0;
  transition: color .15s ease, border-color .15s ease, background .15s ease;
}

.dropzone {
  border: 2px dashed var(--line-strong); border-radius: var(--radius);
  padding: 30px 20px; text-align: center; cursor: pointer; background: #fcfcfc;
  transition: border-color .15s ease, background .15s ease;
}

.dropzone .big { font-size: 16px; font-weight: 600; margin-bottom: 4px; }

.dropzone .hint { font-size: 13px; color: var(--muted); }

.result-text {
  width: 100%; min-height: 90px; font-size: 13px; background: #fafafa;
  border: 1px solid var(--line); border-radius: var(--radius-sm); padding: 12px; white-space: pre-wrap; word-break: break-word;
}

.badges { display: flex; gap: 8px; flex-wrap: wrap; margin: 8px 0; }

.badge { font-size: 12px; border-radius: 999px; padding: 3px 10px; background: var(--clay-soft); color: var(--clay-deep); border: 1px solid #eadfd9; }

/* Toast */
#toast {
  position: fixed; left: 50%; bottom: 26px; transform: translateX(-50%) translateY(20px);
  background: var(--ink); color: #fff; padding: 12px 18px; border-radius: 999px;
  font-size: 14px; box-shadow: var(--shadow-lift); opacity: 0; pointer-events: none;
  transition: opacity .2s ease, transform .2s ease; z-index: 100; max-width: 90vw;
}

/* Snippets */
.snippet { border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden; margin: 14px 0; box-shadow: var(--shadow); }

.snippet-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 12px 16px; background: #fafafa; border-bottom: 1px solid var(--line); }

.snippet-head .n { display: inline-flex; align-items: center; gap: 10px; font-weight: 700; letter-spacing: -0.01em; }

.snippet-head .n .num { background: var(--clay); color: #fff; width: 24px; height: 24px; border-radius: 50%; display: inline-flex; align-items: center; justify-content: center; font-size: 13px; }

.snippet pre { margin: 0; padding: 16px; overflow-x: auto; font-size: 12.5px; line-height: 1.5; background: #fff; max-height: 340px; overflow-y: auto; }

.snippet .cap { font-size: 13px; color: var(--muted); padding: 0 16px 14px; }

/* Docs */
.api-sig {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 14px; background: var(--clay-soft); color: var(--clay-deep);
  border-radius: var(--radius-sm); padding: 10px 14px; margin: 8px 0 14px; overflow-x: auto; white-space: pre;
}

table.doc { width: 100%; border-collapse: collapse; font-size: 14px; margin: 10px 0 6px; }

table.doc th, table.doc td { text-align: left; padding: 9px 12px; border-bottom: 1px solid var(--line); vertical-align: top; }

table.doc th { color: var(--muted); font-weight: 600; font-size: 12px; text-transform: uppercase; letter-spacing: .04em; }

table.doc td.mono, table.doc td .mono { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 13px; }

.err-code { font-weight: 700; color: var(--clay-deep); font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }

.license { font-size: 12.5px; line-height: 1.7; white-space: pre-wrap; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; color: var(--ink-soft); }

.big-dl { text-align: center; padding: 40px 20px; }

.big-dl .lock { font-size: 40px; }

/* Collapsible section, built on the browser's own details element. It needs no
   script, so it still opens and closes if scripts.js fails to load. */
details.sect { padding: 16px 18px; }

details.sect > summary {
  list-style: none;
  cursor: pointer;
  display: flex;
  flex-direction: column;
  gap: 4px;
}

/* Safari draws its own triangle unless this is removed. */
details.sect > summary::-webkit-details-marker { display: none; }

.sect-title {
  font-size: 20px;
  font-weight: 700;
  letter-spacing: -0.01em;
  color: var(--ink);
}

.sect-sub { font-size: 14px; color: var(--muted); }

.chev-d {
  display: inline-block;
  color: var(--clay);
  font-weight: 700;
  transition: transform .18s ease;
}

.sect-body { margin-top: 14px; }

/* The moved sections carried their own card, which would nest a card inside a
   card now that the details element is the card. */
.sect-body > .card { border: 0; box-shadow: none; padding: 0; margin: 0; }

/* ==========================================================================
   HOME - MAKE
   ========================================================================== */

/* THE INVITATION LIVES INSIDE THE BOX. Anywhere else it interrupts the read
   down the column. It rests as an icon in the corner and opens leftward under
   the pointer to say what it is. */
.makebox { position: relative; flex: 1; display: flex; min-height: 0; }

#makeText { flex: 1; resize: none; min-height: 0; padding-bottom: 44px; }

/* The answer to a file held over the box. It matches what the Load zone does
   with a disc, so the two sides of the page behave alike. */
.makeveil {
  position: absolute; inset: 0; z-index: 6;
  display: grid; place-content: center; justify-items: center; gap: 6px;
  text-align: center; pointer-events: none;
  opacity: 0; transition: opacity .15s;
  border-radius: var(--radius-sm);
}

.makeveil svg { color: var(--clay); }

.makeveil .big { font-size: 18px; font-weight: 700; letter-spacing: -.02em; color: var(--clay-deep); }

/* ONE GHOST TREATMENT. It never fills in. The ground stays white and only the
   outline and the mark darken, so a quiet control answers without becoming
   the loudest thing on the card. The attach button and the bin both wear it. */
.ghost {
  background: #fff;
  color: var(--muted);
  border: 1px solid var(--line-strong);
  cursor: pointer;
  transition: color .15s, border-color .15s, transform .12s;
}

/* Held clear of the scrollbar, which appears as soon as the box fills. */
.attach {
  position: absolute; right: 24px; bottom: 12px;
  display: flex; align-items: center; gap: 0;
  font: inherit; font-size: 12.5px; font-weight: 600;
  border-radius: 999px; padding: 6px 8px; overflow: hidden;
}

.attach svg { flex: none; }

.attach .say {
  max-width: 0; overflow: hidden; white-space: nowrap;
  opacity: 0; margin-left: 0;
  transition: max-width .28s cubic-bezier(.3,0,.2,1), margin-left .28s, opacity .2s;
}

/* An attachment covers the box, because it IS what will be pressed. The whole
   panel is the button, and pressing it puts the box back. */
.attached {
  position: absolute; inset: 0;
  display: none; place-items: center; padding: 16px;
  background: #fff; border-radius: var(--radius-sm);
}

.chip {
  display: inline-flex; align-items: center; gap: 7px;
  font: inherit; font-size: 12.5px; font-weight: 600;
  color: var(--ink); background: #fff; cursor: pointer;
  border: 1px solid var(--line-strong); border-radius: 999px; padding: 6px 13px;
  transition: color .15s, border-color .15s, background .15s;
}

.chip svg { flex: none; }

/* EVERY CHIP IS A WHOLE BUTTON. Nothing here asks the pointer to find a small
   target inside a larger one. */
.pill {
  display: inline-flex; align-items: center; gap: 8px;
  font: inherit; font-size: 12.5px;
  color: var(--ink); background: #fff; cursor: pointer; text-align: left;
  border: 1px solid var(--line-strong); border-radius: 999px; padding: 8px 14px;
  min-width: 0; max-width: 100%; position: relative;
  transition: color .15s, border-color .15s, background .15s, transform .12s;
}

/* A class that sets display outranks the browser's own rule for the hidden
   attribute, so the chip needs this to be able to hide at all. */
.pill[hidden] { display: none; }

.pill .ic { flex: none; color: var(--clay-deep); display: grid; }
.pill span.nm { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 600; }
.pill .sz { color: var(--muted); flex: none; }
.pill .act-ic { flex: none; display: grid; color: var(--muted); }
.pill button { border: 0; background: none; font: inherit; color: var(--muted); cursor: pointer; padding: 0 4px; }

/* ==========================================================================
   HOME - THE ACTION BAR AND THE METER
   ========================================================================== */

/* The donut and the button share a row and are centred against each other.
   The reading drops to a row of its own beneath the donut, so nothing pushes
   the button off centre. */
.actbar {
  display: grid;
  grid-template-columns: var(--meter-col) 1fr;
  column-gap: 12px; row-gap: 4px;
  grid-template-areas: "donut make" "bytes .";
  align-items: center;
  margin-top: 14px;
  transition: grid-template-columns var(--grow);
}

.readbox { grid-area: bytes; text-align: center; }

/* THE LABEL GROWS AS THE BUTTON SHRINKS, WHICH IS THE JOKE.
   The donut takes more of the row at every rung, so the button has to give
   the width up. It wraps rather than overflows, and at the tightest rung it
   comes apart to one word a line, which is the point being made. */
.act {
  grid-area: make; align-self: center;
  font: inherit; font-weight: 700; font-size: clamp(12.5px, 1.05vw + 3px, 15px);
  line-height: 1.25; padding: 12px 14px; border-radius: 999px; min-width: 0;
  border: 1px solid transparent; background: var(--ink); color: #fff; cursor: pointer;
  overflow-wrap: anywhere; text-wrap: balance;
  transition: background .15s, transform .08s;
}

.act em {
  font-family: var(--display-font), cursive; font-style: italic;
  font-size: 1.3em; font-weight: 400; letter-spacing: .01em; padding: 0 1px;
}

.meterwrap {
  grid-area: donut; position: relative; text-align: center; align-self: center;
}

#meter { width: 100%; height: auto; aspect-ratio: 1 / 1; display: block; margin: 0 auto; }

.usage {
  font: 700 11px/1.25 ui-monospace, Consolas, monospace;
  color: var(--ink); margin-top: 1px;
  transition: font-size var(--grow);
}

.usage i { display: block; font-style: normal; font-weight: 400; color: var(--ink-soft); }

.rungno { font: 400 10px/1.3 ui-monospace, Consolas, monospace; color: #a6a6ad; letter-spacing: .06em; }

/* ==========================================================================
   HOME - THE DECK AND THE DISC
   ========================================================================== */

/* Everything here is worked out from the one token, so the slot and the disc
   grow at the same moment and the same rate as the donut above them. */
.deckrow { position: relative; height: calc(var(--slot-w) + 34px); transition: height var(--grow); }

.tray {
  position: absolute; left: 0; top: 0;
  width: var(--slot-w); height: calc(var(--slot-w) + 28px);
  /* Only the TOP of this clip matters: it is what hides the disc above the
     slot. The other three sides are held wide so nothing inside is ever cut,
     including the two chips when they sit under the disc on a narrow screen. */
  clip-path: inset(0 -320px -220px -40px);
  transition: width var(--grow), height var(--grow);
}

.lip {
  position: absolute; left: 0; right: 0; top: 0; height: 5px; z-index: 6;
  background: #0b0b0e; border-radius: 3px; box-shadow: 0 2px 7px rgba(0,0,0,.30);
}

.lip::after {
  content: ""; position: absolute; left: 0; right: 0; top: 100%; height: 4px;
  border-radius: 0 0 3px 3px;
  background: linear-gradient(180deg, #f4f4f6, #dedee1 55%, #c4c4ca);
}

.cd {
  position: absolute; left: 0; top: 9px;
  width: var(--slot-w); height: var(--slot-w);
  cursor: grab; touch-action: none; z-index: 8;
  filter: drop-shadow(0 8px 16px rgba(0,0,0,.30));
  transform: translateY(calc(-1 * (var(--slot-w) + 20px)));
  opacity: 0;
  transition: transform 620ms cubic-bezier(.2,.85,.25,1), opacity .2s ease,
              width var(--grow), height var(--grow);
}

/* The pair is centred on the disc, but the two chips align to each other so
   their marks form one column rather than drifting under words of different
   lengths. */
.cdtools {
  position: absolute; left: 0; width: var(--slot-w);
  top: calc(var(--slot-w) * 0.66); z-index: 9;
  display: flex; flex-direction: column; align-items: center; gap: 4px;
  opacity: 0; pointer-events: none;
  transition: opacity .15s, width var(--grow), top var(--grow);
}

.cdtools button {
  display: inline-flex; align-items: center; gap: 6px;
  font: inherit; font-size: 10.5px; font-weight: 700; color: #fff; cursor: pointer;
  background: rgba(20,20,24,.88); border: 0; border-radius: 999px; padding: 5px 11px;
  min-width: 96px;
}

.cdtools button svg { flex: none; }

/* THE TWO THINGS YOU CAN DO WITH A FRESH DISC, STACKED BESIDE IT.
   The arrow points at the invitation only. The bin sits beneath it, indented
   past the arrow so the two chips line up. */
.cdtip {
  position: absolute;
  left: calc(var(--slot-w) + 14px); top: calc(var(--slot-w) * 0.32); z-index: 8;
  display: flex; flex-direction: column; align-items: flex-start; gap: 8px;
  opacity: 0; transform: translateX(-8px); pointer-events: none;
  transition: opacity .25s ease, transform .25s ease, left var(--grow), top var(--grow);
}

.tiprow { display: flex; align-items: center; gap: 6px; }

.cdtip .arrow { color: var(--clay); flex: none; }

.cdtip .say {
  display: inline-flex; align-items: center; gap: 7px;
  font: inherit; font-size: 12px; font-weight: 600; white-space: nowrap;
  border: 0; cursor: pointer;
  background: var(--ink); color: #fff; padding: 8px 14px; border-radius: 999px;
  transition: background .15s, transform .12s;
}

/* Its right edge lines up with the invitation's, so the stack reads as one
   block rather than two chips that happen to be near each other. */
.bin {
  align-self: flex-end;
  display: inline-flex; align-items: center; gap: 7px;
  font: inherit; font-size: 12px; font-weight: 600; white-space: nowrap;
  padding: 8px 14px; border-radius: 999px;
}

.bin svg { flex: none; }

/* ==========================================================================
   HOME - LOAD
   ========================================================================== */

/* The whole body of the column is the target, and it carries its own
   instructions rather than putting them anywhere else. */
.zone {
  position: relative;
  border: 2px dashed var(--line-strong); border-radius: var(--radius);
  background: #fcfcfc;
  transition: border-color .15s, background .15s, box-shadow .15s;
}

/* An invisible field over the whole zone, so a paste lands here without
   asking anyone to click first. */
.sink {
  position: absolute; inset: 0; width: 100%; height: 100%;
  border: 0; background: transparent; resize: none; outline: none;
  color: transparent; caret-color: var(--clay);
  font: inherit; padding: 10px; border-radius: inherit;
}

/* Named for what it is rather than "hint", which the tutorial drop zone
   already uses for something else. */
.dropnote {
  position: absolute; inset: 0;
  display: grid; place-content: center; justify-items: center; gap: 5px;
  text-align: center; padding: 14px; pointer-events: none; color: var(--muted);
}

.dropnote svg.dropmark { color: var(--clay); }
.dropnote .big { font-size: 16px; font-weight: 600; color: var(--ink); }
.dropnote .sub { font-size: 13px; }

.dropnote .chips {
  display: flex; flex-direction: column; align-items: center; gap: 7px;
  margin-top: 10px; pointer-events: auto;
}

.veil {
  position: absolute; inset: 0;
  display: grid; place-content: center; text-align: center;
  opacity: 0; transition: opacity .15s; pointer-events: none;
}

.veil .big { font-size: 21px; font-weight: 700; letter-spacing: -.02em; color: var(--clay-deep); }

/* THREE BANDS. The disc and its name take a row of their own, anything that
   came out as a file takes the next, and the text owns everything below with
   room to breathe before the dashes. */
.got {
  position: absolute; inset: 0; display: none; padding: 18px 18px 30px;
  grid-template-rows: auto minmax(0, 1fr); gap: 12px;
}

.gotrow { display: grid; grid-template-columns: 76px minmax(0, 1fr); gap: 14px; align-items: center; }

/* Files and text share the space under the heading row. With only files there
   they sit in the middle of it. With text as well they step aside and the text
   takes everything, so a whole book can be read here. */
.gotbody { display: flex; flex-direction: column; min-height: 0; gap: 10px; }

.gotfiles { display: flex; flex-wrap: wrap; gap: 8px; }
.gotfiles:empty { display: none; }

.got .nm { font-weight: 600; font-size: 14px; word-break: break-all; }
.got .sz { font-size: 12.5px; color: var(--muted); }

/* NO CEILING ON THE TEXT. It fills whatever the panel gives it and scrolls. */
.got pre {
  margin: 0; font-size: 12.5px; background: #fafafa;
  border: 1px solid var(--line); border-radius: 8px; padding: 10px;
  overflow: auto; white-space: pre-wrap; word-break: break-word;
}

/* It can be selected and corrected. Nothing is done with the change: the disc
   that was read is untouched, and this is a place to fix a line before taking
   it away by hand. */
#gotText { margin: 0; flex: 1; min-height: 0; overflow: auto; cursor: text; }
#gotText:empty { display: none; }

.thumb { position: relative; width: 76px; }

.got img {
  width: 76px; height: 76px; border-radius: 50%; display: block;
  filter: drop-shadow(0 4px 10px rgba(0,0,0,.2));
}

/* Two icons and no words, so the picture is not buried under labels. */
.thumbtools {
  position: absolute; left: 0; right: 0; bottom: -6px;
  display: flex; justify-content: center; gap: 5px;
  opacity: 0; transition: opacity .15s;
}

.thumbtools button {
  width: 24px; height: 24px; border-radius: 50%; border: 0; padding: 0;
  display: grid; place-items: center; cursor: pointer;
  color: #fff; background: rgba(20,20,24,.88);
}

/* THE SURROUND STAYS DASHED WHEN LOADED, AND THE DASHES BREAK TWICE.
   A CSS dashed border cannot open a gap, so the label and the button each
   carry a solid backdrop in the panel colour and punch through it. */
.again {
  position: absolute; left: 50%; bottom: 0; transform: translate(-50%, 50%);
  background: #fff; padding: 0 12px;
  font-size: 13px; font-weight: 700; color: #85858d;
  letter-spacing: .01em; white-space: nowrap; display: none;
}

.xbtn {
  position: absolute; right: 0; top: 0; transform: translate(50%, -50%);
  width: 26px; height: 26px; border-radius: 50%; padding: 0; cursor: pointer;
  border: 3px solid #fff; background: var(--soft-red); place-items: center;
  display: none; transition: background .15s, transform .12s;
}

/* The same generated button, smaller, on the attached file chip. */
.pill .xbtn { position: static; transform: none; width: 18px; height: 18px; border-width: 2px; display: grid; }

/* Reading a big one takes a moment, and says so. */
.decoding {
  position: absolute; inset: 0; z-index: 12; display: none;
  place-content: center; justify-items: center; gap: 12px; text-align: center;
  background: rgba(255,255,255,.97); backdrop-filter: blur(2px);
  border-radius: var(--radius);
}

.decoding .word { font-size: 15px; font-weight: 600; color: var(--ink); }
.decoding .word i { font-style: normal; color: var(--clay); animation: dot 1.2s infinite; }
.decoding .word i:nth-child(2) { animation-delay: .16s; }
.decoding .word i:nth-child(3) { animation-delay: .32s; }
.decoding .spin { animation: turn 1.15s linear infinite; transform-origin: 50% 50%; }
.decoding .pulse { animation: pulse 1.6s ease-in-out infinite; transform-origin: 50% 50%; }

@keyframes dot { 0%, 60%, 100% { opacity: .25; } 30% { opacity: 1; } }
@keyframes turn { to { transform: rotate(360deg); } }
@keyframes pulse { 0%, 100% { opacity: .35; r: 9; } 50% { opacity: .9; r: 13; } }

/* ==========================================================================
   SECTION 6 - STATES AND INTERACTIONS
   Everywhere the page answers the user: hover, focus, press, check, drag,
   and the classes that scripts.js switches on and off.
   ========================================================================== */

/* Tabs */
nav.tabs button:hover { color: var(--ink); }

nav.tabs button.active { color: var(--clay); border-bottom-color: var(--clay); font-weight: 600; }

nav.tabs button:focus-visible { outline: 2px solid var(--clay); outline-offset: 2px; border-radius: 6px; }

.tab-panel.active { display: block; }

/* Buttons */
.btn:active { transform: scale(0.98); }

.btn:focus-visible { outline: 2px solid var(--clay); outline-offset: 2px; }

.btn-primary:hover { background: #000; }

.btn-clay:hover { background: var(--clay-deep); }

.btn-ghost:hover { border-color: var(--ink); }

.btn[disabled] { opacity: .45; cursor: not-allowed; }

/* Fields */
textarea:focus, input:focus, select:focus {
  outline: none; border-color: var(--clay); box-shadow: 0 0 0 3px rgba(201,111,82,.15);
}

.switch input:checked + .track { background: var(--clay); }

.switch input:checked + .track::after { transform: translateX(18px); }

.switch input:focus-visible + .track { outline: 2px solid var(--clay); outline-offset: 2px; }

.seg-opt:has(input:checked) { background: var(--clay); color: #fff; }

.seg-opt:focus-within { outline: 2px solid var(--clay); outline-offset: 2px; }

/* Data input: text, drag-drop file, or attached-file chip */
.file-x:hover { border-color: var(--bad); color: var(--bad); }

.file-x:focus-visible { outline: 2px solid var(--clay); outline-offset: 2px; }

/* Advanced drawer */
.drawer-toggle[aria-expanded="true"] .chev { transform: rotate(90deg); }

/* Dropzone */
/* THE EJECT
   One transition covers the whole move: the rise, the spin, and the tilt.
   Order matters below, because several of these rules have equal weight and
   the later one wins.

   Transform only, so the browser can run it on the compositor and it stays
   smooth on a weak machine. */
/* Parked means fully inside. 104 per cent puts the top edge below the lip, so
   nothing of the disc shows while the drive is holding it.

   RULE: every state below lists the same three functions in the same order.
   When the lists match, the browser interpolates them one by one. When they do
   not, it falls back to decomposing a matrix, which yields NaN while the
   holder still has no height. Do not drop a function from one of these. */
/* ONE MOVE EACH WAY.
   The curve carries the rise. Coming out it overshoots the resting place;
   going in it lifts before it drops. So there is no second move and no hold,
   and nothing stops part way.
   The numbers were solved, not guessed. For a cubic-bezier the reach past the
   end depends only on y1 and y2, and the timing only on x1 and x2, so the
   height and the speed were set apart from each other.
   COMING OUT: 300ms, 5px past the resting place. */
.disc-holder {
  transform: translateY(120%) rotateX(0deg) rotate(-360deg);
  transition: transform 300ms cubic-bezier(.2, 1.112, .4, 1.05);
}

/* A cover that is not a CD is not a disc, so it does not spin. */
.disc-holder.no-spin { transform: translateY(120%) rotateX(0deg) rotate(0deg); }

/* Ejected, and tilted toward the viewer. */
.disc-holder.out { transform: translateY(0%) rotateX(9deg) rotate(0deg); }

/* Being drawn back in. Same place as parked, with no spin, because a disc
   going into a machine does not need to be flung.
   GOING IN: 400ms, lifting 45px before it drops. The negative y1 is what
   makes it wind up first instead of dropping flat. */
.disc-holder.loading {
  transform: translateY(120%) rotateX(0deg) rotate(0deg);
  transition-duration: 400ms;
  transition-timing-function: cubic-bezier(.58, -.785, .65, 1);
}

/* THE Z SWITCH.
   The disc is BEHIND the face while it passes through the slot, which is what
   makes it look like it comes out of a hole. It is ABOVE the face and the lip
   at every other moment, because then it is an object in front of a machine.
   The page adds this class once the disc is clear of the lip, where there is
   no overlap, so the change cannot be seen. */
.disc-holder.above { z-index: 5; }

/* With animation off there is no move at all. The disc is already out. */
.disc-holder.instant { transition: none; }

.back:hover { color: var(--ink); border-color: var(--muted); }

.back:focus-visible { outline: 2px solid var(--clay); outline-offset: 2px; }

/* THE TILT, IN FULL.
   The pointer is over the deck, so the disc lies flat. The pointer is away, so
   it leans. There is no third state and no script, which is why it cannot get
   stuck.
   The whole deck is the target, not only the picture, so it is easy to hit. */
.slot:hover .disc-holder.out { transform: translateY(0%) rotateX(0deg) rotate(0deg); }

/* A touch screen has no pointer to hover, so the disc never leans there.
   It still rises out of the slot, and a CD still spins on the way. */
@media (hover: none) {
  .disc-holder.out { transform: translateY(0%) rotateX(0deg) rotate(0deg); }
}

/* The two disc buttons appear on hover. A touch screen has no hover, so they
   stay visible there instead. */
.slot:hover .disc-tools { opacity: 1; }

@media (hover: none) {
  .disc-tools { opacity: 1; }
}

.disc-btn:hover { background: #fff; color: var(--clay-deep); }

/* THE RAW PIXELS BUTTON, in the corner of the recess.
   A hairline ring with no fill, so it reads as etched into the machine rather
   than sitting on it. It is offered only while a picture is on show, because
   there is nothing to look at otherwise. */
.pixel-corner {
  position: absolute; top: 9px; right: 9px; z-index: 7;
  width: 26px; height: 26px; padding: 0;
  display: grid; place-items: center;
  border: 1px solid rgba(255,255,255,.26);
  border-radius: 999px;
  background: none;
  color: rgba(255,255,255,.68);
  cursor: pointer;
  transition: color .15s ease, border-color .15s ease, box-shadow .15s ease, opacity .18s ease;
}

.pixel-corner:hover {
  color: #fff;
  border-color: rgba(255,255,255,.55);
  box-shadow: 0 0 0 3px rgba(255,255,255,.07);
}

.pixel-corner:focus-visible { outline: 2px solid var(--clay); outline-offset: 2px; }

/* THE RAW PIXELS VIEW. The deck draws the picture smoothed, because it is
   drawn smaller than it really is. This shows it at its true size with no
   smoothing at all, which is the only place the stippling can be seen. */
.pixel-card { max-width: 660px; }

.pixel-frame {
  display: grid; place-items: center;
  margin: 10px 0 12px; padding: 12px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--line);
  /* A chequer, so the transparent corners of a CD cover read as transparent
     rather than as white. */
  background-color: #f4f4f4;
  background-image:
    linear-gradient(45deg, #e4e4e4 25%, transparent 25%, transparent 75%, #e4e4e4 75%),
    linear-gradient(45deg, #e4e4e4 25%, transparent 25%, transparent 75%, #e4e4e4 75%);
  background-size: 16px 16px;
  background-position: 0 0, 8px 8px;
}

.pixel-frame img {
  display: block;
  max-width: 100%; height: auto;
  /* Smoothed by default. Nearest neighbour is switched on by the page code,
     and ONLY when the width came out as a whole number of device pixels for
     every image pixel. Below that it would make some pixels wider than their
     neighbours, which is worse than a smooth picture. */
  image-rendering: auto;
}

.pixel-frame img.even { image-rendering: pixelated; }

.disc-btn:focus-visible { outline: 2px solid var(--clay); outline-offset: 2px; }

.subtabs button:hover { color: var(--ink); }

.subtabs button.active { color: var(--clay-deep); border-bottom-color: var(--clay); font-weight: 600; }

.subtabs button:focus-visible { outline: 2px solid var(--clay); outline-offset: -2px; border-radius: 4px; }

/* A noise cover has no disc and no splat, so those two groups have nothing to
   show. They are dimmed rather than removed, so the shape of Advanced does not
   change under the person's hands. */
.subtabs button[disabled] { opacity: .38; cursor: default; }

.drive-btn:hover { border-color: var(--clay); color: var(--clay-deep); background: var(--clay-soft); }

.drive-btn:focus-visible { outline: 2px solid var(--clay); outline-offset: 2px; }

.clip:hover { color: var(--clay-deep); border-color: var(--clay); background: var(--clay-soft); }

.clip:focus-visible { outline: 2px solid var(--clay); outline-offset: 2px; }

.dropzone:hover { border-color: var(--clay); background: var(--clay-soft); }

.dropzone.drag-over { border-color: var(--clay); background: var(--clay-soft); box-shadow: 0 0 0 3px rgba(201,111,82,.12); }

.dropzone:focus-visible { outline: 2px solid var(--clay); outline-offset: 3px; }

/* Toast */
#toast.show { opacity: 1; transform: translateX(-50%) translateY(0); }

#toast.bad { background: var(--bad); }

#toast.ok { background: var(--ok); }

/* Docs */
.subnav a:hover { border-color: var(--clay); color: var(--clay-deep); background: var(--clay-soft); }

/* Header */
header.masthead .byline a:hover { color: var(--ink); text-decoration-color: currentColor; }

header.masthead .byline a:focus-visible { outline: 2px solid var(--clay); outline-offset: 3px; border-radius: 2px; }

/* A collapsible section turns its chevron when the browser opens it. */
details.sect[open] .chev-d { transform: rotate(90deg); }

details.sect > summary:focus-visible { outline: 2px solid var(--clay); outline-offset: 3px; border-radius: 6px; }

details.sect > summary:hover .sect-title { color: var(--clay-deep); }

/* ==========================================================================
   HOME - MAKE
   ========================================================================== */

/* A file held over the box. */
.makebox.over #makeText {
  border-color: var(--clay); background: var(--clay-soft);
  box-shadow: 0 0 0 4px rgba(201,111,82,.14);
}

.makebox.over .makeveil { opacity: 1; }

/* The invitation steps aside while the answer to the drop is showing. */
.makebox.over .attach { opacity: 0; }

/* An attachment is what will be pressed, so it takes the box over. */
.makebox.has .attached { display: grid; }
.makebox.has #makeText, .makebox.has .attach { visibility: hidden; }

.ghost:hover, .ghost:focus-visible { color: var(--ink); border-color: var(--ink); }
.ghost:active { transform: scale(.97); }

.attach:hover .say, .attach:focus-visible .say { max-width: 190px; opacity: 1; margin-left: 7px; }

.chip:hover { color: var(--clay-deep); border-color: var(--clay); background: var(--clay-soft); }

/* On the Make side the chip takes the attachment off, so it warns in red. */
.pill:hover { border-color: var(--soft-red); background: #fdf0ef; }
.pill:hover .act-ic { color: var(--soft-red); }
.pill:active { transform: scale(.985); }

/* The Load side removes nothing, it hands the file over. */
.gotfiles .pill:hover { border-color: var(--clay); background: var(--clay-soft); }
.gotfiles .pill:hover .act-ic { color: var(--clay-deep); }

/* ==========================================================================
   HOME - THE METER
   ========================================================================== */

.act:hover { background: #000; }
.act:active { transform: scale(.99); }
.act[disabled] { opacity: .45; cursor: not-allowed; }

.meterwrap.over .usage { color: var(--bad); }
.meterwrap.over .rungno { color: var(--bad); }

/* Past the largest disc the reading shudders, so a number that cannot work
   does not sit there looking calm. */
.meterwrap.judder { animation: judder .34s linear; }

@keyframes judder {
  0%,100% { transform: translate(0,0) }
  15% { transform: translate(-2px,1px) } 30% { transform: translate(2px,-1px) }
  45% { transform: translate(-2px,-1px) } 60% { transform: translate(2px,1px) }
  75% { transform: translate(-1px,0) }
}

/* The cut rests as a dark line and is thrown back to full white by any input.
   The flash carries no transition of its own, so it lands on the frame the
   key was pressed and then fades on the rule above it. */
#cut { transition: stroke .60s ease; }
#cut.flash { stroke: #ffffff !important; transition: none; }

/* A big arrival washes the whole ring, not the line alone. */
#ringflash { transition: opacity .58s ease; }
#ringflash.on { opacity: 1; transition: none; }

/* ==========================================================================
   HOME - THE DISC
   ========================================================================== */

.cd.out { transform: translateY(0); opacity: 1; }
.cd.gone { transform: translateY(0) scale(.3); opacity: 0; transition: transform .34s ease-in, opacity .3s ease-in; }

/* While one is being carried, what is left in the slot is only a trace of it. */
.cd.lifted { opacity: .25; }

/* The copy that follows the pointer. It is fixed to the window, so it is put
   on the body and cannot be scoped to the panel. */
.cd.ghost {
  position: fixed; z-index: 100; pointer-events: none;
  transition: none; opacity: 1;
  filter: drop-shadow(0 16px 26px rgba(0,0,0,.38));
}

.cd.out ~ .cdtools { pointer-events: auto; }
.tray:hover .cd.out ~ .cdtools { opacity: 1; }
.cdtools button:hover { background: var(--clay-deep); }
.cdtools button.done { background: var(--ok); }

.cd.out ~ .cdtip { opacity: 1; transform: none; pointer-events: auto; }

/* THE WHOLE STACK CLEARS WHILE THE DISC IS IN HAND. Neither chip is a place
   to drop any more, so there is nothing for them to do until the disc is set
   down again, and both come back if it returns to its slot. */
.cd.lifted ~ .cdtip { opacity: 0; transform: translateX(-10px); pointer-events: none; }

.cdtip .say:hover { background: var(--clay-deep); }
.cdtip .say:active { transform: scale(.97); }
.cdtip .say.done { background: var(--ok); }

.bin:hover { color: var(--bad); border-color: var(--bad); }

/* ==========================================================================
   HOME - LOAD
   ========================================================================== */

.zone.over { border-color: var(--clay); background: var(--clay-soft); box-shadow: 0 0 0 4px rgba(201,111,82,.14); }
.zone.over .veil { opacity: 1; }

/* Whatever is being said stands down once there is something to show, or once
   a disc is over the zone and the answer is the only thing that matters. */
.zone.over .dropnote, .zone.has .dropnote, .zone.has .sink { opacity: 0; pointer-events: none; }

.zone.has { border-color: #cfcfd4; background: #fff; }
.zone.has .got { display: grid; }
.zone.has .again { display: block; }
.zone.has .xbtn { display: grid; }

.gotbody.filesonly { justify-content: center; align-items: center; }

#gotText:focus-visible { outline: 2px solid var(--clay); outline-offset: 1px; }

.thumb:hover .thumbtools { opacity: 1; }
.thumbtools button:hover { background: var(--clay-deep); }

.xbtn:hover { background: #c1443e; transform: translate(50%, -50%) scale(1.09); }
.pill .xbtn:hover { transform: scale(1.12); }

.zone.reading .decoding { display: grid; }

/* ==========================================================================
   SECTION 7 - RESPONSIVE AND UTILITIES
   The one breakpoint, plus the small single-purpose helper classes.
   ========================================================================== */

/* Cards / sections */
.muted { color: var(--muted); }

.small { font-size: 13px; }

/* Layout */
/* Below this the deck cannot hold a readable disc beside the box, so it
   moves under it and is capped so the disc does not fill the screen. */
@media (max-width: 720px) {
  .module { grid-template-columns: 1fr; }
  .module-deck { max-width: 260px; margin: 0 auto; width: 100%; }
  /* An empty deck under the box is dead space on a phone, so it waits until
     it has something to hold. On a wide screen it stays, to reserve the room. */
  .module-deck:not(.filled) { display: none; }
}

/* THE SHEET.
   On a narrow screen the drive sits at the foot of the window instead of far
   down the page. It is NOT a modal: there is no backdrop, and the page behind
   it stays scrollable, because a person may want to edit their text while a
   result is up. */
@media (max-width: 640px) and (orientation: portrait) {
  .module-deck.filled {
    position: fixed; left: 0; right: 0; bottom: 0;
    max-width: none; width: auto; margin: 0;
    padding: 0 18px 16px;
    background: var(--paper);
    border-top: 1px solid var(--line-strong);
    border-radius: var(--radius) var(--radius) 0 0;
    box-shadow: 0 -6px 26px rgba(0,0,0,.16);
    z-index: 70;
    /* Tucked away, with only the handle showing. */
    transform: translateY(calc(100% - 42px));
    transition: transform .3s cubic-bezier(.2, .8, .25, 1);
  }

  /* The slot takes the whole sheet, so the fascia keeps its wings and a phone
     shows the same six values a wide screen does.
     The slot keeps its absolute layout and is reshaped instead. Making the
     face and the lip static broke the whole thing out of the box. */
  /* A FIXED HEIGHT, not an aspect ratio. With a ratio the bay grows with the
     phone's width, and the parked disc stopped clearing the lip on a wider
     screen. A set height makes the geometry the same on every device. */
  .module-deck.filled .slot { max-width: none; aspect-ratio: auto; height: 330px; }

  /* The disc is pinned to a fixed width and centred, because the bay is much
     shorter here than on a wide screen. A 150px disc at 18px down leaves the
     bay deeper than the disc, which is what the rise needs. */
  .module-deck.filled .disc-holder {
    left: 50%; right: auto; width: 150px; margin-left: -75px; top: 18px;
  }

  .module-deck.filled.up { transform: translateY(0); }

  .deck-handle { display: block; }

  /* Room at the foot of the page so the sheet never covers the last control. */
  .tab-panel { padding-bottom: 160px; }
}

/* Fields */
@media (max-width: 560px) { .grid2 { grid-template-columns: 1fr; } }

/* Snippets */
.hidden { display: none !important; }

/* Docs */
.tablewrap { overflow-x: auto; }

.lede { font-size: 17px; color: var(--ink-soft); }

/* THE HOME BOARD, ON A NARROW SCREEN.
   Make and its deck come first as one block, then Load under them, so a
   column can never be separated from the heading that names it. */
@media (max-width: 760px) {
  .board { grid-template-columns: 1fr; grid-template-areas: "hm" "cm" "sm" "hl" "cl"; }

  /* Beside the disc there is no room, so the two chips go under it instead of
     disappearing, which would take the bin away with them. */
  .deckrow { height: calc(var(--slot-w) + 116px); }
  .cdtip { left: 0; top: calc(var(--slot-w) + 20px); }
  .cdtip .arrow { display: none; }
  .bin { align-self: flex-start; }
}

/* A TEST AID. It stops every transition and animation so a rendered page is
   the same every time it is measured. Nothing on the page adds this class:
   only a headless run does. */
body.pose *, body.pose *::before, body.pose *::after {
  transition: none !important;
  animation: none !important;
}
