/* styles.css — Meditation Timer
   Zen / wabi-sabi skin: sumi-ink ground, kinari (unbleached silk) text, gold-leaf accent,
   one vermilion (shu) note for the close control. Mincho serif for the voice, rounded
   gothic for the controls. The brushed ensō is the hero and the progress ring.
   Full-height column (100svh): header, tabs, then the active panel (Meditate: ensō takes
   the spare height, then controls; Settings: manual, bells amount, app, delete). Every
   vertical size is a fluid clamp on svh, so both pages fit a phone screen whole; the
   panels' own scrollbars are a fallback for landscape and very large system text.
   Palette and type choices are documented in context.md ("Design"). */

/* ── Fonts: self-hosted Latin subsets, SIL OFL 1.1 (licences in ../fonts/) ── */
@font-face {
  font-family: 'Shippori Mincho';
  src: url('../fonts/shippori-mincho-700.woff2') format('woff2');
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Zen Maru Gothic';
  src: url('../fonts/zen-maru-gothic-500.woff2') format('woff2');
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Zen Maru Gothic';
  src: url('../fonts/zen-maru-gothic-700.woff2') format('woff2');
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}

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

:root {
  /* Traditional Japanese colours, lifted for legibility on a dark screen. */
  --bg:         #151310;   /* sumi — ink black, warm */
  --bg-deep:    #0d0c0a;
  --surface:    #1f1c17;
  --border:     #3b352c;   /* rikyū-nezumi, darkened */
  --border-hi:  #5a5144;
  --accent:     #d9ae68;   /* kincha / gold leaf */
  --accent-dim: #8f7243;
  --accent-bg:  rgba(217, 174, 104, 0.12);
  --shu:        #d0553a;   /* vermilion, used once: the close control */
  --text:       #efe6d2;   /* kinari — unbleached silk */
  --muted:      #a79d89;   /* ~6.8:1 on --bg, still readable at 12px */
  --faint:      #6f675a;
  --fade:       200ms ease;

  --serif: 'Shippori Mincho', 'Hiragino Mincho ProN', 'Yu Mincho', Georgia, serif;
  --sans:  'Zen Maru Gothic', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;

  --radius:    14px;
  --page-pad-x: clamp(16px, 5vw, 24px);
  /* Every vertical rhythm step scales with the small viewport height (svh), so on a
     short screen the whole column shrinks together instead of only the ensō. */
  --control-gap: clamp(6px, 1.1svh, 10px);
}


/* ── Touch: every tappable surface ───────────────────────────────────────────
   touch-action:manipulation removes double-tap-to-zoom (and with it the tap delay).
   user-select:none stops a slightly long press popping the text-selection toolbar.
   The tap highlight is suppressed because every control defines its own :active. */
button,
.modal-backdrop {
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
  user-select: none;
  -webkit-user-select: none;
}

/* Touch feedback: the tap highlight is gone, so a press must still acknowledge itself. */
button:active:not(:disabled),
.modal-backdrop:active {
  filter: brightness(1.2);
  transform: scale(0.985);
}

button {
  transition: filter 90ms ease, transform 90ms ease;
}

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

@media (prefers-reduced-motion: reduce) {
  button { transition: none; }
  button:active:not(:disabled) { transform: none; }
}

/* On a touchscreen :hover latches on tap, so every :hover rule below is gated behind a
   real pointer; touch gets :active instead. */
html {
  height: 100%;
  overflow: hidden;   /* the page itself never scrolls: everything is sized to fit */
  /* No pull-to-refresh: a flick at the top must never reload mid-meditation. */
  overscroll-behavior-y: contain;
  -webkit-text-size-adjust: 100%;
  background: var(--bg-deep);
}

