4 Commits
Author SHA1 Message Date
ayushyaandClaude Code 8aa7015c3f Set app version to 0.0.1+2
Build App Bundle / build (push) Successful in 5m38s
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-01 10:18:21 -04:00
ayushya 88abca647d Move Website source 2026-10-01 10:13:33 -04:00
ayushya 65b772d6e7 Merge pull request 'Set app version to 0.0.1' (#2) from noo-design-system-rebuild into main
Reviewed-on: #2
2026-10-01 09:37:12 -04:00
ayushyaandClaude Code 8179bf9683 Set app version to 0.0.1
Build App Bundle / build (push) Successful in 5m21s
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-01 09:35:45 -04:00
6 changed files with 1 additions and 1249 deletions
+1 -1
View File
@@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts # In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix. # of the product and file versions while build-number is used as the build suffix.
version: 1.0.0+1 version: 0.0.1+2
environment: environment:
sdk: ^3.12.2 sdk: ^3.12.2
-38
View File
@@ -1,38 +0,0 @@
# 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.
-4
View File
@@ -1,4 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
<rect width="32" height="32" rx="9" fill="#8D0DE3"/>
<rect x="9" y="9" width="14" height="14" rx="5" fill="#FFFFFF" fill-opacity="0.92"/>
</svg>

Before

Width:  |  Height:  |  Size: 210 B

-307
View File
@@ -1,307 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Noo — a Nextcloud client that feels like it belongs on your phone</title>
<meta name="description" content="Noo is a fast, private, deeply customizable Nextcloud client for Android and iOS. Browse files and photos, sync for offline, and make it look like yours.">
<meta property="og:title" content="Noo — a Nextcloud client that feels like it belongs on your phone">
<meta property="og:description" content="Fast, private, and deeply customizable. Browse files and photos, sync for offline, and make Noo look like yours.">
<meta property="og:type" content="website">
<meta property="og:url" content="https://noo.ayushya.dev">
<meta name="theme-color" content="#F3EFE6">
<link rel="icon" href="favicon.svg" type="image/svg+xml">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Instrument+Sans:wght@400;500;600&family=Schibsted+Grotesk:wght@500;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="styles.css">
</head>
<body>
<a class="skip-link" href="#main">Skip to content</a>
<header class="site-header">
<div class="wrap header-row">
<a class="wordmark" href="#top">
<span class="wordmark-mark" aria-hidden="true"></span>
noo
</a>
<nav class="site-nav" aria-label="Primary">
<a href="#features">Features</a>
<a href="#customize">Customize</a>
<a href="#privacy">Privacy</a>
</nav>
<a class="btn btn-primary btn-sm" href="#get">Get Noo</a>
<button class="nav-toggle" id="navToggle" aria-label="Open menu" aria-expanded="false">
<span></span><span></span><span></span>
</button>
</div>
<nav class="mobile-nav" id="mobileNav" aria-label="Primary">
<a href="#features">Features</a>
<a href="#customize">Customize</a>
<a href="#privacy">Privacy</a>
<a class="btn btn-primary btn-sm" href="#get">Get Noo</a>
</nav>
</header>
<main id="main">
<!-- ============ HERO ============ -->
<section class="hero" id="top">
<div class="wrap hero-grid">
<div class="hero-copy">
<p class="eyebrow">For Nextcloud, unofficial</p>
<h1>Your cloud.<br>Looking like <em>yours</em>.</h1>
<p class="hero-sub">
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.
</p>
<div class="hero-actions">
<a class="btn btn-primary btn-lg" href="#get">Get Noo</a>
<a class="btn btn-outline btn-lg" href="https://github.com" target="_blank" rel="noopener">View source</a>
</div>
<ul class="hero-proof">
<li>No password stored — Login Flow v2</li>
<li>Offline device sync</li>
<li>Open source</li>
</ul>
</div>
<div class="hero-visual" id="heroVisual" aria-hidden="true">
<div class="glow"></div>
<div class="phone" data-depth="0.15">
<div class="phone-notch"></div>
<div class="phone-screen">
<div class="mini-topbar">
<span class="mini-title">Files</span>
<span class="mini-dot"></span>
</div>
<div class="mini-list">
<div class="mini-row"><span class="mini-ic ic-folder"></span><span class="mini-lines"><b></b><i></i></span></div>
<div class="mini-row"><span class="mini-ic ic-image"></span><span class="mini-lines"><b></b><i></i></span></div>
<div class="mini-row mini-row-active"><span class="mini-ic ic-file"></span><span class="mini-lines"><b></b><i></i></span></div>
<div class="mini-row"><span class="mini-ic ic-folder"></span><span class="mini-lines"><b></b><i></i></span></div>
</div>
<div class="mini-bar">
<span class="mini-bar-pill"></span>
<span class="mini-bar-dot"></span>
<span class="mini-bar-dot"></span>
<span class="mini-bar-dot"></span>
<span class="mini-bar-dot"></span>
</div>
</div>
</div>
<div class="chip chip-photo" data-depth="0.55">
<div class="chip-photo-grid">
<span></span><span></span><span></span><span></span>
</div>
</div>
<div class="chip chip-row" data-depth="0.4">
<span class="mini-ic ic-share"></span>
<span class="mini-lines"><b></b><i></i></span>
</div>
<div class="chip chip-swatches" data-depth="0.7">
<span class="swatch" style="--c:#8D0DE3"></span>
<span class="swatch" style="--c:#2940A8"></span>
<span class="swatch" style="--c:#3E7A23"></span>
<span class="swatch" style="--c:#8C5A0E"></span>
</div>
<div class="chip chip-toggle" data-depth="0.5">
<span class="mini-lines mini-lines-toggle"><b></b><i></i></span>
<span class="mini-toggle mini-toggle-on"><i></i></span>
</div>
<div class="chip chip-badge" data-depth="0.85">
<span class="mini-ic ic-lock"></span>
Offline ready
</div>
</div>
</div>
</section>
<!-- ============ FEATURES ============ -->
<section class="section" id="features">
<div class="wrap">
<p class="eyebrow">Features</p>
<h2>Everything your Nextcloud does. Nothing it doesn't.</h2>
<p class="section-sub">
Files, photos, sharing and sync — built as a real mobile app, not a
browser tab pinned to your home screen.
</p>
<div class="grid grid-features">
<article class="card">
<span class="card-ic ic-folder-lg"></span>
<h3>Files, browsed properly</h3>
<p>Grid or list, sorted your way, with the same filters and
controls whether you're picking a file, uploading, or moving
one into place.</p>
</article>
<article class="card">
<span class="card-ic ic-image-lg"></span>
<h3>Photos, grouped by month</h3>
<p>Every image and video across your account in one gallery —
filter to just photos, just videos, or just favorites.</p>
</article>
<article class="card">
<span class="card-ic ic-download-lg"></span>
<h3>Offline, for real</h3>
<p>Pick folders to mirror locally and keep browsing — reading,
opening, previewing — with no connection at all.</p>
</article>
<article class="card">
<span class="card-ic ic-share-lg"></span>
<h3>Sharing, without the detour</h3>
<p>Share with people or a public link, see who has access, and
copy that link — all from one sheet, not three menus.</p>
</article>
<article class="card">
<span class="card-ic ic-clock-lg"></span>
<h3>Activity &amp; recent</h3>
<p>What changed, and where — grouped into today, yesterday and
this week, so catching up takes seconds.</p>
</article>
<article class="card">
<span class="card-ic ic-users-lg"></span>
<h3>Multiple accounts</h3>
<p>Switch between personal and work Nextcloud instances with a
swipe — each with its own sync, cache and lock.</p>
</article>
</div>
</div>
</section>
<!-- ============ CUSTOMIZE ============ -->
<section class="section section-alt" id="customize">
<div class="wrap">
<p class="eyebrow">Customize</p>
<h2>Shape it around your thumb, not the other way round.</h2>
<p class="section-sub">
Every screen bends to a handful of real settings — try a few
right here.
</p>
<div class="customize-grid">
<div class="customize-demo" id="accentDemo">
<div class="demo-preview">
<div class="demo-topbar">
<span class="demo-dot" style="background:var(--demo-accent)"></span>
<span class="demo-line" style="width:38%"></span>
</div>
<div class="demo-pill" style="background:var(--surface3); background:color-mix(in srgb, var(--demo-accent) 16%, transparent); color:var(--demo-accent)">Active tab</div>
<div class="demo-row"><span class="demo-line" style="width:60%"></span></div>
<div class="demo-row"><span class="demo-line" style="width:44%"></span></div>
</div>
<div class="demo-controls">
<h3>Pick your accent</h3>
<p>Six built-in colors, or match your wallpaper with dynamic
color on Android.</p>
<div class="swatch-row" role="group" aria-label="Accent color">
<button class="swatch-btn is-active" style="--c:#8D0DE3" data-accent="#8D0DE3" aria-label="Violet"></button>
<button class="swatch-btn" style="--c:#2940A8" data-accent="#2940A8" aria-label="Blue"></button>
<button class="swatch-btn" style="--c:#3E7A23" data-accent="#3E7A23" aria-label="Green"></button>
<button class="swatch-btn" style="--c:#8C5A0E" data-accent="#8C5A0E" aria-label="Amber"></button>
<button class="swatch-btn" style="--c:#A8202A" data-accent="#A8202A" aria-label="Red"></button>
<button class="swatch-btn" style="--c:#0E0D0B" data-accent="#0E0D0B" aria-label="Wallpaper"></button>
</div>
</div>
</div>
<div class="customize-demo" id="barDemo">
<div class="demo-preview demo-preview-bar">
<div class="demo-screen-fill"></div>
<div class="demo-bar" id="demoBar">
<span class="demo-bar-item is-on"></span>
<span class="demo-bar-item"></span>
<span class="demo-bar-item"></span>
<span class="demo-bar-item"></span>
</div>
</div>
<div class="demo-controls">
<h3>Floating or attached</h3>
<p>The bottom bar can sit flush with the edge, or float free
with rounded corners — your call.</p>
<div class="segmented" role="group" aria-label="Bottom bar style">
<button class="segmented-btn" data-bar="attached">Attached</button>
<button class="segmented-btn is-active" data-bar="floating">Floating</button>
</div>
</div>
</div>
<div class="customize-list">
<h3>And the rest</h3>
<ul class="feature-list">
<li>Reorder tabs, hide the ones you never open</li>
<li>Move Search into the bottom bar</li>
<li>Light, dark, or match the system</li>
<li>Pure black backgrounds in dark mode</li>
<li>Choose what swiping a file left or right does</li>
<li>Reorder the bulk-action bar for multi-select</li>
</ul>
</div>
</div>
</div>
</section>
<!-- ============ PRIVACY ============ -->
<section class="section" id="privacy">
<div class="wrap privacy-grid">
<div>
<p class="eyebrow">Privacy</p>
<h2>It's your server. Noo just visits.</h2>
<p class="section-sub">
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.
</p>
</div>
<ul class="privacy-points">
<li><span class="pt-ic ic-lock"></span> No password stored on device</li>
<li><span class="pt-ic ic-server"></span> Talks only to your own server</li>
<li><span class="pt-ic ic-code"></span> Open source, auditable</li>
<li><span class="pt-ic ic-off"></span> No analytics, no trackers</li>
</ul>
</div>
</section>
<!-- ============ GET ============ -->
<section class="section section-cta" id="get">
<div class="wrap cta-inner">
<h2>Bring your Nextcloud with you.</h2>
<p class="section-sub">Free and open source. Point it at your server and go.</p>
<div class="hero-actions cta-actions">
<a class="btn btn-primary btn-lg" href="#">Download for Android</a>
<a class="btn btn-outline btn-lg" href="#">Download for iOS</a>
</div>
<p class="cta-fine">Or <a href="https://github.com" target="_blank" rel="noopener">build it from source</a> — it's just Flutter.</p>
</div>
</section>
</main>
<footer class="site-footer">
<div class="wrap footer-row">
<a class="wordmark wordmark-sm" href="#top">
<span class="wordmark-mark" aria-hidden="true"></span>
noo
</a>
<p class="footer-note">Noo is an independent, unofficial Nextcloud client. Nextcloud is a trademark of Nextcloud GmbH.</p>
<nav class="footer-links" aria-label="Footer">
<a href="https://github.com" target="_blank" rel="noopener">GitHub</a>
<a href="#privacy">Privacy</a>
</nav>
</div>
</footer>
<script src="script.js"></script>
</body>
</html>
-141
View File
@@ -1,141 +0,0 @@
(() => {
"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"));
}
})();
-758
View File
@@ -1,758 +0,0 @@
/* 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; }