/* Bible Minute — the clock face.
 *
 * One column, centred, no chrome. The page is meant to be left open on a
 * second monitor, so nothing animates faster than a minute except the alarm,
 * and everything obeys prefers-reduced-motion.
 */

@font-face {
  font-family: 'EB Garamond';
  src: url('fonts/EBGaramond-Regular.woff2') format('woff2');
  font-weight: 400; font-style: normal; font-display: swap;
}
@font-face {
  font-family: 'EB Garamond';
  src: url('fonts/EBGaramond-Italic.woff2') format('woff2');
  font-weight: 400; font-style: italic; font-display: swap;
}

:root {
  --ink: #f3ece0;
  --ink-dim: #a99f8e;
  --ink-faint: #6b6355;
  --bg: #0d0f14;
  --bg-lift: #151821;
  --gold: #c9a961;
  --serif: 'EB Garamond', Georgia, 'Times New Roman', serif;
  --measure: 40rem;
  --ghost: .1;

  /* The scene. Sky from the top of the page to the bottom, and a sun that arcs
   * left to right across the real day.
   *
   * The sky is bright by day and dark by night, and the type follows it — see
   * the sky-ink set below. */
  --ground: #07080c;
  /* Faint marks that sit ON THE SKY — the masthead, the ghost seconds, the
   * date. --ink-faint was built for a dark page and disappears against a
   * daylight blue, so these flip to a dark ink when the sky is bright. Text on
   * the GROUND keeps --ink-faint, which is what the footer uses. */
  /* ONE INK, AT EVERY HOUR, PLUS A HALO.
   *
   * The type carries a soft halo at every hour, the way type over photography
   * always has. That is what lets ONE ink serve a sky whose brightness varies
   * across the screen and moves with the sun: at sunrise the text near the sun
   * sits on gold while the far corner is violet, and the halo covers both.
   *
   * The three DAYLIGHT phases are the exception, and they are the exception
   * because they are uniformly bright rather than bright in one corner. There,
   * cream with a halo goes hazy and dark ink with a LIGHT halo is crisp — so
   * the ink and the halo turn over together. Tried without the halo flip first:
   * a dark shadow under dark text just smudges it. */
  --sky-ink: var(--ink);
  --sky-dim: rgba(243, 236, 224, .84);
  --sky-faint: rgba(243, 236, 224, .62);
  --sky-ghost: rgba(243, 236, 224, .17);
  --sky-gold: #f0cf86;
  --halo: 0 1px 16px rgba(8, 12, 28, .55), 0 0 3px rgba(8, 12, 28, .35);
  /* The native time-picker glyph is drawn dark, and every background this page
   * has is dark, so it always inverts. It was per-phase while there was a
   * daylight sky to sit on. */
  --picker-invert: 1;


  /* The ghost seconds' size. The MIRROR uses it too — it reserves exactly the
   * seconds' width on the other side, so the two must move together or the
   * bright h:mm stops being centred. */
  --secs: .58em;

  /* main's side padding. The land cancels exactly this to reach the screen
   * edges, so the two must be the same number. */
  --pad-x: 1.25rem;
}

/* SIZING IS PHONE-FIRST, AND THAT IS WHAT THE vw TERMS ARE FOR.
 *
 * Every size below is a clamp, and on a phone the MIDDLE term has to be the one
 * that wins or the page renders at its desktop minimum on a 390px screen. That
 * is what was happening: .time was clamp(3rem, 13vw, 8rem), and 13vw of 390px
 * is 50.7px, just under the 3rem floor — so the floor won, at every phone width,
 * and the clock sat at 48px on a screen with room for 80.
 *
 * The coefficients are chosen against a 390px viewport, and the max terms are
 * what hold desktop where it already was. Crossovers land between 395px and
 * 640px, so the vw term governs every phone and the cap governs everything
 * wider.
 *
 * The binding constraint on .time is width, not taste: the run is the mirror
 * plus h:mm plus the ghost seconds, measured at 3.91em. At 20vw that is 78% of
 * the viewport, which still clears the padding on a 320px phone. */

* { box-sizing: border-box; }

html { height: 100%; }

/* One column the height of the viewport: the clock takes whatever is left over
 * after the alarm bar and the footer, so both of those sit ON the first screen.
 *
 * They did not before. main was min-height:100% with the footer after it, which
 * put the footer exactly one scroll below the fold on every page, and the alarm
 * bar was position:fixed over the top of it. Nobody scrolls a clock. */
body {
  min-height: 100%;
  display: flex;
  flex-direction: column;
  margin: 0;
  background: var(--ground);
  transition: background-color 6s linear;
  color: var(--ink);
  font-family: var(--serif);
  font-size: 18px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}


/* ── the corners ─────────────────────────────────────────────────────────
 *
 * Momentum's shape — the clock alone in the middle, everything else pushed to a
 * corner. Fixed positioning rather than a grid, because main has to be able to
 * fill and centre without the corners taking part in its layout.
 *
 * Both the clock and the 720 minute pages opt in with body.corners; they differ
 * only in what they have to put there. A minute page has no alarm bar, so its
 * top right is empty, and its promo is one baked link rather than a random pick.
 *
 * ⚠️ Below 720px the corners would collide, so everything falls back to the
 * stacked column. The brief was desktop; a phone gets the old flow. */
