/* Base */
*, *::before, *::after {
  box-sizing: border-box;
}

/* Reserve the scrollbar gutter permanently. The response locks scrolling while
   the mouse button is held; without a reserved gutter, hiding the scrollbar
   would widen the container mid-drag and move the scale under the pointer. */
html {
  scrollbar-gutter: stable;
}

body {
  margin: 0;
  padding: 20px;
  background: #f4f4f4;
  font-family: Georgia, 'Times New Roman', serif;
  color: #222;
  line-height: 1.7;
}

#container {
  max-width: 700px;
  margin: 40px auto;
  background: #fff;
  border: 1px solid #ddd;
  border-radius: 4px;
  padding: 48px 56px;
  box-shadow: 0 2px 12px rgba(0,0,0,0.07);
}

/* Screen visibility */
.screen {
  display: none;
}
.screen.active {
  display: block;
}

/* Typography */
h1 {
  font-size: 1.9em;
  font-weight: normal;
  margin-bottom: 4px;
  color: #1a1a1a;
}

h2 {
  font-size: 1.45em;
  font-weight: normal;
  border-bottom: 1px solid #ddd;
  padding-bottom: 10px;
  margin-bottom: 20px;
  color: #1a1a1a;
}

.subtitle {
  color: #666;
  font-style: italic;
  margin-top: 0;
  margin-bottom: 28px;
}

p {
  margin-bottom: 16px;
}

/* Trial progress badge */
.trial-progress {
  font-size: 0.7em;
  color: #888;
  font-style: normal;
  font-weight: normal;
  vertical-align: middle;
}

/* Trial progress bar — tracks valid (non-spoiled) completions, not the trial's
   position in the block, so requeued trials never make it look like progress
   moved backward. */
.trial-progress-bar {
  display: inline-block;
  width: 120px;
  height: 6px;
  margin-left: 8px;
  background: #e0e0e0;
  border-radius: 3px;
  overflow: hidden;
  vertical-align: middle;
}

.trial-progress-fill {
  display: block;
  height: 100%;
  width: 0%;
  background: #888;
  transition: width 0.2s ease;
}

/* Instruction box */
.instruction-box {
  background: #f6f6f6;
  border-left: 4px solid #555;
  padding: 16px 20px;
  margin: 24px 0;
  font-size: 1.05em;
}

/* Consent screen */
.consent-embed {
  border: 1px solid #ddd;
  margin: 20px 0;
}

.consent-embed embed {
  display: block;
  width: 100%;
}

.consent-checkbox-row {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  margin: 20px 0;
  font-size: 0.95em;
}

.consent-checkbox-row input[type="checkbox"] {
  margin-top: 4px;
  flex-shrink: 0;
}

.btn-primary:disabled {
  background: #bbb;
  cursor: not-allowed;
}

/* Sticky stimulus banner */
#stimulus-banner {
  position: sticky;
  top: 0;
  background: #fff;
  border-bottom: 2px solid #ddd;
  padding: 14px 0 10px;
  margin: -48px -56px 20px;
  padding-left: 56px;
  padding-right: 56px;
  z-index: 10;
  text-align: center;
}

#stimulus-word {
  font-size: 1.5em;
  margin: 4px 0 6px;
  color: #1a1a1a;
}

/* The question about the second trait is set at the same size as the sentence
   about the first, and the trait itself sits centred on a line of its own. */
#stimulus-question {
  font-size: 1.5em;
  line-height: 1.4;
  margin: 0;
  color: #1a1a1a;
}

/* The fixed line height is load-bearing, not cosmetic. Dial geometry is
   measured once, when the mouse button goes down; if swapping the mask for a
   longer word changed the height of this line, the whole ring would shift
   under the pointer mid-response and the rating recorded would not be the one
   the participant aimed at. Holding the height constant makes the swap purely
   horizontal. */
#dv-line {
  font-size: 1.5em;
  line-height: 1.4;
  min-height: 1.4em;
  margin: 0 0 4px;
  text-align: center;
  color: #1a1a1a;
}

#stimulus-word strong,
#dv-line strong {
  color: #1a1a1a;
}

/* Instructions — response procedure */
.procedure-list {
  margin: 16px 0 20px;
  padding-left: 24px;
}

.procedure-list li {
  margin-bottom: 16px;
}

.instruction-emphasis {
  background: #f6f6f6;
  border-left: 4px solid #555;
  padding: 14px 18px;
  margin: 20px 0;
}

/* The response is irreversible the moment the pointer meets the scale, which is
   the one thing a participant cannot discover safely by trying it. Same shape as
   .instruction-emphasis, in the red already used by .error rather than a new
   colour. Instruction screens only — a standing red warning on the trial screen
   would sit in the region the trait appears in, and inviting caution there would
   inflate the latency this design exists to measure. */
