Home
Softono
hqtodo

hqtodo

Open source MIT JavaScript
17
Stars
2
Forks
8
Issues
2
Watchers
1 week
Last Commit

About hqtodo

See your Todo list as a Gantt chart, view your velocity, and review finished items by week and by tagged category.

Platforms

Web Self-hosted

Languages

JavaScript

= hqTodo :experimental: :toc: :toc-placement!: ifdef::env-github[] :tip-caption: :bulb: :note-caption: :information_source: :important-caption: :heavy_exclamation_mark: :caution-caption: :fire: :warning-caption: :warning: endif::[] ifndef::env-github[] :icons: font endif::[]

pass:[

build status

]

If you enjoy working from the keyboard (keyboard shortcuts, vim, Emacs Org mode) you might like Fabio Spampinato's VS Code link:https://github.com/fabiospampinato/vscode-todo-plus[TODO+ extension] to capture todo items in a text file, with keyboard shortcuts to prioritize and close items. You can provide tags to each list item to indicate status, estimates, and categories. And because it's all in one central list, it's second nature to add items to it, ensuring you don't lose track of a thing. His extension is awesome!

But you can get lost staring at one long list. The list is a very fast and efficient way to enter and edit information. But to grok and act on the tasks, I needed something more.

That's why I created this li'l hqTodo web app. It further organizes the task information into manageable areas. It provides an easy visual for you (and your team if you want). The hqTodo app uses the provided tags to fit the information into an agile workflow. I've been using this method since about 2018 and much prefer it to dragging cards around.

pass:[

view a sample

]

//// pass:[

 

] ////

The app provides a complete picture of where you're at, all from one launchpad. Using progressive disclosure, it provides you information in consumable chunks:

[verse]


From front page (ordered and categorized overview of all tasks) to project pages (focus on project-specific tasks) to project details pages (how project tasks fulfill each project vision)

From front page to optional task hyperlinks (to details elsewhere)


toc::[]

== Features

  • Open items and their estimates are used to display a https://mermaid-js.github.io/mermaid/#/gantt[Mermaid] Gantt chart. Tasks shift with time, starting from now into the future, providing a quick glimpse of probable completion dates.
  • https://apexcharts.com/[ApexCharts] is used to display cumulative story points you've knocked out to help keep track of your velocity.
  • If you field support questions, you can also track those with the TODO+ extension and view the cumulative time spent in the chart.
  • Completed items are displayed underneath. Great for weekly status reports.
  • Tag your items to provide a detailed status page for each project.
  • Because everyone else uses other forms of tracking, I use the link:https://marketplace.visualstudio.com/items?itemName=ragnoroct.linkme[Link Me extension] to include shortcuts to all those other resources in my Todo list. The hqTodo app exposes those links, too.

== Running the app

=== Option 1: Using Docker Compose (recommended)

. Create your own TODO file anywhere on the file system the app can access, a local folder or in a shared Cloud or Network mounted folder. . Copy the config/default.json file to a production.json file anywhere the app can access, or leave it in the config folder. Update the JSON file with your information. (For information on setting the Linkme links, watch the video.) Don't change the todoFile setting in the production.json file when running in a container because todo/TODO is the path as referenced from inside the container. . Copy compose.sample.yml to compose.yml, and in compose.yml, make the following changes:

  • port::: Set the number before the colon to the port you want to use in the URL on your server. (Or leave the default of 3000.)
  • volumes:::

  • Set the path before the colon to the folder where your TODO file lives.
  • Set the path before the colon to the folder where your production.json file lives (can be the same folder your TODO file is in). . link:https://docs.docker.com/engine/install/[Install Docker]. . Ensure Docker is set to run on system startup. For example, on Ubuntu run:
  • [source, bash]

    systemctl enable docker

    . Run Docker Compose as follows to build the image and start a container in the background.

  • [source, bash]

    docker compose up -d

  • The restart: always setting in the compose file tells Docker to run the container on system startup.

As you change your TODO file, the app re-reads it every 40 seconds. But, if you change anything in the config file, you'll need to restart the container: [source, bash]

docker compose restart

=== Option 2: Using NodeJS on your computer

Prereq: NodeJS v24 or higher.

