/* Public site. Shared tokens, base controls, buttons, forms and alerts are
   loaded first; this file owns only the public-facing composition. */

:root {
  --color-brand: light-dark(var(--green-700), var(--green-400));
  --color-brand-wash: light-dark(var(--green-100), var(--green-900));
  --color-focus: light-dark(var(--green-700), var(--green-400));
  --color-link: light-dark(var(--green-700), var(--green-400));
  --color-action: var(--green-700);
  --color-action-ink: #fff;
  --action-hover-brightness: 0.9;
  --carousel-overlay: rgb(16 21 15 / 0.75);
  --site-shell: 72rem;
}

.public-site {
  min-height: 100vh;
  margin: 0;
  display: flex;
  flex-direction: column;
}

.site-shell {
  width: min(100% - 2 * var(--space-4), var(--site-shell));
  margin-inline: auto;
}

.skip-link {
  position: fixed;
  z-index: var(--z-header);
  top: var(--space-2);
  left: var(--space-2);
  translate: 0 -5rem;
  padding: var(--space-2) var(--space-4);
  border-radius: var(--radius);
  background: var(--color-bg);
  color: var(--color-text);
  box-shadow: var(--shadow-md);
}

.skip-link:focus { translate: 0; }

.site-header {
  background: light-dark(var(--green-600), var(--grey-875));
  border-bottom: 1px solid light-dark(transparent, var(--grey-700));
  box-shadow: var(--shadow-sm);
}

.site-header__inner {
  position: relative;
  display: flex;
  align-items: end;
  justify-content: space-between;
  gap: var(--space-6);
  min-height: 8rem;
  padding-top: var(--space-5);
}

.site-theme {
  position: absolute;
  z-index: var(--z-popover);
  top: var(--space-3);
  right: 0;
}

/* Sized as a compact button (.btn-sm in components.css): this is an ambient
   preference control sitting over the header image, not one of the page's
   actions, and at full .btn height it competed with the brand wordmark next to
   it. Metrics are kept in step with the compact variant by hand because the
   colours are white-alpha over the header photo rather than tokens, so it
   cannot just be a .btn. */
.site-theme__cycle {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  min-height: var(--control-sm);
  padding: var(--space-1) var(--space-3);
  border-radius: var(--radius-pill);
  border: 1px solid rgb(255 255 255 / 0.3);
  background: rgb(255 255 255 / 0.16);
  color: rgb(255 255 255 / 0.82);
  font-size: var(--text-sm);
  font-weight: 500;
  white-space: nowrap;
  cursor: pointer;
}

/* The current theme name is the only part worth reading at a glance, so it
   keeps a little weight -- but only up to the nav's 600, never past it. Left as
   a bare <strong> it rendered at the browser's 700 in full white, which made a
   preference control the brightest text in the header, ahead of the five links
   that are the actual navigation. */
.site-theme__cycle strong {
  font-weight: 600;
}

/* Same reasoning as the compact buttons: without a pointer the shrunk target
   is too small to hit, so touch gets the full tap height back. */
@media (pointer: coarse) {
  .site-theme__cycle {
    min-height: var(--tap);
    padding: var(--space-2) var(--space-3);
  }
}

.site-theme__cycle:hover,
.site-theme__cycle:focus {
  background: rgb(255 255 255 / 0.28);
  color: #fff;
}

.site-header__brand {
  padding-bottom: var(--space-5);
  color: #fff;
  font-family: Georgia, "Times New Roman", serif;
  font-size: clamp(2rem, 6vw, 3.5rem);
  line-height: 1;
  letter-spacing: var(--tracking-tight);
  text-decoration: none;
}

.site-header__brand:hover,
.site-header__brand:focus {
  color: #fff;
  text-decoration: none;
}

.site-nav {
  display: flex;
  align-items: end;
  gap: var(--space-1);
}

