Nimbus Sticky Cards

UserBigcatgj
原作者: motionsites
原文链接
2026/8/17
通用
Cards

提示词内容

---

# Prompt to Recreate the Nimbus Grid Platform Accordion Section (Standalone)

Build a standalone single-section page: the "Platform Accordion" from Nimbus Grid. Use plain HTML, CSS, and vanilla JS (Vite project, no frameworks). Match every detail below exactly.

---

## Global Setup

**Fonts (Google Fonts, preconnect both `fonts.googleapis.com` and `fonts.gstatic.com`):**
- `IBM Plex Sans` weights 400, 500
- `IBM Plex Mono` weights 400, 500

**CSS variables on `:root`:**
```css
:root {
  color-scheme: dark;
  --bg: #17130d;
  --ink: #fff4d5;
  --muted: #dacaa1;
  --line: rgba(255, 240, 199, 0.28);
  --glass: rgba(255, 239, 199, 0.16);
  --glass-strong: rgba(255, 239, 199, 0.24);
  --accent: #ead09a;
  --accent-2: #ffd879;
  --deep: #4d3f24;
  --radius: 8px;
  --font-sans: "IBM Plex Sans", ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  --font-mono: "IBM Plex Mono", "SF Mono", ui-monospace, monospace;
}
```

**Resets:** Universal `box-sizing: border-box`. `html { scroll-behavior: smooth; }`. Body: `margin: 0`, background `var(--bg)`, color `var(--ink)`, `font-family: var(--font-sans)`, `font-size: 1rem`, `font-weight: 400`, `line-height: 1.375`, `letter-spacing: 0.0175rem`, antialiased (`-webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale`). Anchors: `color: inherit; text-decoration: none;`. All headings/paragraphs: `margin-top: 0`.

**Screen-reader utility:**
```css
.sr-only {
  position: absolute; width: 1px; height: 1px;
  overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap;
}
```

---

## Section: `.platform-accordion`

`<section class="platform-accordion" id="platform">` with a screen-reader-only `<h2 class="sr-only">` inside.

**Outer container styles:**
```css
.platform-accordion {
  position: relative;
  min-height: 420svh;
  border-top: 1px solid rgba(255, 240, 199, 0.1);
  background:
    radial-gradient(circle at 86% 30%, rgba(234, 208, 154, 0.13), transparent 24rem),
    #050604;
}
```

### `.accordion-inner` (sticky viewport frame)

```css
.accordion-inner {
  position: sticky;
  top: 0;
  display: grid;
  grid-template-columns: minmax(160px, 0.22fr) minmax(0, 0.78fr);
  gap: clamp(28px, 5vw, 72px);
  height: 100svh;
  padding: clamp(48px, 7vw, 86px) clamp(20px, 5vw, 72px);
  overflow: hidden;
}
```

---

### Left column: `.accordion-nav`

`<div class="accordion-nav" role="tablist">` containing 4 `<button class="accordion-tab">` elements. First button gets class `.active`.

**Tab labels (in order):**
1. `Programmable infra`
2. `Data residency`
3. `Elastic scaling`
4. `Unified visibility`

Each button has `data-accordion-tab="infra|residency|scaling|visibility"` respectively, `role="tab"`, `aria-selected="true|false"`.

```css
.accordion-nav {
  align-self: start;
  display: grid;
  gap: 16px;
  padding-top: 14px;
}

.accordion-tab {
  position: relative;
  border: 0;
  background: transparent;
  color: rgba(255, 247, 222, 0.38);
  font-family: var(--font-mono);
  font-size: 11px;
  line-height: 1rem;
  letter-spacing: 0.08rem;
  text-align: left;
  text-transform: uppercase;
  cursor: pointer;
  transition: color 160ms ease, transform 160ms ease;
}

.accordion-tab::before {
  content: "";
  display: inline-block;
  width: 7px;
  height: 7px;
  margin-right: 12px;
  border-radius: 1px;
  background: currentColor;
  vertical-align: 1px;
}

.accordion-tab.active {
  color: var(--accent);
  transform: translateX(2px);
}
```

---

### Right column: `.accordion-stack`

`<div class="accordion-stack" aria-live="polite">` containing 4 `<article class="accordion-card">` elements. First card gets class `.active`.

Each card has `data-accordion-card="infra|residency|scaling|visibility"`.

```css
.accordion-stack {
  position: relative;
  height: min(80svh, 820px);
  align-self: center;
  overflow: hidden;
}

.accordion-card {
  position: absolute;
  inset: 0;
  display: grid;
  grid-template-columns: minmax(220px, 0.35fr) minmax(340px, 0.65fr);
  border-top: 1px solid rgba(255, 247, 222, 0.2);
  background: #050604;
  transform: translateY(var(--card-y, 100%));
  clip-path: inset(0 0 var(--card-clip-bottom, 0px) 0);
  will-change: transform, clip-path;
}
```