body {
  /* A lamp over paper: warm light high on the page, falling off into ink. */
  background:
    radial-gradient(ellipse 120% 70% at 50% 28%, #2a241b 0%, var(--bg) 55%, var(--bg-deep) 100%)
    fixed;
  color: var(--text);
  font-family: var(--sans);
  font-weight: 500;
  font-size: 15px;
  line-height: 1.45;
  height: 100vh;      /* fallback for engines without the newer units */
  /* svh is the SMALL viewport height — the screen with every browser bar showing. It is
     the one height that never overflows, so the app fits even with Chrome's URL bar up.
     (dvh would grow when the bar retracts and shrink again while the bar animates.) */
  height: 100svh;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  align-items: center;
  overscroll-behavior: none;
}

/* ── App shell: one full-height column ──
   Header, tabs, then the active panel — the panel is the only flexible child (see
   `.panel` below), so on a tall phone the spare height goes to the hero and the controls
   settle at the bottom, within thumb reach. */
.app {
  /* --p is the page position: 0 = Meditate, 1 = Settings. One variable drives the sliding
     track and the tab highlight, so they always move together (see showTab in app.js). */
  --p: 0;
  --slide-dur: 0.38s;
  --slide-ease: cubic-bezier(0.32, 0.72, 0, 1);
  width: 100%;
  max-width: 440px;
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: clamp(5px, 0.9svh, 10px);
  padding-top: max(env(safe-area-inset-top, 0px), clamp(6px, 1.2svh, 10px));
  /* safe-area-max-inset-* is the constant maximum; the plain inset shrinks as Chrome's
     bottom chin retracts, which reflows the page mid-scroll. */
  padding-bottom: max(env(safe-area-max-inset-bottom, env(safe-area-inset-bottom, 0px)), clamp(6px, 1.2svh, 10px));
  padding-left: max(var(--page-pad-x), env(safe-area-inset-left, 0px));
  padding-right: max(var(--page-pad-x), env(safe-area-inset-right, 0px));
  min-height: 0;
}

/* ── Header ── */

.header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-shrink: 0;
}

.header h1 {
  text-align: left;
  font-family: var(--serif);
  font-size: clamp(16px, 2.5svh, 21px);
  font-weight: 700;
  letter-spacing: 0.06em;
  color: var(--text);
  white-space: nowrap;
}

/* "Close app ×": the word says what the cross does. */
.btn-close {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  height: clamp(30px, 5.4svh, 36px);
  padding: 0 10px 0 12px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: transparent;
  color: var(--muted);
  font-family: var(--sans);
  font-size: clamp(12px, 2svh, 13px);
  font-weight: 500;
  letter-spacing: 0.03em;
  white-space: nowrap;
  cursor: pointer;
  transition: color var(--fade), border-color var(--fade), filter 90ms ease, transform 90ms ease;
}

.btn-close-x {
  font-size: clamp(16px, 2.7svh, 20px);
  line-height: 1;
  transform: translateY(-1px);
}

.btn-close:active:not(:disabled) {
  color: var(--shu);
  border-color: var(--shu);
}

@media (hover: hover) and (pointer: fine) {
  .btn-close:hover { color: var(--shu); border-color: var(--shu); }
}

/* ── Tabs: one connected track, one highlight that slides along it ── */

.tabs {
  position: relative;
  flex-shrink: 0;
  display: flex;
  padding: 3px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.02);
}

.tab {
  position: relative;
  z-index: 1;                /* above the highlight, so the label is never tinted */
  flex: 1 1 0;
  min-width: 0;
  min-height: clamp(28px, 4.9svh, 34px);
  padding: 5px 10px;
  border: 0;
  border-radius: 999px;
  background: transparent;
  color: var(--muted);
  font-family: var(--sans);
  font-size: clamp(12px, 2svh, 13px);
  font-weight: 500;
  letter-spacing: 0.04em;
  cursor: pointer;
  transition: color var(--fade);
}

.tab[aria-selected="true"] {
  color: var(--accent);
  font-weight: 700;
}

