/* Reset and base styles */
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

/* Design tokens live in tokens.css; the site footer in site-footer.css.
   Both are linked before this file. */

/* The app footer sits below the content column, unlike the standalone pages. */
:root { --footer-gap: 2rem; }

body {
  font-family: var(--font-family);
  background: var(--page-gradient);
  min-height: 100vh;
  color: var(--text);
  line-height: 1.6;
}

.container {
  max-width: 75rem;
  margin: 0 auto;
  padding: 0 1rem;
}

/* Header */
/* Main content */
.main {
  padding: 2rem 0;
}

/* Tabs */
.tabs {
  display: flex;
  background: var(--surface);
  border-radius: var(--radius);
  padding: 0.25rem;
  margin-bottom: 2rem;
  box-shadow: var(--shadow);
  /* Wrap rather than scroll. Seven tabs at their 7.5rem minimum need ~920px of
     viewport, but the vertical stack below only kicks in at 768px -- which left
     a ~150px dead zone where the strip scrolled horizontally and hid tabs
     entirely. Wrapping keeps every tab visible at every width. */
  flex-wrap: wrap;
  gap: 0.25rem;
}

.tab {
  flex: 1;
  min-width: 7.5rem;
  padding: 0.75rem 1rem;
  border: none;
  background: transparent;
  color: var(--text-secondary);
  font-weight: 500;
  border-radius: calc(var(--radius) - 2px);
  cursor: pointer;
  transition: all 0.2s ease;
  white-space: nowrap;
}

/* 7d: between the stacked-mobile layout and the ~920px seven tabs need at
   their comfortable minimum, shrink the tabs instead of wrapping "Numbers"
   onto a lonely second row. After the base rule, so it wins on source order. */
@media (min-width: 769px) and (max-width: 960px) {
  .tab {
    min-width: 0;
    padding: 0.75rem 0.5rem;
    font-size: 0.85rem;
  }
}

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

.tab.active {
  background: var(--primary);
  color: var(--on-primary);
  box-shadow: var(--shadow);
}

/* Tab content */
.tab-content {
  background: var(--surface);
  border-radius: var(--radius);
  box-shadow: var(--shadow-lg);
  /* clip, not hidden: both trim card corners to the container radius, but
     hidden makes this a scroll container, which silently disables the sticky
     Generate card. clip clips without capturing sticky descendants. */
  overflow: clip;
}

.tab-content > * > .card:last-child {
  margin-bottom: 0;
}

/* Form elements */
.form-group {
  margin-bottom: 1.5rem;
}

.form-label {
  display: block;
  font-weight: 600;
  margin-bottom: 0.5rem;
  color: var(--text);
}

.form-input {
  width: 100%;
  padding: 0.75rem;
  border: 2px solid var(--border-strong);
  border-radius: var(--radius);
  font-size: 1rem;
  transition: border-color 0.2s ease;
  background: var(--surface);
}

.form-input:focus-visible {
  /* Was `outline: none` with a 10%-alpha shadow as the only cue, and that
     shadow was hardcoded to the old primary so it no longer matched the brand
     or the theme. Keep the ring. */
  outline: 2px solid var(--border-focus);
  outline-offset: 2px;
  border-color: var(--border-focus);
  box-shadow: 0 0 0 3px var(--focus-tint);
}

.form-input:read-only {
  background: var(--background);
  cursor: default;
}

.symbol-chips-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 0.5rem;
  /* The label and its All/Common/None buttons do not fit side by side at
     narrow widths once the text size is raised. */
  flex-wrap: wrap;
  gap: 0.5rem;
}

.symbol-chips-header .form-label {
  margin-bottom: 0;
}

.symbol-chips-actions {
  display: flex;
  gap: 0.375rem;
  flex-wrap: wrap;
}

.chip-action {
  padding: 0.2rem 0.6rem;
  border: 1px solid var(--border);
  border-radius: 4px;
  background: var(--surface);
  color: var(--text-secondary);
  font-size: 0.75rem;
  cursor: pointer;
  transition: border-color 0.15s ease, color 0.15s ease;
}

.chip-action:hover {
  border-color: var(--border-focus);
  color: var(--text-primary);
}

.symbol-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 0.375rem;
}

.symbol-chip {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2rem;
  height: 2rem;
  border: 2px solid var(--border-strong);
  border-radius: 6px;
  background: var(--surface);
  color: var(--text-secondary);
  font-family: monospace;
  font-size: 0.9rem;
  cursor: pointer;
  transition: border-color 0.15s ease, background 0.15s ease, color 0.15s ease;
  user-select: none;
}

.symbol-chip:hover {
  border-color: var(--border-focus);
  color: var(--text-primary);
}

.symbol-chip.active {
  border-color: var(--primary);
  background: var(--focus-tint);
  color: var(--primary);
}

.leet-chip {
  width: auto;
  padding: 0 0.5rem;
  font-size: 0.8rem;
  letter-spacing: 0.01em;
}

