updated privacy policy

This commit is contained in:
R-Bharathraj
2026-09-22 16:12:19 +05:30
parent e4dd4e0771
commit 97e70e1fd4
6 changed files with 785 additions and 6 deletions

148
src/app/cookies/page.tsx Normal file
View File

@@ -0,0 +1,148 @@
import type { Metadata } from 'next';
import LegalPage, {
LEGAL_CONTACT_EMAIL as CONTACT_EMAIL,
type LegalHighlight,
type LegalSection,
} from '@/components/LegalPage';
export const metadata: Metadata = {
title: 'Cookie Policy | Loyaly.AI',
description:
'What cookies and similar technologies loyaly.ai and the Loyaly apps use, what each category is for, and how to control them in your browser or device.',
};
const SECTIONS: LegalSection[] = [
{
id: 'what-are-cookies',
title: '1. What cookies are',
body: [
'A cookie is a small text file a website asks your browser to store. It lets the site remember something between page loads or between visits — that a form was submitted, which page you came from, or a preference you set.',
'This policy also covers technologies that do a similar job: local storage and session storage in your browser, and, in our mobile apps, device identifiers and on-device storage. We refer to all of them as "cookies" here for simplicity.',
'Cookies set by loyaly.ai are first-party cookies. Cookies set by another domain whose content appears on our pages — a video embed or a font, for example — are third-party cookies.',
],
},
{
id: 'what-we-use',
title: '2. What we use on loyaly.ai',
body: [
'We keep our use of cookies deliberately small. On the public website we use:',
[
'Strictly necessary cookies: set by our hosting and application layer to route your request correctly, keep a page session consistent, and protect forms such as the Get in Touch enquiry form against abuse. These cannot be switched off without breaking the site.',
'Preference storage: your browser’s local storage may hold small interface preferences so the site behaves consistently as you move between pages. This stays on your device and is not sent to us.',
],
'We do not currently run third-party advertising or cross-site tracking cookies on loyaly.ai, and we do not sell data collected through cookies.',
'If we add analytics or marketing cookies in future, we will list them here and, where consent is required, ask for it before they are set.',
],
},
{
id: 'categories',
title: '3. Cookie categories explained',
body: [
'Where used, cookies fall into these categories:',
[
'Strictly necessary: required to deliver the service you asked for — session handling, security, load balancing, and form protection. No consent is required for these.',
'Functional: remember choices you make, such as a language or a dismissed banner, to improve how the site behaves for you.',
'Analytics and performance: tell us which pages are read, how visitors reach us, and where something is broken. We use this in aggregate to improve the site, not to identify you personally.',
'Marketing: used to measure campaigns or show relevant messages. We set these only with your consent, and you can withdraw that consent at any time.',
],
],
},
{
id: 'third-party',
title: '4. Third-party content',
body: [
'Some pages load resources from third parties — for example web fonts, map tiles on our Contacts page, embedded video, or links to the App Store and Google Play. Those providers can receive your IP address and basic request information, and may set their own cookies under their own policies.',
'We keep third-party embeds to what a page actually needs. Your use of a merchant’s own website, or of a social platform we link to, is governed by that party’s cookie policy, not ours.',
],
},
{
id: 'apps',
title: '5. Cookies in the Loyaly apps',
body: [
'The Loyaly and Loyaly Merchant mobile apps do not use browser cookies in the usual sense. They use equivalent on-device storage and identifiers to:',
[
'keep you signed in between sessions, so you do not re-verify your mobile number every time;',
'store a device identifier used to prevent duplicate accounts and fraudulent reward claims;',
'cache content such as store listings and offer artwork so the app loads quickly and works on a weak connection;',
'record crash and diagnostic information that helps us fix faults.',
],
'Clearing the app’s storage or uninstalling the app removes this local data from your device. Your account and LYT balance stay on our servers unless you delete your account.',
],
},
{
id: 'control',
title: '6. How to control cookies',
body: [
'You are in control of cookies on your own device:',
[
'Browser settings: every major browser lets you see stored cookies, delete them, block third-party cookies, or block cookies entirely. Look under Privacy or Site settings in Chrome, Safari, Firefox, or Edge.',
'Per-site controls: you can clear data for loyaly.ai alone without affecting other sites.',
'Private browsing: opening the site in a private or incognito window discards cookies when you close the window.',
'Mobile: iOS and Android both offer settings to reset or limit the advertising identifier, and you can clear a specific app’s storage from your device settings.',
],
'Blocking strictly necessary cookies will stop parts of the site working — forms may fail to submit and pages may not load correctly. Blocking other categories has no effect on your ability to browse loyaly.ai.',
],
},
{
id: 'retention',
title: '7. How long cookies last',
body: [
[
'Session cookies are deleted as soon as you close your browser.',
'Persistent cookies stay until they expire or you delete them. We keep expiry periods proportionate to the purpose — typically no more than 12 months for anything beyond strictly necessary use.',
'Local storage has no expiry of its own and remains until you clear your browser data for the site.',
],
],
},
{
id: 'consent',
title: '8. Consent and your rights',
body: [
'Where cookies involve personal data, that processing is covered by our Privacy Policy and, in India, by the Digital Personal Data Protection Act, 2023. Strictly necessary cookies are used to deliver a service you have asked for; any analytics or marketing cookies we introduce will be set only with your consent.',
'You can withdraw consent at any time through your browser settings or by writing to us, and you can exercise your access, correction, and erasure rights as described in the Privacy Policy.',
],
},
{
id: 'changes',
title: '9. Changes to this policy',
body: [
'We will update this page whenever we change the cookies we use — including if we add analytics or marketing tools — and we will revise the date shown at the top. We suggest checking back if you want to confirm what is currently in use.',
],
},
{
id: 'contact',
title: '10. Contact us',
body: [
'For questions about this Cookie Policy, or to ask what a specific cookie does:',
[
`Email: ${CONTACT_EMAIL}`,
'Loyaly.AI, Hyderabad, Telangana, India',
'Acknowledgement within one business day.',
],
],
},
];
const HIGHLIGHTS: LegalHighlight[] = [
{ title: 'No ad tracking', text: 'loyaly.ai runs no third-party advertising or cross-site tracking cookies, and we sell nothing collected this way.', color: '#f4be28' },
{ title: 'Mostly the essentials', text: 'What we set keeps sessions consistent and protects forms. Anything beyond that would be listed here first.', color: '#a78bfa' },
{ title: 'Clear them any time', text: 'Your browser and phone settings let you delete or block cookies for this site without affecting your account.', color: '#34d399' },
];
export default function CookiePolicyPage() {
return (
<LegalPage
titleTop="Cookie"
titleAccent="Policy"
intro="What we store on your device, why it is there, and how to clear it. Short version: the essentials, and nothing sold to anyone."
highlights={HIGHLIGHTS}
sections={SECTIONS}
ctaHeading="Want the detail on a cookie?"
ctaText="Ask us what any specific cookie does and we'll tell you — acknowledgement within one business day."
relatedLinks={[
{ label: 'Privacy Policy', href: '/privacy' },
{ label: 'Terms of Use', href: '/terms' },
]}
/>
);
}