/* The highlight: one pill that slides from tab to tab (and follows a swipe, via --p).
   It is half the track's inner width, so it moves by exactly its own width. */
.tab-pill {
  position: absolute;
  z-index: 0;
  top: 3px;
  bottom: 3px;
  left: 3px;
  width: calc((100% - 6px) / 2);
  border: 1px solid var(--accent);
  border-radius: 999px;
  background: var(--accent-bg);
  pointer-events: none;
  transform: translateX(calc(var(--p) * 100%));
  transition: transform var(--slide-dur) var(--slide-ease);
}

@media (hover: hover) and (pointer: fine) {
  .tab:hover:not([aria-selected="true"]) { color: var(--text); }
}

/* ── Pager: the one flexible child of .app ──
   Both pages sit side by side on a track twice the width of the pager; --p slides it.
   Each page scrolls on its own (overflow-y), so a tall Settings page never stretches the
   practice screen, and the controls stay at the bottom of the viewport. The 4px negative
   margin / padding pair keeps focus rings from being clipped at the pager's edges. */
.pager {
  position: relative;
  flex: 1 1 0;
  min-height: 0;
  margin-inline: -4px;
  overflow: hidden;
  touch-action: pan-y;   /* vertical scroll stays native; app.js owns horizontal swipes */
}

.track {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: 200%;
  display: flex;
  transform: translateX(calc(var(--p) * -50%));
  transition: transform var(--slide-dur) var(--slide-ease);
  will-change: transform;
}

/* While a finger is dragging, follow it 1:1 — no easing. */
.app.dragging .track,
.app.dragging .tab-pill { transition: none; }

/* ── Panel: one page. Its own gap carries the spacing that used to sit directly on
   .app, so the hero-to-controls rhythm is unchanged. ── */
.panel {
  flex: 0 0 50%;
  min-width: 0;
  min-height: 0;
  display: flex;
  flex-direction: column;
  gap: clamp(7px, 1.3svh, 16px);
  padding-inline: 4px;
  overflow-x: hidden;
  overflow-y: auto;      /* fallback only: the fluid sizing below keeps both pages fitting */
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  touch-action: pan-y;
  scrollbar-width: none;
}

.panel::-webkit-scrollbar { display: none; }

/* The page that is not showing: out of the tab order and the accessibility tree (inert),
   and not painted once its slide has finished. Mid-drag both are visible. */
.panel[inert] {
  visibility: hidden;
  transition: visibility 0s linear var(--slide-dur);
}

.app.dragging .panel[inert] {
  visibility: visible;
  transition: none;
}

/* Install advice, shared look with the note in Settings below. A quiet, paper-coloured
   strip rather than a loud warning — it is guidance, not an error. */
.install-banner,
.install-note {
  margin: 0;
  padding: 5px 9px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--surface);
  color: var(--text);
  font-size: clamp(11px, 1.8svh, 12.5px);
  line-height: 1.4;
  text-align: center;
}

.install-banner { flex-shrink: 0; }

/* ── Hero: ensō + countdown (the flexible part of the column) ── */

/* The hero takes whatever height the controls leave, and the ring is sized from THAT box
   (container units) — so it fills the room instead of floating in a gap, and shrinks on a
   short phone instead of pushing the controls off the screen. */
.countdown-section {
  flex: 1 1 0;
  min-height: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  container-type: size;
}

/* Spare height (tall phones, where the ring is width-limited) goes ABOVE the ring, so
   Begin always sits close under the timer. */
.countdown-section::before { content: ''; flex: 1 1 0; min-height: 0; }

.enso-wrap {
  position: relative;
  flex: none;
  width: min(74vw, 38svh, 300px);                 /* fallback without container units */
  /* The ring is the only flexible block: it takes whatever height the controls leave,
     capped so it never dominates the column. 24px = the status line's room under the ring. */
  width: min(100cqw, calc(100cqh - 24px), 40svh, 340px);
  aspect-ratio: 1;
  display: grid;
  place-items: center;
  container-type: size;
}