. Set things up. .. Copy the config/default.json file to config/production.json and add your info to the new file. Your own TODO file can be anywhere on the file system the app can access, such as a shared Cloud or Network mounted folder. .. Install server dependencies:

  • [source, bash]

    npm install

    .. Install client dependencies and build it:

  • [source, bash]

    cd client-react-carbon npm install npm run build cd ..

    . Thereafter, run the app with ...

  • [source, bash]

    ./run

  • ... and point your browser to http://localhost:3000.

  • kbd:[Ctrl+C] stops the server.

One way to deploy the app in "production" is to run it on a server with pm2, systemctl, etc., and add your TODO file to a folder on the server that's synced with your local workstation with SyncThing or similar cloud folder sync application.

//// == Deploy

For example with systemd on Linux:

[source,bash]

vi misc/hqtodo.service # change the absolute paths and user sudo cp misc/hqtodo.service /etc/systemd/system/ sudo systemctl start hqtodo sudo journalctl -f # to verify. Also test in a browser sudo systemctl enable hqtodo

////

== Maintaining your TODO file

For the most part, link:https://marketplace.visualstudio.com/items?itemName=fabiospampinato.vscode-todo-plus#usage[update your TODO file] as you normally would as described in the TODO+ Readme documentation. There are a few conventions to use for the app to work. link:todo/TODO[See the sample TODO file] as an example.

Everything in your TODO file is ignored except ...

  • the list in the optional Taginfo: section
  • the todo items between the Todos: project and the next top-level project.
  • the todo items after Archive:

... where a todo item is a line starting with ☐ or ✔.

=== Active todo items

Syntax:

[source,bash]

☐ [@today|@high|@low] [@Nh] [@custom ]*

  • Priority tagging: Items prefixed with @today are placed in order in the In Progress section. Items with no priority label are listed in order in the Backlog section. Items prefixed with @high are colored red. Items prefixed with @low are not displayed. (This is your Icebox.)
  • The optional @Nh tag indicates your estimated N hours. No tag gets the default of 2 hours.
  • You can provide any number of @custom tags for your own custom filtering. They're displayed with the title.
  • When you press kbd:[Super+D] to mark a task with a checkmark and a @done tag, it's listed in the "Recently closed todos" section of the web page.

Example:

[source,bash]

☐ @high Work with so-and-so on such-and-such @4h @prja @prjb

=== Interruptions

You can let folks know about vacations and other "interruptions" to your steady velocity. Specify the duration and start date as business days only.

Syntax:

[source,bash]

