From 4927416277245be9000b95f6393b0b20bb8f4888 Mon Sep 17 00:00:00 2001 From: ayushya Date: Tue, 29 Sep 2026 22:25:18 -0400 Subject: [PATCH] Add product website (deploy target: noo.ayushya.dev) A single-page, static site under website/ - no build step, no framework, just HTML/CSS/vanilla JS plus the two Google Fonts the app already uses. Colors, radii, type scale and motion timing are copied from lib/theme/design_tokens.dart so it reads as the same product as the app, not a separate marketing skin. Sections: hero with an exploded, parallaxing stack of the app's own UI pieces (file row, photo grid, accent swatches, a toggle, the floating bar) that drift apart on scroll and tilt toward the pointer; feature highlights; a Customize section with two live interactive demos (accent color, floating vs attached bottom bar); a privacy section covering Login Flow v2; and a closing CTA. GitHub/download links are placeholders (documented in website/README.md) until real release URLs exist. Co-Authored-By: Claude Sonnet 5 --- website/README.md | 38 +++ website/favicon.svg | 4 + website/index.html | 307 ++++++++++++++++++ website/script.js | 141 ++++++++ website/styles.css | 758 ++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 1248 insertions(+) create mode 100644 website/README.md create mode 100644 website/favicon.svg create mode 100644 website/index.html create mode 100644 website/script.js create mode 100644 website/styles.css diff --git a/website/README.md b/website/README.md new file mode 100644 index 0000000..3d98088 --- /dev/null +++ b/website/README.md @@ -0,0 +1,38 @@ +# Noo website + +The product site for Noo, meant to be deployed at `noo.ayushya.dev`. + +Static HTML/CSS/vanilla JS, no build step and no dependencies beyond the +two Google Fonts already used by the app (Schibsted Grotesk, Instrument +Sans). Colors, radii, type scale and motion timing are copied from +`lib/theme/design_tokens.dart` so the site and the app read as the same +product. + +## Files + +- `index.html` — the whole page (hero, features, customize, privacy, CTA). +- `styles.css` — all styling, incl. the design tokens as CSS custom + properties and a dark-mode variant (`prefers-color-scheme` and an + explicit `data-theme` override for a future toggle). +- `script.js` — mobile nav, the hero's exploded-layer parallax + (scroll + pointer), the two interactive Customize demos (accent color, + bottom bar style), and scroll-reveal. +- `favicon.svg`. + +## Local preview + +Any static file server works, e.g.: + +```bash +cd website +python3 -m http.server 8000 +``` + +Then open `http://localhost:8000`. + +## Deploying + +Point any static host (Cloudflare Pages, Netlify, GitHub Pages, or a +plain nginx `root`) at this folder - there's nothing to build. Update the +`https://github.com` placeholder links and the two "Download for..." +buttons in `index.html` once real release links exist. diff --git a/website/favicon.svg b/website/favicon.svg new file mode 100644 index 0000000..93211a9 --- /dev/null +++ b/website/favicon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/website/index.html b/website/index.html new file mode 100644 index 0000000..81baef7 --- /dev/null +++ b/website/index.html @@ -0,0 +1,307 @@ + + + + + +Noo — a Nextcloud client that feels like it belongs on your phone + + + + + + + + + + + + + + + + + + +
+ + +
+
+
+

For Nextcloud, unofficial

+

Your cloud.
Looking like yours.

+

+ Noo is a fast, private Nextcloud client that browses files and + photos the way a native app should — and lets you rebuild its + whole shell around how you actually use your phone. +

+ +
    +
  • No password stored — Login Flow v2
  • +
  • Offline device sync
  • +
  • Open source
  • +
+
+ + +
+
+ + +
+
+

Features

+

Everything your Nextcloud does. Nothing it doesn't.

+

+ Files, photos, sharing and sync — built as a real mobile app, not a + browser tab pinned to your home screen. +

+ +
+
+ +

Files, browsed properly

+

Grid or list, sorted your way, with the same filters and + controls whether you're picking a file, uploading, or moving + one into place.

+
+
+ +

Photos, grouped by month

+

Every image and video across your account in one gallery — + filter to just photos, just videos, or just favorites.

+
+
+ +

Offline, for real

+

Pick folders to mirror locally and keep browsing — reading, + opening, previewing — with no connection at all.

+
+
+ +

Sharing, without the detour

+

Share with people or a public link, see who has access, and + copy that link — all from one sheet, not three menus.

+
+
+ +

Activity & recent

+