@media (min-width: 720px) {
  body.corners { display: block; }

  body.corners main {
    min-height: 100vh;
    justify-content: center;
    padding: 6rem 1.25rem;
  }

  /* The tray exists only to make a row on a phone. Here the corners are fixed
   * to the window, so it has to stop being a box at all. */
  body.corners .tray { display: contents; }

  body.corners .head,
  body.corners .credits,
  body.corners .promo {
    position: fixed;
    z-index: 3;
    margin: 0;
  }

  body.corners .head {
    top: 1.5rem;
    left: 1.9rem;
    display: flex;
    flex-direction: row;
    align-items: center;
    gap: .62rem;
    text-align: left;
    padding: 0;
    line-height: 1.35;
  }
  /* A minute page wraps its masthead in a link back to the clock; the clock's
   * own has no wrapper. Either way the mark is beside the words, not above. */
  body.corners .head a {
    display: flex;
    flex-direction: row;
    align-items: center;
    gap: .62rem;
    text-decoration: none;
    color: inherit;
  }
  body.corners .head a:hover b { text-decoration: underline; text-underline-offset: .18em; }
  body.corners .head b { font-weight: 400; color: var(--sky-dim); letter-spacing: .04em; }
  body.corners .head span { line-height: 1.2em; }

  /* Collapsed to its label, and it slides open under the cursor. A clock that
   * nobody is setting an alarm on should not carry a time picker and two
   * buttons in its corner all day.
   *
   * The grid 0fr -> 1fr is what makes it animate to the CONTENT's width: a
   * max-width transition has to be given a number, and the number is wrong as
   * soon as the status text appears ("Alarm set for 07:15" plus a Clear button).
   *
   * :focus-within opens it for the keyboard, and data-armed keeps it open once
   * an alarm is set, because the countdown is the whole point then. That used
   * to be a :has(), which worked but depended on a selector the JS could state
   * outright — it already knows when the alarm is armed. */
  /* The pair become one fixed cluster in the corner, so the alarm can grow
   * leftwards as it opens without ever pushing the info button off the edge. */
  body.corners .tools {
    display: flex;
    align-items: center;
    gap: .6rem;
    position: fixed;
    top: 1.35rem;
    right: 1.9rem;
    z-index: 3;
  }
  /* margin:0 matters more than it looks. .alarm keeps a 1.1rem bottom margin for
   * the phone's stacked flow, and inside this flex row that margin is part of
   * its box — so centring the boxes put the visible pill half a margin (8.8px)
   * above the info button. It used to get margin:0 from the shared corner
   * group, which it left when .tools was introduced. */
  body.corners .alarm { position: relative; top: auto; right: auto; margin: 0; }
  body.corners .info { position: relative; top: auto; right: auto; }
  /* Hover and keyboard focus open it too, on top of the tap that works
   * everywhere. Neither exists on a phone, which is why the tap had to. */
  body.corners .alarm:hover .fold,
  body.corners .alarm:focus-within .fold {
    grid-template-columns: 1fr;
    opacity: 1;
  }

  /* Both credits, one block, one corner. Separately they were a picture credit
   * bottom left and a translation credit bottom centre, which read as two
   * afterthoughts rather than one line of provenance. */
  body.corners .credits {
    bottom: 1.5rem;
    left: 1.9rem;
    max-width: 34rem;
    text-align: left;
  }
  body.corners .credit,
  body.corners .foot { padding: 0; text-align: left; }

  /* The promo. Transparent, a squared logo and two lines — the same shape
   * Momentum gives its own footer, and the slot the full-width gold bar used to
   * occupy on this page. */
  body.corners .promo {
    bottom: 1.4rem;
    right: 1.4rem;
    display: flex;
    align-items: center;
    gap: .62rem;
    text-decoration: none;
    color: var(--sky-dim);
    max-width: 21rem;
  }
  body.corners .promo[hidden] { display: none; }
  body.corners .promo img {
    width: 44px;
    height: 44px;
    border-radius: 10px;
    flex: 0 0 auto;
    box-shadow: 0 1px 10px rgba(0, 0, 0, .35);
  }
  body.corners .promo span {
    display: block;
    font-size: clamp(.8rem, 3vw, .88rem);
    line-height: 1.35;
    text-align: right;
  }
  body.corners .promo b { display: block; font-weight: 400; color: var(--sky-ink); }
  body.corners .promo:hover b { text-decoration: underline; text-underline-offset: .18em; }
  /* The logo is the only square thing on the page, so it is what should move.
   * A lift and a slightly deeper shadow: the card picks itself up off the
   * photograph rather than growing, which at 44px would only look blurry. */
  body.corners .promo img { transition: transform .28s cubic-bezier(.2, .7, .2, 1), box-shadow .28s ease; }
  body.corners .promo:hover img {
    transform: translateY(-3px);
    box-shadow: 0 6px 18px rgba(0, 0, 0, .5);
  }
  body.corners .promo:hover span { color: var(--sky-ink); }
  body.corners .promo span { transition: color .28s ease; }
}

/* ── the photograph ──────────────────────────────────────────────────────
 *
 * Behind ?bg=photo only. The gradient sky stays the default and stays the only
 * thing the 720 minute pages ever get: a full-bleed hero is the single largest
 * thing Google measures on a landing page, and those pages currently have no
 * JavaScript and a near-instant LCP. This is the clock's alone.
 *
 * THE SCRIM IS THE WHOLE TRICK. A photograph is not a background until
 * something makes it safe for type. It is heaviest at the top and bottom, where
 * the masthead, the footer and the corners sit, and lighter through the middle
 * so the picture still reads behind the clock. */