.enso {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
}

/* The whole circle, faint: what the session will draw. */
.enso-ghost {
  fill: var(--text);
  opacity: 0.07;
}

.enso-ink {
  transition: opacity 600ms ease;
}

/* The remaining portion. app.js sets stroke-dashoffset once a second; the transition
   turns those steps into one continuous fade along the stroke. */
#enso-arc {
  transition: stroke-dashoffset 1s linear;
}

/* Returning to the full circle (stop, end, sound/duration change) is a gesture, not a tick. */
body:not(.running):not(.paused) #enso-arc {
  transition: stroke-dashoffset 1.4s cubic-bezier(0.4, 0, 0.2, 1);
}

/* Soft lamp-light behind the stroke. Opacity + transform only, so the breathing animation
   runs on the compositor and costs nothing when the page is hidden. */
.enso-glow {
  position: absolute;
  inset: 12%;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(217, 174, 104, 0.20) 0%, rgba(217, 174, 104, 0.06) 45%, transparent 70%);
  opacity: 0.35;
  transform: scale(0.9);
  transition: opacity 1.2s ease;
  pointer-events: none;
}

.enso-center {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  /* Nudged a touch above centre: the brush's heavy start sits lower left, so the optical
     centre of the ring is slightly high. */
  transform: translateY(-2%);
}

.countdown {
  font-family: var(--serif);
  font-weight: 700;
  font-size: clamp(30px, min(13vw, 7svh), 64px);   /* fallback */
  font-size: clamp(20px, 17cqw, 68px);             /* scales with the ring */
  line-height: 1;
  color: var(--text);
  letter-spacing: 0.01em;
  white-space: nowrap;
}

/* Fixed cells so the proportional Mincho digits do not shuffle the clock every second. */
.countdown .d {
  display: inline-block;
  width: 0.6em;
  text-align: center;
}
.countdown .c {
  display: inline-block;
  width: 0.32em;
  text-align: center;
  transform: translateY(-0.06em);
  color: var(--accent);
}

.duration-label {
  font-size: clamp(11px, 1.6svh, 13px);
  font-weight: 500;
  color: var(--muted);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  min-height: 16px;
  text-align: center;
}

.status {
  flex: none;
  font-size: clamp(11px, 1.7svh, 13px);
  color: var(--accent);
  letter-spacing: 0.06em;
  min-height: 15px;
  line-height: 15px;
  text-align: center;
}

/* ── States driven by body classes (set in app.js setPlayBtn) ── */

/* Running: the ensō breathes. ~10 s per breath, about six breaths a minute, the pace
   most slow-breathing practice settles on. The motion is small on purpose. */
body.running .enso-glow {
  opacity: 1;
  animation: breathe-glow 10s ease-in-out infinite;
}
body.running .enso {
  animation: breathe 10s ease-in-out infinite;
}

/* Paused: the breath holds where it is; the ink dims. */
body.paused .enso-glow { opacity: 0.5; }
body.paused .enso-ink  { opacity: 0.55; }
body.paused .countdown { color: var(--muted); }