.emoji-toggle-row {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  margin-top: 0.85rem;
  padding-top: 0.85rem;
  border-top: 1px solid var(--border);
}

.emoji-toggle-row .form-label {
  margin-bottom: 0;
  flex-shrink: 0;
}

.emoji-toggle-btn {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.3rem 0.75rem;
  border-radius: 6px;
  border: 1.5px solid var(--border);
  background: var(--background);
  color: var(--text-secondary);
  font-size: 0.82rem;
  font-weight: 600;
  cursor: pointer;
  transition: all 0.18s ease;
  line-height: 1.4;
}

.emoji-toggle-btn:hover {
  border-color: var(--primary);
  color: var(--text);
}

.emoji-toggle-btn.active {
  border-color: var(--primary);
  background: var(--focus-tint);
  color: var(--primary);
}

.emoji-toggle-icon {
  font-style: normal;
  line-height: 1;
}

.emoji-toggle-label {
  font-variant-numeric: tabular-nums;
  min-width: 1.8rem;
}

/* Buttons */
.btn {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.75rem 1.5rem;
  border: none;
  border-radius: var(--radius);
  font-weight: 600;
  font-size: 1rem;
  cursor: pointer;
  transition: all 0.2s ease;
  text-decoration: none;
}

.btn-primary {
  background: var(--primary);
  color: var(--on-primary);
}

.btn-primary:hover {
  background: var(--primary-dark);
  transform: translateY(-1px);
  box-shadow: var(--shadow-lg);
}

.btn-secondary {
  background: var(--secondary);
  color: var(--on-primary);
}

.btn-secondary:hover {
  background: var(--secondary);
}

/* Cards */
.card {
  background: var(--surface);
  border-radius: var(--radius);
  padding: 1.5rem;
  box-shadow: var(--shadow);
  margin-bottom: 1rem;
}

/* 7d: the Generate card sticks to the viewport bottom while the options above
   it scroll, so sixty controls never separate the button from your thumb. The
   password display lives in the same bar -- a floating button whose result
   appears off-screen would defeat the point. */
.card-generate {
  position: sticky;
  bottom: 0.75rem;
  z-index: 5;
  border: 1px solid var(--border);
  display: flex;
  align-items: center;
  gap: 0.75rem;
  flex-wrap: wrap;
}

.card-generate .btn-primary {
  flex-shrink: 0;
}

/* The display grows to fill the bar and drops below the button when narrow. */
.card-generate .password-display {
  flex: 1 1 16rem;
  margin: 0;
}

/* The strength readout takes the bar's second row: meter, bits, tier and the
   guess-time spread at a glance, with "how?" expanding in place. */
.card-generate .entropy-panel {
  flex-basis: 100%;
}

/* The pin puts the bar back into normal flow for anyone who finds the
   floating version in the way. relative, not static: it stays in flow (no
   more sticking) but keeps being the pin button's containing block. */
.card-generate.bar-unstuck {
  position: relative;
  bottom: auto;
}

.bar-pin {
  position: absolute;
  top: 0.3rem;
  right: 0.3rem;
  width: var(--control-min);
  height: var(--control-min);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: none;
  background: none;
  border-radius: 6px;
  color: var(--text-secondary);
  cursor: pointer;
}

.bar-pin:hover {
  background: var(--overlay-soft);
  color: var(--text);
}

/* 7c: rarely-changed groups collapse behind their header. */
details.card-collapse > summary {
  cursor: pointer;
  list-style: none;
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

details.card-collapse > summary::-webkit-details-marker {
  display: none;
}

details.card-collapse:not([open]) > summary {
  margin-bottom: 0;
}

.collapse-chevron {
  margin-left: auto;
  color: var(--text-secondary);
  transition: transform 0.2s ease;
}

details[open].card-collapse > summary .collapse-chevron {
  transform: rotate(180deg);
}

/* Collapsed is not hidden: the header says when the group is doing something. */
.collapse-inuse {
  font-size: 0.68rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  padding: 0.05rem 0.5rem;
  border: 1px solid var(--primary);
  border-radius: 999px;
  color: var(--primary);
}

.card-header {
  font-weight: 600;
  font-size: 1.1rem;
  margin-bottom: 1rem;
  color: var(--text);
}

.card-header-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
}

/* Sliders */
.slider-container {
  display: flex;
  align-items: center;
  gap: 1rem;
  margin-bottom: 1rem;
}

/* The input is the hit target, and it used to *be* the visible bar: 6px tall,
   which is what you had to hit before finding the 20px thumb. WCAG 2.2 SC 2.5.8
   asks for 24x24. The element is --control-min tall and transparent now, with
   the thin bar drawn by the track pseudo-element inside it, so the target grew
   four-fold without the design changing. */
.slider {
  flex: 1;
  height: var(--control-min);
  background: transparent;
  cursor: pointer;
  -webkit-appearance: none;
  appearance: none;
}

/* Once appearance is none the track has to be drawn explicitly, or the bar
   disappears along with the native rendering. */