.photo { display: none; }
[data-bg='photo'] .photo {
  display: block;
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  overflow: hidden;      /* a zoomed photograph must not spill onto the page */
}
[data-bg='photo'] .photo img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* MOVING THE SUBJECT OUT FROM BEHIND THE CLOCK. A lone tree in the middle of
   * the frame lands directly under the time and the verse, and the two fight.
   * Two knobs, per photo, in credits.json:
   *
   *   focus  an object-position. FREE: cover already throws part of the image
   *          away — our files are about 3:2 and a desktop window nearer 16:9,
   *          so the top and bottom go — and this only says which end to keep.
   *          Vertical moves cost nothing. Horizontal ones do nothing, because
   *          on a wide window there is no horizontal slack to move within.
   *
   *   zoom + pan  scales the image past the viewport and slides it. This is how
   *          you move a subject LEFT or RIGHT. It costs resolution, so it is the
   *          second choice, and pan must stay inside the slack the zoom bought:
   *          zoom 1.2 gives 10% either side, so |pan| <= 8% is safe. */
  object-position: var(--focus, 50% 50%);
  transform: scale(var(--zoom, 1)) translate(var(--pan-x, 0%), var(--pan-y, 0%));
  opacity: 0;
  transition: opacity 1.4s ease;
}
[data-bg='photo'] .photo img[data-ready='true'] { opacity: 1; }

/* ── the drift ───────────────────────────────────────────────────────────
 *
 * A still photograph behind a clock that only changes once a minute makes the
 * whole page look like a screenshot. This is the smallest thing that says
 * otherwise: a push in and across, fifty seconds out and fifty seconds back.
 *
 * The first cut ran 240s over a 4% scale range and was invisible. Speeding it up
 * was only half the fix: the DEFAULT ease-in-out is nearly stationary at both
 * ends, measured at 0.01px per second in the first second, so a page you have
 * just loaded shows you a still photograph and then starts moving once you have
 * stopped looking. The bezier below is a sine ease — soft only at the very
 * turn — so it is moving within a second of arriving.
 *
 * ⚠️ IT USES THE INDEPENDENT `scale` AND `translate` PROPERTIES, NOT `transform`.
 * The img's transform already carries the per-photo framing (zoom and pan from
 * credits.json), and animating transform here would throw that away. scale and
 * translate are applied BEFORE transform and compose with it, so the two do not
 * have to know about each other.
 *
 * The floor is 1.06 rather than 1: at exactly 1 the translate would expose the
 * edge of the picture. 1.06 buys 3% of slack on each side and the drift stays
 * inside 2.4%.
 *
 * The cost is sharpness — these files are 1280px and this enlarges them by six
 * to fourteen percent. Under the scrim, at the sizes a background is actually
 * looked at, that is not visible; on a wall-sized display it would be. */
@keyframes photo-drift {
  from { scale: 1.06; translate: -2.4% 1.2%; }
  to   { scale: 1.14; translate:  2.4% -1.2%; }
}
[data-bg='photo'] .photo img {
  scale: 1.06;
  animation: photo-drift 45s cubic-bezier(.37, 0, .63, 1) infinite alternate;
  /* Its own layer, so the drift never asks the scrim, the grain or the type to
   * repaint with it. */
  will-change: scale, translate;
}
[data-bg='photo'] .photo::after {
  content: '';
  position: absolute;
  inset: 0;
  background:
    linear-gradient(to bottom,
      rgba(6, 9, 18, .74) 0%, rgba(6, 9, 18, .40) 22%,
      rgba(6, 9, 18, .34) 52%, rgba(6, 9, 18, .80) 100%),
    radial-gradient(120% 80% at 50% 45%, transparent 40%, rgba(6, 9, 18, .38) 100%);
}
/* The photo carries its own light, so the daylight ink flip is wrong over it —
 * under that scrim every image is dark. Force the night set back. */
[data-bg='photo'] {
  --sky-ink: var(--ink);
  --sky-dim: rgba(243, 236, 224, .84);
  --sky-faint: rgba(243, 236, 224, .62);
  --sky-ghost: rgba(243, 236, 224, .17);
  --sky-gold: #f0cf86;
  --picker-invert: 1;
  /* The page's own background, seen for the instant before the photograph
   * decodes and anywhere the image cannot cover. Near-black rather than the
   * phase colour, which under a dusk photograph meant a midday blue flash. */
  --ground: rgba(8, 11, 20, .88);
}

/* Who made it, and where it came from. Not decoration: almost nothing in the
 * set is public domain, so this line is the licence term being met. */
.credit {
  position: relative;
  z-index: 1;
  flex: 0 0 auto;
  margin: 0;
  text-align: center;
  font-size: clamp(.8rem, 3.4vw, .85rem);
  line-height: 1.5;
  color: var(--sky-faint);
}
.credit a, .foot a { transition: color .25s ease; }
.credit a:hover, .foot a:hover { color: var(--sky-ink); }
.credit a { color: var(--sky-faint); }

/* ── the tray (phone) ────────────────────────────────────────────────────
 *
 * The two bottom corners, side by side: credits left, promo right. Stacked and
 * centred they took four lines of a small screen and read as a list of
 * footnotes; in a row they read as a footer.
 *
 * align-items:flex-end so the two sit on the same baseline whatever their
 * heights, and the promo is allowed to keep its logo at full size while the
 * credits take whatever width is left. Above 720px this whole element becomes
 * display:contents and the children go back to being fixed to the window. */
.tray {
  position: relative;
  z-index: 1;
  flex: 0 0 auto;
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: .9rem;
  padding: .4rem 1.1rem .9rem;
}
.tray .credits { text-align: left; min-width: 0; }
.tray .credit,
.tray .foot { text-align: left; padding: 0; }
.tray .promo { margin: 0; padding: 0; }

/* ── the cross-promotion slot ────────────────────────────────────────────
 *
 * One of our own apps, picked at random per load (clock.js). It replaced the
 * full-width gold bar on the clock, so this is now the only place a phone gets
 * a way into Rise in Grace: it has to be visible at every width, not just in
 * the desktop corner. z-index because the photograph layer paints over
 * anything that isn't lifted out of the flow. */
