diff --git a/AGENTS.md b/AGENTS.md index 1b041985..3b0b0ba9 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -16,6 +16,16 @@ The canonical task→convention dispatch table lives in `~/.claude/agentics/temp ## Project context Overture Docs is the **centralized documentation site for the Overture stack**, built with [Docusaurus](https://docusaurus.io/). It aggregates Markdown from the `/docs` directory of each Overture project repository (pulled in as git submodules) and renders them as one navigable site. The point of the setup is that documentation stays with its source project while readers get a single hub. Full overview in [README.md](README.md); planned work lives in `.dev/roadmap.md`. +## Verifying a UI change + +A clean `npm run build` says a page compiled, not that it looks right. Before calling any visual change done, render it and look at it. + +- Build and serve: `cd website && npm run build && npm run serve --port `. `npm start` renders in the browser, so its HTML is an empty shell and tells you nothing. +- Screenshot with a real browser at a real viewport. Chrome's `--headless --screenshot --window-size` is unreliable: the window size is not the CSS viewport, and it will show you clipping and overflow that do not exist. Drive it with `puppeteer-core` and `page.setViewport` instead, pointing `executablePath` at the installed Chrome. Install it outside this repo (the scratchpad), never as a dependency here. +- Check the widths that change behaviour, not just the one you are working at. The marketing navbar collapses at 1160px. +- When something looks wrong, measure it in the page (`getComputedStyle`, `getBoundingClientRect`, `scrollWidth` against `clientWidth`) before changing CSS. Reading a screenshot is guessing. +- Check a documentation page too whenever marketing styles or theme components change, since both sites share one build. + ## Project-specific constraints - **Public repository** (`overture-stack/docs`): no credentials, secrets, tokens, or private URLs in any committed file, ever. - **Documentation content is owned by the submodules, not this repo.** Each project's docs live in `submodules//docs/` and are symlinked into `website/docs/`. Edit the **source** file under `submodules//`, never the symlinked copy under `website/docs/`: editing through the link mutates the vendored submodule's working tree in a way that is easy to lose and confusing to review. Symlinks are (re)generated by `symlinker.sh`. @@ -24,7 +34,10 @@ Overture Docs is the **centralized documentation site for the Overture stack**, ## Repository orientation - `submodules/` — vendored Overture repos as git submodules: `arranger`, `lectern`, `lyric`, `maestro`, `score`, `song`, `stage`, and `.github`. Each carries its own `/docs`. Defined in `.gitmodules` (see `.dev/roadmap.md`: release-branch tracking is not yet configured, so `git submodule update --remote` currently follows each repo's default branch). -- `website/` — the Docusaurus site: `docs/` (aggregated, symlinked from submodules), `community/`, `guides/`, and `src/` (`components/`, `css/`, `theme/`, `pages/`). +- `website/` — the Docusaurus site: `docs/` (aggregated, symlinked from submodules), `community/`, `guides/`, and `src/` (`components/`, `css/`, `marketing/`, `theme/`, `pages/`). +- `website/src/marketing/`: the overture.bio marketing pages ported from the Gatsby site: components, constants, case-study data, and the Sass tree. Its styles are all imported inside `.marketing` by `styles/index.scss`, and only `MarketingPage.tsx` imports that file, which is what keeps Bulma off the documentation pages in the same build. Add marketing styles as a partial imported there, never as a stray `import "./styles.scss"` in a component. - `symlinker.sh` — regenerates the symlinks that bring selected submodule `/docs` files into `website/docs/`. +- `website/netlify/` — configuration staged for stage 3 of the overture.bio port, not read by anything today. This repo has no live `netlify.toml`: `docs.overture.bio` is configured through the Netlify UI, and committing one would move that configuration out from under a working site. See the README in that directory before adding anything to it. +- `website-legacy/` is **gone** as of 2026-08-07. It was a staged copy of the [overture-stack/website](https://github.com/overture-stack/website) Gatsby site, kept while the port ran. Everything it held is either ported into `website/` or still in that repo, which is what serves overture.bio until stage 3 and is cloned locally at `~/Desktop/website`. Changes that need to reach the live site go there, not here. - `README.md`, `preview.png` — human-facing project overview and screenshot. - Local setup: Node 18+, `npm ci`, then `npm start` (see `README.md` § Getting Started). diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..0ad25db4 --- /dev/null +++ b/LICENSE @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. diff --git a/README.md b/README.md index 46a080b2..7e3e06e8 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,17 @@ npm start > [!IMPORTANT] > Docusaurus requires node version 18 or higher. All npm commands run from `website/`; there is no package manifest at the repository root. +### Checking your work + +Two commands are worth running before you open a pull request, both from `website/`: + +```bash +npm run typecheck # tsc across website/src/ +npm run build # fails on any broken internal link +``` + +`onBrokenLinks` is set to `throw`, so a link to a page that does not exist fails the build rather than shipping a 404. + ## How Overture Docs Works - **Docusaurus**: We use Docusaurus to render the site, providing a sleek and navigable interface for our documentation. @@ -59,6 +70,25 @@ npm start - **Interactive components**: Some pages embed browser-based tooling (the configuration generator, the Lectern dictionary playground, and the Song schema playground) built as React components under `website/src/components/`. +- **Marketing pages**: This repository is also becoming the home of the overture.bio marketing site, ported from the Gatsby site at [overture-stack/website](https://github.com/overture-stack/website). Those pages are described in the next section. + +## The overture.bio marketing pages + +The marketing site is being ported into this repository so that one codebase serves both hostnames. The port is partway through: the pages are here and they build, but nothing a visitor sees has changed yet. + +- **Where the code lives**: shared components, constants, case-study data and stylesheets are under `website/src/marketing/`. Each route is a page under `website/src/pages/`, one directory apiece: `about-us`, `acknowledgements`, `case-studies`, `getting-started`, `home`, `privacy`, `products`, `services`, and `terms-conditions`. + +- **The two sites have separate navigation**: the marketing pages carry their own navbar and footer, so a reader can tell which of the two sites they are on. `website/src/theme/Navbar/` and `website/src/theme/Footer/` decide which to render based on the route, and the marketing versions live in `website/src/marketing/components/`. Documentation routes are untouched and keep the navbar configured in `docusaurus.config.ts`. Adding a marketing nav item means editing `MarketingNavbar.tsx`, not the site config. + +- **The home page is at `/home/`, not `/`**: this build's `/` is the documentation homepage. The marketing build gets its own root later in the port, when the two hostnames are separated. + +- **Styles are Sass, and they are scoped**: the marketing pages carry their own styling, which would otherwise fight the documentation theme. Every stylesheet is imported inside a `.marketing` block by `website/src/marketing/styles/index.scss`, and only `MarketingPage.tsx` imports that file. If you are adding marketing styles, add a partial and import it there rather than importing a stylesheet from a component. + +- **Assets live in `website/static/img/marketing/`** and are referenced by path, not imported. + +> [!NOTE] +> The staged copy of the Gatsby site that lived here during the port was deleted once the pages were rebuilt. [overture-stack/website](https://github.com/overture-stack/website) still serves overture.bio, so a change that needs to reach the live site goes there, not here. + ## Repository structure ``` @@ -86,9 +116,12 @@ npm start ├── /src/ # Website source code │ ├── /components/ # React components │ ├── /css/ # Component-specific styles + │ ├── /marketing/ # Components, data and Sass for the overture.bio pages │ ├── /theme/ # Global theme configuration and styling - │ └── /pages/ # Static page content + │ └── /pages/ # Static page content, including the marketing routes + ├── /netlify/ # Redirects staged for the marketing site (not live, see its README) ├── /static/ # Static assets served as-is + │ └── /img/marketing/ # Images and icons for the marketing pages ├── docusaurus.config.ts # Site config: plugin instances, navbar, redirects └── *Sidebars.ts # One sidebar file per documentation section ``` @@ -102,6 +135,8 @@ npm start - **/docs/use-docs/**: Task-oriented content for people working with a running platform - **/docs/community-docs/**: Community-focused content, including the org-wide documentation standards linked from the `.github` submodule - **/src/**: Website implementation files including custom components, styling, and page content + - **/src/marketing/**: The overture.bio marketing pages, rebuilt from the Gatsby site + - **/netlify/**: Redirects the marketing site will need when it gets its own Netlify site. Nothing reads them yet > [!IMPORTANT] > Documentation content is owned by the submodules, not by this repository. Where a page under `website/docs/` is a symlink, edit the source file in `submodules//docs/` and land the change through that project's own repository. @@ -118,3 +153,9 @@ npm start > [!TIP] > The Overture Docs repo contains everything, therefore finding & tracking links and content across all our repos has never been easier. + +## License + +Licensed under the GNU Affero General Public License v3.0 (AGPL-3.0), the same license as the rest of the Overture stack. See [LICENSE](LICENSE) for the full text, and the [licensing page](https://docs.overture.bio/community/licensing) for an overview. + +Documentation content vendored from the `submodules/` repositories remains under the license of its own repository. diff --git a/submodules/song b/submodules/song index 9076236f..0912e767 160000 --- a/submodules/song +++ b/submodules/song @@ -1 +1 @@ -Subproject commit 9076236f37fb4cb4ac3555897f2b40d9e3060e0b +Subproject commit 0912e767ebceed7e2448b26af7ba4740b66fff5b diff --git a/website/docs/community-docs/02-funding.md b/website/docs/community-docs/02-funding.md index 5c9b54ab..fc52dba0 100644 --- a/website/docs/community-docs/02-funding.md +++ b/website/docs/community-docs/02-funding.md @@ -1,46 +1,66 @@ # Funding -We gratefully acknowledge the financial support that has made this project possible. Our work has been funded by the following organizations and grants: +Overture is developed and built by the Genome Informatics program at the Ontario +Institute for Cancer Research, on grants from Canadian and American public +funders. This page lists every grant supporting the platform, its funding +period, and the organizations contributing to individual components. -### Digital Research Alliance of Canada +## Digital Research Alliance of Canada -Under this grant we are transforming Overture from a data catalogue into an AI-assisted discovery and analysis platform. Using the Model Context Protocol (MCP), researchers will be able to explore, analyze, and visualize data through transparent, reproducible conversational workflows across Canadian research platforms including ICGC-ARGO, OHCRN, and iMicroSeq. +**2026 to 2028.** Under this grant we are transforming Overture from a data +catalogue into an AI-assisted discovery and analysis platform. Using the Model +Context Protocol (MCP), researchers will be able to explore, analyze, and +visualize data through transparent, reproducible conversational workflows across +Canadian research platforms including ICGC-ARGO, OHCRN, and iMicroSeq. -- **Funding Period:** 2026–2028 +## Canadian Institutes of Health Research -### Canadian Institutes of Health Research +**2023 to 2028.** As working group 2, we are developing a federated framework +for genomic data access and analysis, specifically building an enhanced Overture +data submission system to facilitate the ingestion, validation, and tracking of +clinical and molecular data into the PCGL platform. -As working group 2, we are developing a federated framework for genomic data access and analysis, specifically building an enhanced Overture data submission system to facilitate the ingestion, validation, and tracking of clinical and molecular data into the PCGL platform. +- Project: [Pan-Canadian Genome Library (PCGL)](https://genomelibrary.ca/) -- **Project Title:** [Pan-Canadian Genome Library (PCGL)](https://genomelibrary.ca/) -- **Funding Period:** 2023-2028 +## National Cancer Institute at the US National Institutes of Health -### National Cancer Institute at the US National Institutes of Health +**2021 to 2026.** Under this grant we are making the Overture platform more +accessible by breaking down barriers to adoption, integrating existing analysis +tools and adding federated search functionality between distributed Overture +instances using standardized data governance frameworks +([GA4GH](https://www.ga4gh.org/)). -Under this grant we are making the Overture platform more accessible by breaking down barriers to adoption, integrating existing analysis tools and adding federated search functionality between distributed Overture instances using standardized data governance frameworks ([GA4GH](https://www.ga4gh.org/)). +- Grant number: #U24CA253529 -- **Grant Number:** #U24CA253529 -- **Funding Period:** 2021-2026 +## Canadian COVID-19 Genomic Data Infrastructure -### Canadian COVID-19 Genomic Data Infrastructure +**2020 to 2024.** The [VirusSeq Data +Portal](https://virusseq-dataportal.ca/), since renamed iMicroSeq, demonstrated +Overture's expansion beyond oncology data management. This funding adapted the +platform to support viral genomic data, and enabled the development, deployment +and ongoing maintenance of Canada's central SARS-CoV-2 sequence repository. -The [VirusSeq Data Portal](https://virusseq-dataportal.ca/) demonstrates Overture's successful expansion beyond oncology data management. This funding facilitated the platform's adaptation to support viral genomic data, while enabling the development, deployment, and ongoing maintenance of Canada's central SARS-CoV-2 sequence repository. +- Project: VirusSeq Data Portal, now iMicroSeq -- **Project Title:** VirusSeq Data Portal -- **Funding Period:** 2020-2024 +## The Government of Ontario -### The Government of Ontario - -The Overture platform is developed and maintained by the Genome Informatics Software Engineering group at the Ontario Institute for Cancer Research (OICR). Our team is partly supported through operational funding from the Government of Ontario via OICR. OICR receives core funding from the Government of Ontario through the Ministry of Colleges and Universities. +Our team is partly supported through operational funding from the Government of +Ontario via OICR, which receives core funding from the Government of Ontario +through the Ministry of Colleges and Universities. ## Additional Support -We also thank the following organizations for their contributions: - -- [**EMBL's European Bioinformatics Institute**](https://www.ebi.ac.uk/) - For contributions to Lectern -- [**The Ferretti Lab at the Research Center of the CHU Sainte-Justine**](https://ferlab.bio/a-propos) - For contributions to Arranger -- [**The South African National Bioinformatics Institute (SANBI)**](https://www.sanbi.ac.za/) - Provided valuable feedback on documentation gaps and identified improvements that significantly enhanced the Overture's platform and user experience. +We also thank the following organizations for their contributions to Overture. -For more information about our funding or to discuss potential collaborations, please email us at contact@overture.bio. +- [EMBL's European Bioinformatics Institute](https://www.ebi.ac.uk/), for + contributions to Lectern. +- [The Ferretti Lab at the Research Center of the CHU + Sainte-Justine](https://ferlab.bio/a-propos), for contributions to Arranger. +- [The South African National Bioinformatics Institute + (SANBI)](https://www.sanbi.ac.za/), for feedback that identified documentation + gaps and improvements to the platform and its user experience. -**Last updated:** 07-22-26 +:::info Talk to us about funding +For more information about our funding, or to discuss a potential collaboration, +email us at **contact@overture.bio** +::: diff --git a/website/docs/community-docs/06-citing-us.md b/website/docs/community-docs/06-citing-us.md index 6abbaf60..84e191cf 100644 --- a/website/docs/community-docs/06-citing-us.md +++ b/website/docs/community-docs/06-citing-us.md @@ -1,39 +1,51 @@ -# Publication & Citation +# How to Cite Us -## About Our Publication +Please cite the Overture paper when you use Overture components in your research, +build a data platform with them, reference the architecture, or compare genomics +data platforms. -Overture has been published in **GigaScience**, Volume 14, 2025, with the article ID giaf038. This publication describes our open-source genomics data platform and its impact on the research community. +## The paper -### Publication Details +**Overture: an open-source genomics data platform.** Published in *GigaScience*, +volume 14, 2025, article giaf038. The paper describes Overture's architecture, +the responsibilities of each component, and the research platforms built on it. -**Title:** Overture: an open-source genomics data platform +[Read the paper](https://doi.org/10.1093/gigascience/giaf038), available under a +[Creative Commons Attribution 4.0 International +licence](https://creativecommons.org/licenses/by/4.0/). -**Journal:** GigaScience +- Journal: GigaScience +- Volume 14, 2025 +- Article ID: giaf038 +- DOI: [10.1093/gigascience/giaf038](https://doi.org/10.1093/gigascience/giaf038) +- Licence: CC BY 4.0 -**Volume:** 14 +## Citation formats -**Year:** 2025 +### Standard -**Article ID:** giaf038 - -**DOI:** [https://doi.org/10.1093/gigascience/giaf038](https://doi.org/10.1093/gigascience/giaf038) - -**License:** Creative Commons Attribution 4.0 International License (CC BY 4.0) - -### Authors +``` +Shiell, M., Bajari, R., Andric, D., et al. Overture: an open-source genomics data platform. +GigaScience 14, giaf038 (2025). https://doi.org/10.1093/gigascience/giaf038 +``` -Mitchell Shiell, Rosi Bajari, Dusan Andric, Jon Eubank, Brandon F Chan, Anders J Richardsson, Azher Ali, Bashar Allabadi, Yelizar Alturmessov, Jared Baker, Ann Catton, Kim Cullion, Daniel DeMaria, Patrick Dos Santos, Henrich Feher, Francois Gerthoffert, Minh Ha, Robin A Haw, Atul Kachru, Alexandru Lepsa, Alexis Li, Rakesh N Mistry, Hardeep K Nahal-Bose, Aleksandra Pejovic, Samantha Rich, Leonardo Rivera, Ciarán Schütte, Edmund Su, Robert Tisma, Jaser Uddin, Chang Wang, Alex N Wilmer, Linda Xiang, Junjun Zhang, Lincoln D Stein, Vincent Ferretti, Mélanie Courtot, Christina K Yung +### APA -## How to Cite Overture +``` +Shiell, M., Bajari, R., Andric, D., Eubank, J., Chan, B. F., Richardsson, A. J., ... Yung, C. K. (2025). +Overture: an open-source genomics data platform. GigaScience, 14, giaf038. +https://doi.org/10.1093/gigascience/giaf038 +``` -### Standard Citation Format +### Vancouver ``` -Shiell, M., Bajari, R., Andric, D., et al. Overture: an open-source genomics data platform. -GigaScience 14, giaf038 (2025). https://doi.org/10.1093/gigascience/giaf038 +Shiell M, Bajari R, Andric D, Eubank J, Chan BF, Richardsson AJ, et al. +Overture: an open-source genomics data platform. GigaScience. 2025;14:giaf038. +Available from: https://doi.org/10.1093/gigascience/giaf038 ``` -### BibTeX Citation +### BibTeX ```bibtex @article{shiell2025overture, @@ -49,27 +61,13 @@ GigaScience 14, giaf038 (2025). https://doi.org/10.1093/gigascience/giaf038 } ``` -### APA Format - -``` -Shiell, M., Bajari, R., Andric, D., Eubank, J., Chan, B. F., Richardsson, A. J., ... Yung, C. K. (2025). -Overture: an open-source genomics data platform. GigaScience, 14, giaf038. -https://doi.org/10.1093/gigascience/giaf038 -``` - -### Vancouver Format - -``` -Shiell M, Bajari R, Andric D, Eubank J, Chan BF, Richardsson AJ, et al. -Overture: an open-source genomics data platform. GigaScience. 2025;14:giaf038. -Available from: https://doi.org/10.1093/gigascience/giaf038 -``` - -## When to Cite - -Please cite our publication when: +## Authors -- Using Overture software components in your research -- Building data platforms with Overture -- Referencing Overture's architecture or methodology -- Comparing genomics data platforms +Mitchell Shiell, Rosi Bajari, Dusan Andric, Jon Eubank, Brandon F Chan, Anders J +Richardsson, Azher Ali, Bashar Allabadi, Yelizar Alturmessov, Jared Baker, Ann +Catton, Kim Cullion, Daniel DeMaria, Patrick Dos Santos, Henrich Feher, Francois +Gerthoffert, Minh Ha, Robin A Haw, Atul Kachru, Alexandru Lepsa, Alexis Li, +Rakesh N Mistry, Hardeep K Nahal-Bose, Aleksandra Pejovic, Samantha Rich, +Leonardo Rivera, Ciarán Schütte, Edmund Su, Robert Tisma, Jaser Uddin, Chang +Wang, Alex N Wilmer, Linda Xiang, Junjun Zhang, Lincoln D Stein, Vincent +Ferretti, Mélanie Courtot, Christina K Yung. diff --git a/website/docusaurus.config.ts b/website/docusaurus.config.ts index e652faf6..7cce90cf 100644 --- a/website/docusaurus.config.ts +++ b/website/docusaurus.config.ts @@ -58,6 +58,37 @@ const config: Config = { plugins: [ "./docsPlugin.ts", + [ + // The marketing pages ported from the Gatsby site are written in Sass. + // `includePaths` and `quietDeps` went with Bulma in rebuild phase 2: there + // is no longer a node_modules stylesheet to resolve by package name, and no + // third-party Sass whose deprecations need silencing. + "docusaurus-plugin-sass", + { + sassOptions: { + // The marketing styles nest their imports inside `.marketing` to keep + // their bare class names and element rules off the documentation pages, + // which share one emitted stylesheet with them. `@use` cannot be nested + // inside a selector, so `@import` is the only way to express that. + // Removing Bulma did not resolve this: the site's own partials need the + // same scoping, so the Dart Sass 3.0 deadline is still live and now + // needs a different answer. See .dev/roadmap.md. + silenceDeprecations: ["import"], + }, + }, + ], + // Tailwind runs through PostCSS. It is prefixed and preflight-free (see + // src/css/tailwind.css), so unlike Bulma it can be a global stylesheet + // without reaching the documentation pages' typography. + function tailwindPlugin() { + return { + name: "tailwind-plugin", + configurePostCss(postCssOptions: { plugins: unknown[] }) { + postCssOptions.plugins.push(require("@tailwindcss/postcss")); + return postCssOptions; + }, + }; + }, [ "@docusaurus/plugin-content-docs", { @@ -360,7 +391,7 @@ const config: Config = { onUntruncatedBlogPosts: "warn", }, theme: { - customCss: "./src/css/custom.css", + customCss: ["./src/css/custom.css", "./src/css/tailwind.css"], }, } satisfies Preset.Options, ], diff --git a/website/netlify/README.md b/website/netlify/README.md new file mode 100644 index 00000000..f9b04b6c --- /dev/null +++ b/website/netlify/README.md @@ -0,0 +1,24 @@ +# Netlify configuration, staged + +Nothing in this directory is live. It holds configuration the **marketing** Netlify site will need at stage 3 of the overture.bio port, kept in version control so it does not have to be rediscovered in another repository. + +## Why it is not a `netlify.toml` + +Netlify reads `netlify.toml` from a site's base directory, which for this repository is `website/`. This directory is one level below that and the file is named differently, so Netlify ignores it. + +That is deliberate. **This repository has no `netlify.toml` today**: `docs.overture.bio` is configured through the Netlify UI, and committing a live config file would move the build command, publish directory and environment out from under a site that is currently working. Whoever sets up the second site should copy these rules into that site's own configuration rather than promoting this file into one. + +## What is here + +- **`marketing-redirects.toml`** — every redirect the `overture.bio` host owes. Two groups: + 1. The twelve `/documentation/*` rules, lifted verbatim from the Gatsby site's `netlify.toml` when `website-legacy/` was deleted on 2026-08-07. These URLs were public for years and each section is sent to its nearest equivalent on the docs site. + 2. The four routes the rebuild retired: `/getting-started/`, `/acknowledgements/`, `/services/` and `/case-studies/`. + +## The part a redirect cannot do + +`/case-studies/#icgcargo` and its siblings are linked from outside the site. Browsers do not send the fragment to the server, so no redirect rule can move it. What makes those links land on the right card is that the `/impact/` hub keeps the same ids on its platform cards. That is the mechanism; the redirect only gets the visitor to the right page. + +## Related + +- `.dev/roadmap.md` § Stage 3, for what else that stage has to write (a sitemap and `robots.txt` under the canonical host, and the apex versus `www` decision). +- `src/marketing/constants/pages.ts`, which lists the same retired routes beside the path constants. diff --git a/website/netlify/marketing-redirects.toml b/website/netlify/marketing-redirects.toml new file mode 100644 index 00000000..5a639519 --- /dev/null +++ b/website/netlify/marketing-redirects.toml @@ -0,0 +1,211 @@ +# Redirects the overture.bio (marketing) Netlify site has to carry. +# +# NOT LIVE. Netlify reads `netlify.toml` from a site's base directory, which for +# this repo is `website/`. This file is one directory down and under a different +# name, so nothing reads it today. It is staged here for stage 3, which is when +# the marketing build gets its own Netlify site and its own `netlify.toml`. See +# `netlify/README.md` next to this file, and `.dev/roadmap.md` § Stage 3. +# +# It carries no `[build]` block on purpose. This repo has no `netlify.toml` at +# all today: docs.overture.bio is configured through the Netlify UI, and adding +# a live config file would move that configuration out from under it. Whoever +# sets up the second site should paste these rules into that site's own config, +# not promote this file into one. +# +# --------------------------------------------------------------------------- +# 1. /documentation/*, lifted verbatim from the Gatsby site's netlify.toml +# --------------------------------------------------------------------------- +# +# The /documentation section was retired: docs.overture.bio is the only Overture +# documentation surface. These URLs were public for years, so each section goes +# to its nearest equivalent on the docs site rather than to a single landing +# page. Ego and the DMS bundle have no successor, so they point at the Keycloak +# deployment guide and Prelude respectively. +# +# No `:splat` on the targets, deliberately: a deep URL under any of these lands +# on the section overview rather than 404ing at a path the docs site does not +# have. That was the Gatsby site's call and it still holds. +# +# These are cross-host, and they stay that way. The hostnames decision keeps +# overture.bio and docs.overture.bio as separate sites, so `onBrokenLinks` +# cannot validate any of these targets. Check them by hand when the docs +# information architecture moves. + +[[redirects]] + from = "/documentation/song/*" + to = "https://docs.overture.bio/develop/Song/overview" + status = 301 + force = true + +[[redirects]] + from = "/documentation/score/*" + to = "https://docs.overture.bio/develop/Score/overview" + status = 301 + force = true + +[[redirects]] + from = "/documentation/maestro/*" + to = "https://docs.overture.bio/develop/Maestro/overview" + status = 301 + force = true + +[[redirects]] + from = "/documentation/arranger/*" + to = "https://docs.overture.bio/develop/Arranger/overview" + status = 301 + force = true + +[[redirects]] + from = "/documentation/stage/*" + to = "https://docs.overture.bio/develop/Stage/overview" + status = 301 + force = true + +[[redirects]] + from = "/documentation/guides/deployment/*" + to = "https://docs.overture.bio/deploy/deployment" + status = 301 + force = true + +[[redirects]] + from = "/documentation/guides/administration/*" + to = "https://docs.overture.bio/use" + status = 301 + force = true + +[[redirects]] + from = "/documentation/guides/submission/*" + to = "https://docs.overture.bio/use/cli-submissions" + status = 301 + force = true + +[[redirects]] + from = "/documentation/guides/download/*" + to = "https://docs.overture.bio/use/cli-downloads" + status = 301 + force = true + +[[redirects]] + from = "/documentation/dms/*" + to = "https://docs.overture.bio/deploy/prelude" + status = 301 + force = true + +[[redirects]] + from = "/documentation/ego/*" + to = "https://docs.overture.bio/deploy/deployment/keycloak" + status = 301 + force = true + +# Catch-all: must stay last of the /documentation rules, since Netlify matches +# redirects in file order. +[[redirects]] + from = "/documentation/*" + to = "https://docs.overture.bio/develop" + status = 301 + force = true + +# --------------------------------------------------------------------------- +# 2. Routes the rebuild retired, per the table in .dev/ia-proposal.md +# --------------------------------------------------------------------------- +# +# No `force` on these: the pages genuinely do not exist in the new build, and +# Netlify applies a redirect when nothing else matches. Forcing would only mask +# a future page at the same path. +# +# `constants/pages.ts` carries the same list beside the path constants. Keep the +# two in step. + +# Retired in rebuild phase 1. The Get Started handoff points at the docs +# quickstart, which is the whole reason this page could go. +[[redirects]] + from = "/getting-started/*" + to = "https://docs.overture.bio/deploy/prelude" + status = 301 + +# Retired in rebuild phase 1, folded into the funding page. That page has since +# been retired too, so this goes straight to the docs site rather than chaining +# through /about/funding/, which no longer exists. +[[redirects]] + from = "/acknowledgements/*" + to = "https://docs.overture.bio/community/funding" + status = 301 + +# The two pages phase 1 moved out of `docs/community-docs/` and onto the +# marketing site, moved back. Each held a second copy of a document whose +# maintained version is on the docs site: five grants, and one paper with its +# citation formats. The docs pages carry that content again and are no longer +# pointers here, so these two routes go to them, cross-host. +[[redirects]] + from = "/about/funding/*" + to = "https://docs.overture.bio/community/funding" + status = 301 + +[[redirects]] + from = "/impact/publications/*" + to = "https://docs.overture.bio/community/citing-us" + status = 301 + +# Renamed in rebuild phase 4. "Services" read commercial for a not-for-profit. +[[redirects]] + from = "/services/*" + to = "/collaborate/" + status = 301 + +# Renamed in rebuild phase 4. This is the one that matters: it is the only +# retired route with inbound fragment links (`#icgcargo` and its siblings). +# Browsers preserve the fragment across a 301 and do not send it to the server, +# so what makes those links land is the matching ids kept on the /impact/ hub, +# not anything in this file. Do not renumber or tidy those ids. +[[redirects]] + from = "/case-studies/*" + to = "/impact/" + status = 301 + +# The four platform pages, retired when the hub absorbed their write-ups: the +# content did not move sites, it moved up one level into a section of /impact/. +# The fragment is written out here, unlike the /case-studies/ rule above, because +# an inbound link to one of these carries no fragment of its own to preserve. +# +# The ids are `Platform.id` in data/platforms.ts, which is why two of them do not +# match their old slug: /imicroseq/ is `#virusseq` and /hcmi/ is +# `#humanCancerModels`, both older spellings kept so existing links still land. +[[redirects]] + from = "/impact/icgc-argo/*" + to = "/impact/#icgcargo" + status = 301 + +[[redirects]] + from = "/impact/imicroseq/*" + to = "/impact/#virusseq" + status = 301 + +[[redirects]] + from = "/impact/ihcc/*" + to = "/impact/#ihcc" + status = 301 + +[[redirects]] + from = "/impact/hcmi/*" + to = "/impact/#humanCancerModels" + status = 301 + +# Removed rather than renamed. /about-us/ was a page; what it said is now the +# "What we do" band on the home page, so this lands on that band rather than on +# the top of the page. The fragment is the `id` on the band's own heading +# (HomeCollaborate.tsx) and is the same address the footer's Our story uses, +# spelled once as `ABOUT_BAND` in constants/pages.ts. The navbar's About item was +# the third user of it and is gone; this rule is unaffected. +# +# The fragment is in this file rather than left to the browser because there is +# nothing to preserve: an inbound /about-us/ link carries no fragment of its own, +# unlike the /case-studies/ rule above. +[[redirects]] + from = "/about-us/*" + to = "/home/#collaborate-heading" + status = 301 + +# Not included, deliberately: +# +# /home/ -> / Not a redirect. Stage 3 gives the marketing build +# its own `url`, which makes /home/ the root. diff --git a/website/package-lock.json b/website/package-lock.json index 981651b3..bd069d1b 100644 --- a/website/package-lock.json +++ b/website/package-lock.json @@ -21,12 +21,17 @@ "@overture-stack/lectern-dictionary": "^2.0.0", "@overture-stack/lectern-ui": "^1.0.0", "@replit/codemirror-indentation-markers": "^6.5.3", + "@tailwindcss/postcss": "^4.3.3", "@uiw/react-codemirror": "^4.25.11", "clsx": "^2.0.0", + "docusaurus-plugin-sass": "^0.2.6", "prism-react-renderer": "^2.3.0", "react": "^18.0.0", "react-dom": "^18.0.0", - "swagger-ui-react": "^5.29.2" + "sass": "^1.102.0", + "simple-icons": "^16.28.0", + "swagger-ui-react": "^5.29.2", + "tailwindcss": "^4.3.3" }, "devDependencies": { "@docusaurus/module-type-aliases": "^3.10.2", @@ -286,6 +291,18 @@ "node": ">= 14.0.0" } }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@antfu/install-pkg": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@antfu/install-pkg/-/install-pkg-1.1.0.tgz", @@ -5227,6 +5244,319 @@ "zod": "^3.23.8" } }, + "node_modules/@parcel/watcher": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.6.0.tgz", + "integrity": "sha512-7FNeNl8NCE7aINx7WXiKQrPYZWC/hvrTsmk6zmxbI7LTXE7hVek/n8AfVgpe2y82zl3w0HvCHN0bVKMBoJcC0w==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "detect-libc": "^2.0.3", + "is-glob": "^4.0.3", + "node-addon-api": "^7.0.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.6.0", + "@parcel/watcher-darwin-arm64": "2.6.0", + "@parcel/watcher-darwin-x64": "2.6.0", + "@parcel/watcher-freebsd-x64": "2.6.0", + "@parcel/watcher-linux-arm-glibc": "2.6.0", + "@parcel/watcher-linux-arm-musl": "2.6.0", + "@parcel/watcher-linux-arm64-glibc": "2.6.0", + "@parcel/watcher-linux-arm64-musl": "2.6.0", + "@parcel/watcher-linux-x64-glibc": "2.6.0", + "@parcel/watcher-linux-x64-musl": "2.6.0", + "@parcel/watcher-win32-arm64": "2.6.0", + "@parcel/watcher-win32-x64": "2.6.0" + } + }, + "node_modules/@parcel/watcher-android-arm64": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.6.0.tgz", + "integrity": "sha512-trgpLSCKRC/huFjXX/Smh+0sWe4+YtKfktIToiMl59ghz7z+qkH6kMvNnUbLyRs9N11t8l4svSCs1+5B3rOAhA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.6.0.tgz", + "integrity": "sha512-Y3QV0gl7Q1zbfueunkWIERICbEojQFCgpyG7YqOGNFLsckXyI1xu9mAIUpKY9QBYzBtSkN8dBPwd3yiAO9ovMw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-x64": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.6.0.tgz", + "integrity": "sha512-Ohv6OpzhUfKYD7Beb8kDvG0jbIxORCYY1JRdZnaBtnjjkJxgD7ZVL0nw2sCYd0yTMKTvz3nnTnOF3cDifK+kvw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-freebsd-x64": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.6.0.tgz", + "integrity": "sha512-5HmXvDgs8VK+74jF9y9/2FE3/OnlcKmc56tjmSrEuZjpSZOGL+fvAu+HKJBdPs9uwoP2hE6TlSUpXZ/C5jUFmQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-glibc": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.6.0.tgz", + "integrity": "sha512-Ps/hui3A+vMbjdqlqAowK2ZL8+BO8dBjxeWXj6npTBs3jx4wWmbPpaLuqwrQrSqIVMCnpWo238bJ1U37GhQOYg==", + "cpu": [ + "arm" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-musl": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.6.0.tgz", + "integrity": "sha512-9c6AUHgHoG+IY88MRIHupztQiQnrbqHYQjkM2btA+Bf/wQnQMuiD0Wfk1EVv3TlNT3x41uU71rn6E4xh/+zvkw==", + "cpu": [ + "arm" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-glibc": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.6.0.tgz", + "integrity": "sha512-yHRqS2owEXe6Hic9z6Mh1ECsCd+ODVOGvZDyciqRd21+v+o+DnXMOrw50DSpIG2sb8GPEaPPmfeCAWKPJdq46g==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-musl": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.6.0.tgz", + "integrity": "sha512-WhB2e/V7rqdHHWZusBSPuy5Ei8S6lSz6FE5TKKQz5h3a0O+C+mhY7vxU9b/stqvMb8beLnPY82ZrFTLKs+SrKA==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-glibc": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.6.0.tgz", + "integrity": "sha512-ulGE6x6Oz6iAwg75T8YQSoguBWasniIbX+QWpaYPcCnDOpdWX3k+4xbEYPZVLxOuoJI+svJJPD3sEj8G7lrQ3A==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-musl": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.6.0.tgz", + "integrity": "sha512-tkBYKt7YQrjIJWYDnto2YgO8MRkjlMTSNoRHzsXinBqbLdeOM3L32wPZJvIZxqaLMfSlS/4sUjH/6STVP/XDLw==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-arm64": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.6.0.tgz", + "integrity": "sha512-gIZAP23jaHjGWasY/TY6yL7NHFClf0Ga7FN+iINvk+KN94rhm94lYZhFsbYFNcA04/onvGD9kKmiJLJB2HbNwQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.6.0.tgz", + "integrity": "sha512-cA+/pXV2YkfxlIcXOQ5fSWqAzzPyD78/x5qbK/I0vUkrlYHA8TIz+MXjAbGouguKVSI4bOmkTSJ1/poVSsgt+A==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher/node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "license": "MIT", + "optional": true + }, + "node_modules/@parcel/watcher/node_modules/picomatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/@peculiar/asn1-cms": { "version": "2.8.0", "resolved": "https://registry.npmjs.org/@peculiar/asn1-cms/-/asn1-cms-2.8.0.tgz", @@ -6535,34 +6865,311 @@ "integrity": "sha512-DPI8YJ0Vznk4CT+ekn3rcFNq1uQwvUHZhH6WvTSPD0YKBIlMS9ur2RYKghXuxxOiqOam/i4lHJH4xTIiTgs3Mg==", "license": "Apache-2.0", "dependencies": { - "apg-lite": "^1.0.3" + "apg-lite": "^1.0.3" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/@swaggerexpert/json-pointer": { + "version": "2.10.2", + "resolved": "https://registry.npmjs.org/@swaggerexpert/json-pointer/-/json-pointer-2.10.2.tgz", + "integrity": "sha512-qMx1nOrzoB+PF+pzb26Q4Tc2sOlrx9Ba2UBNX9hB31Omrq+QoZ2Gly0KLrQWw4Of1AQ4J9lnD+XOdwOdcdXqqw==", + "license": "Apache-2.0", + "dependencies": { + "apg-lite": "^1.0.4" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/@szmarczak/http-timer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz", + "integrity": "sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==", + "license": "MIT", + "dependencies": { + "defer-to-connect": "^2.0.1" + }, + "engines": { + "node": ">=14.16" + } + }, + "node_modules/@tailwindcss/node": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.3.tgz", + "integrity": "sha512-/T8IKEsf9VTU6tLjgC7+sv2mOPtQxzE2jMw7u4Tt40Tx+QSZxpzh95/H6cMKoja9XuW7iMdLJYBB0o9G1CaAgg==", + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "enhanced-resolve": "^5.24.1", + "jiti": "^2.7.0", + "lightningcss": "1.32.0", + "magic-string": "^0.30.21", + "source-map-js": "^1.2.1", + "tailwindcss": "4.3.3" + } + }, + "node_modules/@tailwindcss/node/node_modules/jiti": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.3.tgz", + "integrity": "sha512-krXjAikiaFSPaK/FkAQT5UTx3VormQaiZ5hBFlJZ9UFQGB/rwg1MZIhHAG9smMQRTdyJxP6Qt5MwMtdyU5FWrA==", + "license": "MIT", + "engines": { + "node": ">= 20" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.3.3", + "@tailwindcss/oxide-darwin-arm64": "4.3.3", + "@tailwindcss/oxide-darwin-x64": "4.3.3", + "@tailwindcss/oxide-freebsd-x64": "4.3.3", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.3", + "@tailwindcss/oxide-linux-arm64-gnu": "4.3.3", + "@tailwindcss/oxide-linux-arm64-musl": "4.3.3", + "@tailwindcss/oxide-linux-x64-gnu": "4.3.3", + "@tailwindcss/oxide-linux-x64-musl": "4.3.3", + "@tailwindcss/oxide-wasm32-wasi": "4.3.3", + "@tailwindcss/oxide-win32-arm64-msvc": "4.3.3", + "@tailwindcss/oxide-win32-x64-msvc": "4.3.3" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.3.tgz", + "integrity": "sha512-Y85A2gmPSkl5Ve5qR86GL4HT509cFqQh1aes9p3sSkyTPwt0Pppf3GkwGe4JPACcRYjgJIEhQgM6dBClnr0NYw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.3.tgz", + "integrity": "sha512-BiaWatpBcERQFDlOjRDpIVXuFK5PJez5SA4JMg6VYZdBYU+qKfV/vqjcIs+IYmtitf1xYQZTwXvU/8y4lfZUGw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.3.tgz", + "integrity": "sha512-fAeUqfV5ndhxRwai8cXGzdLvul9utWOmeTkv69unv4ZXixjn61Z+p9lCWdwOwA3TYboG3BwdVuN/RDjhBRl0mw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.3.tgz", + "integrity": "sha512-iyf5bV6+wnAlflVeEy7R25dupxTNECZN5QMI0qNT6eT+EgaGdZcKhGkr5SdoaWiLJ3spLqIY9VCeSGrwmtg4kw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.3.tgz", + "integrity": "sha512-aAYUprJAJQWWbRrPvtjdroZ56Md+JM8pMiopS6xGEwDfLhqj+2ver2p4nU4Mb3CRqcMmNBjo8KkUgcxhkzVQGQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.3.tgz", + "integrity": "sha512-nDxldcEENOxZRzC2uu9jrutZdAAQtb+8WWDCSnWL1zvBk1+FN+x6MtDViPB5AJMfttVCUhehGWus3XBPgatM/w==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.3.tgz", + "integrity": "sha512-Md44bD6veX/PC5iyF8cDVnw4HBIANZepRZZ7a8DQOvkfo5WUBwcp6iAuCUz23u+4SUkhJlD3eL7hNdW8ezd/kA==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.3.tgz", + "integrity": "sha512-tx7us1muwOKAKWao2v/GaafFeQboE6aj88vC6ziN2NCGcRm8gWUhwjzg+YdVB1e4boAtdtma4L43onunI6NS4w==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.3.tgz", + "integrity": "sha512-SJxX60smvHgasZoBy11dX6YRjXJFovwWBoedhbQPOBzgFWBHGB+TVPWB9BxzR7TTxU8FQZAI2AyiNCMzFm8Img==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.3.tgz", + "integrity": "sha512-jx1+rPhY/5Ympkktd656HBWEBLxP7dH06losBLjjf5vgCODXvi9KhtftWcMIwTFIDqBr7cRnQkdLnAG+IOlGvQ==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.11.1", + "@emnapi/runtime": "^1.11.1", + "@emnapi/wasi-threads": "^1.2.2", + "@napi-rs/wasm-runtime": "^1.1.4", + "@tybys/wasm-util": "^0.10.2", + "tslib": "^2.8.1" }, "engines": { - "node": ">=12.20.0" + "node": ">=14.0.0" } }, - "node_modules/@swaggerexpert/json-pointer": { - "version": "2.10.2", - "resolved": "https://registry.npmjs.org/@swaggerexpert/json-pointer/-/json-pointer-2.10.2.tgz", - "integrity": "sha512-qMx1nOrzoB+PF+pzb26Q4Tc2sOlrx9Ba2UBNX9hB31Omrq+QoZ2Gly0KLrQWw4Of1AQ4J9lnD+XOdwOdcdXqqw==", - "license": "Apache-2.0", - "dependencies": { - "apg-lite": "^1.0.4" - }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.3.tgz", + "integrity": "sha512-3rc292Ca2ceK6Ulcc/bAVnTs/3nDtoPhyEKlgPv+yQJQi/JS/AMJlqzxvlDacL1nekbrcf6bTqp/jV4qgnPxNQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=12.20.0" + "node": ">= 20" } }, - "node_modules/@szmarczak/http-timer": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz", - "integrity": "sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==", + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.3.tgz", + "integrity": "sha512-yJ0pwIVc/nYeGoV02WtsN8KYyLQv7kyI2wDnkezyJlGGjkd4QLwDGAwl47YpPJeuI0M0ObaXGSPjvWDPeTPggw==", + "cpu": [ + "x64" + ], "license": "MIT", - "dependencies": { - "defer-to-connect": "^2.0.1" - }, + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=14.16" + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/postcss": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.3.3.tgz", + "integrity": "sha512-JTSZZGQi1AyKirbLN3azmjVzef92tcX7h+iSqPdaeStyFpGpDlKvvpxeOE8njhbUanbRwr3z8DyzhICWnMtQeg==", + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "@tailwindcss/node": "4.3.3", + "@tailwindcss/oxide": "4.3.3", + "postcss": "^8.5.16", + "tailwindcss": "4.3.3" } }, "node_modules/@tanstack/react-table": { @@ -10339,6 +10946,15 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, "node_modules/detect-node": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", @@ -10398,6 +11014,19 @@ "node": ">=6" } }, + "node_modules/docusaurus-plugin-sass": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/docusaurus-plugin-sass/-/docusaurus-plugin-sass-0.2.6.tgz", + "integrity": "sha512-2hKQQDkrufMong9upKoG/kSHJhuwd+FA3iAe/qzS/BmWpbIpe7XKmq5wlz4J5CJaOPu4x+iDJbgAxZqcoQf0kg==", + "license": "MIT", + "dependencies": { + "sass-loader": "^16.0.2" + }, + "peerDependencies": { + "@docusaurus/core": "^2.0.0-beta || ^3.0.0-alpha", + "sass": "^1.30.0" + } + }, "node_modules/dom-converter": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", @@ -13174,6 +13803,267 @@ "node": ">=6" } }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, "node_modules/lilconfig": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", @@ -13330,6 +14220,15 @@ "yallist": "^3.0.2" } }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, "node_modules/make-asynchronous": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/make-asynchronous/-/make-asynchronous-1.1.0.tgz", @@ -19570,6 +20469,100 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "license": "MIT" }, + "node_modules/sass": { + "version": "1.102.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.102.0.tgz", + "integrity": "sha512-NSOyTnaQF7rTAEOtI2fwb386vL+akyiQLBZu8Na7hXCb+umJy0GAqlcMIaqACZ6Z1VgTBS4K9PG6B3IdjHGJsw==", + "license": "MIT", + "dependencies": { + "chokidar": "^5.0.0", + "immutable": "^5.1.5", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=20.19.0" + }, + "optionalDependencies": { + "@parcel/watcher": "^2.4.1" + } + }, + "node_modules/sass-loader": { + "version": "16.0.8", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-16.0.8.tgz", + "integrity": "sha512-hcov4ZwZJIGbEuyNr9EmiTmZueyrxSToE6GOzoZnq5JM7ecRO7ttyvilPn+VmRsqiP16+VYZzVnGZj/hzZgKBA==", + "license": "MIT", + "dependencies": { + "neo-async": "^2.6.2" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "@rspack/core": "0.x || ^1.0.0 || ^2.0.0-0", + "node-sass": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0", + "sass": "^1.3.0", + "sass-embedded": "*", + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "node-sass": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/sass/node_modules/chokidar": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", + "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", + "license": "MIT", + "dependencies": { + "readdirp": "^5.0.0" + }, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/sass/node_modules/immutable": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.9.tgz", + "integrity": "sha512-m8nVez3rwrgmWxtLMt1ZYXB2Lv7OKYn/disyxAlSDYAlKSlFoPPfIAmAM/M5xqL4m4C/wAPw7S2/CNaUii1Hxg==", + "license": "MIT" + }, + "node_modules/sass/node_modules/readdirp": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.1.1.tgz", + "integrity": "sha512-Kko+Y5XQ6fM+Ce3dq3m9YGxnacYZYl9cA1wZjaF3Vbry2L3i1qVg8+CAgNPsXRArPMUMCaOR7oa9Nqntc43JKA==", + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/sax": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.1.tgz", @@ -20073,6 +21066,25 @@ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "license": "ISC" }, + "node_modules/simple-icons": { + "version": "16.28.0", + "resolved": "https://registry.npmjs.org/simple-icons/-/simple-icons-16.28.0.tgz", + "integrity": "sha512-sQPR5AtK/ijRjou7zw7mlLp08oB6FH7i0lOy5XJ2zp9mJs/yejgiOn7KvQoe2q4YJIx6VmgUSW5AOefebPt5kg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/simple-icons" + }, + { + "type": "github", + "url": "https://github.com/sponsors/simple-icons" + } + ], + "license": "CC0-1.0", + "engines": { + "node": ">=0.12.18" + } + }, "node_modules/sirv": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.4.tgz", @@ -20637,6 +21649,12 @@ "react-dom": ">=16.8.0 <20" } }, + "node_modules/tailwindcss": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.3.tgz", + "integrity": "sha512-gOhV3P7ufE62QDGg1zVaTgCR+EtPv92k2nIhVcVKcLmxT1sUBsQGhnZj175j+MqRt4zLF7ic+sCYjfhxMxj7YQ==", + "license": "MIT" + }, "node_modules/tapable": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", diff --git a/website/package.json b/website/package.json index d5caeae8..0b0c8e21 100644 --- a/website/package.json +++ b/website/package.json @@ -28,12 +28,17 @@ "@overture-stack/lectern-dictionary": "^2.0.0", "@overture-stack/lectern-ui": "^1.0.0", "@replit/codemirror-indentation-markers": "^6.5.3", + "@tailwindcss/postcss": "^4.3.3", "@uiw/react-codemirror": "^4.25.11", "clsx": "^2.0.0", + "docusaurus-plugin-sass": "^0.2.6", "prism-react-renderer": "^2.3.0", "react": "^18.0.0", "react-dom": "^18.0.0", - "swagger-ui-react": "^5.29.2" + "sass": "^1.102.0", + "simple-icons": "^16.28.0", + "swagger-ui-react": "^5.29.2", + "tailwindcss": "^4.3.3" }, "overrides": { "@overture-stack/lectern-ui": { diff --git a/website/src/components/MatomoTracking/MatomoTracking.tsx b/website/src/components/MatomoTracking/MatomoTracking.tsx index 8dc67f37..126a214a 100644 --- a/website/src/components/MatomoTracking/MatomoTracking.tsx +++ b/website/src/components/MatomoTracking/MatomoTracking.tsx @@ -1,4 +1,4 @@ -import { useEffect } from 'react'; +import React, { useEffect } from 'react'; import ExecutionEnvironment from '@docusaurus/ExecutionEnvironment'; import { useLocation } from '@docusaurus/router'; @@ -8,7 +8,7 @@ declare global { } } -export default function MatomoTracking(): JSX.Element | null { +export default function MatomoTracking(): React.JSX.Element | null { const location = useLocation(); useEffect(() => { diff --git a/website/src/css/tailwind.css b/website/src/css/tailwind.css new file mode 100644 index 00000000..38c9a592 --- /dev/null +++ b/website/src/css/tailwind.css @@ -0,0 +1,108 @@ +/* + * Tailwind for the marketing pages, added in rebuild phase 2 as Bulma's + * replacement for new work. The layout classes the ported markup already uses + * live in src/marketing/styles/_layout.scss; this is what phase 3's new pages + * are meant to be built with. + * + * Three deliberate choices, and all three are what make it safe to load this + * globally in a build that already has Infima: + * + * 1. `prefix(ow)` namespaces every utility and every custom property, so a + * class is `ow:flex`, not `flex`, and a variable is `--ow-spacing`, not + * `--spacing`. Nothing Tailwind emits can collide with an Infima, + * Docusaurus or marketing class. This is the thing Bulma could not do, and + * the reason its bare `.navbar` and `.button` collided with Infima's. + * + * 2. Preflight is left out. The layer imports below pull in `theme` and + * `utilities` but not `base`, which is where Tailwind's global element + * reset lives. Loading it would restyle every documentation page, since + * Docusaurus emits one shared stylesheet. The marketing pages get their + * reset from `_layout.scss` instead, scoped under `.marketing`. + * + * 3. `source(none)` disables automatic content detection, which would + * otherwise crawl up from this file and scan the whole repository, + * `submodules/` and its eight vendored repos included. The `@source` lines + * below name the two directories that hold page and component code, so + * utilities are generated from those and nothing else. (It also used to + * pick up `website-legacy/`, the staged Gatsby site, which was deleted on + * 2026-08-07; the reason for the flag is unchanged without it.) + * + * A consequence of 2 worth knowing before using this: without preflight there is + * no `box-sizing: border-box` reset and no margin zeroing outside `.marketing`. + * Tailwind utilities used on a documentation page will apply, but they sit on + * Infima's element defaults rather than a clean slate. + * + * A fourth choice, `important` on the utilities import, is about the cascade + * rather than about scoping, and it is not optional here. + * + * A fifth, added in phase 3: the utilities are deliberately NOT in a layer, and + * that is load-bearing. They were in `layer(utilities)` when phase 2 added them, + * which was fine in development and silently wrong in production. Docusaurus's + * production CSS minifier merges every `@media` block and moves the result to + * the end of the file, which lifts Tailwind's responsive variants out of the + * `@layer utilities` block they were emitted inside. Layered and unlayered + * `!important` declarations do not rank by source order (layered wins), so a + * hoisted `.ow\:lg\:flex-row` lost to an in-layer `.ow\:flex-col` no matter how + * wide the window was. Measured, not inferred: `--no-minify` kept both rules in + * the layer and the variant worked, the minified build put them on opposite + * sides of the layer boundary and it did not. + * + * Unlayered, both rules are `!important` and unlayered, so source order decides + * again and Tailwind's own ordering (variants after the utilities they override) + * holds. The layer was never doing anything else: utilities need `important` to + * beat `.marketing X` either way, since `.ow\:py-8` is one class against two. + * `theme` stays layered because it only carries custom properties. + * + * The marketing SCSS is unlayered and nested under `.marketing`, so every one of + * its rules outranks a bare utility twice over: unlayered CSS beats layered CSS + * whatever the specificity, and `.marketing h1` carries a class more than + * `.ow\:py-8` does. Without `important`, utilities silently lose to the reset in + * `_layout.scss` and to any page partial with an opinion. Measured before adding + * it: `ow:py-8` on the privacy `