.instruction-warning {
  background: #fdf3f2;
  border-left: 4px solid #c0392b;
  color: #a5321f;
  padding: 14px 18px;
  margin: 20px 0;
}

.instruction-warning strong {
  color: #c0392b;
}

/* Inline red, for the one phrase in the numbered procedure that the whole
   design depends on being obeyed. Not a box — it sits inside a list item — but
   it carries the same background as .instruction-warning so that the two things
   drawn in this red on the instruction screens read as one warning. */
.text-alert {
  color: #c0392b;
  background: #fdf3f2;
  padding: 1px 4px;
}

/* --- Still picture of the response dial, on the instructions page ---------- */

.scale-figure {
  margin: 20px 0 24px;
  padding: 12px 0 4px;
  background: #fafafa;
  border: 1px solid #e6e6e6;
  border-radius: 4px;
}

/* Sized to match the live dial: this is a reference picture beside the text, not
   something to be aimed at. */
#dial-demo {
  display: block;
  width: 100%;
  max-width: 410px;
  height: auto;
  margin: 0 auto;
  pointer-events: none;
}

/* The path the pointer takes, and where it ends up. */
#dial-demo .demo-leader {
  stroke: #c0392b;
  stroke-width: 1.125;
  stroke-dasharray: 6 5;
}

#dial-demo .demo-dot {
  fill: #c0392b;
}

#dial-demo .demo-callout {
  fill: #c0392b;
  font-family: Georgia, 'Times New Roman', serif;
  font-size: 9.5625px;
}

/* Screen readers get the figcaption instead of the drawing. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* Rating response area */
#rating-container {
  margin: 32px 0 28px;
}

/* --- Custom hold-and-drag dial -------------------------------------------- */

/* Nothing inside the rating screen may be selected or dragged as content:
   a text selection started mid-response would both look broken and swallow
   the pointer stream the response depends on. */
#screen-rating {
  user-select: none;
  -webkit-user-select: none;
  -moz-user-select: none;
}

#screen-rating #dial {
  touch-action: none;
  -webkit-user-drag: none;
}

/* --- The dial ------------------------------------------------------------ */

/* Scales to the card. Everything inside is positioned in viewBox units, and
   experiment.js converts those to client pixels once per trial by dividing the
   rendered width by the viewBox width — so there is one description of the
   geometry, in the markup, and the two never drift apart.

   The width is capped against the viewport height as well as the card, because
   the dial is the tallest thing on the screen and the whole of it has to be
   visible at once: the answer is committed on contact, so a participant must be
   able to see the part of the ring they are aiming at. The viewBox is 326.25 x
   191.25, so a width of 55.2vh is a height of about 32.4vh. Sizing is
   width-driven either way, which is what keeps rect.width / 326.25 an exact
   scale factor. */
#dial {
  display: block;
  width: 100%;
  max-width: min(410px, 55.2vh);
  height: auto;
  margin: 8px auto 0;
}

/* Thickness is purely visual. Contact is detected by intersecting the segment
   between consecutive pointer samples with the circle through the middle of the
   ring (see contactPoint in experiment.js), so a crossing registers however thin
   the ring is drawn and wherever the samples happen to land — a fast movement
   cannot step over it. Only a containment test would have needed a fat target. */
#ring-track,
#dial-demo .demo-track {
  fill: none;
  stroke: #dcdcdc;
  stroke-width: 6.75;
}

/* Deliberately darker than the track in every state, including the two that
   restyle the track: the swept part of the ring reads as filled only if it stays
   the darker of the two. */
#ring-fill {
  fill: none;
  stroke: #8f8f8f;
  stroke-width: 6.75;
  display: none;
}

#dial-ticks line,
#dial-demo .demo-ticks line {
  stroke: #b0b0b0;
  stroke-width: 1.5;
}

#dial-numbers text,
#dial-demo .demo-numbers text {
  fill: #555;
  font-family: Georgia, 'Times New Roman', serif;
  font-size: 12.375px;
  text-anchor: middle;
  dominant-baseline: central;
}

/* Darker than the numerals: the words say what the scale means, the numerals only
   index it. Kept a shade smaller than them so the two do not compete. */
.dial-anchor {
  fill: #333;
  font-family: Georgia, 'Times New Roman', serif;
  font-size: 11.25px;
  font-style: italic;
}

/* A thin radial line, not a round knob — on a continuous scale the participant
   should be able to see exactly which point they are on. The dark part runs from
   the home region out to wherever the pointer currently is, so its length reads
   as distance travelled; the gray track behind it is the rest of the way. */