217
src/app/privacy/page.tsx Normal file
View File

@@ -0,0 +1,217 @@
import type { Metadata } from 'next';
import LegalPage, {
LEGAL_CONTACT_EMAIL as CONTACT_EMAIL,
type LegalHighlight,
type LegalSection,
} from '@/components/LegalPage';
export const metadata: Metadata = {
title: 'Privacy Policy | Loyaly.AI',
description:
'How Loyaly.AI collects, uses, shares, and protects your personal data across the Loyaly app, the Merchant App, and our in-store analytics products.',
};
const SECTIONS: LegalSection[] = [
{
id: 'overview',
title: '1. Who we are and what this policy covers',
body: [
'Loyaly.AI ("Loyaly", "we", "us", "our") operates a loyalty and rewards platform that connects shoppers with offline stores. This Privacy Policy explains what personal data we collect, why we collect it, who we share it with, and the choices and rights you have.',
'This policy applies to:',
[
'the Loyaly consumer mobile app for iOS and Android, where you earn and redeem LYTs;',
'the Loyaly Merchant App and Merchant Dashboard used by partner stores;',
'the loyaly.ai website, including enquiry and contact forms;',
'our in-store analytics products — Behavision AI, SpendSense AI and Dyscount AI — as deployed at partner stores and malls.',
],
'If you use Loyaly at a partner store, that merchant may also process your data under its own privacy policy. We are not responsible for a merchant’s independent use of your data, and we encourage you to read their policy as well.',
],
},
{
id: 'data-we-collect',
title: '2. Information we collect',
body: [
'Information you give us',
[
'Account details: your mobile number, name, email address, date of birth (optional), gender (optional), and profile photo if you add one.',
'Merchant details: for store owners, your business name, address, GSTIN or registration details, product catalogue, campaign settings, and payout information.',
'Communications: messages you send through our contact form, support requests, survey responses, and feedback.',
],
'Information we collect automatically when you use Loyaly',
[
'Reward activity: LYTs earned and spent, games played (Maze, Spin Wheel, Scratch Card, Match Master), coupons issued and redeemed, streaks, and challenge completions.',
'Device and technical data: device model, operating system version, app version, language, time zone, crash logs, IP address, and a device identifier used to prevent duplicate or fraudulent reward claims.',
'Usage data: screens viewed, features used, session length, and referral source.',
'Battery level: the Battery Challenge reads your device battery percentage at the moment you open the app, solely to decide which surprise coupon to show you. We do not build a profile from it.',
],
'Information collected with your permission',
[
'Location: approximate or precise location, used to show nearby participating stores and to confirm a store visit at redemption. You can decline or withdraw this permission in your device settings; store discovery will then rely on a location you enter manually.',
'Step and activity data: step counts read from your phone’s motion sensor or from Apple Health / Google Fit, used only to verify walking challenges such as the 5,000-step challenge and to award LYTs. We do not use this data for advertising and we do not share it with merchants at an individual level.',
'Camera and photos: images you capture at a Selfie Booth or upload to your profile. Selfie Booth images are processed to deliver the reward and any sharing option you choose.',
'Notifications: push notifications about rewards, coupons, and campaigns, which you can turn off at any time.',
],
'In-store sensors and footfall analytics',
[
'Behavision AI measures visitor counts, dwell time, and movement patterns inside partner premises. It is configured to produce aggregated and statistical counts, not to identify individual shoppers, and we do not use it to build facial recognition profiles.',
'Where a partner store deploys cameras or sensors, signage at the premises should inform you. If a merchant configures these tools in a way that identifies individuals, that merchant is responsible for obtaining your consent.',
],
],
},
{
id: 'how-we-use',
title: '3. How we use your information',
body: [
'We use personal data to:',
[
'create and secure your account, and authenticate you by mobile number;',
'calculate, award, and redeem LYTs, coupons, and game-based discounts (1 LYT = ₹1 in redeemable value);',
'show you nearby participating stores and relevant offers;',
'run and settle merchant campaigns, and give merchants aggregated performance analytics;',
'detect and prevent fraud, duplicate accounts, bot play, and abuse of reward mechanics;',
'provide customer support and respond to your enquiries;',
'improve our products, including debugging, measuring feature performance, and research using aggregated or de-identified data;',
'send service messages (for example, reward confirmations or policy updates) and, with your consent, marketing messages;',
'comply with law, enforce our terms, and establish or defend legal claims.',
],
'We do not sell your personal data. We do not share your personal data with third parties for their own independent advertising.',
],
},
{
id: 'legal-basis',
title: '4. Consent and legal basis',
body: [
'Where Indian law applies, we process personal data under the Digital Personal Data Protection Act, 2023 and applicable rules, primarily on the basis of your consent and for the legitimate uses that Act permits — for example, providing a service you have asked for, or complying with a legal obligation.',
'You may withdraw your consent at any time, either in the app or by writing to us. Withdrawing consent does not affect processing already carried out, and some features (such as location-based store discovery or step challenges) will stop working without the relevant permission.',
],
},
{
id: 'sharing',
title: '5. When we share information',
body: [
'We share personal data only in the following situations:',
[
'Partner merchants: when you redeem LYTs or a coupon in store, the merchant receives the information needed to honour the redemption — typically the coupon or redemption code, its value, and the fact of the visit. Merchants receive campaign analytics in aggregate, not your individual step data, battery readings, or location history.',
'Service providers: cloud hosting, database, analytics, crash reporting, SMS/OTP, email, and payment or settlement providers who process data on our instructions under contract.',
'Professional advisers: auditors, lawyers, and insurers, where reasonably necessary.',
'Corporate transactions: if Loyaly is involved in a merger, acquisition, financing, or sale of assets, data may transfer as part of that transaction, subject to this policy.',
'Legal and safety: where required by law, court order, or a lawful request from a public authority, or where necessary to protect the rights, property, or safety of Loyaly, our users, or the public.',
],
],
},
{
id: 'cookies',
title: '6. Cookies and similar technologies',
body: [
'Our website uses cookies and similar technologies for essential functionality, such as routing your request, keeping a page session consistent, and protecting our enquiry forms. We do not run third-party advertising or cross-site tracking cookies on loyaly.ai.',
'You can block or delete cookies in your browser settings. Essential cookies cannot be turned off without affecting how the site works. Our mobile apps use device-level identifiers and on-device storage for the equivalent purposes.',
'Our Cookie Policy sets out each category in full, including how long cookies last and how to clear them.',
],
},
{
id: 'retention',
title: '7. How long we keep data',
body: [
'We keep personal data only as long as needed for the purpose it was collected for, or as long as the law requires.',
[
'Account and reward balance data: for as long as your account is active, and for a reasonable period afterwards to settle disputes and meet accounting obligations.',
'Transaction and redemption records: for the period required under applicable tax and accounting law.',
'Step and battery readings: retained only as long as needed to verify and award the relevant reward, then deleted or aggregated.',
'Selfie Booth images: retained for the campaign period and deleted afterwards, unless you have asked us to keep or share them.',
'Support correspondence: typically up to 24 months from the last message.',
],
'When you delete your account, we delete or irreversibly anonymise your personal data, except where we must retain records by law or to resolve an open dispute. Unredeemed LYTs are forfeited on deletion and cannot be restored.',
],
},
{
id: 'security',
title: '8. How we protect your data',
body: [
'We use industry-standard safeguards including encryption of data in transit, access controls and role-based permissions, secured cloud infrastructure, logging, and regular review of our systems. Access to personal data inside Loyaly is limited to staff who need it for their role.',
'No method of transmission or storage is completely secure. If a personal data breach occurs, we will notify the Data Protection Board of India and affected users as required by law. Please keep your device and account credentials secure and tell us immediately if you suspect unauthorised access.',
],
},
{
id: 'your-rights',
title: '9. Your rights and choices',
body: [
'Subject to applicable law, you have the right to:',
[
'access a summary of the personal data we process about you and the parties it has been shared with;',
'correct or complete inaccurate or outdated data;',
'erase personal data that is no longer needed for the purpose it was collected for;',
'withdraw consent for any processing based on consent, including location, step data, camera access, and marketing;',
'nominate another individual to exercise these rights on your behalf in the event of death or incapacity;',
'raise a grievance with us, and escalate to the Data Protection Board of India if you are not satisfied with our response.',
],
`To exercise any of these rights, write to ${CONTACT_EMAIL} from the email address or mobile number registered with your account. We respond to verified requests within the timelines set by applicable law, and ordinarily within 30 days.`,
'You can also manage most choices directly: permissions in your device settings, notifications in the app, and marketing preferences through the unsubscribe link in any marketing email.',
],
},
{
id: 'children',
title: '10. Children',
body: [
'Loyaly is intended for users aged 18 and above and is not directed at children. We do not knowingly collect personal data from a child without verifiable consent from a parent or lawful guardian, and we do not carry out behavioural tracking or targeted advertising directed at children.',
`If you believe a child has provided us personal data, contact ${CONTACT_EMAIL} and we will delete it.`,
],
},
{
id: 'third-parties',
title: '11. Third-party links and services',
body: [
'Our app and website link to third-party services — app stores, partner merchant sites, social media, and payment providers. Those services are controlled by others and have their own privacy policies. We are not responsible for their practices, and this policy does not cover them.',
],
},
{
id: 'transfers',
title: '12. Storage and international transfers',
body: [
'We primarily store and process personal data in India. Some of our service providers may process data on servers located outside India. Where that happens, we transfer data only to jurisdictions permitted under applicable Indian law and require contractual safeguards that hold the recipient to protections comparable to those in this policy.',
],
},
{
id: 'changes',
title: '13. Changes to this policy',
body: [
'We may update this policy as our products and the law evolve. When we make material changes, we will update the date at the top of this page and, where appropriate, notify you in the app or by email. Continuing to use Loyaly after an update means you accept the revised policy.',
],
},
{
id: 'contact',
title: '14. Contact and grievance redressal',
body: [
'For any question about this policy, or to exercise your rights, contact our Grievance Officer:',
[
`Email: ${CONTACT_EMAIL}`,
'Loyaly.AI, Hyderabad, Telangana, India',
'Response time: acknowledgement within one business day, and resolution within the period set by applicable law.',
],
'If you are not satisfied with our response, you may escalate your complaint to the Data Protection Board of India under the Digital Personal Data Protection Act, 2023.',
],
},
];
const HIGHLIGHTS: LegalHighlight[] = [
{ title: 'We never sell your data', text: 'Your personal data is not sold, rented, or handed to third parties for their own advertising.', color: '#f4be28' },
{ title: 'Permissions stay yours', text: 'Location, steps, and camera access are optional, asked for in context, and revocable at any time.', color: '#a78bfa' },
{ title: 'Merchants see aggregates', text: 'Partner stores get campaign performance and redemption data — not your step history or location trail.', color: '#34d399' },
];
export default function PrivacyPolicyPage() {
return (
<LegalPage
titleTop="Privacy"
titleAccent="Policy"
intro="Rewards should feel good, not costly. Here is exactly what Loyaly collects, why we collect it, and the control you keep over it."
highlights={HIGHLIGHTS}
sections={SECTIONS}
ctaHeading="Questions about your data?"
ctaText="Write to our Grievance Officer and we'll acknowledge within one business day."
relatedLinks={[
{ label: 'Terms of Use', href: '/terms' },
{ label: 'Cookie Policy', href: '/cookies' },
]}
/>
);
}

