diff --git a/assets/css/custom.css b/assets/css/custom.css index 4d271921a..5178716de 100644 --- a/assets/css/custom.css +++ b/assets/css/custom.css @@ -30,12 +30,26 @@ @apply bg-primary border-primary text-white; } +.btn-secondary { + @apply bg-secondary border-secondary text-white; +} + +.btn-tertiary2 { + @apply bg-tertiary2 border-tertiary2 text-white; +} + .btn-primary:hover, -.btn-primary:focus { +.btn-primary:focus, +.btn-secondary:hover, +.btn-secondary:focus, +.btn-tertiary2:hover, +.btn-tertiary2:focus { @apply text-white transition transform duration-200 ease-in-out shadow-md -translate-y-px; } -.btn-primary:disabled { +.btn-primary:disabled, +.btn-secondary:disabled, +.btn-tertiary2:disabled { @apply shadow-none translate-y-0; } @@ -86,15 +100,15 @@ } .font-h1 { - @apply font-headline font-medium text-3xl md:text-4xl leading-tight; + @apply font-headline font-medium text-3xl md:text-4xl leading-tight text-balance; } .font-h2 { - @apply font-headline font-medium text-xl md:text-2xl leading-snug; + @apply font-headline font-medium text-xl md:text-2xl leading-snug text-balance; } .font-h3 { - @apply font-headline text-lg md:text-xl leading-relaxed; + @apply font-headline text-lg md:text-xl leading-relaxed text-balance; } .font-p { @@ -183,7 +197,8 @@ textarea:read-only { @apply relative inline-block; } -.has-tooltip:hover .tooltip { +.has-tooltip:hover .tooltip, +.has-tooltip:focus-within .tooltip { @apply block; } @@ -196,8 +211,8 @@ textarea:read-only { left: 50%; } -/* offsetting anchor targets to adjust for fixed navbar */ -[id]::before { +/* offsetting anchor targets to adjust for fixed navbar; tooltips carry an id only for aria-describedby, not as scroll anchors */ +[id]:not([role="tooltip"])::before { content: ''; display: block; height: 96px; diff --git a/assets/css/hero.css b/assets/css/hero.css new file mode 100644 index 000000000..04df5c2d6 --- /dev/null +++ b/assets/css/hero.css @@ -0,0 +1,522 @@ +/* Homepage hero-section styles — pulled out of custom.css and loaded only by + layouts/index.html as a separate Tailwind sheet, so other pages do not ship + the hero scene rules and animations. + + @reference imports Tailwind and the shared theme/utilities for @apply + resolution only — nothing from them is re-emitted here. Hugo pipes this file + to the Tailwind CLI with the project root as cwd and does not rewrite + @reference like it does @import, so the theme path is root-relative. The + @layer components wrapper keeps these rules in the same cascade layer they + had inside main.css, so utility classes in the markup still win over + component rules. */ +@reference "tailwindcss"; +@reference "./assets/css/theme.css"; + +/* Re-declare main.css's cascade layer order: layer order is fixed by first + occurrence per document, so without this a bare `@layer components` block + would corrupt the order if this sheet were ever loaded before main.css. */ +@layer properties, theme, base, components, utilities; + +@layer components { + .hero-split-contrast { + @apply pb-6 md:pb-8 lg:pb-10; + } + + .hero-split-header { + @apply text-center mb-12 md:mb-16 lg:mb-12 px-4; + } + + /* Backdrop — a soft green/teal glow per edition, anchoring the two panels + without the visual noise of literal clouds. */ + /* Fade the aura glows out toward the hero's bottom so the tint dissolves into + the section below instead of being hard-clipped by overflow-hidden. */ + .hero-aura-wrap { + -webkit-mask-image: linear-gradient(to bottom, #000 55%, transparent 90%); + mask-image: linear-gradient(to bottom, #000 55%, transparent 90%); + } + + .hero-aura { + position: absolute; + top: 30%; + width: 55%; + aspect-ratio: 1 / 1; + border-radius: 9999px; + filter: blur(90px); + opacity: 0.7; + } + + .hero-aura-primary { + left: -12%; + background: radial-gradient(circle, color-mix(in srgb, var(--color-primary) 14%, transparent) 0%, transparent 70%); + } + + .hero-aura-secondary { + right: -12%; + background: radial-gradient(circle, color-mix(in srgb, var(--color-tertiary2) 14%, transparent) 0%, transparent 70%); + } + + /* Uses the same `container` constraint as the content sections below, so the + cards' outer edges line up with the "Take the security…" section at every + width (the `gap-*` matches the other multi-column sections). */ + .hero-split-panels { + @apply lg:container px-6 grid grid-cols-1 md:grid-cols-2 items-stretch gap-6 lg:gap-12; + } + + .hero-panel { + @apply relative flex flex-col items-center text-center p-10 md:p-6 lg:p-12 pt-6 lg:pt-6 rounded-sm shadow-sm; + background: + linear-gradient(to bottom, transparent 60%, color-mix(in srgb, var(--accent, transparent) 4%, transparent)), + #fff; + } + + .hero-panel-primary { --accent: var(--color-primary); } + .hero-panel-secondary { --accent: var(--color-tertiary2); } + + .hero-panel-content { + @apply flex-1 flex flex-col items-center; + } + + .hero-panel-content h2 { + @apply font-headline font-bold text-xl uppercase tracking-wide mb-1; + } + + + .hero-panel-content .hero-panel-description { + @apply flex-1 text-balance; + } + + .hero-panel-content a.btn { + @apply mt-auto; + } + + /* ───────────────────────────────────────────────────────────── + Shared hero scene system — both Cryp and Hub illustrations use + a fixed 320×180 canvas with absolutely positioned anchors so + the two visuals share dimensions, spacing, and easing. + ───────────────────────────────────────────────────────────── */ + + .hero-scene { + @apply lg:scale-[1.1]; + @apply mt-6 mb-4; + position: relative; + width: 320px; + height: 180px; + } + + /* Below 400px the fixed 320px scene crowds (≤352px: clips at) the panel's + padding box (viewport − 32px container padding). Scale the scene down + visually and relax the side padding; layout height stays 180px. Uses the + `scale` property to match the lg:scale-[1.1] utility above (no conflict — + lg is 1280px). */ + @media (max-width: 399px) { + .hero-panel { + padding-left: 1.5rem; + padding-right: 1.5rem; + } + + .hero-scene { + scale: 0.85; + } + } + + .hero-scene-links { + position: absolute; + inset: 0; + pointer-events: none; + z-index: 0; + } + + /* Tier 1 — data-flow lines. Same stroke language across both scenes. */ + .hero-scene-links path { + fill: none; + stroke-width: 1.5; + stroke-dasharray: 4 4; + stroke-linecap: round; + opacity: 0.42; + } + + .anchor-backdrop { fill: white; } + + /* Color logic per segment (identical across both scenes): + - clear / decrypted data → primary (green) — the left segment + - encrypted vault / key → secondary (teal) — the right segment */ + .hero-cryp-link { stroke: var(--color-primary); } + + .hero-hub-link { stroke: var(--color-tertiary2); } + + /* Anchor: the icon/logo is centered on its declared point so the connecting + dashed lines (drawn to that point) pass through the disc's centre. The label + floats below the disc, out of flow, so it never shifts the disc off-centre. */ + .hero-scene-anchor { + position: absolute; + transform: translate(-50%, -50%); + z-index: 2; + } + + /* Per-scene knobs for the shared scene system: accent (anchor glyphs), disc + tint (opaque so the travelers — z-index 1 — actually vanish behind the + anchors — z-index 2 — instead of shining through a translucent tint), token + colour + glow (travelling icons), and the scene clock every choreography + animation runs on. */ + .hero-cryp-scene { + --scene-accent: var(--color-primary); + --scene-disc: var(--color-primary-l2); + --scene-token: var(--color-primary); + --scene-glow: color-mix(in srgb, var(--color-primary) 40%, transparent); + --scene-cycle: 5s; + } + + .hero-hub-scene { + --scene-accent: var(--color-tertiary2); + --scene-disc: #f1f6fb; + --scene-token: var(--color-gray-700); + --scene-glow: color-mix(in srgb, var(--color-gray-600) 40%, transparent); + --scene-cycle: 10s; + } + + /* Descendant selector (0,2,0) so the explicit box beats FontAwesome's own + `.fa-*` width rule (0,1,0) — otherwise the width collapses to the glyph and + the disc renders as a vertical capsule instead of a circle. */ + .hero-scene-anchor .hero-scene-icon { + display: flex; + flex: 0 0 auto; + box-sizing: border-box; + align-items: center; + justify-content: center; + width: 44px; + height: 44px; + border-radius: 9999px; + font-size: 22px; + line-height: 1; + color: var(--scene-accent, var(--color-gray-400)); + background: var(--scene-disc); + /* Idle breathing (keyframes below the scene sections) — deliberately on its + own 8s clock, off the scene cycle, so it never looks coupled to the + travel choreography. */ + animation: hero-scene-anchor-idle 8s cubic-bezier(0.4, 0, 0.2, 1) infinite; + } + + .hero-scene-label { + position: absolute; + top: calc(100% + 0.5rem); + left: 50%; + transform: translateX(-50%); + font-size: 12px; + font-weight: 500; + color: var(--color-gray-500); + letter-spacing: 0.02em; + line-height: 1; + white-space: nowrap; + } + + /* Logo: identical halo on both scenes; the wobble animation is assigned per + scene (name + clock) next to each scene's other rules. */ + .hero-scene-logo { + position: absolute; + top: 90px; + left: 160px; + width: 96px; + height: 96px; + border-radius: 50%; + background: radial-gradient(circle, color-mix(in srgb, var(--color-primary) 14%, transparent) 0%, transparent 70%); + transform: translate(-50%, -50%); + display: flex; + align-items: center; + justify-content: center; + z-index: 2; + } + + .hero-scene-logo-img { + width: 64px; + height: auto; + transform-origin: center bottom; + } + + /* Travelling tokens — one shared scaffold for both scenes. The container + carries the position animation and the 0.3s fade that softens mouse-leave; + the file/vault faces sit on top and hard-swap visibility behind the centre + bot. Colour, glow and clock come from the per-scene variables above; only + the animation names live with each scene. */ + .hero-cryp-traveler, + .hero-hub-traveler { + position: absolute; + top: 90px; + left: 40px; + width: 28px; + height: 28px; + margin-top: -14px; + margin-left: -14px; + pointer-events: none; + z-index: 1; + opacity: 0; + transition: opacity 0.3s ease; + } + + .hero-cryp-traveler-file, + .hero-cryp-traveler-vault, + .hero-hub-traveler-file, + .hero-hub-traveler-vault { + position: absolute; + inset: 0; + display: flex; + align-items: center; + justify-content: center; + color: var(--scene-token); + font-size: 22px; + line-height: 1; + opacity: 0; + filter: drop-shadow(0 0 5px var(--scene-glow)); + transition: opacity 0.3s ease; + } + + .hero-cryp-traveler-vault, + .hero-hub-traveler-vault { + font-size: 24px; + } + + /* ───── Cryptomator scene: horizontal flow You → Logo → Cloud ───── */ + + .hero-cryp-anchor-you { top: 90px; left: 40px; } + .hero-cryp-anchor-cloud { top: 90px; left: 280px; } + + .hero-cryp-scene .hero-scene-logo-img { animation: hero-cryp-logo-wobble var(--scene-cycle) cubic-bezier(0.4, 0, 0.2, 1) infinite; } + + /* Two faces of the travelling token: file on the User→Crypto leg, vault on the + Crypto→Cloud leg. They hard-swap at 50%, hidden behind the Cryptomator bot — + no fades. */ + .hero-cryp-traveler { animation: hero-cryp-travel var(--scene-cycle) ease-in-out infinite; } + .hero-cryp-traveler-file { animation: hero-cryp-file-arc var(--scene-cycle) ease-in-out infinite; } + .hero-cryp-traveler-vault { animation: hero-cryp-vault-arc var(--scene-cycle) ease-in-out infinite; } + + /* Container path — translate3d legs between the anchor centres: User (40,90) + → Cryptomator (160,90) → Cloud (280,90). No fades and no idle time: the + token pops in behind the User disc at 0%, holds only briefly behind the bot + for the file→vault swap, arrives behind the Cloud disc exactly at 100% and + pops away with the loop's snap back to 0%. */ + @keyframes hero-cryp-travel { + 0% { transform: translate3d(0, 0, 0); } /* User — file sets off immediately */ + 48%, 52% { transform: translate3d(120px, 0, 0); } /* Cryptomator — file→vault swap behind the bot */ + 100% { transform: translate3d(240px, 0, 0); } /* Cloud — vault arrives, invisible snap-back on loop */ + } + + /* file — visible from 0% on its outbound User→Cryptomator leg; hard swap to the + vault at 50%, mid-hold, while hidden behind the bot (no fades). */ + @keyframes hero-cryp-file-arc { + 0%, 50% { opacity: 1; } /* pops in at the User side, travels to the bot */ + 50.01%, 100% { opacity: 0; } /* swapped for the vault behind the bot */ + } + + /* vault — takes over from the file at 50% behind the bot and stays visible until + the loop ends at the Cloud, where it vanishes with the 100%→0% snap. */ + @keyframes hero-cryp-vault-arc { + 0%, 50% { opacity: 0; } /* hidden while the file is en route */ + 50.01%, 100% { opacity: 1; } /* carries on to the cloud until the loop snaps */ + } + + /* Cryptomator reacts while the file is absorbed behind it (the 48–52% hold) — + a slight ±1.5° rock (tilt around its base), signalling the encrypt. */ + @keyframes hero-cryp-logo-wobble { + 0%, 47% { transform: rotate(0deg); } + 48% { transform: rotate(-1.5deg); } + 49% { transform: rotate(1.5deg); } + 50% { transform: rotate(-1.5deg); } + 51% { transform: rotate(1.5deg); } + 53%, 100% { transform: rotate(0deg); } + } + + /* Hub bot wobble — the same quick ±1.5° rock as the Cryptomator card (0.5% + steps ≈ the cryp scene's 1% steps on its half-length 5s clock), twice per + sequence: when the file lands (25%) and when the key lands (75%). */ + @keyframes hero-hub-logo-wobble { + 0%, 25% { transform: rotate(0deg); } + 25.5% { transform: rotate(-1.5deg); } + 26% { transform: rotate(1.5deg); } + 26.5% { transform: rotate(-1.5deg); } + 27% { transform: rotate(1.5deg); } + 28%, 75% { transform: rotate(0deg); } + 75.5% { transform: rotate(-1.5deg); } + 76% { transform: rotate(1.5deg); } + 76.5% { transform: rotate(-1.5deg); } + 77% { transform: rotate(1.5deg); } + 78%, 100% { transform: rotate(0deg); } + } + + /* Hub apex wobble — same rock, once per sequence, when the fingerprint + reaches it (50%). */ + @keyframes hero-hub-apex-wobble { + 0%, 50% { transform: rotate(0deg); } + 50.5% { transform: rotate(-1.5deg); } + 51% { transform: rotate(1.5deg); } + 51.5% { transform: rotate(-1.5deg); } + 52% { transform: rotate(1.5deg); } + 53%, 100% { transform: rotate(0deg); } + } + + /* ───── Hub scene: a diamond/prism of the four nodes ───── + Hub (top), Team (left), Cloud (right) and Cryptomator (bottom) form a + diamond, keeping the same outer width as the Cryptomator card. Data flows + along the lower V (Team → Cryptomator → Cloud); the Hub oversees from the + apex, connected down the top edges and a center axis to all three, and + issues the vault key down that axis to the bot. */ + + .hero-hub-anchor-team { top: 90px; left: 40px; } + .hero-hub-anchor-cloud { top: 90px; left: 280px; } + .hero-hub-anchor-hub { top: 30px; left: 160px; } + + /* Hub is the subject of this card — its bot sits above the center at a slightly + reduced size (the tight viewBox makes it read large) so it stays balanced + within the scene, with a soft secondary glow + drop-shadow. */ + .hero-hub-anchor-img { + width: 44px; + height: 44px; + object-fit: contain; + filter: + drop-shadow(0 3px 9px rgba(47, 72, 88, 0.30)) + drop-shadow(0 0 1px rgba(47, 72, 88, 0.35)); + transform-origin: center bottom; + animation: hero-hub-apex-wobble var(--scene-cycle) cubic-bezier(0.4, 0, 0.2, 1) infinite; + } + + /* Cryptomator sits at the bottom vertex of the diamond at full size, the same + as the bot on the Cryptomator card (only the position differs). */ + .hero-hub-scene .hero-scene-logo { + top: 150px; + width: 78px; + height: 78px; + } + + .hero-hub-scene .hero-scene-logo-img { + width: 52px; + animation: hero-hub-logo-wobble var(--scene-cycle) cubic-bezier(0.4, 0, 0.2, 1) infinite; + } + + /* Idle breathing — the outer anchor icons scale gently, offset in time so the + scene feels alive between travel cycles without ever drawing focus (applied + in the shared .hero-scene-icon rule above). */ + @keyframes hero-scene-anchor-idle { + 0%, 100% { transform: scale(1); } + 50% { transform: scale(1.025); } + } + + .hero-hub-anchor-cloud .hero-scene-icon { animation-delay: -2s; } + .hero-cryp-anchor-cloud .hero-scene-icon { animation-delay: -4s; } + + /* Hub traveler — same shared scaffold as the Cryptomator card, mapped onto + the lower V of the diamond: Team (40,90) → bot (160,150) → Cloud (280,90). + Legs start and end exactly on the anchor centres, so the pops are hidden + behind the discs/logos. */ + .hero-hub-traveler { animation: hero-hub-travel var(--scene-cycle) ease-in-out infinite; } + .hero-hub-traveler-file { animation: hero-hub-file-arc var(--scene-cycle) ease-in-out infinite; } + .hero-hub-traveler-vault { animation: hero-hub-vault-arc var(--scene-cycle) ease-in-out infinite; } + + /* Sequential story — four back-to-back quarter legs, no pause: the container + carries the file Team→bot (0–25%), HOLDS behind the bot through the + fingerprint (25–50%) and key (50–75%) steps, then carries the vault + bot→Cloud (75–100%). The loop's 100%→0% snap back to Team happens while + both faces are invisible behind the Cloud disc / Team disc. */ + @keyframes hero-hub-travel { + 0% { transform: translate3d(0, 0, 0); } /* Team (40,90) — file sets off immediately */ + 25%, 75% { transform: translate3d(120px, 60px, 0); } /* bot (160,150) — hold through fingerprint + key */ + 100% { transform: translate3d(240px, 0, 0); } /* Cloud (280,90) — vault arrives, invisible snap-back */ + } + + /* File face — visible from 0% on its Team→bot leg; pops away at 25% while + hidden behind the bot (no fades). */ + @keyframes hero-hub-file-arc { + 0%, 25% { opacity: 1; } /* pops in behind the Team disc, travels to the bot */ + 25.01%, 100% { opacity: 0; } /* absorbed by the bot (encrypt) */ + } + + /* Vault face — pops in behind the bot at 75%, after the key has landed, and + stays visible until the loop ends behind the Cloud disc. */ + @keyframes hero-hub-vault-arc { + 0%, 75% { opacity: 0; } /* hidden through the file, fingerprint and key steps */ + 75.01%, 100% { opacity: 1; } /* carries on to the cloud until the loop snaps */ + } + + /* Key and auth accents — standalone tokens with the same look as the traveler + faces (they keep their own 0.3s opacity transition: unlike the faces they + have no fading parent container to soften mouse-leave). */ + .hero-hub-key, + .hero-hub-auth { + position: absolute; + display: flex; + align-items: center; + justify-content: center; + color: var(--scene-token); + font-size: 22px; + line-height: 1; + pointer-events: none; + z-index: 1; + opacity: 0; + filter: drop-shadow(0 0 5px var(--scene-glow)); + transition: opacity 0.3s ease; + } + + /* Hub key — the Hub issues the vault key down the centre axis to the + Cryptomator bot on the third quarter leg (50–75%), after the fingerprint has + reached the Hub and before the vault sets off. Pops in behind the Hub bot, + pops away behind the Cryptomator bot. */ + .hero-hub-key { + top: 30px; + left: 160px; + width: 28px; + height: 28px; + margin-top: -14px; + margin-left: -14px; + animation: hero-hub-key-drop var(--scene-cycle) ease-in-out infinite; + } + + @keyframes hero-hub-key-drop { + /* Step 3: only AFTER the fingerprint reaches the Hub does the key drop + Hub (160,30) -> bot (160,150), 50-75%. Constant size, single eased fall. */ + 0%, 50% { opacity: 0; transform: translate3d(0, 0, 0); } + 50.01% { opacity: 1; } + 75% { opacity: 1; transform: translate3d(0, 120px, 0); } + 75.01%, 100% { opacity: 0; transform: translate3d(0, 0, 0); } + } + + /* Auth request — per the Hub security docs the member authenticates against + Hub, which then issues the key; Hub itself NEVER talks to the cloud storage + (zero knowledge), so the Hub↔Cloud edge deliberately stays static. A small + fingerprint runs Team → Hub along the mgmt edge on the second quarter leg + (25–50%), between the file landing on the bot and the key drop. */ + .hero-hub-auth { + top: 90px; + left: 40px; + width: 24px; + height: 24px; + margin-top: -12px; + margin-left: -12px; + animation: hero-hub-auth-run var(--scene-cycle) ease-in-out infinite; + } + + @keyframes hero-hub-auth-run { + /* Step 2: only AFTER the file reaches the bot does the fingerprint run + Team (40,90) -> Hub (160,30), 25-50%. Pops in behind the Team disc, pops + away behind the Hub bot. Constant size, single eased run. */ + 0%, 25% { opacity: 0; transform: translate3d(0, 0, 0); } + 25.01% { opacity: 1; } + 50% { opacity: 1; transform: translate3d(120px, -60px, 0); } + 50.01%, 100% { opacity: 0; transform: translate3d(0, 0, 0); } + } + + /* Calm at rest, alive on hover. One structural gate switches every scene + animation off while its panel is not hovered — the resting scene is just + the static nodes, centre logo, and dashed lines (travelers are hidden via + their base opacity). On hover the gate stops matching and all animations + start from 0%; on mouse-leave it matches again and the traveler containers + fade out (0.3s), so the scene returns to rest with no frozen mid-frame. + Its (0,3,0) specificity beats every animation rule above. Reduced-motion + users are covered by main.css's global reduced-motion kill switch; the + traveler opacity is additionally gated below so they never see a parked + token. */ + .hero-panel:not(:hover) .hero-scene * { animation-name: none; } + + @media (prefers-reduced-motion: no-preference) { + /* The traveler containers fade in/out (0.3s) so mouse-leave never pops a + token away mid-leg. */ + .hero-panel:hover .hero-cryp-traveler, + .hero-panel:hover .hero-hub-traveler { opacity: 1; } + } +} diff --git a/assets/css/main.css b/assets/css/main.css index b7bf5fc6d..eb45a8f6c 100644 --- a/assets/css/main.css +++ b/assets/css/main.css @@ -12,195 +12,18 @@ @config './tailwind.config.js'; -@theme { - --color-*: initial; - --color-transparent: transparent; - --color-current: currentColor; - --color-black: #000; - --color-white: #fff; +@import './theme.css'; - --color-gray-100: #f7fafc; - --color-gray-200: #edf2f7; - --color-gray-300: #e2e8f0; - --color-gray-400: #cbd5e0; - --color-gray-500: #a0aec0; - --color-gray-600: #718096; - --color-gray-700: #4a5568; - --color-gray-800: #2d3748; - --color-gray-900: #1a202c; - - --color-red-100: #fff5f5; - --color-red-200: #fed7d7; - --color-red-300: #feb2b2; - --color-red-400: #fc8181; - --color-red-500: #f56565; - --color-red-600: #e53e3e; - --color-red-700: #c53030; - --color-red-800: #9b2c2c; - --color-red-900: #742a2a; - - --color-orange-100: #fffaf0; - --color-orange-200: #feebc8; - --color-orange-300: #fbd38d; - --color-orange-400: #f6ad55; - --color-orange-500: #ed8936; - --color-orange-600: #dd6b20; - --color-orange-700: #c05621; - --color-orange-800: #9c4221; - --color-orange-900: #7b341e; - - --color-yellow-100: #fffff0; - --color-yellow-200: #fefcbf; - --color-yellow-300: #faf089; - --color-yellow-400: #f6e05e; - --color-yellow-500: #ecc94b; - --color-yellow-600: #d69e2e; - --color-yellow-700: #b7791f; - --color-yellow-800: #975a16; - --color-yellow-900: #744210; - - --color-green-100: #f0fff4; - --color-green-200: #c6f6d5; - --color-green-300: #9ae6b4; - --color-green-400: #68d391; - --color-green-500: #48bb78; - --color-green-600: #38a169; - --color-green-700: #2f855a; - --color-green-800: #276749; - --color-green-900: #22543d; - - --color-teal-100: #e6fffa; - --color-teal-200: #b2f5ea; - --color-teal-300: #81e6d9; - --color-teal-400: #4fd1c5; - --color-teal-500: #38b2ac; - --color-teal-600: #319795; - --color-teal-700: #2c7a7b; - --color-teal-800: #285e61; - --color-teal-900: #234e52; - - --color-blue-100: #ebf8ff; - --color-blue-200: #bee3f8; - --color-blue-300: #90cdf4; - --color-blue-400: #63b3ed; - --color-blue-500: #4299e1; - --color-blue-600: #3182ce; - --color-blue-700: #2b6cb0; - --color-blue-800: #2c5282; - --color-blue-900: #2a4365; - - --color-indigo-100: #ebf4ff; - --color-indigo-200: #c3dafe; - --color-indigo-300: #a3bffa; - --color-indigo-400: #7f9cf5; - --color-indigo-500: #667eea; - --color-indigo-600: #5a67d8; - --color-indigo-700: #4c51bf; - --color-indigo-800: #434190; - --color-indigo-900: #3c366b; - - --color-purple-100: #faf5ff; - --color-purple-200: #e9d8fd; - --color-purple-300: #d6bcfa; - --color-purple-400: #b794f4; - --color-purple-500: #9f7aea; - --color-purple-600: #805ad5; - --color-purple-700: #6b46c1; - --color-purple-800: #553c9a; - --color-purple-900: #44337a; - - --color-pink-100: #fff5f7; - --color-pink-200: #fed7e2; - --color-pink-300: #fbb6ce; - --color-pink-400: #f687b3; - --color-pink-500: #ed64a6; - --color-pink-600: #d53f8c; - --color-pink-700: #b83280; - --color-pink-800: #97266d; - --color-pink-900: #702459; - - --color-primary: #49b04a; - --color-primary-l2: #ebf5eb; - --color-secondary: #008a7b; - --color-tertiary: #005e71; - --color-dark: #1e2b33; - - --font-*: initial; - --font-headline: Quicksand, sans-serif; - --font-body: Open Sans, sans-serif; - --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; - - --text-*: initial; - --text-xs: 0.75rem; - --text-sm: 0.875rem; - --text-base: 1rem; - --text-lg: 1.125rem; - --text-xl: 1.25rem; - --text-2xl: 1.5rem; - --text-3xl: 1.875rem; - --text-4xl: 2.25rem; - --text-5xl: 3rem; - --text-6xl: 4rem; - --text-7xl: 4.5rem; - - --breakpoint-*: initial; - --breakpoint-md: 768px; - --breakpoint-lg: 1280px; - - --animate-hover: hover 10s ease-in-out infinite; - --animate-slide: slide 20s cubic-bezier(0.83, 0, 0.17, 1) infinite; - - --height-70px: 70px; - --height-412px: 412px; - - --width-fit: fit-content; - - @keyframes hover { - 0%, 100% { - transform: rotate(0deg) translateY(0); - } - 50% { - transform: rotate(-3deg) translateY(-10px); - } - } - - /* see https://cruip.com/creating-a-sliding-text-animation-with-tailwind-css/ */ - @keyframes slide { - 0%, 10% { - transform: translateY(0%); - } - 12.5%, 22.5% { - transform: translateY(-11.11%); - } - 25%, 35% { - transform: translateY(-22.22%); - } - 37.5%, 47.5% { - transform: translateY(-33.33%); - } - 50%, 60% { - transform: translateY(-44.44%); - } - 62.5%, 72.5% { - transform: translateY(-55.55%); - } - 75%, 85% { - transform: translateY(-66.66%); - } - 87.5%, 97.5% { - transform: translateY(-77.77%); - } - 100% { - transform: translateY(-88.88%); - } +@media (prefers-reduced-motion: reduce) { + *, + *::before, + *::after { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; } } -@utility container { - margin-inline: auto; - padding-inline: 1rem; -} - /* see https://alpinejs.dev/directives/cloak */ [x-cloak] { display: none; diff --git a/assets/css/theme.css b/assets/css/theme.css new file mode 100644 index 000000000..405d23a2e --- /dev/null +++ b/assets/css/theme.css @@ -0,0 +1,162 @@ +/* Shared design tokens and custom utilities, split out of main.css so that + page-specific stylesheets (e.g. hero.css) can pull them in via @reference + without dragging in all of main.css. */ + +@theme { + --color-*: initial; + --color-transparent: transparent; + --color-current: currentColor; + --color-black: #000; + --color-white: #fff; + + --color-gray-100: #f7fafc; + --color-gray-200: #edf2f7; + --color-gray-300: #e2e8f0; + --color-gray-400: #cbd5e0; + --color-gray-500: #a0aec0; + --color-gray-600: #718096; + --color-gray-700: #4a5568; + --color-gray-800: #2d3748; + --color-gray-900: #1a202c; + + --color-red-100: #fff5f5; + --color-red-200: #fed7d7; + --color-red-300: #feb2b2; + --color-red-400: #fc8181; + --color-red-500: #f56565; + --color-red-600: #e53e3e; + --color-red-700: #c53030; + --color-red-800: #9b2c2c; + --color-red-900: #742a2a; + + --color-orange-100: #fffaf0; + --color-orange-200: #feebc8; + --color-orange-300: #fbd38d; + --color-orange-400: #f6ad55; + --color-orange-500: #ed8936; + --color-orange-600: #dd6b20; + --color-orange-700: #c05621; + --color-orange-800: #9c4221; + --color-orange-900: #7b341e; + + --color-yellow-100: #fffff0; + --color-yellow-200: #fefcbf; + --color-yellow-300: #faf089; + --color-yellow-400: #f6e05e; + --color-yellow-500: #ecc94b; + --color-yellow-600: #d69e2e; + --color-yellow-700: #b7791f; + --color-yellow-800: #975a16; + --color-yellow-900: #744210; + + --color-green-100: #f0fff4; + --color-green-200: #c6f6d5; + --color-green-300: #9ae6b4; + --color-green-400: #68d391; + --color-green-500: #48bb78; + --color-green-600: #38a169; + --color-green-700: #2f855a; + --color-green-800: #276749; + --color-green-900: #22543d; + + --color-teal-100: #e6fffa; + --color-teal-200: #b2f5ea; + --color-teal-300: #81e6d9; + --color-teal-400: #4fd1c5; + --color-teal-500: #38b2ac; + --color-teal-600: #319795; + --color-teal-700: #2c7a7b; + --color-teal-800: #285e61; + --color-teal-900: #234e52; + + --color-blue-100: #ebf8ff; + --color-blue-200: #bee3f8; + --color-blue-300: #90cdf4; + --color-blue-400: #63b3ed; + --color-blue-500: #4299e1; + --color-blue-600: #3182ce; + --color-blue-700: #2b6cb0; + --color-blue-800: #2c5282; + --color-blue-900: #2a4365; + + --color-indigo-100: #ebf4ff; + --color-indigo-200: #c3dafe; + --color-indigo-300: #a3bffa; + --color-indigo-400: #7f9cf5; + --color-indigo-500: #667eea; + --color-indigo-600: #5a67d8; + --color-indigo-700: #4c51bf; + --color-indigo-800: #434190; + --color-indigo-900: #3c366b; + + --color-purple-100: #faf5ff; + --color-purple-200: #e9d8fd; + --color-purple-300: #d6bcfa; + --color-purple-400: #b794f4; + --color-purple-500: #9f7aea; + --color-purple-600: #805ad5; + --color-purple-700: #6b46c1; + --color-purple-800: #553c9a; + --color-purple-900: #44337a; + + --color-pink-100: #fff5f7; + --color-pink-200: #fed7e2; + --color-pink-300: #fbb6ce; + --color-pink-400: #f687b3; + --color-pink-500: #ed64a6; + --color-pink-600: #d53f8c; + --color-pink-700: #b83280; + --color-pink-800: #97266d; + --color-pink-900: #702459; + + --color-primary: #49b04a; + --color-primary-l2: #ebf5eb; + --color-secondary: #008a7b; + --color-tertiary: #005e71; + --color-tertiary2: #354960; + --color-dark: #1e2b33; + + --font-*: initial; + --font-headline: Quicksand, sans-serif; + --font-body: Open Sans, sans-serif; + --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; + + --text-*: initial; + --text-xs: 0.75rem; + --text-sm: 0.875rem; + --text-base: 1rem; + --text-lg: 1.125rem; + --text-xl: 1.25rem; + --text-2xl: 1.5rem; + --text-3xl: 1.875rem; + --text-4xl: 2.25rem; + --text-5xl: 3rem; + --text-6xl: 4rem; + --text-7xl: 4.5rem; + + --breakpoint-*: initial; + --breakpoint-md: 768px; + --breakpoint-lg: 1280px; + + --animate-hover: hover 10s ease-in-out infinite; + + --height-70px: 70px; + --height-412px: 412px; + + --width-fit: fit-content; + + @keyframes hover { + 0%, 100% { + transform: rotate(0deg) translateY(0); + } + 50% { + transform: rotate(-3deg) translateY(-10px); + } + } + +} + +@utility container { + margin-inline: auto; + padding-inline: 1rem; +} diff --git a/i18n/de.yaml b/i18n/de.yaml index 5a1c9ca17..dd11ef58d 100644 --- a/i18n/de.yaml +++ b/i18n/de.yaml @@ -306,14 +306,12 @@ translation: "Community beitreten" # Home -- id: home_header_kicker - translation: "4.000.000+ Downloads" - id: home_header_title - translation: "Privatsphäre schützen in" -- id: home_header_title_last_item - translation: "jeder Cloud" -- id: home_header_description - translation: "Mit Cryptomator liegt der Schlüssel zu deinen Daten bei dir. Durch Cryptomator sicherst und verschlüsselst du deine sensiblen Daten in deiner Lieblingscloud. So bleiben sie privat und vor unbefugtem Zugriff geschützt." + translation: "Schütze deine Privatsphäre in jeder Cloud" +- id: home_clouds_sharepoint_tooltip + translation: "Inklusive Microsoft Teams" +- id: home_header_supported_clouds + translation: "Kompatible Clouds ansehen" - id: home_header_woman_cloud_alt translation: "Entspannte Frau auf einer Wolke, die durch ein Schloss gesichert ist" - id: home_header_for_individuals_title @@ -324,6 +322,30 @@ translation: "Kostenlose 30-Tage-Testversion" - id: home_header_for_teams_cta translation: "Für Teams" + +- id: home_hero_card_just_me + translation: "Für Einzelnutzer" +- id: home_hero_card_our_team + translation: "Für Teams" +- id: home_hero_hub_node_team + translation: "Team" +- id: home_hero_cryptomator_description + translation: "Schütze persönliche Dokumente, Fotos und Arbeitsdateien in jeder Cloud." +- id: home_hero_cryptomator_cta + translation: "Kostenlos laden" +- id: home_hero_cryptomator_link + translation: "Alle Funktionen entdecken" +- id: home_hero_hub_description + translation: "Arbeite sicher im Team – mit zentraler Zugriffsverwaltung und Zero-Knowledge-Verschlüsselung." +- id: home_hero_hub_cta + translation: "Kostenlos testen" +- id: home_hero_hub_link + translation: "So funktioniert Hub" +- id: home_hero_cryp_label_you + translation: "Du" +- id: home_hero_cryp_label_cloud + translation: "Cloud" + - id: home_get_started_title translation: "Wie wirst du Cryptomator nutzen?" - id: home_get_started_for_individuals_title diff --git a/i18n/en.yaml b/i18n/en.yaml index b8955a1d3..9f12ce751 100644 --- a/i18n/en.yaml +++ b/i18n/en.yaml @@ -306,14 +306,12 @@ translation: "Join Community" # Home -- id: home_header_kicker - translation: "4,000,000+ Downloads" - id: home_header_title - translation: "Protect your privacy in" -- id: home_header_title_last_item - translation: "any cloud" -- id: home_header_description - translation: "With Cryptomator, the key to your data is in your hands. Cryptomator secures and encrypts your sensitive data in your favorite cloud service. So you can rest assured that only you can access your data." + translation: "Protect your privacy in any cloud" +- id: home_clouds_sharepoint_tooltip + translation: "Includes Microsoft Teams" +- id: home_header_supported_clouds + translation: "See compatible clouds" - id: home_header_woman_cloud_alt translation: "Relaxed woman on a cloud secured by a lock" - id: home_header_for_individuals_title @@ -324,6 +322,30 @@ translation: "Free 30-Day Trial" - id: home_header_for_teams_cta translation: "For Teams" + +- id: home_hero_card_just_me + translation: "For Individuals" +- id: home_hero_card_our_team + translation: "For Teams" +- id: home_hero_hub_node_team + translation: "Team" +- id: home_hero_cryptomator_description + translation: "Protect personal documents, photos, and work files in any cloud." +- id: home_hero_cryptomator_cta + translation: "Download Free" +- id: home_hero_cryptomator_link + translation: "Explore all features" +- id: home_hero_hub_description + translation: "Collaborate securely using centralized access management and zero-knowledge encryption." +- id: home_hero_hub_cta + translation: "Start Free Trial" +- id: home_hero_hub_link + translation: "See how Hub works" +- id: home_hero_cryp_label_you + translation: "You" +- id: home_hero_cryp_label_cloud + translation: "Cloud" + - id: home_get_started_title translation: "How will you use Cryptomator?" - id: home_get_started_for_individuals_title diff --git a/layouts/_default/baseof.html b/layouts/_default/baseof.html index 46db7718c..26280af34 100644 --- a/layouts/_default/baseof.html +++ b/layouts/_default/baseof.html @@ -57,6 +57,9 @@ {{ $css := resources.Get "css/main.css" | css.TailwindCSS $tailwindOpts | fingerprint }} {{ end }} + {{ block "styles" . }} + + {{ end }} {{ partial "hreflang.html" . }} {{ if eq hugo.Environment "staging" }} @@ -92,13 +95,14 @@ {{- if $showAnniversaryBanner -}} {{ partial "anniversary-banner.html" . }} {{- end -}} - {{- $topPadding := cond .IsHome "" "pt-12" -}} + {{- $flushHero := or .IsHome .Params.flushHero -}} + {{- $topPadding := cond $flushHero "" "pt-12" -}} {{- if and $showWebinarBanner (not $showAnniversaryBanner) -}} - {{- $topPadding = cond .IsHome "" "pt-24 md:pt-18" -}} + {{- $topPadding = cond $flushHero "" "pt-24 md:pt-18" -}} {{- else if and (not $showWebinarBanner) $showAnniversaryBanner -}} - {{- $topPadding = cond .IsHome "" "pt-28 md:pt-24" -}} + {{- $topPadding = cond $flushHero "" "pt-28 md:pt-24" -}} {{- else if and $showWebinarBanner $showAnniversaryBanner -}} - {{- $topPadding = cond .IsHome "" "pt-40 md:pt-32" -}} + {{- $topPadding = cond $flushHero "" "pt-40 md:pt-32" -}} {{- end -}}
{{ i18n "home_header_description" . | safeHTML }}
-{{ i18n "home_header_for_individuals_title" . }}
- {{ i18n "home_header_for_individuals_cta" . }} +{{ i18n "home_hero_cryptomator_description" }}
+ + {{ i18n "home_hero_cryptomator_cta" }} + + + {{ i18n "home_hero_cryptomator_link" }} – Cryptomator + + +{{ i18n "home_header_for_teams_title" . }}
- {{ i18n "home_header_for_teams_cta" . }} + + + +{{ i18n "home_hero_hub_description" }}
+ + {{ i18n "home_hero_hub_cta" }} + + + {{ i18n "home_hero_hub_link" }} + + +
+ {{- /* baseof emits no padding for flush heroes, so the hero self-clears the fixed nav + banners; desktop padding already covers all banner combos, so only the mobile value scales below */ -}}
+ {{- $showWebinarBanner := .Scratch.Get "webinarBannerActive" -}}
+ {{- $showAnniversaryBanner := .Scratch.Get "anniversaryBannerActive" -}}
+ {{- $heroTopPad := "pt-16" -}}
+ {{- if and $showWebinarBanner $showAnniversaryBanner -}}
+ {{- $heroTopPad = "pt-40" -}}
+ {{- else if or $showWebinarBanner $showAnniversaryBanner -}}
+ {{- $heroTopPad = "pt-28" -}}
+ {{- end -}}
+