.site-nav a {
  display: flex;
  align-items: center;
  min-height: var(--tap);
  padding: var(--space-2) var(--space-4);
  border-radius: var(--radius) var(--radius) 0 0;
  color: #fff;
  font-size: var(--text-sm);
  font-weight: 600;
  text-decoration: none;
}

.site-nav a:hover,
.site-nav a:focus,
.site-nav a.active {
  background: light-dark(rgb(255 255 255 / 0.9), var(--grey-950));
  color: var(--color-text);
}

.site-main {
  flex: 1;
  padding-block: var(--space-8) var(--space-12);
}

.site-main > .alert:first-child { margin-bottom: var(--space-6); }

.public-footer {
  padding-block: var(--space-8);
  background: var(--grey-900);
  color: var(--grey-300);
}

.public-footer__inner {
  display: flex;
  justify-content: space-between;
  gap: var(--space-8);
  align-items: end;
}

.public-footer h2 { color: #fff; }
.public-footer p { margin: var(--space-2) 0 0; }
.public-footer a {
  color: #fff;
  text-decoration: underline;
  text-underline-offset: 0.15em;
}

.public-footer__meta {
  color: var(--grey-400);
  font-size: var(--text-sm);
  white-space: nowrap;
}

/* Homepage */

/* The standfirst under an h1: one sentence saying what the page below it is,
   which is the difference between a heading and an orientation. Not the
   admin bundle's .lede -- that one is a key fact inside a panel -- and the two
   bundles never load together, but the name says which is meant. */
.site-lede {
  max-width: 62ch;
  color: var(--color-text-muted);
  font-size: var(--text-lg);
  line-height: var(--leading-snug);
}

/* The vacancy list. This is the front page: every object that is free right
   now, dearest first, each card carrying enough -- picture, size, rent, move-in
   date, what the rent includes -- to decide from without opening it, and the
   application button right there for when the decision is yes. */
.vacancies { margin-bottom: var(--space-8); }

.vacancy-list {
  display: grid;
  gap: var(--space-5);
  margin: var(--space-6) 0 0;
  padding: 0;
  list-style: none;
}

/* Picture, description, offer.

   The offer column is a fixed 13rem rather than minmax(13rem, auto). Each card
   is its own grid, so an auto track is sized by that card's own contents and
   nothing else: one object with "Hyra på förfrågan" where the rest have a
   figure took the rail out to 15.6rem for that row alone, and the price, the
   button and the rail's own border stepped sideways in the middle of the list.
   Fixed, the three of them line up straight down the page, which is the whole
   point of giving the offer a column of its own. 13rem is what every row with
   a figure in it already settled on, and it holds "Anmäl intresse" on one
   line; the one phrase that does not fit wraps instead (see the rent). */
.vacancy-card {
  display: grid;
  grid-template-columns: minmax(0, 20rem) minmax(0, 1fr) 13rem;
  gap: var(--space-5);
  padding: var(--space-5);
  background: var(--color-surface);
}

/* Big enough to be the picture of a home rather than an icon beside its name:
   this is the first thing that decides whether the rest of the card gets read.
   It takes the photograph srcset -- :square is cropped for tiles and would
   lose the sides of the house here. */
.vacancy-card__media {
  position: relative;
  display: block;
  overflow: hidden;
  width: 100%;
  aspect-ratio: 4 / 3;
  border-radius: var(--radius);
  background: var(--color-surface-sunk);
}

.vacancy-card__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: scale var(--duration-slow) var(--ease-out);
}

.vacancy-card__media--placeholder {
  display: grid;
  place-items: center;
  border: 1px dashed var(--color-border);
  color: var(--color-text-faint);
}

.vacancy-card__media--placeholder svg {
  width: 28%;
  height: 28%;
  opacity: 0.7;
}

/* The class is the lightbox's, added only once the script has wired the link
   up: without it the picture is a link to the object's page and a magnifier
   would be promising something that does not happen. */
.vacancy-card__media.is-lightbox-ready { cursor: zoom-in; }
.vacancy-card__media:hover img { scale: 1.05; }