@keyframes breathe {
  0%, 100% { transform: scale(0.975); }
  50%      { transform: scale(1.015); }
}
@keyframes breathe-glow {
  0%, 100% { transform: scale(0.86); opacity: 0.55; }
  50%      { transform: scale(1.04); opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  body.running .enso,
  body.running .enso-glow { animation: none; }
  #enso-arc { transition: none; }
}

/* ── Play button ── */

.play-section {
  flex-shrink: 0;
}

.play-controls {
  display: flex;
  gap: var(--control-gap);
}

.btn-play {
  flex: 1;
  min-height: clamp(38px, 6.8svh, 52px);
  padding: 8px 16px;
  border-radius: 999px;
  font-family: var(--serif);
  font-size: clamp(15px, 2.5svh, 19px);
  font-weight: 700;
  letter-spacing: 0.12em;
  border: 1px solid var(--accent);
  color: var(--accent);
  background: rgba(217, 174, 104, 0.05);
  cursor: pointer;
  transition: background var(--fade), color var(--fade), border-color var(--fade), filter 90ms ease, transform 90ms ease;
}

@media (hover: hover) and (pointer: fine) {
  .btn-play:hover:not(:disabled) { background: var(--accent-bg); }
}

.btn-play:disabled {
  border-color: var(--border);
  color: var(--muted);
  background: transparent;
  cursor: default;
}

.btn-reset {
  min-height: clamp(38px, 6.8svh, 52px);
  padding: 8px 18px;
  border-radius: 999px;
  font-family: var(--sans);
  font-size: clamp(13px, 2svh, 15px);
  font-weight: 700;
  letter-spacing: 0.08em;
  border: 1px solid var(--border-hi);
  color: var(--text);
  background: transparent;
  cursor: pointer;
  transition: border-color var(--fade), color var(--fade), filter 90ms ease, transform 90ms ease;
}

@media (hover: hover) and (pointer: fine) {
  .btn-reset:hover { border-color: var(--accent-dim); }
}

/* ── Section ── */

.section {
  flex-shrink: 0;
}

.section-label {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: clamp(10px, 1.7svh, 12px);
  font-weight: 700;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: clamp(3px, 0.8svh, 7px);
}

/* A thin brush-rule after the label: structure without a box. */
.section-label::after {
  content: '';
  flex: 1;
  height: 1px;
  background: linear-gradient(to right, var(--border), transparent);
}

/* ── Button groups ── */

.btn-group {
  display: grid;
  gap: var(--control-gap);
  width: 100%;
}

/* Three across: 5 / 10 / 15, then 20 / 25 / Custom. Two rows instead of three. The bell
   frequency grid matches it: none / 1 / 2, then 3 / 4 / 5. */
#duration-group,
#level-group,
#end-bell-group { grid-template-columns: repeat(3, minmax(0, 1fr)); }

.btn {
  min-width: 0;
  min-height: clamp(32px, 6.2svh, 46px);
  padding: 6px 6px;
  border-radius: clamp(10px, 1.6svh, 14px);
  background: rgba(255, 255, 255, 0.015);
  border: 1px solid var(--border);
  color: var(--text);
  font-family: var(--sans);
  font-size: clamp(12.5px, 2svh, 15px);
  font-weight: 500;
  line-height: 1.2;
  cursor: pointer;
  text-align: center;
  white-space: normal;
  transition: border-color var(--fade), color var(--fade), background var(--fade), filter 90ms ease, transform 90ms ease;
}

.btn-detail {
  display: block;
  margin-top: 2px;
  font-size: clamp(10.5px, 1.7svh, 12px);
  font-weight: 500;
  color: var(--muted);
}

@media (hover: hover) and (pointer: fine) {
  .btn:hover:not(:disabled) { border-color: var(--accent-dim); }
}

.btn.selected {
  border-color: var(--accent);
  background: var(--accent-bg);
  color: var(--accent);
  font-weight: 700;
}

.btn.selected .btn-detail { color: rgba(217, 174, 104, 0.75); }

@media (hover: hover) and (pointer: fine) {
  .btn.selected:hover:not(:disabled) { background: rgba(217, 174, 104, 0.18); }
}

.btn:disabled {
  opacity: 0.35;
  cursor: default;
}

/* ── Settings tab ──
   Same vocabulary as the Meditate tab: a section label with its brush rule, then the
   controls. The sections flow top to bottom at their natural size — no space-between,
   which used to stretch the gaps into large empty bands on a tall phone. Any spare
   height simply stays below Delete app. */

.panel-settings {
  gap: clamp(5px, 1.1svh, 13px);
  padding-top: 2px;
}

.settings-status {
  color: var(--muted);
  font-size: clamp(11.5px, 1.8svh, 13px);
  line-height: 1.4;
  margin-bottom: clamp(3px, 0.8svh, 8px);
}

.panel-settings .install-note {
  text-align: left;
  margin-bottom: clamp(3px, 0.8svh, 8px);
}

.btn-action {
  width: 100%;
  min-height: clamp(32px, 6svh, 44px);
  font-size: clamp(12px, 1.8svh, 14px);
  letter-spacing: 0.04em;
}

/* The one thing Settings wants you to do, while it is still undone. */
#install-btn:not(:disabled) {
  border-color: var(--accent-dim);
  color: var(--accent);
}