.promo {
  position: relative;
  z-index: 1;
  flex: 0 0 auto;
  /* Text first, then the logo. Reversed rather than reordered in the markup so
   * the link still reads name-then-blurb to a screen reader. */
  display: flex;
  flex-direction: row-reverse;
  align-items: center;
  justify-content: center;
  gap: .62rem;
  margin: 0 auto .55rem;
  padding: 0 1.25rem;
  text-decoration: none;
  color: var(--sky-dim);
  font-size: clamp(.82rem, 3.4vw, .9rem);
  line-height: 1.3;
}
.promo img {
  width: 40px;
  height: 40px;
  border-radius: 11px;
  flex: 0 0 auto;
  box-shadow: 0 1px 10px rgba(0, 0, 0, .35);
}
.promo span { display: block; text-align: right; }
.promo b { display: block; font-weight: 400; color: var(--sky-ink); }

/* ── grain ───────────────────────────────────────────────────────────────
 *
 * A flat dark page reads as cheap; the same page with a little grain reads as
 * printed. One tiling SVG of fractal noise, inline as a data URI so it costs no
 * request, at an opacity low enough that you only notice it when it is gone.
 *
 * It sits BELOW the content (z-index 0, above the gradient because ::after
 * paints after ::before). Over the text it would fight the serif at small
 * sizes, and the background is most of the screen anyway. */
body::after {
  content: '';
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  opacity: .055;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  background-size: 180px 180px;
}

/* The masthead. Small and faint on purpose: it names the thing for anyone who
 * arrived on a minute page from a search result, and then gets out of the way
 * of the clock, which is what people came for. */
.head {
  position: relative;
  z-index: 1;
  flex: 0 0 auto;
  padding: 1.15rem 1.25rem .1rem;
  text-align: left;
  font-size: clamp(.85rem, 3.9vw, 1rem);
  letter-spacing: .05em;
  color: var(--sky-faint);
}
/* Top left at every width. Centred, the mark and two lines of different lengths
 * sat as a lump in the middle of a phone with the clock underneath it; against
 * the left edge it reads as a masthead, which is what the desktop corner
 * already did. */
.head { display: flex; align-items: center; justify-content: flex-start; gap: .5rem; }
/* Name over tagline at every width. Side by side they ran to the edge of a
   phone and read as one long sentence rather than a masthead.
   Left-aligned to each other: two centred lines of different lengths give a
   ragged edge on both sides, which reads as an accident rather than a
   masthead. */
.wordmark {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  text-align: left;
  /* The body's 1.5 left a visible gap between the name and the tagline, which
   * read as two separate things rather than a masthead. The desktop corner
   * already set 1.35; this is the same number everywhere. */
  line-height: 1.3;
}

/* THE NAME IS THE MARK, WRITTEN OUT. "Bible:Minute" with the two initials set
 * large is the same B:M the icon carries, so the header no longer needs to show
 * an icon of a name next to the name.
 *
 * The initials are <i> because they are one glyph each and get a size, not an
 * emphasis; the element is the shortest thing that takes a class here and the
 * font-style is turned off. */
.name {
  font-weight: 400;
  font-size: 1.22em;
  letter-spacing: .005em;
  color: var(--sky-ink);
  line-height: 1.15;
}
.name i {
  font-style: normal;
  font-size: 1.3em;
  /* Optical: a larger cap sits on the same baseline but its extra height reads
   * as a step up, so the run needs a hair of space after it, not before. */
  letter-spacing: .01em;
}
/* The colon is the mark's whole idea, but it is set in the same ink as the rest
 * here rather than gold: at this size the tint read as a typo rather than an
 * accent. One line if it should ever echo the icon's gold. */
.name .colon { color: inherit; }
.head a { display: flex; align-items: center; gap: .5rem; }
.head a { color: var(--sky-faint); text-decoration: none; }
.head a:hover { color: var(--ink); }

/* main, .head, .foot {
  text-shadow: var(--halo);
} */

main {
  position: relative;
  z-index: 1;
  flex: 1 0 auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: clamp(1.5rem, 5vh, 3rem);
  padding: 1.75rem var(--pad-x) 1.5rem;
  text-align: center;
}

/* ── the stamp changing ──────────────────────────────────────────────────
 *
 * The book and the digits used to swap instantly while the verse faded, so the
 * citation snapped and the sentence under it drifted. They leave upward and
 * arrive from below now, and the swap happens between the two — 220ms out, then
 * the text changes, then 400ms in.
 *
 * ⚠️ Only .digits moves, not .time. The mirror, the date and the ghost seconds
 * live in the same element and must stay put: the seconds tick every second and
 * animating them would be a strobe.
 *
 * transform does nothing on an inline box, so both get inline-block. That
 * changes .digits' baseline to its own last line box, which is the same line —
 * checked against a render, the date and seconds did not move. */
@keyframes stamp-out {
  to { opacity: 0; transform: translateY(-.16em); }
}
@keyframes stamp-in {
  from { opacity: 0; transform: translateY(.22em); }
}
.book, .digits { display: inline-block; }
.stamp[data-swap='out'] .book,
.stamp[data-swap='out'] .digits { animation: stamp-out .22s ease-in both; }
.stamp[data-swap='in'] .book,
.stamp[data-swap='in'] .digits { animation: stamp-in .4s cubic-bezier(.2, .7, .3, 1) both; }

/* ── the stamp: book over time ──────────────────────────────────────────── */
/* One unit, not two. The time doubles as the chapter:verse, so the book name
 * sits directly on top of it and the gap between them is deliberately tight —
 * far tighter than the gap to the verse below, which is what makes the eye
 * read "1 John 4:18" as a single citation. */
.stamp {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: .1em;
}

