/* KRONKER CHORES - Household Task Manager Styles */

:root {
  /* Colors */
  --primary-orange: #f97316;
  --secondary-orange: #ea580c;
  --success-green: #10b981;
  --warning-yellow: #f59e0b;
  --danger-orange: #f97316;
  --urgent-red: #ef4444;
  --gray-50: #f9fafb;
  --gray-100: #f3f4f6;
  --gray-200: #e5e7eb;
  --gray-300: #d1d5db;
  --gray-400: #9ca3af;
  --gray-500: #6b7280;
  --gray-600: #4b5563;
  --gray-700: #374151;
  --gray-800: #1f2937;
  --gray-900: #111827;
  --white: #ffffff;
  --black: #000000;

  /* Spacing */
  --space-1: 0.25rem;
  --space-2: 0.5rem;
  --space-3: 0.75rem;
  --space-4: 1rem;
  --space-5: 1.25rem;
  --space-6: 1.5rem;
  --space-8: 2rem;
  --space-12: 3rem;
  --space-16: 4rem;

  /* Typography */
  --font-sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  --font-mono: 'SF Mono', Monaco, 'Cascadia Code', monospace;

  /* Shadows */
  --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
  --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
  --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
  --shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1);

  /* Borders */
  --border-radius: 0.5rem;
  --border-radius-lg: 0.75rem;
  --border-radius-xl: 1rem;

  /* Animations */
  --transition-fast: 150ms ease-in-out;
  --transition-normal: 300ms ease-in-out;
  --transition-slow: 500ms ease-in-out;
}

/* Reset & Base */
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

html {
  /* THE GREEN BASE LAYER (the gospel's bottom layer, made real July 11, 2026 -
     intended as the standard on all screens). The canvas
     under everything is brand green; pull-to-refresh and rubber-band
     overscroll are where a person actually sees it. The gray they read on is
     the BODY, which now guarantees full viewport coverage (see the base-layer
     block at the end of this sheet), so inside the window nothing changes. */
  background: #26a11c;
  min-height: 100vh;
}

body {
  font-family: var(--font-sans);
  line-height: 1.6;
  color: #ffffff;
  background: #1a1a1a; /* neutral - matches BILLS/FEED */
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  min-height: 100vh;
}

/* App Container */
.app-container {
  min-height: 100vh;
  max-width: 1000px;
  margin: 0 auto;
  padding: 0;
}

/* Header */
.header {
  background: #1a1a1a; /* neutral - matches BILLS/FEED */
  padding: 40px 0 20px 0;
  margin-bottom: 0;
}

.header-content {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 0 42px;
}

.app-title {
  font-size: 1.75rem;
  font-weight: 800;
  color: #ffffff;
  letter-spacing: -0.025em;
}

/* The header sits just above the black rectangle, and the wordmark, the app icon
   and the three controls share ONE horizontal centerline.
   align-items:center already aligned their middles; both sides carry the same
   14px nudge so that stays true. Nudged visually, not by changing the header's
   height, so nothing reflows. Dropdowns anchor to their own containers and follow. */
.header-actions {
  display: flex;
  align-items: stretch; /* wrappers span the row so all three dropdowns share a top */
  gap: var(--space-4);
  position: relative;
  top: 14px;
}

.logo-section {
  position: relative;
  display: flex;
  align-items: center;
  top: 14px;
}

/* THE APP NAME SITS ABOVE THE KRONKER WORDMARK, right-aligned to its edge.
   RULED July 9, 2026: words above the wordmark - correct and final.
   `bottom: 100%` is what puts it there; `.logo-section { top: 14px }` is what makes
   the room for it. It was flipped from below earlier the same day - HOME's comment was
   updated at the flip, these six were not, and for a while the code and the comment
   disagreed. FEED's header was built to the CODE. Do not "fix" this back to top:100%. */
.brand-stack {
  position: relative;
  display: inline-flex;
  align-items: center;
}
.app-subhead {
  margin-left: 10px; /* inline to the RIGHT of the wordmark, same 11px */
  position: relative;
  top: 6px;   /* FOOT-ALIGN to the wordmark's text foot - eyeballed; the logo PNG carries ~10% blank below the letters, so centred sat too high. Tune this one number. */
  font-size: 11px;
  line-height: 1;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: #40EBF4;
  white-space: nowrap;
  pointer-events: none;
}

/* THE WORDMARK'S SIZE LIVES HERE (Aug 30, 2026). The markup carries an
   inline height: 32px as well, but the brand painter (kr-brand.js)
   clears that inline pin on its decouple road, and with no height in
   this stylesheet the 2506x576 PNG painted at natural size across the
   header. The stylesheet owns the size; the inline pin is a courtesy. */
.logo-section img[src="icons/kronker-logo.png"] {
  height: 32px;
  width: auto;
  filter: drop-shadow(0 0 10px rgba(200, 200, 200, 0.75));
}

.logo-section img[src="icons/CHORES.png"] {
  height: 32px;
  width: auto;
  margin-left: 10px;
  filter: drop-shadow(0 0 10px rgba(64, 235, 244, 0.8));
}

.sync-btn {
  background: none;
  border: none;
  padding: var(--space-2);
  margin-left: 10px;
  cursor: pointer;
  color: rgba(255, 255, 255, 0.6);
  transition: all var(--transition-fast);
  display: flex;
  align-items: center;
  justify-content: center;
}

.sync-btn:hover {
  color: rgba(255, 255, 255, 0.9);
}

.sync-btn.syncing .gear-icon {
  animation: spin 1s linear infinite;
}

.sync-btn.success {
  color: var(--success-green);
  border-color: var(--success-green);
}

/* On success the gray gear is swapped for the green art
   gear image (icons/gear-green.png) and keeps spinning; errors tint red. */
.sync-btn.success .gear-icon {
  animation: spin 1s linear infinite;
  content: url('icons/gear-green.png');
}

.sync-btn.error .gear-icon {
  filter: brightness(0) invert(50%) sepia(100%) saturate(600%) hue-rotate(320deg) brightness(1.1);
}

.sync-container {
  position: relative;
  display: flex;        /* spans the header row so its dropdown shares the common top */
  align-items: center;
}

/* Every dropdown panel is drawn the same. The gear was
   the odd one out: a 20% border where the picker and avatar use 10%, a
   hard-coded 8px radius where they use the token (same value, different words),
   and a heavier shadow. All three now match. */
.sync-dropdown {
  position: absolute;
  top: 100%;
  margin-top: 8px; /* the 8px drop, matching .picker-dropdown */
  right: 0;
  background: rgba(26, 26, 26, 0.95); /* neutral panel - matches BILLS/FEED */
  backdrop-filter: blur(10px);
  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: var(--border-radius);
  box-shadow: 0 10px 25px rgba(0, 0, 0, 0.3);
  width: 238px; /* one width shared with the avatar dropdown (280 -15% by the word) */
  z-index: 1000;
  opacity: 0;
  visibility: hidden;
  transform: translateY(-10px);
  transition: all var(--transition-fast);
}

.sync-dropdown.show {
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
}

.sync-item {
  display: flex;
  align-items: center;
  padding: 12px 16px;
  color: rgba(255, 255, 255, 0.8);
  cursor: pointer;
  transition: all 0.2s ease;
}

.sync-item:last-child {
  border-bottom: none;
}

.sync-item:hover {
  background: rgba(255, 255, 255, 0.1);
}