203
src/app/terms/page.tsx Normal file
View File

@@ -0,0 +1,203 @@
import type { Metadata } from 'next';
import LegalPage, {
LEGAL_CONTACT_EMAIL as CONTACT_EMAIL,
type LegalHighlight,
type LegalSection,
} from '@/components/LegalPage';
export const metadata: Metadata = {
title: 'Terms of Use | Loyaly.AI',
description:
'The terms that govern your use of the Loyaly app, the Loyaly Merchant App, and loyaly.ai — including how LYTs, coupons, and game-based discounts work.',
};
const SECTIONS: LegalSection[] = [
{
id: 'agreement',
title: '1. Agreement to these terms',
body: [
'These Terms of Use ("Terms") are a binding agreement between you and Loyaly.AI ("Loyaly", "we", "us", "our"). They govern your use of the Loyaly consumer app, the Loyaly Merchant App and Merchant Dashboard, the loyaly.ai website, and any related services.',
'By creating an account, downloading the app, or using any part of the service, you confirm that you accept these Terms and our Privacy Policy. If you do not accept them, please do not use Loyaly.',
'If you use Loyaly on behalf of a business, you confirm that you are authorised to bind that business, and "you" means that business.',
],
},
{
id: 'eligibility',
title: '2. Eligibility and your account',
body: [
[
'You must be at least 18 years old and capable of entering into a contract under the Indian Contract Act, 1872.',
'You register with a valid mobile number and verify it by OTP. Keep your number, device, and account secure — activity carried out through your account is treated as yours.',
'One person may hold one account. Creating multiple accounts to earn additional rewards is a breach of these Terms.',
'The information you give us must be accurate and kept up to date. We may suspend an account we cannot verify.',
'Tell us immediately at ' + CONTACT_EMAIL + ' if you believe your account has been accessed without your permission.',
],
],
},
{
id: 'lyts',
title: '3. LYTs, coupons, and rewards',
body: [
'LYTs are Loyaly’s in-app reward points. They are a promotional benefit, not money, not a deposit, and not a stored-value or prepaid payment instrument.',
[
'Value: 1 LYT carries a redeemable value of ₹1 against eligible purchases at participating stores. LYTs cannot be exchanged for cash, transferred to another user, or sold.',
'Earning: you can earn LYTs through activities such as daily app login, walking challenges, in-app games (Maze, Spin Wheel, Scratch Card, Match Master), Selfie Booth visits, and campaigns we run from time to time. Earning rules, rates, and caps are shown in the app and may change.',
'Redeeming: LYTs and coupons are redeemed in person at participating stores, subject to each store’s terms, stock, and any minimum spend shown with the offer.',
'Validity: LYTs remain valid while your account is active. Coupons and campaign offers carry their own expiry, shown at the time of issue.',
'Adjustment: we may withhold, reverse, or cancel LYTs and coupons that were awarded in error, through a technical fault, or through activity that breaches these Terms.',
'Closure: if your account is closed by you or by us, any unredeemed LYTs are forfeited.',
],
],
},
{
id: 'games',
title: '4. Games and the Battery Challenge',
body: [
'Our games and the Battery Challenge are promotional mechanics for distributing discounts. They are not gambling, involve no stake or entry fee, and no purchase is necessary to play.',
[
'The reward available from a game is set by the merchant running the campaign, within the maximum discount they configure.',
'The Battery Challenge selects a surprise coupon based on your device battery percentage when you open the app. The outcome is determined by that reading and the merchant’s configured offers.',
'Each campaign states how often you may play. Automating play, using emulators or modified clients, or manipulating device readings to influence an outcome is prohibited.',
'We may end, pause, or modify any campaign or game at any time.',
],
],
},
{
id: 'merchants',
title: '5. Terms for merchants',
body: [
'If you register a store on Loyaly, the following also applies to you:',
[
'You are responsible for the accuracy of your store profile, product listings, prices, and offer terms, and for honouring every coupon and LYT redemption you have made available through the platform.',
'You must hold the licences, registrations, and consents needed to sell your products and run your promotions, and must comply with applicable consumer protection, advertising, and tax law.',
'You set the maximum discount on each campaign. Campaign costs, discounts funded, and any platform fees are settled under your separate commercial agreement with Loyaly.',
'Analytics in the Merchant Dashboard are provided for your business use. You must not attempt to re-identify individual shoppers from aggregated data, or use the platform’s data to contact users outside Loyaly without their consent.',
'If you deploy cameras, sensors, or a Selfie Booth on your premises, you are responsible for lawful notice and consent at that location.',
'You may not withdraw an offer a customer has already earned, or impose conditions that were not disclosed when the offer was issued.',
],
],
},
{
id: 'acceptable-use',
title: '6. Acceptable use',
body: [
'When using Loyaly you must not:',
[
'use bots, scripts, emulators, rooted or modified builds, GPS spoofing, or any other means to fake activity, steps, location, or store visits;',
'create duplicate or fake accounts, or trade, buy, or sell accounts, LYTs, or coupons;',
'reverse engineer, decompile, scrape, or interfere with the apps, the website, or our infrastructure, or attempt to bypass security or rate limits;',
'upload content that is unlawful, obscene, defamatory, infringing, or that contains another person’s personal data without their consent;',
'misuse a Selfie Booth or upload images of other people without their permission;',
'use Loyaly for any unlawful purpose, or in a way that damages the platform or other users.',
],
'We may investigate suspected breaches, and may withhold rewards, suspend, or terminate accounts involved.',
],
},
{
id: 'content',
title: '7. Your content',
body: [
'You keep ownership of the photos, reviews, and other content you submit. By submitting it, you grant Loyaly a non-exclusive, royalty-free, worldwide licence to host, store, reproduce, and display that content for the purpose of operating and promoting the service, for as long as you keep it on the platform.',
'You confirm you have the rights needed to grant that licence. We may remove content that breaches these Terms or the law.',
],
},
{
id: 'ip',
title: '8. Our intellectual property',
body: [
'The Loyaly platform, including the apps, website, software, designs, and the Loyaly.AI, Behavision, SpendSense, and Dyscount names and logos, belongs to Loyaly or its licensors. These Terms grant you a limited, personal, non-transferable, revocable licence to use the service as intended — they transfer no ownership.',
'Merchant names and logos shown in the app belong to those merchants and are used with their permission.',
],
},
{
id: 'third-parties',
title: '9. Stores, third parties, and app stores',
body: [
'Loyaly is a platform that connects you with independent stores. The sale itself happens between you and the merchant. Product quality, pricing, stock, service, warranties, returns, and refunds are the merchant’s responsibility, and any dispute about a purchase should be raised with them first — though we will help where we can.',
'Your use of the apps is also subject to the Apple App Store or Google Play terms. Those platforms are not parties to these Terms and have no responsibility for the service.',
],
},
{
id: 'availability',
title: '10. Service availability and changes',
body: [
'We aim to keep Loyaly available, but the service is provided on an "as is" and "as available" basis. We may add, change, suspend, or withdraw features, campaigns, or the service itself, and may carry out maintenance that causes downtime.',
'Where a change materially affects rewards you have already earned, we will give reasonable notice in the app or by email.',
],
},
{
id: 'liability',
title: '11. Disclaimers and limitation of liability',
body: [
'To the extent permitted by law, we exclude implied warranties of merchantability, fitness for a particular purpose, and non-infringement. We do not warrant that the service will be uninterrupted, error-free, or that any particular reward or discount will be available.',
'To the extent permitted by law, Loyaly is not liable for indirect, incidental, special, or consequential loss, or for loss of profits, goodwill, or data. Our total liability arising out of or in connection with the service is limited to the greater of the amounts you paid us in the three months before the claim, or ₹5,000.',
'Nothing in these Terms excludes liability that cannot be excluded under applicable law, including liability for fraud or for death or personal injury caused by negligence.',
],
},
{
id: 'indemnity',
title: '12. Indemnity',
body: [
'You agree to indemnify Loyaly and its officers, employees, and partners against claims, losses, and reasonable costs arising from your breach of these Terms, your misuse of the service, your content, or — if you are a merchant — the products, offers, and in-store practices you operate.',
],
},
{
id: 'termination',
title: '13. Suspension and termination',
body: [
'You may stop using Loyaly and delete your account at any time from the app or by writing to us.',
'We may suspend or terminate your access, with notice where reasonably practicable, if you breach these Terms, if we suspect fraud or abuse of reward mechanics, or if we are required to do so by law. Sections that by their nature should survive termination — including intellectual property, liability, indemnity, and governing law — continue to apply.',
],
},
{
id: 'changes',
title: '14. Changes to these terms',
body: [
'We may update these Terms as the service and the law evolve. We will update the date shown at the top of this page and, for material changes, notify you in the app or by email. Continuing to use Loyaly after an update means you accept the revised Terms.',
],
},
{
id: 'law',
title: '15. Governing law and disputes',
body: [
'These Terms are governed by the laws of India. Subject to the paragraph below, the courts at Hyderabad, Telangana have exclusive jurisdiction over any dispute.',
`Before starting proceedings, please contact us at ${CONTACT_EMAIL} so we can try to resolve the matter. We aim to acknowledge complaints within one business day and resolve them within the period required by applicable law. Nothing here limits your rights under the Consumer Protection Act, 2019, including your right to approach a consumer forum.`,
],
},
{
id: 'contact',
title: '16. Contact us',
body: [
'Questions about these Terms, or about an offer, redemption, or account:',
[
`Email: ${CONTACT_EMAIL}`,
'Loyaly.AI, Hyderabad, Telangana, India',
'Acknowledgement within one business day.',
],
],
},
];
const HIGHLIGHTS: LegalHighlight[] = [
{ title: 'LYTs are rewards, not money', text: '1 LYT redeems for ₹1 of value in store. LYTs cannot be cashed out, transferred, or sold.', color: '#f4be28' },
{ title: 'Games are not gambling', text: 'No stake, no entry fee, no purchase necessary — the discount is funded by the merchant running the campaign.', color: '#a78bfa' },
{ title: 'The store owns the sale', text: 'Loyaly connects you with independent merchants. Stock, pricing, returns, and service stay with the store.', color: '#34d399' },
];
export default function TermsPage() {
return (
<LegalPage
titleTop="Terms"
titleAccent="of Use"
intro="The rules for using Loyaly — how LYTs and coupons work, what we expect from shoppers and merchants, and what each of us is responsible for."
highlights={HIGHLIGHTS}
sections={SECTIONS}
ctaHeading="Need something clarified?"
ctaText="Ask us before you rely on an interpretation — we'll acknowledge within one business day."
relatedLinks={[
{ label: 'Privacy Policy', href: '/privacy' },
{ label: 'Cookie Policy', href: '/cookies' },
]}
/>
);
}