.slider::-webkit-slider-runnable-track {
  height: 0.375rem;
  border-radius: 3px;
  background: var(--border);
}

.slider::-moz-range-track {
  height: 0.375rem;
  border-radius: 3px;
  background: var(--border);
}

.slider::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: var(--control-min);
  height: var(--control-min);
  border-radius: 50%;
  background: var(--primary);
  cursor: grab;
  box-shadow: var(--shadow);
  /* WebKit aligns the thumb to the top of the track rather than centring it. */
  margin-top: calc((0.375rem - var(--control-min)) / 2);
  transition: box-shadow 0.15s ease;
}

/* 7b: the handle answers the pointer -- a halo on hover says "grabbable"
   before you commit, and it widens while dragging. The halo is a shadow, so
   the thumb's hit target and layout size never change. */
.slider::-webkit-slider-thumb:hover {
  box-shadow: var(--shadow), 0 0 0 6px var(--focus-tint);
}

.slider:active::-webkit-slider-thumb {
  cursor: grabbing;
  box-shadow: var(--shadow), 0 0 0 9px var(--focus-tint);
}

.slider::-moz-range-thumb {
  width: var(--control-min);
  height: var(--control-min);
  border-radius: 50%;
  background: var(--primary);
  cursor: grab;
  border: none;
  box-shadow: var(--shadow);
  transition: box-shadow 0.15s ease;
}

.slider::-moz-range-thumb:hover {
  box-shadow: var(--shadow), 0 0 0 6px var(--focus-tint);
}

.slider:active::-moz-range-thumb {
  cursor: grabbing;
  box-shadow: var(--shadow), 0 0 0 9px var(--focus-tint);
}

/* `outline: none` used to sit in .slider above. That selector is (0,1,0) --
   the same specificity as the global :where(...):focus-visible ring in
   tokens.css -- and style.css loads second, so it won on source order and the
   sliders had no focus indicator at all. WCAG 2.4.7.
   Stated explicitly rather than deleted and left to the global rule, so the
   next person reaching for `outline: none` on a range input sees why. */
.slider:focus-visible {
  outline: 2px solid var(--border-focus);
  outline-offset: 2px;
  border-radius: 3px;
}

.slider-value {
  min-width: 3.75rem;
  text-align: center;
  font-weight: 600;
  color: var(--text);
}

.stepper-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.75rem;
  height: 1.75rem;
  border-radius: 6px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text-secondary);
  cursor: pointer;
  flex-shrink: 0;
  transition: background 0.15s, color 0.15s, border-color 0.15s;
  padding: 0;
  font-size: 1rem;
}

.stepper-btn:hover {
  background: var(--primary);
  color: var(--on-primary);
  border-color: var(--primary);
}

.stepper-btn:active {
  background: var(--primary-dark);
  border-color: var(--primary-dark);
}

.stepper-label {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  flex-shrink: 0;
}

.stepper-label-text {
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--text);
  min-width: 3.5rem;
  text-align: center;
}

/* Checkboxes and switches */
.checkbox-group {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

.checkbox-item {
  display: flex;
  align-items: center;
  gap: 0.75rem;
}

/* WCAG 2.2 SC 2.5.8: was 1.25rem (20px), under the 24px minimum. */
.checkbox {
  /* This is a real <input type="checkbox"> that draws its own tick via
     :checked::after below. Without this the browser keeps drawing the native
     tick as well, and the two sit on top of each other -- visible as a doubled
     checkmark. It was there all along; how obvious it looked depended on how
     the native control happened to render against the fill. */
  appearance: none;
  -webkit-appearance: none;
  width: var(--control-min);
  height: var(--control-min);
  border: 2px solid var(--border-strong);
  border-radius: 4px;
  cursor: pointer;
  position: relative;
  background: var(--surface);
  flex-shrink: 0;
  margin: 0;
}

.checkbox:checked {
  background: var(--primary);
  border-color: var(--primary);
}

.checkbox:checked::after {
  content: '✓';
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  color: var(--on-primary);
  font-size: 0.75rem;
  font-weight: bold;
}

/* Radio buttons */
.radio-group {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(12.5rem, 1fr));
  gap: 0.75rem;
  margin-bottom: 1rem;
}

.radio-item {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem;
  border-radius: var(--radius);
  cursor: pointer;
  transition: background-color 0.2s ease;
}

.radio-item:hover {
  background: var(--background);
}

/* Matches .checkbox. Invisible today -- the input is sr-only -- but wrong if it is ever shown. */
.radio {
  /* Same reason as .checkbox. This one has no visible symptom today -- the
     input carries .sr-only and nothing selects off its checked state, so these
     visual rules are currently unreachable -- but if it is ever unhidden it
     would arrive with the doubled indicator already in place. */
  appearance: none;
  -webkit-appearance: none;
  width: var(--control-min);
  height: var(--control-min);
  border: 2px solid var(--border-strong);
  border-radius: 50%;
  cursor: pointer;
  position: relative;
  background: var(--surface);
}

