commit 92602b3457cb567758dad87240aaec879843d2f1 Author: ZuoZuo <68836346+Mrz-sakura@users.noreply.github.com> Date: Mon Apr 13 23:56:50 2026 +0800 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..849ddff --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +dist/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..73ded2b --- /dev/null +++ b/README.md @@ -0,0 +1,51 @@ +# Haiyi Public Site + +This directory contains the public website for `www.haiyihy.com` and the H5 landing pages for `h5.haiyihy.com`. + +## Structure + +- `content/site.json`: Official website and company-level content. +- `content/legal.json`: Privacy policy and terms content. +- `content/landing-pages/*.json`: One JSON file per H5 landing page. +- `src/site.css`: Shared styling for all generated pages. +- `scripts/build.mjs`: Static site generator. +- `scripts/new-landing-page.mjs`: Scaffolds a new landing page JSON file. + +## Local workflow + +```bash +cd public-site +node ./scripts/build.mjs +``` + +The generated output is written to `public-site/dist`: + +- `dist/www`: content for `www.haiyihy.com` +- `dist/h5`: content for `h5.haiyihy.com` + +## Landing page management + +Current management is file-driven: + +1. Duplicate an existing JSON file under `content/landing-pages/`, or run: + +```bash +cd public-site +node ./scripts/new-landing-page.mjs your-page-slug +``` + +2. Edit the JSON content only. No Vue or HTML editing is required. +3. Rebuild the site: + +```bash +cd public-site +node ./scripts/build.mjs +``` + +4. Run the public-site deployment script: + +```bash +./deploy/tencent-prod/.venv/bin/python ./deploy/tencent-prod/deploy_public_site.py +``` + +If you later want a browser-based online editor with persistence, this structure is already suitable for adding a small API or CMS layer. diff --git a/content/landing-pages/admin-center.json b/content/landing-pages/admin-center.json new file mode 100644 index 0000000..04786e0 --- /dev/null +++ b/content/landing-pages/admin-center.json @@ -0,0 +1,36 @@ +{ + "slug": "admin-center", + "theme": "steel", + "eyebrow": "Operations gateway", + "title": "A public-facing operations handoff page for admin and campaign distribution needs.", + "summary": "Use this page as a lightweight web entrance when the final system sits behind internal tools or role-based access.", + "highlight": "Separates public routing and internal operations cleanly.", + "cta": { + "label": "Official Website", + "href": "https://www.haiyihy.com/" + }, + "secondaryCta": { + "label": "Host Center", + "href": "https://h5.haiyihy.com/host-center/" + }, + "stats": [ + { + "value": "Public shell", + "label": "for internal-operation entry design" + }, + { + "value": "Reusable", + "label": "for future operational routing" + } + ], + "sections": [ + { + "title": "Controlled access", + "body": "Keep actual admin systems private while still presenting a clear, branded public-facing handoff page when needed." + }, + { + "title": "Simple maintenance", + "body": "Because the content is generated from JSON, copy and target links can be revised quickly." + } + ] +} diff --git a/content/landing-pages/agency-center.json b/content/landing-pages/agency-center.json new file mode 100644 index 0000000..28fc864 --- /dev/null +++ b/content/landing-pages/agency-center.json @@ -0,0 +1,36 @@ +{ + "slug": "agency-center", + "theme": "violet", + "eyebrow": "Agency program", + "title": "A clear H5 entry for agencies, channel partners, and growth collaborators.", + "summary": "Use this page to explain the agency model, onboarding expectations, communication channels, and next actions.", + "highlight": "Tailored for channel-driven growth programs and partner onboarding.", + "cta": { + "label": "Open BD Center", + "href": "https://h5.haiyihy.com/bd-center/" + }, + "secondaryCta": { + "label": "Invite New Users", + "href": "https://h5.haiyihy.com/invite-new-user/" + }, + "stats": [ + { + "value": "Partner", + "label": "orientation and conversion flow" + }, + { + "value": "Flexible", + "label": "content for region-specific campaigns" + } + ], + "sections": [ + { + "title": "Program framing", + "body": "This page can communicate eligibility, relationship structure, reward model, and operational expectations." + }, + { + "title": "Campaign agility", + "body": "You can run multiple variants over time while keeping the page system stable and easy to maintain." + } + ] +} diff --git a/content/landing-pages/apply.json b/content/landing-pages/apply.json new file mode 100644 index 0000000..10d3275 --- /dev/null +++ b/content/landing-pages/apply.json @@ -0,0 +1,46 @@ +{ + "slug": "apply", + "theme": "ember", + "eyebrow": "Host recruitment", + "title": "Apply to become a Yumi host and start your own voice room community.", + "summary": "Designed for creators who want to host conversations, run themed rooms, and grow an audience around live social interaction.", + "highlight": "Fast onboarding for hosts, room operators, and community starters.", + "cta": { + "label": "Download Yumi", + "href": "https://play.google.com/store/apps/details?id=com.org.yumi" + }, + "secondaryCta": { + "label": "View Host Center", + "href": "https://h5.haiyihy.com/host-center/" + }, + "stats": [ + { + "value": "Live", + "label": "voice-room-first host workflow" + }, + { + "value": "Flexible", + "label": "community and event formats" + } + ], + "sections": [ + { + "title": "Who this is for", + "body": "Hosts who can bring conversation energy, build room identity, and keep users engaged through recurring activities.", + "bullets": [ + "Run voice rooms and themed sessions", + "Build your own regular audience rhythm", + "Participate in campaigns and operational support" + ] + }, + { + "title": "What you get", + "body": "Yumi is built to support the host lifecycle from room setup to room growth and activity retention.", + "bullets": [ + "Room profile and room settings support", + "Mic, member, and room interaction tools", + "Campaign entry points connected to H5 funnels" + ] + } + ] +} diff --git a/content/landing-pages/bd-center.json b/content/landing-pages/bd-center.json new file mode 100644 index 0000000..0e39747 --- /dev/null +++ b/content/landing-pages/bd-center.json @@ -0,0 +1,36 @@ +{ + "slug": "bd-center", + "theme": "forest", + "eyebrow": "Business development", + "title": "A campaign-ready H5 entrance for BD activities and partner growth.", + "summary": "Use this page to brief BD participants, explain deliverables, and connect external traffic into measurable operational flows.", + "highlight": "Built for acquisition, activation, and channel-based growth operations.", + "cta": { + "label": "Open Agency Center", + "href": "https://h5.haiyihy.com/agency-center/" + }, + "secondaryCta": { + "label": "Invite New Users", + "href": "https://h5.haiyihy.com/invite-new-user/" + }, + "stats": [ + { + "value": "Partner-ready", + "label": "external communication format" + }, + { + "value": "Structured", + "label": "clear calls to action" + } + ], + "sections": [ + { + "title": "Operational clarity", + "body": "Use this page to define the audience, reward logic, participation steps, and submission or review flows." + }, + { + "title": "Fast reconfiguration", + "body": "Swap campaign copy, target region language, and CTA links by editing content files instead of rebuilding the app." + } + ] +} diff --git a/content/landing-pages/bd-leader-center.json b/content/landing-pages/bd-leader-center.json new file mode 100644 index 0000000..4b87a2c --- /dev/null +++ b/content/landing-pages/bd-leader-center.json @@ -0,0 +1,36 @@ +{ + "slug": "bd-leader-center", + "theme": "ink", + "eyebrow": "Leadership view", + "title": "A structured overview page for BD leaders managing multiple contributors or growth tracks.", + "summary": "Ideal for summarizing current focus areas, reporting expectations, onboarding standards, and campaign coordination notes.", + "highlight": "A cleaner destination for management-level communication and rollout guidance.", + "cta": { + "label": "Open BD Center", + "href": "https://h5.haiyihy.com/bd-center/" + }, + "secondaryCta": { + "label": "Agency Center", + "href": "https://h5.haiyihy.com/agency-center/" + }, + "stats": [ + { + "value": "Manager", + "label": "orientation layer for BD leadership" + }, + { + "value": "Reusable", + "label": "template for future operations" + } + ], + "sections": [ + { + "title": "Use cases", + "body": "Campaign kickoff notes, team dashboards, policy reminders, or coordination pages can all be routed through this destination." + }, + { + "title": "Editorial control", + "body": "Each page is file-driven, so operational owners can prepare a release-ready content update without touching layout code." + } + ] +} diff --git a/content/landing-pages/coin-seller.json b/content/landing-pages/coin-seller.json new file mode 100644 index 0000000..279a3fc --- /dev/null +++ b/content/landing-pages/coin-seller.json @@ -0,0 +1,36 @@ +{ + "slug": "coin-seller", + "theme": "gold", + "eyebrow": "Sales and recharge", + "title": "A dedicated funnel page for coin sales, recharge guidance, and payment-related campaigns.", + "summary": "Use this landing page to explain transaction steps, campaign windows, purchase support, and payment channel notices.", + "highlight": "Useful for external campaign links where a direct app-store page is too generic.", + "cta": { + "label": "Open Google Play", + "href": "https://play.google.com/store/apps/details?id=com.org.yumi" + }, + "secondaryCta": { + "label": "Support Email", + "href": "mailto:support@haiyihy.com" + }, + "stats": [ + { + "value": "Payment-ready", + "label": "for purchase and recharge communication" + }, + { + "value": "Clear", + "label": "support escalation path" + } + ], + "sections": [ + { + "title": "Payment communication", + "body": "Summarize recharge channels, availability windows, support methods, and any temporary campaign mechanics in one stable page." + }, + { + "title": "Review-safe", + "body": "For compliance-sensitive launches, this page can carry explanatory copy without changing in-app billing logic." + } + ] +} diff --git a/content/landing-pages/games-king.json b/content/landing-pages/games-king.json new file mode 100644 index 0000000..b51ebae --- /dev/null +++ b/content/landing-pages/games-king.json @@ -0,0 +1,36 @@ +{ + "slug": "games-king", + "theme": "berry", + "eyebrow": "Games campaign", + "title": "A landing page slot for game-focused events, rankings, and seasonal engagement campaigns.", + "summary": "Use this destination for mini-game promotions, challenge seasons, rankings, and cross-room participation drives.", + "highlight": "Designed for event storytelling rather than dense system UI.", + "cta": { + "label": "Open Room Rankings", + "href": "https://h5.haiyihy.com/rankings-rooms/" + }, + "secondaryCta": { + "label": "Open Wealth Rankings", + "href": "https://h5.haiyihy.com/rankings-wealth/" + }, + "stats": [ + { + "value": "Seasonal", + "label": "for event-based traffic spikes" + }, + { + "value": "Lightweight", + "label": "copy-first promotional format" + } + ], + "sections": [ + { + "title": "Event-ready", + "body": "Turn this page into a game season hub, contest explainer, or campaign teaser with one content update." + }, + { + "title": "Cross-linkable", + "body": "It can point users into rankings, invitation flows, room events, or download paths." + } + ] +} diff --git a/content/landing-pages/host-center.json b/content/landing-pages/host-center.json new file mode 100644 index 0000000..6f6c93b --- /dev/null +++ b/content/landing-pages/host-center.json @@ -0,0 +1,36 @@ +{ + "slug": "host-center", + "theme": "sapphire", + "eyebrow": "Host operations", + "title": "A central landing page for creators, room owners, and active hosts.", + "summary": "Use this page as the distribution point for host-facing updates, onboarding steps, room guidance, and operational notices.", + "highlight": "A stable host-facing H5 entry point linked directly from the app.", + "cta": { + "label": "Apply as Host", + "href": "https://h5.haiyihy.com/apply/" + }, + "secondaryCta": { + "label": "Privacy Policy", + "href": "https://h5.haiyihy.com/privacy.html" + }, + "stats": [ + { + "value": "1 page", + "label": "for recurring host communication" + }, + { + "value": "Low friction", + "label": "distribution from app and campaigns" + } + ], + "sections": [ + { + "title": "Use it as a routing hub", + "body": "This page can carry current season rules, host incentives, room standards, or updated activity instructions without shipping a new app build." + }, + { + "title": "Keep copy editable", + "body": "The page content is managed from a single JSON file so operators can update titles, highlights, bullets, and links with minimal engineering overhead." + } + ] +} diff --git a/content/landing-pages/invite-new-user.json b/content/landing-pages/invite-new-user.json new file mode 100644 index 0000000..c6d8a0d --- /dev/null +++ b/content/landing-pages/invite-new-user.json @@ -0,0 +1,36 @@ +{ + "slug": "invite-new-user", + "theme": "mint", + "eyebrow": "Growth funnel", + "title": "A clean invitation page for user acquisition, referral campaigns, and onboarding traffic.", + "summary": "Use this page to tell new users what Yumi is, why they should join, and where the invite flow leads next.", + "highlight": "Built for referral links, community invite traffic, and campaign attribution handoffs.", + "cta": { + "label": "Download Yumi", + "href": "https://play.google.com/store/apps/details?id=com.org.yumi" + }, + "secondaryCta": { + "label": "Official Website", + "href": "https://www.haiyihy.com/" + }, + "stats": [ + { + "value": "Referral", + "label": "friendly landing format" + }, + { + "value": "App-first", + "label": "clear user conversion path" + } + ], + "sections": [ + { + "title": "What this page should do", + "body": "Explain the value of Yumi quickly, remove ambiguity for first-time visitors, and route them cleanly into app installation." + }, + { + "title": "How to operate it", + "body": "Adjust headline, incentive copy, and campaign validity in JSON whenever a new invite program starts." + } + ] +} diff --git a/content/landing-pages/rankings-charm.json b/content/landing-pages/rankings-charm.json new file mode 100644 index 0000000..a5b62da --- /dev/null +++ b/content/landing-pages/rankings-charm.json @@ -0,0 +1,36 @@ +{ + "slug": "rankings-charm", + "theme": "rose", + "eyebrow": "Rankings", + "title": "A dedicated landing page slot for charm campaigns, profile visibility pushes, and social leaderboard events.", + "summary": "Use this page when the campaign angle is social presence, room influence, or creator energy rather than pure spend.", + "highlight": "Helpful for event framing, rewards copy, and seasonal leaderboards.", + "cta": { + "label": "Open Wealth Rankings", + "href": "https://h5.haiyihy.com/rankings-wealth/" + }, + "secondaryCta": { + "label": "Open Room Rankings", + "href": "https://h5.haiyihy.com/rankings-rooms/" + }, + "stats": [ + { + "value": "Social", + "label": "campaign framing" + }, + { + "value": "Brand-safe", + "label": "copy-first leaderboard destination" + } + ], + "sections": [ + { + "title": "Use it for social momentum", + "body": "This page is a better pre-click story for charm or popularity campaigns than a raw in-app ranking screen." + }, + { + "title": "Keep it current", + "body": "Page content is isolated from app releases so the campaign story can change without app-store friction." + } + ] +} diff --git a/content/landing-pages/rankings-rooms.json b/content/landing-pages/rankings-rooms.json new file mode 100644 index 0000000..f69e918 --- /dev/null +++ b/content/landing-pages/rankings-rooms.json @@ -0,0 +1,36 @@ +{ + "slug": "rankings-rooms", + "theme": "teal", + "eyebrow": "Rankings", + "title": "A room-ranking campaign page for spotlighting active communities, top rooms, and themed room competitions.", + "summary": "Useful when you want to explain what the room ranking means before sending users into the app experience.", + "highlight": "Purpose-built for room growth, discovery, and campaign traffic.", + "cta": { + "label": "Apply as Host", + "href": "https://h5.haiyihy.com/apply/" + }, + "secondaryCta": { + "label": "Open Games Campaign", + "href": "https://h5.haiyihy.com/games-king/" + }, + "stats": [ + { + "value": "Community", + "label": "room-based campaign focus" + }, + { + "value": "High-intent", + "label": "bridge from web to in-app room entry" + } + ], + "sections": [ + { + "title": "Campaign format", + "body": "Use this page to explain ranking time windows, participation criteria, or room activity themes." + }, + { + "title": "Discovery surface", + "body": "It also works as a lightweight discovery page for room-focused users coming from ads, social posts, or direct links." + } + ] +} diff --git a/content/landing-pages/rankings-wealth.json b/content/landing-pages/rankings-wealth.json new file mode 100644 index 0000000..29735be --- /dev/null +++ b/content/landing-pages/rankings-wealth.json @@ -0,0 +1,36 @@ +{ + "slug": "rankings-wealth", + "theme": "gold", + "eyebrow": "Rankings", + "title": "A public landing page for wealth-themed campaigns, visibility pushes, and leaderboard storytelling.", + "summary": "Use this URL when you need a branded web destination for top-spender or premium-activity campaigns before routing users back into the app.", + "highlight": "A stable, linkable page for high-value campaign distribution.", + "cta": { + "label": "Open Charm Rankings", + "href": "https://h5.haiyihy.com/rankings-charm/" + }, + "secondaryCta": { + "label": "Open Room Rankings", + "href": "https://h5.haiyihy.com/rankings-rooms/" + }, + "stats": [ + { + "value": "Public", + "label": "campaign destination" + }, + { + "value": "Flexible", + "label": "can describe seasonal leaderboard logic" + } + ], + "sections": [ + { + "title": "Why use a web page", + "body": "This lets operations explain campaign windows, prize rules, or ranking focus without requiring app copy updates." + }, + { + "title": "How to adapt it", + "body": "Swap the title, hero summary, and CTA targets to match the next season or region." + } + ] +} diff --git a/content/legal.json b/content/legal.json new file mode 100644 index 0000000..e227db6 --- /dev/null +++ b/content/legal.json @@ -0,0 +1,304 @@ +{ + "effectiveDate": "2026-04-13", + "privacy": { + "title": "Privacy Policy", + "summary": "This Privacy Policy explains how Yumi and its operating entity collect, use, store, share, and protect information when users access the Yumi mobile application, websites, H5 pages, and related services.", + "sections": [ + { + "title": "1. Data Controller", + "paragraphs": [ + "Yumi is operated by 環球互動科技有限公司 (Global Interactive Technology Co., Limited).", + "Registered address: 中国香港环球大厦A01.", + "For privacy questions, deletion requests, or complaints, contact: postmaster@haiyinet.com.cn or call +85256283162." + ] + }, + { + "title": "2. Scope of This Policy", + "paragraphs": [ + "This policy applies to the Yumi mobile application, the official website at www.haiyihy.com, H5 pages at h5.haiyihy.com, customer support interactions, and other related online services operated under the Yumi brand." + ] + }, + { + "title": "3. Information We Collect", + "paragraphs": [ + "Depending on how a user interacts with Yumi, we may collect the following categories of information:" + ], + "bullets": [ + "Account information, such as nickname, profile photo, login identifier, country or region, and account preferences.", + "User-generated content, such as room names, room profile information, chat content, posts, reports, profile text, and uploaded media.", + "Voice-room interaction data, including room participation, microphone state, room role, room settings, gift activity, and room engagement logs.", + "Device and technical data, such as device model, operating system version, app version, network type, device identifiers, crash logs, and diagnostic data.", + "Transaction and billing data related to in-app purchases, order status, recharge behavior, wallet history, and fraud-prevention checks.", + "Support and risk data, such as user reports, abuse complaints, moderation decisions, and customer service records.", + "Website usage data, including standard log data and limited browser information when users visit our public web pages." + ] + }, + { + "title": "4. How We Use Information", + "bullets": [ + "Provide account registration, login, room participation, chat, and social features.", + "Maintain platform integrity, trust, anti-abuse workflows, and fraud prevention.", + "Process purchases, virtual items, recharge flows, and related financial support operations.", + "Operate events, rankings, invitations, host programs, and agency-related H5 campaigns.", + "Improve product performance through analytics, diagnostics, and crash resolution.", + "Respond to support requests, complaints, legal notices, and data-rights requests.", + "Comply with applicable laws, lawful requests, and platform obligations." + ] + }, + { + "title": "5. Legal Bases", + "paragraphs": [ + "Where applicable, we process personal information because it is necessary to provide requested services, to comply with legal obligations, to protect the platform and users, or based on legitimate business interests such as product operations, analytics, and service improvement.", + "When consent is required by law for a specific processing activity, we will rely on that consent." + ] + }, + { + "title": "6. How Information May Be Shared", + "paragraphs": [ + "We do not sell personal information. We may share information only as reasonably necessary in the following cases:" + ], + "bullets": [ + "With infrastructure, cloud, analytics, messaging, payment, customer-service, or fraud-prevention vendors that help us operate Yumi.", + "With app-store or platform providers when required to support purchases, app review, crash handling, or policy compliance.", + "With professional advisers, auditors, insurers, or legal representatives where reasonably necessary.", + "When required by law, regulation, subpoena, court order, or lawful government request.", + "As part of a corporate transaction such as a merger, restructuring, or asset transfer, subject to applicable safeguards." + ] + }, + { + "title": "7. International Transfers", + "paragraphs": [ + "Yumi may use systems, vendors, and support workflows that operate across different jurisdictions. Where personal information is transferred internationally, we take reasonable steps to apply appropriate contractual, technical, and organizational safeguards." + ] + }, + { + "title": "8. Data Retention", + "paragraphs": [ + "We retain personal information for as long as necessary to provide services, fulfill account functions, maintain business records, resolve disputes, prevent abuse, enforce agreements, or comply with legal obligations.", + "Retention periods may vary depending on the type of data, the sensitivity of the data, and the operational or legal reason for retaining it." + ] + }, + { + "title": "9. Security", + "paragraphs": [ + "We use reasonable administrative, technical, and organizational measures to protect information against unauthorized access, misuse, alteration, disclosure, or destruction. However, no method of transmission or storage can be guaranteed to be completely secure." + ] + }, + { + "title": "10. Your Rights and Choices", + "paragraphs": [ + "Subject to applicable law, users may request access, correction, deletion, restriction, objection, portability, or withdrawal of consent for certain data-processing activities." + ], + "bullets": [ + "You may review and update some profile information directly inside the app.", + "You may request account deletion or data deletion by contacting postmaster@haiyinet.com.cn.", + "We may need to verify identity before responding to certain requests." + ] + }, + { + "title": "11. Children's Privacy", + "paragraphs": [ + "Yumi is not intended for children who are below the minimum age permitted under applicable law in the relevant market. If you believe a child has provided personal information to Yumi inappropriately, please contact postmaster@haiyinet.com.cn." + ] + }, + { + "title": "12. Third-Party Services", + "paragraphs": [ + "Yumi may integrate third-party services for login, messaging, analytics, purchases, or social sharing. Their data practices are governed by their own terms and privacy notices." + ] + }, + { + "title": "13. Policy Updates", + "paragraphs": [ + "We may update this Privacy Policy from time to time. The updated version will be posted on this page with a revised effective date." + ] + } + ] + }, + "terms": { + "title": "Terms of Service", + "summary": "These Terms of Service govern access to and use of the Yumi app, website, H5 pages, and related services.", + "sections": [ + { + "title": "1. Acceptance of Terms", + "paragraphs": [ + "By accessing or using Yumi, you agree to comply with these Terms of Service and all applicable laws." + ] + }, + { + "title": "2. Eligibility", + "paragraphs": [ + "You must be legally permitted to use the service in your jurisdiction and satisfy the minimum age requirement applicable to your market." + ] + }, + { + "title": "3. Account Responsibilities", + "bullets": [ + "You are responsible for activity under your account.", + "You must provide accurate information where required.", + "You must not use Yumi for unlawful, abusive, fraudulent, or platform-disruptive behavior." + ] + }, + { + "title": "4. User Content and Room Activity", + "paragraphs": [ + "You retain responsibility for content you upload, publish, stream, or otherwise make available through Yumi, including room details, text, images, chat content, and other materials.", + "You must have the right to share the content you upload and the right to participate in or operate the room activity you initiate." + ] + }, + { + "title": "5. Purchases and Virtual Items", + "paragraphs": [ + "Certain services may involve paid content, subscriptions, in-app purchases, or virtual items. Additional purchase terms may apply through the relevant app-store provider.", + "Except where required by law or expressly stated otherwise, virtual items and in-app balances are non-transferable and non-refundable." + ] + }, + { + "title": "6. Moderation and Enforcement", + "paragraphs": [ + "We may suspend, restrict, remove, or terminate access to content, rooms, accounts, campaigns, or features where reasonably necessary to protect the platform, respond to abuse, or comply with legal or platform requirements." + ] + }, + { + "title": "7. Service Availability", + "paragraphs": [ + "We may modify, suspend, or discontinue any part of Yumi at any time. We do not guarantee that the service will always be uninterrupted or error-free." + ] + }, + { + "title": "8. Intellectual Property", + "paragraphs": [ + "All platform branding, software, design, trademarks, operational materials, and related content remain the property of Yumi or its licensors unless otherwise stated." + ] + }, + { + "title": "9. Disclaimer", + "paragraphs": [ + "To the maximum extent allowed by law, Yumi is provided on an as-available basis without warranties of any kind, whether express or implied." + ] + }, + { + "title": "10. Contact", + "paragraphs": [ + "For service questions or legal notices, contact postmaster@haiyinet.com.cn or call +85256283162." + ] + } + ] + }, + "deleteAccount": { + "title": "Delete Yumi Account", + "summary": "This page explains how Yumi users can request permanent deletion of their account and associated data.", + "sections": [ + { + "title": "1. Who can use this page", + "paragraphs": [ + "Use this page if you created a Yumi account and want to request permanent deletion of that account and the associated user data managed by Yumi.", + "You can submit a deletion request even if you no longer have access to the Yumi app on your device." + ] + }, + { + "title": "2. How to submit a deletion request", + "paragraphs": [ + "Send your deletion request by email to postmaster@haiyinet.com.cn.", + "Please use the subject line: Delete Yumi Account." + ], + "bullets": [ + "Your Yumi UID, if available.", + "Your registered email address or phone number.", + "Your Yumi nickname or profile name.", + "A clear statement that you want your Yumi account and associated data to be deleted." + ] + }, + { + "title": "3. Identity verification", + "paragraphs": [ + "To protect account security and prevent unauthorized deletion, we may ask you to complete a reasonable identity-verification step before processing your request." + ] + }, + { + "title": "4. What will be deleted", + "paragraphs": [ + "After your request is verified and approved, we will delete your Yumi account and the associated account data that is managed in our systems, including profile information and other user data linked to that account, unless retention is required for legitimate reasons described below." + ] + }, + { + "title": "5. Data that may be retained", + "paragraphs": [ + "Certain records may be retained for a limited period where reasonably necessary for fraud prevention, abuse handling, dispute resolution, financial recordkeeping, tax, security, or legal and regulatory compliance." + ] + }, + { + "title": "6. Processing time", + "paragraphs": [ + "We aim to complete verified deletion requests within 7 business days, although certain cases may require additional time if further verification or compliance review is necessary." + ] + }, + { + "title": "7. Contact", + "paragraphs": [ + "If you have questions about account deletion or personal-data handling, contact postmaster@haiyinet.com.cn or call +85256283162." + ] + } + ] + }, + "childSafety": { + "title": "Yumi Child Safety Standards", + "summary": "This page publishes Yumi's standards against child sexual abuse and exploitation, including reporting and enforcement expectations for the service.", + "sections": [ + { + "title": "1. Scope", + "paragraphs": [ + "This Child Safety Standards page applies to the Yumi application and related services operated by 環球互動科技有限公司 (Global Interactive Technology Co., Limited).", + "These standards are publicly published for users, reviewers, partners, and regulators who need to understand how Yumi addresses child sexual abuse and exploitation." + ] + }, + { + "title": "2. Zero-tolerance standard", + "paragraphs": [ + "Yumi has zero tolerance for child sexual abuse and exploitation (CSAE), child sexual abuse material (CSAM), grooming, sextortion involving minors, trafficking of children for sexual purposes, or any content or behavior that sexually exploits, abuses, or endangers children." + ] + }, + { + "title": "3. Prohibited content and behavior", + "paragraphs": [ + "Users may not create, upload, publish, distribute, request, promote, facilitate, or engage in any content or conduct involving the sexual exploitation or abuse of children." + ], + "bullets": [ + "Child sexual abuse material (CSAM), whether real, manipulated, or computer-generated where prohibited by law or policy.", + "Grooming, solicitation, or sexualized communication involving a minor.", + "Sextortion, blackmail, or coercion involving a minor.", + "Advertising, arranging, or facilitating trafficking or commercial sexual exploitation of children.", + "Any attempt to use Yumi to endanger, exploit, or abuse a child." + ] + }, + { + "title": "4. Reporting channels", + "paragraphs": [ + "Yumi provides in-app reporting and support pathways for users to report suspected violations.", + "Users, reviewers, or partners may also report child safety concerns by contacting postmaster@haiyinet.com.cn." + ] + }, + { + "title": "5. Enforcement and response", + "paragraphs": [ + "Yumi reviews reports and takes appropriate action when violations are identified or when the service obtains actual knowledge of prohibited child safety content or behavior." + ], + "bullets": [ + "Removing violating content.", + "Restricting, suspending, or permanently terminating offending accounts.", + "Preserving relevant records when reasonably necessary for investigation, legal compliance, or platform integrity.", + "Escalating confirmed violations to relevant authorities or reporting organizations where required by applicable law." + ] + }, + { + "title": "6. Contact information", + "paragraphs": [ + "Developer and operator: Global Interactive Technology Co., Limited.", + "Company email: postmaster@haiyinet.com.cn.", + "Support phone: +85256283162.", + "Registered address: 中国香港环球大厦A01." + ] + } + ] + } +} diff --git a/content/site.json b/content/site.json new file mode 100644 index 0000000..385e0ee --- /dev/null +++ b/content/site.json @@ -0,0 +1,128 @@ +{ + "generatedAt": "2026-04-13", + "brand": { + "name": "Yumi", + "strapline": "Internet technology and information services for live social products.", + "description": "Yumi is a live social product operated within an internet technology and information service model, combining voice rooms, direct chat, campaign H5 funnels, and community interaction tools." + }, + "company": { + "legalNameCn": "環球互動科技有限公司", + "legalNameEn": "Global Interactive Technology Co., Limited", + "address": "中国香港环球大厦A01", + "website": "https://www.haiyihy.com", + "h5Website": "https://h5.haiyihy.com", + "supportEmail": "postmaster@haiyinet.com.cn", + "privacyEmail": "postmaster@haiyinet.com.cn", + "phone": "+85256283162", + "industry": "Internet technology and information services" + }, + "app": { + "packageName": "com.org.yumi", + "googlePlayUrl": "https://play.google.com/store/apps/details?id=com.org.yumi", + "apiBase": "https://jvapi.haiyihy.com" + }, + "hero": { + "eyebrow": "Internet technology and information services", + "title": "To Build Trusted Digital Interaction Worldwide", + "summary": "Global Interactive Technology Co., Limited provides internet technology and information services for live social products, official web presence, and digital campaign delivery.", + "primaryCta": { + "label": "Open Google Play", + "href": "https://play.google.com/store/apps/details?id=com.org.yumi" + }, + "secondaryCta": { + "label": "Read Privacy Policy", + "href": "https://h5.haiyihy.com/privacy.html" + }, + "stats": [ + { + "value": "24/7", + "label": "always-on room activity design" + }, + { + "value": "Real-time", + "label": "voice, chat, gifting, and room state" + }, + { + "value": "One stack", + "label": "app, H5 campaigns, and public presence" + } + ] + }, + "pillars": [ + { + "title": "Live Voice Rooms", + "body": "Users can create rooms, go on mic, manage room roles, run themed sessions, and keep conversations active with lightweight social mechanics." + }, + { + "title": "Direct Chat and Social Graph", + "body": "Yumi supports private chat, social follow relationships, and community discovery flows that keep users returning between live sessions." + }, + { + "title": "Campaign-Ready H5 Pages", + "body": "Operational pages for hosts, agencies, invitations, rankings, and growth programs can be published quickly without rebuilding the app." + } + ], + "audiences": [ + { + "title": "Users", + "body": "Join rooms, meet people, chat, participate in events, and unlock social progression with a mobile-first experience." + }, + { + "title": "Hosts", + "body": "Run rooms, grow a following, manage member interactions, and activate campaigns from dedicated host-focused H5 funnels." + }, + { + "title": "Operators", + "body": "Launch landing pages, direct users into campaigns, and keep public legal pages aligned with app review and external communication requirements." + } + ], + "featureGrid": [ + { + "kicker": "Room lifecycle", + "title": "Create, enter, moderate, and personalize rooms.", + "body": "Room profiles, room announcements, room themes, room roles, and room-based monetization all sit inside the same engagement model." + }, + { + "kicker": "Social retention", + "title": "Keep users active after the conversation ends.", + "body": "Private chat, follow relationships, user discovery, and event-based rewards extend participation beyond a single room session." + }, + { + "kicker": "Growth operations", + "title": "Launch H5 campaigns without touching app binaries.", + "body": "Invite funnels, ranking pages, host programs, and agency acquisition pages can be edited as JSON-driven content and republished quickly." + } + ], + "operationLinks": [ + { + "title": "Host Application", + "href": "https://h5.haiyihy.com/apply/" + }, + { + "title": "Host Center", + "href": "https://h5.haiyihy.com/host-center/" + }, + { + "title": "Agency Center", + "href": "https://h5.haiyihy.com/agency-center/" + }, + { + "title": "Invite New User", + "href": "https://h5.haiyihy.com/invite-new-user/" + } + ], + "faq": [ + { + "question": "What is Yumi?", + "answer": "Yumi is a mobile social audio application focused on voice rooms, chat, community interaction, and in-app campaigns." + }, + { + "question": "Where can I find legal information?", + "answer": "You can review the Privacy Policy and Terms of Service directly on this site. These pages are intended to be used both by end users and app review teams." + }, + { + "question": "How are H5 pages managed?", + "answer": "The current setup uses one JSON content file per landing page. That keeps publishing fast and avoids coupling campaign updates to Flutter app releases." + } + ] +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..ceb776d --- /dev/null +++ b/package.json @@ -0,0 +1,9 @@ +{ + "name": "haiyihy-public-site", + "private": true, + "type": "module", + "scripts": { + "build": "node ./scripts/build.mjs", + "new:landing": "node ./scripts/new-landing-page.mjs" + } +} diff --git a/scripts/build.mjs b/scripts/build.mjs new file mode 100755 index 0000000..1548ec9 --- /dev/null +++ b/scripts/build.mjs @@ -0,0 +1,552 @@ +#!/usr/bin/env node + +import fs from 'node:fs'; +import path from 'node:path'; + +const root = path.resolve(path.dirname(new URL(import.meta.url).pathname), '..'); +const distDir = path.join(root, 'dist'); +const wwwDir = path.join(distDir, 'www'); +const h5Dir = path.join(distDir, 'h5'); + +const siteContent = readJson(path.join(root, 'content', 'site.json')); +const legalContent = readJson(path.join(root, 'content', 'legal.json')); +const H5_PRIVACY_URL = `${siteContent.company.h5Website}/privacy.html`; +const H5_TERMS_URL = `${siteContent.company.h5Website}/terms.html`; +const H5_DELETE_ACCOUNT_URL = `${siteContent.company.h5Website}/delete-account.html`; +const H5_CHILD_SAFETY_URL = `${siteContent.company.h5Website}/child-safety.html`; +const landingPages = fs + .readdirSync(path.join(root, 'content', 'landing-pages')) + .filter((file) => file.endsWith('.json')) + .map((file) => readJson(path.join(root, 'content', 'landing-pages', file))) + .sort((a, b) => a.slug.localeCompare(b.slug)); + +build(); + +function build() { + fs.rmSync(distDir, { force: true, recursive: true }); + ensureDir(path.join(wwwDir, 'assets')); + ensureDir(path.join(h5Dir, 'assets')); + copyFile(path.join(root, 'src', 'site.css'), path.join(wwwDir, 'assets', 'site.css')); + copyFile(path.join(root, 'src', 'site.css'), path.join(h5Dir, 'assets', 'site.css')); + + writeHtml(path.join(wwwDir, 'index.html'), renderHomePage()); + writeHtml( + path.join(wwwDir, 'privacy', 'index.html'), + renderRedirectPage({ + title: 'Privacy Policy Redirect', + target: H5_PRIVACY_URL, + }), + ); + writeHtml( + path.join(wwwDir, 'terms', 'index.html'), + renderRedirectPage({ + title: 'Terms of Service Redirect', + target: H5_TERMS_URL, + }), + ); + writeHtml( + path.join(wwwDir, 'delete-account', 'index.html'), + renderRedirectPage({ + title: 'Delete Account Redirect', + target: H5_DELETE_ACCOUNT_URL, + }), + ); + writeHtml( + path.join(wwwDir, 'child-safety', 'index.html'), + renderRedirectPage({ + title: 'Child Safety Standards Redirect', + target: H5_CHILD_SAFETY_URL, + }), + ); + writeText(path.join(wwwDir, 'robots.txt'), 'User-agent: *\nAllow: /\n'); + writeText(path.join(wwwDir, 'sitemap.xml'), renderSiteMap()); + + writeHtml(path.join(h5Dir, 'index.html'), renderLandingIndexPage()); + writeHtml( + path.join(h5Dir, 'privacy.html'), + renderPlainLegalPage('privacy', { + canonical: H5_PRIVACY_URL, + }), + ); + writeHtml( + path.join(h5Dir, 'terms.html'), + renderLegalPage('terms', { + canonical: H5_TERMS_URL, + homeHref: '/', + privacyHref: '/privacy.html', + termsHref: '/terms.html', + primaryActionHref: 'https://www.haiyihy.com/', + primaryActionLabel: 'Official Site', + }), + ); + writeHtml( + path.join(h5Dir, 'delete-account.html'), + renderPlainLegalPage('deleteAccount', { + canonical: H5_DELETE_ACCOUNT_URL, + }), + ); + writeHtml( + path.join(h5Dir, 'child-safety.html'), + renderPlainLegalPage('childSafety', { + canonical: H5_CHILD_SAFETY_URL, + }), + ); + writeHtml(path.join(h5Dir, 'studio', 'index.html'), renderStudioPage()); + writeText(path.join(h5Dir, 'data', 'pages.json'), `${JSON.stringify(landingPages, null, 2)}\n`); + + for (const page of landingPages) { + writeHtml(path.join(h5Dir, page.slug, 'index.html'), renderLandingPage(page)); + } + + console.log(`Built public site to ${distDir}`); +} + +function renderHomePage() { + const { brand, company, hero } = siteContent; + return renderDocument({ + title: `${brand.name} | Official Website`, + description: brand.description, + canonical: `${company.website}/`, + hostRoot: '', + theme: 'ember', + bodyClass: 'corp-body', + body: ` +
+
+ +
+ +
+
+
+
+
+
${escapeHtml(hero.eyebrow)}
+

