/* Space Grotesk, self-hosted. NOT loaded from fonts.googleapis.com/fonts.gstatic.com, which
   would hand Google the IP address of every visitor to a site whose whole pitch is privacy --
   the exact practice a German court ruled unlawful under the GDPR in 2022 (LG Munchen I,
   3 O 17493/20). Self-hosting also means one less origin to connect to, so the two <link
   rel="preconnect"> hints those pages used to carry are gone with it.

   ONE variable font file per subset covers the whole 300-700 range (the CSS only ever uses
   400/600/700), so this is fewer bytes than the five static weights the old Google URL asked
   for. Licensed SIL OFL 1.1 -- see fonts/OFL.txt, which ships alongside the files because the
   license requires it when the font is redistributed.

   Subsets: latin + latin-ext only, matching what Google served. Vietnamese is deliberately
   omitted. Note the arrow in "<- Back to home" (U+2190) is outside both ranges and always fell
   back to a system font, on Google's copy too -- that is unchanged, not a regression. */
@font-face {
  font-family: 'Space Grotesk';
  font-style: normal;
  font-weight: 300 700;
  font-display: swap;
  src: url('/fonts/space-grotesk-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Space Grotesk';
  font-style: normal;
  font-weight: 300 700;
  font-display: swap;
  src: url('/fonts/space-grotesk-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

:root {
  --bg: #0a1f17;
  --bg-soft: #103026;
  --fg: #f0f4ef;
  --muted: #9bb0a4;
  /* --muted for text sitting on --bg-soft instead of --bg. --muted measures 7.48:1 on
     the page background but only 6.21:1 on --bg-soft, which clears AA (4.5:1 -- this
     copy is ~14-16px, under the 24px large-text threshold) but drops out of AAA. This
     restores 7.96:1 there, so body copy is AAA on both surfaces. */
  --muted-soft: #b4c6ba;
  --accent: #e0a53f;
  --accent-2: #b88734;
  --border: #1d3b2e;
  /* Interactive boundaries only. --border is 1.41:1 against --bg -- fine for the
     decorative card/step-bubble outlines it was made for, but WCAG 1.4.11 wants 3:1
     for anything that draws a control's edge. This has two consumers on two different
     fills -- .cta.secondary's border sits on --bg (3.92:1 here) and .skip-link's sits
     on --bg-soft (3.25:1 here) -- so the value is picked to clear 3:1 against both
     rather than just the first one it was written for. */
  --border-ui: #4f8369;
  /* Destructive actions (the account-page delete panel, .notice.bad). 5.85:1 against --bg, so it
     clears AA as body text and comfortably clears WCAG 1.4.11's 3:1 as a control boundary --
     which matters because it is used for both. */
  --danger: #e0796b;
  --maxw: 880px;
  /* The site is dark-only by design. Declaring it lets the UA render scrollbars and
     any form control in the matching scheme instead of defaulting to light ones. */
  color-scheme: dark;
}

* { box-sizing: border-box; }

/* The three surfaces painted --bg-soft rather than the page background: the How-it-works
   section, the feature cards, and the capability chips. Rebinding --muted for the whole
   subtree lifts every muted descendant at once -- card copy, step copy, the section
   sub-heading, the tile caption, the chips -- rather than patching each rule and leaving
   the next one added on a soft background to silently inherit the darker tone. */
#how, .card, .cap-item { --muted: var(--muted-soft); }

/* Guarded: an unconditional smooth scroll is motion the user didn't ask for on every
   in-page anchor (WCAG 2.3.3). Without the query there is no way to opt out. */
@media (prefers-reduced-motion: no-preference) {
  html { scroll-behavior: smooth; }
}

body {
  margin: 0;
  font-family: 'Space Grotesk', system-ui, sans-serif;
  background: var(--bg);
  color: var(--fg);
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
}

a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }

/* One ring for the whole site. Nothing here removes the UA outline, but the default
   ring is nearly invisible against --bg-soft cards and unreadable on the gold CTA, so
   we draw our own. :focus-visible (not :focus) keeps it off mouse clicks. */
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: 4px;
}

/* Skip link. Off-canvas until focused rather than display:none, which would take it
   out of the tab order entirely and defeat the point. */
.skip-link {
  position: absolute;
  left: 12px;
  top: -100px;
  z-index: 100;
  background: var(--bg-soft);
  color: var(--fg);
  border: 1px solid var(--border-ui);
  border-radius: 8px;
  padding: 10px 18px;
  font-size: 0.95rem;
}
.skip-link:focus { top: 12px; text-decoration: none; }

/* The header is sticky, so an anchor jump (or the skip link) would otherwise land its
   target underneath it -- the focused element ends up obscured, WCAG 2.4.11.
   100px, not the header's 64px: once the nav wraps to two rows on a narrow screen the
   header grows to ~85px, and index.html only cleared an 80px margin because its anchors
   are sections that carry their own top padding. This covers the tallest header outright
   rather than depending on what happens to sit at the target. */
[id] { scroll-margin-top: 100px; }

/* Focused via the skip link's tabindex="-1"; it is a scroll destination, not a control,
   so the ring would just be noise. */
main:focus { outline: none; }

.wrap {
  max-width: var(--maxw);
  margin: 0 auto;
  padding: 0 24px;
}

/* Header */
header {
  border-bottom: 1px solid var(--border);
  position: sticky;
  top: 0;
  background: rgba(10, 31, 23, 0.85);
  backdrop-filter: blur(8px);
  z-index: 10;
}
/* min-height, not height. The nav wraps to two rows below ~500px, and a fixed 64px box
   cannot grow to hold it -- the links then render *outside* the header, on top of the
   hero (WCAG 1.4.10 reflow). It was already within 7px of that before the nav links
   were given a bigger hit area; this makes the header size to its content instead. */
header .wrap {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 4px 16px;
  min-height: 64px;
  padding-block: 8px;
}
.brand {
  font-weight: 700;
  font-size: 1.1rem;
  letter-spacing: -0.01em;
  color: var(--fg);
}
.brand span { color: var(--accent); }
/* inline-block + block padding so the hit area is a real box, not just a line of text.
   At 0.95rem/1.6 the text alone computes to ~24.3px tall -- technically over the 24px
   WCAG 2.5.8 floor, but only just, and it rides on the webfont loading. The padding
   takes it to ~36px so it passes on the fallback font too. */
nav a {
  color: var(--muted);
  margin-left: 24px;
  font-size: 0.95rem;
  display: inline-block;
  padding: 6px 0;
}
nav a:hover { color: var(--fg); text-decoration: none; }

/* The wordmark doubles as the home link on every page. Beats --accent from the base
   `a` rule on specificity, and stays un-underlined on hover -- it reads as a logo, and
   an underlined wordmark looks like a mistake. */
a.brand, a.brand:hover { color: var(--fg); text-decoration: none; }

/* Hero.
   The backdrop is the same dusk scene as the Roku channel tile and splash
   (tools/art/make-tile.py), so the TV and the site read as one product. Two things
   the layer stack is doing:
   - The two scrims on top are not decoration, and the radial one is doing the real
     work. Measured on the rendered page, --muted lead text over the bare sun disc is
     1.59:1 -- far under AA. A flat scrim dark enough to fix that crushes the whole
     scene back into a texture, so the radial darkens only the centre column where the
     copy sits and lets the ridges stay legible at the edges. It is keyed to the
     viewport, not the image, because `cover` re-crops the scene at every width and
     the sun lands behind the copy at some of them.
   - The image's own bottom edge is already exactly --bg (the render's scrim goes
     fully opaque), so it dissolves into the page with no visible seam and needs no
     fade of its own.
   var(--bg) is last as the paint-time fallback, so a failed image load leaves the
   normal dark page rather than unstyled text. */
.hero {
  padding: 96px 0 72px;
  text-align: center;
  background:
    radial-gradient(760px 340px at 50% 42%, rgba(10, 31, 23, 0.84) 0%, rgba(10, 31, 23, 0.6) 55%, transparent 80%),
    linear-gradient(180deg, rgba(10, 31, 23, 0.58) 0%, rgba(10, 31, 23, 0.4) 40%, rgba(10, 31, 23, 0.86) 100%),
    url("/img/hero.webp") center 48% / cover no-repeat,
    var(--bg);
}
.hero h1 {
  font-size: clamp(2.2rem, 5vw, 3.4rem);
  line-height: 1.1;
  margin: 0 0 20px;
  letter-spacing: -0.02em;
}
.hero p.lead {
  font-size: 1.2rem;
  /* Not --muted. Over the hero image --muted measures 3.68:1, under the 4.5:1 AA
     floor for 19px text. This is the exact tone make-tile.py rasterises the "photos
     on the big screen" tagline in, so the lift doubles as a tie to the art. */
  color: #c4d2c8;
  max-width: 560px;
  margin: 0 auto 36px;
}
.cta {
  display: inline-block;
  background: linear-gradient(135deg, var(--accent), var(--accent-2));
  color: #0a1f17;
  padding: 14px 28px;
  border-radius: 10px;
  font-weight: 600;
  font-size: 1.05rem;
}
.cta:hover { text-decoration: none; opacity: 0.92; }
.cta.secondary {
  background: transparent;
  border: 1px solid var(--border-ui);
  color: var(--fg);
  margin-left: 12px;
}
/* The hero pairs two CTAs. A centered flex row spaces them with `gap` — which works in BOTH
   axes, so when they wrap onto separate lines on a narrow phone the second button stays centered
   under the first instead of being nudged off-centre by .cta.secondary's margin-left. That
   margin is neutralised here (it still spaces the account button rows elsewhere). */
.hero-cta {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 12px;
}
.hero-cta .cta { margin: 0; }
/* Auto-renewal disclosure under the hero CTA. Same AA-safe tone as p.lead, since it
   sits over the same hero art (--muted would fall under the 4.5:1 floor here). */
.hero-note {
  margin: 18px auto 0;
  max-width: 480px;
  font-size: 0.9rem;
  color: #c4d2c8;
}

/* Eyebrow labels */
.eyebrow {
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.28em;
  text-transform: uppercase;
  color: var(--accent);
  margin: 0 0 10px;
  text-align: center;
}

/* Sections */
section { padding: 64px 0; }
section h2 {
  font-size: 1.8rem;
  letter-spacing: -0.01em;
  margin: 0 0 8px;
  text-align: center;
}
section .sub {
  text-align: center;
  color: var(--muted);
  margin: 0 auto 48px;
  max-width: 520px;
}

/* .features and .steps are a <ul>/<ol> so the count and the ordering survive without
   CSS (the step numbers live only in a ::before counter, which AT exposes
   inconsistently). The list semantics are the point; the markers are not. */
.features {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 20px;
  list-style: none;
  margin: 0;
  padding: 0;
}
.card {
  background: var(--bg-soft);
  border: 1px solid var(--border);
  border-radius: 14px;
  padding: 28px 24px;
}
.card .icon { width: 28px; height: 28px; margin-bottom: 14px; color: var(--accent); }
.card .icon svg { width: 100%; height: 100%; display: block; }
.card h3 { margin: 0 0 8px; font-size: 1.15rem; }
.card p { margin: 0; color: var(--muted); font-size: 0.97rem; }

/* Steps */
.steps {
  counter-reset: step;
  max-width: 620px;
  margin: 0 auto;
  display: grid;
  gap: 20px;
  list-style: none;
  padding: 0;
}
.step {
  display: flex;
  gap: 18px;
  align-items: flex-start;
}
.step .num {
  counter-increment: step;
  flex: 0 0 auto;
  width: 36px;
  height: 36px;
  border-radius: 50%;
  background: var(--bg-soft);
  border: 1px solid var(--border);
  display: grid;
  place-items: center;
  font-weight: 700;
  color: var(--accent);
}
.step .num::before { content: counter(step); }
.step h3 { margin: 4px 0 4px; font-size: 1.08rem; }
.step p { margin: 0; color: var(--muted); }

/* The channel tile shown in the "open it" step. `width: 290px` is the size Roku itself draws the
   tile at, and the file is a 2x render of it, so it lands pixel-dense on the sort of
   display most people read this on. max-width keeps it from overflowing the step
   column on a phone, where 290px + the number bubble exceeds the viewport. */
.tile-hint { margin: 16px 0 0; }
.tile-hint img {
  display: block;
  width: 290px;
  max-width: 100%;
  height: auto;
  border-radius: 10px;
  border: 1px solid var(--border);
}
.tile-hint figcaption {
  margin-top: 10px;
  font-size: 0.85rem;
  color: var(--muted);
}

/* Footer */
.gold-rule {
  width: 22px;
  height: 1.5px;
  background: var(--accent);
  margin: 0 auto 32px;
}

footer {
  padding: 40px 0;
  color: var(--muted);
  font-size: 0.9rem;
}
footer .wrap {
  display: flex;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 16px;
}
footer a { color: var(--muted); }
footer a:hover { color: var(--fg); }

/* Legal / content pages */
.content { padding: 56px 0 80px; }
.content h1 { font-size: 2rem; letter-spacing: -0.02em; margin: 0 0 4px; }
.content .updated { color: var(--muted); margin: 0 0 40px; font-size: 0.95rem; }
.content h2 { font-size: 1.3rem; margin: 36px 0 10px; text-align: left; }
/* Sub-heading within a legal section (privacy.html's Casting subsections). Sits deliberately
   below h2: smaller, tighter top margin, muted gold so it reads as a division of the section
   above rather than a new one. Without this rule an h3 falls back to the browser default, which
   lands almost on top of h2's size and breaks the page's spacing rhythm. */
.content h3 { font-size: 1.05rem; margin: 24px 0 8px; text-align: left; color: var(--accent); }
.content p, .content li { color: #cdd8d0; }
.content ul, .content ol { padding-left: 22px; }
.content a.back { display: inline-block; margin-bottom: 32px; font-size: 0.95rem; }

/* FAQ accordion (faq.html).
   Native <details>/<summary>, styled but never scripted -- this site loads no JavaScript at all
   and privacy.html turns that into a published claim ("viewing a page here contacts nobody but
   us"). The element brings its own disclosure widget, keyboard operation, and find-in-page
   behaviour, so there is nothing here but paint. Do not replace it with a scripted accordion.
   Rules are on .content details.qa rather than bare <details> so the legal pages could use an
   unstyled one later without inheriting the question-list look. */
/* Every row carries both borders and then gives up its top one when it follows another row, so
   each <h2> group is closed at the bottom without doubling the lines in between. The obvious
   spelling -- border-top on all, border-bottom on :last-of-type -- silently does something else:
   :last-of-type is scoped to the parent, and the parent here is one .wrap holding all four
   groups, so only the very last question on the page got closed and the other three groups ran
   into the next heading with an open edge. There is no "last before an h2" selector; adjacency
   is the way to express this. */
.content details.qa {
  border-top: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
}
.content details.qa + details.qa { border-top: 0; }
/* 14px vertical padding puts each row at a measured 54px tall, so the whole row is the hit area
   rather than just the little triangle -- past 2.5.8's 24px minimum (AA) and past 2.5.5's 44px
   enhanced target (AAA). The nav rule above cites 2.5.8 for its 36px box on the same basis. */
.content details.qa summary {
  cursor: pointer;
  padding: 14px 4px;
  font-weight: 600;
  color: var(--fg);
}
.content details.qa summary:hover,
.content details.qa[open] summary { color: var(--accent); }
/* Zero the browser's 1em paragraph margins so the answer sits against its question, and carry
   the gap on the bottom instead -- otherwise a collapsed item leaves phantom space above the
   next border. */
.content details.qa p,
.content details.qa ul,
.content details.qa ol { margin: 0 0 14px; }

/* Capabilities strip.
   Chips, not a dot-separated run of text. The separator used to be a `::after` on each
   item, and CSS cannot know when an item lands at the end of a wrapped row -- so every
   row ended with an orphaned gold dot floating in whitespace. That was visible at every
   width (the list wraps to three rows even at 880px), and worst on a phone, where each
   item takes its own row and the long one broke mid-item leaving its dot stranded on a
   half-empty line. Spacing is now the delimiter, so there is nothing left to strand.
   Fill + border match .card so the strip reads as part of the same system. */
.capabilities { padding: 0 0 56px; }
.cap-list {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
  list-style: none;
  margin: 0 auto;
  padding: 0;
  max-width: 720px;
  gap: 10px;
}
.cap-item {
  font-size: 0.88rem;
  color: var(--muted);
  background: var(--bg-soft);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 7px 16px;
  /* The one chip long enough to wrap on a narrow phone otherwise orphans "done" on a
     line of its own; balance splits it evenly. Ignored by browsers without it. */
  text-align: center;
  text-wrap: balance;
}

/* A standalone CTA centred under a section's content (onedrive.html closes both the plans
   section and the homepage OneDrive teaser this way). A class rather than an inline style so
   the site keeps a CSP with no 'unsafe-inline' for style-src — the same reason .band exists. */
.cta-row { text-align: center; margin: 40px 0 0; }

/* Trial callout */
.trial-banner {
  display: flex;
  gap: 16px;
  align-items: flex-start;
  background: rgba(224, 165, 63, 0.06);
  border: 1px solid rgba(224, 165, 63, 0.2);
  border-left: 3px solid var(--accent);
  border-radius: 10px;
  padding: 20px 24px;
  margin: 40px auto 0;
  max-width: 620px;
}
.trial-icon { flex: 0 0 auto; width: 18px; height: 18px; color: var(--accent); margin-top: 3px; }
.trial-banner strong { display: block; color: var(--fg); font-size: 0.97rem; margin-bottom: 4px; }
.trial-banner p { margin: 0; color: var(--muted); font-size: 0.93rem; }
/* The block/large-font <strong> rule above is meant for the banner TITLE only. onedrive.html's
   scope note reuses this banner and puts <strong> emphasis inside the body <p>; keep those inline
   at body size so "personal Microsoft account"/"photos" don't break onto their own lines. */
.trial-banner p strong { display: inline; color: var(--fg); font-size: inherit; margin: 0; }

/* Plan chooser (subscribe.html). Radio-cards: the whole label is the hit target, and the
   selected one is marked by an accent border/tint so the choice reads without relying on the
   native radio dot alone. */
.plan-list { border: 0; padding: 0; margin: 24px 0 0; }
.plan-list legend { font-weight: 600; color: var(--fg); margin-bottom: 12px; padding: 0; }
.plan {
  display: flex;
  gap: 14px;
  align-items: flex-start;
  border: 1px solid var(--border-ui);
  border-radius: 10px;
  padding: 16px 18px;
  margin-bottom: 12px;
  cursor: pointer;
}
/* :has lets the card react to its own radio. Where unsupported the native dot still conveys the
   choice, so this is an enhancement, not the only signal. */
.plan:has(input:checked) { border-color: var(--accent); background: rgba(224, 165, 63, 0.06); }
.plan:has(input:focus-visible) { outline: 2px solid var(--accent); outline-offset: 2px; }
.plan input { margin-top: 4px; flex: 0 0 auto; }
.plan-body { display: flex; flex-direction: column; gap: 2px; }
.plan-name { font-weight: 600; color: var(--fg); }
.plan-tag {
  display: inline-block;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: #0a1f17;
  background: var(--accent);
  border-radius: 999px;
  padding: 1px 8px;
  margin-left: 6px;
  vertical-align: middle;
}
.plan-price { color: var(--fg); font-size: 1.05rem; }
.plan-note { color: var(--muted); font-size: 0.9rem; }

/* Windows High Contrast / forced colours.
   The UA overrides `color` and `background-color` here but leaves `background-image`
   alone -- and both of this site's signature surfaces are background-images, so they
   survive into a mode built to guarantee legibility and then sit under forced-white
   text. The hero is the bad one: h1 and the lead land straight on the dusk photo with
   the scrims gone (the scrims are colour, so they ARE stripped). Repaint both from
   system colours instead; the art is decorative and losing it is the point.
   The channel-tile <img> is content, not decoration, so it is deliberately untouched. */
@media (forced-colors: active) {
  .hero { background: Canvas; }
  /* Gold gradient, same problem: a background-image that outlives the forced palette
     and leaves ButtonText on a gold wash. */
  .cta {
    background: ButtonFace;
    color: ButtonText;
    border: 1px solid ButtonBorder;
  }
  .cta.secondary { border-color: ButtonBorder; }
  /* Same reason as .cta above: the danger tint is colour, so it is stripped and the button
     would otherwise lose its edge entirely. GrayText is the forced-palette signal for an
     unavailable control. */
  .cta.danger { border-color: ButtonBorder; color: ButtonText; }
  .cta[disabled] { color: GrayText; border-color: GrayText; }
  .panel.danger { border-color: CanvasText; }
  /* Ensure the ring keeps its own colour slot rather than collapsing into the text. */
  :focus-visible { outline-color: Highlight; }
  .skip-link { background: Canvas; color: CanvasText; border-color: CanvasText; }
}

/* ---------------------------------------------------------------------------
   Account area (/signin, /account)
   ---------------------------------------------------------------------------
   These rules live here rather than in a <style> block on each page so the site
   can keep a CSP with no 'unsafe-inline' at all -- the same reason those pages'
   scripts are in /js/*.js. index.html's one inline style="" attribute became
   .band below for the same reason: an inline style attribute is governed by
   style-src too, so leaving it would have forced 'unsafe-inline' site-wide.

   The account area is the ONLY part of this site that ships JavaScript or sets a
   cookie. Every other page stays script-free and cookie-free, and privacy.html
   states the distinction rather than claiming it of the whole site.
   -------------------------------------------------------------------------- */

/* Was an inline style attribute on index.html's "How it works" section. */
.band {
  background: var(--bg-soft);
  border-top: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
}

/* The origin-story paragraph (index.html's "Why I built this" band). A single narrative
   column: wider than .sub and left-aligned for readable prose, full body color rather than
   muted since it's the point of the section, not a caption under a heading. */
.origin {
  max-width: 640px;
  margin: 0 auto;
  text-align: left;
}

/* Visually hidden but still announced -- form labels, and the aria-live region the
   account pages report progress through. */
.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;
}

/* The account pages toggle whole sections with the `hidden` attribute; `display:flex`
   on .row would otherwise beat the UA's `display:none`. */
[hidden] { display: none !important; }

/* Status / error line shared by both scripted pages. */
.notice {
  border-left: 3px solid var(--accent);
  padding: 2px 0 2px 14px;
  margin: 20px 0;
}
.notice.bad { border-left-color: var(--danger); }

/* Bordered section on the account page. */
.panel {
  border: 1px solid rgba(255, 255, 255, 0.14);
  border-radius: 12px;
  padding: 20px 22px;
  margin: 20px 0;
  background: rgba(255, 255, 255, 0.03);
}
.panel h2 { margin-top: 0; }

/* One item (a signed-in TV, the OneDrive link) with its action on the right. */
.row {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  align-items: center;
  justify-content: space-between;
}
.row + .row {
  margin-top: 14px;
  padding-top: 14px;
  border-top: 1px solid rgba(255, 255, 255, 0.08);
}
.row .meta,
.panel .meta { color: var(--muted); font-size: 0.92rem; }

/* ---- sign-in / account forms (/signin email + code) ---- */

.pair-form {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  align-items: center;
  margin: 24px 0 8px;
}
.pair-form input {
  font: inherit;
  font-size: 1.05rem;
  padding: 12px 14px;
  min-width: 12ch;
  border-radius: 10px;
  border: 1px solid rgba(255, 255, 255, 0.18);
  background: rgba(255, 255, 255, 0.04);
  color: var(--fg);
}
/* The one-time code is a short string typed one character at a time, so give it big, wide-tracked,
   uppercase glyphs that are easy to read back off the email. The email field stays normal size and
   case: addresses are long and lowercase, so uppercasing them at 1.4rem just looked oversized. */
.pair-form input#code {
  font-size: 1.4rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}
