  /* 91黑料 site stylesheet. Values from BRAND.md: Inter Extra Bold headlines in
   91黑料 Black, Inter Light body in 91黑料 Dark Grey, 91黑料 Blue for actions,
   white and light grey backgrounds, gradient 1 for headline highlights. */

/* Inter, self-hosted. It used to come from fonts.googleapis.com, which made
   Google a third party on every pageview of a site that carries no analytics
   and no consent banner -- every visitor's IP, for a font. These are the same
   files Google serves, fetched once and committed: the variable face covering
   300..800, so all three weights BRAND.md names come from one download per
   subset. latin and latin-ext only; unicode-range means a visitor fetches just
   the one their text needs, and the site has no Cyrillic, Greek or Vietnamese
   copy. Inter is under the SIL Open Font License 1.1, see fonts/OFL.txt.

   The paths are relative because HtmlBasePlugin rewrites HTML, not CSS -- a
   root-relative url() here would 404 on a prefixed build. */
@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 300 800;
  font-display: swap;
  src: url(/assets/fonts/inter-var-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;
}
@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 300 800;
  font-display: swap;
  src: url(/assets/fonts/inter-var-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;
}

:root {
  --blue: #0052ff;
  /* Eyebrows in 91黑料 Blue read as links above the headline they label. Muted
     slate-blue keeps them a label. */
  --eyebrow: #3c67ad;
  --pink: #ff4ed8;
  --yellow: #ffb018;
  --green: #00e366;
  --light-grey: #f8f9fb;
  --medium-grey: #d0dbe6;
  --dark-grey: #4a5b82;
  --body: #283968;
  --black: #091c45;
  --white: #fff;
  --gradient-1: linear-gradient(90deg, var(--blue), var(--pink));
  --gradient-2: linear-gradient(90deg, var(--pink), var(--yellow));
  --gradient-3: linear-gradient(90deg, var(--yellow), var(--green));
  --gradient-4: linear-gradient(90deg, var(--green), var(--blue));
  --font: "Inter", "Helvetica Neue", Arial, sans-serif;
  --radius: 3px;
  --container: 1200px;
  --narrow: 760px;
  --gap: 24px;
  /* 72px bar plus its 1px rule; the nav is this tall at every width. */
  --nav-h: 73px;
}

*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
body {
  margin: 0; font: 300 17px/1.6 var(--font); color: var(--body); background: var(--white);
  -webkit-font-smoothing: antialiased;
}
img, svg, video, iframe { max-width: 100%; height: auto; }
a { color: var(--blue); text-decoration: none; }
a:hover { text-decoration: underline; }
h1, h2, h3, h4, h5, h6 { font-weight: 800; color: var(--black); line-height: 1.15; margin: 0 0 .5em; letter-spacing: -.01em; }
h1 { font-size: clamp(36px, 5vw, 60px); letter-spacing: -.02em; }
h2 { font-size: clamp(28px, 3.5vw, 40px); }
h3 { font-size: 22px; }
h4 { font-size: 18px; }
p, ul, ol { margin: 0 0 1em; }
strong { font-weight: 800; color: var(--black); }
hr { border: 0; border-top: 1px solid var(--medium-grey); margin: 48px 0; }
table { border-collapse: collapse; width: 100%; }
th, td { text-align: left; padding: 12px; border-bottom: 1px solid var(--medium-grey); vertical-align: top; }
th { font-weight: 800; color: var(--black); }
::selection { background: var(--blue); color: var(--white); }

.container { width: min(var(--container), 100% - 48px); margin-inline: auto; }
.container.narrow { max-width: var(--narrow); }
.skip { position: absolute; left: -999px; top: 8px; background: var(--white); padding: 8px 16px; z-index: 100; }
.skip:focus { left: 8px; }

/* ---- type helpers ---- */
.eyebrow { font-size: 13px; font-weight: 800; letter-spacing: .08em; text-transform: uppercase; color: var(--eyebrow); margin-bottom: 12px; }
.lede { font-size: 20px; color: var(--dark-grey); max-width: 46em; }
.meta { font-size: 14px; color: var(--dark-grey); display: flex; gap: 12px; align-items: center; }
.small { font-size: 15px; }
.hl { font-style: normal; background: var(--gradient-1); -webkit-background-clip: text; background-clip: text; color: transparent; }
.center { text-align: center; }
.center .lede { margin-inline: auto; }

/* ---- buttons ---- */
.btn {
  display: inline-flex; align-items: center; gap: 5px; border-radius: var(--radius);
  padding: 13px 26px; font: 800 16px/1.2 var(--font); border: 1px solid transparent; cursor: pointer; text-decoration: none;
}
.btn:hover { text-decoration: none; }
.btn::after { content: ""; width: 16px; height: 16px; background: currentColor; mask: url(/assets/brand/icons/arrow-right.svg) center/contain no-repeat; }
.btn-primary { background: var(--blue); color: var(--white); }
.btn-primary:hover { background: var(--black); }
.btn-outline { border-color: var(--black); color: var(--black); }
.btn-outline:hover { background: var(--black); color: var(--white); }
.btn-text { padding-inline: 0; color: var(--blue); }
.btn-text:hover { color: var(--black); }
/* .btn-text is a bare link with an arrow, not a box. Giving it the outline
   button's white border drew a frame around every "Learn more" on a dark
   section -- it only needs the colour. */