` computed to 0px, `ow:font-bold` on a + * `` computed to 900, `ow:py-4` on an `
` computed + * to 48px. An `!important` declaration in a layer does beat an unlayered normal + * one, so this is what makes the utilities usable at all. + * + * The layer beneath cannot be moved instead: putting the marketing SCSS in a + * layer would make it lose to Infima, which is unlayered and outside our control. + * + * What this costs, and it is worth knowing before phase 3: an `ow:` utility can + * no longer be overridden by an ordinary SCSS rule. If a partial needs to win + * against one, remove the utility from the markup rather than escalating with a + * second `!important`. + */ + +@layer theme; + +@import "tailwindcss/theme.css" layer(theme) prefix(ow); +@import "tailwindcss/utilities.css" prefix(ow) source(none) important; + +@source "../marketing"; +@source "../pages"; + +/* + * The marketing palette, so phase 3 markup can say `ow:text-navy` instead of + * carrying a hex value in the JSX. + * + * Mirrored by hand from src/marketing/styles/_color-codes.scss, which stays the + * source of truth: the SCSS tree is 2,800 lines and is not moving to Tailwind, + * so a value changes there first and then here. Only the colours the new pages + * actually use are listed, rather than the whole palette, so the two files are + * cheap to keep in step. + * + * The names deliberately avoid Tailwind's own palette (`blue`, `slate`, `gray` + * and the rest). Redefining `--color-blue-800` would silently change what + * `ow:bg-blue-800` means for anyone who assumes Tailwind's default, whereas + * `navy` and `mist` are unambiguous. Theme variables are declared without the + * prefix and emitted with it, as `--ow-color-navy`. + */ +@theme { + --color-navy: #003055; /* $navy-black, the heading and body colour */ + --color-navy-mid: #04518c; /* $navy-dark, buttons and the darker band */ + --color-ink: #5e6068; /* $c-grey-500, secondary text */ + --color-rule: #dfdfe1; /* $grey-3, hairlines between rows */ + --color-mist: #f2f3f5; /* $grey-2, the grey section background */ + --color-pale: #dfecf2; /* $blue-block, the pale blue band */ + --color-link: #0b75a2; /* $blue-links */ + --color-accent: #f2d021; /* $yellow, the rule under a heading */ +} diff --git a/website/src/marketing/MarketingPage.tsx b/website/src/marketing/MarketingPage.tsx new file mode 100644 index 00000000..f48b30b0 --- /dev/null +++ b/website/src/marketing/MarketingPage.tsx @@ -0,0 +1,35 @@ +import React from "react"; +import Layout from "@theme/Layout"; +import "./styles/index.scss"; + +/** + * The shell every ported marketing route renders inside. + * + * It replaces the Gatsby site's `Layout` component, `NavBar`, `MegaMenu` and + * `Footer`: docusaurus.config.ts supplies all of those, and the swizzled + * src/theme/Layout mounts Matomo, so `gatsby-plugin-matomo` has no successor to + * port. `` and react-helmet likewise collapse into Layout's own title and + * description props. + * + * `wrapperClassName` puts `.marketing` on the content wrapper only, which is + * what keeps Bulma's resets off the documentation pages in the same build. + */ +export type MarketingPageProps = { + title: string; + description: string; + className: string; + children: React.ReactNode; +}; + +export default function MarketingPage({ + title, + description, + className, + children, +}: MarketingPageProps) { + return ( + +
{children}
+
+ ); +} diff --git a/website/src/marketing/components/Button.tsx b/website/src/marketing/components/Button.tsx new file mode 100644 index 00000000..b6586453 --- /dev/null +++ b/website/src/marketing/components/Button.tsx @@ -0,0 +1,115 @@ +import React from "react"; +import { Icon } from "./Icon"; +import Link from "./Link"; + +const btnTypes: Record = { + primary: "button is-primary", // uses bulma + secondary: "button is-white secondary", // bulma + custom css + default: "default", +}; + +export type ButtonSize = + | "default" + | "large" + | "medium" + | "navGithub" + | "navSlack" + | "small"; + +const btnSizes: Record = { + // Bulma classes + default: "", + large: "is-large", + medium: "is-medium", + navGithub: "is-medium", + navSlack: "is-medium", + small: "is-small", +}; + +/** + * Icon pixel size for each button size. Typed against the same `ButtonSize` + * union as `btnSizes` (rather than `Record`) so the two stay + * in step: a size missing from either object is a compile error instead of + * an icon silently rendering at `width: undefined`. + */ +const iconSizes: Record = { + default: 16, + large: 32, + medium: 24, + navGithub: 20, + navSlack: 22, + small: 16, +}; + +export type ButtonProps = { + /** Takes priority over `link` when both are passed (`to={anchorLink ?? link}`); the two behave identically today. */ + anchorLink?: string; + /** Required in practice for icon-only buttons, which have no text to read. */ + ariaLabel?: string; + children?: React.ReactNode; + className?: string; + icon?: string; + iconAlt?: string; + iconStyle?: React.CSSProperties; + link?: string; + onClick?: () => void; + size?: ButtonSize; + type?: keyof typeof btnTypes; +}; + +/** + * Component: Button + * Wraps several of Bulma's classes for easier styling, but can be swapped out + * with custom styles (see above objects.) + * + * Example usage: + * + ); + } + + return ( + + + {children} + + ); +} diff --git a/website/src/marketing/components/CardTile.tsx b/website/src/marketing/components/CardTile.tsx new file mode 100644 index 00000000..5ca921d4 --- /dev/null +++ b/website/src/marketing/components/CardTile.tsx @@ -0,0 +1,95 @@ +import React from "react"; +import Link from "./Link"; +import { H3 } from "./Typography"; + +/** + * The white tile the home page's card bands are built from: a title, a body, + * and a "go" line, with the whole tile one link. + * + * Mirrors the documentation site's entry tile (components/SiteMap) — a + * CSS-module component scoped to a page with no marketing styles, so it + * can't be imported here. The two share shape and, where passed, accent + * colour (`--journey-*-color` in css/custom.css). + * + * Renders an `
  • `; the caller supplies the `
      ` and owns the grid. + * `accent` colours the go line (a CSS colour or `var()`); without one it + * uses the site's link blue. + * + * The single anchor is why `children` must be plain paragraphs, not + * interactive content: a link inside a link is invalid and unnavigable, and + * the whole tile is meant to be clickable. + * + * `image` sits inside the anchor, not behind it as an overhang: tiles sit + * two-by-two here, so an overhang on the second row would clash with the + * row above, and every screenshot already has its own chrome/shadow, which + * a second frame would only repeat. Inset on a pale blue field rather than + * bled to the edge for the same reason; see `.CardTile__media` in + * styles/components/_card-tile.scss. + */ +export type CardTileProps = { + title: string; + href: string; + /** The go line's text. The arrow is appended here, not by the caller. */ + linkLabel: string; + accent?: string; + className?: string; + /** Absolute path into static/. Cropped to 16:9, anchored to its own top. */ + image?: string; + /** + * Empty by default, and deliberately: the tile is one link, so its accessible + * name is already the title and the go line, and alt text on the shot would + * only say a third time what the reader is about to open. + */ + imageAlt?: string; + /** A pill in the corner of the shot, for anything that is not live. */ + imageBadge?: string; + children: React.ReactNode; +}; + +export default function CardTile({ + title, + href, + linkLabel, + accent, + className, + image, + imageAlt = "", + imageBadge, + children, +}: CardTileProps) { + return ( +
    • + + {image && ( +
      + {imageAlt} + {imageBadge && ( + {imageBadge} + )} +
      + )} + {/* The padded part. Separate from the anchor so the shot above it can + reach the tile's own edges while the text still holds its inset. */} +
      +

      {title}

      + {children} + {/* `margin-top: auto` in the stylesheet, so this sits on the floor of + every tile whatever the body above it costs. */} + {linkLabel} → +
      + +
    • + ); +} diff --git a/website/src/marketing/components/ComponentTable.tsx b/website/src/marketing/components/ComponentTable.tsx new file mode 100644 index 00000000..4cdddf8e --- /dev/null +++ b/website/src/marketing/components/ComponentTable.tsx @@ -0,0 +1,272 @@ +import React, { useEffect, useState } from "react"; +import useBrokenLinks from "@docusaurus/useBrokenLinks"; +import clsx from "clsx"; +import Link from "./Link"; +import { H2 } from "./Typography"; +import { IMPACT_PATH } from "../constants/pages"; +import { + componentIcon, + componentsIn, + groups, + type ComponentGroupId, +} from "../data/components"; +import { DEPLOYMENTS_ANCHOR, usedBy } from "../data/deployments"; +import { floatingTooltipPosition } from "../utils/floatingTooltip"; +import { hoverIntentHandlers } from "../utils/hoverIntent"; + +/** + * The column headers, written once so the real `` row and the row + * repeated at the top of every group (see the note on `ComponentTable` below) + * cannot say two different things. + */ +const COLUMN_HEADERS = ["Component", "What it does", "Documentation", "Used by"]; + +/** + * The whole stack, as one table: comparing two components no longer means + * scrolling past a section boundary. The Collect / Explore / Control + * grouping survives as a group row (``), explained by + * the group blurbs below. + * + * Control's blurb covers today's access delegation (Keycloak); its one row + * is the authorization service being built to replace it — named TBD, + * linked to its repo since there's no documentation yet. + * + * Each icon is the same artwork the home hero's diagram uses + * (data/components.ts owns the path), fitted into a fixed square box since + * they're drawn at different aspect ratios. + * + * A real ``, not divs, since this is tabular data. Fighting Infima's + * global table styles is why the look lives in `_products.scss` rather than + * `ow:` utilities, which are `!important` here and couldn't be overridden + * from SCSS. + * + * Used by is the reverse of /impact/'s Runs column, reading `usedBy` from + * the same `data/deployments.ts` array /impact/ renders from, so a link here + * can never land on a missing row. Text links, not icons, since several + * deployments have no logo. + * + * More than one deployment collapses to a single "N deployments" link + * (Arranger runs in seven) rather than naming each — hover/focus opens a + * tooltip with the full list; clicking links to + * `IMPACT_PATH?used-by={component.id}#{DEPLOYMENTS_ANCHOR}`, which + * impact/index.tsx reads back to highlight every matching row (`:target` + * can only point at one). Exactly one deployment still links straight to + * its own row. + * + * The column headers repeat at the top of every group (`COLUMN_HEADERS`, + * rendered again inside each ``): with only the true `` at the + * very top of the table, scrolling into Explore or Control loses the header + * row entirely and a reader can no longer tell which column is which. + */ +export default function ComponentTable() { + // MDX registers its own anchors for Docusaurus's broken-anchor check; a + // plain React page like this never does, so links to these ids would read + // as broken otherwise. + const brokenLinks = useBrokenLinks(); + + const componentGroups: ComponentGroupId[] = ["collect", "explore", "control"]; + + // The "N deployments" tooltip: the full name list, in the same + // floating-card style /impact/ uses (`floatingTooltipPosition`, shared). + // Only one kind here, so state is just text and the trigger's rect. + const [tooltip, setTooltip] = useState<{ + content: string; + rect: DOMRect; + } | null>(null); + const showTooltip = (content: string) => (event: React.SyntheticEvent) => { + setTooltip({ content, rect: event.currentTarget.getBoundingClientRect() }); + }; + const hideTooltip = () => setTooltip(null); + + // The component id to highlight on arrival, from /impact/'s Runs column + // linking here as `?highlight={id}#{id}` — the mirror of this table's own + // `used-by` link. Read in an effect, not during render, so initial markup + // is unaffected. + // + // A row here is never one of several, so the fragment alone already + // scrolls to it; this state adds the persistent highlight /impact/'s own + // rows get, instead of just the brief `:target` flash. + const [highlightedComponentId, setHighlightedComponentId] = useState< + string | null + >(null); + useEffect(() => { + const componentId = new URLSearchParams(window.location.search).get( + "highlight", + ); + if (componentId) setHighlightedComponentId(componentId); + }, []); + + // Clears the highlight on a click outside the highlighted row, the same + // rule impact/index.tsx uses for its own `highlightedComponent` — see the + // note there. + useEffect(() => { + if (!highlightedComponentId) return; + const clearOnOutsideClick = (event: MouseEvent) => { + const target = event.target as HTMLElement; + if (!target.closest(".ProductsTable__highlight")) { + setHighlightedComponentId(null); + } + }; + document.addEventListener("click", clearOnOutsideClick); + return () => document.removeEventListener("click", clearOnOutsideClick); + }, [highlightedComponentId]); + + return ( +
      +
      +
      + + + {COLUMN_HEADERS.map((label) => ( + + ))} + + + + {componentGroups.map((groupId) => { + const group = groups.find((candidate) => candidate.id === groupId)!; + brokenLinks.collectAnchor(group.id); + + return ( + + + + + + {/* Repeats the real `` row's words at the top of every + group; see the note on the component above. `th scope="col"` + here too, since these are column headers for the rows below + them and not row headers. */} + + {COLUMN_HEADERS.map((label) => ( + + ))} + + + {componentsIn(groupId).map((component) => { + // The id is the anchor: /products/#song and its siblings are + // linked from the home hero's diagram and predate this page. + brokenLinks.collectAnchor(component.id); + const deployments = usedBy(component.id); + return ( + + + + + + + ); + })} + + ); + })} +
      + {label} +
      + + {group.title} + + + {group.blurb} + + {group.link && ( + + {group.link.label} + + )} +
      + {label} +
      + {/* The flex row is a div inside the cell rather than + the cell itself: `display: flex` on a `th` stops it + being a table cell at all, and the browser wraps an + anonymous one around it. */} +
      + + + + {component.name} + + {component.codename && ( + + {component.codename} + + )} + +
      +
      {component.summary} + {/* The visible words are enough beside the row's own + header, but a link read out of context is not, so + the accessible name carries the component too. */} + + {component.linkLabel ?? "Documentation"} + + + {deployments.length === 0 && "—"} + {deployments.length === 1 && ( + + {deployments[0].name} + + )} + {deployments.length > 1 && ( + d.name).join(", "), + ), + hideTooltip, + )} + > + {deployments.length} deployments + + )} +
      + + + {tooltip && ( + + )} + + ); +} diff --git a/website/src/marketing/components/Hero.tsx b/website/src/marketing/components/Hero.tsx new file mode 100644 index 00000000..20d92d14 --- /dev/null +++ b/website/src/marketing/components/Hero.tsx @@ -0,0 +1,25 @@ +/** + * Commonly used site banner that displays + * text describing the page, and an image. + **/ + +import React from "react"; +import { H1, P3 } from "./Typography"; + +export type HeroProps = { + title: React.ReactNode; + subtitle?: React.ReactNode; +}; + +export default function Hero({ title, subtitle }: HeroProps) { + return ( +
      +
      +
      +

      {title}

      + {subtitle} +
      +
      +
      + ); +} diff --git a/website/src/marketing/components/HeroDiagram.tsx b/website/src/marketing/components/HeroDiagram.tsx new file mode 100644 index 00000000..c738c79f --- /dev/null +++ b/website/src/marketing/components/HeroDiagram.tsx @@ -0,0 +1,167 @@ +import React, { useState } from "react"; +import clsx from "clsx"; +import Link from "./Link"; +import { + heroDiagramHotspots, + type HeroDiagramHotspot, +} from "../data/heroDiagram"; +import { componentUsage } from "../data/componentUsage"; +import { useComponentHighlight } from "../context/ComponentHighlightContext"; +import { floatingTooltipPosition } from "../utils/floatingTooltip"; +import { hoverIntentHandlers } from "../utils/hoverIntent"; + +const PORTAL = "/img/marketing/home/portal.svg"; + +/** + * The home hero's diagram: an orbit of Overture's seven components plus + * Control (in development), positioned to match the reference artwork + * rather than evenly spaced. Each icon is a focusable/hoverable hotspot + * linking to `/products/#` with a name/codename/summary tooltip. + * + * Who runs Overture used to live here too, as a second carousel slide; it's + * now LogoCarousel, its own scrolling section right below the hero. + * + * Hovering a hotspot filters LogoCarousel below to platforms using that + * component; hovering a platform there highlights its components back here + * (both via `data/componentUsage.ts` and shared `ComponentHighlightContext`). + * Every hotspot is always on screen, so this direction just dims the rest + * rather than filtering. + * + * The tooltip is `position: fixed`, positioned in JS from the hovered + * hotspot's `getBoundingClientRect()` (utils/floatingTooltip.ts) rather than + * `position: absolute`: an absolute tooltip could cover the rest of the + * diagram, or run off-window near a shorter viewport's top edge. + * + * Rendered only from `tablet-up` (see _home.scss), hidden with + * `display: none` so nothing here sits in the tab order when the artwork + * isn't shown. + */ +export default function HeroDiagram() { + const { setHighlightedComponent, highlightedPlatform } = + useComponentHighlight(); + const [tooltip, setTooltip] = useState<{ + hotspot: HeroDiagramHotspot; + rect: DOMRect; + } | null>(null); + + return ( +
      +
      +
      + +
      + +
      + + {heroDiagramHotspots.map((hotspot) => { + const usedByHoveredPlatform = highlightedPlatform + ? (componentUsage[highlightedPlatform]?.includes(hotspot.id) ?? false) + : false; + const style: React.CSSProperties = { + left: `${hotspot.left}%`, + top: `${hotspot.top}%`, + width: `${hotspot.width}%`, + height: `${hotspot.height}%`, + }; + const icon = ( + + ); + + // Pointer events, not onMouseEnter/onMouseLeave: @docusaurus/Link + // overwrites `onMouseEnter` internally (for hover-preload), silently + // discarding any passed in. Pointer events are untouched by it. + const showTooltip = (event: React.SyntheticEvent) => { + setHighlightedComponent(hotspot.id); + setTooltip({ + hotspot, + rect: event.currentTarget.getBoundingClientRect(), + }); + }; + const hideTooltip = () => { + setHighlightedComponent(null); + setTooltip(null); + }; + const highlightHandlers = hoverIntentHandlers(showTooltip, hideTooltip); + const platformHighlightClassName = clsx( + "HeroDiagram__hotspot", + usedByHoveredPlatform && "HeroDiagram__hotspot--highlighted", + highlightedPlatform && + !usedByHoveredPlatform && + "HeroDiagram__hotspot--dimmed", + ); + + if (hotspot.comingSoon || !hotspot.href) { + return ( + + {icon} + + ); + } + + return ( + + {icon} + + ); + })} + + {tooltip && ( +
      + + {tooltip.hotspot.name} + {tooltip.hotspot.codename ? ` (${tooltip.hotspot.codename})` : ""} + + + {tooltip.hotspot.summary} + +
      + )} +
      + ); +} diff --git a/website/src/marketing/components/HomeCapabilities.tsx b/website/src/marketing/components/HomeCapabilities.tsx new file mode 100644 index 00000000..2a77e269 --- /dev/null +++ b/website/src/marketing/components/HomeCapabilities.tsx @@ -0,0 +1,86 @@ +import React from "react"; +import CardTile from "./CardTile"; +import { H2, P1 } from "./Typography"; +import { capabilities } from "../data/capabilities"; +import { platforms } from "../data/platforms"; +import { IMPACT_PATH } from "../constants/pages"; + +/** + * "What you can do with Overture": four capability cards, each opening with a + * screenshot as proof it's a running system. Copy is data/capabilities.ts; + * the platform, name, launch year and screenshot each card cites are + * resolved by id from data/platforms.ts, so none of it can drift from + * /impact/. The fourth card has no real deployment behind it and carries a + * labelled mockup instead. + * + * Uses `CardTile`, the same tile `HomeDocs` uses below, with the whole card + * as one link: the four claims vary by up to five lines, and a per-card box + * keeps each link flush with its own floor rather than trailing the + * sentence above it. + */ +export default function HomeCapabilities() { + return ( + // White, as the first band under the blue carousel; the grey went to + // `HomeCollaborate` below when the two swapped, so colours stayed put. + // Tiles take that grey back via `--card-tile-bg` in pages/_home.scss. +
      +
      +

      + What you can do with Overture +

      + {/* Same bar under the same size of heading as "What we do" in + HomeCollaborate: the two bands open identically. */} +
      + + Modular, flexible and reusable components, built for problems of all + shapes and sizes. + + + {/* Four cards on a two-by-two grid, the same `CardTile` the + documentation band below is built from. These were four columns + separated by a yellow rule; the tile's edges do that job now, + plus a link line flush with every card's floor rather than + wherever the outcome sentence ends. */} +
        + {capabilities.map((capability) => { + const platform = platforms.find( + (entry) => entry.id === capability.platform, + ); + + // `?highlight={id}#{id}`, not `platform.href`'s bare fragment: + // the same param partnerLogos.ts uses for LogoCarousel, so a + // card here gets the persistent `ImpactTable__highlight`, not + // just a `:target` flash. + const href = + capability.href ?? + (platform + ? `${IMPACT_PATH}?highlight=${platform.id}#${platform.id}` + : IMPACT_PATH); + + return ( + +

        {capability.outcome}

        + {capability.note && ( +

        {capability.note}

        + )} +
        + ); + })} +
      +
      +
      + ); +} diff --git a/website/src/marketing/components/HomeCollaborate.tsx b/website/src/marketing/components/HomeCollaborate.tsx new file mode 100644 index 00000000..a162d6d9 --- /dev/null +++ b/website/src/marketing/components/HomeCollaborate.tsx @@ -0,0 +1,177 @@ +import React from "react"; +import CardTile from "./CardTile"; +import { Icon } from "./Icon"; +import Link from "./Link"; +import { H2, H3 } from "./Typography"; +import { offers } from "../data/collaboration"; +import metrics from "../data/metrics"; +import { COLLABORATE_PATH, IMPACT_PATH } from "../constants/pages"; +import { + GIGASCIENCE_PAPER_LINK, + GI_PROGRAM_LINK, + OVERTURE_DOCUMENTATION_TEAM_LINK, + OVERTURE_GITHUB_LINK, +} from "../constants/externalLinks"; + +/** + * "What we do": who builds Overture, how, and what to contact us about. Sits + * behind `HomeCapabilities` (what before who), and ahead of the + * documentation/quickstart bands as the staffed route out of the page. + * + * The only place the retired /about-us/ page's material exists now, + * condensed into three paragraphs and a four-item list. The footer's Our + * story and the /about-us/ 301 both land on the H2 below via its `id` + * (`ABOUT_BAND` in constants/pages.ts), which is load-bearing, not just an + * aria target. + * + * Opens on what Overture is for, crediting the team second — the heading + * order asks for the portfolio answer first, who-builds-it as a byline + * under it. + * + * The three offers are `offers` in data/collaboration.ts, the same array + * /collaborate/ renders from (reading `oneLine` here), so the two pages + * can't drift apart. Under their own subhead since the band opens on prose + * about the program, not the offers. + * + * No "Our funders / Publications / Who builds Overture" link row at the + * foot: the funder claim survives as the public-funding sentence below, and + * the three routes are in the footer. + */ +/** + * The four claims about how the software is built, each with its + * /about-us/ badge. `icon` is a key in Icon's own map, not a path. Local + * here rather than in a data file, since /about-us/ (the only other reader) + * is gone. + */ +const values = [ + { + name: "Open source", + text: "Freely available, and open to contributions from the community.", + icon: "aboutUsOpenSource", + }, + { + name: "Modular", + text: "Each component has one responsibility and integrates with the rest.", + icon: "aboutUsModular", + }, + { + name: "Extensible", + text: "Expandable and customizable to what a project actually needs.", + icon: "aboutUsExtensible", + }, + { + name: "Reusable", + text: "Designed to carry across contexts with minimal modification.", + icon: "aboutUsReusable", + }, +]; + +export default function HomeCollaborate() { + // One offer carries a free route; the line under the cards is composed from + // whichever one that is rather than hard-coded to technical support. + const selfServe = offers.find((offer) => offer.selfServe)?.selfServe; + + return ( + // Grey, which came over from `HomeCapabilities` when the two swapped so that + // the page still runs white then grey below the carousel whatever is in the + // two slots. Its tiles stay white on the fallback in + // components/_card-tile.scss: every card band on this page gets a tile that + // is whatever colour its band is not. +
      +
      +
      +

      + What we do with Overture +

      +
      + {/* No `P1`: all three paragraphs share one class, the smallest + prose size this site sets, so the opening statement isn't + visibly bigger than the notes under it. Rules in + pages/_home.scss `&__prose`. */} +

      + Our ability to build{" "} + a diverse portfolio of data platforms{" "} + rests on the continuous development and maintenance of Overture. + Developed at OICR and{" "} + released as open source, + Overture lets our engineers and teams elsewhere build systems that + help researchers and consortiums across disciplines collect, + organize and share their research data. +

      + {/* The one credential on this page: a sentence stating the platform + has been peer reviewed, since the footer's "How to cite us" link + is about citing, not reviewing. `/impact/` carries the other + four papers. */} +

      + The platform is described in{" "} + GigaScience (2025), peer + reviewed and open access. +

      +
      + + {/* The values grid from /about-us/, one line each rather than a card + each — at card size these four claims read as a second page + opening inside this band. Badges are /about-us/'s own, carried + over rather than redrawn. `alt=""` on each: it sits beside the + word it illustrates, so a screen reader reading it twice is worse + than not reading it. */} +
        + {values.map((value) => ( +
      • + + {value.name} + {value.text} +
      • + ))} +
      + +

      + How we collaborate +

      + + {/* The same `CardTile` the documentation band below is built from. + These were bare text columns with a link under each — the tile + adds edges marking where one offer stops and the next starts, and + keeps all three link lines flush regardless of the sentence + above. */} +
        + {offers.map((offer) => ( + // Into the section for this offer, not the top of the page — + // /collaborate/ gives each an id, so three cards feel like one + // page rather than a detour. + +

        {offer.oneLine}

        +
        + ))} +
      + + {/* The self-serve route, under the cards rather than inside one: a + tile is a single link end to end, so a second link inside it + isn't valid or reachable. Keeps this honest that not everything + needs a conversation first. The URL comes from + data/collaboration.ts; the wording is this sentence's own since + `selfServe.label` is written to stand alone, not mid-sentence. */} + {selfServe && ( +

      + Most questions do not need any of this,{" "} + our community support forum is free + and public. +

      + )} +
      +
      + ); +} diff --git a/website/src/marketing/components/HomeDocs.tsx b/website/src/marketing/components/HomeDocs.tsx new file mode 100644 index 00000000..5eb14b94 --- /dev/null +++ b/website/src/marketing/components/HomeDocs.tsx @@ -0,0 +1,71 @@ +import React from "react"; +import useBrokenLinks from "@docusaurus/useBrokenLinks"; +import CardTile from "./CardTile"; +import { H2, P1 } from "./Typography"; +import { docJourneys } from "../data/docJourneys"; + +/** + * The documentation band: one card per hands-on journey. + * + * Cards are `CardTile`, shared with `HomeCapabilities` above. Journey + * colours (`--journey-*-color` in css/custom.css) are shared outright with + * the documentation site, so a "Browse Deploy" line is the same teal on both. + * + * No deep links inside the cards and no "All documentation" button under + * them: three named destinations is the whole offer, and a card with four + * links no longer reads as one click. Those links are all one click + * further in, from the journey landing page each card opens. + * + * No Algolia search here: the navbar's sticky one is already in reach, and + * two search boxes on one page would split one affordance rather than add + * a second. + * + * Each card's "who it is for" line is quoted from that journey's landing + * page (data/docJourneys.ts names the files to check). Links are cross-host + * docs.overture.bio URLs onBrokenLinks can't validate, so they go through + * the URL-check script instead. + */ +export default function HomeDocs() { + // The hero's second button links in-page to `#docs`; a plain React page + // registers no anchors on its own for Docusaurus's broken-anchor check, + // so this does it by hand (same as HomeQuickstart). + useBrokenLinks().collectAnchor("docs"); + + return ( +
      +
      + {/* Heading, bar and lede match `HomeCapabilities` above and "What we + do" before it: the three bands open identically. */} +

      + Getting hands on +

      +
      + + The documentation is organized by what you are doing rather than by + what we built, so each journey starts from a different question. + + +
        + {docJourneys.map((journey) => ( + // The journey ids are the same three the documentation site names + // its colours after, so the accent is composed rather than mapped + // here: a fourth journey would arrive with its colour already set. + +

        {journey.audience}

        +
        + ))} +
      +
      +
      + ); +} diff --git a/website/src/marketing/components/HomeQuickstart.tsx b/website/src/marketing/components/HomeQuickstart.tsx new file mode 100644 index 00000000..8f871bac --- /dev/null +++ b/website/src/marketing/components/HomeQuickstart.tsx @@ -0,0 +1,162 @@ +import React from "react"; +import useBrokenLinks from "@docusaurus/useBrokenLinks"; +import Button from "./Button"; +import { Icon } from "./Icon"; +import Link from "./Link"; +import Terminal from "./Terminal"; +import { H2, H3, P1 } from "./Typography"; +import { + quickstartClaim, + quickstartPortalUrl, + quickstartSteps, +} from "../data/quickstart"; +import { WORKSHOP_DOCS_LINK } from "../constants/externalLinks"; + +const PORTAL_SCREENSHOT = "/img/marketing/home/overtureQuickstartPortal.webp"; + +/** + * The quickstart, at the foot of the home page: an introduction, a blue band + * under it, and the portal screenshot straddling the seam between the two. + * The introduction carries the documentation band's own grey rather than + * white, so that seam is the section's only colour change. + * + * Commands are mirrored from the documentation, not rewritten — + * data/quickstart.ts names the files to check when the demo changes, and + * derives the "3 steps, 2 commands" claim from the steps themselves so the + * subtitle can't fall behind. + * + * Last on the page deliberately: the hero's first button points here, so + * the page's first CTA and last section are the same offer, and scrolling + * the whole way lands on something runnable. + */ +export default function HomeQuickstart() { + // The hero's button links in-page to `#quickstart`; a plain React page + // registers no anchors on its own for Docusaurus's broken-anchor check, + // so this does it by hand (same as HomeDocs). + useBrokenLinks().collectAnchor("quickstart"); + + return ( +
      + {/* Same grey as the documentation band above, so the two read as one + surface and the section's only colour break is the blue band below, + where the screenshot crosses it. */} +
      +
      +

      Getting Started

      +

      + {quickstartClaim.steps} Steps,{" "} + {quickstartClaim.commands} Commands,{" "} + {quickstartClaim.platforms} Platform +

      + + The Overture Quickstart enables a fast and frictionless setup of our + data platform locally. + +
      +
      + +
      + {/* Decorative (`alt=""`): what the portal looks like is the payoff, + not a step. First in source order since it visually joins the two + halves of the section; a screen reader reading an empty image + first costs nothing. Outside `.container`: it's positioned + against the blue band's own top edge, avoiding having to cancel + `.container`'s padding. See _quickstart.scss. */} +
      + +
      + +
      +
      +
        + {quickstartSteps.map((step, index) => ( +
      1. +

        + + + {step.title} + {step.titleLink && ( + <> + {" "} + + {step.titleLink.label} + + + )} + +

        + + {step.note && ( +

        {step.note}

        + )} + + {step.settings && ( +
        + +
        + {step.settings.map((setting) => ( +
        +
        {setting.label}
        +
        + {setting.value} +
        +
        + ))} +
        +
        + )} + + {step.footnote && ( +

        {step.footnote}

        + )} + + {step.commands && ( + + )} +
      2. + ))} +
      + +

      + Your portal will now be accessible from your{" "} + {quickstartPortalUrl}. +

      + +
      + +
      +
      +
      +
      +
      + ); +} diff --git a/website/src/marketing/components/Icon.tsx b/website/src/marketing/components/Icon.tsx new file mode 100644 index 00000000..8cbd4869 --- /dev/null +++ b/website/src/marketing/components/Icon.tsx @@ -0,0 +1,149 @@ +import React from "react"; + +/** + * Every icon the marketing pages can render, by name. + * + * Docusaurus hands SVG imports to SVGR, which returns a React component + * rather than a URL, so these are addressed by path into + * `static/img/marketing/icons/` instead of imported as modules. + */ +const icons: Record = { + aboutUsExtensible: "/img/marketing/icons/aboutUsExtensible.svg", + aboutUsModular: "/img/marketing/icons/aboutUsModule.svg", + aboutUsOpenSource: "/img/marketing/icons/aboutUsOpenSource.svg", + aboutUsReusable: "/img/marketing/icons/aboutUsReusable.svg", + arrowDownNavbar: "/img/marketing/icons/arrow_down_navbar.svg", + arrowDown: "/img/marketing/icons/arrow_down.svg", + arrowLeftBlack: "/img/marketing/icons/arrow-left-black.svg", + arrowLeftBlue: "/img/marketing/icons/arrow-left-blue.svg", + arrowLeftRound: "/img/marketing/icons/arrow_left_round.svg", + arrowRight: "/img/marketing/icons/arrow_right.svg", + arrowRightBlack: "/img/marketing/icons/arrow-right-black.svg", + arrowRightMagenta: "/img/marketing/icons/arrow_right_magenta.svg", + arrowRightRound: "/img/marketing/icons/arrow_right_round.svg", + arrowsRight: "/img/marketing/icons/arrows_right.svg", + assistance: "/img/marketing/icons/icon_assistance.svg", + barGraph: "/img/marketing/icons/bar_graph.svg", + bigData: "/img/marketing/icons/big-data.svg", + cardStack: "/img/marketing/icons/card_stack.svg", + checkmark: "/img/marketing/icons/checkmark.svg", + chevronGrey: "/img/marketing/icons/chevron-grey.svg", + chevronMagenta: "/img/marketing/icons/chevron-magenta.svg", + cloudWeather: "/img/marketing/icons/cloud-weather.svg", + cloud: "/img/marketing/icons/cloud.svg", + cloudSquare: "/img/marketing/icons/cloud_square.svg", + cog: "/img/marketing/icons/cog.svg", + copy: "/img/marketing/icons/copy.svg", + customizable: "/img/marketing/icons/customizable.svg", + data: "/img/marketing/icons/data.svg", + database: "/img/marketing/icons/database.svg", + dna: "/img/marketing/icons/dna.svg", + download: "/img/marketing/icons/download.svg", + emailUs: "/img/marketing/icons/email-us.svg", + exploreDocs: "/img/marketing/icons/explore-docs.svg", + extensible: "/img/marketing/icons/extensible.svg", + feedback: "/img/marketing/icons/icon_feedback.svg", + fingerSnap: "/img/marketing/icons/finger_snap.svg", + githubGrey: "/img/marketing/icons/github_grey.svg", + githubMagenta: "/img/marketing/icons/github_magenta.svg", + githubWhite: "/img/marketing/icons/github_white.svg", + githubYellow: "/img/marketing/icons/github_yellow.svg", + githubFindUs: "/img/marketing/icons/github-find-us.svg", + graphCycle: "/img/marketing/icons/graph_cycle.svg", + gridGlass: "/img/marketing/icons/grid_magnifying_glass.svg", + history: "/img/marketing/icons/history.svg", + identifyNeeds: "/img/marketing/icons/identify-needs.svg", + joinCommunity: "/img/marketing/icons/join-community.svg", + kafka: "/img/marketing/icons/kafka.svg", + lock: "/img/marketing/icons/lock.svg", + lockCode: "/img/marketing/icons/lock_code.svg", + magnify: "/img/marketing/icons/magnify.svg", + mail: "/img/marketing/icons/mail.png", + notes: "/img/marketing/icons/notes.svg", + notes2: "/img/marketing/icons/notes2.svg", + pageWhite: "/img/marketing/icons/page_white.svg", + palette: "/img/marketing/icons/palette.svg", + play: "/img/marketing/icons/play.svg", + power: "/img/marketing/icons/power.svg", + productAdministrativeUI: "/img/marketing/icons/productAdministrativeUI.svg", + productArranger: "/img/marketing/icons/productArranger.svg", + productArrangerWhite: "/img/marketing/icons/productArrangerWhite.svg", + productBilling: "/img/marketing/icons/productBilling.svg", + productBillingWhite: "/img/marketing/icons/productBillingWhite.svg", + productBuiltInUIComponents: "/img/marketing/icons/productBuiltInUIComponents.svg", + productCloudSupport: "/img/marketing/icons/productCloudSupport.svg", + productDMS: "/img/marketing/icons/productDMS.svg", + productDMSWhite: "/img/marketing/icons/productDMSWhite.svg", + productEgo: "/img/marketing/icons/productEgo.svg", + productEgoWhite: "/img/marketing/icons/productEgoWhite.svg", + productEnrolment: "/img/marketing/icons/productEnrolment.svg", + productEnrolmentWhite: "/img/marketing/icons/productEnrolmentWhite.svg", + productHighTransfer: "/img/marketing/icons/productHighTransfer.svg", + productJukebox: "/img/marketing/icons/productJukebox.svg", + productJukeboxWhite: "/img/marketing/icons/productJukeboxWhite.svg", + productMaestro: "/img/marketing/icons/productMaestro.svg", + productMaestroWhite: "/img/marketing/icons/productMaestroWhite.svg", + productMetadataTracking: "/img/marketing/icons/productMetadataTracking.svg", + productMetadataValidation: "/img/marketing/icons/productMetadataValidation.svg", + productMultipleIndexLevels: "/img/marketing/icons/productMultipleIndexLevels.svg", + productMultipleSongsIndex: "/img/marketing/icons/productMultipleSongsIndex.svg", + productOnco: "/img/marketing/icons/productOnco.svg", + productOncoWhite: "/img/marketing/icons/productOncoWhite.svg", + productPersona: "/img/marketing/icons/productPersona.svg", + productPersonaWhite: "/img/marketing/icons/productPersonaWhite.svg", + productRiff: "/img/marketing/icons/productRiff.svg", + productRiffWhite: "/img/marketing/icons/productRiffWhite.svg", + productSamtools: "/img/marketing/icons/productSamtools.svg", + productScalable: "/img/marketing/icons/productScalable.svg", + productScore: "/img/marketing/icons/productScore.svg", + productScoreWhite: "/img/marketing/icons/productScoreWhite.svg", + productSearchAPI: "/img/marketing/icons/productSearchAPI.svg", + productSingleSignOn: "/img/marketing/icons/productSingleSignOn.svg", + productSlackIntegration: "/img/marketing/icons/productSlackIntegration.svg", + productSong: "/img/marketing/icons/productSong.svg", + productSongWhite: "/img/marketing/icons/productSongWhite.svg", + productStateControls: "/img/marketing/icons/productStateControls.svg", + productStateless: "/img/marketing/icons/productStateless.svg", + rocketWhite: "/img/marketing/icons/rocket_white.svg", + search: "/img/marketing/icons/search.svg", + searchBar: "/img/marketing/icons/searchBar.svg", + security: "/img/marketing/icons/security.svg", + share: "/img/marketing/icons/share.svg", + shield: "/img/marketing/icons/shield.svg", + slack: "/img/marketing/icons/icon_slack.svg", + slackJoin: "/img/marketing/icons/slack-join.svg", + smileyFace: "/img/marketing/icons/smileyFace.svg", + slackNew: "/img/marketing/icons/icon_slack_new.svg", + softwareEngineers: "/img/marketing/icons/software-engineers.svg", + spiral: "/img/marketing/icons/spiral.svg", + star: "/img/marketing/icons/star.svg", + support: "/img/marketing/icons/icon_support.svg", + target: "/img/marketing/icons/target.svg", + unsmileyFace: "/img/marketing/icons/unsmileyFace.svg", + user: "/img/marketing/icons/user.svg", + vennDiagram: "/img/marketing/icons/venn_diagram.svg", + xGrey: "/img/marketing/icons/x-grey.svg", +}; + +export type IconProps = { + /** + * Required, so a decorative icon has to say so with `alt=""` rather than + * silently shipping an unlabelled image. + */ + alt: string; + img: string; + size?: number | string; + className?: string; + style?: React.CSSProperties; +}; + +export function Icon({ alt, img, size, className, style }: IconProps) { + return ( + {alt} + ); +} diff --git a/website/src/marketing/components/Link.tsx b/website/src/marketing/components/Link.tsx new file mode 100644 index 00000000..4cc53330 --- /dev/null +++ b/website/src/marketing/components/Link.tsx @@ -0,0 +1,47 @@ +import React from "react"; +import DocusaurusLink from "@docusaurus/Link"; + +/** + * A thin wrapper around Docusaurus's own `Link`, which already handles + * internal routing and same-page hash targets. The one thing it doesn't do — + * opening external links safely in a new tab — is all this adds. + */ +export type LinkProps = React.ComponentProps & { + to?: string; +}; + +export default function Link({ to = "", children, ...props }: LinkProps) { + const isExternal = /^\b(http|mailto)/.test(to); + + if (isExternal) { + return ( + + {children} + + ); + } + + if (!to) { + return {children}; + } + + // A hash on its own is a target on the page already open, so hand it to the + // browser rather than to the router: react-router resolves a bare hash + // against the current path and pushes a new history entry, which lands on the + // same page without scrolling anywhere. Docusaurus's Link is for changing + // route, and this is not one. (The home page's "Get Started" button is the + // first thing here to need it.) + if (to.startsWith("#")) { + return ( + + {children} + + ); + } + + return ( + + {children} + + ); +} diff --git a/website/src/marketing/components/LogoCarousel.tsx b/website/src/marketing/components/LogoCarousel.tsx new file mode 100644 index 00000000..852075ac --- /dev/null +++ b/website/src/marketing/components/LogoCarousel.tsx @@ -0,0 +1,494 @@ +import React, { + useContext, + useEffect, + useLayoutEffect, + useRef, + useState, +} from "react"; +import clsx from "clsx"; +import Link from "./Link"; +import { H3 } from "./Typography"; +import { partnerLogos, type PartnerLogo } from "../data/partnerLogos"; +import { componentUsage } from "../data/componentUsage"; +import { useComponentHighlight } from "../context/ComponentHighlightContext"; +import { floatingTooltipPosition } from "../utils/floatingTooltip"; +import { + hoverIntentHandlers, + isHoverIntent, + wantsFocusHint, +} from "../utils/hoverIntent"; +import { OVERTURE_DOCUMENTATION_FUNDING } from "../constants/externalLinks"; + +// The one line under the heading. Only shown where the interaction it describes +// is possible: tablet-up (see _logo-carousel.scss). +const HOVER_HINT = + "Hover a component above, or a platform below, to see how they connect"; + +// Pixels per millisecond. Slow enough that a logo is legible for a couple of +// seconds as it drifts past, not just a blur you have to stop to read. +const SPEED = 0.015; + +// How long the loop keeps its hands off `scrollLeft` after something other +// than itself last moved it. A touch swipe hands over to momentum once the +// finger lifts, and every momentum frame refreshes this, so the figure only +// has to outlast the gap between two frames of that glide rather than the +// whole of it. +const EXTERNAL_SCROLL_HOLD = 700; + +// Pixels of mouse movement past a mousedown before this treats the gesture +// as a drag rather than a click. Below this, nothing here touches +// `scrollLeft` or captures the pointer, so a plain click still reaches +// whatever logo link is under it — see the note on `handlePointerDown`. +const DRAG_THRESHOLD = 4; + +type TooltipInfo = { id: string; name: string; impact: string; rect: DOMRect }; + +// Internal to this file, not exported: a way for any LogoItem, however deep +// (the marquee's two lists, or the filtered view), to hand its impact +// tooltip up to the one place that renders it, without threading a callback +// prop through LogoList as well. +const LogoTooltipContext = React.createContext< + (info: TooltipInfo | null) => void +>(() => {}); + +function LogoItem({ + logo, + hiddenForLoop, + highlighted, +}: { + logo: PartnerLogo; + hiddenForLoop: boolean; + highlighted: boolean; +}) { + const { setHighlightedPlatform } = useComponentHighlight(); + const setTooltip = useContext(LogoTooltipContext); + const content = ( + {hiddenForLoop + ); + // No production date rendered under the logo (still in + // data/partnerLogos.ts, unused here). A logo with an impact statement + // skips the plain browser `title` in favor of the tooltip below, so the + // two never stack. + const title = logo.name; + const itemClassName = clsx( + "LogoCarousel__item", + highlighted && "LogoCarousel__item--highlighted", + ); + + // Pointer events, not onMouseEnter/onMouseLeave: @docusaurus/Link spreads + // `...props` and then unconditionally sets its own `onMouseEnter` after + // (for its hover-preload behaviour), silently discarding whatever the + // caller passed in. Pointer events are untouched by it. + const handleEnter = (element: HTMLElement) => { + setHighlightedPlatform(logo.id); + if (logo.impact) { + setTooltip({ + id: logo.id, + name: logo.name, + impact: logo.impact, + rect: element.getBoundingClientRect(), + }); + } + }; + const handleLeave = () => { + setHighlightedPlatform(null); + setTooltip(null); + }; + // Guarded to mouse-only pointer events and keyboard-caused focus: a + // touchscreen has no hover, which is why the hint line above the logos and + // the diagram this highlights are both `display: none` below tablet-up + // (_logo-carousel.scss, _home.scss). A tap on a logo follows its link, as + // it did before. + const highlightHandlers = hoverIntentHandlers( + (event) => handleEnter(event.currentTarget), + handleLeave, + ); + + if (!logo.href) { + return ( +
    • + + {content} + +
    • + ); + } + return ( +
    • + + {content} + +
    • + ); +} + +const LogoList = React.forwardRef( + function LogoList({ hidden }, ref) { + return ( +
        + {partnerLogos.map((logo) => ( + + ))} +
      + ); + }, +); + +/** + * Who runs Overture, scrolling right below the hero: a continuous marquee, + * so every logo shows at once rather than hiding behind an arrow. + * + * The list renders twice back to back inside a real scroll container + * (`overflow-x: auto`), not a CSS transform animation, so a + * `requestAnimationFrame` loop can advance `scrollLeft` each frame while + * still letting a trackpad swipe, shift+wheel, or mouse drag take over + * without fighting it. The second copy is `aria-hidden`/`tabIndex={-1}` (it + * only exists to make the loop seamless) and drops out entirely under + * `prefers-reduced-motion`. + * + * Two independent links through `ComponentHighlightContext`: hovering a + * component in HeroDiagram swaps the marquee for a static list of the + * platforms that use it (dimming in place would miss anything currently + * off-screen); hovering a logo here sets `highlightedPlatform`, which + * HeroDiagram reads to highlight its own matching components. + * + * The impact-statement tooltip is `position: fixed`, not the `position: + * absolute` popup HeroDiagram's tooltip uses, because `&__viewport` needs + * `overflow-y: hidden` for the marquee illusion, which would clip anything + * anchored inside it. + */ +export default function LogoCarousel() { + const viewportRef = useRef(null); + const listRef = useRef(null); + const stageRef = useRef(null); + const filteredListRef = useRef(null); + const pausedRef = useRef(false); + const draggingRef = useRef(false); + // A finger or pen resting on the viewport, separate from `draggingRef` + // (the mouse drag this component runs itself) — touch scrolling is the + // browser's; this only needs to stay out of its way. + const touchHoldRef = useRef(false); + const dragStartRef = useRef({ x: 0, scrollLeft: 0 }); + // A mousedown not yet proven a drag, kept separate from + // `dragStartRef`/`draggingRef` so a plain click never captures the + // pointer — see `DRAG_THRESHOLD`. + const dragCandidateRef = useRef<{ + pointerId: number; + x: number; + scrollLeft: number; + } | null>(null); + const highlightedComponentRef = useRef(null); + const { highlightedComponent } = useComponentHighlight(); + const [tooltip, setTooltip] = useState(null); + const [filteredScale, setFilteredScale] = useState(1); + + // The filtered view only ever shrinks to fit one line, never grows or + // wraps. `scrollWidth` reads the list's natural width regardless of any + // transform already applied, so recomputing repeatedly is safe. + useLayoutEffect(() => { + const list = filteredListRef.current; + const stage = stageRef.current; + if (!highlightedComponent || !list || !stage) { + setFilteredScale(1); + return; + } + + const recompute = () => { + const naturalWidth = list.scrollWidth; + if (naturalWidth === 0) return; + setFilteredScale(Math.min(1, stage.clientWidth / naturalWidth)); + }; + + recompute(); + const observer = new ResizeObserver(recompute); + observer.observe(stage); + return () => observer.disconnect(); + }, [highlightedComponent]); + + useEffect(() => { + highlightedComponentRef.current = highlightedComponent; + }, [highlightedComponent]); + + useEffect(() => { + const viewport = viewportRef.current; + const list = listRef.current; + if (!viewport || !list) return; + if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) { + return; + } + + let frame: number; + let last: number | null = null; + // `scrollLeft` is integer-quantized; at this speed each frame's move is + // under a pixel, so writing straight to it would round to zero forever. + // Tracked as a float here, only rounded on write. + let position = viewport.scrollLeft; + // The previous frame's `scrollLeft`, the only way to tell this loop's + // own writes apart from the visitor's. + let lastSeen: number | null = null; + // `-Infinity`, not `0`: a plain zero would read as a recent scroll for + // the first `EXTERNAL_SCROLL_HOLD` of the page's life. + let externalScrollAt = Number.NEGATIVE_INFINITY; + + const tick = (now: number) => { + const elapsed = last === null ? 0 : now - last; + last = now; + const listWidth = list.offsetWidth; + if (listWidth === 0) { + frame = requestAnimationFrame(tick); + return; + } + + // Native scrolling (touch, trackpad, shift+wheel) only shows up as + // `scrollLeft` moving since the last frame; nothing else marks it. + const actual = viewport.scrollLeft; + if (lastSeen !== null && Math.abs(actual - lastSeen) > 1) { + externalScrollAt = now; + } + + if ( + pausedRef.current || + draggingRef.current || + touchHoldRef.current || + now - externalScrollAt < EXTERNAL_SCROLL_HOLD || + highlightedComponentRef.current !== null + ) { + // Resync from the real offset so resuming continues from there, + // not a snap back. + position = actual; + } else { + position += SPEED * elapsed; + if (position >= listWidth) { + position -= listWidth; + } else if (position < 0) { + position += listWidth; + } + viewport.scrollLeft = position; + } + + // Read back after writing, not before: `scrollLeft` is quantized and + // clamped, so the next frame must compare against what's actually + // there, not what was requested. + lastSeen = viewport.scrollLeft; + frame = requestAnimationFrame(tick); + }; + + frame = requestAnimationFrame(tick); + return () => cancelAnimationFrame(frame); + }, []); + + // Mouse hover only: a touchscreen has none, and the compatibility + // `mouseenter` a tap fires has no matching `mouseleave`, which would leave + // the marquee paused for good after one tap. + const pause = (event: React.PointerEvent) => { + if (!isHoverIntent(event)) return; + pausedRef.current = true; + }; + const resume = (event: React.PointerEvent) => { + if (!isHoverIntent(event)) return; + pausedRef.current = false; + }; + // `event.target`, not `currentTarget`: React's `onFocus` is `focusin`, so + // `currentTarget` is the viewport, but it's the focused link inside it + // that needs checking. Blur stays unconditional; clearing an unset pause + // costs nothing. + const pauseForFocus = (event: React.FocusEvent) => { + if (!wantsFocusHint(event.target as HTMLElement)) return; + pausedRef.current = true; + }; + const resumeFromFocus = () => { + pausedRef.current = false; + }; + + const handlePointerDown = (event: React.PointerEvent) => { + // Touch and pen already get native drag-to-scroll; only mouse drag + // needs handling here. The loop still needs to know contact exists — a + // still finger scrolls nothing for it to notice otherwise. + if (event.pointerType !== "mouse") { + touchHoldRef.current = true; + return; + } + const viewport = viewportRef.current; + if (!viewport) return; + // Not yet a drag: `draggingRef` stays false and the pointer uncaptured + // until `handlePointerMove` confirms movement past `DRAG_THRESHOLD`. + // Capturing here instead would retarget the browser's own `click` onto + // this viewport, making every logo unclickable by mouse. + dragCandidateRef.current = { + pointerId: event.pointerId, + x: event.clientX, + scrollLeft: viewport.scrollLeft, + }; + }; + + const handlePointerMove = (event: React.PointerEvent) => { + const viewport = viewportRef.current; + if (!viewport) return; + if (draggingRef.current) { + const { x, scrollLeft } = dragStartRef.current; + viewport.scrollLeft = scrollLeft - (event.clientX - x); + return; + } + const candidate = dragCandidateRef.current; + if (!candidate || candidate.pointerId !== event.pointerId) return; + if (Math.abs(event.clientX - candidate.x) < DRAG_THRESHOLD) return; + // Crossed the slop: a drag, not a click. Captured only now, so the + // click-retargeting only applies to a confirmed drag. + draggingRef.current = true; + viewport.setPointerCapture(event.pointerId); + dragStartRef.current = { x: candidate.x, scrollLeft: candidate.scrollLeft }; + viewport.scrollLeft = candidate.scrollLeft - (event.clientX - candidate.x); + }; + + // Before the `draggingRef` guard: touch never sets that ref, so returning + // early here would leave the hold set for good after the first tap. + const endDrag = (event: React.PointerEvent) => { + touchHoldRef.current = false; + dragCandidateRef.current = null; + if (!draggingRef.current) return; + draggingRef.current = false; + viewportRef.current?.releasePointerCapture(event.pointerId); + }; + + const matches = highlightedComponent + ? partnerLogos.filter((logo) => + componentUsage[logo.id]?.includes(highlightedComponent), + ) + : null; + + return ( + +
      +
      +
      +
      +

      {HOVER_HINT}

      +
      + +
      +
      +
      +
      +
      +
      +
      + + {/* Always mounted, not conditional on `matches`, so the crossfade + with the marquee above has something to transition between — + a freshly-mounted element has no "before" state to animate + from. */} +
      + {matches && + (matches.length > 0 ? ( +
        + {matches.map((logo) => ( + + ))} +
      + ) : ( +

      + No confirmed platforms yet for this component. +

      + ))} +
      +
      +
      +
      +
      + + {tooltip && ( + + )} +
      + ); +} diff --git a/website/src/marketing/components/MarketingFooter.tsx b/website/src/marketing/components/MarketingFooter.tsx new file mode 100644 index 00000000..a5cd2e97 --- /dev/null +++ b/website/src/marketing/components/MarketingFooter.tsx @@ -0,0 +1,168 @@ +import React from "react"; +import Link from "./Link"; +import { + ABOUT_BAND, + COLLABORATE_PATH, + IMPACT_PATH, + PRIVACY_PATH, + PRODUCTS_PATH, + TERMS_PATH, +} from "../constants/pages"; +import { + DOCUMENTATION_LINK, + DOCS_COMMUNITY, + EMAIL_LINK, + NETLIFY_LINK, + NETLIFY_IMAGE_LINK, + OICR_LINK, + OVERTURE_DOCUMENTATION_CITING, + OVERTURE_DOCUMENTATION_FUNDING, + OVERTURE_DOCUMENTATION_LICENSING, + OVERTURE_DOCUMENTATION_TEAM_LINK, + OVERTURE_GITHUB_LINK, + OVERTURE_SUPPORT, +} from "../constants/externalLinks"; + +const OICR_LOGO = "/img/marketing/chrome/oicr_logo.svg"; + +type FooterLink = { name: string; url: string; external?: boolean }; +type FooterColumn = { heading: string; links: FooterLink[] }; + +/** + * Four columns, replacing a flat list of eight links with no way to group + * them as pages were added. The columns are the difference between a + * footer and a pile of links. + * + * The only navigation left to the funding/citation content (Impact column, + * leaving the site) and to `ABOUT_BAND`, since the navbar's About item is + * gone. + * + * `external` drives the arrow, not a URL test: `Link` already decides how to + * open a link, and marking these by hand keeps the two decisions independent. + */ +const columns: FooterColumn[] = [ + { + heading: "Platform", + links: [ + { name: "Products", url: PRODUCTS_PATH }, + { name: "Documentation", url: DOCUMENTATION_LINK, external: true }, + { name: "GitHub", url: OVERTURE_GITHUB_LINK, external: true }, + { + name: "Software licensing", + url: OVERTURE_DOCUMENTATION_LICENSING, + external: true, + }, + ], + }, + { + // Two of these three leave the site: /impact/publications/ and + // /about/funding/ are gone, their maintained content on the docs site + // instead. "How to cite us" is a clearer label than "Publications" for + // a link a reader follows to get a citation string. + heading: "Impact", + links: [ + { name: "Deployments", url: IMPACT_PATH }, + { + name: "How to cite us", + url: OVERTURE_DOCUMENTATION_CITING, + external: true, + }, + { name: "Funding", url: OVERTURE_DOCUMENTATION_FUNDING, external: true }, + ], + }, + { + heading: "Connect", + links: [ + { name: "Collaboration", url: COLLABORATE_PATH }, + { name: "Support forum", url: OVERTURE_SUPPORT, external: true }, + { name: "Community", url: DOCS_COMMUNITY, external: true }, + { name: "Contact", url: EMAIL_LINK }, + ], + }, + { + heading: "About", + links: [ + // /about-us/ is gone; the "What we do" band on the home page carries + // what it said, via `ABOUT_BAND` in constants/pages.ts. + { name: "Our story", url: ABOUT_BAND }, + { name: "Team", url: OVERTURE_DOCUMENTATION_TEAM_LINK, external: true }, + { name: "OICR", url: OICR_LINK, external: true }, + ], + }, +]; + +const NetlifyBadge = ({ className }: { className: string }) => ( +
      + + Deploys by Netlify + +
      +); + +/** + * The marketing site's own footer, paired with MarketingNavbar. + * + * Wrapped in `.marketing` for the same reason the navbar is: these render + * outside the page wrapper that carries that class, and without it the styles + * scoped under it would not reach them. + */ +export default function MarketingFooter() { + return ( +
      +
      +
      + + Ontario Institute for Cancer Research + + + + + +
      +
      +
      +
      +
      © {new Date().getFullYear()} Overture.
      + +
      + + {/* Acknowledgements used to repeat here, a second link to a page + already in the row above. Retired with the page itself. */} +
      + Privacy + | + Terms & Conditions +
      + +
      +
      +
      +
      + ); +} diff --git a/website/src/marketing/components/MarketingNavbar.tsx b/website/src/marketing/components/MarketingNavbar.tsx new file mode 100644 index 00000000..74179257 --- /dev/null +++ b/website/src/marketing/components/MarketingNavbar.tsx @@ -0,0 +1,150 @@ +import React, { useState } from "react"; +import { useLocation } from "@docusaurus/router"; +import SearchBar from "@theme/SearchBar"; +import Link from "./Link"; +import { + HOME_PATH, + IMPACT_PATH, + PRODUCTS_PATH, + COLLABORATE_PATH, +} from "../constants/pages"; +import { + DOCUMENTATION_LINK, + OVERTURE_GITHUB_LINK, +} from "../constants/externalLinks"; + +const LOGO = "/img/marketing/chrome/overture_logo.svg"; + +// Three items plus two actions. `Documentation` moved into the actions +// group since it leaves the site; `Support` dropped out entirely and became +// "Support forum" in the footer's Connect column. +// +// No `About`: it was the one item that scrolled the page a visitor was +// already on rather than navigating, since /about-us/ was removed. "Who +// are these people" is answered by that band and by the footer's Our +// story, both via `ABOUT_BAND` in constants/pages.ts. Don't re-add it +// without a real route to point at. +// +// Every label left here points at the page it names. +const navLinks = [ + { name: "Products", url: PRODUCTS_PATH }, + { name: "Impact", url: IMPACT_PATH }, + { name: "Collaboration", url: COLLABORATE_PATH }, +]; + +// Both leave the site, so both carry the external-link icon. No `Get +// Started` beside them: it read as a second call to action, and the docs +// quickstart it pointed at is already one click away via `Docs`. +const navActions = [ + { name: "Docs", url: DOCUMENTATION_LINK }, + { name: "GitHub", url: OVERTURE_GITHUB_LINK }, +]; + +/** + * The marketing site's own navigation. + * + * The documentation navbar (Develop, Deploy, Use, Community) is the wrong + * navigation for these pages, and showing it made the two sites look like one. + * The megamenu this could have carried is already unreachable elsewhere on + * the site, and the mobile menu needs nothing more than a single piece of + * state to open and close. + * + * Docusaurus renders the navbar outside the content wrapper that carries + * `.marketing`, so this brings its own — as a wrapper, not a second class on + * `
      +
      + + ))} + + ); +} diff --git a/website/src/pages/home/index.tsx b/website/src/pages/home/index.tsx new file mode 100644 index 00000000..563840ed --- /dev/null +++ b/website/src/pages/home/index.tsx @@ -0,0 +1,103 @@ +import React from "react"; +import MarketingPage from "../../marketing/MarketingPage"; +import Button from "../../marketing/components/Button"; +import HeroDiagram from "../../marketing/components/HeroDiagram"; +import HomeCapabilities from "../../marketing/components/HomeCapabilities"; +import HomeCollaborate from "../../marketing/components/HomeCollaborate"; +import HomeDocs from "../../marketing/components/HomeDocs"; +import HomeQuickstart from "../../marketing/components/HomeQuickstart"; +import LogoCarousel from "../../marketing/components/LogoCarousel"; +import { ComponentHighlightProvider } from "../../marketing/context/ComponentHighlightContext"; +import { H1, P1 } from "../../marketing/components/Typography"; + +/** + * The marketing home page. One job: get each of the three audiences to the + * right second page inside one scroll. + * + * Four bands under the hero group, each answering a different question, in + * the order a visitor asks them: + * + * 1. **What can I do with this?** `HomeCapabilities`. It replaced a band that + * led with a platform's name, which answered "who uses this" instead. Same + * evidence, inverted: the capability is the heading, the platform proves it. + * 2. **Who do I talk to?** `HomeCollaborate`. What to contact us about and how + * to do it. + * 3. **Where do I read?** `HomeDocs`, one column per hands-on journey. + * 4. **Can I just run it?** `HomeQuickstart`, which is where the hero's first + * button lands. + * + * The first two have swapped places twice and are now back to capabilities + * first: the hero says what Overture is and the carousel says who runs it, so + * the next thing a visitor asks is what it does, not who we are. + * `HomeCollaborate` reads as the staffed route offered just ahead of the two + * self-serve ones below it. + * + * The background colours have never moved with them. The band right under the + * carousel is white and the one after it is grey, whichever content is in them, + * so the page's colour rhythm and its floating cubes are unchanged by either + * swap and only the content order is: pages/_home.scss keeps `grey-bg` and + * `--card-tile-bg` on the slot rather than on the band, which is why a swap here + * is two edits there and not a repaint. + * + * No "Publicly funded, openly built" band any more: its one substantive claim + * is now a paragraph inside `HomeCollaborate`, and its three links are the row + * at that band's foot. It was a second ending in front of the quickstart. + * + * No funder logos and no labelled Collect / Explore / Control diagram: each + * section is written to read correctly without its artwork rather than to + * hold a gap open. + */ +export default function HomePage() { + return ( + + {/* 1. Hero, with the product story in it. Wrapped with LogoCarousel below + in ComponentHighlightProvider: hovering a component here + highlights, in the carousel, the platforms that use it + (data/componentUsage.ts). */} + +
      +
      +
      +

      Connecting research through shared data.

      + + Overture is a collection of open-source software used for + building platforms to store, organize and explore research data. + +
      + {/* Points at the quickstart at the foot of this page, not out + to the documentation: the page's first CTA and its last + section are now the same offer. A bare hash goes to the + browser rather than the router, see Link.tsx. */} + + {/* Points at the documentation band further down this page, + not out to the docs site: same in-page pattern as Run + Quickstart above. A bare hash goes to the browser rather + than the router, see Link.tsx. */} + +
      +
      + +
      +
      + + {/* 2. Who runs Overture, scrolling right below the hero: its own + section, but the shared blue background reads as a continuation + of the hero rather than a new one starting. */} + +
      + + + + + +
      + ); +} diff --git a/website/src/pages/impact/index.tsx b/website/src/pages/impact/index.tsx new file mode 100644 index 00000000..e4fba97d --- /dev/null +++ b/website/src/pages/impact/index.tsx @@ -0,0 +1,630 @@ +import React, { useEffect, useState } from "react"; +import clsx from "clsx"; +import MarketingPage from "../../marketing/MarketingPage"; +import Hero from "../../marketing/components/Hero"; +import Link from "../../marketing/components/Link"; +import { H3 } from "../../marketing/components/Typography"; +import { + OVERTURE_DOCUMENTATION_CITING, + OVERTURE_DOCUMENTATION_FUNDING, + OVERTURE_GITHUB_LINK, +} from "../../marketing/constants/externalLinks"; +import { PRODUCTS_PATH } from "../../marketing/constants/pages"; +import { componentIcon, componentLabel } from "../../marketing/data/components"; +import { + DEPLOYMENTS_ANCHOR, + deploymentRows, +} from "../../marketing/data/deployments"; +import { + DOCKER_HUB_LINK, + GHCR_PACKAGES_LINK, + NPM_SCOPE_LINK, + containerRegistries, + ghcrTags, + npmPackageLink, + npmPackages, + releaseHistory, +} from "../../marketing/data/distribution"; +import metrics from "../../marketing/data/metrics"; +import publications from "../../marketing/data/publications"; +import { floatingTooltipPosition } from "../../marketing/utils/floatingTooltip"; +import { hoverIntentHandlers } from "../../marketing/utils/hoverIntent"; + +/** + * The /impact/ hub, in three sections: who runs Overture, what has been + * published about it, and how far the code travels. + * + * - **Deployments** (`#platforms`): one table of every deployment there + * is, ours and other people's — consolidated from two separate card + * grids, since one table reads faster than two. + * - **Publications** (`#publications`), five papers. + * - **Distribution and release history** (`#distribution`), the + * registries and the release table. + * + * **Every claim on this page links to the source it came from**, and that is the + * rule the sections are built to rather than a finishing touch. The one + * exception is our own documentation traffic, which is our analytics and cannot + * be checked from outside; the page says so in the sentence that gives the + * figure rather than letting it sit among the linked ones. + * + * Rows are ordered by launch year, most recent first, undated at the foot: + * see the sort in `data/deployments.ts`, which also owns the type and + * row-building now — moved out of this file so /products/'s "Used by" + * column (ComponentTable.tsx) can read the same rows and never link to one + * that isn't here. Replaced an order that put other people's deployments + * above ours with an `Independent` badge; the Led by column names the + * institution behind every row instead. + * + * Every row carries its own anchor id, `Platform.id` and the same slug + * case studies have used since the Gatsby site, so `/impact/#icgcargo` and + * the 301 from `/case-studies/#icgcargo` still land — on a table row now, + * not a write-up. `data/caseStudies.tsx` and `components/CaseStudy` had no + * caller left once the write-up sections were removed, and were deleted. + * + * All seven platform rows link to a live portal as of 2026-08-12, when the + * developer confirmed OHCRN's and PCGL's URLs, except the Drug Discovery + * Portal, whose access is internal to one group. + * + * The lineage tier is still cut from this page; `lineage` stays in + * `data/platforms.ts` because the home page logo carousel reads it. `adopters` + * is gone from that file entirely, replaced by `data/dependents.ts`, which is + * where the AGARI and CQDG rows come from. + */ + +/** + * The aggregate band, three figures. + * + * Container pulls and package downloads are gone from here: both count + * activity (`npm install` runs per CI job, not per person) rather than + * adoption, and get a fuller treatment in `#distribution`. The + * external-projects count is gone too: at two organizations, a bare + * number overstated a claim `#beyond` makes better with names and + * sources. Every figure that remains carries its source in the label — a + * figure a reader can't check is one they have to trust, and this page's + * whole argument is that they don't have to. + * + * `key` is its own field since every label is now an element, not a + * string. Figures come from data/metrics.ts without exception; nothing + * here is typed in, which is the rule that stopped the last set going stale. + */ +const aggregates: { key: string; figure: string; label: React.ReactNode }[] = [ + { + key: "platforms", + figure: metrics.activePlatforms.value, + label: ( + <> + platforms in production{" "} + today + + ), + }, + { + key: "releases", + figure: metrics.releaseTags.value, + label: ( + <> + published releases across{" "} + {metrics.stableReleaseHistory.value} + + ), + }, + { + key: "grants", + figure: metrics.activeGrants.value, + label: ( + <> + active grants funding{" "} + the platform + + ), + }, +]; + +export default function ImpactPage() { + /** + * One floating tooltip, two things in it: a screenshot of a deployment, from + * a row's name, and the name of a component, from an icon in its Runs cell. + * One piece of state rather than two, because only one can ever be open — + * they are both driven by the pointer — and two would let a stale one sit + * behind the other. + * + * Rows and icons that have nothing to show are simply not triggers, rather + * than triggers that open onto nothing: every independent adopter and two of + * the seven platforms have no screenshot. + */ + const [tooltip, setTooltip] = useState<{ + kind: "shot" | "label"; + content: string; + rect: DOMRect; + } | null>(null); + const showTooltip = + (kind: "shot" | "label", content: string) => + (event: React.SyntheticEvent) => { + setTooltip({ + kind, + content, + rect: event.currentTarget.getBoundingClientRect(), + }); + }; + const hideTooltip = () => setTooltip(null); + + /** + * The component id to highlight on arrival, from /products/'s "N + * deployments" link (ComponentTable.tsx): that link can't point `:target` + * at several rows at once, so it carries the component id as a query param + * instead — `?used-by=arranger#platforms` — and this reads it back + * client-side. Read in an effect rather than during render, so the page's + * initial markup is the same with or without the param and only gains the + * highlight after mount, the same way `tooltip` above starts closed on + * both server and client. + * + * The id itself, not a precomputed row list: it drives two things a row id + * alone couldn't — which rows light up (`highlightedRowIds` below) and + * which icon in each of those rows' Runs cell gets the glow, the same one + * the home hero's diagram gives a highlighted hotspot (see + * `HeroDiagram.tsx`'s `--highlighted` and `_hero-diagram.scss`). + */ + const [highlightedComponent, setHighlightedComponent] = useState< + string | null + >(null); + /** + * A single row's own anchor id to highlight on arrival, from the home + * page's logo carousel (data/partnerLogos.ts): every logo there is exactly + * one deployment, so it carries that deployment's own id as `?highlight=` + * — the same param name and "highlight this one thing" meaning + * ComponentTable.tsx's own `?highlight=` gives a component on /products/ — + * rather than `used-by` above, which is for a component with several + * deployments to fan out to. Kept separate from `highlightedComponent` + * rather than folded into one id, since they answer different questions (a + * component id vs. a row's own id) and a visitor could in principle arrive + * with either. + */ + const [highlightedDeployment, setHighlightedDeployment] = useState< + string | null + >(null); + useEffect(() => { + const params = new URLSearchParams(window.location.search); + const componentId = params.get("used-by"); + if (componentId) setHighlightedComponent(componentId); + const deploymentId = params.get("highlight"); + if (deploymentId) setHighlightedDeployment(deploymentId); + }, []); + + const highlightedRowIds = + highlightedComponent || highlightedDeployment + ? new Set([ + ...(highlightedComponent + ? deploymentRows + .filter((row) => row.runs.includes(highlightedComponent)) + .map((row) => row.anchorId) + : []), + ...(highlightedDeployment ? [highlightedDeployment] : []), + ]) + : null; + + // Clears the highlight on a click outside every highlighted row's own + // bounds, rather than fading it on a timer: with several rows lit at once + // (see above), a reader scrolling down the table to find all of them could + // otherwise arrive after the fade had already finished. Only listens while + // something is actually highlighted, and a click inside a highlighted + // row — its deployment name, a Runs icon — leaves it lit. + useEffect(() => { + if (!highlightedComponent && !highlightedDeployment) return; + const clearOnOutsideClick = (event: MouseEvent) => { + const target = event.target as HTMLElement; + if (!target.closest(".ImpactTable__highlight")) { + setHighlightedComponent(null); + setHighlightedDeployment(null); + } + }; + document.addEventListener("click", clearOnOutsideClick); + return () => document.removeEventListener("click", clearOnOutsideClick); + }, [highlightedComponent, highlightedDeployment]); + + return ( + + {/* No figure is typed into this page, here included: the subtitle reads + from metrics.ts like everything else. */} + + + {/* Aggregate band. Three figures, each linked to what it was counted + from. See `aggregates` above. */} +
      +
      +
      + {aggregates.map((stat) => ( +
      + {/* `first-letter:uppercase` for a figure metrics.ts spells as a + word: those read correctly mid-sentence in the subtitle above + but cannot start a stat tile in lower case. Numerals, which + all six of these are today, are unaffected. */} +
      + {stat.figure} +
      +
      {stat.label}
      +
      + ))} +
      +
      +
      + + {/* One table, both tiers: the platforms we build and run, and the + organizations running Overture with no involvement from us. Every row + carries its own anchor id, since there are no write-up sections below + for those ids to live on any more; see the note at the top of this + file. */} + {/* A heading and a lede stood here until 2026-08-12, then just a lede + until the developer had the heading restored the same day to match + the yellow-bar-under-heading pattern the Publications and + Distribution sections below use. No lede: the table's own column + heads say what the table is. */} +
      +
      +

      + Deployments +

      +
      + + + + + + + + + + + + + + {deploymentRows.map((row) => ( + + + + + {/* Icons rather than the comma-joined list of names this + cell used to hold. Seven functional names with their + codenames beside them ran to five lines in a column this + narrow and was the widest thing in the table; the same + seven as artwork is one line. The name is not lost, it + moves to the hover, and each icon links to its component + on /products/, which the words never did. Same artwork the + home hero's diagram and the products table use, by id, so + no icon here can drift from the component it names. + + The link also carries `?highlight={componentId}`, the + mirror of this table's own `used-by` param (see above): + ComponentTable.tsx reads it back client-side and gives + the matching row the same persistent highlight this + page's `ImpactTable__highlight` gives a row lit from + /products/, rather than only the brief `:target` flash + an anchor jump gets on its own. */} + + + + + ))} + +
      DeploymentWhat it isLed byRunsLive sinceLink
      + {/* The name is the deployment: hovering it previews the + running portal where there is a shot of one, and + clicking it opens the thing itself. It was a `span` + with nowhere to go until 2026-08-12, which made the one + element on the row that shows you the portal the one + element that could not take you to it. Rows with no + `href` (the internal one) stay plain text. */} + {row.href ? ( + + {row.name} + + ) : ( + row.name + )} + {row.summary} + {[row.institution, row.where].filter(Boolean).join(" · ") || + "—"} + + {row.runs.length > 0 ? ( +
        + {row.runs.map((componentId) => ( +
      • + + + +
      • + ))} +
      + ) : ( + "—" + )} +
      {row.since ? `Launched ${row.since}` : "—"} +
        + {row.links.map((link) => ( +
      • + {link.label} +
      • + ))} +
      +
      +
      + + {/* One tooltip element for both kinds, sized to what it is about to + hold: a portal screenshot needs a box, a component's name needs a + line, and passing the screenshot's dimensions for a label would flip + it to the wrong side of a trigger near the foot of the window. */} + {tooltip && ( + + )} +
      + + {/* The four long-form write-up sections stood here, one per platform with + a case study, each with its own way back up to the table. Removed + 2026-08-12 on the developer's instruction. Their anchor ids moved onto + the table rows, so the links that used to land on them still land; see + the note at the top of this file. data/caseStudies.tsx and + components/CaseStudy were deleted with them, having had no caller + left on this site. */} + + {/* The peer-reviewed record. Not a citation page: docs.overture.bio owns + how to cite us and the link at the foot of this section says so. What + this section is for is the statement that the work has been reviewed + at all, which this site made nowhere after /impact/publications/ was + retired. */} +
      +
      +

      + Publications +

      +
      + +
        + {publications.map((paper) => ( +
      • +

        {paper.authors}

        + {/* The title is the link, since the DOI is what a reader + follows and the title is what they are following it for. + The one paper with no DOI is not a link and says why in the + line under it rather than resolving to nothing. */} +

        + {paper.href ? ( + {paper.title} + ) : ( + paper.title + )} +

        +

        + {paper.venue} + {paper.year ? ` (${paper.year})` : ""} + {paper.doi ? ` · ${paper.doi}` : ""} + {paper.status ? ` · ${paper.status}` : ""} +

        +

        + {paper.relevance} +

        +
      • + ))} +
      + +

      + How to cite Overture{" "} + is on the documentation site, with the full author list and a + citation block. +

      +
      +
      + + {/* How far the code travels, and how long it has been travelling. Last, + because it is the section a reader scrolls to rather than the one they + arrive for, and because the release table is the longest thing on the + page. */} +
      +
      +

      + Distribution and release history +

      +
      + +
      +
      +

      + Packages +

      +

      + {metrics.npmDownloads.value} +

      +

      + downloads in the twelve months to 9 August 2026, across{" "} + + {metrics.npmPackages.value} published packages + + . +

      + {/* All thirteen, not a top five. The tail is the more interesting + half: it says the packages are being taken individually rather + than as one bundle. */} +
      + {npmPackages.map((pkg) => ( +
      +
      + {pkg.name} +
      +
      {pkg.downloads}
      +
      + ))} +
      +
      + +
      +

      + Container images +

      +

      + {metrics.containerPulls.value} +

      +

      + cumulative pulls of the seven components’ images on{" "} + Docker Hub, led by{" "} + {containerRegistries + .map((image) => `${image.label} at ${image.pulls}`) + .join(", ")} + . Docker Hub is the historical registry. Current distribution + is the{" "} + GitHub Container Registry, + which publishes no pull count but carries{" "} + {ghcrTags.map((image, index) => ( + + {index > 0 ? ", " : ""} + {image.tags} tags for {image.label} + + ))} + . The figure above therefore stops growing at the point + distribution moved, and understates use rather than overstating + it. +

      +
      +
      + +

      + Releases +

      +

      + {metrics.releaseTags.value} +

      +

      + {metrics.releaseTags.value} published releases in total: + npm packages, container images, and merged release builds where + that is a component's own mechanism, counted from each one's + registry rather than from git tags, which this org uses too + inconsistently to total on their own, spanning{" "} + {metrics.stableReleaseHistory.value} of continuous release + activity. All seven components are{" "} + developed in the open{" "} + under an OSI-approved AGPL-3.0 licence. +

      + + + + + + + + + + + + + {releaseHistory.map((component) => ( + + + + + + + + ))} + +
      ComponentFirst releaseReleasesLatestContributors
      + + {component.name} ({component.codename}) + + + + {component.firstRelease} + + {component.tags} + {component.latest} + + + {component.contributors} + +
      +
      +
      +
      + ); +} diff --git a/website/src/pages/index.tsx b/website/src/pages/index.tsx index faf88f76..249be56c 100644 --- a/website/src/pages/index.tsx +++ b/website/src/pages/index.tsx @@ -25,7 +25,7 @@ function HomepageHeader() { ); } -export default function Home(): JSX.Element { +export default function Home(): React.JSX.Element { const {siteConfig} = useDocusaurusContext(); return ( + + + ); +} diff --git a/website/src/pages/products/index.tsx b/website/src/pages/products/index.tsx new file mode 100644 index 00000000..84a788a4 --- /dev/null +++ b/website/src/pages/products/index.tsx @@ -0,0 +1,50 @@ +import React from "react"; +import MarketingPage from "../../marketing/MarketingPage"; +import Hero from "../../marketing/components/Hero"; +import ComponentTable from "../../marketing/components/ComponentTable"; + +/** + * What Overture is made of: a hero and one table. + * + * What it replaced was seven near-identical sections of three feature tiles + * each, one per component, in codename order. Two notable choices: + * + * - The stack is grouped as Collect, Explore and Control, so an evaluator + * reads a shape rather than a catalogue. All three are group rows in one + * table (ComponentTable) rather than a band each: Control was the last one + * still rendering as prose here, and it now holds the row for the + * authorization service being built. + * - Components are named functionally with the codename beside them, in a + * fixed house style. This is the page that teaches the pairing: someone + * who arrives knowing only "Arranger" leaves knowing it is the search + * service, and someone who needs search finds it without knowing any + * codename at all. + * + * The copy is data, in src/marketing/data/components.ts, so this file is a + * layout and nothing on it needs a code change to reword. + * + * `StackDiagram` (a flow diagram of the same seven components) lived here + * briefly and was pulled: the click-through to a table row cut instantly with + * no visual continuity ("loses my place"), and the diagram's own footprint + * read as unexplained empty space even after two rounds of tightening it. + * `components/StackDiagram.tsx`, `data/componentFlow.ts`, `data/techIcons.ts` + * and `styles/components/_stack-diagram.scss` were deleted with it. + * `ComponentTable`'s hover/focus row highlighting, which existed only to sync + * with the diagram, was reverted at the same time. + */ +export default function ProductsPage() { + return ( + + + + + + ); +} diff --git a/website/src/pages/terms-conditions/index.tsx b/website/src/pages/terms-conditions/index.tsx new file mode 100644 index 00000000..96fb8fa9 --- /dev/null +++ b/website/src/pages/terms-conditions/index.tsx @@ -0,0 +1,20 @@ +import React from "react"; +import ProsePage from "../../marketing/components/ProsePage"; +import Prose from "../../marketing/content/terms-conditions.mdx"; + +/** + * The copy for this page is in src/marketing/content/terms-conditions.mdx. Edit + * it there, including the section numbers. + */ +export default function TermsConditionsPage() { + return ( + + + + ); +} diff --git a/website/src/theme/DocSidebar/index.tsx b/website/src/theme/DocSidebar/index.tsx index 331c8675..4ccea90c 100644 --- a/website/src/theme/DocSidebar/index.tsx +++ b/website/src/theme/DocSidebar/index.tsx @@ -7,7 +7,7 @@ import styles from './styles.module.css'; type Props = WrapperProps; -export default function DocSidebarWrapper(props: Props): JSX.Element { +export default function DocSidebarWrapper(props: Props): React.JSX.Element { return (
      diff --git a/website/src/theme/Footer/index.tsx b/website/src/theme/Footer/index.tsx new file mode 100644 index 00000000..302d08c5 --- /dev/null +++ b/website/src/theme/Footer/index.tsx @@ -0,0 +1,18 @@ +import React from "react"; +import Footer from "@theme-original/Footer"; +import MarketingFooter from "@site/src/marketing/components/MarketingFooter"; +import useIsMarketingRoute from "@site/src/marketing/useIsMarketingRoute"; + +/** + * The footer half of the same split. See src/theme/Navbar/index.tsx. + * + * A marketing navbar over the documentation footer would still read as one + * site, which is the thing this is meant to fix. + */ +export default function FooterWrapper( + props: React.ComponentProps, +): React.JSX.Element { + const isMarketing = useIsMarketingRoute(); + + return isMarketing ? :