View File

@@ -0,0 +1,191 @@
import React from 'react';
import Link from 'next/link';
import GSAPAnimator from '@/components/GSAPAnimator';
/** Shared chrome for the legal pages (/privacy, /terms, /cookies). */
export const LEGAL_CONTACT_EMAIL = 'loyalydigital@gmail.com';
export const LEGAL_LAST_UPDATED = '22 September 2026';
export type LegalSection = {
id: string;
title: string;
/** Rendered in order. Strings are paragraphs; arrays are bullet lists. */
body: (string | string[])[];
};
export type LegalHighlight = {
title: string;
text: string;
color: string;
};
type Props = {
/** Hero heading, split so the second line takes the yellow accent. */
titleTop: string;
titleAccent: string;
intro: string;
lastUpdated?: string;
highlights: LegalHighlight[];
sections: LegalSection[];
ctaHeading: string;
ctaText: string;
/** Cross-links shown next to the email button in the closing CTA. */
relatedLinks?: { label: string; href: string }[];
};
export default function LegalPage({
titleTop,
titleAccent,
intro,
lastUpdated = LEGAL_LAST_UPDATED,
highlights,
sections,
ctaHeading,
ctaText,
relatedLinks = [],
}: Props) {
return (
// overflow-x is `clip`, not `hidden`: `hidden` would make <main> a scroll
// container and stop the sticky table of contents below from sticking.
<main style={{ background: '#ffffff', minHeight: '100vh', overflowX: 'clip' }}>
<GSAPAnimator />
{/* ── HERO ── */}
<section style={{
padding: '140px 8% 100px',
background: 'radial-gradient(ellipse 120% 90% at 50% 0%, #2c2350 0%, #211c3e 38%, #181429 100%)',
margin: 'var(--hero-top) var(--sec-gutter) var(--sec-gap) var(--sec-gutter)', borderRadius: '32px',
position: 'relative', overflow: 'hidden', display: 'flex', alignItems: 'center',
}}>
{/* Accent blobs */}
<div style={{ position: 'absolute', top: '-14%', right: '8%', width: 480, height: 480, borderRadius: '50%', background: 'radial-gradient(circle, rgba(244,190,40,0.2) 0%, transparent 70%)', pointerEvents: 'none' }} />
<div style={{ position: 'absolute', bottom: '-16%', left: '4%', width: 420, height: 420, borderRadius: '50%', background: 'radial-gradient(circle, rgba(167,139,250,0.18) 0%, transparent 70%)', pointerEvents: 'none' }} />
<div style={{ position: 'absolute', inset: 0, backgroundImage: 'linear-gradient(rgba(244,190,40,0.025) 1px,transparent 1px),linear-gradient(90deg,rgba(244,190,40,0.025) 1px,transparent 1px)', backgroundSize: '60px 60px', pointerEvents: 'none' }} />
<div style={{ position: 'relative', zIndex: 2, width: '100%', display: 'flex', flexDirection: 'column', alignItems: 'center', textAlign: 'center' }}>
<div style={{ maxWidth: 760 }}>
<span style={{
display: 'inline-flex', alignItems: 'center', gap: 8, padding: '9px 18px', borderRadius: 100,
background: 'rgba(255,255,255,0.07)', border: '1px solid rgba(255,255,255,0.14)',
fontSize: 13, fontWeight: 700, letterSpacing: '0.04em', textTransform: 'uppercase',
color: 'rgba(255,255,255,0.75)', fontFamily: 'Sora, sans-serif', marginBottom: 28,
}}>
<span style={{ width: 6, height: 6, borderRadius: '50%', background: '#f4be28', display: 'block' }} />
Last updated {lastUpdated}
</span>
<h1 data-gsap="heading" style={{
fontSize: 'clamp(48px, 8vw, 110px)', fontWeight: 800, letterSpacing: '-0.05em', lineHeight: 0.9,
color: '#fff', margin: '0 0 24px', fontFamily: 'Sora, sans-serif',
}}>
{titleTop}<br />
<span style={{ color: '#f4be28' }}>{titleAccent}</span>
</h1>
<p data-gsap="text" style={{
fontSize: 'clamp(16px, 1.5vw, 20px)', lineHeight: 1.75, color: 'rgba(255,255,255,0.55)',
fontFamily: 'Sora, sans-serif', maxWidth: 560, margin: '0 auto',
}}>
{intro}
</p>
</div>
</div>
</section>
{/* ── HIGHLIGHTS ── */}
<section style={{ padding: '72px 8%', background: '#fff', margin: '0 var(--sec-gutter) var(--sec-gap)', borderRadius: 24 }}>
<div data-gsap="stagger" className="ly-legal-highlights" style={{ display: 'grid', gridTemplateColumns: `repeat(${highlights.length}, 1fr)`, gap: 20 }}>
{highlights.map((h) => (
<div key={h.title} style={{
padding: '28px 26px', borderRadius: 18, background: '#fafafa',
border: '1px solid rgba(0,0,0,0.07)', borderTop: `3px solid ${h.color}`,
}}>
<h3 style={{ margin: '0 0 10px', fontSize: 17, fontWeight: 800, letterSpacing: '-0.02em', color: '#0a0a0f', fontFamily: 'Sora, sans-serif' }}>{h.title}</h3>
<p style={{ margin: 0, fontSize: 14.5, lineHeight: 1.75, color: 'rgba(0,0,0,0.55)', fontFamily: 'Sora, sans-serif' }}>{h.text}</p>
</div>
))}
</div>
</section>
{/* ── BODY ── */}
<section style={{ padding: '72px 8% 88px', background: '#fff', margin: '0 var(--sec-gutter) var(--sec-gap)', borderRadius: 24 }}>
{/* Grid items intentionally stretch: the ToC column must be full height
for its sticky nav to have room to travel. */}
<div className="ly-legal-layout" style={{ display: 'grid', gridTemplateColumns: '260px 1fr', gap: 56 }}>
{/* Table of contents — the <nav> stretches to the full grid row;
the inner box is what sticks. */}
<nav className="ly-legal-toc" aria-label="Contents">
<div className="ly-legal-toc-inner" style={{ position: 'sticky', top: 120 }}>
<h2 style={{ margin: '0 0 18px', fontSize: 12, fontWeight: 800, letterSpacing: '0.12em', textTransform: 'uppercase', color: 'rgba(0,0,0,0.4)', fontFamily: 'Sora, sans-serif' }}>Contents</h2>
<ul style={{ listStyle: 'none', margin: 0, padding: 0, display: 'flex', flexDirection: 'column', gap: 9 }}>
{sections.map((s) => (
<li key={s.id}>
<a href={`#${s.id}`} style={{
fontSize: 14, lineHeight: 1.5, color: 'rgba(0,0,0,0.55)', textDecoration: 'none',
fontFamily: 'Sora, sans-serif', fontWeight: 500,
}}>{s.title}</a>
</li>
))}
</ul>
</div>
</nav>
{/* Sections */}
<div style={{ maxWidth: 780 }}>
{sections.map((s) => (
<article key={s.id} id={s.id} style={{ scrollMarginTop: 120, marginBottom: 48 }}>
<h2 style={{
fontSize: 'clamp(21px, 2.4vw, 30px)', fontWeight: 800, letterSpacing: '-0.03em',
color: '#0a0a0f', margin: '0 0 18px', fontFamily: 'Sora, sans-serif', lineHeight: 1.2,
}}>{s.title}</h2>
{s.body.map((block, i) =>
Array.isArray(block) ? (
<ul key={i} style={{ margin: '0 0 18px', padding: 0, listStyle: 'none', display: 'flex', flexDirection: 'column', gap: 11 }}>
{block.map((li, j) => (
<li key={j} style={{ display: 'flex', gap: 12, fontSize: 15.5, lineHeight: 1.8, color: 'rgba(0,0,0,0.6)', fontFamily: 'Sora, sans-serif' }}>
<span style={{ flexShrink: 0, width: 6, height: 6, borderRadius: '50%', background: '#f4be28', marginTop: 11 }} />
<span>{li}</span>
</li>
))}
</ul>
) : (
<p key={i} style={{ margin: '0 0 18px', fontSize: 15.5, lineHeight: 1.85, color: 'rgba(0,0,0,0.6)', fontFamily: 'Sora, sans-serif' }}>{block}</p>
)
)}
</article>
))}
<p style={{ margin: 0, fontSize: 13.5, lineHeight: 1.8, color: 'rgba(0,0,0,0.4)', fontFamily: 'Sora, sans-serif', fontStyle: 'italic' }}>
This page is published for transparency and is not legal advice. Please have it reviewed by your legal counsel before relying on it for compliance.
</p>
</div>
</div>
</section>
{/* ── CTA ── */}
<section style={{ padding: '80px 8%', background: '#f4be28', margin: '0 var(--sec-gutter) var(--sec-gap)', borderRadius: 24, textAlign: 'center' }}>
<h2 style={{ fontSize: 'clamp(28px, 4vw, 56px)', fontWeight: 800, letterSpacing: '-0.04em', color: '#111', margin: '0 0 16px', fontFamily: 'Sora, sans-serif' }}>{ctaHeading}</h2>
<p style={{ fontSize: 16, color: 'rgba(0,0,0,0.55)', fontFamily: 'Sora, sans-serif', margin: '0 0 36px' }}>{ctaText}</p>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 14, justifyContent: 'center' }}>
<a href={`mailto:${LEGAL_CONTACT_EMAIL}`} style={{ display: 'inline-flex', alignItems: 'center', gap: 10, padding: '16px 40px', background: '#111', color: '#f4be28', fontSize: 15, fontWeight: 800, borderRadius: 100, textDecoration: 'none', fontFamily: 'Sora, sans-serif' }}>
{LEGAL_CONTACT_EMAIL}
</a>
{relatedLinks.map((l) => (
<Link key={l.href} href={l.href} style={{ display: 'inline-flex', alignItems: 'center', gap: 10, padding: '16px 40px', background: 'rgba(0,0,0,0.08)', color: '#111', fontSize: 15, fontWeight: 800, borderRadius: 100, textDecoration: 'none', fontFamily: 'Sora, sans-serif' }}>
{l.label} →
</Link>
))}
</div>
</section>
<style>{`
.ly-legal-toc a:hover { color: #0a0a0f; }
@media (max-width: 900px) {
.ly-legal-highlights { grid-template-columns: 1fr !important; }
.ly-legal-layout { grid-template-columns: 1fr !important; gap: 36px !important; }
.ly-legal-toc-inner { position: static !important; }
}
`}</style>
</main>
);
}