.pair-form input:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* ---- the delete-account panel (/account) ----
   Its own colour so it never reads as one more thing to click past. The panel keeps .panel's
   geometry and only re-tints the edge, so it sits in the column with the others rather than
   shouting from a different layout. */
.panel.danger { border-color: rgba(224, 121, 107, 0.42); }
.panel.danger h2 { color: var(--danger); }

.cta.danger {
  background: transparent;
  border: 1px solid var(--danger);
  color: var(--danger);
}
/* Disabled until the confirmation is typed exactly (the account delete button). Inactive controls
   are exempt from the contrast minimum, and looking clearly unavailable is the point of the state. */
.cta[disabled] {
  opacity: 0.45;
  cursor: not-allowed;
}
.cta[disabled]:hover { opacity: 0.45; }

/* Same wide/uppercase treatment as the sign-in code field: this one also takes a short
   string typed character by character, and it must be visibly exact. Not `text-transform:
   uppercase` though -- that changes only the rendering, so it would show DELETE while the
   value stayed "delete" and the button stayed dead with no visible reason why. */
.confirm-form {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  align-items: center;
  margin: 18px 0 8px;
}
.confirm-form input {
  font: inherit;
  letter-spacing: 0.12em;
  padding: 12px 14px;
  min-width: 12ch;
  border-radius: 10px;
  border: 1px solid rgba(255, 255, 255, 0.18);
  background: rgba(255, 255, 255, 0.04);
  color: var(--fg);
}
.confirm-form input:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* The confirm step: which Roku you are about to add. Deliberately prominent --
   it is what makes a phished code visible as belonging to someone else's TV. */
.device-box {
  border: 1px solid rgba(255, 255, 255, 0.14);
  border-radius: 12px;
  padding: 18px 20px;
  margin: 24px 0;
  background: rgba(255, 255, 255, 0.03);
}
.device-box .model {
  font-size: 1.25rem;
  margin: 6px 0 0;
  color: var(--accent);
}