#dial-needle-group {
  display: none;
}

#dial-needle {
  stroke: #1a1a1a;
  stroke-width: 1.6875;
  stroke-linecap: round;
  pointer-events: none;
}

/* The same tone as #ring-track, so everything on the dial still to be done — the
   unswept ring and the part of the needle not yet travelled — is drawn at one
   weight, and everything already done is dark. Light enough that the split point
   on the needle is obvious without having to look for it. */
#dial-needle-track {
  stroke: #dcdcdc;
  stroke-width: 1.6875;
  stroke-linecap: round;
  pointer-events: none;
}

#screen-rating.state-moving #ring-fill,
#screen-rating.state-moving #dial-needle-group,
#screen-rating.committed #ring-fill,
#screen-rating.committed #dial-needle-group {
  display: block;
}

/* Approach feedback: the ring lights up as the pointer comes up to it, so the
   participant can see what they are about to commit against. */
#screen-rating.over-ring #ring-track {
  stroke: #c6c6c6;
}

/* Contact registered — the answer is in. Held for COMMIT_HOLD_MS so the
   participant can read the value they just gave. */
#screen-rating.committed #ring-track {
  stroke: #cbcbcb;
}

/* --- Home region (the resting position) ----------------------------------

   The circle sits at the centre of the ring, so every point on the ring is one
   radius away from it and how far the pointer has to travel does not depend on
   the answer. A resting position off to one side — the obvious alternative, and
   what a straight scale forces — makes movement time a function of the rating
   for purely mechanical reasons. */

#hold-home,
#dial-demo .demo-home {
  fill: #f6f6f6;
  stroke: #9a9a9a;
  stroke-width: 1.125;
  stroke-dasharray: 4 3;
  cursor: pointer;
}

#hold-home-dot {
  fill: #1a1a1a;
  display: none;
  pointer-events: none;
}

#hold-home-label,
#dial-demo .demo-home-label {
  fill: #888;
  font-family: Georgia, 'Times New Roman', serif;
  font-size: 10.24px;
  text-anchor: middle;
  pointer-events: none;
}

#hold-home.is-holding {
  stroke-dasharray: none;
  stroke: #4a4a4a;
  fill: #ededed;
  cursor: grabbing;
}

#hold-home.is-holding ~ #hold-home-dot {
  display: block;
}

#hold-home.is-holding ~ #hold-home-label {
  display: none;
}

#hold-home.is-disabled {
  opacity: 0.45;
  cursor: default;
}

/* The circle is too small to carry the instruction, so it sits below: three
   short steps, one per line, centred as a block under the dial. */
#hold-hint {
  list-style: none;
  counter-reset: hold-step;
  text-align: center;
  color: #777;
  font-size: 0.9em;
  margin: 36px 0 0;
  padding: 0;
}

#hold-hint li {
  counter-increment: hold-step;
  margin: 2px 0;
}

#hold-hint li::before {
  content: 'Step ' counter(hold-step) ': ';
  color: #999;
}

/* --- Masked trait word ---------------------------------------------------- */

/* The trait is what the response time is a response to, so it stays covered
   until the trial actually starts. The mask is sized like a word so the
   layout does not jump when it is swapped out. */
#dv-mask {
  display: inline-block;
  color: #c4c4c4;
  letter-spacing: 1px;
}

#screen-rating.trait-hidden #dv-word {
  display: none;
}

#screen-rating.trait-visible #dv-mask {
  display: none;
}

/* The placeholder pulses while the button is held and the trait is still
   hidden, so the eyes stay on the region the word will appear in.

   Two constraints shape this. Only opacity and transform are animated, so the
   pulse is composited and cannot delay the frame that revealTrait times the
   stimulus onset from. And the cycle is deliberately fast rather than a slow
   ramp: the foreperiod is a random 400-800ms, and an animation slow enough to
   be read as a progress cue would let participants infer how much of it has
   elapsed and start moving in anticipation. At ~3Hz the phase wraps two or
   three times inside the window and carries no usable clock. */
@keyframes dv-mask-pulse {
  from { opacity: 0.45; transform: scale(1); }
  to   { opacity: 1;    transform: scale(1.06); }
}

#screen-rating.state-foreperiod #dv-mask {
  animation: dv-mask-pulse 150ms ease-in-out infinite alternate;
}

@media (prefers-reduced-motion: reduce) {
  #screen-rating.state-foreperiod #dv-mask {
    animation: none;
    color: #8f8f8f;
  }
}

/* --- Per-trial feedback --------------------------------------------------- */