.radio:checked {
  border-color: var(--primary);
}

.radio:checked::after {
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 50%;
  background: var(--primary);
}

/* Password display */
.password-display {
  position: relative;
  margin-bottom: 0;
  /* Shared by the field and by the controls overlaid on it, so the two cannot
     drift apart -- see .copy-btn for why that matters once the field wraps. */
  --pw-pad: 0.75rem;
  --pw-line: 1.65rem;
}

/* This was an <input type="text">, which cannot wrap: a password longer than
   the box simply scrolled out of sight. It is a readonly div now, so a long
   result breaks onto as many lines as it needs.
   role="textbox" with aria-readonly keeps it announced as a read-only field
   rather than as loose text, and tabindex keeps it reachable and selectable
   from the keyboard. Copying never read the DOM -- it uses the value
   directly -- so the copy button is unaffected. */
.password-input {
  font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
  font-size: 1.1rem;
  padding: var(--pw-pad);
  padding-right: 3rem;
  background: var(--background);
  border: 2px solid var(--border-strong);
  line-height: var(--pw-line);
  /* break-all rather than break-word: a password has no word boundaries worth
     respecting, and any point in it is a legal place to wrap. */
  word-break: break-all;
  overflow-wrap: anywhere;
  white-space: pre-wrap;
  /* Keep the height the input had, so an empty field does not collapse. */
  min-height: calc(var(--pw-pad) * 2 + var(--pw-line) + 4px);
  cursor: text;
}

.password-placeholder {
  color: var(--text-secondary);
}

.password-input.has-length-pill {
  padding-right: 6rem;
}

.length-pill {
  position: absolute;
  right: 2.75rem;
  top: calc(var(--pw-pad) + var(--pw-line) / 2);
  transform: translateY(-50%);
  font-size: 0.72rem;
  font-weight: 700;
  line-height: 1;
  padding: 0.25rem 0.5rem;
  border-radius: 999px;
  background: color-mix(in srgb, var(--primary) 12%, transparent);
  color: var(--primary-dark);
  pointer-events: none;
  user-select: none;
  white-space: nowrap;
  letter-spacing: 0.02em;
}

/* Centered on the first line, not on the box. These used top: 50%, which was
   the same thing while the field was always one line tall; now that it wraps,
   a three-line password would have left the copy button floating in the
   middle of the block. */
.copy-btn {
  position: absolute;
  right: 0.5rem;
  top: calc(var(--pw-pad) + var(--pw-line) / 2);
  transform: translateY(-50%);
  background: none;
  border: none;
  color: var(--text-secondary);
  cursor: pointer;
  padding: 0.5rem;
  border-radius: 4px;
  transition: all 0.2s ease;
}

.copy-btn:hover {
  background: var(--border);
  color: var(--text);
}

.copy-btn.copied {
  color: var(--primary);
  background: color-mix(in srgb, var(--primary) 12%, transparent);
}

.copy-btn.copied:hover {
  background: color-mix(in srgb, var(--primary) 18%, transparent);
  color: var(--primary);
}

/* Notifications */
.notification {
  position: fixed;
  top: 1rem;
  right: 1rem;
  padding: 1rem 1.5rem;
  border-radius: var(--radius);
  color: var(--on-status);
  font-weight: 500;
  box-shadow: var(--shadow-lg);
  z-index: 1000;
  animation: slideIn 0.3s ease;
}

/* The toast appears and disappears on its own, so it must be announced.
   role="status" is polite: it will not interrupt what a screen reader is
   already saying. Tracked as ROADMAP Epic 3. */
@media (prefers-reduced-motion: reduce) {
  .notification { animation: none; }
}

/* The accent, not --success.
   Status colors deliberately ignore the palette so that a warning or an error
   stays recognisable, which left this toast cyan on a violet or rose theme.
   But the only thing that ever raises a "success" here is the copy
   confirmation -- every other notification is an error -- and confirming the
   action you just took is not a system status, it is an acknowledgement. So it
   wears the theme. Errors below keep --error, which is the case the fixed
   status colors exist for.
   --on-primary rather than the inherited --on-status: the accent is bright in
   dark mode, so white on it is unreadable. */
.notification.success {
  background: var(--primary);
  color: var(--on-primary);
}

.notification.error {
  background: var(--error);
}

@keyframes slideIn {
  from {
    transform: translateX(100%);
    opacity: 0;
  }
  to {
    transform: translateX(0);
    opacity: 1;
  }
}

/* Separator chip grid */
.separator-grid {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-bottom: 0.75rem;
}

.sep-option {
  display: inline-flex;
  align-items: center;
  padding: 0.4rem 0.85rem;
  border: 2px solid var(--border-strong);
  border-radius: 20px;
  font-size: 0.875rem;
  font-weight: 500;
  color: var(--text-secondary);
  cursor: pointer;
  transition: all 0.15s ease;
  user-select: none;
  white-space: nowrap;
}