---

### Card inner structure (each card)

**Left: `.accordion-copy`**
```html
<div class="accordion-copy">
  <h3>[Card title]</h3>
  <p>[Card description]</p>
</div>
```

```css
.accordion-copy {
  padding: 26px 30px 0 0;
}

.accordion-card h3 {
  margin-bottom: 28px;
  color: var(--ink);
  font-size: clamp(24px, 2.2vw, 40px);
  font-weight: 400;
  line-height: 1.2;
  letter-spacing: 0.0125rem;
}

.accordion-card p {
  max-width: 340px;
  margin-bottom: 0;
  color: var(--muted);
  font-size: clamp(15px, 1.4vw, 22px);
  line-height: 1.55;
}
```

**Right: `.accordion-visual`**

Gold gradient panel containing a dark code window.

```css
.accordion-visual {
  min-height: 100%;
  padding: clamp(34px, 5vw, 64px);
  background:
    linear-gradient(135deg, rgba(234, 208, 154, 0.92), rgba(106, 91, 52, 0.68)),
    radial-gradient(circle at 15% 20%, rgba(255, 247, 222, 0.65), transparent 20rem);
  overflow: hidden;
}
```

**Code window (`.code-window`):**
```css
.code-window {
  width: min(420px, 100%);
  margin-inline: auto;
  padding: 18px 20px 24px;
  border-radius: 8px;
  background: rgba(8, 10, 10, 0.88);
  box-shadow: 0 28px 70px rgba(0, 0, 0, 0.34);
}

.metric-window {
  margin-top: clamp(18px, 5vw, 70px);
}

[data-accordion-card="infra"] .code-window {
  margin-top: 40px;
}

[data-accordion-card="visibility"] .metric-window {
  margin-top: clamp(12px, 3.75vw, 52px);
}

.code-window > span {
  display: inline-block;
  width: 7px;
  height: 7px;
  margin-right: 5px;
  border-radius: 50%;
  background: rgba(255, 247, 222, 0.56);
}

.code-window pre {
  margin: 22px 0 0;
  color: rgba(255, 247, 222, 0.82);
  font-family: var(--font-mono);
  font-size: clamp(11px, 1vw, 14px);
  line-height: 1.55;
  letter-spacing: 0;
  white-space: pre-wrap;
}
```

Each code window has 3 `<span></span>` dot elements (the 3 macOS-style dots), then a `<pre><code>` block.

---

### Card content (exact text)

**Card 1 — `infra`:**
- Title: `Programmable infra`
- Description: `Define storage pools, quotas, regions, and access policy in code so every workspace ships with the same controls.`
- Code window (no `.metric-window` class):
```
01  storage_pool = {
02    name      = "client-vault"
03    region    = "eu-central"
04    quota     = "24 TiB"
05    policy    = encrypted_fast
06  }
```

**Card 2 — `residency`:**
- Title: `Data residency`
- Description: `Pin departments and client workspaces to approved regions with retention and encryption rules attached from day one.`
- Code window (class: `.code-window .metric-window`):
```
Region policy

EU Central     locked
US East        allowed
AP Southeast   review
Retention      7 years
```

**Card 3 — `scaling`:**
- Title: `Elastic scaling`
- Description: `Expand capacity before teams hit limits, route large transfers through faster lanes, and keep procurement predictable.`
- Code window (class: `.code-window .metric-window`):
```
Capacity forecast

Used       18.4 TiB
Reserved   24 TiB
Burst      ready
Next tier  approved
```

**Card 4 — `visibility`:**
- Title: `Unified visibility`
- Description: `Track growth, usage pressure, inactive files, and compliance drift from a single operational surface.`
- Code window (class: `.code-window .metric-window`):
```
Operations view

Sync health       stable
Cold data         14%
Policy drift       0
Audit export      live
```

---

## JavaScript: Scroll-driven accordion