/* How many pictures are behind the one on show -- the reason to press it. Sits
   where the carousel's own counter sits, bottom right and over the picture,
   so the two read as the same piece of furniture. */
.vacancy-card__count {
  position: absolute;
  right: var(--space-2);
  bottom: var(--space-2);
  display: inline-flex;
  gap: var(--space-1);
  align-items: center;
  padding: var(--space-1) var(--space-2);
  border-radius: var(--radius-pill);
  background: var(--carousel-overlay);
  color: #fff;
  font-size: var(--text-xs);
  font-weight: 600;
  line-height: 1;
  /* The badge is inside a link, and the browser's default underline would run
     under the camera as well as under the count. A line under a glyph reads as
     a stray mark; the picture behind it is the affordance here, not the rule. */
  text-decoration: none;
}

.vacancy-card__count svg {
  width: 0.9rem;
  height: 0.9rem;
}

.vacancy-card__body { min-width: 0; }

/* The name of the object and the way to see where it is, on one line. The
   button is pushed out with margin-left rather than by space-between: when a
   long title takes the whole width and the button wraps below it, auto margin
   still holds it at the right edge, where space-between would drop it back to
   the left. */
.vacancy-card__head {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-3);
  align-items: center;
}

.vacancy-card__title {
  margin: 0;
  min-width: 0;
  font-size: var(--text-xl);
  line-height: var(--leading-snug);
}

.vacancy-card__body address {
  margin: var(--space-1) 0 0;
  color: var(--color-text-muted);
}

/* The map button in the corner of the text column. It is a .btn, so the glyph
   is centred against its label by the button's own flex box rather than
   balanced on a text baseline -- which is what a bare inline pin was doing,
   and never quite straight.

   Little else is new here: the capsule, the 14px label and the --control-sm
   height that grows back to a full tap target on a touch screen are all
   .btn.btn-sm, as every other secondary action on the site wears it. */
.vacancy-card__map {
  flex: none;
  margin-left: auto;
}

/* Sized to the label's own cap height so the pin reads as a word's worth of
   mark rather than as a picture with writing next to it. */
.vacancy-card__map svg {
  width: 1.05rem;
  height: 1.05rem;
  flex: none;
}

/* Label above value, wrapping into as many columns as fit: an apartment shows
   two of these pairs and a house four, and neither should leave a gap. */
.vacancy-card__facts {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-6);
  margin: var(--space-4) 0 0;
}

.vacancy-card__facts dt {
  color: var(--color-text-faint);
  font-size: var(--text-sm);
  text-transform: uppercase;
  letter-spacing: var(--tracking-wide);
}

.vacancy-card__facts dd {
  margin: 0;
  font-weight: 600;
}

.vacancy-card__blurb {
  margin: var(--space-4) 0 0;
  color: var(--color-text-muted);
}

/* The three things in the order the decision is made: what it costs, the
   button, and the way out to the pictures if the answer is not yet. Centred
   rather than pinned to the ends, so a tall card does not leave its own price
   and its own button at opposite corners. */
.vacancy-card__offer {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  justify-content: center;
  gap: var(--space-4);
  padding-left: var(--space-5);
  border-left: 1px solid var(--color-border);
  text-align: center;
}

.vacancy-card__rent { margin: 0; }

/* A figure never wraps at this width; "Hyra på förfrågan" does, now that the
   rail no longer widens to take it. Balanced rather than left to the greedy
   line-breaker, which would hang "förfrågan" alone under a nearly full line --
   a lopsided pair of lines in the one slot on the card the eye goes to first. */
.vacancy-card__rent strong {
  display: block;
  color: var(--color-brand);
  font-size: var(--text-2xl);
  line-height: var(--leading-snug);
  text-wrap: balance;
}

.vacancy-card__rent span {
  color: var(--color-text-muted);
  font-size: var(--text-sm);
}

.vacancy-card__more { font-size: var(--text-sm); }