/* Centre the whole run and the bright part sits left of centre, which puts the
 * book name over the gap between the minutes and the seconds instead of over
 * the citation. Optical centring ignores what is barely visible, so the thing
 * that has to end up centred is "4:30", not "4:30:47".
 *
 * The fix is a mirror: an invisible ":00" of exactly the ghost's width on the
 * other side. Tabular figures (set on .time) are what make "exactly" true —
 * every ":NN" is the same width as every other, so the balance never shifts as
 * the clock runs. Measured at a 390px box: book centre 195, bright digits
 * centre 195.
 *
 * In flow rather than absolutely positioned, so the run stays symmetric and
 * self-accounting: the layout reserves space for the ghost instead of letting
 * it hang off an edge nothing is measuring.
 *
 * ⚠️ The mirror is a SPAN in the markup, deliberately, and not a ::before on
 * .time. As generated content it applied to every .time on the site — but only
 * the live clock has seconds, so the 720 minute pages and the alarm ring got
 * the left half of a mirror with nothing on the right and sat visibly off
 * centre. A mirror has to ship with the thing it mirrors or not at all. */
.time .mirror {
  font-size: var(--secs);
  visibility: hidden;
}

/* The date rides INSIDE the mirror's reserved space rather than beside it, so
 * adding it costs the composition nothing: the mirror still reserves exactly
 * the ghost seconds' width on the left, and the bright h:mm stays centred to
 * the pixel. The wrapper is the reserved box; the date is centred in it. */
.datewrap { position: relative; display: inline-block; }
.date {
  position: absolute;
  /* Right-aligned inside the reserved box, not centred in it. The ghost
   * seconds begin flush against the digits' right edge, so the date should end
   * flush against their left — that is what makes the two read as a pair
   * rather than as two things that happen to be nearby. left:auto lets the box
   * shrink to its content and hug that edge. */
  left: auto;
  right: -.045em;
  /* Baseline, not centre. The seconds sit on the numerals' baseline, so the
   * date joining them there gives the whole run one line to stand on. Centred
   * instead, it straddled the numerals and read as misaligned.
   *
   * Both offsets are tuned against measured renders and are in em, so they hold
   * at every clamp size. Final: baseline flush with the digits, 12px of gap on
   * the date side against 14px on the seconds side.
   *
   * ⚠️ Measuring this needs care. The clock shows different digits every
   * render, and round glyphs overshoot the baseline by a few px where 1 and 4
   * do not — so a gap or a baseline compared ACROSS two screenshots is noise.
   * Both sides have to be read from the same frame. */
  bottom: .02em;
  display: flex;
  flex-direction: column;
  align-items: center;
  font-size: .26em;
  line-height: 1.2;
  letter-spacing: .04em;
  font-variant-numeric: tabular-nums;
  /* Literally the same ink as the seconds. That only works at this size: an
   * earlier, much smaller date needed .3 to register and measured twice the
   * seconds' brightness, and dropping it to .17 still left it heavier. At .26em
   * there is enough stroke to carry it, so the two share one token and cannot
   * drift apart — including the flip to dark ink over a daylight sky. */
  color: var(--sky-ghost);
}
.date .day { letter-spacing: .06em; }

.book {
  font-style: normal;
  font-size: clamp(1.05rem, 5.9vw, 1.65rem);
  letter-spacing: .16em;
  color: var(--sky-gold);
  /* Optical alignment: letter-spacing adds a trailing space to the last glyph,
   * which pushes a centred line left of the time under it. */
  text-indent: .16em;
}
/* HOLD THE LINE OPEN. This was display:none, so on the clock the book took no
 * space until slots.json arrived and then shoved everything below it down. The
 * non-breaking space gives the empty element a line box of exactly the height
 * the real book name will have. It still shows nothing. */
.book:empty::after { content: '\00a0'; }

/* Ten characters wide including the mirror and the ghost, so the vw term is set
 * from that measured width rather than from how big the digits ought to look. */
.time {
  /* Width sets it, height caps it. Sized on vw alone, a short wide window (a
   * half-height browser, a phone in landscape) got a clock as tall as the room
   * left for everything under it. */
  font-size: min(clamp(3rem, 20vw, 8rem), 19vh);
  line-height: .95;
  letter-spacing: -.02em;
  font-variant-numeric: tabular-nums lining-nums;
  margin: 0 0.05em !important;
  color: var(--sky-ink);
}

/* Hovering the clock brings the ghosts up. They are ghosted by COLOUR, not by
 * opacity — --sky-ghost is a low-alpha ink — so this raises the colour. Setting
 * opacity here instead would multiply an already-faint colour and make them
 * fainter, which is the opposite of the intent and hard to see in a screenshot. */
.time:hover .seconds,
.time:hover .date { color: var(--sky-faint); }

/* Seconds, barely there. The page is meant to be left open, and a clock that
 * never moves reads as a screenshot — this is the pulse that says otherwise.
 *
 * Same size as the hours and minutes: at a tenth opacity the size costs
 * nothing, and matching it keeps one unbroken run of numerals instead of a
 * headline with something small bolted on the end.
 *
 * The opacity is what does the work. The minutes ARE the verse number, so at
 * full strength "4:26:19" reads as a citation that does not exist; at a tenth
 * it is a texture you notice only when you look for it, and "2 Kings 4:26" is
 * still what the eye takes away.
 *
 * aria-hidden in the markup, not here: a number changing every second is noise
 * to a screen reader, and it carries nothing the minutes do not.
 *
 * Tabular figures are inherited from .time, so the ghost never changes width
 * and cannot nudge the centring sixty times a minute. */
.time .seconds {
  font-size: var(--secs);
  color: var(--sky-ghost);
}
.datewrap, .seconds { margin: 0 0.05em; }
.seconds, .date { transition: color .35s ease; }

