/* =====================================================================
   textsms.io — design system
   Ported from the Claude Design export (textcode.io.html). Tokens are
   verbatim; the prototype's inline styles are lifted into components here.

   Rule the design lives by: IBM Plex Sans for prose and labels, IBM Plex
   Mono for every number, code, ID, price and status. Data is monospaced.
   ===================================================================== */

@import url('fonts.css');

:root{
  --bg:#f7f9fb; --surface:#ffffff; --surface2:#f1f4f8; --surface3:#e9edf3;
  --border:#e1e7ef; --border2:#cfd8e4;
  --text:#111827; --text2:#556074; --text3:#8794a7;
  --blue:oklch(0.52 0.19 258); --blueHover:oklch(0.46 0.19 258);
  --blueSoft:oklch(0.96 0.025 258); --blueLine:oklch(0.88 0.06 258); --onBlue:#ffffff;
  --green:oklch(0.55 0.13 150); --greenSoft:oklch(0.95 0.04 150);
  --amber:oklch(0.58 0.12 70); --amberSoft:oklch(0.95 0.05 80);
  --red:oklch(0.55 0.20 25); --redSoft:oklch(0.95 0.05 25);
  --code:#151a21; --codeText:#dfe6ee;
  --shadow:0 1px 2px rgba(16,24,40,.05);
  --mono:'NairaMono','IBM Plex Mono',ui-monospace,'DejaVu Sans Mono',monospace;
}

[data-theme="dark"]{
  --bg:#0d1117; --surface:#151b23; --surface2:#1b222c; --surface3:#232b36;
  --border:#252d39; --border2:#333d4b;
  --text:#e7ecf3; --text2:#9aa6b6; --text3:#6f7c8d;
  --blue:oklch(0.68 0.16 258); --blueHover:oklch(0.74 0.15 258);
  --blueSoft:oklch(0.28 0.07 258); --blueLine:oklch(0.38 0.09 258); --onBlue:#0b1017;
  --green:oklch(0.74 0.14 150); --greenSoft:oklch(0.30 0.06 150);
  --amber:oklch(0.78 0.13 75); --amberSoft:oklch(0.32 0.06 75);
  --red:oklch(0.70 0.17 25); --redSoft:oklch(0.30 0.08 25);
  --code:#0a0e13; --codeText:#d7dee7;
  --shadow:0 1px 2px rgba(0,0,0,.4);
}

*{box-sizing:border-box;}
html,body{margin:0;padding:0;background:var(--bg);}
body{
  font-family:'IBM Plex Sans',system-ui,sans-serif;
  -webkit-font-smoothing:antialiased;
  color:var(--text);
  min-height:100vh;
}
a{color:var(--blue);text-decoration:none;}
a:hover{color:var(--blueHover);text-decoration:underline;}
input,button,select,textarea{font-family:inherit;}
input::placeholder{color:var(--text3);}

/* Alpine: hide un-initialised markup so templates never flash raw. */
[x-cloak]{display:none !important;}

/* IBM Plex Mono carries no naira glyph, so a price falls back mid-number and
   the symbol arrives in a different weight to the digits beside it. Naming the
   fallbacks explicitly at least makes that substitution consistent across
   platforms instead of whatever each OS reaches for first. */
.mono{font-family:var(--mono);}

/* The currency symbol, set apart from the figure. Slightly smaller and lighter
   so the eye lands on the number, and in the sans face, which does have a
   naira glyph — this is what stops the mixed-weight look. */
.cur{
  font-family:'IBM Plex Sans',system-ui,sans-serif;
  font-weight:500;opacity:.7;margin-right:1px;
}

/* ---------- App shell ---------- */
.tsx-header{
  display:flex;align-items:center;gap:28px;padding:0 28px;height:64px;
  background:var(--surface);border-bottom:1px solid var(--border);
}
.tsx-brand{font-size:19px;font-weight:600;letter-spacing:-.01em;color:var(--text);}
.tsx-brand:hover{text-decoration:none;}
.tsx-nav{display:flex;gap:22px;font-size:14px;color:var(--text2);}
.tsx-nav a{color:var(--text2);}
.tsx-header-right{margin-left:auto;display:flex;align-items:center;gap:10px;}

.tsx-balance{
  font:500 13px/1 var(--mono);padding:8px 12px;
  border:1px solid var(--border2);border-radius:7px;color:var(--text);
}