What changed, and where — grouped into today, yesterday and + this week, so catching up takes seconds.

+
+
+ +

Multiple accounts

+

Switch between personal and work Nextcloud instances with a + swipe — each with its own sync, cache and lock.

+
+
+
+
+ + +
+
+

Customize

+

Shape it around your thumb, not the other way round.

+

+ Every screen bends to a handful of real settings — try a few + right here. +

+ +
+ +
+
+
+ + +
+
Active tab
+
+
+
+
+

Pick your accent

+

Six built-in colors, or match your wallpaper with dynamic + color on Android.

+
+ + + + + + +
+
+
+ +
+
+
+
+ + + + +
+
+
+

Floating or attached

+

The bottom bar can sit flush with the edge, or float free + with rounded corners — your call.

+
+ + +
+
+
+ +
+

And the rest

+
    +
  • Reorder tabs, hide the ones you never open
  • +
  • Move Search into the bottom bar
  • +
  • Light, dark, or match the system
  • +
  • Pure black backgrounds in dark mode
  • +
  • Choose what swiping a file left or right does
  • +
  • Reorder the bulk-action bar for multi-select
  • +
+
+ +
+
+
+ + +
+
+
+

Privacy

+

It's your server. Noo just visits.

+

+ Noo never asks for your password. Signing in hands off to your + Nextcloud server's own login page (Login Flow v2), which returns + a revocable app token — not your credentials. Nothing but that + token, and whatever you choose to sync, ever leaves your device. +

+
+
    +
  • No password stored on device
  • +
  • Talks only to your own server
  • +
  • Open source, auditable
  • +
  • No analytics, no trackers
  • +
+
+
+ + +
+
+

Bring your Nextcloud with you.

+

Free and open source. Point it at your server and go.

+ +

Or build it from source — it's just Flutter.