.btn-action:disabled {
  opacity: 1;
  color: var(--accent);
  border-color: var(--border);
  background: transparent;
}

/* Destructive: quiet outlined vermilion, the colour otherwise reserved for the close
   control. It sits under Updates and must never compete with the Install button. */
.btn-danger {
  border-color: rgba(208, 85, 58, 0.55);
  color: var(--shu);
}

@media (hover: hover) and (pointer: fine) {
  .btn-danger:hover:not(:disabled) { border-color: var(--shu); color: var(--shu); }
}

.btn-danger:active:not(:disabled) { background: rgba(208, 85, 58, 0.12); }

.settings-list {
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 1px;
  font-family: var(--serif);
  font-size: clamp(10.5px, 1.8svh, 12.5px);
  line-height: 1.4;
  color: var(--muted);
}

.settings-list li {
  position: relative;
  padding-left: 16px;
}

/* A small gold dot in place of a bullet. */
.settings-list li::before {
  content: '';
  position: absolute;
  left: 2px;
  top: 0.65em;
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--accent-dim);
}

/* Install, Check for updates and the version share one section: a gap between them. */
.section .btn-action + .btn-action { margin-top: clamp(4px, 0.9svh, 10px); }

/* The app card: icon, name, version, one status line. The version is a button (tap to
   check for updates), kept small and quiet beside the name. */
.app-card {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: clamp(3px, 0.8svh, 8px);
}

.app-icon {
  width: clamp(34px, 6svh, 48px);
  height: clamp(34px, 6svh, 48px);
  flex: none;
  border-radius: clamp(8px, 1.4svh, 12px);
  box-shadow: 0 0 0 1px var(--border);
}

.app-card-text { min-width: 0; }

.app-name {
  display: flex;
  align-items: baseline;
  gap: 7px;
  font-family: var(--serif);
  font-size: clamp(14px, 2.1svh, 15px);
  font-weight: 700;
  color: var(--text);
}

.app-card .settings-status { margin: 2px 0 0; }

#version {
  min-height: 22px;
  padding: 0 6px;
  font-family: var(--sans);
}

/* Bell frequency: a cross, or one to five bell glyphs per button. The count is the label
   (aria-label carries the words), so no text and no sub-line. Same height as the
   duration buttons. Glyphs shrink so five always fit across a third of a phone. */
.btn-amount {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 1px;
}

.btn-amount svg {
  width: min(clamp(14px, 2.5svh, 20px), 19%);
  aspect-ratio: 1;
  height: auto;
  flex: none;
  fill: currentColor;
}

/* Settings → Bell frequency: what each level means. The glyphs repeat the Meditate page's
   buttons, small and gold; the text is the measured silence between bells. */
.freq-table {
  width: 100%;
  border-collapse: collapse;
  font-size: clamp(11.5px, 1.8svh, 13px);
  line-height: 1.3;
  color: var(--text);
}

.freq-table th {
  text-align: left;
  font-weight: 500;
  color: var(--muted);
  padding: 0 0 clamp(2px, 0.5svh, 5px);
}

.freq-table td {
  padding: clamp(2px, 0.55svh, 5px) 0;
  border-top: 1px solid var(--border);
  vertical-align: middle;
}