.sync-item span {
  font-size: 0.9rem;
  font-weight: 500;
}

/* Divider lines between EXPORT, IMPORT and USE YOUR AI,
   drawn exactly as the picker dropdown draws its own: a 5%-white hairline,
   dropped on the last section so the panel does not end on a rule. */
.sync-section {
  padding: 8px 0;
  border-bottom: 1px solid rgba(255, 255, 255, 0.05);
}

.sync-section:last-child {
  border-bottom: none;
}

/* A line of plain English under each section heading.
   Left edge lines up with the title and the buttons; sentence case, not caps,
   because it is a sentence. The title's bottom padding is dropped to 2px so the
   description reads as belonging to it rather than floating between sections. */
.sync-section-desc {
  font-size: 0.84rem; /* descriptor type, +20% by the word */
  line-height: 1.45;
  color: rgba(255, 255, 255, 0.45);
  padding: 0 16px 10px 16px;
}

.sync-section-title {
  font-size: 0.8rem;
  font-weight: 600;
  color: rgba(255, 255, 255, 0.6);
  padding: 12px 16px 2px 16px;
  text-transform: uppercase;
  letter-spacing: 0.5px;
}

.sync-section:first-child { padding-top: 0; }
.sync-section:first-child .sync-section-title {
  /* the panel's first line starts exactly where the avatar's does:
     12px down, 16px in */
  padding-top: 12px;
}

.sync-checkbox-group {
  padding: 0 16px 8px 16px;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.sync-checkbox-label {
  display: flex;
  align-items: center;
  cursor: pointer;
  margin: 0;
}

.sync-checkbox-label input[type="checkbox"],
.sync-checkbox-label input[type="radio"] {
  width: auto;
  margin-right: 8px;
  transform: scale(1.1);
  cursor: pointer;
  background: #000000 !important;
  border: 2px solid rgba(255, 255, 255, 0.2) !important;
  -webkit-appearance: none;
  -moz-appearance: none;
  appearance: none;
  border-radius: 3px;
  width: 16px;
  height: 16px;
  position: relative;
}

.sync-checkbox-label input[type="radio"] {
  border-radius: 50%;
}

.sync-checkbox-label input[type="checkbox"]:checked {
  background: #26a11c !important;
  border-color: #26a11c !important;
}

/* A ticked RADIO keeps the unticked button exactly - black fill, faint gray
   ring - and only its centre goes brand green.
   Checkboxes above are unchanged: they still fill green and show a white tick. */
.sync-checkbox-label input[type="radio"]:checked {
  background: #000000 !important;
  border-color: rgba(255, 255, 255, 0.2) !important;
}

.sync-checkbox-label input[type="checkbox"]:checked::after {
  content: '✓';
  position: absolute;
  color: white;
  font-size: 12px;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}

.sync-checkbox-label input[type="radio"]:checked::after {
  content: '';
  position: absolute;
  width: 6px;
  height: 6px;
  background: #26a11c; /* brand green - the only thing that changes when ticked */
  border-radius: 50%;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}

.sync-checkbox-label span {
  font-size: 0.85rem;
  color: rgba(255, 255, 255, 0.8);
  user-select: none;
}

.export-password-field input {
  width: calc(100% - 16px) !important;
  margin: 8px 8px !important;
}

.sync-divider {
  height: 1px;
  background: rgba(255, 255, 255, 0.15);
  margin: 8px 0;
}

/* The gear dropdown's buttons wear the site's gray, not a color.
   Solid #1a1a1a - the shared background of every site - which
   sits a shade lighter than the panel behind it, so the button reads as a
   button. Light gray border, caps. The text was green too; it goes gray. */
.sync-action-btn {
  /* Dressed as the avatar dropdown's buttons (SIGN OUT / CANCEL):
     solid gray, no border, rounded, centered caps - one button
     family across both header dropdowns. */
  width: calc(100% - 32px);
  background: #333333;
  border: none;
  color: #ffffff;
  padding: var(--space-3) var(--space-4);
  border-radius: var(--border-radius);
  cursor: pointer;
  font-size: 0.825rem; /* the standard button type, +10% by the word */
  font-weight: 600;
  text-align: center;
  text-transform: uppercase;
  transition: background var(--transition-fast);
  margin: 8px 16px 12px 16px;
}

.sync-action-btn:hover {
  background: #555555;
}

/* User Avatar */
.user-avatar {
  position: relative;
  display: flex;        /* spans the header row so its dropdown shares the common top */
  align-items: center;
}

.avatar-btn {
  background: rgba(255, 255, 255, 0.1);
  border: none;
  border-radius: 50%;
  width: 32px;
  height: 32px;
  cursor: pointer;
  color: rgba(255, 255, 255, 0.4);
  transition: all var(--transition-fast);
  display: flex;
  align-items: center;
  justify-content: center;
  position: relative;
}

.avatar-btn:hover {
  color: rgba(255, 255, 255, 0.7);
  background: rgba(255, 255, 255, 0.15);
}

.avatar-btn.authenticated {
  color: #10b981;
  background: rgba(255, 255, 255, 0.1);
}

.avatar-dot {
  display: none;
}

.avatar-dropdown {
  position: absolute;
  top: 100%;
  margin-top: 8px; /* the 8px drop, matching .picker-dropdown */
  right: 0;
  background: rgba(26, 26, 26, 0.95); /* neutral panel - matches BILLS/FEED */
  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: var(--border-radius);
  backdrop-filter: blur(10px);
  box-shadow: 0 10px 25px rgba(0, 0, 0, 0.3);
  width: 238px; /* one width shared with the gear dropdown (280 -15% by the word) */
  z-index: 1000;
  opacity: 0;
  visibility: hidden;
  transform: translateY(-10px);
  transition: all var(--transition-fast);
}

.avatar-dropdown.show {
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
}

.avatar-item {
  padding: var(--space-3) var(--space-4);
  color: rgba(255, 255, 255, 0.8);
  cursor: pointer;
  transition: background var(--transition-fast);
  border-bottom: 1px solid rgba(255, 255, 255, 0.05);
}

.avatar-item:last-child {
  border-bottom: none;
}

.avatar-item:hover {
  background: rgba(255, 255, 255, 0.1);
  color: rgba(255, 255, 255, 1);
}

/* SIGN OUT reads as a button like every other: the
   site's gray, a light gray border, centered caps. It stays a <div> because
   app.js toggles it with style.display = 'block'. The separator line above it
   is .avatar-account-info's own bottom border. Targeted by id so it outranks
   .avatar-item:hover. */
#logoutItem,
#loginItem {
  margin: 12px 16px;
  padding: var(--space-3) var(--space-4);
  background: #333333;
  border: none;
  border-radius: var(--border-radius);
  color: #ffffff;
  font-size: 0.85rem;
  font-weight: 600;
  text-align: center;
  text-transform: uppercase;
  cursor: pointer;
  transition: background var(--transition-fast);
}

#logoutItem:hover,
#loginItem:hover {
  background: #555555;
}

/* .avatar-item span forces 0.875rem, which was overriding the 0.75rem set on
   the container - that is why SIGN OUT read larger than CANCEL SUBSCRIPTION,
   a plain <button> with no span. Sign In keeps the original size. */
#logoutItem span,
#loginItem span {
  font-size: 0.825rem; /* the standard button type */
}

.avatar-item span {
  font-size: 0.875rem;
}