View File

@@ -11,6 +11,13 @@ if (typeof window !== 'undefined') gsap.registerPlugin(ScrollTrigger);
const useIsomorphicLayoutEffect = typeof window !== 'undefined' ? useLayoutEffect : useEffect; const useIsomorphicLayoutEffect = typeof window !== 'undefined' ? useLayoutEffect : useEffect;
/* Embedded LytsUp web app — logical viewport it is designed against. The width
pairs with --pm-scale below; the height sizes the screen so the app is never
given a shorter viewport than it lays out for. */
const DESIGN_HEIGHT = 844;
/* Opaque strip that keeps the app's top bar clear of the dynamic island. */
const NOTCH_STRIP = 32;
export default function EarnRewardsSection() { export default function EarnRewardsSection() {
const panelRef = useRef<HTMLDivElement>(null); const panelRef = useRef<HTMLDivElement>(null);
const phoneRef = useRef<HTMLDivElement>(null); const phoneRef = useRef<HTMLDivElement>(null);
@@ -88,10 +95,15 @@ export default function EarnRewardsSection() {
position: 'relative', position: 'relative',
}} }}
> >
{/* Screen — locked to a real phone's aspect ratio (~9:19.5) */} {/* Screen — height is derived, not a fixed aspect ratio: the notch
strip plus the app's own 844px design height scaled down. A hard
9/19.5 left the iframe only ~797 logical px once the 32px strip
was subtracted, so the app rendered ~47px short and clipped its
bottom nav. This lands the embed on exactly 390x844 at every
breakpoint; the device still reads as ~9:19.8. */}
<div style={{ <div style={{
borderRadius: 32, overflow: 'hidden', background: '#ffffff', borderRadius: 32, overflow: 'hidden', background: '#ffffff',
position: 'relative', aspectRatio: '9 / 19.5', position: 'relative', height: `calc(${NOTCH_STRIP}px + ${DESIGN_HEIGHT}px * var(--pm-scale))`,
display: 'flex', flexDirection: 'column', display: 'flex', flexDirection: 'column',
}}> }}>
{/* Dynamic island */} {/* Dynamic island */}
@@ -100,7 +112,7 @@ export default function EarnRewardsSection() {
width: 82, height: 22, borderRadius: 14, background: '#000', zIndex: 5, width: 82, height: 22, borderRadius: 14, background: '#000', zIndex: 5,
}} /> }} />
{/* Safe area — reserves the notch strip above the app's top bar */} {/* Safe area — reserves the notch strip above the app's top bar */}
<div style={{ height: 32, flexShrink: 0, background: '#ffffff' }} /> <div style={{ height: NOTCH_STRIP, flexShrink: 0, background: '#ffffff' }} />
{/* The embed is a responsive Flutter web app. We render it at a {/* The embed is a responsive Flutter web app. We render it at a
comfortable logical width (390px) and scale it down to fill the comfortable logical width (390px) and scale it down to fill the
screen exactly, so the UI stays proportioned as designed instead screen exactly, so the UI stays proportioned as designed instead

View File

@@ -9,6 +9,11 @@ if (typeof window !== 'undefined') gsap.registerPlugin(ScrollTrigger);
const useIsomorphicLayoutEffect = typeof window !== 'undefined' ? useLayoutEffect : useEffect; const useIsomorphicLayoutEffect = typeof window !== 'undefined' ? useLayoutEffect : useEffect;
/* Logical viewport the embedded LytsUp web app is designed against — see
EarnRewardsSection for the full rationale. */
const DESIGN_HEIGHT = 844;
const NOTCH_STRIP = 32;
export default function SpendAnywhereSection() { export default function SpendAnywhereSection() {
const panelRef = useRef<HTMLDivElement>(null); const panelRef = useRef<HTMLDivElement>(null);
const phoneRef = useRef<HTMLDivElement>(null); const phoneRef = useRef<HTMLDivElement>(null);
@@ -88,10 +93,13 @@ export default function SpendAnywhereSection() {
position: 'relative', position: 'relative',
}} }}
> >
{/* Screen — locked to a real phone's aspect ratio (~9:19.5) */} {/* Screen — height is derived, not a fixed aspect ratio: see
EarnRewardsSection. A hard 9/19.5 left the iframe ~47 logical px
short once the notch strip was subtracted, clipping the app's
bottom nav; this lands it on exactly 390x844 at every breakpoint. */}
<div style={{ <div style={{
borderRadius: 32, overflow: 'hidden', background: '#ffffff', borderRadius: 32, overflow: 'hidden', background: '#ffffff',
position: 'relative', aspectRatio: '9 / 19.5', position: 'relative', height: `calc(${NOTCH_STRIP}px + ${DESIGN_HEIGHT}px * var(--pm-scale))`,
display: 'flex', flexDirection: 'column', display: 'flex', flexDirection: 'column',
}}> }}>
{/* Dynamic island */} {/* Dynamic island */}
@@ -100,7 +108,7 @@ export default function SpendAnywhereSection() {
width: 82, height: 22, borderRadius: 14, background: '#000', zIndex: 5, width: 82, height: 22, borderRadius: 14, background: '#000', zIndex: 5,
}} /> }} />
{/* Safe area — reserves the notch strip above the app's top bar */} {/* Safe area — reserves the notch strip above the app's top bar */}
<div style={{ height: 32, flexShrink: 0, background: '#ffffff' }} /> <div style={{ height: NOTCH_STRIP, flexShrink: 0, background: '#ffffff' }} />
{/* The embed is a responsive Flutter web app. We render it at a {/* The embed is a responsive Flutter web app. We render it at a
comfortable logical width (390px) and scale it down to fill the comfortable logical width (390px) and scale it down to fill the
screen exactly, so the UI stays proportioned as designed instead screen exactly, so the UI stays proportioned as designed instead