.sep-option:hover {
  border-color: var(--primary);
  color: var(--primary);
  background: var(--focus-tint);
}

.sep-option.active {
  border-color: var(--primary);
  background: var(--primary);
  color: var(--on-primary);
}

/* 6d: pool size + bits shown inside a category pill; inherits the pill's
   color in every state so it never falls below the pill's own contrast. */
.cat-meta {
  font-size: 0.72rem;
  margin-left: 0.35rem;
}

/* With Bit hints off the meta functions return '', and an empty span must
   not leave its margin behind. */
.cat-meta:empty {
  display: none;
}

/* 6g: the look-alike toggle sits below a grid inside its card. */
.exclude-ambiguous {
  margin-top: 0.75rem;
}

/* 7b: the slider's range, visible at its ends instead of nowhere. */
.slider-end {
  font-size: 0.72rem;
  color: var(--text-secondary);
  font-variant-numeric: tabular-nums;
  flex-shrink: 0;
}

/* 7e: regeneration confirms itself with a brief tint on the fresh value. */
@keyframes password-flash {
  from { background-color: var(--focus-tint); }
}

.password-input {
  animation: password-flash 0.5s ease-out;
}

@media (prefers-reduced-motion: reduce) {
  .password-input {
    animation: none;
  }
}

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0,0,0,0);
  white-space: nowrap;
  border: 0;
}

.custom-sep-row {
  margin-top: 0.5rem;
}

.structure-chip span {
  font-family: 'Courier New', Courier, monospace;
  font-size: 0.82rem;
  letter-spacing: 0.02em;
}

/* Mad Lib */
.madlib-template-preview {
  margin-top: 0.75rem;
  padding: 0.6rem 0.9rem;
  background: var(--background);
  border: 1px solid var(--border);
  border-radius: 6px;
}

.madlib-template-text {
  font-family: 'Courier New', Courier, monospace;
  font-size: 0.88rem;
  color: var(--text-secondary);
  word-break: break-word;
}

.madlib-template-text .slot {
  color: var(--primary);
  font-weight: 600;
}

.madlib-preview-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-left: 3px solid var(--primary);
  border-radius: 8px;
  padding: 0.9rem 1rem;
  margin-bottom: 1rem;
}

.madlib-preview-label {
  font-size: 0.72rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--text-secondary);
  margin-bottom: 0.35rem;
}

.madlib-preview-phrase {
  font-size: 1rem;
  color: var(--text);
  font-style: italic;
  word-break: break-word;
}

/* Prefix / Suffix affix pickers */
.affix-pair {
  display: flex;
  gap: 0;
}

.affix-block {
  flex: 1;
  min-width: 0;
}

.affix-label {
  font-weight: 600;
  font-size: 0.85rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-secondary);
  margin-bottom: 0.6rem;
}

.affix-divider {
  width: 1px;
  background: var(--border);
  margin: 0 1.25rem;
  flex-shrink: 0;
}

/* Responsive design */
@media (max-width: 768px) {
  .container {
    padding: 0 0.5rem;
  }
  

  
  .tabs {
    flex-direction: column;
  }
  
  .tab {
    flex: none;
  }
  
  .radio-group {
    grid-template-columns: 1fr;
  }
  
  .slider-container {
    flex-direction: column;
    align-items: stretch;
  }

  .affix-pair {
    flex-direction: column;
  }

  .affix-divider {
    width: 100%;
    height: 1px;
    margin: 1rem 0;
  }
}

/* ── Slot builder ─────────────────────────────────────── */

.slot-add-row {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: wrap;
  margin-bottom: 1rem;
}

.slot-add-label {
  font-size: 0.8rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--text-secondary);
  white-space: nowrap;
}

.slot-add-btn {
  padding: 0.3rem 0.75rem;
  border-radius: 999px;
  border: 2px solid transparent;
  font-size: 0.8rem;
  font-weight: 700;
  cursor: pointer;
  transition: opacity 0.15s, transform 0.1s;
  letter-spacing: 0.03em;
}

.slot-add-btn:disabled {
  opacity: 0.35;
  cursor: not-allowed;
}

.slot-add-btn:not(:disabled):hover { opacity: 0.85; transform: scale(1.04); }

.slot-add-btn.slot-adj { background: var(--badge-blue-bg); color: var(--badge-blue-fg); border-color: var(--badge-blue-fg); }
.slot-add-btn.slot-adv { background: var(--badge-teal-bg); color: var(--badge-teal-fg); border-color: var(--badge-teal-fg); }
.slot-add-btn.slot-noun { background: var(--badge-slate-bg); color: var(--badge-slate-fg); border-color: var(--badge-slate-border); }
.slot-add-btn.slot-verb { background: var(--badge-amber-bg); color: var(--badge-amber-fg); border-color: var(--badge-amber-fg); }

.slot-tray {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem;
}

.slot-pill {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  padding: 0.55rem 0.7rem;
  border-radius: 10px;
  border: 2px solid transparent;
  min-width: 5.625rem;
}