${escapeHtml(hero.title)}

+

${escapeHtml(hero.summary)}

+
+
+ +
+
+
+ About us +

${escapeHtml(company.legalNameEn)}

+

${escapeHtml(company.legalNameCn)}

+

${escapeHtml(company.address)}

+
+ + +
+
+
+
+ `, + }); +} + +function renderLegalPage(kind, options = {}) { + const { company, brand } = siteContent; + const doc = legalContent[kind]; + const canonical = options.canonical || `${company.website}/${kind}/`; + const homeHref = options.homeHref || '/'; + const privacyHref = options.privacyHref || H5_PRIVACY_URL; + const termsHref = options.termsHref || H5_TERMS_URL; + const primaryActionHref = + options.primaryActionHref || 'https://play.google.com/store/apps/details?id=com.org.yumi'; + const primaryActionLabel = options.primaryActionLabel || 'Get Yumi'; + return renderDocument({ + title: `${doc.title} | ${brand.name}`, + description: doc.summary, + canonical, + hostRoot: '', + theme: kind === 'privacy' ? 'steel' : 'ink', + body: ` + ${renderHeader({ + brandName: brand.name, + subline: doc.title, + links: [ + { href: homeHref, label: 'Home' }, + { href: privacyHref, label: 'Privacy' }, + { href: termsHref, label: 'Terms' }, + ], + action: { href: primaryActionHref, label: primaryActionLabel }, + })} +
+
${escapeHtml(kind === 'privacy' ? 'Legal disclosure' : 'Service terms')}
+

