/* dolfin -- dark, high-contrast, glanceable from across the room. */

:root {
  color-scheme: dark;

  --bg: #05070a;
  --panel: #0c121a;
  --line: #1d2733;
  --text: #e9eff6;
  --muted: #8ba0b6;
  --danger: #ff7a6b;
  --accent: #ff6b3d;

  /* overridden per phase on <body> */
  --phase: #9ab0c6;

  --font: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", sans-serif;
  --mono: ui-monospace, "SF Mono", "JetBrains Mono", Menlo, Consolas, monospace;

  --pad: clamp(0.75rem, 2.5vmin, 2rem);
  --value-box: 9rem; /* every value box, and the Reset button under them */
}

/* the swatches on the Customization tab set the --color-* properties; the
   fallbacks here are the defaults, and stay in step with PHASE_COLORS in
   dolfin.js. Everything else -- the bars, the background, the phase name -- is
   derived from --phase, so these five values are the only ones to set. */
body.phase-warmup   { --phase: var(--color-warmup, #f2a63a); }
body.phase-effort   { --phase: var(--color-effort, #ff5c3d); }
body.phase-rest     { --phase: var(--color-rest, #35b7ff); }
body.phase-cooldown { --phase: var(--color-cooldown, #2fd6a6); }
body.phase-done     { --phase: var(--color-done, #9ab0c6); }

* { box-sizing: border-box; }
[hidden] { display: none !important; }

html, body {
  margin: 0;
  height: 100%;
  overflow: hidden;
}

body {
  font-family: var(--font);
  color: var(--text);
  background: var(--bg);
  -webkit-text-size-adjust: 100%;
  -webkit-tap-highlight-color: transparent;
}

body.screen-session {
  background: var(--bg);
  background: color-mix(in oklab, var(--phase) 13%, var(--bg));
  transition: background-color 350ms ease;
}

.screen {
  height: 100dvh;
  padding:
    calc(var(--pad) + env(safe-area-inset-top))
    calc(var(--pad) + env(safe-area-inset-right))
    calc(var(--pad) + env(safe-area-inset-bottom))
    calc(var(--pad) + env(safe-area-inset-left));
}

/* -- setup --------------------------------------------------------------- */

.setup {
  display: grid;
  justify-items: center;
  /* the card hangs from the top rather than sitting in the middle: the four
     panels are of four different heights, and centring moved the head and the
     tab strip every time you switched tab. Starting at the top also keeps a
     card taller than the viewport scrollable from its head, which is what the
     "safe" in a centred alignment was there for */
  align-content: start;
  overflow-y: auto;
}

.setup-card {
  width: min(34rem, 100%);
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 1rem;
  padding: clamp(1.25rem, 4vmin, 2.25rem);
}

.setup-head {
  display: flex;
  align-items: center;
  gap: 0.9rem;
  margin-bottom: 1.75rem;
}

.logo {
  width: 3.25rem;
  height: 3.25rem;
  border-radius: 0.7rem;
  flex: none;
}

.setup-head h1 {
  margin: 0;
  font-size: 1.9rem;
  letter-spacing: -0.02em;
}

.tagline {
  margin: 0.15rem 0 0;
  color: var(--muted);
  font-size: 0.9rem;
}

/* the sections of the form share one strip; with four of them "Customization"
   no longer fits a narrow screen, so the strip wraps rather than being clipped
   by the page's own overflow:hidden */
.tabs {
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem;
  margin-bottom: 1.25rem;
  border-bottom: 1px solid var(--line);
}

/* deliberately not .btn: a filled box here would compete with Start underneath */
.tab {
  flex: 1 1 auto;
  min-height: 2.9rem;
  padding: 0 0.75rem;
  font-family: inherit;
  font-size: 0.95rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: var(--muted);
  background: transparent;
  border: 0;
  /* sits on top of the strip's own line, so the two never double up */
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
  cursor: pointer;
}

.tab:hover { color: var(--text); }

/* keyed off the ARIA state, so what is seen and what is announced cannot drift */
.tab[aria-selected="true"] {
  color: var(--text);
  border-bottom-color: var(--accent);
}

.tab:focus-visible {
  outline: 2px solid var(--accent);
  /* inset, so the ring is not clipped by the card padding */
  outline-offset: -2px;
  border-radius: 0.4rem 0.4rem 0 0;
}

.field { margin-bottom: 1.1rem; }

/* a box drawn round a set of related controls, named by its legend. A fieldset
   carries a good deal of browser styling of its own, hence the reset, and the
   min-width, without which it refuses to shrink below its content */
.group {
  min-width: 0;
  /* a wider gap below than the fields use: the box has a border of its own to
     clear, and its Reset colours button must not read as the form's Reset */
  margin: 0 0 2rem;
  padding: 0.75rem 0.9rem 0.9rem;
  border: 1px solid var(--line);
  border-radius: 0.7rem;
}

.group legend {
  padding: 0 0.4rem;
  font-size: 0.92rem;
  color: var(--muted);
}

.field label {
  display: block;
  margin-bottom: 0.4rem;
  font-size: 0.92rem;
  color: var(--muted);
}

/* slider left, value box right, on one row */
.row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) var(--value-box);
  align-items: center;
  gap: 0.75rem;
}

/* bare counts are short enough to look stranded at the left edge of their
   box, unlike the durations, which very nearly fill theirs */
#notice,
#reps { text-align: center; }

.field .row input[type="range"] {
  width: 100%;
  min-height: 0;
  height: 1.6rem;
  padding: 0;
  background: none;
  border: 0;
  border-radius: 0;
  accent-color: var(--accent);
  cursor: pointer;
}

.field input {
  width: 100%;
  min-height: 3.1rem;
  padding: 0 0.9rem;
  font-family: var(--mono);
  font-size: 1.35rem;
  font-variant-numeric: tabular-nums;
  color: var(--text);
  background: #070c12;
  border: 1px solid var(--line);
  border-radius: 0.6rem;
}

/* the plan box: tall enough to hold a whole session at a glance, and monospaced
   so the times line up under each other. It grows downwards only: widening it
   would push it out of the card */
.field textarea {
  display: block;
  width: 100%;
  min-height: clamp(9rem, 32vh, 18rem);
  padding: 0.7rem 0.9rem;
  font-family: var(--mono);
  font-size: 1rem;
  line-height: 1.5;
  color: var(--text);
  background: #070c12;
  border: 1px solid var(--line);
  border-radius: 0.6rem;
  resize: vertical;
}

/* the combobox wears the same box as the inputs; it spans the panel because
   "(01:45 + 00:45) x 24" does not fit the narrow value box the rows use */
.field select {
  width: 100%;
  min-height: 3.1rem;
  padding: 0 2.4rem 0 0.9rem;
  font-family: var(--mono);
  /* the labels are long and fixed-width: the longest of them,
     "(01:45 + 00:45) x 24", is 20 monospace characters, and a native select
     clips rather than scrolls. On a 320px screen the box gives the text about
     200px, so it has to scale down with the viewport, capping at a step below
     the inputs' 1.35rem. The group headings run longer than any label, but
     they are only ever drawn in the popup, which the browser sizes itself */
  font-size: clamp(0.8rem, 4vw, 1.15rem);
  color: var(--text);
  background: #070c12;
  border: 1px solid var(--line);
  border-radius: 0.6rem;
  cursor: pointer;
  /* chevron drawn inline rather than fetched, so the app stays offline-complete */
  appearance: none;
  -webkit-appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8'%3E%3Cpath fill='none' stroke='%238ba0b6' stroke-width='1.75' stroke-linecap='round' stroke-linejoin='round' d='M1 1.5 6 6.5l5-5'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 0.9rem center;
  background-size: 0.75rem;
}

/* the headings of the groups the programs are listed under. The popup is drawn
   by the browser, so this only lands where it lets them be styled; the point is
   that a heading reads as a heading and not as one more program */
.field optgroup {
  color: var(--muted);
  background: #070c12;
  font-style: normal;
}

.field select:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-color: transparent;
}

.field input:focus-visible,
.field textarea:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-color: transparent;
}

.field input[aria-invalid="true"],
.field textarea[aria-invalid="true"] { border-color: var(--danger); }

/* a line of guidance under a control, quieter than the label above it */
.field-hint {
  margin: 0.45rem 0 0;
  font-size: 0.82rem;
  line-height: 1.45;
  color: var(--muted);
}

.field-hint b {
  font-family: var(--mono);
  font-weight: 700;
  color: var(--text);
}

.error {
  margin: 0.4rem 0 0;
  font-size: 0.85rem;
  color: var(--danger);
}

.field-check { margin-bottom: 1.25rem; }

/* `.field label` above is display:block, muted and small -- these rules have to
   out-specify it, hence `label.check` rather than plain `.check` */
.field label.check {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.7rem;
  min-height: 3.1rem;
  margin-bottom: 0;
  padding: 0 0.9rem;
  font-size: 1.1rem;
  color: var(--text);
  background: #070c12;
  border: 1px solid var(--line);
  border-radius: 0.6rem;
  cursor: pointer;
}

/* likewise `.field input`, which would stretch the box to full width */
.field label.check input {
  flex: none;
  width: 1.4rem;
  height: 1.4rem;
  min-height: 0;
  padding: 0;
  background: none;
  border: 0;
  border-radius: 0;
  accent-color: var(--accent);
  cursor: pointer;
}

/* the swatch takes the place of a checkbox in its row, so it is sized like one
   -- a touch wider, since a colour is worth seeing */
.field label.check input[type="color"] {
  width: 3.2rem;
  height: 1.9rem;
  border: 1px solid var(--line);
  border-radius: 0.35rem;
}

/* without these the browser draws its own frame inside our box */
.field input[type="color"]::-webkit-color-swatch-wrapper { padding: 0; }
.field input[type="color"]::-webkit-color-swatch { border: 0; border-radius: 0.25rem; }
.field input[type="color"]::-moz-color-swatch { border: 0; border-radius: 0.25rem; }

.field label.check:focus-within {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* the session total is a reading, not a control: it sits on the same two-track
   row as the durations, so its caption spans the slider and its value lines up
   with the boxes above. Nothing in it can be typed into or selected. */
.field .readonly-box {
  display: flex;
  align-items: center;
  min-height: 3.1rem;
  margin-bottom: 0;
  padding: 0 0.9rem;
  background: #070c12;
  border: 1px solid var(--line);
  border-radius: 0.6rem;
  cursor: default;
  user-select: none;
  -webkit-user-select: none;
}

/* out-specifies the generic `.field label`, which is block, small and muted */
.field label.readonly-box {
  font-size: 1.1rem;
  color: var(--text);
}

.readonly-value {
  font-family: var(--mono);
  font-size: 1.35rem;
  font-variant-numeric: tabular-nums;
  color: var(--text);
}

.actions {
  display: flex;
  gap: 0.75rem;
}

.btn {
  flex: 1;
  min-height: 3.25rem;
  padding: 0 1.25rem;
  font-family: inherit;
  font-size: 1.05rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  border-radius: 0.6rem;
  border: 1px solid transparent;
  cursor: pointer;
}

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

.btn-primary {
  color: #140904;
  background: var(--accent);
}

.btn-primary:hover { filter: brightness(1.08); }

/* with Start hidden on the Customization tab, Reset has the row to itself */
#start-btn[hidden] + #reset-btn { flex: 1; }

.btn-ghost {
  flex: 0 0 var(--value-box);
  color: var(--muted);
  background: transparent;
  border-color: var(--line);
}

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

/* a lone ghost button spanning its own row, rather than sharing one with Start.
   Must come after .btn-ghost, whose `flex: 0 0` it overrides at equal weight */
.btn-wide { flex: 1; }

.hint {
  margin: 1.1rem 0 0;
  text-align: center;
  font-size: 0.82rem;
  color: var(--muted);
}

.hint a {
  color: var(--accent);
  text-decoration: none;
}

/* keep each phrase whole when the footer wraps */
.made,
.version { white-space: nowrap; }

/* drawn rather than the emoji, so it takes the theme colour */
.heart {
  width: 1.05em;
  height: 1.05em;
  vertical-align: -0.18em;
  fill: var(--accent);
}

.hint a:hover { text-decoration: underline; }

.hint a:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: 0.2rem;
}

/* -- session ------------------------------------------------------------- */

/* two bands: the phase being run takes two thirds of the height, the session as
   a whole the remaining third */
.session {
  display: grid;
  grid-template-rows: 2fr 1fr;
  gap: clamp(0.5rem, 2vh, 1.5rem);
  cursor: pointer;
  user-select: none;
  -webkit-user-select: none;
  /* every reading on this screen is capped in vh, and each cap is set for the
     band that holds it, so the caps travel with the split: these are the ones
     the bands above are due, the "Small" the setting opens on. The rules that spend them are at the end of this
     section, and nothing else moves with the split -- the width caps there
     answer to the width, which the split leaves alone. */
  --cap-counter: 13vh;
  --cap-counter-done: 7.5vh;
  --cap-counter-long: 10.5vh;
  --cap-countdown: 33vh;
  --cap-countdown-long: 24vh;
  --cap-reading: 20vh;
}

/* "Large", from the "Progress panel" setting: two equal bands instead, the
   session taking half the height and the phase the other half. The top band
   loses a quarter of its height and the bottom one gains a half, so each cap
   moves the same way -- back to what these were before the phase was given two
   thirds of the screen. */
.session.panel-large {
  grid-template-rows: 1fr 1fr;
  --cap-counter: 20vh;
  --cap-counter-done: 11vh;
  --cap-counter-long: 16vh;
  --cap-countdown: 25vh;
  --cap-countdown-long: 18vh;
  --cap-reading: 15vh;
}

/* "Hide": no strip at all, so the phase band runs down to the footer, which
   stays -- the time left and the buttons live in it. The readings it holds gain
   half again the height the bands above leave them, and the counter caps are
   left out: nothing they size is on screen. */
.session.panel-hide {
  grid-template-rows: 1fr auto;
  --cap-countdown: 50vh;
  --cap-countdown-long: 36vh;
  --cap-reading: 30vh;
}

/* the strip, the veil, and the counter drawn over them go together; the footer
   under them is then the only row the band has, and wants no gap above it */
.session.panel-hide .overall-bar { display: none; }
.session.panel-hide .half-bottom { grid-template-rows: auto; }

.half {
  display: grid;
  gap: clamp(0.35rem, 1.2vh, 0.9rem);
  min-height: 0;
}

/* the halves mirror each other: caption then bar, bar then meta */
.half-top    { grid-template-rows: auto minmax(0, 1fr); }
.half-bottom { grid-template-rows: minmax(0, 1fr) auto; }

.phase-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
}

.phase-name {
  font-size: clamp(1.4rem, 5vmin, 3rem);
  font-weight: 800;
  letter-spacing: 0.06em;
  color: var(--phase);
}

.next {
  font-size: clamp(0.9rem, 3vmin, 1.8rem);
  letter-spacing: 0.04em;
  color: var(--muted);
  text-align: right;
}

.bar {
  position: relative;
  min-height: 0;
  background: rgba(255, 255, 255, 0.07);
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: 0.9rem;
  overflow: hidden;
}

.bar-fill {
  position: absolute;
  inset: 0;
  transform: scaleX(0);
  transform-origin: left center;
  will-change: transform;
}

.phase-bar-fill { background: var(--phase); }

/* the whole session laid out end to end: one box per phase, each taking the
   share of the width its duration takes of the session, in the colour of its
   kind. The bar clips it, so the ends come out rounded with it. */
.strip {
  position: absolute;
  inset: 0;
  display: flex;
  gap: 0 var(--seg-gap, 0px);
}

.strip-seg {
  /* the basis is zero and the grow factor is the phase's length in ms, set by
     buildStrip(), so the boxes share the width in proportion */
  flex: 1 1 0;
  min-width: 0;
}

/* too many phases to draw one by one: the strip stays a single band that
   follows the phase running, which is what the bar was before */
.strip.plain {
  background: rgba(255, 255, 255, 0.28);
  background: color-mix(in oklab, var(--phase) 24%, transparent);
}

/* well back from the phase colour, quieter even than the single bar this
   replaced was: any stronger and the strip pulls the eye off the countdown,
   which is what is being read. This is the one number to turn to have it louder
   or quieter. */
.strip-seg-warmup   { background: color-mix(in oklab, var(--color-warmup, #f2a63a) 24%, transparent); }
.strip-seg-effort   { background: color-mix(in oklab, var(--color-effort, #ff5c3d) 24%, transparent); }
.strip-seg-rest     { background: color-mix(in oklab, var(--color-rest, #35b7ff) 24%, transparent); }
.strip-seg-cooldown { background: color-mix(in oklab, var(--color-cooldown, #2fd6a6) 24%, transparent); }

/* what is left to run, dimmed: it shrinks to the right as the session goes, so
   its edge is where you are */
.strip-veil {
  position: absolute;
  inset: 0;
  transform-origin: right center;
  background: rgba(5, 7, 10, 0.7);
  will-change: transform;
}

/* the reading sits over the bar, so it needs to survive both track and fill.
   It wraps: a long label drops under the countdown instead of overflowing */
.bar-label {
  position: absolute;
  inset: 0;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  align-content: center;
  justify-content: center;
  padding: 0 0.5rem;
  text-align: center;
  text-shadow: 0 0.02em 0.09em rgba(0, 0, 0, 0.55);
}

.countdown {
  font-family: var(--mono);
  font-size: min(var(--cap-countdown), 27vw);
  font-weight: 700;
  line-height: 1;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.02em;
}

.rep-counter {
  font-family: var(--mono);
  font-size: min(var(--cap-counter), 16vw);
  font-weight: 700;
  line-height: 1;
  font-variant-numeric: tabular-nums;
  /* sized in em, so it holds up over whichever box it happens to sit on --
     the one on .bar-label is worth a pixel at the size that box's text is */
  text-shadow: 0 0.02em 0.05em rgba(0, 0, 0, 0.75), 0 0 0.14em rgba(0, 0, 0, 0.55);
}

/* the "@ 20 spm" a generic plan may carry, on a line of its own under the
   countdown of the phase it belongs to, and the same size as it -- see the
   rules below */
.phase-label {
  font-family: var(--mono);
  font-weight: 700;
  line-height: 1;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.02em;
  white-space: nowrap;
}

/* no label, no gap: an empty span would still push the countdown off centre */
.phase-label:empty { display: none; }

/* long readings (hour-long phases, three-digit reps) step down a size so they
   never run out of the bar on a narrow screen */
.countdown.long   { font-size: min(var(--cap-countdown-long), 13vw); }
.rep-counter.long { font-size: min(var(--cap-counter-long), 11vw); }
/* carrying a label, the bar holds two lines -- the countdown, and the label under
   it -- both in one size, so the label stands exactly as tall as the countdown.
   That size is the tightest of three limits: the height the two lines and the gap
   between them have to share, the width the countdown needs, and the width the
   label needs, which is what the session code counts its characters for. */
.phase-bar.labelled {
  --reading: min(var(--cap-reading), 27vw, calc(140vw / var(--label-chars, 8)));
}

/* the countdown on top, the label under it, parted by a gap in their own size */
.phase-bar.labelled .bar-label {
  flex-direction: column;
  row-gap: calc(0.12 * var(--reading));
}

.phase-bar.labelled .countdown,
.phase-bar.labelled .phase-label { font-size: var(--reading); }

/* "Workout Completed" is seventeen characters, half again as long as the widest
   reading the counter otherwise shows, so the finished state drops a step more */
body.phase-done .rep-counter { font-size: min(var(--cap-counter-done), 8vw); }

@media (prefers-reduced-motion: no-preference) {
  .session.urgent .countdown { animation: throb 1s ease-in-out infinite; }
}

@keyframes throb {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.5; }
}

.overall-meta {
  display: flex;
  align-items: center;
  justify-content: space-between;
  /* three buttons plus the clock outgrow a narrow phone, so the buttons drop to
     a line of their own rather than running off the edge */
  flex-wrap: wrap;
  gap: 0.5rem 1rem;
  font-family: var(--mono);
  /* same size as "next: ..." in the top half, for symmetry */
  font-size: clamp(0.9rem, 3vmin, 1.8rem);
  font-variant-numeric: tabular-nums;
  color: var(--muted);
}

.session-buttons {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  /* keeps the group hard right whether or not it wrapped onto its own line */
  margin-left: auto;
}

.btn-end {
  flex: 0 0 auto;
  min-height: 2.1rem;
  padding: 0 0.9rem;
  font-family: var(--font);
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--muted);
  background: transparent;
  border-color: rgba(255, 255, 255, 0.16);
  border-radius: 0.45rem;
}

.btn-end:hover { color: var(--text); }

/* ending the session is worth spotting from across the room, so it carries the
   accent throughout, not only once the session is over */
#end-btn {
  color: #140904;
  background: var(--accent);
  border-color: transparent;
}

#end-btn:hover { filter: brightness(1.08); }

/* nothing left to skip once the session is over */
body.phase-done #skip-btn { display: none; }

/* by then it is the only thing left to do, so it also grows */
body.phase-done #end-btn {
  min-height: 2.6rem;
  font-size: 1rem;
}

.paused {
  position: fixed;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.75rem;
  background: rgba(5, 7, 10, 0.72);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
}

.paused-word {
  font-size: clamp(2.5rem, 14vmin, 9rem);
  font-weight: 800;
  letter-spacing: 0.12em;
}

.paused-hint {
  font-size: clamp(0.85rem, 2.6vmin, 1.3rem);
  color: var(--muted);
}