.tsx-shell{display:flex;align-items:stretch;min-height:calc(100vh - 64px);}
.tsx-side{
  width:212px;flex:none;border-right:1px solid var(--border);
  background:var(--surface);padding:20px 12px;
}
.tsx-side a{
  display:block;width:100%;text-align:left;border:none;border-radius:7px;
  padding:9px 12px;margin-bottom:2px;font-size:14px;font-weight:500;
  cursor:pointer;background:transparent;color:var(--text2);
}
.tsx-side a:hover{background:var(--surface2);text-decoration:none;}
.tsx-side a.is-active{background:var(--blueSoft);color:var(--blue);}
.tsx-main{flex:1;min-width:0;padding:32px;}

/* Support. Sits with the nav items rather than apart from them: it is
   something a customer reaches for mid-task, not an escape hatch. */
.tsx-side a.nav-support{display:flex;align-items:center;gap:8px;}
.tsx-side a.nav-support svg{
  width:14px;height:14px;flex:none;
  fill:none;stroke:currentColor;stroke-width:2;
  stroke-linecap:round;stroke-linejoin:round;
}

/* Admin entry. Set apart from the customer-facing items with a rule above it,
   so nobody reaches for it by muscle memory while meaning to click Profile. */
.tsx-side a.nav-admin{
  display:flex;align-items:center;gap:8px;
  margin-top:10px;padding-top:14px;
  border-top:1px solid var(--border);
  border-radius:0;color:var(--text3);
}
.tsx-side a.nav-admin:hover{background:transparent;color:var(--blue);}
.tsx-side a.nav-admin svg{
  width:14px;height:14px;flex:none;
  fill:none;stroke:currentColor;stroke-width:2;
  stroke-linecap:round;stroke-linejoin:round;
}

/* ---------- Buttons ---------- */
.btn{
  border:none;border-radius:7px;font-size:13px;font-weight:600;cursor:pointer;
  padding:9px 16px;background:var(--blue);color:var(--onBlue);white-space:nowrap;
}
.btn:hover{background:var(--blueHover);}
.btn:disabled{opacity:.55;cursor:not-allowed;}
.btn-ghost{
  border:1px solid var(--border2);background:transparent;color:var(--text);
  padding:9px 14px;font-weight:500;
}
.btn-ghost:hover{background:var(--surface2);}
.btn-sm{padding:5px 11px;font-size:12px;border-radius:6px;}
.btn-rent{
  border:1px solid var(--blueLine);background:var(--blueSoft);color:var(--blue);
  padding:5px 11px;border-radius:6px;font-size:12px;font-weight:600;cursor:pointer;
}
.btn-rent:hover:not(:disabled){background:var(--blue);color:var(--onBlue);border-color:var(--blue);}
.btn-rent:disabled{opacity:.5;cursor:not-allowed;}
.btn-tiny{
  border:1px solid var(--border2);background:transparent;color:var(--text2);
  padding:4px 9px;border-radius:6px;font-size:11px;cursor:pointer;
}
/* Row actions on the dashboard. The plain .btn-tiny is grey on transparent,
   which sits next to bright blue Rent buttons and reads as already disabled —
   a customer reported being unable to cancel when the button was working
   perfectly well. Give them enough weight to look pressable, and a real touch
   target: 11px text with 4px padding is far under the 44px minimum. */
.btn-act{
  min-height:34px;padding:7px 14px;font-size:12.5px;font-weight:600;
  border-radius:7px;cursor:pointer;
  border:1px solid var(--border2);background:var(--surface2);color:var(--text);
}
.btn-act:hover:not(:disabled){background:var(--surface3);border-color:var(--blueLine);}
.btn-act:active:not(:disabled){transform:translateY(1px);}
.btn-act:disabled{opacity:.45;cursor:not-allowed;}
.btn-act.is-cancel{color:var(--red);border-color:var(--redSoft);}
.btn-act.is-cancel:hover:not(:disabled){background:var(--redSoft);border-color:var(--red);}

/* Theme toggle: icon-only, square, level with the other header controls.
   The sun/moon swap is driven entirely by the root data-theme attribute —
   no JS is involved in deciding which icon is visible, so the button is
   never empty while Alpine is still booting. */