/* Part-of-speech pills.
 *
 * These were hardcoded pale backgrounds with no text color, so in dark mode
 * the inherited light text landed on a light pill: 1.01:1, effectively
 * invisible, and the worst contrast anywhere in the app. Setting the color
 * explicitly is the actual fix -- a background is only half a pair.
 *
 * They reuse the docs badge tokens rather than defining a fifth set of
 * category colors: same four hues, already verified in both themes and in
 * both palettes. */
.slot-adj  { background: var(--badge-blue-bg);  color: var(--badge-blue-fg);  border-color: var(--badge-blue-fg); }
.slot-adv  { background: var(--badge-teal-bg);  color: var(--badge-teal-fg);  border-color: var(--badge-teal-fg); }
.slot-noun { background: var(--badge-slate-bg); color: var(--badge-slate-fg); border-color: var(--badge-slate-border); }
.slot-verb { background: var(--badge-amber-bg); color: var(--badge-amber-fg); border-color: var(--badge-amber-fg); }

.slot-pill-label {
  font-size: 0.7rem;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  opacity: 0.7;
}

.slot-pill-actions {
  display: flex;
  gap: 2px;
}

/* WCAG 2.2 SC 2.5.8: these were 1.375rem (22px). They are the most-used
   controls on Passphrase and Wireless and were the smallest on the page. */
.slot-arrow,
.slot-remove {
  background: var(--overlay-soft);
  /* Neither set a color, so both fell through to the UA's `buttontext`. That
     tracks color-scheme and so looked right in both themes by luck, but it is
     not the theme's color and would not follow a palette. */
  color: var(--text);
  border: none;
  border-radius: 4px;
  width: var(--control-min);
  height: var(--control-min);
  font-size: 0.8rem;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background 0.12s;
  padding: 0;
  line-height: 1;
}

/* 0.25 was tuned against the UA's disabled text, which is already translucent;
   stacking the two left the arrow at roughly 7% alpha. Now that the color is
   ours, the opacity is the only fade applied. */
.slot-arrow:disabled { opacity: 0.4; cursor: default; }
.slot-arrow:not(:disabled):hover,
.slot-remove:hover { background: var(--overlay-strong); }

.slot-remove { margin-left: auto; color: var(--error); }

.slot-cat-select {
  font-size: 0.75rem;
  /* Was a translucent white fill with var(--text) on top. In dark mode that put
     light text on a near-white control, and the browser draws the native option
     list from these same colors -- so the whole dropdown was unreadable.
     A solid themed surface fixes the control and the popup together. */
  border: 1px solid var(--border-strong);
  border-radius: 5px;
  padding: 0.2rem 0.3rem;
  background: var(--surface);
  color: var(--text);
  cursor: pointer;
  width: 100%;
}

.slot-empty {
  font-size: 0.88rem;
  color: var(--text-secondary);
  text-align: center;
  padding: 1rem 0;
}

/* ── Mad Lib template token preview ──────────────────── */

.madlib-template-preview {
  margin-top: 0.85rem;
  padding: 0.6rem 0.75rem;
  background: var(--background);
  border-radius: 6px;
  border: 1px solid var(--border);
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.15rem;
  font-size: 0.95rem;
  line-height: 1.8;
}

.madlib-literal {
  color: var(--text-secondary);
  white-space: pre;
}

.madlib-token {
  display: inline-block;
  padding: 0.05rem 0.45rem;
  border-radius: 5px;
  font-weight: 700;
  font-size: 0.78rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  line-height: 1.6;
}

.madlib-token.slot-adj { background: var(--badge-blue-bg); color: var(--badge-blue-fg); border: 1px solid var(--badge-blue-fg); }
.madlib-token.slot-adv { background: var(--badge-teal-bg); color: var(--badge-teal-fg); border: 1px solid var(--badge-teal-fg); }
.madlib-token.slot-noun { background: var(--badge-slate-bg); color: var(--badge-slate-fg); border: 1px solid var(--badge-slate-border); }
.madlib-token.slot-verb { background: var(--badge-amber-bg); color: var(--badge-amber-fg); border: 1px solid var(--badge-amber-fg); }

/* ── Word cat label accent dots ──────────────────────── */

.word-cat-label { display: flex; align-items: center; gap: 0.4rem; }
.word-cat-label::before {
  content: '';
  display: inline-block;
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 50%;
  flex-shrink: 0;
}
.wc-label-adj::before { background: var(--badge-blue-fg); }
.wc-label-adv::before { background: var(--badge-teal-fg); }
.wc-label-noun::before { background: var(--badge-slate-fg); }
.wc-label-verb::before { background: var(--badge-amber-fg); }

.wc-ordinal {
  font-weight: 400;
  opacity: 0.6;
  font-size: 0.78rem;
}

.word-cat-row + .word-cat-row {
  margin-top: 0.85rem;
  padding-top: 0.85rem;
  border-top: 1px solid var(--border);
}