${escapeHtml(doc.title)}

+ + + +
+ ${renderFooter({ company, brandName: brand.name })} + `, + }); +} + +function renderPlainLegalPage(kind, options = {}) { + const { company, brand } = siteContent; + const doc = legalContent[kind]; + const canonical = options.canonical || `${company.h5Website}/${kind}.html`; + return ` + + + + + ${escapeHtml(doc.title)} | ${escapeHtml(brand.name)} + + + + +

${escapeHtml(doc.title)}

+

Effective date: ${escapeHtml(legalContent.effectiveDate)}

+

Operator: ${escapeHtml(company.legalNameEn)} (${escapeHtml(company.legalNameCn)})

+

Address: ${escapeHtml(company.address)}

+

Contact: ${escapeHtml(company.privacyEmail)} / ${escapeHtml(company.phone)}

+ ${kind === 'deleteAccount' ? `

Start a deletion request by email

` : ''} +

${escapeHtml(doc.summary)}

+ ${doc.sections.map((section) => renderPlainLegalSection(section)).join('\n')} + +`; +} + +function renderLandingIndexPage() { + const { brand, company } = siteContent; + return renderDocument({ + title: `${brand.name} H5 | Campaign Pages`, + description: 'Index of JSON-driven Yumi landing pages.', + canonical: `${company.h5Website}/`, + hostRoot: '', + theme: 'sapphire', + body: ` + ${renderHeader({ + brandName: brand.name, + subline: 'H5 landing pages', + links: [ + { href: '/', label: 'H5 Home' }, + { href: '/studio/', label: 'Studio' }, + { href: 'https://www.haiyihy.com/', label: 'Official Site' }, + ], + action: { href: H5_PRIVACY_URL, label: 'Privacy' }, + })} +
+
Operations and campaigns
+