.avatar-account-info {
  padding: var(--space-3) var(--space-4);
  border-bottom: 1px solid rgba(255, 255, 255, 0.05);
}

.account-email {
  color: rgba(255, 255, 255, 0.9);
  font-size: 0.875rem;
  margin-bottom: var(--space-2);
  font-weight: 500;
}

.account-plan {
  color: rgba(255, 255, 255, 0.6);
  font-size: 0.75rem;
  margin-bottom: var(--space-3);
}

/* The small green dot on the plan's left: the mark that this plan is
   the CURRENT one. */
.account-plan::before {
  content: '';
  display: inline-block;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: #26a11c;
  margin-right: 6px;
}

/* Wears the same face as the gear dropdown's buttons - no red.
   The site's gray, a light gray border, caps. */
.cancel-subscription-btn {
  display: block;
  width: 100%;
  background: #333333;
  color: #ffffff;
  border: none;
  border-radius: var(--border-radius);
  padding: var(--space-3) var(--space-4);
  /* THE STANDARD HEIGHT: every dropdown button wears 0.75rem type in
     this padding - CANCEL and SIGN OUT identical, the gear's buttons
     and the hidden SIGN IN in the same family. */
  font-size: 0.825rem;
  font-weight: 600;
  cursor: pointer;
  text-transform: uppercase;
  transition: background var(--transition-fast);
}

.cancel-subscription-btn:hover {
  background: #555555;
}

/* Main Content */
.main-content {
  background: #000000; /* unified solid black — matches BILLS/FEED */
  margin: 0 40px 40px 40px;
  padding: 0 20px 40px 20px;
  border-radius: 0 0 12px 12px;
  box-shadow: inset 0 0 20px rgba(0, 0, 0, 0.8);
  min-height: calc(100vh - 200px);
  overflow: hidden;
  position: relative;
}

/* Date Header Row */
/* THE TICKETING | HISTORY RULE: the same fading hairline the
   FEED draws between its date and the collapse toggle. flex:1 auto eats the gap so
   HISTORY stays right; the gradient holds 0.3 white then fades to 0 alpha (never to
   black - the R2.33 lesson) just before HISTORY. Copied byte-for-byte from FEED .head-rule. */
.head-rule { flex: 1 1 auto; height: 1px; background: linear-gradient(to right, rgba(255, 255, 255, 0.3) 80%, rgba(255, 255, 255, 0) 100%); margin-left: 14px; border-radius: 1px; }   /* line kept put; the row moved up 2px instead so word+line+button share one centre */
.head-rule + .history-btn { margin-left: 16px; }

.date-header-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin: 24px 0 10px 4px; /* -2px top lifts TICKETING+HISTORY onto the line; right 0 = HISTORY flush to the card edge */
}

.archive-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-top: 16px;
  margin-bottom: var(--space-4);
  padding: 0 2px;
}

.archive-title {
  color: rgba(255, 255, 255, 0.7);
  font-size: 0.9rem;
  font-weight: 600;
}

.back-btn {
  background: none;
  border: none;
  color: rgba(255, 255, 255, 0.8);
  padding: 8px 6px;
  cursor: pointer;
  font-size: 0.8rem;
  transition: all 0.2s;
}

.back-btn:hover {
  color: white;
}

.date-header {
  color: rgba(255, 255, 255, 0.7);
  font-size: 0.9rem;
  font-weight: 600;
}

/* Bills Grid */
.bills-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--space-4);
  margin-bottom: var(--space-8);
}

/* Bill Card */
/* ONE COLOUR ON EVERY CARD, AND IT IS BILLS'
.
   #1a1a1a is what a default BILLS ticket actually renders - .bill-card.future and .soon
   both paint it. BILLS' own base rule carries rgba(26,26,26,0.9), which composites to
   rgb(23,23,23) over the black backer, and NOBODY EVER SEES IT: every bills card wears an
   urgency class that repaints the colour opaque. The colour a person sees is this one.

   IT REPLACED #4D788459, this app's translucent wash. Exactly four rules carried it -
   .bill-card and its .future / .week / .soon twins - and all four read #1a1a1a now, so
   every ticket in this app is one colour in every state. Nothing else in this sheet ever
   used that hex; that was checked before the swap, not after.

   BILLS, FEED, SOCIAL AND BIG DAYS KEEP THEIR OWN COLOURS. The ruling names the four
   apps that change and the four that do not. Do not "finish the job".

   THE CAP IS NOT THE COLOUR. Colour and image stay separate declarations here, exactly as BILLS warns:
   a `background:` shorthand on an urgency rule would silently wipe the cap out.
   Never collapse them. */
.bill-card {
  /* ONE CAP PER APP: "reduce each to 1... the same one that
     they see in feed for that same color". The cap used to be picked by the card's AREA
     or CATEGORY - eight PNGs, eight colours, a different endcap for KITCHEN than for
     INTERIOR. It told a person nothing they needed. Every ticket in this app now wears
     the app's own cap: the very one the FEED has always painted it with (BLUE.png).
     SCHOOL alone keeps its many colours, because there the colour is the SUBJECT.

     app.js still stamps color-<x> on each card from getColorForArea()/Category(). Those
     classes are now deliberately UNSTYLED, and the colour is still stored and synced -
     so the colours can come back one day without a data migration.

     background-COLOR, never the `background` shorthand. The shorthand resets
     background-image and would take this cap straight back out. Same law as FEED
     (R2.18). Every rule below that repaints a card had to learn it too. */
  background-color: #1a1a1a; /* BILLS' default - ALL cards, every state. See above. */
  background-image: url('images/BLUE.png');
  background-repeat: no-repeat;
  background-position: right center;
  background-size: auto 100%;   /* natural 32x180 - NEVER scaled */
  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: 12px;
  padding: 12px 16px 16px 16px;
  cursor: pointer;
  transition: all 0.3s ease;
  position: relative;
  height: 180px;
  display: flex;
  flex-direction: column;
  justify-content: flex-start;
  box-sizing: border-box;
  overflow: hidden;
}

.bill-card:hover {
  transform: translateY(-2px);
  box-shadow: 0 10px 25px rgba(0, 0, 0, 0.3);
}