/* The offer is the narrowest of the three columns and the description is the
   hungriest, so the rail is the first thing to go -- not at the phone
   breakpoint, but as soon as the window is off full width. Below this the
   offer is the card's footer and hands its 13rem to the text.

   A footer that kept the column layout would strand the price at one end of a
   full-width strip and its button at the other, so it turns into a line: what
   it costs on the left, the way to act on it on the right. */
@media (max-width: 68rem) {
  .vacancy-card { grid-template-columns: minmax(0, 18rem) minmax(0, 1fr); }

  .vacancy-card__offer {
    grid-column: 1 / -1;
    flex-flow: row wrap;
    align-items: center;
    justify-content: flex-end;
    gap: var(--space-3) var(--space-5);
    padding-left: 0;
    padding-top: var(--space-4);
    border-left: 0;
    border-top: 1px solid var(--color-border);
    text-align: left;
  }

  .vacancy-card__rent { margin-right: auto; }
}

.vacancy-card,
.property-showcase,
.info-card,
.welcome,
.detail-aside,
.content-card,
.form-card,
.application-form__panel {
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
}

.property-showcase {
  padding: var(--space-6);
  background: light-dark(var(--orange-100), var(--grey-875));
}

.property-group + .property-group {
  margin-top: var(--space-6);
  padding-top: var(--space-5);
  border-top: 1px solid var(--color-border);
}

.property-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr));
  gap: var(--space-4);
}

.property-card {
  position: relative;
  min-width: 0;
  padding: var(--space-2);
  border-radius: var(--radius-lg);
  background: transparent;
  transition: background-color 150ms ease;
}

.property-card:hover,
.property-card:focus-within {
  background: color-mix(in srgb, var(--color-brand-wash) 45%, transparent);
}

.property-card__image {
  display: block;
  overflow: hidden;
  border-radius: var(--radius);
  background: var(--color-surface-sunk);
  aspect-ratio: 1 / 1;
  text-decoration: none;
}

.property-card__image img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/*
 * A property we have no picture of. It still gets a card -- an address with no
 * photograph is a gap in the gallery, not a reason to leave the building off
 * the page -- so the tile keeps the grid's square and carries a house mark in
 * place of the image. Muted enough to read as absent rather than as a
 * photograph that failed to load.
 */
.property-card__image--placeholder {
  display: grid;
  place-items: center;
  border: 1px dashed var(--color-border);
  color: var(--color-text-faint);
}

.property-card__image--placeholder svg {
  width: 40%;
  height: 40%;
  opacity: 0.7;
}

.property-card__title {
  display: block;
  width: 100%;
  margin-top: var(--space-2);
  color: var(--color-text);
  font-size: var(--text-sm);
  font-weight: 600;
  line-height: var(--leading-snug);
  text-align: center;
  text-decoration: none;
  text-wrap: balance;
}

.property-card__title:hover,
.property-card__title:focus { text-decoration: none; }

.property-card .label {
  position: absolute;
  z-index: 1;
  top: var(--space-2);
  left: var(--space-2);
  box-shadow: var(--shadow-sm);
}

.home-info { margin-top: var(--space-5); }

.info-card {
  padding: var(--space-6);
  background: var(--color-surface);
}

.info-card h2 { color: light-dark(var(--amber-700), var(--amber-400)); }

.welcome {
  display: flow-root;
  margin-top: var(--space-5);
  padding: var(--space-6);
  background: light-dark(var(--green-600), var(--green-900));
  border-color: transparent;
  color: #fff;
}

.welcome figure {
  float: right;
  width: min(11rem, 35%);
  margin: 0 0 var(--space-4) var(--space-6);
}

.welcome figure img {
  display: block;
  width: 100%;
  border-radius: var(--radius-lg);
}

.welcome figcaption {
  color: var(--green-100);
  font-size: var(--text-sm);
  text-align: center;
}

/* Listings and property details */

.listing-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(17rem, 100%), 1fr));
  gap: var(--space-4);
}