A structured index of all public H5 destinations.

+

Each page below is generated from a single JSON file, which keeps campaign updates lightweight and predictable.

+
+ ${landingPages.map((page) => ` + + ${escapeHtml(page.eyebrow)} +

${escapeHtml(page.title)}

+

${escapeHtml(page.summary)}

+ Open page +
+ `).join('')} +
+
+ ${renderFooter({ company, brandName: brand.name })} + `, + }); +} + +function renderLandingPage(page) { + const { brand, company } = siteContent; + return renderDocument({ + title: `${page.title} | ${brand.name} H5`, + description: page.summary, + canonical: `${company.h5Website}/${page.slug}/`, + hostRoot: '../', + theme: page.theme || 'ember', + body: ` + ${renderHeader({ + brandName: brand.name, + subline: 'H5 landing page', + links: [ + { href: '/', label: 'H5 Home' }, + { href: '/studio/', label: 'Studio' }, + { href: 'https://www.haiyihy.com/', label: 'Official Site' }, + ], + action: { href: H5_PRIVACY_URL, label: 'Privacy' }, + })} +
+
${escapeHtml(page.eyebrow)}
+

${escapeHtml(page.title)}

+

${escapeHtml(page.summary)}

+
+ ${page.stats.map((item) => `
${escapeHtml(item.value)} · ${escapeHtml(item.label)}
`).join('')} +
+
+