/* ── Header meta row (privacy notice + history control) ─── */

/* ── Word pills (per-word regeneration) ─────────────────── */

.word-pills-row {
  display: flex;
  align-items: flex-start;
  gap: 0.5rem;
  margin-bottom: 0.85rem;
}

.word-pills {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  flex: 1;
}

.lock-affixes-btn {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 2rem;
  height: 2rem;
  border-radius: 8px;
  border: 1.5px solid var(--border);
  background: var(--background);
  color: var(--text-secondary);
  cursor: pointer;
  transition: border-color 0.15s, background 0.15s, color 0.15s;
  font-size: 1rem;
  margin-top: 1px;
}

.lock-affixes-btn:hover {
  border-color: var(--primary);
  color: var(--primary);
  background: var(--focus-tint);
}

.lock-affixes-btn.active {
  border-color: var(--primary);
  background: var(--focus-tint);
  color: var(--primary);
}

.word-pill {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  padding: 0.35rem 0.6rem 0.35rem 0.75rem;
  border-radius: 999px;
  border: 2px solid var(--border-strong);
  background: var(--background);
  color: var(--text);
  font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
  font-size: 0.85rem;
  cursor: pointer;
  transition: border-color 0.15s, background 0.15s, color 0.15s;
  user-select: none;
}

.word-pill:hover {
  border-color: var(--primary);
  background: var(--focus-tint);
  color: var(--primary);
}

.word-pill:hover .word-pill-icon {
  opacity: 1;
  transform: rotate(30deg);
}

.word-pill-icon {
  font-size: 0.8rem;
  opacity: 0.35;
  transition: opacity 0.15s, transform 0.25s;
}

/* Slot-type color accents on passphrase word pills */
.word-pill-adj { border-color: var(--badge-blue-fg); background: var(--badge-blue-bg); color: var(--badge-blue-fg); }
.word-pill-adj:hover { border-color: var(--badge-blue-fg); background: var(--badge-blue-bg); }
.word-pill-adv { border-color: var(--badge-teal-fg); background: var(--badge-teal-bg); color: var(--badge-teal-fg); }
.word-pill-adv:hover { border-color: var(--badge-teal-fg); background: var(--badge-teal-bg); }
.word-pill-noun { border-color: var(--badge-slate-border); background: var(--badge-slate-bg); color: var(--badge-slate-fg); }
.word-pill-noun:hover { border-color: var(--badge-slate-fg); background: var(--badge-slate-bg); }
.word-pill-verb { border-color: var(--badge-amber-fg); background: var(--badge-amber-bg); color: var(--badge-amber-fg); }
.word-pill-verb:hover { border-color: var(--badge-amber-fg); background: var(--badge-amber-bg); }

/* ── History strip ──────────────────────────────────────── */

.history-strip {
  margin-top: 0.85rem;
  padding-top: 0.75rem;
  border-top: 1px solid var(--border);
}

.history-label {
  font-size: 0.7rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-secondary);
  margin-bottom: 0.5rem;
}

.history-list {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
}

.history-item {
  display: block;
  width: 100%;
  text-align: left;
  padding: 0.4rem 0.65rem;
  border-radius: 6px;
  /* 7e: rests looking like the button it is, not like inert text. */
  border: 1px solid var(--border);
  background: var(--background);
  font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
  font-size: 0.82rem;
  color: var(--text-secondary);
  cursor: pointer;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  transition: background 0.12s, border-color 0.12s, color 0.12s;
}

.history-item:hover {
  background: var(--background);
  border-color: var(--primary);
  color: var(--text);
}

.history-item-active {
  background: var(--focus-tint);
  border-color: var(--primary);
  color: var(--primary);
}

.history-item-warn {
  border-color: var(--warning);
  color: var(--warning);
}

.history-warn-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 0.875rem;
  height: 0.875rem;
  border-radius: 50%;
  background: var(--warning);
  color: var(--on-status);
  font-size: 0.5625rem;
  font-weight: 700;
  margin-left: 4px;
  line-height: 1;
  flex-shrink: 0;
}

/* ── Site footer ────────────────────────────────────────── */

/* Alliteration toggle */
.alliteration-toggle {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  font-size: 0.8rem;
  font-weight: 500;
  color: var(--text-muted);
  cursor: pointer;
  padding: 0.25rem 0.6rem;
  border-radius: 999px;
  border: 1.5px solid var(--border);
  background: transparent;
  transition: background 0.15s, color 0.15s, border-color 0.15s;
  user-select: none;
  white-space: nowrap;
}

.alliteration-toggle:hover {
  background: var(--surface-hover);
  color: var(--text);
}

.alliteration-toggle.active {
  background: var(--primary);
  color: var(--on-primary);
  border-color: var(--primary);
}

.alliteration-letter {
  font-weight: 700;
  font-size: 0.9rem;
  background: var(--overlay-strong);
  border-radius: 4px;
  padding: 0 0.3rem;
  min-width: 1.4em;
  text-align: center;
  letter-spacing: 0.05em;
}