.freq-table th:first-child,
.freq-table .freq-glyphs {
  width: 6.2em;
  padding-right: 10px;
  white-space: nowrap;
}

.freq-table .freq-glyphs { color: var(--accent); }

.freq-glyphs svg {
  width: clamp(11px, 1.7svh, 13px);
  height: clamp(11px, 1.7svh, 13px);
  margin-right: 1px;
  vertical-align: -2px;
  fill: currentColor;
}

#end-bell-group { margin-bottom: clamp(4px, 0.9svh, 10px); }

.btn-small {
  background: transparent;
  border: 1px solid var(--border);
  border-radius: 999px;
  color: var(--muted);
  font-size: 12px;
  font-weight: 500;
  font-family: var(--sans);
  /* WCAG 2.2 AA (2.5.8): at least 24x24 CSS px; these sit at 34. */
  padding: 7px 13px;
  min-height: 34px;
  cursor: pointer;
  letter-spacing: 0.04em;
  transition: border-color var(--fade), color var(--fade), filter 90ms ease, transform 90ms ease;
}

@media (hover: hover) and (pointer: fine) {
  .btn-small:hover:not(:disabled) { border-color: var(--muted); color: var(--text); }
}

.btn-small:disabled {
  opacity: 0.6;
  cursor: default;
}

.hidden {
  display: none !important;
}

/* Tappable version → checks for an update (see app.js). */
.version {
  min-height: 34px;
  appearance: none;
  background: transparent;
  border: 1px solid transparent;
  border-radius: 999px;
  font-family: var(--sans);
  font-size: 11px;
  color: var(--faint);
  letter-spacing: 0.04em;
  cursor: pointer;
  padding: 4px 8px;
  transition: color var(--fade), border-color var(--fade);
}
@media (hover: hover) and (pointer: fine) { .version:hover { color: var(--text); border-color: var(--border); } }

/* ── Closed screen (after the close button, when the window survives) ── */