.listing-card {
  padding: var(--space-5);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  background: var(--color-surface);
}

.listing-card h2,
.listing-card h3 { margin-top: 0; }

.house-card { text-align: center; }

.house-card img {
  display: block;
  width: 9.375rem;
  height: 9.375rem;
  margin: var(--space-4) auto 0;
  border-radius: var(--radius);
  object-fit: cover;
}

.property-detail {
  display: grid;
  grid-template-columns: minmax(0, 2fr) minmax(16rem, 1fr);
  gap: var(--space-6);
}

.property-detail__main,
.detail-aside { min-width: 0; }

.property-detail__back {
  display: inline-block;
  margin-bottom: var(--space-4);
}

.property-detail__actions,
.detail-aside__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  justify-content: flex-end;
  margin-top: var(--space-4);
}

.detail-aside {
  align-self: start;
  padding: var(--space-5);
  background: var(--color-surface);
}

.detail-aside h2 { font-size: var(--text-lg); }

.fact-list {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--space-2) var(--space-4);
  margin: 0;
}

.fact-list dt { color: var(--color-text-muted); font-weight: 600; }
.fact-list dd { margin: 0; }
.feature-list { margin-top: var(--space-5); }
.feature-list .cluster { margin-top: var(--space-2); }

.rent-status {
  display: flex;
  justify-content: flex-end;
  margin-top: var(--space-4);
  font-size: var(--text-sm);
}

.rent-status--unavailable {
  color: var(--color-danger);
  font-size: var(--text-lg);
  font-weight: 700;
}

.apartment-list { width: 100%; margin-top: var(--space-5); }

.apartment-card {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: var(--space-2) var(--space-4);
  padding: var(--space-4);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  background: var(--color-surface);
}

.apartment-card + .apartment-card { margin-top: var(--space-3); }
.apartment-card p { grid-column: 1 / -1; margin: 0; }
.important-info { color: var(--status-active); font-weight: 600; }

/* Carousel and galleries */

.carousel {
  position: relative;
  overflow: hidden;
  border-radius: var(--radius-lg);
  background: var(--color-surface-sunk);
}

.carousel__slide { margin: 0; }
.carousel__slide[hidden] { display: none; }

.carousel__slide img {
  display: block;
  width: 100%;
  max-height: 36rem;
  object-fit: contain;
}

.carousel__slide figcaption {
  padding: var(--space-3) var(--space-4);
  color: var(--color-text-muted);
  font-size: var(--text-sm);
  text-align: center;
}

.carousel__control {
  position: absolute;
  top: 50%;
  translate: 0 -50%;
  display: grid;
  place-items: center;
  width: var(--tap);
  height: var(--tap);
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: var(--carousel-overlay);
  color: #fff;
  cursor: pointer;
}

.carousel__control svg {
  width: 1.25rem;
  height: 1.25rem;
}

.carousel__control path {
  fill: none;
  stroke: currentColor;
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-width: 2;
}

.carousel__control--prev { left: var(--space-3); }
.carousel__control--next { right: var(--space-3); }

/* One picture is not a carousel, and the lightbox hides the two controls by
   setting their hidden attribute -- which the display above would otherwise
   win against, the way .carousel__slide[hidden] just below has to say so too.
   The property pages never render the controls for a lone picture at all. */
.carousel__control[hidden] { display: none; }

.carousel__status {
  position: absolute;
  right: var(--space-3);
  bottom: var(--space-3);
  padding: var(--space-1) var(--space-2);
  border-radius: var(--radius-pill);
  background: var(--carousel-overlay);
  color: #fff;
  font-size: var(--text-xs);
}

/* The front page's pictures open here. Everything inside the frame is the
   carousel above -- same slide, same two controls, same counter -- and this
   supplies the dark room around it.

   position: fixed rather than the dialog's own centring: it makes the element
   its own containing block, so the close button can sit in the corner of the
   screen instead of the corner of the picture, and it leaves the whole
   backdrop as one click target for closing. The closed state is left to the
   browser's `dialog:not([open]) { display: none }`, which is why the display
   lives on [open]. */