${escapeHtml(page.highlight)}

+
+
+ ${escapeHtml(page.cta.label)} + ${escapeHtml(page.secondaryCta.label)} +
+
+ ${page.sections.map((section) => ` +
+

${escapeHtml(section.title)}

+

${escapeHtml(section.body)}

+ ${renderBullets(section.bullets)} +
+ `).join('')} +
+
+ ${renderFooter({ company, brandName: brand.name })} + `, + }); +} + +function renderStudioPage() { + const { brand, company } = siteContent; + return renderDocument({ + title: `${brand.name} H5 Studio`, + description: 'Editorial cockpit for landing page content files.', + canonical: `${company.h5Website}/studio/`, + hostRoot: '../', + theme: 'forest', + body: ` + ${renderHeader({ + brandName: brand.name, + subline: 'Content studio', + links: [ + { href: '/', label: 'H5 Home' }, + { href: 'https://www.haiyihy.com/', label: 'Official Site' }, + { href: H5_PRIVACY_URL, label: 'Privacy' }, + ], + action: { href: '/', label: 'View Pages' }, + })} +
+
Editorial cockpit
+

Manage landing pages from JSON, not from page code.

+

This page is a publishing reference for operators and developers. It documents the current slugs and shows the exact JSON structure that drives each landing page.