/* ── the verse ──────────────────────────────────────────────────────────── */
.verse {
  position: relative;   /* anchors .share-note */
  max-width: var(--measure);
  /* Two transitions with different durations. The fade is driven by --fade
   * rather than by setting transition-duration from JS: that property applies
   * to every transitioned property in order, so writing "5s" to it would drag
   * the height along with the opacity.
   *
   * The height is animated because a verse is anything from one line to seven,
   * and main is a centred column — so a swap moved the clock above it and the
   * alarm below it in the same instant. Now the layout glides instead, and it
   * does so in the first second of a five second fade, while the new verse is
   * still almost invisible. */
  transition: opacity var(--fade, .6s) ease,
              transform .25s ease,
              height .85s cubic-bezier(.33, .1, .25, 1);
}
.verse[data-loading='true'] { opacity: 0; }
/* And hold the verse's space too, so the arriving verse nudges the layout rather
 * than throwing it. The number is the MEDIAN rendered line count, measured over
 * all 720 verses in this element at each width: three on a phone, two on a
 * desktop, where the measure is wider. Reserving the median rather than the
 * maximum is what minimises the expected movement (0.56 lines against 0.84 if
 * three were used on desktop too).
 *
 * min-height on .text, not .verse, because the em has to resolve against the
 * text's own size. It is dropped the moment the verse lands, which is before
 * fitVerse measures anything. */
.verse[data-loading='true'] .text { min-height: calc(3 * 1.6em); }
@media (min-width: 720px) {
  .verse[data-loading='true'] .text { min-height: calc(2 * 1.6em); }
}
/* The last seconds of a minute. The duration is set inline by clock.js from the
 * seconds actually left, so this only says where it is going. */
.verse[data-leaving='true'] { opacity: 0; }

.text {
  font-size: min(clamp(1.15rem, 5.9vw, 1.55rem), 4.4vh);
  line-height: 1.6;
  margin: 0;
  text-wrap: pretty;
  color: var(--sky-ink);
}
.verse[data-kind='gap'] .text { font-style: italic; color: var(--sky-dim); }

/* ── share ──────────────────────────────────────────────────────────────── */
/* The whole verse is the target and the icon trails the last word, so the
 * affordance sits inside the sentence instead of on a row of its own. */
.verse[data-share] { cursor: pointer; }
/* A small lift, and the icon lights up with it. `color: opacity(.8)` was here
 * and is not a colour — opacity() is a filter function — so the declaration was
 * dropped and hovering the verse stopped highlighting the icon at all.
 *
 * The scale is on .verse, whose transition list is shared with the minute
 * change; transform is appended there so this eases rather than snapping. */
.verse[data-share]:hover { transform: scale(1.015); }
.verse[data-share]:hover .share { color: var(--gold); opacity: .85; }

/* Inline, riding the text baseline. The padding is the tap target — negative
 * margins keep it from opening up the line height to pay for it. */
.share {
  display: inline-flex;
  align-items: center;
  padding: .35em .3em;
  margin: -.35em -.1em -.35em .12em;
  border: 0;
  background: none;
  color: white;
  opacity: 0.3;
  cursor: pointer;
  vertical-align: baseline;
  transition: color .2s ease;
}
.share svg { width: 1.5em; height: 1.5em; }
.share:hover, .share:focus-visible { opacity: 1; }

/* Absolute, so confirming a copy cannot move the verse it belongs to. */
.share-note {
  position: absolute;
  left: 0;
  right: 0;
  top: 100%;
  margin-top: .7rem;
  font-size: clamp(.85rem, 3.6vw, .95rem);
  color: var(--ink-faint);
  opacity: 0;
  transition: opacity .25s ease;
  pointer-events: none;
}
.share-note[data-show='true'] { opacity: 1; }

/* ── alarm ──────────────────────────────────────────────────────────────── */
.alarm {
  position: relative;   /* in flow, above the ambient gradient */
  z-index: 2;
  align-self: center;
  margin-bottom: 1.1rem;
  display: flex;
  align-items: center;
  gap: .6rem;
  padding: .3rem .8rem;
  /* An outline on the sky, not a slab over it. The frosted panel it used to be
   * was a second surface competing with the horizon; a hairline lets the sky
   * run straight through. Every colour here follows the sky ink, so it holds on
   * a night indigo and on a noon blue alike. */
  border: 1px solid var(--sky-faint);
  border-radius: 999px;
  background: none;
  font-size: clamp(.9rem, 4.4vw, 1.1rem);
  color: var(--sky-dim);
  /* Set + Clear + the label can outgrow a narrow phone once the type is this
   * size, and a control that overflows is worse than one on two lines. */
  flex-wrap: wrap;
  justify-content: center;
  max-width: 100%;
}
.alarm .alarm-label {
  letter-spacing: .04em;
  font-family: var(--serif);
  font-size: inherit;
  color: inherit;
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
}

/* THE FOLD, AT EVERY WIDTH. It was desktop-only, so a phone carried a time
 * picker and two buttons in the bar all day. What differs by width is only what
 * OPENS it: a phone has no hover, so it opens on a tap (data-open, set by
 * clock.js); a desktop keeps hover and focus-within on top of that.
 *
 * The grid 0fr -> 1fr is what makes it animate to the CONTENT's width. A
 * max-width transition has to be given a number, and the number is wrong the
 * moment the status text appears. */
.alarm { gap: 0; flex-wrap: nowrap; white-space: nowrap; max-width: 100%; }
.alarm .fold {
  display: grid;
  grid-template-columns: 0fr;
  transition: grid-template-columns .42s cubic-bezier(.2, .7, .2, 1), opacity .3s ease;
  opacity: 0;
}
.alarm .fold-in {
  display: flex;
  align-items: center;
  gap: .6rem;
  padding-left: .6rem;
  overflow: hidden;
  min-width: 0;
}
.alarm[data-open='true'] .fold,
.alarm[data-armed='true'] .fold {
  grid-template-columns: 1fr;
  opacity: 1;
}