+
+
+ +
+ +
+ +
+ + + + diff --git a/website/script.js b/website/script.js new file mode 100644 index 0000000..337149e --- /dev/null +++ b/website/script.js @@ -0,0 +1,141 @@ +(() => { + "use strict"; + + /* ---------- mobile nav ---------- */ + + const navToggle = document.getElementById("navToggle"); + const mobileNav = document.getElementById("mobileNav"); + if (navToggle && mobileNav) { + navToggle.addEventListener("click", () => { + const open = mobileNav.classList.toggle("is-open"); + navToggle.setAttribute("aria-expanded", String(open)); + navToggle.setAttribute("aria-label", open ? "Close menu" : "Open menu"); + }); + mobileNav.querySelectorAll("a").forEach((a) => + a.addEventListener("click", () => { + mobileNav.classList.remove("is-open"); + navToggle.setAttribute("aria-expanded", "false"); + }) + ); + } + + /* ---------- exploded hero parallax ---------- */ + /* Each [data-depth] layer drifts at its own rate on scroll, and tilts + gently toward the pointer - the "exploded" pieces read as separate + depths rather than a flat collage. Skipped entirely under + prefers-reduced-motion. */ + + const reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches; + const heroVisual = document.getElementById("heroVisual"); + + if (heroVisual && !reduceMotion) { + const layers = Array.from(heroVisual.querySelectorAll("[data-depth]")).map((el) => ({ + el, + depth: parseFloat(el.dataset.depth) || 0.3, + })); + + let scrollOffset = 0; + let pointer = { x: 0, y: 0 }; // -1..1 + let ticking = false; + + const apply = () => { + const rect = heroVisual.getBoundingClientRect(); + const inView = rect.bottom > 0 && rect.top < window.innerHeight; + if (inView) { + for (const layer of layers) { + const scrollShift = scrollOffset * layer.depth * 40; + const px = pointer.x * layer.depth * 14; + const py = pointer.y * layer.depth * 14; + layer.el.style.transform = + `translate3d(${px}px, ${scrollShift + py}px, 0)`; + } + } + ticking = false; + }; + + const requestTick = () => { + if (!ticking) { + ticking = true; + requestAnimationFrame(apply); + } + }; + + const onScroll = () => { + const rect = heroVisual.getBoundingClientRect(); + // -1 (scrolled well past) .. 1 (below viewport) - 0 while centered. + scrollOffset = (window.innerHeight / 2 - (rect.top + rect.height / 2)) / window.innerHeight; + requestTick(); + }; + + const onPointerMove = (e) => { + const rect = heroVisual.getBoundingClientRect(); + pointer.x = ((e.clientX - rect.left) / rect.width) * 2 - 1; + pointer.y = ((e.clientY - rect.top) / rect.height) * 2 - 1; + requestTick(); + }; + + const onPointerLeave = () => { + pointer = { x: 0, y: 0 }; + requestTick(); + }; + + window.addEventListener("scroll", onScroll, { passive: true }); + heroVisual.addEventListener("pointermove", onPointerMove); + heroVisual.addEventListener("pointerleave", onPointerLeave); + onScroll(); + } + + /* ---------- accent color demo ---------- */ + + const accentDemo = document.getElementById("accentDemo"); + if (accentDemo) { + const buttons = accentDemo.querySelectorAll(".swatch-btn"); + buttons.forEach((btn) => { + btn.addEventListener("click", () => { + buttons.forEach((b) => b.classList.remove("is-active")); + btn.classList.add("is-active"); + const color = btn.dataset.accent; + accentDemo.style.setProperty("--demo-accent", color); + }); + }); + } + + /* ---------- bottom bar style demo ---------- */ + + const barDemo = document.getElementById("barDemo"); + const demoBar = document.getElementById("demoBar"); + if (barDemo && demoBar) { + const buttons = barDemo.querySelectorAll(".segmented-btn"); + buttons.forEach((btn) => { + btn.addEventListener("click", () => { + buttons.forEach((b) => b.classList.remove("is-active")); + btn.classList.add("is-active"); + demoBar.classList.toggle("is-attached", btn.dataset.bar === "attached"); + }); + }); + } + + /* ---------- scroll reveal ---------- */ + + const revealTargets = document.querySelectorAll( + ".card, .customize-demo, .customize-list, .privacy-points li" + ); + revealTargets.forEach((el) => el.classList.add("reveal")); + + if ("IntersectionObserver" in window && !reduceMotion) { + const io = new IntersectionObserver( + (entries) => { + for (const entry of entries) { + if (entry.isIntersecting) { + entry.target.classList.add("is-visible"); + io.unobserve(entry.target); + } + } + }, + { threshold: 0.15, rootMargin: "0px 0px -40px 0px" } + ); + revealTargets.forEach((el) => io.observe(el)); + } else { + revealTargets.forEach((el) => el.classList.add("is-visible")); + } +})(); diff --git a/website/styles.css b/website/styles.css new file mode 100644 index 0000000..f6c1bbd --- /dev/null +++ b/website/styles.css @@ -0,0 +1,758 @@ +/* Noo marketing site — tokens mirror lib/theme/design_tokens.dart exactly, + so this reads as the same product, not a separate marketing skin. */ + +:root { + --bg: #F3EFE6; + --surface: #FFFFFF; + --surface2: #F6F5F2; + --surface3: #EAE8E2; + --line: #EAE8E2; + --fg1: #0E0D0B; + --fg2: #5A5852; + --fg3: #7C7A72; + --accent: #8D0DE3; + --accent-text: #8D0DE3; + --accent-soft: #ECDFFF; + --danger: #A8202A; + + --demo-accent: #8D0DE3; + + --font-display: 'Schibsted Grotesk', system-ui, sans-serif; + --font-body: 'Instrument Sans', system-ui, sans-serif; + + --ease: cubic-bezier(0.7, 0, 0.15, 1); + --motion-fast: 180ms; + --motion-base: 280ms; + + --radius-pill: 999px; + --radius-card: 20px; + --radius-input: 14px; + --radius-panel: 28px; + + --gutter: clamp(20px, 5vw, 64px); + --content-max: 1120px; + + color-scheme: light; +} + +@media (prefers-color-scheme: dark) { + :root:not([data-theme="light"]) { + --bg: #0E0D0B; + --surface: #25241F; + --surface2: #3D3C38; + --surface3: #5A5852; + --line: #3D3C38; + --fg1: #F3EFE6; + --fg2: #BDBAB1; + --fg3: #9E9B92; + --accent: #8D0DE3; + --accent-text: #CBA5FD; + --accent-soft: #2F0454; + --danger: #F49E9E; + color-scheme: dark; + } +} + +:root[data-theme="dark"] { + --bg: #0E0D0B; + --surface: #25241F; + --surface2: #3D3C38; + --surface3: #5A5852; + --line: #3D3C38; + --fg1: #F3EFE6; + --fg2: #BDBAB1; + --fg3: #9E9B92; + --accent: #8D0DE3; + --accent-text: #CBA5FD; + --accent-soft: #2F0454; + --danger: #F49E9E; + color-scheme: dark; +} + +* { box-sizing: border-box; } + +html { scroll-behavior: smooth; } + +body { + margin: 0; + background: var(--bg); + color: var(--fg1); + font-family: var(--font-body); + font-size: 16px; + line-height: 1.5; + -webkit-font-smoothing: antialiased; +} + +img, svg { display: block; max-width: 100%; } + +a { color: inherit; } + +h1, h2, h3 { + font-family: var(--font-display); + font-weight: 600; + margin: 0; + letter-spacing: -0.02em; +} + +.wrap { + max-width: var(--content-max); + margin: 0 auto; + padding-inline: var(--gutter); +} + +.skip-link { + position: absolute; + left: -9999px; + top: 0; + background: var(--accent); + color: #fff; + padding: 10px 16px; + border-radius: var(--radius-input); + z-index: 100; +} +.skip-link:focus { left: 16px; top: 16px; } + +.eyebrow { + font-family: var(--font-body); + font-size: 13px; + font-weight: 600; + letter-spacing: 0.04em; + text-transform: uppercase; + color: var(--accent-text); + margin: 0 0 12px; +} + +/* ---------- buttons ---------- */ + +.btn { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 8px; + font-family: var(--font-body); + font-weight: 600; + text-decoration: none; + border-radius: var(--radius-pill); + border: 1px solid transparent; + cursor: pointer; + transition: transform var(--motion-fast) var(--ease), opacity var(--motion-fast) var(--ease), background var(--motion-fast) var(--ease); + white-space: nowrap; +} +.btn:active { transform: scale(0.98); } + +.btn-primary { background: var(--accent); color: #fff; } +.btn-primary:hover { opacity: 0.92; } + +.btn-outline { background: transparent; color: var(--fg1); border-color: var(--line); } +.btn-outline:hover { background: var(--surface2); } + +.btn-lg { padding: 14px 26px; font-size: 16px; } +.btn-sm { padding: 9px 18px; font-size: 14px; } + +/* ---------- header ---------- */ + +.site-header { + position: sticky; + top: 0; + z-index: 40; + background: var(--bg); + border-bottom: 1px solid var(--line); +} + +.header-row { + display: flex; + align-items: center; + gap: 24px; + height: 72px; +} + +.wordmark { + display: flex; + align-items: center; + gap: 10px; + font-family: var(--font-display); + font-weight: 700; + font-size: 20px; + text-decoration: none; + color: var(--fg1); + margin-right: auto; +} +.wordmark-sm { font-size: 17px; } + +.wordmark-mark { + width: 26px; + height: 26px; + border-radius: 8px; + background: var(--accent); + position: relative; + flex: none; +} +.wordmark-mark::after { + content: ""; + position: absolute; + inset: 7px; + border-radius: 4px; + background: #fff; + opacity: 0.92; +} + +.site-nav { + display: flex; + gap: 28px; +} +.site-nav a { + font-size: 15px; + font-weight: 500; + text-decoration: none; + color: var(--fg2); + transition: color var(--motion-fast) var(--ease); +} +.site-nav a:hover { color: var(--fg1); } + +.nav-toggle { + display: none; + flex-direction: column; + justify-content: center; + gap: 4px; + width: 40px; + height: 40px; + border: none; + background: none; + cursor: pointer; + margin-left: -6px; +} +.nav-toggle span { + display: block; + height: 2px; + border-radius: 2px; + background: var(--fg1); + transition: transform var(--motion-base) var(--ease), opacity var(--motion-base) var(--ease); +} +.nav-toggle[aria-expanded="true"] span:nth-child(1) { transform: translateY(6px) rotate(45deg); } +.nav-toggle[aria-expanded="true"] span:nth-child(2) { opacity: 0; } +.nav-toggle[aria-expanded="true"] span:nth-child(3) { transform: translateY(-6px) rotate(-45deg); } + +.mobile-nav { + display: none; + flex-direction: column; + gap: 4px; + padding: 8px var(--gutter) 20px; + border-top: 1px solid var(--line); +} +.mobile-nav a { + padding: 12px 4px; + text-decoration: none; + color: var(--fg1); + font-weight: 500; + border-bottom: 1px solid var(--line); +} +.mobile-nav a:last-child { border-bottom: none; margin-top: 8px; text-align: center; } +.mobile-nav.is-open { display: flex; } + +@media (max-width: 760px) { + .site-nav, .header-row > .btn { display: none; } + .nav-toggle { display: flex; } +} + +/* ---------- hero ---------- */ + +.hero { padding: clamp(48px, 8vw, 96px) 0 clamp(40px, 6vw, 72px); overflow: clip; } + +.hero-grid { + display: grid; + grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); + gap: clamp(32px, 6vw, 64px); + align-items: center; +} + +.hero-copy h1 { + font-size: clamp(38px, 5.2vw, 60px); + line-height: 1.02; + margin-bottom: 20px; +} +.hero-copy h1 em { + font-style: normal; + color: var(--accent-text); +} + +.hero-sub { + font-size: clamp(16px, 1.6vw, 18px); + color: var(--fg2); + max-width: 46ch; + margin: 0 0 28px; +} + +.hero-actions { display: flex; gap: 14px; flex-wrap: wrap; } + +.hero-proof { + list-style: none; + margin: 28px 0 0; + padding: 0; + display: flex; + gap: 20px; + flex-wrap: wrap; + font-size: 13px; + color: var(--fg3); +} +.hero-proof li { display: flex; align-items: center; gap: 7px; } +.hero-proof li::before { + content: ""; + width: 6px; height: 6px; + border-radius: 50%; + background: var(--accent); +} + +/* ---------- hero exploded visual ---------- */ + +.hero-visual { + position: relative; + height: clamp(360px, 46vw, 520px); + display: flex; + align-items: center; + justify-content: center; +} + +.glow { + position: absolute; + inset: -10%; + background: radial-gradient(closest-side, color-mix(in srgb, var(--accent) 16%, transparent), transparent 70%); + pointer-events: none; +} + +.phone { + position: relative; + width: clamp(180px, 19vw, 220px); + aspect-ratio: 9 / 18.5; + background: var(--fg1); + border-radius: 34px; + padding: 10px; + will-change: transform; +} + +.phone-notch { + position: absolute; + top: 10px; left: 50%; + transform: translateX(-50%); + width: 34%; height: 16px; + background: var(--fg1); + border-radius: 0 0 12px 12px; + z-index: 2; +} + +.phone-screen { + position: relative; + height: 100%; + background: var(--bg); + border-radius: 24px; + overflow: hidden; + display: flex; + flex-direction: column; +} + +.mini-topbar { + display: flex; + align-items: center; + justify-content: space-between; + padding: 22px 14px 8px; +} +.mini-title { font-family: var(--font-display); font-weight: 600; font-size: 15px; color: var(--fg1); } +.mini-dot { width: 16px; height: 16px; border-radius: 50%; background: var(--surface2); } + +.mini-list { flex: 1; padding: 4px 10px; display: flex; flex-direction: column; gap: 6px; } +.mini-row { + display: flex; + align-items: center; + gap: 8px; + background: var(--surface); + border-radius: 10px; + padding: 7px 8px; +} +.mini-row-active { background: var(--accent-soft); } + +.mini-ic { width: 16px; height: 16px; border-radius: 5px; flex: none; } +.ic-folder { background: var(--accent); opacity: 0.85; } +.ic-image { background: #2940A8; opacity: 0.8; } +.ic-file { background: var(--fg2); opacity: 0.6; } +.ic-share { background: #3E7A23; opacity: 0.85; } +.ic-lock { background: var(--fg1); opacity: 0.9; } + +.mini-lines { display: flex; flex-direction: column; gap: 4px; flex: 1; min-width: 0; } +.mini-lines b, .mini-lines i { + display: block; + height: 5px; + border-radius: 3px; + background: var(--fg2); + opacity: 0.35; + font-style: normal; +} +.mini-lines b { width: 70%; opacity: 0.55; } +.mini-lines i { width: 40%; } + +.mini-bar { + display: flex; + align-items: center; + justify-content: space-around; + padding: 10px 8px; + border-top: 1px solid var(--line); + position: relative; +} +.mini-bar-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--fg3); opacity: 0.4; } +.mini-bar-pill { + position: absolute; + left: 10px; + width: 20px; height: 10px; + border-radius: var(--radius-pill); + background: var(--accent-soft); +} + +.chip { + position: absolute; + background: var(--surface); + border: 1px solid var(--line); + border-radius: var(--radius-card); + will-change: transform; +} + +.chip-photo { + top: 6%; right: 2%; + width: 92px; height: 92px; + padding: 8px; + transform: rotate(-6deg); +} +.chip-photo-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 6px; height: 100%; } +.chip-photo-grid span:nth-child(1) { background: #8DCDE2; border-radius: 8px; } +.chip-photo-grid span:nth-child(2) { background: #E5D8BD; border-radius: 8px; } +.chip-photo-grid span:nth-child(3) { background: #DA9AC7; border-radius: 8px; } +.chip-photo-grid span:nth-child(4) { background: #A7D296; border-radius: 8px; } + +.chip-row { + bottom: 20%; right: -2%; + display: flex; align-items: center; gap: 10px; + padding: 12px 16px; + transform: rotate(4deg); + width: 160px; +} + +.chip-swatches { + bottom: 6%; left: 4%; + display: flex; gap: 8px; + padding: 12px; + transform: rotate(-4deg); +} +.chip-swatches .swatch { width: 18px; height: 18px; border-radius: 50%; background: var(--c); } + +.chip-toggle { + top: 16%; left: -4%; + display: flex; align-items: center; gap: 14px; + padding: 12px 14px; + width: 150px; + transform: rotate(3deg); +} +.mini-lines-toggle { flex: 1; } +.mini-toggle { width: 30px; height: 18px; border-radius: var(--radius-pill); background: var(--surface3); position: relative; flex: none; } +.mini-toggle i { position: absolute; top: 2px; left: 2px; width: 14px; height: 14px; border-radius: 50%; background: var(--fg1); opacity: 0.3; } +.mini-toggle-on { background: var(--accent); } +.mini-toggle-on i { left: 14px; background: #fff; opacity: 1; } + +.chip-badge { + bottom: -2%; left: 30%; + display: flex; align-items: center; gap: 8px; + padding: 9px 14px; + font-size: 13px; + font-weight: 500; + border-radius: var(--radius-pill); + transform: rotate(-2deg); +} +.chip-badge .mini-ic { width: 14px; height: 14px; } + +@media (max-width: 980px) { + .hero-grid { grid-template-columns: 1fr; text-align: center; } + .hero-copy { order: 2; } + .hero-copy .hero-sub { margin-inline: auto; } + .hero-actions, .hero-proof { justify-content: center; } + .hero-visual { order: 1; } +} + +/* ---------- sections ---------- */ + +.section { padding: clamp(56px, 8vw, 104px) 0; } +.section-alt { background: var(--surface); } + +.section h2 { + font-size: clamp(28px, 3.4vw, 42px); + line-height: 1.08; + max-width: 20ch; +} +.section-sub { + font-size: clamp(15px, 1.4vw, 17px); + color: var(--fg2); + max-width: 56ch; + margin: 16px 0 0; +} + +.grid { display: grid; gap: 20px; margin-top: 44px; } +.grid-features { grid-template-columns: repeat(3, minmax(0, 1fr)); } + +.card { + background: var(--surface); + border-radius: var(--radius-card); + padding: 28px; +} +.section-alt .card { background: var(--bg); } + +.card h3 { + font-size: 19px; + margin: 16px 0 8px; +} +.card p { margin: 0; color: var(--fg2); font-size: 15px; } + +.card-ic, .pt-ic { + display: inline-flex; + align-items: center; + justify-content: center; + width: 44px; height: 44px; + border-radius: var(--radius-input); + background: var(--accent-soft); + position: relative; +} +.card-ic::after, .pt-ic::after { + content: ""; + width: 20px; height: 20px; + background: var(--accent-text); + -webkit-mask-size: contain; + mask-size: contain; + -webkit-mask-repeat: no-repeat; + mask-repeat: no-repeat; + -webkit-mask-position: center; + mask-position: center; +} + +/* Minimal inline icon set (stroke-style, Lucide-ish) via CSS mask so the + whole page stays dependency-free - no icon font, no SVG sprite fetch. */ +.ic-folder-lg::after { -webkit-mask-image: var(--i-folder); mask-image: var(--i-folder); } +.ic-image-lg::after { -webkit-mask-image: var(--i-image); mask-image: var(--i-image); } +.ic-download-lg::after { -webkit-mask-image: var(--i-download); mask-image: var(--i-download); } +.ic-share-lg::after { -webkit-mask-image: var(--i-share); mask-image: var(--i-share); } +.ic-clock-lg::after { -webkit-mask-image: var(--i-clock); mask-image: var(--i-clock); } +.ic-users-lg::after { -webkit-mask-image: var(--i-users); mask-image: var(--i-users); } +.pt-ic.ic-lock::after { -webkit-mask-image: var(--i-lock); mask-image: var(--i-lock); } +.pt-ic.ic-server::after { -webkit-mask-image: var(--i-server); mask-image: var(--i-server); } +.pt-ic.ic-code::after { -webkit-mask-image: var(--i-code); mask-image: var(--i-code); } +.pt-ic.ic-off::after { -webkit-mask-image: var(--i-off); mask-image: var(--i-off); } + +:root { + --i-folder: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 20h16a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13c0 1.1.9 2 2 2Z'/%3E%3C/svg%3E"); + --i-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect width='18' height='18' x='3' y='3' rx='2'/%3E%3Ccircle cx='9' cy='9' r='2'/%3E%3Cpath d='m21 15-3.1-3.1a2 2 0 0 0-2.8 0L6 21'/%3E%3C/svg%3E"); + --i-download: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4'/%3E%3Cpolyline points='7 10 12 15 17 10'/%3E%3Cline x1='12' y1='15' x2='12' y2='3'/%3E%3C/svg%3E"); + --i-share: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='18' cy='5' r='3'/%3E%3Ccircle cx='6' cy='12' r='3'/%3E%3Ccircle cx='18' cy='19' r='3'/%3E%3Cline x1='8.6' y1='10.5' x2='15.4' y2='6.5'/%3E%3Cline x1='8.6' y1='13.5' x2='15.4' y2='17.5'/%3E%3C/svg%3E"); + --i-clock: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='9'/%3E%3Cpolyline points='12 7 12 12 15.5 14.5'/%3E%3C/svg%3E"); + --i-users: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2'/%3E%3Ccircle cx='9' cy='7' r='4'/%3E%3Cpath d='M22 21v-2a4 4 0 0 0-3-3.87'/%3E%3Cpath d='M16 3.13a4 4 0 0 1 0 7.75'/%3E%3C/svg%3E"); + --i-lock: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='11' width='18' height='11' rx='2'/%3E%3Cpath d='M7 11V7a5 5 0 0 1 10 0v4'/%3E%3C/svg%3E"); + --i-server: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='2' y='3' width='20' height='7' rx='1.5'/%3E%3Crect x='2' y='14' width='20' height='7' rx='1.5'/%3E%3Cline x1='6' y1='6.5' x2='6.01' y2='6.5'/%3E%3Cline x1='6' y1='17.5' x2='6.01' y2='17.5'/%3E%3C/svg%3E"); + --i-code: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='16 18 22 12 16 6'/%3E%3Cpolyline points='8 6 2 12 8 18'/%3E%3C/svg%3E"); + --i-off: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='2' y1='2' x2='22' y2='22'/%3E%3Cpath d='M16.9 16.9A9 9 0 0 1 4 12a9 9 0 0 1 2.6-6.3'/%3E%3Cpath d='M9.9 4.24A9 9 0 0 1 20 12a9 9 0 0 1-.8 3.7'/%3E%3C/svg%3E"); +} + +@media (max-width: 900px) { .grid-features { grid-template-columns: repeat(2, minmax(0, 1fr)); } } +@media (max-width: 600px) { .grid-features { grid-template-columns: 1fr; } } + +/* ---------- customize ---------- */ + +.customize-grid { + margin-top: 44px; + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 20px; +} + +.customize-demo { + background: var(--bg); + border-radius: var(--radius-card); + padding: 24px; + display: flex; + flex-direction: column; + gap: 22px; +} + +.customize-demo h3 { font-size: 18px; margin: 0 0 8px; } +.customize-demo p { margin: 0; color: var(--fg2); font-size: 14px; } + +.demo-preview { + background: var(--surface); + border: 1px solid var(--line); + border-radius: var(--radius-input); + padding: 16px; + display: flex; + flex-direction: column; + gap: 10px; +} +.demo-topbar { display: flex; align-items: center; gap: 10px; } +.demo-dot { width: 12px; height: 12px; border-radius: 50%; transition: background var(--motion-base) var(--ease); } +.demo-line { display: block; height: 8px; border-radius: 4px; background: var(--surface3); } +.demo-pill { + align-self: flex-start; + padding: 6px 14px; + border-radius: var(--radius-pill); + font-size: 13px; + font-weight: 600; + transition: background var(--motion-base) var(--ease), color var(--motion-base) var(--ease); +} +.demo-row { padding: 6px 0; } + +.swatch-row { display: flex; gap: 10px; } +.swatch-btn { + width: 32px; height: 32px; + border-radius: 50%; + border: 2px solid transparent; + background: var(--c); + cursor: pointer; + padding: 0; + transition: transform var(--motion-fast) var(--ease), border-color var(--motion-fast) var(--ease); +} +.swatch-btn:last-child { + background: linear-gradient(135deg, #8DCDE2, #DA9AC7 50%, #E5D8BD); +} +.swatch-btn.is-active { border-color: var(--fg1); transform: scale(1.08); } +.swatch-btn:hover { transform: scale(1.08); } + +.demo-preview-bar { position: relative; height: 128px; padding: 0; overflow: hidden; } +.demo-screen-fill { + position: absolute; inset: 0; + background: repeating-linear-gradient(180deg, var(--surface2) 0 34px, var(--surface) 34px 68px); + opacity: 0.6; +} +.demo-bar { + position: absolute; + left: 12px; right: 12px; bottom: 12px; + height: 44px; + background: var(--surface); + border: 1px solid var(--line); + border-radius: var(--radius-pill); + display: flex; + align-items: center; + justify-content: space-around; + transition: left var(--motion-base) var(--ease), right var(--motion-base) var(--ease), bottom var(--motion-base) var(--ease), border-radius var(--motion-base) var(--ease); +} +.demo-bar.is-attached { left: 0; right: 0; bottom: 0; border-radius: 0; border-width: 1px 0 0; } +.demo-bar-item { width: 18px; height: 18px; border-radius: 6px; background: var(--surface3); transition: background var(--motion-fast) var(--ease); } +.demo-bar-item.is-on { background: var(--accent); } + +.segmented { + display: inline-flex; + padding: 3px; + background: var(--surface); + border-radius: var(--radius-pill); + gap: 2px; + align-self: flex-start; +} +.segmented-btn { + border: none; + background: transparent; + padding: 8px 16px; + border-radius: var(--radius-pill); + font-family: var(--font-body); + font-size: 13px; + font-weight: 600; + color: var(--fg2); + cursor: pointer; + transition: background var(--motion-fast) var(--ease), color var(--motion-fast) var(--ease); +} +.segmented-btn.is-active { background: var(--accent-soft); color: var(--accent-text); } + +.customize-list { + grid-column: 1 / -1; + background: var(--bg); + border-radius: var(--radius-card); + padding: 28px 32px; +} +.customize-list h3 { font-size: 18px; margin: 0 0 16px; } +.feature-list { + margin: 0; + padding: 0; + list-style: none; + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 10px 32px; + color: var(--fg2); + font-size: 15px; +} +.feature-list li { display: flex; align-items: center; gap: 10px; } +.feature-list li::before { + content: ""; + width: 6px; height: 6px; + border-radius: 50%; + background: var(--accent); + flex: none; +} + +@media (max-width: 760px) { + .customize-grid { grid-template-columns: 1fr; } + .feature-list { grid-template-columns: 1fr; } +} + +/* ---------- privacy ---------- */ + +.privacy-grid { + display: grid; + grid-template-columns: minmax(0, 1.1fr) minmax(0, 0.9fr); + gap: clamp(32px, 6vw, 64px); + align-items: center; +} + +.privacy-points { + list-style: none; + margin: 0; + padding: 0; + display: grid; + gap: 14px; +} +.privacy-points li { + display: flex; + align-items: center; + gap: 14px; + background: var(--surface); + border-radius: var(--radius-input); + padding: 16px 18px; + font-weight: 500; +} + +@media (max-width: 820px) { + .privacy-grid { grid-template-columns: 1fr; } +} + +/* ---------- cta ---------- */ + +.section-cta { text-align: center; background: var(--surface); } +.cta-inner { display: flex; flex-direction: column; align-items: center; } +.cta-inner .section-sub { margin-bottom: 32px; } +.cta-actions { justify-content: center; } +.cta-fine { margin-top: 20px; font-size: 14px; color: var(--fg3); } +.cta-fine a { color: var(--accent-text); font-weight: 600; text-decoration: none; } + +/* ---------- footer ---------- */ + +.site-footer { border-top: 1px solid var(--line); padding: 32px 0; } +.footer-row { + display: flex; + align-items: center; + gap: 24px; + flex-wrap: wrap; +} +.footer-note { color: var(--fg3); font-size: 13px; margin: 0; flex: 1; min-width: 200px; } +.footer-links { display: flex; gap: 20px; } +.footer-links a { font-size: 14px; text-decoration: none; color: var(--fg2); } +.footer-links a:hover { color: var(--fg1); } + +/* ---------- motion ---------- */ + +@media (prefers-reduced-motion: reduce) { + html { scroll-behavior: auto; } + .phone, .chip, .btn { transition: none !important; } +} + +.reveal { + opacity: 0; + transform: translateY(16px); + transition: opacity 600ms var(--ease), transform 600ms var(--ease); +} +.reveal.is-visible { opacity: 1; transform: none; }