/* The entropy readout under each password field (ROADMAP 6a/6b).
   A <details>: the closed summary is the one number that matters, the open
   state is the per-draw breakdown in which zero-bit options say so out loud. */
.entropy-panel {
  margin-top: 0.5rem;
  font-size: 0.8rem;
  color: var(--text-secondary);
}

.entropy-summary {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.5rem;
  cursor: pointer;
  padding: 0.2rem 0.3rem;
  border-radius: 4px;
}

/* The scenario spread, always visible: 480 guesses a day is a different world
   from 10^11 a second, and that contrast should not hide behind "how?". */
.entropy-range {
  flex-basis: 100%;
  font-size: 0.75rem;
  color: var(--text-secondary);
}

.entropy-range strong {
  color: var(--text);
  font-variant-numeric: tabular-nums;
}

.entropy-total {
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--text);
}

/* The delta and the warning are separate statements, so they are drawn as
   separate chips -- run together as bare text, "33.1 bits +10.3 low" read as
   one garbled number. */
.entropy-delta,
.entropy-warn {
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  padding: 0.05rem 0.45rem;
  border-radius: 999px;
  border: 1px solid currentColor;
  white-space: nowrap;
}
.entropy-delta.is-up { color: var(--accent-ok); }
.entropy-delta.is-down { color: var(--warning); }

.entropy-warn { color: var(--warning); }

.entropy-low .entropy-total { color: var(--warning); }

.entropy-how {
  text-decoration: underline;
  text-underline-offset: 2px;
}

.entropy-parts {
  list-style: none;
  margin: 0.4rem 0 0;
  padding: 0.5rem 0.75rem;
  background: var(--background);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
}

.entropy-parts li {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  flex-wrap: wrap;
}

.ep-bits {
  min-width: 3.2em;
  text-align: right;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  flex-shrink: 0;
}

/* Zero-bit rows recede but stay legible: they are the 6b message. */
.ep-zero .ep-bits,
.ep-zero .ep-label { opacity: 0.75; }

.ep-note {
  font-style: italic;
}

/* 6c/6e/6f: comparison anchors, bits-per-character and guess times, shown at
   the foot of the opened breakdown. */
.entropy-extras {
  margin-top: 0.4rem;
  padding: 0.5rem 0.75rem;
  background: var(--background);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
}

.entropy-extra-line {
  font-size: 0.78rem;
  color: var(--text-secondary);
}

.entropy-crack-title {
  font-size: 0.72rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-secondary);
  margin-top: 0.15rem;
}

.entropy-crack-row {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  font-size: 0.78rem;
  color: var(--text-secondary);
}

.crack-time {
  color: var(--text);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  text-align: right;
}

/* The guess rate, inline where the scenario is named -- 480/day vs 10^11/sec
   is the whole point, and a hover-only note undersold it. */
.crack-rate {
  margin-left: 0.4rem;
  padding: 0.05rem 0.45rem;
  border: 1px solid var(--border);
  border-radius: 999px;
  font-size: 0.68rem;
  white-space: nowrap;
}

/* Stored strength beside each history entry. Entries saved before v2.17.0
   have no figure and simply show none. */
.history-item {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
}

.history-pw {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.history-bits {
  margin-left: auto;
  flex-shrink: 0;
  font-size: 0.72rem;
  color: var(--text-secondary);
  font-variant-numeric: tabular-nums;
}

/* The strength meter. Color is never the only signal -- the tier word sits
   beside the bar, the bits are stated, and width alone separates good from
   strong. Every color is a status token, so the meter follows the palette and
   stays legible under Mono, where the tokens become distinct grays. */
.entropy-meter {
  width: 5rem;
  height: 0.4rem;
  border-radius: 999px;
  /* --background, not --border: the fill must clear 3:1 against the track
     (1.4.11), and --error on --border measured 2.74:1 in dark sky. A test in
     contrast.test.js pins this across every palette. */
  background: var(--background);
  border: 1px solid var(--border);
  overflow: hidden;
  flex-shrink: 0;
  align-self: center;
}

.entropy-meter-fill {
  display: block;
  height: 100%;
  border-radius: 999px;
  transition: width 0.25s ease;
}

.entropy-meter.meter-weak   .entropy-meter-fill { background: var(--error); }
.entropy-meter.meter-fair   .entropy-meter-fill { background: var(--warning); }
.entropy-meter.meter-good   .entropy-meter-fill { background: var(--success); }
.entropy-meter.meter-strong .entropy-meter-fill { background: var(--success); }

.entropy-tier {
  font-weight: 600;
  padding: 0.05rem 0.45rem;
  border-radius: 999px;
  border: 1px solid currentColor;
  white-space: nowrap;
}
.entropy-tier.meter-weak   { color: var(--error); }
.entropy-tier.meter-fair   { color: var(--warning); }
.entropy-tier.meter-good   { color: var(--success); }
.entropy-tier.meter-strong { color: var(--success); }