/* Urgency Colors - Full Card Backgrounds */
.bill-card.future { background-color: #1a1a1a; border: 2px solid #6b7280; }
.bill-card.week { background-color: #1a1a1a; border: 2px solid #6b7280; }
.bill-card.soon { background-color: #1a1a1a; border: 2px solid #6b7280; }
.bill-card.urgent { border: 2px solid #6b7280; }
.bill-card.urgent .bill-due,
.bill-card.urgent .bill-actual-date { color: #ffffff; }

.bill-card.overdue { border: 2px solid #6b7280; }
.bill-card.overdue .bill-due,
.bill-card.overdue .bill-actual-date { color: #ffffff; }

.bill-card.completed {
  border: 2px solid #6b7280;
  opacity: 0.6;
}

/* Color Classes: RETIRED July 10, 2026. TWO families lived here: the plain
   .bill-card.color-<x> (8) AND .bill-card.<tier>.color-<x> (24, one per urgency
   tier), which out-specified the plain ones and re-asserted the cap with
   !important. Removing only the first family changes NOTHING on screen - the
   second still paints. Both are gone. Eight caps became one (see .bill-card).
   The color-<x> class is still emitted by app.js and matches nothing here. */

.bill-header {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  margin-bottom: var(--space-4);
  margin-top: 0;
}

.bill-name {
  font-size: 1.8rem;
  font-weight: 700;
  color: #ffffff;
  margin-bottom: var(--space-1);
  display: flex;
  align-items: center;
  gap: 8px;
  position: relative;
  z-index: 2;
  top: 18px;
  line-height: 1.1;
}

.bill-description {
  font-size: 0.9rem;
  font-weight: 500;
  color: #ffffff;
  margin-top: 8px;
  position: relative;
  z-index: 2;
}

.payment-favicon {
  border-radius: 3px;
  opacity: 0.8;
}

.bill-name a {
  padding: 8px;
  margin: -8px;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  text-decoration: none;
}

.bill-amount {
  font-size: 1rem;
  font-weight: 600;
  color: rgba(255, 255, 255, 0.8);
  font-family: var(--font-sans);
  text-transform: uppercase;
  position: relative;
  z-index: 2;
}

.bill-icon {
  position: absolute;
  top: 50%;
  right: var(--space-6);
  transform: translateY(-50%);
  opacity: 0.85;
  z-index: 1;
}

.bill-date-section {
  position: absolute;
  bottom: var(--space-3);
  left: var(--space-4);
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
  z-index: 2;
}

.bill-due {
  font-size: 0.84rem;
  color: #ffffff;
  margin-bottom: 2px;
}

.bill-actual-date {
  font-size: 1.17rem;
  color: #ffffff;
  font-weight: 500;
  display: flex;
  align-items: center;
  gap: 8px;
}

.recurring-indicator {
  opacity: 0.7;
  display: flex;
  align-items: center;
  position: relative;
}

.recurring-indicator img {
  display: block;
}

.autopay-dot {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 6px;
  height: 6px;
  background: #10b981;
  border-radius: 50%;
  pointer-events: none;
}

.payment-link-indicator {
  opacity: 0.7;
  display: flex;
  align-items: center;
  margin-left: 4px;
}

.payment-link-indicator img {
  display: block;
}

.edit-indicator {
  position: absolute;
  top: 12px;
  right: 32px;
  opacity: 0.6;
  transition: opacity var(--transition-fast);
  color: #ffffff;
  display: flex;
  align-items: center;
  justify-content: center;
}

.bill-content:hover .edit-indicator {
  opacity: 1;
}

.bill-brandmark {
  position: absolute;
  bottom: var(--space-3);
  right: 32px;
  cursor: pointer;
  opacity: 0.7;
  transition: opacity var(--transition-fast);
}

.bill-brandmark:hover {
  opacity: 1;
}

.bill-brandmark img {
  display: block;
  width: 32px;
  height: 32px;
  object-fit: contain;
}

/* The TOOLTIP card's completion icon: 48px - half again
   the 32px a real ticket wears - sitting 12px off the bottom and HALF that off
   the right, so it tucks toward the corner. The right cushion is written as half
   the bottom so the relationship survives a change to the spacing scale.
   Scoped to .tooltip-card - the same class draws the completion icon on every
   real ticket, and those keep their 32px size and their 32px right inset,
   which is what clears the cap on BILLS and BIG DAYS. */
.tooltip-card .bill-brandmark {
  bottom: var(--space-3);
  right: calc(var(--space-3) / 2);
  opacity: 1;
}

.tooltip-card .bill-brandmark img {
  width: 48px;
  height: 48px;
  filter: brightness(0) invert(1);
  transition: filter var(--transition-fast);
}

.tooltip-card .bill-brandmark:hover img {
  filter: brightness(0) invert(1) drop-shadow(0 0 10px #FFFFFF);
}

.bill-content {
  cursor: pointer;
  flex: 1;
  display: flex;
  flex-direction: row;
  align-items: stretch;
  justify-content: space-between;
  gap: 12px;
  transition: all var(--transition-fast);
}

/* ── THE UNIFIED CARD: CONTENT LEFT, KRONKER RIGHT ──────
   Same template as BILLS. TITLE here is the task NAME at THIS app's own size (1.8rem,
   not bills' huge amount). SECTION-WORD is the area/type. No background watermark on
   this app. The when-block (relative-time over the date) floor-pins by margin-top:auto.
   .edit-indicator markup is gone from the card; a click enters it. */
.card-left {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-width: 0;
}
.card-title {
  font-size: 1.53rem;
  font-weight: 700;
  color: #ffffff;
  line-height: 1.1;
}
.card-section {
  margin-top: 2px;
  font-size: 0.9rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.85);
}
.card-when {
  margin-top: auto;
}
.card-relative {
  font-size: 0.8rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: #9ca3af;
  line-height: 1;
  margin-bottom: 3px;
}
.card-relative.is-today   { color: #26a11c; }
.card-relative.is-overdue { color: #ef4444; }
.card-time {
  font-size: 1.05rem;
  color: rgba(255, 255, 255, 0.6);
  font-weight: 500;
  display: flex;
  align-items: center;
  gap: 8px;
  line-height: 1;
}
.card-bucket {
  display: flex;
  flex-direction: column;
  align-items: center;
  flex-shrink: 0;
  margin-right: 16px;
}
.card-bucket-icon {
  width: 34px;
  height: 34px;
  display: block;
}

.bill-content:hover {
  opacity: 0.9;
}

/* Add Card */
.add-card {
  border: 2px dashed rgba(255, 255, 255, 0.2);
  background: rgba(26, 26, 26, 0.9);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  height: 180px;
  color: rgba(255, 255, 255, 0.6);
  transition: all 0.3s ease;
  text-align: center;
}

.add-card[style*="display: none"] {
  display: none !important;
}

.add-card:hover {
  border-color: rgba(255, 255, 255, 0.4);
  color: rgba(255, 255, 255, 0.9);
  background: rgba(26, 26, 26, 1);
}

.add-icon {
  margin: 0; opacity: 0.6; transition: opacity var(--transition-fast); display: flex; align-items: center; justify-content: center;
  position: relative; z-index: 2; /* above the band layer */
}

.add-icon img { display: block; width: 48px; height: 48px; object-fit: contain; }

.add-card:hover .add-icon {
  opacity: 1;
}

.add-text {
  font-weight: 600;
  font-size: 0.875rem;
}

/* Tooltip Card in Grid */
.tooltip-card { background: linear-gradient(to bottom right, #1A5E62, #40EBF4) !important; border: 2px solid rgba(255, 255, 255, 0.2) !important; box-shadow: none !important; }
.tooltip-close { position: absolute; top: 8px; right: 12px; background: none; border: none; color: #FFFFFF; cursor: pointer; font-size: 18px; }
.tooltip-title { font-size: 1.53rem; font-weight: 700; line-height: 1.1; color: #FFFFFF; text-transform: uppercase; }
.tooltip-text { font-size: 0.9rem; line-height: 1.4; color: #FFFFFF; margin-top: 2px; margin-bottom: 12px; }
.tooltip-action { background: none; border: none; color: #FFFFFF; padding: 0; cursor: pointer; font-size: 0.85rem; font-weight: 600; transition: all 0.2s; }
.tooltip-action:hover { background: rgba(255, 255, 255, 0.3); }

/* HISTORY and its caret both read at 40%. The caret is
   part of the button's own text, so one color governs the pair. It was 80%; 60%
   barely moved on a dark ground, so it went to 40%. Hover still hits full white. */
.history-btn { background: none; border: none; color: rgba(255,255,255,0.4); padding: 8px 0; margin: -8px 0; cursor: pointer; font-size: 0.8rem; transition: all 0.2s; }
.history-btn:hover { color: white; }

.pagination { display: flex; justify-content: center; gap: 12px; margin-top: 20px; }
.pagination button { background: rgba(255,255,255,0.1); border: 1px solid rgba(255,255,255,0.2); color: white; padding: 8px 16px; border-radius: 4px; cursor: pointer; }
.pagination button:disabled { opacity: 0.5; cursor: not-allowed; }

/* Modal */
.modal {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: rgba(0, 0, 0, 0.5);
  display: none;
  align-items: center;
  justify-content: center;
  z-index: 1000;
  backdrop-filter: blur(4px);
}

.modal.show {
  display: flex;
}

/* THE SECTION ICON above the add/edit popup: the app's own icon rides a
   shell in NORMAL FLOW, centered over the box - and the overlay
   scrolls, so on a short viewport nothing clips off the top: icon and
   popup stay reachable. Auto margins on the shell beat the overlay's
   centring exactly when space runs out (the scroll-safe pattern). */
.modal { overflow-y: auto; }
.modal-shell { display: flex; flex-direction: column; align-items: center;
               /* 300px at 320-and-up (300/320 = 93.75vw exactly), scaling
                  in proportion below the floor like the photograph rule. */
               width: min(300px, 93.75vw); max-width: min(300px, 93.75vw);
               margin: auto; padding: 16px 0; }
.modal-app-icon { height: 40px; width: auto; margin: 0 0 10px; }
.modal-shell .modal-content { width: 100%; }

.modal-content {
  background: rgba(26, 26, 26, 0.95); /* neutral panel - matches BILLS/FEED */
  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: var(--border-radius-xl);
  padding: 0.5rem var(--space-6) var(--space-6) var(--space-6);
  width: 90%;
  max-width: 480px;
  box-shadow: 0 20px 40px rgba(0, 0, 0, 0.8);
  transform: scale(0.9);
  transition: transform var(--transition-normal);
  backdrop-filter: blur(10px);
}

.modal.show .modal-content {
  transform: scale(1);
}

.modal-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 0.5rem;
  margin-top: 0;
}

.modal-header h2 {
  font-size: 1.05rem;
  font-weight: 700;
  color: #ffffff;
  margin: 0;
  line-height: 1;
}

.close-btn {
  background: none;
  border: none;
  font-size: 1.5rem;
  color: rgba(255, 255, 255, 0.6);
  cursor: pointer;
  padding: var(--space-1);
  transition: color var(--transition-fast);
}

.close-btn:hover {
  color: rgba(255, 255, 255, 0.9);
}

/* Form */
.form-group {
  margin-bottom: var(--space-4);
}

.form-group label {
  display: block;
  margin-bottom: var(--space-2);
  font-weight: 600;
  font-family: var(--font-sans);
  color: #888888;
  font-size: 0.8rem;
  text-transform: uppercase;
}

.form-group input,
.form-group select,
.form-group textarea {
  width: 100%;
  padding: var(--space-3);
  border: 2px solid rgba(255, 255, 255, 0.2);
  border-radius: var(--border-radius);
  font-size: 1rem;
  font-family: var(--font-sans);
  transition: border-color var(--transition-fast);
  background: rgba(0, 0, 0, 0.3);
  color: #ffffff;
}

.form-group textarea {
  resize: vertical;
  min-height: 80px;
}

.form-group input:focus,
.form-group select:focus,
.form-group textarea:focus {
  outline: none;
  border-color: #26a11c;
  box-shadow: 0 0 0 3px rgba(38, 161, 28, 0.1);
}

.checkbox-group .checkbox-label {
  display: flex;
  align-items: center;
  cursor: pointer;
  margin-bottom: 0;
}

.checkbox-group input[type="checkbox"] {
  width: auto;
  margin-right: var(--space-2);
  transform: scale(1.1);
  cursor: pointer;
  background: #000000 !important;
  border: 2px solid rgba(255, 255, 255, 0.2) !important;
}

.checkbox-text {
  font-weight: 600;
  color: #888888;
  font-size: 0.8rem;
  user-select: none;
}

.recurring-checkboxes {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  margin-top: var(--space-2);
}

.recurring-checkboxes .checkbox-label {
  display: flex;
  align-items: center;
  cursor: pointer;
  margin-bottom: 0;
}

.recurring-checkboxes input[type="checkbox"] {
  width: auto;
  margin-right: var(--space-2);
  transform: scale(1.1);
  cursor: pointer;
  background: #000000 !important;
  border: 2px solid rgba(255, 255, 255, 0.2) !important;
  -webkit-appearance: none;
  -moz-appearance: none;
  appearance: none;
  border-radius: 3px;
  width: 16px;
  height: 16px;
  position: relative;
}

.recurring-checkboxes input[type="checkbox"]:checked {
  background: #26a11c !important;
  border-color: #26a11c !important;
}

.recurring-checkboxes input[type="checkbox"]:checked::after {
  content: '✓';
  position: absolute;
  color: white;
  font-size: 12px;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}

.recurring-checkboxes .checkbox-text {
  font-size: 0.85rem;
  color: rgba(255, 255, 255, 0.8);
  user-select: none;
}

.form-actions {
  display: flex;
  /* STACKED, ALWAYS: every action button owns one full row, whatever
     the mix (SAVE alone; DELETE when the card exists; END RECUR when
     ticked) - so the buttons read the same on every card and screen. */
  flex-direction: column;
  gap: var(--space-3);
  margin-top: var(--space-6);
}

.submit-btn {
  flex: 1;
  background: #26a11c;
  color: var(--white);
  border: none;
  border-radius: var(--border-radius);
  padding: var(--space-3) var(--space-4);
  font-weight: 600;
  cursor: pointer;
  text-transform: uppercase;
  transition: background var(--transition-fast);
}

.submit-btn:hover {
  background: #1e8a15;
}

.delete-btn {
  flex: 1;
  background: #333333;
  color: var(--white);
  border: none;
  border-radius: var(--border-radius);
  padding: var(--space-3) var(--space-4);
  font-weight: 600;
  cursor: pointer;
  text-transform: uppercase;
  transition: background var(--transition-fast);
}

.color-picker {
  display: flex;
  gap: var(--space-3);
  margin-top: var(--space-2);
}

.color-picker input[type="radio"] {
  display: none;
}

.color-option {
  width: 40px;
  height: 40px;
  border-radius: 50%;
  cursor: pointer;
  border: 3px solid transparent;
  transition: all var(--transition-fast);
  position: relative;
}

.color-option:hover {
  transform: scale(1.1);
  border-color: rgba(255, 255, 255, 0.3);
}

.color-picker input[type="radio"]:checked + .color-option {
  border-color: #ffffff;
  box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.3), 0 0 20px rgba(255, 255, 255, 0.4);
  transform: scale(1.15);
}

.color-picker input[type="radio"]:checked + .color-option::after {
  content: '✓';
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  color: white;
  font-size: 20px;
  font-weight: bold;
  text-shadow: 0 0 3px rgba(0, 0, 0, 0.5);
}

.delete-btn:hover {
  background: #555555;
}

.end-recur-btn {
  flex: 1;
  background: #333333;
  color: var(--white);
  border: none;
  border-radius: var(--border-radius);
  padding: var(--space-3) var(--space-4);
  font-weight: 600;
  cursor: pointer;
  transition: background var(--transition-fast);
}

.end-recur-btn:hover {
  background: #555555;
}

/* Success Animation */
.success-animation {
  position: fixed;
  top: 0;
  left: 0;
  width: 100vw;
  height: 100vh;
  background: rgba(38, 161, 28, 0.9);
  display: none;
  align-items: center;
  justify-content: center;
  z-index: 9999;
  animation: successPulse 1s ease-out forwards;
}

.success-animation.show {
  display: flex;
}

.success-checkmark {
  animation: checkmarkPop 0.5s ease-out forwards;
}

/* Card completion animation */
.bill-card.completing {
  background: #26a11c !important;
  border: none !important;
  animation: greenFadeOut 1s ease-out forwards;
}

@keyframes greenFadeOut {
  0% {
    background: #26a11c;
    opacity: 1;
    transform: scale(1);
  }
  50% {
    background: #26a11c;
    opacity: 1;
    transform: scale(1.02);
  }
  100% {
    background: #26a11c;
    opacity: 0;
    transform: scale(0.98);
  }
}

/* Animations */
@keyframes spin {
  from { transform: rotate(0deg); }
  to { transform: rotate(360deg); }
}

@keyframes successPulse {
  0% { opacity: 0; }
  50% { opacity: 1; }
  100% { opacity: 0; }
}

@keyframes checkmarkPop {
  0% { transform: scale(0) rotate(45deg); }
  50% { transform: scale(1.2) rotate(45deg); }
  100% { transform: scale(1) rotate(45deg); }
}

@keyframes completeBounce {
  0%, 20%, 53%, 80%, 100% { transform: translate3d(0, 0, 0); }
  40%, 43% { transform: translate3d(0, -8px, 0); }
  70% { transform: translate3d(0, -4px, 0); }
  90% { transform: translate3d(0, -2px, 0); }
}

/* Responsive Design */
@media (max-width: 768px) {
  /* .app-container padding DELETED (July 9, 2026). var(--space-2) put 8px on every
     side below 768px: it pushed the whole page down 8px and pulled the black box in
     8px, and FEED - confirmed correct - has no such rule at any
     width. Do not reintroduce it without changing FEED in the same breath. */

  /* BOTH AXES MATTER, and they broke in opposite directions.
     HORIZONTAL: 42px, not var(--space-4)'s 16px. The black box keeps its 40px margin
     until 480px, so a 16px inset threw the wordmark, gear and avatar 24px OUTSIDE
     the box across the whole tablet range.
     VERTICAL: 0, not var(--space-3)'s 12px. 12px top and bottom made the header 24px
     taller and dropped the black box 32px down the screen. FEED's header-content is
     `0 42px` at every width; these apps must read the same. */
  .header-content {
    padding: 0 42px;
  }

  .app-title {
    font-size: 1.5rem;
  }

  /* columns now live in the standard block at the foot of this file */
  .bills-grid {
    gap: var(--space-3);
  }

  .modal-content {
    margin: var(--space-4);
    padding: var(--space-4);
  }

  .form-actions {
    flex-direction: column;
  }
}

@media (max-width: 480px) {
  .header-actions {
    gap: var(--space-2);
  }

  .auth-btn {
    padding: var(--space-2) var(--space-3);
    font-size: 0.875rem;
  }

  /* Cushions match FEED's: 12px, and the header comes with them. */
  .main-content {
    margin: 0 12px 40px 12px;
    padding: 0 12px 40px 12px;
  }

  /* vertical 0, exactly as FEED's 480 block. Only the inset follows the cushion. */
  .header-content {
    padding: 0 12px;
  }
}
/* NEW card style: gray->black gradient border, transition starts ~80% (left->right).
   Masked overlay so rounded corners + overflow:hidden + cap band are preserved.
   Excludes add-card (dashed) and tooltip-card (solid outline). */
.bill-card:not(.add-card):not(.tooltip-card) { border: 0 !important; }
.bill-card:not(.add-card):not(.tooltip-card)::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: 12px;
  padding: 2px;
  background: linear-gradient(to right, #4b505a 80%, #000000 100%);
  -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
  -webkit-mask-composite: xor;
          mask-composite: exclude;
  pointer-events: none;
  z-index: 3;
}

/* ADD-CARD, July 9 2026: it should read as an unpainted ticket, not a
   different object. Two layers:
   ::before = the GRAY.png band on the right edge at 25%, placed exactly as the
              real cards place theirs (background-image cannot take an opacity,
              so the band needs its own layer).
   ::after  = the SAME gray->black gradient the cards use, transition at 80%,
              pulled through a DASHED mask so the slot still reads as empty.
              CSS cannot paint a gradient onto a dashed border directly. */
.add-card { border: 0 !important; }
.add-card::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: 12px;
  background-image: url('images/GRAY.png');
  background-repeat: no-repeat;
  background-position: right center;
  background-size: auto 100%;
  opacity: 0.25;
  pointer-events: none;
  z-index: 1;
}
.add-card::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: 12px;
  background: linear-gradient(to right, #4b505a 80%, #000000 100%);
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 360 180' preserveAspectRatio='none'%3E%3Crect x='1' y='1' width='358' height='178' rx='12' ry='12' fill='none' stroke='white' stroke-width='2' stroke-dasharray='8 5'/%3E%3C/svg%3E");
          mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 360 180' preserveAspectRatio='none'%3E%3Crect x='1' y='1' width='358' height='178' rx='12' ry='12' fill='none' stroke='white' stroke-width='2' stroke-dasharray='8 5'/%3E%3C/svg%3E");
  -webkit-mask-size: 100% 100%;
          mask-size: 100% 100%;
  -webkit-mask-repeat: no-repeat;
          mask-repeat: no-repeat;
  pointer-events: none;
  z-index: 3;
}
.add-card:hover::after {
  background: linear-gradient(to right, #6b7280 80%, #000000 100%);
}

/* CHORES icon beside the card's top-left text - matches the HOME
   treatment: 19px, flush-left, 5px outdent */
.bill-amount {
  display: flex;
  align-items: center;
  gap: 6px;
}
.bill-amount .cat-app-icon {
  height: 19px;
  width: auto;
  margin-left: -5px;
}

/* CARD TEXT HIERARCHY:
   title AND category read at full strength - description, due-in
   warning, and date step back to 75% (25% transparent). */
.bill-card .bill-description,
.bill-card .bill-due,
.bill-card .bill-actual-date {
  opacity: 0.75;
}

/* =========================================================================
   THE BREAKPOINT STANDARD. One set of numbers for
   every app:
       >= 1024px   desktop   3 columns
       600-1023px  tablet    2 columns   <- new; an iPad in portrait showed 1
       <= 599px    phone     1 column
       <= 480px    tight cushions (not a column change)
   FEED is exempt: it is one river in date order and has no columns at any
   width. These sit at the FOOT of the file on purpose - equal specificity to
   the 3-column rule above, so source order decides, and the narrower block
   must come last. Do not move them up.
   ========================================================================= */
@media (max-width: 1199px) {
  .bills-grid { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 799px) {
  .bills-grid { grid-template-columns: 1fr; }
}

/* ── THE APP-BAR (ported from FEED, July 10, 2026) ─────────────────────────
   The feed's chooser, now the ONE navigation on every page. Here the .ab elements
   are anchors (links to feed.kronker.com/#app=<app>), not the feed's filter
   buttons, but the look is identical - 34px cap, 0.3 / 0.6 / 1 opacity for
   off / hover / lit. The lit one (.on) is the app you are on.
   Numbers are the feed's, to the pixel; keep them in sync with FEED's copy. */
.app-bar {
  display: flex;
  gap: 8px;
  padding: 18px 0 0 4px;
}
.ab {
  background: none; border: none; padding: 0; line-height: 0;
  cursor: pointer;
  opacity: 0.4;
  transition: opacity 0.12s ease;
  flex: 0 1 34px; min-width: 0;
  text-decoration: none;
}
.ab img { width: 100%; height: auto; display: block; }
/* :not(.on) so a hover never dims the app you are on. */
.ab:not(.on):hover { opacity: 0.6; }
.ab.on { opacity: 1; }
@media (max-width: 480px) {
  .app-bar { gap: 5px; }
}


/* ── THE VIEW SWITCHER (EDITOR · FEED · ADD), ported from FEED July 10, 2026 ──────
   Rides the right end of the app-bar. EDITOR is lit here (you are on the editor);
   FEED is this app's single-column feed; ADD is the feed's mini-add card, wired in
   app.js to the page's own add-card. Tiles are white art; opacity carries state. */
.bar-tools { margin-left: auto; display: flex; align-items: center; gap: 14px; }
.header-tiles { display: flex; align-items: center; gap: 17px; margin-right: -14px; }   /* view tiles live in the header now */
.view-tile { display: flex; align-items: center; opacity: 0.3; transition: opacity 0.12s ease; }
.view-tile.on { opacity: 1; }
.view-tile:not(.on):hover { opacity: 0.6; }
.view-tile img { display: block; height: 26px; width: auto; }
/* .mini-add (the bar's little add card) RETIRED - the full add card
   leads this page's grid and is the one adder. icons/add.png stays. */
@media (max-width: 480px) {
  .bar-tools { gap: 8px; }
  .view-tile img { height: 22px; }
}


/* ── THE WORDS CAME BACK.
   R2.55
   hid the app's name under 400px because it could not fit beside the header's
   view tiles; R2.63 removed the tiles from phones entirely and the arithmetic
   flipped - wordmark ~139 + name (~66 for BIG DAYS, the widest) + gear 55 +
   8px gap + avatar 32 + 24 cushions ≈ 324: inside a 360 phone with room. The
   name now rides at EVERY width.
   THE SEATBELT IS ALL THAT REMAINS: on widths nobody plans (display-zoom
   ~320, split screen) the widest name would still kiss the gear, so the flex
   chain lets the NAME ALONE give way - it trims to an ellipsis instead of
   riding under the buttons. At every normal width it never fires.
   min-width:0 is what permits a flex child to narrow below its content;
   flex-shrink:0 keeps the wordmark art whole; overflow:hidden both trims and
   (per the flex spec) is what allows the shrink at all. */
.logo-section { min-width: 0; }
.logo-section img { flex-shrink: 0; }
.brand-stack { min-width: 0; }
.app-subhead { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }


/* ── THE TOP BAR SITS TIGHT AT PHONE WIDTHS.
   The header always
   wore 40px of air above and 20px below at every width. At <=480 the 40
   above drops to 7, so the bar rides nearly against the top of
   the screen. The 20 below stays exactly as it always was - it went to 7 for
   one deploy, wrongly, and came straight back. A second 480 block on purpose
   - appended after the first, it wins the tie by order, and nothing inside
   the original block had to be touched in seven files. */
@media (max-width: 480px) {
  .header { padding-top: 7px; }
  /* THE VIEW TILES ARE DESKTOP FURNITURE.
   On a phone the pair vanishes; the
     wordmark is still the door to the FEED (it always was), and the feed's
     tickets and mini-add are the door back. Tablets and desktops keep the
     tiles exactly as they are. */
  .header-tiles { display: none; }
  /* THE LOGO STEPS 4px BACK ONTO THE MARGIN.
   The art is not the culprit - the wordmark
     PNG is flush to its first pixel, 0 transparent columns, measured - it is
     the 10px drop-shadow glow. At the desktop's 42px inset the glow never
     approaches the margin line; at the 12px cushion it spills toward the
     screen edge and the logo READS as overshooting the margin by ~4. (The
     crossover also costs 2px of relative inset: desktop letters sit at 42
     against a box at 40; the phone had both at 12.) His eye, his number:
     the whole logo-section steps 4px right - subhead and all, so the pair
     never splits - at phone widths only. */
  .logo-section { margin-left: 4px; }
}

/* ── THE FOOTER SITS AT THE BOTTOM OF THE SCROLL, NOT THE WINDOW.
   IT NEVER LEFT THE APPS - THAT WAS THE
   MISTAKE TO FIND. The cure shipped July 9 on FEED ONLY, where
   the footer is styled from the sheet; the seven apps carried the footer's
   whole old recipe - position:fixed;bottom:12px;z-index:50 - as an INLINE
   style in index.html, where no stylesheet fix could ever reach it (inline
   always wins). The inline style is now gone from the markup and the class
   rules live here, word for word FEED's: the footer is the last element in
   <body>, so ordinary flow puts it below the black box at the end of the
   scroll; main-content's own bottom margin gives it air; the padding keeps
   it off the window's edge. No position, no z-index. If this ever "returns"
   again, look for an inline style FIRST. */
.site-footer {
  text-align: center;
  font-size: 12px;
  color: #6b7280;
  padding: 0 20px 24px 20px;
}
.site-footer a { color: #6b7280; }

/* ── THE EXPORT-PASSWORD BOX GLOWS brand green WHEN SELECTED
.
   This was the LAST data box in the family with no focus style - its tag
   carries inline border styling and no sheet rule existed, so the browser
   drew its own default blue ring. Same glow as every .form-group input.
   !important because the resting border rides the tag inline - a sheet rule
   loses to it without the override. */
.export-password-field input:focus {
  outline: none;
  border-color: #26a11c !important;
  box-shadow: 0 0 0 3px rgba(38, 161, 28, 0.1);
}

/* ── EVERY TICK IS brand green IN EVERY BROWSER.
   The
   gear's boxes are hand-drawn (appearance:none) and were already green; the
   MODAL's checkboxes (Recurring, Autopay and kin) were native, and a native
   tick wears the browser's own blue unless told otherwise. accent-color is
   the one lever Chrome, Safari, Firefox and Edge all honour for native
   ticks; it is inert wherever appearance:none already rules. */
input[type="checkbox"], input[type="radio"] { accent-color: #26a11c; }

/* The body must always cover the viewport now that the canvas below it is the
   GREEN BASE LAYER - without this a short page would show green below the
   gray instead of only in the overscroll. */
body { min-height: 100vh; }

/* THE IPHONE SAFE AREA, CLAIMED: viewport-fit=cover lets the page paint
   edge to edge in an installed app, so the bottom home-indicator zone
   (white by default) wears the site's own gray; the env() cushion keeps
   content clear of the home bar. */
html { background: #1a1a1a; }
body { padding-bottom: env(safe-area-inset-bottom); }

/* THE CALENDAR OPENER SHOWS ITSELF: browsers draw the date field's
   calendar icon black by default - invisible on our dark fields, so
   the picker's one button was hiding in plain sight. Inverted to
   white; typing in the date slots stays exactly as it was. */
.form-group input[type="date"]::-webkit-calendar-picker-indicator {
  filter: invert(1);
  opacity: 0.8;
  cursor: pointer;
}

/* THE PHOTOGRAPH RULE (tiny screens): below 600px a card keeps a 2.3:1
   width-to-height shape and everything inside it - text AND graphics -
   scales with the screen, anchored at the 600px line (a size there
   = size/6 in vw units). The card shrinks like a photograph. The
   compact feed view is deliberately untouched (its own pass). */
@media (max-width: 599px) {
  .bill-card { height: auto; aspect-ratio: 2.4 / 1; width: 100%; padding: 3vw 4vw 4vw 4vw; border-radius: 2vw; }
  /* width definite: MORE's grow sets an inline height, and height +
     ratio would compute WIDTH from it (the room-tile lesson). */
  .bill-content { gap: 2vw; }
  /* Titles read up to TWO clamped lines instead of clipping at one. */
  .card-title { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; line-height: 1.1; }
  .card-desc { font-size: 2.4vw; }
  .desc-more { font-size: 2.133vw; }
  .card-time { gap: 1.333vw; }
  .kronk-count { font-size: 2.267vw; }
  .recurring-indicator img { width: 4vw; height: 4vw; }
  .card-bg { right: 7.333vw; }
  .card-bg img { width: 24vw; height: 24vw; }
  .card-bg-emoji { font-size: 15vw; }
  .autopay-dot { width: 1vw; height: 1vw; }
  .payment-favicon { width: 2.667vw; height: 2.667vw; }
  .bill-card img[src*="BRANDMARK"] { width: 8vw; height: 8vw; }
  /* Section icon unified onto the brandmark mechanic (8vw box, pinned 32px right; 6.8vw icon centred inside - the old flex-flow + fixed 34px drifted off-centre as the layout scaled). */
  .card-bucket { position: absolute; top: 3vw; right: 32px; width: 8vw; margin-right: 0; }
  .card-bucket-icon { width: 6.8vw; height: 6.8vw; }
}

/* PHONES: the wordmark yields up to 20% (32px -> 25.6px) so the
   header's text boxes fit whole instead of truncating. !important
   only because the logo's height rides an inline style attribute
   in every page's markup - one rule here beats ten markup edits. */
@media (max-width: 599px) {
  .logo-section img[src="icons/kronker-logo.png"] { height: 25.6px !important; }
}

/* THE ALL-CAPS BUTTON LAW (the toast law's sibling, ruled July 17,
   2026): every button speaks in caps, enforced at the render -
   never per-string. The avatar rows' spans ride along (Sign In /
   Sign Out are buttons in spirit). */
button, .avatar-item span { text-transform: uppercase; }

/* THE SPONSORED STATE: personal tool icons ride muted - never
   hidden, always navigable; the feed and the school door stay
   bright (the newsletter's own rooms). */
/* THE PICKER HAS TWO STATES: ON AND OFF. Nothing else may touch it.
   The sponsored dress used to add a THIRD - grayscale on every icon except
   EVERYTHING and SCHOOL - so a sponsored member saw on (opacity 1), off
   (0.4 in colour) and off-and-grayed all at once, a distinction no other
   account had. It also arrived LATE, since the class lands only after the
   server answers, which is what read as darkness jumping on after the paint.
   The sponsored state still says its piece where it belongs: the upgrade tip
   and the captured add roads. The navigator just navigates. */
body.kr-sponsored .tooltip-card.kr-up-tip { cursor: pointer; }

/* THE COUPLED MEMBER'S SKIN (B6) IS PART CANVAS, PART SHEET. html and
   body are painted inline by kr-brand.js; the header bar and the mark's
   fade are owned HERE, at the end of this sheet. The bottom safe-area is body PADDING on this tree
   (`body { padding-bottom: env(safe-area-inset-bottom) }`), so it shows
   the body's own painted background and follows the group's colour for
   free. DO NOT add a `body.kr-coupled::after` rule here: this tree has
   no base `body::after` strip, and a pseudo-element with no `content`
   generates no box - the rule would be dead. Only the FEED, which does
   carry a fixed strip, needs that override. */


/* ---- THE COUPLED PAINT (the flash of KRONKER, killed) --------------
   kr-brand.js loads a few lines ABOVE the header markup so its token and
   its canvas land on the first frame. That placement means a
   document.querySelector('.header') inside it finds nothing, so the bar
   used to keep KRONKER's colour until the /api/me.php round trip
   answered - a gray bar for a beat on every single load. CSS has no
   ordering problem: the class and the token are stamped before this
   header is parsed, so its first paint is already the group's colour. */
body.kr-coupled .header { background: var(--kr-brand); }

/* On a FIRST-EVER load the drawer is empty and the answer can only come
   from the network, so the neutral dissolves into the group's colour
   instead of snapping to it. On every load after that the colour is
   already the first paint and nothing moves - a transition only fires
   on a change. */
html, body, .header { transition: background-color 0.45s ease; }

/* THE MARK FADES ON FROM BLANK rather than swapping KRONKER's wordmark
   for the group's in view. kr-brand.js holds it, sets the final src,
   and lifts the hold once that image has actually loaded. The hold is
   both stamped and lifted by that script, so a page that never loads it
   shows its mark the ordinary way. */
html.kr-mark-hold .brand-stack img { opacity: 0; }
.brand-stack img { transition: opacity 0.35s ease; }

/* THE TEXT LADDER - card text only, no other breakpoints touched:
   title, section line and date each read 10% under their desktop
   size at 599 and 20% under their old 320 size at the floor, one
   straight calc ramp between the anchors. Below 320 the floor block
   keeps scaling in vw; 600 and up stays the desktop sizes. */
@media (max-width: 599px) {
    .card-title { font-size: calc(16.78px + 0.877vw); }
    .card-section { font-size: calc(9.87px + 0.516vw); }
    .card-time { font-size: calc(1.92px + 2.199vw); }
    .card-relative { font-size: calc(-1.47px + 2.165vw); }
}
@media (max-width: 320px) {
    .card-title { font-size: 6.12vw; }
    .card-section { font-size: 3.6vw; }
    .card-time { font-size: 2.8vw; }
    .card-relative { font-size: 1.706vw; }
}

/* Selected text inside the add/edit dialogue's boxes: the native
   selection painted black text on dark blue in these dark inputs -
   unreadable. The selection keeps a blue ground; the text stays
   white. */
.modal input::selection, .modal textarea::selection {
    background: rgba(28, 101, 247, 0.6);
    color: #ffffff;
}

/* THE VERTICAL SQUEEZE (~15%): the dialogue's rhythm compressed -
   group gaps, label gaps, the boxes' vertical padding, the header's
   air and the buttons' top gap all come down together so the form
   reads tighter. Modal scope only; nothing outside the popup moves. */
.modal-content .modal-header { margin-bottom: 0.4rem; }
.modal-content .form-group { margin-bottom: 0.85rem; }
.modal-content .form-group label { margin-bottom: 0.4rem; }
.modal-content .form-group input,
.modal-content .form-group select,
.modal-content .form-group textarea { padding: 0.65rem 0.75rem; }
.modal-content .form-actions { margin-top: 1.25rem; gap: 0.65rem; }

/* THE ADD CARD'S BUFFER: a card due TODAY leads the grid with no
   section header, so it sat against the add card with only the bare
   grid gap - every other group gets its air from the header above
   it. The add card carries the buffer itself on the single-column
   widths where the two stack. */
@media (max-width: 799px) {
  .add-card { margin-bottom: 18px; }
}