+
+ Current management mode is file-driven. Edit JSON under public-site/content/landing-pages, rebuild the site, and redeploy. This keeps operations fast while avoiding app-release coupling. +
+
+ ${landingPages.map((page) => ` +
+ ${escapeHtml(page.slug)} +

${escapeHtml(page.title)}

+

${escapeHtml(page.summary)}

+ +
${escapeHtml(JSON.stringify(page, null, 2))}
+
+ `).join('')} +
+
+ ${renderFooter({ company, brandName: brand.name })} + `, + }); +} + +function renderHeader({ brandName, subline, links, action }) { + return ` + + `; +} + +function renderFooter({ company, brandName }) { + return ` + + `; +} + +function renderDocument({ title, description, canonical, theme, hostRoot, body, bodyClass = '' }) { + return ` + + + + + ${escapeHtml(title)} + + + + + + ${body} + + +`; +} + +function renderParagraphs(paragraphs = []) { + return paragraphs.map((paragraph) => `

${escapeHtml(paragraph)}

`).join(''); +} + +function renderBullets(bullets = []) { + if (!bullets?.length) { + return ''; + } + return ``; +} + +function renderPlainLegalSection(section) { + return ` +

${escapeHtml(section.title)}

+ ${renderParagraphs(section.paragraphs)} + ${renderPlainBullets(section.bullets)} + `; +} + +function renderPlainBullets(bullets = []) { + if (!bullets?.length) { + return ''; + } + return bullets.map((item) => `

- ${escapeHtml(item)}

`).join(''); +} + +function renderDeleteAccountMailto(email) { + const subject = encodeURIComponent('Delete Yumi Account'); + return `mailto:${email}?subject=${subject}`; +} + +function renderSiteMap() { + const pages = [ + 'https://www.haiyihy.com/', + 'https://h5.haiyihy.com/', + H5_PRIVACY_URL, + H5_TERMS_URL, + H5_DELETE_ACCOUNT_URL, + H5_CHILD_SAFETY_URL, + 'https://h5.haiyihy.com/studio/', + ...landingPages.map((page) => `https://h5.haiyihy.com/${page.slug}/`), + ]; + return ` + +${pages.map((url) => ` ${url}`).join('\n')} + +`; +} + +function renderRedirectPage({ title, target }) { + return ` + + + + + + ${escapeHtml(title)} + + + + +

Redirecting to ${escapeHtml(target)}...