.btn-icon{
  display:inline-flex;align-items:center;justify-content:center;
  width:34px;height:34px;padding:0;flex:none;
  border:1px solid var(--border2);border-radius:8px;
  background:transparent;color:var(--text2);cursor:pointer;
  transition:background .12s ease,color .12s ease,border-color .12s ease;
}
.btn-icon:hover{background:var(--surface2);color:var(--text);border-color:var(--blueLine);}
.btn-icon:focus-visible{outline:2px solid var(--blue);outline-offset:2px;}

.btn-icon svg{
  width:17px;height:17px;display:block;
  stroke:currentColor;fill:none;
  stroke-width:2;stroke-linecap:round;stroke-linejoin:round;
}
/* Light: offer the moon. Dark: offer the sun. */
.btn-icon .icon-sun{display:none;}
[data-theme="dark"] .btn-icon .icon-moon{display:none;}
[data-theme="dark"] .btn-icon .icon-sun{display:block;}
.btn-tiny:hover:not(:disabled){background:var(--surface2);color:var(--text);}
.btn-tiny:disabled{opacity:.5;cursor:not-allowed;}

/* ---------- Cards & tables ---------- */
.card{
  border:1px solid var(--border);border-radius:12px;
  background:var(--surface);overflow:hidden;
}
.card-head{padding:16px 18px;border-bottom:1px solid var(--border);display:flex;align-items:center;}
.card-title{font-size:14px;font-weight:600;}
.card-sub{font-size:12px;color:var(--text3);margin-top:4px;}

.search{
  display:flex;align-items:center;gap:10px;border:1px solid var(--border2);
  border-radius:8px;padding:0 12px;height:38px;background:var(--bg);
}
.search input{border:none;background:transparent;outline:none;color:var(--text);font-size:14px;width:100%;}
.search-icon{color:var(--text3);font-size:14px;}

.thead{
  padding:11px 20px;background:var(--surface2);border-bottom:1px solid var(--border);
  font:500 11px/1 var(--mono);letter-spacing:.06em;text-transform:uppercase;color:var(--text3);
}
.trow{padding:15px 20px;border-bottom:1px solid var(--border);align-items:center;font-size:13px;}
.trow:last-child{border-bottom:none;}

/* Active rentals — grid tracks lifted straight from the design. */
/* Last track was 64px, narrower than the Cancel button it has to hold, so the
   button overflowed its own cell. */
.g-rentals{display:grid;grid-template-columns:1fr 140px 1fr 80px 110px 100px;min-width:660px;}
/* Services table */
.g-services{display:grid;grid-template-columns:1.6fr .9fr .8fr .9fr 90px;}

/* Landing pricing table.
   MUST live in a class, never an inline style: these rows carry x-show, and
   Alpine reveals an element by setting style.display = '' — which erases any
   display it finds in the inline style attribute. Grid declared here survives
   that, so the row keeps its columns instead of collapsing to inline flow. */
.g-pricing{display:grid;grid-template-columns:2fr 100px 110px;align-items:center;}
.g-pricing > :last-child{text-align:right;}

/* API settings + docs.
   Every one of these that carries a display value is a class for the same
   reason as .g-pricing above: x-show reveals by clearing the inline display,
   so a grid or flex declared inline would be wiped the moment Alpine showed
   the element. */
.g-deliveries{display:grid;grid-template-columns:1.5fr 1fr 92px 1.1fr;}
.g-endpoint{display:grid;grid-template-columns:58px 1fr;gap:12px;align-items:baseline;padding:11px 0;border-bottom:1px solid var(--border);}
.g-endpoint:last-child{border-bottom:none;}
.g-endpoint code{font:500 13px/1.5 var(--mono);color:var(--text);}
.g-endpoint .g-desc{grid-column:2;font-size:12.5px;color:var(--text3);line-height:1.6;}

.method{font:600 10px/1 var(--mono);padding:5px 0;border-radius:5px;text-align:center;letter-spacing:.04em;}
.method-get{background:var(--blueSoft);color:var(--blue);}
.method-post{background:var(--greenSoft);color:var(--green);}

.field{
  width:100%;height:42px;padding:0 12px;border:1px solid var(--border2);
  border-radius:8px;background:var(--bg);color:var(--text);font-size:14px;outline:none;
}
.field:focus{border-color:var(--blue);}

.secret-box{
  display:flex;align-items:center;gap:10px;min-height:42px;padding:0 12px;
  border:1px solid var(--border2);border-radius:8px;background:var(--bg);
  font-family:var(--mono);font-size:13px;overflow:hidden;
}
.secret-box .val{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}