.trial-feedback {
  text-align: center;
  margin-top: 18px;
  min-height: 1.7em;
  font-size: 0.95em;
}

.trial-feedback.good {
  color: #2b6a3f;
}

.trial-feedback.bad {
  color: #c0392b;
}

/* --- Invalid-response alert ----------------------------------------------- *

   A response that did not count is the one thing a participant has to notice,
   and as a line of small red text under the dial it was missable. It is a modal
   instead: it covers the dial, takes the pointer events the dial would otherwise
   get, and clears only on the button. The dim layer is what stops a participant
   pressing the knob again while reading. */

.alert-overlay {
  position: fixed;
  inset: 0;
  z-index: 1000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  background: rgba(0, 0, 0, 0.45);
}

.alert-box {
  background: #fff;
  border-top: 5px solid #c0392b;
  border-radius: 4px;
  box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
  padding: 26px 30px 24px;
  max-width: 460px;
  text-align: center;
}

.alert-title {
  color: #c0392b;
  margin: 0 0 12px;
}

#alert-text {
  margin: 0 0 22px;
}

/* --- Scroll lock during a response --------------------------------------- */

/* Applied only while the button is held. #stimulus-banner is sticky, so a
   scroll mid-drag would slide the ring out from under the pointer and
   corrupt both the rating and the movement record. Locking only during the
   hold means a short viewport can still be scrolled between trials. */
body.pointer-locked {
  overflow: hidden;
}

.slider-value-display {
  text-align: center;
  margin-top: 14px;
  color: #555;
  font-size: 1.1em;
}

#rating-value {
  font-weight: bold;
  color: #1a1a1a;
}

/* Buttons */
.btn-primary {
  display: inline-block;
  margin-top: 20px;
  padding: 11px 30px;
  background: #4a4a4a;
  color: #fff;
  border: none;
  border-radius: 3px;
  font-family: Georgia, serif;
  font-size: 1em;
  cursor: pointer;
  transition: background 0.2s;
}

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

.btn-primary:active {
  background: #222;
}

/* Back, on the instruction pages. Same geometry and type as .btn-primary but
   outlined, so re-reading is offered without competing with going forward. */
.btn-secondary {
  display: inline-block;
  margin-top: 20px;
  padding: 11px 22px;
  background: transparent;
  color: #4a4a4a;
  border: 1px solid #bbb;
  border-radius: 3px;
  font-family: Georgia, serif;
  font-size: 1em;
  cursor: pointer;
  transition: background 0.2s, border-color 0.2s;
}

.btn-secondary:hover {
  background: #f0f0f0;
  border-color: #999;
}

.btn-secondary:active {
  background: #e6e6e6;
}

/* The debriefing link on the done screen wears .btn-secondary. An anchor is not
   a button: it arrives underlined and in the user agent's link colour, so both
   are turned off here to match the buttons above. */
a.btn-secondary {
  text-decoration: none;
  color: #4a4a4a;
}

.button-row {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-top: 20px;
}

.button-row .btn-primary,
.button-row .btn-secondary {
  margin-top: 0;
}

/* Error messages */
.error {
  color: #c0392b;
  font-size: 0.9em;
  margin-top: 8px;
}

/* Done screen */
.done-note {
  color: #777;
  font-size: 0.9em;
  font-style: italic;
  margin-top: 16px;
}

/* Demographics survey */
.survey-question {
  background: #f7f7f7;
  border-radius: 6px;
  padding: 16px 20px;
  margin: 16px 0;
  font-size: 0.95em;
}

.survey-prompt {
  font-weight: bold;
  margin: 0 0 10px;
}

.survey-question label {
  display: block;
  margin-bottom: 4px;
  cursor: pointer;
}

.survey-question label input[type="radio"] {
  margin-right: 6px;
}

#survey-age {
  font-family: Georgia, serif;
  font-size: 1em;
  padding: 6px 8px;
  border: 1px solid #ccc;
  border-radius: 3px;
  width: 120px;
}

#survey-feedback {
  display: block;
  width: 100%;
  font-family: Georgia, serif;
  font-size: 1em;
  padding: 6px 8px;
  border: 1px solid #ccc;
  border-radius: 3px;
  resize: vertical;
}

.survey-optional {
  font-weight: normal;
  color: #777;
}

/* Responsive */
@media (max-width: 600px) {
  #container {
    padding: 28px 20px;
    margin: 10px auto;
  }

  #stimulus-word,
  #stimulus-question,
  #dv-line {
    font-size: 1.3em;
  }

  /* The dial needs a fine pointer, so narrow viewports are blocked before a
     trial is ever shown. The SVG scales to whatever width it is given, so it
     needs no override here; the labels just get small. */
}