+ + +`; +} + +function readJson(filePath) { + return JSON.parse(fs.readFileSync(filePath, 'utf8')); +} + +function ensureDir(dirPath) { + fs.mkdirSync(dirPath, { recursive: true }); +} + +function writeHtml(filePath, content) { + ensureDir(path.dirname(filePath)); + fs.writeFileSync(filePath, `${content}\n`, 'utf8'); +} + +function writeText(filePath, content) { + ensureDir(path.dirname(filePath)); + fs.writeFileSync(filePath, content, 'utf8'); +} + +function copyFile(source, target) { + ensureDir(path.dirname(target)); + fs.copyFileSync(source, target); +} + +function escapeHtml(value = '') { + return String(value) + .replaceAll('&', '&') + .replaceAll('<', '<') + .replaceAll('>', '>') + .replaceAll('"', '"') + .replaceAll("'", '''); +} + +function escapeAttr(value = '') { + return escapeHtml(value); +} diff --git a/scripts/new-landing-page.mjs b/scripts/new-landing-page.mjs new file mode 100755 index 0000000..f6a6ff3 --- /dev/null +++ b/scripts/new-landing-page.mjs @@ -0,0 +1,61 @@ +#!/usr/bin/env node + +import fs from 'node:fs'; +import path from 'node:path'; + +const slug = process.argv[2]; + +if (!slug) { + console.error('Usage: node ./scripts/new-landing-page.mjs '); + process.exit(1); +} + +if (!/^[a-z0-9-]+$/.test(slug)) { + console.error('Slug must use lowercase letters, numbers, and hyphens only.'); + process.exit(1); +} + +const root = path.resolve(path.dirname(new URL(import.meta.url).pathname), '..'); +const target = path.join(root, 'content', 'landing-pages', `${slug}.json`); + +if (fs.existsSync(target)) { + console.error(`Landing page already exists: ${target}`); + process.exit(1); +} + +const template = { + slug, + theme: 'ember', + eyebrow: 'Campaign', + title: 'Add your landing page headline here.', + summary: 'Summarize what this page is for and what action the visitor should take.', + highlight: 'One concise line that explains the page value.', + cta: { + label: 'Primary action', + href: 'https://www.haiyihy.com/', + }, + secondaryCta: { + label: 'Secondary action', + href: 'https://h5.haiyihy.com/', + }, + stats: [ + { + value: 'Fast', + label: 'update-safe content model', + }, + { + value: 'JSON', + label: 'driven page editing', + }, + ], + sections: [ + { + title: 'Section title', + body: 'Describe the section content here.', + bullets: ['Optional bullet 1', 'Optional bullet 2'], + }, + ], +}; + +fs.writeFileSync(target, `${JSON.stringify(template, null, 2)}\n`, 'utf8'); +console.log(`Created ${target}`); diff --git a/src/site.css b/src/site.css new file mode 100644 index 0000000..46c4e80 --- /dev/null +++ b/src/site.css @@ -0,0 +1,757 @@ +:root { + --bg: oklch(0.98 0.01 95); + --bg-soft: oklch(0.95 0.02 95); + --surface: rgba(255, 255, 255, 0.88); + --surface-strong: rgba(255, 255, 255, 0.96); + --line: rgba(83, 64, 40, 0.12); + --text: oklch(0.27 0.03 60); + --text-soft: oklch(0.49 0.03 60); + --accent: oklch(0.67 0.16 40); + --accent-soft: color-mix(in oklab, var(--accent) 14%, white); + --accent-strong: color-mix(in oklab, var(--accent) 80%, black); + --shadow: 0 28px 72px rgba(66, 43, 24, 0.12); + --radius-xl: 32px; + --radius-lg: 24px; + --radius-md: 18px; + --font-display: "Iowan Old Style", "Palatino Linotype", "Book Antiqua", "Times New Roman", serif; + --font-body: "Avenir Next", "Segoe UI", "PingFang SC", "Helvetica Neue", sans-serif; +} + +* { + box-sizing: border-box; +} + +html { + scroll-behavior: smooth; +} + +body { + margin: 0; + min-height: 100vh; + color: var(--text); + font-family: var(--font-body); + background: + radial-gradient(circle at top left, color-mix(in oklab, var(--accent) 12%, transparent) 0, transparent 34rem), + radial-gradient(circle at bottom right, color-mix(in oklab, var(--accent) 10%, transparent) 0, transparent 28rem), + linear-gradient(180deg, var(--bg) 0%, oklch(0.965 0.01 95) 100%); +} + +body.corp-body { + background: #070c12; + color: #f3f6fb; +} + +body[data-theme='sapphire'] { --accent: oklch(0.6 0.15 246); } +body[data-theme='forest'] { --accent: oklch(0.62 0.14 155); } +body[data-theme='ink'] { --accent: oklch(0.46 0.04 254); } +body[data-theme='gold'] { --accent: oklch(0.77 0.15 85); } +body[data-theme='steel'] { --accent: oklch(0.58 0.04 250); } +body[data-theme='violet'] { --accent: oklch(0.62 0.18 314); } +body[data-theme='berry'] { --accent: oklch(0.61 0.2 355); } +body[data-theme='rose'] { --accent: oklch(0.69 0.17 18); } +body[data-theme='teal'] { --accent: oklch(0.66 0.13 200); } +body[data-theme='mint'] { --accent: oklch(0.75 0.13 160); } +body[data-theme='ember'] { --accent: oklch(0.68 0.18 35); } + +a { + color: inherit; +} + +img { + max-width: 100%; + display: block; +} + +.shell { + width: min(1180px, calc(100% - 32px)); + margin: 0 auto; +} + +.site-header { + position: sticky; + top: 0; + z-index: 10; + backdrop-filter: blur(18px); + background: rgba(250, 248, 243, 0.72); + border-bottom: 1px solid rgba(83, 64, 40, 0.08); +} + +.site-header__inner { + display: flex; + align-items: center; + justify-content: space-between; + gap: 16px; + min-height: 74px; +} + +.brand { + display: inline-flex; + align-items: center; + gap: 12px; + text-decoration: none; +} + +.brand__mark { + width: 42px; + height: 42px; + border-radius: 14px; + background: linear-gradient(135deg, color-mix(in oklab, var(--accent) 74%, white) 0%, color-mix(in oklab, var(--accent) 48%, black) 100%); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.65), 0 12px 28px color-mix(in oklab, var(--accent) 26%, transparent); +} + +.brand__text { + display: grid; + gap: 2px; +} + +.brand__name { + font-family: var(--font-display); + font-size: 1.15rem; + font-weight: 700; +} + +.brand__sub { + color: var(--text-soft); + font-size: 0.88rem; +} + +.header-links { + display: flex; + align-items: center; + gap: 12px; + flex-wrap: wrap; +} + +.header-links a { + color: var(--text-soft); + text-decoration: none; + font-size: 0.94rem; +} + +.header-pill { + display: inline-flex; + align-items: center; + gap: 8px; + padding: 11px 15px; + border-radius: 999px; + background: color-mix(in oklab, var(--accent) 9%, white); + color: var(--accent-strong); + text-decoration: none; + font-weight: 600; +} + +.hero { + padding: clamp(52px, 8vw, 92px) 0 48px; +} + +.hero-grid { + display: grid; + grid-template-columns: 1.2fr 0.8fr; + gap: clamp(24px, 4vw, 48px); + align-items: start; +} + +.eyebrow { + display: inline-flex; + align-items: center; + gap: 10px; + padding: 10px 14px; + border-radius: 999px; + background: color-mix(in oklab, var(--accent) 9%, white); + color: var(--accent-strong); + font-size: 0.85rem; + letter-spacing: 0.08em; + text-transform: uppercase; +} + +.hero h1, +.section-title, +.legal-title, +.landing-title { + margin: 18px 0 0; + font-family: var(--font-display); + font-size: clamp(2.8rem, 6vw, 5.6rem); + line-height: 0.94; + letter-spacing: -0.03em; +} + +.hero p.lead, +.landing-lead, +.legal-summary { + margin: 18px 0 0; + max-width: 62ch; + font-size: clamp(1.03rem, 1.5vw, 1.22rem); + line-height: 1.8; + color: var(--text-soft); +} + +.actions { + display: flex; + flex-wrap: wrap; + gap: 14px; + margin-top: 30px; +} + +.button, +.button-secondary { + display: inline-flex; + align-items: center; + justify-content: center; + min-height: 52px; + padding: 0 22px; + border-radius: 999px; + font-weight: 700; + text-decoration: none; + transition: transform 180ms ease, box-shadow 180ms ease, background 180ms ease; +} + +.button { + background: linear-gradient(135deg, color-mix(in oklab, var(--accent) 85%, white), color-mix(in oklab, var(--accent) 50%, black)); + color: white; + box-shadow: 0 18px 42px color-mix(in oklab, var(--accent) 22%, transparent); +} + +.button-secondary { + background: rgba(255, 255, 255, 0.64); + color: var(--text); + border: 1px solid rgba(83, 64, 40, 0.1); +} + +.button:hover, +.button-secondary:hover, +.header-pill:hover { + transform: translateY(-1px); +} + +.glass-panel { + border-radius: var(--radius-xl); + padding: 26px; + background: linear-gradient(180deg, var(--surface-strong), var(--surface)); + border: 1px solid rgba(83, 64, 40, 0.08); + box-shadow: var(--shadow); +} + +.hero-card { + display: grid; + gap: 18px; +} + +.metric-grid, +.card-grid, +.audience-grid, +.link-grid, +.landing-grid { + display: grid; + gap: 18px; +} + +.metric-grid { + grid-template-columns: repeat(3, minmax(0, 1fr)); + margin-top: 34px; +} + +.metric { + padding: 18px 20px; + border-radius: var(--radius-md); + background: rgba(255, 255, 255, 0.55); + border: 1px solid rgba(83, 64, 40, 0.08); +} + +.metric strong { + display: block; + font-size: 1.25rem; + font-family: var(--font-display); +} + +.metric span { + display: block; + margin-top: 6px; + color: var(--text-soft); + line-height: 1.6; + font-size: 0.94rem; +} + +.section { + padding: 34px 0 26px; +} + +.section-head { + display: flex; + align-items: end; + justify-content: space-between; + gap: 18px; + margin-bottom: 22px; +} + +.section-kicker { + margin: 0; + color: var(--text-soft); + line-height: 1.7; + max-width: 50ch; +} + +.card-grid { + grid-template-columns: repeat(3, minmax(0, 1fr)); +} + +.audience-grid { + grid-template-columns: repeat(3, minmax(0, 1fr)); +} + +.landing-grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); +} + +.card, +.link-card, +.section-card { + border-radius: var(--radius-lg); + padding: 22px; + background: rgba(255, 255, 255, 0.66); + border: 1px solid rgba(83, 64, 40, 0.08); + box-shadow: 0 12px 32px rgba(89, 62, 33, 0.06); +} + +.card small, +.section-card small { + display: inline-block; + color: var(--accent-strong); + letter-spacing: 0.08em; + text-transform: uppercase; + font-size: 0.78rem; + margin-bottom: 12px; +} + +.card h3, +.link-card h3, +.section-card h3, +.faq-item h3 { + margin: 0; + font-size: 1.18rem; +} + +.card p, +.link-card p, +.section-card p, +.faq-item p, +.site-footer p, +.legal-block p, +.legal-block li { + color: var(--text-soft); + line-height: 1.78; +} + +.link-grid { + grid-template-columns: repeat(4, minmax(0, 1fr)); +} + +.link-card { + text-decoration: none; +} + +.link-card span { + color: var(--accent-strong); + font-weight: 700; +} + +.faq-grid { + display: grid; + gap: 14px; +} + +.faq-item { + padding: 20px 22px; + border-radius: var(--radius-lg); + background: rgba(255, 255, 255, 0.62); + border: 1px solid rgba(83, 64, 40, 0.08); +} + +.site-footer { + padding: 38px 0 70px; +} + +.corp-home { + min-height: 100vh; + background: #05080d; +} + +.corp-header { + position: absolute; + inset: 0 0 auto 0; + z-index: 20; + background: rgba(0, 0, 0, 0.9); + border-bottom: 1px solid rgba(255, 255, 255, 0.08); +} + +.corp-header__inner { + display: flex; + align-items: center; + justify-content: space-between; + gap: 24px; + min-height: 78px; +} + +.corp-brand { + display: inline-flex; + align-items: center; + gap: 14px; + color: #fff; + text-decoration: none; +} + +.corp-brand__mark { + width: 40px; + height: 40px; + border-radius: 12px; + background: + linear-gradient(135deg, #1742ff 0 34%, transparent 34% 44%, #00b8ff 44% 68%, transparent 68% 78%, #ff9e2d 78% 100%); + box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.08); +} + +.corp-brand__text { + display: grid; + gap: 2px; +} + +.corp-brand__text strong { + font-size: 1.18rem; + letter-spacing: 0.02em; +} + +.corp-brand__text span { + color: rgba(255, 255, 255, 0.58); + font-size: 0.78rem; + letter-spacing: 0.08em; + text-transform: uppercase; +} + +.corp-nav { + display: flex; + align-items: center; + gap: clamp(14px, 2vw, 34px); + flex-wrap: wrap; +} + +.corp-nav a { + color: rgba(255, 255, 255, 0.86); + text-decoration: none; + text-transform: uppercase; + letter-spacing: 0.08em; + font-size: 0.82rem; +} + +.corp-main { + min-height: 100vh; +} + +.corp-hero { + position: relative; + min-height: 100vh; + display: flex; + align-items: center; + overflow: hidden; + background: + linear-gradient(180deg, rgba(5, 8, 13, 0.18) 0%, rgba(5, 8, 13, 0.76) 100%), + linear-gradient(135deg, rgba(12, 18, 28, 0.88) 0%, rgba(3, 7, 14, 0.72) 100%); +} + +.corp-hero__image, +.corp-hero__overlay { + position: absolute; + inset: 0; +} + +.corp-hero__image { + background: + linear-gradient(180deg, rgba(4, 8, 14, 0.24), rgba(4, 8, 14, 0.58)), + radial-gradient(circle at 16% 22%, rgba(39, 83, 132, 0.3), transparent 24rem), + radial-gradient(circle at 82% 20%, rgba(32, 130, 182, 0.24), transparent 20rem), + url("https://images.unsplash.com/photo-1522202176988-66273c2fd55f?auto=format&fit=crop&w=1800&q=80") center / cover no-repeat; + transform: scale(1.02); + filter: saturate(0.78) brightness(0.72); +} + +.corp-hero__overlay { + background: + linear-gradient(180deg, rgba(0, 0, 0, 0.14), rgba(0, 0, 0, 0.32)), + linear-gradient(90deg, rgba(7, 10, 15, 0.7), rgba(7, 10, 15, 0.18) 36%, rgba(7, 10, 15, 0.54)); +} + +.corp-hero__content { + position: relative; + z-index: 2; + display: grid; + place-items: center; + padding: 150px 0 110px; + text-align: center; +} + +.corp-hero__eyebrow { + color: rgba(255, 255, 255, 0.75); + letter-spacing: 0.18em; + text-transform: uppercase; + font-size: 0.84rem; +} + +.corp-hero__title { + margin: 18px 0 0; + max-width: 12ch; + color: #fff; + font-family: "SF Pro Display", "Avenir Next", "Helvetica Neue", sans-serif; + font-weight: 500; + font-size: clamp(3.8rem, 8vw, 6.9rem); + line-height: 1.14; + letter-spacing: -0.03em; +} + +.corp-hero__summary { + margin: 26px 0 0; + max-width: 70ch; + color: rgba(255, 255, 255, 0.8); + font-size: clamp(1rem, 1.5vw, 1.14rem); + line-height: 1.9; +} + +.corp-strip { + position: relative; + z-index: 3; + margin-top: -1px; + background: #06090f; + border-top: 1px solid rgba(255, 255, 255, 0.08); +} + +.corp-strip__grid { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 0; +} + +.corp-strip__item { + padding: 34px 28px 38px; + border-right: 1px solid rgba(255, 255, 255, 0.08); +} + +.corp-strip__item:last-child { + border-right: none; +} + +.corp-strip__item small { + display: inline-block; + margin-bottom: 14px; + color: rgba(255, 255, 255, 0.46); + letter-spacing: 0.12em; + text-transform: uppercase; + font-size: 0.76rem; +} + +.corp-strip__item h2 { + margin: 0 0 14px; + color: #fff; + font-size: 1.26rem; + font-weight: 600; + line-height: 1.5; +} + +.corp-strip__item p, +.corp-strip__item a { + margin: 0 0 10px; + color: rgba(255, 255, 255, 0.74); + text-decoration: none; + line-height: 1.8; +} + +.footer-grid { + display: grid; + grid-template-columns: 1.1fr 0.9fr; + gap: 24px; + padding: 28px; + border-radius: var(--radius-xl); + background: linear-gradient(180deg, rgba(255, 255, 255, 0.82), rgba(255, 255, 255, 0.7)); + border: 1px solid rgba(83, 64, 40, 0.08); +} + +.footer-links { + display: flex; + flex-wrap: wrap; + gap: 12px 18px; +} + +.footer-links a { + color: var(--text-soft); + text-decoration: none; +} + +.legal-shell, +.landing-shell, +.studio-shell { + padding: clamp(38px, 7vw, 78px) 0 72px; +} + +.legal-meta, +.landing-meta { + display: flex; + flex-wrap: wrap; + gap: 10px; + margin-top: 20px; +} + +.meta-chip { + padding: 10px 14px; + border-radius: 999px; + background: rgba(255, 255, 255, 0.68); + border: 1px solid rgba(83, 64, 40, 0.08); + color: var(--text-soft); +} + +.legal-stack, +.landing-stack { + display: grid; + gap: 18px; + margin-top: 30px; +} + +.legal-block ul, +.section-card ul { + margin: 12px 0 0; + padding-left: 20px; +} + +.legal-block li, +.section-card li { + margin: 8px 0; +} + +.landing-banner { + margin-top: 28px; + padding: 24px; + border-radius: var(--radius-xl); + background: + linear-gradient(135deg, color-mix(in oklab, var(--accent) 14%, white), rgba(255, 255, 255, 0.72)), + radial-gradient(circle at top right, color-mix(in oklab, var(--accent) 30%, transparent), transparent 14rem); + border: 1px solid rgba(83, 64, 40, 0.08); +} + +.landing-banner p { + margin: 0; + color: var(--accent-strong); + font-size: 1.02rem; + line-height: 1.72; +} + +.studio-toolbar { + display: flex; + flex-wrap: wrap; + gap: 14px; + justify-content: space-between; + align-items: center; + margin-top: 24px; +} + +.studio-note { + padding: 18px 20px; + border-radius: var(--radius-lg); + background: rgba(255, 255, 255, 0.68); + border: 1px solid rgba(83, 64, 40, 0.08); + color: var(--text-soft); +} + +.code-block { + margin-top: 14px; + padding: 16px; + border-radius: 20px; + background: oklch(0.18 0.02 250); + color: oklch(0.95 0.01 250); + overflow: auto; + font-size: 0.88rem; + line-height: 1.6; +} + +@media (max-width: 980px) { + .hero-grid, + .footer-grid, + .landing-grid, + .card-grid, + .audience-grid, + .link-grid { + grid-template-columns: 1fr; + } + + .metric-grid { + grid-template-columns: 1fr; + } + + .section-head { + align-items: start; + flex-direction: column; + } + + .corp-header__inner, + .corp-strip__grid { + grid-template-columns: 1fr; + } + + .corp-header__inner { + flex-direction: column; + align-items: start; + padding: 14px 0; + } + + .corp-strip__item { + border-right: none; + border-bottom: 1px solid rgba(255, 255, 255, 0.08); + } + + .corp-strip__item:last-child { + border-bottom: none; + } +} + +@media (max-width: 640px) { + .shell { + width: min(100% - 22px, 1180px); + } + + .site-header__inner { + min-height: 66px; + } + + .hero h1, + .section-title, + .legal-title, + .landing-title { + font-size: clamp(2.3rem, 12vw, 3.5rem); + } + + .glass-panel, + .card, + .link-card, + .section-card, + .faq-item, + .footer-grid, + .landing-banner { + padding: 18px; + border-radius: 22px; + } + + .corp-nav { + gap: 10px 16px; + } + + .corp-nav a { + font-size: 0.74rem; + } + + .corp-hero__content { + padding: 140px 0 84px; + } + + .corp-hero__title { + max-width: 10ch; + font-size: clamp(2.9rem, 13vw, 4.6rem); + } + + .corp-hero__summary { + max-width: 34ch; + line-height: 1.75; + } +}