:root {
  --bg: #141414;
  --panel: #1d1d1d;
  --line: #333;
  --text: #fff;
  --muted: #a3a3a3;
  --accent: #009bd8;
  --ui-font: system-ui, -apple-system, "Segoe UI", sans-serif;
  --word-size-ref: 56px;
  --size-ratio: 1;
  --spiral-em: 2.02;
  color-scheme: dark;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  padding: 1rem max(1rem, env(safe-area-inset-right)) 3rem max(1rem, env(safe-area-inset-left));
  background: var(--bg);
  color: var(--text);
  font: 15px/1.4 var(--ui-font);
}

p { margin: 0; }

input[type="range"], input[type="checkbox"], input[type="radio"] { accent-color: var(--accent); }
button { font: inherit; }
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ---------- shared control pieces ---------- */

.field { display: flex; flex-direction: column; gap: 0.25rem; }
.field-label { color: var(--muted); font-size: calc(0.8rem + 1pt); }
.check { display: inline-flex; align-items: center; gap: 0.4rem; }
.slider { display: inline-flex; align-items: center; gap: 0.6rem; }

/* Segmented toggle (the three-way Case control): radio buttons drawn as one pill, the chosen one filled. */
.segmented { position: relative; display: inline-flex; border: 1px solid #4a4a4a; border-radius: 6px; overflow: hidden; }
.segmented label { padding: 0.15rem 0.6rem; color: var(--muted); cursor: pointer; }
.segmented label + label { border-left: 1px solid #4a4a4a; }
.segmented input { position: absolute; opacity: 0; pointer-events: none; }
.segmented label:has(input:checked) { color: #fff; background: var(--accent); }
.segmented label:has(input:focus-visible) { outline: 2px solid var(--accent); outline-offset: -2px; }
.slider output { min-width: 6.5ch; font-variant-numeric: tabular-nums; color: var(--muted); }
/* Fixed-width readouts in the settings strip: a label that changes length (a font's weight names, a negative
   spacing) never shifts the controls beside it, and anything too long is clipped instead of pushing them over. */
.slider output.readout { display: inline-block; flex: none; min-width: 0; width: var(--readout-w); overflow: hidden; white-space: nowrap; text-overflow: clip; }
/* Widths are the measured widest text each readout can show (15px UI font): "900 Extra Black", "+0.400 em", "80px". */
.readout-weight { --readout-w: 108px; }
.readout-spacing { --readout-w: 76px; }
.readout-size { --readout-w: 36px; }

[hidden] { display: none !important; }

/* ---------- rows ---------- */

.row { margin-top: 0.5rem; }

/* Gaps and slider lengths are kept tight so the whole strip, both copy buttons included, fits on one line at 1440px wide. */
.row-controls { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 0.75rem 1rem; }
.row-controls input[type="range"] { width: 112px; }
.row-actions { display: flex; gap: 0.5rem; }

.row-actions button {
  padding: 0.4rem 0.65rem;
  color: var(--text);
  background: var(--panel);
  border: 1px solid #4a4a4a;
  border-radius: 6px;
  cursor: pointer;
}

.summary { margin-top: 0.75rem; color: var(--muted); }
.status { margin-left: 0.75rem; color: var(--accent); overflow-wrap: anywhere; }
.unavailable-note { margin-top: 0.75rem; padding: 1.5rem; border: 1px dashed var(--line); border-radius: 8px; color: var(--muted); }

/* ---------- font picker ---------- */

.picker { position: relative; min-width: 13rem; }

.picker-button {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  width: 100%;
  padding: 0.35rem 0.6rem;
  color: var(--text);
  background: var(--panel);
  border: 1px solid #4a4a4a;
  border-radius: 6px;
  font-size: 1.1rem;
  text-align: left;
  cursor: pointer;
}
.picker-name { flex: 1; }
.picker-note { color: var(--muted); font: calc(0.75rem + 1pt) var(--ui-font); }
.picker-chevron { color: var(--muted); font: calc(0.8rem + 1pt) var(--ui-font); }

.picker-list {
  position: absolute;
  z-index: 10;
  top: calc(100% + 4px);
  left: 0;
  min-width: 100%;
  max-height: min(60vh, 22rem);
  margin: 0;
  padding: 0.25rem;
  overflow-y: auto;
  list-style: none;
  background: #222;
  border: 1px solid #555;
  border-radius: 6px;
  box-shadow: 0 8px 24px rgb(0 0 0 / 0.6);
}

.picker-option {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.35rem 0.6rem;
  font-size: 1.15rem;
  border-radius: 4px;
  cursor: pointer;
  white-space: nowrap;
}
.picker-option.is-active { background: #333; }
.picker-option[aria-selected="true"] { box-shadow: inset 3px 0 var(--accent); }
.picker-option[aria-disabled="true"] { color: #777; cursor: not-allowed; }
.picker-option[aria-disabled="true"] .picker-name { font: 1rem var(--ui-font); }
.picker-button .picker-name { white-space: nowrap; }

/* ---------- specimen ---------- */

.specimen {
  margin-top: 0.75rem;
  padding: 0.75rem 1rem;
  overflow-x: auto;
  border: 1px solid var(--line);
  border-radius: 8px;
}

/* Spiral and gaps are fixed from the reference text size (--ref-font, TEXT_SIZE_REF in app.js), so the Text size
   slider changes only the word. The word's size is the reference scaled by --size-ratio (slider / default). */
.lockups {
  --ref-font: min(var(--word-size-ref), 5.6vw);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: calc(var(--ref-font) * 0.35) calc(var(--ref-font) * 1.2);
  font-size: calc(var(--ref-font) * var(--size-ratio));
  line-height: 1.15;
  font-kerning: normal;
}

/* Each lockup is a button: clicking it puts that word on the helmet. The one showing gets an accent underline. */
.lockup {
  display: flex;
  align-items: center;
  gap: calc(var(--ref-font) * 0.3);
  min-height: calc(var(--ref-font) * var(--spiral-em));
  padding: 0 0 0.4rem;
  white-space: nowrap;
  font: inherit;
  letter-spacing: inherit;
  color: inherit;
  background: none;
  border: 0;
  border-bottom: 2px solid transparent;
  cursor: pointer;
}
.lockup[aria-pressed="true"] { border-bottom-color: var(--accent); }

/* The floating tooltip (see showTip in app.js): one element outside the cards, fixed to the viewport, so it lays
   over whatever is there, takes no space, and is never clipped by a card. */
.tip {
  position: fixed;
  z-index: 1000;
  padding: 0.3rem 0.6rem;
  color: #fff;
  background: rgb(0 0 0 / 0.9);
  border: 1px solid #4a4a4a;
  border-radius: 6px;
  font: 500 calc(0.8rem + 1pt)/1.2 var(--ui-font);
  white-space: nowrap;
  pointer-events: none;
}
.spiral { height: calc(var(--ref-font) * var(--spiral-em)); width: auto; flex: none; }
body[data-spiral="off"] .spiral { display: none; }

/* Drawn macron: a zero-size anchor sitting on the baseline, with the bar positioned in em from there. */
.macron-bar { position: relative; display: inline-block; width: 0; height: 0; }
.macron-bar::after {
  content: "";
  position: absolute;
  left: calc(var(--x) * 1em);
  bottom: calc(var(--y) * 1em);
  width: calc(var(--w) * 1em);
  height: calc(var(--t) * 1em);
  background: currentColor;
}

/* ---------- helmet ---------- */

.helmet { margin-top: 2.5rem; padding-top: 1rem; border-top: 1px solid var(--line); }
.helmet-caption { margin: 0.75rem 0 0.25rem; color: var(--muted); }
.helmet-realistic { margin-bottom: 0.75rem; font-size: 1.15rem; cursor: pointer; }
.helmet-realistic input { width: 1.1em; height: 1.1em; }

/* The stage is the visible window; the canvas inside it is the whole photo with the text on top. Cropping only
   trims: the photo keeps its size (--photo-width) and the window gets smaller. --visible is the share of the photo's
   width left after the left and right crops, so the canvas is 1/--visible times the stage width. Margins are
   percentages of the stage width, and the plates are square (photo height = photo width), which is why the
   top and bottom crops divide by --visible too. The canvas is the size container, so the helmet text (sized in cqw)
   scales with the whole photo no matter how it is cropped. */
.helmet-stage {
  --visible: 1;
  position: relative;
  flex: none;
  width: min(100%, calc(var(--photo-width, 900px) * var(--visible)));
  overflow: hidden;
  background: #000;
}
.helmet-canvas {
  position: relative;
  container-type: inline-size;
  width: calc(100% / var(--visible));
  margin-left: calc(var(--crop-left, 0) * -1% / var(--visible));
  margin-top: calc(var(--crop-top, 0) * -1% / var(--visible));
  margin-bottom: calc(var(--crop-bottom, 0) * -1% / var(--visible));
}
.helmet-stage img { display: block; width: 100%; height: auto; }

/* Download button in the photo's lower right corner (left out of the saved image). */
.helmet-download {
  position: absolute;
  right: 10px;
  bottom: 10px;
  z-index: 3;
  padding: 0.4rem 0.8rem;
  color: #fff;
  background: rgb(0 0 0 / 0.65);
  border: 1px solid rgb(255 255 255 / 0.35);
  border-radius: 6px;
  cursor: pointer;
}
.helmet-download:hover { background: rgb(0 0 0 / 0.85); }
.helmet-download:disabled { opacity: 0.7; cursor: progress; }

.helmet-text {
  position: absolute;
  transform-origin: left center;
  isolation: isolate;
  color: var(--ink, #fff);
  opacity: var(--ink-opacity, 1);
  line-height: 1;
  white-space: nowrap;
  pointer-events: none;
}

/* Left-to-right blur: two copies of the word, one blurred by --blur-left and one by --blur-right, each masked
   with an opposite linear gradient so they crossfade along the word. The right copy is blended additively
   (plus-lighter) inside the isolated group, so the two ramps always sum to full strength and the word does not
   dim in the middle. Padding gives the blur room inside the mask box and the equal negative margin cancels it, for
   both copies: the absolute copy sits at 0,0 and its negative margin alone lines it up with the in-flow copy. */
.helmet-layer { display: block; padding: 0.3em 0.4em; margin: -0.3em -0.4em; }
.helmet-layer-left {
  filter: blur(var(--blur-left));
  -webkit-mask-image: linear-gradient(to right, #000, transparent);
  mask-image: linear-gradient(to right, #000, transparent);
}
.helmet-layer-right {
  position: absolute;
  top: 0;
  left: 0;
  mix-blend-mode: plus-lighter;
  filter: blur(var(--blur-right));
  /* --right-strength below 1 dims the word toward its right end (the left copy still fades to nothing there). */
  -webkit-mask-image: linear-gradient(to left, rgb(0 0 0 / var(--right-strength, 1)), transparent);
  mask-image: linear-gradient(to left, rgb(0 0 0 / var(--right-strength, 1)), transparent);
}

.calibrate { margin-top: 1rem; max-width: 1200px; }
.calibrate summary { cursor: pointer; }
/* Four controls per row; the crop controls start their own row (.row-start) so all four sit on one line. */
.calibrate-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 0.75rem 1.5rem; margin-top: 0.75rem; }
.calibrate-grid .row-start { grid-column-start: 1; }
.calibrate-grid .slider { display: flex; }
.calibrate-grid .slider input { flex: 1; min-width: 0; }
@media (max-width: 760px) {
  .calibrate-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
.calibrate-actions { display: flex; justify-content: flex-end; align-items: center; gap: 0.75rem; margin-top: 0.75rem; }
.calibrate-actions button {
  padding: 0.4rem 0.8rem;
  color: var(--text);
  background: var(--panel);
  border: 1px solid #4a4a4a;
  border-radius: 6px;
  cursor: pointer;
}

/* ---------- small screens, including iPhone landscape ---------- */

@media (max-height: 500px), (max-width: 700px) {
  body { padding-top: 0.5rem; }
  .row-controls { gap: 0.5rem 1rem; }
}