.section-dark .btn-outline { color: var(--white); border-color: var(--white); }
.section-dark .btn-text { color: var(--white); }
/* A .card keeps its own white surface inside a dark section, so the dark
   section's text colours were painting white type onto a white card -- the
   headline was invisible and the quote sat at about 1.3:1. Cards restate
   their own colours rather than inheriting the band's. */
.section-dark .card { color: var(--body); }
.section-dark .card :is(h1, h2, h3, h4, strong) { color: var(--black); }
.section-dark .card .eyebrow { color: var(--eyebrow); }
.section-dark .card .btn-text { color: var(--blue); }
.section-dark .btn-outline:hover { background: var(--white); color: var(--black); }
.btn-primary.btn-white { background: var(--white); color: var(--black); }
/* Buttons sit inline inside a paragraph. A left margin on the second one is
   fine on one line and pushes it off-centre the moment the pair wraps, which
   is every phone. Lay the pair out instead, so wrapped rows stay aligned. */
p:has(> .btn) { display: flex; flex-wrap: wrap; gap: 12px; align-items: center; }
:is(.center, .cta-band, .quote) p:has(> .btn) { justify-content: center; }

/* ---- layout ---- */
.section { padding: 80px 0; }
/* Two sections sharing a background read as one band, so their 80px paddings
   stack into 160px of empty colour with nothing in it. Collapse the seam.
   Where the background does change, the full 160px is the deliberate break. */
:is(.section-grey, .cta-band) + :is(.section-grey, .cta-band),
.section-dark + .section-dark,
.section:not(.section-grey, .section-dark, .cta-band) + .section:not(.section-grey, .section-dark, .cta-band) { padding-top: 0; }
.section-grey { background: var(--light-grey); }
.section-dark { background: var(--black); color: var(--medium-grey); }
.section-dark h1, .section-dark h2, .section-dark h3, .section-dark strong { color: var(--white); }
.section-dark .lede, .section-dark .eyebrow { color: var(--medium-grey); }
.section-dark .hl { background: none; color: var(--white); }
.grid-2, .grid-3, .grid-4 { display: grid; gap: var(--gap); }
.grid-2 { grid-template-columns: repeat(auto-fit, minmax(min(100%, 380px), 1fr)); }
.grid-3 { grid-template-columns: repeat(auto-fit, minmax(min(100%, 300px), 1fr)); }
.grid-4 { grid-template-columns: repeat(auto-fit, minmax(min(100%, 240px), 1fr)); }
.split { display: grid; gap: 48px; align-items: center; grid-template-columns: repeat(auto-fit, minmax(min(100%, 420px), 1fr)); }
/* A grid nested in one half of a .split has about 420px to work with, which is
   under auto-fit's own minimum -- so it collapses to one column and the four
   cards run down the page instead of sitting two-up beside the artwork. Inside
   a split the pair is explicit, and only drops to one column on a phone. */
.split .grid-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
@media (max-width: 560px) { .split .grid-2 { grid-template-columns: 1fr; } }
.split.reverse > :first-child { order: 1; }
.section-head { max-width: 46em; margin-bottom: 48px; }

/* ---- hero ---- */
.hero { padding: 96px 0 64px; position: relative; overflow: hidden; }
/* The bar is sticky, so it reserves its own height in flow and a hero begins
   underneath it -- which left the wash stopping short of the top of the window
   while the bar itself was transparent. Pull the first hero up behind the bar
   and hand the height back as padding: the content sits exactly where it did,
   but the gradient now runs to the top of the page. */
main > .hero:first-child { margin-top: calc(-1 * var(--nav-h)); padding-top: calc(96px + var(--nav-h)); }
.hero-plain { padding-bottom: 32px; }
.hero-split { display: grid; gap: 48px; align-items: center; grid-template-columns: repeat(auto-fit, minmax(min(100%, 420px), 1fr)); }
.hero .lede { margin-bottom: 32px; }
.hero-image { border-radius: 8px; }
/* The path: a single rounded gradient ribbon sweeping under the hero, from
   BRAND.md. Thickness ~2x the H1 size. */
.hero-path { position: absolute; inset: auto -10% -40px -10%; height: 120px; pointer-events: none; z-index: -1; }

/* Gradient wash: soft brand-colour bloom filling a hero, clipped by the hero's
   own overflow. Colours come from the --wash-* vars so a page can retint it.
 *
 * Every hero gets it. It was opt-in per page, and page after page quietly
 * missed it -- the two product pages and everything under Solutions among them
 * -- which read as broken rather than as a choice. Listing heroes opt out:
 * .hero-plain is a compact utility header with no room for a bloom, and it
 * repeats across paginated archives where it would be wallpaper. */