.lightbox {
  position: fixed;
  inset: 0;
  width: auto;
  height: auto;
  max-width: none;
  max-height: none;
  margin: 0;
  padding: var(--space-6) var(--space-4);
  border: 0;
  background: transparent;
}

.lightbox::backdrop { background: rgb(8 10 8 / 0.85); }

.lightbox[open] {
  display: grid;
  place-items: center;
  animation: lightbox-rise var(--duration-slow) var(--ease-out);
}

.lightbox[open]::backdrop { animation: lightbox-fade var(--duration-slow) var(--ease-out); }

@keyframes lightbox-rise {
  from {
    opacity: 0;
    translate: 0 3rem;
    scale: 0.94;
  }
}

@keyframes lightbox-fade {
  from { opacity: 0; }
}

/* fit-content so the frame hugs the photograph: a portrait picture in a fixed
   width box would sit between two bars of the carousel's own background, and
   the controls would hang over the bars rather than over the picture. */
.lightbox__carousel {
  width: fit-content;
  max-width: 100%;
  box-shadow: var(--shadow-lg);
}

/* The page's carousel is one column of a property page and caps at 36rem. Here
   the picture is the entire point, so it takes what the window will give it
   and the frame follows its shape. */
.lightbox .carousel__slide img {
  width: auto;
  max-width: 100%;
  max-height: min(78vh, 52rem);
}

.lightbox__close {
  position: absolute;
  top: var(--space-4);
  right: var(--space-4);
  display: grid;
  place-items: center;
  width: var(--tap);
  height: var(--tap);
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: var(--carousel-overlay);
  color: #fff;
  font-size: var(--text-2xl);
  line-height: 1;
  cursor: pointer;
}

.gallery { display: grid; gap: var(--space-6); }
.gallery figure { margin: 0; }

.gallery img {
  display: block;
  width: min(100%, 43.75rem);
  border-radius: var(--radius-lg);
}

.gallery figcaption {
  margin-top: var(--space-2);
  color: var(--color-text-muted);
}

/* Running text. base.css deliberately leaves links marked by colour alone --
   right for admin rows and toolbars, wrong here, where a link sits mid-sentence
   in owner-authored copy and colour is then the only thing distinguishing it.
   That is the case its comment on `a` says to handle exactly this way: in the
   block that has the body copy, rather than by reverting the rule.

   Buttons are excluded because a button is already a shape. */
.prose a:not(.btn) {
  text-decoration: underline;
  text-underline-offset: 0.15em;
}

/* Icons are never underlined -- the rule icons.css states and enforces for the
   admin. It is repeated here because the public bundle does not load icons.css
   (SITE_STYLESHEETS carries no icon font), so nothing else would stop the
   underline above from running under a glyph in owner-authored copy.
   inline-block makes the glyph an atomic box, so an ancestor's underline stops
   at its edge. */
.prose [class^="icon-"],
.prose [class*=" icon-"] {
  display: inline-block;
  text-decoration: none;
}

/* Content and forms */

.content-card,
.form-card {
  max-width: var(--measure);
  padding: var(--space-6);
  background: var(--color-surface);
}

.content-card--centered { margin-inline: auto; text-align: center; }
.contact-form { margin-top: var(--space-6); }

.form-card legend {
  width: 100%;
  margin-bottom: var(--space-5);
  padding: 0;
  border: 0;
}

.form-card .form-actions { margin-top: var(--space-6); }

.form-actions--centered {
  display: flex;
  justify-content: center;
}

.form-card .form-control,
.form-card input:not([type="checkbox"]):not([type="radio"]),
.form-card select,
.form-card textarea { width: min(100%, 32rem); }

.form-card textarea { resize: vertical; }
.form-section + .form-section { margin-top: var(--space-8); }

