diff --git a/custom.d.ts b/custom.d.ts index 4000901..d3f4b12 100644 --- a/custom.d.ts +++ b/custom.d.ts @@ -1,4 +1,4 @@ declare module "*.md" { - const value: string; - export default value; -} \ No newline at end of file + const value: string; + export default value; +} diff --git a/src/api.ts b/src/api.ts index 44e4adc..8317608 100644 --- a/src/api.ts +++ b/src/api.ts @@ -102,7 +102,7 @@ export class LNVpsApi { constructor( readonly url: string, readonly publisher: EventPublisher | undefined, - ) { } + ) {} async getAccount() { const { data } = await this.#handleResponse>( diff --git a/src/components/markdown.css b/src/components/markdown.css index 2ba5664..4355498 100644 --- a/src/components/markdown.css +++ b/src/components/markdown.css @@ -36,4 +36,4 @@ .markdown h5, .markdown h6 { margin: 0.5em 0; -} \ No newline at end of file +} diff --git a/src/components/markdown.tsx b/src/components/markdown.tsx index 12e2b53..6b96f3e 100644 --- a/src/components/markdown.tsx +++ b/src/components/markdown.tsx @@ -5,130 +5,178 @@ import { Token, Tokens, marked } from "marked"; import { Link } from "react-router-dom"; interface MarkdownProps { - content: string; + content: string; } -const Markdown = forwardRef((props: MarkdownProps, ref) => { +const Markdown = forwardRef( + (props: MarkdownProps, ref) => { let ctr = 0; function renderToken(t: Token): ReactNode { - try { - switch (t.type) { - case "paragraph": { - return
{t.tokens ? t.tokens.map(renderToken) : t.raw}
; - } - case "image": { - return ; - } - case "heading": { - switch (t.depth) { - case 1: - return

{t.tokens ? t.tokens.map(renderToken) : t.raw}

; - case 2: - return

{t.tokens ? t.tokens.map(renderToken) : t.raw}

; - case 3: - return

{t.tokens ? t.tokens.map(renderToken) : t.raw}

; - case 4: - return

{t.tokens ? t.tokens.map(renderToken) : t.raw}

; - case 5: - return
{t.tokens ? t.tokens.map(renderToken) : t.raw}
; - case 6: - return
{t.tokens ? t.tokens.map(renderToken) : t.raw}
; - } - throw new Error("Invalid heading"); - } - case "codespan": { - return {t.raw}; - } - case "code": { - return
{t.raw}
; - } - case "br": { - return
; - } - case "hr": { - return
; - } - case "strong": { - return {t.tokens ? t.tokens.map(renderToken) : t.raw}; - } - case "blockquote": { - return
{t.tokens ? t.tokens.map(renderToken) : t.raw}
; - } - case "link": { - return ( - - {t.tokens ? t.tokens.map(renderToken) : t.raw} - - ); - } - case "list": { - if (t.ordered) { - return
    {t.items.map(renderToken)}
; - } else { - return
    {t.items.map(renderToken)}
; - } - } - case "list_item": { - return
  • {t.tokens ? t.tokens.map(renderToken) : t.raw}
  • ; - } - case "em": { - return {t.tokens ? t.tokens.map(renderToken) : t.raw}; - } - case "del": { - return {t.tokens ? t.tokens.map(renderToken) : t.raw}; - } - case "table": { - return ( - - - - {(t.header as Tokens.TableCell[]).map(v => ( - - ))} - - - - {(t.rows as Tokens.TableCell[][]).map(v => ( - - {v.map((d, d_key) => ( - - ))} - - ))} - -
    - {v.tokens ? v.tokens.map(renderToken) : v.text} -
    - {d.tokens ? d.tokens.map(renderToken) : d.text} -
    - ); - } - case "text": { - if ("tokens" in t) { - return (t.tokens as Array).map(renderToken); - } - return t.raw; - } - case "space": { - return " "; - } - default: { - console.debug(`Unknown token ${t.type}`); - } + try { + switch (t.type) { + case "paragraph": { + return ( +
    + {t.tokens ? t.tokens.map(renderToken) : t.raw} +
    + ); + } + case "image": { + return ; + } + case "heading": { + switch (t.depth) { + case 1: + return ( +

    + {t.tokens ? t.tokens.map(renderToken) : t.raw} +

    + ); + case 2: + return ( +

    + {t.tokens ? t.tokens.map(renderToken) : t.raw} +

    + ); + case 3: + return ( +

    + {t.tokens ? t.tokens.map(renderToken) : t.raw} +

    + ); + case 4: + return ( +

    + {t.tokens ? t.tokens.map(renderToken) : t.raw} +

    + ); + case 5: + return ( +
    + {t.tokens ? t.tokens.map(renderToken) : t.raw} +
    + ); + case 6: + return ( +
    + {t.tokens ? t.tokens.map(renderToken) : t.raw} +
    + ); } - } catch (e) { - console.error(e); + throw new Error("Invalid heading"); + } + case "codespan": { + return {t.raw}; + } + case "code": { + return
    {t.raw}
    ; + } + case "br": { + return
    ; + } + case "hr": { + return
    ; + } + case "strong": { + return ( + {t.tokens ? t.tokens.map(renderToken) : t.raw} + ); + } + case "blockquote": { + return ( +
    + {t.tokens ? t.tokens.map(renderToken) : t.raw} +
    + ); + } + case "link": { + return ( + + {t.tokens ? t.tokens.map(renderToken) : t.raw} + + ); + } + case "list": { + if (t.ordered) { + return
      {t.items.map(renderToken)}
    ; + } else { + return
      {t.items.map(renderToken)}
    ; + } + } + case "list_item": { + return ( +
  • + {t.tokens ? t.tokens.map(renderToken) : t.raw} +
  • + ); + } + case "em": { + return ( + + {t.tokens ? t.tokens.map(renderToken) : t.raw} + + ); + } + case "del": { + return ( + {t.tokens ? t.tokens.map(renderToken) : t.raw} + ); + } + case "table": { + return ( + + + + {(t.header as Tokens.TableCell[]).map((v) => ( + + ))} + + + + {(t.rows as Tokens.TableCell[][]).map((v) => ( + + {v.map((d, d_key) => ( + + ))} + + ))} + +
    + {v.tokens ? v.tokens.map(renderToken) : v.text} +
    + {d.tokens ? d.tokens.map(renderToken) : d.text} +
    + ); + } + case "text": { + if ("tokens" in t) { + return (t.tokens as Array).map(renderToken); + } + return t.raw; + } + case "space": { + return " "; + } + default: { + console.debug(`Unknown token ${t.type}`); + } } + } catch (e) { + console.error(e); + } } const parsed = useMemo(() => { - return marked.lexer(props.content); + return marked.lexer(props.content); }, [props.content]); return ( -
    - {parsed.filter(a => a.type !== "footnote" && a.type !== "footnotes").map(a => renderToken(a))} -
    +
    + {parsed + .filter((a) => a.type !== "footnote" && a.type !== "footnotes") + .map((a) => renderToken(a))} +
    ); -}); + }, +); export default Markdown; diff --git a/src/components/vps-resources.tsx b/src/components/vps-resources.tsx index 51f309f..4f73f86 100644 --- a/src/components/vps-resources.tsx +++ b/src/components/vps-resources.tsx @@ -3,16 +3,15 @@ import BytesSize from "./bytes"; export default function VpsResources({ vm }: { vm: VmInstance | VmTemplate }) { const diskType = "template" in vm ? vm.template?.disk_type : vm.disk_type; - const region = - "region" in vm ? vm.region.name : vm.template?.region?.name; + const region = "region" in vm ? vm.region.name : vm.template?.region?.name; const status = "status" in vm ? vm.status : undefined; - const template = "template" in vm ? vm.template : vm as VmTemplate; + const template = "template" in vm ? vm.template : (vm as VmTemplate); return ( <>
    {template?.cpu} vCPU, RAM,{" "} - {diskType?.toUpperCase()},{" "} - {region && <>Location: {region}} + {diskType?.toUpperCase()} + , {region && <>Location: {region}}
    {status && status.state === "running" && (
    diff --git a/src/hooks/login.tsx b/src/hooks/login.tsx index 9269e63..fa05410 100644 --- a/src/hooks/login.tsx +++ b/src/hooks/login.tsx @@ -10,13 +10,17 @@ export default function useLogin() { () => LoginState.snapshot(), ); const system = useContext(SnortContext); - return useMemo(() => session - ? { - type: session.type, - publicKey: session.publicKey, - system, - api: new LNVpsApi(ApiUrl, LoginState.getSigner()), - logout: () => LoginState.logout() - } - : undefined, [session, system]); + return useMemo( + () => + session + ? { + type: session.type, + publicKey: session.publicKey, + system, + api: new LNVpsApi(ApiUrl, LoginState.getSigner()), + logout: () => LoginState.logout(), + } + : undefined, + [session, system], + ); } diff --git a/src/index.css b/src/index.css index db73177..24a73e7 100644 --- a/src/index.css +++ b/src/index.css @@ -45,4 +45,4 @@ select { input:disabled { @apply text-neutral-200/50; -} \ No newline at end of file +} diff --git a/src/main.tsx b/src/main.tsx index 27b12aa..b0a9547 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -43,7 +43,7 @@ const router = createBrowserRouter([ }, { path: "/account/settings", - element: + element: , }, { path: "/order", @@ -60,7 +60,7 @@ const router = createBrowserRouter([ { path: "/status", element: , - } + }, ], }, ]); diff --git a/src/pages/account-settings.tsx b/src/pages/account-settings.tsx index 31fb1f9..5c84563 100644 --- a/src/pages/account-settings.tsx +++ b/src/pages/account-settings.tsx @@ -5,48 +5,71 @@ import { AsyncButton } from "../components/button"; import { Icon } from "../components/icon"; export function AccountSettings() { - const login = useLogin(); - const [acc, setAcc] = useState(); - const [editEmail, setEditEmail] = useState(false); + const login = useLogin(); + const [acc, setAcc] = useState(); + const [editEmail, setEditEmail] = useState(false); - useEffect(() => { - login?.api.getAccount().then(setAcc); - }, [login]); - - function notifications() { - return <> -

    Notification Settings

    -
    - { - setAcc((s) => (s ? { ...s, contact_email: e.target.checked } : undefined)); - }} /> - Email - { - setAcc((s) => (s ? { ...s, contact_nip17: e.target.checked } : undefined)); - }} /> - Nostr DM -
    -
    -

    Email

    - setAcc(s => (s ? { ...s, email: e.target.value } : undefined))} /> - {!editEmail && setEditEmail(true)} />} -
    -
    - { - if (login?.api && acc) { - await login.api.updateAccount(acc); - const newAcc = await login.api.getAccount(); - setAcc(newAcc); - setEditEmail(false); - } - }}> - Save - -
    - - } + useEffect(() => { + login?.api.getAccount().then(setAcc); + }, [login]); - return <> - {notifications()} - -} \ No newline at end of file + function notifications() { + return ( + <> +

    Notification Settings

    +
    + { + setAcc((s) => + s ? { ...s, contact_email: e.target.checked } : undefined, + ); + }} + /> + Email + { + setAcc((s) => + s ? { ...s, contact_nip17: e.target.checked } : undefined, + ); + }} + /> + Nostr DM +
    +
    +

    Email

    + + setAcc((s) => (s ? { ...s, email: e.target.value } : undefined)) + } + /> + {!editEmail && ( + setEditEmail(true)} /> + )} +
    +
    + { + if (login?.api && acc) { + await login.api.updateAccount(acc); + const newAcc = await login.api.getAccount(); + setAcc(newAcc); + setEditEmail(false); + } + }} + > + Save + +
    + + ); + } + + return <>{notifications()}; +} diff --git a/src/pages/account.tsx b/src/pages/account.tsx index d786dab..bf2f054 100644 --- a/src/pages/account.tsx +++ b/src/pages/account.tsx @@ -26,42 +26,49 @@ export default function AccountPage() { } }, [login]); - const npub = hexToBech32("npub", login?.publicKey); const subjectLine = `[${npub}] Account Query`; return (
    Your Public Key: -
    {npub}
    +
    +        {npub}
    +      
    - navigate("settings")}> - Settings - - { - login?.logout(); - navigate("/") - }}> + navigate("settings")}>Settings + { + login?.logout(); + navigate("/"); + }} + > Logout

    My Resources

    Something doesnt look right?
    - Please contact support on: {" "} - + Please contact support on:{" "} + sales@lnvps.net
    Please include your public key in all communications.
    {vms.map((a) => ( - { - if (login?.api) { - loadVms(login.api); - } - }} /> + { + if (login?.api) { + loadVms(login.api); + } + }} + /> ))} -
    ); } diff --git a/src/pages/home.tsx b/src/pages/home.tsx index eec3d1e..cea96cc 100644 --- a/src/pages/home.tsx +++ b/src/pages/home.tsx @@ -17,7 +17,8 @@ export default function HomePage() {
    VPS Offers
    - Virtual Private Server hosting with flexible plans, high uptime, and dedicated support, tailored to your needs. + Virtual Private Server hosting with flexible plans, high uptime, and + dedicated support, tailored to your needs.
    {offers.map((a) => ( @@ -36,7 +37,10 @@ export default function HomePage() { {" | "} Terms {" | "} - + Nostr {" | "} @@ -49,13 +53,13 @@ export default function HomePage() {
    - LNVPS is a trading name of Apex Strata Ltd, a company registered in Ireland. + LNVPS is a trading name of Apex Strata Ltd, a company registered in + Ireland.
    - Comany Number: 702423, - Address: Suite 10628, 26/27 Upper Pembroke Street, Dublin 2, D02 X361, Ireland + Comany Number: 702423, Address: Suite 10628, 26/27 Upper Pembroke + Street, Dublin 2, D02 X361, Ireland
    -
    ); diff --git a/src/pages/layout.tsx b/src/pages/layout.tsx index 75c6a64..342da80 100644 --- a/src/pages/layout.tsx +++ b/src/pages/layout.tsx @@ -5,7 +5,9 @@ export default function Layout() { return (
    - LNVPS + + LNVPS +
    diff --git a/src/pages/status.tsx b/src/pages/status.tsx index aadedd2..19f6d79 100644 --- a/src/pages/status.tsx +++ b/src/pages/status.tsx @@ -1,49 +1,56 @@ - import Markdown from "../components/markdown"; import Status from "../status.json"; export function StatusPage() { - const totalDowntime = Status.events.reduce((acc, v) => { - if (v.end_time) { - const end = new Date(v.end_time); - const start = new Date(v.start_time); - const duration = end.getTime() - start.getTime(); - acc += duration; - } - return acc; - }, 0); - const birth = new Date(Status.birth); - const now = new Date(); - const age = now.getTime() - birth.getTime(); - const uptime = 1 - (totalDowntime / age); - - function formatDuration(n: number) { - if (n > 3600) { - return `${(n / 3600).toFixed(0)}h ${((n % 3600) / 60).toFixed(0)}m`; - } else if (n > 60) { - return `${(n % 60).toFixed(0)}m`; - } else { - return `${n.toFixed(0)}s`; - } + const totalDowntime = Status.events.reduce((acc, v) => { + if (v.end_time) { + const end = new Date(v.end_time); + const start = new Date(v.start_time); + const duration = end.getTime() - start.getTime(); + acc += duration; } + return acc; + }, 0); + const birth = new Date(Status.birth); + const now = new Date(); + const age = now.getTime() - birth.getTime(); + const uptime = 1 - totalDowntime / age; - return
    -
    Uptime: {(100 * uptime).toFixed(5)}%
    + function formatDuration(n: number) { + if (n > 3600) { + return `${(n / 3600).toFixed(0)}h ${((n % 3600) / 60).toFixed(0)}m`; + } else if (n > 60) { + return `${(n % 60).toFixed(0)}m`; + } else { + return `${n.toFixed(0)}s`; + } + } -
    Incidents:
    - {Status.events.map(e => { - const end = e.end_time ? new Date(e.end_time) : undefined; - const start = new Date(e.start_time); - const duration = end ? end.getTime() - start.getTime() : undefined; + return ( +
    +
    Uptime: {(100 * uptime).toFixed(5)}%
    - return
    -
    -
    {e.title}
    -
    {new Date(e.start_time).toLocaleString()}
    -
    - {duration &&
    Duration: {formatDuration(duration / 1000)}
    } - +
    Incidents:
    + {Status.events.map((e) => { + const end = e.end_time ? new Date(e.end_time) : undefined; + const start = new Date(e.start_time); + const duration = end ? end.getTime() - start.getTime() : undefined; + + return ( +
    +
    +
    {e.title}
    +
    {new Date(e.start_time).toLocaleString()}
    - })} + {duration && ( +
    + Duration: {formatDuration(duration / 1000)} +
    + )} + +
    + ); + })}
    -} \ No newline at end of file + ); +} diff --git a/src/pages/terms.tsx b/src/pages/terms.tsx index de1fee5..3e6399f 100644 --- a/src/pages/terms.tsx +++ b/src/pages/terms.tsx @@ -2,5 +2,5 @@ import Markdown from "../components/markdown"; import TOS from "../tos.md?raw"; export function TosPage() { - return -} \ No newline at end of file + return ; +} diff --git a/src/status.json b/src/status.json index af1ba8e..f54cfb9 100644 --- a/src/status.json +++ b/src/status.json @@ -1,11 +1,11 @@ { - "birth": "2024-06-05T00:00:00Z", - "events":[ - { - "start_time": "2025-02-10T05:00:00Z", - "end_time": "2025-02-10T10:08:00Z", - "title": "VPS outage", - "description": "Primary disk full, causing system to halt" - } - ] -} \ No newline at end of file + "birth": "2024-06-05T00:00:00Z", + "events": [ + { + "start_time": "2025-02-10T05:00:00Z", + "end_time": "2025-02-10T10:08:00Z", + "title": "VPS outage", + "description": "Primary disk full, causing system to halt" + } + ] +} diff --git a/src/tos.md b/src/tos.md index 39941b7..e3c1e2a 100644 --- a/src/tos.md +++ b/src/tos.md @@ -1,107 +1,126 @@ -# Terms of Service +# Terms of Service + **LNVPS** -*Last Updated: February 26, 2025* +_Last Updated: February 26, 2025_ Welcome to LNVPS, a trading name of Apex Strata Ltd, a company registered in Ireland. These Terms of Service ("Terms") govern your use of our Virtual Private Server (VPS) hosting services, website, and related offerings (collectively, the "Services"). By accessing or using our Services, you agree to be bound by these Terms. If you do not agree, please do not use our Services. --- -## 1. Company Information -LNVPS is a trading name of Apex Strata Ltd, a company registered in Ireland. +## 1. Company Information + +LNVPS is a trading name of Apex Strata Ltd, a company registered in Ireland. + - **Company Registration Number**: 702423 - **Registered Office**: Suite 10628, 26/27 Upper Pembroke Street, Dublin 2, D02 X361, Ireland - **Email**: sales@lnvps.net --- -## 2. Definitions -- **"You" or "Customer"**: The individual or entity subscribing to or using the Services. -- **"We", "Us", or "LNVPS"**: Apex Strata Ltd, operating as LNVPS. -- **"Services"**: VPS hosting, support, and any additional features provided by LNVPS. +## 2. Definitions + +- **"You" or "Customer"**: The individual or entity subscribing to or using the Services. +- **"We", "Us", or "LNVPS"**: Apex Strata Ltd, operating as LNVPS. +- **"Services"**: VPS hosting, support, and any additional features provided by LNVPS. --- -## 3. Eligibility +## 3. Eligibility + You must be at least 18 years old and capable of entering into a legally binding agreement to use our Services. By signing up, you confirm that all information provided is accurate and that you are authorized to act on behalf of any entity you represent. --- -## 4. Services +## 4. Services + LNVPS provides VPS hosting services, including server resources, bandwidth, and technical support, as outlined on our website ([lnvps.net](https://lnvps.net) or applicable domain). Specific features, pricing, and resource limits are detailed in your chosen service plan at the time of purchase. -- **Service Availability**: We strive for 99.9% uptime but do not guarantee uninterrupted service. Downtime may occur due to maintenance, upgrades, or unforeseen events. -- **Modifications**: We reserve the right to modify or discontinue any aspect of the Services with reasonable notice. +- **Service Availability**: We strive for 99.9% uptime but do not guarantee uninterrupted service. Downtime may occur due to maintenance, upgrades, or unforeseen events. +- **Modifications**: We reserve the right to modify or discontinue any aspect of the Services with reasonable notice. --- -## 5. Account Responsibilities -- **Account Security**: You are responsible for maintaining the confidentiality of your account credentials and for all activities under your account. -- **Usage**: You agree to use the Services only for lawful purposes and in compliance with these Terms. -- **Notification**: You must notify us immediately of any unauthorized use of your account. +## 5. Account Responsibilities + +- **Account Security**: You are responsible for maintaining the confidentiality of your account credentials and for all activities under your account. +- **Usage**: You agree to use the Services only for lawful purposes and in compliance with these Terms. +- **Notification**: You must notify us immediately of any unauthorized use of your account. --- -## 6. Acceptable Use Policy -You agree not to use the Services to: -- Host, store, or distribute illegal content, including but not limited to pirated software, child exploitation material, or content inciting violence or hate. -- Engage in spamming, phishing, or other abusive activities. -- Overload or disrupt our servers, networks, or other customers’ services (e.g., DDoS attacks). -- Violate intellectual property rights or privacy laws. +## 6. Acceptable Use Policy + +You agree not to use the Services to: + +- Host, store, or distribute illegal content, including but not limited to pirated software, child exploitation material, or content inciting violence or hate. +- Engage in spamming, phishing, or other abusive activities. +- Overload or disrupt our servers, networks, or other customers’ services (e.g., DDoS attacks). +- Violate intellectual property rights or privacy laws. We reserve the right to suspend or terminate your Services without notice if we detect violations, subject to applicable law. --- -## 7. Payment and Billing -- **Fees**: You agree to pay the fees for your chosen plan as outlined at checkout. All prices are in Euro (€) and include VAT where applicable. -- **Billing Cycle**: Payments are due in advance (monthly, quarterly, or annually, depending on your plan). +## 7. Payment and Billing + +- **Fees**: You agree to pay the fees for your chosen plan as outlined at checkout. All prices are in Euro (€) and include VAT where applicable. +- **Billing Cycle**: Payments are due in advance (monthly, quarterly, or annually, depending on your plan). - **Late Payment**: Overdue accounts may be suspended until payment is received. -- **Refunds**: Refunds are available within 7 days of initial purchase, provided no excessive usage has occurred, as determined by us. +- **Refunds**: Refunds are available within 7 days of initial purchase, provided no excessive usage has occurred, as determined by us. --- -## 8. Termination -- **By You**: You may terminate your account at any time via your control panel or by contacting us, subject to the billing cycle terms. -- **By Us**: We may suspend or terminate your Services for non-payment, violation of these Terms, or if required by law, with or without notice depending on the severity of the breach. -- **Effect of Termination**: Upon termination, your access to the Services ends, and we may delete your data after 7 days unless otherwise required by law. +## 8. Termination + +- **By You**: You may terminate your account at any time via your control panel or by contacting us, subject to the billing cycle terms. +- **By Us**: We may suspend or terminate your Services for non-payment, violation of these Terms, or if required by law, with or without notice depending on the severity of the breach. +- **Effect of Termination**: Upon termination, your access to the Services ends, and we may delete your data after 7 days unless otherwise required by law. --- -## 9. Data and Privacy -- **Your Data**: You retain ownership of data uploaded to your VPS. We do not access it except as needed to provide the Services or comply with legal obligations. -- **Backups**: You are responsible for maintaining backups of your data unless a backup service is included in your plan. -- **GDPR Compliance**: We process personal data in accordance with our [Privacy Policy](#), which complies with the General Data Protection Regulation (GDPR). +## 9. Data and Privacy + +- **Your Data**: You retain ownership of data uploaded to your VPS. We do not access it except as needed to provide the Services or comply with legal obligations. +- **Backups**: You are responsible for maintaining backups of your data unless a backup service is included in your plan. +- **GDPR Compliance**: We process personal data in accordance with our [Privacy Policy](#), which complies with the General Data Protection Regulation (GDPR). --- -## 10. Limitation of Liability -To the fullest extent permitted by Irish law: -- Our liability for any claim arising from the Services is limited to the amount you paid us in the previous 12 months. -- We are not liable for indirect, consequential, or incidental damages (e.g., loss of profits, data, or business opportunities). -- We are not responsible for issues beyond our reasonable control, such as force majeure events (e.g., natural disasters, cyber-attacks). +## 10. Limitation of Liability + +To the fullest extent permitted by Irish law: + +- Our liability for any claim arising from the Services is limited to the amount you paid us in the previous 12 months. +- We are not liable for indirect, consequential, or incidental damages (e.g., loss of profits, data, or business opportunities). +- We are not responsible for issues beyond our reasonable control, such as force majeure events (e.g., natural disasters, cyber-attacks). --- -## 11. Intellectual Property -- **Our IP**: The LNVPS website, branding, and software remain our property or that of our licensors. -- **Your IP**: You grant us a limited license to use your content solely to provide the Services. +## 11. Intellectual Property + +- **Our IP**: The LNVPS website, branding, and software remain our property or that of our licensors. +- **Your IP**: You grant us a limited license to use your content solely to provide the Services. --- -## 12. Governing Law and Dispute Resolution -- These Terms are governed by the laws of Ireland. -- Any disputes will be subject to the exclusive jurisdiction of the courts of Ireland, though you may have additional rights under EU consumer law if applicable. +## 12. Governing Law and Dispute Resolution + +- These Terms are governed by the laws of Ireland. +- Any disputes will be subject to the exclusive jurisdiction of the courts of Ireland, though you may have additional rights under EU consumer law if applicable. --- -## 13. Changes to Terms +## 13. Changes to Terms + We may update these Terms from time to time. We will notify you of significant changes via email or on our website. Continued use of the Services after changes constitutes acceptance of the updated Terms. --- -## 14. Contact Us -For questions or support: +## 14. Contact Us + +For questions or support: + - **Email**: sales@lnvps.net - **Address**: Suite 10628, 26/27 Upper Pembroke Street, Dublin 2, D02 X361, Ireland ---- \ No newline at end of file +---