☐ [Nd starting YYYY-MM-DD]: </h2> <p>Example:</p> <h2>[source,bash]</h2> <h2>☐ [5d starting 2020-05-18]: Whiz-Bang-Boom conference</h2> <p>=== Milestones</p> <p>Milestones have the same form as an interrupt, but with a length of zero days (<code>0d</code>).</p> <p>Example:</p> <h2>[source,bash]</h2> <h2>☐ [0d starting 2020-06-15]: Complete Project A @proja</h2> <p>Milestones show up in their own section above the others.</p> <p>=== Referencing a GitHub repo</p> <p>If you reference a particular issue in your todo text (for example if you use the linkme VS Code extension), the Gantt chart will link to the issue from the task bar and todo text.</p> <p>Edit the config file repo URL to point to your own repo. As set up now, it has two patterns:</p> <ul> <li>one pattern that links to the default repo that you specify in the config file <code>ghihq•4</code></li> <li>one pattern that links to whatever repo you specify in the todo file itself <code>ghi•ragnoroct/linkme•9</code></li> </ul> <p>You can of course change the patterns to link to whatever you want.</p> <p>=== Archived todos</p> <p>If you archive your done items (e.g. kbd:[Cmd+Shift+A]]), they will be available at the bottom along with a graph of the cumulative story points you've knocked out over time. Marvel at your progress!</p> <p>TIP: Use category tags in your todo items to see them grouped and totaled by category.</p> <p>=== The taginfo section</p> <p>Use this section if you want to provide a more descriptive title and link to more information on the project.</p> <p>The structure of each entry:</p> <h2>[source,bash]</h2> <h2>☐ @tagname full title of the project or category - url or linkme link</h2> <p>Example:</p> <h2>[source,bash]</h2> <h2>☐ @prja Project A - den•bRNzBMOVaqYl6i7C.html</h2> <p>I personally link to pages published with link:<a href="https://dendron.so[Dendron">https://dendron.so[Dendron</a>], because I like to also use VS Code for my note taking, and Dendron has a cool feature where you can publish Notes to a web site.</p> <p>=== A support section</p> <p>Like interrupts, support questions disrupt your velocity, so you might want to keep track of them as well. To do so, Add a <code>Support:</code> project with todo items. For these items, use the TODO+ extension's time tracking feature:</p> <ul> <li>kbd:[Super+S] to start the timer</li> <li>kbd:[Super+D] to stop the timer and mark the item done</li> </ul> <p>When you archive your todos, the support items will have the following form, and the time you spent on them is graphed with a yellow line in the UI.</p> <h2>[source,bash]</h2> <h2>✔ ticket324: Help debug website3 @started(2023-08-20 15:46) @done(2023-08-20 16:46) @lasted(1h) @project(Support) ✔ ticket323: Help debug website2 @started(2023-08-20 13:56) @done(2023-08-20 14:56) @lasted(1h) @project(Support) ✔ ticket322: Help debug website1 @started(2023-08-19 10:56) @done(2023-08-19 11:26) @lasted(30m) @project(Support)</h2> <p>Items of that form are exposed with following endpoints as well:</p> <ul> <li><code>/todos/supportdata</code></li> <li><code>/todos/supportondate/YYYY-MM-DD</code></li> </ul> <p>== What's next?</p> <p>See the link:<a href="https://arkadianriver.github.io/hqtodo/ejs/hq.html[@hq">https://arkadianriver.github.io/hqtodo/ejs/hq.html[@hq</a> project] in the Classic-styled hqTodo file.</p> </article> </div> </div> </div> </div> </section> </div> </div> </main> <!-- ========================= Footer v3 ===========================--> <footer class="footer footer-three dark:bg-background-8 {=$class} relative overflow-hidden bg-white"> <div class="main-container"> <div class="grid grid-cols-12 justify-between gap-x-0 gap-y-16 pt-16 pb-16 lg:gap-x-8 xl:gap-x-0 xl:pt-[100px]"> <div class="col-span-12 lg:col-span-4"> <div data-ns-animate data-delay="0.3" class="xl:max-w-[306px]"> <figure> <img src="https://img.softono.com/qoj701ib3Ld4bDgb-icIWTSfvTWeTYajWDUdTPwHgQ0/aHR0cHM6Ly9zb2Z0b25vLmNvbS91cGxvYWQvbG9nby9sb2dvLnBuZw" class="dark:hidden" alt="Nexsass" /> <img src="https://img.softono.com/qoj701ib3Ld4bDgb-icIWTSfvTWeTYajWDUdTPwHgQ0/aHR0cHM6Ly9zb2Z0b25vLmNvbS91cGxvYWQvbG9nby9sb2dvLnBuZw" class="hidden dark:block" alt="Nexsass" /> </figure> <p class="text-secondary dark:text-accent mt-4 mb-7"> Turpis tortor nunc sed amet et faucibus vitae morbi congue sed id mauris. </p> <div class="flex items-center gap-3"> <a href="#" class="footer-social-link"> <span class="sr-only">Facebook</span> <svg xmlns="http://www.w3.org/2000/svg" width="7" height="16" viewBox="0 0 7 16" fill="none"> <path d="M2.25 15C2.25 15.4142 2.58579 15.75 3 15.75C3.41421 15.75 3.75 15.4142 3.75 15H2.25ZM3.75 7C3.75 6.58579 3.41421 6.25 3 6.25C2.58579 6.25 2.25 6.58579 2.25 7H3.75ZM6 1.75C6.41421 1.75 6.75 1.41421 6.75 1C6.75 0.585786 6.41421 0.25 6 0.25V1.75ZM3 4H2.25H3ZM2.25 7C2.25 7.41421 2.58579 7.75 3 7.75C3.41421 7.75 3.75 7.41421 3.75 7H2.25ZM3 6.25C2.58579 6.25 2.25 6.58579 2.25 7C2.25 7.41421 2.58579 7.75 3 7.75V6.25ZM5 7.75C5.41421 7.75 5.75 7.41421 5.75 7C5.75 6.58579 5.41421 6.25 5 6.25V7.75ZM3 7.75C3.41421 7.75 3.75 7.41421 3.75 7C3.75 6.58579 3.41421 6.25 3 6.25V7.75ZM1 6.25C0.585786 6.25 0.25 6.58579 0.25 7C0.25 7.41421 0.585786 7.75 1 7.75V6.25ZM3 15H3.75V7H3H2.25V15H3ZM6 1V0.25C3.92893 0.25 2.25 1.92893 2.25 4H3H3.75C3.75 2.75736 4.75736 1.75 6 1.75V1ZM3 4H2.25V7H3H3.75V4H3ZM3 7V7.75H5V7V6.25H3V7ZM3 7V6.25H1V7V7.75H3V7Z" class="fill-secondary dark:fill-accent" /> </svg> </a> <div class="bg-stroke-1 dark:bg-stroke-8 h-5 w-px"></div> <a href="#" class="footer-social-link"> <span class="sr-only">Instagram</span> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="none"> <path fill-rule="evenodd" clip-rule="evenodd" d="M11 1H5C2.79086 1 1 2.79086 1 5V11C1 13.2091 2.79086 15 5 15H11C13.2091 15 15 13.2091 15 11V5C15 2.79086 13.2091 1 11 1Z" class="stroke-secondary dark:stroke-accent" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" /> <path fill-rule="evenodd" clip-rule="evenodd" d="M8 11C6.34315 11 5 9.65685 5 8C5 6.34315 6.34315 5 8 5C9.65685 5 11 6.34315 11 8C11 8.79565 10.6839 9.55871 10.1213 10.1213C9.55871 10.6839 8.79565 11 8 11Z" class="stroke-secondary dark:stroke-accent" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" /> <rect x="11" y="5" width="2" height="2" rx="1" transform="rotate(-90 11 5)" class="fill-secondary dark:fill-accent" /> <rect x="11.5" y="4.5" width="1" height="1" rx="0.5" transform="rotate(-90 11.5 4.5)" class="stroke-secondary dark:stroke-accent" stroke-linecap="round" /> </svg> </a> <div class="bg-stroke-1 dark:bg-stroke-8 h-5 w-px"></div> <a href="#" class="footer-social-link"> <span class="sr-only">Youtube</span> <svg xmlns="http://www.w3.org/2000/svg" width="22" height="16" viewBox="0 0 22 16" fill="none"> <path fill-rule="evenodd" clip-rule="evenodd" d="M16.668 15.0028C18.9724 15.0867 20.91 13.29 21 10.9858V5.01982C20.91 2.71569 18.9724 0.918929 16.668 1.00282H5.332C3.02763 0.918929 1.08998 2.71569 1 5.01982V10.9858C1.08998 13.29 3.02763 15.0867 5.332 15.0028H16.668Z" class="stroke-secondary dark:stroke-accent" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" /> <path fill-rule="evenodd" clip-rule="evenodd" d="M10.508 5.17711L13.669 7.32511C13.8738 7.44468 13.9997 7.66398 13.9997 7.90111C13.9997 8.13824 13.8738 8.35754 13.669 8.47711L10.508 10.8271C9.908 11.2341 9 10.8871 9 10.2511V5.75111C9 5.11811 9.909 4.77011 10.508 5.17711Z" class="stroke-secondary dark:stroke-accent" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" /> </svg> </a> <div class="bg-stroke-1 dark:bg-stroke-8 h-5 w-px"></div> <a href="#" class="footer-social-link"> <span class="sr-only">LinkedIn</span> <svg xmlns="http://www.w3.org/2000/svg" width="13" height="11" viewBox="0 0 13 11" fill="none"> <path d="M2.25 4C2.25 3.58579 1.91421 3.25 1.5 3.25C1.08579 3.25 0.75 3.58579 0.75 4H2.25ZM0.75 10C0.75 10.4142 1.08579 10.75 1.5 10.75C1.91421 10.75 2.25 10.4142 2.25 10H0.75ZM10.75 10C10.75 10.4142 11.0858 10.75 11.5 10.75C11.9142 10.75 12.25 10.4142 12.25 10H10.75ZM5.5 7H4.75H5.5ZM4.75 10C4.75 10.4142 5.08579 10.75 5.5 10.75C5.91421 10.75 6.25 10.4142 6.25 10H4.75ZM2.25 1C2.25 0.585786 1.91421 0.25 1.5 0.25C1.08579 0.25 0.75 0.585786 0.75 1H2.25ZM0.75 2C0.75 2.41421 1.08579 2.75 1.5 2.75C1.91421 2.75 2.25 2.41421 2.25 2H0.75ZM1.5 4H0.75V10H1.5H2.25V4H1.5ZM11.5 10H12.25V7H11.5H10.75V10H11.5ZM11.5 7H12.25C12.25 4.92893 10.5711 3.25 8.5 3.25V4V4.75C9.74264 4.75 10.75 5.75736 10.75 7H11.5ZM8.5 4V3.25C6.42893 3.25 4.75 4.92893 4.75 7H5.5H6.25C6.25 5.75736 7.25736 4.75 8.5 4.75V4ZM5.5 7H4.75V10H5.5H6.25V7H5.5ZM1.5 1H0.75V2H1.5H2.25V1H1.5Z" class="fill-secondary dark:fill-accent" /> </svg> </a> <div class="bg-stroke-1 dark:bg-stroke-8 h-5 w-px"></div> <a href="#" class="footer-social-link"> <span class="sr-only">Dribbble</span> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="none"> <path fill-rule="evenodd" clip-rule="evenodd" d="M9.81146 14.7617C6.69789 15.5957 3.41731 14.1957 1.86521 11.3707C0.313116 8.54567 0.890795 5.02595 3.26447 2.84524C5.63814 0.66452 9.19411 0.386619 11.8777 2.1721C14.5614 3.95759 15.6788 7.34483 14.5845 10.3767C13.8079 12.532 12.0248 14.1702 9.81146 14.7617Z" class="stroke-secondary dark:stroke-accent" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" /> <path d="M9.06142 14.7162C9.03653 15.1297 9.35153 15.485 9.765 15.5099C10.1785 15.5348 10.5338 15.2198 10.5587 14.8063L9.06142 14.7162ZM6.84286 0.874373C6.64188 0.512186 6.18534 0.381502 5.82315 0.582483C5.46097 0.783464 5.33028 1.24 5.53126 1.60219L6.84286 0.874373ZM13.2187 2.9035C13.3591 2.5138 13.157 2.08408 12.7673 1.94368C12.3776 1.80328 11.9479 2.00537 11.8075 2.39506L13.2187 2.9035ZM7.74006 7.03428L7.54644 6.30971L7.54546 6.30997L7.74006 7.03428ZM1.89802 5.05032C1.58158 4.78304 1.10838 4.82289 0.841101 5.13932C0.573819 5.45576 0.613667 5.92896 0.930105 6.19624L1.89802 5.05032ZM2.77955 13.0958C2.63901 13.4855 2.84095 13.9153 3.23059 14.0558C3.62023 14.1963 4.05003 13.9944 4.19057 13.6048L2.77955 13.0958ZM8.25822 8.96384L8.06412 8.23939L8.25822 8.96384ZM14.1013 10.9494C14.4178 11.2166 14.891 11.1766 15.1582 10.8601C15.4254 10.5435 15.3854 10.0703 15.0688 9.80317L14.1013 10.9494ZM9.81006 14.7613L10.5587 14.8063C10.7186 12.1509 10.1178 9.27114 9.32769 6.78072C8.53534 4.28333 7.53363 2.11922 6.84286 0.874373L6.18706 1.23828L5.53126 1.60219C6.17449 2.76135 7.13628 4.83373 7.89793 7.23434C8.66179 9.64192 9.20557 12.3216 9.06142 14.7162L9.81006 14.7613ZM12.5131 2.64928L11.8075 2.39506C11.1142 4.31922 9.52233 5.7817 7.54644 6.30971L7.74006 7.03428L7.93369 7.75886C10.3844 7.10397 12.3588 5.29004 13.2187 2.9035L12.5131 2.64928ZM7.74006 7.03428L7.54546 6.30997C5.57029 6.84064 3.46046 6.37005 1.89802 5.05032L1.41406 5.62328L0.930105 6.19624C2.86801 7.83311 5.48485 8.41679 7.93467 7.75859L7.74006 7.03428ZM3.48506 13.3503L4.19057 13.6048C4.88464 11.6805 6.47642 10.2177 8.45232 9.68829L8.25822 8.96384L8.06412 8.23939C5.614 8.89585 3.64019 10.7097 2.77955 13.0958L3.48506 13.3503ZM8.25822 8.96384L8.45232 9.68829C10.4282 9.15889 12.5381 9.62992 14.1013 10.9494L14.5851 10.3763L15.0688 9.80317C13.1305 8.16701 10.5142 7.58293 8.06412 8.23939L8.25822 8.96384Z" class="fill-secondary dark:fill-accent" /> </svg> </a> <div class="bg-stroke-1 dark:bg-stroke-8 h-5 w-px"></div> <a href="#" class="footer-social-link"> <span class="sr-only">Behance</span> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="14" viewBox="0 0 16 14" fill="none"> <path fill-rule="evenodd" clip-rule="evenodd" d="M1 7V1H4C5.65685 1 7 2.34315 7 4C7 5.65685 5.65685 7 4 7C5.65685 7 7 8.34315 7 10C7 11.6569 5.65685 13 4 13H1V7Z" class="stroke-secondary dark:stroke-accent" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" /> <path fill-rule="evenodd" clip-rule="evenodd" d="M15 10C15 8.34315 13.6569 7 12 7C10.3431 7 9 8.34315 9 10H15Z" class="stroke-secondary dark:stroke-accent" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" /> <path d="M1 6.25C0.585786 6.25 0.25 6.58579 0.25 7C0.25 7.41421 0.585786 7.75 1 7.75V6.25ZM4 7.75C4.41421 7.75 4.75 7.41421 4.75 7C4.75 6.58579 4.41421 6.25 4 6.25V7.75ZM9.75 9.99998C9.74999 9.58577 9.41419 9.24999 8.99998 9.25C8.58577 9.25001 8.24999 9.58581 8.25 10L9.75 9.99998ZM10.9295 12.8024L10.6619 13.5031L10.9295 12.8024ZM14.795 12.5C15.0712 12.1913 15.0447 11.7172 14.736 11.441C14.4273 11.1648 13.9532 11.1913 13.677 11.5L14.795 12.5ZM14 5.75C14.4142 5.75 14.75 5.41421 14.75 5C14.75 4.58579 14.4142 4.25 14 4.25V5.75ZM10 4.25C9.58579 4.25 9.25 4.58579 9.25 5C9.25 5.41421 9.58579 5.75 10 5.75V4.25ZM1 7V7.75H4V7V6.25H1V7ZM9 10L8.25 10C8.25004 11.5548 9.20948 12.9483 10.6619 13.5031L10.9295 12.8024L11.1971 12.1018C10.3257 11.7689 9.75002 10.9328 9.75 9.99998L9 10ZM10.9295 12.8024L10.6619 13.5031C12.1143 14.0578 13.7584 13.6588 14.795 12.5L14.236 12L13.677 11.5C13.0551 12.1953 12.0686 12.4347 11.1971 12.1018L10.9295 12.8024ZM14 5V4.25H10V5V5.75H14V5Z" class="fill-secondary dark:fill-accent" /> </svg> </a> </div> </div> </div> <div class="col-span-12 grid grid-cols-12 gap-x-0 gap-y-8 lg:col-span-8"> <div class="col-span-12 md:col-span-4"> <div data-ns-animate data-delay="0.4" class="space-y-8"> <p class="sm:text-heading-6 text-tagline-1 text-secondary dark:text-accent font-normal"> Company </p> <ul class="space-y-3"> <li> <a href="page/about-us" class="footer-link-v2 router pjax"> About Us </a> </li> <li> <a href="himanshu" class="footer-link-v2 router pjax"> About Founder </a> </li> <li> <a href="services" class="footer-link-v2 router pjax"> Our Services </a> </li> <li> <a href="testimonials" class="footer-link-v2 router pjax"> Testimonials </a> </li> <li> <a href="contact" class="footer-link-v2 router pjax"> Contact Us </a> </li> </ul> </div> </div> <div class="col-span-12 md:col-span-4"> <div data-ns-animate data-delay="0.5" class="space-y-8"> <p class="sm:text-heading-6 text-tagline-1 text-secondary dark:text-accent font-normal"> Explore </p> <ul class="space-y-3"> <li> <a href="softwares" class="footer-link-v2 router pjax"> Softwares </a> </li> <li> <a href="vendors" class="footer-link-v2 router pjax"> Software Vendors </a> </li> <li> <a href="softwares/categories" class="footer-link-v2 router pjax"> Software Categories </a> </li> <li> <a href="blog" class="footer-link-v2 router pjax"> Tech Blog </a> </li> </ul> </div> </div> <div class="col-span-12 md:col-span-4"> <div data-ns-animate data-delay="0.6" class="space-y-8"> <p class="sm:text-heading-6 text-tagline-1 text-secondary dark:text-accent font-normal"> Legal Policies </p> <ul class="space-y-3"> <li> <a href="page/terms-condition" class="footer-link-v2 router pjax"> Terms & Conditions </a> </li> <li> <a href="page/privacy-policy" class="footer-link-v2 router pjax"> Privacy Policy </a> </li> </ul> </div> </div> </div> </div> <div class="relative overflow-hidden pt-6 pb-[60px] text-center"> <div class="footer-divider bg-stroke-2 dark:bg-accent/5 absolute top-0 right-0 left-0 mx-auto h-px w-0 origin-center"></div> <p data-ns-animate data-delay="0.7" data-offset="10" data-start="top 105%" class="text-secondary dark:text-accent/60"> ©2026 , made by <a href="http://softono.com" target="_blank" class="text-secondary dark:text-accent">Softono</a> </p> </div> </div> <div class="{=$hide-theme-toggle}"> <!-- ========================= Theme Toggle Button ===========================--> <button id="theme-toggle" data-default-theme="{=$default-theme}" aria-label="Theme toggle button" class="size-12 bg-background-8 !z-[9999] dark:bg-white rounded-l-2xl cursor-pointer flex items-center justify-center fixed right-0 bottom-5"> <span id="dark-theme-icon"> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6 stroke-black"> <path stroke-linecap="round" stroke-linejoin="round" d="M12 3v2.25m6.364.386-1.591 1.591M21 12h-2.25m-.386 6.364-1.591-1.591M12 18.75V21m-4.773-4.227-1.591 1.591M5.25 12H3m4.227-4.773L5.636 5.636M15.75 12a3.75 3.75 0 1 1-7.5 0 3.75 3.75 0 0 1 7.5 0Z" /> </svg> </span> <span id="light-theme-icon"> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" class="size-6 stroke-white"> <path stroke-linecap="round" stroke-linejoin="round" d="M21.752 15.002A9.72 9.72 0 0 1 18 15.75c-5.385 0-9.75-4.365-9.75-9.75 0-1.33.266-2.597.748-3.752A9.753 9.753 0 0 0 3 11.25C3 16.635 7.365 21 12.75 21a9.753 9.753 0 0 0 9.002-5.998Z" /> </svg> </span> </button> </div> <div id="common-modal" class="modal fade"> <div class="modal-dialog"> <div class="modal-content" id="common-modal-content"> </div> </div> </div> </footer> <script src="https://code.jquery.com/jquery-4.0.0.min.js" integrity="sha256-OaVG6prZf4v69dPg6PhVattBXkcOWQB62pdZ3ORyrao=" crossorigin="anonymous"></script> <script src="theme/vendor/swiper.min.js"></script> <script src="theme/vendor/leaflet.min.js"></script> <script src="theme/vendor/vanilla-infinite-marquee.min.js"></script> <script src="theme/vendor/split-text.min.js"></script> <script src="theme/vendor/gsap.min.js"></script> <script src="theme/vendor/scroll-trigger.min.js"></script> <script src="theme/vendor/draw-svg.min.js"></script> <script src="theme/vendor/scroll-trigger.min.js"></script> <script src="theme/vendor/motionpathplugin.min.js"></script> <script src="theme/vendor/lenis.min.js"></script> <script src="theme/vendor/springer.min.js"></script> <script src="theme/vendor/number-counter.js"></script> <script src="theme/vendor/stack-card.min.js"></script> <script src="theme/assets/main.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validate/1.21.0/jquery.validate.min.js" integrity="sha512-KFHXdr2oObHKI9w4Hv1XPKc898mE4kgYx58oqsc/JqqdLMDI4YjOLzom+EMlW8HFUd0QfjfAvxSL6sEq/a42fQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script> <script src="assets/js/app.js?v=4"></script> <script src="assets/js/pjax.js?v=8"></script> <script src="assets/js/common.js"></script> <script> $(document).ready(function() { pjax.onLinkClick = function(target){ updateActiveMenu(target); }; pjax.onPageLoaded = function(url){ initRevealElements(); }; pjax.init(); }); </script> <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/orestbida/cookieconsent@3.0.1/dist/cookieconsent.css"> <script type="module"> import 'https://cdn.jsdelivr.net/gh/orestbida/cookieconsent@3.0.1/dist/cookieconsent.umd.js'; window.addEventListener('load', function() { if (window.templateCustomizer?.settings?.style === 'dark') { document.documentElement.classList.add('cc--darkmode'); } CookieConsent.run({ // root: 'body', // autoShow: true, //disablePageInteraction: true, // hideFromBots: true, // mode: 'opt-in', //revision: 100, cookie: { name: 'cc_cookie', // domain: location.hostname, // path: '/', // sameSite: "Lax", expiresAfterDays: 365, }, // https://cookieconsent.orestbida.com/reference/configuration-reference.html#guioptions guiOptions: { consentModal: { layout: 'cloud inline', position: 'bottom right', equalWeightButtons: true, flipButtons: false }, preferencesModal: { layout: 'box', equalWeightButtons: true, flipButtons: false } }, onChange: ({changedCategories, changedServices}) => { alert(changedCategories); alert(changedServices,changedServices); if(CookieConsent.getUserPreferences().rejectedCategories.indexOf('necessary')<0){ app.setCookie('cookie_consent',1); } }, onModalHide: ({modalName}) => { if(CookieConsent.getUserPreferences().rejectedCategories.indexOf('necessary')<0){ app.setCookie('cookie_consent',1); } }, categories: { necessary: { enabled: true, // this category is enabled by default readOnly: true // this category cannot be disabled }, analytics: { autoClear: { cookies: [ { name: /^_ga/, // regex: match all cookies starting with '_ga' }, { name: '_gid', // string: exact cookie name } ] }, // https://cookieconsent.orestbida.com/reference/configuration-reference.html#category-services services: { ga: { label: 'Google Analytics', onAccept: () => {}, onReject: () => {} }, youtube: { label: 'Youtube Embed', onAccept: () => {}, onReject: () => {} }, } }, //ads: {} }, language: { default: 'en', translations: { en: { consentModal: { title: 'We use cookies', description: 'We use cookies to provide our services and for analytics and marketing. To find out more about our use of cookies, please see our Privacy Policy. By continuing to browse our website, you agree to our use of cookies. <a href="page/cookie-policy">Cookie policy</a>', acceptAllBtn: 'Accept all', acceptNecessaryBtn: 'Accept Necessary', showPreferencesBtn: 'Manage Individual preferences', // closeIconLabel: 'Reject all and close modal', footer: ``, }, preferencesModal: { title: 'Manage cookie preferences', acceptAllBtn: 'Accept all', acceptNecessaryBtn: 'Accept Necessary', savePreferencesBtn: 'Accept current selection', closeIconLabel: 'Close modal', serviceCounterLabel: 'Service|Services', sections: [ { title: 'Your Privacy Choices', description: `In this panel you can express some preferences related to the processing of your personal information. You may review and change expressed choices at any time by resurfacing this panel via the provided link. To deny your consent to the specific processing activities described below, switch the toggles to off or use the “Reject all” button and confirm you want to save your choices.`, }, { title: 'Strictly Necessary', description: 'These cookies are essential for the proper functioning of the website and cannot be disabled.', //this field will generate a toggle linked to the 'necessary' category linkedCategory: 'necessary', cookieTable: { caption: 'Cookie table', headers: { name: 'Cookie', domain: 'Domain', desc: 'Description' }, body: [ { name: 'XSRF-TOKEN', domain: location.hostname, desc: 'csrf security', }, { name: APP_UID+'_session', domain: location.hostname, desc: 'user session', }, { name: APP_UID+'_token', domain: location.hostname, desc: 'user remember', }, { name: APP_UID+'_tz', domain: location.hostname, desc: 'timezone', } ] } }, { title: 'Performance and Analytics', description: 'These cookies collect information about how you use our website. All of the data is anonymized and cannot be used to identify you.', linkedCategory: 'analytics', cookieTable: { caption: 'Cookie table', headers: { name: 'Cookie', domain: 'Domain', desc: 'Description' }, body: [ { name: '_ga', domain: location.hostname, desc: 'Description 1', }, { name: '_gid', domain: location.hostname, desc: 'Description 2', } ] } }, { title: 'More information', description: 'For any queries in relation to my policy on cookies and your choices, please <a href="contact">contact us</a>' } ] } } } } }); }); </script> </body> </html>