/* The at/in switch. Two words rather than a dropdown: there are exactly two
 * kinds and both fit, so a control that hides one of them behind a click is
 * more machinery than the choice deserves. */
/* A TRACK, so it reads as a switch. Two bare words with only a tint behind the
 * chosen one did not look clickable at all — the unchosen word looked like a
 * label and nobody would guess it was the other half of a control. The border
 * is the whole fix: it says "these two belong together and one of them is on". */
.kinds {
  display: inline-flex;
  gap: 0;
  padding: 2px;
  border: 1px solid var(--sky-faint);
  border-radius: 999px;
}
/* Scoped under .alarm on purpose. `.alarm button` is the gold Set button and
 * has the same specificity as a bare `.kinds button`, so source order decided
 * it and these came out gold — the UNPRESSED one loudest. */
.alarm .kinds button {
  font-family: var(--serif);
  font-size: .92rem;
  letter-spacing: .03em;
  color: var(--sky-faint);
  background: none;
  border: 0;
  border-radius: 999px;
  padding: .16rem .5rem;
  cursor: pointer;
  transition: color .18s ease, background-color .18s ease;
}
.alarm .kinds button[aria-pressed='true'] {
  color: var(--bg);
  background: var(--sky-ink);
}
.alarm .kinds button:hover { color: var(--sky-ink); }
.alarm .kinds button[aria-pressed='true']:hover { color: var(--bg); }

/* ARMED. The kind switch, the field and Set have nothing left to say once an
 * alarm exists; what is left is how long, and a way out. */
.alarm[data-armed='true'] .alarm-label,
.alarm[data-armed='true'] .kinds,
.alarm[data-armed='true'] .field,
.alarm[data-armed='true'] [data-act='set'] { display: none; }
.alarm[data-armed='true'] .status { color: var(--sky-ink); }

.alarm .field { display: inline-flex; align-items: center; gap: .25rem; }
.alarm .field[hidden] { display: none; }
.alarm .unit { color: var(--sky-faint); font-size: .85rem; }
/* Visible to a screen reader, gone from the page: the fields are labelled, but
 * "at 07:15" already reads as a labelled control to anyone who can see it. */
.sr {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0;
  clip-path: inset(50%);
  overflow: hidden;
  white-space: nowrap;
}

.alarm input[type='time'],
.alarm input[type='number'] {
  font-family: var(--serif);
  font-size: .95rem;
  color: var(--sky-ink);
  background: transparent;
  border: 0;
  border-bottom: 1px solid var(--sky-faint);
  padding: .1rem .15rem;
}
/* Wide enough for three digits and no wider. The spinners are dropped because
 * the field is two taps from a keyboard anyway and they eat the width. */
.alarm input[type='number'] { width: 3.1rem; -moz-appearance: textfield; appearance: textfield; }
/* The browser default here is a blue slab on a page with no blue in it. Still a
 * visible focus indicator, just one made of the page's own gold. */
.alarm input:focus-visible,
.alarm .kinds button:focus-visible,
.alarm button:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 2px;
  border-radius: 4px;
}
.alarm input:focus-visible { border-bottom-color: var(--sky-ink); }
.alarm input[type='number']::-webkit-outer-spin-button,
.alarm input[type='number']::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
/* The native picker glyph is drawn dark. It has to invert on a dark sky and
 * must not on a bright one, so the phase carries the switch. */
.alarm input[type='time']::-webkit-calendar-picker-indicator {
  filter: invert(var(--picker-invert, 1));
  opacity: .75;
  cursor: pointer;
}
.alarm button {
  font-family: var(--serif);
  font-size: .9rem;
  color: var(--bg);
  background: var(--gold);
  border: 0;
  border-radius: 999px;
  padding: .32rem .95rem;
  cursor: pointer;
}
.alarm button.ghost {
  background: transparent;
  color: var(--sky-dim);
  border: 1px solid var(--sky-faint);
}
.alarm .status { font-variant-numeric: tabular-nums; }

/* ── embedded in an app ──────────────────────────────────────────────────
 *
 * ?embed=1, for the WKWebView inside Rise in Grace. The alarm would be a second
 * and worse alarm inside an alarm clock, and the promo would be the app selling
 * itself to someone who has already installed it.
 *
 * ⚠️ THE PHOTO CREDIT STAYS. Everything else here is ours to hide; the credit
 * names the photographer whose picture we are showing, and it is the reason we
 * can show it.
 *
 * display:none rather than a build flag, so the embedded page is byte for byte
 * the page the web serves and there is no second thing to keep working. */
[data-embed='app'] .tools,
[data-embed='app'] .promo { display: none; }

/* ── the info button and its dialog ─────────────────────────────────────
 *
 * .tools is display:contents on a phone, so the alarm stays in the flow where
 * it was and only the button is pinned to the corner. Above 720px the pair
 * become one fixed cluster, which is why .alarm's own top/right went away. */
.tools { display: contents; }

.info {
  position: fixed;
  top: 1.1rem;
  right: 1.1rem;
  z-index: 4;
  width: 2rem;
  height: 2rem;
  border-radius: 999px;
  border: 1px solid var(--sky-faint);
  background: none;
  color: var(--sky-dim);
  font-family: var(--serif);
  font-style: italic;
  font-size: 1rem;
  /* A button centres its text on the LINE BOX, and a lower-case italic "i" has
   * a tall ascender and no descender, so the ink lands below the middle of the
   * circle. Flex centres the box; the nudge centres the ink, which is the thing
   * anyone actually looks at. */
  line-height: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  cursor: pointer;
  transition: color .2s ease, border-color .2s ease, background-color .2s ease;
}
.info::before {
  content: 'i';
  display: block;
  transform: translateY(-.02em);
}
.info:hover,
.info:focus-visible {
  color: var(--sky-ink);
  border-color: var(--sky-dim);
  background-color: rgba(255, 255, 255, .08);
}