.hero { --wash-a: var(--blue); --wash-b: var(--pink); --wash-c: var(--yellow); }
.hero-wash-cool { --wash-a: var(--green); --wash-b: var(--blue); --wash-c: var(--pink); }
.hero:not(.hero-plain)::before {
  content: ""; position: absolute; inset: 0; z-index: -1; pointer-events: none;
  background:
    radial-gradient(60% 55% at 12% 15%, color-mix(in srgb, var(--wash-a) 16%, transparent), transparent 70%),
    radial-gradient(55% 50% at 88% 8%, color-mix(in srgb, var(--wash-b) 18%, transparent), transparent 70%),
    radial-gradient(70% 60% at 60% 95%, color-mix(in srgb, var(--wash-c) 12%, transparent), transparent 70%);
  /* fade out before the hero's edge so the wash has no hard cut-off line */
  mask-image: linear-gradient(to bottom, #000 50%, transparent);
}

/* Tab switcher: pure CSS, driven by :target so /pricing/#object-mount deep links
   still land on the right panel. The first panel shows when nothing is targeted. */
.tabs-bar { display: flex; gap: 4px; background: var(--light-grey); border-radius: 99px; padding: 6px; width: max-content; max-width: 100%; margin: 32px auto 0; flex-wrap: wrap; justify-content: center; }
.tabs-bar a { padding: 12px 28px; border-radius: 99px; font: 800 16px/1.2 var(--font); color: var(--black); }
.tabs-bar a:hover { text-decoration: none; color: var(--blue); }
.tab-panel { display: none; }
.tabs:not(:has(.tab-panel:target)) > .tab-panel:first-of-type,
.tab-panel:target { display: block; }
.tabs:not(:has(.tab-panel:target)) .tabs-bar a:first-child,
.tabs:has(#object-storage:target) .tabs-bar a[href="#object-storage"],
.tabs:has(#object-mount:target) .tabs-bar a[href="#object-mount"],
.tabs:has(#cloud-compute:target) .tabs-bar a[href="#cloud-compute"] {
  background: var(--white); box-shadow: 0 2px 8px rgba(9,28,69,.1); color: var(--black);
}

/* Blog lead: featured post on the left, the latest posts listed on the right. */
.blog-lead { display: grid; gap: var(--gap); grid-template-columns: 3fr 2fr; align-items: start; }
.blog-lead-list { display: flex; flex-direction: column; gap: 8px; }
.blog-lead-head { display: flex; flex-wrap: wrap; gap: 16px; align-items: center; justify-content: space-between; }
.blog-lead-head h2 { margin: 0; font-size: 26px; }
.blog-lead-list ul { list-style: none; padding: 0; margin: 0; }
.blog-lead-list li { border-top: 1px solid var(--medium-grey); padding: 16px 0; }
.blog-lead-list a { font-weight: 800; color: var(--black); display: block; }
.blog-lead-list a:hover { color: var(--blue); text-decoration: none; }
.blog-lead-list time { font-size: 14px; color: var(--dark-grey); }
@media (max-width: 860px) {
  .blog-lead { grid-template-columns: 1fr; }
}

/* ---- cards ---- */
.card { background: var(--white); border: 1px solid var(--medium-grey); border-radius: 8px; overflow: hidden; display: flex; flex-direction: column; }
.card-link { color: inherit; }
.card-link:hover { text-decoration: none; border-color: var(--blue); }
.card-link:hover h3 { color: var(--blue); }
.card > img { aspect-ratio: 16/9; object-fit: cover; width: 100%; }
.card-body { padding: 24px; display: flex; flex-direction: column; gap: 8px; flex: 1; }
.card-body h3 { margin: 0; font-size: 19px; }
.card-body .eyebrow, .card-body .meta { margin: 0; }
.card-body > :last-child { margin-top: auto; }
.card-body > p { margin: 0; }
/* The card that ends an integrations row: no partner behind it, so it carries
   the app icons over the brand gradient and centres its own copy. */
.card-stack { border: 0; background: url(/assets/images/icon-grid-partners-wide.svg) 50% 29% / auto 80px no-repeat, url(/assets/images/bg-gradient.png) 0 -10px repeat; }
.card-stack .card-body { justify-content: flex-end; gap: 12px; }
/* Case-study card: the customer's photo fills the card and the copy sits on
   top of it, so the cards read as the customers rather than as a list of
   links. Tint, art and crop come from content/customer.json -- the art is
   composed with room for the copy at the top, so there is no scrim. One tint
   is pale, and cardInk flips the copy for it. Logos are per-card assets
   (cardLogo), already the right colour -- a CSS knockout turns a two-colour
   wordmark into a solid rectangle. */
.card-study { position: relative; min-height: 460px; border: 0; background: var(--card-tint, var(--black)); color: var(--white); }
.card-study > img { position: absolute; inset: 0; width: 100%; height: 100%; aspect-ratio: auto; object-fit: cover; }
.card-study .card-body { position: relative; padding: 40px; gap: 24px; flex: 0; }
.card-study .study-logo { height: 34px; width: auto; max-width: 70%; object-fit: contain; object-position: left; }
.card-study h3 { font-size: clamp(22px, 1.8vw, 28px); line-height: 1.25; color: var(--white); }
.card-study .meta { color: var(--white); font-weight: 600; }
.card-study-dark-ink :is(h3, .meta) { color: var(--black); }
.card-link.card-study:hover h3 { color: inherit; }
.card-link.card-study:hover .meta { text-decoration: underline; }

/* The globe is deliberately bigger than its section and hangs off the right,
   clipped by it -- these are storj.io's own numbers, which grow the sphere with
   the viewport rather than scaling it, so the visible crop stays about the same
   at every width. It is decoration behind the copy, so it takes no pointer
   events, and the section keeps 200px of extra floor for the sphere to fill.
   globe.gl measures the container, so width and height are explicit: an
   aspect-ratio box would still be 0px tall when the WebGL context is built. */
.globe-section { position: relative; overflow: hidden; padding-bottom: 200px; }
.globe-section > .container { position: relative; z-index: 1; }
.globe-copy { max-width: 560px; }
.globe { position: absolute; top: 10%; left: 400px; width: 1200px; height: 1200px; pointer-events: none; }
@media (min-width: 1280px) { .globe { width: 1400px; height: 1400px; } }
@media (min-width: 1440px) { .globe { width: 1600px; height: 1600px; left: 610px; } }
@media (min-width: 1920px) { .globe { left: 900px; } }
@media (max-width: 991px) { .globe { width: 1100px; height: 1100px; left: 100px; margin-top: 140px; } }
@media (max-width: 767px) { .globe { top: 20%; } }
@media (max-width: 479px) { .globe { width: 900px; height: 900px; top: 160px; left: 0; margin-top: 140px; } }

.feature { padding: 0; }
/* Size the icon itself, not just the ones inside .feature. Under .tile it had
   no rule at all, and once .tile became a flex column the images stretched to
   the full cell width -- 309px and 508px leaves. flex: none keeps them out of
   the flex sizing entirely. */
.icon { width: 48px; height: 48px; flex: none; }
:is(.feature, .tile) > .icon { margin-bottom: 16px; }
/* Standing alone as one half of a split, an icon is that column's artwork
   rather than a label for it. A 20px mark stranded in a 560px column read as
   an empty half, so the section centres on a single column instead. */
.split:has(> .icon) { display: flex; flex-direction: column; align-items: center; text-align: center; max-width: var(--narrow); }
.split:has(> .icon) > .icon { order: -1; width: 64px; height: 64px; margin-bottom: 24px; }
.feature h3 { font-size: 20px; }
.tile { background: var(--light-grey); border-radius: 8px; padding: 32px; }

/* Art cards. storj.io builds most of its card grids this way: a tall rounded
   panel with the copy at the top and artwork filling the rest. Which artwork is
   the page's business, not the stylesheet's -- the same slug takes different
   art on different pages, so a rule per card here would need a name per page
   per card. Cards set --card-art and friends on themselves, as the heroes do.

   Backgrounds, not <img>: several crops are percentage offsets with the image
   larger than the card, which object-fit cannot place.

   Which cards take white copy is not derivable from the tint either -- one card
   sits on #0c1d44 and still wants dark copy, because the photo over it is
   light -- so .art-card-white is set per card, from the export. */
.art-card {
  border-radius: 20px; min-height: 525px; padding: 40px;
  background-color: var(--card-tint, transparent);
  background-image: var(--card-art);
  background-position: var(--card-art-pos, 50%);
  background-size: var(--card-art-size, cover);
  background-repeat: var(--card-art-repeat, no-repeat);
}
.art-card h3 { font-size: clamp(22px, 1.8vw, 26px); margin-bottom: 12px; }
.art-card-white, .art-card-white :is(h3, .eyebrow, .btn-text) { color: var(--white); }
.art-card .icon { margin-bottom: 16px; }

/* Paired with .section-dark, which already paints white type, white button
   borders and flattens the .hl gradient -- this adds only the photograph and
   the taller measure the design gives these heroes. ::before is the light
   hero's colour wash, which has nothing to sit on here.

   The artwork itself is not here: each page sets --hero-art and friends on its
   own hero, because a stylesheet rule per page is a rule nobody finds when they
   open the page. The navy gradient is the last layer under whatever the page
   supplies, which is why the position and size lists end in its own values. */
.hero-photo {
  background-image: var(--hero-art), linear-gradient(139deg, #091c45, #040f27);
  background-position: var(--hero-art-pos, 50%), 0 0;
  background-size: var(--hero-art-size, cover), auto;
  background-repeat: var(--hero-art-repeat, no-repeat), repeat;
  background-color: var(--black);
}
.hero-photo::before { display: none; }
/* 15vh top and bottom, the measure the design uses. It has to beat
   `main > .hero:first-child`, which is what sets the padding that lets the
   transparent nav sit over the hero -- at that rule's 96px these heroes came
   out about a third of their height, which crops every `cover` background to a
   fragment of itself. The nav height stays in the top padding for that reason. */
main > .hero-photo:first-child { padding-top: calc(15vh + var(--nav-h)); padding-bottom: 15vh; }
/* The art is placed to the right of the copy, so the copy has to stop before it
   reaches it -- at full container width the headline and lede ran straight over
   the artwork. 600px, left-aligned, is what every one of these heroes is in the
   export: the class list reads "img-hero left dark", and the copy block carries
   a _600w. Ours had several of them centred. */
.hero-photo .container > * { max-width: 600px; }
/* .section-dark flattens the .hl gradient to plain white, which is right for a
   body heading on a dark band and wrong here: these heroes are where the
   gradient headline lives. */
.hero-photo .hl { background: var(--gradient-1); -webkit-background-clip: text; background-clip: text; color: transparent; }
/* The hero eyebrow is sentence case on storj.io, not the uppercase label the
   rest of the site uses, and it carries the product icon. */
.hero-photo .eyebrow { color: var(--white); display: flex; align-items: center; gap: 10px; text-transform: none; letter-spacing: 0; font-size: 16px; }
.hero-photo .eyebrow img { width: 28px; height: 28px; }


.section-grey .tile { background: var(--white); }
.stat { text-align: center; }
.stat-num { font-size: clamp(40px, 5vw, 64px); font-weight: 800; color: var(--black); line-height: 1; margin: 0 0 8px; background: var(--gradient-1); -webkit-background-clip: text; background-clip: text; color: transparent; display: inline-block; }
.logos { display: flex; flex-wrap: wrap; gap: 32px 48px; align-items: center; justify-content: center; }
.logos img { height: 36px; width: auto; max-width: 160px; filter: grayscale(1); opacity: .7; }
.pills { list-style: none; padding: 0; display: flex; flex-wrap: wrap; gap: 8px; }
.pills a { display: inline-block; padding: 6px 14px; border: 1px solid var(--medium-grey); border-radius: 99px; font-size: 14px; color: var(--black); }
.pills a:hover, .pills a[aria-current] { background: var(--black); color: var(--white); text-decoration: none; }
/* `margin: 0` here also zeroed the inline margins that .container sets to auto
   -- same specificity, and this rule comes later -- so every quote sat flush
   against the left edge instead of centred. Zero the block margins only. */
.quote { margin: 0 auto; text-align: center; }
/* The pull-quote treatment belongs to the quotation, not to every <p> in the
   block. An .eyebrow label and a <p> wrapping a CTA button are also <p>
   children of .quote, and a blanket `.quote p` blew both up to 30px. */
.quote > p:not(.eyebrow):not(:has(.btn)) { font-size: clamp(22px, 2.5vw, 30px); font-weight: 500; line-height: 1.35; }
/* Where the block leads with a headline, the headline carries the emphasis and
   the quotation steps down -- otherwise a 40px h2 and a 30px quote compete. */
.quote:has(:is(h2, h3)) :is(h2, h3) { font-size: clamp(24px, 2.2vw, 30px); }
.quote:has(:is(h2, h3)) > p:not(.eyebrow):not(:has(.btn)) { font-size: clamp(17px, 1.3vw, 20px); font-weight: 400; }
.quote > p:not(.eyebrow) { color: var(--black); }
.quote footer { color: var(--dark-grey); }
/* --black IS the .section-dark background, so the quotation was rendering in a
   colour identical to what it sat on (contrast 1.0) and the attribution at 2.5:1. */
.section-dark .quote > p:not(.eyebrow) { color: var(--white); }
.section-dark .quote footer { color: var(--medium-grey); }
.quote .avatar { width: 64px; height: 64px; border-radius: 50%; object-fit: cover; padding: 3px; background: var(--gradient-1); margin-bottom: 16px; }
.avatar { border-radius: 50%; object-fit: cover; }
/* A person is a portrait with a caption under it, so the three parts centre on
   each other. The name was sitting flush against the photo -- .avatar carried
   no bottom margin outside a quote -- and the whole cell was left-aligned. */
.people .feature { align-items: center; text-align: center; }
.people .avatar { width: 96px; height: 96px; flex: none; margin-bottom: 20px; }
.check-list { list-style: none; padding: 0; }
.check-list li { padding-left: 32px; position: relative; margin-bottom: 12px; }
.check-list li::before { content: ""; position: absolute; left: 0; top: 2px; width: 20px; height: 20px; background: var(--blue); mask: url(/assets/brand/icons/circle-check.svg) center/contain no-repeat; }
details.faq { border-bottom: 1px solid var(--medium-grey); padding: 16px 0; }
details.faq summary { cursor: pointer; font-weight: 800; color: var(--black); font-size: 18px; list-style: none; display: flex; justify-content: space-between; gap: 16px; }
details.faq summary::-webkit-details-marker { display: none; }
details.faq summary::after { content: ""; flex: none; width: 20px; height: 20px; background: var(--black); mask: url(/assets/brand/icons/plus.svg) center/contain no-repeat; transition: transform .2s; }
details.faq[open] summary::after { transform: rotate(45deg); }
details.faq > :not(summary) { margin-top: 12px; }
.cta-band { text-align: center; background: var(--light-grey); }
.cta-band .lede { margin-inline: auto; }
.pager { display: flex; gap: 16px; align-items: center; justify-content: center; margin-top: 48px; }
.pager .btn::after { display: none; }
/* A row of these is a comparison: the eye reads across it. Logos have wildly
   different aspect ratios and the headlines run two to four lines, so left to
   stack naturally nothing lined up -- every logo, headline and link sat at its
   own height. Fixing the logo box aligns the headlines, and pushing the link
   down aligns the links, since the grid already stretches the cells to match. */
.customer-logo, .partner-logo {
  height: 48px; width: auto; max-width: 100%;
  object-fit: contain; object-position: left center;
  margin-bottom: 24px;
}
/* The grid stretches every cell in a row to a common height, so a trailing
   link should sit on the row's baseline rather than trailing whatever length
   of copy happens to be above it. Without this the "Learn more" links stepped
   down the row by as much as 99px. */
:is(.tile, .feature) { display: flex; flex-direction: column; }
:is(.tile, .feature) > :is(.btn, p:has(> .btn)):last-child { margin-top: auto; }
.stats { text-align: center; }
/* ---- pricing cards ---- */
/* A pricing row is a comparison table cut into three columns, and it only works
   if every card has the same skeleton -- eyebrow, name, description, rate,
   specs, CTA. Before this the free plan showed a 40px $0 over a check-list
   while the paid plans buried $7/TB and $10/TB in a bordered table, and the two
   halves of the row read as different objects. Every card is a check-list now,
   with no rules in it: the row rhythm carries the structure. */
/* The card row is the one thing on the page allowed to break the 1200px column.
   The 48px page gutter, not the max-width, is what caps it on a laptop, so this
   takes a narrower one too. */
.container.wide { --container: 1600px; width: min(var(--container), 100% - 32px); }
.plan {
  display: flex; flex-direction: column;
  background: var(--light-grey); border: 1px solid transparent;
  border-radius: 8px; padding: 36px;
}
.plan h3 { font-size: 26px; margin-bottom: 8px; }
/* Descriptions run one or two lines depending on how wide the cards get.
   Reserving two -- 3.2em is two lines of the 1.6 body leading -- puts every
   rate below them on a common baseline, which is what makes the row scan. */
.plan h3 + p { color: var(--dark-grey); min-height: 3.2em; margin-bottom: 20px; }

/* The recommended plan is the one place this row raises its voice: it lifts out
   of the grey with a white fill and a blue edge, capped by the house gradient.
   Everything else stays quiet. */
.plan-featured { background: var(--white); border-color: var(--blue); position: relative; overflow: hidden; }
.plan-featured::before { content: ""; position: absolute; inset: 0 0 auto; height: 3px; background: var(--gradient-1); }

/* The rate. The caption track is reserved whether or not a card has a label, so
   the free plan's bare $0 lands on the same line as the paid plans' rates. */
.plan-rate { display: grid; grid-template-rows: 1.4em auto; margin-bottom: 24px; }
.plan-rate-label { font: 800 13px/1.4 var(--font); letter-spacing: .08em; text-transform: uppercase; color: var(--dark-grey); }
.plan-rate .price { grid-row: 2; font-size: 36px; line-height: 1.1; }

/* Specs are one line per fact in every card, so the label leads and the value
   follows it inline -- no second column to align and nothing to wrap awkwardly. */
.plan .check-list { margin-bottom: 0; }
.plan .check-list li { margin-bottom: 14px; }
.plan .check-list li strong { color: var(--black); }

/* An auto top margin drops each CTA to the bottom of its card, and the grid
   stretches cards to equal height, so the three line up across the row. */
.plans .plan-cta { margin: auto 0 0; padding-top: 24px; }
.plans .plan-cta .btn { width: 100%; justify-content: center; }

@media (max-width: 600px) {
  .plan h3 + p { min-height: 0; }
  /* The caption track above the rate exists to line the rates up across the
     row. Stacked on a phone there is no row to line up with, so it reads as a
     gap over the free plan's bare $0. */
  .plan-rate { grid-template-rows: auto auto; }
}
.price { font-size: 40px; font-weight: 800; color: var(--black); line-height: 1; }
/* A three-column comparison cannot compress to a phone. Without this the third
   column ran off the side with no way to reach it -- the page did not even
   scroll, so the content was unreachable rather than merely awkward. */
.table-scroll { overflow-x: auto; }
@media (max-width: 860px) { .table-scroll table { min-width: 620px; } }
.price small { font-size: 15px; font-weight: 300; color: var(--dark-grey); }
.video { aspect-ratio: 16/9; width: 100%; border: 0; border-radius: 8px; }
/* iframes and the logo in a quote are replaced elements, which are inline by
   default: they pick up a stray baseline gap and ignore block margins. The
   video sat 8px above the grid under it while the block above it had 48. */
.container > iframe, .quote img { display: block; margin-inline: auto; }
.video:not(:last-child) { margin-bottom: 48px; }
.quote img:not(.avatar) { margin-bottom: 24px; }
/* A <p> that only wraps buttons has no top margin, so a CTA dropped straight
   after a grid or a quote attribution sat flush against it. */
:is(.grid-2, .grid-3, .grid-4, .logos, table, .video, .quote footer) + p:has(.btn):not(.plan-cta) { margin-top: 24px; }

/* ---- prose (markdown and CMS rich text) ---- */
.prose > :first-child { margin-top: 0; }
.prose h2 { margin-top: 1.6em; font-size: 28px; }
.prose h3 { margin-top: 1.4em; }
.prose img, .prose figure { margin: 32px 0; border-radius: 8px; }
.prose figure > div { display: contents; }
.prose figcaption { font-size: 14px; color: var(--dark-grey); }
.prose li { margin-bottom: .4em; }
.prose blockquote { margin: 32px 0; padding-left: 24px; border-left: 3px solid var(--blue); font-size: 20px; color: var(--black); }
.prose pre { background: var(--black); color: var(--white); padding: 20px; border-radius: 8px; overflow-x: auto; font-size: 14px; }
.prose code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: .9em; }
.prose :not(pre) > code { background: var(--light-grey); padding: 2px 6px; border-radius: 3px; }
.prose a { text-decoration: underline; text-underline-offset: 2px; }
.prose iframe { width: 100%; aspect-ratio: 16/9; border: 0; height: auto; }
.post-head { text-align: center; }
.post-head .meta { justify-content: center; }
.post-head .lede { margin-inline: auto; }
.post-image { border-radius: 8px; margin: 32px 0; width: 100%; }
.author-card { display: flex; gap: 20px; align-items: flex-start; margin-top: 64px; padding-top: 32px; border-top: 1px solid var(--medium-grey); }
.author-card img { border-radius: 50%; flex: none; width: 72px; height: 72px; object-fit: cover; }
.author-card .footer-head { color: var(--black); margin-bottom: 4px; }

/* ---- nav ---- */
/* At the top of the page the bar carries no chrome at all, so the logo and the
   links read as the first line of the page rather than as a fixed header. The
   fill and the rule arrive once the page starts moving underneath it -- and
   also whenever a panel is down, since an open dropdown floating over bare
   page with nothing behind the bar looks detached. */
.nav { position: sticky; top: 0; z-index: 50; background: transparent; border-bottom: 1px solid transparent; transition: background-color .2s ease, border-color .2s ease; }
.nav.is-stuck, .nav:has(.nav-drop[open]), .nav:has(.nav-toggle:checked) {
  background: rgba(255,255,255,.92); backdrop-filter: blur(12px); border-bottom-color: var(--medium-grey);
}
/* The nav is transparent until it sticks, so on a page that opens with a dark
   hero it was navy type and a navy wordmark on navy -- invisible. Ink and logo
   are driven by two custom properties so that state lives in one place: light
   while the nav is still floating over a dark hero, back to dark the moment it
   gains its white background, whether from sticking or from an open menu. */
.nav { --nav-ink: var(--black); --logo-dark: block; --logo-light: none; }
body:has(main > .hero-photo:first-child) .nav {
  --nav-ink: var(--white); --logo-dark: none; --logo-light: block;
}
body:has(main > .hero-photo:first-child) :is(.nav.is-stuck, .nav:has(.nav-drop[open]), .nav:has(.nav-toggle:checked)) {
  --nav-ink: var(--black); --logo-dark: block; --logo-light: none;
}
.nav-logo .logo-dark { display: var(--logo-dark); }
.nav-logo .logo-light { display: var(--logo-light); }
/* Takes no space and never paints: it exists only so the observer has
   something at the top of the document to watch. */
.nav-sentinel { position: absolute; top: 0; left: 0; width: 1px; height: 1px; pointer-events: none; }
@media (prefers-reduced-motion: reduce) { .nav { transition: none; } }
.nav-bar { display: flex; align-items: center; justify-content: space-between; min-height: 72px; gap: 32px; }
.nav-logo img { display: block; }
.nav-toggle, .nav-burger { display: none; }
.nav-menu { display: flex; align-items: center; justify-content: space-between; flex: 1; }
.nav-list { list-style: none; margin: 0; padding: 0; display: flex; gap: 8px; }
.nav-link { display: block; padding: 24px 14px; font-weight: 500; font-size: 15px; color: var(--nav-ink); cursor: pointer; list-style: none; }
.nav-link::-webkit-details-marker { display: none; }
.nav-link:hover { color: var(--blue); text-decoration: none; }
.nav-drop summary::after { content: ""; display: inline-block; width: 12px; height: 12px; margin-left: 6px; vertical-align: -1px; background: currentColor; mask: url(/assets/brand/icons/chevron-down.svg) center/contain no-repeat; }
.nav-drop[open] summary::after { transform: rotate(180deg); }
.nav-panel { position: absolute; background: var(--white); border: 1px solid var(--medium-grey); border-radius: 8px; padding: 16px; display: flex; gap: 24px; box-shadow: 0 16px 40px rgba(9,28,69,.12); margin-top: -8px; }
.nav-group { min-width: 240px; }
.nav-group .eyebrow { padding: 8px 12px 0; margin-bottom: 4px; font-size: 12px; }
.nav-entry { display: block; padding: 10px 12px; border-radius: 6px; color: var(--black); }
.nav-entry:hover { background: var(--light-grey); text-decoration: none; }
.nav-entry span { display: block; font-size: 14px; color: var(--dark-grey); }
.nav-actions { display: flex; align-items: center; gap: 12px; }
.nav-actions .btn { padding: 10px 18px; font-size: 14px; }
.nav-actions .btn-text::after { display: none; }
/* "Sign in" is 91黑料 Blue, which is legible on white and nearly gone on the
   navy hero. It follows the nav ink like everything else up there. */
body:has(main > .hero-photo:first-child) .nav:not(.is-stuck) .nav-actions .btn-text { color: var(--nav-ink); }

@media (max-width: 960px) {
  .nav-burger { display: block; width: 44px; height: 44px; position: relative; cursor: pointer; }
  .nav-burger span, .nav-burger span::before, .nav-burger span::after { content: ""; position: absolute; left: 10px; width: 24px; height: 2px; background: var(--nav-ink); transition: .2s; }
  .nav-burger span { top: 21px; }
  .nav-burger span::before { top: -8px; left: 0; }
  .nav-burger span::after { top: 8px; left: 0; }
  .nav-toggle:checked ~ .nav-burger span { background: transparent; }
  .nav-toggle:checked ~ .nav-burger span::before { transform: translateY(8px) rotate(45deg); }
  .nav-toggle:checked ~ .nav-burger span::after { transform: translateY(-8px) rotate(-45deg); }
  .nav-menu { display: none; position: absolute; top: 100%; left: 0; right: 0; background: var(--white); border-bottom: 1px solid var(--medium-grey); flex-direction: column; align-items: stretch; padding: 8px 24px 24px; max-height: calc(100vh - 72px); overflow: auto; }
  .nav-toggle:checked ~ .nav-menu { display: flex; }
  .nav-list { flex-direction: column; gap: 0; }
  .nav-link { padding: 14px 0; border-bottom: 1px solid var(--light-grey); }
  .nav-panel { position: static; border: 0; box-shadow: none; padding: 0 0 8px; flex-direction: column; gap: 0; margin: 0; }
  .nav-actions { margin-top: 16px; justify-content: stretch; }
  .nav-actions .btn { flex: 1; justify-content: center; }
  .section { padding: 56px 0; }
  .hero { padding: 56px 0 40px; }
  main > .hero:first-child { padding-top: calc(56px + var(--nav-h)); }
}

/* ---- footer ---- */
.footer { background: var(--black); color: var(--medium-grey); padding: 64px 0 32px; font-size: 15px; }
.footer a { color: var(--medium-grey); }
.footer a:hover { color: var(--white); }
.footer ul { list-style: none; padding: 0; margin: 0; }
.footer li { margin-bottom: 8px; }
.footer-grid { display: grid; gap: 40px; grid-template-columns: 2fr repeat(4, 1fr); }
.footer-brand p { max-width: 28em; margin: 20px 0; }
.footer-social { display: flex; gap: 16px; }
.footer-head { font-weight: 800; color: var(--white); margin-bottom: 16px; }
.footer-legal { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 16px; margin-top: 48px; padding-top: 24px; border-top: 1px solid var(--dark-grey); font-size: 13px; }
.footer-legal ul { display: flex; flex-wrap: wrap; gap: 16px; }
.footer-legal p { margin: 0; }
@media (max-width: 960px) { .footer-grid { grid-template-columns: 1fr 1fr; } .footer-brand { grid-column: 1 / -1; } }

/* ---- forms (partials/contact-form.njk, partials/data-form.njk) ---- */
.contact-form { max-width: 640px; }
.cf-form { display: block; }
.cf-grid, .cf-row { display: grid; gap: 16px; grid-template-columns: 1fr 1fr; }
.cf-field { grid-column: 1 / -1; display: flex; flex-direction: column; gap: 6px; margin-bottom: 8px; }
.cf-field.cf-half, .cf-row .cf-field { grid-column: auto; }
.cf-label { font-weight: 500; color: var(--black); font-size: 15px; }
.cf-required { color: var(--pink); margin-left: 2px; }
.cf-help { font-size: 13px; color: var(--dark-grey); }
.cf-form input[type="text"], .cf-form input[type="email"], .cf-form input[type="tel"], .cf-form input[type="url"], .cf-form input[type="number"], .cf-form select, .cf-form textarea {
  font: inherit; font-size: 16px; color: var(--black); width: 100%; padding: 12px 14px; border: 1px solid var(--medium-grey); border-radius: var(--radius); background: var(--white);
}
.cf-form textarea { min-height: 120px; resize: vertical; }
.cf-form input:focus, .cf-form select:focus, .cf-form textarea:focus { outline: 2px solid var(--blue); outline-offset: 0; border-color: var(--blue); }
.cf-checkbox { display: flex; gap: 10px; align-items: center; font-weight: 300; cursor: pointer; }
.cf-checkbox input { width: 18px; height: 18px; accent-color: var(--blue); }
.cf-privacy, .cf-note { font-size: 13px; color: var(--dark-grey); }
.cf-submit { background: var(--blue); color: var(--white); border: 0; border-radius: var(--radius); padding: 13px 26px; font: 800 16px var(--font); cursor: pointer; }
.cf-submit:hover { background: var(--black); }
.cf-sent, .cf-note { display: none; }
.cf-done .cf-sent, .cf-handed-off .cf-note { display: block; color: var(--black); font-weight: 500; }
.cf-done .cf-submit { display: none; }
@media (max-width: 600px) { .cf-grid, .cf-row { grid-template-columns: 1fr; } }

/* media kit: white logo variants need a dark swatch behind them */
.logo-on-dark { background: #001c4a; padding: 1rem; border-radius: .5rem; }

/* Comparison tables (backblaze-comparison): logo headers and rating icons */
.compare-table { margin-bottom: 48px; }
.compare-table th img { height: 28px; width: auto; }
.compare-table td:first-child { font-weight: 800; color: var(--black); width: 18%; }
.compare-table .rating { width: 20px; height: 20px; vertical-align: -4px; }

/* figure captions under split images (performance charts) */
.split figure { margin: 0; }
.split figcaption { margin-top: 12px; font-size: 14px; color: var(--dark-grey); text-align: center; }

/* Homepage hero: the wash carries all the way down behind the artwork rather
   than fading at the midpoint, which is what the Lottie's own gradient wants.
   The animation is 1920x1080 and bleeds past the container on purpose. */
.hero-home::before { mask-image: none; }
.hero-home { --wash-a: var(--blue); --wash-b: var(--pink); --wash-c: var(--blue); }
/* The player sizes its SVG to the container, so the container needs a height
   of its own before the animation loads -- without one the box is 0px tall and
   nothing renders. 16/9 is the animation's own 1920x1080. */
.hero-lottie { aspect-ratio: 16 / 9; max-width: 1100px; margin: 24px auto -6%; }
.hero-lottie svg { display: block; }