/* --- Intresseanmälan ------------------------------------------------------
 *
 * The one long form on the public site, and the only page here that wants more
 * than a reading measure. It used to be a single 32rem column of eighteen
 * fields inside the 68ch .form-card -- two thirds of a 72rem window left empty
 * beside a scroll that read as a questionnaire.
 *
 * Two cards instead, split by what the answers are about rather than by length:
 * one is the object (area, house, apartment, furnished) and one is the person.
 * They are peers -- neither is inside the other, and there is no third card
 * around them -- so the heading and the submit button sit on the page itself.
 *
 * That split is also the order someone fills it in, so when the grid collapses
 * to one column on a phone the object card stays first and the sequence is
 * unchanged.
 */
.application-form__lead {
  max-width: var(--measure);
  margin-bottom: var(--space-6);
  color: var(--color-text-muted);
}

.application-form__columns {
  display: grid;
  grid-template-columns: minmax(0, 22rem) minmax(0, 1fr);
  gap: var(--space-5);
  /* start, not stretch: the object card is four fields against a dozen, and a
     card stretched to its taller neighbour is mostly empty background. */
  align-items: start;
}

.application-form__panel {
  padding: var(--space-5);
  background: var(--color-surface);
  /* Contains the floated legend below. */
  display: flow-root;
}

/* A <legend> is normally laid out *in* its fieldset's top border, which is
   exactly right when the fieldset is invisible -- as it is everywhere else on
   this site -- and wrong the moment the fieldset is the card. Here the heading
   sat astride the card's own top edge with the rounded corner running through
   it. Floating it opts the legend out of that special layout and back into
   ordinary flow; the full width keeps the fields below it rather than beside. */
.application-form__panel legend {
  float: left;
  width: 100%;
  padding: 0;
  margin-bottom: var(--space-4);
}

/* Both cards are .form-section, and they are siblings, so the stacked-section
   rule above would push the second one down by itself. */
.application-form .form-section + .form-section { margin-top: 0; }

/* The object card is the shorter one and the one the rest of the form is an
   answer to, so it stays in view while the person card is filled in. */
.application-form__panel--object {
  position: sticky;
  top: var(--space-4);
}

/* A control has no reason to stop short of its own card here: the cards are
   already narrow enough to read, which is what .form-card's 32rem cap is for
   on the one-column pages. */
.application-form .form-group :is(.form-control, select, textarea),
.application-form .form-group input:not([type="checkbox"]):not([type="radio"]) {
  width: 100%;
}

.application-form .form-actions { justify-content: center; }

/* Say a field is wrong where it is wrong, rather than only after a round trip.
 *
 * :user-invalid, not :invalid. Every required field is invalid the moment the
 * page loads, so :invalid would open this form with a dozen red boxes and no
 * mistake made yet. :user-invalid holds off until the visitor has typed in the
 * field and left it -- which is also when Personnummer's pattern first has
 * something to say. The server still decides; this only stops a typo from
 * costing a submit. */
.application-form .form-group :is(input, select, textarea):user-invalid {
  border-color: var(--color-danger);
}

/* --- Yes/no answers -------------------------------------------------------
 *
 * Rökare, husdjur, betalningsanmärkningar and möblerat were four required
 * Ja/Nej dropdowns. A dropdown for a two-value answer costs a click to open, a
 * read of two options and a click to choose, and it starts empty -- so the
 * commonest answer (no to all four) was four interactions that changed nothing.
 * As checkboxes the same answer is silence, and the labels are statements
 * rather than questions so an unticked box reads as the "nej" it sends.
 *
 * They are given a row each, boxed, because a bare checkbox in a form of
 * text fields is a small target that is easy to scroll past.
 */
.question-group {
  display: grid;
  gap: var(--space-2);
  margin-bottom: var(--space-4);
}

/* The group is a block of rows after a run of single fields, so it needs more
   air above it than the gap between two of those fields. */
.form-group + .question-group { margin-top: var(--space-5); }