/* Terminal-dark regardless of theme: sample code reads as something you copy
   rather than something you edit. */
.code-block{
  background:var(--code);color:var(--codeText);border-radius:9px;
  padding:15px 17px;overflow-x:auto;margin:0;
  font:400 12.5px/1.75 var(--mono);white-space:pre;
}

/* Pager. In a class rather than inline for the x-show reason above: these
   controls are revealed with x-show="pages > 1", and Alpine's show() calls
   style.removeProperty('display'), which would strip an inline flex and leave
   the buttons stacked in normal flow. */
.pager{display:flex;align-items:center;gap:6px;margin-left:auto;}

.row-actions{display:flex;align-items:center;gap:8px;flex-wrap:wrap;}
.stack{display:flex;flex-direction:column;gap:6px;}

/* Remaining data tables. Same rule as everywhere else in this file: the
   track definition lives in a class so a breakpoint can reach it. */
.g-history{display:grid;grid-template-columns:1fr 150px 1fr 80px 110px;min-width:640px;}
.g-txn{display:grid;grid-template-columns:1.2fr 1fr 1.4fr 1fr;}
.g-profile{display:grid;grid-template-columns:200px 1fr;}
.g-refer{display:grid;grid-template-columns:1fr 1fr;}

/* Wallet stat cards. These were flex with min-width and no grow, so each card
   shrank to its own content and the row came out ragged — a wide Balance next
   to a narrow "Numbers rented". Equal grid tracks fix the widths; a single
   column on a phone makes each card full-bleed instead of half-empty. */
.g-stats{display:grid;grid-template-columns:repeat(3,1fr);gap:16px;}
@media (max-width:760px){ .g-stats{grid-template-columns:1fr;gap:12px;} }

.stat-label{font:500 11px/1 var(--mono);letter-spacing:.08em;text-transform:uppercase;color:var(--text3);}
.stat-value{margin-top:10px;font:500 30px/1.1 var(--mono);word-break:break-all;}
@media (max-width:560px){ .stat-value{font-size:26px;} }

/* Deposit amount field, sized so a six-figure figure still fits. */
.amount-field{
  width:100%;max-width:260px;height:46px;padding:0 14px;
  border:1px solid var(--border2);border-radius:9px;
  background:var(--bg);color:var(--text);font-size:17px;outline:none;
  font-family:var(--mono);
}
.amount-field:focus{border-color:var(--blue);}

.scroll-x{overflow-x:auto;}

/* ---------- Status chips ---------- */
.chip{display:inline-block;font-size:11px;font-weight:600;padding:4px 9px;border-radius:5px;font-family:var(--mono);}
.chip-wait{background:var(--amberSoft);color:var(--amber);}
.chip-ok{background:var(--greenSoft);color:var(--green);}
.chip-off{background:var(--surface3);color:var(--text3);}
.chip-bad{background:var(--redSoft);color:var(--red);}

/* The received code: the single most important string on the page. */
.code-value{
  font:500 15px/1 var(--mono);color:var(--text);letter-spacing:.04em;
  cursor:pointer;border:none;background:transparent;padding:0;
}
.code-value:hover{color:var(--blue);}
.code-wait{font:400 13px/1 var(--mono);color:var(--text3);}

/* ---------- Toasts ---------- */
.toasts{
  position:fixed;right:20px;bottom:20px;z-index:100;
  display:flex;flex-direction:column;align-items:flex-end;gap:8px;
  /* Never wider than the viewport, whatever the message length. */
  max-width:calc(100vw - 40px);
}
.toast{
  background:var(--surface);border:1px solid var(--border2);border-radius:10px;
  padding:13px 16px;font-size:13.5px;line-height:1.5;color:var(--text);
  box-shadow:0 6px 20px rgba(16,24,40,.10),var(--shadow);
  min-width:240px;max-width:380px;
}
[data-theme="dark"] .toast{box-shadow:0 6px 20px rgba(0,0,0,.45),var(--shadow);}

/* On a phone a 380px tile pinned 20px from the right leaves a lopsided sliver
   down one edge. Stretch it across instead, with equal gutters, so the message
   sits square in the screen rather than shoved into a corner. */
@media (max-width:560px){
  .toasts{
    left:12px;right:12px;bottom:12px;
    align-items:stretch;max-width:none;
  }
  .toast{min-width:0;max-width:none;width:100%;padding:14px 16px;}
}
.toast-ok{border-left:3px solid var(--green);}
.toast-err{border-left:3px solid var(--red);}