/* A real <dialog>, so Esc, the focus trap and inertness come from the browser
 * rather than from three listeners that would each have to be got right. */
.about {
  max-width: 34rem;
  margin: auto;
  padding: 2rem 2rem 1.5rem;
  border: 1px solid rgba(243, 236, 224, .16);
  border-radius: 16px;
  background: var(--bg-lift);
  color: var(--ink);
  font-family: var(--serif);
  font-size: 1.02rem;
  line-height: 1.65;
}
.about::backdrop { background: rgba(4, 6, 12, .72); }
.about h2 {
  margin: 0 0 .8rem;
  font-weight: 400;
  font-size: 1.5rem;
  letter-spacing: .04em;
  color: var(--gold);
}
.about p { margin: 0 0 .9rem; color: var(--ink-dim); }
.about em { font-style: italic; color: var(--ink); }
.about a { color: var(--ink); }
.about .cta {
  /* .cta is the ring's gold button; inside the dialog it is a link in a
   * sentence, so every part of the button has to be undone, not just the
   * obvious ones. The letter-spacing was the one that got missed and it showed:
   * "R i s e  i n  G r a c e" in the middle of a paragraph. */
  display: inline;
  padding: 0;
  border-radius: 0;
  background: none;
  color: var(--gold);
  font-size: inherit;
  letter-spacing: inherit;
  font-weight: inherit;
}
.about .by { margin-bottom: 1.3rem; font-size: .92rem; }
.about .close {
  font-family: var(--serif);
  font-size: .95rem;
  color: var(--ink-dim);
  background: none;
  border: 1px solid rgba(243, 236, 224, .2);
  border-radius: 999px;
  padding: .5rem 1.4rem;
  cursor: pointer;
}
.about .close:hover { color: var(--ink); border-color: rgba(243, 236, 224, .4); }

/* ── the ring ───────────────────────────────────────────────────────────── */
.ring {
  position: fixed;
  inset: 0;
  z-index: 10;
  display: none;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1.6rem;
  padding: 2rem 1.5rem;
  text-align: center;
  background: var(--bg-lift);
  animation: pulse 2.6s ease-in-out infinite;
}
.ring[data-open='true'] { display: flex; }
@keyframes pulse {
  0%, 100% { background-color: #151821; }
  50%      { background-color: #1d212d; }
}
.ring .ring-time { font-size: clamp(3rem, 12vw, 6rem); line-height: 1; margin: 0; }
.ring .ring-book { font-size: clamp(.95rem, 3vw, 1.3rem); }
.ring .ring-verse { max-width: var(--measure); }

/* The call to action. It is the whole reason this page exists, so it is the one
 * place with a real button. */
.cta {
  display: inline-block;
  font-size: clamp(1rem, 4.4vw, 1.15rem);
  letter-spacing: .05em;
  color: var(--bg);
  background: var(--gold);
  border-radius: 999px;
  padding: .7rem 1.6rem;
  text-decoration: none;
}
.cta:hover { background: #d8ba75; }
/* A real secondary button beside the gold one, not an underlined afterthought.
 * This is the control someone reaches for while an alarm is going off, which is
 * the worst possible moment to be hunting for a faint line of text. */
.dismiss {
  font-family: var(--serif);
  font-size: 1rem;
  color: var(--ink-dim);
  background: none;
  border: 1px solid var(--ink-faint);
  border-radius: 999px;
  padding: .62rem 1.7rem;
  cursor: pointer;
  transition: color .2s ease, border-color .2s ease, background-color .2s ease;
}
.dismiss:hover,
.dismiss:focus-visible {
  color: var(--ink);
  border-color: var(--ink-dim);
  background-color: rgba(255, 255, 255, .07);
}

/* ── footer / static pages ──────────────────────────────────────────────── */
.foot {
  position: relative;
  z-index: 1;
  flex: 0 0 auto;
  padding: .6rem 1.25rem .9rem;
  text-align: center;
  font-size: clamp(.8rem, 3.4vw, .85rem);
  line-height: 1.5;
  /* On the sky, not the ground, so this follows the sky ink and flips dark when
   * the sky is bright. */
  color: var(--sky-faint);
}
.foot a { color: var(--sky-faint); }
.foot p { margin: 0; }
/* A link must not break mid-phrase — "Rise in" on one line and "Grace" on the
 * next, or "World English" and "Bible". Where the footer wants a line break it
 * says so with <br>. */
.foot a { white-space: nowrap; }

.neighbours {
  display: flex;
  justify-content: center;
  gap: 1.5rem;
  font-size: clamp(.85rem, 4vw, 1rem);
  letter-spacing: .08em;
}
.neighbours a { color: var(--sky-dim); text-decoration: none; }
/* Underlined: the neighbouring minutes, and inside the middle link only the
 * words that name the destination — so that one reads as a sentence with a
 * target in it rather than as a rule under a phrase. Matched by rel, which the
 * prev/next links already carry for crawlers. */
.neighbours .under,
.neighbours a[rel] {
  text-decoration: underline;
  text-underline-offset: .18em;
  text-decoration-thickness: .055em;
}
.neighbours a:hover { color: var(--sky-ink); }

@media (prefers-reduced-motion: reduce) {
  .ring { animation: none; }
  /* The drift is decoration and the first thing to go. The scale stays, because
   * dropping it would show the edge of a photograph that is mid-translate. */
  [data-bg='photo'] .photo img { animation: none; translate: none; }
  .stamp[data-swap] .book, .stamp[data-swap] .digits { animation: none; }
  body { transition: none; }
  .verse { transition: none; }
}