.question-group__title {
  margin: 0;
  color: var(--color-text-muted);
  font-size: var(--text-sm);
}

.question-group .form-group { margin-bottom: 0; }

.question-group .checkbox {
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  background: var(--color-surface);
  transition: border-color 150ms ease, background-color 150ms ease;
}

/* The label is the whole row, so the row is the hit area. simple_form nests the
   box inside its label, which is what makes that free. */
.question-group .checkbox label {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  width: 100%;
  cursor: pointer;
}

.question-group .checkbox:hover {
  border-color: var(--color-brand);
}

/* A ticked row says so at row scale rather than at box scale -- :has() lets the
   row read its own checkbox, which is the whole reason these can be rows. */
.question-group .checkbox:has(input:checked) {
  border-color: var(--color-brand);
  background: var(--color-brand-wash);
}

.question-group .checkbox:has(input:focus-visible) {
  outline: 2px solid var(--color-brand);
  outline-offset: 2px;
}

/* --- Two fields on a row --------------------------------------------------
 *
 * .form-pair is components.css's, written for .form-horizontal, where it also
 * has to undo that layout's label column. Here the labels already sit above
 * their fields, so only the two tracks are wanted. */
.application-form .form-pair {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0 var(--space-4);
}

@media (max-width: 47.9375rem) {
  .application-form__columns { grid-template-columns: minmax(0, 1fr); }

  /* Nothing to stick to in one column, and a sticky card over a scrolling form
     would cover the fields it introduces. */
  .application-form__panel--object { position: static; }

  .application-form__panel { padding: var(--space-4); }

  /* Telefon + E-post side by side is two half-width fields at 375px. */
  .application-form .form-pair { grid-template-columns: minmax(0, 1fr); }
}

.fault-contact { max-width: var(--measure); }
.fault-contact__list { padding-left: var(--space-6); }

.urgent {
  margin-top: var(--space-6);
  padding: var(--space-4);
  border-left: 4px solid var(--color-danger);
  background: var(--color-danger-wash);
  color: var(--color-danger);
}

@media (max-width: 47.9375rem) {
  .site-shell { width: min(100% - 2 * var(--space-3), var(--site-shell)); }

  .site-header__inner {
    display: block;
    min-height: 0;
    padding-top: var(--space-4);
  }

  .site-header__brand {
    display: block;
    padding: 0 6.5rem var(--space-4) 0;
    font-size: clamp(2rem, 11vw, 3rem);
  }

  .site-nav {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
  }

  .site-nav a {
    justify-content: center;
    padding-inline: var(--space-2);
  }

  /* Five items in two columns leaves one alone on the last row. Give it the
     full width so the menu ends on a line rather than on a gap. */
  .site-nav a:last-child:nth-child(odd) { grid-column: 1 / -1; }

  .site-main { padding-block: var(--space-6) var(--space-8); }

  .property-detail { grid-template-columns: 1fr; }

  /* Picture over text over offer: the offer is the end of the card's argument,
     and its rule becomes the line that separates one card from its own footer
     rather than one column from the next. */
  .vacancy-card { grid-template-columns: 1fr; }

  /* Back to the column the rail started as: the footer's one line does not fit
     across a phone, and left-aligned wrapped fragments read worse than a
     centred stack. Every property the wider footer set has to be named again,
     since that rule matches here too and the two are the same weight. */
  .vacancy-card__offer {
    flex-flow: column nowrap;
    align-items: stretch;
    justify-content: center;
    gap: var(--space-4);
    padding-left: 0;
    padding-top: var(--space-5);
    border-left: 0;
    border-top: 1px solid var(--color-border);
    text-align: center;
  }

  .vacancy-card__rent { margin-right: 0; }
  .public-footer__inner { display: block; }

  .public-footer__meta {
    margin-top: var(--space-6);
    white-space: normal;
  }

  .welcome figure {
    float: none;
    width: min(12rem, 100%);
    margin: 0 auto var(--space-5);
  }
}