.empty{padding:28px 20px;text-align:center;font-size:13px;color:var(--text3);}
.muted{font-size:12px;color:var(--text3);}

/* ---------- Landing ----------
   These grids were inline, which is why the page never collapsed on a phone:
   a media query cannot override a style attribute. Anything that needs to
   change at a breakpoint has to live in a class. */
.auth-main{padding:64px 28px;}
@media (max-width:560px){ .auth-main{padding:32px 14px;} }

.l-wrap{max-width:1080px;margin:0 auto;}
.l-section{padding:56px 28px;}
.l-hero{display:grid;grid-template-columns:1fr 1fr;gap:48px;align-items:start;}
.l-hero h1{margin:22px 0 0;font-size:clamp(30px,7vw,44px);line-height:1.12;font-weight:600;letter-spacing:-.025em;}
.l-stats{margin-top:34px;display:flex;gap:40px;flex-wrap:wrap;}
.l-footer{display:grid;grid-template-columns:2fr 1fr 1fr 1fr;gap:32px;}
.l-cta{display:flex;align-items:center;gap:24px;flex-wrap:wrap;}

@media (max-width:900px){
  /* Single column. The rent widget goes below the pitch rather than beside
     it — at half of a phone screen its labels wrap one word per line. */
  .l-hero{grid-template-columns:1fr;gap:30px;}
  .l-footer{grid-template-columns:1fr 1fr;gap:28px;}
  .l-section{padding:44px 20px;}
  .l-stats{gap:28px;}
}

@media (max-width:560px){
  .l-footer{grid-template-columns:1fr;gap:22px;}
  .l-section{padding:36px 16px;}
  .l-cta .btn{margin-left:0;width:100%;text-align:center;}
  /* Country and price need far less room than the service name. */
  .g-pricing{grid-template-columns:1fr 42px 68px;}
  .trow.g-pricing,.thead.g-pricing{padding-left:14px;padding-right:14px;}
  /* Drop the service code — it is a 24-character opaque id and the least
     useful thing on a phone. Service, country, price and the Rent button
     all stay: with the code hidden, four children map onto four tracks. */
  .g-services{grid-template-columns:1.5fr 52px 70px 62px;}
  .g-services > :nth-child(2){display:none;}
  .trow.g-services,.thead.g-services{padding-left:14px;padding-right:14px;}

  /* Wallet transactions: drop Date. Type, amount and status are what you
     scan for; the date is recoverable by tapping through. */
  .g-txn{grid-template-columns:1.2fr 1fr 1fr;}
  .g-txn > :nth-child(3){display:none;}

  /* Profile: a 200px label column leaves nothing for the value on a phone.
     Stack the label above it instead. */
  .g-profile{grid-template-columns:1fr;gap:3px;}

  /* API delivery log: the detail column is the first to go. */
  .g-deliveries{grid-template-columns:1.4fr 1fr 84px;}
  .g-deliveries > :nth-child(4){display:none;}

  .trow,.thead{padding-left:14px;padding-right:14px;}
  .tsx-main{padding:16px 12px;}
  .card-head{padding:14px;}

  /* Rentals and history stay wide and scroll inside their own card — a
     six-column table cannot usefully collapse, but the page must never
     scroll sideways because of it. */
  .g-rentals{min-width:600px;}   /* must still fit the 100px action column */
  .g-history{min-width:560px;}
}

/* Nothing may push the page itself sideways; wide tables scroll in .scroll-x. */
html,body{max-width:100%;overflow-x:hidden;}
img,svg,video{max-width:100%;height:auto;}
pre,.code-block{max-width:100%;}

@media (max-width:860px){
  .tsx-shell{flex-direction:column;}
  .tsx-side{width:100%;border-right:none;border-bottom:1px solid var(--border);display:flex;gap:4px;overflow-x:auto;padding:10px;}
  .tsx-side a{width:auto;white-space:nowrap;margin-bottom:0;}
  /* The sidebar is a horizontal strip here, so the separator has to turn on
     its side too — a top border would read as an underline on one item. */
  .tsx-side a.nav-admin{
    margin-top:0;padding-top:9px;
    border-top:none;border-left:1px solid var(--border);
    padding-left:14px;margin-left:4px;
  }
  .tsx-main{padding:20px 16px;}
  .tsx-header{padding:0 16px;gap:14px;}
  .tsx-nav{display:none;}
}