```js
const accordionSection = document.querySelector(".platform-accordion");
const accordionTabs = Array.from(document.querySelectorAll("[data-accordion-tab]"));
const accordionCards = Array.from(document.querySelectorAll("[data-accordion-card]"));

function activateAccordionPanel(panelName) {
  accordionTabs.forEach((tab) => {
    const isActive = tab.dataset.accordionTab === panelName;
    tab.classList.toggle("active", isActive);
    tab.setAttribute("aria-selected", String(isActive));
  });
  accordionCards.forEach((card) => {
    card.classList.toggle("active", card.dataset.accordionCard === panelName);
  });
}

function updateScrollAccordion() {
  if (!accordionSection || !accordionCards.length) return;

  const rect = accordionSection.getBoundingClientRect();
  const scrollable = Math.max(1, rect.height - window.innerHeight);
  const progress = Math.min(1, Math.max(0, -rect.top / scrollable));
  const maxIndex = accordionCards.length - 1;
  const rawIndex = progress * maxIndex;
  const activeIndex = Math.min(maxIndex, Math.max(0, Math.round(rawIndex)));
  const stack = document.querySelector(".accordion-stack");
  const stackHeight = stack ? stack.clientHeight : window.innerHeight * 0.74;
  const collapsedHeight = window.innerWidth <= 820 ? 96 : 84;

  const cardPositions = accordionCards.map((_, index) => {
    let y = 0;
    if (index > 0) {
      const segmentProgress = Math.min(1, Math.max(0, rawIndex - (index - 1)));
      const startY = stackHeight + collapsedHeight;
      const endY = index * collapsedHeight;
      y = startY + (endY - startY) * segmentProgress;
    }
    return Math.round(y);
  });

  accordionCards.forEach((card, index) => {
    const y = cardPositions[index];
    const nextY = cardPositions[index + 1];
    const visibleHeight = typeof nextY === "number"
      ? Math.max(collapsedHeight, Math.min(stackHeight, nextY + 2))
      : stackHeight;
    const clipBottom = Math.max(0, stackHeight - visibleHeight);

    card.style.setProperty("--card-y", `${Math.round(y)}px`);
    card.style.setProperty("--card-clip-bottom", `${Math.round(clipBottom)}px`);
    card.style.zIndex = String(index + 1);
  });

  const activeCard = accordionCards[activeIndex];
  if (activeCard) activateAccordionPanel(activeCard.dataset.accordionCard);
}

// Tab click scrolls to the correct segment
accordionTabs.forEach((tab) => {
  tab.addEventListener("click", () => {
    if (!accordionSection || !accordionCards.length) return;
    const index = accordionCards.findIndex(
      (card) => card.dataset.accordionCard === tab.dataset.accordionTab
    );
    const maxIndex = accordionCards.length - 1;
    const scrollable = accordionSection.offsetHeight - window.innerHeight;
    const target = accordionSection.offsetTop + (index / maxIndex) * scrollable;
    window.scrollTo({ top: target, behavior: "smooth" });
  });
});

window.addEventListener("scroll", updateScrollAccordion, { passive: true });
window.addEventListener("resize", updateScrollAccordion);
updateScrollAccordion();
```

**How the scroll math works:**
- The section is `420svh` tall. As the user scrolls through it, `progress` goes from 0 to 1.
- `rawIndex` maps progress to a float 0..3 (for 4 cards).
- Each card starts off-screen at `stackHeight + collapsedHeight` px below, then slides up to `index * collapsedHeight` (84px desktop, 96px mobile) — stacking as visible header strips.
- `clip-path: inset(0 0 VAR 0)` clips the bottom of each card so only the header strip shows when another card sits on top.
- The active card (nearest integer index) is fully revealed (no clip).

---

## Responsive Breakpoints

### `@media (max-width: 820px)`

```css
.platform-accordion {
  min-height: 420svh;
}

.accordion-inner {
  grid-template-columns: 1fr;
  grid-template-rows: auto 1fr;
  gap: 22px;
  padding: 34px 20px;
}

.accordion-nav {
  align-self: start;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 12px;
  padding-top: 0;
}

.accordion-stack {
  height: 78svh;
  align-self: stretch;
}

.accordion-card {
  grid-template-columns: 1fr;
  grid-template-rows: auto 1fr;
}

.accordion-copy {
  padding: 22px 0 24px;
}

.accordion-visual {
  min-height: 0;
  padding: 28px;
}
```

At 820px: Nav becomes a 2-column grid above the stack. Cards become single-column (copy stacked above visual). Collapsed height changes to 96px (handled in JS via `window.innerWidth <= 820 ? 96 : 84`).

### `@media (max-width: 520px)`

```css
.accordion-nav {
  grid-template-columns: 1fr;
}

.accordion-inner {
  padding-inline: 18px;
}

.accordion-card h3 {
  font-size: 26px;
}

.accordion-card p {
  font-size: 14px;
}

.accordion-visual {
  padding: 18px;
}
```

At 520px: Nav becomes single-column. Tighter padding. Smaller text.

---

## Project structure

```
index.html       (section markup + font links)
styles.css       (all styles + media queries)
script.js        (scroll-driven accordion + tab click)
package.json     (vite ^5.4.2, "type": "module", scripts: dev/build/preview)
vite.config.js   (default export)
```

暂无效果展示

作者尚未提供生成效果案例;新的公开提示词需要至少添加一个案例。

想要不同效果?只改几处即可

AI 自动找出可替换的部分(主体/风格/场景),填入想要的值,其余保持不变

提示词广场登录