.closed-layer {
  position: fixed;
  inset: 0;
  z-index: 30;
  display: grid;
  place-items: center;
  padding: 24px;
  background: radial-gradient(ellipse 120% 70% at 50% 35%, #211d17 0%, var(--bg-deep) 70%);
  animation: fade-in 500ms ease both;
}

.closed-panel {
  max-width: 320px;
  text-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
}

.closed-enso {
  width: 84px;
  height: 84px;
  fill: var(--accent-dim);
  margin-bottom: 6px;
}

.closed-panel h2 {
  font-family: var(--serif);
  font-size: 26px;
  font-weight: 700;
  letter-spacing: 0.08em;
}

.closed-copy { color: var(--text); font-size: 15px; }
.closed-sub  { color: var(--muted); font-size: 13px; line-height: 1.5; margin-bottom: 8px; }

@keyframes fade-in { from { opacity: 0; } to { opacity: 1; } }

/* ── Install instructions ── */
.install-steps {
  list-style: none;
  counter-reset: step;
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin: 6px 0 18px;
}
.install-steps li {
  position: relative;
  padding-left: 34px;
  color: var(--text);
  font-size: 14px;
  line-height: 1.5;
  display: flex;
  align-items: center;
  min-height: 24px;
}
.install-steps li::before {
  counter-increment: step;
  content: counter(step);
  position: absolute;
  left: 0;
  top: 0;
  width: 24px;
  height: 24px;
  display: grid;
  place-items: center;
  font-size: 12px;
  font-weight: 700;
  color: var(--bg);
  background: var(--accent);
  border-radius: 50%;
}

body.modal-open {
  overflow: hidden;
}

.modal-layer {
  position: fixed;
  inset: 0;
  z-index: 20;
  /* Flex + `margin: auto` on the panel, NOT `place-items: center`: a centred grid item
     taller than the container overflows off BOTH edges and neither is reachable. This
     scrolls instead, and still centres whenever it fits. */
  display: flex;
  justify-content: center;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 18px;
  padding-top: max(18px, env(safe-area-inset-top, 0px));
  padding-bottom: max(18px, env(safe-area-inset-bottom, 0px));
}

.modal-backdrop {
  position: fixed;
  inset: 0;
  border: 0;
  background: rgba(8, 7, 6, 0.78);
  backdrop-filter: blur(10px);
  cursor: pointer;
}

.custom-modal {
  position: relative;
  margin: auto;
  width: min(100%, 360px);
  border: 1px solid var(--border-hi);
  border-radius: 20px;
  background:
    radial-gradient(circle at top, rgba(217, 174, 104, 0.12), transparent 55%),
    linear-gradient(180deg, #1f1c17, #14120f);
  box-shadow: 0 28px 70px rgba(0, 0, 0, 0.5);
  padding: 22px;
}

.custom-modal-kicker {
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--accent);
  margin-bottom: 8px;
}

.custom-modal h2 {
  font-family: var(--serif);
  font-size: 24px;
  font-weight: 700;
  line-height: 1.15;
  margin-bottom: 10px;
}

.custom-modal-copy {
  color: var(--muted);
  font-size: 14px;
  margin-bottom: 16px;
}

.custom-modal-form {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.custom-modal-label {
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--muted);
}

.custom-input-wrap {
  display: flex;
  align-items: center;
  gap: 10px;
  border: 1px solid rgba(217, 174, 104, 0.3);
  border-radius: var(--radius);
  background: rgba(255, 255, 255, 0.03);
  padding: 12px 16px;
}

.custom-display {
  justify-content: space-between;
  min-height: 68px;
}

.custom-input {
  width: 100%;
  color: var(--text);
  font-family: var(--serif);
  font-weight: 700;
  font-size: 30px;
}

.custom-input-unit {
  color: var(--accent);
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}

.custom-keypad {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 8px;
}

.custom-keypad-btn {
  min-height: 54px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: rgba(255, 255, 255, 0.025);
  color: var(--text);
  font-family: var(--sans);
  font-weight: 700;
  font-size: 20px;
  cursor: pointer;
  transition: background var(--fade), border-color var(--fade), color var(--fade), filter 90ms ease, transform 90ms ease;
}

@media (hover: hover) and (pointer: fine) {
  .custom-keypad-btn:hover { border-color: var(--accent-dim); background: rgba(217, 174, 104, 0.08); }
}

.custom-keypad-btn-muted {
  color: var(--muted);
  font-size: 13px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.custom-modal-error {
  min-height: 18px;
  color: #e8a07e;
  font-size: 13px;
}

.custom-modal-actions {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 8px;
}

.custom-modal-btn {
  width: 100%;
}

/* ── Session complete: the lamp brightens over the page for a moment ── */

body::before {
  content: '';
  position: fixed;
  inset: 0;
  z-index: -1;
  background: radial-gradient(ellipse 90% 60% at 50% 32%, rgba(217, 174, 104, 0.28) 0%, rgba(217, 174, 104, 0.06) 45%, transparent 75%);
  opacity: 0;
  transition: opacity 1.4s ease;
  pointer-events: none;
}

body.session-complete::before {
  opacity: 1;
  animation: complete-glow 3.5s ease-in-out infinite;
}

body.session-complete .enso-glow { opacity: 1; transform: scale(1.05); }

@keyframes complete-glow {
  0%, 100% { opacity: 0.7; }
  50%      { opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  body.session-complete::before { animation: none; }
  .app { --slide-dur: 0.01s; }   /* switch pages without the slide */
}

/* ── Short screens ──
   No height breakpoints: every vertical size above is a clamp on svh, so a short phone
   scales the whole column down smoothly (ring, buttons, labels together) instead of
   jumping between fixed sizes. The panel scroll stays as a safety net only. */

@media (min-width: 700px) and (min-height: 820px) {
  .app { max-width: 460px; }
}
